From 0a1a9b8356357e5d744f8a94061fc1d42ae0c009 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Wed, 23 Dec 2015 21:02:47 +0100 Subject: parse arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- src/parse_time.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/parse_time.h | 50 ++++++++++ src/sat.c | 103 ++++++++++++++++++++ 3 files changed, 433 insertions(+) create mode 100644 src/parse_time.c create mode 100644 src/parse_time.h create mode 100644 src/sat.c (limited to 'src') diff --git a/src/parse_time.c b/src/parse_time.c new file mode 100644 index 0000000..9ce97dc --- /dev/null +++ b/src/parse_time.c @@ -0,0 +1,280 @@ +/** + * Copyright © 2015 Mattias Andrée + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "parse_time.h" +#include +#include +#include +#include +#include +#include + + + +/** + * The number of seconds in a day. + */ +#define ONE_DAY (time_t)(24 * 60 * 60) + +/** + * Set errno and return. + * + * @return e:int The error number. + */ +#define FAIL(e) return errno = (e), -1 + + + +/** + * The name of the process. + */ +extern char *argv0; + + + +/** + * Wrapper for `strtoll` and `strtol` that selects + * the appropriate function for `time_t`, does not + * allow whitespace or signs. It also forces the + * base to be 10, and will only return 0 on error, + * and will set `errno` on success. + * + * @param str Please see strtol(3), first parameter. + * @param end Please see strtol(3), second parameter. + * @return Please see strtol(3), only 0 may be + * returned at error. + * + * @throws 0 On success. + * @throws Please see strtol(3). + */ +static time_t +strtotime(const char *str, const char **end) +{ + time_t rc; + long long int rcll; + long int rcl; + + if (!isdigit(*str)) + FAIL(EINVAL); + + /* The outer if-statement is for when `time_t` is update to `long long int`. + * which is need to avoid the year 2038 problem. Be we have to use `strtol` + * if `time_t` is `long int`, otherwise we will not detect overflow. */ + + errno = 0; + if (sizeof(time_t) == sizeof(long long int)) { + rcll = strtoll(str, end, 10); + rc = (time_t)rcll; + } else { + rcl = strtol(str, end, 10); + rc = (time_t)rcl; + } + + return errno ? 0 : rc; +} + + +/** + * Parse a time on the format HH:MM[:SS]. + * + * @param str The time string. + * @param ts Output parameter for the POSIX time the string + * represents. + * @parma end Output parameter for the end of the parsing of `str`. + * @return 0 on success, -1 on error. + * + * @throws EINVAL `str` could not be parsed. + * @throws ERANGE `str` specifies a time beyond what can be stored. + */ +static int +parse_time_time(const char *str, struct timespec *ts, const char **end) +{ +#define MUL(a, b) if (a > timemax / (b)) FAIL(ERANGE); else a *= (b) +#define ADD(a, b) if (a > timemax - (b)) FAIL(ERANGE); else a += (b) + + char *end; + time_t t; + const time_t timemax = (sizeof(time_t) == sizeof(long long int)) ? LLONG_MAX : LONG_MAX; + + memset(ts, 0, sizeof(*ts)); + + ts->tv_sec = strtotime(str, end), str = *end; + if (errno) + return -1; + /* Must not be restricted to 23, beyond 24 is legal. */ + MUL(ts->tv_sec, (time_t)(60 * 60)); + + if (*str++ != ':') + FAIL(EINVAL); + t = strtotime(str, end), str = *end; + if (errno) + return -1; + if (t >= 60) + FAIL(EINVAL); + MUL(t, (time_t)60); + ADD(ts->tv_sec, t); + + switch (*str++) { + case 0: return 0; + case ':': break; + default: FAIL(EINVAL); + } + + t = strtotime(str, end), str = *end; + if (errno) + return -1; + /* Do not restrict to 59, or 60, there can be more than +1 leap second. */ + ADD(ts->tv_sec, t); + + return 0; +} + + +/** + * Parse a time with only a second-count. + * + * @param str The time string. + * @param ts Output parameter for the POSIX time the string + * represents. + * @parma end Output parameter for the end of the parsing of `str`. + * @return 0 on success, -1 on error. + * + * @throws EINVAL `str` could not be parsed. + * @throws ERANGE `str` specifies a time beyond what can be stored. + */ +static int +parse_time_seconds(const char *str, struct timespec *ts, const char **end) +{ + size_t points = 0; + + memset(ts, 0, sizeof(*ts)); + + ts->tv_sec = strtotime(str, end); + if (errno) + return -1; + + return 0; +} + + +/** + * Trivial time-parsing. + * + * @param str The time string. + * @param ts Output parameter for the POSIX time the string + * represents. If the time is in the pasted, but + * within a day, one day is added. This is in case + * you are using a external parser that did not + * release they you wanted a future time but + * thought you wanted the time to be the closed or + * in the same day. + * @param clk Output parameter for the clock in which the + * returned time is specified. + * @return 0 on success, -1 on error. + * + * @throws EINVAL `str` could not be parsed. + * @throws ERANGE `str` specifies a time beyond what can be stored. + * @throws EDOM The specified the was over a day ago. + * + * @futuredirection Add environment variable that allows + * parsing through another program. + */ +int +parse_time(const char *str, struct timespec *ts, clockid_t *clk) +{ + struct timespec now; + int plus = *str == '+'; + char *start = str; + char *end; + + /* Get current time and clock. */ + clock_gettime(CLOCK_REALTIME, &now); + *clk = plus ? CLOCK_MONOTONIC : CLOCK_REALTIME; + + /* Mañana? */ + if (!strcmp("mañana")) { /* Do not documented. */ + ts->tv_sec = now.tv_sec + ONE_DAY; + ts->tv_nsec = now.tv_nsec; + return 0; + } + + /* HH:MM[:SS[.NNNNNNNNN]] or seconds? */ + if (strchr(str, ':')) { + if (parse_time_time(str, ts, &end)) + return -1; + ts->tv_sec += now.sec - (now.sec % ONE_DAY); + } else { + if (parse_time_seconds(str + plus, ts, &end)) + return -1; + } + str = end; + + /* Any fractions of a second? */ + switch (*str) { + case 0: return 0; + case '.': break; + default: FAIL(EINVAL); + } + + /* Parse up to nanosecond resolution. */ + for (; isdigit(*str); points++) { + if (points < 9) { + ts->tv_nsec *= 10; + ts->tv_nsec += *str++ & 15; + } else if ((points == 10) && (*str >= '5')) { + ts->tv_nsec += 1; + } + } + if (ts->tv_nsec > 999999999L) { + ts->tv_sec += 1; + ts->tv_nsec = 0; + } + + /* Check for error at end, and missing explicit UTC. */ + if (*str) { + if (*clk == CLOCK_MONOTONIC) + FAIL(einval); + while (*str == ' ') str++; + if (!strcasecmp(str, "Z") && !strcasecmp(str, "UTC")) + FAIL(einval); + } else if (*clk == CLOCK_REALTIME) { + fprintf(stderr, + "%s: warning: parsing as UTC, you can avoid " + "this warning by adding a 'Z' at the end of " + "the time argument.\n", argv0); + } + + /* Adjust the day? */ + if (*clk == CLOCK_MONOTONIC) + return 0; + if (ts->tv_sec < now.tv_sec) { /* Ignore partial second. */ + ts->tv_sec += ONE_DAY; + if (ts->tv_sec < now.tv_sec) + FAIL(EDOM); + if (!strchr(start, ':')) + fprintf(stderr, + "%s: warning: the specified time is in the past, " + "it is being adjust to be tomorrow instead.\n", argv0); + } + + return 0; +} + diff --git a/src/parse_time.h b/src/parse_time.h new file mode 100644 index 0000000..8e74607 --- /dev/null +++ b/src/parse_time.h @@ -0,0 +1,50 @@ +/** + * Copyright © 2015 Mattias Andrée + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include + + + +/** + * Trivial time-parsing. + * + * @param str The time string. + * @param ts Output parameter for the POSIX time the string + * represents. If the time is in the pasted, but + * within a day, one day is added. This is in case + * you are using a external parser that did not + * release they you wanted a future time but + * thought you wanted the time to be the closed or + * in the same day. + * @param clk Output parameter for the clock in which the + * returned time is specified. + * @return 0 on success, -1 on error. + * + * @throws EINVAL `str` could not be parsed. + * @throws ERANGE `str` specifies a time beyond what can be stored. + * @throws EDOM The specified the was over a day ago. + * + * @futuredirection Add environment variable that allows + * parsing through another program. + */ +int +parse_time(const char *str, struct timespec *ts, clockid_t *clk); + diff --git a/src/sat.c b/src/sat.c new file mode 100644 index 0000000..4860a4d --- /dev/null +++ b/src/sat.c @@ -0,0 +1,103 @@ +/** + * Copyright © 2015 Mattias Andrée + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include +#include + +#include "parse_time.h" + + + +/** + * The name of the process. + */ +char *argv0 = "sat"; + + + +/** + * Print usage information. + */ +static void +usage(void) +{ + fprintf(stderr, "usage: %s TIME COMMAND...\n", + strrchr(argv0) ? (strrchr(argv0) + 1) : argv0); + exit(2); +} + + +/** + * Queue a job for later execution. + * + * @param argc You guess! + * @param argv The first element should be the name of the process, + * the second argument should be the POSIX time (seconds + * since Epoch (1970-01-01 00:00:00 UTC), disregarding + * leap seconds) the job shall be executed. The rest of + * the arguments (being at least one) shoul be the + * command line arguments for the job the run. + * @return 0 The process was successful. + * @return 1 The process failed queuing the job. + * @return 2 User error, you do not know what you are doing. + * @return 3 satd(1) failed to queue the job. + */ +int +main(int argc, char *argv[]) +{ + struct timespec ts; + clockid_t clk; + + if ((argc < 3) || (argv[1][0] == '-')) { + usage(); + } + + argv0 = argv[0]; + + if (parse_time(str, &ts, &clk)) { + switch (errno) { + case EINVAL: + fprintf(stderr, + "%s: time parameter cound not be parsed, perhaps you " + "you need an external parser: %s\n", argv0, str); + return 2; + case ERANGE: + fprintf(stderr, + "%s: the specified time is beyond the limit of what " + "can be represented by `struct timespec`: %s\n", argv0, str); + return 2; + case EDOM: + fprintf(stderr, + "%s: the specified time is in past, and more than " + "a day ago: %s\n", argv0, str); + return 2; + default: + goto fail; + } + } + +fail: + perror(argv0); + return 1; +} + -- cgit v1.2.3-70-g09d2