static void test_one(const char *input, const char *output) { CalendarSpec *c; _cleanup_free_ char *p = NULL, *q = NULL; usec_t u; char buf[FORMAT_TIMESTAMP_MAX]; int r; assert_se(calendar_spec_from_string(input, &c) >= 0); assert_se(calendar_spec_to_string(c, &p) >= 0); printf("\"%s\" → \"%s\"\n", input, p); assert_se(streq(p, output)); u = now(CLOCK_REALTIME); r = calendar_spec_next_usec(c, u, &u); printf("Next: %s\n", r < 0 ? strerror(-r) : format_timestamp(buf, sizeof(buf), u)); calendar_spec_free(c); assert_se(calendar_spec_from_string(p, &c) >= 0); assert_se(calendar_spec_to_string(c, &q) >= 0); calendar_spec_free(c); assert_se(streq(q, p)); }
static void test_timestamp(void) { char buf[FORMAT_TIMESTAMP_MAX]; _cleanup_free_ char *t = NULL; CalendarSpec *c; usec_t x, y; /* Ensure that a timestamp is also a valid calendar specification. Convert forth and back */ x = now(CLOCK_REALTIME); assert_se(format_timestamp_us(buf, sizeof(buf), x)); printf("%s\n", buf); assert_se(calendar_spec_from_string(buf, &c) >= 0); assert_se(calendar_spec_to_string(c, &t) >= 0); calendar_spec_free(c); printf("%s\n", t); assert_se(parse_timestamp(t, &y) >= 0); assert_se(y == x); }
static void test_hourly_bug_4031(void) { CalendarSpec *c; usec_t n, u, w; char buf[FORMAT_TIMESTAMP_MAX], zaf[FORMAT_TIMESTAMP_MAX]; int r; assert_se(calendar_spec_from_string("hourly", &c) >= 0); n = now(CLOCK_REALTIME); assert_se((r = calendar_spec_next_usec(c, n, &u)) >= 0); printf("Now: %s (%"PRIu64")\n", format_timestamp_us(buf, sizeof buf, n), n); printf("Next hourly: %s (%"PRIu64")\n", r < 0 ? strerror(-r) : format_timestamp_us(buf, sizeof buf, u), u); assert_se((r = calendar_spec_next_usec(c, u, &w)) >= 0); printf("Next hourly: %s (%"PRIu64")\n", r < 0 ? strerror(-r) : format_timestamp_us(zaf, sizeof zaf, w), w); assert_se(n < u); assert_se(u <= n + USEC_PER_HOUR); assert_se(u < w); assert_se(w <= u + USEC_PER_HOUR); calendar_spec_free(c); }
static void test_next(const char *input, const char *new_tz, usec_t after, usec_t expect) { CalendarSpec *c; usec_t u; char *old_tz; char buf[FORMAT_TIMESTAMP_MAX]; int r; old_tz = getenv("TZ"); if (old_tz) old_tz = strdupa(old_tz); if (new_tz) assert_se(setenv("TZ", new_tz, 1) >= 0); else assert_se(unsetenv("TZ") >= 0); tzset(); assert_se(calendar_spec_from_string(input, &c) >= 0); printf("\"%s\"\n", input); u = after; r = calendar_spec_next_usec(c, after, &u); printf("At: %s\n", r < 0 ? strerror(-r) : format_timestamp_us(buf, sizeof buf, u)); if (expect != (usec_t)-1) assert_se(r >= 0 && u == expect); else assert(r == -ENOENT); calendar_spec_free(c); if (old_tz) assert_se(setenv("TZ", old_tz, 1) >= 0); else assert_se(unsetenv("TZ") >= 0); tzset(); }
int calendar_spec_from_string(const char *p, CalendarSpec **spec) { CalendarSpec *c; int r; assert(p); assert(spec); if (isempty(p)) return -EINVAL; c = new0(CalendarSpec, 1); if (!c) return -ENOMEM; if (strcaseeq(p, "hourly")) { r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else if (strcaseeq(p, "daily")) { r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else if (strcaseeq(p, "monthly")) { r = const_chain(1, &c->day); if (r < 0) goto fail; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else if (strcaseeq(p, "weekly")) { c->weekdays_bits = 1; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else { r = parse_weekdays(&p, c); if (r < 0) goto fail; r = parse_date(&p, c); if (r < 0) goto fail; r = parse_time(&p, c); if (r < 0) goto fail; if (*p != 0) { r = -EINVAL; goto fail; } } r = calendar_spec_normalize(c); if (r < 0) goto fail; if (!calendar_spec_valid(c)) { r = -EINVAL; goto fail; } *spec = c; return 0; fail: calendar_spec_free(c); return r; }
int calendar_spec_from_string(const char *p, CalendarSpec **spec) { const char *utc; CalendarSpec *c; int r; assert(p); assert(spec); c = new0(CalendarSpec, 1); if (!c) return -ENOMEM; c->dst = -1; c->timezone = NULL; utc = endswith_no_case(p, " UTC"); if (utc) { c->utc = true; p = strndupa(p, utc - p); } else { const char *e = NULL; int j; tzset(); /* Check if the local timezone was specified? */ for (j = 0; j <= 1; j++) { if (isempty(tzname[j])) continue; e = endswith_no_case(p, tzname[j]); if (!e) continue; if (e == p) continue; if (e[-1] != ' ') continue; break; } /* Found one of the two timezones specified? */ if (IN_SET(j, 0, 1)) { p = strndupa(p, e - p - 1); c->dst = j; } else { const char *last_space; last_space = strrchr(p, ' '); if (last_space != NULL && timezone_is_valid(last_space + 1)) { c->timezone = strdup(last_space + 1); if (!c->timezone) { r = -ENOMEM; goto fail; } p = strndupa(p, last_space - p); } } } if (isempty(p)) { r = -EINVAL; goto fail; } if (strcaseeq(p, "minutely")) { r = const_chain(0, &c->microsecond); if (r < 0) goto fail; } else if (strcaseeq(p, "hourly")) { r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->microsecond); if (r < 0) goto fail; } else if (strcaseeq(p, "daily")) { r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->microsecond); if (r < 0) goto fail; } else if (strcaseeq(p, "monthly")) { r = const_chain(1, &c->day); if (r < 0) goto fail; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->microsecond); if (r < 0) goto fail; } else if (strcaseeq(p, "annually") || strcaseeq(p, "yearly") || strcaseeq(p, "anually") /* backwards compatibility */ ) { r = const_chain(1, &c->month); if (r < 0) goto fail; r = const_chain(1, &c->day); if (r < 0) goto fail; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->microsecond); if (r < 0) goto fail; } else if (strcaseeq(p, "weekly")) { c->weekdays_bits = 1; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->microsecond); if (r < 0) goto fail; } else if (strcaseeq(p, "quarterly")) { r = const_chain(1, &c->month); if (r < 0) goto fail; r = const_chain(4, &c->month); if (r < 0) goto fail; r = const_chain(7, &c->month); if (r < 0) goto fail; r = const_chain(10, &c->month); if (r < 0) goto fail; r = const_chain(1, &c->day); if (r < 0) goto fail; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->microsecond); if (r < 0) goto fail; } else if (strcaseeq(p, "biannually") || strcaseeq(p, "bi-annually") || strcaseeq(p, "semiannually") || strcaseeq(p, "semi-annually")) { r = const_chain(1, &c->month); if (r < 0) goto fail; r = const_chain(7, &c->month); if (r < 0) goto fail; r = const_chain(1, &c->day); if (r < 0) goto fail; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->microsecond); if (r < 0) goto fail; } else { r = parse_weekdays(&p, c); if (r < 0) goto fail; r = parse_date(&p, c); if (r < 0) goto fail; if (r == 0) { r = parse_calendar_time(&p, c); if (r < 0) goto fail; } if (*p != 0) { r = -EINVAL; goto fail; } } r = calendar_spec_normalize(c); if (r < 0) goto fail; if (!calendar_spec_valid(c)) { r = -EINVAL; goto fail; } *spec = c; return 0; fail: calendar_spec_free(c); return r; }
static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_NO_ASK_PASSWORD, ARG_USER, ARG_SYSTEM, ARG_SCOPE, ARG_UNIT, ARG_DESCRIPTION, ARG_SLICE, ARG_SEND_SIGHUP, ARG_EXEC_USER, ARG_EXEC_GROUP, ARG_SERVICE_TYPE, ARG_NICE, ARG_SETENV, ARG_TTY, ARG_ON_ACTIVE, ARG_ON_BOOT, ARG_ON_STARTUP, ARG_ON_UNIT_ACTIVE, ARG_ON_UNIT_INACTIVE, ARG_ON_CALENDAR, ARG_TIMER_PROPERTY, ARG_NO_BLOCK, }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "user", no_argument, NULL, ARG_USER }, { "system", no_argument, NULL, ARG_SYSTEM }, { "scope", no_argument, NULL, ARG_SCOPE }, { "unit", required_argument, NULL, ARG_UNIT }, { "description", required_argument, NULL, ARG_DESCRIPTION }, { "slice", required_argument, NULL, ARG_SLICE }, { "remain-after-exit", no_argument, NULL, 'r' }, { "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP }, { "host", required_argument, NULL, 'H' }, { "machine", required_argument, NULL, 'M' }, { "service-type", required_argument, NULL, ARG_SERVICE_TYPE }, { "uid", required_argument, NULL, ARG_EXEC_USER }, { "gid", required_argument, NULL, ARG_EXEC_GROUP }, { "nice", required_argument, NULL, ARG_NICE }, { "setenv", required_argument, NULL, ARG_SETENV }, { "property", required_argument, NULL, 'p' }, { "tty", no_argument, NULL, 't' }, { "quiet", no_argument, NULL, 'q' }, { "on-active", required_argument, NULL, ARG_ON_ACTIVE }, { "on-boot", required_argument, NULL, ARG_ON_BOOT }, { "on-startup", required_argument, NULL, ARG_ON_STARTUP }, { "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE }, { "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE }, { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR }, { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY }, { "no-block", no_argument, NULL, ARG_NO_BLOCK }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, {}, }; int r, c; assert(argc >= 0); assert(argv); while ((c = getopt_long(argc, argv, "+hrH:M:p:tq", options, NULL)) >= 0) switch (c) { case 'h': help(); return 0; case ARG_NO_ASK_PASSWORD: arg_ask_password = false; break; case ARG_VERSION: return version(); case ARG_USER: arg_user = true; break; case ARG_SYSTEM: arg_user = false; break; case ARG_SCOPE: arg_scope = true; break; case ARG_UNIT: arg_unit = optarg; break; case ARG_DESCRIPTION: arg_description = optarg; break; case ARG_SLICE: arg_slice = optarg; break; case ARG_SEND_SIGHUP: arg_send_sighup = true; break; case 'r': arg_remain_after_exit = true; break; case 'H': arg_transport = BUS_TRANSPORT_REMOTE; arg_host = optarg; break; case 'M': arg_transport = BUS_TRANSPORT_MACHINE; arg_host = optarg; break; case ARG_SERVICE_TYPE: arg_service_type = optarg; break; case ARG_EXEC_USER: arg_exec_user = optarg; break; case ARG_EXEC_GROUP: arg_exec_group = optarg; break; case ARG_NICE: r = safe_atoi(optarg, &arg_nice); if (r < 0 || arg_nice < PRIO_MIN || arg_nice >= PRIO_MAX) { log_error("Failed to parse nice value"); return -EINVAL; } arg_nice_set = true; break; case ARG_SETENV: if (strv_extend(&arg_environment, optarg) < 0) return log_oom(); break; case 'p': if (strv_extend(&arg_property, optarg) < 0) return log_oom(); break; case 't': arg_pty = true; break; case 'q': arg_quiet = true; break; case ARG_ON_ACTIVE: r = parse_sec(optarg, &arg_on_active); if (r < 0) { log_error("Failed to parse timer value: %s", optarg); return r; } break; case ARG_ON_BOOT: r = parse_sec(optarg, &arg_on_boot); if (r < 0) { log_error("Failed to parse timer value: %s", optarg); return r; } break; case ARG_ON_STARTUP: r = parse_sec(optarg, &arg_on_startup); if (r < 0) { log_error("Failed to parse timer value: %s", optarg); return r; } break; case ARG_ON_UNIT_ACTIVE: r = parse_sec(optarg, &arg_on_unit_active); if (r < 0) { log_error("Failed to parse timer value: %s", optarg); return r; } break; case ARG_ON_UNIT_INACTIVE: r = parse_sec(optarg, &arg_on_unit_inactive); if (r < 0) { log_error("Failed to parse timer value: %s", optarg); return r; } break; case ARG_ON_CALENDAR: { CalendarSpec *spec = NULL; r = calendar_spec_from_string(optarg, &spec); if (r < 0) { log_error("Invalid calendar spec: %s", optarg); return r; } calendar_spec_free(spec); arg_on_calendar = optarg; break; } case ARG_TIMER_PROPERTY: if (strv_extend(&arg_timer_property, optarg) < 0) return log_oom(); break; case ARG_NO_BLOCK: arg_no_block = true; break; case '?': return -EINVAL; default: assert_not_reached("Unhandled option"); } if ((optind >= argc) && (!arg_unit || !with_timer())) { log_error("Command line to execute required."); return -EINVAL; } if (arg_user && arg_transport != BUS_TRANSPORT_LOCAL) { log_error("Execution in user context is not supported on non-local systems."); return -EINVAL; } if (arg_scope && arg_transport != BUS_TRANSPORT_LOCAL) { log_error("Scope execution is not supported on non-local systems."); return -EINVAL; } if (arg_scope && (arg_remain_after_exit || arg_service_type)) { log_error("--remain-after-exit and --service-type= are not supported in --scope mode."); return -EINVAL; } if (arg_pty && (with_timer() || arg_scope)) { log_error("--pty is not compatible in timer or --scope mode."); return -EINVAL; } if (arg_pty && arg_transport == BUS_TRANSPORT_REMOTE) { log_error("--pty is only supported when connecting to the local system or containers."); return -EINVAL; } if (arg_scope && with_timer()) { log_error("Timer options are not supported in --scope mode."); return -EINVAL; } if (arg_timer_property && !with_timer()) { log_error("--timer-property= has no effect without any other timer options."); return -EINVAL; } return 1; }
int calendar_spec_from_string(const char *p, CalendarSpec **spec) { CalendarSpec *c; int r; const char *utc; assert(p); assert(spec); if (isempty(p)) return -EINVAL; c = new0(CalendarSpec, 1); if (!c) return -ENOMEM; utc = endswith_no_case(p, " UTC"); if (utc) { c->utc = true; p = strndupa(p, utc - p); } if (strcaseeq(p, "minutely")) { r = const_chain(0, &c->second); if (r < 0) goto fail; } else if (strcaseeq(p, "hourly")) { r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else if (strcaseeq(p, "daily")) { r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else if (strcaseeq(p, "monthly")) { r = const_chain(1, &c->day); if (r < 0) goto fail; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else if (strcaseeq(p, "annually") || strcaseeq(p, "yearly") || strcaseeq(p, "anually") /* backwards compatibility */ ) { r = const_chain(1, &c->month); if (r < 0) goto fail; r = const_chain(1, &c->day); if (r < 0) goto fail; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else if (strcaseeq(p, "weekly")) { c->weekdays_bits = 1; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else if (strcaseeq(p, "quarterly")) { r = const_chain(1, &c->month); if (r < 0) goto fail; r = const_chain(4, &c->month); if (r < 0) goto fail; r = const_chain(7, &c->month); if (r < 0) goto fail; r = const_chain(10, &c->month); if (r < 0) goto fail; r = const_chain(1, &c->day); if (r < 0) goto fail; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else if (strcaseeq(p, "biannually") || strcaseeq(p, "bi-annually") || strcaseeq(p, "semiannually") || strcaseeq(p, "semi-annually")) { r = const_chain(1, &c->month); if (r < 0) goto fail; r = const_chain(7, &c->month); if (r < 0) goto fail; r = const_chain(1, &c->day); if (r < 0) goto fail; r = const_chain(0, &c->hour); if (r < 0) goto fail; r = const_chain(0, &c->minute); if (r < 0) goto fail; r = const_chain(0, &c->second); if (r < 0) goto fail; } else { r = parse_weekdays(&p, c); if (r < 0) goto fail; r = parse_date(&p, c); if (r < 0) goto fail; r = parse_time(&p, c); if (r < 0) goto fail; if (*p != 0) { r = -EINVAL; goto fail; } } r = calendar_spec_normalize(c); if (r < 0) goto fail; if (!calendar_spec_valid(c)) { r = -EINVAL; goto fail; } *spec = c; return 0; fail: calendar_spec_free(c); return r; }