diff --git a/Config.in b/Config.in index ad0cd1e26f..6b925b517d 100644 --- a/Config.in +++ b/Config.in @@ -204,6 +204,13 @@ config FEATURE_INSTALLER busybox at runtime to create hard links or symlinks for all the applets that are compiled into busybox. +config FEATURE_VERSION + bool "Support --version" + default y + depends on BUSYBOX + help + Enable 'busybox --version' support. + config INSTALL_NO_USR bool "Don't use /usr" default n diff --git a/Makefile b/Makefile index e67553d5fc..3af11c0e69 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ VERSION = 1 -PATCHLEVEL = 37 +PATCHLEVEL = 38 SUBLEVEL = 0 EXTRAVERSION = .git NAME = Unnamed @@ -476,6 +476,7 @@ libs-y := \ findutils/ \ init/ \ libbb/ \ + libbb/yescrypt/ \ libpwdgrp/ \ loginutils/ \ mailutils/ \ @@ -612,8 +613,12 @@ quiet_cmd_busybox__ ?= LINK $@ "$(core-y)" \ "$(libs-y)" \ "$(LDLIBS)" \ - "$(CONFIG_EXTRA_LDLIBS)" \ + $(CONFIG_EXTRA_LDLIBS) \ && $(srctree)/scripts/generate_BUFSIZ.sh --post include/common_bufsiz.h +# ^^^ note: CONFIG_xyz strings already have double quotes: their value +# is '"LIB LIB2"', therefore $(CONFIG_EXTRA_LDLIBS) above must NOT be written +# as "$(CONFIG_EXTRA_LDLIBS)", it would be passed as ""LIB LIB2"", +# and LIB2 would end up in $9, not $8 (and lost or misinterpreted). # Generate System.map quiet_cmd_sysmap = SYSMAP diff --git a/Makefile.custom b/Makefile.custom index 6f679c4e12..ea5edb1a8a 100644 --- a/Makefile.custom +++ b/Makefile.custom @@ -156,12 +156,12 @@ docs/busybox.pod: $(srctree)/docs/busybox_header.pod \ docs/BusyBox.txt: docs/busybox.pod $(disp_doc) $(Q)-mkdir -p docs - $(Q)-pod2text $< > $@ + $(Q)-pod2text --quotes=none $< > $@ docs/busybox.1: docs/busybox.pod $(disp_doc) $(Q)-mkdir -p docs - $(Q)-pod2man --center=busybox --release="version $(KERNELVERSION)" $< > $@ + $(Q)-pod2man --quotes=none --center=busybox --release="version $(KERNELVERSION)" $< > $@ docs/BusyBox.html: docs/busybox.net/BusyBox.html $(disp_doc) diff --git a/NOFORK_NOEXEC.lst b/NOFORK_NOEXEC.lst index 055f9fb248..a000de45b8 100644 --- a/NOFORK_NOEXEC.lst +++ b/NOFORK_NOEXEC.lst @@ -336,6 +336,7 @@ setuidgid - noexec. spawner sha1sum - noexec. runner sha256sum - noexec. runner sha3sum - noexec. runner +sha384sum - noexec. runner sha512sum - noexec. runner showkey - interactive, longterm shred - runner diff --git a/applets/usage_pod.c b/applets/usage_pod.c index 9e6d3f0ee5..2c177be905 100644 --- a/applets/usage_pod.c +++ b/applets/usage_pod.c @@ -67,30 +67,37 @@ int main(void) } if (col == 0) { col = 6; - printf("\t"); } else { printf(", "); } - printf("%s", usage_array[i].aname); + if (usage_array[i].usage[0] != NOUSAGE_STR[0]) { + /* + * If the applet usage string will be included in the final document + * optimistically link to its header (which is just the applet name). + */ + printf("L|/\"%1$s\">", usage_array[i].aname); + } else { + /* Without a usage string, just output the applet name with no link. */ + printf("C<%s>", usage_array[i].aname); + } col += len2; } printf("\n\n"); printf("=head1 COMMAND DESCRIPTIONS\n\n"); - printf("=over 4\n\n"); for (i = 0; i < num_messages; i++) { if (usage_array[i].aname[0] >= 'a' && usage_array[i].aname[0] <= 'z' && usage_array[i].usage[0] != NOUSAGE_STR[0] ) { - printf("=item B<%s>\n\n", usage_array[i].aname); + /* This is the heading that will be linked from the command list. */ + printf("=head2 %s\n\n", usage_array[i].aname); if (usage_array[i].usage[0]) printf("%s %s\n\n", usage_array[i].aname, usage_array[i].usage); else printf("%s\n\n", usage_array[i].aname); } } - printf("=back\n\n"); return 0; } diff --git a/archival/Config.src b/archival/Config.src index 6f4f30c43d..cbcd7217ce 100644 --- a/archival/Config.src +++ b/archival/Config.src @@ -35,4 +35,15 @@ config FEATURE_LZMA_FAST This option reduces decompression time by about 25% at the cost of a 1K bigger binary. +config FEATURE_PATH_TRAVERSAL_PROTECTION + bool "Prevent extraction of filenames with /../ path component" + default n + help + busybox tar and unzip remove "PREFIX/../" (if it exists) + from extracted names. + This option enables this behavior for all other unpacking applets, + such as cpio, ar, rpm. + GNU cpio 2.15 has NO such sanity check. +# try other archivers and document their behavior? + endmenu diff --git a/archival/bbunzip.c b/archival/bbunzip.c index b7944a62ad..42b4baf886 100644 --- a/archival/bbunzip.c +++ b/archival/bbunzip.c @@ -71,8 +71,8 @@ int FAST_FUNC bbunpack(char **argv, goto err; } else { /* "clever zcat" with FILE */ - /* fail_if_not_compressed because zcat refuses uncompressed input */ - int fd = open_zipped(filename, /*fail_if_not_compressed:*/ 1); + /* die_if_not_compressed because zcat refuses uncompressed input */ + int fd = open_zipped(filename, /*die_if_not_compressed:*/ 1); if (fd < 0) goto err_name; xmove_fd(fd, STDIN_FILENO); @@ -80,7 +80,7 @@ int FAST_FUNC bbunpack(char **argv, } else if (option_mask32 & BBUNPK_SEAMLESS_MAGIC) { /* "clever zcat" on stdin */ - if (setup_unzip_on_fd(STDIN_FILENO, /*fail_if_not_compressed*/ 1)) + if (setup_unzip_on_fd(STDIN_FILENO, /*die_if_not_compressed*/ 1)) goto err; } diff --git a/archival/cpio.c b/archival/cpio.c index f0d9900485..b033b37337 100644 --- a/archival/cpio.c +++ b/archival/cpio.c @@ -350,6 +350,12 @@ static NOINLINE int cpio_o(void) st.st_dev = st.st_rdev = 0; #endif + if (sizeof(st.st_size) > 4 + && st.st_size > (off_t)0xffffffff + ) { + bb_error_msg_and_die("error: file '%s' is larger than 4GB", name); + } + bytes += printf("070701" "%08X%08X%08X%08X%08X%08X%08X" "%08X%08X%08X%08X" /* GNU cpio uses uppercase hex */ @@ -421,6 +427,7 @@ int cpio_main(int argc UNUSED_PARAM, char **argv) #endif #endif "owner\0" Required_argument "R" + "file\0" Required_argument "F" "verbose\0" No_argument "v" "null\0" No_argument "0" "quiet\0" No_argument "\xff" diff --git a/archival/libarchive/data_extract_all.c b/archival/libarchive/data_extract_all.c index 049c2c1563..8a69711c1f 100644 --- a/archival/libarchive/data_extract_all.c +++ b/archival/libarchive/data_extract_all.c @@ -65,6 +65,14 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) } while (--n != 0); } #endif +#if ENABLE_FEATURE_PATH_TRAVERSAL_PROTECTION + /* Strip leading "/" and up to last "/../" path component */ + dst_name = (char *)strip_unsafe_prefix(dst_name); +#endif +// ^^^ This may be a problem if some applets do need to extract absolute names. +// (Probably will need to invent ARCHIVE_ALLOW_UNSAFE_NAME flag). +// You might think that rpm needs it, but in my tests rpm's internal cpio +// archive has names like "./usr/bin/FOO", not "/usr/bin/FOO". if (archive_handle->ah_flags & ARCHIVE_CREATE_LEADING_DIRS) { char *slash = strrchr(dst_name, '/'); diff --git a/archival/libarchive/header_list.c b/archival/libarchive/header_list.c index 0621aa406a..9490b36352 100644 --- a/archival/libarchive/header_list.c +++ b/archival/libarchive/header_list.c @@ -8,5 +8,5 @@ void FAST_FUNC header_list(const file_header_t *file_header) { //TODO: cpio -vp DIR should output "DIR/NAME", not just "NAME" */ - puts(file_header->name); + puts(printable_string(file_header->name)); } diff --git a/archival/libarchive/header_verbose_list.c b/archival/libarchive/header_verbose_list.c index a575a08a02..e7a09430d0 100644 --- a/archival/libarchive/header_verbose_list.c +++ b/archival/libarchive/header_verbose_list.c @@ -57,13 +57,13 @@ void FAST_FUNC header_verbose_list(const file_header_t *file_header) ptm->tm_hour, ptm->tm_min, ptm->tm_sec, - file_header->name); + printable_string(file_header->name)); #endif /* FEATURE_TAR_UNAME_GNAME */ /* NB: GNU tar shows "->" for symlinks and "link to" for hardlinks */ if (file_header->link_target) { - printf(" -> %s", file_header->link_target); + printf(" -> %s", printable_string(file_header->link_target)); } bb_putchar('\n'); } diff --git a/archival/libarchive/open_transformer.c b/archival/libarchive/open_transformer.c index 44715ef252..353f682170 100644 --- a/archival/libarchive/open_transformer.c +++ b/archival/libarchive/open_transformer.c @@ -157,7 +157,7 @@ void FAST_FUNC fork_transformer(int fd, const char *transform_prog) /* Used by e.g. rpm which gives us a fd without filename, * thus we can't guess the format from filename's extension. */ -static transformer_state_t *setup_transformer_on_fd(int fd, int fail_if_not_compressed) +static transformer_state_t *setup_transformer_on_fd(int fd, int die_if_not_compressed) { transformer_state_t *xstate; @@ -204,7 +204,7 @@ static transformer_state_t *setup_transformer_on_fd(int fd, int fail_if_not_comp } /* No known magic seen */ - if (fail_if_not_compressed) + if (die_if_not_compressed) bb_simple_error_msg_and_die("no gzip" IF_FEATURE_SEAMLESS_BZ2("/bzip2") IF_FEATURE_SEAMLESS_XZ("/xz") @@ -240,13 +240,15 @@ static void fork_transformer_and_free(transformer_state_t *xstate) /* Used by e.g. rpm which gives us a fd without filename, * thus we can't guess the format from filename's extension. */ -int FAST_FUNC setup_unzip_on_fd(int fd, int fail_if_not_compressed) +int FAST_FUNC setup_unzip_on_fd(int fd, int die_if_not_compressed) { - transformer_state_t *xstate = setup_transformer_on_fd(fd, fail_if_not_compressed); + transformer_state_t *xstate = setup_transformer_on_fd(fd, die_if_not_compressed); if (!xstate->xformer) { + /* Not compressed */ + int retval = xstate->signature_skipped; /* never zero */ free(xstate); - return 1; + return retval; } fork_transformer_and_free(xstate); @@ -264,7 +266,7 @@ void FAST_FUNC setup_lzma_on_fd(int fd) } #endif -static transformer_state_t *open_transformer(const char *fname, int fail_if_not_compressed) +static transformer_state_t *open_transformer(const char *fname, int die_if_not_compressed) { transformer_state_t *xstate; int fd; @@ -284,18 +286,18 @@ static transformer_state_t *open_transformer(const char *fname, int fail_if_not_ } } - xstate = setup_transformer_on_fd(fd, fail_if_not_compressed); + xstate = setup_transformer_on_fd(fd, die_if_not_compressed); return xstate; } -int FAST_FUNC open_zipped(const char *fname, int fail_if_not_compressed) +int FAST_FUNC open_zipped(const char *fname, int die_if_not_compressed) { int fd; transformer_state_t *xstate; - xstate = open_transformer(fname, fail_if_not_compressed); - if (!xstate) + xstate = open_transformer(fname, die_if_not_compressed); + if (!xstate) /* open error */ return -1; fd = xstate->src_fd; @@ -326,7 +328,7 @@ void* FAST_FUNC xmalloc_open_zipped_read_close(const char *fname, size_t *maxsz_ transformer_state_t *xstate; char *image; - xstate = open_transformer(fname, /*fail_if_not_compressed:*/ 0); + xstate = open_transformer(fname, /*die_if_not_compressed:*/ 0); if (!xstate) /* file open error */ return NULL; @@ -371,7 +373,7 @@ void* FAST_FUNC xmalloc_open_zipped_read_close(const char *fname, size_t *maxsz_ int fd; char *image; - fd = open_zipped(fname, /*fail_if_not_compressed:*/ 0); + fd = open_zipped(fname, /*die_if_not_compressed:*/ 0); if (fd < 0) return NULL; diff --git a/archival/libarchive/unsafe_prefix.c b/archival/libarchive/unsafe_prefix.c index 33e487bf94..667081195f 100644 --- a/archival/libarchive/unsafe_prefix.c +++ b/archival/libarchive/unsafe_prefix.c @@ -14,7 +14,11 @@ const char* FAST_FUNC strip_unsafe_prefix(const char *str) cp++; continue; } - if (is_prefixed_with(cp, "/../"+1)) { + /* We are called lots of times. + * is_prefixed_with(cp, "../") is slower than open-coding it, + * with minimal code growth (~few bytes). + */ + if (cp[0] == '.' && cp[1] == '.' && cp[2] == '/') { cp += 3; continue; } diff --git a/archival/rpm.c b/archival/rpm.c index af8db99a6a..95a8c79b6b 100644 --- a/archival/rpm.c +++ b/archival/rpm.c @@ -316,7 +316,7 @@ static void extract_cpio(int fd, const char *source_rpm) archive_handle->src_fd = fd; /*archive_handle->offset = 0; - init_handle() did it */ - setup_unzip_on_fd(archive_handle->src_fd, /*fail_if_not_compressed:*/ 1); + setup_unzip_on_fd(archive_handle->src_fd, /*die_if_not_compressed:*/ 1); while (get_header_cpio(archive_handle) == EXIT_SUCCESS) continue; } @@ -533,6 +533,7 @@ int rpm2cpio_main(int argc UNUSED_PARAM, char **argv) // /* We need to know whether child (gzip/bzip/etc) exits abnormally */ // signal(SIGCHLD, check_errors_in_children); + str = NULL; if (ENABLE_FEATURE_SEAMLESS_LZMA && (str = rpm_getstr0(TAG_PAYLOADCOMPRESSOR)) != NULL && strcmp(str, "lzma") == 0 @@ -541,7 +542,11 @@ int rpm2cpio_main(int argc UNUSED_PARAM, char **argv) // set up decompressor without detection setup_lzma_on_fd(rpm_fd); } else { - setup_unzip_on_fd(rpm_fd, /*fail_if_not_compressed:*/ 1); + int signature_bytes = setup_unzip_on_fd(rpm_fd, /*die_if_not_compressed:*/ 0); + if (signature_bytes != 0) { + xlseek(rpm_fd, - signature_bytes, SEEK_CUR); + bb_error_msg("warning, unknown compression '%s'", str); + } } if (bb_copyfd_eof(rpm_fd, STDOUT_FILENO) < 0) diff --git a/archival/tar.c b/archival/tar.c index d6ca6c1e0d..38906ba024 100644 --- a/archival/tar.c +++ b/archival/tar.c @@ -1164,7 +1164,7 @@ int tar_main(int argc UNUSED_PARAM, char **argv) * on e.g. tarball with 1st file named "BZh5". */ ) { - tar_handle->src_fd = open_zipped(tar_filename, /*fail_if_not_compressed:*/ 0); + tar_handle->src_fd = open_zipped(tar_filename, /*die_if_not_compressed:*/ 0); if (tar_handle->src_fd < 0) bb_perror_msg_and_die("can't open '%s'", tar_filename); } else { diff --git a/console-tools/showkey.c b/console-tools/showkey.c index 87532a8ac9..5767f2098c 100644 --- a/console-tools/showkey.c +++ b/console-tools/showkey.c @@ -70,8 +70,8 @@ int showkey_main(int argc UNUSED_PARAM, char **argv) INIT_G(); - // FIXME: aks are all mutually exclusive - getopt32(argv, "aks"); + // a,k,s are all mutually exclusive + getopt32(argv, "^" "aks" "\0" "a--ks:k--as:s--ak"); // prepare for raw mode xget1(&tio, &tio0); diff --git a/coreutils/cp.c b/coreutils/cp.c index ee40af50b9..961de5b427 100644 --- a/coreutils/cp.c +++ b/coreutils/cp.c @@ -215,7 +215,8 @@ int cp_main(int argc, char **argv) (flags & FILEUTILS_DEREFERENCE) ? stat : lstat); if (s_flags < 0) /* error other than ENOENT */ return EXIT_FAILURE; - d_flags = cp_mv_stat(last, &dest_stat); + d_flags = cp_mv_stat2(last, &dest_stat, + (flags & FILEUTILS_NO_TARGET_DIR) ? lstat : stat); if (d_flags < 0) /* error other than ENOENT */ return EXIT_FAILURE; diff --git a/coreutils/cut.c b/coreutils/cut.c index d129f9b9dd..d81f36bcd9 100644 --- a/coreutils/cut.c +++ b/coreutils/cut.c @@ -27,21 +27,34 @@ //kbuild:lib-$(CONFIG_CUT) += cut.o //usage:#define cut_trivial_usage -//usage: "[OPTIONS] [FILE]..." +//usage: "{-b|c LIST | -f"IF_FEATURE_CUT_REGEX("|F")" LIST [-d SEP] [-s]} [-D] [-O SEP] [FILE]..." +// --output-delimiter SEP is too long to fit into 80 char-wide help ----------------^^^^^^^^ //usage:#define cut_full_usage "\n\n" //usage: "Print selected fields from FILEs to stdout\n" //usage: "\n -b LIST Output only bytes from LIST" //usage: "\n -c LIST Output only characters from LIST" -//usage: "\n -d SEP Field delimiter for input (default -f TAB, -F run of whitespace)" -//usage: "\n -O SEP Field delimeter for output (default = -d for -f, one space for -F)" -//usage: "\n -D Don't sort/collate sections or match -fF lines without delimeter" +//usage: IF_FEATURE_CUT_REGEX( +//usage: "\n -d SEP Input field delimiter (default -f TAB, -F run of whitespace)" +//usage: ) IF_NOT_FEATURE_CUT_REGEX( +//usage: "\n -d SEP Input field delimiter (default TAB)" +//usage: ) //usage: "\n -f LIST Print only these fields (-d is single char)" //usage: IF_FEATURE_CUT_REGEX( //usage: "\n -F LIST Print only these fields (-d is regex)" //usage: ) -//usage: "\n -s Output only lines containing delimiter" +//usage: "\n -s Drop lines with no delimiter (else print them in full)" +//usage: "\n -D Don't sort ranges; line without delimiters has one field" +//usage: IF_LONG_OPTS( +//usage: "\n --output-delimiter SEP Output field delimeter" +//usage: ) IF_NOT_LONG_OPTS( +//usage: IF_FEATURE_CUT_REGEX( +//usage: "\n -O SEP Output field delimeter (default = -d for -f, one space for -F)" +//usage: ) IF_NOT_FEATURE_CUT_REGEX( +//usage: "\n -O SEP Output field delimeter (default = -d)" +//usage: ) +//usage: ) //usage: "\n -n Ignored" -//(manpage:-n with -b: don't split multibyte characters) +//(manpage:-n with -b: don't split multibyte characters) //usage: //usage:#define cut_example_usage //usage: "$ echo \"Hello world\" | cut -f 1 -d ' '\n" @@ -53,11 +66,6 @@ #if ENABLE_FEATURE_CUT_REGEX #include "xregex.h" -#else -#define regex_t int -typedef struct { int rm_eo, rm_so; } regmatch_t; -#define xregcomp(x, ...) *(x) = 0 -#define regexec(...) 0 #endif /* This is a NOEXEC applet. Be very careful! */ @@ -65,250 +73,346 @@ typedef struct { int rm_eo, rm_so; } regmatch_t; /* option vars */ #define OPT_STR "b:c:f:d:O:sD"IF_FEATURE_CUT_REGEX("F:")"n" -#define CUT_OPT_BYTE_FLGS (1 << 0) -#define CUT_OPT_CHAR_FLGS (1 << 1) -#define CUT_OPT_FIELDS_FLGS (1 << 2) -#define CUT_OPT_DELIM_FLGS (1 << 3) -#define CUT_OPT_ODELIM_FLGS (1 << 4) -#define CUT_OPT_SUPPRESS_FLGS (1 << 5) -#define CUT_OPT_NOSORT_FLGS (1 << 6) -#define CUT_OPT_REGEX_FLGS ((1 << 7) * ENABLE_FEATURE_CUT_REGEX) - -struct cut_list { - int startpos; - int endpos; +#define OPT_BYTE (1 << 0) +#define OPT_CHAR (1 << 1) +#define OPT_FIELDS (1 << 2) +#define OPT_DELIM (1 << 3) +#define OPT_ODELIM (1 << 4) +#define OPT_SUPPRESS (1 << 5) +#define OPT_NOSORT (1 << 6) +#define OPT_REGEX ((1 << 7) * ENABLE_FEATURE_CUT_REGEX) + +#define opt_REGEX (option_mask32 & OPT_REGEX) + +struct cut_range { + unsigned startpos; + unsigned endpos; }; static int cmpfunc(const void *a, const void *b) { - return (((struct cut_list *) a)->startpos - - ((struct cut_list *) b)->startpos); + const struct cut_range *aa = a; + const struct cut_range *bb = b; + return aa->startpos - bb->startpos; } +#define END_OF_LIST(list_elem) ((list_elem).startpos == UINT_MAX) +#define NOT_END_OF_LIST(list_elem) ((list_elem).startpos != UINT_MAX) + static void cut_file(FILE *file, const char *delim, const char *odelim, - const struct cut_list *cut_lists, unsigned nlists) + const struct cut_range *cut_list) { char *line; unsigned linenum = 0; /* keep these zero-based to be consistent */ - regex_t reg; - int spos, shoe = option_mask32 & CUT_OPT_REGEX_FLGS; - - if (shoe) xregcomp(®, delim, REG_EXTENDED); + int first_print = 1; /* go through every line in the file */ while ((line = xmalloc_fgetline(file)) != NULL) { /* set up a list so we can keep track of what's been printed */ - int linelen = strlen(line); - char *printed = xzalloc(linelen + 1); - char *orig_line = line; + unsigned linelen = strlen(line); unsigned cl_pos = 0; - /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */ - if (option_mask32 & (CUT_OPT_CHAR_FLGS | CUT_OPT_BYTE_FLGS)) { + /* Cut based on chars/bytes XXX: only works when sizeof(char) == byte */ + if (option_mask32 & (OPT_CHAR | OPT_BYTE)) { + char *printed = xzalloc(linelen + 1); + int need_odelim = 0; + /* print the chars specified in each cut list */ - for (; cl_pos < nlists; cl_pos++) { - for (spos = cut_lists[cl_pos].startpos; spos < linelen;) { + for (; NOT_END_OF_LIST(cut_list[cl_pos]); cl_pos++) { + unsigned spos = cut_list[cl_pos].startpos; + while (spos < linelen) { if (!printed[spos]) { printed[spos] = 'X'; + if (need_odelim && spos != 0 && !printed[spos-1]) { + need_odelim = 0; + fputs_stdout(odelim); + } putchar(line[spos]); } - if (++spos > cut_lists[cl_pos].endpos) { + spos++; + if (spos > cut_list[cl_pos].endpos) { + /* will print OSEP (if not empty) */ + need_odelim = (odelim && odelim[0]); break; } } } - } else if (*delim == '\n') { /* cut by lines */ - spos = cut_lists[cl_pos].startpos; + free(printed); + /* Cut by lines */ + } else if (!opt_REGEX && *delim == '\n') { + unsigned spos = cut_list[cl_pos].startpos; - /* get out if we have no more lists to process or if the lines + linenum++; + /* get out if we have no more ranges to process or if the lines * are lower than what we're interested in */ - if (((int)linenum < spos) || (cl_pos >= nlists)) + if (linenum <= spos || END_OF_LIST(cut_list[cl_pos])) goto next_line; /* if the line we're looking for is lower than the one we were * passed, it means we displayed it already, so move on */ - while (spos < (int)linenum) { - spos++; + while (++spos < linenum) { /* go to the next list if we're at the end of this one */ - if (spos > cut_lists[cl_pos].endpos) { + if (spos > cut_list[cl_pos].endpos) { cl_pos++; - /* get out if there's no more lists to process */ - if (cl_pos >= nlists) + /* get out if there's no more ranges to process */ + if (END_OF_LIST(cut_list[cl_pos])) goto next_line; - spos = cut_lists[cl_pos].startpos; + spos = cut_list[cl_pos].startpos; /* get out if the current line is lower than the one * we just became interested in */ - if ((int)linenum < spos) + if (linenum <= spos) goto next_line; } } /* If we made it here, it means we've found the line we're * looking for, so print it */ - puts(line); + if (first_print) { + first_print = 0; + fputs_stdout(line); + } else + printf("%s%s", odelim, line); goto next_line; - } else { /* cut by fields */ - unsigned uu = 0, start = 0, end = 0, out = 0; - int dcount = 0; + /* Cut by fields */ + } else { + unsigned next = 0, start = 0, end = 0; + unsigned dcount = 0; /* we saw Nth delimiter (0 - didn't see any yet) */ + + /* Blank line? Check -s (later check for -s does not catch empty lines) */ + if (linelen == 0) { + if (option_mask32 & OPT_SUPPRESS) + goto next_line; + } + + if (!odelim) + odelim = "\t"; + first_print = 1; /* Loop through bytes, finding next delimiter */ for (;;) { /* End of current range? */ - if (end == linelen || dcount > cut_lists[cl_pos].endpos) { - if (++cl_pos >= nlists) break; - if (option_mask32 & CUT_OPT_NOSORT_FLGS) - start = dcount = uu = 0; - end = 0; + if (end == linelen || dcount > cut_list[cl_pos].endpos) { + end_of_range: + cl_pos++; + if (END_OF_LIST(cut_list[cl_pos])) + break; + if (option_mask32 & OPT_NOSORT) + start = dcount = next = 0; + end = 0; /* (why?) */ + //bb_error_msg("End of current range"); } /* End of current line? */ - if (uu == linelen) { - /* If we've seen no delimiters, check -s */ - if (!cl_pos && !dcount && !shoe) { - if (option_mask32 & CUT_OPT_SUPPRESS_FLGS) + if (next == linelen) { + end = linelen; /* print up to end */ + /* If we've seen no delimiters, and no -D, check -s */ + if (!(option_mask32 & OPT_NOSORT) && cl_pos == 0 && dcount == 0) { + if (option_mask32 & OPT_SUPPRESS) goto next_line; - } else if (dcount < cut_lists[cl_pos].startpos) - start = linelen; - end = linelen; + /* else: will print entire line */ + } else if (dcount < cut_list[cl_pos].startpos) { + /* echo 1.2 | cut -d. -f1,3: prints "1", not "1." */ + //break; + /* ^^^ this fails a case with -D: + * echo 1 2 | cut -DF 1,3,2: + * do not end line processing when didn't find field#3 + */ + //if (option_mask32 & OPT_NOSORT) - no, just do it always + goto end_of_range; + } + //bb_error_msg("End of current line: s:%d e:%d", start, end); } else { /* Find next delimiter */ - if (shoe) { - regmatch_t rr = {-1, -1}; - - if (!regexec(®, line+uu, 1, &rr, REG_NOTBOL|REG_NOTEOL)) { - end = uu + rr.rm_so; - uu += rr.rm_eo; - } else { - uu = linelen; +#if ENABLE_FEATURE_CUT_REGEX + if (opt_REGEX) { + regmatch_t rr; + regex_t *reg = (void*) delim; + + if (regexec(reg, line + next, 1, &rr, REG_NOTBOL|REG_NOTEOL) != 0) { + /* not found, go to "end of line" logic */ + next = linelen; continue; } - } else if (line[end = uu++] != *delim) - continue; - - /* Got delimiter. Loop if not yet within range. */ - if (dcount++ < cut_lists[cl_pos].startpos) { - start = uu; + end = next + rr.rm_so; + next += (rr.rm_eo ? rr.rm_eo : 1); + /* ^^^ advancing by at least 1 prevents infinite loops */ + /* testcase: echo "no at sign" | cut -d'@*' -F 1- */ + } else +#endif + { + end = next++; + if (line[end] != *delim) + continue; + } + /* Got delimiter */ + dcount++; + if (dcount <= cut_list[cl_pos].startpos) { + /* Not yet within range - loop */ + start = next; continue; } + /* -F N-M preserves intermediate delimiters: */ + //printf "1 2 3 4 5 6 7\n" | toybox cut -O: -F2,4-6,7 + //2:4 5 6:7 + if (opt_REGEX && dcount <= cut_list[cl_pos].endpos) + continue; +// NB: toybox does the above for -f too, but it's a compatibility bug: +//printf "1 2 3 4 5 6 7 8\n" | toybox cut -d' ' -O: -f2,4-6,7 +//2:4 5 6:7 // WRONG! +//printf "1 2 3 4 5 6 7 8\n" | cut -d' ' --output-delimiter=: -f2,4-6,7 +//2:4:5:6:7 // GNU coreutils 9.1 } - if (end != start || !shoe) - printf("%s%.*s", out++ ? odelim : "", end-start, line + start); - start = uu; - if (!dcount) - break; - } +#if ENABLE_FEATURE_CUT_REGEX + if (end != start || !opt_REGEX) +#endif + { + if (first_print) { + first_print = 0; + printf("%.*s", end - start, line + start); + } else + printf("%s%.*s", odelim, end - start, line + start); + } + start = next; + //if (dcount == 0) + // break; - why? + } /* byte loop */ } /* if we printed anything, finish with newline */ putchar('\n'); next_line: - linenum++; - free(printed); - free(orig_line); - } + free(line); + } /* while (got line) */ + + /* For -d$'\n' --output-delimiter=^, the overall output is still terminated with \n, not ^ */ + if (!opt_REGEX && *delim == '\n' && !first_print) + putchar('\n'); } int cut_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int cut_main(int argc UNUSED_PARAM, char **argv) { - /* growable array holding a series of lists */ - struct cut_list *cut_lists = NULL; - unsigned nlists = 0; /* number of elements in above list */ - char *sopt, *ltok; + /* growable array holding a series of ranges */ + struct cut_range *cut_list = NULL; + unsigned nranges = 0; /* number of elements in above list */ + char *LIST, *ltok; const char *delim = NULL; const char *odelim = NULL; unsigned opt; +#if ENABLE_FEATURE_CUT_REGEX + regex_t reg; +#endif +#if ENABLE_LONG_OPTS + static const char cut_longopts[] ALIGN1 = + "output-delimiter\0" Required_argument "O" + ; +#endif #define ARG "bcf"IF_FEATURE_CUT_REGEX("F") - opt = getopt32(argv, "^" +#if ENABLE_LONG_OPTS + opt = getopt32long +#else + opt = getopt32 +#endif + (argv, "^" OPT_STR // = "b:c:f:d:O:sD"IF_FEATURE_CUT_REGEX("F:")"n" - "\0" "b--"ARG":c--"ARG":f--"ARG IF_FEATURE_CUT_REGEX("F--"ARG), - &sopt, &sopt, &sopt, &delim, &odelim IF_FEATURE_CUT_REGEX(, &sopt) - ); - if (!delim || !*delim) - delim = (opt & CUT_OPT_REGEX_FLGS) ? "[[:space:]]+" : "\t"; - if (!odelim) odelim = (opt & CUT_OPT_REGEX_FLGS) ? " " : delim; + "\0" "b:c:f:" IF_FEATURE_CUT_REGEX("F:") /* one of -bcfF is required */ + "b--"ARG":c--"ARG":f--"ARG IF_FEATURE_CUT_REGEX(":F--"ARG), /* they are mutually exclusive */ + IF_LONG_OPTS(cut_longopts,) + &LIST, &LIST, &LIST, &delim, &odelim IF_FEATURE_CUT_REGEX(, &LIST) + ); + if (!odelim) + odelim = (opt & OPT_REGEX) ? " " : delim; + if (!delim) + delim = (opt & OPT_REGEX) ? "[[:space:]]+" : "\t"; // argc -= optind; argv += optind; - if (!(opt & (CUT_OPT_BYTE_FLGS | CUT_OPT_CHAR_FLGS | CUT_OPT_FIELDS_FLGS | CUT_OPT_REGEX_FLGS))) - bb_simple_error_msg_and_die("expected a list of bytes, characters, or fields"); - - /* non-field (char or byte) cutting has some special handling */ - if (!(opt & (CUT_OPT_FIELDS_FLGS|CUT_OPT_REGEX_FLGS))) { - static const char _op_on_field[] ALIGN1 = " only when operating on fields"; - - if (opt & CUT_OPT_SUPPRESS_FLGS) { - bb_error_msg_and_die - ("suppressing non-delimited lines makes sense%s", _op_on_field); - } - if (opt & CUT_OPT_DELIM_FLGS) { - bb_error_msg_and_die - ("a delimiter may be specified%s", _op_on_field); - } + //if (!(opt & (OPT_BYTE | OPT_CHAR | OPT_FIELDS | OPT_REGEX))) + // bb_simple_error_msg_and_die("expected a list of bytes, characters, or fields"); + //^^^ handled by getopt32 + + /* non-field (char or byte) cutting has some special handling */ + if (!(opt & (OPT_FIELDS|OPT_REGEX))) { + static const char requires_f[] ALIGN1 = " requires -f" + IF_FEATURE_CUT_REGEX(" or -F"); + if (opt & OPT_SUPPRESS) + bb_error_msg_and_die("-s%s", requires_f); + if (opt & OPT_DELIM) + bb_error_msg_and_die("-d DELIM%s", requires_f); } /* * parse list and put values into startpos and endpos. - * valid list formats: N, N-, N-M, -M - * more than one list can be separated by commas + * valid range formats: N, N-, N-M, -M + * more than one range can be separated by commas */ - { + /* take apart the ranges, one by one (separated with commas) */ + while ((ltok = strsep(&LIST, ",")) != NULL) { char *ntok; - int s = 0, e = 0; - - /* take apart the lists, one by one (they are separated with commas) */ - while ((ltok = strsep(&sopt, ",")) != NULL) { + int s, e; + + /* it's actually legal to pass an empty list */ + //if (!ltok[0]) + // continue; + //^^^ testcase? + + /* get the start pos */ + ntok = strsep(<ok, "-"); + if (!ntok[0]) { + if (!ltok) /* testcase: -f '' */ + bb_show_usage(); + if (!ltok[0]) /* testcase: -f - */ + bb_show_usage(); + s = 0; /* "-M" means "1-M" */ + } else { + /* "N" or "N-[M]" */ + /* arrays are zero based, while the user expects + * the first field/char on the line to be char #1 */ + s = xatoi_positive(ntok) - 1; + } - /* it's actually legal to pass an empty list */ - if (!ltok[0]) - continue; + /* get the end pos */ + if (!ltok) { + e = s; /* "N" means "N-N" */ + } else if (!ltok[0]) { + /* "N-" means "until the end of the line" */ + e = INT_MAX; + } else { + /* again, arrays are zero based, fields are 1 based */ + e = xatoi_positive(ltok) - 1; + } - /* get the start pos */ - ntok = strsep(<ok, "-"); - if (!ntok[0]) { - s = 0; - } else { - s = xatoi_positive(ntok); - /* account for the fact that arrays are zero based, while - * the user expects the first char on the line to be char #1 */ - if (s != 0) - s--; - } + if (s < 0 || e < s) + bb_error_msg_and_die("invalid range %s-%s", ntok, ltok ?: ntok); - /* get the end pos */ - if (ltok == NULL) { - e = s; - } else if (!ltok[0]) { - e = INT_MAX; - } else { - e = xatoi_positive(ltok); - /* if the user specified and end position of 0, - * that means "til the end of the line" */ - if (!*ltok) - e = INT_MAX; - else if (e < s) - bb_error_msg_and_die("%d<%d", e, s); - e--; /* again, arrays are zero based, lines are 1 based */ - } + /* add the new range */ + cut_list = xrealloc_vector(cut_list, 4, nranges); + /* NB: s is always >= 0 */ + cut_list[nranges].startpos = s; + cut_list[nranges].endpos = e; + nranges++; + } + cut_list[nranges].startpos = UINT_MAX; /* end indicator */ - /* add the new list */ - cut_lists = xrealloc_vector(cut_lists, 4, nlists); - /* NB: startpos is always >= 0 */ - cut_lists[nlists].startpos = s; - cut_lists[nlists].endpos = e; - nlists++; - } + /* make sure we got some cut positions out of all that */ + //if (nranges == 0) + // bb_simple_error_msg_and_die("missing list of positions"); + //^^^ this is impossible since one of -bcfF is required, + // they populate LIST with non-NULL string and when it is parsed, + // cut_list[] gets at least one element. - /* make sure we got some cut positions out of all that */ - if (nlists == 0) - bb_simple_error_msg_and_die("missing list of positions"); + /* now that the lists are parsed, we need to sort them to make life + * easier on us when it comes time to print the chars / fields / lines + */ + if (!(opt & OPT_NOSORT)) + qsort(cut_list, nranges, sizeof(cut_list[0]), cmpfunc); - /* now that the lists are parsed, we need to sort them to make life - * easier on us when it comes time to print the chars / fields / lines - */ - if (!(opt & CUT_OPT_NOSORT_FLGS)) - qsort(cut_lists, nlists, sizeof(cut_lists[0]), cmpfunc); +#if ENABLE_FEATURE_CUT_REGEX + if (opt & OPT_REGEX) { + xregcomp(®, delim, REG_EXTENDED); + delim = (void*) ® } +#endif { exitcode_t retval = EXIT_SUCCESS; @@ -322,12 +426,12 @@ int cut_main(int argc UNUSED_PARAM, char **argv) retval = EXIT_FAILURE; continue; } - cut_file(file, delim, odelim, cut_lists, nlists); + cut_file(file, delim, odelim, cut_list); fclose_if_not_stdin(file); } while (*++argv); if (ENABLE_FEATURE_CLEAN_UP) - free(cut_lists); + free(cut_list); fflush_stdout_and_exit(retval); } } diff --git a/coreutils/date.c b/coreutils/date.c index 3a89b6caf0..ef482af1b2 100644 --- a/coreutils/date.c +++ b/coreutils/date.c @@ -289,7 +289,7 @@ int date_main(int argc UNUSED_PARAM, char **argv) /* if setting time, set it */ if ((opt & OPT_SET) && clock_settime(CLOCK_REALTIME, &ts) < 0) { - bb_simple_perror_msg("can't set date"); + bb_simple_perror_msg_and_die("can't set date"); } } diff --git a/coreutils/dd.c b/coreutils/dd.c index 8bb7827814..7a64c35132 100644 --- a/coreutils/dd.c +++ b/coreutils/dd.c @@ -272,12 +272,6 @@ static bool write_and_stats(const void *buf, size_t len, size_t obs, return 1; } -#if ENABLE_LFS -# define XATOU_SFX xatoull_sfx -#else -# define XATOU_SFX xatoul_sfx -#endif - #if ENABLE_FEATURE_DD_IBS_OBS static int parse_comma_flags(char *val, const char *words, const char *error_in) { @@ -457,15 +451,15 @@ int dd_main(int argc UNUSED_PARAM, char **argv) /* These can be large: */ if (what == OP_count) { G.flags |= FLAG_COUNT; - count = XATOU_SFX(val, cwbkMG_suffixes); + count = XATOOFF_SFX(val, cwbkMG_suffixes); /*continue;*/ } if (what == OP_seek) { - seek = XATOU_SFX(val, cwbkMG_suffixes); + seek = XATOOFF_SFX(val, cwbkMG_suffixes); /*continue;*/ } if (what == OP_skip) { - skip = XATOU_SFX(val, cwbkMG_suffixes); + skip = XATOOFF_SFX(val, cwbkMG_suffixes); /*continue;*/ } if (what == OP_if) { diff --git a/coreutils/df.c b/coreutils/df.c index 03aa781485..fba23246fe 100644 --- a/coreutils/df.c +++ b/coreutils/df.c @@ -155,6 +155,9 @@ int df_main(int argc UNUSED_PARAM, char **argv) , &opt_t IF_FEATURE_DF_FANCY(, &chp) ); + /* -k overrides $POSIXLY_CORRECT: */ + if (opt & OPT_KILO) + df_disp_hr = 1024; if (opt & OPT_MEGA) df_disp_hr = 1024*1024; @@ -185,8 +188,8 @@ int df_main(int argc UNUSED_PARAM, char **argv) if (disp_units_hdr == NULL) { #if ENABLE_FEATURE_HUMAN_READABLE disp_units_hdr = xasprintf("%s-blocks", - /* print df_disp_hr, show no fractionals, - * use suffixes if OPT_POSIX is set in opt */ + /* print df_disp_hr; show no fractionals; + * if -P, unit=1 (print it in full, no KMG suffixes) */ make_human_readable_str(df_disp_hr, 0, !!(opt & OPT_POSIX)) ); #else diff --git a/coreutils/ls.c b/coreutils/ls.c index cc809b7979..40017efc63 100644 --- a/coreutils/ls.c +++ b/coreutils/ls.c @@ -126,6 +126,8 @@ //usage: "\n -F Append indicator (one of */=@|) to names" //usage: ) //usage: "\n -l Long format" +////usage: "\n -g Long format without group column" +////TODO: support -G too ("suppress owner column", GNUism) //usage: "\n -i List inode numbers" //usage: "\n -n List numeric UIDs and GIDs instead of names" //usage: "\n -s List allocated blocks" @@ -159,6 +161,8 @@ //usage: IF_FEATURE_LS_WIDTH( //usage: "\n -w N Format N columns wide" //usage: ) +////usage: "\n -Q Double-quote names" +////usage: "\n -q Replace unprintable chars with '?'" //usage: IF_FEATURE_LS_COLOR( //usage: "\n --color[={always,never,auto}]" //usage: ) @@ -196,27 +200,47 @@ SPLIT_SUBDIR = 2, /* -Cadi1l Std options, busybox always supports */ /* -gnsxA Std options, busybox always supports */ -/* -Q GNU option, busybox always supports */ -/* -k Std option, busybox always supports (by ignoring) */ -/* It means "for -s, show sizes in kbytes" */ -/* Seems to only affect "POSIXLY_CORRECT=1 ls -sk" */ -/* since otherwise -s shows kbytes anyway */ +/* -Q GNU option, busybox always supports: */ +/* -Q, --quote-name */ +/* enclose entry names in double quotes */ /* -LHRctur Std options, busybox optionally supports */ /* -Fp Std options, busybox optionally supports */ /* -SXvhTw GNU options, busybox optionally supports */ /* -T WIDTH Ignored (we don't use tabs on output) */ /* -Z SELinux mandated option, busybox optionally supports */ +/* -q Std option, busybox always supports: */ +/* https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html: */ +/* Force each instance of non-printable filename characters and */ +/* characters to be written as the ('?') */ +/* character. Implementations may provide this option by default */ +/* if the output is to a terminal device. */ +/* -k Std option, busybox always supports (by ignoring) */ +/* It means "for -s, show sizes in kbytes" */ +/* Seems to only affect "POSIXLY_CORRECT=1 ls -sk" */ +/* since otherwise -s shows kbytes anyway */ #define ls_options \ - "Cadi1lgnsxAk" /* 12 opts, total 12 */ \ - IF_FEATURE_LS_FILETYPES("Fp") /* 2, 14 */ \ - IF_FEATURE_LS_RECURSIVE("R") /* 1, 15 */ \ - IF_SELINUX("Z") /* 1, 16 */ \ - "Q" /* 1, 17 */ \ - IF_FEATURE_LS_TIMESTAMPS("ctu") /* 3, 20 */ \ - IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 24 */ \ - IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 26 */ \ - IF_FEATURE_HUMAN_READABLE("h") /* 1, 27 */ \ - IF_FEATURE_LS_WIDTH("T:w:") /* 2, 29 */ + "Cadi1lgnsxA" /* 11 opts, total 11 */ \ + IF_FEATURE_LS_FILETYPES("Fp") /* 2, 13 */ \ + IF_FEATURE_LS_RECURSIVE("R") /* 1, 14 */ \ + IF_SELINUX("Z") /* 1, 15 */ \ + "Q" /* 1, 16 */ \ + IF_FEATURE_LS_TIMESTAMPS("ctu") /* 3, 19 */ \ + IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 23 */ \ + IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 25 */ \ + IF_FEATURE_HUMAN_READABLE("h") /* 1, 26 */ \ + IF_FEATURE_LS_WIDTH("T:w:") /* 2, 28 */ \ + IF_LONG_OPTS("\xff") /* 1, 29 */ \ + IF_LONG_OPTS("\xfe") /* 1, 30 */ \ + IF_LONG_OPTS("\xfd::") /* 1, 31 */ \ + "qk" /* 2, 33 */ + +#if ENABLE_LONG_OPTS +static const char ls_longopts[] ALIGN1 = + "full-time\0" No_argument "\xff" + "group-directories-first\0" No_argument "\xfe" + IF_FEATURE_LS_COLOR("color\0" Optional_argument "\xfd") +; +#endif enum { OPT_C = (1 << 0), @@ -230,29 +254,31 @@ enum { OPT_s = (1 << 8), OPT_x = (1 << 9), OPT_A = (1 << 10), - //OPT_k = (1 << 11), - OPTBIT_F = 12, - OPTBIT_p, /* 13 */ + OPTBIT_F = 11, + OPTBIT_p, /* 12 */ OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES, OPTBIT_Z = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE, OPTBIT_Q = OPTBIT_Z + 1 * ENABLE_SELINUX, - OPTBIT_c, /* 17 */ - OPTBIT_t, /* 18 */ - OPTBIT_u, /* 19 */ + OPTBIT_c, /* 16 */ + OPTBIT_t, /* 17 */ + OPTBIT_u, /* 18 */ OPTBIT_S = OPTBIT_c + 3 * ENABLE_FEATURE_LS_TIMESTAMPS, - OPTBIT_X, /* 21 */ - OPTBIT_r, /* 22 */ - OPTBIT_v, /* 23 */ + OPTBIT_X, /* 20 */ + OPTBIT_r, /* 21 */ + OPTBIT_v, /* 22 */ OPTBIT_L = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES, - OPTBIT_H, /* 25 */ + OPTBIT_H, /* 24 */ OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS, OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE, - OPTBIT_w, /* 28 */ + OPTBIT_w, /* 27 */ OPTBIT_full_time = OPTBIT_T + 2 * ENABLE_FEATURE_LS_WIDTH, OPTBIT_dirs_first, - OPTBIT_color, /* 31 */ - /* with long opts, we use all 32 bits */ + OPTBIT_color, /* 30 */ + OPTBIT_q = OPTBIT_color + 1, /* 31 */ + OPTBIT_k = OPTBIT_q + 1, /* 32 */ + /* with all options enabled, we use all 32 bits and even one extra bit! */ + /* this works because -k is ignored, and getopt32 allows such "ignore" options past 31th bit */ OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES, OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES, @@ -274,6 +300,8 @@ enum { OPT_full_time = (1 << OPTBIT_full_time ) * ENABLE_LONG_OPTS, OPT_dirs_first = (1 << OPTBIT_dirs_first) * ENABLE_LONG_OPTS, OPT_color = (1 << OPTBIT_color ) * ENABLE_FEATURE_LS_COLOR, + OPT_q = (1 << OPTBIT_q), + //-k is ignored: OPT_k = (1 << OPTBIT_k), }; /* @@ -327,6 +355,7 @@ struct globals { #endif smallint exit_code; smallint show_dirname; + smallint tty_out; #if ENABLE_FEATURE_LS_WIDTH unsigned terminal_width; # define G_terminal_width (G.terminal_width) @@ -343,16 +372,21 @@ struct globals { setup_common_bufsiz(); \ /* we have to zero it out because of NOEXEC */ \ memset(&G, 0, sizeof(G)); \ - IF_FEATURE_LS_WIDTH(G_terminal_width = TERMINAL_WIDTH;) \ + IF_FEATURE_LS_WIDTH(G_terminal_width = ~0U;) \ IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \ } while (0) #define ESC "\033" +static int G_isatty(void) +{ + if (!G.tty_out) /* not known yet? */ + G.tty_out = isatty(STDOUT_FILENO) + 1; + return (G.tty_out == 2); +} /*** Output code ***/ - /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file * 3/7:multiplexed char/block device) @@ -415,56 +449,93 @@ static char append_char(mode_t mode) } #endif +/* Return the number of used columns. + * Note that only columnar output uses return value. + * -l and -1 modes don't care. + * coreutils 7.2 also supports: + * ls -b (--escape) = octal escapes (although it doesn't look like working) + * ls -N (--literal) = not escape at all + */ static unsigned calc_name_len(const char *name) { unsigned len; uni_stat_t uni_stat; - // TODO: quote tab as \t, etc, if -Q - name = printable_string2(&uni_stat, name); + if (!(option_mask32 & (OPT_q|OPT_Q))) + return strlen(name); if (!(option_mask32 & OPT_Q)) { + /* the most likely branch: "ls" to tty (it auto-enables -q behavior) */ + printable_string2(&uni_stat, name); return uni_stat.unicode_width; } - len = 2 + uni_stat.unicode_width; + len = 2 + strlen(name); while (*name) { + unsigned char ch = (unsigned char)*name; + if (ch < ' ' || ch > 0x7e) { + ch -= 7; + if (ch <= 6) { + /* quote chars 7..13 as \a,b,t,n,v,f,r */ + goto two; + } + /* other chars <32 or >126 as \ooo octal */ + len += 3; + goto next; + } if (*name == '"' || *name == '\\') { + two: len++; } + next: name++; } return len; } - -/* Return the number of used columns. - * Note that only columnar output uses return value. - * -l and -1 modes don't care. - * coreutils 7.2 also supports: - * ls -b (--escape) = octal escapes (although it doesn't look like working) - * ls -N (--literal) = not escape at all - */ static unsigned print_name(const char *name) { unsigned len; uni_stat_t uni_stat; - // TODO: quote tab as \t, etc, if -Q - name = printable_string2(&uni_stat, name); + if (!(option_mask32 & (OPT_q|OPT_Q))) { + fputs_stdout(name); + return strlen(name); + } if (!(option_mask32 & OPT_Q)) { + /* the most likely branch: "ls" to tty (it auto-enables -q behavior) */ + name = printable_string2(&uni_stat, name); fputs_stdout(name); return uni_stat.unicode_width; } - len = 2 + uni_stat.unicode_width; + len = 2 + strlen(name); putchar('"'); while (*name) { - if (*name == '"' || *name == '\\') { + unsigned char ch = (unsigned char)*name; + if (ch < ' ' || ch > 0x7e) { putchar('\\'); + ch -= 7; + if (ch <= 6) { + /* quote chars 7..13 as \a,b,t,n,v,f,r */ + ch = c_escape_conv_str07[1 + 3 * ch]; + goto two; + } + /* other chars <32 or >126 as \ooo octal */ + ch = (unsigned char)*name; + putchar('0' + (ch>>6)); + putchar('0' + ((ch>>3) & 7)); + ch = '0' + (ch & 7); + len += 3; + goto put_ch; + } + if (ch == '"' || ch == '\\') { + putchar('\\'); + two: len++; } - putchar(*name); + put_ch: + putchar(ch); name++; } putchar('"'); @@ -646,7 +717,7 @@ static void display_files(struct dnode **dn, unsigned nfiles) unsigned i, ncols, nrows, row, nc; unsigned column; unsigned nexttab; - unsigned column_width = 0; /* used only by coulmnal output */ + unsigned column_width = 0; /* used only by columnar output */ if (option_mask32 & (OPT_l|OPT_1)) { ncols = 1; @@ -691,6 +762,11 @@ static void display_files(struct dnode **dn, unsigned nfiles) } nexttab = column + column_width; column += display_single(dn[i]); + } else { + /* if -w999999999, ncols can be very large */ + //bb_error_msg(" col:%u ncol:%u i:%i", nc, ncols, i); sleep1(); + /* without "break", we loop millions of times here */ + break; } } putchar('\n'); @@ -1090,25 +1166,11 @@ int ls_main(int argc UNUSED_PARAM, char **argv) /* need to initialize since --color has _an optional_ argument */ const char *color_opt = color_str; /* "always" */ #endif -#if ENABLE_LONG_OPTS - static const char ls_longopts[] ALIGN1 = - "full-time\0" No_argument "\xff" - "group-directories-first\0" No_argument "\xfe" - IF_FEATURE_LS_COLOR("color\0" Optional_argument "\xfd") - ; -#endif INIT_G(); init_unicode(); -#if ENABLE_FEATURE_LS_WIDTH - /* obtain the terminal width */ - G_terminal_width = get_terminal_width(STDIN_FILENO); - /* go one less... */ - G_terminal_width--; -#endif - /* process options */ opt = getopt32long(argv, "^" ls_options @@ -1144,6 +1206,29 @@ int ls_main(int argc UNUSED_PARAM, char **argv) exit(0); #endif + /* ftpd secret backdoor? */ + if (ENABLE_FTPD && applet_name[0] == 'f') { + /* dirs first are much nicer */ + opt = option_mask32 |= OPT_dirs_first; + /* don't show SEcontext */ + IF_SELINUX(opt = option_mask32 &= ~OPT_Z;) + /* do not query stdout about size and tty-ness */ + IF_FEATURE_LS_WIDTH(G_terminal_width = INT_MAX;) + G.tty_out = 1; /* not a tty */ + goto skip_if_ftpd; + } + +#if ENABLE_FEATURE_LS_WIDTH + if ((int)G_terminal_width < 0) { + /* obtain the terminal width */ + G_terminal_width = get_terminal_width(STDIN_FILENO); + /* go one less... */ + G_terminal_width--; + } + if (G_terminal_width == 0) /* -w0 */ + G_terminal_width = INT_MAX; /* "infinite" */ +#endif + #if ENABLE_SELINUX if (opt & OPT_Z) { if (!is_selinux_enabled()) @@ -1157,7 +1242,7 @@ int ls_main(int argc UNUSED_PARAM, char **argv) char *p = getenv("LS_COLORS"); /* LS_COLORS is unset, or (not empty && not "none") ? */ if (!p || (p[0] && strcmp(p, "none") != 0)) { - if (isatty(STDOUT_FILENO)) { + if (G_isatty()) { /* check isatty() last because it's expensive (syscall) */ G_show_color = 1; } @@ -1166,23 +1251,28 @@ int ls_main(int argc UNUSED_PARAM, char **argv) if (opt & OPT_color) { if (color_opt[0] == 'n') G_show_color = 0; - else switch (index_in_substrings(color_str, color_opt)) { - case 3: - case 4: - case 5: - if (!is_TERM_dumb() && isatty(STDOUT_FILENO)) { - case 0: - case 1: - case 2: - G_show_color = 1; + else if (!G_show_color) { + /* if() is not needed, but avoids extra isatty() if G_show_color is already set */ + /* Check --color=COLOR_OPT and maybe set show_color=1 */ + switch (index_in_substrings(color_str, color_opt)) { + case 3: // auto + case 4: // tty + case 5: // if-tty + if (!is_TERM_dumb() && G_isatty()) { + case 0: // always + case 1: // yes + case 2: // force + G_show_color = 1; + } } } } #endif + skip_if_ftpd: /* sort out which command line options take precedence */ if (ENABLE_FEATURE_LS_RECURSIVE && (opt & OPT_d)) - option_mask32 &= ~OPT_R; /* no recurse if listing only dir */ + opt = option_mask32 &= ~OPT_R; /* no recurse if listing only dir */ if (!(opt & OPT_l)) { /* not -l? */ if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) { /* when to sort by time? -t[cu] sorts by time even with -l */ @@ -1190,19 +1280,17 @@ int ls_main(int argc UNUSED_PARAM, char **argv) /* without -l, bare -c or -u enable sort too */ /* (with -l, bare -c or -u just select which time to show) */ if (opt & (OPT_c|OPT_u)) { - option_mask32 |= OPT_t; + opt = option_mask32 |= OPT_t; } } } /* choose a display format if one was not already specified by an option */ - if (!(option_mask32 & (OPT_l|OPT_1|OPT_x|OPT_C))) - option_mask32 |= (isatty(STDOUT_FILENO) ? OPT_C : OPT_1); + if (!(opt & (OPT_l|OPT_1|OPT_x|OPT_C))) + opt = option_mask32 |= (G_isatty() ? OPT_C : OPT_1); - if (ENABLE_FTPD && applet_name[0] == 'f') { - /* ftpd secret backdoor. dirs first are much nicer */ - option_mask32 |= OPT_dirs_first; - } + if (!(opt & OPT_q) && G_isatty()) + opt = option_mask32 |= OPT_q; argv += optind; if (!argv[0]) diff --git a/coreutils/md5_sha1_sum.c b/coreutils/md5_sha1_sum.c index 978d328f12..4506aeb56f 100644 --- a/coreutils/md5_sha1_sum.c +++ b/coreutils/md5_sha1_sum.c @@ -23,6 +23,12 @@ //config: help //config: Compute and check SHA256 message digest //config: +//config:config SHA384SUM +//config: bool "sha384sum (7.3 kb)" +//config: default y +//config: help +//config: Compute and check SHA384 message digest +//config: //config:config SHA512SUM //config: bool "sha512sum (7.3 kb)" //config: default y @@ -35,13 +41,13 @@ //config: help //config: Compute and check SHA3 message digest //config: -//config:comment "Common options for md5sum, sha1sum, sha256sum, sha512sum, sha3sum" -//config: depends on MD5SUM || SHA1SUM || SHA256SUM || SHA512SUM || SHA3SUM +//config:comment "Common options for md5sum, sha1sum, sha256sum, ..., sha3sum" +//config: depends on MD5SUM || SHA1SUM || SHA256SUM || SHA384SUM || SHA512SUM || SHA3SUM //config: //config:config FEATURE_MD5_SHA1_SUM_CHECK //config: bool "Enable -c, -s and -w options" //config: default y -//config: depends on MD5SUM || SHA1SUM || SHA256SUM || SHA512SUM || SHA3SUM +//config: depends on MD5SUM || SHA1SUM || SHA256SUM || SHA384SUM || SHA512SUM || SHA3SUM //config: help //config: Enabling the -c options allows files to be checked //config: against pre-calculated hash values. @@ -51,11 +57,13 @@ //applet:IF_SHA1SUM(APPLET_NOEXEC(sha1sum, md5_sha1_sum, BB_DIR_USR_BIN, BB_SUID_DROP, sha1sum)) //applet:IF_SHA3SUM(APPLET_NOEXEC(sha3sum, md5_sha1_sum, BB_DIR_USR_BIN, BB_SUID_DROP, sha3sum)) //applet:IF_SHA256SUM(APPLET_NOEXEC(sha256sum, md5_sha1_sum, BB_DIR_USR_BIN, BB_SUID_DROP, sha256sum)) +//applet:IF_SHA384SUM(APPLET_NOEXEC(sha384sum, md5_sha1_sum, BB_DIR_USR_BIN, BB_SUID_DROP, sha384sum)) //applet:IF_SHA512SUM(APPLET_NOEXEC(sha512sum, md5_sha1_sum, BB_DIR_USR_BIN, BB_SUID_DROP, sha512sum)) //kbuild:lib-$(CONFIG_MD5SUM) += md5_sha1_sum.o //kbuild:lib-$(CONFIG_SHA1SUM) += md5_sha1_sum.o //kbuild:lib-$(CONFIG_SHA256SUM) += md5_sha1_sum.o +//kbuild:lib-$(CONFIG_SHA384SUM) += md5_sha1_sum.o //kbuild:lib-$(CONFIG_SHA512SUM) += md5_sha1_sum.o //kbuild:lib-$(CONFIG_SHA3SUM) += md5_sha1_sum.o @@ -99,6 +107,16 @@ //usage: "\n -w Warn about improperly formatted checksum lines" //usage: ) //usage: +//usage:#define sha384sum_trivial_usage +//usage: IF_FEATURE_MD5_SHA1_SUM_CHECK("[-c[sw]] ")"[FILE]..." +//usage:#define sha384sum_full_usage "\n\n" +//usage: "Print" IF_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " SHA384 checksums" +//usage: IF_FEATURE_MD5_SHA1_SUM_CHECK( "\n" +//usage: "\n -c Check sums against list in FILEs" +//usage: "\n -s Don't output anything, status code shows success" +//usage: "\n -w Warn about improperly formatted checksum lines" +//usage: ) +//usage: //usage:#define sha512sum_trivial_usage //usage: IF_FEATURE_MD5_SHA1_SUM_CHECK("[-c[sw]] ")"[FILE]..." //usage:#define sha512sum_full_usage "\n\n" @@ -130,11 +148,12 @@ enum { /* 4th letter of applet_name is... */ - HASH_MD5 = 's', /* "md5>ssegid)) return 1; - if (ngroups == 0) - initialize_group_array(); - - /* Search through the list looking for GID. */ - for (i = 0; i < ngroups; i++) - if (gid == group_array[i]) - return 1; - - return 0; + return is_in_supplementary_groups(groupinfo, gid); } - -/* Do the same thing access(2) does, but use the effective uid and gid, - and don't make the mistake of telling root that any file is - executable. */ -static int test_eaccess(struct stat *st, int mode) +/* + * Similar to what access(2) does, but uses the effective uid and gid. + * Doesn't make the mistake of telling root that any file is executable. + * Returns non-zero if the file is accessible. + */ +static int test_st_mode(struct stat *st, int mode) { - unsigned int euid = geteuid(); + enum { ANY_IX = S_IXUSR | S_IXGRP | S_IXOTH }; + unsigned euid; + + if (mode == X_OK) { + /* Do we already know with no extra syscalls? */ + //if (!S_ISREG(st->st_mode)) + // return 0; /* not a regular file */ + // ^^^ bash 5.2.15 "test -x" does not check this! + if ((st->st_mode & ANY_IX) == 0) + return 0; /* no one can execute */ + if ((st->st_mode & ANY_IX) == ANY_IX) + return 1; /* anyone can execute */ + } + euid = get_cached_euid(&groupinfo->euid); if (euid == 0) { /* Root can read or write any file. */ if (mode != X_OK) - return 0; + return 1; /* Root can execute any file that has any one of the execute * bits set. */ - if (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) - return 0; - } - - if (st->st_uid == euid) /* owner */ + mode = S_IXUSR | S_IXGRP | S_IXOTH; + } else if (st->st_uid == euid) /* owner */ mode <<= 6; else if (is_a_group_member(st->st_gid)) mode <<= 3; - if (st->st_mode & mode) - return 0; - - return -1; + return st->st_mode & mode; } @@ -722,7 +709,7 @@ static int filstat(char *nm, enum token mode) i = W_OK; if (mode == FILEX) i = X_OK; - return test_eaccess(&s, i) == 0; + return test_st_mode(&s, i); } if (is_file_type(mode)) { if (mode == FILREG) @@ -760,7 +747,7 @@ static int filstat(char *nm, enum token mode) return ((s.st_mode & i) != 0); } if (mode == FILGZ) - return s.st_size > 0L; + return s.st_size != 0L; /* shorter than "> 0" test */ if (mode == FILUID) return s.st_uid == geteuid(); if (mode == FILGID) @@ -891,7 +878,7 @@ static number_t primary(enum token n) } -int test_main(int argc, char **argv) +int FAST_FUNC test_main2(struct cached_groupinfo *pgroupinfo, int argc, char **argv) { int res; const char *arg0; @@ -924,6 +911,7 @@ int test_main(int argc, char **argv) /* We must do DEINIT_S() prior to returning */ INIT_S(); + groupinfo = pgroupinfo; #if BASH_TEST2 bash_test2 = bt2; @@ -1026,3 +1014,18 @@ int test_main(int argc, char **argv) DEINIT_S(); return res; } + +int test_main(int argc, char **argv) +{ + struct cached_groupinfo info; + int r; + + info.euid = -1; + info.egid = -1; + info.ngroups = 0; + info.supplementary_array = NULL; + r = test_main2(&info, argc, argv); + free(info.supplementary_array); + + return r; +} diff --git a/debianutils/which.c b/debianutils/which.c index 1f547919f2..a7d55a2159 100644 --- a/debianutils/which.c +++ b/debianutils/which.c @@ -31,15 +31,12 @@ int which_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int which_main(int argc UNUSED_PARAM, char **argv) { - char *env_path; + const char *env_path; int status = 0; - /* This sizeof(): bb_default_root_path is shorter than BB_PATH_ROOT_PATH */ - char buf[sizeof(BB_PATH_ROOT_PATH)]; env_path = getenv("PATH"); if (!env_path) - /* env_path must be writable, and must not alloc, so... */ - env_path = strcpy(buf, bb_default_root_path); + env_path = bb_default_root_path; getopt32(argv, "^" "a" "\0" "-1"/*at least one arg*/); argv += optind; @@ -54,7 +51,7 @@ int which_main(int argc UNUSED_PARAM, char **argv) puts(*argv); } } else { - char *path; + const char *path; char *p; path = env_path; diff --git a/e2fsprogs/fsck.c b/e2fsprogs/fsck.c index fd4ea737c5..f7e93497dc 100644 --- a/e2fsprogs/fsck.c +++ b/e2fsprogs/fsck.c @@ -423,13 +423,11 @@ static int wait_one(int flags) /* if (G.noexecute) { already returned -1; } */ while (1) { - pid = waitpid(-1, &status, flags); + pid = safe_waitpid(-1, &status, flags); kill_all_if_got_signal(); if (pid == 0) /* flags == WNOHANG and no children exited */ return -1; if (pid < 0) { - if (errno == EINTR) - continue; if (errno == ECHILD) { /* paranoia */ bb_simple_error_msg("wait: no more children"); return -1; diff --git a/editors/cmp.c b/editors/cmp.c index 54f3475081..89539f8cf1 100644 --- a/editors/cmp.c +++ b/editors/cmp.c @@ -113,8 +113,8 @@ int cmp_main(int argc UNUSED_PARAM, char **argv) fmt = fmt_differ; if (ENABLE_DESKTOP) { - while (skip1) { getc(fp1); skip1--; } - while (skip2) { getc(fp2); skip2--; } + while (skip1) { if (getc(fp1) == EOF) break; skip1--; } + while (skip2) { if (getc(fp2) == EOF) break; skip2--; } } do { if (max_count >= 0 && --max_count < 0) @@ -146,7 +146,7 @@ int cmp_main(int argc UNUSED_PARAM, char **argv) line_pos = c1; /* line_pos is unused in the -l case. */ } fprintf(outfile, fmt, filename1, filename2, char_pos, line_pos, c2); - if (opt) { /* This must be -l since not -s. */ + if (opt & CMP_OPT_l) { /* If we encountered an EOF, * the while check will catch it. */ continue; diff --git a/editors/diff.c b/editors/diff.c index 1adc4cbc78..6f4ed97120 100644 --- a/editors/diff.c +++ b/editors/diff.c @@ -741,7 +741,7 @@ static int diffreg(char *file[2]) fd = fd_tmp; xlseek(fd, 0, SEEK_SET); } - fp[i] = fdopen(fd, "r"); + fp[i] = xfdopen_for_read(fd); } setup_common_bufsiz(); diff --git a/editors/ed.c b/editors/ed.c index 8ec23d07fc..a02634ec71 100644 --- a/editors/ed.c +++ b/editors/ed.c @@ -345,6 +345,8 @@ static int insertLine(int num, const char *data, int len) lp->prev->next = newLp; lp->prev = newLp; + if (num <= curNum) + curLine = curLine->prev; lastNum++; dirty = TRUE; return setCurNum(num); diff --git a/editors/vi.c b/editors/vi.c index 34932f60cf..f48bcf5142 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -2951,6 +2951,10 @@ static void colon(char *buf) else if (cmd[0] == '!') { // run a cmd int retcode; // :!ls run the + if (GOT_ADDRESS) { + status_line_bold("Range not allowed"); + goto ret; + } exp = expand_args(buf + 1); if (exp == NULL) goto ret; diff --git a/include/libbb.h b/include/libbb.h index 01cdb1bdc8..8d252d4550 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -290,6 +290,7 @@ PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN /* "long" is long enough on this system */ typedef unsigned long uoff_t; # define XATOOFF(a) xatoul_range((a), 0, LONG_MAX) +# define XATOOFF_SFX(a, s) xatoul_range_sfx((a), 0, LONG_MAX, s) /* usage: sz = BB_STRTOOFF(s, NULL, 10); if (errno || sz < 0) die(); */ # define BB_STRTOOFF bb_strtoul # define STRTOOFF strtoul @@ -299,6 +300,7 @@ typedef unsigned long uoff_t; /* "long" is too short, need "long long" */ typedef unsigned long long uoff_t; # define XATOOFF(a) xatoull_range((a), 0, LLONG_MAX) +# define XATOOFF_SFX(a, s) xatoull_range_sfx((a), 0, LLONG_MAX, s) # define BB_STRTOOFF bb_strtoull # define STRTOOFF strtoull # define OFF_FMT "ll" @@ -314,12 +316,14 @@ typedef unsigned long long uoff_t; # if UINT_MAX == ULONG_MAX typedef unsigned long uoff_t; # define XATOOFF(a) xatoi_positive(a) +# define XATOOFF_SFX(a, s) xatoul_range_sfx((a), 0, INT_MAX, s) # define BB_STRTOOFF bb_strtou # define STRTOOFF strtol # define OFF_FMT "l" # else typedef unsigned long uoff_t; # define XATOOFF(a) xatoul_range((a), 0, LONG_MAX) +# define XATOOFF_SFX(a, s) xatoul_range_sfx((a), 0, LONG_MAX, s) # define BB_STRTOOFF bb_strtoul # define STRTOOFF strtol # define OFF_FMT "l" @@ -643,6 +647,8 @@ int sigaction_set(int sig, const struct sigaction *act) FAST_FUNC; int sigprocmask_allsigs(int how) FAST_FUNC; /* Return old set in the same set: */ int sigprocmask2(int how, sigset_t *set) FAST_FUNC; +/* SIG_BLOCK all signals, return old set: */ +int sigblockall(sigset_t *set) FAST_FUNC; /* Standard handler which just records signo */ extern smallint bb_got_signal; void record_signo(int signo); /* not FAST_FUNC! */ @@ -1011,13 +1017,13 @@ unsigned bb_clk_tck(void) FAST_FUNC; #if SEAMLESS_COMPRESSION /* Autodetects gzip/bzip2 formats. fd may be in the middle of the file! */ -int setup_unzip_on_fd(int fd, int fail_if_not_compressed) FAST_FUNC; +int setup_unzip_on_fd(int fd, int die_if_not_compressed) FAST_FUNC; /* Autodetects .gz etc */ -extern int open_zipped(const char *fname, int fail_if_not_compressed) FAST_FUNC; +extern int open_zipped(const char *fname, int die_if_not_compressed) FAST_FUNC; extern void *xmalloc_open_zipped_read_close(const char *fname, size_t *maxsz_p) FAST_FUNC RETURNS_MALLOC; #else # define setup_unzip_on_fd(...) (0) -# define open_zipped(fname, fail_if_not_compressed) open((fname), O_RDONLY); +# define open_zipped(fname, die_if_not_compressed) open((fname), O_RDONLY); # define xmalloc_open_zipped_read_close(fname, maxsz_p) xmalloc_open_read_close((fname), (maxsz_p)) #endif /* lzma has no signature, need a little helper. NB: exist only for ENABLE_FEATURE_SEAMLESS_LZMA=y */ @@ -1113,6 +1119,32 @@ char *bin2hex(char *dst, const char *src, int count) FAST_FUNC; /* Reverse */ char* hex2bin(char *dst, const char *src, int count) FAST_FUNC; +/* Returns strlen as a bonus */ +//size_t replace_char(char *s, char what, char with) FAST_FUNC; +static inline size_t replace_char(char *str, char from, char to) +{ + char *p = str; + while (*p) { + if (*p == from) + *p = to; + p++; + } + return p - str; +} + +extern const char c_escape_conv_str00[]; +#define c_escape_conv_str07 (c_escape_conv_str00+3) + +void FAST_FUNC xorbuf_3(void *dst, const void *src1, const void *src2, unsigned count); +void FAST_FUNC xorbuf(void* buf, const void* mask, unsigned count); +void FAST_FUNC xorbuf16_aligned_long(void* buf, const void* mask); +void FAST_FUNC xorbuf64_3_aligned64(void *dst, const void *src1, const void *src2); +#if BB_UNALIGNED_MEMACCESS_OK +# define xorbuf16(buf,mask) xorbuf16_aligned_long(buf,mask) +#else +void FAST_FUNC xorbuf16(void* buf, const void* mask); +#endif + /* Generate a UUID */ void generate_uuid(uint8_t *buf) FAST_FUNC; @@ -1201,6 +1233,19 @@ void die_if_bad_username(const char* name) FAST_FUNC; * Dies on errors (on Linux, only xrealloc can cause this, not internal getgroups call). */ gid_t *bb_getgroups(int *ngroups, gid_t *group_array) FAST_FUNC; +/* + * True if GID is in our getgroups() result. + * getgroups() is cached in supplementary_array[], to make successive calls faster. + */ +struct cached_groupinfo { + uid_t euid; + gid_t egid; + int ngroups; + gid_t *supplementary_array; +}; +uid_t FAST_FUNC get_cached_euid(uid_t *euid); +gid_t FAST_FUNC get_cached_egid(gid_t *egid); +int FAST_FUNC is_in_supplementary_groups(struct cached_groupinfo *groupinfo, gid_t gid); #if ENABLE_FEATURE_UTMP void FAST_FUNC write_new_utmp(pid_t pid, int new_type, const char *tty_name, const char *username, const char *hostname); @@ -1214,7 +1259,7 @@ void FAST_FUNC update_utmp_DEAD_PROCESS(pid_t pid); int file_is_executable(const char *name) FAST_FUNC; -char *find_executable(const char *filename, char **PATHp) FAST_FUNC; +char *find_executable(const char *filename, const char **PATHp) FAST_FUNC; int executable_exists(const char *filename) FAST_FUNC; /* BB_EXECxx always execs (it's not doing NOFORK/NOEXEC stuff), @@ -1539,6 +1584,7 @@ int test_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE #endif ; +int FAST_FUNC test_main2(struct cached_groupinfo *pgroupinfo, int argc, char **argv); int kill_main(int argc, char **argv) #if ENABLE_KILL || ENABLE_KILLALL || ENABLE_KILLALL5 MAIN_EXTERNALLY_VISIBLE @@ -1792,18 +1838,25 @@ extern char *pw_encrypt(const char *clear, const char *salt, int cleanup) FAST_F extern int obscure(const char *old, const char *newval, const struct passwd *pwdp) FAST_FUNC; /* * rnd is additional random input. New one is returned. - * Useful if you call crypt_make_salt many times in a row: - * rnd = crypt_make_salt(buf1, 4, 0); - * rnd = crypt_make_salt(buf2, 4, rnd); - * rnd = crypt_make_salt(buf3, 4, rnd); + * Useful if you call crypt_make_rand64encoded many times in a row: + * rnd = crypt_make_rand64encoded(buf1, 4, 0); + * rnd = crypt_make_rand64encoded(buf2, 4, rnd); + * rnd = crypt_make_rand64encoded(buf3, 4, rnd); * (otherwise we risk having same salt generated) */ -extern int crypt_make_salt(char *p, int cnt /*, int rnd*/) FAST_FUNC; -/* "$N$" + sha_salt_16_bytes + NUL */ -#define MAX_PW_SALT_LEN (3 + 16 + 1) +extern int crypt_make_rand64encoded(char *p, int cnt /*, int rnd*/) FAST_FUNC; +/* Size of char salt[] to hold randomly-generated salt string + * sha256/512: + * "$5$" ["rounds=999999999$"] "" + * "$6$" ["rounds=999999999$"] "" + * #define MAX_PW_SALT_LEN (3 + sizeof("rounds=999999999$")-1 + 16 + 1) + * yescrypt: + * "$y$" "$" + * (86 chars are ascii64-encoded 64 binary bytes) + */ +#define MAX_PW_SALT_LEN (3 + 8*6 + 1 + 86 + 1) extern char* crypt_make_pw_salt(char p[MAX_PW_SALT_LEN], const char *algo) FAST_FUNC; - /* Returns number of lines changed, or -1 on error */ #if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP) #define update_passwd(filename, username, data, member) \ @@ -1937,6 +1990,7 @@ int64_t read_key(int fd, char *buffer, int timeout) FAST_FUNC; int64_t safe_read_key(int fd, char *buffer, int timeout) FAST_FUNC; void read_key_ungets(char *buffer, const char *str, unsigned len) FAST_FUNC; +int check_got_signal_and_poll(struct pollfd pfd[1], int timeout) FAST_FUNC; #if ENABLE_FEATURE_EDITING /* It's NOT just ENABLEd or disabled. It's a number: */ @@ -1974,7 +2028,7 @@ typedef struct line_input_t { # if MAX_HISTORY int cnt_history; int cur_history; - int max_history; /* must never be <= 0 */ + int max_history; /* must never be < 0 */ # if ENABLE_FEATURE_EDITING_SAVEHISTORY /* meaning of this field depends on FEATURE_EDITING_SAVE_ON_EXIT: * if !FEATURE_EDITING_SAVE_ON_EXIT: "how many lines are @@ -1998,11 +2052,11 @@ enum { FOR_SHELL = DO_HISTORY | TAB_COMPLETION | USERNAME_COMPLETION | LI_INTERRUPTIBLE, }; line_input_t *new_line_input_t(int flags) FAST_FUNC; -#if ENABLE_FEATURE_EDITING_SAVEHISTORY +# if ENABLE_FEATURE_EDITING_SAVEHISTORY void free_line_input_t(line_input_t *n) FAST_FUNC; -#else -# define free_line_input_t(n) free(n) -#endif +# else +# define free_line_input_t(n) free(n) +# endif /* * maxsize must be >= 2. * Returns: @@ -2013,7 +2067,7 @@ void free_line_input_t(line_input_t *n) FAST_FUNC; int read_line_input(line_input_t *st, const char *prompt, char *command, int maxsize) FAST_FUNC; void show_history(const line_input_t *st) FAST_FUNC; # if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT -void save_history(line_input_t *st); +void save_history(line_input_t *st) FAST_FUNC; # endif #else #define MAX_HISTORY 0 @@ -2033,33 +2087,6 @@ enum { COMM_LEN = 16 }; # endif #endif -struct smaprec { - unsigned long mapped_rw; - unsigned long mapped_ro; - unsigned long shared_clean; - unsigned long shared_dirty; - unsigned long private_clean; - unsigned long private_dirty; - unsigned long stack; - unsigned long smap_pss, smap_swap; - unsigned long smap_size; - // For mixed 32/64 userspace, 32-bit pmap still needs - // 64-bit field here to correctly show 64-bit processes: - unsigned long long smap_start; - // (strictly speaking, other fields need to be wider too, - // but they are in kbytes, not bytes, and they hold sizes, - // not start addresses, sizes tend to be less than 4 terabytes) - char smap_mode[5]; - char *smap_name; -}; - -#if !ENABLE_PMAP -#define procps_read_smaps(pid, total, cb, data) \ - procps_read_smaps(pid, total) -#endif -int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total, - void (*cb)(struct smaprec *, void *), void *data); - typedef struct procps_status_t { DIR *dir; IF_FEATURE_SHOW_THREADS(DIR *task_dir;) @@ -2089,7 +2116,13 @@ typedef struct procps_status_t { #endif unsigned tty_major,tty_minor; #if ENABLE_FEATURE_TOPMEM - struct smaprec smaps; + unsigned long mapped_rw; + unsigned long mapped_ro; + unsigned long shared_clean; + unsigned long shared_dirty; + unsigned long private_clean; + unsigned long private_dirty; + unsigned long stack; #endif char state[4]; /* basename of executable in exec(2), read from /proc/N/stat @@ -2138,11 +2171,15 @@ void free_procps_scan(procps_status_t* sp) FAST_FUNC; procps_status_t* procps_scan(procps_status_t* sp, int flags) FAST_FUNC; /* Format cmdline (up to col chars) into char buf[size] */ /* Puts [comm] if cmdline is empty (-> process is a kernel thread) */ -void read_cmdline(char *buf, int size, unsigned pid, const char *comm) FAST_FUNC; +int read_cmdline(char *buf, int size, unsigned pid, const char *comm) FAST_FUNC; pid_t *find_pid_by_name(const char* procName) FAST_FUNC; pid_t *pidlist_reverse(pid_t *pidList) FAST_FUNC; int starts_with_cpu(const char *str) FAST_FUNC; unsigned get_cpu_count(void) FAST_FUNC; +/* Some internals reused by pmap: */ +unsigned long FAST_FUNC fast_strtoul_10(char **endptr); +unsigned long long FAST_FUNC fast_strtoull_16(char **endptr); +char* FAST_FUNC skip_fields(char *str, int count); /* Use strict=1 if you process input from untrusted source: @@ -2168,6 +2205,21 @@ char *decode_base64(char *dst, const char **pp_src) FAST_FUNC; char *decode_base32(char *dst, const char **pp_src) FAST_FUNC; void read_base64(FILE *src_stream, FILE *dst_stream, int flags) FAST_FUNC; +int FAST_FUNC i2a64(int i); +int FAST_FUNC a2i64(char c); +char* FAST_FUNC num2str64_lsb_first(char *s, unsigned v, int n); + +enum { + /* how many bytes XYZ_end() fills */ + MD5_OUTSIZE = 16, + SHA1_OUTSIZE = 20, + SHA256_OUTSIZE = 32, + SHA384_OUTSIZE = 48, + SHA512_OUTSIZE = 64, + //SHA3-224_OUTSIZE = 28, + /* size of input block */ + SHA2_INSIZE = 64, +}; typedef struct md5_ctx_t { uint8_t wbuffer[64]; /* always correctly aligned for uint64_t */ void (*process_block)(struct md5_ctx_t*) FAST_FUNC; @@ -2181,6 +2233,7 @@ typedef struct sha512_ctx_t { uint64_t hash[8]; uint8_t wbuffer[128]; /* always correctly aligned for uint64_t */ } sha512_ctx_t; +typedef struct sha512_ctx_t sha384_ctx_t; typedef struct sha3_ctx_t { uint64_t state[25]; unsigned bytes_queued; @@ -2198,20 +2251,46 @@ void sha256_begin(sha256_ctx_t *ctx) FAST_FUNC; void sha512_begin(sha512_ctx_t *ctx) FAST_FUNC; void sha512_hash(sha512_ctx_t *ctx, const void *buffer, size_t len) FAST_FUNC; unsigned sha512_end(sha512_ctx_t *ctx, void *resbuf) FAST_FUNC; +void sha384_begin(sha384_ctx_t *ctx) FAST_FUNC; +#define sha384_hash sha512_hash +unsigned sha384_end(sha384_ctx_t *ctx, void *resbuf) FAST_FUNC; void sha3_begin(sha3_ctx_t *ctx) FAST_FUNC; void sha3_hash(sha3_ctx_t *ctx, const void *buffer, size_t len) FAST_FUNC; unsigned sha3_end(sha3_ctx_t *ctx, void *resbuf) FAST_FUNC; +void FAST_FUNC sha256_block(const void *in, size_t len, uint8_t hash[32]); /* TLS benefits from knowing that sha1 and sha256 share these. Give them "agnostic" names too */ typedef struct md5_ctx_t md5sha_ctx_t; #define md5sha_hash md5_hash #define sha_end sha1_end -enum { - MD5_OUTSIZE = 16, - SHA1_OUTSIZE = 20, - SHA256_OUTSIZE = 32, - SHA512_OUTSIZE = 64, - SHA3_OUTSIZE = 28, -}; + +/* RFC 2104 HMAC (hash-based message authentication code) */ +typedef struct hmac_ctx { + md5sha_ctx_t hashed_key_xor_ipad; + md5sha_ctx_t hashed_key_xor_opad; +} hmac_ctx_t; +#define HMAC_ONLY_SHA256 (!ENABLE_FEATURE_TLS_SHA1) +typedef void md5sha_begin_func(md5sha_ctx_t *ctx) FAST_FUNC; +#if HMAC_ONLY_SHA256 +#define hmac_begin(ctx,key,key_size,begin) \ + hmac_begin(ctx,key,key_size) +#endif +void FAST_FUNC hmac_begin(hmac_ctx_t *ctx, const uint8_t *key, unsigned key_size, md5sha_begin_func *begin); +static ALWAYS_INLINE void hmac_hash(hmac_ctx_t *ctx, const void *in, size_t len) +{ + md5sha_hash(&ctx->hashed_key_xor_ipad, in, len); +} +unsigned FAST_FUNC hmac_end(hmac_ctx_t *ctx, uint8_t *out); +#if HMAC_ONLY_SHA256 +#define hmac_block(key,key_size,begin,in,sz,out) \ + hmac_block(key,key_size,in,sz,out) +#endif +unsigned FAST_FUNC hmac_block(const uint8_t *key, unsigned key_size, + md5sha_begin_func *begin, + const void *in, unsigned sz, + uint8_t *out); +/* HMAC helpers for TLS: */ +void FAST_FUNC hmac_hash_v(hmac_ctx_t *ctx, va_list va); +unsigned FAST_FUNC hmac_peek_hash(hmac_ctx_t *ctx, uint8_t *out, ...); extern uint32_t *global_crc32_table; uint32_t *crc32_filltable(uint32_t *tbl256, int endian) FAST_FUNC; @@ -2323,26 +2402,10 @@ extern struct globals *BB_GLOBAL_CONST ptr_to_globals; #define barrier() asm volatile ("":::"memory") #if defined(__clang_major__) && __clang_major__ >= 9 -/* Clang/llvm drops assignment to "constant" storage. Silently. - * Needs serious convincing to not eliminate the store. - */ -static ALWAYS_INLINE void* not_const_pp(const void *p) -{ - void *pp; - asm volatile ( - "# forget that p points to const" - : /*outputs*/ "=r" (pp) - : /*inputs*/ "0" (p) - ); - return pp; -} -# define ASSIGN_CONST_PTR(pptr, v) do { \ - *(void**)not_const_pp(pptr) = (void*)(v); \ - barrier(); \ -} while (0) -/* XZALLOC_CONST_PTR() is an out-of-line function to prevent - * clang from reading pointer before it is assigned. +/* {ASSIGN,XZALLOC}_CONST_PTR() are out-of-line functions + * to prevent clang from reading pointer before it is assigned. */ +void ASSIGN_CONST_PTR(const void *pptr, void *v) FAST_FUNC; void XZALLOC_CONST_PTR(const void *pptr, size_t size) FAST_FUNC; #else # define ASSIGN_CONST_PTR(pptr, v) do { \ diff --git a/include/platform.h b/include/platform.h index ea0512f365..a5b61757f6 100644 --- a/include/platform.h +++ b/include/platform.h @@ -187,7 +187,7 @@ #elif defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN # define BB_BIG_ENDIAN 0 # define BB_LITTLE_ENDIAN 1 -#elif defined(__386__) +#elif defined(__i386__) # define BB_BIG_ENDIAN 0 # define BB_LITTLE_ENDIAN 1 #else @@ -209,6 +209,8 @@ # define SWAP_LE64(x) bb_bswap_64(x) # define IF_BIG_ENDIAN(...) __VA_ARGS__ # define IF_LITTLE_ENDIAN(...) +/* How do bytes a,b,c,d (sequential in memory) look if fetched into uint32_t? */ +# define PACK32_BYTES(a,b,c,d) (uint32_t)((d)+((c)<<8)+((b)<<16)+((a)<<24)) #else # define SWAP_BE16(x) bswap_16(x) # define SWAP_BE32(x) bswap_32(x) @@ -218,6 +220,7 @@ # define SWAP_LE64(x) (x) # define IF_BIG_ENDIAN(...) # define IF_LITTLE_ENDIAN(...) __VA_ARGS__ +# define PACK32_BYTES(a,b,c,d) (uint32_t)((a)+((b)<<8)+((c)<<16)+((d)<<24)) #endif diff --git a/include/usage.src.h b/include/usage.src.h index 5d20388341..0881337f85 100644 --- a/include/usage.src.h +++ b/include/usage.src.h @@ -17,11 +17,11 @@ #define scripted_trivial_usage NOUSAGE_STR #define scripted_full_usage "" -#if !ENABLE_USE_BB_CRYPT || ENABLE_USE_BB_CRYPT_SHA -# define CRYPT_METHODS_HELP_STR "des,md5,sha256/512" \ +#if !ENABLE_USE_BB_CRYPT +# define CRYPT_METHODS_HELP_STR "des,md5,sha256/512,yescrypt" \ " (default "CONFIG_FEATURE_DEFAULT_PASSWD_ALGO")" #else -# define CRYPT_METHODS_HELP_STR "des,md5" \ +# define CRYPT_METHODS_HELP_STR "des,md5"IF_USE_BB_CRYPT_SHA(",sha256/512")IF_USE_BB_CRYPT_YES(",yescrypt") \ " (default "CONFIG_FEATURE_DEFAULT_PASSWD_ALGO")" #endif diff --git a/init/bootchartd.c b/init/bootchartd.c index 0929890a32..a5447c6ad4 100644 --- a/init/bootchartd.c +++ b/init/bootchartd.c @@ -133,7 +133,7 @@ static void dump_file(FILE *fp, const char *filename) static int dump_procs(FILE *fp, int look_for_login_process) { struct dirent *entry; - DIR *dir = opendir("/proc"); + DIR *dir = xopendir("/proc"); int found_login_process = 0; fputs(G.jiffy_line, fp); diff --git a/init/init.c b/init/init.c index 2ee1e4cde5..294be99529 100644 --- a/init/init.c +++ b/init/init.c @@ -1198,17 +1198,29 @@ int init_main(int argc UNUSED_PARAM, char **argv) /* Wait for any child process(es) to exit */ while (1) { pid_t wpid; + int status; struct init_action *a; - wpid = waitpid(-1, NULL, WNOHANG); + wpid = wait_any_nohang(&status); if (wpid <= 0) break; a = mark_terminated(wpid); if (a) { - message(L_LOG, "process '%s' (pid %u) exited. " + const char *s = "killed, signal"; + int ex = WTERMSIG(status); + /* "if (!WIFSIGNALED(status))" generates more code: + * on linux, WIFEXITED(status) is "WTERMSIG(status) == 0" + * and WTERMSIG(status) is known, so compiler optimizes. + */ + if (WIFEXITED(status)) { + s = "exited, exitcode"; + ex = WEXITSTATUS(status); + } + message(L_LOG, "process '%s' (pid %u) %s:%d. " "Scheduling for restart.", - a->command, (unsigned)wpid); + a->command, (unsigned)wpid, + s, ex); } } diff --git a/libbb/Config.src b/libbb/Config.src index b980f19a92..55e670dcd7 100644 --- a/libbb/Config.src +++ b/libbb/Config.src @@ -182,8 +182,8 @@ config FEATURE_EDITING_VI config FEATURE_EDITING_HISTORY int "History size" # Don't allow way too big values here, code uses fixed "char *history[N]" struct member - range 0 9999 - default 255 + range 0 2000 + default 200 depends on FEATURE_EDITING help Specify command history size (0 - disable). diff --git a/libbb/appletlib.c b/libbb/appletlib.c index d9cc48423b..17216baab9 100644 --- a/libbb/appletlib.c +++ b/libbb/appletlib.c @@ -127,17 +127,19 @@ static const char packed_usage[] ALIGN1 = { PACKED_USAGE }; void FAST_FUNC bb_show_usage(void) { if (ENABLE_SHOW_USAGE) { + ssize_t FAST_FUNC (*full_write_fn)(const char *) = + xfunc_error_retval ? full_write2_str : full_write1_str; #ifdef SINGLE_APPLET_STR /* Imagine that this applet is "true". Dont link in printf! */ const char *usage_string = unpack_usage_messages(); if (usage_string) { if (*usage_string == '\b') { - full_write2_str("No help available\n"); + full_write_fn("No help available\n"); } else { - full_write2_str("Usage: "SINGLE_APPLET_STR" "); - full_write2_str(usage_string); - full_write2_str("\n"); + full_write_fn("Usage: "SINGLE_APPLET_STR" "); + full_write_fn(usage_string); + full_write_fn("\n"); } if (ENABLE_FEATURE_CLEAN_UP) dealloc_usage_messages((char*)usage_string); @@ -153,19 +155,19 @@ void FAST_FUNC bb_show_usage(void) while (*p++) continue; ap--; } - full_write2_str(bb_banner); - full_write2_str(" multi-call binary.\n"); /* common string */ + full_write_fn(bb_banner); + full_write_fn(" multi-call binary.\n"); /* common string */ if (*p == '\b') - full_write2_str("\nNo help available\n"); + full_write_fn("\nNo help available\n"); else { - full_write2_str("\nUsage: "); - full_write2_str(applet_name); + full_write_fn("\nUsage: "); + full_write_fn(applet_name); if (p[0]) { if (p[0] != '\n') - full_write2_str(" "); - full_write2_str(p); + full_write_fn(" "); + full_write_fn(p); } - full_write2_str("\n"); + full_write_fn("\n"); } if (ENABLE_FEATURE_CLEAN_UP) dealloc_usage_messages((char*)usage_string); @@ -268,8 +270,10 @@ void lbb_prepare(const char *applet && !(ENABLE_TRUE && strcmp(applet_name, "true") == 0) && !(ENABLE_FALSE && strcmp(applet_name, "false") == 0) && !(ENABLE_ECHO && strcmp(applet_name, "echo") == 0) - ) + ) { + xfunc_error_retval = 0; bb_show_usage(); + } } #endif } @@ -776,10 +780,9 @@ int busybox_main(int argc UNUSED_PARAM, char **argv) help: output_width = get_terminal_width(2); - dup2(1, 2); - full_write2_str(bb_banner); /* reuse const string */ - full_write2_str(" multi-call binary.\n"); /* reuse */ - full_write2_str( + full_write1_str(bb_banner); /* reuse const string */ + full_write1_str(" multi-call binary.\n"); /* reuse */ + full_write1_str( "BusyBox is copyrighted by many authors between 1998-2015.\n" "Licensed under GPLv2. See source distribution for detailed\n" "copyright notices.\n" @@ -817,20 +820,20 @@ int busybox_main(int argc UNUSED_PARAM, char **argv) while (*a) { int len2 = strlen(a) + 2; if (col >= (int)output_width - len2) { - full_write2_str(",\n"); + full_write1_str(",\n"); col = 0; } if (col == 0) { col = 6; - full_write2_str("\t"); + full_write1_str("\t"); } else { - full_write2_str(", "); + full_write1_str(", "); } - full_write2_str(a); + full_write1_str(a); col += len2; a += len2 - 1; } - full_write2_str("\n"); + full_write1_str("\n"); return 0; } @@ -850,14 +853,13 @@ int busybox_main(int argc UNUSED_PARAM, char **argv) if (is_prefixed_with(argv[1], "--list")) { unsigned i = 0; const char *a = applet_names; - dup2(1, 2); while (*a) { # if ENABLE_FEATURE_INSTALLER if (argv[1][6]) /* --list-full? */ - full_write2_str(install_dir[APPLET_INSTALL_LOC(i)] + 1); + full_write1_str(install_dir[APPLET_INSTALL_LOC(i)] + 1); # endif - full_write2_str(a); - full_write2_str("\n"); + full_write1_str(a); + full_write1_str("\n"); i++; while (*a++ != '\0') continue; @@ -890,14 +892,14 @@ int busybox_main(int argc UNUSED_PARAM, char **argv) } if (strcmp(argv[1], "--help") == 0) { - /* "busybox --help []" */ + /* "busybox --help [APPLET]" */ if (!argv[2] # if ENABLE_FEATURE_SH_STANDALONE && ENABLE_FEATURE_TAB_COMPLETION || strcmp(argv[2], "busybox") == 0 /* prevent getting "No help available" */ # endif ) goto help; - /* convert to " --help" */ + /* convert to "APPLET --help" */ applet_name = argv[0] = argv[2]; argv[2] = NULL; if (find_applet_by_name(applet_name) >= 0) { @@ -905,8 +907,16 @@ int busybox_main(int argc UNUSED_PARAM, char **argv) xfunc_error_retval = 0; bb_show_usage(); } /* else: unknown applet, fall through (causes "applet not found" later) */ - } else { - /* "busybox arg1 arg2 ..." */ + } +# if ENABLE_FEATURE_VERSION + else if (!argv[2] && strcmp(argv[1], "--version") == 0) { + full_write1_str(bb_banner); /* reuse const string */ + full_write1_str("\n"); + return 0; + } +# endif + else { + /* "busybox APPLET arg1 arg2 ..." */ argv++; /* We support "busybox /a/path/to/applet args..." too. Allows for * "#!/bin/busybox"-style wrappers diff --git a/libbb/bb_getgroups.c b/libbb/bb_getgroups.c index 5d83c729a9..31cff2b41d 100644 --- a/libbb/bb_getgroups.c +++ b/libbb/bb_getgroups.c @@ -45,3 +45,37 @@ gid_t* FAST_FUNC bb_getgroups(int *ngroups, gid_t *group_array) *ngroups = n; return group_array; } + +uid_t FAST_FUNC get_cached_euid(uid_t *euid) +{ + if (*euid == (uid_t)-1) + *euid = geteuid(); + return *euid; +} + +gid_t FAST_FUNC get_cached_egid(gid_t *egid) +{ + if (*egid == (gid_t)-1) + *egid = getegid(); + return *egid; +} + +/* Return non-zero if GID is in our supplementary group list. */ +int FAST_FUNC is_in_supplementary_groups(struct cached_groupinfo *groupinfo, gid_t gid) +{ + int i; + int ngroups; + gid_t *group_array; + + if (groupinfo->ngroups == 0) + groupinfo->supplementary_array = bb_getgroups(&groupinfo->ngroups, NULL); + ngroups = groupinfo->ngroups; + group_array = groupinfo->supplementary_array; + + /* Search through the list looking for GID. */ + for (i = 0; i < ngroups; i++) + if (gid == group_array[i]) + return 1; + + return 0; +} diff --git a/libbb/bitops.c b/libbb/bitops.c new file mode 100644 index 0000000000..467e1a2d93 --- /dev/null +++ b/libbb/bitops.c @@ -0,0 +1,128 @@ +/* + * Utility routines. + * + * Copyright (C) 2025 by Denys Vlasenko + * + * Licensed under GPLv2, see file LICENSE in this source tree. + */ +//kbuild:lib-y += bitops.o + +#include "libbb.h" + +void FAST_FUNC xorbuf_3(void *dst, const void *src1, const void *src2, unsigned count) +{ + uint8_t *d = dst; + const uint8_t *s1 = src1; + const uint8_t *s2 = src2; +#if BB_UNALIGNED_MEMACCESS_OK + while (count >= sizeof(long)) { + *(long*)d = *(long*)s1 ^ *(long*)s2; + count -= sizeof(long); + d += sizeof(long); + s1 += sizeof(long); + s2 += sizeof(long); + } +#endif + while (count--) + *d++ = *s1++ ^ *s2++; +} + +void FAST_FUNC xorbuf(void *dst, const void *src, unsigned count) +{ + xorbuf_3(dst, dst, src, count); +} + +void FAST_FUNC xorbuf16_aligned_long(void *dst, const void *src) +{ +#if defined(__SSE__) /* any x86_64 has it */ + asm volatile( +"\n movups (%0),%%xmm0" +"\n movups (%1),%%xmm1" // can't just xorps(%1),%%xmm0: +"\n xorps %%xmm1,%%xmm0" // SSE requires 16-byte alignment +"\n movups %%xmm0,(%0)" +"\n" + : "=r" (dst), "=r" (src) + : "0" (dst), "1" (src) + : "xmm0", "xmm1", "memory" + ); +#else + unsigned long *d = dst; + const unsigned long *s = src; + d[0] ^= s[0]; +# if LONG_MAX <= 0x7fffffffffffffff + d[1] ^= s[1]; +# if LONG_MAX == 0x7fffffff + d[2] ^= s[2]; + d[3] ^= s[3]; +# endif +# endif +#endif +} +// The above can be inlined in libbb.h, in a way where compiler +// is even free to use better addressing modes than (%reg), and +// to keep the result in a register +// (to not store it to memory after each XOR): +//#if defined(__SSE__) +//#include +//^^^ or just: typedef float __m128_u attribute((__vector_size__(16),__may_alias__,__aligned__(1))); +//static ALWAYS_INLINE void xorbuf16_aligned_long(void *dst, const void *src) +//{ +// __m128_u xmm0, xmm1; +// asm volatile( +//"\n xorps %1,%0" +// : "=x" (xmm0), "=x" (xmm1) +// : "0" (*(__m128_u*)dst), "1" (*(__m128_u*)src) +// ); +// *(__m128_u*)dst = xmm0; // this store may be optimized out! +//} +//#endif +// but I don't trust gcc optimizer enough to not generate some monstrosity. +// See GMULT() function in TLS code as an example. + +void FAST_FUNC xorbuf64_3_aligned64(void *dst, const void *src1, const void *src2) +{ +#if defined(__SSE__) /* any x86_64 has it */ + asm volatile( +"\n movups 0*16(%1),%%xmm0" +"\n movups 0*16(%2),%%xmm1" // can't just xorps(%2),%%xmm0: +"\n xorps %%xmm1,%%xmm0" // SSE requires 16-byte alignment, we have only 8-byte +"\n movups %%xmm0,0*16(%0)" +"\n movups 1*16(%1),%%xmm0" +"\n movups 1*16(%2),%%xmm1" +"\n xorps %%xmm1,%%xmm0" +"\n movups %%xmm0,1*16(%0)" +"\n movups 2*16(%1),%%xmm0" +"\n movups 2*16(%2),%%xmm1" +"\n xorps %%xmm1,%%xmm0" +"\n movups %%xmm0,2*16(%0)" +"\n movups 3*16(%1),%%xmm0" +"\n movups 3*16(%2),%%xmm1" +"\n xorps %%xmm1,%%xmm0" +"\n movups %%xmm0,3*16(%0)" +"\n" + : "=r" (dst), "=r" (src1), "=r" (src2) + : "0" (dst), "1" (src1), "2" (src2) + : "xmm0", "xmm1", "memory" + ); +#else + long *d = dst; + const long *s1 = src1; + const long *s2 = src2; + unsigned count = 64 / sizeof(long); + do { + *d++ = *s1++ ^ *s2++; + } while (--count != 0); +#endif +} + +#if !BB_UNALIGNED_MEMACCESS_OK +void FAST_FUNC xorbuf16(void *dst, const void *src) +{ +#define p_aligned(a) (((uintptr_t)(a) & (sizeof(long)-1)) == 0) + if (p_aligned(src) && p_aligned(dst)) { + xorbuf16_aligned_long(dst, src); + return; + } + xorbuf_3(dst, dst, src, 16); +} +#endif diff --git a/libbb/c_escape.c b/libbb/c_escape.c new file mode 100644 index 0000000000..6c109f2e01 --- /dev/null +++ b/libbb/c_escape.c @@ -0,0 +1,20 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2025 by Denys Vlasenko + * + * Licensed under GPLv2, see file LICENSE in this source tree. + */ +//kbuild:lib-y += c_escape.o + +#include "libbb.h" + +const char c_escape_conv_str00[] ALIGN1 = + "\\""0""\0" // [0]:00 + "\\""a""\0" // [1]:07 + "\\""b""\0" // [2]:08 + "\\""t""\0" // [3]:09 + "\\""n""\0" // [4]:0a + "\\""v""\0" // [5]:0b + "\\""f""\0" // [6]:0c + "\\""r" // [7]:0d + ; diff --git a/libbb/concat_path_file.c b/libbb/concat_path_file.c index 5b4b7f1131..aefb84f455 100644 --- a/libbb/concat_path_file.c +++ b/libbb/concat_path_file.c @@ -17,6 +17,7 @@ char* FAST_FUNC concat_path_file(const char *path, const char *filename) { +#if 0 char *lc; if (!path) @@ -25,4 +26,78 @@ char* FAST_FUNC concat_path_file(const char *path, const char *filename) while (*filename == '/') filename++; return xasprintf("%s%s%s", path, (lc==NULL ? "/" : ""), filename); +#else +/* ^^^^^^^^^^^ timing of xasprintf-based code above: + * real 7.074s + * user 0.156s <<< + * sys 6.394s + * "rm -rf" of a Linux kernel tree from tmpfs (run time still dominated by in-kernel work, though) + * real 6.989s + * user 0.055s <<< 3 times less CPU used + * sys 6.450s + * vvvvvvvvvvv timing of open-coded malloc+memcpy code below (+59 bytes): + */ + char *buf, *p; + size_t n1, n2, n3; + + while (*filename == '/') + filename++; + + if (!path || !path[0]) + return xstrdup(filename); + + n1 = strlen(path); + n2 = (path[n1 - 1] != '/'); /* 1: "path has no trailing slash" */ + n3 = strlen(filename) + 1; + + buf = xmalloc(n1 + n2 + n3); + p = mempcpy(buf, path, n1); + if (n2) + *p++ = '/'; + memcpy(p, filename, n3); + return buf; +#endif +} + +/* If second component comes from struct dirent, + * it's possible to eliminate one strlen() by using name length + * provided by kernel in struct dirent. See below. + * However, the win seems to be insignificant. + */ + +#if 0 + +/* Extract d_namlen from struct dirent */ +static size_t get_d_namlen(const struct dirent *de) +{ +#if defined(_DIRENT_HAVE_D_NAMLEN) + return de->d_namlen; +#elif defined(_DIRENT_HAVE_D_RECLEN) + const size_t prefix_sz = offsetof(struct dirent, d_name); + return de->d_reclen - prefix_sz; +#else + return strlen(de->d_name); +#endif +} + +char* FAST_FUNC concat_path_dirent(const char *path, const struct dirent *de) +{ + char *buf, *p; + size_t n1, n2, n3; + + if (!path || !path[0]) + return xstrdup(de->d_name); + + n1 = strlen(path); + n2 = (path[n1 - 1] != '/'); + n3 = get_d_namlen(de) + 1; + + buf = xmalloc(n1 + n2 + n3); + p = mempcpy(buf, path, n1); + if (n2) + *p++ = '/'; + memcpy(p, de->d_name, n3); + return buf; } + +#endif diff --git a/libbb/const_hack.c b/libbb/const_hack.c index 9575e6d676..1d175481b0 100644 --- a/libbb/const_hack.c +++ b/libbb/const_hack.c @@ -9,8 +9,27 @@ #include "libbb.h" #if defined(__clang_major__) && __clang_major__ >= 9 +/* Clang/llvm drops assignment to "constant" storage. Silently. + * Needs serious convincing to not eliminate the store. + */ +static ALWAYS_INLINE void* not_const_pp(const void *p) +{ + void *pp; + asm volatile ( + "# forget that p points to const" + : /*outputs*/ "=r" (pp) + : /*inputs*/ "0" (p) + ); + return pp; +} +void FAST_FUNC ASSIGN_CONST_PTR(const void *pptr, void *v) +{ + *(void**)not_const_pp(pptr) = v; + barrier(); +} void FAST_FUNC XZALLOC_CONST_PTR(const void *pptr, size_t size) { - ASSIGN_CONST_PTR(pptr, xzalloc(size)); + *(void**)not_const_pp(pptr) = xzalloc(size); + barrier(); } #endif diff --git a/libbb/dump.c b/libbb/dump.c index b406a24283..d340945760 100644 --- a/libbb/dump.c +++ b/libbb/dump.c @@ -198,9 +198,11 @@ static NOINLINE void rewrite(priv_dumper_t *dumper, FS *fs) if (!e) goto DO_BAD_CONV_CHAR; pr->flags = F_INT; - if (e > int_convs + 1) /* not d or i? */ - pr->flags = F_UINT; byte_count_str = "\010\004\002\001"; + if (e > int_convs + 1) { /* not d or i? */ + pr->flags = F_UINT; + byte_count_str++; + } goto DO_BYTE_COUNT; } else if (strchr(int_convs, *p1)) { /* %d etc */ @@ -476,37 +478,52 @@ static void bpad(PR *pr) continue; } -static const char conv_str[] ALIGN1 = - "\0" "\\""0""\0" - "\007""\\""a""\0" - "\b" "\\""b""\0" - "\f" "\\""f""\0" - "\n" "\\""n""\0" - "\r" "\\""r""\0" - "\t" "\\""t""\0" - "\v" "\\""v""\0" - ; - static void conv_c(PR *pr, unsigned char *p) { - const char *str = conv_str; - - do { - if (*p == *str) { - ++str; - goto strpr; /* map e.g. '\n' to "\\n" */ - } - str += 4; - } while (*str); + const char *str; + unsigned char ch; + + ch = *p; + if (ch == 0 || (ch -= 6, (signed char)ch > 0 && ch <= 7)) { + /* map chars 0,7..13 to "\0","\{a,b,t,n,v,f,r}" */ + str = c_escape_conv_str00 + 3 * ch; + goto strpr; + } if (isprint_asciionly(*p)) { *pr->cchar = 'c'; printf(pr->fmt, *p); } else { +#if defined(__i386__) || defined(__x86_64__) + /* Abuse partial register operations */ + uint32_t buf; + unsigned n = *p; + asm ( //00000000 00000000 00000000 aabbbccc +"\n shll $10,%%eax" //00000000 000000aa bbbccc00 00000000 +"\n shrw $5,%%ax" //00000000 000000aa 00000bbb ccc00000 +"\n shrb $5,%%al" //00000000 000000aa 00000bbb 00000ccc +"\n shll $8,%%eax" //000000aa 00000bbb 00000ccc 00000000 +"\n bswapl %%eax" //00000000 00000ccc 00000bbb 000000aa +"\n addl $0x303030,%%eax" +"\n" : "=a" (n) + : "0" (n) + ); + buf = n; + str = (void*)&buf; +#elif 1 char buf[4]; /* gcc-8.0.1 needs lots of casts to shut up */ sprintf(buf, "%03o", (unsigned)(uint8_t)*p); str = buf; +#else // use faster version? +20 bytes of code relative to sprintf() method + char buf[4]; + buf[3] = '\0'; + ch = *p; + buf[2] = '0' + (ch & 7); ch >>= 3; + buf[1] = '0' + (ch & 7); ch >>= 3; + buf[0] = '0' + ch; + str = buf; +#endif strpr: *pr->cchar = 's'; printf(pr->fmt, str); @@ -665,15 +682,21 @@ static NOINLINE void display(priv_dumper_t* dumper) conv_u(pr, bp); break; case F_UINT: { + union { + uint16_t uval16; + uint32_t uval32; + } u; unsigned value = (unsigned char)*bp; switch (pr->bcnt) { case 1: break; case 2: - move_from_unaligned16(value, bp); + move_from_unaligned16(u.uval16, bp); + value = u.uval16; break; case 4: - move_from_unaligned32(value, bp); + move_from_unaligned32(u.uval32, bp); + value = u.uval32; break; /* case 8: no users yet */ } diff --git a/libbb/executable.c b/libbb/executable.c index a033b74d99..09bed1eaf0 100644 --- a/libbb/executable.c +++ b/libbb/executable.c @@ -21,14 +21,11 @@ int FAST_FUNC file_is_executable(const char *name) /* search (*PATHp) for an executable file; * return allocated string containing full path if found; * PATHp points to the component after the one where it was found - * (or NULL), + * (or NULL if found in last component), * you may call find_executable again with this PATHp to continue - * (if it's not NULL). - * return NULL otherwise; (PATHp is undefined) - * in all cases (*PATHp) contents are temporarily modified - * but are restored on return (s/:/NUL/ and back). + * return NULL otherwise (PATHp is undefined) */ -char* FAST_FUNC find_executable(const char *filename, char **PATHp) +char* FAST_FUNC find_executable(const char *name, const char **PATHp) { /* About empty components in $PATH: * http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html @@ -38,38 +35,45 @@ char* FAST_FUNC find_executable(const char *filename, char **PATHp) * initial colon preceding the rest of the list, or as a trailing colon * following the rest of the list. */ - char *p, *n; + char *p = (char*) *PATHp; - p = *PATHp; - while (p) { - int ex; + if (!p) + return NULL; + while (1) { + const char *end = strchrnul(p, ':'); + int sz = end - p; - n = strchr(p, ':'); - if (n) *n = '\0'; - p = concat_path_file( - p[0] ? p : ".", /* handle "::" case */ - filename - ); - ex = file_is_executable(p); - if (n) *n++ = ':'; - if (ex) { - *PATHp = n; + if (sz != 0) { + p = xasprintf("%.*s/%s", sz, p, name); + } else { + /* We have xxx::yyy in $PATH, + * it means "use current dir" */ + p = xstrdup(name); +// A bit of discrepancy wrt the path used if file is found here. +// bash 5.2.15 "type" returns "./NAME". +// GNU which v2.21 returns "/CUR/DIR/NAME". +// With -a, both skip over all colons: xxx::::yyy is the same as xxx::yyy, +// current dir is not tried the second time. + } + if (file_is_executable(p)) { + *PATHp = (*end ? end+1 : NULL); return p; } free(p); - p = n; - } /* on loop exit p == NULL */ - return p; + if (*end == '\0') + return NULL; + p = (char *) end + 1; + } } /* search $PATH for an executable file; * return 1 if found; * return 0 otherwise; */ -int FAST_FUNC executable_exists(const char *filename) +int FAST_FUNC executable_exists(const char *name) { - char *path = getenv("PATH"); - char *ret = find_executable(filename, &path); + const char *path = getenv("PATH"); + char *ret = find_executable(name, &path); free(ret); return ret != NULL; } diff --git a/libbb/getopt32.c b/libbb/getopt32.c index e861d05676..4c05dcb97f 100644 --- a/libbb/getopt32.c +++ b/libbb/getopt32.c @@ -93,7 +93,7 @@ getopt32(char **argv, const char *applet_opts, ...) "!" If the first character in the applet_opts string is a '!', report bad options, missing required options, - inconsistent options with all-ones return value (instead of abort. + inconsistent options with all-ones return value instead of aborting. "+" If the first character in the applet_opts string is a plus, then option processing will stop as soon as a non-option is @@ -265,7 +265,7 @@ Special characters: for "long options only" cases, such as tar --exclude=PATTERN, wget --header=HDR cases. - "a?b" A "?" between an option and a group of options means that + "a?bc" A "?" between an option and a group of options means that at least one of them is required to occur if the first option occurs in preceding command line arguments. @@ -348,9 +348,6 @@ vgetopt32(char **argv, const char *applet_opts, const char *applet_long_options, unsigned trigger; int min_arg = 0; int max_arg = -1; - int spec_flgs = 0; - -#define SHOW_USAGE_IF_ERROR 1 on_off = complementary; memset(on_off, 0, sizeof(complementary)); @@ -449,9 +446,7 @@ vgetopt32(char **argv, const char *applet_opts, const char *applet_long_options, continue; c = s[1]; if (*s == '?') { - if (c < '0' || c > '9') { - spec_flgs |= SHOW_USAGE_IF_ERROR; - } else { + if (c >= '0' && c <= '9') { max_arg = c - '0'; s++; } @@ -465,8 +460,10 @@ vgetopt32(char **argv, const char *applet_opts, const char *applet_long_options, continue; } if (*s == '=') { - min_arg = max_arg = c - '0'; - s++; + if (c >= '0' && c <= '9') { + min_arg = max_arg = c - '0'; + s++; + } continue; } for (on_off = complementary; on_off->opt_char; on_off++) @@ -533,6 +530,7 @@ vgetopt32(char **argv, const char *applet_opts, const char *applet_long_options, * "fake" short options, like this one: * wget $'-\203' "Test: test" http://kernel.org/ * (supposed to act as --header, but doesn't) */ + next_opt: #if ENABLE_LONG_OPTS while ((c = getopt_long(argc, argv, applet_opts, long_options, NULL)) != -1) { @@ -547,8 +545,16 @@ vgetopt32(char **argv, const char *applet_opts, const char *applet_long_options, * but we construct long opts so that flag * is always NULL (see above) */ if (on_off->opt_char == '\0' /* && c != '\0' */) { - /* c is probably '?' - "bad option" */ - goto error; + /* We reached the end of complementary[] and did not find -c */ + if (c == '?') /* getopt says: "bad option, or option has no required argument" */ + goto error; + /* if there were options beyond 32 bits (example: ls), + * they got no complementary[] slot, and no result bit. + * IOW: they must be "accept but ignore" options. + * For them, we end up here. + */ + //bb_error_msg("ignored option '%c', skipping", c); + goto next_opt; } } if (flags & on_off->incongruously) diff --git a/libbb/hash_hmac.c b/libbb/hash_hmac.c new file mode 100644 index 0000000000..9e48e0f511 --- /dev/null +++ b/libbb/hash_hmac.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2025 Denys Vlasenko + * + * Licensed under GPLv2, see file LICENSE in this source tree. + */ +//kbuild:lib-$(CONFIG_TLS) += hash_hmac.o +//kbuild:lib-$(CONFIG_USE_BB_CRYPT_YES) += hash_hmac.o + +#include "libbb.h" + +// RFC 2104: +// HMAC(key, text) based on a hash H (say, sha256) is: +// ipad = [0x36 x INSIZE] +// opad = [0x5c x INSIZE] +// HMAC(key, text) = H((key XOR opad) + H((key XOR ipad) + text)) +// +// H(key XOR opad) and H(key XOR ipad) can be precomputed +// if we often need HMAC hmac with the same key. +// +// text is often given in disjoint pieces. +void FAST_FUNC hmac_begin(hmac_ctx_t *ctx, const uint8_t *key, unsigned key_size, md5sha_begin_func *begin) +{ +#if HMAC_ONLY_SHA256 +#define begin sha256_begin +#endif + uint8_t key_xor_ipad[SHA2_INSIZE]; + uint8_t key_xor_opad[SHA2_INSIZE]; + unsigned i; + + // "The authentication key can be of any length up to INSIZE, the + // block length of the hash function. Applications that use keys longer + // than INSIZE bytes will first hash the key using H and then use the + // resultant OUTSIZE byte string as the actual key to HMAC." + if (key_size > SHA2_INSIZE) { + uint8_t tempkey[SHA1_OUTSIZE < SHA256_OUTSIZE ? SHA256_OUTSIZE : SHA1_OUTSIZE]; + /* use ctx->hashed_key_xor_ipad as scratch ctx */ + begin(&ctx->hashed_key_xor_ipad); + md5sha_hash(&ctx->hashed_key_xor_ipad, key, key_size); + key_size = sha_end(&ctx->hashed_key_xor_ipad, tempkey); + key = tempkey; + } + + for (i = 0; i < key_size; i++) { + key_xor_ipad[i] = key[i] ^ 0x36; + key_xor_opad[i] = key[i] ^ 0x5c; + } + for (; i < SHA2_INSIZE; i++) { + key_xor_ipad[i] = 0x36; + key_xor_opad[i] = 0x5c; + } + + begin(&ctx->hashed_key_xor_ipad); + begin(&ctx->hashed_key_xor_opad); + md5sha_hash(&ctx->hashed_key_xor_ipad, key_xor_ipad, SHA2_INSIZE); + md5sha_hash(&ctx->hashed_key_xor_opad, key_xor_opad, SHA2_INSIZE); +} +#undef begin + +unsigned FAST_FUNC hmac_end(hmac_ctx_t *ctx, uint8_t *out) +{ + unsigned len = sha_end(&ctx->hashed_key_xor_ipad, out); + /* out = H((key XOR opad) + out) */ + md5sha_hash(&ctx->hashed_key_xor_opad, out, len); + return sha_end(&ctx->hashed_key_xor_opad, out); +} + +unsigned FAST_FUNC hmac_block(const uint8_t *key, unsigned key_size, md5sha_begin_func *begin, const void *in, unsigned sz, uint8_t *out) +{ + hmac_ctx_t ctx; + hmac_begin(&ctx, key, key_size, begin); + hmac_hash(&ctx, in, sz); + return hmac_end(&ctx, out); +} + +/* TLS helpers */ + +void FAST_FUNC hmac_hash_v( + hmac_ctx_t *ctx, + va_list va) +{ + uint8_t *in; + + /* ctx->hashed_key_xor_ipad contains unclosed "H((key XOR ipad) +" state */ + /* ctx->hashed_key_xor_opad contains unclosed "H((key XOR opad) +" state */ + + /* calculate out = H((key XOR ipad) + text) */ + while ((in = va_arg(va, uint8_t*)) != NULL) { + unsigned size = va_arg(va, unsigned); + md5sha_hash(&ctx->hashed_key_xor_ipad, in, size); + } +} + +/* Using HMAC state, make a copy of it (IOW: without affecting this state!) + * hash in the list of (ptr,size) blocks, and finish the HMAC to out[] buffer. + * This function is useful for TLS PRF. + */ +unsigned FAST_FUNC hmac_peek_hash(hmac_ctx_t *ctx, uint8_t *out, ...) +{ + hmac_ctx_t tmpctx = *ctx; /* struct copy */ + va_list va; + + va_start(va, out); + hmac_hash_v(&tmpctx, va); + va_end(va); + + return hmac_end(&tmpctx, out); +} diff --git a/libbb/hash_md5_sha.c b/libbb/hash_md5_sha.c index 57a801459e..9ebda232a2 100644 --- a/libbb/hash_md5_sha.c +++ b/libbb/hash_md5_sha.c @@ -11,7 +11,7 @@ #define STR1(s) #s #define STR(s) STR1(s) -#define NEED_SHA512 (ENABLE_SHA512SUM || ENABLE_USE_BB_CRYPT_SHA) +#define NEED_SHA512 (ENABLE_SHA512SUM || ENABLE_SHA384SUM || ENABLE_USE_BB_CRYPT_SHA) #if ENABLE_SHA1_HWACCEL || ENABLE_SHA256_HWACCEL # if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) @@ -1032,7 +1032,7 @@ static const sha_K_int sha_K[] ALIGN8 = { K(0x84c87814a1f0ab72ULL), K(0x8cc702081a6439ecULL), K(0x90befffa23631e28ULL), K(0xa4506cebde82bde9ULL), K(0xbef9a3f7b2c67915ULL), K(0xc67178f2e372532bULL), -#if NEED_SHA512 /* [64]+ are used for sha512 only */ +#if NEED_SHA512 /* [64]+ are used for sha384 and sha512 only */ K(0xca273eceea26619cULL), K(0xd186b8c721c0c207ULL), K(0xeada7dd6cde0eb1eULL), K(0xf57d4f7fee6ed178ULL), K(0x06f067aa72176fbaULL), K(0x0a637dc5a2c898a6ULL), @@ -1229,11 +1229,20 @@ static const uint32_t init512_lo[] ALIGN4 = { 0x137e2179, }; #endif /* NEED_SHA512 */ - -// Note: SHA-384 is identical to SHA-512, except that initial hash values are -// 0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, -// 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4, -// and the output is constructed by omitting last two 64-bit words of it. +#if ENABLE_SHA384SUM +static const uint64_t init384[] ALIGN8 = { + 0, + 0, + 0xcbbb9d5dc1059ed8, + 0x629a292a367cd507, + 0x9159015a3070dd17, + 0x152fecd8f70e5939, + 0x67332667ffc00b31, + 0x8eb44a8768581511, + 0xdb0c2e0d64f98fa7, + 0x47b5481dbefa4fa4, +}; +#endif /* Initialize structure containing state of computation. (FIPS 180-2:5.3.2) */ @@ -1255,9 +1264,19 @@ void FAST_FUNC sha256_begin(sha256_ctx_t *ctx) #endif } -#if NEED_SHA512 +#if ENABLE_SHA384SUM /* Initialize structure containing state of computation. (FIPS 180-2:5.3.3) */ +void FAST_FUNC sha384_begin(sha512_ctx_t *ctx) +{ + memcpy(&ctx->total64, init384, sizeof(init384)); + /*ctx->total64[0] = ctx->total64[1] = 0; - already done */ +} +#endif + +#if NEED_SHA512 +/* Initialize structure containing state of computation. + (FIPS 180-2:5.3.4) */ void FAST_FUNC sha512_begin(sha512_ctx_t *ctx) { int i; @@ -1313,7 +1332,9 @@ unsigned FAST_FUNC sha1_end(sha1_ctx_t *ctx, void *resbuf) hash_size = 8; if (ctx->process_block == sha1_process_block64 #if ENABLE_SHA1_HWACCEL +# if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) || ctx->process_block == sha1_process_block64_shaNI +# endif #endif ) { hash_size = 5; @@ -1330,7 +1351,7 @@ unsigned FAST_FUNC sha1_end(sha1_ctx_t *ctx, void *resbuf) } #if NEED_SHA512 -unsigned FAST_FUNC sha512_end(sha512_ctx_t *ctx, void *resbuf) +static unsigned FAST_FUNC sha512384_end(sha512_ctx_t *ctx, void *resbuf, unsigned outsize) { unsigned bufpos = ctx->total64[0] & 127; @@ -1361,11 +1382,21 @@ unsigned FAST_FUNC sha512_end(sha512_ctx_t *ctx, void *resbuf) for (i = 0; i < ARRAY_SIZE(ctx->hash); ++i) ctx->hash[i] = SWAP_BE64(ctx->hash[i]); } - memcpy(resbuf, ctx->hash, sizeof(ctx->hash)); - return sizeof(ctx->hash); + memcpy(resbuf, ctx->hash, outsize); + return outsize; +} +unsigned FAST_FUNC sha512_end(sha384_ctx_t *ctx, void *resbuf) +{ + return sha512384_end(ctx, resbuf, SHA512_OUTSIZE); } #endif /* NEED_SHA512 */ +#if ENABLE_SHA384SUM +unsigned FAST_FUNC sha384_end(sha384_ctx_t *ctx, void *resbuf) +{ + return sha512384_end(ctx, resbuf, SHA384_OUTSIZE); +} +#endif /* * The Keccak sponge function, designed by Guido Bertoni, Joan Daemen, @@ -1902,6 +1933,8 @@ void FAST_FUNC sha3_hash(sha3_ctx_t *ctx, const void *buffer, size_t len) unsigned FAST_FUNC sha3_end(sha3_ctx_t *ctx, void *resbuf) { + unsigned hash_len; + /* Padding */ uint8_t *buf = (uint8_t*)ctx->state; /* @@ -1924,6 +1957,7 @@ unsigned FAST_FUNC sha3_end(sha3_ctx_t *ctx, void *resbuf) sha3_process_block72(ctx->state); /* Output */ - memcpy(resbuf, ctx->state, 64); - return 64; + hash_len = (1600/8 - ctx->input_block_bytes) / 2; + memcpy(resbuf, ctx->state, hash_len); + return hash_len; } diff --git a/libbb/hash_sha256_block.c b/libbb/hash_sha256_block.c new file mode 100644 index 0000000000..3c43663215 --- /dev/null +++ b/libbb/hash_sha256_block.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 2025 Denys Vlasenko + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ +//kbuild:lib-y += hash_sha256_block.o +#include "libbb.h" + +void FAST_FUNC +sha256_block(const void *in, size_t len, uint8_t hash[32]) +{ + sha256_ctx_t ctx; + sha256_begin(&ctx); + sha256_hash(&ctx, in, len); + sha256_end(&ctx, hash); +} diff --git a/libbb/hash_sha256_hwaccel_x86-32.S b/libbb/hash_sha256_hwaccel_x86-32.S index a0e4a571a0..8d84055e83 100644 --- a/libbb/hash_sha256_hwaccel_x86-32.S +++ b/libbb/hash_sha256_hwaccel_x86-32.S @@ -34,21 +34,21 @@ #define MSG %xmm0 #define STATE0 %xmm1 #define STATE1 %xmm2 -#define MSGTMP0 %xmm3 -#define MSGTMP1 %xmm4 -#define MSGTMP2 %xmm5 -#define MSGTMP3 %xmm6 +#define MSG0 %xmm3 +#define MSG1 %xmm4 +#define MSG2 %xmm5 +#define MSG3 %xmm6 #define XMMTMP %xmm7 -#define SHUF(a,b,c,d) $(a+(b<<2)+(c<<4)+(d<<6)) +#define SHUF(a,b,c,d) $((a)+((b)<<2)+((c)<<4)+((d)<<6)) .balign 8 # allow decoders to fetch at least 2 first insns sha256_process_block64_shaNI: - movu128 76+0*16(%eax), XMMTMP /* ABCD (little-endian dword order) */ + movu128 76+0*16(%eax), XMMTMP /* ABCD (shown least-significant-dword-first) */ movu128 76+1*16(%eax), STATE1 /* EFGH */ -/* shufps takes dwords 0,1 from *2nd* operand, and dwords 2,3 from 1st one */ +/* shufps: dwords 0,1 of the result are selected from *2nd* operand, and dwords 2,3 from 1st operand */ mova128 STATE1, STATE0 /* --- -------------- ABCD -- EFGH */ shufps SHUF(1,0,1,0), XMMTMP, STATE0 /* FEBA */ @@ -58,190 +58,208 @@ sha256_process_block64_shaNI: mova128 PSHUFFLE_BSWAP32_FLIP_MASK, XMMTMP movl $K256+8*16, SHA256CONSTANTS +// sha256rnds2 instruction uses only lower 64 bits of MSG. +// The code below needs to move upper 64 bits to lower 64 bits +// for the second sha256rnds2 invocation +// (what remains in upper bits does not matter). +// There are several ways to do it: +// movhlps MSG, MSG // abcd -> cdcd (3 bytes of code) +// shuf128_32 SHUF(2,3,n,n), MSG, MSG // abcd -> cdXX (4 bytes) +// punpckhqdq MSG, MSG // abcd -> cdcd (4 bytes) +// unpckhpd MSG, MSG // abcd -> cdcd (4 bytes) +// psrldq $8, MSG // abcd -> cd00 (5 bytes) +// palignr $8, MSG, MSG // abcd -> cdab (6 bytes, SSSE3 insn) +#define MOVE_UPPER64_DOWN(reg) movhlps reg, reg +//#define MOVE_UPPER64_DOWN(reg) shuf128_32 SHUF(2,3,0,0), reg, reg +//#define MOVE_UPPER64_DOWN(reg) punpckhqdq reg, reg +//#define MOVE_UPPER64_DOWN(reg) unpckhpd reg, reg +//#define MOVE_UPPER64_DOWN(reg) psrldq $8, reg +//#define MOVE_UPPER64_DOWN(reg) palignr $8, reg, reg + /* Rounds 0-3 */ movu128 0*16(DATA_PTR), MSG pshufb XMMTMP, MSG - mova128 MSG, MSGTMP0 + mova128 MSG, MSG0 paddd 0*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - shuf128_32 $0x0E, MSG, MSG + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 /* Rounds 4-7 */ movu128 1*16(DATA_PTR), MSG pshufb XMMTMP, MSG - mova128 MSG, MSGTMP1 + mova128 MSG, MSG1 paddd 1*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - shuf128_32 $0x0E, MSG, MSG + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP1, MSGTMP0 + sha256msg1 MSG1, MSG0 /* Rounds 8-11 */ movu128 2*16(DATA_PTR), MSG pshufb XMMTMP, MSG - mova128 MSG, MSGTMP2 + mova128 MSG, MSG2 paddd 2*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - shuf128_32 $0x0E, MSG, MSG + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP2, MSGTMP1 + sha256msg1 MSG2, MSG1 /* Rounds 12-15 */ movu128 3*16(DATA_PTR), MSG pshufb XMMTMP, MSG /* ...to here */ - mova128 MSG, MSGTMP3 + mova128 MSG, MSG3 paddd 3*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP3, XMMTMP - palignr $4, MSGTMP2, XMMTMP - paddd XMMTMP, MSGTMP0 - sha256msg2 MSGTMP3, MSGTMP0 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG3, XMMTMP + palignr $4, MSG2, XMMTMP + paddd XMMTMP, MSG0 + sha256msg2 MSG3, MSG0 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP3, MSGTMP2 + sha256msg1 MSG3, MSG2 /* Rounds 16-19 */ - mova128 MSGTMP0, MSG + mova128 MSG0, MSG paddd 4*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP0, XMMTMP - palignr $4, MSGTMP3, XMMTMP - paddd XMMTMP, MSGTMP1 - sha256msg2 MSGTMP0, MSGTMP1 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG0, XMMTMP + palignr $4, MSG3, XMMTMP + paddd XMMTMP, MSG1 + sha256msg2 MSG0, MSG1 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP0, MSGTMP3 + sha256msg1 MSG0, MSG3 /* Rounds 20-23 */ - mova128 MSGTMP1, MSG + mova128 MSG1, MSG paddd 5*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP1, XMMTMP - palignr $4, MSGTMP0, XMMTMP - paddd XMMTMP, MSGTMP2 - sha256msg2 MSGTMP1, MSGTMP2 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG1, XMMTMP + palignr $4, MSG0, XMMTMP + paddd XMMTMP, MSG2 + sha256msg2 MSG1, MSG2 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP1, MSGTMP0 + sha256msg1 MSG1, MSG0 /* Rounds 24-27 */ - mova128 MSGTMP2, MSG + mova128 MSG2, MSG paddd 6*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP2, XMMTMP - palignr $4, MSGTMP1, XMMTMP - paddd XMMTMP, MSGTMP3 - sha256msg2 MSGTMP2, MSGTMP3 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG2, XMMTMP + palignr $4, MSG1, XMMTMP + paddd XMMTMP, MSG3 + sha256msg2 MSG2, MSG3 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP2, MSGTMP1 + sha256msg1 MSG2, MSG1 /* Rounds 28-31 */ - mova128 MSGTMP3, MSG + mova128 MSG3, MSG paddd 7*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP3, XMMTMP - palignr $4, MSGTMP2, XMMTMP - paddd XMMTMP, MSGTMP0 - sha256msg2 MSGTMP3, MSGTMP0 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG3, XMMTMP + palignr $4, MSG2, XMMTMP + paddd XMMTMP, MSG0 + sha256msg2 MSG3, MSG0 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP3, MSGTMP2 + sha256msg1 MSG3, MSG2 /* Rounds 32-35 */ - mova128 MSGTMP0, MSG + mova128 MSG0, MSG paddd 8*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP0, XMMTMP - palignr $4, MSGTMP3, XMMTMP - paddd XMMTMP, MSGTMP1 - sha256msg2 MSGTMP0, MSGTMP1 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG0, XMMTMP + palignr $4, MSG3, XMMTMP + paddd XMMTMP, MSG1 + sha256msg2 MSG0, MSG1 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP0, MSGTMP3 + sha256msg1 MSG0, MSG3 /* Rounds 36-39 */ - mova128 MSGTMP1, MSG + mova128 MSG1, MSG paddd 9*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP1, XMMTMP - palignr $4, MSGTMP0, XMMTMP - paddd XMMTMP, MSGTMP2 - sha256msg2 MSGTMP1, MSGTMP2 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG1, XMMTMP + palignr $4, MSG0, XMMTMP + paddd XMMTMP, MSG2 + sha256msg2 MSG1, MSG2 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP1, MSGTMP0 + sha256msg1 MSG1, MSG0 /* Rounds 40-43 */ - mova128 MSGTMP2, MSG + mova128 MSG2, MSG paddd 10*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP2, XMMTMP - palignr $4, MSGTMP1, XMMTMP - paddd XMMTMP, MSGTMP3 - sha256msg2 MSGTMP2, MSGTMP3 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG2, XMMTMP + palignr $4, MSG1, XMMTMP + paddd XMMTMP, MSG3 + sha256msg2 MSG2, MSG3 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP2, MSGTMP1 + sha256msg1 MSG2, MSG1 /* Rounds 44-47 */ - mova128 MSGTMP3, MSG + mova128 MSG3, MSG paddd 11*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP3, XMMTMP - palignr $4, MSGTMP2, XMMTMP - paddd XMMTMP, MSGTMP0 - sha256msg2 MSGTMP3, MSGTMP0 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG3, XMMTMP + palignr $4, MSG2, XMMTMP + paddd XMMTMP, MSG0 + sha256msg2 MSG3, MSG0 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP3, MSGTMP2 + sha256msg1 MSG3, MSG2 /* Rounds 48-51 */ - mova128 MSGTMP0, MSG + mova128 MSG0, MSG paddd 12*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP0, XMMTMP - palignr $4, MSGTMP3, XMMTMP - paddd XMMTMP, MSGTMP1 - sha256msg2 MSGTMP0, MSGTMP1 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG0, XMMTMP + palignr $4, MSG3, XMMTMP + paddd XMMTMP, MSG1 + sha256msg2 MSG0, MSG1 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP0, MSGTMP3 + sha256msg1 MSG0, MSG3 /* Rounds 52-55 */ - mova128 MSGTMP1, MSG + mova128 MSG1, MSG paddd 13*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP1, XMMTMP - palignr $4, MSGTMP0, XMMTMP - paddd XMMTMP, MSGTMP2 - sha256msg2 MSGTMP1, MSGTMP2 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG1, XMMTMP + palignr $4, MSG0, XMMTMP + paddd XMMTMP, MSG2 + sha256msg2 MSG1, MSG2 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 /* Rounds 56-59 */ - mova128 MSGTMP2, MSG + mova128 MSG2, MSG paddd 14*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP2, XMMTMP - palignr $4, MSGTMP1, XMMTMP - paddd XMMTMP, MSGTMP3 - sha256msg2 MSGTMP2, MSGTMP3 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG2, XMMTMP + palignr $4, MSG1, XMMTMP + paddd XMMTMP, MSG3 + sha256msg2 MSG2, MSG3 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 /* Rounds 60-63 */ - mova128 MSGTMP3, MSG + mova128 MSG3, MSG paddd 15*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - shuf128_32 $0x0E, MSG, MSG + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 /* Write hash values back in the correct order */ mova128 STATE0, XMMTMP -/* shufps takes dwords 0,1 from *2nd* operand, and dwords 2,3 from 1st one */ +/* shufps: dwords 0,1 of the result are selected from *2nd* operand, and dwords 2,3 from 1st operand */ /* --- -------------- HGDC -- FEBA */ shufps SHUF(3,2,3,2), STATE1, STATE0 /* ABCD */ shufps SHUF(1,0,1,0), STATE1, XMMTMP /* EFGH */ diff --git a/libbb/hash_sha256_hwaccel_x86-64.S b/libbb/hash_sha256_hwaccel_x86-64.S index 172c2eae26..ee3abbd1fb 100644 --- a/libbb/hash_sha256_hwaccel_x86-64.S +++ b/libbb/hash_sha256_hwaccel_x86-64.S @@ -34,24 +34,24 @@ #define MSG %xmm0 #define STATE0 %xmm1 #define STATE1 %xmm2 -#define MSGTMP0 %xmm3 -#define MSGTMP1 %xmm4 -#define MSGTMP2 %xmm5 -#define MSGTMP3 %xmm6 +#define MSG0 %xmm3 +#define MSG1 %xmm4 +#define MSG2 %xmm5 +#define MSG3 %xmm6 #define XMMTMP %xmm7 #define SAVE0 %xmm8 #define SAVE1 %xmm9 -#define SHUF(a,b,c,d) $(a+(b<<2)+(c<<4)+(d<<6)) +#define SHUF(a,b,c,d) $((a)+((b)<<2)+((c)<<4)+((d)<<6)) .balign 8 # allow decoders to fetch at least 2 first insns sha256_process_block64_shaNI: - movu128 80+0*16(%rdi), XMMTMP /* ABCD (little-endian dword order) */ + movu128 80+0*16(%rdi), XMMTMP /* ABCD (shown least-significant-dword-first) */ movu128 80+1*16(%rdi), STATE1 /* EFGH */ -/* shufps takes dwords 0,1 from *2nd* operand, and dwords 2,3 from 1st one */ +/* shufps: dwords 0,1 of the result are selected from *2nd* operand, and dwords 2,3 from 1st operand */ mova128 STATE1, STATE0 /* --- -------------- ABCD -- EFGH */ shufps SHUF(1,0,1,0), XMMTMP, STATE0 /* FEBA */ @@ -65,185 +65,203 @@ sha256_process_block64_shaNI: mova128 STATE0, SAVE0 mova128 STATE1, SAVE1 +// sha256rnds2 instruction uses only lower 64 bits of MSG. +// The code below needs to move upper 64 bits to lower 64 bits +// for the second sha256rnds2 invocation +// (what remains in upper bits does not matter). +// There are several ways to do it: +// movhlps MSG, MSG // abcd -> cdcd (3 bytes of code) +// shuf128_32 SHUF(2,3,n,n), MSG, MSG // abcd -> cdXX (4 bytes) +// punpckhqdq MSG, MSG // abcd -> cdcd (4 bytes) +// unpckhpd MSG, MSG // abcd -> cdcd (4 bytes) +// psrldq $8, MSG // abcd -> cd00 (5 bytes) +// palignr $8, MSG, MSG // abcd -> cdab (6 bytes, SSSE3 insn) +#define MOVE_UPPER64_DOWN(reg) movhlps reg, reg +//#define MOVE_UPPER64_DOWN(reg) shuf128_32 SHUF(2,3,0,0), reg, reg +//#define MOVE_UPPER64_DOWN(reg) punpckhqdq reg, reg +//#define MOVE_UPPER64_DOWN(reg) unpckhpd reg, reg +//#define MOVE_UPPER64_DOWN(reg) psrldq $8, reg +//#define MOVE_UPPER64_DOWN(reg) palignr $8, reg, reg + /* Rounds 0-3 */ movu128 0*16(DATA_PTR), MSG pshufb XMMTMP, MSG - mova128 MSG, MSGTMP0 + mova128 MSG, MSG0 paddd 0*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - shuf128_32 $0x0E, MSG, MSG + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 /* Rounds 4-7 */ movu128 1*16(DATA_PTR), MSG pshufb XMMTMP, MSG - mova128 MSG, MSGTMP1 + mova128 MSG, MSG1 paddd 1*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - shuf128_32 $0x0E, MSG, MSG + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP1, MSGTMP0 + sha256msg1 MSG1, MSG0 /* Rounds 8-11 */ movu128 2*16(DATA_PTR), MSG pshufb XMMTMP, MSG - mova128 MSG, MSGTMP2 + mova128 MSG, MSG2 paddd 2*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - shuf128_32 $0x0E, MSG, MSG + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP2, MSGTMP1 + sha256msg1 MSG2, MSG1 /* Rounds 12-15 */ movu128 3*16(DATA_PTR), MSG pshufb XMMTMP, MSG /* ...to here */ - mova128 MSG, MSGTMP3 + mova128 MSG, MSG3 paddd 3*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP3, XMMTMP - palignr $4, MSGTMP2, XMMTMP - paddd XMMTMP, MSGTMP0 - sha256msg2 MSGTMP3, MSGTMP0 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG3, XMMTMP + palignr $4, MSG2, XMMTMP + paddd XMMTMP, MSG0 + sha256msg2 MSG3, MSG0 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP3, MSGTMP2 + sha256msg1 MSG3, MSG2 /* Rounds 16-19 */ - mova128 MSGTMP0, MSG + mova128 MSG0, MSG paddd 4*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP0, XMMTMP - palignr $4, MSGTMP3, XMMTMP - paddd XMMTMP, MSGTMP1 - sha256msg2 MSGTMP0, MSGTMP1 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG0, XMMTMP + palignr $4, MSG3, XMMTMP + paddd XMMTMP, MSG1 + sha256msg2 MSG0, MSG1 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP0, MSGTMP3 + sha256msg1 MSG0, MSG3 /* Rounds 20-23 */ - mova128 MSGTMP1, MSG + mova128 MSG1, MSG paddd 5*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP1, XMMTMP - palignr $4, MSGTMP0, XMMTMP - paddd XMMTMP, MSGTMP2 - sha256msg2 MSGTMP1, MSGTMP2 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG1, XMMTMP + palignr $4, MSG0, XMMTMP + paddd XMMTMP, MSG2 + sha256msg2 MSG1, MSG2 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP1, MSGTMP0 + sha256msg1 MSG1, MSG0 /* Rounds 24-27 */ - mova128 MSGTMP2, MSG + mova128 MSG2, MSG paddd 6*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP2, XMMTMP - palignr $4, MSGTMP1, XMMTMP - paddd XMMTMP, MSGTMP3 - sha256msg2 MSGTMP2, MSGTMP3 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG2, XMMTMP + palignr $4, MSG1, XMMTMP + paddd XMMTMP, MSG3 + sha256msg2 MSG2, MSG3 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP2, MSGTMP1 + sha256msg1 MSG2, MSG1 /* Rounds 28-31 */ - mova128 MSGTMP3, MSG + mova128 MSG3, MSG paddd 7*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP3, XMMTMP - palignr $4, MSGTMP2, XMMTMP - paddd XMMTMP, MSGTMP0 - sha256msg2 MSGTMP3, MSGTMP0 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG3, XMMTMP + palignr $4, MSG2, XMMTMP + paddd XMMTMP, MSG0 + sha256msg2 MSG3, MSG0 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP3, MSGTMP2 + sha256msg1 MSG3, MSG2 /* Rounds 32-35 */ - mova128 MSGTMP0, MSG + mova128 MSG0, MSG paddd 8*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP0, XMMTMP - palignr $4, MSGTMP3, XMMTMP - paddd XMMTMP, MSGTMP1 - sha256msg2 MSGTMP0, MSGTMP1 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG0, XMMTMP + palignr $4, MSG3, XMMTMP + paddd XMMTMP, MSG1 + sha256msg2 MSG0, MSG1 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP0, MSGTMP3 + sha256msg1 MSG0, MSG3 /* Rounds 36-39 */ - mova128 MSGTMP1, MSG + mova128 MSG1, MSG paddd 9*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP1, XMMTMP - palignr $4, MSGTMP0, XMMTMP - paddd XMMTMP, MSGTMP2 - sha256msg2 MSGTMP1, MSGTMP2 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG1, XMMTMP + palignr $4, MSG0, XMMTMP + paddd XMMTMP, MSG2 + sha256msg2 MSG1, MSG2 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP1, MSGTMP0 + sha256msg1 MSG1, MSG0 /* Rounds 40-43 */ - mova128 MSGTMP2, MSG + mova128 MSG2, MSG paddd 10*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP2, XMMTMP - palignr $4, MSGTMP1, XMMTMP - paddd XMMTMP, MSGTMP3 - sha256msg2 MSGTMP2, MSGTMP3 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG2, XMMTMP + palignr $4, MSG1, XMMTMP + paddd XMMTMP, MSG3 + sha256msg2 MSG2, MSG3 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP2, MSGTMP1 + sha256msg1 MSG2, MSG1 /* Rounds 44-47 */ - mova128 MSGTMP3, MSG + mova128 MSG3, MSG paddd 11*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP3, XMMTMP - palignr $4, MSGTMP2, XMMTMP - paddd XMMTMP, MSGTMP0 - sha256msg2 MSGTMP3, MSGTMP0 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG3, XMMTMP + palignr $4, MSG2, XMMTMP + paddd XMMTMP, MSG0 + sha256msg2 MSG3, MSG0 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP3, MSGTMP2 + sha256msg1 MSG3, MSG2 /* Rounds 48-51 */ - mova128 MSGTMP0, MSG + mova128 MSG0, MSG paddd 12*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP0, XMMTMP - palignr $4, MSGTMP3, XMMTMP - paddd XMMTMP, MSGTMP1 - sha256msg2 MSGTMP0, MSGTMP1 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG0, XMMTMP + palignr $4, MSG3, XMMTMP + paddd XMMTMP, MSG1 + sha256msg2 MSG0, MSG1 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 - sha256msg1 MSGTMP0, MSGTMP3 + sha256msg1 MSG0, MSG3 /* Rounds 52-55 */ - mova128 MSGTMP1, MSG + mova128 MSG1, MSG paddd 13*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP1, XMMTMP - palignr $4, MSGTMP0, XMMTMP - paddd XMMTMP, MSGTMP2 - sha256msg2 MSGTMP1, MSGTMP2 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG1, XMMTMP + palignr $4, MSG0, XMMTMP + paddd XMMTMP, MSG2 + sha256msg2 MSG1, MSG2 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 /* Rounds 56-59 */ - mova128 MSGTMP2, MSG + mova128 MSG2, MSG paddd 14*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - mova128 MSGTMP2, XMMTMP - palignr $4, MSGTMP1, XMMTMP - paddd XMMTMP, MSGTMP3 - sha256msg2 MSGTMP2, MSGTMP3 - shuf128_32 $0x0E, MSG, MSG + mova128 MSG2, XMMTMP + palignr $4, MSG1, XMMTMP + paddd XMMTMP, MSG3 + sha256msg2 MSG2, MSG3 + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 /* Rounds 60-63 */ - mova128 MSGTMP3, MSG + mova128 MSG3, MSG paddd 15*16-8*16(SHA256CONSTANTS), MSG sha256rnds2 MSG, STATE0, STATE1 - shuf128_32 $0x0E, MSG, MSG + MOVE_UPPER64_DOWN(MSG) sha256rnds2 MSG, STATE1, STATE0 /* Add current hash values with previously saved */ @@ -252,7 +270,7 @@ sha256_process_block64_shaNI: /* Write hash values back in the correct order */ mova128 STATE0, XMMTMP -/* shufps takes dwords 0,1 from *2nd* operand, and dwords 2,3 from 1st one */ +/* shufps: dwords 0,1 of the result are selected from *2nd* operand, and dwords 2,3 from 1st operand */ /* --- -------------- HGDC -- FEBA */ shufps SHUF(3,2,3,2), STATE1, STATE0 /* ABCD */ shufps SHUF(1,0,1,0), STATE1, XMMTMP /* EFGH */ diff --git a/libbb/lineedit.c b/libbb/lineedit.c index 543a3f11c3..1ca21e9244 100644 --- a/libbb/lineedit.c +++ b/libbb/lineedit.c @@ -451,7 +451,7 @@ static void put_cur_glyph_and_inc_cursor(void) * have automargin (IOW: it is moving cursor to next line * by itself (which is wrong for VT-10x terminals)), * this will break things: there will be one extra empty line */ - puts("\r"); /* + implicit '\n' */ + fputs("\r\n", stderr); #else /* VT-10x terminals don't wrap cursor to next line when last char * on the line is printed - cursor stays "over" this char. @@ -1170,9 +1170,10 @@ static void showfiles(void) ); } if (ENABLE_UNICODE_SUPPORT) - puts(printable_string(matches[n])); + fputs(printable_string(matches[n]), stderr); else - puts(matches[n]); + fputs(matches[n], stderr); + bb_putchar_stderr('\n'); } } @@ -1405,8 +1406,8 @@ unsigned FAST_FUNC size_from_HISTFILESIZE(const char *hp) int size = MAX_HISTORY; if (hp) { size = atoi(hp); - if (size <= 0) - return 1; + if (size < 0) + return 0; if (size > MAX_HISTORY) return MAX_HISTORY; } @@ -1463,7 +1464,7 @@ void FAST_FUNC show_history(const line_input_t *st) if (!st) return; for (i = 0; i < st->cnt_history; i++) - fprintf(stderr, "%4d %s\n", i, st->history[i]); + printf("%4d %s\n", i, st->history[i]); } # if ENABLE_FEATURE_EDITING_SAVEHISTORY @@ -1500,18 +1501,21 @@ static void load_history(line_input_t *st_parm) /* NB: do not trash old history if file can't be opened */ fp = fopen_for_read(st_parm->hist_file); - if (fp) { - /* clean up old history */ - for (idx = st_parm->cnt_history; idx > 0;) { - idx--; - free(st_parm->history[idx]); - st_parm->history[idx] = NULL; - } + if (!fp) + return; + + /* clean up old history */ + for (idx = st_parm->cnt_history; idx > 0;) { + idx--; + free(st_parm->history[idx]); + st_parm->history[idx] = NULL; + } - /* fill temp_h[], retaining only last MAX_HISTORY lines */ - memset(temp_h, 0, sizeof(temp_h)); - idx = 0; - st_parm->cnt_history_in_file = 0; + /* fill temp_h[], retaining only last max_history lines */ + memset(temp_h, 0, sizeof(temp_h)); + idx = 0; + st_parm->cnt_history_in_file = 0; + if (st_parm->max_history != 0) { while ((line = xmalloc_fgetline(fp)) != NULL) { if (line[0] == '\0') { free(line); @@ -1524,52 +1528,62 @@ static void load_history(line_input_t *st_parm) if (idx == st_parm->max_history) idx = 0; } - fclose(fp); - - /* find first non-NULL temp_h[], if any */ - if (st_parm->cnt_history_in_file) { - while (temp_h[idx] == NULL) { - idx++; - if (idx == st_parm->max_history) - idx = 0; - } - } + } + fclose(fp); - /* copy temp_h[] to st_parm->history[] */ - for (i = 0; i < st_parm->max_history;) { - line = temp_h[idx]; - if (!line) - break; + /* find first non-NULL temp_h[], if any */ + if (st_parm->cnt_history_in_file != 0) { + while (temp_h[idx] == NULL) { idx++; if (idx == st_parm->max_history) idx = 0; - line_len = strlen(line); - if (line_len >= MAX_LINELEN) - line[MAX_LINELEN-1] = '\0'; - st_parm->history[i++] = line; } - st_parm->cnt_history = i; - if (ENABLE_FEATURE_EDITING_SAVE_ON_EXIT) - st_parm->cnt_history_in_file = i; } + + /* copy temp_h[] to st_parm->history[] */ + for (i = 0; i < st_parm->max_history;) { + line = temp_h[idx]; + if (!line) + break; + idx++; + if (idx == st_parm->max_history) + idx = 0; + line_len = strlen(line); + if (line_len >= MAX_LINELEN) + line[MAX_LINELEN-1] = '\0'; + st_parm->history[i++] = line; + } + st_parm->cnt_history = i; + if (ENABLE_FEATURE_EDITING_SAVE_ON_EXIT) + st_parm->cnt_history_in_file = i; } # if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT -void save_history(line_input_t *st) +void FAST_FUNC save_history(line_input_t *st) { FILE *fp; - if (!st || !st->hist_file) + /* bash compat: HISTFILE="" disables history saving */ + if (!st || !st->hist_file || !st->hist_file[0]) return; if (st->cnt_history <= st->cnt_history_in_file) - return; + return; /* no new entries were added */ + /* note: if st->max_history is 0, we do not abort: we truncate the history to 0 lines */ - fp = fopen(st->hist_file, "a"); + fp = fopen(st->hist_file, (st->max_history == 0 ? "w" : "a")); if (fp) { int i, fd; char *new_name; line_input_t *st_temp; + /* max_history==0 needs special-casing in general code, + * just handle it in a simpler way: */ + if (st->max_history == 0) { + /* fopen("w") already truncated it */ + fclose(fp); + return; + } + for (i = st->cnt_history_in_file; i < st->cnt_history; i++) fprintf(fp, "%s\n", st->history[i]); fclose(fp); @@ -1579,6 +1593,8 @@ void save_history(line_input_t *st) st_temp = new_line_input_t(st->flags); st_temp->hist_file = st->hist_file; st_temp->max_history = st->max_history; + /* load no more than max_history last lines */ + /* (in unlikely case that file disappeared, st_temp gets empty history) */ load_history(st_temp); /* write out temp file and replace hist_file atomically */ @@ -1602,13 +1618,13 @@ static void save_history(char *str) int fd; int len, len2; - if (!state->hist_file) + /* bash compat: HISTFILE="" disables history saving */ + if (!state->hist_file || !state->hist_file[0]) return; fd = open(state->hist_file, O_WRONLY | O_CREAT | O_APPEND, 0600); if (fd < 0) return; - xlseek(fd, 0, SEEK_END); /* paranoia */ len = strlen(str); str[len] = '\n'; /* we (try to) do atomic write */ len2 = full_write(fd, str, len + 1); @@ -1663,13 +1679,10 @@ static void remember_in_history(char *str) if (str[0] == '\0') return; i = state->cnt_history; - /* Don't save dupes */ - if (i && strcmp(state->history[i-1], str) == 0) + /* Don't save dups */ + if (i != 0 && strcmp(state->history[i-1], str) == 0) return; - free(state->history[state->max_history]); /* redundant, paranoia */ - state->history[state->max_history] = NULL; /* redundant, paranoia */ - /* If history[] is full, remove the oldest command */ /* we need to keep history[state->max_history] empty, hence >=, not > */ if (i >= state->max_history) { @@ -1682,7 +1695,7 @@ static void remember_in_history(char *str) state->cnt_history_in_file--; # endif } - /* i <= state->max_history-1 */ + /* i < state->max_history */ state->history[i++] = xstrdup(str); /* i <= state->max_history */ state->cur_history = i; @@ -2066,7 +2079,7 @@ static void parse_and_put_prompt(const char *prmt_ptr) if (c == 'w') break; cp = strrchr(pbuf, '/'); - if (cp) + if (cp && cp[1]) pbuf = (char*)cp + 1; break; // bb_process_escape_sequence does this now: @@ -2194,7 +2207,6 @@ static int lineedit_read_key(char *read_key_buffer, int timeout) errno = EINTR; return -1; } -//FIXME: still races here with signals, but small window to poll() inside read_key IF_FEATURE_EDITING_WINCH(S.ok_to_redraw = 1;) /* errno = 0; - read_key does this itself */ ic = read_key(STDIN_FILENO, read_key_buffer, timeout); diff --git a/libbb/poll_with_signals.c b/libbb/poll_with_signals.c new file mode 100644 index 0000000000..47419f240f --- /dev/null +++ b/libbb/poll_with_signals.c @@ -0,0 +1,48 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 2025 Denys Vlasenko + * + * Licensed under GPLv2, see file LICENSE in this source tree. + */ +//kbuild:lib-y += poll_with_signals.o + +#include "libbb.h" + +/* Shells, for example, need their line input and "read" builtin + * to be interruptible, and the naive handling of it a-la: + * if (bb_got_signal) { + * errno = EINTR; + * return -1; + * } + * poll(pfd, 1, -1); // signal here would set EINTR + * is racy. + * This is a bit heavy-handed, but safe wrt races: + */ +int FAST_FUNC check_got_signal_and_poll(struct pollfd pfd[1], int timeout) +{ + int n; + struct timespec tv; + sigset_t orig_mask; + + if (bb_got_signal) /* optimization */ + goto eintr; + + if (timeout >= 0) { + tv.tv_sec = timeout / 1000; + tv.tv_nsec = (timeout % 1000) * 1000000; + } + /* test bb_got_signal, then poll(), atomically wrt signals */ + sigfillset(&orig_mask); + sigprocmask2(SIG_BLOCK, &orig_mask); + if (bb_got_signal) { + sigprocmask2(SIG_SETMASK, &orig_mask); + eintr: + errno = EINTR; /* inform the caller that we got a signal */ + return -1; + } + n = ppoll(pfd, 1, timeout >= 0 ? &tv : NULL, &orig_mask); + sigprocmask2(SIG_SETMASK, &orig_mask); + return n; +} diff --git a/libbb/procps.c b/libbb/procps.c index f56b71b210..de640d29e0 100644 --- a/libbb/procps.c +++ b/libbb/procps.c @@ -109,7 +109,7 @@ void FAST_FUNC free_procps_scan(procps_status_t* sp) } #if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP -static unsigned long long fast_strtoull_16(char **endptr) +unsigned long long FAST_FUNC fast_strtoull_16(char **endptr) { unsigned char c; char *str = *endptr; @@ -130,7 +130,7 @@ static unsigned long long fast_strtoull_16(char **endptr) #if ENABLE_FEATURE_FAST_TOP || ENABLE_FEATURE_TOPMEM || ENABLE_PMAP /* We cut a lot of corners here for speed */ -static unsigned long fast_strtoul_10(char **endptr) +unsigned long FAST_FUNC fast_strtoul_10(char **endptr) { unsigned char c; char *str = *endptr; @@ -143,6 +143,24 @@ static unsigned long fast_strtoul_10(char **endptr) *endptr = str + 1; /* We skip trailing space! */ return n; } +# if LONG_MAX < LLONG_MAX +/* For VSZ, which can be very large */ +static unsigned long long fast_strtoull_10(char **endptr) +{ + unsigned char c; + char *str = *endptr; + unsigned long long n = *str - '0'; + + /* Need to stop on both ' ' and '\n' */ + while ((c = *++str) > ' ') + n = n*10 + (c - '0'); + + *endptr = str + 1; /* We skip trailing space! */ + return n; +} +# else +# define fast_strtoull_10(endptr) fast_strtoul_10(endptr) +# endif # if ENABLE_FEATURE_FAST_TOP static long fast_strtol_10(char **endptr) @@ -155,7 +173,7 @@ static long fast_strtol_10(char **endptr) } # endif -static char *skip_fields(char *str, int count) +char* FAST_FUNC skip_fields(char *str, int count) { do { while (*str++ != ' ') @@ -166,35 +184,25 @@ static char *skip_fields(char *str, int count) } #endif -#if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP -static char* skip_whitespace_if_prefixed_with(char *buf, const char *prefix) +#if ENABLE_FEATURE_TOPMEM +static NOINLINE void procps_read_smaps(pid_t pid, procps_status_t *sp) { - char *tp = is_prefixed_with(buf, prefix); - if (tp) { - tp = skip_whitespace(tp); - } - return tp; -} + // There is A LOT of /proc/PID/smaps data on a big system. + // Optimize this for speed, makes "top -m" faster. +//TODO large speedup: +//read /proc/PID/smaps_rollup (cumulative stats of all mappings, much faster) +//and /proc/PID/maps to get mapped_ro and mapped_rw (IOW: VSZ,VSZRW) -int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total, - void (*cb)(struct smaprec *, void *), void *data) -{ FILE *file; - struct smaprec currec; char filename[sizeof("/proc/%u/smaps") + sizeof(int)*3]; - char buf[PROCPS_BUFSIZE]; -#if !ENABLE_PMAP - void (*cb)(struct smaprec *, void *) = NULL; - void *data = NULL; -#endif + char buf[PROCPS_BUFSIZE] ALIGN4; sprintf(filename, "/proc/%u/smaps", (int)pid); file = fopen_for_read(filename); if (!file) - return 1; + return; - memset(&currec, 0, sizeof(currec)); while (fgets(buf, PROCPS_BUFSIZE, file)) { // Each mapping datum has this form: // f7d29000-f7d39000 rw-s FILEOFS M:m INODE FILENAME @@ -202,80 +210,60 @@ int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total, // Rss: nnn kB // ..... - char *tp, *p; - + char *tp; +#define bytes4 *(uint32_t*)buf +#define Priv PACK32_BYTES('P','r','i','v') +#define Shar PACK32_BYTES('S','h','a','r') #define SCAN(S, X) \ - if ((tp = skip_whitespace_if_prefixed_with(buf, S)) != NULL) { \ - total->X += currec.X = fast_strtoul_10(&tp); \ - continue; \ +if (memcmp(buf+4, S, sizeof(S)-1) == 0) { \ + tp = skip_whitespace(buf+4 + sizeof(S)-1); \ + sp->X += fast_strtoul_10(&tp); \ + continue; \ +} + if (bytes4 == Priv) { + SCAN("ate_Dirty:", private_dirty) + SCAN("ate_Clean:", private_clean) } - if (cb) { - SCAN("Pss:" , smap_pss ); - SCAN("Swap:" , smap_swap ); + if (bytes4 == Shar) { + SCAN("ed_Dirty:" , shared_dirty ) + SCAN("ed_Clean:" , shared_clean ) } - SCAN("Private_Dirty:", private_dirty); - SCAN("Private_Clean:", private_clean); - SCAN("Shared_Dirty:" , shared_dirty ); - SCAN("Shared_Clean:" , shared_clean ); +#undef bytes4 +#undef Priv +#undef Shar #undef SCAN tp = strchr(buf, '-'); if (tp) { // We reached next mapping - the line of this form: // f7d29000-f7d39000 rw-s FILEOFS M:m INODE FILENAME - if (cb) { - /* If we have a previous record, there's nothing more - * for it, call the callback and clear currec - */ - if (currec.smap_size) - cb(&currec, data); - free(currec.smap_name); - } - memset(&currec, 0, sizeof(currec)); + char *rwx; + unsigned long long sz; *tp = ' '; tp = buf; - currec.smap_start = fast_strtoull_16(&tp); - currec.smap_size = (fast_strtoull_16(&tp) - currec.smap_start) >> 10; - - strncpy(currec.smap_mode, tp, sizeof(currec.smap_mode)-1); - + sz = fast_strtoull_16(&tp); // start + sz = (fast_strtoull_16(&tp) - sz) >> 10; // end - start + // tp -> "rw-s" string + rwx = tp; // skipping "rw-s FILEOFS M:m INODE " tp = skip_whitespace(skip_fields(tp, 4)); - // filter out /dev/something (something != zero) - if (!is_prefixed_with(tp, "/dev/") || strcmp(tp, "/dev/zero\n") == 0) { - if (currec.smap_mode[1] == 'w') { - currec.mapped_rw = currec.smap_size; - total->mapped_rw += currec.smap_size; - } else if (currec.smap_mode[1] == '-') { - currec.mapped_ro = currec.smap_size; - total->mapped_ro += currec.smap_size; - } + // if not a device memory mapped... + if (memcmp(tp, "/dev/", 5) != 0 // not "/dev/something" + || strcmp(tp + 5, "zero\n") == 0 // or is "/dev/zero" (which isn't a device) + ) { + if (rwx[1] == 'w') + sp->mapped_rw += sz; + else if (rwx[0] == 'r' || rwx[2] == 'x') + sp->mapped_ro += sz; + // else: seen "---p" mappings (mmap guard gaps?), + // do NOT account these as VSZ, they aren't really } - if (strcmp(tp, "[stack]\n") == 0) - total->stack += currec.smap_size; - if (cb) { - p = skip_non_whitespace(tp); - if (p == tp) { - currec.smap_name = xstrdup(" [ anon ]"); - } else { - *p = '\0'; - currec.smap_name = xstrdup(tp); - } - } - total->smap_size += currec.smap_size; + sp->stack += sz; } } fclose(file); - - if (cb) { - if (currec.smap_size) - cb(&currec, data); - free(currec.smap_name); - } - - return 0; } #endif @@ -370,7 +358,8 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) char *cp, *comm1; int tty; #if !ENABLE_FEATURE_FAST_TOP - unsigned long vsz, rss; + unsigned long long vsz; + unsigned long rss; #endif /* see proc(5) for some details on this */ strcpy(filename_tail, "stat"); @@ -396,7 +385,7 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) "%ld " /* nice */ "%*s %*s " /* timeout, it_real_value */ "%lu " /* start_time */ - "%lu " /* vsize */ + "%llu " /* vsize - can be very large */ "%lu " /* rss */ # if ENABLE_FEATURE_TOP_SMP_PROCESS "%*s %*s %*s %*s %*s %*s " /*rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip */ @@ -449,7 +438,7 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) cp = skip_fields(cp, 2); /* timeout, it_real_value */ sp->start_time = fast_strtoul_10(&cp); /* vsz is in bytes and we want kb */ - sp->vsz = fast_strtoul_10(&cp) >> 10; + sp->vsz = fast_strtoull_10(&cp) >> 10; /* vsz is in bytes but rss is in *PAGES*! Can you believe that? */ sp->rss = fast_strtoul_10(&cp) << sp->shift_pages_to_kb; # if ENABLE_FEATURE_TOP_SMP_PROCESS @@ -483,7 +472,7 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) #if ENABLE_FEATURE_TOPMEM if (flags & PSSCAN_SMAPS) - procps_read_smaps(pid, &sp->smaps, NULL, NULL); + procps_read_smaps(pid, sp); #endif /* TOPMEM */ #if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS if (flags & PSSCAN_RUIDGID) { @@ -566,36 +555,45 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) return sp; } -void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm) +int FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm) { int sz; char filename[sizeof("/proc/%u/cmdline") + sizeof(int)*3]; sprintf(filename, "/proc/%u/cmdline", pid); sz = open_read_close(filename, buf, col - 1); + if (sz < 0) + return sz; if (sz > 0) { - const char *base; + const char *program_basename; int comm_len; buf[sz] = '\0'; while (--sz >= 0 && buf[sz] == '\0') continue; - /* Prevent basename("process foo/bar") = "bar" */ - strchrnul(buf, ' ')[0] = '\0'; - base = bb_basename(buf); /* before we replace argv0's NUL with space */ + + /* Find "program" in "[-][/PATH/TO/]program" */ + strchrnul(buf, ' ')[0] = '\0'; /* prevent basename("program foo/bar") = "bar" */ + program_basename = bb_basename(buf[0] == '-' ? buf + 1 : buf); + /* ^^^ note: must do it *before* replacing argv0's NUL with space */ + + /* Prevent stuff like this: + * echo 'sleep 999; exit' >`printf '\ec'`; sh ?c + * messing up top and ps output (or worse). + * This also replaces NULs with spaces, converting + * list of NUL-strings into one string. + */ while (sz >= 0) { if ((unsigned char)(buf[sz]) < ' ') - buf[sz] = ' '; + buf[sz] = (buf[sz] ? /*ctrl*/'?' : /*NUL*/' '); sz--; } - if (base[0] == '-') /* "-sh" (login shell)? */ - base++; /* If comm differs from argv0, prepend "{comm} ". * It allows to see thread names set by prctl(PR_SET_NAME). */ if (!comm) - return; + return 0; comm_len = strlen(comm); /* Why compare up to comm_len, not COMM_LEN-1? * Well, some processes rewrite argv, and use _spaces_ there @@ -603,19 +601,20 @@ void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm) * I prefer to still treat argv0 "process foo bar" * as 'equal' to comm "process". */ - if (strncmp(base, comm, comm_len) != 0) { + if (strncmp(program_basename, comm, comm_len) != 0) { comm_len += 3; if (col > comm_len) memmove(buf + comm_len, buf, col - comm_len); snprintf(buf, col, "{%s}", comm); if (col <= comm_len) - return; + return 0; buf[comm_len - 1] = ' '; buf[col - 1] = '\0'; } } else { snprintf(buf, col, "[%s]", comm ? comm : "?"); } + return 0; } /* from kernel: diff --git a/libbb/pw_ascii64.c b/libbb/pw_ascii64.c new file mode 100644 index 0000000000..3993932cab --- /dev/null +++ b/libbb/pw_ascii64.c @@ -0,0 +1,91 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ + +/* Returns >=64 for invalid chars */ +int FAST_FUNC a2i64(char c) +{ + unsigned char ch = c; + if (ch >= 'a') + /* "a..z" to 38..63 */ + /* anything after "z": positive int >= 64 */ + return (ch - 'a' + 38); + + if (ch > 'Z') + /* after "Z" but before "a": positive byte >= 64 */ + return ch; + + if (ch >= 'A') + /* "A..Z" to 12..37 */ + return (ch - 'A' + 12); + + if (ch > '9') + return 64; + + /* "./0123456789" to 0,1,2..11 */ + /* anything before "." becomes positive byte >= 64 */ + return (unsigned char)(ch - '.'); +} + +/* 0..63 -> + * "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + */ +int FAST_FUNC i2a64(int i) +{ + i &= 0x3f; + + i += '.'; + /* the above maps 0..11 to "./0123456789": + * ACSII codes of "./" are ('0'-2) and ('0'-1) */ + + if (i > '9') + i += ('A' - '9' - 1); + if (i > 'Z') + i += ('a' - 'Z' - 1); + return i; +} + +char* FAST_FUNC +num2str64_lsb_first(char *s, unsigned v, int n) +{ + while (--n >= 0) { + *s++ = i2a64(v); + v >>= 6; + } + return s; +} + +static void +num2str64_4chars_msb_first(char *s, unsigned v) +{ + *s++ = i2a64(v >> 18); /* bits 23..18 */ + *s++ = i2a64(v >> 12); /* bits 17..12 */ + *s++ = i2a64(v >> 6); /* bits 11..6 */ + *s = i2a64(v); /* bits 5..0 */ +} + +int FAST_FUNC crypt_make_rand64encoded(char *p, int cnt /*, int x */) +{ + /* was: x += ... */ + unsigned x = getpid() + monotonic_us(); + do { + /* x = (x*1664525 + 1013904223) % 2^32 generator is lame + * (low-order bit is not "random", etc...), + * but for our purposes it is good enough */ + x = x*1664525 + 1013904223; + /* BTW, Park and Miller's "minimal standard generator" is + * x = x*16807 % ((2^31)-1) + * It has no problem with visibly alternating lowest bit + * but is also weak in cryptographic sense + needs div, + * which needs more code (and slower) on many CPUs */ + *p++ = i2a64(x >> 16); + *p++ = i2a64(x >> 22); + } while (--cnt); + *p = '\0'; + return x; +} diff --git a/libbb/pw_encrypt.c b/libbb/pw_encrypt.c index 3463fd95ba..93653de9ff 100644 --- a/libbb/pw_encrypt.c +++ b/libbb/pw_encrypt.c @@ -13,48 +13,11 @@ #endif #include "libbb.h" -/* static const uint8_t ascii64[] ALIGN1 = - * "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - */ - -static int i64c(int i) -{ - i &= 0x3f; - if (i == 0) - return '.'; - if (i == 1) - return '/'; - if (i < 12) - return ('0' - 2 + i); - if (i < 38) - return ('A' - 12 + i); - return ('a' - 38 + i); -} - -int FAST_FUNC crypt_make_salt(char *p, int cnt /*, int x */) -{ - /* was: x += ... */ - unsigned x = getpid() + monotonic_us(); - do { - /* x = (x*1664525 + 1013904223) % 2^32 generator is lame - * (low-order bit is not "random", etc...), - * but for our purposes it is good enough */ - x = x*1664525 + 1013904223; - /* BTW, Park and Miller's "minimal standard generator" is - * x = x*16807 % ((2^31)-1) - * It has no problem with visibly alternating lowest bit - * but is also weak in cryptographic sense + needs div, - * which needs more code (and slower) on many CPUs */ - *p++ = i64c(x >> 16); - *p++ = i64c(x >> 22); - } while (--cnt); - *p = '\0'; - return x; -} +#include "pw_ascii64.c" char* FAST_FUNC crypt_make_pw_salt(char salt[MAX_PW_SALT_LEN], const char *algo) { - int len = 2/2; + int len = 2 / 2; char *salt_ptr = salt; /* Standard chpasswd uses uppercase algos ("MD5", not "md5"). @@ -67,28 +30,61 @@ char* FAST_FUNC crypt_make_pw_salt(char salt[MAX_PW_SALT_LEN], const char *algo) *salt_ptr++ = '$'; #if !ENABLE_USE_BB_CRYPT || ENABLE_USE_BB_CRYPT_SHA if ((algo[0]|0x20) == 's') { /* sha */ - salt[1] = '5' + (strcasecmp(algo, "sha512") == 0); - len = 16/2; + salt[1] = '5' + (strncasecmp(algo, "sha512", 6) == 0); + len = 16 / 2; + } +#endif +#if !ENABLE_USE_BB_CRYPT || ENABLE_USE_BB_CRYPT_YES + if ((algo[0]|0x20) == 'y') { /* yescrypt */ + int rnd; + salt[1] = 'y'; +// The "j9T$" below is the default "yescrypt parameters" encoded by yescrypt_encode_params_r(): +//shadow-4.17.4/src/passwd.c +// salt = crypt_make_salt(NULL, NULL); +//shadow-4.17.4/lib/salt.c +//const char *crypt_make_salt(const char *meth, void *arg) +// if (streq(method, "YESCRYPT")) { +// MAGNUM(result, 'y'); +// salt_len = YESCRYPT_SALT_SIZE; // 24 +// rounds = YESCRYPT_get_salt_cost(arg); // always Y_COST_DEFAULT == 5 for NULL arg +// YESCRYPT_salt_cost_to_buf(result, rounds); // always "j9T$" +// char *retval = crypt_gensalt(result, rounds, NULL, 0); +//libxcrypt-4.4.38/lib/crypt-yescrypt.c +//void gensalt_yescrypt_rn (unsigned long count, +// const uint8_t *rbytes, size_t nrbytes, +// uint8_t *output, size_t o_size) +// yescrypt_params_t params = { +// .flags = YESCRYPT_DEFAULTS, +// .p = 1, +// }; +// if (count < 3) ... else +// params.r = 32; // N in 4KiB +// params.N = 1ULL << (count + 7); // 3 -> 1024, 4 -> 2048, ... 11 -> 262144 +// yescrypt_encode_params_r(¶ms, rbytes, nrbytes, outbuf, o_size) // always "$y$j9T$" + len = 22 / 2; + salt_ptr = stpcpy(salt_ptr, "j9T$"); + /* append 2*len random chars */ + rnd = crypt_make_rand64encoded(salt_ptr, len); + /* fix up last char: it must be in 0..3 range (encoded as one of "./01"). + * IOW: salt_ptr[20..21] encode 16th random byte, must not be > 0xff. + * Without this, we can generate salts which are rejected + * by implementations with more strict salt length check. + */ + salt_ptr[21] = i2a64(rnd & 3); + /* For "mkpasswd -m yescrypt PASS j9T$" use case, + * "j9T$" is considered part of salt, + * need to return pointer to 'j'. Without -4, + * we'd end up using "j9T$j9T$" as salt. + */ + return salt_ptr - 4; } #endif } - crypt_make_salt(salt_ptr, len); + crypt_make_rand64encoded(salt_ptr, len); /* appends 2*len random chars */ return salt_ptr; } #if ENABLE_USE_BB_CRYPT - -static char* -to64(char *s, unsigned v, int n) -{ - while (--n >= 0) { - /* *s++ = ascii64[v & 0x3f]; */ - *s++ = i64c(v); - v >>= 6; - } - return s; -} - /* * DES and MD5 crypt implementations are taken from uclibc. * They were modified to not use static buffers. @@ -99,6 +95,9 @@ to64(char *s, unsigned v, int n) #if ENABLE_USE_BB_CRYPT_SHA #include "pw_encrypt_sha.c" #endif +#if ENABLE_USE_BB_CRYPT_YES +#include "pw_encrypt_yes.c" +#endif /* Other advanced crypt ids (TODO?): */ /* $2$ or $2a$: Blowfish */ @@ -109,13 +108,17 @@ static struct des_ctx *des_ctx; /* my_crypt returns malloc'ed data */ static char *my_crypt(const char *key, const char *salt) { - /* MD5 or SHA? */ + /* "$x$...." string? */ if (salt[0] == '$' && salt[1] && salt[2] == '$') { if (salt[1] == '1') return md5_crypt(xzalloc(MD5_OUT_BUFSIZE), (unsigned char*)key, (unsigned char*)salt); #if ENABLE_USE_BB_CRYPT_SHA if (salt[1] == '5' || salt[1] == '6') return sha_crypt((char*)key, (char*)salt); +#endif +#if ENABLE_USE_BB_CRYPT_YES + if (salt[1] == 'y') + return yes_crypt(key, salt); #endif } diff --git a/libbb/pw_encrypt_des.c b/libbb/pw_encrypt_des.c index fe8237cfef..ca8aa9bccf 100644 --- a/libbb/pw_encrypt_des.c +++ b/libbb/pw_encrypt_des.c @@ -186,39 +186,9 @@ static const uint8_t pbox[32] ALIGN1 = { 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 }; -static const uint32_t bits32[32] ALIGN4 = { - 0x80000000, 0x40000000, 0x20000000, 0x10000000, - 0x08000000, 0x04000000, 0x02000000, 0x01000000, - 0x00800000, 0x00400000, 0x00200000, 0x00100000, - 0x00080000, 0x00040000, 0x00020000, 0x00010000, - 0x00008000, 0x00004000, 0x00002000, 0x00001000, - 0x00000800, 0x00000400, 0x00000200, 0x00000100, - 0x00000080, 0x00000040, 0x00000020, 0x00000010, - 0x00000008, 0x00000004, 0x00000002, 0x00000001 -}; - static const uint8_t bits8[8] ALIGN1 = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; -static int -ascii_to_bin(char ch) -{ - if (ch > 'z') - return 0; - if (ch >= 'a') - return (ch - 'a' + 38); - if (ch > 'Z') - return 0; - if (ch >= 'A') - return (ch - 'A' + 12); - if (ch > '9') - return 0; - if (ch >= '.') - return (ch - '.'); - return 0; -} - - /* Static stuff that stays resident and doesn't change after * being initialized, and therefore doesn't need to be made * reentrant. */ @@ -354,11 +324,18 @@ des_init(struct des_ctx *ctx, const struct const_des_ctx *cctx) int i, j, b, k, inbit, obit; uint32_t p; const uint32_t *bits28, *bits24; + uint32_t bits32[32]; if (!ctx) ctx = xmalloc(sizeof(*ctx)); const_ctx = cctx; + p = 0x80000000U; + for (i = 0; p; i++) { + bits32[i] = p; + p >>= 1; + } + #if USE_REPETITIVE_SPEEDUP old_rawkey0 = old_rawkey1 = 0; old_salt = 0; @@ -694,21 +671,6 @@ do_des(struct des_ctx *ctx, /*uint32_t l_in, uint32_t r_in,*/ uint32_t *l_out, u #define DES_OUT_BUFSIZE 21 -static void -to64_msb_first(char *s, unsigned v) -{ -#if 0 - *s++ = ascii64[(v >> 18) & 0x3f]; /* bits 23..18 */ - *s++ = ascii64[(v >> 12) & 0x3f]; /* bits 17..12 */ - *s++ = ascii64[(v >> 6) & 0x3f]; /* bits 11..6 */ - *s = ascii64[v & 0x3f]; /* bits 5..0 */ -#endif - *s++ = i64c(v >> 18); /* bits 23..18 */ - *s++ = i64c(v >> 12); /* bits 17..12 */ - *s++ = i64c(v >> 6); /* bits 11..6 */ - *s = i64c(v); /* bits 5..0 */ -} - static char * NOINLINE des_crypt(struct des_ctx *ctx, char output[DES_OUT_BUFSIZE], @@ -740,44 +702,28 @@ des_crypt(struct des_ctx *ctx, char output[DES_OUT_BUFSIZE], */ output[0] = salt_str[0]; output[1] = salt_str[1]; - salt = (ascii_to_bin(salt_str[1]) << 6) - | ascii_to_bin(salt_str[0]); + + salt = a2i64(salt_str[0]); + if (salt >= 64) + return NULL; /* bad salt char */ + salt |= (a2i64(salt_str[1]) << 6); + if (salt >= (64 << 6)) + return NULL; /* bad salt char */ setup_salt(ctx, salt); /* set ctx->saltbits for do_des() */ /* Do it. */ do_des(ctx, /*0, 0,*/ &r0, &r1, 25 /* count */); /* Now encode the result. */ -#if 0 -{ - uint32_t l = (r0 >> 8); - q = (uint8_t *)output + 2; - *q++ = ascii64[(l >> 18) & 0x3f]; /* bits 31..26 of r0 */ - *q++ = ascii64[(l >> 12) & 0x3f]; /* bits 25..20 of r0 */ - *q++ = ascii64[(l >> 6) & 0x3f]; /* bits 19..14 of r0 */ - *q++ = ascii64[l & 0x3f]; /* bits 13..8 of r0 */ - l = ((r0 << 16) | (r1 >> 16)); - *q++ = ascii64[(l >> 18) & 0x3f]; /* bits 7..2 of r0 */ - *q++ = ascii64[(l >> 12) & 0x3f]; /* bits 1..2 of r0 and 31..28 of r1 */ - *q++ = ascii64[(l >> 6) & 0x3f]; /* bits 27..22 of r1 */ - *q++ = ascii64[l & 0x3f]; /* bits 21..16 of r1 */ - l = r1 << 2; - *q++ = ascii64[(l >> 12) & 0x3f]; /* bits 15..10 of r1 */ - *q++ = ascii64[(l >> 6) & 0x3f]; /* bits 9..4 of r1 */ - *q++ = ascii64[l & 0x3f]; /* bits 3..0 of r1 + 00 */ - *q = 0; -} -#else /* Each call takes low-order 24 bits and stores 4 chars */ /* bits 31..8 of r0 */ - to64_msb_first(output + 2, (r0 >> 8)); + num2str64_4chars_msb_first(output + 2, (r0 >> 8)); /* bits 7..0 of r0 and 31..16 of r1 */ - to64_msb_first(output + 6, (r0 << 16) | (r1 >> 16)); + num2str64_4chars_msb_first(output + 6, (r0 << 16) | (r1 >> 16)); /* bits 15..0 of r1 and two zero bits (plus extra zero byte) */ - to64_msb_first(output + 10, (r1 << 8)); + num2str64_4chars_msb_first(output + 10, (r1 << 8)); /* extra zero byte is encoded as '.', fixing it */ output[13] = '\0'; -#endif return output; } diff --git a/libbb/pw_encrypt_md5.c b/libbb/pw_encrypt_md5.c index 1e52ecaea8..92d039f960 100644 --- a/libbb/pw_encrypt_md5.c +++ b/libbb/pw_encrypt_md5.c @@ -149,9 +149,9 @@ md5_crypt(char result[MD5_OUT_BUFSIZE], const unsigned char *pw, const unsigned final[16] = final[5]; for (i = 0; i < 5; i++) { unsigned l = (final[i] << 16) | (final[i+6] << 8) | final[i+12]; - p = to64(p, l, 4); + p = num2str64_lsb_first(p, l, 4); } - p = to64(p, final[11], 2); + p = num2str64_lsb_first(p, final[11], 2); *p = '\0'; /* Don't leave anything around in vm they could use. */ diff --git a/libbb/pw_encrypt_sha.c b/libbb/pw_encrypt_sha.c index 5457d7ab6a..695a5c07fa 100644 --- a/libbb/pw_encrypt_sha.c +++ b/libbb/pw_encrypt_sha.c @@ -84,8 +84,7 @@ sha_crypt(/*const*/ char *key_data, /*const*/ char *salt_data) as a scratch space later. */ salt_data = xstrndup(salt_data, salt_len); /* add "salt$" to result */ - strcpy(resptr, salt_data); - resptr += salt_len; + resptr = stpcpy(resptr, salt_data); *resptr++ = '$'; /* key data doesn't need much processing */ key_len = strlen(key_data); @@ -198,7 +197,7 @@ sha_crypt(/*const*/ char *key_data, /*const*/ char *salt_data) #define b64_from_24bit(B2, B1, B0, N) \ do { \ unsigned w = ((B2) << 16) | ((B1) << 8) | (B0); \ - resptr = to64(resptr, w, N); \ + resptr = num2str64_lsb_first(resptr, w, N); \ } while (0) if (_32or64 == 32) { /* sha256 */ unsigned i = 0; diff --git a/libbb/pw_encrypt_yes.c b/libbb/pw_encrypt_yes.c new file mode 100644 index 0000000000..50bd064189 --- /dev/null +++ b/libbb/pw_encrypt_yes.c @@ -0,0 +1,24 @@ +/* + * Utility routines. + * + * Copyright (C) 2025 by Denys Vlasenko + * + * Licensed under GPLv2, see file LICENSE in this source tree. + */ +#include "yescrypt/alg-yescrypt.h" + +static char * +yes_crypt(const char *passwd, const char *salt_data) +{ + /* prefix, '$', hash, NUL */ + char buf[YESCRYPT_PREFIX_LEN + 1 + YESCRYPT_HASH_LEN + 1]; + char *retval; + + retval = yescrypt_r( + (const uint8_t *)passwd, strlen(passwd), + (const uint8_t *)salt_data, + buf, sizeof(buf)); + /* The returned value is either buf[], or NULL on error */ + + return xstrdup(retval); +} diff --git a/libbb/read_key.c b/libbb/read_key.c index cf8ed411e8..3df9769f75 100644 --- a/libbb/read_key.c +++ b/libbb/read_key.c @@ -11,7 +11,7 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) { - struct pollfd pfd; + struct pollfd pfd[1]; const char *seq; int n; @@ -112,8 +112,8 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) 0 }; - pfd.fd = fd; - pfd.events = POLLIN; + pfd->fd = fd; + pfd->events = POLLIN; buffer++; /* saved chars counter is in buffer[-1] now */ @@ -121,12 +121,16 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) errno = 0; n = (unsigned char)buffer[-1]; if (n == 0) { - /* If no data, wait for input. - * If requested, wait TIMEOUT ms. TIMEOUT = -1 is useful - * if fd can be in non-blocking mode. - */ + /* No data. Wait for input. */ + + /* timeout == -2 means "do not poll". Else: */ if (timeout >= -1) { - n = poll(&pfd, 1, timeout); + /* We must poll even if timeout == -1: + * we want to be interrupted if signal arrives, + * regardless of SA_RESTART-ness of that signal! + */ + /* test bb_got_signal, then poll(), atomically wrt signals */ + n = check_got_signal_and_poll(pfd, timeout); if (n < 0 && errno == EINTR) return n; if (n == 0) { @@ -135,6 +139,7 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) return -1; } } + /* It is tempting to read more than one byte here, * but it breaks pasting. Example: at shell prompt, * user presses "c","a","t" and then pastes "\nline\n". @@ -173,7 +178,7 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) * so if we block for long it's not really an escape sequence. * Timeout is needed to reconnect escape sequences * split up by transmission over a serial console. */ - if (safe_poll(&pfd, 1, 50) == 0) { + if (safe_poll(pfd, 1, 50) == 0) { /* No more data! * Array is sorted from shortest to longest, * we can't match anything later in array - @@ -222,7 +227,7 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) * n = bytes read. Try to read more until we time out. */ while (n < KEYCODE_BUFFER_SIZE-1) { /* 1 for count byte at buffer[-1] */ - if (safe_poll(&pfd, 1, 50) == 0) { + if (safe_poll(pfd, 1, 50) == 0) { /* No more data! */ break; } diff --git a/libbb/replace.c b/libbb/replace.c index 6183d3e6fa..bc26b04cc9 100644 --- a/libbb/replace.c +++ b/libbb/replace.c @@ -46,3 +46,17 @@ char* FAST_FUNC xmalloc_substitute_string(const char *src, int count, const char //dbg_msg("subst9:'%s'", buf); return buf; } + +#if 0 /* inlined in libbb.h */ +/* Returns strlen as a bonus */ +size_t FAST_FUNC replace_char(char *str, char from, char to) +{ + char *p = str; + while (*p) { + if (*p == from) + *p = to; + p++; + } + return p - str; +} +#endif diff --git a/libbb/signals.c b/libbb/signals.c index 0bebc847d0..87d40bd24d 100644 --- a/libbb/signals.c +++ b/libbb/signals.c @@ -24,13 +24,6 @@ int FAST_FUNC sigaction_set(int signum, const struct sigaction *act) return sigaction(signum, act, NULL); } -int FAST_FUNC sigprocmask_allsigs(int how) -{ - sigset_t set; - sigfillset(&set); - return sigprocmask(how, &set, NULL); -} - int FAST_FUNC sigprocmask2(int how, sigset_t *set) { // Grr... gcc 8.1.1: @@ -41,6 +34,25 @@ int FAST_FUNC sigprocmask2(int how, sigset_t *set) return sigprocmask(how, set, oset); } +int FAST_FUNC sigprocmask_allsigs(int how) +{ + sigset_t set; + sigfillset(&set); + return sigprocmask2(how, &set); +} + +int FAST_FUNC sigblockall(sigset_t *set) +{ +#if 0 /* nope. set can be NULL */ + sigfillset(set); + return sigprocmask2(SIG_SETMASK, set); +#else + sigset_t mask; + sigfillset(&mask); + return sigprocmask(SIG_SETMASK, &mask, set); +#endif +} + void FAST_FUNC bb_signals(int sigs, void (*f)(int)) { int sig_no = 0; @@ -82,7 +94,7 @@ void FAST_FUNC sig_block(int sig) sigset_t ss; sigemptyset(&ss); sigaddset(&ss, sig); - sigprocmask(SIG_BLOCK, &ss, NULL); + sigprocmask2(SIG_BLOCK, &ss); } void FAST_FUNC sig_unblock(int sig) @@ -90,7 +102,7 @@ void FAST_FUNC sig_unblock(int sig) sigset_t ss; sigemptyset(&ss); sigaddset(&ss, sig); - sigprocmask(SIG_UNBLOCK, &ss, NULL); + sigprocmask2(SIG_UNBLOCK, &ss); } void FAST_FUNC wait_for_any_sig(void) diff --git a/libbb/xfuncs_printf.c b/libbb/xfuncs_printf.c index 842d10cd2f..d413c81e80 100644 --- a/libbb/xfuncs_printf.c +++ b/libbb/xfuncs_printf.c @@ -696,6 +696,14 @@ pid_t FAST_FUNC xfork(void) } #endif +#if 0 +/* DO NOT DO THIS. This can't be a function. + * It works on some arches (x86) but fails on others (ppc64le: SEGV). + * The reason is: the child returns from this function + * and likely pops up the stack in an arch-dependent way. + * When child eventually exits or execs, parent "reappear" + * in the now-unwound stack (!) and the behavior is undefined. + */ void FAST_FUNC xvfork_parent_waits_and_exits(void) { pid_t pid; @@ -711,6 +719,7 @@ void FAST_FUNC xvfork_parent_waits_and_exits(void) } /* Child continues */ } +#endif // Useful when we do know that pid is valid, and we just want to wait // for it to exit. Not existing pid is fatal. waitpid() status is not returned. diff --git a/libbb/yescrypt/Kbuild.src b/libbb/yescrypt/Kbuild.src new file mode 100644 index 0000000000..a61211a292 --- /dev/null +++ b/libbb/yescrypt/Kbuild.src @@ -0,0 +1,9 @@ +# Makefile for busybox +# +# Copyright (C) 2025 by Denys Vlasenko +# +# Licensed under GPLv2, see file LICENSE in this source tree. + +lib-y:= + +INSERT diff --git a/libbb/yescrypt/PARAMETERS b/libbb/yescrypt/PARAMETERS new file mode 100644 index 0000000000..d9f5d24e6c --- /dev/null +++ b/libbb/yescrypt/PARAMETERS @@ -0,0 +1,196 @@ + Optimal yescrypt configuration. + +yescrypt is very flexible, but configuring it optimally is complicated. +Here are some guidelines to simplify near-optimal configuration. We +start by listing the parameters and their typical values, and then give +currently recommended parameter sets by use case. + + + Parameters and their typical values. + +Set flags (yescrypt flavor) to YESCRYPT_DEFAULTS to use the currently +recommended flavor. (Other flags values exist for compatibility and for +specialized cases where you think you know what you're doing.) + +Set N (block count) based on target memory usage and running time, as +well as on the value of r (block size in 128 byte units). N must be a +power of two. + +Set r (block size) to 8 (so that N is in KiB, which is convenient) or to +another small value (if more optimal or for fine-tuning of the total +size and/or running time). Reasonable values for r are from 8 to 96. + +Set p (parallelism) to 1 meaning no thread-level parallelism within one +computation of yescrypt. (Use of thread-level parallelism within +yescrypt makes sense for ROM initialization and for key derivation at +high memory usage, but usually not for password hashing where +parallelism is available through concurrent authentication attempts. +Don't use p > 1 unnecessarily.) + +Set t (time) to 0 to use the optimal running time for a given memory +usage. This will allow you to maximize the memory usage (the value of +N*r) while staying within your running time constraints. (Non-zero t +makes sense in specialized cases where you can't afford higher memory +usage but can afford more time.) + +Set g (upgrades) to 0 because there have been no hash upgrades yet. + +Set NROM (block count of ROM) to 0 unless you use a ROM (see below). +NROM must be a power of two. + + + Password hashing for user authentication, no ROM. + +Small and fast (memory usage 2 MiB, performance like bcrypt cost 2^5 - +latency 2-3 ms and throughput 10,000+ per second on a 16-core server): + +flags = YESCRYPT_DEFAULTS, N = 2048, r = 8, p = 1, t = 0, g = 0, NROM = 0 + +Large and slow (memory usage 16 MiB, performance like bcrypt cost 2^8 - +latency 10-30 ms and throughput 1000+ per second on a 16-core server): + +flags = YESCRYPT_DEFAULTS, N = 4096, r = 32, p = 1, t = 0, g = 0, NROM = 0 + +Of course, even heavier and slower settings are possible, if affordable. +Simply double the value of N as many times as needed. Since N must be a +power of two, you may use r (in the range of 8 to 32) or/and t (in the +range of 0 to 2) for fine-tuning the running time, but first bring N to +the maximum you can afford. If this feels too complicated, just use one +of the two parameter sets given above (preferably the second) as-is. + + + Password hashing for user authentication, with ROM. + +It's similar to the above, except that you need to adjust r, set NROM, +and initialize the ROM. + +First decide on a ROM size, such as making it a large portion of your +dedicated authentication servers' RAM sizes. Since NROM (block count) +must be a power of two, you might need to choose r (block size) based on +how your desired ROM size corresponds to a power of two. Also tuning +for performance on current hardware, you'll likely end up with r in the +range from slightly below 16 to 32. For example, to use 15/16 of a +server's 256 GiB RAM as ROM (thus, making it 240 GiB), you could use +r=15 or r=30. To use 23/24 of a server's 384 GiB RAM as ROM (thus, +making it 368 GiB), you'd use r=23. Then set NROM to your desired ROM +size in KiB divided by 128*r. Note that these examples might (or might +not) be too extreme, leaving little memory for the rest of the system. +You could as well opt for 7/8 with r=14 or 11/12 with r=11 or r=22. + +Note that higher r may make placing of ROM in e.g. NVMe flash memory +instead of in RAM more reasonable (or less unreasonable) than it would +have been with a lower r. If this is a concern as it relates to +possible attacks and you do not intend to ever do it defensively, you +might want to keep r lower (e.g., prefer r=15 over r=30 in the example +above, even if 30 performs slightly faster). + +Your adjustments to r, if you deviate from powers of two, will also +result in weirder memory usage per hash. Like 1.75 MiB at r=14 instead +of 2 MiB at r=8 that you would have used without a ROM. That's OK. + +For ROM initialization, which you do with yescrypt_init_shared(), use +the same r and NROM that you'd later use for password hashing, choose p +based on your servers' physical and/or logical CPU count (maybe +considering eventual upgrades as you won't be able to change this later, +but without going unnecessarily high - e.g., p=28, p=56, or p=112 make +sense on servers that currently have 28 physical / 56 logical CPUs), and +set the rest of the parameters to: + +flags = YESCRYPT_DEFAULTS, N = 0, t = 0, g = 0 + +N is set to 0 because it isn't relevant during ROM initialization (you +can use different values of N for hashing passwords with the same ROM). + +To keep the ROM in e.g. SysV shared memory and reuse it across your +authentication service restarts, you'd need to allocate the memory and +set the flags to "YESCRYPT_DEFAULTS | YESCRYPT_SHARED_PREALLOCATED". + +For actual password hashing, you'd use your chosen values for N, r, +NROM, and set the rest of the parameters to: + +flags = YESCRYPT_DEFAULTS, p = 1, t = 0, g = 0 + +Note that although you'd use a large p for ROM initialization, you +should use p=1 for actual password hashing like you would without a ROM. + +Do not forget to pass the ROM into the actual password hashing (and keep +r and NROM set accordingly). + +Since N must be a power of two and r is dependent on ROM size, you may +use t (in the range of 0 to 2) for fine-tuning the running time, but +first bring N to the maximum you can afford. + +If this feels too complicated, or even if it doesn't, please consider +engaging Openwall for your yescrypt deployment. We'd be happy to help. + + + Password-based key derivation. + +(Or rather passphrase-based.) + +Use settings similar to those for password hashing without a ROM, but +adjusted for higher memory usage and running time, and optionally with +thread-level parallelism. + +Small and fast (memory usage 128 MiB, running time under 100 ms on a +fast desktop): + +flags = YESCRYPT_DEFAULTS, N = 32768, r = 32, p = 1, t = 0, g = 0, NROM = 0 + +Large and fast (memory usage 1 GiB, running time under 200 ms on a fast +quad-core desktop not including memory allocation overhead, under 250 ms +with the overhead included), but requires build with OpenMP support (or +otherwise will run as slow as yet be weaker than its p=1 alternative): + +flags = YESCRYPT_DEFAULTS, N = 262144, r = 32, p = 4, t = 0, g = 0, NROM = 0 + +Large and slower (memory usage 1 GiB, running time under 300 ms on a +fast quad-core desktop not including memory allocation overhead, under +350 ms with the overhead included), also requires build with OpenMP +support (or otherwise will run slower than the p=1 alternative below): + +flags = YESCRYPT_DEFAULTS, N = 262144, r = 32, p = 4, t = 2, g = 0, NROM = 0 + +Large and slow (memory usage 1 GiB, running time under 600 ms on a fast +desktop not including memory allocation overhead, under 650 ms with the +overhead included): + +flags = YESCRYPT_DEFAULTS, N = 262144, r = 32, p = 1, t = 0, g = 0, NROM = 0 + +Just like with password hashing, even heavier and slower settings are +possible, if affordable, and you achieve them by adjusting N, r, t in +the same way and in the same preferred ranges (please see the section on +password hashing without a ROM, above). Unlike with password hashing, +it makes some sense to go above t=2 if you expect that your users might +not be able to afford more memory but can afford more time. However, +increasing the memory usage provides better protection, and we don't +recommend forcing your users to wait for more than 1 second as they +could as well type more characters in that time. If this feels too +complicated, just use one of the above parameter sets as-is. + + + Amortization of memory allocation overhead. + +It takes a significant fraction of yescrypt's total running time to +allocate memory from the operating system, especially considering that +the kernel zeroizes the memory before handing it over to your program. + +Unless you naturally need to compute yescrypt just once per process, you +may achieve greater efficiency by fully using advanced yescrypt APIs +that let you preserve and reuse the memory allocation across yescrypt +invocations. This is done by reusing the structure pointed to by the +"yescrypt_local_t *local" argument of yescrypt_r() or yescrypt_kdf() +without calling yescrypt_free_local() inbetween the repeated invocations +of yescrypt. + + + YESCRYPT_DEFAULTS macro. + +Please note that the value of the YESCRYPT_DEFAULTS macro might change +later, so if you use the macro like it's recommended here then for +results reproducible across versions you might need to store its value +somewhere along with the hashes or the encrypted data. + +If you use yescrypt's standard hash string encoding, then yescrypt +already encodes and decodes this value for you, so you don't need to +worry about this. diff --git a/libbb/yescrypt/README b/libbb/yescrypt/README new file mode 100644 index 0000000000..c1011c56a3 --- /dev/null +++ b/libbb/yescrypt/README @@ -0,0 +1,4 @@ +The yescrypt code in this directory is adapted from libxcrypt-4.4.38 +with minimal edits, hopefully making it easier to track +backports by resetting the tree to the commit which created this file, +then comparing changes in upstream libxcrypt to the tree. diff --git a/libbb/yescrypt/alg-sha256.c b/libbb/yescrypt/alg-sha256.c new file mode 100644 index 0000000000..dc748c9681 --- /dev/null +++ b/libbb/yescrypt/alg-sha256.c @@ -0,0 +1,91 @@ +/*- + * Copyright 2005-2016 Colin Percival + * Copyright 2016-2018,2021 Alexander Peslyak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +static void +PBKDF2_SHA256(const uint8_t *passwd, size_t passwdlen, + const uint8_t *salt, size_t saltlen, + uint64_t c, uint8_t *buf, size_t dkLen) +{ + hmac_ctx_t Phctx, PShctx; + uint32_t i; + + /* Compute HMAC state after processing P. */ + hmac_begin(&Phctx, passwd, passwdlen, sha256_begin); + + /* Compute HMAC state after processing P and S. */ + PShctx = Phctx; + hmac_hash(&PShctx, salt, saltlen); + + /* Iterate through the blocks. */ + for (i = 0; dkLen != 0; ) { + long U[32 / sizeof(long)]; + long T[32 / sizeof(long)]; +// Do not make these ^^ uint64_t[]. Keep them long[]. +// Even though the XORing loop below is optimized out, +// gcc is not smart enough to realize that 64-bit alignment of the stack +// is no longer useful, and generates ~50 more bytes of code on i386... + uint32_t ivec; + size_t clen; + int k; + + /* Generate INT(i). */ + i++; + ivec = SWAP_BE32(i); + + /* Compute U_1 = PRF(P, S || INT(i)). */ + hmac_peek_hash(&PShctx, (void*)T, &ivec, 4, NULL); +//TODO: the above is a vararg function, might incur some ABI pain +//does libbb need a non-vararg version with just one (buf,len)? + + if (c > 1) { +//in yescrypt, c is always 1, so this if() branch is optimized out + uint64_t j; + /* T_i = U_1 ... */ + memcpy(U, T, 32); + for (j = 2; j <= c; j++) { + /* Compute U_j. */ + hmac_peek_hash(&Phctx, (void*)U, U, 32, NULL); + /* ... xor U_j ... */ + for (k = 0; k < 32 / sizeof(long); k++) + T[k] ^= U[k]; + //TODO: xorbuf32_aligned_long(T, U); + } + } + + /* Copy as many bytes as necessary into buf. */ + clen = dkLen; + if (clen > 32) + clen = 32; + buf = mempcpy(buf, T, clen); + dkLen -= clen; + } +} diff --git a/libbb/yescrypt/alg-yescrypt-common.c b/libbb/yescrypt/alg-yescrypt-common.c new file mode 100644 index 0000000000..c518237874 --- /dev/null +++ b/libbb/yescrypt/alg-yescrypt-common.c @@ -0,0 +1,408 @@ +/*- + * Copyright 2013-2018 Alexander Peslyak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if RESTRICTED_PARAMS + +#define decode64_uint32(dst, src, min) \ +({ \ + uint32_t d32 = a2i64(*(src)); \ + if (d32 > 47) \ + goto fail; \ + *(dst) = d32 + (min); \ + ++src; \ +}) +#define test_decode64_uint32() ((void)0) +#define FULL_PARAMS(...) + +#else + +#define FULL_PARAMS(...) __VA_ARGS__ + +/* Not inlining: + * de/encode64 functions are only used to read + * yescrypt_params_t field, and convert salt to binary - + * both of these are negligible compared to main hashing operation + */ +static NOINLINE const uint8_t *decode64_uint32( + uint32_t *dst, + const uint8_t *src, uint32_t val) +{ + uint32_t start = 0, end = 47, bits = 0; + uint32_t c; + + if (!src) /* previous decode failed already? */ + goto fail; + + c = a2i64(*src++); + if (c > 63) + goto fail; + +// The encoding of number N: +// start = 0 end = 47 +// If N < 48, it is encoded verbatim, else +// N -= 48 +// start = end+1 = 48 +// end += (64-end)/2 = 55 +// If N < (end+1-start)<<6 = 8<<6, it is encoded as 48+(N>>6)|low6bits (that is, 48...55|<6bit>), else +// N -= 8<<6 +// start = end+1 = 56 +// end += (64-end)/2 = 59 +// If N < (end+1-start)<<2*6 = 4<<12, it is encoded as 56+(N>>2*6)|low12bits (that is, 56...59|<6bit>|<6bit>), else +// ...same for 60..61|<6bit>|<6bit>|<6bit> +// .......same for 62|<6bit>|<6bit>|<6bit>|<6bit> +// .......same for 63|<6bit>|<6bit>|<6bit>|<6bit>|<6bit> + dbg_dec64("c:%d val:0x%08x", (int)c, (unsigned)val); + while (c > end) { + dbg_dec64("c:%d > end:%d", (int)c, (int)end); + val += (end + 1 - start) << bits; + dbg_dec64("val+=0x%08x", (int)((end + 1 - start) << bits)); + dbg_dec64(" val:0x%08x", (unsigned)val); + start = end + 1; + end += (64 - end) / 2; + bits += 6; + dbg_dec64("start=%d", (int)start); + dbg_dec64("end=%d", (int)end); + dbg_dec64("bits=%d", (int)bits); + } + + val += (c - start) << bits; + dbg_dec64("final val+=0x%08x", (int)((c - start) << bits)); + dbg_dec64(" val:0x%08x", (unsigned)val); + + while (bits != 0) { + c = a2i64(*src++); + if (c > 63) + goto fail; + bits -= 6; + val += c << bits; + dbg_dec64("low bits val+=0x%08x", (int)(c << bits)); + dbg_dec64(" val:0x%08x", (unsigned)val); + } + ret: + *dst = val; + return src; + fail: + val = 0; + src = NULL; + goto ret; +} + +#if TEST_DECODE64 +static void test_decode64_uint32(void) +{ + const uint8_t *src, *end; + uint32_t u32; + int a = 48; + int b = 8<<6; // 0x0200 + int c = 4<<12; // 0x04000 + int d = 2<<18; // 0x080000 + int e = 1<<24; // 0x1000000 + + src = (void*)"wzzz"; + end = decode64_uint32(&u32, src, 0); + if (u32 != 0x0003ffff+c+b+a) bb_error_msg_and_die("Incorrect decode '%s':0x%08x", src, (unsigned)u32); + if (end != src + 4) bb_error_msg_and_die("Incorrect decode '%s': %p end:%p", src, src, end); + src = (void*)"xzzz"; + end = decode64_uint32(&u32, src, 0); + if (u32 != 0x0007ffff+c+b+a) bb_error_msg_and_die("Incorrect decode '%s':0x%08x", src, (unsigned)u32); + if (end != src + 4) bb_error_msg_and_die("Incorrect decode '%s': %p end:%p", src, src, end); + // Note how the last representable "x---" encoding, 0x7ffff, is exactly d-1! + // And if we now increment it, we get: + src = (void*)"y...."; + end = decode64_uint32(&u32, src, 0); + if (u32 != 0x00000000+d+c+b+a) bb_error_msg_and_die("Incorrect decode '%s':0x%08x", src, (unsigned)u32); + if (end != src + 5) bb_error_msg_and_die("Incorrect decode '%s': %p end:%p", src, src, end); + src = (void*)"yzzzz"; + end = decode64_uint32(&u32, src, 0); + if (u32 != 0x00ffffff+d+c+b+a) bb_error_msg_and_die("Incorrect decode '%s':0x%08x", src, (unsigned)u32); + if (end != src + 5) bb_error_msg_and_die("Incorrect decode '%s': %p end:%p", src, src, end); + + src = (void*)"zzzzzz"; + end = decode64_uint32(&u32, src, 0); + if (u32 != 0x3fffffff+e+d+c+b+a) bb_error_msg_and_die("Incorrect decode '%s':0x%08x", src, (unsigned)u32); + if (end != src + 6) bb_error_msg_and_die("Incorrect decode '%s': %p end:%p", src, src, end); + + bb_error_msg("test_decode64_uint32() OK"); +} +#else +# define test_decode64_uint32() ((void)0) +#endif + +#endif /* !RESTRICTED_PARAMS */ + +#if 1 +static const uint8_t *decode64( + uint8_t *dst, size_t *dstlen, + const uint8_t *src) +{ + unsigned dstpos = 0; + + dbg_dec64("src:'%s'", src); + for (;;) { + uint32_t c, value = 0; + int bits = 0; + while (*src != '\0' && *src != '$') { + c = a2i64(*src); + if (c > 63) { /* bad ascii64 char, stop decoding at it */ + break; + } + src++; + value |= c << bits; + bits += 6; + if (bits == 24) /* got 4 chars */ + goto store; + } + /* we read entire src, or met a non-ascii64 char (such as "$") */ + if (bits == 0) + break; + /* else: we got last, partial bit block - store it */ + store: + dbg_dec64(" storing bits:%d dstpos:%u v:%08x", bits, dstpos, (int)SWAP_BE32(value)); //BE to see lsb first + for (;;) { + if ((*src == '\0' || *src == '$') + && value == 0 && bits < 8 + ) { + /* Example: mkpasswd PWD '$y$j9T$123': + * the "123" is bits:18 value:03,51,00 + * is considered to be 2 bytes, not 3! + * + * '$y$j9T$zzz' in upstream fails outright (3rd byte isn't zero). + * IOW: for upstream, validity of salt depends on VALUE, + * not just size of salt. Which is a bug. + * The '$y$j9T$zzz.' salt is the same + * (it adds 6 zero msbits) but upstream works with it, + * thus '$y$j9T$zzz' should work too and give the same result. + */ + goto end; + } + if (dstpos >= *dstlen) { + dbg_dec64(" ERR: bits:%d dstpos:%u dst[] is too small", bits, dstpos); + goto fail; + } + *dst++ = value; + dstpos++; + value >>= 8; + bits -= 8; + if (bits <= 0) /* can get negative, if we e.g. had 6 bits */ + break; + } + if (*src == '\0' || *src == '$') + break; + } + end: + *dstlen = dstpos; + dbg_dec64("dec64: OK, dst[%d]", (int)dstpos); + return src; + fail: + /* *dstlen = 0; - not needed, caller detects error by seeing NULL */ + return NULL; +} +#else +/* Buggy (and larger) original code */ +static const uint8_t *decode64( + uint8_t *dst, size_t *dstlen, + const uint8_t *src, size_t srclen) +{ + size_t dstpos = 0; + + while (dstpos <= *dstlen && srclen) { + uint32_t value = 0, bits = 0; + while (srclen--) { + uint32_t c = a2i64(*src); + if (c > 63) { + srclen = 0; + break; + } + src++; + value |= c << bits; + bits += 6; + if (bits >= 24) + break; + } + if (!bits) + break; + if (bits < 12) /* must have at least one full byte */ + goto fail; + dbg_dec64(" storing bits:%d v:%08x", (int)bits, (int)SWAP_BE32(value)); //BE to see lsb first + while (dstpos++ < *dstlen) { + *dst++ = value; + value >>= 8; + bits -= 8; + if (bits < 8) { /* 2 or 4 */ + if (value) /* must be 0 */ + goto fail; + bits = 0; + break; + } + } + if (bits) + goto fail; + } + + if (!srclen && dstpos <= *dstlen) { + *dstlen = dstpos; + dbg_dec64("dec64: OK, dst[%d]", (int)dstpos); + return src; + } + fail: + /* *dstlen = 0; - not needed, caller detects error by seeing NULL */ + return NULL; +} +#endif + +static char *encode64( + char *dst, size_t dstlen, + const uint8_t *src, size_t srclen) +{ + while (srclen) { + uint32_t value = 0, b = 0; + do { + value |= (uint32_t)(*src++ << b); + b += 8; + srclen--; + } while (srclen && b < 24); + + b >>= 3; /* number of bits to number of bytes */ + b++; /* 1, 2 or 3 bytes will become 2, 3 or 4 ascii64 chars */ + dstlen -= b; + if ((ssize_t)dstlen <= 0) + return NULL; + dst = num2str64_lsb_first(dst, value, b); + } + *dst = '\0'; + return dst; +} + +char *yescrypt_r( + const uint8_t *passwd, size_t passwdlen, + const uint8_t *setting, + char *buf, size_t buflen) +{ + struct { + yescrypt_ctx_t yctx[1]; + unsigned char hashbin32[32]; + } u; +#define yctx u.yctx +#define hashbin32 u.hashbin32 + char *dst; + const uint8_t *src, *saltend; + size_t need, prefixlen; + uint32_t u32; + + test_decode64_uint32(); + + memset(yctx, 0, sizeof(yctx)); + FULL_PARAMS(yctx->param.p = 1;) + + /* we assume setting starts with "$y$" (caller must ensure this) */ + src = setting + 3; + + src = decode64_uint32(&yctx->param.flags, src, 0); + /* "j9T" returns: 0x2f */ + //if (!src) + // goto fail; + + if (yctx->param.flags < YESCRYPT_RW) { + dbg("yctx->param.flags=0x%x", (unsigned)yctx->param.flags); + goto fail; // bbox: we don't support scrypt - only yescrypt + } else if (yctx->param.flags <= YESCRYPT_RW + (YESCRYPT_RW_FLAVOR_MASK >> 2)) { + /* "j9T" sets flags to 0xb6 */ + yctx->param.flags = YESCRYPT_RW + ((yctx->param.flags - YESCRYPT_RW) << 2); + dbg("yctx->param.flags=0x%x", (unsigned)yctx->param.flags); + dbg(" YESCRYPT_RW:%u", !!(yctx->param.flags & YESCRYPT_RW)); + dbg((yctx->param.flags & YESCRYPT_RW_FLAVOR_MASK) == + (YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 | YESCRYPT_SIMPLE_2 | YESCRYPT_SBOX_12K) + ? " YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 | YESCRYPT_SIMPLE_2 | YESCRYPT_SBOX_12K" + : " flags are not standard" + ); + } else { + goto fail; + } + + src = decode64_uint32(&u32, src, 1); + if (/*!src ||*/ u32 > 63) + goto fail; + yctx->param.N = (uint64_t)1 << u32; + /* "j9T" sets to 4096 (1<<12) */ + dbg("yctx->param.N=%llu (1<<%u)", (unsigned long long)yctx->param.N, (unsigned)u32); + + src = decode64_uint32(&yctx->param.r, src, 1); + /* "j9T" sets to 32 */ + dbg("yctx->param.r=%u", yctx->param.r); + + if (!src) + goto fail; + if (*src != '$') { +#if RESTRICTED_PARAMS + goto fail; +#else + src = decode64_uint32(&u32, src, 1); + dbg("yescrypt has extended params:0x%x", (unsigned)u32); + if (u32 & 1) + src = decode64_uint32(&yctx->param.p, src, 2); + if (u32 & 2) + src = decode64_uint32(&yctx->param.t, src, 1); + if (u32 & 4) + src = decode64_uint32(&yctx->param.g, src, 1); + if (u32 & 8) { + src = decode64_uint32(&u32, src, 1); + if (/*!src ||*/ u32 > 63) + goto fail; + yctx->param.NROM = (uint64_t)1 << u32; + } + if (!src) + goto fail; + if (*src != '$') + goto fail; +#endif + } + + yctx->saltlen = sizeof(yctx->salt); + src++; /* now points to salt */ + saltend = decode64(yctx->salt, &yctx->saltlen, src); + if (!saltend || (*saltend != '\0' && *saltend != '$')) + goto fail; /* salt[] is too small, or bad char during decode */ + dbg_dec64("salt is %d ascii64 chars -> %d bytes (in binary)", (int)(saltend - src), (int)yctx->saltlen); + + prefixlen = saltend - setting; + need = prefixlen + 1 + YESCRYPT_HASH_LEN + 1; + if (need > buflen /*overflow is quite unlikely: || need < prefixlen*/) + goto fail; + + if (yescrypt_kdf32(yctx, passwd, passwdlen, hashbin32)) { + dbg("error in yescrypt_kdf32"); + goto fail; + } + + dst = mempcpy(buf, setting, prefixlen); + *dst++ = '$'; + dst = encode64(dst, buflen - (dst - buf), hashbin32, sizeof(hashbin32)); + if (!dst) + goto fail; + ret: + free_region(yctx->local); + explicit_bzero(&u, sizeof(u)); + return buf; + fail: + buf = NULL; + goto ret; +#undef yctx +#undef hashbin32 +} diff --git a/libbb/yescrypt/alg-yescrypt-kdf.c b/libbb/yescrypt/alg-yescrypt-kdf.c new file mode 100644 index 0000000000..a9a1bd5915 --- /dev/null +++ b/libbb/yescrypt/alg-yescrypt-kdf.c @@ -0,0 +1,1212 @@ +/*- + * Copyright 2009 Colin Percival + * Copyright 2012-2018 Alexander Peslyak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file was originally written by Colin Percival as part of the Tarsnap + * online backup system. + */ + +#if __STDC_VERSION__ >= 199901L +/* Have restrict */ +#elif defined(__GNUC__) +#define restrict __restrict +#else +#define restrict +#endif + +#ifdef __GNUC__ +#define unlikely(exp) __builtin_expect(exp, 0) +#else +#define unlikely(exp) (exp) +#endif + +typedef union { + uint32_t w[16]; + uint64_t d[8]; +} salsa20_blk_t; + +static void salsa20_simd_shuffle( + const salsa20_blk_t *Bin, + salsa20_blk_t *Bout) +{ +#define COMBINE(out, in1, in2) \ +do { \ + Bout->d[out] = Bin->w[in1 * 2] | ((uint64_t)Bin->w[in2 * 2 + 1] << 32); \ +} while (0) + COMBINE(0, 0, 2); + COMBINE(1, 5, 7); + COMBINE(2, 2, 4); + COMBINE(3, 7, 1); + COMBINE(4, 4, 6); + COMBINE(5, 1, 3); + COMBINE(6, 6, 0); + COMBINE(7, 3, 5); +#undef COMBINE +} + +static void salsa20_simd_unshuffle( + const salsa20_blk_t *Bin, + salsa20_blk_t *Bout) +{ +#define UNCOMBINE(out, in1, in2) \ +do { \ + Bout->w[out * 2] = Bin->d[in1]; \ + Bout->w[out * 2 + 1] = Bin->d[in2] >> 32; \ +} while (0) + UNCOMBINE(0, 0, 6); + UNCOMBINE(1, 5, 3); + UNCOMBINE(2, 2, 0); + UNCOMBINE(3, 7, 5); + UNCOMBINE(4, 4, 2); + UNCOMBINE(5, 1, 7); + UNCOMBINE(6, 6, 4); + UNCOMBINE(7, 3, 1); +#undef UNCOMBINE +} + +#define DECL_X \ + salsa20_blk_t X +#define DECL_Y \ + salsa20_blk_t Y + +#if KDF_UNROLL_COPY +#define COPY(out, in) \ +do { \ + (out).d[0] = (in).d[0]; \ + (out).d[1] = (in).d[1]; \ + (out).d[2] = (in).d[2]; \ + (out).d[3] = (in).d[3]; \ + (out).d[4] = (in).d[4]; \ + (out).d[5] = (in).d[5]; \ + (out).d[6] = (in).d[6]; \ + (out).d[7] = (in).d[7]; \ +} while (0) +#else +#define COPY(out, in) \ +do { \ + memcpy((out).d, (in).d, sizeof((in).d)); \ +} while (0) +#endif + +#define READ_X(in) COPY(X, in) +#define WRITE_X(out) COPY(out, X) + +/** + * salsa20(B): + * Apply the Salsa20 core to the provided block. + */ +static void salsa20(salsa20_blk_t *restrict B, + salsa20_blk_t *restrict Bout, + uint32_t doublerounds) +{ + salsa20_blk_t X; +#define x X.w + + salsa20_simd_unshuffle(B, &X); + + do { +#define R(a,b) (((a) << (b)) | ((a) >> (32 - (b)))) + /* Operate on columns */ +#if KDF_UNROLL_SALSA20 + x[ 4] ^= R(x[ 0]+x[12], 7); // x[j] ^= R(x[k]+x[l], CONST) + x[ 8] ^= R(x[ 4]+x[ 0], 9); + x[12] ^= R(x[ 8]+x[ 4],13); + x[ 0] ^= R(x[12]+x[ 8],18); + + x[ 9] ^= R(x[ 5]+x[ 1], 7); + x[13] ^= R(x[ 9]+x[ 5], 9); + x[ 1] ^= R(x[13]+x[ 9],13); + x[ 5] ^= R(x[ 1]+x[13],18); + + x[14] ^= R(x[10]+x[ 6], 7); + x[ 2] ^= R(x[14]+x[10], 9); + x[ 6] ^= R(x[ 2]+x[14],13); + x[10] ^= R(x[ 6]+x[ 2],18); + + x[ 3] ^= R(x[15]+x[11], 7); + x[ 7] ^= R(x[ 3]+x[15], 9); + x[11] ^= R(x[ 7]+x[ 3],13); + x[15] ^= R(x[11]+x[ 7],18); +#else + { + unsigned j, k, l; + j = 4; k = 0; l = 12; + for (;;) { + uint32_t t; + x[j] ^= ({ t = x[k] + x[l]; R(t, 7); }); l = k; k = j; j = (j+4) & 0xf; + x[j] ^= ({ t = x[k] + x[l]; R(t, 9); }); l = k; k = j; j = (j+4) & 0xf; + x[j] ^= ({ t = x[k] + x[l]; R(t,13); }); l = k; k = j; j = (j+4) & 0xf; + x[j] ^= ({ t = x[k] + x[l]; R(t,18); }); + if (j == 15) break; + l = j + 1; k = j + 5; j = (j+9) & 0xf; + } + } +#endif + /* Operate on rows */ +#if KDF_UNROLL_SALSA20 +// i=0 n=0 + x[ 1] ^= R(x[ 0]+x[ 3], 7); // [i + (n+1)&3] [i + (n+0)&3] [i + (n+3)&3] + x[ 2] ^= R(x[ 1]+x[ 0], 9); // [i + (n+2)&3] [i + (n+1)&3] [i + (n+0)&3] + x[ 3] ^= R(x[ 2]+x[ 1],13); // [i + (n+3)&3] [i + (n+2)&3] [i + (n+1)&3] + x[ 0] ^= R(x[ 3]+x[ 2],18); // [i + (n+0)&3] [i + (n+3)&3] [i + (n+2)&3] +// i=4 n=1 ^^^j^^^ ^^^k^^^ ^^^l^^^ + x[ 6] ^= R(x[ 5]+x[ 4], 7); // [i + (n+1)&3] [i + (n+0)&3] [i + (n+3)&3] + x[ 7] ^= R(x[ 6]+x[ 5], 9); // [i + (n+2)&3] [i + (n+1)&3] [i + (n+0)&3] + x[ 4] ^= R(x[ 7]+x[ 6],13); // [i + (n+3)&3] [i + (n+2)&3] [i + (n+1)&3] + x[ 5] ^= R(x[ 4]+x[ 7],18); // [i + (n+0)&3] [i + (n+3)&3] [i + (n+2)&3] +// i=8 n=2 + x[11] ^= R(x[10]+x[ 9], 7); // [i + (n+1)&3] [i + (n+0)&3] [i + (n+3)&3] + x[ 8] ^= R(x[11]+x[10], 9); // [i + (n+2)&3] [i + (n+1)&3] [i + (n+0)&3] + x[ 9] ^= R(x[ 8]+x[11],13); // [i + (n+3)&3] [i + (n+2)&3] [i + (n+1)&3] + x[10] ^= R(x[ 9]+x[ 8],18); // [i + (n+0)&3] [i + (n+3)&3] [i + (n+2)&3] +// i=12 n=3 + x[12] ^= R(x[15]+x[14], 7); // [i + (n+1)&3] [i + (n+0)&3] [i + (n+3)&3] + x[13] ^= R(x[12]+x[15], 9); // [i + (n+2)&3] [i + (n+1)&3] [i + (n+0)&3] + x[14] ^= R(x[13]+x[12],13); // [i + (n+3)&3] [i + (n+2)&3] [i + (n+1)&3] + x[15] ^= R(x[14]+x[13],18); // [i + (n+0)&3] [i + (n+3)&3] [i + (n+2)&3] +#else + { + unsigned j, k, l; + uint32_t *xrow; + j = 1; k = 0; l = 3; + xrow = &x[0]; + for (;;) { + uint32_t t; + xrow[j] ^= ({ t = xrow[k] + xrow[l]; R(t, 7); }); l = k; k = j; j = (j+1) & 3; + xrow[j] ^= ({ t = xrow[k] + xrow[l]; R(t, 9); }); l = k; k = j; j = (j+1) & 3; + xrow[j] ^= ({ t = xrow[k] + xrow[l]; R(t,13); }); l = k; k = j; j = (j+1) & 3; + xrow[j] ^= ({ t = xrow[k] + xrow[l]; R(t,18); }); + if (j == 3) break; + l = j; k = j + 1; j = (j+2) & 3; + xrow += 4; + } + } +#endif + +#undef R + } while (--doublerounds); +#undef x + + { + uint32_t i; + salsa20_simd_shuffle(&X, Bout); + for (i = 0; i < 16; i++) { + // bbox: note: was unrolled x4 + B->w[i] = Bout->w[i] += B->w[i]; + } + } +#if 0 + /* Too expensive */ + explicit_bzero(&X, sizeof(X)); +#endif +} + +/** + * Apply the Salsa20/2 core to the block provided in X. + */ +#define SALSA20_2(out) \ + salsa20(&X, &out, 1) + +#if 0 +#define XOR(out, in1, in2) \ +do { \ + (out).d[0] = (in1).d[0] ^ (in2).d[0]; \ + (out).d[1] = (in1).d[1] ^ (in2).d[1]; \ + (out).d[2] = (in1).d[2] ^ (in2).d[2]; \ + (out).d[3] = (in1).d[3] ^ (in2).d[3]; \ + (out).d[4] = (in1).d[4] ^ (in2).d[4]; \ + (out).d[5] = (in1).d[5] ^ (in2).d[5]; \ + (out).d[6] = (in1).d[6] ^ (in2).d[6]; \ + (out).d[7] = (in1).d[7] ^ (in2).d[7]; \ +} while (0) +#else +#define XOR(out, in1, in2) \ +do { \ + xorbuf64_3_aligned64(&(out).d, &(in1).d, &(in2).d); \ +} while (0) +#endif + +#define XOR_X(in) XOR(X, X, in) +#define XOR_X_2(in1, in2) XOR(X, in1, in2) +#define XOR_X_WRITE_XOR_Y_2(out, in) \ +do { \ + XOR(Y, out, in); \ + COPY(out, Y); \ + XOR(X, X, Y); \ +} while (0) + +/** + * Apply the Salsa20/8 core to the block provided in X ^ in. + */ +#define SALSA20_8_XOR_MEM(in, out) \ +do { \ + XOR_X(in); \ + salsa20(&X, &out, 4); \ +} while (0) + +#define INTEGERIFY ((uint32_t)X.d[0]) + +/** + * blockmix_salsa8(Bin, Bout, r): + * Compute Bout = BlockMix_{salsa20/8, r}(Bin). The input Bin must be 128r + * bytes in length; the output Bout must also be the same size. + */ +static void blockmix_salsa8( + const salsa20_blk_t *restrict Bin, + salsa20_blk_t *restrict Bout, + size_t r) +{ + size_t i; + DECL_X; + + READ_X(Bin[r * 2 - 1]); + for (i = 0; i < r; i++) { + SALSA20_8_XOR_MEM(Bin[i * 2], Bout[i]); + SALSA20_8_XOR_MEM(Bin[i * 2 + 1], Bout[r + i]); + } +} + +static uint32_t blockmix_salsa8_xor( + const salsa20_blk_t *restrict Bin1, + const salsa20_blk_t *restrict Bin2, + salsa20_blk_t *restrict Bout, + size_t r) +{ + size_t i; + DECL_X; + + XOR_X_2(Bin1[r * 2 - 1], Bin2[r * 2 - 1]); + for (i = 0; i < r; i++) { + XOR_X(Bin1[i * 2]); + SALSA20_8_XOR_MEM(Bin2[i * 2], Bout[i]); + XOR_X(Bin1[i * 2 + 1]); + SALSA20_8_XOR_MEM(Bin2[i * 2 + 1], Bout[r + i]); + } + + return INTEGERIFY; +} + +/* This is tunable */ +#define Swidth 8 + +/* Not tunable in this implementation, hard-coded in a few places */ +#define PWXsimple 2 +#define PWXgather 4 + +/* Derived values. Not tunable except via Swidth above. */ +#define PWXbytes (PWXgather * PWXsimple * 8) +#define Sbytes (3 * (1 << Swidth) * PWXsimple * 8) +#define Smask (((1 << Swidth) - 1) * PWXsimple * 8) +#define Smask2 (((uint64_t)Smask << 32) | Smask) + +#define DECL_SMASK2REG do {} while (0) +#define FORCE_REGALLOC_3 do {} while (0) +#define MAYBE_MEMORY_BARRIER do {} while (0) + +#define PWXFORM_SIMD(x0, x1) \ +do { \ + uint64_t x = x0 & Smask2; \ + uint64_t *p0 = (uint64_t *)(S0 + (uint32_t)x); \ + uint64_t *p1 = (uint64_t *)(S1 + (x >> 32)); \ + x0 = ((x0 >> 32) * (uint32_t)x0 + p0[0]) ^ p1[0]; \ + x1 = ((x1 >> 32) * (uint32_t)x1 + p0[1]) ^ p1[1]; \ +} while (0) + +#if KDF_UNROLL_PWXFORM_ROUND +#define PWXFORM_ROUND \ +do { \ + PWXFORM_SIMD(X.d[0], X.d[1]); \ + PWXFORM_SIMD(X.d[2], X.d[3]); \ + PWXFORM_SIMD(X.d[4], X.d[5]); \ + PWXFORM_SIMD(X.d[6], X.d[7]); \ +} while (0) +#else +#define PWXFORM_ROUND \ +do { \ + for (int pwxi=0; pwxi<8; pwxi+=2) \ + PWXFORM_SIMD(X.d[pwxi], X.d[pwxi + 1]); \ +} while (0) +#endif + +/* + * This offset helps address the 256-byte write block via the single-byte + * displacements encodable in x86(-64) instructions. It is needed because the + * displacements are signed. Without it, we'd get 4-byte displacements for + * half of the writes. Setting it to 0x80 instead of 0x7c would avoid needing + * a displacement for one of the writes, but then the LEA instruction would + * need a 4-byte displacement. + */ +#define PWXFORM_WRITE_OFFSET 0x7c + +#define PWXFORM_WRITE \ +do { \ + WRITE_X(*(salsa20_blk_t *)(Sw - PWXFORM_WRITE_OFFSET)); \ + Sw += 64; \ +} while (0) + +#if KDF_UNROLL_PWXFORM +#define PWXFORM \ +do { \ + uint8_t *Sw = S2 + w + PWXFORM_WRITE_OFFSET; \ + FORCE_REGALLOC_3; \ + MAYBE_MEMORY_BARRIER; \ + PWXFORM_ROUND; \ + PWXFORM_ROUND; PWXFORM_WRITE; \ + PWXFORM_ROUND; PWXFORM_WRITE; \ + PWXFORM_ROUND; PWXFORM_WRITE; \ + PWXFORM_ROUND; PWXFORM_WRITE; \ + PWXFORM_ROUND; \ + w = (w + 64 * 4) & Smask2; \ + { \ + uint8_t *Stmp = S2; \ + S2 = S1; \ + S1 = S0; \ + S0 = Stmp; \ + } \ +} while (0) +#else +#define PWXFORM \ +do { \ + uint8_t *Sw = S2 + w + PWXFORM_WRITE_OFFSET; \ + FORCE_REGALLOC_3; \ + MAYBE_MEMORY_BARRIER; \ + PWXFORM_ROUND; \ + for (int pwxj=0; pwxj<4; pwxj++) {\ + PWXFORM_ROUND; PWXFORM_WRITE; \ + } \ + PWXFORM_ROUND; \ + w = (w + 64 * 4) & Smask2; \ + { \ + uint8_t *Stmp = S2; \ + S2 = S1; \ + S1 = S0; \ + S0 = Stmp; \ + } \ +} while (0) +#endif + +typedef struct { + uint8_t *S0, *S1, *S2; + size_t w; +} pwxform_ctx_t; + +#define Salloc (Sbytes + ((sizeof(pwxform_ctx_t) + 63) & ~63U)) + +/** + * blockmix_pwxform(Bin, Bout, r, S): + * Compute Bout = BlockMix_pwxform{salsa20/2, r, S}(Bin). The input Bin must + * be 128r bytes in length; the output Bout must also be the same size. + */ +static void blockmix( + const salsa20_blk_t *restrict Bin, + salsa20_blk_t *restrict Bout, + size_t r, + pwxform_ctx_t *restrict ctx) +{ + uint8_t *S0 = ctx->S0, *S1 = ctx->S1, *S2 = ctx->S2; + size_t w = ctx->w; + size_t i; + DECL_X; + + /* Convert count of 128-byte blocks to max index of 64-byte block */ + r = r * 2 - 1; + + READ_X(Bin[r]); + + DECL_SMASK2REG; + + i = 0; + for (;;) { + XOR_X(Bin[i]); + PWXFORM; + if (unlikely(i >= r)) + break; + WRITE_X(Bout[i]); + i++; + } + + ctx->S0 = S0; + ctx->S1 = S1; + ctx->S2 = S2; + ctx->w = w; + + SALSA20_2(Bout[i]); +} + +static uint32_t blockmix_xor( + const salsa20_blk_t *Bin1, + const salsa20_blk_t *restrict Bin2, + salsa20_blk_t *Bout, + size_t r, + pwxform_ctx_t *restrict ctx) +{ + uint8_t *S0 = ctx->S0, *S1 = ctx->S1, *S2 = ctx->S2; + size_t w = ctx->w; + size_t i; + DECL_X; + + /* Convert count of 128-byte blocks to max index of 64-byte block */ + r = r * 2 - 1; + + XOR_X_2(Bin1[r], Bin2[r]); + + DECL_SMASK2REG; + + i = 0; + r--; + for (;;) { + XOR_X(Bin1[i]); + XOR_X(Bin2[i]); + PWXFORM; + if (unlikely(i > r)) + break; + WRITE_X(Bout[i]); + i++; + } + + ctx->S0 = S0; + ctx->S1 = S1; + ctx->S2 = S2; + ctx->w = w; + + SALSA20_2(Bout[i]); + + return INTEGERIFY; +} + +static uint32_t blockmix_xor_save( + salsa20_blk_t *restrict Bin1out, + salsa20_blk_t *restrict Bin2, + size_t r, + pwxform_ctx_t *restrict ctx) +{ + uint8_t *S0 = ctx->S0, *S1 = ctx->S1, *S2 = ctx->S2; + size_t w = ctx->w; + size_t i; + DECL_X; + DECL_Y; + + /* Convert count of 128-byte blocks to max index of 64-byte block */ + r = r * 2 - 1; + + XOR_X_2(Bin1out[r], Bin2[r]); + + DECL_SMASK2REG; + + i = 0; + r--; + for (;;) { + XOR_X_WRITE_XOR_Y_2(Bin2[i], Bin1out[i]); + PWXFORM; + if (unlikely(i > r)) + break; + WRITE_X(Bin1out[i]); + i++; + } + + ctx->S0 = S0; + ctx->S1 = S1; + ctx->S2 = S2; + ctx->w = w; + + SALSA20_2(Bin1out[i]); + + return INTEGERIFY; +} + +/** + * integerify(B, r): + * Return the result of parsing B_{2r-1} as a little-endian integer. + */ +static inline uint32_t integerify(const salsa20_blk_t *B, size_t r) +{ +/* + * Our 64-bit words are in host byte order, which is why we don't just read + * w[0] here (would be wrong on big-endian). Also, our 32-bit words are + * SIMD-shuffled (so the next 32 bits would be part of d[6]), but currently + * this does not matter as we only care about the least significant 32 bits. + */ + return (uint32_t)B[2 * r - 1].d[0]; +} + +/** + * smix1(B, r, N, flags, V, NROM, VROM, XY, ctx): + * Compute first loop of B = SMix_r(B, N). The input B must be 128r bytes in + * length; the temporary storage V must be 128rN bytes in length; the temporary + * storage XY must be 128r+64 bytes in length. N must be even and at least 4. + * The array V must be aligned to a multiple of 64 bytes, and arrays B and XY + * to a multiple of at least 16 bytes. + */ +#if DISABLE_NROM_CODE +#define smix1(B,r,N,flags,V,NROM,VROM,XY,ctx) \ + smix1(B,r,N,flags,V,XY,ctx) +#endif +static void smix1(uint8_t *B, size_t r, uint32_t N, + uint32_t flags, + salsa20_blk_t *V, + uint32_t NROM, const salsa20_blk_t *VROM, + salsa20_blk_t *XY, + pwxform_ctx_t *ctx) +{ +#if DISABLE_NROM_CODE + uint32_t NROM = 0; + const salsa20_blk_t *VROM = NULL; +#endif + size_t s = 2 * r; + salsa20_blk_t *X = V, *Y = &V[s]; + uint32_t i, j; + + for (i = 0; i < 2 * r; i++) { + const salsa20_blk_t *src = (salsa20_blk_t *)&B[i * 64]; + salsa20_blk_t *tmp = Y; + salsa20_blk_t *dst = &X[i]; + size_t k; + for (k = 0; k < 16; k++) + tmp->w[k] = SWAP_LE32(src->w[k]); + salsa20_simd_shuffle(tmp, dst); + } + + if (VROM) { + uint32_t n; + const salsa20_blk_t *V_j; + + V_j = &VROM[(NROM - 1) * s]; + j = blockmix_xor(X, V_j, Y, r, ctx) & (NROM - 1); + V_j = &VROM[j * s]; + X = Y + s; + j = blockmix_xor(Y, V_j, X, r, ctx); + + for (n = 2; n < N; n <<= 1) { + uint32_t m = (n < N / 2) ? n : (N - 1 - n); + for (i = 1; i < m; i += 2) { + j &= n - 1; + j += i - 1; + V_j = &V[j * s]; + Y = X + s; + j = blockmix_xor(X, V_j, Y, r, ctx) & (NROM - 1); + V_j = &VROM[j * s]; + X = Y + s; + j = blockmix_xor(Y, V_j, X, r, ctx); + } + } + n >>= 1; + + j &= n - 1; + j += N - 2 - n; + V_j = &V[j * s]; + Y = X + s; + j = blockmix_xor(X, V_j, Y, r, ctx) & (NROM - 1); + V_j = &VROM[j * s]; + blockmix_xor(Y, V_j, XY, r, ctx); + } else if (flags & YESCRYPT_RW) { +//can't use flags___YESCRYPT_RW, smix1() may be called with flags = 0 + uint32_t n; + salsa20_blk_t *V_j; + + blockmix(X, Y, r, ctx); + X = Y + s; + blockmix(Y, X, r, ctx); + j = integerify(X, r); + + for (n = 2; n < N; n <<= 1) { + uint32_t m = (n < N / 2) ? n : (N - 1 - n); + for (i = 1; i < m; i += 2) { + Y = X + s; + j &= n - 1; + j += i - 1; + V_j = &V[j * s]; + j = blockmix_xor(X, V_j, Y, r, ctx); + j &= n - 1; + j += i; + V_j = &V[j * s]; + X = Y + s; + j = blockmix_xor(Y, V_j, X, r, ctx); + } + } + n >>= 1; + + j &= n - 1; + j += N - 2 - n; + V_j = &V[j * s]; + Y = X + s; + j = blockmix_xor(X, V_j, Y, r, ctx); + j &= n - 1; + j += N - 1 - n; + V_j = &V[j * s]; + blockmix_xor(Y, V_j, XY, r, ctx); + } else { + N -= 2; + do { + blockmix_salsa8(X, Y, r); + X = Y + s; + blockmix_salsa8(Y, X, r); + Y = X + s; + } while ((N -= 2)); + + blockmix_salsa8(X, Y, r); + blockmix_salsa8(Y, XY, r); + } + + for (i = 0; i < 2 * r; i++) { + const salsa20_blk_t *src = &XY[i]; + salsa20_blk_t *tmp = &XY[s]; + salsa20_blk_t *dst = (salsa20_blk_t *)&B[i * 64]; + size_t k; + for (k = 0; k < 16; k++) + tmp->w[k] = SWAP_LE32(src->w[k]); + salsa20_simd_unshuffle(tmp, dst); + } +} + +/** + * smix2(B, r, N, Nloop, flags, V, NROM, VROM, XY, ctx): + * Compute second loop of B = SMix_r(B, N). The input B must be 128r bytes in + * length; the temporary storage V must be 128rN bytes in length; the temporary + * storage XY must be 256r bytes in length. N must be a power of 2 and at + * least 2. Nloop must be even. The array V must be aligned to a multiple of + * 64 bytes, and arrays B and XY to a multiple of at least 16 bytes. + */ +#if DISABLE_NROM_CODE +#define smix2(B,r,N,Nloop,flags,V,NROM,VROM,XY,ctx) \ + smix2(B,r,N,Nloop,flags,V,XY,ctx) +#endif +static void smix2(uint8_t *B, size_t r, uint32_t N, uint64_t Nloop, + uint32_t flags, + salsa20_blk_t *V, + uint32_t NROM, const salsa20_blk_t *VROM, + salsa20_blk_t *XY, + pwxform_ctx_t *ctx) +{ +#if DISABLE_NROM_CODE + uint32_t NROM = 0; + const salsa20_blk_t *VROM = NULL; +#endif + size_t s = 2 * r; + salsa20_blk_t *X = XY, *Y = &XY[s]; + uint32_t i, j; + + if (Nloop == 0) + return; + + for (i = 0; i < 2 * r; i++) { + const salsa20_blk_t *src = (salsa20_blk_t *)&B[i * 64]; + salsa20_blk_t *tmp = Y; + salsa20_blk_t *dst = &X[i]; + size_t k; + for (k = 0; k < 16; k++) + tmp->w[k] = SWAP_LE32(src->w[k]); + salsa20_simd_shuffle(tmp, dst); + } + + j = integerify(X, r) & (N - 1); + +/* + * Normally, VROM implies YESCRYPT_RW, but we check for these separately + * because our SMix resets YESCRYPT_RW for the smix2() calls operating on the + * entire V when p > 1. + */ +//and this is why bbox can't use flags___YESCRYPT_RW in this function + if (VROM && (flags & YESCRYPT_RW)) { + do { + salsa20_blk_t *V_j = &V[j * s]; + const salsa20_blk_t *VROM_j; + j = blockmix_xor_save(X, V_j, r, ctx) & (NROM - 1); + VROM_j = &VROM[j * s]; + j = blockmix_xor(X, VROM_j, X, r, ctx) & (N - 1); + } while (Nloop -= 2); + } else if (VROM) { + do { + const salsa20_blk_t *V_j = &V[j * s]; + j = blockmix_xor(X, V_j, X, r, ctx) & (NROM - 1); + V_j = &VROM[j * s]; + j = blockmix_xor(X, V_j, X, r, ctx) & (N - 1); + } while (Nloop -= 2); + } else if (flags & YESCRYPT_RW) { + do { + salsa20_blk_t *V_j = &V[j * s]; + j = blockmix_xor_save(X, V_j, r, ctx) & (N - 1); + V_j = &V[j * s]; + j = blockmix_xor_save(X, V_j, r, ctx) & (N - 1); + } while (Nloop -= 2); + } else if (ctx) { + do { + const salsa20_blk_t *V_j = &V[j * s]; + j = blockmix_xor(X, V_j, X, r, ctx) & (N - 1); + V_j = &V[j * s]; + j = blockmix_xor(X, V_j, X, r, ctx) & (N - 1); + } while (Nloop -= 2); + } else { + do { + const salsa20_blk_t *V_j = &V[j * s]; + j = blockmix_salsa8_xor(X, V_j, Y, r) & (N - 1); + V_j = &V[j * s]; + j = blockmix_salsa8_xor(Y, V_j, X, r) & (N - 1); + } while (Nloop -= 2); + } + + for (i = 0; i < 2 * r; i++) { + const salsa20_blk_t *src = &X[i]; + salsa20_blk_t *tmp = Y; + salsa20_blk_t *dst = (salsa20_blk_t *)&B[i * 64]; + size_t k; + for (k = 0; k < 16; k++) + tmp->w[k] = SWAP_LE32(src->w[k]); + salsa20_simd_unshuffle(tmp, dst); + } +} + +/** + * p2floor(x): + * Largest power of 2 not greater than argument. + */ +static uint64_t p2floor(uint64_t x) +{ + uint64_t y; + while ((y = x & (x - 1))) + x = y; + return x; +} + +/** + * smix(B, r, N, p, t, flags, V, NROM, VROM, XY, S, passwd): + * Compute B = SMix_r(B, N). The input B must be 128rp bytes in length; the + * temporary storage V must be 128rN bytes in length; the temporary storage + * XY must be 256r or 256rp bytes in length (the larger size is required with + * OpenMP-enabled builds). N must be a power of 2 and at least 4. The array V + * must be aligned to a multiple of 64 bytes, and arrays B and XY to a multiple + * of at least 16 bytes (aligning them to 64 bytes as well saves cache lines + * and helps avoid false sharing in OpenMP-enabled builds when p > 1, but it + * might also result in cache bank conflicts). + */ +#if DISABLE_NROM_CODE +#define smix(B,r,N,p,t,flags,V,NROM,VROM,XY,S,passwd) \ + smix(B,r,N,p,t,flags,V,XY,S,passwd) +#endif +static void smix(uint8_t *B, size_t r, uint32_t N, uint32_t p, uint32_t t, + uint32_t flags, + salsa20_blk_t *V, + uint32_t NROM, const salsa20_blk_t *VROM, + salsa20_blk_t *XY, + uint8_t *S, uint8_t *passwd) +{ + size_t s = 2 * r; + uint32_t Nchunk; + uint64_t Nloop_all, Nloop_rw; + uint32_t i; + + Nchunk = N / p; + Nloop_all = Nchunk; + if (flags___YESCRYPT_RW) { + if (t <= 1) { + if (t) + Nloop_all *= 2; /* 2/3 */ + Nloop_all = (Nloop_all + 2) / 3; /* 1/3, round up */ + } else { + Nloop_all *= t - 1; + } + } else if (t) { + if (t == 1) + Nloop_all += (Nloop_all + 1) / 2; /* 1.5, round up */ + Nloop_all *= t; + } + + Nloop_rw = 0; + if (flags___YESCRYPT_RW) + Nloop_rw = Nloop_all / p; + + Nchunk &= ~(uint32_t)1; /* round down to even */ + Nloop_all++; Nloop_all &= ~(uint64_t)1; /* round up to even */ + Nloop_rw++; Nloop_rw &= ~(uint64_t)1; /* round up to even */ + + for (i = 0; i < p; i++) { + uint32_t Vchunk = i * Nchunk; + uint32_t Np = (i < p - 1) ? Nchunk : (N - Vchunk); + uint8_t *Bp = &B[128 * r * i]; + salsa20_blk_t *Vp = &V[Vchunk * s]; + salsa20_blk_t *XYp = XY; + pwxform_ctx_t *ctx_i = NULL; + if (flags___YESCRYPT_RW) { + uint8_t *Si = S + i * Salloc; + smix1(Bp, 1, Sbytes / 128, 0 /* no flags */, + (salsa20_blk_t *)Si, 0, NULL, XYp, NULL); + ctx_i = (pwxform_ctx_t *)(Si + Sbytes); + ctx_i->S2 = Si; + ctx_i->S1 = Si + Sbytes / 3; + ctx_i->S0 = Si + Sbytes / 3 * 2; + ctx_i->w = 0; + if (i == 0) + hmac_block( + /* key,len: */ Bp + (128 * r - 64), 64, + /* hash fn: */ sha256_begin, + /* in,len: */ passwd, 32, + /* outbuf: */ passwd + ); + } + smix1(Bp, r, Np, flags, Vp, NROM, VROM, XYp, ctx_i); + smix2(Bp, r, p2floor(Np), Nloop_rw, flags, Vp, + NROM, VROM, XYp, ctx_i); + } + + if (Nloop_all > Nloop_rw) { + for (i = 0; i < p; i++) { + uint8_t *Bp = &B[128 * r * i]; + salsa20_blk_t *XYp = XY; + pwxform_ctx_t *ctx_i = NULL; + if (flags___YESCRYPT_RW) { + uint8_t *Si = S + i * Salloc; + ctx_i = (pwxform_ctx_t *)(Si + Sbytes); + } + smix2(Bp, r, N, Nloop_all - Nloop_rw, + flags & (uint32_t)~YESCRYPT_RW, + V, NROM, VROM, XYp, ctx_i); + } + } +} + +/* Allocator code */ + +static void alloc_region(yescrypt_region_t *region, size_t size) +{ + uint8_t *base; + int flags = +# ifdef MAP_NOCORE /* huh? */ + MAP_NOCORE | +# endif + MAP_ANON | MAP_PRIVATE; + + base = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0); + if (base == MAP_FAILED) + bb_die_memory_exhausted(); + +#if defined(MADV_HUGEPAGE) + /* Reduces mkpasswd qweRTY123@-+ '$y$jHT$123' + * (which allocates 4 Gbytes) + * run time from 10.543s to 5.635s + * Seen on linux-5.18.0. + */ + madvise(base, size, MADV_HUGEPAGE); +#endif + //region->base = base; + //region->base_size = size; + region->aligned = base; + region->aligned_size = size; +} + +static void free_region(yescrypt_region_t *region) +{ + if (region->aligned) + munmap(region->aligned, region->aligned_size); + //region->base = NULL; + //region->base_size = 0; + region->aligned = NULL; + region->aligned_size = 0; +} +/** + * yescrypt_kdf_body(shared, local, passwd, passwdlen, salt, saltlen, + * flags, N, r, p, t, NROM, buf, buflen): + * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, + * p, buflen), or a revision of scrypt as requested by flags and shared, and + * write the result into buf. + * + * shared and flags may request special modes as described in yescrypt.h. + * + * local is the thread-local data structure, allowing to preserve and reuse a + * memory allocation across calls, thereby reducing its overhead. + * + * t controls computation time while not affecting peak memory usage. + * + * Return 0 on success; or -1 on error. + * + * This optimized implementation currently limits N to the range from 4 to + * 2^31, but other implementations might not. + */ +static int yescrypt_kdf32_body( + yescrypt_ctx_t *yctx, + const uint8_t *passwd, size_t passwdlen, + uint32_t flags, uint64_t N, uint32_t t, + uint8_t *buf32) +{ +#if !DISABLE_NROM_CODE + const salsa20_blk_t *VROM; +#endif + size_t B_size, V_size, XY_size, need; + uint8_t *B, *S; + salsa20_blk_t *V, *XY; + struct { + uint8_t sha256[32]; + uint8_t dk[32]; + } u; +#define sha256 u.sha256 +#define dk u.dk + uint8_t *dkp = buf32; + uint32_t r, p; + + /* Sanity-check parameters */ + switch (flags___YESCRYPT_MODE_MASK) { + case 0: /* classic scrypt - can't have anything non-standard */ + if (flags || t || YCTX_param_NROM) + goto out_EINVAL; + break; + case YESCRYPT_WORM: + if (flags != YESCRYPT_WORM || YCTX_param_NROM) + goto out_EINVAL; + break; + case YESCRYPT_RW: + if (flags != (flags & YESCRYPT_KNOWN_FLAGS)) + goto out_EINVAL; +#if PWXsimple == 2 && PWXgather == 4 && Sbytes == 12288 + if ((flags & YESCRYPT_RW_FLAVOR_MASK) == + (YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 | + YESCRYPT_SIMPLE_2 | YESCRYPT_SBOX_12K)) + break; +#else +#error "Unsupported pwxform settings" +#endif + /* FALLTHRU */ + default: + goto out_EINVAL; + } + + r = YCTX_param_r; + p = YCTX_param_p; + if ((uint64_t)r * (uint64_t)p >= 1 << 30) { + dbg("r * n >= 2^30"); + goto out_EINVAL; + } + if (N > UINT32_MAX) { + dbg("N > 0x%lx", (long)UINT32_MAX); + goto out_EINVAL; + } + if (N <= 3 + || r < 1 + || p < 1 + ) { + dbg("bad N, r or p"); + goto out_EINVAL; + } + if (r > SIZE_MAX / 256 / p + || N > SIZE_MAX / 128 / r + ) { + /* 32-bit testcase: mkpasswd qweRTY123@-+ '$y$jHT$123' + * (works on 64-bit, needs buffer > 4Gbytes) + */ + dbg("r > SIZE_MAX / 256 / p? %c", "NY"[r > SIZE_MAX / 256 / p]); + dbg("N > SIZE_MAX / 128 / r? %c", "NY"[N > SIZE_MAX / 128 / r]); + goto out_EINVAL; + } + if (flags___YESCRYPT_RW) { + /* p cannot be greater than SIZE_MAX/Salloc on 64-bit systems, + but it can on 32-bit systems. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (N / p <= 3 || p > SIZE_MAX / Salloc) { + dbg("bad p:%ld", (long)p); + goto out_EINVAL; + } +#pragma GCC diagnostic pop + } + +#if !DISABLE_NROM_CODE + VROM = NULL; + if (YCTX_param_NROM) + goto out_EINVAL; +#endif + + /* Allocate memory */ + V = NULL; + V_size = (size_t)128 * r * N; + need = V_size; + B_size = (size_t)128 * r * p; + need += B_size; + if (need < B_size) { + dbg("integer overflow at += B_size(%lu)", (long)B_size); + goto out_EINVAL; + } + XY_size = (size_t)256 * r; + need += XY_size; + if (need < XY_size) { + dbg("integer overflow at += XY_size(%lu)", (long)XY_size); + goto out_EINVAL; + } + if (flags___YESCRYPT_RW) { + size_t S_size = (size_t)Salloc * p; + need += S_size; + if (need < S_size) { + dbg("integer overflow at += S_size(%lu)", (long)S_size); + goto out_EINVAL; + } + } + if (yctx->local->aligned_size < need) { + free_region(yctx->local); + alloc_region(yctx->local, need); + dbg("allocated local:%lu 0x%lx", (long)need, (long)need); + /* standard "j9T" params allocate 16Mbytes here */ + } + if (flags & YESCRYPT_ALLOC_ONLY) + return -3; /* expected "failure" */ + B = (uint8_t *)yctx->local->aligned; + V = (salsa20_blk_t *)((uint8_t *)B + B_size); + XY = (salsa20_blk_t *)((uint8_t *)V + V_size); + S = NULL; + if (flags___YESCRYPT_RW) + S = (uint8_t *)XY + XY_size; + + if (flags) { + hmac_block( + /* key,len: */ (const void*)"yescrypt-prehash", (flags & YESCRYPT_PREHASH) ? 16 : 8, + /* hash fn: */ sha256_begin, + /* in,len: */ passwd, passwdlen, + /* outbuf: */ sha256 + ); + passwd = sha256; + passwdlen = sizeof(sha256); + } + + PBKDF2_SHA256(passwd, passwdlen, yctx->salt, yctx->saltlen, 1, B, B_size); + + if (flags) + memcpy(sha256, B, sizeof(sha256)); + + if (p == 1 || (flags___YESCRYPT_RW)) { + smix(B, r, N, p, t, flags, V, YCTX_param_NROM, VROM, XY, S, sha256); + } else { + uint32_t i; + for (i = 0; i < p; i++) { + smix(&B[(size_t)128 * r * i], r, N, 1, t, flags, V, + YCTX_param_NROM, VROM, XY, NULL, NULL); + } + } + + dkp = buf32; + if (flags && /*buflen:*/32 < sizeof(dk)) { + PBKDF2_SHA256(passwd, passwdlen, B, B_size, 1, dk, sizeof(dk)); + dkp = dk; + } + + PBKDF2_SHA256(passwd, passwdlen, B, B_size, 1, buf32, /*buflen:*/32); + + /* + * Except when computing classic scrypt, allow all computation so far + * to be performed on the client. The final steps below match those of + * SCRAM (RFC 5802), so that an extension of SCRAM (with the steps so + * far in place of SCRAM's use of PBKDF2 and with SHA-256 in place of + * SCRAM's use of SHA-1) would be usable with yescrypt hashes. + */ + if (flags && !(flags & YESCRYPT_PREHASH)) { + /* Compute ClientKey */ + hmac_block( + /* key,len: */ dkp, sizeof(dk), + /* hash fn: */ sha256_begin, + /* in,len: */ "Client Key", 10, + /* outbuf: */ sha256 + ); + /* Compute StoredKey */ + { + size_t clen = /*buflen:*/32; + if (clen > sizeof(dk)) + clen = sizeof(dk); + if (sizeof(dk) != 32) { /* not true, optimize it out */ + sha256_block(sha256, sizeof(sha256), dk); + memcpy(buf32, dk, clen); + } else { + sha256_block(sha256, sizeof(sha256), buf32); + } + } + } + + explicit_bzero(&u, sizeof(u)); + + /* Success! */ + return 0; + + out_EINVAL: + //bbox does not need this: errno = EINVAL; + return -1; +#undef sha256 +#undef dk +} + +/** + * yescrypt_kdf(shared, local, passwd, passwdlen, salt, saltlen, params, + * buf, buflen): + * Compute scrypt or its revision as requested by the parameters. The inputs + * to this function are the same as those for yescrypt_kdf_body() above, with + * the addition of g, which controls hash upgrades (0 for no upgrades so far). + */ +static +int yescrypt_kdf32( + yescrypt_ctx_t *yctx, + const uint8_t *passwd, size_t passwdlen, + uint8_t *buf32) +{ + uint32_t flags = YCTX_param_flags; + uint64_t N = YCTX_param_N; + uint32_t r = YCTX_param_r; + uint32_t p = YCTX_param_p; + uint32_t t = YCTX_param_t; + uint32_t g = YCTX_param_g; + uint8_t dk32[32]; + int retval; + + /* Support for hash upgrades has been temporarily removed */ + if (g) { + //bbox does not need this: errno = EINVAL; + return -1; + } + + if ((flags___YESCRYPT_RW) + && p >= 1 + && N / p >= 0x100 + && N / p * r >= 0x20000 + ) { + if (yescrypt_kdf32_body(yctx, + passwd, passwdlen, + flags | YESCRYPT_ALLOC_ONLY, N, t, + buf32) != -3 + ) { + dbg("yescrypt_kdf32_body: not -3"); + return -1; + } + retval = yescrypt_kdf32_body(yctx, + passwd, passwdlen, + flags | YESCRYPT_PREHASH, N >> 6, 0, + dk32); + if (retval) { + dbg("yescrypt_kdf32_body(PREHASH):%d", retval); + return retval; + } + passwd = dk32; + passwdlen = sizeof(dk32); + } + + retval = yescrypt_kdf32_body(yctx, + passwd, passwdlen, + flags, N, t, buf32); + + explicit_bzero(dk32, sizeof(dk32)); + + dbg("yescrypt_kdf32_body:%d", retval); + return retval; +} diff --git a/libbb/yescrypt/alg-yescrypt.h b/libbb/yescrypt/alg-yescrypt.h new file mode 100644 index 0000000000..b69843f5d2 --- /dev/null +++ b/libbb/yescrypt/alg-yescrypt.h @@ -0,0 +1,247 @@ +/*- + * Copyright 2009 Colin Percival + * Copyright 2013-2018 Alexander Peslyak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file was originally written by Colin Percival as part of the Tarsnap + * online backup system. + */ + +// busybox debug and size-reduction configuration + +#ifdef YESCRYPT_INTERNAL +# if 1 +# define dbg(...) ((void)0) +# else +# define dbg(...) bb_error_msg(__VA_ARGS__) +# endif +# if 1 +# define dbg_dec64(...) ((void)0) +# else +# define dbg_dec64(...) bb_error_msg(__VA_ARGS__) +# endif +# define TEST_DECODE64 0 +#endif + +// Only accept one-char parameters in salt, and only first three? +// Almost any reasonable yescrypt hashes in /etc/shadow should +// only ever use "jXY" parameters which set N and r. +// Fancy multi-byte-encoded wide integers are not needed for that. +#define RESTRICTED_PARAMS 1 +// Note: if you enable the above, please also enable +// YCTX_param_p, YCTX_param_t, YCTX_param_g, YCTX_param_NROM +// optimizations, and DISABLE_NROM_CODE. + +#define DISABLE_NROM_CODE 1 + +// How much we save by forcing "standard" value by commenting the next line: +// 160 bytes +//#define YCTX_param_flags yctx->param.flags +// 260 bytes +//#define flags___YESCRYPT_RW (flags & YESCRYPT_RW) +// 140 bytes +//#define flags___YESCRYPT_MODE_MASK (flags & YESCRYPT_MODE_MASK) +// ^^^^ forcing the above since the code already requires (checks for) this +// 50 bytes +#define YCTX_param_N yctx->param.N +// -100 bytes (negative!!!) +#define YCTX_param_r yctx->param.r +// 400 bytes +//#define YCTX_param_p yctx->param.p +// 130 bytes +//#define YCTX_param_t yctx->param.t +// 2 bytes +//#define YCTX_param_g yctx->param.g +// 1 bytes +// ^^^^ this looks wrong, compiler should be able to constant-propagate the fact that NROM code is dead +//#define YCTX_param_NROM yctx->param.NROM + +#ifndef YCTX_param_flags +#define YCTX_param_flags (YESCRYPT_RW | YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 | YESCRYPT_SIMPLE_2 | YESCRYPT_SBOX_12K) +#endif +#ifndef flags___YESCRYPT_RW +#define flags___YESCRYPT_RW ((void)flags, YESCRYPT_RW) +#endif +#ifndef flags___YESCRYPT_MODE_MASK +#define flags___YESCRYPT_MODE_MASK ((void)flags, YESCRYPT_RW) +#endif +// standard ("j9T") values: +#ifndef YCTX_param_N +#define YCTX_param_N 4096 +#endif +#ifndef YCTX_param_r +#define YCTX_param_r 32 +#endif +#ifndef YCTX_param_p +#define YCTX_param_p 1 +#endif +#ifndef YCTX_param_t +#define YCTX_param_t 0 +#endif +#ifndef YCTX_param_g +#define YCTX_param_g 0 +#endif +#ifndef YCTX_param_NROM +#define YCTX_param_NROM 0 +#endif + +// "Faster/smaller code" knobs: +// -941 bytes: +#define KDF_UNROLL_COPY 0 +// -5324 bytes if 0: +#define KDF_UNROLL_PWXFORM_ROUND 0 +// -4864 bytes if 0: +#define KDF_UNROLL_PWXFORM 0 +// if both this ^^^^^^^^^^ and PWXFORM_ROUND set to 0: -7666 bytes +// -464 bytes: +#define KDF_UNROLL_SALSA20 0 + +/** + * Type and possible values for the flags argument of yescrypt_kdf(), + * yescrypt_encode_params_r(), yescrypt_encode_params(). Most of these may be + * OR'ed together, except that YESCRYPT_WORM stands on its own. + * Please refer to the description of yescrypt_kdf() below for the meaning of + * these flags. + */ +/* yescrypt flags: + * bits pos: 7654321076543210 + * ss r w + * sbox gg y + */ +/* Public */ +#define YESCRYPT_WORM 1 +#define YESCRYPT_RW 0x002 +#define YESCRYPT_ROUNDS_3 0x000 //r=0 +#define YESCRYPT_ROUNDS_6 0x004 //r=1 +#define YESCRYPT_GATHER_1 0x000 //gg=00 +#define YESCRYPT_GATHER_2 0x008 //gg=01 +#define YESCRYPT_GATHER_4 0x010 //gg=10 +#define YESCRYPT_GATHER_8 0x018 //gg=11 +#define YESCRYPT_SIMPLE_1 0x000 //ss=00 +#define YESCRYPT_SIMPLE_2 0x020 //ss=01 +#define YESCRYPT_SIMPLE_4 0x040 //ss=10 +#define YESCRYPT_SIMPLE_8 0x060 //ss=11 +#define YESCRYPT_SBOX_6K 0x000 //sbox=0000 +#define YESCRYPT_SBOX_12K 0x080 //sbox=0001 +#define YESCRYPT_SBOX_24K 0x100 //sbox=0010 +#define YESCRYPT_SBOX_48K 0x180 //sbox=0011 +#define YESCRYPT_SBOX_96K 0x200 //sbox=0100 +#define YESCRYPT_SBOX_192K 0x280 //sbox=0101 +#define YESCRYPT_SBOX_384K 0x300 //sbox=0110 +#define YESCRYPT_SBOX_768K 0x380 //sbox=0111 + +#ifdef YESCRYPT_INTERNAL +/* Private */ +#define YESCRYPT_MODE_MASK 0x003 +#define YESCRYPT_RW_FLAVOR_MASK 0x3fc +#define YESCRYPT_ALLOC_ONLY 0x08000000 +#define YESCRYPT_PREHASH 0x10000000 +#endif + +#define YESCRYPT_RW_DEFAULTS \ + (YESCRYPT_RW | \ + YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 | YESCRYPT_SIMPLE_2 | \ + YESCRYPT_SBOX_12K) + +#define YESCRYPT_DEFAULTS YESCRYPT_RW_DEFAULTS + +#ifdef YESCRYPT_INTERNAL +#define YESCRYPT_KNOWN_FLAGS \ + (YESCRYPT_MODE_MASK | YESCRYPT_RW_FLAVOR_MASK | \ + YESCRYPT_ALLOC_ONLY | YESCRYPT_PREHASH) +#endif + +/* How many chars base-64 encoded bytes require? */ +#define YESCRYPT_BYTES2CHARS(bytes) ((((bytes) * 8) + 5) / 6) +/* The /etc/passwd-style hash is "$" */ +/* + * "$y$", up to 8 params of up to 6 chars each, '$', salt + * Alternatively, but that's smaller: + * "$7$", 3 params encoded as 1+5+5 chars, salt + */ +#define YESCRYPT_PREFIX_LEN (3 + 8 * 6 + 1 + YESCRYPT_BYTES2CHARS(32)) + +#define YESCRYPT_HASH_SIZE 32 +#define YESCRYPT_HASH_LEN YESCRYPT_BYTES2CHARS(YESCRYPT_HASH_SIZE) + +/** + * Internal type used by the memory allocator. Please do not use it directly. + * Use yescrypt_shared_t and yescrypt_local_t as appropriate instead, since + * they might differ from each other in a future version. + */ +typedef struct { +// void *base; + void *aligned; +// size_t base_size; + size_t aligned_size; +} yescrypt_region_t; + +/** + * yescrypt parameters combined into one struct. N, r, p are the same as in + * classic scrypt, except that the meaning of p changes when YESCRYPT_RW is + * set. flags, t, g, NROM are special to yescrypt. + */ +typedef struct { + uint32_t flags; + uint32_t r; + uint64_t N; +#if !RESTRICTED_PARAMS + uint32_t p, t, g; + uint64_t NROM; +#endif +} yescrypt_params_t; + +typedef struct { + yescrypt_params_t param; + + /* salt in binary form */ + /* stored here to cut down on the amount of function paramaters */ + unsigned char salt[64]; + size_t saltlen; + + /* used by the memory allocator */ + //yescrypt_region_t shared[1]; + yescrypt_region_t local[1]; +} yescrypt_ctx_t; + +/** + * yescrypt_r(shared, local, passwd, passwdlen, setting, key, buf, buflen): + * Compute and encode an scrypt or enhanced scrypt hash of passwd given the + * parameters and salt value encoded in setting. If shared is not NULL, a ROM + * is used and YESCRYPT_RW is required. Otherwise, whether to compute classic + * scrypt, YESCRYPT_WORM (a slight deviation from classic scrypt), or + * YESCRYPT_RW (time-memory tradeoff discouraging modification) is determined + * by the setting string. shared (if not NULL) and local must be initialized + * as described above for yescrypt_kdf(). buf must be large enough (as + * indicated by buflen) to hold the encoded hash string. + * + * Return the encoded hash string on success; or NULL on error. + * + * MT-safe as long as local and buf are local to the thread. + */ +extern char *yescrypt_r( + const uint8_t *passwd, size_t passwdlen, + const uint8_t *setting, + char *buf, size_t buflen +); diff --git a/libbb/yescrypt/y.c b/libbb/yescrypt/y.c new file mode 100644 index 0000000000..d5ab8903fe --- /dev/null +++ b/libbb/yescrypt/y.c @@ -0,0 +1,16 @@ +/* + * The compilation unit for yescrypt-related code. + * + * Copyright (C) 2025 by Denys Vlasenko + * + * Licensed under GPLv2, see file LICENSE in this source tree. + */ +//kbuild:lib-$(CONFIG_USE_BB_CRYPT_YES) += y.o + +#include "libbb.h" + +#define YESCRYPT_INTERNAL +#include "alg-yescrypt.h" +#include "alg-sha256.c" +#include "alg-yescrypt-kdf.c" +#include "alg-yescrypt-common.c" diff --git a/loginutils/Config.src b/loginutils/Config.src index cbb09646bd..a7812bd32c 100644 --- a/loginutils/Config.src +++ b/loginutils/Config.src @@ -91,6 +91,17 @@ config USE_BB_CRYPT_SHA With this option off, login will fail password check for any user which has password encrypted with these algorithms. +config USE_BB_CRYPT_YES + bool "Enable yescrypt functions" + default y + depends on USE_BB_CRYPT + help + Enable this if you have passwords starting with "$y$" or + in your /etc/passwd or /etc/shadow files. These passwords + are hashed using yescrypt algorithms. + With this option off, login will fail password check for any + user which has password encrypted with these algorithms. + INSERT endmenu diff --git a/loginutils/chpasswd.c b/loginutils/chpasswd.c index 65530b6147..353f19961a 100644 --- a/loginutils/chpasswd.c +++ b/loginutils/chpasswd.c @@ -17,7 +17,7 @@ //config: default "des" //config: depends on PASSWD || CRYPTPW || CHPASSWD //config: help -//config: Possible choices are "d[es]", "m[d5]", "s[ha256]" or "sha512". +//config: Possible choices: "d[es]", "m[d5]", "s[ha256]", "sha512", "yescrypt" //applet:IF_CHPASSWD(APPLET(chpasswd, BB_DIR_USR_SBIN, BB_SUID_DROP)) diff --git a/loginutils/cryptpw.c b/loginutils/cryptpw.c index 1c338540fc..666deff0b2 100644 --- a/loginutils/cryptpw.c +++ b/loginutils/cryptpw.c @@ -84,8 +84,7 @@ to cryptpw. -a option (alias for -m) came from cryptpw. int cryptpw_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int cryptpw_main(int argc UNUSED_PARAM, char **argv) { - /* Supports: cryptpw -m sha256 PASS 'rounds=999999999$SALT' */ - char salt[MAX_PW_SALT_LEN + sizeof("rounds=999999999$")]; + char salt[MAX_PW_SALT_LEN]; char *salt_ptr; char *password; const char *opt_m, *opt_S; @@ -100,7 +99,7 @@ int cryptpw_main(int argc UNUSED_PARAM, char **argv) ; #endif fd = STDIN_FILENO; - opt_m = CONFIG_FEATURE_DEFAULT_PASSWD_ALGO; + opt_m = NULL; opt_S = NULL; /* at most two non-option arguments; -P NUM */ getopt32long(argv, "^" "sP:+S:m:a:" "\0" "?2", @@ -114,10 +113,34 @@ int cryptpw_main(int argc UNUSED_PARAM, char **argv) if (argv[0] && !opt_S) opt_S = argv[1]; - salt_ptr = crypt_make_pw_salt(salt, opt_m); - if (opt_S) - /* put user's data after the "$N$" prefix */ - safe_strncpy(salt_ptr, opt_S, sizeof(salt) - (sizeof("$N$")-1)); + if (opt_S && !opt_S[0]) { + /* mkpasswd 5.6.2 compat: SALT of "" + * is treated as not specified + * (both forms: -S "" and argv[1] of "") + */ + opt_S = NULL; + } + + if (opt_m) { + /* "cryptpw -m ALGO PASSWORD [SALT]" */ + /* generate "$x$" algo prefix + random salt */ + salt_ptr = crypt_make_pw_salt(salt, opt_m); + if (opt_S) { + /* "cryptpw -m ALGO PASSWORD SALT" */ + /* put SALT data after the "$x$" prefix */ + safe_strncpy(salt_ptr, opt_S, sizeof(salt) - (sizeof("$N$")-1)); + } + } else { + if (!opt_S) { + /* "cryptpw PASSWORD" */ + /* generate random salt with default algo */ + crypt_make_pw_salt(salt, CONFIG_FEATURE_DEFAULT_PASSWD_ALGO); + } else { + /* "cryptpw PASSWORD '$x$SALT'" */ + /* use given salt; algo will be detected by pw_encrypt() */ + safe_strncpy(salt, opt_S, sizeof(salt)); + } + } xmove_fd(fd, STDIN_FILENO); diff --git a/loginutils/sulogin.c b/loginutils/sulogin.c index 9c927ed79b..984889915e 100644 --- a/loginutils/sulogin.c +++ b/loginutils/sulogin.c @@ -79,7 +79,7 @@ int sulogin_main(int argc UNUSED_PARAM, char **argv) break; } pause_after_failed_login(); - bb_simple_info_msg("Login incorrect"); + bb_simple_error_msg("Login incorrect"); } /* util-linux 2.36.1 compat: no message */ @@ -119,9 +119,12 @@ int sulogin_main(int argc UNUSED_PARAM, char **argv) } /* - * Note: login does this (should we do it too?): + * Note: login does this. util-linux's sulogin does NOT. + * But it's rather unpleasant to have non-functioning ^C in a shell, + * and surprisingly, there is no easy way to remove SIG_IGN from ^C + * in the shell. So, we are doing it: */ - /*signal(SIGINT, SIG_DFL);*/ + signal(SIGINT, SIG_DFL); /* Exec shell with no additional parameters. Never returns. */ exec_shell(shell, /* -p? then shell is login:*/(opts & 1), NULL); diff --git a/miscutils/crond.c b/miscutils/crond.c index b3762d327f..6a384fdfba 100644 --- a/miscutils/crond.c +++ b/miscutils/crond.c @@ -177,7 +177,7 @@ static void crondlog(unsigned level, const char *msg, va_list va) { if (level >= G.log_level) { /* - * We are called only for info meesages. + * We are called only for info messages. * Warnings/errors use plain bb_[p]error_msg's, which * need not touch syslog_level * (they are ok with LOG_ERR default). @@ -989,7 +989,7 @@ static int check_completions(void) if (line->cl_pid <= 0) continue; - r = waitpid(line->cl_pid, NULL, WNOHANG); + r = safe_waitpid(line->cl_pid, NULL, WNOHANG); if (r < 0 || r == line->cl_pid) { process_finished_job(file->cf_username, line); if (line->cl_pid == 0) { @@ -1001,6 +1001,14 @@ static int check_completions(void) /* else: r == 0: "process is still running" */ file->cf_has_running = 1; } + + /* Reap any other children we don't actively track. + * Reportedly, some people run crond as init process! + * Thus, we need to reap orphans, like init does. + */ + while (wait_any_nohang(NULL) > 0) + continue; + //FIXME: if !file->cf_has_running && file->deleted: delete it! //otherwise deleted entries will stay forever, right? num_still_running += file->cf_has_running; diff --git a/miscutils/fbsplash.c b/miscutils/fbsplash.c index 2934d8eb7b..912a501a31 100644 --- a/miscutils/fbsplash.c +++ b/miscutils/fbsplash.c @@ -382,7 +382,7 @@ static void fb_drawimage(void) if (LONE_DASH(G.image_filename)) { theme_file = stdin; } else { - int fd = open_zipped(G.image_filename, /*fail_if_not_compressed:*/ 0); + int fd = open_zipped(G.image_filename, /*die_if_not_compressed:*/ 0); if (fd < 0) bb_simple_perror_msg_and_die(G.image_filename); theme_file = xfdopen_for_read(fd); diff --git a/miscutils/less.c b/miscutils/less.c index 8a0525cb7b..2204b1cecb 100644 --- a/miscutils/less.c +++ b/miscutils/less.c @@ -1139,7 +1139,7 @@ static int64_t getch_nowait(void) /* We have kbd_fd in O_NONBLOCK mode, read inside safe_read_key() * would not block even if there is no input available */ - key64 = safe_read_key(kbd_fd, kbd_input, /*timeout off:*/ -2); + key64 = safe_read_key(kbd_fd, kbd_input, /*do not poll:*/ -2); if ((int)key64 == -1) { if (errno == EAGAIN) { /* No keyboard input available. Since poll() did return, diff --git a/miscutils/man.c b/miscutils/man.c index deaf9e5abb..6fa1fbfdcc 100644 --- a/miscutils/man.c +++ b/miscutils/man.c @@ -143,7 +143,7 @@ static int run_pipe(char *man_filename, int man, int level) ordinary_manpage: close(STDIN_FILENO); - open_zipped(man_filename, /*fail_if_not_compressed:*/ 0); /* guaranteed to use fd 0 (STDIN_FILENO) */ + open_zipped(man_filename, /*die_if_not_compressed:*/ 0); /* guaranteed to use fd 0 (STDIN_FILENO) */ if (man) { int w = get_terminal_width(-1); if (w > 10) diff --git a/modutils/modprobe-small.c b/modutils/modprobe-small.c index 77e42e3fb1..31a215a297 100644 --- a/modutils/modprobe-small.c +++ b/modutils/modprobe-small.c @@ -186,15 +186,6 @@ static char* find_keyword(char *ptr, size_t len, const char *word) return NULL; } -static void replace(char *s, char what, char with) -{ - while (*s) { - if (what == *s) - *s = with; - ++s; - } -} - static char *filename2modname(const char *filename, char *modname) { int i; @@ -230,7 +221,7 @@ static char* str_2_list(const char *str) dst[len] = '\0'; memcpy(dst, str, len); //TODO: protect against 2+ spaces: "word word" - replace(dst, ' ', '\0'); + replace_char(dst, ' ', '\0'); return dst; } @@ -369,14 +360,14 @@ static int parse_module(module_info *info, const char *pathname) } bksp(); /* remove last ' ' */ info->aliases = copy_stringbuf(); - replace(info->aliases, '-', '_'); + replace_char(info->aliases, '-', '_'); /* "dependency1 depandency2" */ reset_stringbuf(); ptr = find_keyword(module_image, len, "depends="); if (ptr && *ptr) { - replace(ptr, ',', ' '); - replace(ptr, '-', '_'); + replace_char(ptr, ',', ' '); + replace_char(ptr, '-', '_'); dbg2_error_msg("dep:'%s'", ptr); append(ptr); } @@ -707,7 +698,7 @@ static int process_module(char *name, const char *cmdline_options) dbg1_error_msg("process_module('%s','%s')", name, cmdline_options); - replace(name, '-', '_'); + replace_char(name, '-', '_'); dbg1_error_msg("already_loaded:%d is_remove:%d", already_loaded(name), is_remove); @@ -735,7 +726,7 @@ static int process_module(char *name, const char *cmdline_options) char *opt_filename = xasprintf("/etc/modules/%s", name); options = xmalloc_open_read_close(opt_filename, NULL); if (options) - replace(options, '\n', ' '); + replace_char(options, '\n', ' '); #if ENABLE_FEATURE_CMDLINE_MODULE_OPTIONS if (cmdline_options) { /* NB: cmdline_options always have one leading ' ' diff --git a/modutils/modprobe.c b/modutils/modprobe.c index 543f53e996..f890abe53e 100644 --- a/modutils/modprobe.c +++ b/modutils/modprobe.c @@ -579,7 +579,7 @@ int modprobe_main(int argc UNUSED_PARAM, char **argv) parser_t *p = config_open2(CONFIG_DEFAULT_DEPMOD_FILE, xfopen_for_read); for (i = 0; argv[i]; i++) - replace(argv[i], '-', '_'); + replace_char(argv[i], '-', '_'); while (config_read(p, tokens, 2, 1, "# \t", PARSE_NORMAL)) { colon = last_char_is(tokens[0], ':'); diff --git a/modutils/modutils.c b/modutils/modutils.c index cbff209610..862f71f572 100644 --- a/modutils/modutils.c +++ b/modutils/modutils.c @@ -69,15 +69,6 @@ void FAST_FUNC moddb_free(module_db *db) } } -void FAST_FUNC replace(char *s, char what, char with) -{ - while (*s) { - if (what == *s) - *s = with; - ++s; - } -} - int FAST_FUNC string_to_llist(char *string, llist_t **llist, const char *delim) { char *tok; diff --git a/modutils/modutils.h b/modutils/modutils.h index 4a702e97c0..9b05116d1e 100644 --- a/modutils/modutils.h +++ b/modutils/modutils.h @@ -47,7 +47,6 @@ module_entry *moddb_get(module_db *db, const char *s) FAST_FUNC; module_entry *moddb_get_or_create(module_db *db, const char *s) FAST_FUNC; void moddb_free(module_db *db) FAST_FUNC; -void replace(char *s, char what, char with) FAST_FUNC; int string_to_llist(char *string, llist_t **llist, const char *delim) FAST_FUNC; char *filename2modname(const char *filename, char *modname) FAST_FUNC; #if ENABLE_FEATURE_CMDLINE_MODULE_OPTIONS diff --git a/networking/ftpd.c b/networking/ftpd.c index 0d6a289c7c..c3125410e0 100644 --- a/networking/ftpd.c +++ b/networking/ftpd.c @@ -190,54 +190,39 @@ struct globals { } while (0) +/* escape_text("pfx:", str, (0xff << 8) + '\r') + * Duplicate 0xff, append \r ^^^^^^^^^^^^^^^^^^ + */ static char * escape_text(const char *prepend, const char *str, unsigned escapee) { - unsigned retlen, remainlen, chunklen; - char *ret, *found; + char *ret, *p; char append; append = (char)escapee; escapee >>= 8; - remainlen = strlen(str); - retlen = strlen(prepend); - ret = xmalloc(retlen + remainlen * 2 + 1 + 1); - strcpy(ret, prepend); + ret = xmalloc(strlen(prepend) + strlen(str) * 2 + 1 + 1); + p = stpcpy(ret, prepend); for (;;) { - found = strchrnul(str, escapee); - chunklen = found - str + 1; + char *found = strchrnul(str, escapee); - /* Copy chunk up to and including escapee (or NUL) to ret */ - memcpy(ret + retlen, str, chunklen); - retlen += chunklen; + /* Copy up to and including escapee (or NUL) */ + p = mempcpy(p, str, found - str + 1); if (*found == '\0') { /* It wasn't escapee, it was NUL! */ - ret[retlen - 1] = append; /* replace NUL */ - ret[retlen] = '\0'; /* add NUL */ break; } - ret[retlen++] = escapee; /* duplicate escapee */ str = found + 1; + *p++ = escapee; /* duplicate escapee */ } + p[-1] = append; /* replace NUL */ + *p = '\0'; /* add NUL */ return ret; } -/* Returns strlen as a bonus */ -static unsigned -replace_char(char *str, char from, char to) -{ - char *p = str; - while (*p) { - if (*p == from) - *p = to; - p++; - } - return p - str; -} - static void verbose_log(const char *str) { diff --git a/networking/hostname.c b/networking/hostname.c index 36cb708669..101b89e77c 100644 --- a/networking/hostname.c +++ b/networking/hostname.c @@ -25,8 +25,8 @@ //applet:IF_DNSDOMAINNAME(APPLET_NOEXEC(dnsdomainname, hostname, BB_DIR_BIN, BB_SUID_DROP, dnsdomainname)) //applet:IF_HOSTNAME( APPLET_NOEXEC(hostname, hostname, BB_DIR_BIN, BB_SUID_DROP, hostname )) -//kbuild: lib-$(CONFIG_HOSTNAME) += hostname.o -//kbuild: lib-$(CONFIG_DNSDOMAINNAME) += hostname.o +//kbuild:lib-$(CONFIG_HOSTNAME) += hostname.o +//kbuild:lib-$(CONFIG_DNSDOMAINNAME) += hostname.o //usage:#define hostname_trivial_usage //usage: "[-sidf] [HOSTNAME | -F FILE]" diff --git a/networking/httpd.c b/networking/httpd.c index ddcb03bca1..e1a447fa1a 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -2826,7 +2826,7 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) salt[0] = '$'; salt[1] = '1'; salt[2] = '$'; - crypt_make_salt(salt + 3, 4); + crypt_make_rand64encoded(salt + 3, 8 / 2); /* 8 chars */ puts(pw_encrypt(pass, salt, /*cleanup:*/ 0)); return 0; } diff --git a/networking/libiproute/iplink.c b/networking/libiproute/iplink.c index 37ed114bc9..67602a4663 100644 --- a/networking/libiproute/iplink.c +++ b/networking/libiproute/iplink.c @@ -51,6 +51,27 @@ struct ifla_vlan_flags { }; #endif +#if ENABLE_FEATURE_IP_LINK_CAN +# ifndef CAN_CTRLMODE_ONE_SHOT +# define CAN_CTRLMODE_ONE_SHOT 0x08 /* One-Shot mode */ +# endif +# ifndef CAN_CTRLMODE_BERR_REPORTING +# define CAN_CTRLMODE_BERR_REPORTING 0x10 /* Bus-error reporting */ +# endif +# ifndef CAN_CTRLMODE_FD +# define CAN_CTRLMODE_FD 0x20 /* CAN FD mode */ +# endif +# ifndef CAN_CTRLMODE_PRESUME_ACK +# define CAN_CTRLMODE_PRESUME_ACK 0x40 /* Ignore missing CAN ACKs */ +# endif +# ifndef CAN_CTRLMODE_FD_NON_ISO +# define CAN_CTRLMODE_FD_NON_ISO 0x80 /* CAN FD in non-ISO mode */ +# endif +# ifndef IFLA_CAN_TERMINATION +# define IFLA_CAN_TERMINATION 11 +# endif +#endif + /* taken from linux/sockios.h */ #define SIOCSIFNAME 0x8923 /* set interface name */ diff --git a/networking/libiproute/iproute.c b/networking/libiproute/iproute.c index cd77f642ff..a30f070eb4 100644 --- a/networking/libiproute/iproute.c +++ b/networking/libiproute/iproute.c @@ -302,24 +302,22 @@ static int FAST_FUNC print_route(const struct sockaddr_nl *who UNUSED_PARAM, printf("notify "); } - if (r->rtm_family == AF_INET6) { - struct rta_cacheinfo *ci = NULL; - if (tb[RTA_CACHEINFO]) { - ci = RTA_DATA(tb[RTA_CACHEINFO]); - } - if ((r->rtm_flags & RTM_F_CLONED) || (ci && ci->rta_expires)) { + if (r->rtm_family == AF_INET || r->rtm_family == AF_INET6) { + if (r->rtm_family == AF_INET) { if (r->rtm_flags & RTM_F_CLONED) { printf("%c cache ", _SL_); + /* upstream: print_cache_flags() prints more here */ } + } + if (tb[RTA_CACHEINFO]) { + struct rta_cacheinfo *ci = RTA_DATA(tb[RTA_CACHEINFO]); if (ci->rta_expires) { printf(" expires %dsec", ci->rta_expires / get_hz()); } if (ci->rta_error != 0) { printf(" error %d", ci->rta_error); } - } else if (ci) { - if (ci->rta_error != 0) - printf(" error %d", ci->rta_error); + /* upstream: print_rta_cacheinfo() prints more here */ } } if (tb[RTA_IIF] && G_filter.iif == 0) { diff --git a/networking/ntpd.c b/networking/ntpd.c index dcbdb8e600..efe9f53268 100644 --- a/networking/ntpd.c +++ b/networking/ntpd.c @@ -205,7 +205,7 @@ #define MINDISP 0.01 /* minimum dispersion (sec) */ #define MAXDISP 16 /* maximum dispersion (sec) */ #define MAXSTRAT 16 /* maximum stratum (infinity metric) */ -#define MAXDIST 1 /* distance threshold (sec) */ +#define MAXDIST 3 /* distance threshold (sec): do not use peers who are farther away */ #define MIN_SELECTED 1 /* minimum intersection survivors */ #define MIN_CLUSTERED 3 /* minimum cluster survivors */ @@ -1057,7 +1057,7 @@ step_time(double offset) } tval = tvn.tv_sec; strftime_YYYYMMDDHHMMSS(buf, sizeof(buf), &tval); - bb_info_msg("setting time to %s.%06u (offset %+fs)", buf, (unsigned)tvn.tv_usec, offset); + bb_error_msg("setting time to %s.%06u (offset %+fs)", buf, (unsigned)tvn.tv_usec, offset); //maybe? G.FREQHOLD_cnt = 0; /* Correct various fields which contain time-relative values: */ @@ -1645,7 +1645,7 @@ update_local_clock(peer_t *p) /* 65536 is one ppm */ tmx.freq = G.discipline_freq_drift * 65536e6; #endif - tmx.modes = ADJ_OFFSET | ADJ_STATUS | ADJ_TIMECONST;// | ADJ_MAXERROR | ADJ_ESTERROR; + tmx.modes = ADJ_OFFSET | ADJ_STATUS | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR; tmx.offset = (long)(offset * 1000000); /* usec */ if (SLEW_THRESHOLD < STEP_THRESHOLD) { @@ -1738,16 +1738,23 @@ update_local_clock(peer_t *p) if (tmx.constant < 0) tmx.constant = 0; - //tmx.esterror = (uint32_t)(clock_jitter * 1e6); - //tmx.maxerror = (uint32_t)((sys_rootdelay / 2 + sys_rootdisp) * 1e6); + /* For ADJ_MAXERROR and ADJ_ESTERROR: */ + /* kernel increments this by 500us each second, sets STA_UNSYNC if exceeds 16 seconds: */ + tmx.maxerror = (uint32_t)((G.rootdelay / 2 + G.rootdisp) * 1000000.0); + /* (without ADJ_MAXERROR, time adjustment still works, but kernel uses + * conservative maxerror value and quickly sets STA_UNSYNC) + */ + /* esterror is not used by kernel, presumably may be used by other programs reading adjtimex result: */ + tmx.esterror = (uint32_t)(G.discipline_jitter * 1000000.0); + rc = adjtimex(&tmx); if (rc < 0) bb_simple_perror_msg_and_die("adjtimex"); /* NB: here kernel returns constant == G.poll_exp, not == G.poll_exp - 4. * Not sure why. Perhaps it is normal. */ - VERB4 bb_error_msg("adjtimex:%d freq:%ld offset:%+ld status:0x%x", - rc, (long)tmx.freq, (long)tmx.offset, tmx.status); + VERB4 bb_error_msg("adjtimex:%d freq:%ld offset:%+ld esterror:%ld maxerror:%ld status:0x%x", + rc, (long)tmx.freq, (long)tmx.offset, (long)tmx.esterror, (long)tmx.maxerror, tmx.status); G.kernel_freq_drift = tmx.freq / 65536; VERB2 bb_error_msg("update from:%s offset:%+f delay:%f jitter:%f clock drift:%+.3fppm tc:%d", p->p_dotted, @@ -1976,7 +1983,7 @@ recv_and_process_peer_pkt(peer_t *p) p->reachable_bits |= 1; if ((MAX_VERBOSE && G.verbose) || (option_mask32 & OPT_w)) { - bb_info_msg("reply from %s: offset:%+f delay:%f status:0x%02x strat:%d refid:0x%08x rootdelay:%f reach:0x%02x", + bb_error_msg("reply from %s: offset:%+f delay:%f status:0x%02x strat:%d refid:0x%08x rootdelay:%f reach:0x%02x", p->p_dotted, offset, p->p_raw_delay, diff --git a/networking/telnetd.c b/networking/telnetd.c index bfeea1400b..a5a7830478 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c @@ -104,8 +104,8 @@ //usage:#define telnetd_full_usage "\n\n" //usage: "Handle incoming telnet connections" //usage: IF_NOT_FEATURE_TELNETD_STANDALONE(" via inetd") "\n" -//usage: "\n -l LOGIN Exec LOGIN on connect" -//usage: "\n -f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue" +//usage: "\n -l LOGIN Exec LOGIN on connect (default /bin/login)" +//usage: "\n -f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue.net" //usage: "\n -K Close connection as soon as login exits" //usage: "\n (normally wait until all programs close slave pty)" //usage: IF_FEATURE_TELNETD_STANDALONE( diff --git a/networking/tftp.c b/networking/tftp.c index f5b4367ca9..b698a92884 100644 --- a/networking/tftp.c +++ b/networking/tftp.c @@ -250,7 +250,7 @@ static int tftp_blksize_check(const char *blksize_str, int maxsize) return -1; } # if ENABLE_TFTP_DEBUG - bb_info_msg("using blksize %u", blksize); + bb_error_msg("using blksize %u", blksize); # endif return blksize; } diff --git a/networking/tls.c b/networking/tls.c index 8d074c058c..ec8ce20e62 100644 --- a/networking/tls.c +++ b/networking/tls.c @@ -188,8 +188,6 @@ #define TLS_MAX_OUTBUF (1 << 14) enum { - SHA_INSIZE = 64, - AES128_KEYSIZE = 16, AES256_KEYSIZE = 32, @@ -335,34 +333,6 @@ void FAST_FUNC tls_get_random(void *buf, unsigned len) xfunc_die(); } -static void xorbuf3(void *dst, const void *src1, const void *src2, unsigned count) -{ - uint8_t *d = dst; - const uint8_t *s1 = src1; - const uint8_t* s2 = src2; - while (count--) - *d++ = *s1++ ^ *s2++; -} - -void FAST_FUNC xorbuf(void *dst, const void *src, unsigned count) -{ - xorbuf3(dst, dst, src, count); -} - -void FAST_FUNC xorbuf_aligned_AES_BLOCK_SIZE(void *dst, const void *src) -{ - unsigned long *d = dst; - const unsigned long *s = src; - d[0] ^= s[0]; -#if ULONG_MAX <= 0xffffffffffffffff - d[1] ^= s[1]; - #if ULONG_MAX == 0xffffffff - d[2] ^= s[2]; - d[3] ^= s[3]; - #endif -#endif -} - #if !TLS_DEBUG_HASH # define hash_handshake(tls, fmt, buffer, len) \ hash_handshake(tls, buffer, len) @@ -393,128 +363,6 @@ static void hash_handshake(tls_state_t *tls, const char *fmt, const void *buffer # define TLS_MAC_SIZE(tls) (tls)->MAC_size #endif -// RFC 2104: -// HMAC(key, text) based on a hash H (say, sha256) is: -// ipad = [0x36 x INSIZE] -// opad = [0x5c x INSIZE] -// HMAC(key, text) = H((key XOR opad) + H((key XOR ipad) + text)) -// -// H(key XOR opad) and H(key XOR ipad) can be precomputed -// if we often need HMAC hmac with the same key. -// -// text is often given in disjoint pieces. -typedef struct hmac_precomputed { - md5sha_ctx_t hashed_key_xor_ipad; - md5sha_ctx_t hashed_key_xor_opad; -} hmac_precomputed_t; - -typedef void md5sha_begin_func(md5sha_ctx_t *ctx) FAST_FUNC; -#if !ENABLE_FEATURE_TLS_SHA1 -#define hmac_begin(pre,key,key_size,begin) \ - hmac_begin(pre,key,key_size) -#define begin sha256_begin -#endif -static void hmac_begin(hmac_precomputed_t *pre, uint8_t *key, unsigned key_size, md5sha_begin_func *begin) -{ - uint8_t key_xor_ipad[SHA_INSIZE]; - uint8_t key_xor_opad[SHA_INSIZE]; -// uint8_t tempkey[SHA1_OUTSIZE < SHA256_OUTSIZE ? SHA256_OUTSIZE : SHA1_OUTSIZE]; - unsigned i; - - // "The authentication key can be of any length up to INSIZE, the - // block length of the hash function. Applications that use keys longer - // than INSIZE bytes will first hash the key using H and then use the - // resultant OUTSIZE byte string as the actual key to HMAC." - if (key_size > SHA_INSIZE) { - bb_simple_error_msg_and_die("HMAC key>64"); //does not happen (yet?) -// md5sha_ctx_t ctx; -// begin(&ctx); -// md5sha_hash(&ctx, key, key_size); -// key_size = sha_end(&ctx, tempkey); -// //key = tempkey; - right? RIGHT? why does it work without this? -// // because SHA_INSIZE is 64, but hmac() is always called with -// // key_size = tls->MAC_size = SHA1/256_OUTSIZE (20 or 32), -// // and prf_hmac_sha256() -> hmac_sha256() key sizes are: -// // - RSA_PREMASTER_SIZE is 48 -// // - CURVE25519_KEYSIZE is 32 -// // - master_secret[] is 48 - } - - for (i = 0; i < key_size; i++) { - key_xor_ipad[i] = key[i] ^ 0x36; - key_xor_opad[i] = key[i] ^ 0x5c; - } - for (; i < SHA_INSIZE; i++) { - key_xor_ipad[i] = 0x36; - key_xor_opad[i] = 0x5c; - } - - begin(&pre->hashed_key_xor_ipad); - begin(&pre->hashed_key_xor_opad); - md5sha_hash(&pre->hashed_key_xor_ipad, key_xor_ipad, SHA_INSIZE); - md5sha_hash(&pre->hashed_key_xor_opad, key_xor_opad, SHA_INSIZE); -} -#undef begin - -static unsigned hmac_sha_precomputed_v( - hmac_precomputed_t *pre, - uint8_t *out, - va_list va) -{ - uint8_t *text; - unsigned len; - - /* pre->hashed_key_xor_ipad contains unclosed "H((key XOR ipad) +" state */ - /* pre->hashed_key_xor_opad contains unclosed "H((key XOR opad) +" state */ - - /* calculate out = H((key XOR ipad) + text) */ - while ((text = va_arg(va, uint8_t*)) != NULL) { - unsigned text_size = va_arg(va, unsigned); - md5sha_hash(&pre->hashed_key_xor_ipad, text, text_size); - } - len = sha_end(&pre->hashed_key_xor_ipad, out); - - /* out = H((key XOR opad) + out) */ - md5sha_hash(&pre->hashed_key_xor_opad, out, len); - return sha_end(&pre->hashed_key_xor_opad, out); -} - -static unsigned hmac_sha_precomputed(hmac_precomputed_t *pre_init, uint8_t *out, ...) -{ - hmac_precomputed_t pre; - va_list va; - unsigned len; - - va_start(va, out); - pre = *pre_init; /* struct copy */ - len = hmac_sha_precomputed_v(&pre, out, va); - va_end(va); - return len; -} - -#if !ENABLE_FEATURE_TLS_SHA1 -#define hmac(tls,out,key,key_size,...) \ - hmac(out,key,key_size, __VA_ARGS__) -#endif -static unsigned hmac(tls_state_t *tls, uint8_t *out, uint8_t *key, unsigned key_size, ...) -{ - hmac_precomputed_t pre; - va_list va; - unsigned len; - - va_start(va, key_size); - - hmac_begin(&pre, key, key_size, - (ENABLE_FEATURE_TLS_SHA1 && tls->MAC_size == SHA1_OUTSIZE) - ? sha1_begin - : sha256_begin - ); - len = hmac_sha_precomputed_v(&pre, out, va); - - va_end(va); - return len; -} - // RFC 5246: // 5. HMAC and the Pseudorandom Function //... @@ -559,7 +407,7 @@ static void prf_hmac_sha256(/*tls_state_t *tls,*/ const char *label, uint8_t *seed, unsigned seed_size) { - hmac_precomputed_t pre; + hmac_ctx_t ctx; uint8_t a[TLS_MAX_MAC_SIZE]; uint8_t *out_p = outbuf; unsigned label_size = strlen(label); @@ -569,26 +417,26 @@ static void prf_hmac_sha256(/*tls_state_t *tls,*/ #define SEED label, label_size, seed, seed_size #define A a, MAC_size - hmac_begin(&pre, secret, secret_size, sha256_begin); + hmac_begin(&ctx, secret, secret_size, sha256_begin); /* A(1) = HMAC_hash(secret, seed) */ - hmac_sha_precomputed(&pre, a, SEED, NULL); + hmac_peek_hash(&ctx, a, SEED, NULL); for (;;) { /* HMAC_hash(secret, A(1) + seed) */ if (outbuf_size <= MAC_size) { /* Last, possibly incomplete, block */ /* (use a[] as temp buffer) */ - hmac_sha_precomputed(&pre, a, A, SEED, NULL); + hmac_peek_hash(&ctx, a, A, SEED, NULL); memcpy(out_p, a, outbuf_size); return; } /* Not last block. Store directly to result buffer */ - hmac_sha_precomputed(&pre, out_p, A, SEED, NULL); + hmac_peek_hash(&ctx, out_p, A, SEED, NULL); out_p += MAC_size; outbuf_size -= MAC_size; /* A(2) = HMAC_hash(secret, A(1)) */ - hmac_sha_precomputed(&pre, a, A, NULL); + hmac_peek_hash(&ctx, a, A, NULL); } #undef A #undef SECRET @@ -655,6 +503,29 @@ static void *tls_get_zeroed_outbuf(tls_state_t *tls, int len) return record; } +/* Calculate the HMAC over the list of blocks */ +#if !ENABLE_FEATURE_TLS_SHA1 +#define hmac_blocks(tls,out,key,key_size,...) \ + hmac_blocks(out,key,key_size, __VA_ARGS__) +#endif +static unsigned hmac_blocks(tls_state_t *tls, uint8_t *out, uint8_t *key, unsigned key_size, ...) +{ + hmac_ctx_t ctx; + va_list va; + + hmac_begin(&ctx, key, key_size, + (ENABLE_FEATURE_TLS_SHA1 && tls->MAC_size == SHA1_OUTSIZE) + ? sha1_begin + : sha256_begin + ); + + va_start(va, key_size); + hmac_hash_v(&ctx, va); + va_end(va); + + return hmac_end(&ctx, out); +} + static void xwrite_encrypted_and_hmac_signed(tls_state_t *tls, unsigned size, unsigned type) { uint8_t *buf = tls->outbuf + OUTBUF_PFX; @@ -676,7 +547,7 @@ static void xwrite_encrypted_and_hmac_signed(tls_state_t *tls, unsigned size, un xhdr->len16_lo = size & 0xff; /* Calculate MAC signature */ - hmac(tls, buf + size, /* result */ + hmac_blocks(tls, buf + size, /* result */ tls->client_write_MAC_key, TLS_MAC_SIZE(tls), &tls->write_seq64_be, sizeof(tls->write_seq64_be), xhdr, RECHDR_LEN, @@ -865,8 +736,13 @@ static void xwrite_encrypted_aesgcm(tls_state_t *tls, unsigned size, unsigned ty cnt++; COUNTER(nonce) = htonl(cnt); /* yes, first cnt here is 2 (!) */ aes_encrypt_one_block(&tls->aes_encrypt, nonce, scratch); - n = remaining > AES_BLOCK_SIZE ? AES_BLOCK_SIZE : remaining; - xorbuf(buf, scratch, n); + if (remaining >= AES_BLOCK_SIZE) { + n = AES_BLOCK_SIZE; + xorbuf_AES_BLOCK_SIZE(buf, scratch); + } else { + n = remaining; + xorbuf(buf, scratch, n); + } buf += n; remaining -= n; } @@ -1024,7 +900,7 @@ static void tls_aesgcm_decrypt(tls_state_t *tls, uint8_t *buf, int size) COUNTER(nonce) = htonl(cnt); /* yes, first cnt here is 2 (!) */ aes_encrypt_one_block(&tls->aes_decrypt, nonce, scratch); n = remaining > AES_BLOCK_SIZE ? AES_BLOCK_SIZE : remaining; - xorbuf3(buf, scratch, buf + 8, n); + xorbuf_3(buf, scratch, buf + 8, n); buf += n; remaining -= n; } @@ -1095,7 +971,10 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) tls->buffered_size = 0; goto end; } - bb_perror_msg_and_die("short read, have only %d", total); + /* Used to say "wget: short read, have only 186" here. More informative: */ + if (total < RECHDR_LEN) + bb_perror_msg_and_die("%s header: got %d bytes", "truncated TLS record", total); + bb_perror_msg_and_die("%s: expected %d, got %d bytes", "truncated TLS record", target, total); } dump_raw_in("<< %s\n", tls->inbuf + total, sz); total += sz; diff --git a/networking/tls.h b/networking/tls.h index 0173b87b27..9751d30ff7 100644 --- a/networking/tls.h +++ b/networking/tls.h @@ -82,10 +82,9 @@ typedef int16_t int16; void tls_get_random(void *buf, unsigned len) FAST_FUNC; -void xorbuf(void* buf, const void* mask, unsigned count) FAST_FUNC; - #define ALIGNED_long ALIGNED(sizeof(long)) -void xorbuf_aligned_AES_BLOCK_SIZE(void* buf, const void* mask) FAST_FUNC; +#define xorbuf_aligned_AES_BLOCK_SIZE(dst,src) xorbuf16_aligned_long(dst,src) +#define xorbuf_AES_BLOCK_SIZE(dst,src) xorbuf16(dst,src) #define matrixCryptoGetPrngData(buf, len, userPtr) (tls_get_random(buf, len), PS_SUCCESS) diff --git a/networking/tls_aesgcm.c b/networking/tls_aesgcm.c index 5ddcdd2ad8..9c2381a571 100644 --- a/networking/tls_aesgcm.c +++ b/networking/tls_aesgcm.c @@ -167,10 +167,7 @@ void FAST_FUNC aesgcm_GHASH(byte* h, blocks = cSz / AES_BLOCK_SIZE; partial = cSz % AES_BLOCK_SIZE; while (blocks--) { - if (BB_UNALIGNED_MEMACCESS_OK) // c is not guaranteed to be aligned - xorbuf_aligned_AES_BLOCK_SIZE(x, c); - else - xorbuf(x, c, AES_BLOCK_SIZE); + xorbuf_AES_BLOCK_SIZE(x, c); GMULT(x, h); c += AES_BLOCK_SIZE; } diff --git a/networking/udhcp/d6_dhcpc.c b/networking/udhcp/d6_dhcpc.c index 79cef19993..19c961d5c4 100644 --- a/networking/udhcp/d6_dhcpc.c +++ b/networking/udhcp/d6_dhcpc.c @@ -148,10 +148,11 @@ enum { OPT_o = 1 << 12, OPT_x = 1 << 13, OPT_f = 1 << 14, - OPT_l = 1 << 15, - OPT_d = 1 << 16, + OPT_m = 1 << 15, + OPT_l = 1 << 16, + OPT_d = 1 << 17, /* The rest has variable bit positions, need to be clever */ - OPTBIT_d = 16, + OPTBIT_d = 17, USE_FOR_MMU( OPTBIT_b,) ///IF_FEATURE_UDHCPC_ARPING(OPTBIT_a,) IF_FEATURE_UDHCP_PORT( OPTBIT_P,) @@ -268,6 +269,23 @@ static void option_to_env(const uint8_t *option, const uint8_t *option_end) //case D6_OPT_SERVERID: case D6_OPT_IA_NA: case D6_OPT_IA_PD: +/* 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | OPTION_IA_PD | option-length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | IAID (4 octets) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | T1 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | T2 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * . . + * . IA_PD-options . + * . . + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + /* recurse to handle "IA_PD-options" field */ option_to_env(option + 16, option + 4 + option[3]); break; //case D6_OPT_IA_TA: @@ -604,6 +622,31 @@ static NOINLINE int send_d6_info_request(void) return d6_mcast_from_client_data_ifindex(&packet, opt_ptr); } +/* + * RFC 3315 10. Identity Association + * + * An "identity-association" (IA) is a construct through which a server + * and a client can identify, group, and manage a set of related IPv6 + * addresses. Each IA consists of an IAID and associated configuration + * information. + * + * A client must associate at least one distinct IA with each of its + * network interfaces for which it is to request the assignment of IPv6 + * addresses from a DHCP server. The client uses the IAs assigned to an + * interface to obtain configuration information from a server for that + * interface. Each IA must be associated with exactly one interface. + * + * The IAID uniquely identifies the IA and must be chosen to be unique + * among the IAIDs on the client. The IAID is chosen by the client. + * For any given use of an IA by the client, the IAID for that IA MUST + * be consistent across restarts of the DHCP client... + */ +/* Generate IAID. We base it on our MAC address' last 4 bytes */ +static void generate_iaid(uint8_t *iaid) +{ + memcpy(iaid, &client_data.client_mac[2], 4); +} + /* Multicast a DHCPv6 Solicit packet to the network, with an optionally requested IP. * * RFC 3315 17.1.1. Creation of Solicit Messages @@ -703,7 +746,7 @@ static NOINLINE int send_d6_discover(struct in6_addr *requested_ipv6) client6_data.ia_na = xzalloc(len); client6_data.ia_na->code = D6_OPT_IA_NA; client6_data.ia_na->len = len - 4; - *(bb__aliased_uint32_t*)client6_data.ia_na->data = rand(); /* IAID */ + generate_iaid(client6_data.ia_na->data); /* IAID */ if (requested_ipv6) { struct d6_option *iaaddr = (void*)(client6_data.ia_na->data + 4+4+4); iaaddr->code = D6_OPT_IAADDR; @@ -721,7 +764,7 @@ static NOINLINE int send_d6_discover(struct in6_addr *requested_ipv6) client6_data.ia_pd = xzalloc(len); client6_data.ia_pd->code = D6_OPT_IA_PD; client6_data.ia_pd->len = len - 4; - *(bb__aliased_uint32_t*)client6_data.ia_pd->data = rand(); /* IAID */ + generate_iaid(client6_data.ia_pd->data); /* IAID */ opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, len); } @@ -1131,12 +1174,11 @@ static void client_background(void) //usage:#endif //usage:#define udhcpc6_trivial_usage //usage: "[-fbq"IF_UDHCP_VERBOSE("v")"R] [-t N] [-T SEC] [-A SEC|-n] [-i IFACE] [-s PROG]\n" -//usage: " [-p PIDFILE]"IF_FEATURE_UDHCP_PORT(" [-P PORT]")" [-ldo] [-r IPv6] [-x OPT:VAL]... [-O OPT]..." +//usage: " [-p PIDFILE]"IF_FEATURE_UDHCP_PORT(" [-P PORT]")" [-mldo] [-r IPv6] [-x OPT:VAL]... [-O OPT]..." //usage:#define udhcpc6_full_usage "\n" //usage: "\n -i IFACE Interface to use (default "CONFIG_UDHCPC_DEFAULT_INTERFACE")" //usage: "\n -p FILE Create pidfile" //usage: "\n -s PROG Run PROG at DHCP events (default "CONFIG_UDHCPC6_DEFAULT_SCRIPT")" -//usage: "\n -B Request broadcast replies" //usage: "\n -t N Send up to N discover packets" //usage: "\n -T SEC Pause between packets (default 3)" //usage: "\n -A SEC Wait if lease is not obtained (default 20)" @@ -1154,6 +1196,7 @@ static void client_background(void) ////usage: IF_FEATURE_UDHCPC_ARPING( ////usage: "\n -a Use arping to validate offered address" ////usage: ) +//usage: "\n -m Send multicast renew requests rather than unicast ones" //usage: "\n -l Send 'information request' instead of 'solicit'" //usage: "\n (used for servers which do not assign IPv6 addresses)" //usage: "\n -r IPv6 Request this address ('no' to not request any IP)" @@ -1211,7 +1254,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv) /* Parse command line */ opt = getopt32long(argv, "^" /* O,x: list; -T,-t,-A take numeric param */ - "i:np:qRr:s:T:+t:+SA:+O:*ox:*fld" + "i:np:qRr:s:T:+t:+SA:+O:*ox:*fmld" USE_FOR_MMU("b") ///IF_FEATURE_UDHCPC_ARPING("a") IF_FEATURE_UDHCP_PORT("P:") @@ -1464,7 +1507,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv) if (opt & OPT_l) send_d6_info_request(); else - send_d6_renew(&srv6_buf, requested_ipv6); + send_d6_renew(OPT_m ? NULL : &srv6_buf, requested_ipv6); timeout = discover_timeout; packet_num++; continue; @@ -1606,62 +1649,6 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv) case RENEW_REQUESTED: case REBINDING: if (packet.d6_msg_type == D6_MSG_REPLY) { - unsigned start; - uint32_t lease_seconds; - struct d6_option *option; - unsigned address_timeout; - unsigned prefix_timeout; - type_is_ok: - change_listen_mode(LISTEN_NONE); - - address_timeout = 0; - prefix_timeout = 0; - option = d6_find_option(packet.d6_options, packet_end, D6_OPT_STATUS_CODE); - if (option && (option->data[0] | option->data[1]) != 0) { -///FIXME: -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | OPTION_STATUS_CODE | option-len | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | status-code | | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | -// . status-message . -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// so why do we think it's NAK if data[0] is zero but data[1] is not? That's wrong... -// we should also check that option->len is ok (i.e. not 0), right? - /* return to init state */ - bb_info_msg("received DHCP NAK (%u)", option->data[4]); - d6_run_script(packet.d6_options, - packet_end, "nak"); - if (client_data.state != REQUESTING) - d6_run_script_no_option("deconfig"); - sleep(3); /* avoid excessive network traffic */ - client_data.state = INIT_SELECTING; - client_data.first_secs = 0; /* make secs field count from 0 */ - requested_ipv6 = NULL; - timeout = 0; - packet_num = 0; - continue; - } - option = d6_copy_option(packet.d6_options, packet_end, D6_OPT_SERVERID); - if (!option) { - bb_simple_info_msg("no server ID, ignoring packet"); - continue; - /* still selecting - this server looks bad */ - } -//Note: we do not bother comparing server IDs in Advertise and Reply msgs. -//server_id variable is used solely for creation of proper server_id option -//in outgoing packets. (why DHCPv6 even introduced it is a mystery). - free(client6_data.server_id); - client6_data.server_id = option; - if (packet.d6_msg_type == D6_MSG_ADVERTISE) { - /* enter requesting state */ - change_listen_mode(LISTEN_RAW); - client_data.state = REQUESTING; - timeout = 0; - packet_num = 0; - continue; - } - /* It's a D6_MSG_REPLY */ /* * RFC 3315 18.1.8. Receipt of Reply Messages * @@ -1747,6 +1734,67 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv) * . . * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ + unsigned start; + uint32_t lease_seconds; + struct d6_option *option; + unsigned address_timeout; + unsigned prefix_timeout; + type_is_ok: + change_listen_mode(LISTEN_NONE); + + address_timeout = 0; + prefix_timeout = 0; + option = d6_find_option(packet.d6_options, packet_end, D6_OPT_STATUS_CODE); + if (option) { +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | OPTION_STATUS_CODE | option-len | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | status-code | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +// . status-message . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + unsigned len, status; + len = ((unsigned)option->len_hi << 8) + option->len; + if (len < 2) { + bb_simple_error_msg("invalid OPTION_STATUS_CODE, ignoring packet"); + continue; + } + status = ((unsigned)option->data[0] << 8) + option->data[1]; + if (status != 0) { +//TODO: handle status == 5 (UseMulticast)? + /* return to init state */ + bb_info_msg("received DHCP NAK: %u '%.*s'", status, len - 2, option->data + 2); + d6_run_script(packet.d6_options, packet_end, "nak"); + if (client_data.state != REQUESTING) + d6_run_script_no_option("deconfig"); + sleep(3); /* avoid excessive network traffic */ + client_data.state = INIT_SELECTING; + client_data.first_secs = 0; /* make secs field count from 0 */ + requested_ipv6 = NULL; + timeout = 0; + packet_num = 0; + continue; + } + } + option = d6_copy_option(packet.d6_options, packet_end, D6_OPT_SERVERID); + if (!option) { + bb_simple_info_msg("no server ID, ignoring packet"); + continue; + /* still selecting - this server looks bad */ + } +//Note: we do not bother comparing server IDs in Advertise and Reply msgs. +//server_id variable is used solely for creation of proper server_id option +//in outgoing packets. (why DHCPv6 even introduced it is a mystery). + free(client6_data.server_id); + client6_data.server_id = option; + if (packet.d6_msg_type == D6_MSG_ADVERTISE) { + /* enter requesting state */ + change_listen_mode(LISTEN_RAW); + client_data.state = REQUESTING; + timeout = 0; + packet_num = 0; + continue; + } if (option_mask32 & OPT_r) { struct d6_option *iaaddr; @@ -1790,6 +1838,21 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv) free(client6_data.ia_pd); client6_data.ia_pd = d6_copy_option(packet.d6_options, packet_end, D6_OPT_IA_PD); +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | OPTION_IA_PD | option-length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | IAID (4 octets) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | T1 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | T2 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . IA_PD-options . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ if (!client6_data.ia_pd) { bb_info_msg("no %s option%s", "IA_PD", ", ignoring packet"); continue; diff --git a/networking/udhcp/d6_packet.c b/networking/udhcp/d6_packet.c index 142de9b435..1d7541948b 100644 --- a/networking/udhcp/d6_packet.c +++ b/networking/udhcp/d6_packet.c @@ -153,13 +153,15 @@ int FAST_FUNC d6_send_kernel_packet_from_client_data_ifindex( } setsockopt_reuseaddr(fd); - memset(&sa, 0, sizeof(sa)); - sa.sin6_family = AF_INET6; - sa.sin6_port = htons(source_port); - sa.sin6_addr = *src_ipv6; /* struct copy */ - if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { - msg = "bind(%s)"; - goto ret_close; + if (src_ipv6) { + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_port = htons(source_port); + sa.sin6_addr = *src_ipv6; /* struct copy */ + if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + msg = "bind(%s)"; + goto ret_close; + } } memset(&sa, 0, sizeof(sa)); diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c index 2904119e54..b9cbd6464a 100644 --- a/networking/udhcp/dhcpd.c +++ b/networking/udhcp/dhcpd.c @@ -575,29 +575,51 @@ static void send_packet_to_client(struct dhcp_packet *dhcp_pkt, int force_broadc const uint8_t *chaddr; uint32_t ciaddr; - // Was: + // Logic: //if (force_broadcast) { /* broadcast */ } //else if (dhcp_pkt->ciaddr) { /* unicast to dhcp_pkt->ciaddr */ } + // ^^^ dhcp_pkt->ciaddr comes from client's request packet. + // We expect such clients to have an UDP socket listening on that IP. //else if (dhcp_pkt->flags & htons(BROADCAST_FLAG)) { /* broadcast */ } //else { /* unicast to dhcp_pkt->yiaddr */ } - // But this is wrong: yiaddr is _our_ idea what client's IP is - // (for example, from lease file). Client may not know that, - // and may not have UDP socket listening on that IP! - // We should never unicast to dhcp_pkt->yiaddr! - // dhcp_pkt->ciaddr, OTOH, comes from client's request packet, - // and can be used. - - if (force_broadcast - || (dhcp_pkt->flags & htons(BROADCAST_FLAG)) - || dhcp_pkt->ciaddr == 0 + // ^^^ The last case is confusing, but *should* work. + // It's a case where client have sent a DISCOVER + // and does not have a kernel UDP socket listening on the IP + // we are offering in yiaddr (it does not know the IP yet)! + // This *should* work because client *should* listen on a raw socket + // instead at this time (IOW: it should examine ALL IPv4 packets + // "by hand", not relying on kernel's UDP stack.) + + chaddr = dhcp_pkt->chaddr; + + if (dhcp_pkt->ciaddr == 0 + || force_broadcast /* sending DHCPNAK pkt? */ ) { - log1s("broadcasting packet to client"); - ciaddr = INADDR_BROADCAST; - chaddr = MAC_BCAST_ADDR; + if (dhcp_pkt->flags & htons(BROADCAST_FLAG) + || force_broadcast /* sending DHCPNAK pkt? */ + ) { +// RFC 2131: +// If 'giaddr' is zero and 'ciaddr' is zero, and the broadcast bit is +// set, then the server broadcasts DHCPOFFER and DHCPACK messages to +// 0xffffffff. ... +// In all cases, when 'giaddr' is zero, the server broadcasts any DHCPNAK +// messages to 0xffffffff. + ciaddr = INADDR_BROADCAST; + chaddr = MAC_BCAST_ADDR; + log1s("broadcasting packet to client"); + } else { +// If the broadcast bit is not set and 'giaddr' is zero and +// 'ciaddr' is zero, then the server unicasts DHCPOFFER and DHCPACK +// messages to the client's hardware address and 'yiaddr' address. + ciaddr = dhcp_pkt->yiaddr; + log1("unicasting packet to client %ciaddr", 'y'); + } } else { - log1s("unicasting packet to client ciaddr"); +// If the 'giaddr' +// field is zero and the 'ciaddr' field is nonzero, then the server +// unicasts DHCPOFFER and DHCPACK messages to the address in 'ciaddr'. ciaddr = dhcp_pkt->ciaddr; - chaddr = dhcp_pkt->chaddr; + log1("unicasting packet to client %ciaddr", 'c'); } udhcp_send_raw_packet(dhcp_pkt, @@ -624,6 +646,10 @@ static void send_packet_to_relay(struct dhcp_packet *dhcp_pkt) static void send_packet(struct dhcp_packet *dhcp_pkt, int force_broadcast) { if (dhcp_pkt->gateway_nip) +// RFC 2131: +// If the 'giaddr' field in a DHCP message from a client is non-zero, +// the server sends any return messages to the 'DHCP server' port on the +// BOOTP relay agent whose address appears in 'giaddr'. send_packet_to_relay(dhcp_pkt); else send_packet_to_client(dhcp_pkt, force_broadcast); diff --git a/networking/wget.c b/networking/wget.c index 512bebfc2f..ec3767793d 100644 --- a/networking/wget.c +++ b/networking/wget.c @@ -1178,7 +1178,9 @@ static void download_one_url(const char *url) /*G.content_len = 0; - redundant, got_clen = 0 is enough */ G.got_clen = 0; G.chunked = 0; - if (use_proxy || target.protocol[0] != 'f' /*not ftp[s]*/) { + if (!ENABLE_FEATURE_WGET_FTP + || use_proxy || target.protocol[0] != 'f' /*not ftp[s]*/ + ) { /* * HTTP session */ @@ -1447,14 +1449,15 @@ However, in real world it was observed that some web servers /* For HTTP, data is pumped over the same connection */ dfp = sfp; - } else { + } #if ENABLE_FEATURE_WGET_FTP + else { /* * FTP session */ sfp = prepare_ftp_session(&dfp, &target, lsa); -#endif } +#endif free(lsa); diff --git a/procps/pgrep.c b/procps/pgrep.c index 04ae92a672..299e2dac72 100644 --- a/procps/pgrep.c +++ b/procps/pgrep.c @@ -215,16 +215,16 @@ int pgrep_main(int argc UNUSED_PARAM, char **argv) if (!match) { again: match = (regexec(&re_buffer, cmd, 1, re_match, 0) == 0); + if (match && OPT_ANCHOR) { + /* -x requires full string match */ + match = (re_match[0].rm_so == 0 && cmd[re_match[0].rm_eo] == '\0'); + } if (!match && cmd != proc->comm) { /* if argv[] did not match, try comm */ cmdlen = -1; cmd = proc->comm; goto again; } - if (match && OPT_ANCHOR) { - /* -x requires full string match */ - match = (re_match[0].rm_so == 0 && cmd[re_match[0].rm_eo] == '\0'); - } } /* NB: OPT_INVERT is always 0 or 1 */ diff --git a/procps/pmap.c b/procps/pmap.c index 49f7688d9c..0cac265c84 100644 --- a/procps/pmap.c +++ b/procps/pmap.c @@ -29,10 +29,14 @@ #if ULLONG_MAX == 0xffffffff # define TABS "\t" +# define SIZEWIDTHx "7" +# define SIZEWIDTH "9" # define AFMTLL "8" # define DASHES "" #else # define TABS "\t\t" +# define SIZEWIDTHx "15" +# define SIZEWIDTH "17" # define AFMTLL "16" # define DASHES "--------" #endif @@ -42,49 +46,160 @@ enum { OPT_q = 1 << 1, }; -static void print_smaprec(struct smaprec *currec, void *data) -{ - unsigned opt = (uintptr_t)data; +struct smaprec { + // For mixed 32/64 userspace, 32-bit pmap still needs + // 64-bit field here to correctly show 64-bit processes: + unsigned long long smap_start; + // Make size wider too: + // I've seen 1203765248 kb large "---p" mapping in a browser, + // this cuts close to 4 terabytes. + unsigned long long smap_size; + // (strictly speaking, other fields need to be wider too, + // but they are in kbytes, not bytes, and they hold sizes, + // not start addresses, sizes tend to be less than 4 terabytes) + unsigned long private_dirty; + unsigned long smap_pss, smap_swap; + char smap_mode[5]; + char *smap_name; +}; +// How long the filenames and command lines we want to handle? +#define PMAP_BUFSZ 4096 + +static void print_smaprec(struct smaprec *currec) +{ printf("%0" AFMTLL "llx ", currec->smap_start); - if (opt & OPT_x) - printf("%7lu %7lu %7lu %7lu ", + if (option_mask32 & OPT_x) + printf("%7llu %7lu %7lu %7lu ", currec->smap_size, currec->smap_pss, currec->private_dirty, currec->smap_swap); else - printf("%7luK", currec->smap_size); + printf("%7lluK", currec->smap_size); + + printf(" %.4s %s\n", currec->smap_mode, currec->smap_name ? : " [ anon ]"); +} + +/* libbb's procps_read_smaps() looks somewhat similar, + * but the collected information is sufficiently different + * that merging them into one function is not a good idea + * (unless you feel masochistic today). + */ +static int read_smaps(pid_t pid, char buf[PMAP_BUFSZ], struct smaprec *total) +{ + FILE *file; + struct smaprec currec; + char filename[sizeof("/proc/%u/smaps") + sizeof(int)*3]; + + sprintf(filename, "/proc/%u/smaps", (int)pid); + + file = fopen_for_read(filename); + if (!file) + return 1; + + memset(&currec, 0, sizeof(currec)); + while (fgets(buf, PMAP_BUFSZ, file)) { + // Each mapping datum has this form: + // f7d29000-f7d39000 rw-s FILEOFS M:m INODE FILENAME + // Size: nnn kB + // Rss: nnn kB + // ..... + + char *tp, *p; + +#define bytes4 *(uint32_t*)buf +#define Pss_ PACK32_BYTES('P','s','s',':') +#define Swap PACK32_BYTES('S','w','a','p') +#define Priv PACK32_BYTES('P','r','i','v') +#define FETCH(X, N) \ + tp = skip_whitespace(buf+4 + (N)); \ + total->X += currec.X = fast_strtoul_10(&tp); \ + continue +#define SCAN(S, X) \ +if (memcmp(buf+4, S, sizeof(S)-1) == 0) { \ + FETCH(X, sizeof(S)-1); \ +} + if (bytes4 == Pss_) { + FETCH(smap_pss, 0); + } + if (bytes4 == Swap && buf[4] == ':') { + FETCH(smap_swap, 1); + } + if (bytes4 == Priv) { + SCAN("ate_Dirty:", private_dirty); + } +#undef bytes4 +#undef Pss_ +#undef Swap +#undef Priv +#undef FETCH +#undef SCAN + tp = strchr(buf, '-'); + if (tp) { + // We reached next mapping - the line of this form: + // f7d29000-f7d39000 rw-s FILEOFS M:m INODE FILENAME + + // If we have a previous record, there's nothing more + // for it, print and clear currec + if (currec.smap_size) + print_smaprec(&currec); + free(currec.smap_name); + memset(&currec, 0, sizeof(currec)); + + *tp = ' '; + tp = buf; + currec.smap_start = fast_strtoull_16(&tp); + currec.smap_size = (fast_strtoull_16(&tp) - currec.smap_start) >> 10; + strncpy(currec.smap_mode, tp, sizeof(currec.smap_mode)-1); + + // skipping "rw-s FILEOFS M:m INODE " + tp = skip_fields(tp, 4); + tp = skip_whitespace(tp); // there may be many spaces, can't just "tp++" + p = strchrnul(tp, '\n'); + if (p != tp) { + currec.smap_name = xstrndup(tp, p - tp); + } + total->smap_size += currec.smap_size; + } + } // while (got line) + fclose(file); + + if (currec.smap_size) + print_smaprec(&currec); + free(currec.smap_name); - printf(" %.4s %s\n", currec->smap_mode, currec->smap_name); + return 0; } static int procps_get_maps(pid_t pid, unsigned opt) { struct smaprec total; int ret; - char buf[256]; + char buf[PMAP_BUFSZ] ALIGN4; + + ret = read_cmdline(buf, sizeof(buf), pid, NULL); + if (ret < 0) + return ret; - read_cmdline(buf, sizeof(buf), pid, NULL); printf("%u: %s\n", (int)pid, buf); if (!(opt & OPT_q) && (opt & OPT_x)) puts("Address" TABS " Kbytes PSS Dirty Swap Mode Mapping"); memset(&total, 0, sizeof(total)); - - ret = procps_read_smaps(pid, &total, print_smaprec, (void*)(uintptr_t)opt); + ret = read_smaps(pid, buf, &total); if (ret) return ret; if (!(opt & OPT_q)) { if (opt & OPT_x) printf("--------" DASHES " ------ ------ ------ ------\n" - "total" TABS " %7lu %7lu %7lu %7lu\n", + "total kB %"SIZEWIDTHx"llu %7lu %7lu %7lu\n", total.smap_size, total.smap_pss, total.private_dirty, total.smap_swap); else - printf("mapped: %luK\n", total.smap_size); + printf(" total %"SIZEWIDTH"lluK\n", total.smap_size); } return 0; diff --git a/procps/top.c b/procps/top.c index 09d31c673d..7902e82f06 100644 --- a/procps/top.c +++ b/procps/top.c @@ -117,10 +117,15 @@ #include "libbb.h" -#define ESC "\033" +#define ESC "\033" +#define HOME ESC"[H" +#define CLREOS ESC"[J" +#define CLREOL ESC"[K" +#define REVERSE ESC"[7m" +#define NORMAL ESC"[m" typedef struct top_status_t { - unsigned long vsz; + unsigned long memsize; #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE unsigned long ticks; unsigned pcpu; /* delta of ticks */ @@ -161,13 +166,16 @@ struct globals { top_status_t *top; int ntop; smallint inverted; + smallint first_line_printed; #if ENABLE_FEATURE_TOPMEM smallint sort_field; #endif #if ENABLE_FEATURE_TOP_SMP_CPU smallint smp_cpu_info; /* one/many cpu info lines? */ #endif - unsigned lines; /* screen height */ + int lines_remaining; + unsigned lines; /* screen height */ + unsigned scr_width; /* width, clamped <= LINE_BUF_SIZE-2 */ #if ENABLE_FEATURE_TOP_INTERACTIVE struct termios initial_settings; int scroll_ofs; @@ -212,7 +220,6 @@ struct globals { #define cpu_prev_jif (G.cpu_prev_jif ) #define num_cpus (G.num_cpus ) #define total_pcpu (G.total_pcpu ) -#define line_buf (G.line_buf ) #define INIT_G() do { \ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ BUILD_BUG_ON(LINE_BUF_SIZE <= 80); \ @@ -241,8 +248,8 @@ static int pid_sort(top_status_t *P, top_status_t *Q) static int mem_sort(top_status_t *P, top_status_t *Q) { /* We want to avoid unsigned->signed and truncation errors */ - if (Q->vsz < P->vsz) return -1; - return Q->vsz != P->vsz; /* 0 if ==, 1 if > */ + if (Q->memsize < P->memsize) return -1; + return Q->memsize != P->memsize; /* 0 if ==, 1 if > */ } @@ -283,9 +290,9 @@ static NOINLINE int read_cpu_jiffy(FILE *fp, jiffy_counts_t *p_jif) #endif int ret; - if (!fgets(line_buf, LINE_BUF_SIZE, fp) || line_buf[0] != 'c' /* not "cpu" */) + if (!fgets(G.line_buf, LINE_BUF_SIZE, fp) || G.line_buf[0] != 'c' /* not "cpu" */) return 0; - ret = sscanf(line_buf, fmt, + ret = sscanf(G.line_buf, fmt, &p_jif->usr, &p_jif->nic, &p_jif->sys, &p_jif->idle, &p_jif->iowait, &p_jif->irq, &p_jif->softirq, &p_jif->steal); @@ -362,7 +369,7 @@ static void do_stats(void) get_jiffy_counts(); total_pcpu = 0; - /* total_vsz = 0; */ + /* total_memsize = 0; */ new_hist = xmalloc(sizeof(new_hist[0]) * ntop); /* * Make a pass through the data to get stats. @@ -394,7 +401,7 @@ static void do_stats(void) i = (i+1) % prev_hist_count; /* hist_iterations++; */ } while (i != last_i); - /* total_vsz += cur->vsz; */ + /* total_memsize += cur->memsize; */ } /* @@ -407,6 +414,39 @@ static void do_stats(void) #endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ +static void print_line_buf(void) +{ + const char *fmt; + + G.lines_remaining--; + fmt = OPT_BATCH_MODE ? "\n""%.*s" : "\n""%.*s"CLREOL; + if (!G.first_line_printed) { + G.first_line_printed = 1; + /* Go to top */ + fmt = OPT_BATCH_MODE ? "%.*s" : HOME"%.*s"CLREOL; + } + printf(fmt, G.scr_width - 1, G.line_buf); +} + +static void print_line_bold(void) +{ + G.lines_remaining--; +//we never print first line in bold +// if (!G.first_line_printed) { +// printf(OPT_BATCH_MODE ? "%.*s" : HOME"%.*s"CLREOL, G.scr_width - 1, G.line_buf); +// G.first_line_printed = 1; +// } else { + printf(OPT_BATCH_MODE ? "\n""%.*s" : "\n"REVERSE"%.*s"NORMAL CLREOL, G.scr_width - 1, G.line_buf); +// } +} + +static void print_end(void) +{ + fputs_stdout(OPT_BATCH_MODE ? "\n" : CLREOS"\r"); + /* next print will be "first line" (will clear the screen) */ + G.first_line_printed = 0; +} + #if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS && ENABLE_FEATURE_TOP_DECIMALS /* formats 7 char string (8 with terminating NUL) */ static char *fmt_100percent_8(char pbuf[8], unsigned value, unsigned total) @@ -433,7 +473,7 @@ static char *fmt_100percent_8(char pbuf[8], unsigned value, unsigned total) #endif #if ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS -static void display_cpus(int scr_width, char *scrbuf, int *lines_rem_p) +static void display_cpus(void) { /* * xxx% = (cur_jif.xxx - prev_jif.xxx) / (cur_jif.total - prev_jif.total) * 100% @@ -469,8 +509,8 @@ static void display_cpus(int scr_width, char *scrbuf, int *lines_rem_p) # else /* Loop thru CPU(s) */ n_cpu_lines = smp_cpu_info ? num_cpus : 1; - if (n_cpu_lines > *lines_rem_p) - n_cpu_lines = *lines_rem_p; + if (n_cpu_lines > G.lines_remaining) + n_cpu_lines = G.lines_remaining; for (i = 0; i < n_cpu_lines; i++) { p_jif = &cpu_jif[i]; @@ -488,7 +528,7 @@ static void display_cpus(int scr_width, char *scrbuf, int *lines_rem_p) CALC_STAT(softirq); /*CALC_STAT(steal);*/ - snprintf(scrbuf, scr_width, + sprintf(G.line_buf, /* Barely fits in 79 chars when in "decimals" mode. */ # if ENABLE_FEATURE_TOP_SMP_CPU "CPU%s:"FMT"usr"FMT"sys"FMT"nic"FMT"idle"FMT"io"FMT"irq"FMT"sirq", @@ -501,16 +541,15 @@ static void display_cpus(int scr_width, char *scrbuf, int *lines_rem_p) /*, SHOW_STAT(steal) - what is this 'steal' thing? */ /* I doubt anyone wants to know it */ ); - puts(scrbuf); + print_line_buf(); } } # undef SHOW_STAT # undef CALC_STAT # undef FMT - *lines_rem_p -= i; } #else /* !ENABLE_FEATURE_TOP_CPU_GLOBAL_PERCENTS */ -# define display_cpus(scr_width, scrbuf, lines_rem) ((void)0) +# define display_cpus() ((void)0) #endif enum { @@ -564,52 +603,55 @@ static void parse_meminfo(unsigned long meminfo[MI_MAX]) fclose(f); } -static unsigned long display_header(int scr_width, int *lines_rem_p) +static void cmdline_to_line_buf_and_print(unsigned offset, unsigned pid, const char *comm) +{ + int width = G.scr_width - offset; + if (width > 1) /* wider than to fit just the NUL? */ + read_cmdline(G.line_buf + offset, width, pid, comm); +//TODO: read_cmdline() sanitizes control chars, but not chars above 0x7e + print_line_buf(); +} + +static unsigned long display_header(void) { - char scrbuf[100]; /* [80] was a bit too low on 8Gb ram box */ char *buf; unsigned long meminfo[MI_MAX]; parse_meminfo(meminfo); /* Output memory info */ - if (scr_width > (int)sizeof(scrbuf)) - scr_width = sizeof(scrbuf); - snprintf(scrbuf, scr_width, + sprintf(G.line_buf, "Mem: %luK used, %luK free, %luK shrd, %luK buff, %luK cached", meminfo[MI_MEMTOTAL] - meminfo[MI_MEMFREE], meminfo[MI_MEMFREE], meminfo[MI_MEMSHARED] + meminfo[MI_SHMEM], meminfo[MI_BUFFERS], meminfo[MI_CACHED]); - /* Go to top & clear to the end of screen */ - printf(OPT_BATCH_MODE ? "%s\n" : ESC"[H" ESC"[J" "%s\n", scrbuf); - (*lines_rem_p)--; + print_line_buf(); /* Display CPU time split as percentage of total time. * This displays either a cumulative line or one line per CPU. */ - display_cpus(scr_width, scrbuf, lines_rem_p); + display_cpus(); /* Read load average as a string */ - buf = stpcpy(scrbuf, "Load average: "); - open_read_close("loadavg", buf, sizeof(scrbuf) - sizeof("Load average: ")); - scrbuf[scr_width - 1] = '\0'; + buf = stpcpy(G.line_buf, "Load average: "); + open_read_close("loadavg", buf, sizeof(G.line_buf) - sizeof("Load average: ")); + G.line_buf[sizeof(G.line_buf) - 1] = '\0'; /* paranoia */ strchrnul(buf, '\n')[0] = '\0'; - puts(scrbuf); - (*lines_rem_p)--; + print_line_buf(); return meminfo[MI_MEMTOTAL]; } -static NOINLINE void display_process_list(int lines_rem, int scr_width) +static NOINLINE void display_process_list(void) { enum { BITS_PER_INT = sizeof(int) * 8 }; top_status_t *s; - unsigned long total_memory = display_header(scr_width, &lines_rem); /* or use total_vsz? */ + unsigned long total_memory = display_header(); /* xxx_shift and xxx_scale variables allow us to replace * expensive divides with multiply and shift */ unsigned pmem_shift, pmem_scale, pmem_half; @@ -621,7 +663,7 @@ static NOINLINE void display_process_list(int lines_rem, int scr_width) #if ENABLE_FEATURE_TOP_DECIMALS # define UPSCALE 1000 -typedef struct { unsigned quot, rem; } bb_div_t; + typedef struct { unsigned quot, rem; } bb_div_t; /* Used to have "div_t name = div((val), 10)" here * (IOW: intended to use libc-compatible way to divide and use * both result and remainder, but musl does not inline div()...) @@ -629,28 +671,34 @@ typedef struct { unsigned quot, rem; } bb_div_t; */ # define CALC_STAT(name, val) bb_div_t name = { (val) / 10, (val) % 10 } # define SHOW_STAT(name) name.quot, '0'+name.rem +# define SANITIZE(name) if (name.quot > 99) name.quot = 99, name.rem = (unsigned char)('+' - '0') # define FMT "%3u.%c" #else # define UPSCALE 100 # define CALC_STAT(name, val) unsigned name = (val) +# define SANITIZE(name) if (name > 99) name = 99 # define SHOW_STAT(name) name # define FMT "%4u%%" #endif - /* what info of the processes is shown */ - printf(OPT_BATCH_MODE ? "%.*s" : ESC"[7m" "%.*s" ESC"[m", scr_width, - " PID PPID USER STAT VSZ %VSZ" + strcpy(G.line_buf, " PID PPID USER STAT RSS %RSS" IF_FEATURE_TOP_SMP_PROCESS(" CPU") IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE(" %CPU") " COMMAND"); - lines_rem--; - - /* - * %VSZ = s->vsz/MemTotal + print_line_bold(); + + /* %RSS = s->memsize / MemTotal * 100% + * Calculate this with multiply and shift. Example: + * shift = 12 + * scale = 100 * 0x1000 / total_memory + * percent_mem = (size_mem * scale) >> shift + * ~= (size_mem >> shift) * scale + * ~= (size_mem >> shift) * 100 * (1 << shift) / total_memory + * ~= size_mem * 100 / total_memory */ pmem_shift = BITS_PER_INT-11; pmem_scale = UPSCALE*(1U<<(BITS_PER_INT-11)) / total_memory; - /* s->vsz is in kb. we want (s->vsz * pmem_scale) to never overflow */ + /* s->memsize is in kb. we want (s->memsize * pmem_scale) to never overflow */ while (pmem_scale >= 512) { pmem_scale /= 4; pmem_shift -= 2; @@ -689,25 +737,29 @@ typedef struct { unsigned quot, rem; } bb_div_t; pcpu_half = (1U << pcpu_shift) / (ENABLE_FEATURE_TOP_DECIMALS ? 20 : 2); /* printf(" pmem_scale=%u pcpu_scale=%u ", pmem_scale, pcpu_scale); */ #endif + if (G.lines_remaining > ntop - G_scroll_ofs) + G.lines_remaining = ntop - G_scroll_ofs; /* Ok, all preliminary data is ready, go through the list */ - scr_width += 2; /* account for leading '\n' and trailing NUL */ - if (lines_rem > ntop - G_scroll_ofs) - lines_rem = ntop - G_scroll_ofs; s = top + G_scroll_ofs; - while (--lines_rem >= 0) { + while (G.lines_remaining > 0) { int n; char *ppu; char ppubuf[sizeof(int)*3 * 2 + 12]; - char vsz_str_buf[8]; + char memsize_str_buf[8]; unsigned col; - CALC_STAT(pmem, (s->vsz*pmem_scale + pmem_half) >> pmem_shift); + CALC_STAT(pmem, (s->memsize*pmem_scale + pmem_half) >> pmem_shift); #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE CALC_STAT(pcpu, (s->pcpu*pcpu_scale + pcpu_half) >> pcpu_shift); #endif + /* VSZ can be much larger than total memory + * (seen values close to 2Tbyte), don't try to display + * "uses 12345.6% of MemTotal" (won't fit the column) + */ + SANITIZE(pmem); - smart_ulltoa5(s->vsz, vsz_str_buf, " mgtpezy"); + smart_ulltoa5(s->memsize, memsize_str_buf, " mgtpezy"); /* PID PPID USER STAT VSZ %VSZ [%CPU] COMMAND */ n = sprintf(ppubuf, "%5u %5u %-8.8s", s->pid, s->ppid, get_cached_username(s->uid)); ppu = ppubuf; @@ -736,27 +788,23 @@ typedef struct { unsigned quot, rem; } bb_div_t; ppu[6+6+8] = '\0'; /* truncate USER */ } shortened: - col = snprintf(line_buf, scr_width, - "\n" "%s %s %.5s" FMT + col = sprintf(G.line_buf, + "%s %s %.5s" FMT IF_FEATURE_TOP_SMP_PROCESS(" %3d") IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE(FMT) " ", ppu, - s->state, vsz_str_buf, + s->state, memsize_str_buf, SHOW_STAT(pmem) IF_FEATURE_TOP_SMP_PROCESS(, s->last_seen_on_cpu) IF_FEATURE_TOP_CPU_USAGE_PERCENTAGE(, SHOW_STAT(pcpu)) ); - if ((int)(scr_width - col) > 1) - read_cmdline(line_buf + col, scr_width - col, s->pid, s->comm); - fputs_stdout(line_buf); + cmdline_to_line_buf_and_print(col, s->pid, s->comm); /* printf(" %d/%d %lld/%lld", s->pcpu, total_pcpu, cur_jif.busy - prev_jif.busy, cur_jif.total - prev_jif.total); */ s++; } /* printf(" %d", hist_iterations); */ - bb_putchar(OPT_BATCH_MODE ? '\n' : '\r'); - fflush_all(); } #undef UPSCALE #undef SHOW_STAT @@ -828,36 +876,34 @@ static int topmem_sort(char *a, char *b) } /* display header info (meminfo / loadavg) */ -static void display_topmem_header(int scr_width, int *lines_rem_p) +static void display_topmem_header(void) { unsigned long meminfo[MI_MAX]; parse_meminfo(meminfo); - snprintf(line_buf, LINE_BUF_SIZE, + sprintf(G.line_buf, "Mem total:%lu anon:%lu map:%lu free:%lu", meminfo[MI_MEMTOTAL], meminfo[MI_ANONPAGES], meminfo[MI_MAPPED], meminfo[MI_MEMFREE]); - printf(OPT_BATCH_MODE ? "%.*s\n" : ESC"[H" ESC"[J" "%.*s\n", scr_width, line_buf); + print_line_buf(); - snprintf(line_buf, LINE_BUF_SIZE, + sprintf(G.line_buf, " slab:%lu buf:%lu cache:%lu dirty:%lu write:%lu", meminfo[MI_SLAB], meminfo[MI_BUFFERS], meminfo[MI_CACHED], meminfo[MI_DIRTY], meminfo[MI_WRITEBACK]); - printf("%.*s\n", scr_width, line_buf); + print_line_buf(); - snprintf(line_buf, LINE_BUF_SIZE, + sprintf(G.line_buf, "Swap total:%lu free:%lu", // TODO: % used? meminfo[MI_SWAPTOTAL], meminfo[MI_SWAPFREE]); - printf("%.*s\n", scr_width, line_buf); - - (*lines_rem_p) -= 3; + print_line_buf(); } /* see http://en.wikipedia.org/wiki/Tera */ @@ -870,75 +916,57 @@ static void ulltoa4_and_space(unsigned long long ul, char buf[5]) smart_ulltoa4(ul, buf, " mgtpezy")[0] = ' '; } -static NOINLINE void display_topmem_process_list(int lines_rem, int scr_width) +static NOINLINE void display_topmem_process_list(void) { -#define HDR_STR " PID VSZ VSZRW RSS (SHR) DIRTY (SHR) STACK" -#define MIN_WIDTH sizeof(HDR_STR) const topmem_status_t *s = topmem + G_scroll_ofs; char *cp, ch; - display_topmem_header(scr_width, &lines_rem); + display_topmem_header(); - strcpy(line_buf, HDR_STR " COMMAND"); + strcpy(G.line_buf, " PID VSZ VSZRW RSS (SHR) DIRTY (SHR) STACK COMMAND"); /* Mark the ^FIELD^ we sort by */ - cp = &line_buf[5 + sort_field * 6]; + cp = &G.line_buf[5 + sort_field * 6]; ch = "^_"[inverted]; cp[6] = ch; do *cp++ = ch; while (*cp == ' '); + print_line_bold(); - printf(OPT_BATCH_MODE ? "%.*s" : ESC"[7m" "%.*s" ESC"[m", scr_width, line_buf); - lines_rem--; - - if (lines_rem > ntop - G_scroll_ofs) - lines_rem = ntop - G_scroll_ofs; - while (--lines_rem >= 0) { + if (G.lines_remaining > ntop - G_scroll_ofs) + G.lines_remaining = ntop - G_scroll_ofs; + while (G.lines_remaining > 0) { /* PID VSZ VSZRW RSS (SHR) DIRTY (SHR) COMMAND */ - int n = sprintf(line_buf, "%5u ", s->pid); + int n = sprintf(G.line_buf, "%5u ", s->pid); if (n > 7) { /* PID is 7 chars long (up to 4194304) */ - ulltoa4_and_space(s->vsz , &line_buf[8]); - ulltoa4_and_space(s->vszrw, &line_buf[8+5]); + ulltoa4_and_space(s->vsz , &G.line_buf[8]); + ulltoa4_and_space(s->vszrw, &G.line_buf[8+5]); /* the next field (RSS) starts at 8+10 = 3*6 */ } else { if (n == 7) /* PID is 6 chars long */ - ulltoa4_and_space(s->vsz, &line_buf[7]); + ulltoa4_and_space(s->vsz, &G.line_buf[7]); /* the next field (VSZRW) starts at 7+5 = 2*6 */ else /* PID is 5 chars or less */ - ulltoa5_and_space(s->vsz, &line_buf[6]); - ulltoa5_and_space(s->vszrw, &line_buf[2*6]); - } - ulltoa5_and_space(s->rss , &line_buf[3*6]); - ulltoa5_and_space(s->rss_sh , &line_buf[4*6]); - ulltoa5_and_space(s->dirty , &line_buf[5*6]); - ulltoa5_and_space(s->dirty_sh, &line_buf[6*6]); - ulltoa5_and_space(s->stack , &line_buf[7*6]); - line_buf[8*6] = '\0'; - if (scr_width > (int)MIN_WIDTH) { - read_cmdline(&line_buf[8*6], scr_width - MIN_WIDTH, s->pid, s->comm); + ulltoa5_and_space(s->vsz, &G.line_buf[6]); + ulltoa5_and_space(s->vszrw, &G.line_buf[2*6]); } - printf("\n""%.*s", scr_width, line_buf); + ulltoa5_and_space(s->rss , &G.line_buf[3*6]); + ulltoa5_and_space(s->rss_sh , &G.line_buf[4*6]); + ulltoa5_and_space(s->dirty , &G.line_buf[5*6]); + ulltoa5_and_space(s->dirty_sh, &G.line_buf[6*6]); + ulltoa5_and_space(s->stack , &G.line_buf[7*6]); + G.line_buf[8*6] = '\0'; + cmdline_to_line_buf_and_print(8*6, s->pid, s->comm); s++; } - bb_putchar(OPT_BATCH_MODE ? '\n' : '\r'); - fflush_all(); -#undef HDR_STR -#undef MIN_WIDTH } -#else -void display_topmem_process_list(int lines_rem, int scr_width); -int topmem_sort(char *a, char *b); -#endif /* TOPMEM */ - -/* - * end TOPMEM support - */ +#endif /* end TOPMEM support */ enum { TOP_MASK = 0 | PSSCAN_PID | PSSCAN_PPID - | PSSCAN_VSZ + | PSSCAN_RSS | PSSCAN_STIME | PSSCAN_UTIME | PSSCAN_STATE @@ -950,7 +978,7 @@ enum { | PSSCAN_SMAPS | PSSCAN_COMM, EXIT_MASK = 0, - NO_RESCAN_MASK = (unsigned)-1, + ONLY_REDRAW = (unsigned)-1, }; #if ENABLE_FEATURE_TOP_INTERACTIVE @@ -963,15 +991,22 @@ static unsigned handle_input(unsigned scan_mask, duration_t interval) } while (1) { - int32_t c; + int32_t c, cc; c = safe_read_key(STDIN_FILENO, G.kbd_input, interval * 1000); - if (c == -1 && errno != EAGAIN) { - /* error/EOF */ - option_mask32 |= OPT_EOF; + if (c == -1) { + if (errno != EAGAIN) + /* error/EOF */ + option_mask32 |= OPT_EOF; + /* else: timeout - rescan and refresh */ break; } interval = 0; + /* "continue" statements below return to do one additional + * quick attempt to read a key. This prevents + * long sequence of e.g. "nnnnnnnnnnnnnnnnnnnnnnnnnn" + * to cause lots of rescans. + */ if (c == initial_settings.c_cc[VINTR]) return EXIT_MASK; @@ -1005,9 +1040,10 @@ static unsigned handle_input(unsigned scan_mask, duration_t interval) G_scroll_ofs = ntop - 1; if (G_scroll_ofs < 0) G_scroll_ofs = 0; - return NO_RESCAN_MASK; + return ONLY_REDRAW; } + cc = c; c |= 0x20; /* lowercase */ if (c == 'q') return EXIT_MASK; @@ -1055,9 +1091,17 @@ static unsigned handle_input(unsigned scan_mask, duration_t interval) continue; } # if ENABLE_FEATURE_TOPMEM + if (cc == 'S') { + if (--sort_field < 0) + sort_field = NUM_SORT_FIELD - 1; + if (--sort_field < 0) + sort_field = NUM_SORT_FIELD - 1; + } if (c == 's') { - scan_mask = TOPMEM_MASK; sort_field = (sort_field + 1) % NUM_SORT_FIELD; + if (scan_mask == TOPMEM_MASK) + return ONLY_REDRAW; + scan_mask = TOPMEM_MASK; free(prev_hist); prev_hist = NULL; prev_hist_count = 0; @@ -1066,7 +1110,7 @@ static unsigned handle_input(unsigned scan_mask, duration_t interval) # endif if (c == 'r') { inverted ^= 1; - continue; + return ONLY_REDRAW; } # if ENABLE_FEATURE_TOP_SMP_CPU /* procps-2.0.18 uses 'C', 3.2.7 uses '1' */ @@ -1088,8 +1132,8 @@ static unsigned handle_input(unsigned scan_mask, duration_t interval) } # endif # endif - break; /* unknown key -> force refresh */ - } + /* Unknown key. Eat remaining buffered input (if any) */ + } /* while (1) */ return scan_mask; } @@ -1155,12 +1199,15 @@ int top_main(int argc UNUSED_PARAM, char **argv) { duration_t interval; int iterations; - unsigned col; + unsigned opt; char *str_interval, *str_iterations; unsigned scan_mask = TOP_MASK; INIT_G(); +//worth it? +// setvbuf(stdout, /*buf*/ NULL, _IOFBF, /*size*/ 0); + interval = 5; /* default update interval is 5 seconds */ iterations = 0; /* infinite */ #if ENABLE_FEATURE_TOP_SMP_CPU @@ -1172,13 +1219,13 @@ int top_main(int argc UNUSED_PARAM, char **argv) /* all args are options; -n NUM */ make_all_argv_opts(argv); /* options can be specified w/o dash */ - col = getopt32(argv, "d:n:bHm", &str_interval, &str_iterations); + opt = getopt32(argv, "d:n:bHm", &str_interval, &str_iterations); /* NB: -m and -H are accepted even if not configured */ #if ENABLE_FEATURE_TOPMEM - if (col & OPT_m) /* -m (busybox specific) */ + if (opt & OPT_m) /* -m (busybox specific) */ scan_mask = TOPMEM_MASK; #endif - if (col & OPT_d) { + if (opt & OPT_d) { /* work around for "-d 1" -> "-d -1" done by make_all_argv_opts() */ if (str_interval[0] == '-') str_interval++; @@ -1187,18 +1234,17 @@ int top_main(int argc UNUSED_PARAM, char **argv) if (interval > INT_MAX / 1000) interval = INT_MAX / 1000; } - if (col & OPT_n) { + if (opt & OPT_n) { if (str_iterations[0] == '-') str_iterations++; iterations = xatou(str_iterations); } #if ENABLE_FEATURE_SHOW_THREADS - if (col & OPT_H) { + if (opt & OPT_H) { scan_mask |= PSSCAN_TASKS; } #endif - /* change to /proc */ xchdir("/proc"); #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE @@ -1226,23 +1272,22 @@ int top_main(int argc UNUSED_PARAM, char **argv) #endif while (scan_mask != EXIT_MASK) { - IF_FEATURE_TOP_INTERACTIVE(unsigned new_mask;) + IF_FEATURE_TOP_INTERACTIVE(unsigned new_mask = scan_mask;) procps_status_t *p = NULL; - if (OPT_BATCH_MODE) { - G.lines = INT_MAX; - col = LINE_BUF_SIZE - 2; /* +2 bytes for '\n', NUL */ - } else { + G.lines = INT_MAX; + G.scr_width = LINE_BUF_SIZE - 2; /* +2 bytes for '\n', NUL */ + if (!OPT_BATCH_MODE) { G.lines = 24; /* default */ - col = 79; + G.scr_width = 80; /* We output to stdout, we need size of stdout (not stdin)! */ - get_terminal_width_height(STDOUT_FILENO, &col, &G.lines); - if (G.lines < 5 || col < 10) { + get_terminal_width_height(STDOUT_FILENO, &G.scr_width, &G.lines); + if (G.lines < 5 || G.scr_width < 10) { sleep_for_duration(interval); continue; } - if (col > LINE_BUF_SIZE - 2) - col = LINE_BUF_SIZE - 2; + if (G.scr_width > LINE_BUF_SIZE - 2) + G.scr_width = LINE_BUF_SIZE - 2; } /* read process IDs & status for all the processes */ @@ -1255,7 +1300,7 @@ int top_main(int argc UNUSED_PARAM, char **argv) top = xrealloc_vector(top, 6, ntop++); top[n].pid = p->pid; top[n].ppid = p->ppid; - top[n].vsz = p->vsz; + top[n].memsize = p->rss; #if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE top[n].ticks = p->stime + p->utime; #endif @@ -1268,20 +1313,20 @@ int top_main(int argc UNUSED_PARAM, char **argv) } #if ENABLE_FEATURE_TOPMEM else { /* TOPMEM */ - if (!(p->smaps.mapped_ro | p->smaps.mapped_rw)) + if (!(p->mapped_ro | p->mapped_rw)) continue; /* kernel threads are ignored */ n = ntop; /* No bug here - top and topmem are the same */ top = xrealloc_vector(topmem, 6, ntop++); strcpy(topmem[n].comm, p->comm); topmem[n].pid = p->pid; - topmem[n].vsz = p->smaps.mapped_rw + p->smaps.mapped_ro; - topmem[n].vszrw = p->smaps.mapped_rw; - topmem[n].rss_sh = p->smaps.shared_clean + p->smaps.shared_dirty; - topmem[n].rss = p->smaps.private_clean + p->smaps.private_dirty + topmem[n].rss_sh; - topmem[n].dirty = p->smaps.private_dirty + p->smaps.shared_dirty; - topmem[n].dirty_sh = p->smaps.shared_dirty; - topmem[n].stack = p->smaps.stack; + topmem[n].vsz = p->mapped_rw + p->mapped_ro; + topmem[n].vszrw = p->mapped_rw; + topmem[n].rss_sh = p->shared_clean + p->shared_dirty; + topmem[n].rss = p->private_clean + p->private_dirty + topmem[n].rss_sh; + topmem[n].dirty = p->private_dirty + p->shared_dirty; + topmem[n].dirty_sh = p->shared_dirty; + topmem[n].stack = p->stack; } #endif } /* end of "while we read /proc" */ @@ -1310,30 +1355,40 @@ int top_main(int argc UNUSED_PARAM, char **argv) qsort(topmem, ntop, sizeof(topmem_status_t), (void*)topmem_sort); } #endif - IF_FEATURE_TOP_INTERACTIVE(display:) + IF_FEATURE_TOP_INTERACTIVE(redraw:) + G.lines_remaining = G.lines; IF_FEATURE_TOPMEM(if (scan_mask != TOPMEM_MASK)) { - display_process_list(G.lines, col); + display_process_list(); } #if ENABLE_FEATURE_TOPMEM else { /* TOPMEM */ - display_topmem_process_list(G.lines, col); + display_topmem_process_list(); } #endif + print_end(); + fflush_all(); if (iterations >= 0 && !--iterations) break; #if !ENABLE_FEATURE_TOP_INTERACTIVE clearmems(); sleep_for_duration(interval); #else - new_mask = handle_input(scan_mask, interval); - if (new_mask == NO_RESCAN_MASK) - goto display; + new_mask = handle_input(scan_mask, + /* After "redraw with no rescan", have one + * key timeout shorter that normal + * (IOW: rescan sooner): + */ + (new_mask == ONLY_REDRAW ? 1 : interval) + ); + if (new_mask == ONLY_REDRAW) + goto redraw; scan_mask = new_mask; clearmems(); #endif } /* end of "while (not Q)" */ - bb_putchar('\n'); + if (!OPT_BATCH_MODE) + bb_putchar('\n'); #if ENABLE_FEATURE_TOP_INTERACTIVE reset_term(); #endif diff --git a/runit/chpst.c b/runit/chpst.c index 2be1a57754..3d04ee50d4 100644 --- a/runit/chpst.c +++ b/runit/chpst.c @@ -38,7 +38,8 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //config: bool "setuidgid (4.2 kb)" //config: default y //config: help -//config: Sets soft resource limits as specified by options +//config: Sets UID and GID to those of the given account, and execs +//config: specified program. //config: //config:config ENVUIDGID //config: bool "envuidgid (4.1 kb)" @@ -466,7 +467,8 @@ int chpst_main(int argc UNUSED_PARAM, char **argv) /* nice should be done before xsetuid */ if (opt & OPT_n) { errno = 0; - if (nice(xatoi(nicestr)) == -1) + nice(xatoi(nicestr)); + if (errno) bb_simple_perror_msg_and_die("nice"); } diff --git a/scripts/basic/fixdep.c b/scripts/basic/fixdep.c index 66be73aadd..071c3b4073 100644 --- a/scripts/basic/fixdep.c +++ b/scripts/basic/fixdep.c @@ -105,6 +105,7 @@ #include #include #include +#include #include #include #include @@ -292,7 +293,10 @@ void do_config_file(char *filename) perror(filename); exit(2); } - fstat(fd, &st); + if (fstat(fd, &st) < 0) { + fprintf(stderr, "fixdep: fstat %s %s\n", filename, strerror(errno)); + exit(2); + } if (st.st_size == 0) { close(fd); return; @@ -368,7 +372,10 @@ void print_deps(void) perror(depfile); exit(2); } - fstat(fd, &st); + if (fstat(fd, &st) < 0) { + fprintf(stderr, "fixdep: fstat %s %s\n", depfile, strerror(errno)); + exit(2); + } if (st.st_size == 0) { fprintf(stderr,"fixdep: %s is empty\n",depfile); close(fd); diff --git a/scripts/kconfig/lxdialog/check-lxdialog.sh b/scripts/kconfig/lxdialog/check-lxdialog.sh index 5075ebf2d3..910ca1f7ce 100755 --- a/scripts/kconfig/lxdialog/check-lxdialog.sh +++ b/scripts/kconfig/lxdialog/check-lxdialog.sh @@ -47,7 +47,7 @@ trap "rm -f $tmp" 0 1 2 3 15 check() { $cc -x c - -o $tmp 2>/dev/null <<'EOF' #include CURSES_LOC -main() {} +int main() { return 0; } EOF if [ $? != 0 ]; then echo " *** Unable to find the ncurses libraries or the" 1>&2 diff --git a/selinux/sestatus.c b/selinux/sestatus.c index 098a4d1891..cf664cc18a 100644 --- a/selinux/sestatus.c +++ b/selinux/sestatus.c @@ -131,13 +131,13 @@ static void display_verbose(void) puts("\nFile contexts:"); cterm = xmalloc_ttyname(0); -//FIXME: if cterm == NULL, we segfault!?? - puts(cterm); if (cterm && lgetfilecon(cterm, &con) >= 0) { printf(COL_FMT "%s\n", "Controlling term:", con); if (ENABLE_FEATURE_CLEAN_UP) freecon(con); } + if (ENABLE_FEATURE_CLEAN_UP) + free(cterm); for (i = 0; fc[i] != NULL; i++) { struct stat stbuf; diff --git a/shell/Config.src b/shell/Config.src index 5efbf99959..5b3fe08f38 100644 --- a/shell/Config.src +++ b/shell/Config.src @@ -166,9 +166,10 @@ config FEATURE_SH_HISTFILESIZE default y depends on SHELL_ASH || SHELL_HUSH help - This option makes busybox shells to use $HISTFILESIZE variable - to set shell history size. Note that its max value is capped - by "History size" setting in library tuning section. + This option makes busybox shells to use $HISTSIZE and + $HISTFILESIZE variables to set shell history size. + Note that its max value is capped by "History size" setting + in library tuning section. config FEATURE_SH_EMBEDDED_SCRIPTS bool "Embed scripts in the binary" diff --git a/shell/ash.c b/shell/ash.c index bbd7307703..841ffe8808 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -329,7 +329,7 @@ typedef long arith_t; /* ============ Shell options */ -/* If you add/change options hare, update --help text too */ +/* If you add/change options here, update --help text too */ static const char *const optletters_optnames[] ALIGN_PTR = { "e" "errexit", "f" "noglob", @@ -386,6 +386,60 @@ static const char *const optletters_optnames[] ALIGN_PTR = { enum { NOPTS = ARRAY_SIZE(optletters_optnames) }; +/* ============ Parser data */ + +struct strlist { + struct strlist *next; + char *text; +}; + +struct alias; + +struct strpush { + struct strpush *prev; /* preceding string on stack */ + char *prev_string; + int prev_left_in_line; +#if ENABLE_ASH_ALIAS + struct alias *ap; /* if push was associated with an alias */ +#endif + char *string; /* remember the string since it may change */ + + /* Delay freeing so we can stop nested aliases. */ + struct strpush *spfree; + + /* Remember last two characters for pungetc. */ + int lastc[2]; + + /* Number of outstanding calls to pungetc. */ + int unget; +}; + +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ +struct parsefile { + struct parsefile *prev; /* preceding file on stack */ + int linno; /* current line */ + int pf_fd; /* file descriptor (or -1 if string) */ + int left_in_line; /* number of chars left in this line */ + int left_in_buffer; /* number of chars left in this buffer past the line */ + char *next_to_pgetc; /* next char in buffer */ + char *buf; /* input buffer */ + struct strpush *strpush; /* for pushing strings at this level */ + struct strpush basestrpush; /* so pushing one is fast */ + + /* Delay freeing so we can stop nested aliases. */ + struct strpush *spfree; + + /* Remember last two characters for pungetc. */ + int lastc[2]; + + /* Number of outstanding calls to pungetc. */ + int unget; +}; + + /* ============ Misc data */ #define msg_illnum "Illegal number: %s" @@ -408,6 +462,7 @@ struct globals_misc { uint8_t back_exitstatus;/* exit status of backquoted command */ smallint job_warning; /* user was warned about stopped jobs (can be 2, 1 or 0). */ smallint inps4; /* Prevent PS4 nesting. */ + smallint vforked; int savestatus; /* exit status of last command outside traps */ int rootpid; /* pid of main shell */ /* shell level: 0 for the main shell, 1 for its children, and so on */ @@ -431,7 +486,7 @@ struct globals_misc { * but we do read it async. */ volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */ - volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */ + volatile /*sig_atomic_t*/ smallint gotsigchld; /* 1 = got SIGCHLD */ volatile /*sig_atomic_t*/ smallint pending_sig; /* last pending signal */ smallint exception_type; /* kind of exception: */ #define EXINT 0 /* SIGINT received */ @@ -490,7 +545,17 @@ struct globals_misc { char **trap_ptr; /* used only by "trap hack" */ + char *commandname; /* currently executing command */ + struct parsefile *g_parsefile; /* = &basepf, current input file */ + struct parsefile basepf; /* top level input file */ + /* Rarely referenced stuff */ + + struct jmploc main_handler; + + /* Cached supplementary group array (for testing executable'ity of files) */ + struct cached_groupinfo groupinfo; + #if ENABLE_ASH_RANDOM_SUPPORT random_t random_gen; #endif @@ -502,6 +567,7 @@ extern struct globals_misc *BB_GLOBAL_CONST ash_ptr_to_globals_misc; #define back_exitstatus (G_misc.back_exitstatus ) #define job_warning (G_misc.job_warning) #define inps4 (G_misc.inps4 ) +#define vforked (G_misc.vforked ) #define savestatus (G_misc.savestatus ) #define rootpid (G_misc.rootpid ) #define shlvl (G_misc.shlvl ) @@ -514,7 +580,7 @@ extern struct globals_misc *BB_GLOBAL_CONST ash_ptr_to_globals_misc; #define exception_type (G_misc.exception_type ) #define suppress_int (G_misc.suppress_int ) #define pending_int (G_misc.pending_int ) -#define got_sigchld (G_misc.got_sigchld ) +#define gotsigchld (G_misc.gotsigchld ) #define pending_sig (G_misc.pending_sig ) #define nullstr (G_misc.nullstr ) #define optlist (G_misc.optlist ) @@ -523,6 +589,11 @@ extern struct globals_misc *BB_GLOBAL_CONST ash_ptr_to_globals_misc; #define may_have_traps (G_misc.may_have_traps ) #define trap (G_misc.trap ) #define trap_ptr (G_misc.trap_ptr ) +#define commandname (G_misc.commandname) +#define g_parsefile (G_misc.g_parsefile ) +#define basepf (G_misc.basepf ) +#define main_handler (G_misc.main_handler ) +#define groupinfo (G_misc.groupinfo ) #define random_gen (G_misc.random_gen ) #define backgndpid (G_misc.backgndpid ) #define INIT_G_misc() do { \ @@ -531,6 +602,9 @@ extern struct globals_misc *BB_GLOBAL_CONST ash_ptr_to_globals_misc; curdir = nullstr; \ physdir = nullstr; \ trap_ptr = trap; \ + g_parsefile = &basepf; \ + groupinfo.euid = -1; \ + groupinfo.egid = -1; \ } while (0) @@ -573,66 +647,24 @@ var_end(const char *var) return var; } - -/* ============ Parser data */ - -/* - * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up. - */ -struct strlist { - struct strlist *next; - char *text; -}; - -struct alias; - -struct strpush { - struct strpush *prev; /* preceding string on stack */ - char *prev_string; - int prev_left_in_line; -#if ENABLE_ASH_ALIAS - struct alias *ap; /* if push was associated with an alias */ -#endif - char *string; /* remember the string since it may change */ - - /* Delay freeing so we can stop nested aliases. */ - struct strpush *spfree; - - /* Remember last two characters for pungetc. */ - int lastc[2]; - - /* Number of outstanding calls to pungetc. */ - int unget; -}; - -/* - * The parsefile structure pointed to by the global variable parsefile - * contains information about the current file being read. +/* Our signal logic never blocks individual signals + * using signal mask - only by setting SIG_IGN handler. + * Therefore just unmasking all of them instead of "restore old mask" + * approach is safe, modulo a case where the shell itself inherited + * a _masked_ signal. */ -struct parsefile { - struct parsefile *prev; /* preceding file on stack */ - int linno; /* current line */ - int pf_fd; /* file descriptor (or -1 if string) */ - int left_in_line; /* number of chars left in this line */ - int left_in_buffer; /* number of chars left in this buffer past the line */ - char *next_to_pgetc; /* next char in buffer */ - char *buf; /* input buffer */ - struct strpush *strpush; /* for pushing strings at this level */ - struct strpush basestrpush; /* so pushing one is fast */ - - /* Delay freeing so we can stop nested aliases. */ - struct strpush *spfree; - - /* Remember last two characters for pungetc. */ - int lastc[2]; - - /* Number of outstanding calls to pungetc. */ - int unget; -}; +static void +sigclearmask(void) +{ + sigprocmask_allsigs(SIG_UNBLOCK); +} -static struct parsefile basepf; /* top level input file */ -static struct parsefile *g_parsefile = &basepf; /* current input file */ -static char *commandname; /* currently executing command */ +/* Reset handler when entering a subshell */ +static void +reset_exception_handler(void) +{ + exception_handler = &main_handler; +} /* ============ Interrupts / exceptions */ @@ -646,13 +678,13 @@ static void exitshell(void) NORETURN; * more fun than worrying about efficiency and portability. :-)) */ #if DEBUG_INTONOFF -# define INT_OFF do { \ - TRACE(("%s:%d INT_OFF(%d)\n", __func__, __LINE__, suppress_int)); \ +# define INTOFF do { \ + TRACE(("%s:%d INTOFF(%d)\n", __func__, __LINE__, suppress_int)); \ suppress_int++; \ barrier(); \ } while (0) #else -# define INT_OFF do { \ +# define INTOFF do { \ suppress_int++; \ barrier(); \ } while (0) @@ -671,7 +703,11 @@ raise_exception(int e) if (exception_handler == NULL) abort(); #endif - INT_OFF; + + if (vforked) + _exit(exitstatus); + + INTOFF; exception_type = e; longjmp(exception_handler->loc, 1); } @@ -686,7 +722,7 @@ raise_exception(int e) * Called when a SIGINT is received. (If the user specifies * that SIGINT is to be trapped or ignored using the trap builtin, then * this routine is not called.) suppress_int is nonzero when interrupts - * are held using the INT_OFF macro. (The test for iflag is just + * are held using the INTOFF macro. (The test for iflag is just * defensive programming.) */ static void raise_interrupt(void) NORETURN; @@ -725,12 +761,12 @@ int_on(void) barrier(); } #if DEBUG_INTONOFF -# define INT_ON do { \ - TRACE(("%s:%d INT_ON(%d)\n", __func__, __LINE__, suppress_int-1)); \ +# define INTON do { \ + TRACE(("%s:%d INTON(%d)\n", __func__, __LINE__, suppress_int-1)); \ int_on(); \ } while (0) #else -# define INT_ON int_on() +# define INTON int_on() #endif static IF_NOT_ASH_OPTIMIZE_FOR_SIZE(inline) void force_int_on(void) @@ -741,7 +777,7 @@ force_int_on(void) raise_interrupt(); /* does not return */ barrier(); } -#define FORCE_INT_ON force_int_on() +#define FORCEINTON force_int_on() #define SAVE_INT(v) ((v) = suppress_int) @@ -759,27 +795,27 @@ force_int_on(void) static void outstr(const char *p, FILE *file) { - INT_OFF; + INTOFF; fputs(p, file); - INT_ON; + INTON; } static void flush_stdout_stderr(void) { - INT_OFF; + INTOFF; fflush_all(); - INT_ON; + INTON; } /* Was called outcslow(c,FILE*), but c was always '\n' */ static void newline_and_flush(FILE *dest) { - INT_OFF; + INTOFF; putc('\n', dest); fflush(dest); - INT_ON; + INTON; } static int out1fmt(const char *, ...) __attribute__((__format__(__printf__,1,2))); @@ -789,11 +825,11 @@ out1fmt(const char *fmt, ...) va_list ap; int r; - INT_OFF; + INTOFF; va_start(ap, fmt); r = vprintf(fmt, ap); va_end(ap); - INT_ON; + INTON; return r; } @@ -804,11 +840,11 @@ fmtstr(char *outbuf, size_t length, const char *fmt, ...) va_list ap; int ret; - INT_OFF; + INTOFF; va_start(ap, fmt); ret = vsnprintf(outbuf, length, fmt, ap); va_end(ap); - INT_ON; + INTON; return ret > (int)length ? length : ret; } @@ -845,28 +881,47 @@ out2str(const char *p) # define CTL_LAST CTLFROMPROC #endif -/* variable substitution byte (follows CTLVAR) */ -#define VSTYPE 0x0f /* type of variable substitution */ -#define VSNUL 0x10 /* colon--treat the empty string as unset */ - -/* values of VSTYPE field */ -#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ -#define VSMINUS 0x2 /* ${var-text} */ -#define VSPLUS 0x3 /* ${var+text} */ -#define VSQUESTION 0x4 /* ${var?message} */ -#define VSASSIGN 0x5 /* ${var=text} */ +/* ${VAR[ops]} encoding is CTLVAR,,"VARNAME=",,CTLENDVAR */ +/* variable type byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon: the op is one of :- :+ :? := */ +/* values of VSTYPE field. The first 5 must be in this order, "}-+?=" string is used elsewhere to index into them */ +#define VSNORMAL 0x1 /* $var or ${var} */ +#define VSMINUS 0x2 /* ${var[:]-text} */ +#define VSPLUS 0x3 /* ${var[:]+text} */ +#define VSQUESTION 0x4 /* ${var[:]?message} */ +#define VSASSIGN 0x5 /* ${var[:]=text} */ #define VSTRIMRIGHT 0x6 /* ${var%pattern} */ #define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ #define VSTRIMLEFT 0x8 /* ${var#pattern} */ #define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ #define VSLENGTH 0xa /* ${#var} */ #if BASH_SUBSTR -#define VSSUBSTR 0xc /* ${var:position:length} */ +#define VSSUBSTR 0xb /* ${var:position:length} */ +#endif +#if BASH_PATTERN_SUBST +#define VSREPLACE 0xc /* ${var/pattern/replacement} */ +#define VSREPLACEALL 0xd /* ${var//pattern/replacement} */ +#endif +static const char vstype_suffix[][3] ALIGN1 = { + [VSNORMAL - VSNORMAL] = "}", // $var or ${var} + [VSMINUS - VSNORMAL] = "-", // ${var-text} + [VSPLUS - VSNORMAL] = "+", // ${var+text} + [VSQUESTION - VSNORMAL] = "?", // ${var?message} + [VSASSIGN - VSNORMAL] = "=", // ${var=text} + [VSTRIMRIGHT - VSNORMAL] = "%", // ${var%pattern} + [VSTRIMRIGHTMAX - VSNORMAL] = "%%",// ${var%%pattern} + [VSTRIMLEFT - VSNORMAL] = "#", // ${var#pattern} + [VSTRIMLEFTMAX - VSNORMAL] = "##",// ${var##pattern} + [VSLENGTH - VSNORMAL] = "", // ${#var} +#if BASH_SUBSTR + [VSSUBSTR - VSNORMAL] = ":", // ${var:position:length} #endif #if BASH_PATTERN_SUBST -#define VSREPLACE 0xd /* ${var/pattern/replacement} */ -#define VSREPLACEALL 0xe /* ${var//pattern/replacement} */ + [VSREPLACE - VSNORMAL] = "/", // ${var/pattern/replacement} + [VSREPLACEALL - VSNORMAL] = "//",// ${var//pattern/replacement} #endif +}; static const char dolatstr[] ALIGN1 = { CTLQUOTEMARK, CTLVAR, VSNORMAL, '@', '=', CTLQUOTEMARK, '\0' @@ -895,14 +950,15 @@ static const char dolatstr[] ALIGN1 = { #endif #define NCLOBBER 18 #define NFROM 19 -#define NFROMTO 20 -#define NAPPEND 21 -#define NTOFD 22 -#define NFROMFD 23 -#define NHERE 24 -#define NXHERE 25 -#define NNOT 26 -#define N_NUMBER 27 +#define NFROMSTR 20 +#define NFROMTO 21 +#define NAPPEND 22 +#define NTOFD 23 +#define NFROMFD 24 +#define NHERE 25 +#define NXHERE 26 +#define NNOT 27 +#define N_NUMBER 28 union node; @@ -1240,7 +1296,9 @@ sharg(union node *arg, FILE *fp) if (subtype & VSNUL) putc(':', fp); - +#if 1 + fputs(vstype_suffix[(subtype & VSTYPE) - VSNORMAL], fp); +#else switch (subtype & VSTYPE) { case VSNORMAL: putc('}', fp); @@ -1276,6 +1334,7 @@ sharg(union node *arg, FILE *fp) default: out1fmt("", subtype); } +#endif break; case CTLENDVAR: putc('}', fp); @@ -1330,6 +1389,7 @@ shcmd(union node *cmd, FILE *fp) #endif case NTOFD: s = ">&"; dftfd = 1; break; case NFROM: s = "<"; break; + case NFROMSTR: s = "<<<"; break; case NFROMFD: s = "<&"; break; case NFROMTO: s = "<>"; break; default: s = "*error*"; break; @@ -1416,13 +1476,14 @@ showtree(union node *n) static void ash_vmsg(const char *msg, va_list ap) { +//In dash, the order/format is different: +// arg0: LINENO: [commandname:] MSG +//If you fix it, change testsuite to match fprintf(stderr, "%s: ", arg0); - if (commandname) { - if (strcmp(arg0, commandname)) - fprintf(stderr, "%s: ", commandname); - if (!iflag || g_parsefile->pf_fd > 0) - fprintf(stderr, "line %d: ", errlinno); - } + if (commandname && strcmp(arg0, commandname) != 0) + fprintf(stderr, "%s: ", commandname); + if (!iflag || g_parsefile->pf_fd > 0) + fprintf(stderr, "line %d: ", errlinno); vfprintf(stderr, msg, ap); newline_and_flush(stderr); } @@ -1451,6 +1512,7 @@ ash_vmsg_and_raise(int cond, const char *msg, va_list ap) /* NOTREACHED */ } +/* This function is called sh_error() in dash */ static void ash_msg_and_raise_error(const char *, ...) NORETURN; static void ash_msg_and_raise_error(const char *msg, ...) @@ -1504,18 +1566,61 @@ ash_msg(const char *fmt, ...) va_end(ap); } +/* + * Types of operations (passed to the errmsg routine). + */ +#define E_OPEN 01 /* opening a file */ +#define E_CREAT 02 /* creating a file */ +#define E_EXEC 04 /* executing a program */ /* * Return a string describing an error. The returned string may be a * pointer to a static buffer that will be overwritten on the next call. * Action describes the operation that got the error. */ static const char * -errmsg(int e, const char *em) +errmsg(int e, int action) { - if (e == ENOENT || e == ENOTDIR) { - return em; + if (e != ENOENT && e != ENOTDIR) + return strerror(e); + + if (action & E_OPEN) + return "no such file"; + else if (action & E_CREAT) + return "nonexistent directory"; + else + return "not found"; +} + +static int sh_open_fail(const char *, int, int) NORETURN; +static int sh_open_fail(const char *pathname, int flags, int e) +{ + const char *word; + int action; + + word = "open"; + action = E_OPEN; + if (flags & O_CREAT) { + word = "create"; + action = E_CREAT; } - return strerror(e); + + ash_msg_and_raise_error("can't %s %s: %s", word, pathname, errmsg(e, action)); +} + +static int sh_open(const char *pathname, int flags, int mayfail) +{ + int fd; + int e; + + do { + fd = open(pathname, flags, 0666); + e = errno; + } while (fd < 0 && e == EINTR && !pending_sig); + + if (mayfail || fd >= 0) + return fd; + + sh_open_fail(pathname, flags, e); } @@ -1589,7 +1694,6 @@ struct stackmark { size_t stacknleft; }; - struct globals_memstack { struct stack_block *g_stackp; // = &stackbase; char *g_stacknxt; // = stackbase.space; @@ -1612,7 +1716,6 @@ extern struct globals_memstack *BB_GLOBAL_CONST ash_ptr_to_globals_memstack; sstrend = stackbase.space + MINSIZE; \ } while (0) - #define stackblock() ((void *)g_stacknxt) #define stackblocksize() g_stacknleft @@ -1642,14 +1745,14 @@ stalloc(size_t nbytes) len = sizeof(struct stack_block) - MINSIZE + blocksize; if (len < blocksize) ash_msg_and_raise_error(bb_msg_memory_exhausted); - INT_OFF; + INTOFF; sp = ckmalloc(len); sp->prev = g_stackp; g_stacknxt = sp->space; g_stacknleft = blocksize; sstrend = g_stacknxt + blocksize; g_stackp = sp; - INT_ON; + INTON; } p = g_stacknxt; g_stacknxt += aligned; @@ -1715,7 +1818,7 @@ popstackmark(struct stackmark *mark) if (!mark->stackp) return; - INT_OFF; + INTOFF; while (g_stackp != mark->stackp) { sp = g_stackp; g_stackp = sp->prev; @@ -1724,7 +1827,7 @@ popstackmark(struct stackmark *mark) g_stacknxt = mark->stacknxt; g_stacknleft = mark->stacknleft; sstrend = mark->stacknxt + mark->stacknleft; - INT_ON; + INTON; } /* @@ -1753,7 +1856,7 @@ growstackblock(size_t min) struct stack_block *prevstackp; size_t grosslen; - INT_OFF; + INTOFF; sp = g_stackp; prevstackp = sp->prev; grosslen = newlen + sizeof(struct stack_block) - MINSIZE; @@ -1763,7 +1866,7 @@ growstackblock(size_t min) g_stacknxt = sp->space; g_stacknleft = newlen; sstrend = sp->space + newlen; - INT_ON; + INTON; } else { char *oldspace = g_stacknxt; size_t oldlen = g_stacknleft; @@ -2114,7 +2217,6 @@ struct localvar { # define VDYNAMIC 0 #endif - /* Need to be before varinit_data[] */ #if ENABLE_LOCALE_SUPPORT static void FAST_FUNC @@ -2314,7 +2416,7 @@ initvar(void) #if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT vps1.var_text = "PS1=\\w \\$ "; #else - if (!geteuid()) + if (!get_cached_euid(&groupinfo.euid)); vps1.var_text = "PS1=# "; #endif vp = varinit; @@ -2497,7 +2599,7 @@ setvar(const char *name, const char *val, int flags) vallen = strlen(val); } - INT_OFF; + INTOFF; nameeq = ckzalloc(namelen + vallen + 2); p = mempcpy(nameeq, name, namelen); if (val) { @@ -2505,7 +2607,7 @@ setvar(const char *name, const char *val, int flags) strcpy(p, val); } vp = setvareq(nameeq, flags | VNOSAVE); - INT_ON; + INTON; return vp; } @@ -2864,7 +2966,7 @@ setpwd(const char *val, int setold) if (setold) { setvar("OLDPWD", oldcur, VEXPORT); } - INT_OFF; + INTOFF; if (physdir != nullstr) { if (physdir != oldcur) free(physdir); @@ -2881,7 +2983,7 @@ setpwd(const char *val, int setold) free(oldcur); } curdir = dir; - INT_ON; + INTON; setvar("PWD", dir, VEXPORT); } @@ -2899,7 +3001,7 @@ docd(const char *dest, int flags) TRACE(("docd(\"%s\", %d) called\n", dest, flags)); - INT_OFF; + INTOFF; if (!(flags & CD_PHYSICAL)) { dir = updatepwd(dest); if (dir) @@ -2911,7 +3013,7 @@ docd(const char *dest, int flags) setpwd(dir, 1); hashcd(); out: - INT_ON; + INTON; return err; } @@ -3000,7 +3102,6 @@ pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) /* ============ ... */ - #define IBUFSIZ (ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 1024) /* Syntax classes */ @@ -3407,13 +3508,11 @@ struct alias { int flag; }; - static struct alias **atab; // [ATABSIZE]; #define INIT_G_alias() do { \ atab = xzalloc(ATABSIZE * sizeof(atab[0])); \ } while (0) - static struct alias ** __lookupalias(const char *name) { @@ -3475,7 +3574,7 @@ setalias(const char *name, const char *val) app = __lookupalias(name); ap = *app; - INT_OFF; + INTOFF; if (ap) { if (!(ap->flag & ALIASINUSE)) { free(ap->val); @@ -3491,7 +3590,7 @@ setalias(const char *name, const char *val) /*ap->next = NULL;*/ *app = ap; } - INT_ON; + INTON; } static int @@ -3502,9 +3601,9 @@ unalias(const char *name) app = __lookupalias(name); if (*app) { - INT_OFF; + INTOFF; *app = freealias(*app); - INT_ON; + INTON; return 0; } @@ -3517,7 +3616,7 @@ rmaliases(void) struct alias *ap, **app; int i; - INT_OFF; + INTOFF; for (i = 0; i < ATABSIZE; i++) { app = &atab[i]; for (ap = *app; ap; ap = *app) { @@ -3527,7 +3626,7 @@ rmaliases(void) } } } - INT_ON; + INTON; } static void @@ -3595,7 +3694,6 @@ unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) #endif /* ASH_ALIAS */ - /* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ #define FORK_FG 0 #define FORK_BG 1 @@ -3642,15 +3740,14 @@ struct job { struct job *prev_job; /* previous job */ }; -static struct job *makejob(/*union node *,*/ int); static int forkshell(struct job *, union node *, int); static int waitforjob(struct job *); #if !JOBS -enum { doing_jobctl = 0 }; +enum { jobctl = 0 }; #define setjobctl(on) do {} while (0) #else -static smallint doing_jobctl; //references:8 +static smallint jobctl; //references:8 static void setjobctl(int); #endif @@ -3665,22 +3762,30 @@ ignoresig(int signo) /* No, need to do it */ signal(signo, SIG_IGN); } - sigmode[signo - 1] = S_HARD_IGN; + if (!vforked) + sigmode[signo - 1] = S_HARD_IGN; } /* * Only one usage site - in setsignal() + * This function is called onsig() in dash */ static void signal_handler(int signo) { + // parent momentarily has vforked == 1 too, but it masks + // all signals until after it resets vforked to 0. + if (vforked) + /* We are vfork child, DO NOT MODIFY ANY VARIABLES! */ + return; + if (signo == SIGCHLD) { - got_sigchld = 1; + gotsigchld = 1; if (!trap[SIGCHLD]) return; } #if ENABLE_FEATURE_EDITING - bb_got_signal = signo; /* for read_line_input: "we got a signal" */ + bb_got_signal = signo; /* for read_line_input / read builtin: "we got a signal" */ #endif gotsig[signo - 1] = 1; pending_sig = signo; @@ -3701,10 +3806,13 @@ signal_handler(int signo) static void setsignal(int signo) { + int lvforked; char *t; char cur_act, new_act; struct sigaction act; + lvforked = vforked; + t = trap[signo]; new_act = S_DFL; if (t != NULL) { /* trap for this sig is set */ @@ -3713,7 +3821,7 @@ setsignal(int signo) new_act = S_IGN; } - if (rootshell && new_act == S_DFL) { + if (rootshell && new_act == S_DFL && !lvforked) { switch (signo) { case SIGINT: if (iflag || minusc || sflag == 0) @@ -3783,8 +3891,8 @@ setsignal(int signo) if (cur_act == S_HARD_IGN || cur_act == new_act) return; - *t = new_act; - + if (!lvforked) + *t = new_act; act.sa_handler = SIG_DFL; switch (new_act) { case S_CATCH: @@ -4035,7 +4143,7 @@ freejob(struct job *jp) struct procstat *ps; int i; - INT_OFF; + INTOFF; for (i = jp->nprocs, ps = jp->ps; --i >= 0; ps++) { if (ps->ps_cmd != nullstr) free(ps->ps_cmd); @@ -4044,14 +4152,29 @@ freejob(struct job *jp) free(jp->ps); jp->used = 0; set_curjob(jp, CUR_DELETE); - INT_ON; + INTON; } #if JOBS static void xtcsetpgrp(int fd, pid_t pgrp) { - if (tcsetpgrp(fd, pgrp)) + int err; + + sigblockall(NULL); + err = tcsetpgrp(fd, pgrp); + sigclearmask(); + // Unmasking signals would cause any arrived signal to trigger, so why? + // Generally yes, but there are exceptions. Such as: + // """ + // Attempts to use tcsetpgrp() from a process which is a member of + // a background process group on a fd associated with its controlling + // terminal shall cause the process group to be sent a SIGTTOU signal. + // If the calling thread is blocking SIGTTOU signals or the process + // is ignoring SIGTTOU signals, the process shall be allowed + // to perform the operation, and no signal is sent.""" + + if (err) ash_msg_and_raise_perror("can't set tty process group"); } @@ -4070,11 +4193,11 @@ setjobctl(int on) int fd; int pgrp; - if (on == doing_jobctl || rootshell == 0) + if (on == jobctl || rootshell == 0) return; if (on) { int ofd; - ofd = fd = open(_PATH_TTY, O_RDWR); + ofd = fd = sh_open(_PATH_TTY, O_RDWR, 1); if (fd < 0) { /* BTW, bash will try to open(ttyname(0)) if open("/dev/tty") fails. * That sometimes helps to acquire controlling tty. @@ -4130,7 +4253,7 @@ setjobctl(int on) fd = -1; } ttyfd = fd; - doing_jobctl = on; + jobctl = on; } static int FAST_FUNC @@ -4209,7 +4332,7 @@ restartjob(struct job *jp, int mode) int status; pid_t pgid; - INT_OFF; + INTOFF; if (jp->state == JOBDONE) goto out; jp->state = JOBRUNNING; @@ -4229,7 +4352,7 @@ restartjob(struct job *jp, int mode) } while (--i); out: status = (mode == FORK_FG) ? waitforjob(jp) : 0; - INT_ON; + INTON; return status; } @@ -4291,11 +4414,12 @@ sprint_status48(char *os, int status, int sigonly) return s - os; } -#define DOWAIT_NONBLOCK 0 -#define DOWAIT_BLOCK 1 -#define DOWAIT_BLOCK_OR_SIG 2 +/* Inside dowait(): */ +#define DOWAIT_NONBLOCK 0 /* waitpid() will use WNOHANG and won't wait for signals */ +#define DOWAIT_BLOCK 1 /* waitpid() will NOT use WNOHANG */ +#define DOWAIT_CHILD_OR_SIG 2 /* waitpid() will use WNOHANG and if got 0, will wait for signals, then loop back */ #if BASH_WAIT_N -# define DOWAIT_JOBSTATUS 0x10 /* OR this to get job's exitstatus instead of pid */ +# define DOWAIT_JOBSTATUS 0x10 /* OR this to get job's exitstatus instead of pid */ #endif static int @@ -4306,35 +4430,43 @@ waitproc(int block, int *status) int err; #if JOBS - if (doing_jobctl) + if (jobctl) flags |= WUNTRACED; #endif do { - got_sigchld = 0; + gotsigchld = 0; do err = waitpid(-1, status, flags); while (err < 0 && errno == EINTR); + /* Return if error (for example, ECHILD); or if pid found; + * or if "block" is DOWAIT_NONBLOCK (=0), in this case return -1. + */ if (err || (err = -!block)) break; - sigfillset(&oldmask); - sigprocmask2(SIG_SETMASK, &oldmask); /* mask is updated */ - while (!got_sigchld && !pending_sig) - sigsuspend(&oldmask); - sigprocmask(SIG_SETMASK, &oldmask, NULL); + /* "block" is DOWAIT_CHILD_OR_SIG. All children are running + * (waitpid(WNOHAG) above returned 0), wait for signals: + */ + //simpler, but unsafe: a signal can set pending_sig after check, but before pause(): - //while (!got_sigchld && !pending_sig) + //while (!gotsigchld && !pending_sig) // pause(); - } while (got_sigchld); + sigblockall(&oldmask); + + while (!gotsigchld && !pending_sig) + sigsuspend(&oldmask); + + sigclearmask(); + } while (gotsigchld); + /* If we fall off the loop, err is 0, which means we got a !SIGCHLD signal */ return err; } -static int -waitone(int block, struct job *job) +static int waitone(int block, struct job *job) { int pid; int status; @@ -4347,25 +4479,25 @@ waitone(int block, struct job *job) TRACE(("dowait(0x%x) called\n", block)); - /* It's wrong to call waitpid() outside of INT_OFF region: + /* It's wrong to call waitpid() outside of INTOFF region: * signal can arrive just after syscall return and handler can * longjmp away, losing stop/exit notification processing. * Thus, for "jobs" builtin, and for waiting for a fg job, - * we call waitpid() (blocking or non-blocking) inside INT_OFF. + * we call waitpid() (blocking or non-blocking) inside INTOFF. * * However, for "wait" builtin it is wrong to simply call waitpid() - * in INT_OFF region: "wait" needs to wait for any running job + * in INTOFF region: "wait" needs to wait for any running job * to change state, but should exit on any trap too. - * In INT_OFF region, a signal just before syscall entry can set + * In INTOFF region, a signal just before syscall entry can set * pending_sig variables, but we can't check them, and we would * either enter a sleeping waitpid() (BUG), or need to busy-loop. * - * Because of this, we run inside INT_OFF, but use a special routine + * Because of this, we run inside INTOFF, but use a special routine * which combines waitpid() and sigsuspend(). * This is the reason why we need to have a handler for SIGCHLD: * SIG_DFL handler does not wake sigsuspend(). */ - INT_OFF; + INTOFF; pid = waitproc(block, &status); TRACE(("wait returns pid %d, status=%d\n", pid, status)); if (pid <= 0) @@ -4373,32 +4505,30 @@ waitone(int block, struct job *job) for (jp = curjob; jp; jp = jp->prev_job) { int jobstate; - struct procstat *ps; - struct procstat *psend; + struct procstat *sp; + struct procstat *spend; if (jp->state == JOBDONE) continue; jobstate = JOBDONE; - ps = jp->ps; - psend = ps + jp->nprocs; + spend = jp->ps + jp->nprocs; + sp = jp->ps; do { - if (ps->ps_pid == pid) { - TRACE(("Job %d: changing status of proc %d " - "from 0x%x to 0x%x\n", - jobno(jp), pid, ps->ps_status, status)); - ps->ps_status = status; + if (sp->ps_pid == pid) { + TRACE(("Job %d: changing status of proc %d from 0x%x to 0x%x\n", jobno(jp), pid, sp->ps_status, status)); + sp->ps_status = status; thisjob = jp; } - if (ps->ps_status == -1) + if (sp->ps_status == -1) jobstate = JOBRUNNING; #if JOBS if (jobstate == JOBRUNNING) continue; - if (WIFSTOPPED(ps->ps_status)) { - jp->stopstatus = ps->ps_status; + if (WIFSTOPPED(sp->ps_status)) { + jp->stopstatus = sp->ps_status; jobstate = JOBSTOPPED; } #endif - } while (++ps < psend); + } while (++sp < spend); if (!thisjob) continue; @@ -4410,8 +4540,7 @@ waitone(int block, struct job *job) */ thisjob->changed = 1; if (thisjob->state != jobstate) { - TRACE(("Job %d: changing state from %d to %d\n", - jobno(thisjob), thisjob->state, jobstate)); + TRACE(("Job %d: changing state from %d to %d\n", jobno(thisjob), thisjob->state, jobstate)); thisjob->state = jobstate; #if JOBS if (jobstate == JOBSTOPPED) @@ -4423,7 +4552,7 @@ waitone(int block, struct job *job) } /* The process wasn't found in job list */ out: - INT_ON; + INTON; #if BASH_WAIT_N if (want_jobexitstatus) { @@ -4446,10 +4575,9 @@ waitone(int block, struct job *job) return pid; } -static int -dowait(int block, struct job *jp) +static int dowait(int block, struct job *jp) { - smallint gotchld = *(volatile smallint *)&got_sigchld; + smallint gotchld = *(volatile smallint *)&gotsigchld; int rpid; int pid; @@ -4673,9 +4801,9 @@ waitcmd(int argc UNUSED_PARAM, char **argv) * the trap is executed." */ #if BASH_WAIT_N - status = dowait(DOWAIT_BLOCK_OR_SIG | DOWAIT_JOBSTATUS, NULL); + status = dowait(DOWAIT_CHILD_OR_SIG | DOWAIT_JOBSTATUS, NULL); #else - dowait(DOWAIT_BLOCK_OR_SIG, NULL); + dowait(DOWAIT_CHILD_OR_SIG, NULL); #endif /* if child sends us a signal *and immediately exits*, * dowait() returns pid > 0. Check this case, @@ -4716,7 +4844,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv) job = getjob(*argv, 0); } /* loop until process terminated or stopped */ - dowait(DOWAIT_BLOCK_OR_SIG, job); + dowait(DOWAIT_CHILD_OR_SIG, job); if (pending_sig) goto sigout; job->waited = 1; @@ -4779,7 +4907,7 @@ growjobtab(void) * Called with interrupts off. */ static struct job * -makejob(/*union node *node,*/ int nprocs) +makejob(int nprocs) { int i; struct job *jp; @@ -4794,7 +4922,7 @@ makejob(/*union node *node,*/ int nprocs) if (jp->state != JOBDONE || !jp->waited) continue; #if JOBS - if (doing_jobctl) + if (jobctl) continue; #endif freejob(jp); @@ -4803,8 +4931,8 @@ makejob(/*union node *node,*/ int nprocs) memset(jp, 0, sizeof(*jp)); #if JOBS /* jp->jobctl is a bitfield. - * "jp->jobctl |= doing_jobctl" likely to give awful code */ - if (doing_jobctl) + * "jp->jobctl |= jobctl" likely to give awful code */ + if (jobctl) jp->jobctl = 1; #endif jp->prev_job = curjob; @@ -4829,13 +4957,6 @@ static char *cmdnextc; static void cmdputs(const char *s) { - static const char vstype[VSTYPE + 1][3] ALIGN1 = { - "", "}", "-", "+", "?", "=", - "%", "%%", "#", "##" - IF_BASH_SUBSTR(, ":") - IF_BASH_PATTERN_SUBST(, "/", "//") - }; - const char *p, *str; char cc[2]; char *nextc; @@ -4898,32 +5019,34 @@ cmdputs(const char *s) case '=': if (subtype == 0) break; + /* We are in variable name */ if ((subtype & VSTYPE) != VSNORMAL) quoted <<= 1; - str = vstype[subtype & VSTYPE]; - if (subtype & VSNUL) - c = ':'; - else - goto checkstr; + str = vstype_suffix[(subtype & VSTYPE) - VSNORMAL]; + if (!(subtype & VSNUL)) + goto dostr; + c = ':'; break; - case '\'': + case '$': + /* Can happen inside quotes, or in variable name $$ */ + if (subtype != 0) + // Testcase: + // $ true $$ & + // $ + // [1]+ Done true ${$} // shows ${\$} without "if (subtype)" check + break; + /* Not in variable name - show as \$ */ + case '\'': /* These can only happen inside quotes */ case '\\': case '"': - case '$': - /* These can only happen inside quotes */ cc[0] = c; str = cc; -//FIXME: -// $ true $$ & -// $ -// [1]+ Done true ${\$} <<=== BUG: ${\$} is not a valid way to write $$ (${$} would be ok) c = '\\'; break; default: break; } USTPUTC(c, nextc); - checkstr: if (!str) continue; dostr: @@ -4935,7 +5058,7 @@ cmdputs(const char *s) if (quoted & 1) { USTPUTC('"', nextc); } - *nextc = 0; + *nextc = '\0'; cmdnextc = nextc; } @@ -5059,6 +5182,9 @@ cmdtxt(union node *n) case NXHERE: p = "<<..."; goto dotail2; + case NFROMSTR: + p = "<<<"; + goto dotail2; case NCASE: cmdputs("case "); cmdputs(n->ncase.expr->narg.text); @@ -5146,7 +5272,7 @@ clear_traps(void) { char **tp; - INT_OFF; + INTOFF; for (tp = trap; tp <= &trap[NTRAP_LAST]; tp++) { if (*tp && **tp) { /* trap not NULL or "" (SIG_IGN) */ if (trap_ptr == trap) @@ -5158,7 +5284,7 @@ clear_traps(void) } } may_have_traps = 0; - INT_ON; + INTON; } /* Lives far away from here, needed for forkchild */ @@ -5169,18 +5295,28 @@ static void closescript(void); static NOINLINE void forkchild(struct job *jp, union node *n, int mode) { + int lvforked; int oldlvl; TRACE(("Child shell %d\n", getpid())); oldlvl = shlvl; - shlvl++; + lvforked = vforked; + + if (!lvforked) { + shlvl++; + + closescript(); + +#if JOBS + /* do job control only in root shell */ + jobctl = 0; +#endif + } /* man bash: "Non-builtin commands run by bash have signal handlers * set to the values inherited by the shell from its parent". * Do we do it correctly? */ - closescript(); - if (n && n->type == NCMD /* is it single cmd? */ /* && n->ncmd.args->type == NARG - always true? */ && n->ncmd.args && strcmp(n->ncmd.args->narg.text, "trap") == 0 @@ -5226,10 +5362,9 @@ forkchild(struct job *jp, union node *n, int mode) trap_ptr = xmemdup(trap, sizeof(trap)); /* Fall through into clearing traps */ } - clear_traps(); + if (!lvforked) + clear_traps(); #if JOBS - /* do job control only in root shell */ - doing_jobctl = 0; if (mode != FORK_NOJOB && jp->jobctl && oldlvl == 0) { pid_t pgrp; @@ -5252,8 +5387,7 @@ forkchild(struct job *jp, union node *n, int mode) ignoresig(SIGQUIT); if (jp->nprocs == 0) { close(0); - if (open(bb_dev_null, O_RDONLY) != 0) - ash_msg_and_raise_perror("can't open '%s'", bb_dev_null); + sh_open(_PATH_DEVNULL, O_RDONLY, 0); } } if (oldlvl == 0) { @@ -5284,6 +5418,10 @@ forkchild(struct job *jp, union node *n, int mode) return; } #endif + + if (lvforked) + return; + for (jp = curjob; jp; jp = jp->prev_job) freejob(jp); } @@ -5295,7 +5433,15 @@ forkchild(struct job *jp, union node *n, int mode) static void forkparent(struct job *jp, union node *n, int mode, pid_t pid) { - TRACE(("In parent shell: child = %d\n", pid)); + if (pid < 0) { + TRACE(("Fork failed, errno=%d", errno)); + if (jp) + freejob(jp); + ash_msg_and_raise_perror("can't fork"); + /* NOTREACHED */ + } + + TRACE(("In parent shell: child = %d\n", pid)); if (!jp) /* jp is NULL when called by openhere() for heredoc support */ return; #if JOBS @@ -5320,7 +5466,7 @@ forkparent(struct job *jp, union node *n, int mode, pid_t pid) ps->ps_status = -1; ps->ps_cmd = nullstr; #if JOBS - if (doing_jobctl && n) + if (jobctl && n) ps->ps_cmd = commandtext(n); #endif } @@ -5334,21 +5480,44 @@ forkshell(struct job *jp, union node *n, int mode) TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode)); pid = fork(); - if (pid < 0) { - TRACE(("Fork failed, errno=%d", errno)); - if (jp) - freejob(jp); - ash_msg_and_raise_perror("can't fork"); - } if (pid == 0) { CLEAR_RANDOM_T(&random_gen); /* or else $RANDOM repeats in child */ forkchild(jp, n, mode); - } else { + } else forkparent(jp, n, mode, pid); - } + return pid; } +static void shellexec(char *prog, char **argv, const char *path, int idx) NORETURN; + +static struct job* +vforkexec(union node *n, char **argv, const char *path, int idx) +{ + struct job *jp; + int pid; + + jp = makejob(1); + + sigblockall(NULL); + vforked = 1; + + pid = vfork(); + + if (!pid) { + forkchild(jp, n, FORK_FG); + sigclearmask(); + shellexec(argv[0], argv, path, idx); + /* NOTREACHED */ + } + + vforked = 0; + sigclearmask(); + forkparent(jp, n, FORK_FG, pid); + + return jp; +} + /* * Wait for job to finish. * @@ -5455,7 +5624,6 @@ stoppedjobs(void) return retval; } - /* * Code for dealing with input/output redirection. */ @@ -5470,25 +5638,9 @@ stoppedjobs(void) * data to a pipe. If the document is short, we can stuff the data in * the pipe without forking. */ -/* openhere needs this forward reference */ -static void expandhere(union node *arg); static int -openhere(union node *redir) +write2pipe(int pip[2], const char *p, size_t len) { - char *p; - int pip[2]; - size_t len = 0; - - if (pipe(pip) < 0) - ash_msg_and_raise_perror("can't create pipe"); - - p = redir->nhere.doc->narg.text; - if (redir->type == NXHERE) { - expandhere(redir->nhere.doc); - p = stackblock(); - } - - len = strlen(p); if (len <= PIPE_BUF) { xwrite(pip[1], p, len); goto out; @@ -5510,86 +5662,109 @@ openhere(union node *redir) return pip[0]; } +/* openhere needs this forward reference */ +static void expandhere(union node *arg); +static int +openhere(union node *redir) +{ + char *p; + int pip[2]; + + if (pipe(pip) < 0) + ash_msg_and_raise_perror("can't create pipe"); + + p = redir->nhere.doc->narg.text; + if (redir->type == NXHERE) { + expandhere(redir->nhere.doc); + p = stackblock(); + } + + return write2pipe(pip, p, strlen(p)); +} + +static int +openherestr(char *str) +{ + int pip[2]; + size_t len; + + if (pipe(pip) < 0) + ash_msg_and_raise_perror("can't create pipe"); + + len = strlen(str); + str[len] = '\n'; + write2pipe(pip, str, len + 1); + str[len] = '\0'; + return pip[0]; +} + static int openredirect(union node *redir) { struct stat sb; char *fname; + int flags; int f; switch (redir->nfile.type) { -/* Can't happen, our single caller does this itself */ -// case NTOFD: -// case NFROMFD: -// return -1; - case NHERE: - case NXHERE: - return openhere(redir); - } - - /* For N[X]HERE, reading redir->nfile.expfname would touch beyond - * allocated space. Do it only when we know it is safe. - */ - fname = redir->nfile.expfname; - - switch (redir->nfile.type) { - default: -#if DEBUG - abort(); -#endif case NFROM: - f = open(fname, O_RDONLY); - if (f < 0) - goto eopen; + flags = O_RDONLY; + do_open: + f = sh_open(redir->nfile.expfname, flags, 0); break; - case NFROMTO: - f = open(fname, O_RDWR|O_CREAT, 0666); - if (f < 0) - goto ecreate; + case NFROMSTR: + f = openherestr(redir->nfile.expfname); break; + case NFROMTO: + flags = O_RDWR|O_CREAT; + goto do_open; case NTO: #if BASH_REDIR_OUTPUT case NTO2: #endif /* Take care of noclobber mode. */ if (Cflag) { + fname = redir->nfile.expfname; if (stat(fname, &sb) < 0) { - f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); - if (f < 0) - goto ecreate; - } else if (!S_ISREG(sb.st_mode)) { - f = open(fname, O_WRONLY, 0666); - if (f < 0) - goto ecreate; - if (!fstat(f, &sb) && S_ISREG(sb.st_mode)) { - close(f); - errno = EEXIST; - goto ecreate; - } - } else { - errno = EEXIST; + flags = O_WRONLY|O_CREAT|O_EXCL; + goto do_open; + } + + if (S_ISREG(sb.st_mode)) + goto ecreate; + + f = sh_open(fname, O_WRONLY, 0); + if (!fstat(f, &sb) && S_ISREG(sb.st_mode)) { + close(f); goto ecreate; } break; } /* FALLTHROUGH */ case NCLOBBER: - f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666); - if (f < 0) - goto ecreate; - break; + flags = O_WRONLY|O_CREAT|O_TRUNC; + goto do_open; case NAPPEND: - f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666); - if (f < 0) - goto ecreate; + flags = O_WRONLY|O_CREAT|O_APPEND; + goto do_open; +/* Can't happen, our single caller does this itself */ +// case NTOFD: +// case NFROMFD: +// return -1; + default: +#ifdef DEBUG + abort(); +#endif + /* Fall through to eliminate warning. */ + case NHERE: + case NXHERE: + f = openhere(redir); break; } return f; ecreate: - ash_msg_and_raise_error("can't create %s: %s", fname, errmsg(errno, "nonexistent directory")); - eopen: - ash_msg_and_raise_error("can't open %s: %s", fname, errmsg(errno, "no such file")); + sh_open_fail(fname, O_CREAT, EEXIST); } /* @@ -5836,7 +6011,7 @@ redirect(union node *redir, int flags) return; sv = NULL; - INT_OFF; + INTOFF; if (flags & REDIR_PUSH) sv = redirlist; do { @@ -5901,7 +6076,7 @@ redirect(union node *redir, int flags) #endif } } while ((redir = redir->nfile.next) != NULL); - INT_ON; + INTON; //dash:#define REDIR_SAVEFD2 03 /* set preverrout */ #define REDIR_SAVEFD2 0 @@ -5989,7 +6164,7 @@ popredir(int drop) if (redirlist == NULL) return; - INT_OFF; + INTOFF; rp = redirlist; for (i = 0; i < rp->pair_count; i++) { int fd = rp->two_fd[i].orig_fd; @@ -6009,7 +6184,7 @@ popredir(int drop) } redirlist = rp->next; free(rp); - INT_ON; + INTON; } static void @@ -6036,11 +6211,11 @@ ash_arith(const char *s) math_state.setvar = setvar0; //math_state.endofname = endofname; - INT_OFF; + INTOFF; result = arith(&math_state, s); if (math_state.errmsg) ash_msg_and_raise_error(math_state.errmsg); - INT_ON; + INTON; return result; } @@ -6232,7 +6407,7 @@ ifsfree(void) if (!p) goto out; - INT_OFF; + INTOFF; do { struct ifsregion *ifsp; ifsp = p->next; @@ -6240,7 +6415,7 @@ ifsfree(void) p = ifsp; } while (p); ifsfirst.next = NULL; - INT_ON; + INTON; out: ifslastp = NULL; } @@ -6455,11 +6630,11 @@ recordregion(int start, int end, int nulonly) if (ifslastp == NULL) { ifsp = &ifsfirst; } else { - INT_OFF; + INTOFF; ifsp = ckzalloc(sizeof(*ifsp)); /*ifsp->next = NULL; - ckzalloc did it */ ifslastp->next = ifsp; - INT_ON; + INTON; } ifslastp = ifsp; ifslastp->begoff = start; @@ -6476,11 +6651,11 @@ removerecordregions(int endoff) if (ifsfirst.endoff > endoff) { while (ifsfirst.next) { struct ifsregion *ifsp; - INT_OFF; + INTOFF; ifsp = ifsfirst.next->next; free(ifsfirst.next); ifsfirst.next = ifsp; - INT_ON; + INTON; } if (ifsfirst.begoff > endoff) { ifslastp = NULL; @@ -6496,11 +6671,11 @@ removerecordregions(int endoff) ifslastp = ifslastp->next; while (ifslastp->next) { struct ifsregion *ifsp; - INT_OFF; + INTOFF; ifsp = ifslastp->next->next; free(ifslastp->next); ifslastp->next = ifsp; - INT_ON; + INTON; } if (ifslastp->endoff > endoff) ifslastp->endoff = endoff; @@ -6612,10 +6787,11 @@ evalbackcmd(union node *n, struct backcmd *result if (pipe(pip) < 0) ash_msg_and_raise_perror("can't create pipe"); /* process substitution uses NULL job, like openhere() */ - jp = (ctl == CTLBACKQ) ? makejob(/*n,*/ 1) : NULL; + jp = (ctl == CTLBACKQ) ? makejob(1) : NULL; if (forkshell(jp, n, FORK_NOJOB) == 0) { /* child */ - FORCE_INT_ON; + reset_exception_handler(); + FORCEINTON; close(pip[ip]); /* ic is index of child end of pipe *and* fd to connect it to */ if (pip[ic] != ic) { @@ -6676,7 +6852,7 @@ expbackq(union node *cmd, int flag IF_BASH_PROCESS_SUBST(, int ctl)) if (flag & EXP_DISCARD) goto out; - INT_OFF; + INTOFF; startloc = expdest - (char *)stackblock(); pushstackmark(&smark, startloc); evalbackcmd(cmd, &in IF_BASH_PROCESS_SUBST(, ctl)); @@ -6710,7 +6886,7 @@ expbackq(union node *cmd, int flag IF_BASH_PROCESS_SUBST(, int ctl)) back_exitstatus = waitforjob(in.jp); } done: - INT_ON; + INTON; /* Eat all trailing newlines */ dest = expdest; @@ -7808,7 +7984,7 @@ expandmeta(struct strlist *str /*, int flag*/) if (!hasmeta(str->text)) goto nometa; - INT_OFF; + INTOFF; p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); // GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match // GLOB_NOCHECK: if no match, return unchanged pattern (sans \* escapes?) @@ -7839,12 +8015,12 @@ expandmeta(struct strlist *str /*, int flag*/) #endif addglob(&pglob); globfree(&pglob); - INT_ON; + INTON; break; case GLOB_NOMATCH: //nometa2: globfree(&pglob); - INT_ON; + INTON; nometa: *exparg.lastp = str; rmescapes(str->text, 0, NULL); @@ -7852,7 +8028,7 @@ expandmeta(struct strlist *str /*, int flag*/) break; default: /* GLOB_NOSPACE */ globfree(&pglob); - INT_ON; + INTON; ash_msg_and_raise_error(bb_msg_memory_exhausted); } str = str->next; @@ -8076,7 +8252,7 @@ expandmeta(struct strlist *str /*, int flag*/) goto nometa; savelastp = exparg.lastp; - INT_OFF; + INTOFF; p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); len = strlen(p); exp.dir_max = len + PATH_MAX; @@ -8086,7 +8262,7 @@ expandmeta(struct strlist *str /*, int flag*/) free(exp.dir); if (p != str->text) free(p); - INT_ON; + INTON; if (exparg.lastp == savelastp) { /* * no matches @@ -8268,7 +8444,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c { #if ENABLE_FEATURE_SH_STANDALONE if (applet_no >= 0) { - if (APPLET_IS_NOEXEC(applet_no)) { + if (!vforked && APPLET_IS_NOEXEC(applet_no)) { clearenv(); while (*envp) putenv(*envp++); @@ -8324,7 +8500,6 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c * have to change the find_command routine as well. * argv[-1] must exist and be writable! See tryexec() for why. */ -static void shellexec(char *prog, char **argv, const char *path, int idx) NORETURN; static void shellexec(char *prog, char **argv, const char *path, int idx) { char *cmdname; @@ -8376,7 +8551,7 @@ static void shellexec(char *prog, char **argv, const char *path, int idx) exitstatus = exerrno; TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", prog, e, suppress_int)); - ash_msg_and_raise(EXEND, "%s: %s", prog, errmsg(e, "not found")); + ash_msg_and_raise(EXEND, "%s: %s", prog, errmsg(e, E_EXEC)); /* NOTREACHED */ } @@ -8406,7 +8581,7 @@ clearcmdentry(void) struct tblentry **pp; struct tblentry *cmdp; - INT_OFF; + INTOFF; for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) { pp = tblp; while ((cmdp = *pp) != NULL) { @@ -8423,7 +8598,7 @@ clearcmdentry(void) } } } - INT_ON; + INTON; } /* @@ -8477,13 +8652,13 @@ delete_cmd_entry(void) { struct tblentry *cmdp; - INT_OFF; + INTOFF; cmdp = *lastcmdentry; *lastcmdentry = cmdp->next; if (cmdp->cmdtype == CMDFUNCTION) freefunc(cmdp->param.func); free(cmdp); - INT_ON; + INTON; } /* @@ -8928,7 +9103,6 @@ commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) } #endif - /*static int funcblocksize; // size of structures in function */ /*static int funcstringsize; // size of strings in node */ static void *funcblock; /* block to allocate function from */ @@ -9042,6 +9216,7 @@ calcsize(int funcblocksize, union node *n) #endif case NCLOBBER: case NFROM: + case NFROMSTR: case NFROMTO: case NAPPEND: funcblocksize = calcsize(funcblocksize, n->nfile.fname); @@ -9164,6 +9339,7 @@ copynode(union node *n) #endif case NCLOBBER: case NFROM: + case NFROMSTR: case NFROMTO: case NAPPEND: new->nfile.fname = copynode(n->nfile.fname); @@ -9218,11 +9394,11 @@ defun(union node *func) { struct cmdentry entry; - INT_OFF; + INTOFF; entry.cmdtype = CMDFUNCTION; entry.u.func = copyfunc(func); addcmdentry(func->ndefun.text, &entry); - INT_ON; + INTON; } /* Reasons for skipping commands (see comment on breakcmd routine) */ @@ -9342,7 +9518,7 @@ evaltree(union node *n, int flags) #endif case NNOT: status = !evaltree(n->nnot.com, EV_TESTED); - goto setstatus; + break; case NREDIR: errlinno = lineno = n->nredir.linno; expredir(n->nredir.redirect); @@ -9353,7 +9529,7 @@ evaltree(union node *n, int flags) } if (n->nredir.redirect) popredir(/*drop:*/ 0); - goto setstatus; + break; case NCMD: evalfn = evalcommand; checkexit: @@ -9397,7 +9573,7 @@ evaltree(union node *n, int flags) evalfn = evaltree; calleval: status = evalfn(n, flags); - goto setstatus; + break; } case NIF: status = evaltree(n->nif.test, EV_TESTED); @@ -9411,17 +9587,18 @@ evaltree(union node *n, int flags) goto evaln; } status = 0; - goto setstatus; + break; case NDEFUN: defun(n); /* Not necessary. To test it: * "false; f() { qwerty; }; echo $?" should print 0. */ /* status = 0; */ - setstatus: - exitstatus = status; break; } + + exitstatus = status; + out: /* Order of checks below is important: * signal handlers trigger before exit caused by "set -e". @@ -9590,17 +9767,18 @@ evalsubshell(union node *n, int flags) expredir(n->nredir.redirect); if (!backgnd && (flags & EV_EXIT) && !may_have_traps) goto nofork; - INT_OFF; + INTOFF; if (backgnd == FORK_FG) get_tty_state(); - jp = makejob(/*n,*/ 1); + jp = makejob(1); if (forkshell(jp, n, backgnd) == 0) { /* child */ - INT_ON; + INTON; flags |= EV_EXIT; if (backgnd) flags &= ~EV_TESTED; nofork: + reset_exception_handler(); redirect(n->nredir.redirect, 0); evaltreenr(n->nredir.n, flags); /* never returns */ @@ -9609,7 +9787,7 @@ evalsubshell(union node *n, int flags) status = 0; if (backgnd == FORK_FG) status = waitforjob(jp); - INT_ON; + INTON; return status; } @@ -9630,6 +9808,7 @@ expredir(union node *n) switch (redir->type) { case NFROMTO: case NFROM: + case NFROMSTR: case NTO: #if BASH_REDIR_OUTPUT case NTO2: @@ -9697,10 +9876,10 @@ evalpipe(union node *n, int flags) for (lp = n->npipe.cmdlist; lp; lp = lp->next) pipelen++; flags |= EV_EXIT; - INT_OFF; + INTOFF; if (n->npipe.pipe_backgnd == 0) get_tty_state(); - jp = makejob(/*n,*/ pipelen); + jp = makejob(pipelen); prevfd = -1; for (lp = n->npipe.cmdlist; lp; lp = lp->next) { prehash(lp->n); @@ -9713,7 +9892,8 @@ evalpipe(union node *n, int flags) } if (forkshell(jp, lp->n, n->npipe.pipe_backgnd) == 0) { /* child */ - INT_ON; + reset_exception_handler(); + INTON; if (pip[1] >= 0) { close(pip[0]); } @@ -9740,7 +9920,7 @@ evalpipe(union node *n, int flags) status = waitforjob(jp); TRACE(("evalpipe: job done exit status %d\n", status)); } - INT_ON; + INTON; return status; } @@ -9832,7 +10012,7 @@ poplocalvars(int keep) struct localvar *lvp, *next; struct var *vp; - INT_OFF; + INTOFF; ll = localvar_stack; localvar_stack = ll->next; @@ -9876,7 +10056,7 @@ poplocalvars(int keep) } free(lvp); } - INT_ON; + INTON; } /* @@ -9892,12 +10072,12 @@ pushlocalvars(int push) if (!push) goto out; - INT_OFF; + INTOFF; ll = ckzalloc(sizeof(*ll)); /*ll->lv = NULL; - zalloc did it */ ll->next = top; localvar_stack = ll; - INT_ON; + INTON; out: return top; } @@ -9934,13 +10114,13 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) if (e) { goto funcdone; } - INT_OFF; + INTOFF; exception_handler = &jmploc; shellparam.malloced = 0; func->count++; funcname = func->n.ndefun.text; funcline = func->n.ndefun.linno; - INT_ON; + INTON; shellparam.nparam = argc - 1; shellparam.p = argv + 1; #if ENABLE_ASH_GETOPTS @@ -9949,7 +10129,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) #endif evaltree(func->n.ndefun.body, flags & EV_TESTED); funcdone: - INT_OFF; + INTOFF; funcname = savefuncname; if (savetrap) { if (!trap[NTRAP_ERR]) @@ -9963,7 +10143,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) freeparam(&shellparam); shellparam = saveparam; exception_handler = savehandler; - INT_ON; + INTON; evalskip &= ~(SKIPFUNC | SKIPFUNCDEF); return e; } @@ -9982,7 +10162,7 @@ mklocal(char *name, int flags) struct var *vp; char *eq = strchr(name, '='); - INT_OFF; + INTOFF; /* Cater for duplicate "local". Examples: * x=0; f() { local x=1; echo $x; local x; echo $x; }; f; echo $x * x=0; f() { local x=1; echo $x; local x=2; echo $x; }; f; echo $x @@ -10033,7 +10213,7 @@ mklocal(char *name, int flags) lvp->next = localvar_stack->lv; localvar_stack->lv = lvp; ret: - INT_ON; + INTON; } /* @@ -10170,7 +10350,7 @@ static int FAST_FUNC echocmd(int argc, char **argv) { return echo_main(argc, a static int FAST_FUNC printfcmd(int argc, char **argv) { return printf_main(argc, argv); } #endif #if ENABLE_ASH_TEST || BASH_TEST2 -static int FAST_FUNC testcmd(int argc, char **argv) { return test_main(argc, argv); } +static int FAST_FUNC testcmd(int argc, char **argv) { return test_main2(&groupinfo, argc, argv); } #endif #if ENABLE_ASH_SLEEP static int FAST_FUNC sleepcmd(int argc, char **argv) { return sleep_main(argc, argv); } @@ -10569,7 +10749,7 @@ evalcommand(union node *cmd, int flags) if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) { char **sv_environ; - INT_OFF; + INTOFF; sv_environ = environ; environ = listvars(VEXPORT, VUNSET, varlist.list, /*end:*/ NULL); /* @@ -10591,26 +10771,21 @@ evalcommand(union node *cmd, int flags) * our signals to SA_RESTART? */ /*clearerr(stdout);*/ - INT_ON; + INTON; break; } #endif + /* Fork off a child process if necessary. */ /* Can we avoid forking? For example, very last command * in a script or a subshell does not need forking, * we can just exec it. */ if (!(flags & EV_EXIT) || may_have_traps) { /* No, forking off a child is necessary */ - INT_OFF; + INTOFF; get_tty_state(); - jp = makejob(/*cmd,*/ 1); - if (forkshell(jp, cmd, FORK_FG) != 0) { - /* parent */ - break; - } - /* child */ - FORCE_INT_ON; - /* fall through to exec'ing external program */ + jp = vforkexec(cmd, argv, path, cmdentry.u.index); + break; } shellexec(argv[0], argv, path, cmdentry.u.index); /* NOTREACHED */ @@ -10633,7 +10808,7 @@ evalcommand(union node *cmd, int flags) status = waitforjob(jp); if (jp) TRACE(("forked child exited with %d\n", status)); - FORCE_INT_ON; + FORCEINTON; out: if (cmd->ncmd.redirect) @@ -10691,7 +10866,6 @@ goodname(const char *p) return endofname(p)[0] == '\0'; } - /* * Search for a command. This is called before we fork so that the * location of the command will be available in the parent as well as @@ -10771,7 +10945,7 @@ pushstring(char *s, struct alias *ap) int len; len = strlen(s); - INT_OFF; + INTOFF; if (g_parsefile->strpush || g_parsefile->spfree) { sp = ckzalloc(sizeof(*sp)); sp->prev = g_parsefile->strpush; @@ -10795,14 +10969,14 @@ pushstring(char *s, struct alias *ap) g_parsefile->left_in_line = len; g_parsefile->unget = 0; g_parsefile->spfree = NULL; - INT_ON; + INTON; } static void popstring(void) { struct strpush *sp = g_parsefile->strpush; - INT_OFF; + INTOFF; #if ENABLE_ASH_ALIAS if (sp->ap) { if (g_parsefile->next_to_pgetc[-1] == ' ' @@ -10821,7 +10995,7 @@ static void popstring(void) memcpy(g_parsefile->lastc, sp->lastc, sizeof(sp->lastc)); g_parsefile->strpush = sp->prev; g_parsefile->spfree = sp; - INT_ON; + INTON; } static int @@ -10866,11 +11040,11 @@ preadfd(void) * #(bash 5.0.17 exits after first "T", looks like a bug) */ bb_got_signal = 0; - INT_OFF; /* no longjmp'ing out of read_line_input please */ + INTOFF; /* no longjmp'ing out of read_line_input please */ nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ); if (bb_got_signal == SIGINT) write(STDOUT_FILENO, "^C\n", 3); - INT_ON; /* here non-blocked SIGINT will longjmp */ + INTON; /* here non-blocked SIGINT will longjmp */ if (nr == 0) { /* ^C pressed, "convert" to SIGINT */ write(STDOUT_FILENO, "^C\n", 3); @@ -11023,7 +11197,7 @@ nlnoprompt(void) static void freestrings(struct strpush *sp) { - INT_OFF; + INTOFF; do { struct strpush *psp; #if ENABLE_ASH_ALIAS @@ -11042,7 +11216,7 @@ static void freestrings(struct strpush *sp) } while (sp); g_parsefile->spfree = NULL; - INT_ON; + INTON; } static int __pgetc(void) @@ -11175,7 +11349,7 @@ popfile(void) if (pf == &basepf) return; - INT_OFF; + INTOFF; if (pf->pf_fd >= 0) close(pf->pf_fd); free(pf->buf); @@ -11187,7 +11361,7 @@ popfile(void) } g_parsefile = pf->prev; free(pf); - INT_ON; + INTON; } static void @@ -11248,14 +11422,10 @@ setinputfile(const char *fname, int flags) { int fd; - INT_OFF; - fd = open(fname, O_RDONLY | O_CLOEXEC); - if (fd < 0) { - if (flags & INPUT_NOFILE_OK) - goto out; - exitstatus = 127; - ash_msg_and_raise_perror("can't open '%s'", fname); - } + INTOFF; + fd = sh_open(fname, O_RDONLY, flags & INPUT_NOFILE_OK); + if (fd < 0) + goto out; if (fd < 10) fd = savefd(fd); else if (O_CLOEXEC == 0) /* old libc */ @@ -11263,7 +11433,7 @@ setinputfile(const char *fname, int flags) setinputfd(fd, flags & INPUT_PUSH_FILE); out: - INT_ON; + INTON; return fd; } @@ -11273,13 +11443,13 @@ setinputfile(const char *fname, int flags) static void setinputstring(char *string) { - INT_OFF; + INTOFF; pushfile(); g_parsefile->next_to_pgetc = string; g_parsefile->left_in_line = strlen(string); g_parsefile->buf = NULL; g_parsefile->linno = lineno; - INT_ON; + INTON; } @@ -11502,11 +11672,12 @@ options(int *login_sh) if (val && (c == '-')) { /* long options */ if (strcmp(p, "login") == 0) { *login_sh = 1; + break; } /* TODO: --noprofile: e.g. if I want to run emergency shell from sulogin, * I want minimal/no shell init scripts - but it insists on running it as "-ash"... */ - break; + ash_msg_and_raise_error("bad option '%s'", p - 2); } } if (c == 'o') { @@ -11538,7 +11709,7 @@ shiftcmd(int argc UNUSED_PARAM, char **argv) n = number(argv[1]); if (n > shellparam.nparam) return 1; - INT_OFF; + INTOFF; shellparam.nparam -= n; for (ap1 = shellparam.p; --n >= 0; ap1++) { if (shellparam.malloced) @@ -11551,7 +11722,7 @@ shiftcmd(int argc UNUSED_PARAM, char **argv) shellparam.optind = 1; shellparam.optoff = -1; #endif - INT_ON; + INTON; return 0; } @@ -11605,7 +11776,7 @@ setcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) if (!argv[1]) return showvars(nullstr, 0, VUNSET); - INT_OFF; + INTOFF; retval = options(/*login_sh:*/ NULL); if (retval == 0) { /* if no parse error... */ optschanged(); @@ -11613,7 +11784,7 @@ setcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) setparam(argptr); } } - INT_ON; + INTON; return retval; } @@ -12023,7 +12194,7 @@ fixredir(union node *n, const char *text, int err) * silently truncate results to word width. */ if (err) - raise_error_syntax("bad fd number"); + ash_msg_and_raise_error("bad fd number"); n->ndup.vname = makename(); } } @@ -12456,12 +12627,12 @@ decode_dollar_squote(void) #endif /* Used by expandstr to get here-doc like behaviour. */ -#define FAKEEOFMARK ((char*)(uintptr_t)1) +#define FAKEEOFMARK ((struct heredoc*)(uintptr_t)1) static ALWAYS_INLINE int -realeofmark(const char *eofmark) +realeofmark(struct heredoc *here) { - return eofmark && eofmark != FAKEEOFMARK; + return here && here != FAKEEOFMARK; } /* @@ -12483,7 +12654,7 @@ realeofmark(const char *eofmark) #define PARSEPROCSUB() {style = PSUB; goto parsebackq; parsebackq_psreturn:;} #define PARSEARITH() {goto parsearith; parsearith_return:;} static int -readtoken1(int c, int syntax, char *eofmark, int striptabs) +readtoken1(int c, int syntax, struct heredoc *eofmark) { /* NB: syntax parameter fits into smallint */ /* c parameter is an unsigned char or PEOF */ @@ -12735,23 +12906,30 @@ checkend: { int markloc; char *p; - if (striptabs) { + if (eofmark->striptabs) { while (c == '\t') - c = pgetc(); + if (eofmark->here->type == NHERE) + c = pgetc(); /* dash always does pgetc() */ + else /* NXHERE */ + c = pgetc_eatbnl(); + /* (see heredoc_bkslash_newline3a.tests) */ } markloc = out - (char *)stackblock(); - for (p = eofmark; STPUTC(c, out), *p; p++) { + for (p = eofmark->eofmark; STPUTC(c, out), *p; p++) { if (c != *p) goto more_heredoc; - /* FIXME: fails for backslash-newlined terminator: + /* dash still has this not fixed (as of 2025-08) * cat <here->type == NHERE) + c = pgetc(); /* dash always does pgetc() */ + else /* NXHERE */ + c = pgetc_eatbnl(); } if (c == '\n' || c == PEOF) { @@ -12761,7 +12939,6 @@ checkend: { needprompt = doprompt; } else { int len_here; - more_heredoc: p = (char *)stackblock() + markloc + 1; len_here = out - p; @@ -12824,6 +13001,11 @@ parseredir: { c = pgetc_eatbnl(); switch (c) { case '<': + c = pgetc_eatbnl(); + if (c == '<') { + np->type = NFROMSTR; + break; + } if (sizeof(struct nfile) != sizeof(struct nhere)) { np = stzalloc(sizeof(struct nhere)); /*np->nfile.fd = 0; - stzalloc did it */ @@ -12831,7 +13013,6 @@ parseredir: { np->type = NHERE; heredoc = stzalloc(sizeof(struct heredoc)); heredoc->here = np; - c = pgetc_eatbnl(); if (c == '-') { heredoc->striptabs = 1; } else { @@ -13270,7 +13451,7 @@ xxreadtoken(void) } } /* for (;;) */ - return readtoken1(c, BASESYNTAX, (char *) NULL, 0); + return readtoken1(c, BASESYNTAX, NULL); } #else /* old xxreadtoken */ #define RETURN(token) return lasttoken = token @@ -13321,7 +13502,7 @@ xxreadtoken(void) } break; } - return readtoken1(c, BASESYNTAX, (char *)NULL, 0); + return readtoken1(c, BASESYNTAX, NULL); #undef RETURN } #endif /* old xxreadtoken */ @@ -13427,9 +13608,9 @@ parseheredoc(void) tokpushback = 0; setprompt_if(needprompt, 2); if (here->here->type == NHERE) - readtoken1(pgetc(), SQSYNTAX, here->eofmark, here->striptabs); + readtoken1(pgetc(), SQSYNTAX, here); else - readtoken1(pgetc_eatbnl(), DQSYNTAX, here->eofmark, here->striptabs); + readtoken1(pgetc_eatbnl(), DQSYNTAX, here); n = stzalloc(sizeof(struct narg)); n->narg.type = NARG; /*n->narg.next = NULL; - stzalloc did it */ @@ -13440,7 +13621,6 @@ parseheredoc(void) } } - static const char * expandstr(const char *ps, int syntax_type) { @@ -13473,8 +13653,7 @@ expandstr(const char *ps, int syntax_type) * PS1='$(date "+%H:%M:%S) > ' */ exception_handler = &jmploc; - readtoken1(pgetc_eatbnl(), syntax_type, FAKEEOFMARK, 0); - + readtoken1(pgetc_eatbnl(), syntax_type, FAKEEOFMARK); n.narg.type = NARG; n.narg.next = NULL; n.narg.text = wordtext; @@ -13608,7 +13787,7 @@ cmdloop(int top) setstackmark(&smark); #if JOBS - if (doing_jobctl) + if (jobctl) showjobs(SHOW_CHANGED|SHOW_STDERR); #endif inter = 0; @@ -13781,6 +13960,42 @@ readcmdfile(char *name) /* ============ find_command inplementation */ +static int test_exec(/*const char *fullname,*/ struct stat *statb) +{ + /* + * TODO: use faccessat(AT_FDCWD, fullname, X_OK, AT_EACCESS) + * instead: executability may depend on ACLs, capabilities + * and who knows what else, not just mode bits. + * (faccessat2 syscall was added to Linux in May 14 2020) + */ + mode_t stmode; + uid_t euid; + enum { ANY_IX = S_IXUSR | S_IXGRP | S_IXOTH }; + + /* Do we already know with no extra syscalls? */ + if (!S_ISREG(statb->st_mode)) + return 0; /* not a regular file */ + if ((statb->st_mode & ANY_IX) == 0) + return 0; /* no one can execute */ + if ((statb->st_mode & ANY_IX) == ANY_IX) + return 1; /* anyone can execute */ + + /* Executability depends on our euid/egid/supplementary groups */ + stmode = S_IXOTH; + euid = get_cached_euid(&groupinfo.euid); + if (euid == 0) + /* for root user, any X bit is good enough */ + stmode = ANY_IX; + else if (statb->st_uid == euid) + stmode = S_IXUSR; + else if (statb->st_gid == get_cached_egid(&groupinfo.egid)) + stmode = S_IXGRP; + else if (is_in_supplementary_groups(&groupinfo, statb->st_gid)) + stmode = S_IXGRP; + + return statb->st_mode & stmode; +} + /* * Resolve a command name. If you change this routine, you may have to * change the shellexec routine as well. @@ -13807,9 +14022,12 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) if (errno == EINTR) continue; #endif + absfail: entry->cmdtype = CMDUNKNOWN; return; } + if (!test_exec(/*name,*/ &statb)) + goto absfail; } entry->cmdtype = CMDNORMAL; return; @@ -13922,9 +14140,6 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) e = errno; goto loop; } - e = EACCES; /* if we fail, this will be the error */ - if (!S_ISREG(statb.st_mode)) - continue; if (lpathopt) { /* this is a %func directory */ stalloc(len); /* NB: stalloc will return space pointed by fullname @@ -13937,17 +14152,20 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) stunalloc(fullname); goto success; } + e = EACCES; /* if we fail, this will be the error */ + if (!test_exec(/*fullname,*/ &statb)) + continue; TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); if (!updatetbl) { entry->cmdtype = CMDNORMAL; entry->u.index = idx; return; } - INT_OFF; + INTOFF; cmdp = cmdlookup(name, 1); cmdp->cmdtype = CMDNORMAL; cmdp->param.index = idx; - INT_ON; + INTON; goto success; } @@ -13967,7 +14185,7 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) return; } #endif - ash_msg("%s: %s", name, errmsg(e, "not found")); + ash_msg("%s: %s", name, errmsg(e, E_EXEC)); } fail: entry->cmdtype = CMDUNKNOWN; @@ -13979,18 +14197,17 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) entry->u.cmd = bcmd; return; } - INT_OFF; + INTOFF; cmdp = cmdlookup(name, 1); cmdp->cmdtype = CMDBUILTIN; cmdp->param.cmd = bcmd; - INT_ON; + INTON; success: cmdp->rehash = 0; entry->cmdtype = cmdp->cmdtype; entry->u = cmdp->param; } - /* * The trap builtin. */ @@ -14046,7 +14263,7 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) exitcode = 1; goto next; } - INT_OFF; + INTOFF; if (action) { if (LONE_DASH(action)) action = NULL; @@ -14060,7 +14277,7 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) trap[signo] = action; if (signo != 0 && signo < NSIG) setsignal(signo); - INT_ON; + INTON; next: ap++; } @@ -14336,9 +14553,9 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) * to jump out of it. */ again: - INT_OFF; + INTOFF; r = shell_builtin_read(¶ms); - INT_ON; + INTON; if ((uintptr_t)r == 1 && errno == EINTR) { /* To get SIGCHLD: sleep 1 & read x; echo $x @@ -14348,6 +14565,11 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) goto again; } + if ((uintptr_t)r == 2) /* -t SEC timeout? */ + /* bash: "The exit status is greater than 128 if the timeout is exceeded." */ + /* The actual value observed with bash 5.2.15: */ + return 128 + SIGALRM; + if ((uintptr_t)r > 1) ash_msg_and_raise_error(r); @@ -14366,10 +14588,10 @@ umaskcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) symbolic_mode = 1; } - INT_OFF; + INTOFF; mask = umask(0); umask(mask); - INT_ON; + INTON; if (*argptr == NULL) { if (symbolic_mode) { @@ -14473,8 +14695,25 @@ exitshell(void) char *p; #if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT - save_history(line_input_state); /* may be NULL */ + if (line_input_state) { + const char *hp; +# if ENABLE_FEATURE_SH_HISTFILESIZE +// in bash: +// HISTFILESIZE controls the on-disk history file size (in lines, 0=no history): +// "When this variable is assigned a value, the history file is truncated, if necessary" +// but we do it only at exit, not on assignment: + /* Use HISTFILESIZE to limit file size */ + hp = lookupvar("HISTFILESIZE"); + if (hp) + line_input_state->max_history = size_from_HISTFILESIZE(hp); +# endif + /* HISTFILE: "If unset, the command history is not saved when a shell exits." */ + hp = lookupvar("HISTFILE"); + line_input_state->hist_file = hp; + save_history(line_input_state); /* no-op if hist_file is NULL or "" */ + } #endif + savestatus = exitstatus; TRACE(("pid %d, exitshell(%d)\n", getpid(), savestatus)); if (setjmp(loc.loc)) @@ -14562,7 +14801,6 @@ init(void) } } - //usage:#define ash_trivial_usage //usage: "[-il] [-|+Cabefmnuvx] [-|+o OPT]... [-c 'SCRIPT' [ARG0 ARGS] | FILE ARGS | -s ARGS]" //////// comes from ^^^^^^^^^^optletters @@ -14573,15 +14811,12 @@ init(void) * Process the shell command line arguments. */ static int -procargs(char **argv) +procargs(char **xargv) { int i; - const char *xminusc; - char **xargv; int login_sh; - xargv = argv; - login_sh = xargv[0] && xargv[0][0] == '-'; + login_sh = /*xargv[0] &&*/ xargv[0][0] == '-'; #if NUM_SCRIPTS > 0 if (minusc) goto setarg0; @@ -14597,9 +14832,8 @@ procargs(char **argv) raise_exception(EXERROR); /* does not return */ } xargv = argptr; - xminusc = minusc; if (*xargv == NULL) { - if (xminusc) + if (minusc) ash_msg_and_raise_error(bb_msg_requires_arg, "-c"); sflag = 1; } @@ -14619,7 +14853,7 @@ procargs(char **argv) debug = 1; #endif /* POSIX 1003.2: first arg after "-c CMD" is $0, remainder $1... */ - if (xminusc) { + if (minusc) { minusc = *xargv++; if (*xargv) goto setarg0; @@ -14627,7 +14861,6 @@ procargs(char **argv) setinputfile(*xargv, 0); setarg0: arg0 = *xargv++; - commandname = arg0; } shellparam.p = xargv; @@ -14690,7 +14923,6 @@ int ash_main(int argc UNUSED_PARAM, char **argv) /* note: 'argc' is used only if embedded scripts are enabled */ { volatile smallint state; - struct jmploc jmploc; struct stackmark smark; int login_sh; @@ -14708,7 +14940,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv) #endif state = 0; - if (setjmp(jmploc.loc)) { + if (setjmp(main_handler.loc)) { smallint e; smallint s; @@ -14727,7 +14959,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv) } popstackmark(&smark); - FORCE_INT_ON; /* enable interrupts */ + FORCEINTON; /* enable interrupts */ if (s == 1) goto state1; if (s == 2) @@ -14736,7 +14968,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv) goto state3; goto state4; } - exception_handler = &jmploc; + exception_handler = &main_handler; rootpid = getpid(); init(); @@ -14804,18 +15036,23 @@ int ash_main(int argc UNUSED_PARAM, char **argv) if (!hp) { hp = lookupvar("HOME"); if (hp) { - INT_OFF; + INTOFF; hp = concat_path_file(hp, ".ash_history"); setvar0("HISTFILE", hp); free((char*)hp); - INT_ON; + INTON; hp = lookupvar("HISTFILE"); } } if (hp) line_input_state->hist_file = xstrdup(hp); # if ENABLE_FEATURE_SH_HISTFILESIZE - hp = lookupvar("HISTFILESIZE"); + hp = lookupvar("HISTSIZE"); + /* Using HISTFILESIZE above to limit max_history would be WRONG: + * users may set HISTFILESIZE=0 in their profile scripts + * to prevent _saving_ of history files, but still want to have + * non-zero history limit for in-memory list. + */ line_input_state->max_history = size_from_HISTFILESIZE(hp); # endif } @@ -14837,7 +15074,6 @@ int ash_main(int argc UNUSED_PARAM, char **argv) /* NOTREACHED */ } - /*- * Copyright (c) 1989, 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.right b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.right new file mode 100644 index 0000000000..3d79316d7f --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.right @@ -0,0 +1 @@ +Ok1 diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.tests b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.tests new file mode 100755 index 0000000000..eb22230311 --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.tests @@ -0,0 +1,4 @@ +cat <<-EOF + Ok1 + EO\ +F diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.right b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.right new file mode 100644 index 0000000000..3d79316d7f --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.right @@ -0,0 +1 @@ +Ok1 diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.tests b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.tests new file mode 100755 index 0000000000..de21132d1d --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.tests @@ -0,0 +1,4 @@ +cat << ->< ->test< ->test< +>te:142< +>:142< +>test:0< +>test:0< diff --git a/shell/ash_test/ash-read/read_t.tests b/shell/ash_test/ash-read/read_t.tests index d65f1aeaa0..9fbeec5172 100755 --- a/shell/ash_test/ash-read/read_t.tests +++ b/shell/ash_test/ash-read/read_t.tests @@ -1,10 +1,10 @@ -# bash 3.2 outputs: +# bash 5.2 outputs: -# >< -{ echo -n 'te'; sleep 2; echo 'st'; } | (read -t 1 reply; echo ">$reply<") -# >< -{ sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply<") -# >test< -{ echo -n 'te'; sleep 1; echo 'st'; } | (read -t 2 reply; echo ">$reply<") -# >test< -{ sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply<") +# >te:142< +{ echo -n 'te'; sleep 2; echo 'st'; } | (read -t 1 reply; echo ">$reply:$?<") +# >:142< +{ sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply:$?<") +# >test:0< +{ echo -n 'te'; sleep 1; echo 'st'; } | (read -t 2 reply; echo ">$reply:$?<") +# >test:0< +{ sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply:$?<") diff --git a/shell/ash_test/ash-redir/redir_EINTR1.right b/shell/ash_test/ash-redir/redir_EINTR1.right new file mode 100644 index 0000000000..287d91f678 --- /dev/null +++ b/shell/ash_test/ash-redir/redir_EINTR1.right @@ -0,0 +1,2 @@ +Hello +Done:0 diff --git a/shell/ash_test/ash-redir/redir_EINTR1.tests b/shell/ash_test/ash-redir/redir_EINTR1.tests new file mode 100755 index 0000000000..cede4d4d8b --- /dev/null +++ b/shell/ash_test/ash-redir/redir_EINTR1.tests @@ -0,0 +1,13 @@ +rm -f test.fifo +mkfifo test.fifo + +(sleep 1; kill -chld $$) & +(sleep 2; echo Hello >test.fifo) & + +# We get open("test.fifo") interrupted by SIGCHLD from the first subshell. +# The shell MUST retry the open (no printing of error messages). +# Then, the second subshell opens fifo for writing and open unblocks and succeeds. +cat test.fifo) & + +# We get open("test.fifo") interrupted by SIGCHLD from the first subshell. +# The shell MUST retry the open (no printing of error messages). +# Then, the second subshell opens fifo for writing and open unblocks and succeeds. +read HELLO #include -void strprint(); +void strprint(char *); int main(int argc, char **argv) { diff --git a/shell/hush.c b/shell/hush.c index 707b77c9cc..da0db79481 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -44,23 +44,20 @@ * make complex ${var%...} constructs support optional * make here documents optional * special variables (done: PWD, PPID, RANDOM) - * follow IFS rules more precisely, including update semantics * tilde expansion - * aliases * "command" missing features: * command -p CMD: run CMD using default $PATH * (can use this to override standalone shell as well?) * command BLTIN: disables special-ness (e.g. errors do not abort) * command -V CMD1 CMD2 CMD3 (multiple args) (not in standard) * builtins mandated by standards we don't support: - * [un]alias, fc: * fc -l[nr] [BEG] [END]: list range of commands in history * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP * * Bash compat TODO: * redirection of stdout+stderr: &> and >& - * reserved words: function select + * reserved words: select * advanced test: [[ ]] * process substitution: <(list) and >(list) * let EXPR [EXPR...] @@ -102,7 +99,7 @@ //config: //config: It will compile and work on no-mmu systems. //config: -//config: It does not handle select, aliases, tilde expansion, +//config: It does not handle select, tilde expansion, //config: &>file and >&file redirection of stdout+stderr. //config: // This option is visible (has a description) to make it possible to select @@ -115,6 +112,11 @@ //config:# It's only needed to get "nice" menuconfig indenting. //config:if SHELL_HUSH || HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: +//config:config HUSH_NEED_FOR_SPEED +//config: bool "Faster, but larger code" +//config: default y +//config: depends on SHELL_HUSH +//config: //config:config HUSH_BASH_COMPAT //config: bool "bash-compatible extensions" //config: default y @@ -189,6 +191,13 @@ //config: help //config: Enable case ... esac statement. +400 bytes. //config: +//config:config HUSH_ALIAS +//config: bool "Support aliases" +//config: default y +//config: depends on SHELL_HUSH +//config: help +//config: Enable aliases. +//config: //config:config HUSH_FUNCTIONS //config: bool "Support funcname() { commands; } syntax" //config: default y @@ -196,6 +205,13 @@ //config: help //config: Enable support for shell functions. +800 bytes. //config: +//config:config HUSH_FUNCTION_KEYWORD +//config: bool "Support function keyword" +//config: default y +//config: depends on HUSH_FUNCTIONS +//config: help +//config: Support "function FUNCNAME { CMD; }" syntax. +//config: //config:config HUSH_LOCAL //config: bool "local builtin" //config: default y @@ -390,9 +406,10 @@ #define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST) #define BASH_READ_D ENABLE_HUSH_BASH_COMPAT - /* Build knobs */ #define LEAK_HUNTING 0 +#define LEAK_PRINTF(...) fdprintf(__VA_ARGS__) +//#define LEAK_PRINTF(...) do { if (ptr_to_globals && G.root_pid == getpid()) fdprintf(__VA_ARGS__); } while (0) #define BUILD_AS_NOMMU 0 /* Enable/disable sanity checks. Ok to enable in production, * only adds a bit of bloat. Set to >1 to get non-production level verbosity. @@ -411,7 +428,6 @@ */ #define ENABLE_HUSH_DOLLAR_OPS 1 - #if BUILD_AS_NOMMU # undef BB_MMU # undef USE_FOR_NOMMU @@ -553,25 +569,22 @@ typedef struct o_string { smallint has_empty_slot; smallint ended_in_ifs; } o_string; +#define IS_NULL_WORD(str) \ + ((str).length == 0 && !(str).has_quoted_part) enum { - EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ - EXP_FLAG_GLOB = 0x2, - /* Protect newly added chars against globbing - * by prepending \ to *, ?, [, \ */ - EXP_FLAG_ESC_GLOB_CHARS = 0x1, + /* Protect all newly added chars against globbing by prepending \ to *, ?, [, -, \ */ + EXP_FLAG_GLOBPROTECT_CHARS = 0x1, + /* Protect quoted vars against globbing */ + EXP_FLAG_GLOBPROTECT_VARS = 0x2, + /* On word-split, perform globbing (one word may become many) */ + EXP_FLAG_DO_GLOBBING = 0x4, + /* Do not word-split */ + EXP_FLAG_SINGLEWORD = 0x80, + /* ^^^^ EXP_FLAG_SINGLEWORD must be 0x80 */ }; /* Used for initialization: o_string foo = NULL_O_STRING; */ #define NULL_O_STRING { NULL } -#ifndef debug_printf_parse -static const char *const assignment_flag[] ALIGN_PTR = { - "MAYBE_ASSIGNMENT", - "DEFINITELY_ASSIGNMENT", - "NOT_ASSIGNMENT", - "WORD_IS_KEYWORD", -}; -#endif - /* We almost can use standard FILE api, but we need an ability to move * its fd when redirects coincide with it. No api exists for that * (RFE for it at https://sourceware.org/bugzilla/show_bug.cgi?id=21902). @@ -589,22 +602,28 @@ typedef struct HFILE { typedef struct in_str { const char *p; +#if ENABLE_HUSH_ALIAS + /* During alias expansion, p is saved in saved_ibuf, and replaced by alias expansion */ + const char *saved_ibuf; + char *albuf; /* malloced */ +#endif int peek_buf[2]; int last_char; HFILE *file; } in_str; -/* The descrip member of this structure is only used to make +/* The descrip3 member of this structure is only used to make * debugging output pretty */ static const struct { int32_t mode; signed char default_fd; - char descrip[3]; + char descrip3[3]; } redir_table[] ALIGN4 = { { O_RDONLY, 0, "<" }, { O_CREAT|O_TRUNC|O_WRONLY, 1, ">" }, { O_CREAT|O_APPEND|O_WRONLY, 1, ">>" }, { O_CREAT|O_RDWR, 1, "<>" }, + { O_RDONLY, 0, "<<<" }, { O_RDONLY, 0, "<<" }, /* Should not be needed. Bogus default_fd helps in debugging */ /* { O_RDONLY, 77, "<<" }, */ @@ -624,12 +643,13 @@ struct redir_struct { */ }; typedef enum redir_type { - REDIRECT_INPUT = 0, - REDIRECT_OVERWRITE = 1, - REDIRECT_APPEND = 2, - REDIRECT_IO = 3, - REDIRECT_HEREDOC = 4, - REDIRECT_HEREDOC2 = 5, /* REDIRECT_HEREDOC after heredoc is loaded */ + REDIRECT_INPUT = 0, + REDIRECT_OVERWRITE = 1, + REDIRECT_APPEND = 2, + REDIRECT_IO = 3, + REDIRECT_HERESTRING = 4, + REDIRECT_HEREDOC = 5, + REDIRECT_HEREDOC2 = 6, /* REDIRECT_HEREDOC after heredoc is loaded */ REDIRFD_CLOSE = -3, REDIRFD_SYNTAX_ERR = -2, @@ -640,7 +660,6 @@ typedef enum redir_type { HEREDOC_QUOTED = 2, } redir_type; - struct command { pid_t pid; /* 0 if exited */ unsigned assignment_cnt; /* how many argv[i] are assignments? */ @@ -659,8 +678,12 @@ struct command { # define CMD_SINGLEWORD_NOGLOB 3 #endif #if ENABLE_HUSH_FUNCTIONS -# define CMD_FUNCDEF 4 +# if ENABLE_HUSH_FUNCTION_KEYWORD +# define CMD_FUNCTION_KWORD 4 +# endif +# define CMD_FUNCDEF 5 #endif +/* ^^^ if you change this, update CMDTYPE[] array too */ smalluint cmd_exitcode; /* if non-NULL, this "command" is { list }, ( list ), or a compound statement */ @@ -696,7 +719,9 @@ struct command { }; /* Is there anything in this command at all? */ #define IS_NULL_CMD(cmd) \ - (!(cmd)->group && !(cmd)->argv && !(cmd)->redirects) + (!(cmd)->group && !(cmd)->argv && !(cmd)->redirects \ + /* maybe? IF_HUSH_FUNCTION_KEYWORD(&& !(cmd)->cmd_type) */ \ + ) struct pipe { struct pipe *next; @@ -737,13 +762,9 @@ struct parse_context { #if !BB_MMU o_string as_string; #endif - smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */ + smallint is_assignment; #if HAS_KEYWORDS smallint ctx_res_w; - smallint ctx_inverted; /* "! cmd | cmd" */ -#if ENABLE_HUSH_CASE - smallint ctx_dsemicolon; /* ";;" seen */ -#endif /* bitmask of FLAG_xxx, for figuring out valid reserved words */ int old_flag; /* group we are enclosed in: @@ -762,9 +783,14 @@ enum { MAYBE_ASSIGNMENT = 0, DEFINITELY_ASSIGNMENT = 1, NOT_ASSIGNMENT = 2, - /* Not an assignment, but next word may be: "if v=xyz cmd;" */ - WORD_IS_KEYWORD = 3, }; +#ifndef debug_printf_parse +static const char *const assignment_flag[] ALIGN_PTR = { + "MAYBE_ASSIGNMENT", + "DEFINITELY_ASSIGNMENT", + "NOT_ASSIGNMENT", +}; +#endif /* On program start, environ points to initial environment. * putenv adds new pointers into it, unsetenv removes them. @@ -798,6 +824,13 @@ struct function { }; #endif +#if ENABLE_HUSH_ALIAS +struct alias { + struct alias *next; + char *str; + smallint dont_recurse; +}; +#endif /* set -/+o OPT support. (TODO: make it optional) * bash supports the following opts: @@ -920,6 +953,7 @@ struct globals { #endif /* set by signal handler if SIGINT is received _and_ its trap is not set */ smallint flag_SIGINT; + smallint flag_startup_done; #if ENABLE_HUSH_LOOPS smallint flag_break_continue; #endif @@ -934,6 +968,12 @@ struct globals { # define G_flag_return_in_progress 0 #endif smallint exiting; /* used to prevent EXIT trap recursion */ +#if !BB_MMU + smallint reexeced_on_NOMMU; +# define G_reexeced_on_NOMMU (G.reexeced_on_NOMMU) +#else +# define G_reexeced_on_NOMMU 0 +#endif /* These support $? */ smalluint last_exitcode; smalluint expand_exitcode; @@ -973,6 +1013,9 @@ struct globals { unsigned func_nest_level; /* solely to prevent "local v" in non-functions */ # endif struct function *top_func; +#endif +#if ENABLE_HUSH_ALIAS + struct alias *top_alias; #endif /* Signal and trap handling */ #if ENABLE_HUSH_FAST @@ -1025,6 +1068,13 @@ struct globals { #endif #if HUSH_DEBUG >= 2 int debug_indent; +#endif +#if ENABLE_HUSH_TEST || BASH_TEST2 + /* Cached supplementary group array (for testing executable'ity of files) */ + struct cached_groupinfo groupinfo; +# define GROUPINFO_INIT { G.groupinfo.euid = -1; G.groupinfo.egid = -1; } +#else +# define GROUPINFO_INIT /* nothing */ #endif struct sigaction sa; char optstring_buf[sizeof("eixcs")]; @@ -1044,9 +1094,9 @@ struct globals { /* memset(&G.sa, 0, sizeof(G.sa)); */ \ sigfillset(&G.sa.sa_mask); \ G.sa.sa_flags = SA_RESTART; \ + GROUPINFO_INIT; \ } while (0) - /* Function prototypes for builtins */ static int builtin_cd(char **argv) FAST_FUNC; #if ENABLE_HUSH_ECHO @@ -1069,6 +1119,10 @@ static int builtin_jobs(char **argv) FAST_FUNC; #if ENABLE_HUSH_GETOPTS static int builtin_getopts(char **argv) FAST_FUNC; #endif +#if ENABLE_HUSH_ALIAS +static int builtin_alias(char **argv) FAST_FUNC; +static int builtin_unalias(char **argv) FAST_FUNC; +#endif #if ENABLE_HUSH_HELP static int builtin_help(char **argv) FAST_FUNC; #endif @@ -1146,6 +1200,9 @@ struct built_in_command { static const struct built_in_command bltins1[] ALIGN_PTR = { BLTIN("." , builtin_source , "Run commands in file"), BLTIN(":" , builtin_true , NULL), +#if ENABLE_HUSH_ALIAS + BLTIN("alias" , builtin_alias , "Define or display aliases"), +#endif #if ENABLE_HUSH_JOB BLTIN("bg" , builtin_fg_bg , "Resume job in background"), #endif @@ -1197,7 +1254,7 @@ static const struct built_in_command bltins1[] ALIGN_PTR = { BLTIN("return" , builtin_return , "Return from function"), #endif #if ENABLE_HUSH_SET - BLTIN("set" , builtin_set , "Set positional parameters"), + BLTIN("set" , builtin_set , "Set options or positional parameters"), #endif BLTIN("shift" , builtin_shift , "Shift positional parameters"), #if BASH_SOURCE @@ -1207,7 +1264,7 @@ static const struct built_in_command bltins1[] ALIGN_PTR = { BLTIN("times" , builtin_times , NULL), #endif #if ENABLE_HUSH_TRAP - BLTIN("trap" , builtin_trap , "Trap signals"), + BLTIN("trap" , builtin_trap , "Define or display signal handlers"), #endif BLTIN("true" , builtin_true , NULL), #if ENABLE_HUSH_TYPE @@ -1219,6 +1276,9 @@ static const struct built_in_command bltins1[] ALIGN_PTR = { #if ENABLE_HUSH_UMASK BLTIN("umask" , builtin_umask , "Set file creation mask"), #endif +#if ENABLE_HUSH_ALIAS + BLTIN("unalias" , builtin_unalias , "Delete aliases"), +#endif #if ENABLE_HUSH_UNSET BLTIN("unset" , builtin_unset , "Unset variables"), #endif @@ -1248,8 +1308,8 @@ static const struct built_in_command bltins2[] ALIGN_PTR = { #endif }; - -/* Debug printouts. +/* + * Debug printouts. */ #if HUSH_DEBUG >= 2 /* prevent disasters with G.debug_indent < 0 */ @@ -1342,41 +1402,78 @@ static void debug_print_strings(const char *prefix, char **vv) # define debug_print_strings(prefix, vv) ((void)0) #endif - -/* Leak hunting. Use hush_leaktool.sh for post-processing. +/* + * Leak hunting. Use hush_leaktool.sh for post-processing. */ #if LEAK_HUNTING static void *xxmalloc(int lineno, size_t size) { void *ptr = xmalloc((size + 0xff) & ~0xff); - fdprintf(2, "line %d: malloc %p\n", lineno, ptr); + LEAK_PRINTF(2, "line %d: malloc %p\n", lineno, ptr); + return ptr; +} +static void *xxzalloc(int lineno, size_t size) +{ + void *ptr = xzalloc((size + 0xff) & ~0xff); + LEAK_PRINTF(2, "line %d: zalloc %p\n", lineno, ptr); return ptr; } static void *xxrealloc(int lineno, void *ptr, size_t size) { + char *p = ptr; ptr = xrealloc(ptr, (size + 0xff) & ~0xff); - fdprintf(2, "line %d: realloc %p\n", lineno, ptr); + if (p != ptr) + LEAK_PRINTF(2, "line %d: realloc %p\n", lineno, ptr); + return ptr; +} +static void *xxrealloc_getcwd_or_warn(int lineno, char *ptr) +{ + char *p = ptr; + ptr = xrealloc_getcwd_or_warn(ptr); + if (p != ptr) + LEAK_PRINTF(2, "line %d: xrealloc_getcwd_or_warn %p\n", lineno, ptr); return ptr; } static char *xxstrdup(int lineno, const char *str) { char *ptr = xstrdup(str); - fdprintf(2, "line %d: strdup %p\n", lineno, ptr); + LEAK_PRINTF(2, "line %d: strdup %p\n", lineno, ptr); + return ptr; +} +static char *xxstrndup(int lineno, const char *str, size_t n) +{ + char *ptr = xstrndup(str, n); + LEAK_PRINTF(2, "line %d: strndup %p\n", lineno, ptr); + return ptr; +} +static char *xxasprintf(int lineno, const char *f, ...) +{ + char *ptr; + va_list args; + va_start(args, f); + if (vasprintf(&ptr, f, args) < 0) + bb_die_memory_exhausted(); + va_end(args); + LEAK_PRINTF(2, "line %d: xasprintf %p\n", lineno, ptr); return ptr; } static void xxfree(void *ptr) { - fdprintf(2, "free %p\n", ptr); + LEAK_PRINTF(2, "free %p\n", ptr); free(ptr); } -# define xmalloc(s) xxmalloc(__LINE__, s) -# define xrealloc(p, s) xxrealloc(__LINE__, p, s) -# define xstrdup(s) xxstrdup(__LINE__, s) -# define free(p) xxfree(p) +# define xmalloc(s) xxmalloc(__LINE__, s) +# define xzalloc(s) xxzalloc(__LINE__, s) +# define xrealloc(p, s) xxrealloc(__LINE__, p, s) +# define xrealloc_getcwd_or_warn(p) xxrealloc_getcwd_or_warn(__LINE__, p) +# define xstrdup(s) xxstrdup(__LINE__, s) +# define xstrndup(s, n) xxstrndup(__LINE__, s, n) +# define xasprintf(f, ...) xxasprintf(__LINE__, f, __VA_ARGS__) +# define free(p) xxfree(p) #endif - -/* Syntax and runtime errors. They always abort scripts. +/* + * Syntax and runtime errors. They always abort scripts. * In interactive use they usually discard unparsed and/or unexecuted commands * and return to the prompt. * HUSH_DEBUG >= 2 prints line number in this file where it was detected. @@ -1388,11 +1485,14 @@ static void xxfree(void *ptr) # define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch) # define syntax_error_unterm_str(lineno, s) syntax_error_unterm_str(s) # define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch) +# define syntax_error_unexpected_str(lineno, s) syntax_error_unexpected_str(s) #endif static void die_if_script(void) { - if (!G_interactive_fd) { + if (G.flag_startup_done /* when not yet set, allows "hush -l" to not die on errors in /etc/profile */ + && !G_interactive_fd + ) { if (G.last_exitcode) /* sometines it's 2, not 1 (bash compat) */ xfunc_error_retval = G.last_exitcode; xfunc_die(); @@ -1435,24 +1535,27 @@ static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s) // die_if_script(); } -static void syntax_error_unterm_ch(unsigned lineno, char ch) +static void syntax_error_unterm_ch(unsigned lineno, int ch) { char msg[2] = { ch, '\0' }; - syntax_error_unterm_str(lineno, msg); + syntax_error_unterm_str(lineno, ch == EOF ? "EOF" : msg); } -static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) +static void syntax_error_unexpected_str(unsigned lineno UNUSED_PARAM, const char *s) { - char msg[2]; - msg[0] = ch; - msg[1] = '\0'; #if HUSH_DEBUG >= 2 bb_error_msg("hush.c:%u", lineno); #endif - bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg); + bb_error_msg("syntax error: unexpected %s", s); die_if_script(); } +static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) +{ + char msg[2] = { ch, '\0' }; + syntax_error_unexpected_str(lineno, ch == EOF ? "EOF" : msg); +} + #if HUSH_DEBUG < 2 # undef msg_and_die_if_script # undef syntax_error @@ -1460,6 +1563,7 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) # undef syntax_error_unterm_ch # undef syntax_error_unterm_str # undef syntax_error_unexpected_ch +# undef syntax_error_unexpected_str #else # define msg_and_die_if_script(...) msg_and_die_if_script(__LINE__, __VA_ARGS__) # define syntax_error(msg) syntax_error(__LINE__, msg) @@ -1467,10 +1571,11 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) # define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch) # define syntax_error_unterm_str(s) syntax_error_unterm_str(__LINE__, s) # define syntax_error_unexpected_ch(ch) syntax_error_unexpected_ch(__LINE__, ch) +# define syntax_error_unexpected_str(s) syntax_error_unexpected_str(__LINE__, s) #endif - -/* Utility functions +/* + * Utility functions */ /* Replace each \x with x in place, return ptr past NUL. */ static char *unbackslash(char *src) @@ -1616,8 +1721,24 @@ static int xdup_CLOEXEC_and_close(int fd, int avoid_fd) return newfd; } +#if ENABLE_HUSH_ALIAS +static void enable_all_aliases(void) +{ + if (G_interactive_fd) { + struct alias *alias = G.top_alias; + while (alias) { + alias->dont_recurse = 0; + alias = alias->next; + } + } +} +#else +#define enable_all_aliases() ((void)0) +#endif -/* Manipulating HFILEs */ +/* + * Manipulating HFILEs + */ static HFILE *hfopen(const char *name) { HFILE *fp; @@ -1763,8 +1884,8 @@ static int fd_in_HFILEs(int fd) return 0; } - -/* Helpers for setting new $n and restoring them back +/* + * Helpers for setting new $n and restoring them back */ typedef struct save_arg_t { char *sv_argv0; @@ -1804,8 +1925,8 @@ static void restore_G_args(save_arg_t *sv, char **argv) IF_HUSH_SET(G.global_args_malloced = sv->sv_g_malloced;) } - -/* Basic theory of signal handling in shell +/* + * Basic theory of signal handling in shell * ======================================== * This does not describe what hush does, rather, it is current understanding * what it _should_ do. If it doesn't, it's a bug. @@ -1925,7 +2046,7 @@ static void restore_G_args(save_arg_t *sv, char **argv) * "trap - SIGxxx": * if sig is in special_sig_mask, set handler back to: * record_pending_signo, or to IGN if it's a tty stop signal - * if sig is in fatal_sig_mask, set handler back to sigexit. + * if sig is in fatal_sig_mask, set handler back to restore_ttypgrp_and_killsig_or__exit. * else: set handler back to SIG_DFL * "trap 'cmd' SIGxxx": * set handler to record_pending_signo. @@ -1969,7 +2090,7 @@ static void record_pending_signo(int sig) || (G_traps && G_traps[SIGCHLD] && G_traps[SIGCHLD][0]) /* ^^^ if SIGCHLD, interrupt line reading only if it has a trap */ ) { - bb_got_signal = sig; /* for read_line_input: "we got a signal" */ + bb_got_signal = sig; /* for read_line_input / read builtin: "we got a signal" */ } #endif #if ENABLE_HUSH_FAST @@ -1998,19 +2119,6 @@ static sighandler_t install_sighandler(int sig, sighandler_t handler) return old_sa.sa_handler; } -static void hush_exit(int exitcode) NORETURN; - -static void restore_ttypgrp_and__exit(void) NORETURN; -static void restore_ttypgrp_and__exit(void) -{ - /* xfunc has failed! die die die */ - /* no EXIT traps, this is an escape hatch! */ - G.exiting = 1; - hush_exit(xfunc_error_retval); -} - -#if ENABLE_HUSH_JOB - /* Needed only on some libc: * It was observed that on exit(), fgetc'ed buffered data * gets "unwound" via lseek(fd, -NUM, SEEK_CUR). @@ -2024,26 +2132,20 @@ static void restore_ttypgrp_and__exit(void) * and in `cmd` handling. * If set as die_func(), this makes xfunc_die() exit via _exit(), not exit(): */ -static void fflush_and__exit(void) NORETURN; -static void fflush_and__exit(void) +static NORETURN void fflush_and__exit(void) { fflush_all(); _exit(xfunc_error_retval); } -/* After [v]fork, in child: do not restore tty pgrp on xfunc death */ -# define disable_restore_tty_pgrp_on_exit() (die_func = fflush_and__exit) -/* After [v]fork, in parent: restore tty pgrp on xfunc death */ -# define enable_restore_tty_pgrp_on_exit() (die_func = restore_ttypgrp_and__exit) - +#if ENABLE_HUSH_JOB /* Restores tty foreground process group, and exits. * May be called as signal handler for fatal signal * (will resend signal to itself, producing correct exit state) * or called directly with -EXITCODE. * We also call it if xfunc is exiting. */ -static void sigexit(int sig) NORETURN; -static void sigexit(int sig) +static NORETURN void restore_ttypgrp_and_killsig_or__exit(int sig) { /* Careful: we can end up here after [v]fork. Do not restore * tty pgrp then, only top-level shell process does that */ @@ -2061,6 +2163,19 @@ static void sigexit(int sig) kill_myself_with_sig(sig); /* does not return */ } + +static NORETURN void fflush_restore_ttypgrp_and__exit(void) +{ + /* xfunc has failed! die die die */ + fflush_all(); + restore_ttypgrp_and_killsig_or__exit(- xfunc_error_retval); +} + +/* After [v]fork, in child: do not restore tty pgrp on xfunc death */ +# define disable_restore_tty_pgrp_on_exit() (die_func = fflush_and__exit) +/* After [v]fork, in parent: restore tty pgrp on xfunc death */ +# define enable_restore_tty_pgrp_on_exit() (die_func = fflush_restore_ttypgrp_and__exit) + #else # define disable_restore_tty_pgrp_on_exit() ((void)0) @@ -2077,7 +2192,7 @@ static sighandler_t pick_sighandler(unsigned sig) #if ENABLE_HUSH_JOB /* is sig fatal? */ if (G_fatal_sig_mask & sigmask) - handler = sigexit; + handler = restore_ttypgrp_and_killsig_or__exit; else #endif /* sig has special handling? */ @@ -2095,11 +2210,33 @@ static sighandler_t pick_sighandler(unsigned sig) return handler; } -/* Restores tty foreground process group, and exits. */ -static void hush_exit(int exitcode) +static const char* FAST_FUNC get_local_var_value(const char *name); + +/* Self-explanatory. + * Restores tty foreground process group too. + */ +static NORETURN void save_history_run_exit_trap_and_exit(int exitcode) { #if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT - save_history(G.line_input_state); /* may be NULL */ + if (G.line_input_state + && getpid() == G.root_pid /* exits in subshells do not save history */ + ) { + const char *hp; +# if ENABLE_FEATURE_SH_HISTFILESIZE +// in bash: +// HISTFILESIZE controls the on-disk history file size (in lines, 0=no history): +// "When this variable is assigned a value, the history file is truncated, if necessary" +// but we do it only at exit, not on every assignment: + /* Use HISTFILESIZE to limit file size */ + hp = get_local_var_value("HISTFILESIZE"); + if (hp) + G.line_input_state->max_history = size_from_HISTFILESIZE(hp); +# endif + /* HISTFILE: "If unset, the command history is not saved when a shell exits." */ + hp = get_local_var_value("HISTFILE"); + G.line_input_state->hist_file = hp; + save_history(G.line_input_state); /* no-op if hist_file is NULL or "" */ + } #endif fflush_all(); @@ -2133,7 +2270,7 @@ static void hush_exit(int exitcode) fflush_all(); #if ENABLE_HUSH_JOB - sigexit(- (exitcode & 0xff)); + restore_ttypgrp_and_killsig_or__exit(- (exitcode & 0xff)); #else _exit(exitcode); #endif @@ -2218,7 +2355,7 @@ static int check_and_run_traps(void) } } /* this restores tty pgrp, then kills us with SIGHUP */ - sigexit(SIGHUP); + restore_ttypgrp_and_killsig_or__exit(SIGHUP); } #endif #if ENABLE_HUSH_FAST @@ -2246,7 +2383,9 @@ static int check_and_run_traps(void) return last_sig; } - +/* + * Shell and environment variable support + */ static const char *get_cwd(int force) { if (force || G.cwd == NULL) { @@ -2261,10 +2400,6 @@ static const char *get_cwd(int force) return G.cwd; } - -/* - * Shell and environment variable support - */ static struct variable **get_ptr_to_local_var(const char *name) { struct variable **pp; @@ -2373,7 +2508,7 @@ static int set_local_var(char *str, unsigned flags) if (cur->flg_read_only) { bb_error_msg("%s: readonly variable", str); free(str); -//NOTE: in bash, assignment in "export READONLY_VAR=Z" fails, and sets $?=1, +//NOTE: in bash, assignment in "export READONLY_VAR=Z" fails, and sets $? to 1, //but export per se succeeds (does put the var in env). We don't mimic that. return -1; } @@ -2431,10 +2566,10 @@ static int set_local_var(char *str, unsigned flags) /* Replace the value in the found "struct variable" */ if (cur->max_len != 0) { - if (cur->max_len >= strnlen(str, cur->max_len + 1)) { - /* This one is from startup env, reuse space */ - debug_printf_env("reusing startup env for '%s'\n", str); - strcpy(cur->varstr, str); + /* This one is from startup env, try to reuse space */ + int new_len = stpncpy(cur->varstr, str, cur->max_len + 1) - cur->varstr; + if (new_len <= cur->max_len) { + debug_printf_env("reused startup env for '%s'\n", str); goto free_and_exp; } /* Can't reuse */ @@ -2451,7 +2586,7 @@ static int set_local_var(char *str, unsigned flags) goto set_str_and_exp; } - /* Not found or shadowed - create new variable struct */ + /* Not found, or found but shadowed - create new variable struct */ debug_printf_env("%s: alloc new var '%s'/%u\n", __func__, str, local_lvl); cur = xzalloc(sizeof(*cur)); cur->var_nest_level = local_lvl; @@ -2538,7 +2673,6 @@ static int unset_local_var(const char *name) } #endif - /* * Helpers for "var1=val1 var2=val2 cmd" feature */ @@ -2612,7 +2746,6 @@ static void set_vars_and_save_old(char **strings) free(strings); } - /* * Unicode helper */ @@ -2669,20 +2802,16 @@ static const char *setup_prompt_string(void) debug_printf("prompt_str '%s'\n", prompt_str); return prompt_str; } -static int get_user_input(struct in_str *i) +static int show_prompt_and_get_stdin(struct in_str *i) { # if ENABLE_FEATURE_EDITING /* In EDITING case, this function reads next input line, * saves it in i->p, then returns 1st char of it. */ - int r; - const char *prompt_str; - - prompt_str = setup_prompt_string(); for (;;) { - reinit_unicode_for_hush(); - G.flag_SIGINT = 0; + int r; + G.flag_SIGINT = 0; bb_got_signal = 0; if (!sigisemptyset(&G.pending_set)) { /* Whoops, already got a signal, do not call read_line_input */ @@ -2702,6 +2831,8 @@ static int get_user_input(struct in_str *i) * #^^^ prints "T", prints prompt, repeats * #(bash 5.0.17 exits after first "T", looks like a bug) */ + const char *prompt_str = setup_prompt_string(); + reinit_unicode_for_hush(); r = read_line_input(G.line_input_state, prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1 ); @@ -2732,7 +2863,7 @@ static int get_user_input(struct in_str *i) /* it was a signal: go back, read another input line */ } i->p = G.user_input_buf; - return (unsigned char)*i->p++; + return (unsigned char)*i->p++; /* can't be NUL */ # else /* In !EDITING case, this function gets called for every char. * Buffering happens deeper in the call chain, in hfgetc(i->file). @@ -2773,13 +2904,13 @@ static int get_user_input(struct in_str *i) } /* This is the magic location that prints prompts * and gets data back from the user */ -static int fgetc_interactive(struct in_str *i) +static int i_getch_interactive(struct in_str *i) { int ch; /* If it's interactive stdin, get new line. */ if (G_interactive_fd && i->file == G.HFILE_stdin) { /* Returns first char (or EOF), the rest is in i->p[] */ - ch = get_user_input(i); + ch = show_prompt_and_get_stdin(i); G.promptmode = 1; /* PS2 */ debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode); } else { @@ -2789,7 +2920,7 @@ static int fgetc_interactive(struct in_str *i) return ch; } #else /* !INTERACTIVE */ -static ALWAYS_INLINE int fgetc_interactive(struct in_str *i) +static ALWAYS_INLINE int i_getch_interactive(struct in_str *i) { int ch; do ch = hfgetc(i->file); while (ch == '\0'); @@ -2797,37 +2928,88 @@ static ALWAYS_INLINE int fgetc_interactive(struct in_str *i) } #endif /* !INTERACTIVE */ +#if ENABLE_HUSH_ALIAS +static ALWAYS_INLINE int i_has_alias_buffer(struct in_str *i) +{ + return i->saved_ibuf && i->p && *i->p; +} +static void i_prepend_to_alias_buffer(struct in_str *i, char *prepend, char ch) +{ + if (i->saved_ibuf) { + /* Nested alias expansion example: + * alias a='b c'; alias b='echo A:' + * a + * ^^^ runs "echo A: c" + */ + char *old = i->albuf; + //bb_error_msg("before'%s' p'%s'", i->albuf, i->p); + i->albuf = xasprintf("%s%c%s", prepend, ch, i->p); + i->p = i->albuf; + //bb_error_msg("after'%s' p'%s'", i->albuf, i->p); + free(old); + return; + } + i->saved_ibuf = i->p; + i->p = i->albuf = xasprintf("%s%c", prepend, ch); + //bb_error_msg("albuf'%s'", i->albuf); +} +static void i_free_alias_buffer(struct in_str *i) +{ + if (i->saved_ibuf) { + /* We are here if alias expansion has ended just now */ + free(i->albuf); + i->p = i->saved_ibuf; + i->saved_ibuf = NULL; +/* We re-enable aliases only if expansion has finished, not on command boundaries. + * Example: + * alias a="nice&&a" + * a;a + * This should run "nice" and then "can't execute 'a': No such file or directory", + * then should run "nice" again and then "can't execute 'a': No such file or directory" again + * because the second "a" in alias definition must not expand (to prevent infinite expansion), + * but the "a" after ; must expand (there is no danger of infinite expansion). + * alias a="nice&&nice" + * a;a&&a + * should execute "nice" six times. + */ + debug_printf_parse("end of alias\n"); + enable_all_aliases(); + } +} +#else +# define i_has_alias_buffer(i) 0 +# define i_free_alias_buffer(i) ((void)0) +#endif + static int i_getch(struct in_str *i) { int ch; - if (!i->file) { - /* string-based in_str */ + IF_HUSH_ALIAS(again:) + if (i->p) { + /* string-based in_str, or line editing buffer, or alias buffer */ ch = (unsigned char)*i->p; if (ch != '\0') { i->p++; - i->last_char = ch; -#if ENABLE_HUSH_LINENO_VAR - if (ch == '\n') { - G.parse_lineno++; - debug_printf_parse("G.parse_lineno++ = %u\n", G.parse_lineno); - } + goto out; + } +#if ENABLE_HUSH_ALIAS + if (i->saved_ibuf) { + i_free_alias_buffer(i); + goto again; + } #endif - return ch; + /* If string-based in_str, end-of-string is EOF */ + if (!i->file) { + debug_printf("i_getch: got EOF from string\n"); + return EOF; } - return EOF; } /* FILE-based in_str */ -#if ENABLE_FEATURE_EDITING - /* This can be stdin, check line editing char[] buffer */ - if (i->p && *i->p != '\0') { - ch = (unsigned char)*i->p++; - goto out; - } -#endif - /* peek_buf[] is an int array, not char. Can contain EOF. */ + /* Use what i_peek / i_peek2 saved (if anything) */ + /* peek_buf[] is an int array, not char - can contain EOF */ ch = i->peek_buf[0]; if (ch != 0) { int ch2 = i->peek_buf[1]; @@ -2838,70 +3020,71 @@ static int i_getch(struct in_str *i) goto out; } - ch = fgetc_interactive(i); + ch = i_getch_interactive(i); out: - debug_printf("file_get: got '%c' %d\n", ch, ch); - i->last_char = ch; + debug_printf("i_getch: got '%c' %d\n", ch, ch); #if ENABLE_HUSH_LINENO_VAR if (ch == '\n') { G.parse_lineno++; debug_printf_parse("G.parse_lineno++ = %u\n", G.parse_lineno); } #endif + i->last_char = ch; return ch; } +/* Called often, has optimizations to make it faster: + * = May return NUL instead of EOF. + * It's ok because use cases are "got '&', peek next ch to see whether it is '&'" + * = Must not be called after '\n' (it would cause unexpected line editing prompt). + */ static int i_peek(struct in_str *i) { int ch; - if (!i->file) { - /* string-based in_str */ - /* Doesn't report EOF on NUL. None of the callers care. */ + if (i->p) { + /* string-based in_str, or line editing buffer, or alias buffer */ +#if ENABLE_HUSH_ALIAS + if (*i->p == '\0' && i->saved_ibuf) { + /* corner case: "an_alias&&..." expansion will have + * i->p = "&", i->saved_ibuf = "&..." + * and at the end of it, we must not return NUL, + * this would logically split && into & & during parsing. + */ + return (unsigned char)*i->saved_ibuf; + } +#endif return (unsigned char)*i->p; } - /* FILE-based in_str */ + /* Now we know it is a file-based in_str. */ -#if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE - /* This can be stdin, check line editing char[] buffer */ - if (i->p && *i->p != '\0') - return (unsigned char)*i->p; -#endif /* peek_buf[] is an int array, not char. Can contain EOF. */ ch = i->peek_buf[0]; - if (ch != 0) - return ch; + if (ch == 0) { + /* We did not read it yet, get it now */ + do ch = hfgetc(i->file); while (ch == '\0'); + i->peek_buf[0] = ch; + } - /* Need to get a new char */ - ch = fgetc_interactive(i); debug_printf("file_peek: got '%c' %d\n", ch, ch); - - /* Save it by either rolling back line editing buffer, or in i->peek_buf[0] */ -#if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE - if (i->p) { - i->p -= 1; - return ch; - } -#endif - i->peek_buf[0] = ch; - /*i->peek_buf[1] = 0; - already is */ return ch; } -/* Only ever called if i_peek() was called, and did not return EOF. - * IOW: we know the previous peek saw an ordinary char, not EOF, not NUL, - * not end-of-line. Therefore we never need to read a new editing line here. +/* Called if i_peek() was called, and saw an ordinary char + * (not EOF, not NUL, not end-of-line). + * Therefore we never need to read a new editing line here. */ static int i_peek2(struct in_str *i) { int ch; - /* There are two cases when i->p[] buffer exists. + /* There are three cases when i->p[] buffer exists. + * (0) alias expansion in progress. * (1) it's a string in_str. * (2) It's a file, and we have a saved line editing buffer. - * In both cases, we know that i->p[0] exists and not NUL, and - * the peek2 result is in i->p[1]. + * In all cases, we know that i->p[0] exists and not NUL. + * The peek2 result is in i->p[1]. */ if (i->p) return (unsigned char)i->p[1]; @@ -2971,7 +3154,6 @@ static void setup_string_in_str(struct in_str *i, const char *s) i->p = s; } - /* * o_string support */ @@ -3004,7 +3186,7 @@ static void o_grow_by(o_string *o, int len) } } -static void o_addchr(o_string *o, int ch) +static ALWAYS_INLINE void INLINED_o_addchr(o_string *o, int ch) { debug_printf("o_addchr: '%c' o->length=%d o=%p\n", ch, o->length, o); if (o->length < o->maxlen) { @@ -3018,6 +3200,10 @@ static void o_addchr(o_string *o, int ch) o_grow_by(o, 1); goto add; } +static void o_addchr(o_string *o, int ch) +{ + INLINED_o_addchr(o, ch); +} #if 0 /* Valid only if we know o_string is not empty */ @@ -3128,7 +3314,7 @@ static void o_addqchr(o_string *o, int ch) static void o_addQchr(o_string *o, int ch) { int sz = 1; - if ((o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS) + if ((o->o_expflags & EXP_FLAG_GLOBPROTECT_CHARS) && strchr("*?[-\\" MAYBE_BRACES, ch) ) { sz++; @@ -3171,7 +3357,7 @@ static void o_addqblock(o_string *o, const char *str, int len) static void o_addQblock(o_string *o, const char *str, int len) { - if (!(o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)) { + if (!(o->o_expflags & EXP_FLAG_GLOBPROTECT_CHARS)) { o_addblock(o, str, len); return; } @@ -3183,6 +3369,11 @@ static void o_addQstr(o_string *o, const char *str) o_addQblock(o, str, strlen(str)); } +static void o_addqstr(o_string *o, const char *str) +{ + o_addqblock(o, str, strlen(str)); +} + /* A special kind of o_string for $VAR and `cmd` expansion. * It contains char* list[] at the beginning, which is grown in 16 element * increments. Actual string data starts at the next multiple of 16 * (char*). @@ -3201,11 +3392,11 @@ static void debug_print_list(const char *prefix, o_string *o, int n) int i = 0; indent(); - fdprintf(2, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d glob:%d quoted:%d escape:%d\n", + fdprintf(2, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d do_glob:%d has_quoted:%d globprotect:%d\n", prefix, list, n, string_start, o->length, o->maxlen, - !!(o->o_expflags & EXP_FLAG_GLOB), + !!(o->o_expflags & EXP_FLAG_DO_GLOBBING), o->has_quoted_part, - !!(o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); + !!(o->o_expflags & EXP_FLAG_GLOBPROTECT_CHARS)); while (i < n) { indent(); fdprintf(2, " list[%d]=%d '%s' %p\n", i, (int)(uintptr_t)list[i], @@ -3328,7 +3519,19 @@ static int glob_needed(const char *s) s += 2; continue; } - if (*s == '*' || *s == '[' || *s == '?' || *s == '{') + if (*s == '*' || *s == '?') + return 1; + /* Only force glob if "..[..].." detected. + * Not merely "[", "[[", "][" etc. + * Optimization to avoid glob() + * on "[ COND ]" and "[[ COND ]]": + * strace hush -c 'i=0; while [ $((++i)) != 50000 ]; do :; done' + * shouldn't be doing 50000 stat("["). + * (Can do it for "{" too, but it's not a common case). + */ + if (*s == '[' && strchr(s+1, ']')) + return 1; + if (*s == '{' /* && strchr(s+1, '}')*/) return 1; s++; } @@ -3519,7 +3722,16 @@ static int glob_needed(const char *s) s += 2; continue; } - if (*s == '*' || *s == '[' || *s == '?') + if (*s == '*' || *s == '?') + return 1; + /* Only force glob if "..[..].." detected. + * Not merely "[", "[[", "][" etc. + * Optimization to avoid glob() + * on "[ COND ]" and "[[ COND ]]": + * strace hush -c 'i=0; while [ $((++i)) != 50000 ]; do :; done' + * shouldn't be doing 50000 stat("["). + */ + if (*s == '[' && strchr(s+1, ']')) return 1; s++; } @@ -3586,11 +3798,11 @@ static int perform_glob(o_string *o, int n) #endif /* !HUSH_BRACE_EXPANSION */ -/* If o->o_expflags & EXP_FLAG_GLOB, glob the string so far remembered. +/* If o->o_expflags & EXP_FLAG_DO_GLOBBING, glob the string so far remembered. * Otherwise, just finish current list[] and start new */ static int o_save_ptr(o_string *o, int n) { - if (o->o_expflags & EXP_FLAG_GLOB) { + if (o->o_expflags & EXP_FLAG_DO_GLOBBING) { /* If o->has_empty_slot, list[n] was already globbed * (if it was requested back then when it was filled) * so don't do that again! */ @@ -3666,8 +3878,8 @@ static struct pipe *free_pipe(struct pipe *pi) //command->group_as_string = NULL; #endif for (r = command->redirects; r; r = rnext) { - debug_printf_clean(" redirect %d%s", - r->rd_fd, redir_table[r->rd_type].descrip); + debug_printf_clean(" redirect %d%.3s", + r->rd_fd, redir_table[r->rd_type].descrip3); /* guard against the case >$FOO, where foo is unset or blank */ if (r->rd_filename) { debug_printf_clean(" fname:'%s'\n", r->rd_filename); @@ -3703,9 +3915,9 @@ static void free_pipe_list(struct pipe *pi) } } - -/*** Parsing routines ***/ - +/* + * Parsing routines + */ #ifndef debug_print_tree static void debug_print_tree(struct pipe *pi, int lvl) { @@ -3736,7 +3948,7 @@ static void debug_print_tree(struct pipe *pi, int lvl) # endif # if ENABLE_HUSH_CASE [RES_CASE ] = "CASE" , - [RES_CASE_IN ] = "CASE_IN" , + [RES_CASE_IN] = "CASE_IN", [RES_MATCH] = "MATCH", [RES_CASE_BODY] = "CASE_BODY", [RES_ESAC ] = "ESAC" , @@ -3745,11 +3957,13 @@ static void debug_print_tree(struct pipe *pi, int lvl) [RES_SNTX ] = "SNTX" , }; static const char *const CMDTYPE[] ALIGN_PTR = { - "{}", - "()", - "[noglob]", + "{}", //CMD_NORMAL + "()", //CMD_SUBSHELL + "[test2]", //CMD_TEST2_SINGLEWORD_NOGLOB + "[noglob]", //CMD_SINGLEWORD_NOGLOB # if ENABLE_HUSH_FUNCTIONS - "func()", + "func()", //CMD_FUNCTION_KWORD + "funcdef", //CMD_FUNCDEF # endif }; @@ -3813,9 +4027,11 @@ static struct pipe *new_pipe(void) return pi; } -/* Command (member of a pipe) is complete, or we start a new pipe - * if ctx->command is NULL. - * No errors possible here. +/* Parsing of command (member of a pipe) is completed. + * If it's not null, a new empty command structure is added + * to the current pipe, and ctx->command is set to it. + * Return the current number of already parsed commands in the pipe. + * No errors are possible here. */ static int done_command(struct parse_context *ctx) { @@ -3831,17 +4047,16 @@ static int done_command(struct parse_context *ctx) ctx->pending_redirect = NULL; } #endif - if (command) { if (IS_NULL_CMD(command)) { - debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds); + debug_printf_parse("done_command: skipping null cmd, num_cmds:%d\n", pi->num_cmds); goto clear_and_ret; } pi->num_cmds++; debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds); //debug_print_tree(ctx->list_head, 20); } else { - debug_printf_parse("done_command: initializing, num_cmds=%d\n", pi->num_cmds); + debug_printf_parse("done_command: initializing, num_cmds:%d\n", pi->num_cmds); } /* Only real trickiness here is that the uncommitted @@ -3852,21 +4067,36 @@ static int done_command(struct parse_context *ctx) memset(command, 0, sizeof(*command)); #if ENABLE_HUSH_LINENO_VAR command->lineno = G.parse_lineno; - debug_printf_parse("command->lineno = G.parse_lineno (%u)\n", G.parse_lineno); + debug_printf_parse("command->lineno=G.parse_lineno (%u)\n", G.parse_lineno); #endif - return pi->num_cmds; /* used only for 0/nonzero check */ + return pi->num_cmds; } -static void done_pipe(struct parse_context *ctx, pipe_style type) +/* Parsing of a pipe is completed. + * Finish parsing current command via done_command(). + * (If the pipe is not empty, but done_command() did not change the number + * of commands in pipe, return value is 1. Used for catching syntax errors) + * Then append a new pipe with one empty command. + */ +static int done_pipe(struct parse_context *ctx, pipe_style type) { - int not_null; + int num_cmds; + int oldnum; + int last_cmd_is_null; debug_printf_parse("done_pipe entered, followup %d\n", type); /* Close previous command */ - not_null = done_command(ctx); + oldnum = ctx->pipe->num_cmds; + num_cmds = done_command(ctx); + + /* This is true if this was a non-empty pipe, + * but done_command didn't add a new member to it. + * Usually it is a syntax error. + * Examples: "date | | ...", "date | ; ..." + */ + last_cmd_is_null = (oldnum != 0 && num_cmds == oldnum); + #if HAS_KEYWORDS - ctx->pipe->pi_inverted = ctx->ctx_inverted; - ctx->ctx_inverted = 0; ctx->pipe->res_word = ctx->ctx_res_w; #endif if (type == PIPE_BG && ctx->list_head != ctx->pipe) { @@ -3905,7 +4135,7 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) ctx->list_head = ctx->pipe = pi; /* for cases like "cmd && &", do not be tricked by last command * being null - the entire {...} & is NOT null! */ - not_null = 1; + num_cmds = 1; } else { no_conv: ctx->pipe->followup = type; @@ -3913,8 +4143,8 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) /* Without this check, even just on command line generates * tree of three NOPs (!). Which is harmless but annoying. - * IOW: it is safe to do it unconditionally. */ - if (not_null + * IOW: it is safe to do the following unconditionally. */ + if (num_cmds != 0 #if ENABLE_HUSH_IF || ctx->ctx_res_w == RES_FI #endif @@ -3929,37 +4159,39 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) ) { struct pipe *new_p; debug_printf_parse("done_pipe: adding new pipe: " - "not_null:%d ctx->ctx_res_w:%d\n", - not_null, ctx->ctx_res_w); + "num_cmds:%d ctx->ctx_res_w:%d\n", + num_cmds, ctx->ctx_res_w); new_p = new_pipe(); ctx->pipe->next = new_p; ctx->pipe = new_p; /* RES_THEN, RES_DO etc are "sticky" - * they remain set for pipes inside if/while. * This is used to control execution. - * RES_FOR and RES_IN are NOT sticky (needed to support - * cases where variable or value happens to match a keyword): */ #if ENABLE_HUSH_LOOPS + /* RES_FOR and RES_IN are NOT sticky (needed to support + * cases where variable or value happens to match a keyword): + */ if (ctx->ctx_res_w == RES_FOR || ctx->ctx_res_w == RES_IN) ctx->ctx_res_w = RES_NONE; #endif #if ENABLE_HUSH_CASE - if (ctx->ctx_res_w == RES_MATCH) - ctx->ctx_res_w = RES_CASE_BODY; if (ctx->ctx_res_w == RES_CASE) ctx->ctx_res_w = RES_CASE_IN; + if (ctx->ctx_res_w == RES_MATCH) + ctx->ctx_res_w = RES_CASE_BODY; #endif - ctx->command = NULL; /* trick done_command below */ /* Create the memory for command, roughly: * ctx->pipe->cmds = new struct command; * ctx->command = &ctx->pipe->cmds[0]; */ + ctx->command = NULL; done_command(ctx); //debug_print_tree(ctx->list_head, 10); } - debug_printf_parse("done_pipe return\n"); + debug_printf_parse("done_pipe return: last_cmd_is_null:%d\n", last_cmd_is_null); + return last_cmd_is_null; } static void initialize_context(struct parse_context *ctx) @@ -3973,6 +4205,10 @@ static void initialize_context(struct parse_context *ctx) * ctx->command = &ctx->pipe->cmds[0]; */ done_command(ctx); + /* If very first arg is "" or '', ctx.word.data may end up NULL. + * Prevent this: + */ + ctx->word.data = xzalloc(1); /* start as "", not as NULL */ } /* If a reserved word is found and processed, parse context is modified @@ -4009,39 +4245,39 @@ enum { FLAG_START = (1 << RES_XXXX ), }; -static const struct reserved_combo* match_reserved_word(o_string *word) -{ - /* Mostly a list of accepted follow-up reserved words. - * FLAG_END means we are done with the sequence, and are ready - * to turn the compound list into a command. - * FLAG_START means the word must start a new compound list. - */ - static const struct reserved_combo reserved_list[] ALIGN4 = { +/* Mostly a list of accepted follow-up reserved words. + * FLAG_END means we are done with the sequence, and are ready + * to turn the compound list into a command. + * FLAG_START means the word must start a new compound list. + */ +static const struct reserved_combo reserved_list[] ALIGN4 = { # if ENABLE_HUSH_IF - { "!", RES_NONE, NOT_ASSIGNMENT , 0 }, - { "if", RES_IF, MAYBE_ASSIGNMENT, FLAG_THEN | FLAG_START }, - { "then", RES_THEN, MAYBE_ASSIGNMENT, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, - { "elif", RES_ELIF, MAYBE_ASSIGNMENT, FLAG_THEN }, - { "else", RES_ELSE, MAYBE_ASSIGNMENT, FLAG_FI }, - { "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END }, + { "!", RES_NONE, NOT_ASSIGNMENT , 0 }, + { "if", RES_IF, MAYBE_ASSIGNMENT, FLAG_THEN | FLAG_START }, + { "then", RES_THEN, MAYBE_ASSIGNMENT, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, + { "elif", RES_ELIF, MAYBE_ASSIGNMENT, FLAG_THEN }, + { "else", RES_ELSE, MAYBE_ASSIGNMENT, FLAG_FI }, + { "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END }, # endif # if ENABLE_HUSH_LOOPS - { "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START }, - { "while", RES_WHILE, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START }, - { "until", RES_UNTIL, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START }, - { "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO }, - { "do", RES_DO, MAYBE_ASSIGNMENT, FLAG_DONE }, - { "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END }, + { "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START }, + { "while", RES_WHILE, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START }, + { "until", RES_UNTIL, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START }, + { "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO }, + { "do", RES_DO, MAYBE_ASSIGNMENT, FLAG_DONE }, + { "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END }, # endif # if ENABLE_HUSH_CASE - { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, - { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, + { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, + { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, # endif - }; +}; +static const struct reserved_combo* match_reserved_word(const char *word) +{ const struct reserved_combo *r; for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) { - if (strcmp(word->data, r->literal) == 0) + if (strcmp(word, r->literal) == 0) return r; } return NULL; @@ -4052,16 +4288,36 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx) { # if ENABLE_HUSH_CASE static const struct reserved_combo reserved_match = { - "", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC + "", RES_MATCH, NOT_ASSIGNMENT, FLAG_MATCH | FLAG_ESAC }; # endif const struct reserved_combo *r; - if (ctx->word.has_quoted_part) - return 0; - r = match_reserved_word(&ctx->word); +# if ENABLE_HUSH_FUNCTION_KEYWORD + /* This is ~60 bytes smaller than adding "function" to reserved_list[] */ + if (strcmp(ctx->word.data, "function") == 0) { + ctx->command->cmd_type = CMD_FUNCTION_KWORD; + /* Return something harmless !NULL */ + return &reserved_list[0]; + } +# endif + + r = match_reserved_word(ctx->word.data); if (!r) return r; /* NULL */ +# if ENABLE_HUSH_CASE /* "case" syntax has a curveball */ + if (ctx->ctx_res_w == RES_MATCH + && r->res != RES_ESAC + ) { + /* We are at WORD in ";; WORD" or "case .. in WORD". + * Here, only "esac" is a keyword. + * Else WORD is a case pattern, can be keyword-like: + * if) echo got_if;; + * is allowed. + */ + return NULL; + } +# endif debug_printf("found reserved word %s, res %d\n", r->literal, r->res); # if ENABLE_HUSH_CASE @@ -4071,11 +4327,13 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx) } else # endif if (r->flag == 0) { /* '!' */ - if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ - syntax_error("! ! command"); + if (ctx->pipe->num_cmds != 0 /* bash disallows: nice | ! cat */ + /* || ctx->pipe->pi_inverted - bash used to disallow "! ! true" bash 5.2.15 allows it */ + ) { + syntax_error_unexpected_ch('!'); ctx->ctx_res_w = RES_SNTX; } - ctx->ctx_inverted = 1; + ctx->pipe->pi_inverted = 1 - ctx->pipe->pi_inverted; return r; } if (r->flag & FLAG_START) { @@ -4142,8 +4400,11 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx) } #endif /* HAS_KEYWORDS */ -/* Word is complete, look at it and update parsing context. - * Normal return is 0. Syntax errors return 1. +/* Parsing of a word is complete. + * Look at it and update current command: + * update current command's argv/cmd_type/etc, fill in redirect name and type, + * check reserved-ness and assignment-ness, etc... + * Normal return is 0. Syntax errors print error message and return 1. * Note: on return, word is reset, but not o_free'd! */ static int done_word(struct parse_context *ctx) @@ -4151,8 +4412,8 @@ static int done_word(struct parse_context *ctx) struct command *command = ctx->command; debug_printf_parse("done_word entered: '%s' %p\n", ctx->word.data, command); - if (ctx->word.length == 0 && !ctx->word.has_quoted_part) { - debug_printf_parse("done_word return 0: true null, ignored\n"); + if (IS_NULL_WORD(ctx->word)) { + debug_printf_parse("done_word return 0: no word, ignored\n"); return 0; } @@ -4179,7 +4440,7 @@ static int done_word(struct parse_context *ctx) // as written: // <pending_redirect->rd_filename = xstrdup(ctx->word.data); /* Cater for >\file case: @@ -4196,124 +4457,124 @@ static int done_word(struct parse_context *ctx) } debug_printf_parse("word stored in rd_filename: '%s'\n", ctx->word.data); ctx->pending_redirect = NULL; - } else { + goto ret; + } + #if HAS_KEYWORDS -# if ENABLE_HUSH_CASE - if (ctx->ctx_dsemicolon - && strcmp(ctx->word.data, "esac") != 0 /* not "... pattern) cmd;; esac" */ - ) { - /* already done when ctx_dsemicolon was set to 1: */ - /* ctx->ctx_res_w = RES_MATCH; */ - ctx->ctx_dsemicolon = 0; - } else -# endif # if defined(CMD_TEST2_SINGLEWORD_NOGLOB) - if (command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB - && strcmp(ctx->word.data, "]]") == 0 - ) { - /* allow "[[ ]] >file" etc */ - command->cmd_type = CMD_SINGLEWORD_NOGLOB; - } else + if (command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB + && !ctx->word.has_quoted_part + && strcmp(ctx->word.data, "]]") == 0 + ) { + /* End test2-specific parsing rules */ + /* Allow "[[ ]] >file" etc (> is a redirect symbol again) */ + command->cmd_type = CMD_SINGLEWORD_NOGLOB; + } else +# endif + /* Is it a place where keyword can appear? */ +//FIXME: this is wrong, it allows invalid syntax: { echo t; } if true; then echo YES; fi + if ((!command->argv /* if it's the first word of command... */ +# if ENABLE_HUSH_FUNCTIONS + || command->cmd_type == CMD_FUNCDEF /* ^^^ or after FUNC() {} */ # endif - if (!command->argv /* if it's the first word... */ + ) + && !ctx->word.has_quoted_part /* ""WORD never matches any keywords */ + && !command->redirects /* no redirects yet... disallows: ctx_res_w != RES_FOR /* ...not after FOR or IN */ - && ctx->ctx_res_w != RES_IN + && ctx->ctx_res_w != RES_FOR /* not after "for" or "in" */ + && ctx->ctx_res_w != RES_IN # endif # if ENABLE_HUSH_CASE - && ctx->ctx_res_w != RES_CASE + && ctx->ctx_res_w != RES_CASE /* not after "case" */ # endif - ) { - const struct reserved_combo *reserved; - reserved = reserved_word(ctx); - debug_printf_parse("checking for reserved-ness: %d\n", !!reserved); - if (reserved) { + ) { + const struct reserved_combo *reserved; + reserved = reserved_word(ctx); + debug_printf_parse("checking for reserved-ness: %d\n", !!reserved); + if (reserved) { # if ENABLE_HUSH_LINENO_VAR /* Case: - * "while ...; do - * cmd ..." + * while ...; do + * CMD * If we don't close the pipe _now_, immediately after "do", lineno logic - * sees "cmd" as starting at "do" - i.e., at the previous line. + * sees CMD as starting at "do" - i.e., at the previous line. */ - if (0 - IF_HUSH_IF(|| reserved->res == RES_THEN) - IF_HUSH_IF(|| reserved->res == RES_ELIF) - IF_HUSH_IF(|| reserved->res == RES_ELSE) - IF_HUSH_LOOPS(|| reserved->res == RES_DO) - ) { - done_pipe(ctx, PIPE_SEQ); - } -# endif - o_reset_to_empty_unquoted(&ctx->word); - debug_printf_parse("done_word return %d\n", - (ctx->ctx_res_w == RES_SNTX)); - return (ctx->ctx_res_w == RES_SNTX); + if (0 + IF_HUSH_IF(|| reserved->res == RES_THEN) + IF_HUSH_IF(|| reserved->res == RES_ELIF) + IF_HUSH_IF(|| reserved->res == RES_ELSE) + IF_HUSH_LOOPS(|| reserved->res == RES_DO) + ) { + done_pipe(ctx, PIPE_SEQ); } +# endif + o_reset_to_empty_unquoted(&ctx->word); + debug_printf_parse("done_word return %d\n", + (ctx->ctx_res_w == RES_SNTX)); + return (ctx->ctx_res_w == RES_SNTX); + } # if defined(CMD_TEST2_SINGLEWORD_NOGLOB) - if (strcmp(ctx->word.data, "[[") == 0) { - command->cmd_type = CMD_TEST2_SINGLEWORD_NOGLOB; - } else + if (strcmp(ctx->word.data, "[[") == 0) { + /* Inside [[ ]], parsing rules are different */ + command->cmd_type = CMD_TEST2_SINGLEWORD_NOGLOB; + } else # endif # if defined(CMD_SINGLEWORD_NOGLOB) - if (0 - /* In bash, local/export/readonly are special, args - * are assignments and therefore expansion of them - * should be "one-word" expansion: - * $ export i=`echo 'a b'` # one arg: "i=a b" - * compare with: - * $ ls i=`echo 'a b'` # two args: "i=a" and "b" - * ls: cannot access i=a: No such file or directory - * ls: cannot access b: No such file or directory - * Note: bash 3.2.33(1) does this only if export word - * itself is not quoted: - * $ export i=`echo 'aaa bbb'`; echo "$i" - * aaa bbb - * $ "export" i=`echo 'aaa bbb'`; echo "$i" - * aaa - */ - IF_HUSH_LOCAL( || strcmp(ctx->word.data, "local") == 0) - IF_HUSH_EXPORT( || strcmp(ctx->word.data, "export") == 0) - IF_HUSH_READONLY(|| strcmp(ctx->word.data, "readonly") == 0) - ) { - command->cmd_type = CMD_SINGLEWORD_NOGLOB; - } + if (0 + /* In bash, local/export/readonly are special, args + * are assignments and therefore expansion of them + * should be "one-word" expansion: + * $ export i=`echo 'a b'` # one arg: "i=a b" + * compare with: + * $ ls i=`echo 'a b'` # two args: "i=a" and "b" + * ls: cannot access i=a: No such file or directory + * ls: cannot access b: No such file or directory + * Note: bash 3.2.33(1) does this only if export word + * itself is not quoted: + * $ export i=`echo 'aaa bbb'`; echo "$i" + * aaa bbb + * $ "export" i=`echo 'aaa bbb'`; echo "$i" + * aaa + */ + IF_HUSH_LOCAL( || strcmp(ctx->word.data, "local") == 0) + IF_HUSH_EXPORT( || strcmp(ctx->word.data, "export") == 0) + IF_HUSH_READONLY(|| strcmp(ctx->word.data, "readonly") == 0) + ) { + command->cmd_type = CMD_SINGLEWORD_NOGLOB; + } # else - { /* empty block to pair "if ... else" */ } + { /* empty block to pair "if ... else" */ } # endif - } + } #endif /* HAS_KEYWORDS */ - if (command->group) { - /* "{ echo foo; } echo bar" - bad */ - syntax_error_at(ctx->word.data); - debug_printf_parse("done_word return 1: syntax error, " - "groups and arglists don't mix\n"); - return 1; - } + if (command->group) { + /* "{ echo foo; } echo bar" - bad */ + syntax_error_at(ctx->word.data); + debug_printf_parse("done_word return 1: syntax error, " + "groups and arglists don't mix\n"); + return 1; + } - /* If this word wasn't an assignment, next ones definitely - * can't be assignments. Even if they look like ones. */ - if (ctx->is_assignment != DEFINITELY_ASSIGNMENT - && ctx->is_assignment != WORD_IS_KEYWORD - ) { - ctx->is_assignment = NOT_ASSIGNMENT; - } else { - if (ctx->is_assignment == DEFINITELY_ASSIGNMENT) { - command->assignment_cnt++; - debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt); - } - debug_printf_parse("ctx->is_assignment was:'%s'\n", assignment_flag[ctx->is_assignment]); - ctx->is_assignment = MAYBE_ASSIGNMENT; - } - debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]); - command->argv = add_string_to_strings(command->argv, xstrdup(ctx->word.data)); - debug_print_strings("word appended to argv", command->argv); + /* If this word wasn't an assignment, next ones definitely + * can't be assignments. Even if they look like ones. */ + if (ctx->is_assignment != DEFINITELY_ASSIGNMENT) { + ctx->is_assignment = NOT_ASSIGNMENT; + } else { + /* This was an assignment word, next one maybe will be too */ + command->assignment_cnt++; + debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt); + ctx->is_assignment = MAYBE_ASSIGNMENT; } + debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]); + + command->argv = add_string_to_strings(command->argv, xstrdup(ctx->word.data)); + debug_print_strings("word appended to argv", command->argv); #if ENABLE_HUSH_LOOPS if (ctx->ctx_res_w == RES_FOR) { if (ctx->word.has_quoted_part - || endofname(command->argv[0])[0] != '\0' + || endofname(ctx->word.data)[0] != '\0' ) { /* bash says just "not a valid identifier" */ syntax_error("bad for loop variable"); @@ -4328,18 +4589,20 @@ static int done_word(struct parse_context *ctx) #endif #if ENABLE_HUSH_CASE /* Force CASE to have just one word */ - if (ctx->ctx_res_w == RES_CASE) { + if (ctx->ctx_res_w == RES_CASE) done_pipe(ctx, PIPE_SEQ); - } +//TODO syntax check? +// if (ctx->ctx_res_w == RES_MATCH) +// eat all following spaces and tabs (but not newlines), +// check that next char is | or ) #endif + ret: o_reset_to_empty_unquoted(&ctx->word); - debug_printf_parse("done_word return 0\n"); return 0; } - /* Peek ahead in the input to find out if we have a "&n" construct, * as in "2>&1", that represents duplicating a file descriptor. * Return: @@ -4370,7 +4633,7 @@ static int parse_redir_right_fd(o_string *as_string, struct in_str *input) } d = 0; ok = 0; - while (ch != EOF && isdigit(ch)) { + while (/*ch != EOF &&*/ isdigit(ch)) { d = d*10 + (ch-'0'); ok = 1; ch = i_getch(input); @@ -4398,31 +4661,31 @@ static int parse_redirect(struct parse_context *ctx, int dup_num; dup_num = REDIRFD_TO_FILE; - if (style != REDIRECT_HEREDOC) { + if (style != REDIRECT_HEREDOC && style != REDIRECT_HERESTRING) { /* Check for a '>&1' type redirect */ dup_num = parse_redir_right_fd(&ctx->as_string, input); if (dup_num == REDIRFD_SYNTAX_ERR) return 1; - } else { - int ch = i_peek_and_eat_bkslash_nl(input); - dup_num = (ch == '-'); /* HEREDOC_SKIPTABS bit is 1 */ - if (dup_num) { /* <<-... */ - ch = i_getch(input); - nommu_addchr(&ctx->as_string, ch); - ch = i_peek(input); + if (style == REDIRECT_OVERWRITE && dup_num == REDIRFD_TO_FILE) { + int ch = i_peek_and_eat_bkslash_nl(input); + if (ch == '|') { + /* >|FILE redirect ("clobbering" >). + * Since we do not support "set -o noclobber" yet, + * >| and > are the same for now. Just eat |. + */ + ch = i_getch(input); + nommu_addchr(&ctx->as_string, ch); + } } - } - - if (style == REDIRECT_OVERWRITE && dup_num == REDIRFD_TO_FILE) { + } else if (style == REDIRECT_HEREDOC) { int ch = i_peek_and_eat_bkslash_nl(input); - if (ch == '|') { - /* >|FILE redirect ("clobbering" >). - * Since we do not support "set -o noclobber" yet, - * >| and > are the same for now. Just eat |. - */ + dup_num = (ch == '-'); /* HEREDOC_SKIPTABS bit is 1 */ + if (dup_num) { /* "<<-HEREDOC"? */ ch = i_getch(input); nommu_addchr(&ctx->as_string, ch); } + } else { /* REDIRECT_HERESTRING */ + dup_num = 0; /* make sure no bits like HEREDOC_QUOTED are set */ } /* Create a new redir_struct and append it to the linked list */ @@ -4436,11 +4699,14 @@ static int parse_redirect(struct parse_context *ctx, redir->rd_type = style; redir->rd_fd = (fd == -1) ? redir_table[style].default_fd : fd; - debug_printf_parse("redirect type %d %s\n", redir->rd_fd, - redir_table[style].descrip); + debug_printf_parse("redirect type %d %.3s\n", redir->rd_fd, + redir_table[style].descrip3); redir->rd_dup = dup_num; - if (style != REDIRECT_HEREDOC && dup_num != REDIRFD_TO_FILE) { + if (style != REDIRECT_HEREDOC + && style != REDIRECT_HERESTRING + && dup_num != REDIRFD_TO_FILE + ) { /* Erik had a check here that the file descriptor in question * is legit; I postpone that to "run time" * A "-" representation of "close me" shows up as a -3 here */ @@ -4449,7 +4715,7 @@ static int parse_redirect(struct parse_context *ctx, } else { #if 0 /* Instead we emit error message at run time */ if (ctx->pending_redirect) { - /* For example, "cmd > foo # redirects fd 2 to file "foo", nothing passed to echo - * echo 49>foo # redirects fd 49 to file "foo", nothing passed to echo - * echo -2>foo # redirects fd 1 to file "foo", "-2" passed to echo - * echo 49x>foo # redirects fd 1 to file "foo", "49x" passed to echo + * echo 2>FILE # redirects fd 2 to FILE, nothing passed to echo + * echo 49>FILE # redirects fd 49 to FILE, nothing passed to echo + * echo -2>FILE # redirects fd 1 to FILE, "-2" passed to echo + * echo 49x>FILE # redirects fd 1 to FILE, "49x" passed to echo * * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html * "2.7 Redirection @@ -4504,7 +4770,7 @@ static char *fetch_till_str(o_string *as_string, { o_string heredoc = NULL_O_STRING; unsigned past_EOL; - int prev = 0; /* not \ */ + int prev = 0; /* not '\' */ int ch; /* Starting with "" is necessary for this case: @@ -4540,7 +4806,15 @@ static char *fetch_till_str(o_string *as_string, past_EOL = heredoc.length; /* Get 1st char of next line, possibly skipping leading tabs */ do { - ch = i_getch(input); + if (heredoc_flags & HEREDOC_QUOTED) + ch = i_getch(input); + else { /* see heredoc_bkslash_newline3a.tests: + * cat <<-EOF + * \ + * EOF + */ + ch = i_getch_and_eat_bkslash_nl(input); + } if (ch != EOF) nommu_addchr(as_string, ch); } while ((heredoc_flags & HEREDOC_SKIPTABS) && ch == '\t'); @@ -4566,7 +4840,7 @@ static char *fetch_till_str(o_string *as_string, prev = 0; /* not '\' */ continue; } - } + } /* if (\n or EOF) */ if (ch == EOF) { o_free(&heredoc); return NULL; /* error */ @@ -4609,6 +4883,11 @@ static int fetch_heredocs(o_string *as_string, struct pipe *pi, int heredoc_cnt, redir->rd_type = REDIRECT_HEREDOC2; /* redir->rd_dup is (ab)used to indicate <<- */ + if (!redir->rd_filename) { + /* examples: "echo <<", "echo <<<", "echo <<>" */ + syntax_error("missing here document terminator"); + return -1; + } p = fetch_till_str(as_string, input, redir->rd_filename, redir->rd_dup); if (!p) { @@ -4635,7 +4914,6 @@ static int fetch_heredocs(o_string *as_string, struct pipe *pi, int heredoc_cnt, return heredoc_cnt; } - static int run_list(struct pipe *pi); #if BB_MMU #define parse_stream(pstring, heredoc_cnt_ptr, input, end_trigger) \ @@ -4646,8 +4924,7 @@ static struct pipe *parse_stream(char **pstring, struct in_str *input, int end_trigger); -/* Returns number of heredocs not yet consumed, - * or -1 on error. +/* Returns number of heredocs not yet consumed, or -1 on error. */ static int parse_group(struct parse_context *ctx, struct in_str *input, int ch) @@ -4667,25 +4944,36 @@ static int parse_group(struct parse_context *ctx, debug_printf_parse("parse_group entered\n"); #if ENABLE_HUSH_FUNCTIONS - if (ch == '(' && !ctx->word.has_quoted_part) { + if ((ch == '(' +# if ENABLE_HUSH_FUNCTION_KEYWORD + || command->cmd_type == CMD_FUNCTION_KWORD /* "function WORD" */ +# endif + ) + && !ctx->word.has_quoted_part + ) { if (ctx->word.length) if (done_word(ctx)) return -1; if (!command->argv) goto skip; /* (... */ if (command->argv[1]) { /* word word ... (... */ - syntax_error_unexpected_ch('('); + if (ch == '(') + syntax_error_unexpected_ch('('); + else + syntax_error("expected funcdef"); return -1; } - /* it is "word(..." or "word (..." */ - do - ch = i_getch(input); - while (ch == ' ' || ch == '\t'); - if (ch != ')') { - syntax_error_unexpected_ch(ch); - return -1; + if (ch == '(') { + /* it is "word(..." or "word (..." */ + do + ch = i_getch(input); + while (ch == ' ' || ch == '\t'); + if (ch != ')') { + syntax_error_unexpected_ch(ch); + return -1; + } + nommu_addchr(&ctx->as_string, ch); } - nommu_addchr(&ctx->as_string, ch); do ch = i_getch(input); while (ch == ' ' || ch == '\t' || ch == '\n'); @@ -4706,13 +4994,10 @@ static int parse_group(struct parse_context *ctx, #if 0 /* Prevented by caller */ if (command->argv /* word [word]{... */ - || ctx->word.length /* word{... */ - || ctx->word.has_quoted_part /* ""{... */ + || !IS_NULL_WORD(ctx->word) /* word{... or ""{... */ ) { - syntax_error(NULL); debug_printf_parse("parse_group return -1: " "syntax error, groups and arglists don't mix\n"); - return -1; } #endif @@ -4990,16 +5275,16 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign # if BB_MMU #define parse_dollar_squote(as_string, dest, input) \ parse_dollar_squote(dest, input) -#define as_string NULL # endif static int parse_dollar_squote(o_string *as_string, o_string *dest, struct in_str *input) { int start; int ch = i_peek_and_eat_bkslash_nl(input); /* first character after the $ */ - debug_printf_parse("parse_dollar_squote entered: ch='%c'\n", ch); + if (ch != '\'') return 0; + debug_printf_parse("parse_dollar_squote entered: ch='%c'\n", ch); dest->has_quoted_part = 1; start = dest->length; @@ -5074,7 +5359,6 @@ static int parse_dollar_squote(o_string *as_string, o_string *dest, struct in_st } return 1; -# undef as_string } #else # define parse_dollar_squote(as_string, dest, input) 0 @@ -5352,7 +5636,6 @@ static int parse_dollar(o_string *as_string, #if BB_MMU #define encode_string(as_string, dest, input, dquote_end) \ encode_string(dest, input, dquote_end) -#define as_string NULL #endif static int encode_string(o_string *as_string, o_string *dest, @@ -5379,8 +5662,8 @@ static int encode_string(o_string *as_string, if (ch != '\n') { next = i_peek(input); } - debug_printf_parse("\" ch=%c (%d) escape=%d\n", - ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); + debug_printf_parse("\" ch:%c (%d) globprotect:%d\n", + ch, ch, !!(dest->o_expflags & EXP_FLAG_GLOBPROTECT_CHARS)); if (ch == '\\') { if (next == EOF) { /* Testcase: in interactive shell a file with @@ -5408,8 +5691,6 @@ static int encode_string(o_string *as_string, goto again; } if (ch == '$') { - //if (parse_dollar_squote(as_string, dest, input)) - // goto again; if (!parse_dollar(as_string, dest, input, /*quote_mask:*/ 0x80)) { debug_printf_parse("encode_string return 0: " "parse_dollar returned 0 (error)\n"); @@ -5436,9 +5717,113 @@ static int encode_string(o_string *as_string, o_addchr(dest, SPECIAL_VAR_SYMBOL); } goto again; -#undef as_string } +#if ENABLE_HUSH_ALIAS +static char* end_of_alias_name(const char *name) +{ + while (*name) { + if (!isalnum(*name) +// Uncommented chars are allowed in alias names. +// Commented out with // are disallowed in bash: space, "$&'();<=>\`| +// Commented out with //bb are allowed in bash, but disallowed in hush: !#*-/?[]{}~ +// (do you really want alias named '?' to be allowed?) +// && *name != ' ' // 20 +//bb && *name != '!' // 21 +// && *name != '"' // 22 +//bb && *name != '#' // 23 +// && *name != '$' // 24 + && *name != '%' // 25 +// && *name != '&' // 26 +// && *name != '\'' // 27 +// && *name != '(' // 28 +// && *name != ')' // 29 +//bb && *name != '*' // 2a + && *name != '+' // 2b + && *name != ',' // 2c +//bb && *name != '-' // 2d bash _can_ set it: "alias -- -=STR" (and it lists it as "alias -- -='STR'" in "alias" output!) + && *name != '.' // 2e seen Fedora defining alias "l." +//bb && *name != '/' // 2f + && *name != ':' // 3a +// && *name != ';' // 3b +// && *name != '<' // 3c +// && *name != '=' // 3d +// && *name != '>' // 3e +//bb && *name != '?' // 3f + && *name != '@' // 40 +//bb && *name != '[' // 5b +// && *name != '\\' // 5c +//bb && *name != ']' // 5d + && *name != '^' // 5e + && *name != '_' // 5f +// && *name != '`' // 60 +//bb && *name != '{' // 7b +// && *name != '|' // 7c +//bb && *name != '}' // 7d +//bb && *name != '~' // 7e + ) { + break; /* disallowed char, stop */ + } + name++; + } + return (char*)name; +} +#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) +#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) + +static struct alias **find_alias_slot(const char *name, const char *eq) +{ + unsigned len; + struct alias *alias; + struct alias **aliaspp; + + len = eq - name; + aliaspp = &G.top_alias; + while ((alias = *aliaspp) != NULL) { + //bb_error_msg("alias->str'%s' name'%.*s'", alias->str, len, name); + if (strncmp(name, alias->str, len) == 0 + && alias->str[len] == '=' + ) { + //bb_error_msg("match!"); + break; + } + aliaspp = &alias->next; + } + return aliaspp; +} + +static ALWAYS_INLINE const struct alias *find_alias(const char *name) +{ + //bb_error_msg("%s:%d: -> find_alias_slot", __func__, __LINE__); + return *find_alias_slot(name, strchr(name, '\0')); +} + +static const struct alias *word_matches_alias(struct parse_context *ctx) +{ + if (ctx->ctx_res_w != RES_CASE_BODY +/* && !ctx.command->argv - caller checked this */ + && !ctx->word.has_quoted_part + && ctx->word.data[0] != '\0' /* optimization */ + ) { + const char *word = ctx->word.data; + const char *end = end_of_alias_name(word); + if (*end == '\0') { + struct alias *alias; + + //bb_error_msg("%s:%d: -> find_alias_slot", __func__, __LINE__); + alias = *find_alias_slot(word, end); + if (alias && !alias->dont_recurse) { + alias->dont_recurse = 1; + o_reset_to_empty_unquoted(&ctx->word); + return alias; + } + } + } + return NULL; +} + +#endif /* ENABLE_HUSH_ALIAS */ + /* * Scan input until EOF or end_trigger char. * Return a list of pipes to execute, or NULL on EOF @@ -5452,6 +5837,8 @@ static struct pipe *parse_stream(char **pstring, struct in_str *input, int end_trigger) { + IF_HUSH_ALIAS(const struct alias *alias;) + struct pipe *pi; struct parse_context ctx; int heredoc_cnt; @@ -5462,13 +5849,9 @@ static struct pipe *parse_stream(char **pstring, end_trigger ? end_trigger : 'X'); debug_enter(); + enable_all_aliases(); initialize_context(&ctx); - /* If very first arg is "" or '', ctx.word.data may end up NULL. - * Preventing this: - */ - ctx.word.data = xzalloc(1); /* start as "", not as NULL */ - /* We used to separate words on $IFS here. This was wrong. * $IFS is used only for word splitting when $var is expanded, * here we should use blank chars as separators, not $IFS @@ -5476,74 +5859,29 @@ static struct pipe *parse_stream(char **pstring, heredoc_cnt = 0; while (1) { - const char *is_blank; - const char *is_special; int ch; int next; int redir_fd; redir_type redir_style; ch = i_getch(input); - debug_printf_parse(": ch=%c (%d) escape=%d\n", - ch, ch, !!(ctx.word.o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); - if (ch == EOF) { - struct pipe *pi; - - if (heredoc_cnt) { - syntax_error_unterm_str("here document"); - goto parse_error_exitcode1; - } - if (end_trigger == ')') { - syntax_error_unterm_ch('('); - goto parse_error_exitcode1; - } - if (end_trigger == '}') { - syntax_error_unterm_ch('{'); - goto parse_error_exitcode1; - } - - if (done_word(&ctx)) { - goto parse_error_exitcode1; - } - o_free_and_set_NULL(&ctx.word); - done_pipe(&ctx, PIPE_SEQ); - - /* Do we sit inside of any if's, loops or case's? */ - if (HAS_KEYWORDS - IF_HAS_KEYWORDS(&& (ctx.ctx_res_w != RES_NONE || ctx.old_flag != 0)) - ) { - syntax_error_unterm_str("compound statement"); - goto parse_error_exitcode1; - } - - pi = ctx.list_head; - /* If we got nothing... */ - /* (this makes bare "&" cmd a no-op. - * bash says: "syntax error near unexpected token '&'") */ - if (pi->num_cmds == 0 - IF_HAS_KEYWORDS(&& pi->res_word == RES_NONE) - ) { - free_pipe_list(pi); - pi = NULL; - } -#if !BB_MMU - debug_printf_parse("as_string1 '%s'\n", ctx.as_string.data); - if (pstring) - *pstring = ctx.as_string.data; - else - o_free(&ctx.as_string); -#endif - // heredoc_cnt must be 0 here anyway - //if (heredoc_cnt_ptr) - // *heredoc_cnt_ptr = heredoc_cnt; - debug_leave(); - debug_printf_heredoc("parse_stream return heredoc_cnt:%d\n", heredoc_cnt); - debug_printf_parse("parse_stream return %p: EOF\n", pi); - return pi; + debug_printf_parse(": ch:%c (%d) globprotect:%d\n", + ch, ch, !!(ctx.word.o_expflags & EXP_FLAG_GLOBPROTECT_CHARS)); +# if ENABLE_HUSH_NEED_FOR_SPEED + if ((ch >= '.' && ch <= ':') /* ASCII "./0123456789:" */ + /* can't include preceding "+,-" above: "-" needs glob-escaping (example?) */ + || (ch >= '@' && ch <= 'Z') /* ASCII "@A..Z" */ + || (ch >= 'a' && ch <= 'z') /* ASCII "a..Z" */ + /* can't include preceding "^_`" above because of "`". Pity. "_" is relatively common */ + ) { + /* These are never special and just go into the current word */ + /* ~5% faster parsing of typical shell scripts */ + INLINED_o_addchr(&ctx.word, ch); + continue; } - +#endif /* Handle "'" and "\" first, as they won't play nice with - * i_peek_and_eat_bkslash_nl() anyway: + * i_peek_and_eat_bkslash_nl(): * echo z\\ * and * echo '\ @@ -5564,9 +5902,9 @@ static struct pipe *parse_stream(char **pstring, if (ch == EOF) { /* Testcase: eval 'echo Ok\' */ /* bash-4.3.43 was removing backslash, - * but 4.4.19 retains it, most other shells too + * but 4.4.19 retains it, most other shells retain too */ - continue; /* get next char */ + break; } /* Example: echo Hello \2>file * we need to know that word 2 is quoted @@ -5576,14 +5914,25 @@ static struct pipe *parse_stream(char **pstring, o_addchr(&ctx.word, ch); continue; /* get next char */ } + if (ch == EOF) + break; nommu_addchr(&ctx.as_string, ch); if (ch == '\'') { ctx.word.has_quoted_part = 1; - next = i_getch(input); - if (next == '\'' && !ctx.pending_redirect) - goto insert_empty_quoted_str_marker; - - ch = next; + ch = i_getch(input); + if (ch == '\'' && !ctx.pending_redirect/*why?*/) { + insert_empty_quoted_str_marker: + nommu_addchr(&ctx.as_string, ch); +//Just inserting nothing doesn't work: consider +// CMD $EMPTYVAR +// CMD '' +//At execution time both will expand argv[1] to empty string +//and thus the argument will "vanish". +//But for second CMD, it should not vanish! + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + continue; /* get next char */ + } while (1) { if (ch == EOF) { syntax_error_unterm_ch('\''); @@ -5603,135 +5952,173 @@ static struct pipe *parse_stream(char **pstring, continue; /* get next char */ } - next = '\0'; - if (ch != '\n') - next = i_peek_and_eat_bkslash_nl(input); - - is_special = "{}<>&|();#" /* special outside of "str" */ - "$\"" IF_HUSH_TICK("`") /* always special */ - SPECIAL_VAR_SYMBOL_STR; -#if defined(CMD_TEST2_SINGLEWORD_NOGLOB) - if (ctx.command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB) { - /* In [[ ]], {}<>&|() are not special */ - is_special += 8; - } else -#endif - /* Are { and } special here? */ - if (ctx.command->argv /* word [word]{... - non-special */ - || ctx.word.length /* word{... - non-special */ - || ctx.word.has_quoted_part /* ""{... - non-special */ - || (next != ';' /* }; - special */ - && next != ')' /* }) - special */ - && next != '(' /* {( - special */ - && next != '&' /* }& and }&& ... - special */ - && next != '|' /* }|| ... - special */ - && !strchr(defifs, next) /* {word - non-special */ - ) - ) { - /* They are not special, skip "{}" */ - is_special += 2; - } - is_special = strchr(is_special, ch); - is_blank = strchr(defifs, ch); - - if (!is_special && !is_blank) { /* ordinary char */ - ordinary_char: - o_addQchr(&ctx.word, ch); - if ((ctx.is_assignment == MAYBE_ASSIGNMENT - || ctx.is_assignment == WORD_IS_KEYWORD) - && ch == '=' - && endofname(ctx.word.data)[0] == '=' - ) { - ctx.is_assignment = DEFINITELY_ASSIGNMENT; - debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); + if (ch == ' ' || ch == '\t') { +#if ENABLE_HUSH_ALIAS + /* Check for alias expansion (only for first word of command) */ + if (G_interactive_fd && !ctx.command->argv) { + alias = word_matches_alias(&ctx); + if (alias) { + add_to_albuf_and_get_next_char: + i_prepend_to_alias_buffer(input, strchr(alias->str, '=') + 1, ch); + continue; /* get next char (which will be from albuf) */ + } } - continue; - } +#endif - if (is_blank) { #if ENABLE_HUSH_LINENO_VAR -/* Case: - * "while ...; do - * cmd ..." - * would think that "cmd" starts in - +/* "while ...; do + * CMD" + * would think that CMD starts in - * i.e., at the previous line. - * We need to skip all whitespace before newlines. + * Need to skip whitespace up to next newline (and eat it) + * or not-whitespace (and do not eat it). */ - while (ch != '\n') { + for (;;) { next = i_peek(input); if (next != ' ' && next != '\t' && next != '\n') break; /* next char is not ws */ ch = i_getch(input); + if (ch == '\n') + goto ch_is_newline; } - /* ch == last eaten whitespace char */ #endif - if (done_word(&ctx)) { + if (done_word(&ctx)) goto parse_error_exitcode1; +#if ENABLE_HUSH_FUNCTION_KEYWORD + if (ctx.command->cmd_type == CMD_FUNCTION_KWORD + && ctx.command->argv /* "function WORD" */ + ) + goto parse_group; +#endif + continue; /* get next char */ + } + + if (ch == '\n') { + IF_HUSH_LINENO_VAR(ch_is_newline:) +#if ENABLE_HUSH_ALIAS + /* Check for alias expansion (only for first word of command) */ + if (G_interactive_fd && !ctx.command->argv) { + alias = word_matches_alias(&ctx); + if (alias) + goto add_to_albuf_and_get_next_char; } - if (ch == '\n') { - /* Is this a case when newline is simply ignored? - * Some examples: - * "cmd | cmd ..." - * "case ... in word) ..." +#endif + if (done_word(&ctx)) + goto parse_error_exitcode1; + /* Is this a case when newline is simply ignored? + * Some examples: + * "CMD | CMD ..." + * "case ... in PATTERN) ..." + */ + if (IS_NULL_CMD(ctx.command) + && heredoc_cnt == 0 + ) { + /* This newline can be ignored. But... + * Without check #1, interactive shell + * ignores even bare , + * and shows the continuation prompt: + * ps1$ + * ps2> _ <=== wrong, should be ps1 + * Without check #2, "CMD & " + * is similarly mistreated. + * (BTW, this makes "CMD & CMD" + * and "CMD && CMD" non-orthogonal. + * Really, ask yourself, why + * "CMD && " doesn't start + * CMD but waits for more input? + * The only reason is that it might be + * a "CMD1 && CMD2 &" construct: + * CMD1 may need to run in BG). */ - if (IS_NULL_CMD(ctx.command) - && ctx.word.length == 0 - && !ctx.word.has_quoted_part - && heredoc_cnt == 0 + pi = ctx.list_head; + if (pi->num_cmds != 0 /* check #1 */ + && pi->followup != PIPE_BG /* check #2 */ ) { - /* This newline can be ignored. But... - * Without check #1, interactive shell - * ignores even bare , - * and shows the continuation prompt: - * ps1_prompt$ - * ps2> _ <=== wrong, should be ps1 - * Without check #2, "cmd & " - * is similarly mistreated. - * (BTW, this makes "cmd & cmd" - * and "cmd && cmd" non-orthogonal. - * Really, ask yourself, why - * "cmd && " doesn't start - * cmd but waits for more input? - * The only reason is that it might be - * a "cmd1 && cmd2 &" construct, - * cmd1 may need to run in BG). - */ - struct pipe *pi = ctx.list_head; - if (pi->num_cmds != 0 /* check #1 */ - && pi->followup != PIPE_BG /* check #2 */ - ) { - continue; - } + debug_printf_parse("newline is treated as ws\n"); + continue; /* ignore newline */ } - /* Treat newline as a command separator. */ - done_pipe(&ctx, PIPE_SEQ); - debug_printf_heredoc("heredoc_cnt:%d\n", heredoc_cnt); - if (heredoc_cnt) { - heredoc_cnt = fetch_heredocs(&ctx.as_string, ctx.list_head, heredoc_cnt, input); - if (heredoc_cnt != 0) - goto parse_error_exitcode1; + } +#if ENABLE_HUSH_FUNCTION_KEYWORD + if (ctx.command->cmd_type == CMD_FUNCTION_KWORD) { + if (!ctx.command->argv) { + /* Testcase: sh -c $'function\n' */ + syntax_error("expected funcdef"); + goto parse_error_exitcode1; } - ctx.is_assignment = MAYBE_ASSIGNMENT; - debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); - ch = ';'; - /* note: if (is_blank) continue; - * will still trigger for us */ + /* "function WORD" */ + goto parse_group; + } +#endif + /* Treat newline as a command separator */ + done_pipe(&ctx, PIPE_SEQ); + debug_printf_heredoc("heredoc_cnt:%d\n", heredoc_cnt); + if (heredoc_cnt) { + heredoc_cnt = fetch_heredocs(&ctx.as_string, ctx.list_head, heredoc_cnt, input); + if (heredoc_cnt != 0) + goto parse_error_exitcode1; + } + ctx.is_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("newline is treated as ';', ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); + next = '\0'; + ch = ';'; + } else { + const char *is_special; + + next = i_peek_and_eat_bkslash_nl(input); + is_special = "{}<>&|();#" /* special outside of "str" */ + "$\"" IF_HUSH_TICK("`") /* always special */ + SPECIAL_VAR_SYMBOL_STR; +#if defined(CMD_TEST2_SINGLEWORD_NOGLOB) + if (ctx.command->cmd_type == CMD_TEST2_SINGLEWORD_NOGLOB) { + /* In [[ ]], {}<>&|() are not special */ + is_special += 8; + } else +#endif + /* Are { and } special here? */ + if ((ctx.command->argv /* WORD [WORD]{... - non-special */ +#if ENABLE_HUSH_FUNCTIONS + && ctx.command->cmd_type != CMD_FUNCDEF /* ^^^ unless FUNC() {} */ +#endif + ) + || !IS_NULL_WORD(ctx.word) /* WORD{... ""{... - non-special */ + || (next != ';' /* }; - special */ + && next != ')' /* }) - special */ + && next != '(' /* {( - special */ + && next != '&' /* }& and }&& ... - special */ + && next != '|' /* }|| ... - special */ + && !strchr(defifs, next) /* {WORD - non-special */ + ) + ) { + /* They are not special, skip "{}" */ + is_special += 2; + } + if (!strchr(is_special, ch)) { /* ordinary char? */ + ordinary_char: + o_addQchr(&ctx.word, ch); + if (ctx.is_assignment == MAYBE_ASSIGNMENT + && ch == '=' + && !ctx.word.has_quoted_part /* a''=b a'b'c=d: not assignments */ + && endofname(ctx.word.data)[0] == '=' + ) { + ctx.is_assignment = DEFINITELY_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); + } + continue; /* get next char */ } } - /* "cmd}" or "cmd }..." without semicolon or &: - * } is an ordinary char in this case, even inside { cmd; } - * Pathological example: { ""}; } should exec "}" cmd + /* "CMD}" or "CMD }..." without semicolon or &: + * } is an ordinary char in this case, even inside { CMD; } + * Pathological example: { ""}; } should run "}" command. */ if (ch == '}') { - if (ctx.word.length != 0 /* word} */ - || ctx.word.has_quoted_part /* ""} */ - ) { + if (!IS_NULL_WORD(ctx.word)) { + /* WORD} or ""} */ goto ordinary_char; } - if (!IS_NULL_CMD(ctx.command)) { /* cmd } */ - /* Generally, there should be semicolon: "cmd; }" - * However, bash allows to omit it if "cmd" is + if (!IS_NULL_CMD(ctx.command)) { /* CMD } */ + /* Generally, there should be semicolon: "CMD; }" + * However, bash allows to omit it if "CMD" is * a group. Examples: * { { echo 1; } } * {(echo 1)} @@ -5743,14 +6130,17 @@ static struct pipe *parse_stream(char **pstring, goto term_group; goto ordinary_char; } - if (!IS_NULL_PIPE(ctx.pipe)) /* cmd | } */ - /* Can't be an end of {cmd}, skip the check */ - goto skip_end_trigger; + if (!IS_NULL_PIPE(ctx.pipe)) /* CMD | } */ + /* Can't be an end of {CMD}, skip the check */ + goto rbrace_skips_end_trigger; /* else: } does terminate a group */ } term_group: if (end_trigger && end_trigger == ch - && (ch != ';' || heredoc_cnt == 0) + && (ch != ';' + /* it's ";". Can exit parse_stream() only if have no heredocs to consume, and alias buffer is empty */ + || (heredoc_cnt == 0 && !i_has_alias_buffer(input)) + ) #if ENABLE_HUSH_CASE && (ch != ')' || ctx.ctx_res_w != RES_MATCH @@ -5758,13 +6148,32 @@ static struct pipe *parse_stream(char **pstring, ) #endif ) { - if (done_word(&ctx)) { +#if ENABLE_HUSH_ALIAS + /* Check for alias expansion (only for first word of command) */ + if (G_interactive_fd && !ctx.command->argv) { + alias = word_matches_alias(&ctx); + if (alias) + goto add_to_albuf_and_get_next_char; + } +#endif + if (done_word(&ctx)) + goto parse_error_exitcode1; +#if ENABLE_HUSH_FUNCTION_KEYWORD + if (ctx.command->cmd_type == CMD_FUNCTION_KWORD) { + /* Testcase: sh -c '(function)' */ + syntax_error("expected funcdef"); goto parse_error_exitcode1; } - done_pipe(&ctx, PIPE_SEQ); +#endif + if (done_pipe(&ctx, PIPE_SEQ)) { + /* Testcase: sh -c 'date|;not_reached' */ + syntax_error_unterm_ch('|'); + goto parse_error_exitcode1; + }; ctx.is_assignment = MAYBE_ASSIGNMENT; debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); /* Do we sit outside of any if's, loops or case's? */ +//TODO? just check ctx.stack != NULL instead? if (!HAS_KEYWORDS IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0)) ) { @@ -5789,21 +6198,18 @@ static struct pipe *parse_stream(char **pstring, "end_trigger char found\n", ctx.list_head); debug_leave(); + i_free_alias_buffer(input); return ctx.list_head; } } - if (is_blank) - continue; - /* Catch <, > before deciding whether this word is * an assignment. a=1 2>z b=2: b=2 is still assignment */ switch (ch) { case '>': redir_fd = redirect_opt_num(&ctx.word); - if (done_word(&ctx)) { + if (done_word(&ctx)) goto parse_error_exitcode1; - } redir_style = REDIRECT_OVERWRITE; if (next == '>') { redir_style = REDIRECT_APPEND; @@ -5821,9 +6227,8 @@ static struct pipe *parse_stream(char **pstring, continue; /* get next char */ case '<': redir_fd = redirect_opt_num(&ctx.word); - if (done_word(&ctx)) { + if (done_word(&ctx)) goto parse_error_exitcode1; - } redir_style = REDIRECT_INPUT; if (next == '<') { redir_style = REDIRECT_HEREDOC; @@ -5831,6 +6236,14 @@ static struct pipe *parse_stream(char **pstring, debug_printf_heredoc("++heredoc_cnt=%d\n", heredoc_cnt); ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); + /* Check for here-string (<<<) */ + next = i_peek(input); + if (next == '<') { + redir_style = REDIRECT_HERESTRING; + ch = i_getch(input); + nommu_addchr(&ctx.as_string, ch); + heredoc_cnt--; /* here-strings don't use heredoc lines */ + } } else if (next == '>') { redir_style = REDIRECT_IO; ch = i_getch(input); @@ -5846,13 +6259,13 @@ static struct pipe *parse_stream(char **pstring, goto parse_error_exitcode1; continue; /* get next char */ case '#': - if (ctx.word.length == 0 && !ctx.word.has_quoted_part) { - /* skip "#comment" */ + if (IS_NULL_WORD(ctx.word)) { + /* skip "#COMMENT" */ /* note: we do not add it to &ctx.as_string */ /* TODO: in bash: * comment inside $() goes to the next \n, even inside quoted string (!): - * cmd "$(cmd2 #comment)" - syntax error - * cmd "`cmd2 #comment`" - ok + * CMD "$(CMD2 #COMMENT)" - syntax error + * CMD "`CMD2 #COMMENT`" - ok * We accept both (comment ends where command subst ends, in both cases). */ while (1) { @@ -5863,16 +6276,16 @@ static struct pipe *parse_stream(char **pstring, } ch = i_getch(input); if (ch == EOF) - break; + goto eof; } continue; /* get next char */ } break; } - skip_end_trigger: + rbrace_skips_end_trigger: if (ctx.is_assignment == MAYBE_ASSIGNMENT - /* check that we are not in word in "a=1 2>word b=1": */ + /* check that we are not in WORD in "a=1 2>WORD b=1": */ && !ctx.pending_redirect ) { /* ch is a special char and thus this word @@ -5895,8 +6308,8 @@ static struct pipe *parse_stream(char **pstring, o_addchr(&ctx.word, ch); continue; /* get next char */ case '$': - if (parse_dollar_squote(&ctx.as_string, &ctx.word, input)) - continue; /* get next char */ + if (next == '\'' && parse_dollar_squote(&ctx.as_string, &ctx.word, input)) + continue; /* ate $'...', get next char */ if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { debug_printf_parse("parse_stream parse error: " "parse_dollar returned 0 (error)\n"); @@ -5906,18 +6319,14 @@ static struct pipe *parse_stream(char **pstring, case '"': ctx.word.has_quoted_part = 1; if (next == '"' && !ctx.pending_redirect) { - i_getch(input); /* eat second " */ - insert_empty_quoted_str_marker: - nommu_addchr(&ctx.as_string, next); - o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); - o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); - continue; /* get next char */ + ch = i_getch(input); /* eat second " */ + goto insert_empty_quoted_str_marker; } if (ctx.is_assignment == NOT_ASSIGNMENT) - ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; + ctx.word.o_expflags |= EXP_FLAG_GLOBPROTECT_CHARS; if (!encode_string(&ctx.as_string, &ctx.word, input, '"')) goto parse_error_exitcode1; - ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; + ctx.word.o_expflags &= ~EXP_FLAG_GLOBPROTECT_CHARS; continue; /* get next char */ #if ENABLE_HUSH_TICK case '`': { @@ -5938,112 +6347,240 @@ static struct pipe *parse_stream(char **pstring, } #endif case ';': -#if ENABLE_HUSH_CASE - case_semi: +#if ENABLE_HUSH_ALIAS + /* Check for alias expansion (only for first word of command) */ + if (G_interactive_fd && !ctx.command->argv) { + alias = word_matches_alias(&ctx); + if (alias) + goto add_to_albuf_and_get_next_char; + } #endif - if (done_word(&ctx)) { + if (done_word(&ctx)) + goto parse_error_exitcode1; +#if ENABLE_HUSH_FUNCTION_KEYWORD + if (ctx.command->cmd_type == CMD_FUNCTION_KWORD) { + /* Testcase: sh -c '{ function; }'; sh -c '{ function f; }' */ + syntax_error("expected funcdef"); goto parse_error_exitcode1; } +#endif done_pipe(&ctx, PIPE_SEQ); #if ENABLE_HUSH_CASE - /* Eat multiple semicolons, detect - * whether it means something special */ - while (1) { - ch = i_peek_and_eat_bkslash_nl(input); - if (ch != ';') - break; + if (ctx.ctx_res_w == RES_CASE_BODY + && next == ';' /* and next char is ';' too? */ + ) { ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); - if (ctx.ctx_res_w == RES_CASE_BODY) { - ctx.ctx_dsemicolon = 1; - ctx.ctx_res_w = RES_MATCH; - break; - } + ctx.ctx_res_w = RES_MATCH; /* "we are in PATTERN)" */ } #endif - new_cmd: + finished_cmd: /* We just finished a cmd. New one may start * with an assignment */ ctx.is_assignment = MAYBE_ASSIGNMENT; debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); continue; /* get next char */ case '&': - if (done_word(&ctx)) { +#if ENABLE_HUSH_ALIAS + /* Check for alias expansion (only for first word of command) */ + if (G_interactive_fd && !ctx.command->argv) { + alias = word_matches_alias(&ctx); + if (alias) + goto add_to_albuf_and_get_next_char; + } +#endif + if (done_word(&ctx)) + goto parse_error_exitcode1; + if (ctx.pipe->num_cmds == 0 && IS_NULL_CMD(ctx.command)) { + /* Testcase: sh -c '&& date' */ + /* Testcase: sh -c '&' */ + syntax_error_unexpected_str("&&" + (next != '&')); goto parse_error_exitcode1; } if (next == '&') { ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); - done_pipe(&ctx, PIPE_AND); + if (done_pipe(&ctx, PIPE_AND)) { + /* Testcase: sh -c 'date | && date' */ + syntax_error_unterm_ch('|'); + goto parse_error_exitcode1; + } } else { - done_pipe(&ctx, PIPE_BG); + if (done_pipe(&ctx, PIPE_BG)) { + /* Testcase: sh -c 'date | &' */ + syntax_error_unterm_ch('|'); + goto parse_error_exitcode1; + } } - goto new_cmd; + goto finished_cmd; case '|': - if (done_word(&ctx)) { - goto parse_error_exitcode1; - } #if ENABLE_HUSH_CASE - if (ctx.ctx_res_w == RES_MATCH) - break; /* we are in case's "word | word)" */ + if (ctx.ctx_res_w == RES_MATCH) { +// if (IS_NULL_WORD(ctx.word)) { +// /* Testcase: sh -c 'case w in |w) nice; esac' */ +// /* Testcase: sh -c 'case w in v||w) nice; esac' */ +//also rejects valid: sh -c 'case w in v |w) nice; esac' +// syntax_error_unexpected_ch(ch); +// goto parse_error_exitcode1; +// } + if (done_word(&ctx)) + goto parse_error_exitcode1; + continue; /* get next char */ + } +#endif +#if ENABLE_HUSH_ALIAS + /* Check for alias expansion (only for first word of command) */ + if (G_interactive_fd && !ctx.command->argv) { + alias = word_matches_alias(&ctx); + if (alias) + goto add_to_albuf_and_get_next_char; + } #endif + if (done_word(&ctx)) + goto parse_error_exitcode1; if (next == '|') { /* || */ ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); - done_pipe(&ctx, PIPE_OR); + if (ctx.pipe->num_cmds == 0 && IS_NULL_CMD(ctx.command)) { + /* Testcase: sh -c '|| date' */ + syntax_error_unexpected_str("||"); + goto parse_error_exitcode1; + } + if (done_pipe(&ctx, PIPE_OR)) { + /* Testcase: sh -c 'date | || date' */ + syntax_error_unterm_ch('|'); + goto parse_error_exitcode1; + } } else { - /* we could pick up a file descriptor choice here - * with redirect_opt_num(), but bash doesn't do it. - * "echo foo 2| cat" yields "foo 2". */ + if (IS_NULL_CMD(ctx.command)) { + /* Testcase: sh -c '| cat' */ + /* Testcase: sh -c 'date | | cat' */ + syntax_error_unexpected_ch('|'); + goto parse_error_exitcode1; + } done_command(&ctx); } - goto new_cmd; + goto finished_cmd; case '(': #if ENABLE_HUSH_CASE - /* "case... in [(]word)..." - skip '(' */ + /* "case... in (PATTERNS)..."? */ if (ctx.ctx_res_w == RES_MATCH - && ctx.command->argv == NULL /* not (word|(... */ - && ctx.word.length == 0 /* not word(... */ - && ctx.word.has_quoted_part == 0 /* not ""(... */ + && ctx.command->argv == NULL /* not WORD (... */ + && IS_NULL_WORD(ctx.word) /* not WORD(... or ""(... */ ) { - continue; /* get next char */ + continue; /* skip '(', get next char */ } #endif /* fall through */ case '{': { - int n = parse_group(&ctx, input, ch); - if (n < 0) { + int n; + /* "function WORD" -> */ + IF_HUSH_FUNCTION_KEYWORD(parse_group:) + /* Try to parse as { CMDS; } or (CMDS) */ + n = parse_group(&ctx, input, ch); + if (n < 0) goto parse_error_exitcode1; - } debug_printf_heredoc("parse_group done, needs heredocs:%d\n", n); heredoc_cnt += n; - goto new_cmd; + goto finished_cmd; } case ')': #if ENABLE_HUSH_CASE - if (ctx.ctx_res_w == RES_MATCH) - goto case_semi; + if (ctx.ctx_res_w == RES_MATCH) { +// if (IS_NULL_WORD(ctx.word)) { +// /* Testcase: sh -c 'case w in w|) nice; esac' */ +//also rejects valid: sh -c 'case w in w ) nice; esac' +// syntax_error_unexpected_ch(ch); +// goto parse_error_exitcode1; +// } + if (done_word(&ctx)) + goto parse_error_exitcode1; +//FIXME: accepts "W1 W2) CMD;;" as if it was "W1|W2) CMD;;", should not allow this syntax at all + done_pipe(&ctx, PIPE_SEQ); + goto finished_cmd; + } #endif - case '}': - /* proper use of this character is caught by end_trigger: + /* case ')' and !RES_MATCH: falls through to the error: */ + /* Testcase for bad ')' ? */ + /* case '}': */ + /* Proper use of this character is caught by end_trigger: * if we see {, we call parse_group(..., end_trigger='}') - * and it will match } earlier (not here). */ + * and it will match } earlier (not here). + */ + /* Testcase for bad '}' ? */ + default: /* all other chars should not ever reach this... */ G.last_exitcode = 2; syntax_error_unexpected_ch(ch); goto parse_error; - default: - if (HUSH_DEBUG) - bb_error_msg_and_die("BUG: unexpected %c", ch); } } /* while (1) */ + eof: + /* Reached EOF */ + if (heredoc_cnt) { + syntax_error_unterm_str("here document"); + goto parse_error_exitcode1; + } + if (end_trigger == ')') { + syntax_error_unterm_ch('('); + goto parse_error_exitcode1; + } + if (end_trigger == '}') { + syntax_error_unterm_ch('{'); + goto parse_error_exitcode1; + } + + if (done_word(&ctx)) + goto parse_error_exitcode1; +#if ENABLE_HUSH_FUNCTION_KEYWORD + if (ctx.command->cmd_type == CMD_FUNCTION_KWORD) { + /* Testcase: sh -c 'function'; sh -c 'function f' */ + syntax_error("expected funcdef"); + goto parse_error_exitcode1; + } +#endif + o_free_and_set_NULL(&ctx.word); + if (done_pipe(&ctx, PIPE_SEQ)) { + /* Testcase: sh -c 'date |' */ + syntax_error_unterm_ch('|'); + goto parse_error_exitcode1; + } +// TODO: catch 'date &&' and 'date ||' too + +#if HAS_KEYWORDS + /* Do we sit inside of any if's, loops or case's? */ + if (ctx.ctx_res_w != RES_NONE || ctx.old_flag != 0) { + syntax_error_unterm_str("compound statement"); + goto parse_error_exitcode1; + } +#endif + pi = ctx.list_head; + /* If we got nothing... */ + if (pi->num_cmds == 0 + IF_HAS_KEYWORDS(&& pi->res_word == RES_NONE) + ) { + free_pipe_list(pi); + pi = NULL; + } +#if !BB_MMU + debug_printf_parse("as_string1 '%s'\n", ctx.as_string.data); + if (pstring) + *pstring = ctx.as_string.data; + else + o_free(&ctx.as_string); +#endif + // heredoc_cnt must be 0 here anyway + //if (heredoc_cnt_ptr) + // *heredoc_cnt_ptr = heredoc_cnt; + debug_leave(); + debug_printf_heredoc("parse_stream return heredoc_cnt:%d\n", heredoc_cnt); + debug_printf_parse("parse_stream return %p: EOF\n", pi); + i_free_alias_buffer(input); + return pi; parse_error_exitcode1: G.last_exitcode = 1; parse_error: { - struct parse_context *pctx; - IF_HAS_KEYWORDS(struct parse_context *p2;) - /* Clean up allocated tree. * Sample for finding leaks on syntax error recovery path. * Run it from interactive shell, watch pmap `pidof hush`. @@ -6052,44 +6589,39 @@ static struct pipe *parse_stream(char **pstring, * while if (true | { true;}); then echo ok; fi; do break; done * while if (true | { true;}); then echo ok; fi; do (if echo ok; break; then :; fi) | cat; break; done */ - pctx = &ctx; + IF_HAS_KEYWORDS(struct parse_context *stk;) + struct parse_context *pctx = &ctx; do { /* Update pipe/command counts, * otherwise freeing may miss some */ done_pipe(pctx, PIPE_SEQ); - debug_printf_clean("freeing list %p from ctx %p\n", - pctx->list_head, pctx); + debug_printf_clean("freeing list %p from ctx %p\n", pctx->list_head, pctx); debug_print_tree(pctx->list_head, 0); free_pipe_list(pctx->list_head); debug_printf_clean("freed list %p\n", pctx->list_head); #if !BB_MMU o_free(&pctx->as_string); #endif - IF_HAS_KEYWORDS(p2 = pctx->stack;) - if (pctx != &ctx) { + IF_HAS_KEYWORDS(stk = pctx->stack;) + if (pctx != &ctx) free(pctx); - } - IF_HAS_KEYWORDS(pctx = p2;) + IF_HAS_KEYWORDS(pctx = stk;) } while (HAS_KEYWORDS && pctx); - - o_free(&ctx.word); + } + o_free(&ctx.word); #if !BB_MMU - if (pstring) - *pstring = NULL; + if (pstring) + *pstring = NULL; #endif - debug_leave(); - return ERR_PTR; - } + debug_leave(); + i_free_alias_buffer(input); + return ERR_PTR; } - -/*** Execution routines ***/ - +/* + * Execution routines + */ /* Expansion can recurse, need forward decls: */ -#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE -#define expand_string_to_string(str, EXP_flags, do_unbackslash) \ - expand_string_to_string(str) -#endif static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash); #if ENABLE_HUSH_TICK static int process_command_subs(o_string *dest, const char *s); @@ -6154,7 +6686,7 @@ static int expand_on_ifs(o_string *output, int n, const char *str) word_len = strcspn(str, G.ifs); if (word_len) { /* We have WORD_LEN leading non-IFS chars */ - if (!(output->o_expflags & EXP_FLAG_GLOB)) { + if (!(output->o_expflags & EXP_FLAG_DO_GLOBBING)) { o_addblock(output, str, word_len); } else { /* Protect backslashes against globbing up :) @@ -6247,7 +6779,7 @@ static char *encode_then_expand_string(const char *str) //TODO: error check (encode_string returns 0 on error)? //bb_error_msg("'%s' -> '%s'", str, dest.data); exp_str = expand_string_to_string(dest.data, - EXP_FLAG_ESC_GLOB_CHARS, + EXP_FLAG_GLOBPROTECT_CHARS, /* example: `echo '_\t_\\_\"_'` in heredoc */ /*unbackslash:*/ 1 ); //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); @@ -6282,15 +6814,11 @@ static const char *first_special_char_in_vararg(const char *cp) * a dquoted string: "${var#"zz"}"), the difference only comes later * (word splitting and globbing of the ${var...} result). */ -#if !BASH_PATTERN_SUBST -#define encode_then_expand_vararg(str, handle_squotes, do_unbackslash) \ - encode_then_expand_vararg(str, handle_squotes) -#endif -static char *encode_then_expand_vararg(const char *str, int handle_squotes, int do_unbackslash) -{ -#if !BASH_PATTERN_SUBST && ENABLE_HUSH_CASE - const int do_unbackslash = 0; -#endif +static char *encode_then_expand_vararg(const char *str, + int handle_squotes, /* 'str' substrings are parsed as literals (unless inside "")*/ + int protect_vars, /* glob-protect double-quoted $VARS */ + int do_unbackslash /* run unbackslash on result before returning it */ +) { char *exp_str; struct in_str input; o_string dest = NULL_O_STRING; @@ -6325,7 +6853,7 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int goto ret; /* error */ } if (ch == '"') { - dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS; + dest.o_expflags ^= EXP_FLAG_GLOBPROTECT_CHARS; continue; } if (ch == '\\') { @@ -6341,7 +6869,10 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int if (ch == '$') { if (parse_dollar_squote(NULL, &dest, &input)) continue; - if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ 0x80)) { + if (!parse_dollar(NULL, &dest, &input, + /*quote_mask:*/ (dest.o_expflags & EXP_FLAG_GLOBPROTECT_CHARS) ? 0x80 : 0 + ) + ) { debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__); goto ret; } @@ -6353,7 +6884,7 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int o_addchr(&dest, SPECIAL_VAR_SYMBOL); o_addchr(&dest, 0x80 | '`'); if (!add_till_backquote(&dest, &input, - /*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_ESC_GLOB_CHARS set */ + /*in_dquote:*/ (dest.o_expflags & EXP_FLAG_GLOBPROTECT_CHARS) ) ) { goto ret; /* error */ @@ -6366,9 +6897,12 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int o_addQchr(&dest, ch); } /* for (;;) */ - debug_printf_parse("encode: '%s' -> '%s'\n", str, dest.data); + debug_printf_parse("encode(do_unbackslash:%d): '%s' -> '%s'\n", do_unbackslash, str, dest.data); exp_str = expand_string_to_string(dest.data, - do_unbackslash ? EXP_FLAG_ESC_GLOB_CHARS : 0, + 0 + | (do_unbackslash ? EXP_FLAG_GLOBPROTECT_CHARS : 0) + | (protect_vars ? EXP_FLAG_GLOBPROTECT_VARS : 0) + , do_unbackslash ); ret: @@ -6412,7 +6946,10 @@ static NOINLINE int encode_then_append_var_plusminus(o_string *output, int n, if (!dest.o_expflags) { if (ch == EOF) break; - if (!dquoted && !(output->o_expflags & EXP_FLAG_SINGLEWORD) && strchr(G.ifs, ch)) { + if (!dquoted + && !(output->o_expflags & EXP_FLAG_SINGLEWORD) + && strchr(G.ifs, ch) + ) { /* PREFIX${x:d${e}f ...} and we met space: expand "d${e}f" and start new word. * do not assume we are at the start of the word (PREFIX above). */ @@ -6452,7 +6989,7 @@ static NOINLINE int encode_then_append_var_plusminus(o_string *output, int n, goto ret; /* error */ } if (ch == '"') { - dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS; + dest.o_expflags ^= EXP_FLAG_GLOBPROTECT_CHARS; if (dest.o_expflags) { o_addchr(&dest, SPECIAL_VAR_SYMBOL); o_addchr(&dest, SPECIAL_VAR_SYMBOL); @@ -6482,7 +7019,7 @@ static NOINLINE int encode_then_append_var_plusminus(o_string *output, int n, o_addchr(&dest, SPECIAL_VAR_SYMBOL); o_addchr(&dest, (dest.o_expflags || dquoted) ? 0x80 | '`' : '`'); if (!add_till_backquote(&dest, &input, - /*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_ESC_GLOB_CHARS set */ + /*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_GLOBPROTECT_CHARS set */ ) ) { goto ret; /* error */ @@ -6610,27 +7147,39 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c #endif /* BASH_PATTERN_SUBST */ static int append_str_maybe_ifs_split(o_string *output, int n, - int first_ch, const char *val) + int quoted, const char *val) { - if (!(first_ch & 0x80)) { /* unquoted $VAR */ - debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, - !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); - if (val && val[0]) + if (!quoted) { /* unquoted $VAR */ + debug_printf_expand("unquoted variable value '%s', o_escape:%d o_varescape:%d, singleword:%d\n", val, + !!(output->o_expflags & EXP_FLAG_GLOBPROTECT_CHARS), + !!(output->o_expflags & EXP_FLAG_GLOBPROTECT_VARS), + !!(output->o_expflags & EXP_FLAG_SINGLEWORD) + ); + if (val && val[0]) { + if (output->o_expflags & EXP_FLAG_SINGLEWORD) + goto singleword; n = expand_on_ifs(output, n, val); + } } else { /* quoted "$VAR" */ output->has_quoted_part = 1; - debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, - !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); - if (val && val[0]) - o_addQstr(output, val); + debug_printf_expand("quoted variable value '%s', o_escape:%d o_varescape:%d\n", val, + !!(output->o_expflags & EXP_FLAG_GLOBPROTECT_CHARS), + !!(output->o_expflags & EXP_FLAG_GLOBPROTECT_VARS) + ); + if (val && val[0]) { + if (output->o_expflags & EXP_FLAG_GLOBPROTECT_VARS) + o_addqstr(output, val); + else + singleword: + o_addQstr(output, val); + } } return n; } /* Handle varname... construct. */ -static NOINLINE int expand_one_var(o_string *output, int n, - int first_ch, char *arg, char **pp) +static NOINLINE int expand_one_var(o_string *output, int n, char *arg, char **pp) { const char *val; char *to_be_freed; @@ -6766,7 +7315,11 @@ static NOINLINE int expand_one_var(o_string *output, int n, if (exp_op == *exp_word) /* ## or %% */ exp_word++; debug_printf_expand("expand: exp_word:'%s'\n", exp_word); - exp_exp_word = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 0); + exp_exp_word = encode_then_expand_vararg(exp_word, + /*handle_squotes:*/ 1, /* 'str' are processed (and glob-protected) */ + /*globprotect_vars:*/ 1, /* value of "$VAR" is not glob-expanded */ + /*unbackslash:*/ 0 + ); if (exp_exp_word) exp_word = exp_exp_word; debug_printf_expand("expand: exp_word:'%s'\n", exp_word); @@ -6813,7 +7366,11 @@ static NOINLINE int expand_one_var(o_string *output, int n, * (note that a*z _pattern_ is never globbed!) */ char *pattern, *repl, *t; - pattern = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 0); + pattern = encode_then_expand_vararg(exp_word, + /*handle_squotes:*/ 1, + /*globprotect_vars:*/ 0, + /*unbackslash:*/ 0 + ); if (!pattern) pattern = xstrdup(exp_word); debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern); @@ -6821,7 +7378,11 @@ static NOINLINE int expand_one_var(o_string *output, int n, exp_word = p; p = strchr(p, SPECIAL_VAR_SYMBOL); *p = '\0'; - repl = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 1); + repl = encode_then_expand_vararg(exp_word, + /*handle_squotes:*/ 1, + /*globprotect_vars:*/ 0, + /*unbackslash:*/ 1 + ); debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl); /* HACK ALERT. We depend here on the fact that * G.global_argv and results of utoa and get_local_var_value @@ -6974,6 +7535,7 @@ static NOINLINE int expand_one_var(o_string *output, int n, /* ${var=word} - assign and use default value */ to_be_freed = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ !(arg0 & 0x80), + /*globprotect_vars:*/ 0, /*unbackslash:*/ 0 ); if (to_be_freed) @@ -7018,7 +7580,7 @@ static NOINLINE int expand_one_var(o_string *output, int n, arg[0] = arg0; *pp = p; - n = append_str_maybe_ifs_split(output, n, first_ch, val); + n = append_str_maybe_ifs_split(output, n, /*quoted:*/ (arg0 & 0x80), val); free(to_be_freed); return n; @@ -7146,7 +7708,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) G.last_exitcode = process_command_subs(&subst_result, arg); G.expand_exitcode = G.last_exitcode; debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); - n = append_str_maybe_ifs_split(output, n, first_ch, subst_result.data); + n = append_str_maybe_ifs_split(output, n, /*quoted:*/(first_ch & 0x80), subst_result.data); o_free(&subst_result); break; } @@ -7164,7 +7726,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) sprintf(arith_buf, ARITH_FMT, res); if (res < 0 && first_ch == (char)('+'|0x80) - /* && (output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS) */ + /* && (output->o_expflags & EXP_FLAG_GLOBPROTECT_CHARS) */ ) { /* Quoted negative ariths, like filename[0"$((-9))"], * should not be interpreted as glob ranges. @@ -7179,7 +7741,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) #endif default: /* varname[ops] */ - n = expand_one_var(output, n, first_ch, arg, &p); + n = expand_one_var(output, n, arg, &p); break; } /* switch (char after ) */ @@ -7247,7 +7809,7 @@ static char **expand_variables(char **argv, unsigned expflags) static char **expand_strvec_to_strvec(char **argv) { - return expand_variables(argv, EXP_FLAG_GLOB | EXP_FLAG_ESC_GLOB_CHARS); + return expand_variables(argv, EXP_FLAG_DO_GLOBBING | EXP_FLAG_GLOBPROTECT_CHARS); } #if defined(CMD_SINGLEWORD_NOGLOB) || defined(CMD_TEST2_SINGLEWORD_NOGLOB) @@ -7265,10 +7827,6 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv) */ static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash) { -#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE - const int do_unbackslash = 1; - const int EXP_flags = EXP_FLAG_ESC_GLOB_CHARS; -#endif char *argv[2], **list; debug_printf_expand("string_to_string<='%s'\n", str); @@ -7337,7 +7895,7 @@ static char **expand_assignments(char **argv, int count) for (i = 0; i < count; i++) { p = add_string_to_strings(p, expand_string_to_string(argv[i], - EXP_FLAG_ESC_GLOB_CHARS, + EXP_FLAG_GLOBPROTECT_CHARS, /*unbackslash:*/ 1 ) ); @@ -7347,7 +7905,6 @@ static char **expand_assignments(char **argv, int count) return p; } - static void switch_off_special_sigs(unsigned mask) { unsigned sig = 0; @@ -7370,11 +7927,6 @@ static void switch_off_special_sigs(unsigned mask) } #if BB_MMU -/* never called */ -void re_execute_shell(char ***to_free, const char *s, - char *g_argv0, char **g_argv, - char **builtin_argv) NORETURN; - static void reset_traps_to_defaults(void) { /* This function is always called in a child shell @@ -7424,10 +7976,8 @@ static void reset_traps_to_defaults(void) #else /* !BB_MMU */ -static void re_execute_shell(char ***to_free, const char *s, - char *g_argv0, char **g_argv, - char **builtin_argv) NORETURN; -static void re_execute_shell(char ***to_free, const char *s, +static NORETURN void re_execute_shell( + char * *volatile * to_free, const char *s, char *g_argv0, char **g_argv, char **builtin_argv) { @@ -7565,7 +8115,6 @@ static void re_execute_shell(char ***to_free, const char *s, } #endif /* !BB_MMU */ - static int run_and_free_list(struct pipe *pi); /* Executing from string: eval, sh -c '...' @@ -7658,7 +8207,13 @@ static int generate_stream_from_string(const char *s, pid_t *pid_p) pid_t pid; int channel[2]; # if !BB_MMU - char **to_free = NULL; + /* _volatile_ pointer to "char*". + * Or else compiler can peek from inside re_execute_shell() + * and see that this pointer is a local var (i.e. not globally visible), + * and decide to optimize out the store to it. Yes, + * it was seen in the wild. + */ + char * *volatile to_free = NULL; # endif xpipe(channel); @@ -7796,7 +8351,6 @@ static int process_command_subs(o_string *dest, const char *s) } #endif /* ENABLE_HUSH_TICK */ - static void setup_heredoc(struct redir_struct *redir) { struct fd_pair pair; @@ -7806,16 +8360,29 @@ static void setup_heredoc(struct redir_struct *redir) const char *heredoc = redir->rd_filename; char *expanded; #if !BB_MMU - char **to_free; + char * *volatile to_free; #endif expanded = NULL; - if (!(redir->rd_dup & HEREDOC_QUOTED)) { + if (redir->rd_type == REDIRECT_HERESTRING) { + heredoc = expanded = expand_string_to_string(heredoc, + EXP_FLAG_GLOBPROTECT_CHARS, + /* ^^^^why? testcases: + * cat <<<`echo '_\t_\\_\"_'` prints _\t_\_\"_ + * cat <<<"`echo '_\t_\\_\"_'`" prints _\t_\_"_ + */ + /*unbackslash:*/ 1 + /* ^^^^^^^^^^ cat <<<\_ prints _ */ + ); + } else if (!(redir->rd_dup & HEREDOC_QUOTED)) { expanded = encode_then_expand_string(heredoc); if (expanded) heredoc = expanded; } + len = strlen(heredoc); + if (redir->rd_type == REDIRECT_HERESTRING) + expanded[len++] = '\n'; close(redir->rd_fd); /* often saves dup2+close in xmove_fd */ xpiped_pair(pair); @@ -8059,7 +8626,7 @@ static void restore_redirects(struct squirrel *sq) * Redirect moves ->fd to e.g. 10, * and it is not restored above (we do not restore script fds * after redirects, we just use new, "moved" fds). - * However for stdin, get_user_input() -> read_line_input(), + * However for stdin, show_prompt_and_get_stdin() -> read_line_input(), * and read builtin, depend on fd == STDIN_FILENO. */ debug_printf_redir("restoring %d to stdin\n", G.HFILE_stdin->fd); @@ -8111,12 +8678,14 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) int newfd; int closed; - if (redir->rd_type == REDIRECT_HEREDOC2) { - /* "rd_fd<rd_type == REDIRECT_HEREDOC2 + || redir->rd_type == REDIRECT_HERESTRING + ) { + /* "rd_fd<rd_fd, /*avoid:*/ 0, sqp) < 0) return 1; - /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ - * of the heredoc */ + /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ of the heredoc */ + /* for REDIRECT_HERESTRING, rd_filename holds "WORD" */ debug_printf_redir("set heredoc '%s'\n", redir->rd_filename); setup_heredoc(redir); @@ -8138,7 +8707,9 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) } mode = redir_table[redir->rd_type].mode; p = expand_string_to_string(redir->rd_filename, - EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1); + EXP_FLAG_GLOBPROTECT_CHARS, + /*unbackslash:*/ 1 + ); newfd = open_or_warn(p, mode); free(p); if (newfd < 0) { @@ -8207,38 +8778,53 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) return 0; } -static char *find_in_path(const char *arg) +/* Find a file in PATH, not necessarily executable + * Name is known to not contain '/'. + */ +//TODO: shares code with find_executable() in libbb, factor out? +static char *find_in_PATH(const char *name) { - char *ret = NULL; - const char *PATH = get_local_var_value("PATH"); + char *p = (char*) get_local_var_value("PATH"); - if (!PATH) + if (!p) return NULL; - while (1) { - const char *end = strchrnul(PATH, ':'); - int sz = end - PATH; /* must be int! */ + const char *end = strchrnul(p, ':'); + int sz = end - p; /* must be int! */ - free(ret); if (sz != 0) { - ret = xasprintf("%.*s/%s", sz, PATH, arg); + p = xasprintf("%.*s/%s", sz, p, name); } else { - /* We have xxx::yyyy in $PATH, + /* We have xxx::yyy in $PATH, * it means "use current dir" */ - ret = xstrdup(arg); + p = xstrdup(name); } - if (access(ret, F_OK) == 0) - break; - - if (*end == '\0') { - free(ret); + if (access(p, F_OK) == 0) + return p; + free(p); + if (*end == '\0') return NULL; - } - PATH = end + 1; + p = (char *) end + 1; } +} - return ret; +#if ENABLE_HUSH_TYPE || ENABLE_HUSH_COMMAND +static char *find_executable_in_PATH(const char *name) +{ + const char *PATH; + if (strchr(name, '/')) { + /* Name with '/' is tested verbatim, with no PATH traversal: + * "cd /bin; type ./cat" should print "./cat is ./cat", + * NOT "./cat is /bin/./cat" + */ + if (file_is_executable(name)) + return xstrdup(name); + return NULL; + } + PATH = get_local_var_value("PATH"); + return find_executable(name, &PATH); /* path == NULL is ok */ } +#endif static const struct built_in_command *find_builtin_helper(const char *name, const struct built_in_command *x, @@ -8266,7 +8852,7 @@ static const struct built_in_command *find_builtin(const char *name) return find_builtin_helper(name, bltins2, &bltins2[ARRAY_SIZE(bltins2)]); } -#if ENABLE_HUSH_JOB && ENABLE_FEATURE_TAB_COMPLETION +#if ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_TAB_COMPLETION static const char * FAST_FUNC hush_command_name(int i) { if (/*i >= 0 && */ i < ARRAY_SIZE(bltins1)) { @@ -8432,10 +9018,8 @@ static void unset_func(const char *name) #define exec_function(to_free, funcp, argv) \ exec_function(funcp, argv) # endif -static void exec_function(char ***to_free, - const struct function *funcp, - char **argv) NORETURN; -static void exec_function(char ***to_free, +static NORETURN void exec_function( + char * *volatile *to_free, const struct function *funcp, char **argv) { @@ -8524,7 +9108,6 @@ static int run_function(const struct function *funcp, char **argv) } #endif /* ENABLE_HUSH_FUNCTIONS */ - #if BB_MMU #define exec_builtin(to_free, x, argv) \ exec_builtin(x, argv) @@ -8532,10 +9115,8 @@ static int run_function(const struct function *funcp, char **argv) #define exec_builtin(to_free, x, argv) \ exec_builtin(to_free, argv) #endif -static void exec_builtin(char ***to_free, - const struct built_in_command *x, - char **argv) NORETURN; -static void exec_builtin(char ***to_free, +static NORETURN void exec_builtin( + char * *volatile *to_free, const struct built_in_command *x, char **argv) { @@ -8558,9 +9139,7 @@ static void exec_builtin(char ***to_free, #endif } - -static void execvp_or_die(char **argv) NORETURN; -static void execvp_or_die(char **argv) +static NORETURN void execvp_or_die(char **argv) { int e; debug_printf_exec("execing '%s'\n", argv[0]); @@ -8651,10 +9230,11 @@ static void if_command_vV_print_and_exit(char opt_vV, char *cmd, const char *exp to_free = NULL; if (!explanation) { - char *path = getenv("PATH"); - explanation = to_free = find_executable(cmd, &path); /* path == NULL is ok */ - if (!explanation) + explanation = to_free = find_executable_in_PATH(cmd); + if (!explanation) { + bb_error_msg("%s: %s: not found", "command", cmd); _exit(1); /* PROG was not found */ + } if (opt_vV != 'V') cmd = to_free; /* -v PROG prints "/path/to/PROG" */ } @@ -8680,10 +9260,8 @@ static void if_command_vV_print_and_exit(char opt_vV, char *cmd, const char *exp * The at_exit handlers apparently confuse the calling process, * in particular stdin handling. Not sure why? -- because of vfork! (vda) */ -static void pseudo_exec_argv(nommu_save_t *nommu_save, - char **argv, int assignment_cnt, - char **argv_expanded) NORETURN; -static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, +static NORETURN NOINLINE void pseudo_exec_argv( + volatile nommu_save_t *nommu_save, char **argv, int assignment_cnt, char **argv_expanded) { @@ -8709,7 +9287,8 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, #if BB_MMU G.shadowed_vars_pp = NULL; /* "don't save, free them instead" */ #else - G.shadowed_vars_pp = &nommu_save->old_vars; + /* cast away volatility */ + G.shadowed_vars_pp = (struct variable **)&nommu_save->old_vars; G.var_nest_level++; #endif set_vars_and_save_old(new_env); @@ -8836,10 +9415,8 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, /* Called after [v]fork() in run_pipe */ -static void pseudo_exec(nommu_save_t *nommu_save, - struct command *command, - char **argv_expanded) NORETURN; -static void pseudo_exec(nommu_save_t *nommu_save, +static NORETURN void pseudo_exec( + volatile nommu_save_t *nommu_save, struct command *command, char **argv_expanded) { @@ -8866,13 +9443,14 @@ static void pseudo_exec(nommu_save_t *nommu_save, */ #if BB_MMU int rcode; - debug_printf_exec("pseudo_exec: run_list\n"); + debug_printf_exec("pseudo_exec: run_list(command->group)\n"); reset_traps_to_defaults(); rcode = run_list(command->group); /* OK to leak memory by not calling free_pipe_list, * since this process is about to exit */ _exit(rcode); #else + debug_printf_exec("pseudo_exec: re_exec(command->group_as_string)\n"); re_execute_shell(&nommu_save->argv_from_re_execing, command->group_as_string, G.global_argv[0], @@ -9448,7 +10026,7 @@ static NOINLINE int run_pipe(struct pipe *pi) i = 0; while (i < command->assignment_cnt) { char *p = expand_string_to_string(argv[i], - EXP_FLAG_ESC_GLOB_CHARS, + EXP_FLAG_GLOBPROTECT_CHARS, /*unbackslash:*/ 1 ); #if ENABLE_HUSH_MODE_X @@ -9670,13 +10248,20 @@ static NOINLINE int run_pipe(struct pipe *pi) #if ENABLE_HUSH_LINENO_VAR G.execute_lineno = command->lineno; #endif - command->pid = BB_MMU ? fork() : vfork(); if (!command->pid) { /* child */ -#if ENABLE_HUSH_JOB disable_restore_tty_pgrp_on_exit(); - CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ +#if ENABLE_HUSH_INTERACTIVE + if (BB_MMU && pi->followup == PIPE_BG) { + /* This disables alias expansion in . FILE & */ + /* (bash does it only for (. FILE)& */ + G.interactive_fd = 0; + } +#endif + if (BB_MMU) + CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ +#if ENABLE_HUSH_JOB /* Every child adds itself to new process group * with pgid == pid_of_first_child_in_pipe */ if (G.run_list_level == 1 && G_interactive_fd) { @@ -9724,8 +10309,7 @@ static NOINLINE int run_pipe(struct pipe *pi) /* Stores to nommu_save list of env vars putenv'ed * (NOMMU, on MMU we don't need that) */ - /* cast away volatility... */ - pseudo_exec((nommu_save_t*) &nommu_save, command, argv_expanded); + pseudo_exec(&nommu_save, command, argv_expanded); /* pseudo_exec() does not return */ } @@ -9970,7 +10554,9 @@ static int run_list(struct pipe *pi) if (rword == RES_CASE) { debug_printf_exec("CASE cond_code:%d\n", cond_code); case_word = expand_string_to_string(pi->cmds->argv[0], - EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1); + EXP_FLAG_GLOBPROTECT_CHARS, + /*unbackslash:*/ 1 + ); debug_printf_exec("CASE word1:'%s'\n", case_word); //unbackslash(case_word); //debug_printf_exec("CASE word2:'%s'\n", case_word); @@ -9988,7 +10574,7 @@ static int run_list(struct pipe *pi) char *pattern; debug_printf_exec("expand_string_to_string('%s')\n", *argv); pattern = expand_string_to_string(*argv, - EXP_FLAG_ESC_GLOB_CHARS, + EXP_FLAG_GLOBPROTECT_CHARS, /*unbackslash:*/ 0 ); /* TODO: which FNM_xxx flags to use? */ @@ -10113,7 +10699,7 @@ static int run_list(struct pipe *pi) if (rcode != 0 && G.o_opt[OPT_O_ERREXIT]) { debug_printf_exec("ERREXIT:1 errexit_depth:%d\n", G.errexit_depth); if (G.errexit_depth == 0) - hush_exit(rcode); + save_history_run_exit_trap_and_exit(rcode); } G.errexit_depth = sv_errexit_depth; @@ -10184,6 +10770,55 @@ static int run_and_free_list(struct pipe *pi) return rcode; } +/* + * Initialization and main + */ +#if ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING +static void init_line_editing(void) +{ + G.line_input_state = new_line_input_t(FOR_SHELL); +# if ENABLE_FEATURE_TAB_COMPLETION + G.line_input_state->get_exe_name = hush_command_name; +# endif +# if EDITING_HAS_sh_get_var + G.line_input_state->sh_get_var = get_local_var_value; +# endif +# if ENABLE_HUSH_SAVEHISTORY && MAX_HISTORY > 0 + { + const char *hp = get_local_var_value("HISTFILE"); + if (!hp) { + hp = get_local_var_value("HOME"); + if (hp) { + hp = concat_path_file(hp, ".hush_history"); + /* Make HISTFILE set on exit (else history won't be saved) */ + set_local_var_from_halves("HISTFILE", hp); + } + } else { + hp = xstrdup(hp); + } + if (hp) { + G.line_input_state->hist_file = hp; + } +# if ENABLE_FEATURE_SH_HISTFILESIZE + hp = get_local_var_value("HISTSIZE"); + /* Using HISTFILESIZE above to limit max_history would be WRONG: + * users may set HISTFILESIZE=0 in their profile scripts + * to prevent _saving_ of history files, but still want to have + * non-zero history limit for in-memory list. + */ +// in bash, runtime history size is controlled by HISTSIZE (0=no history), +// HISTFILESIZE controls on-disk history file size (in lines, 0=no history): + G.line_input_state->max_history = size_from_HISTFILESIZE(hp); +// HISTFILESIZE: "The shell sets the default value to the value of HISTSIZE after reading any startup files." +// HISTSIZE: "The shell sets the default value to 500 after reading any startup files." +// (meaning: if the value wasn't set after startup files, the default value is set as described above) +# endif + } +# endif +} +#else +# define init_line_editing() ((void)0) +#endif static void install_sighandlers(unsigned mask) { @@ -10323,7 +10958,6 @@ static int set_mode(int state, char mode, const char *o_opt) int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int hush_main(int argc, char **argv) { - pid_t cached_getpid; enum { OPT_login = (1 << 0), }; @@ -10336,6 +10970,11 @@ int hush_main(int argc, char **argv) struct variable *shell_ver; INIT_G(); +#if ENABLE_HUSH_JOB + die_func = fflush_restore_ttypgrp_and__exit; +#else + die_func = fflush_and__exit; +#endif if (EXIT_SUCCESS != 0) /* if EXIT_SUCCESS == 0, it is already done */ G.last_exitcode = EXIT_SUCCESS; #if !BB_MMU @@ -10358,14 +10997,11 @@ int hush_main(int argc, char **argv) # endif G.pre_trap_exitcode = -1; #endif - #if ENABLE_HUSH_FAST G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ #endif - - cached_getpid = getpid(); /* for tcsetpgrp() during init */ - G.root_pid = cached_getpid; /* for $PID (NOMMU can override via -$HEXPID:HEXPPID:...) */ - G.root_ppid = getppid(); /* for $PPID (NOMMU can override) */ + G.root_pid = getpid(); /* for $PID (NOMMU can override via -$HEXPID:HEXPPID:...) */ + G.root_ppid = getppid(); /* for $PPID (NOMMU can override) */ /* Deal with HUSH_VERSION */ debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION"); @@ -10414,7 +11050,7 @@ int hush_main(int argc, char **argv) if (!get_local_var_value("PATH")) set_local_var_from_halves("PATH", bb_default_root_path); - /* PS1/PS2 are set later, if we determine that we are interactive */ + /* PS1/PS2/HISTFILE are set later, if we determine that we are interactive */ /* bash also exports SHLVL and _, * and sets (but doesn't export) the following variables: @@ -10436,7 +11072,6 @@ int hush_main(int argc, char **argv) * BASH_SOURCE=() * DIRSTACK=() * PIPESTATUS=([0]="0") - * HISTFILE=//.bash_history * HISTFILESIZE=500 * HISTSIZE=500 * MAILCHECK=60 @@ -10449,27 +11084,24 @@ int hush_main(int argc, char **argv) * PS4='+ ' */ + /* Shell is non-interactive at first. We need to call + * install_special_sighandlers() if we are going to execute "sh