date: Use 64 prefix syscall if we have to
[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 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  # syscall(__NR_clock_gettime) or syscall(__NR_clock_gettime64)
40 //config:       depends on DATE
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' and '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 (strptime format) 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 = getopt32long(argv, "^"
196                         "Rs:ud:r:"
197                         IF_FEATURE_DATE_ISOFMT("I::D:")
198                         "\0"
199                         "d--s:s--d"
200                         IF_FEATURE_DATE_ISOFMT(":R--I:I--R"),
201                         date_longopts,
202                         &date_str, &date_str, &filename
203                         IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt)
204         );
205         argv += optind;
206
207         maybe_set_utc(opt);
208
209         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
210                 ifmt = 0; /* default is date */
211                 if (isofmt_arg) {
212                         static const char isoformats[] ALIGN1 =
213                                 "date\0""hours\0""minutes\0""seconds\0"; /* ns? */
214                         ifmt = index_in_substrings(isoformats, isofmt_arg);
215                         if (ifmt < 0)
216                                 bb_show_usage();
217                 }
218         }
219
220         fmt_dt2str = NULL;
221         if (argv[0] && argv[0][0] == '+') {
222                 fmt_dt2str = &argv[0][1]; /* skip over the '+' */
223                 argv++;
224         }
225         if (!(opt & (OPT_SET | OPT_DATE))) {
226                 opt |= OPT_SET;
227                 date_str = argv[0]; /* can be NULL */
228                 if (date_str) {
229 #if ENABLE_FEATURE_DATE_COMPAT
230                         int len = strspn(date_str, "0123456789");
231                         if (date_str[len] == '\0'
232                          || (date_str[len] == '.'
233                             && isdigit(date_str[len+1])
234                             && isdigit(date_str[len+2])
235                             && date_str[len+3] == '\0'
236                             )
237                         ) {
238                                 /* Dreaded MMDDhhmm[[CC]YY][.ss] format!
239                                  * It does not match -d or -s format.
240                                  * Some users actually do use it.
241                                  */
242                                 len -= 8;
243                                 if (len < 0 || len > 4 || (len & 1))
244                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
245                                 if (len != 0) { /* move YY or CCYY to front */
246                                         char buf[4];
247                                         memcpy(buf, date_str + 8, len);
248                                         memmove(date_str + len, date_str, 8);
249                                         memcpy(date_str, buf, len);
250                                 }
251                         }
252 #endif
253                         argv++;
254                 }
255         }
256         if (*argv)
257                 bb_show_usage();
258
259         /* Now we have parsed all the information except the date format
260          * which depends on whether the clock is being set or read */
261
262         if (opt & OPT_REFERENCE) {
263                 struct stat statbuf;
264                 xstat(filename, &statbuf);
265                 ts.tv_sec = statbuf.st_mtime;
266 #if ENABLE_FEATURE_DATE_NANO
267                 ts.tv_nsec = statbuf.st_mtim.tv_nsec;
268                 /* Some toolchains use .st_mtimensec instead of st_mtim.tv_nsec.
269                  * If you need #define _SVID_SOURCE 1 to enable st_mtim.tv_nsec,
270                  * drop a mail to project mailing list please
271                  */
272 #endif
273         } else {
274 #if ENABLE_FEATURE_DATE_NANO && defined(__NR_clock_gettime)
275                 /* libc has incredibly messy way of doing this,
276                  * typically requiring -lrt. We just skip all this mess */
277                 syscall(__NR_clock_gettime, CLOCK_REALTIME, &ts);
278 #elif ENABLE_FEATURE_DATE_NANO && __TIMESIZE == 64
279                 /* Let's only support the 64 suffix syscalls for 64-bit time_t.
280                  * This simplifies the code for us as we don't need to convert
281                  * between 64-bit and 32-bit. We also don't have a way to
282                  * report overflow errors here.
283                  */
284                 syscall(__NR_clock_gettime64, CLOCK_REALTIME, &ts);
285 #else
286                 time(&ts.tv_sec);
287 #endif
288         }
289         localtime_r(&ts.tv_sec, &tm_time);
290
291         /* If date string is given, update tm_time, and maybe set date */
292         if (date_str != NULL) {
293                 /* Zero out fields - take her back to midnight! */
294                 tm_time.tm_sec = 0;
295                 tm_time.tm_min = 0;
296                 tm_time.tm_hour = 0;
297
298                 /* Process any date input to UNIX time since 1 Jan 1970 */
299                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
300                         if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
301                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
302                 } else {
303                         parse_datestr(date_str, &tm_time);
304                 }
305
306                 /* Correct any day of week and day of year etc. fields */
307                 /* Be sure to recheck dst (but not if date is time_t format) */
308                 if (date_str[0] != '@')
309                         tm_time.tm_isdst = -1;
310                 ts.tv_sec = validate_tm_time(date_str, &tm_time);
311
312                 /* if setting time, set it */
313                 if ((opt & OPT_SET) && stime(&ts.tv_sec) < 0) {
314                         bb_simple_perror_msg("can't set date");
315                 }
316         }
317
318         /* Display output */
319
320         /* Deal with format string */
321         if (fmt_dt2str == NULL) {
322                 int i;
323                 fmt_dt2str = buf_fmt_dt2str;
324                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
325                         /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds */
326                         strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
327                         i = 8 + 3 * ifmt;
328                         if (ifmt != 0) {
329                                 /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
330  format_utc:
331                                 fmt_dt2str[i++] = '%';
332                                 fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
333                         }
334                         fmt_dt2str[i] = '\0';
335                 } else if (opt & OPT_RFC2822) {
336                         /* -R. undo busybox.c setlocale */
337                         if (ENABLE_LOCALE_SUPPORT)
338                                 setlocale(LC_TIME, "C");
339                         strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
340                         i = sizeof("%a, %d %b %Y %H:%M:%S ")-1;
341                         goto format_utc;
342                 } else { /* default case */
343                         fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
344                 }
345         }
346 #if ENABLE_FEATURE_DATE_NANO
347         else {
348                 /* User-specified fmt_dt2str */
349                 /* Search for and process "%N" */
350                 char *p = fmt_dt2str;
351                 while ((p = strchr(p, '%')) != NULL) {
352                         int n, m;
353                         unsigned pres, scale;
354
355                         p++;
356                         if (*p == '%') {
357                                 p++;
358                                 continue;
359                         }
360                         n = strspn(p, "0123456789");
361                         if (p[n] != 'N') {
362                                 p += n;
363                                 continue;
364                         }
365                         /* We have "%[nnn]N" */
366                         p[-1] = '\0';
367                         p[n] = '\0';
368                         scale = 1;
369                         pres = 9;
370                         if (n) {
371                                 pres = xatoi_positive(p);
372                                 if (pres == 0)
373                                         pres = 9;
374                                 m = 9 - pres;
375                                 while (--m >= 0)
376                                         scale *= 10;
377                         }
378
379                         m = p - fmt_dt2str;
380                         p += n + 1;
381                         fmt_dt2str = xasprintf("%s%0*u%s", fmt_dt2str, pres, (unsigned)ts.tv_nsec / scale, p);
382                         p = fmt_dt2str + m;
383                 }
384         }
385 #endif
386
387 #define date_buf bb_common_bufsiz1
388         setup_common_bufsiz();
389         if (*fmt_dt2str == '\0') {
390                 /* With no format string, just print a blank line */
391                 date_buf[0] = '\0';
392         } else {
393                 /* Handle special conversions */
394                 if (is_prefixed_with(fmt_dt2str, "%f")) {
395                         fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
396                 }
397                 /* Generate output string */
398                 strftime(date_buf, COMMON_BUFSIZE, fmt_dt2str, &tm_time);
399         }
400         puts(date_buf);
401
402         return EXIT_SUCCESS;
403 }