summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/basic/calendarspec.c98
-rw-r--r--src/basic/calendarspec.h1
-rw-r--r--src/core/macros.systemd.in2
-rw-r--r--src/core/service.c31
-rw-r--r--src/core/service.h1
-rw-r--r--src/libsystemd/sd-hwdb/sd-hwdb.c2
-rw-r--r--src/libsystemd/sd-netlink/netlink-types.c27
-rw-r--r--src/network/networkd-ndisc.c8
-rw-r--r--src/network/networkd-network.h2
-rw-r--r--src/network/networkd-route.c14
-rw-r--r--src/network/networkd-route.h1
-rw-r--r--src/test/test-calendarspec.c9
12 files changed, 154 insertions, 42 deletions
diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c
index fda293fcb9..359bb16cae 100644
--- a/src/basic/calendarspec.c
+++ b/src/basic/calendarspec.c
@@ -34,6 +34,8 @@
/* Longest valid date/time range is 1970..2199 */
#define MAX_RANGE_LEN 230
+#define MIN_YEAR 1970
+#define MAX_YEAR 2199
#define BITS_WEEKDAYS 127
static void free_chain(CalendarComponent *c) {
@@ -135,6 +137,9 @@ int calendar_spec_normalize(CalendarSpec *c) {
if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS)
c->weekdays_bits = -1;
+ if (c->end_of_month && !c->day)
+ c->end_of_month = false;
+
fix_year(c->year);
sort_chain(&c->year);
@@ -147,18 +152,27 @@ int calendar_spec_normalize(CalendarSpec *c) {
return 0;
}
-_pure_ static bool chain_valid(CalendarComponent *c, int from, int to) {
+_pure_ static bool chain_valid(CalendarComponent *c, int from, int to, bool eom) {
if (!c)
return true;
if (c->value < from || c->value > to)
return false;
- if (c->value + c->repeat > to)
+ /*
+ * c->repeat must be short enough so at least one repetition may
+ * occur before the end of the interval. For dates scheduled
+ * relative to the end of the month (eom), c->value corresponds
+ * to the Nth last day of the month.
+ */
+ if (eom && c->value - c->repeat < from)
+ return false;
+
+ if (!eom && c->value + c->repeat > to)
return false;
if (c->next)
- return chain_valid(c->next, from, to);
+ return chain_valid(c->next, from, to, eom);
return true;
}
@@ -169,22 +183,22 @@ _pure_ bool calendar_spec_valid(CalendarSpec *c) {
if (c->weekdays_bits > BITS_WEEKDAYS)
return false;
- if (!chain_valid(c->year, 1970, 2199))
+ if (!chain_valid(c->year, MIN_YEAR, MAX_YEAR, false))
return false;
- if (!chain_valid(c->month, 1, 12))
+ if (!chain_valid(c->month, 1, 12, false))
return false;
- if (!chain_valid(c->day, 1, 31))
+ if (!chain_valid(c->day, 1, 31, c->end_of_month))
return false;
- if (!chain_valid(c->hour, 0, 23))
+ if (!chain_valid(c->hour, 0, 23, false))
return false;
- if (!chain_valid(c->minute, 0, 59))
+ if (!chain_valid(c->minute, 0, 59, false))
return false;
- if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1))
+ if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1, false))
return false;
return true;
@@ -291,7 +305,7 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) {
format_chain(f, 4, c->year, false);
fputc('-', f);
format_chain(f, 2, c->month, false);
- fputc('-', f);
+ fputc(c->end_of_month ? '~' : '-', f);
format_chain(f, 2, c->day, false);
fputc(' ', f);
format_chain(f, 2, c->hour, false);
@@ -540,7 +554,7 @@ static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
}
}
- if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
+ if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != '~' && *e != ':')
return -EINVAL;
cc = new0(CalendarComponent, 1);
@@ -620,7 +634,9 @@ static int parse_date(const char **p, CalendarSpec *c) {
return 0;
}
- if (*t != '-') {
+ if (*t == '~')
+ c->end_of_month = true;
+ else if (*t != '-') {
free_chain(first);
return -EINVAL;
}
@@ -638,9 +654,12 @@ static int parse_date(const char **p, CalendarSpec *c) {
c->month = first;
c->day = second;
return 0;
- }
+ } else if (c->end_of_month)
+ return -EINVAL;
- if (*t != '-') {
+ if (*t == '~')
+ c->end_of_month = true;
+ else if (*t != '-') {
free_chain(first);
free_chain(second);
return -EINVAL;
@@ -654,7 +673,7 @@ static int parse_date(const char **p, CalendarSpec *c) {
return r;
}
- /* Got tree parts, hence it is year, month and day */
+ /* Got three parts, hence it is year, month and day */
if (*t == ' ' || *t == 0) {
*p = t + strspn(t, " ");
c->year = first;
@@ -965,9 +984,11 @@ fail:
return r;
}
-static int find_matching_component(const CalendarComponent *c, int *val) {
- const CalendarComponent *n;
- int d = -1;
+static int find_matching_component(const CalendarSpec *spec, const CalendarComponent *c,
+ struct tm *tm, int *val) {
+ const CalendarComponent *n, *p = c;
+ struct tm t;
+ int v, d = -1;
bool d_set = false;
int r;
@@ -979,17 +1000,30 @@ static int find_matching_component(const CalendarComponent *c, int *val) {
while (c) {
n = c->next;
- if (c->value >= *val) {
+ if (spec->end_of_month && p == spec->day) {
+ t = *tm;
+ t.tm_mon++;
+ t.tm_mday = 1 - c->value;
+
+ if (mktime_or_timegm(&t, spec->utc) == (time_t) -1 ||
+ t.tm_mon != tm->tm_mon)
+ v = -1;
+ else
+ v = t.tm_mday;
+ } else
+ v = c->value;
+
+ if (v >= *val) {
- if (!d_set || c->value < d) {
- d = c->value;
+ if (!d_set || v < d) {
+ d = v;
d_set = true;
}
} else if (c->repeat > 0) {
int k;
- k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat);
+ k = v + c->repeat * ((*val - v + c->repeat -1) / c->repeat);
if (!d_set || k < d) {
d = k;
@@ -1017,6 +1051,14 @@ static bool tm_out_of_bounds(const struct tm *tm, bool utc) {
if (mktime_or_timegm(&t, utc) == (time_t) -1)
return true;
+ /*
+ * Set an upper bound on the year so impossible dates like "*-02-31"
+ * don't cause find_next() to loop forever. tm_year contains years
+ * since 1900, so adjust it accordingly.
+ */
+ if (tm->tm_year + 1900 > MAX_YEAR)
+ return true;
+
/* Did any normalization take place? If so, it was out of bounds before */
return
t.tm_year != tm->tm_year ||
@@ -1059,7 +1101,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
c.tm_isdst = spec->dst;
c.tm_year += 1900;
- r = find_matching_component(spec->year, &c.tm_year);
+ r = find_matching_component(spec, spec->year, &c, &c.tm_year);
c.tm_year -= 1900;
if (r > 0) {
@@ -1073,7 +1115,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
return -ENOENT;
c.tm_mon += 1;
- r = find_matching_component(spec->month, &c.tm_mon);
+ r = find_matching_component(spec, spec->month, &c, &c.tm_mon);
c.tm_mon -= 1;
if (r > 0) {
@@ -1088,7 +1130,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
continue;
}
- r = find_matching_component(spec->day, &c.tm_mday);
+ r = find_matching_component(spec, spec->day, &c, &c.tm_mday);
if (r > 0)
c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
@@ -1104,7 +1146,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
continue;
}
- r = find_matching_component(spec->hour, &c.tm_hour);
+ r = find_matching_component(spec, spec->hour, &c, &c.tm_hour);
if (r > 0)
c.tm_min = c.tm_sec = tm_usec = 0;
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
@@ -1113,7 +1155,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
continue;
}
- r = find_matching_component(spec->minute, &c.tm_min);
+ r = find_matching_component(spec, spec->minute, &c, &c.tm_min);
if (r > 0)
c.tm_sec = tm_usec = 0;
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
@@ -1123,7 +1165,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
}
c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec;
- r = find_matching_component(spec->microsecond, &c.tm_sec);
+ r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec);
tm_usec = c.tm_sec % USEC_PER_SEC;
c.tm_sec /= USEC_PER_SEC;
diff --git a/src/basic/calendarspec.h b/src/basic/calendarspec.h
index c6087228fd..78af27403c 100644
--- a/src/basic/calendarspec.h
+++ b/src/basic/calendarspec.h
@@ -36,6 +36,7 @@ typedef struct CalendarComponent {
typedef struct CalendarSpec {
int weekdays_bits;
+ bool end_of_month;
bool utc;
int dst;
diff --git a/src/core/macros.systemd.in b/src/core/macros.systemd.in
index 6e8a3b3e3d..8d7ce1c238 100644
--- a/src/core/macros.systemd.in
+++ b/src/core/macros.systemd.in
@@ -81,7 +81,7 @@ fi \
%systemd_user_postun_with_restart() %{nil}
%udev_hwdb_update() \
-udevadm hwdb --update >/dev/null 2>&1 || : \
+systemd-hwdb update >/dev/null 2>&1 || : \
%{nil}
%udev_rules_update() \
diff --git a/src/core/service.c b/src/core/service.c
index 9ad4cf5070..180854b57c 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1694,7 +1694,9 @@ static void service_enter_running(Service *s, ServiceResult f) {
service_arm_timer(s, usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec));
}
- } else if (s->remain_after_exit)
+ } else if (f != SERVICE_SUCCESS)
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, f);
+ else if (s->remain_after_exit)
service_set_state(s, SERVICE_EXITED);
else
service_enter_stop(s, SERVICE_SUCCESS);
@@ -2578,16 +2580,18 @@ static void service_notify_cgroup_empty_event(Unit *u) {
case SERVICE_START:
case SERVICE_START_POST:
- /* If we were hoping for the daemon to write its PID file,
- * we can give up now. */
- if (s->pid_file_pathspec) {
+ if (s->type == SERVICE_NOTIFY)
+ /* No chance of getting a ready notification anymore */
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_PROTOCOL);
+ else if (s->pid_file_pathspec) {
+ /* Give up hoping for the daemon to write its PID file */
log_unit_warning(u, "Daemon never wrote its PID file. Failing.");
service_unwatch_pid_file(s);
if (s->state == SERVICE_START)
- service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_PROTOCOL);
else
- service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
+ service_enter_stop(s, SERVICE_FAILURE_PROTOCOL);
}
break;
@@ -2721,6 +2725,16 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
else
service_enter_signal(s, SERVICE_FINAL_SIGTERM, f);
break;
+ } else if (s->type == SERVICE_NOTIFY) {
+ /* Only enter running through a notification, so that the
+ * SERVICE_START state signifies that no ready notification
+ * has been received */
+ if (f != SERVICE_SUCCESS)
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, f);
+ else if (!s->remain_after_exit)
+ /* The service has never been active */
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_PROTOCOL);
+ break;
}
/* Fall through */
@@ -2825,7 +2839,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
if (!has_start_post && r < 0) {
r = service_demand_pid_file(s);
if (r < 0 || !cgroup_good(s))
- service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES);
+ service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_PROTOCOL);
break;
}
} else
@@ -2847,7 +2861,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
if (r < 0) {
r = service_demand_pid_file(s);
if (r < 0 || !cgroup_good(s))
- service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
+ service_enter_stop(s, SERVICE_FAILURE_PROTOCOL);
break;
}
} else
@@ -3384,6 +3398,7 @@ DEFINE_STRING_TABLE_LOOKUP(notify_state, NotifyState);
static const char* const service_result_table[_SERVICE_RESULT_MAX] = {
[SERVICE_SUCCESS] = "success",
[SERVICE_FAILURE_RESOURCES] = "resources",
+ [SERVICE_FAILURE_PROTOCOL] = "protocol",
[SERVICE_FAILURE_TIMEOUT] = "timeout",
[SERVICE_FAILURE_EXIT_CODE] = "exit-code",
[SERVICE_FAILURE_SIGNAL] = "signal",
diff --git a/src/core/service.h b/src/core/service.h
index 2869144fcb..278cc1ceb8 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -81,6 +81,7 @@ typedef enum NotifyState {
typedef enum ServiceResult {
SERVICE_SUCCESS,
SERVICE_FAILURE_RESOURCES, /* a bit of a misnomer, just our catch-all error for errnos we didn't expect */
+ SERVICE_FAILURE_PROTOCOL,
SERVICE_FAILURE_TIMEOUT,
SERVICE_FAILURE_EXIT_CODE,
SERVICE_FAILURE_SIGNAL,
diff --git a/src/libsystemd/sd-hwdb/sd-hwdb.c b/src/libsystemd/sd-hwdb/sd-hwdb.c
index 488e101ea8..719e3505c1 100644
--- a/src/libsystemd/sd-hwdb/sd-hwdb.c
+++ b/src/libsystemd/sd-hwdb/sd-hwdb.c
@@ -321,7 +321,7 @@ _public_ int sd_hwdb_new(sd_hwdb **ret) {
}
if (!hwdb->f) {
- log_debug("hwdb.bin does not exist, please run udevadm hwdb --update");
+ log_debug("hwdb.bin does not exist, please run systemd-hwdb update");
return -ENOENT;
}
diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c
index 1c10dd55a7..0f8b0cc70b 100644
--- a/src/libsystemd/sd-netlink/netlink-types.c
+++ b/src/libsystemd/sd-netlink/netlink-types.c
@@ -500,6 +500,28 @@ static const NLTypeSystem rtnl_address_type_system = {
.types = rtnl_address_types,
};
+/* RTM_METRICS --- array of struct rtattr with types of RTAX_* */
+
+static const NLType rtnl_route_metrics_types[] = {
+ [RTAX_MTU] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_WINDOW] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_RTT] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_RTTVAR] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_SSTHRESH] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_CWND] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_ADVMSS] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_REORDERING] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_HOPLIMIT] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_INITCWND] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_FEATURES] = { .type = NETLINK_TYPE_U32 },
+ [RTAX_RTO_MIN] = { .type = NETLINK_TYPE_U32 },
+};
+
+static const NLTypeSystem rtnl_route_metrics_type_system = {
+ .count = ELEMENTSOF(rtnl_route_metrics_types),
+ .types = rtnl_route_metrics_types,
+};
+
static const NLType rtnl_route_types[] = {
[RTA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */
[RTA_SRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */
@@ -508,9 +530,8 @@ static const NLType rtnl_route_types[] = {
[RTA_GATEWAY] = { .type = NETLINK_TYPE_IN_ADDR },
[RTA_PRIORITY] = { .type = NETLINK_TYPE_U32 },
[RTA_PREFSRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */
-/*
- [RTA_METRICS] = { .type = NETLINK_TYPE_NESTED },
- [RTA_MULTIPATH] = { .len = sizeof(struct rtnexthop) },
+ [RTA_METRICS] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_metrics_type_system},
+/* [RTA_MULTIPATH] = { .len = sizeof(struct rtnexthop) },
*/
[RTA_FLOW] = { .type = NETLINK_TYPE_U32 }, /* 6? */
/*
diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c
index 70283e5347..5320592f7a 100644
--- a/src/network/networkd-ndisc.c
+++ b/src/network/networkd-ndisc.c
@@ -56,6 +56,7 @@ static void ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
struct in6_addr gateway;
uint16_t lifetime;
unsigned preference;
+ uint32_t mtu;
usec_t time_now;
int r;
Address *address;
@@ -116,6 +117,12 @@ static void ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
return;
}
+ r = sd_ndisc_router_get_mtu(rt, &mtu);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get default router MTU from RA: %m");
+ return;
+ }
+
r = route_new(&route);
if (r < 0) {
log_link_error_errno(link, r, "Could not allocate route: %m");
@@ -128,6 +135,7 @@ static void ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
route->pref = preference;
route->gw.in6 = gateway;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
+ route->mtu = mtu;
r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 17cff956da..4dbc19fc3b 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -174,7 +174,7 @@ struct Network {
IPv6PrivacyExtensions ipv6_privacy_extensions;
struct ether_addr *mac;
- unsigned mtu;
+ size_t mtu;
int arp;
uint32_t iaid;
DUID duid;
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index f78e106991..bde26a42d4 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -605,6 +605,20 @@ int route_configure(
if (r < 0)
return log_error_errno(r, "Could not append RTA_OIF attribute: %m");
+ r = sd_netlink_message_open_container(req, RTA_METRICS);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_METRICS attribute: %m");
+
+ if (route->mtu > 0) {
+ r = sd_netlink_message_append_u32(req, RTAX_MTU, route->mtu);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTAX_MTU attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_METRICS attribute: %m");
+
r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
if (r < 0)
return log_error_errno(r, "Could not send rtnetlink message: %m");
diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h
index d4e4dbac0b..02f0b27675 100644
--- a/src/network/networkd-route.h
+++ b/src/network/networkd-route.h
@@ -37,6 +37,7 @@ struct Route {
unsigned char tos;
uint32_t priority; /* note that ip(8) calls this 'metric' */
uint32_t table;
+ uint32_t mtu;
unsigned char pref;
unsigned flags;
diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c
index 59217b131c..76f5819bb9 100644
--- a/src/test/test-calendarspec.c
+++ b/src/test/test-calendarspec.c
@@ -176,6 +176,9 @@ int main(int argc, char* argv[]) {
test_one("1..3-1..3 1..3:1..3", "*-01,02,03-01,02,03 01,02,03:01,02,03:00");
test_one("00:00:1.125..2.125", "*-*-* 00:00:01.125000,02.125000");
test_one("00:00:1.0..3.8", "*-*-* 00:00:01,02,03");
+ test_one("*-*~1 Utc", "*-*~01 00:00:00 UTC");
+ test_one("*-*~05,3 ", "*-*~03,05 00:00:00");
+ test_one("*-*~* 00:00:00", "*-*-* 00:00:00");
test_next("2016-03-27 03:17:00", "", 12345, 1459048620000000);
test_next("2016-03-27 03:17:00", "CET", 12345, 1459041420000000);
@@ -190,6 +193,10 @@ int main(int argc, char* argv[]) {
test_next("2015-11-13 09:11:23.42/1.77", "EET", 1447398683420000, 1447398685190000);
test_next("2015-11-13 09:11:23.42/1.77", "EET", 1447398683419999, 1447398683420000);
test_next("Sun 16:00:00", "CET", 1456041600123456, 1456066800000000);
+ test_next("*-04-31", "", 12345, -1);
+ test_next("2016-02~01 UTC", "", 12345, 1456704000000000);
+ test_next("Mon 2017-05~01..07 UTC", "", 12345, 1496016000000000);
+ test_next("Mon 2017-05~07/1 UTC", "", 12345, 1496016000000000);
assert_se(calendar_spec_from_string("test", &c) < 0);
assert_se(calendar_spec_from_string("", &c) < 0);
@@ -199,6 +206,8 @@ int main(int argc, char* argv[]) {
assert_se(calendar_spec_from_string("2000-03-05 00:00.1:00", &c) < 0);
assert_se(calendar_spec_from_string("00:00:00/0.00000001", &c) < 0);
assert_se(calendar_spec_from_string("00:00:00.0..00.9", &c) < 0);
+ assert_se(calendar_spec_from_string("2016~11-22", &c) < 0);
+ assert_se(calendar_spec_from_string("*-*~5/5", &c) < 0);
test_timestamp();
test_hourly_bug_4031();