sv,svc: fix NOEXEC fallout
[oweals/busybox.git] / coreutils / date.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini date implementation for busybox
4  *
5  * by Matthew Grant <grantma@anathoth.gen.nz>
6  *
7  * iso-format handling added by Robert Griebl <griebl@gmx.de>
8  * bugfixes and cleanup by Bernhard Reutner-Fischer
9  *
10  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11  */
12 /* This 'date' command supports only 2 time setting formats,
13    all the GNU strftime stuff (its in libc, lets use it),
14    setting time using UTC and displaying it, as well as
15    an RFC 2822 compliant date output for shell scripting
16    mail commands */
17
18 /* Input parsing code is always bulky - used heavy duty libc stuff as
19    much as possible, missed out a lot of bounds checking */
20
21 //config:config DATE
22 //config:       bool "date (7.1 kb)"
23 //config:       default y
24 //config:       help
25 //config:       date is used to set the system date or display the
26 //config:       current time in the given format.
27 //config:
28 //config:config FEATURE_DATE_ISOFMT
29 //config:       bool "Enable ISO date format output (-I)"
30 //config:       default y
31 //config:       depends on DATE
32 //config:       help
33 //config:       Enable option (-I) to output an ISO-8601 compliant
34 //config:       date/time string.
35 //config:
36 //config:# defaults to "no": stat's nanosecond field is a bit non-portable
37 //config:config FEATURE_DATE_NANO
38 //config:       bool "Support %[num]N nanosecond format specifier"
39 //config:       default n
40 //config:       depends on DATE  # syscall(__NR_clock_gettime)
41 //config:       select PLATFORM_LINUX
42 //config:       help
43 //config:       Support %[num]N format specifier. Adds ~250 bytes of code.
44 //config:
45 //config:config FEATURE_DATE_COMPAT
46 //config:       bool "Support weird 'date MMDDhhmm[[YY]YY][.ss]' format"
47 //config:       default y
48 //config:       depends on DATE
49 //config:       help
50 //config:       System time can be set by 'date -s DATE' and simply 'date DATE',
51 //config:       but formats of DATE string are different. 'date DATE' accepts
52 //config:       a rather weird MMDDhhmm[[YY]YY][.ss] format with completely
53 //config:       unnatural placement of year between minutes and seconds.
54 //config:       date -s (and other commands like touch -d) use more sensible
55 //config:       formats (for one, ISO format YYYY-MM-DD hh:mm:ss.ssssss).
56 //config:
57 //config:       With this option off, 'date DATE' is 'date -s DATE' support
58 //config:       the same format. With it on, 'date DATE' additionally supports
59 //config:       MMDDhhmm[[YY]YY][.ss] format.
60
61 //applet:IF_DATE(APPLET_NOEXEC(date, date, BB_DIR_BIN, BB_SUID_DROP, date))
62 /* bb_common_bufsiz1 usage here is safe wrt NOEXEC: not expecting it to be zeroed. */
63
64 //kbuild:lib-$(CONFIG_DATE) += date.o
65
66 /* GNU coreutils 6.9 man page:
67  * date [OPTION]... [+FORMAT]
68  * date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
69  * -d, --date=STRING
70  *      display time described by STRING, not 'now'
71  * -f, --file=DATEFILE
72  *      like --date once for each line of DATEFILE
73  * -r, --reference=FILE
74  *      display the last modification time of FILE
75  * -R, --rfc-2822
76  *      output date and time in RFC 2822 format.
77  *      Example: Mon, 07 Aug 2006 12:34:56 -0600
78  * --rfc-3339=TIMESPEC
79  *      output date and time in RFC 3339 format.
80  *      TIMESPEC='date', 'seconds', or 'ns'
81  *      Date and time components are separated by a single space:
82  *      2006-08-07 12:34:56-06:00
83  * -s, --set=STRING
84  *      set time described by STRING
85  * -u, --utc, --universal
86  *      print or set Coordinated Universal Time
87  *
88  * Busybox:
89  * long options are not supported
90  * -f is not supported
91  * -I seems to roughly match --rfc-3339, but -I has _optional_ param
92  *    (thus "-I seconds" doesn't work, only "-Iseconds"),
93  *    and does not support -Ins
94  * -D FMT is a bbox extension for _input_ conversion of -d DATE
95  */
96
97 //usage:#define date_trivial_usage
98 //usage:       "[OPTIONS] [+FMT] [TIME]"
99 //usage:#define date_full_usage "\n\n"
100 //usage:       "Display time (using +FMT), or set time\n"
101 //usage:        IF_NOT_LONG_OPTS(
102 //usage:     "\n        [-s] TIME       Set time to TIME"
103 //usage:     "\n        -u              Work in UTC (don't convert to local time)"
104 //usage:     "\n        -R              Output RFC-2822 compliant date string"
105 //usage:        ) IF_LONG_OPTS(
106 //usage:     "\n        [-s,--set] TIME Set time to TIME"
107 //usage:     "\n        -u,--utc        Work in UTC (don't convert to local time)"
108 //usage:     "\n        -R,--rfc-2822   Output RFC-2822 compliant date string"
109 //usage:        )
110 //usage:        IF_FEATURE_DATE_ISOFMT(
111 //usage:     "\n        -I[SPEC]        Output ISO-8601 compliant date string"
112 //usage:     "\n                        SPEC='date' (default) for date only,"
113 //usage:     "\n                        'hours', 'minutes', or 'seconds' for date and"
114 //usage:     "\n                        time to the indicated precision"
115 //usage:        )
116 //usage:        IF_NOT_LONG_OPTS(
117 //usage:     "\n        -r FILE         Display last modification time of FILE"
118 //usage:     "\n        -d TIME         Display TIME, not 'now'"
119 //usage:        ) IF_LONG_OPTS(
120 //usage:     "\n        -r,--reference FILE     Display last modification time of FILE"
121 //usage:     "\n        -d,--date TIME  Display TIME, not 'now'"
122 //usage:        )
123 //usage:        IF_FEATURE_DATE_ISOFMT(
124 //usage:     "\n        -D FMT          Use FMT for -d TIME conversion"
125 //usage:        )
126 //usage:     "\n"
127 //usage:     "\nRecognized TIME formats:"
128 //usage:     "\n        hh:mm[:ss]"
129 //usage:     "\n        [YYYY.]MM.DD-hh:mm[:ss]"
130 //usage:     "\n        YYYY-MM-DD hh:mm[:ss]"
131 //usage:     "\n        [[[[[YY]YY]MM]DD]hh]mm[.ss]"
132 //usage:        IF_FEATURE_DATE_COMPAT(
133 //usage:     "\n        'date TIME' form accepts MMDDhhmm[[YY]YY][.ss] instead"
134 //usage:        )
135 //usage:
136 //usage:#define date_example_usage
137 //usage:       "$ date\n"
138 //usage:       "Wed Apr 12 18:52:41 MDT 2000\n"
139
140 #include "libbb.h"
141 #include "common_bufsiz.h"
142 #if ENABLE_FEATURE_DATE_NANO
143 # include <sys/syscall.h>
144 #endif
145
146 enum {
147         OPT_RFC2822   = (1 << 0), /* R */
148         OPT_SET       = (1 << 1), /* s */
149         OPT_UTC       = (1 << 2), /* u */
150         OPT_DATE      = (1 << 3), /* d */
151         OPT_REFERENCE = (1 << 4), /* r */
152         OPT_TIMESPEC  = (1 << 5) * ENABLE_FEATURE_DATE_ISOFMT, /* I */
153         OPT_HINT      = (1 << 6) * ENABLE_FEATURE_DATE_ISOFMT, /* D */
154 };
155
156 #if ENABLE_LONG_OPTS
157 static const char date_longopts[] ALIGN1 =
158                 "rfc-822\0"   No_argument       "R"
159                 "rfc-2822\0"  No_argument       "R"
160                 "set\0"       Required_argument "s"
161                 "utc\0"       No_argument       "u"
162         /*      "universal\0" No_argument       "u" */
163                 "date\0"      Required_argument "d"
164                 "reference\0" Required_argument "r"
165                 ;
166 #endif
167
168 /* We are a NOEXEC applet.
169  * Obstacles to NOFORK:
170  * - we change env
171  * - xasprintf result not freed
172  * - after xasprintf we use other xfuncs
173  */
174
175 static void maybe_set_utc(int opt)
176 {
177         if (opt & OPT_UTC)
178                 putenv((char*)"TZ=UTC0");
179 }
180
181 int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
182 int date_main(int argc UNUSED_PARAM, char **argv)
183 {
184         struct timespec ts;
185         struct tm tm_time;
186         char buf_fmt_dt2str[64];
187         unsigned opt;
188         int ifmt = -1;
189         char *date_str;
190         char *fmt_dt2str;
191         char *fmt_str2dt;
192         char *filename;
193         char *isofmt_arg = NULL;
194
195         opt_complementary = "d--s:s--d"
196                 IF_FEATURE_DATE_ISOFMT(":R--I:I--R");
197         IF_LONG_OPTS(applet_long_options = date_longopts;)
198         opt = getopt32(argv, "Rs:ud:r:"
199                         IF_FEATURE_DATE_ISOFMT("I::D:"),
200                         &date_str, &date_str, &filename
201                         IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt));
202         argv += optind;
203         maybe_set_utc(opt);
204
205         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
206                 ifmt = 0; /* default is date */
207                 if (isofmt_arg) {
208                         static const char isoformats[] ALIGN1 =
209                                 "date\0""hours\0""minutes\0""seconds\0"; /* ns? */
210                         ifmt = index_in_substrings(isoformats, isofmt_arg);
211                         if (ifmt < 0)
212                                 bb_show_usage();
213                 }
214         }
215
216         fmt_dt2str = NULL;
217         if (argv[0] && argv[0][0] == '+') {
218                 fmt_dt2str = &argv[0][1]; /* skip over the '+' */
219                 argv++;
220         }
221         if (!(opt & (OPT_SET | OPT_DATE))) {
222                 opt |= OPT_SET;
223                 date_str = argv[0]; /* can be NULL */
224                 if (date_str) {
225 #if ENABLE_FEATURE_DATE_COMPAT
226                         int len = strspn(date_str, "0123456789");
227                         if (date_str[len] == '\0'
228                          || (date_str[len] == '.'
229                             && isdigit(date_str[len+1])
230                             && isdigit(date_str[len+2])
231                             && date_str[len+3] == '\0'
232                             )
233                         ) {
234                                 /* Dreaded MMDDhhmm[[CC]YY][.ss] format!
235                                  * It does not match -d or -s format.
236                                  * Some users actually do use it.
237                                  */
238                                 len -= 8;
239                                 if (len < 0 || len > 4 || (len & 1))
240                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
241                                 if (len != 0) { /* move YY or CCYY to front */
242                                         char buf[4];
243                                         memcpy(buf, date_str + 8, len);
244                                         memmove(date_str + len, date_str, 8);
245                                         memcpy(date_str, buf, len);
246                                 }
247                         }
248 #endif
249                         argv++;
250                 }
251         }
252         if (*argv)
253                 bb_show_usage();
254
255         /* Now we have parsed all the information except the date format
256          * which depends on whether the clock is being set or read */
257
258         if (opt & OPT_REFERENCE) {
259                 struct stat statbuf;
260                 xstat(filename, &statbuf);
261                 ts.tv_sec = statbuf.st_mtime;
262 #if ENABLE_FEATURE_DATE_NANO
263                 ts.tv_nsec = statbuf.st_mtim.tv_nsec;
264                 /* Some toolchains use .st_mtimensec instead of st_mtim.tv_nsec.
265                  * If you need #define _SVID_SOURCE 1 to enable st_mtim.tv_nsec,
266                  * drop a mail to project mailing list please
267                  */
268 #endif
269         } else {
270 #if ENABLE_FEATURE_DATE_NANO
271                 /* libc has incredibly messy way of doing this,
272                  * typically requiring -lrt. We just skip all this mess */
273                 syscall(__NR_clock_gettime, CLOCK_REALTIME, &ts);
274 #else
275                 time(&ts.tv_sec);
276 #endif
277         }
278         localtime_r(&ts.tv_sec, &tm_time);
279
280         /* If date string is given, update tm_time, and maybe set date */
281         if (date_str != NULL) {
282                 /* Zero out fields - take her back to midnight! */
283                 tm_time.tm_sec = 0;
284                 tm_time.tm_min = 0;
285                 tm_time.tm_hour = 0;
286
287                 /* Process any date input to UNIX time since 1 Jan 1970 */
288                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
289                         if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
290                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
291                 } else {
292                         parse_datestr(date_str, &tm_time);
293                 }
294
295                 /* Correct any day of week and day of year etc. fields */
296                 /* Be sure to recheck dst (but not if date is time_t format) */
297                 if (date_str[0] != '@')
298                         tm_time.tm_isdst = -1;
299                 ts.tv_sec = validate_tm_time(date_str, &tm_time);
300
301                 maybe_set_utc(opt);
302
303                 /* if setting time, set it */
304                 if ((opt & OPT_SET) && stime(&ts.tv_sec) < 0) {
305                         bb_perror_msg("can't set date");
306                 }
307         }
308
309         /* Display output */
310
311         /* Deal with format string */
312         if (fmt_dt2str == NULL) {
313                 int i;
314                 fmt_dt2str = buf_fmt_dt2str;
315                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
316                         /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds */
317                         strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
318                         i = 8 + 3 * ifmt;
319                         if (ifmt != 0) {
320                                 /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
321  format_utc:
322                                 fmt_dt2str[i++] = '%';
323                                 fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
324                         }
325                         fmt_dt2str[i] = '\0';
326                 } else if (opt & OPT_RFC2822) {
327                         /* -R. undo busybox.c setlocale */
328                         if (ENABLE_LOCALE_SUPPORT)
329                                 setlocale(LC_TIME, "C");
330                         strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
331                         i = sizeof("%a, %d %b %Y %H:%M:%S ")-1;
332                         goto format_utc;
333                 } else { /* default case */
334                         fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
335                 }
336         }
337 #if ENABLE_FEATURE_DATE_NANO
338         else {
339                 /* User-specified fmt_dt2str */
340                 /* Search for and process "%N" */
341                 char *p = fmt_dt2str;
342                 while ((p = strchr(p, '%')) != NULL) {
343                         int n, m;
344                         unsigned pres, scale;
345
346                         p++;
347                         if (*p == '%') {
348                                 p++;
349                                 continue;
350                         }
351                         n = strspn(p, "0123456789");
352                         if (p[n] != 'N') {
353                                 p += n;
354                                 continue;
355                         }
356                         /* We have "%[nnn]N" */
357                         p[-1] = '\0';
358                         p[n] = '\0';
359                         scale = 1;
360                         pres = 9;
361                         if (n) {
362                                 pres = xatoi_positive(p);
363                                 if (pres == 0)
364                                         pres = 9;
365                                 m = 9 - pres;
366                                 while (--m >= 0)
367                                         scale *= 10;
368                         }
369
370                         m = p - fmt_dt2str;
371                         p += n + 1;
372                         fmt_dt2str = xasprintf("%s%0*u%s", fmt_dt2str, pres, (unsigned)ts.tv_nsec / scale, p);
373                         p = fmt_dt2str + m;
374                 }
375         }
376 #endif
377
378 #define date_buf bb_common_bufsiz1
379         setup_common_bufsiz();
380         if (*fmt_dt2str == '\0') {
381                 /* With no format string, just print a blank line */
382                 date_buf[0] = '\0';
383         } else {
384                 /* Handle special conversions */
385                 if (is_prefixed_with(fmt_dt2str, "%f")) {
386                         fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
387                 }
388                 /* Generate output string */
389                 strftime(date_buf, COMMON_BUFSIZE, fmt_dt2str, &tm_time);
390         }
391         puts(date_buf);
392
393         return EXIT_SUCCESS;
394 }