From 9346ea9550887ee81de4df3731f8d4ff533f4aed Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 11 Sep 2021 00:47:03 +0200 Subject: df: "support" -H as an alias of -h function old new delta df_main 1065 1068 +3 .rodata 104232 104233 +1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 4/0) Total: 4 bytes Signed-off-by: Denys Vlasenko --- coreutils/df.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/coreutils/df.c b/coreutils/df.c index e8d4bc8f2..9f8b3a71e 100644 --- a/coreutils/df.c +++ b/coreutils/df.c @@ -99,15 +99,16 @@ int df_main(int argc UNUSED_PARAM, char **argv) struct mntent *mount_entry; struct statvfs s; enum { - OPT_KILO = (1 << 0), - OPT_POSIX = (1 << 1), - OPT_FSTYPE = (1 << 2), - OPT_t = (1 << 3), - OPT_ALL = (1 << 4) * ENABLE_FEATURE_DF_FANCY, - OPT_INODE = (1 << 5) * ENABLE_FEATURE_DF_FANCY, - OPT_BSIZE = (1 << 6) * ENABLE_FEATURE_DF_FANCY, - OPT_HUMAN = (1 << (4 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE, - OPT_MEGA = (1 << (5 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE, + OPT_KILO = (1 << 0), + OPT_POSIX = (1 << 1), + OPT_FSTYPE = (1 << 2), + OPT_t = (1 << 3), + OPT_ALL = (1 << 4) * ENABLE_FEATURE_DF_FANCY, + OPT_INODE = (1 << 5) * ENABLE_FEATURE_DF_FANCY, + OPT_BSIZE = (1 << 6) * ENABLE_FEATURE_DF_FANCY, + OPT_HUMAN = (1 << (4 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE, + OPT_HUMANDEC = (1 << (5 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE, + OPT_MEGA = (1 << (6 + 3*ENABLE_FEATURE_DF_FANCY)) * ENABLE_FEATURE_HUMAN_READABLE, }; const char *disp_units_hdr = NULL; char *chp, *opt_t; @@ -124,7 +125,7 @@ int df_main(int argc UNUSED_PARAM, char **argv) opt = getopt32(argv, "^" "kPTt:" IF_FEATURE_DF_FANCY("aiB:") - IF_FEATURE_HUMAN_READABLE("hm") + IF_FEATURE_HUMAN_READABLE("hHm") "\0" #if ENABLE_FEATURE_HUMAN_READABLE && ENABLE_FEATURE_DF_FANCY "k-mB:m-Bk:B-km" @@ -151,8 +152,11 @@ int df_main(int argc UNUSED_PARAM, char **argv) got_it: ; } - if (opt & OPT_HUMAN) { + if (opt & (OPT_HUMAN|OPT_HUMANDEC)) { df_disp_hr = 0; +//TODO: need to add support in make_human_readable_str() for "decimal human readable" + //if (opt & OPT_HUMANDEC) + // df_disp_hr--; disp_units_hdr = " Size"; } if (opt & OPT_INODE) -- cgit v1.2.3-55-g6feb From 0beee209778870888c3a80a9ae57e74888bc8e7b Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Sun, 12 Sep 2021 11:20:33 +0100 Subject: ash: fix ignoreeof option The ignoreeof option should prevent an interactive shell from exiting on EOF. This hasn't worked in BusyBox ash since commit 727752d2d (ash: better fix for ash -c 'echo 5&' and ash -c 'sleep 5&' with testcase). Commit 3b4d04b77e (ash: input: Allow two consecutive calls to pungetc) pulled in improved support for multiple calls to pungetc from dash, thus rendering much of commit 727752d2d obsolete. Removing this old code fixes the problem with ignoreeof. function old new delta __pgetc 605 587 -18 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/1 up/down: 0/-18) Total: -18 bytes Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- shell/ash.c | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 3524d046e..152b3b46a 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -10826,9 +10826,7 @@ preadfd(void) * Refill the input buffer and return the next input character: * * 1) If a string was pushed back on the input, pop it; - * 2) If an EOF was pushed back (g_parsefile->left_in_line < -BIGNUM) - * or we are reading from a string so we can't refill the buffer, - * return EOF. + * 2) If we are reading from a string we can't refill the buffer, return EOF. * 3) If there is more stuff in this buffer, use it else call read to fill it. * 4) Process input up to the next newline, deleting nul characters. */ @@ -10845,21 +10843,9 @@ preadbuffer(void) popstring(); return __pgetc(); } - /* on both branches above g_parsefile->left_in_line < 0. - * "pgetc" needs refilling. - */ - /* -90 is our -BIGNUM. Below we use -99 to mark "EOF on read", - * pungetc() may increment it a few times. - * Assuming it won't increment it to less than -90. - */ - if (g_parsefile->left_in_line < -90 || g_parsefile->buf == NULL) { + if (g_parsefile->buf == NULL) { pgetc_debug("preadbuffer PEOF1"); - /* even in failure keep left_in_line and next_to_pgetc - * in lock step, for correct multi-layer pungetc. - * left_in_line was decremented before preadbuffer(), - * must inc next_to_pgetc: */ - g_parsefile->next_to_pgetc++; return PEOF; } @@ -10869,10 +10855,8 @@ preadbuffer(void) again: more = preadfd(); if (more <= 0) { - /* don't try reading again */ - g_parsefile->left_in_line = -99; + g_parsefile->left_in_buffer = g_parsefile->left_in_line = 0; pgetc_debug("preadbuffer PEOF2"); - g_parsefile->next_to_pgetc++; return PEOF; } } -- cgit v1.2.3-55-g6feb From 50239a665c88f5a95ce41146804500f5da90b19e Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Sun, 12 Sep 2021 11:21:08 +0100 Subject: ash: stopped jobs should only prevent exit from interactive shell When the user tries to exit an interactive shell with stopped jobs present the shell issues a warning and only exits if the user insists by trying to exit again. This shouldn't apply to non-interactive shells. Reported-by: Roberto A. Foglietta Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/ash.c b/shell/ash.c index 152b3b46a..5743b2377 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5451,7 +5451,7 @@ stoppedjobs(void) int retval; retval = 0; - if (job_warning) + if (!iflag || job_warning) goto out; jp = curjob; if (jp && jp->state == JOBSTOPPED) { -- cgit v1.2.3-55-g6feb From 5726df5f94f973eaa097d9853ceff2bd6b748d97 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Sun, 12 Sep 2021 11:21:48 +0100 Subject: ash: let ignoreeof only affect interactive shells Commit fb7d6c89 from Harald van Dijk's gwsh variant of ash (https://github.com/hvdijk/gwsh): ignoreeof is documented as only having an effect for interactive shells, but is implemented as having mostly the same effect for interactive shells as for non-interactive shells. Change the implementation to match the documentation. Test case: $SHELL -o ignoreeof /dev/null function old new delta cmdloop 359 361 +2 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/0 up/down: 2/0) Total: 2 bytes Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- shell/ash.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 5743b2377..b12b859d5 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -13508,12 +13508,13 @@ cmdloop(int top) if (!top || numeof >= 50) break; if (!stoppedjobs()) { + if (!iflag) + break; if (!Iflag) { - if (iflag) { - newline_and_flush(stderr); - } + newline_and_flush(stderr); break; } + /* "set -o ignoreeof" active, do not exit command loop on ^D */ out2str("\nUse \"exit\" to leave shell.\n"); } numeof++; -- cgit v1.2.3-55-g6feb From 3512ef801839feeb20d178776fff999e1da532fd Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Tue, 14 Sep 2021 08:52:49 +0100 Subject: libbb: code shrink parse_datestr The default build uses strptime() in parse_datestr() to support the 'month_name d HH:MM:SS YYYY' format of GNU date. If we've linked with strptime() there's an advantage is using it for other formats too. There's no change to the non-default, non-DESKTOP build. function old new delta fmt_str - 106 +106 .rodata 99216 99145 -71 parse_datestr 948 624 -324 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/2 up/down: 106/-395) Total: -289 bytes Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- libbb/time.c | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/libbb/time.c b/libbb/time.c index cf5f2e5c8..365b1df02 100644 --- a/libbb/time.c +++ b/libbb/time.c @@ -11,13 +11,45 @@ void FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm) { char end = '\0'; +#if ENABLE_DESKTOP +/* + * strptime is BIG: ~1k in uclibc, ~10k in glibc + * We need it for 'month_name d HH:MM:SS YYYY', supported by GNU date, + * but if we've linked it we might as well use it for everything. + */ + static const char fmt_str[] ALIGN1 = + "%R" "\0" /* HH:MM */ + "%T" "\0" /* HH:MM:SS */ + "%m.%d-%R" "\0" /* mm.dd-HH:MM */ + "%m.%d-%T" "\0" /* mm.dd-HH:MM:SS */ + "%Y.%m.%d-%R" "\0" /* yyyy.mm.dd-HH:MM */ + "%Y.%m.%d-%T" "\0" /* yyyy.mm.dd-HH:MM:SS */ + "%b %d %T %Y" "\0" /* month_name d HH:MM:SS YYYY */ + "%Y-%m-%d %R" "\0" /* yyyy-mm-dd HH:MM */ + "%Y-%m-%d %T" "\0" /* yyyy-mm-dd HH:MM:SS */ + "%Y-%m-%d %H" "\0" /* yyyy-mm-dd HH */ + "%Y-%m-%d" "\0" /* yyyy-mm-dd */ + /* extra NUL */; + struct tm save; + const char *fmt; + char *endp; + + save = *ptm; + fmt = fmt_str; + while (*fmt) { + endp = strptime(date_str, fmt, ptm); + if (endp && *endp == '\0') + return; + *ptm = save; + while (*++fmt) + continue; + ++fmt; + } +#else const char *last_colon = strrchr(date_str, ':'); if (last_colon != NULL) { /* Parse input and assign appropriately to ptm */ -#if ENABLE_DESKTOP - const char *endp; -#endif /* HH:MM */ if (sscanf(date_str, "%u:%u%c", @@ -50,14 +82,6 @@ void FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm) ptm->tm_year -= 1900; /* Adjust years */ ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */ } else -#if ENABLE_DESKTOP /* strptime is BIG: ~1k in uclibc, ~10k in glibc */ - /* month_name d HH:MM:SS YYYY. Supported by GNU date */ - if ((endp = strptime(date_str, "%b %d %T %Y", ptm)) != NULL - && *endp == '\0' - ) { - return; /* don't fall through to end == ":" check */ - } else -#endif { bb_error_msg_and_die(bb_msg_invalid_date, date_str); } @@ -89,6 +113,7 @@ void FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm) ptm->tm_year -= 1900; /* Adjust years */ ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */ } else +#endif /* ENABLE_DESKTOP */ if (date_str[0] == '@') { time_t t; if (sizeof(t) <= sizeof(long)) -- cgit v1.2.3-55-g6feb From 704c596563a5b4da4349b272d0c9c71aacea34a7 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 15 Sep 2021 19:31:44 +0200 Subject: ash: introduce bash-like $FUNCNAME Patch adapted from Roberto A. Foglietta work. function old new delta lookupvar 106 150 +44 evalfun 369 408 +39 ash_main 1218 1242 +24 varinit_data 156 168 +12 .rodata 104162 104172 +10 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 5/0 up/down: 129/0) Total: 129 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index b12b859d5..4bc4f55d0 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -2158,6 +2158,7 @@ static const struct { { VSTRFIXED|VTEXTFIXED , defoptindvar, getoptsreset }, #endif { VSTRFIXED|VTEXTFIXED , NULL /* inited to linenovar */, NULL }, + { VSTRFIXED|VTEXTFIXED , NULL /* inited to funcnamevar */, NULL }, #if ENABLE_ASH_RANDOM_SUPPORT { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random }, #endif @@ -2184,6 +2185,8 @@ struct globals_var { struct var varinit[ARRAY_SIZE(varinit_data)]; int lineno; char linenovar[sizeof("LINENO=") + sizeof(int)*3]; + char funcnamevar[sizeof("FUNCNAME=") + 64]; + char *funcname; unsigned trap_depth; bool in_trap_ERR; /* ERR cannot recurse, no need to be a counter */ }; @@ -2196,6 +2199,8 @@ extern struct globals_var *BB_GLOBAL_CONST ash_ptr_to_globals_var; #define varinit (G_var.varinit ) #define lineno (G_var.lineno ) #define linenovar (G_var.linenovar ) +#define funcnamevar (G_var.funcnamevar ) +#define funcname (G_var.funcname ) #define trap_depth (G_var.trap_depth ) #define in_trap_ERR (G_var.in_trap_ERR ) #define vifs varinit[0] @@ -2213,13 +2218,14 @@ extern struct globals_var *BB_GLOBAL_CONST ash_ptr_to_globals_var; #endif #define VAR_OFFSET2 (VAR_OFFSET1 + ENABLE_ASH_GETOPTS) #define vlineno varinit[VAR_OFFSET2 + 5] +#define vfuncname varinit[VAR_OFFSET2 + 6] #if ENABLE_ASH_RANDOM_SUPPORT -# define vrandom varinit[VAR_OFFSET2 + 6] +# define vrandom varinit[VAR_OFFSET2 + 7] #endif #define VAR_OFFSET3 (VAR_OFFSET2 + ENABLE_ASH_RANDOM_SUPPORT) #if BASH_EPOCH_VARS -# define vepochs varinit[VAR_OFFSET3 + 6] -# define vepochr varinit[VAR_OFFSET3 + 7] +# define vepochs varinit[VAR_OFFSET3 + 7] +# define vepochr varinit[VAR_OFFSET3 + 8] #endif #define INIT_G_var() do { \ unsigned i; \ @@ -2232,6 +2238,8 @@ extern struct globals_var *BB_GLOBAL_CONST ash_ptr_to_globals_var; } \ strcpy(linenovar, "LINENO="); \ vlineno.var_text = linenovar; \ + strcpy(funcnamevar, "FUNCNAME="); \ + vfuncname.var_text = funcnamevar; \ } while (0) /* @@ -2371,6 +2379,9 @@ lookupvar(const char *name) if (!(v->flags & VUNSET)) { if (v->var_text == linenovar) { fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno); + } else + if (v->var_text == funcnamevar) { + safe_strncpy(funcnamevar+9, funcname ? funcname : "", sizeof(funcnamevar)-9); } return var_end(v->var_text); } @@ -9875,6 +9886,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) int e; int savelineno; int savefuncline; + char *savefuncname; char *savetrap = NULL; if (!Eflag) { @@ -9884,6 +9896,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) savelineno = lineno; saveparam = shellparam; savefuncline = funcline; + savefuncname = funcname; savehandler = exception_handler; e = setjmp(jmploc.loc); if (e) { @@ -9893,6 +9906,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) exception_handler = &jmploc; shellparam.malloced = 0; func->count++; + funcname = func->n.ndefun.text; funcline = func->n.ndefun.linno; INT_ON; shellparam.nparam = argc - 1; @@ -9904,6 +9918,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) evaltree(func->n.ndefun.body, flags & EV_TESTED); funcdone: INT_OFF; + funcname = savefuncname; if (savetrap) { if (!trap[NTRAP_ERR]) trap[NTRAP_ERR] = savetrap; @@ -13639,6 +13654,12 @@ exitcmd(int argc UNUSED_PARAM, char **argv) if (argv[1]) savestatus = number(argv[1]); +//TODO: this script +// trap 'echo trap:$FUNCNAME' EXIT +// f() { exit; } +// f +//prints "trap:f" in bash. We can call exitshell() here to achieve this. +//For now, keeping dash code: raise_exception(EXEXIT); /* NOTREACHED */ } -- cgit v1.2.3-55-g6feb From 83e20cb81ca6d22a1ca268a0a64523b5af67325a Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Sun, 12 Sep 2021 12:26:03 +0100 Subject: getopt: code shrink function old new delta .rodata 99277 99290 +13 normalize 177 142 -35 getopt_main 675 622 -53 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/2 up/down: 13/-88) Total: -75 bytes Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- util-linux/getopt.c | 64 +++++++++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/util-linux/getopt.c b/util-linux/getopt.c index 1fa402429..4148586d3 100644 --- a/util-linux/getopt.c +++ b/util-linux/getopt.c @@ -156,57 +156,43 @@ enum { static const char *normalize(const char *arg) { char *bufptr; -#if ENABLE_FEATURE_CLEAN_UP - static char *BUFFER = NULL; - free(BUFFER); -#else char *BUFFER; -#endif - if (!quote) { /* Just copy arg */ - BUFFER = xstrdup(arg); - return BUFFER; + if (!quote) { /* Just return arg */ + return arg; } /* Each character in arg may take up to four characters in the result: For a quote we need a closing quote, a backslash, a quote and an opening quote! We need also the global opening and closing quote, and one extra character for '\0'. */ - BUFFER = xmalloc(strlen(arg)*4 + 3); + BUFFER = auto_string(xmalloc(strlen(arg)*4 + 3)); bufptr = BUFFER; *bufptr ++= '\''; while (*arg) { - if (*arg == '\'') { - /* Quote: replace it with: '\'' */ - *bufptr ++= '\''; - *bufptr ++= '\\'; - *bufptr ++= '\''; - *bufptr ++= '\''; - } else if (shell_TCSH && *arg == '!') { - /* Exclamation mark: replace it with: \! */ - *bufptr ++= '\''; - *bufptr ++= '\\'; - *bufptr ++= '!'; - *bufptr ++= '\''; - } else if (shell_TCSH && *arg == '\n') { + if (shell_TCSH && *arg == '\n') { /* Newline: replace it with: \n */ - *bufptr ++= '\\'; - *bufptr ++= 'n'; - } else if (shell_TCSH && isspace(*arg)) { - /* Non-newline whitespace: replace it with \ */ - *bufptr ++= '\''; - *bufptr ++= '\\'; - *bufptr ++= *arg; - *bufptr ++= '\''; + *bufptr++ = '\\'; + *bufptr++ = 'n'; } else + if ((shell_TCSH && (*arg == '!' || isspace(*arg))) + || *arg == '\'' + ) { + /* Quote exclamation marks, non-NL whitespace and quotes */ + *bufptr++ = '\''; + *bufptr++ = '\\'; + *bufptr++ = *arg; + *bufptr++ = '\''; + } else { /* Just copy */ *bufptr ++= *arg; + } arg++; } - *bufptr ++= '\''; - *bufptr ++= '\0'; + *bufptr++ = '\''; + *bufptr++ = '\0'; return BUFFER; } @@ -327,12 +313,18 @@ static struct option *add_long_options(struct option *long_options, char *option static void set_shell(const char *new_shell) { - if (strcmp(new_shell, "bash") == 0 || strcmp(new_shell, "sh") == 0) - return; - if (strcmp(new_shell, "tcsh") == 0 || strcmp(new_shell, "csh") == 0) + switch (index_in_strings("bash\0sh\0tcsh\0csh\0", new_shell)) { + case 0: + case 1: + break; + case 2: + case 3: option_mask32 |= SHELL_IS_TCSH; - else + break; + default: bb_error_msg("unknown shell '%s', assuming bash", new_shell); + break; + } } -- cgit v1.2.3-55-g6feb From 9fe1548bbfde548d54acaab113656a56ea0ccc72 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Thu, 16 Sep 2021 10:26:14 +0100 Subject: date,touch: allow timezone offsets in dates Allow ISO 8601 style dates to include a timezone offset. Like the '@' format these dates aren't relative to the user's current timezone and shouldn't be subject to DST adjustment. - The implementation uses the strptime() '%z' format specifier. This an extension which may not be available so the use of timezones is a configuration option. - The 'touch' applet has been updated to respect whether DST adjustment is required, matching 'date'. function old new delta parse_datestr 624 730 +106 static.fmt_str 106 136 +30 touch_main 388 392 +4 date_main 818 819 +1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/0 up/down: 141/0) Total: 141 bytes Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- coreutils/date.c | 7 ++++--- coreutils/touch.c | 6 ++++-- include/libbb.h | 2 +- libbb/Config.src | 11 +++++++++++ libbb/time.c | 35 +++++++++++++++++++++++++++++++---- testsuite/date/date-timezone | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 testsuite/date/date-timezone diff --git a/coreutils/date.c b/coreutils/date.c index 7061f1719..abcc37c33 100644 --- a/coreutils/date.c +++ b/coreutils/date.c @@ -266,6 +266,7 @@ int date_main(int argc UNUSED_PARAM, char **argv) /* If date string is given, update tm_time, and maybe set date */ if (date_str != NULL) { + int check_dst = 1; /* Zero out fields - take her back to midnight! */ tm_time.tm_sec = 0; tm_time.tm_min = 0; @@ -276,12 +277,12 @@ int date_main(int argc UNUSED_PARAM, char **argv) if (strptime(date_str, fmt_str2dt, &tm_time) == NULL) bb_error_msg_and_die(bb_msg_invalid_date, date_str); } else { - parse_datestr(date_str, &tm_time); + check_dst = parse_datestr(date_str, &tm_time); } /* Correct any day of week and day of year etc. fields */ - /* Be sure to recheck dst (but not if date is time_t format) */ - if (date_str[0] != '@') + /* Be sure to recheck dst (but not if date is UTC) */ + if (check_dst) tm_time.tm_isdst = -1; ts.tv_sec = validate_tm_time(date_str, &tm_time); ts.tv_nsec = 0; diff --git a/coreutils/touch.c b/coreutils/touch.c index 78100ba1d..7e13a27be 100644 --- a/coreutils/touch.c +++ b/coreutils/touch.c @@ -140,15 +140,17 @@ int touch_main(int argc UNUSED_PARAM, char **argv) if (opts & (OPT_d|OPT_t)) { struct tm tm_time; time_t t; + int check_dst; //memset(&tm_time, 0, sizeof(tm_time)); /* Better than memset: makes "HH:MM" dates meaningful */ time(&t); localtime_r(&t, &tm_time); - parse_datestr(date_str, &tm_time); + check_dst = parse_datestr(date_str, &tm_time); /* Correct any day of week and day of year etc. fields */ - tm_time.tm_isdst = -1; /* Be sure to recheck dst */ + if (check_dst) + tm_time.tm_isdst = -1; /* recheck dst unless date is UTC */ t = validate_tm_time(date_str, &tm_time); timebuf[1].tv_sec = timebuf[0].tv_sec = t; diff --git a/include/libbb.h b/include/libbb.h index 7d6ab4a93..1ec8d2d3b 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -690,7 +690,7 @@ struct BUG_too_small { }; -void parse_datestr(const char *date_str, struct tm *ptm) FAST_FUNC; +int parse_datestr(const char *date_str, struct tm *ptm) FAST_FUNC; time_t validate_tm_time(const char *date_str, struct tm *ptm) FAST_FUNC; char *strftime_HHMMSS(char *buf, unsigned len, time_t *tp) FAST_FUNC; char *strftime_YYYYMMDDHHMMSS(char *buf, unsigned len, time_t *tp) FAST_FUNC; diff --git a/libbb/Config.src b/libbb/Config.src index f97de8ef7..58c5fad50 100644 --- a/libbb/Config.src +++ b/libbb/Config.src @@ -395,3 +395,14 @@ config FEATURE_HWIB default y help Support for printing infiniband addresses in network applets. + +config FEATURE_TIMEZONE + bool "Allow timezone in dates" + default y + depends on DESKTOP + help + Permit the use of timezones when parsing user-provided data + strings, e.g. '1996-04-09 12:45:00 -0500'. + + This requires support for the '%z' extension to strptime() which + may not be available in all implementations. diff --git a/libbb/time.c b/libbb/time.c index 365b1df02..41a69c754 100644 --- a/libbb/time.c +++ b/libbb/time.c @@ -8,7 +8,9 @@ */ #include "libbb.h" -void FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm) +/* Returns 0 if the time structure contains an absolute UTC time which + * should not be subject to DST adjustment by the caller. */ +int FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm) { char end = '\0'; #if ENABLE_DESKTOP @@ -27,6 +29,10 @@ void FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm) "%b %d %T %Y" "\0" /* month_name d HH:MM:SS YYYY */ "%Y-%m-%d %R" "\0" /* yyyy-mm-dd HH:MM */ "%Y-%m-%d %T" "\0" /* yyyy-mm-dd HH:MM:SS */ +#if ENABLE_FEATURE_TIMEZONE + "%Y-%m-%d %R %z" "\0" /* yyyy-mm-dd HH:MM TZ */ + "%Y-%m-%d %T %z" "\0" /* yyyy-mm-dd HH:MM:SS TZ */ +#endif "%Y-%m-%d %H" "\0" /* yyyy-mm-dd HH */ "%Y-%m-%d" "\0" /* yyyy-mm-dd */ /* extra NUL */; @@ -38,8 +44,28 @@ void FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm) fmt = fmt_str; while (*fmt) { endp = strptime(date_str, fmt, ptm); - if (endp && *endp == '\0') - return; + if (endp && *endp == '\0') { +#if ENABLE_FEATURE_TIMEZONE + if (strchr(fmt, 'z')) { + time_t t; + struct tm *utm; + + /* we have timezone offset: obtain Unix time_t */ + ptm->tm_sec -= ptm->tm_gmtoff; + ptm->tm_isdst = 0; + t = timegm(ptm); + if (t == (time_t)-1) + break; + /* convert Unix time_t to struct tm in user's locale */ + utm = localtime(&t); + if (!utm) + break; + *ptm = *utm; + return 0; + } +#endif + return 1; + } *ptm = save; while (*++fmt) continue; @@ -124,7 +150,7 @@ void FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm) struct tm *lt = localtime(&t); if (lt) { *ptm = *lt; - return; + return 0; } } end = '1'; @@ -241,6 +267,7 @@ void FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm) if (end != '\0') { bb_error_msg_and_die(bb_msg_invalid_date, date_str); } + return 1; } time_t FAST_FUNC validate_tm_time(const char *date_str, struct tm *ptm) diff --git a/testsuite/date/date-timezone b/testsuite/date/date-timezone new file mode 100644 index 000000000..8628aa1d7 --- /dev/null +++ b/testsuite/date/date-timezone @@ -0,0 +1,32 @@ +# FEATURE: CONFIG_FEATURE_TIMEZONE + +# 'Z' is UTC +dt=$(TZ=UTC0 busybox date -d '1999-1-2 3:4:5Z') +dt=$(echo "$dt" | cut -b1-19) +test x"$dt" = x"Sat Jan 2 03:04:05" + +# '+0600' is six hours ahead of UTC +dt=$(TZ=UTC0 busybox date -d '1999-1-2 3:4:5 +0600') +dt=$(echo "$dt" | cut -b1-19) +test x"$dt" = x"Fri Jan 1 21:04:05" + +# '-0600' is six hours behind UTC +dt=$(TZ=UTC0 busybox date -d '1999-1-2 3:4:5 -0600') +dt=$(echo "$dt" | cut -b1-19) +test x"$dt" = x"Sat Jan 2 09:04:05" + +# before dst is switched on +dt=$(TZ=GMT0BST,M3.5.0/1,M10.5.0/2 busybox date -d '2021-03-28 00:59:59 +0000') +test x"$dt" = x"Sun Mar 28 00:59:59 GMT 2021" + +# after dst is switched on +dt=$(TZ=GMT0BST,M3.5.0/1,M10.5.0/2 busybox date -d '2021-03-28 01:00:01 +0000') +test x"$dt" = x"Sun Mar 28 02:00:01 BST 2021" + +# before dst is switched off +dt=$(TZ=GMT0BST,M3.5.0/1,M10.5.0/2 busybox date -d '2021-10-31 00:00:01 +0000') +test x"$dt" = x"Sun Oct 31 01:00:01 BST 2021" + +# after dst is switched off: back to 01:00:01 but with different TZ +dt=$(TZ=GMT0BST,M3.5.0/1,M10.5.0/2 busybox date -d '2021-10-31 01:00:01 +0000') +test x"$dt" = x"Sun Oct 31 01:00:01 GMT 2021" -- cgit v1.2.3-55-g6feb