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