aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README2
-rw-r--r--src/parse_time.c280
-rw-r--r--src/parse_time.h50
-rw-r--r--src/sat.c103
4 files changed, 434 insertions, 1 deletions
diff --git a/README b/README
index 1bfc250..01af6a4 100644
--- a/README
+++ b/README
@@ -21,7 +21,7 @@ RATIONALE
sat does not do natural language time parsing, it is far too complex,
and locale dependent, for all programs to implemeent and should be
- done in a separate program.
+ done in a separate program. sat does however do some trivial parsing.
sat does not sanitise the environment. You have env(1) for that.
Relaying on sat to sanitise the environment appropriately could prove
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 <maandree@member.fsf.org>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+
+
+
+/**
+ * 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 <maandree@member.fsf.org>
+ *
+ * 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 <time.h>
+
+
+
+/**
+ * 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 <maandree@member.fsf.org>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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;
+}
+