From dc04439450e5929880205d920de3a26409727563 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 23 Aug 2010 02:53:58 +0200 Subject: [PATCH] Version 1.17.2: apply post 1.17.1 fixes, replace patch applet by Rob's version Signed-off-by: Denys Vlasenko --- Makefile | 15 +- debianutils/mktemp.c | 2 +- editors/patch.c | 851 ++++++++++++++++------- editors/patch_bbox.c | 306 ++++++++ editors/sed.c | 27 +- findutils/grep.c | 10 +- include/libbb.h | 1 + libbb/wfopen_input.c | 10 +- shell/ash.c | 1 + shell/ash_test/ash-redir/redir9.tests | 0 shell/ash_test/ash-signals/signal7.right | 1 + shell/ash_test/ash-signals/signal7.tests | 18 + shell/hush.c | 4 +- shell/hush_test/hush-trap/signal7.right | 1 + shell/hush_test/hush-trap/signal7.tests | 18 + shell/shell_common.c | 7 +- testsuite/grep.tests | 4 + testsuite/mdev.tests | 10 + testsuite/patch.tests | 74 +- testsuite/sed.tests | 21 +- util-linux/mdev.c | 5 +- 21 files changed, 1083 insertions(+), 303 deletions(-) create mode 100644 editors/patch_bbox.c mode change 100644 => 100755 shell/ash_test/ash-redir/redir9.tests create mode 100644 shell/ash_test/ash-signals/signal7.right create mode 100755 shell/ash_test/ash-signals/signal7.tests create mode 100644 shell/hush_test/hush-trap/signal7.right create mode 100755 shell/hush_test/hush-trap/signal7.tests diff --git a/Makefile b/Makefile index 03e988557..3866318f9 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ VERSION = 1 PATCHLEVEL = 17 -SUBLEVEL = 1 +SUBLEVEL = 2 EXTRAVERSION = NAME = Unnamed @@ -433,7 +433,12 @@ ifeq ($(config-targets),1) -include $(srctree)/arch/$(ARCH)/Makefile export KBUILD_DEFCONFIG -config %config: scripts_basic outputmakefile gen_build_files FORCE +config: scripts_basic outputmakefile gen_build_files FORCE + $(Q)mkdir -p include + $(Q)$(MAKE) $(build)=scripts/kconfig $@ + $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= .kernelrelease + +%config: scripts_basic outputmakefile gen_build_files FORCE $(Q)mkdir -p include $(Q)$(MAKE) $(build)=scripts/kconfig $@ $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= .kernelrelease @@ -1285,9 +1290,13 @@ endif $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) # Modules -/ %/: prepare scripts FORCE +%/: prepare scripts FORCE $(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \ $(build)=$(build-dir) +/: prepare scripts FORCE + $(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \ + $(build)=$(build-dir) + %.ko: prepare scripts FORCE $(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \ $(build)=$(build-dir) $(@:.ko=.o) diff --git a/debianutils/mktemp.c b/debianutils/mktemp.c index 2c4e19670..7ce9f1096 100644 --- a/debianutils/mktemp.c +++ b/debianutils/mktemp.c @@ -50,7 +50,7 @@ int mktemp_main(int argc UNUSED_PARAM, char **argv) opts = getopt32(argv, "dqtp:", &path); chp = argv[optind] ? argv[optind] : xstrdup("tmp.XXXXXX"); - if (chp[0] != '/' || (opts & 8)) + if (!strchr(chp, '/') || (opts & 8)) chp = concat_path_file(path, chp); if (opts & 1) { /* -d */ diff --git a/editors/patch.c b/editors/patch.c index 62477af16..c40f54155 100644 --- a/editors/patch.c +++ b/editors/patch.c @@ -1,306 +1,621 @@ -/* vi: set sw=4 ts=4: */ -/* - * busybox patch applet to handle the unified diff format. - * Copyright (C) 2003 Glenn McGrath +/* Adapted from toybox's patch. */ + +/* vi: set sw=4 ts=4: + * + * patch.c - Apply a "universal" diff. * - * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * Copyright 2007 Rob Landley * - * This applet is written to work with patches generated by GNU diff, - * where there is equivalent functionality busybox patch shall behave - * as per GNU patch. + * see http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html + * (But only does -u, because who still cares about "ed"?) * - * There is a SUSv3 specification for patch, however it looks to be - * incomplete, it doesnt even mention unified diff format. - * http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html + * TODO: + * -b backup + * -l treat all whitespace as a single space + * -d chdir first + * -D define wrap #ifdef and #ifndef around changes + * -o outfile output here instead of in place + * -r rejectfile write rejected hunks to this file * - * Issues - * - Non-interactive - * - Patches must apply cleanly or patch (not just one hunk) will fail. - * - Reject file isnt saved - */ + * -E remove empty files --remove-empty-files + * -f force (no questions asked) + * -F fuzz (number, default 2) + * [file] which file to patch + +USE_PATCH(NEWTOY(patch, USE_TOYBOX_DEBUG("x")"up#i:R", TOYFLAG_USR|TOYFLAG_BIN)) + +config PATCH + bool "patch" + default y + help + usage: patch [-i file] [-p depth] [-Ru] + + Apply a unified diff to one or more files. + -i Input file (defaults=stdin) + -p number of '/' to strip from start of file paths (default=all) + -R Reverse patch. + -u Ignored (only handles "unified" diffs) + + This version of patch only handles unified diffs, and only modifies + a file when all all hunks to that file apply. Patch prints failed + hunks to stderr, and exits with nonzero status if any hunks fail. + + A file compared against /dev/null (or with a date <= the epoch) is + created/deleted as appropriate. +*/ #include "libbb.h" -static unsigned copy_lines(FILE *src_stream, FILE *dst_stream, unsigned lines_count) +struct double_list { + struct double_list *next; + struct double_list *prev; + char *data; +}; + +// Return the first item from the list, advancing the list (which must be called +// as &list) +static +void *TOY_llist_pop(void *list) { - while (src_stream && lines_count) { - char *line; - line = xmalloc_fgets(src_stream); - if (line == NULL) { - break; - } - if (fputs(line, dst_stream) == EOF) { - bb_perror_msg_and_die("error writing to new file"); + // I'd use a void ** for the argument, and even accept the typecast in all + // callers as documentation you need the &, except the stupid compiler + // would then scream about type-punned pointers. Screw it. + void **llist = (void **)list; + void **next = (void **)*llist; + *llist = *next; + + return (void *)next; +} + +// Free all the elements of a linked list +// if freeit!=NULL call freeit() on each element before freeing it. +static +void TOY_llist_free(void *list, void (*freeit)(void *data)) +{ + while (list) { + void *pop = TOY_llist_pop(&list); + if (freeit) freeit(pop); + else free(pop); + + // End doubly linked list too. + if (list==pop) break; + } +} +//Override bbox's names +#define llist_pop TOY_llist_pop +#define llist_free TOY_llist_free + +// Add an entry to the end off a doubly linked list +static +struct double_list *dlist_add(struct double_list **list, char *data) +{ + struct double_list *line = xmalloc(sizeof(struct double_list)); + + line->data = data; + if (*list) { + line->next = *list; + line->prev = (*list)->prev; + (*list)->prev->next = line; + (*list)->prev = line; + } else *list = line->next = line->prev = line; + + return line; +} + +// Ensure entire path exists. +// If mode != -1 set permissions on newly created dirs. +// Requires that path string be writable (for temporary null terminators). +static +void xmkpath(char *path, int mode) +{ + char *p, old; + mode_t mask; + int rc; + struct stat st; + + for (p = path; ; p++) { + if (!*p || *p == '/') { + old = *p; + *p = rc = 0; + if (stat(path, &st) || !S_ISDIR(st.st_mode)) { + if (mode != -1) { + mask = umask(0); + rc = mkdir(path, mode); + umask(mask); + } else rc = mkdir(path, 0777); + } + *p = old; + if(rc) bb_perror_msg_and_die("mkpath '%s'", path); } - free(line); - lines_count--; + if (!*p) break; + } +} + +// Slow, but small. +static +char *get_rawline(int fd, long *plen, char end) +{ + char c, *buf = NULL; + long len = 0; + + for (;;) { + if (1>read(fd, &c, 1)) break; + if (!(len & 63)) buf=xrealloc(buf, len+65); + if ((buf[len++]=c) == end) break; + } + if (buf) buf[len]=0; + if (plen) *plen = len; + + return buf; +} + +static +char *get_line(int fd) +{ + long len; + char *buf = get_rawline(fd, &len, '\n'); + + if (buf && buf[--len]=='\n') buf[len]=0; + + return buf; +} + +// Copy the rest of in to out and close both files. +static +void xsendfile(int in, int out) +{ + long len; + char buf[4096]; + + if (in<0) return; + for (;;) { + len = safe_read(in, buf, 4096); + if (len<1) break; + xwrite(out, buf, len); + } +} + +// Copy the rest of the data and replace the original with the copy. +static +void replace_tempfile(int fdin, int fdout, char **tempname) +{ + char *temp = xstrdup(*tempname); + + temp[strlen(temp)-6]=0; + if (fdin != -1) { + xsendfile(fdin, fdout); + xclose(fdin); } - return lines_count; + xclose(fdout); + rename(*tempname, temp); + free(*tempname); + free(temp); + *tempname = NULL; +} + +// Open a temporary file to copy an existing file into. +static +int copy_tempfile(int fdin, char *name, char **tempname) +{ + struct stat statbuf; + int fd; + + *tempname = xasprintf("%sXXXXXX", name); + fd = mkstemp(*tempname); + if(-1 == fd) bb_perror_msg_and_die("no temp file"); + + // Set permissions of output file + fstat(fdin, &statbuf); + fchmod(fd, statbuf.st_mode); + + return fd; } -/* If patch_level is -1 it will remove all directory names - * char *line must be greater than 4 chars - * returns NULL if the file doesnt exist or error - * returns malloc'ed filename - * NB: frees 1st argument! - */ -static char *extract_filename(char *line, int patch_level, const char *pat) +// Abort the copy and delete the temporary file. +static +void delete_tempfile(int fdin, int fdout, char **tempname) { - char *temp = NULL, *filename_start_ptr = line + 4; - - if (strncmp(line, pat, 4) == 0) { - /* Terminate string at end of source filename */ - line[strcspn(line, "\t\n\r")] = '\0'; - - /* Skip over (patch_level) number of leading directories */ - while (patch_level--) { - temp = strchr(filename_start_ptr, '/'); - if (!temp) - break; - filename_start_ptr = temp + 1; + close(fdin); + close(fdout); + unlink(*tempname); + free(*tempname); + *tempname = NULL; +} + + + +struct globals { + char *infile; + long prefix; + + struct double_list *current_hunk; + long oldline, oldlen, newline, newlen; + long linenum; + int context, state, filein, fileout, filepatch, hunknum; + char *tempname; + + // was toys.foo: + int exitval; +}; +#define TT (*ptr_to_globals) +#define INIT_TT() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(TT))); \ +} while (0) + + +#define FLAG_STR "Rup:i:Nx" +/* FLAG_REVERSE must be == 1! Code uses this fact. */ +#define FLAG_REVERSE (1 << 0) +#define FLAG_u (1 << 1) +#define FLAG_PATHLEN (1 << 2) +#define FLAG_INPUT (1 << 3) +#define FLAG_IGNORE (1 << 4) +//non-standard: +#define FLAG_DEBUG (1 << 5) + +// Dispose of a line of input, either by writing it out or discarding it. + +// state < 2: just free +// state = 2: write whole line to stderr +// state = 3: write whole line to fileout +// state > 3: write line+1 to fileout when *line != state + +#define PATCH_DEBUG (option_mask32 & FLAG_DEBUG) + +static void do_line(void *data) +{ + struct double_list *dlist = (struct double_list *)data; + + if (TT.state>1 && *dlist->data != TT.state) + fdprintf(TT.state == 2 ? 2 : TT.fileout, + "%s\n", dlist->data+(TT.state>3 ? 1 : 0)); + + if (PATCH_DEBUG) fdprintf(2, "DO %d: %s\n", TT.state, dlist->data); + + free(dlist->data); + free(data); +} + +static void finish_oldfile(void) +{ + if (TT.tempname) replace_tempfile(TT.filein, TT.fileout, &TT.tempname); + TT.fileout = TT.filein = -1; +} + +static void fail_hunk(void) +{ + if (!TT.current_hunk) return; + TT.current_hunk->prev->next = 0; + + fdprintf(2, "Hunk %d FAILED %ld/%ld.\n", TT.hunknum, TT.oldline, TT.newline); + TT.exitval = 1; + + // If we got to this point, we've seeked to the end. Discard changes to + // this file and advance to next file. + + TT.state = 2; + llist_free(TT.current_hunk, do_line); + TT.current_hunk = NULL; + delete_tempfile(TT.filein, TT.fileout, &TT.tempname); + TT.state = 0; +} + +// Given a hunk of a unified diff, make the appropriate change to the file. +// This does not use the location information, but instead treats a hunk +// as a sort of regex. Copies data from input to output until it finds +// the change to be made, then outputs the changed data and returns. +// (Finding EOF first is an error.) This is a single pass operation, so +// multiple hunks must occur in order in the file. + +static int apply_one_hunk(void) +{ + struct double_list *plist, *buf = NULL, *check; + int matcheof = 0, reverse = option_mask32 & FLAG_REVERSE, backwarn = 0; + /* Do we try "dummy" revert to check whether + * to silently skip this hunk? Used to implement -N. + */ + int dummy_revert = 0; + + // Break doubly linked list so we can use singly linked traversal function. + TT.current_hunk->prev->next = NULL; + + // Match EOF if there aren't as many ending context lines as beginning + for (plist = TT.current_hunk; plist; plist = plist->next) { + if (plist->data[0]==' ') matcheof++; + else matcheof = 0; + if (PATCH_DEBUG) fdprintf(2, "HUNK:%s\n", plist->data); + } + matcheof = matcheof < TT.context; + + if (PATCH_DEBUG) fdprintf(2,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N'); + + // Loop through input data searching for this hunk. Match all context + // lines and all lines to be removed until we've found the end of a + // complete hunk. + plist = TT.current_hunk; + buf = NULL; + if (TT.context) for (;;) { + char *data = get_line(TT.filein); + + TT.linenum++; + + // Figure out which line of hunk to compare with next. (Skip lines + // of the hunk we'd be adding.) + while (plist && *plist->data == "+-"[reverse]) { + if (data && !strcmp(data, plist->data+1)) { + if (!backwarn) { + backwarn++; + if (option_mask32 & FLAG_IGNORE) { + dummy_revert = 1; + reverse ^= 1; + continue; + } + fdprintf(2,"Possibly reversed hunk %d at %ld\n", + TT.hunknum, TT.linenum); + } + } + plist = plist->next; } - temp = xstrdup(filename_start_ptr); + + // Is this EOF? + if (!data) { + if (PATCH_DEBUG) fdprintf(2, "INEOF\n"); + + // Does this hunk need to match EOF? + if (!plist && matcheof) break; + + // File ended before we found a place for this hunk. + fail_hunk(); + goto done; + } else if (PATCH_DEBUG) fdprintf(2, "IN: %s\n", data); + check = dlist_add(&buf, data); + + // Compare this line with next expected line of hunk. + // todo: teach the strcmp() to ignore whitespace. + + // A match can fail because the next line doesn't match, or because + // we hit the end of a hunk that needed EOF, and this isn't EOF. + + // If match failed, flush first line of buffered data and + // recheck buffered data for a new match until we find one or run + // out of buffer. + + for (;;) { + if (!plist || strcmp(check->data, plist->data+1)) { + // Match failed. Write out first line of buffered data and + // recheck remaining buffered data for a new match. + + if (PATCH_DEBUG) + fdprintf(2, "NOT: %s\n", plist->data); + + TT.state = 3; + check = llist_pop(&buf); + check->prev->next = buf; + buf->prev = check->prev; + do_line(check); + plist = TT.current_hunk; + + // If we've reached the end of the buffer without confirming a + // match, read more lines. + if (check==buf) { + buf = 0; + break; + } + check = buf; + } else { + if (PATCH_DEBUG) + fdprintf(2, "MAYBE: %s\n", plist->data); + // This line matches. Advance plist, detect successful match. + plist = plist->next; + if (!plist && !matcheof) goto out; + check = check->next; + if (check == buf) break; + } + } + } +out: + // We have a match. Emit changed data. + TT.state = "-+"[reverse ^ dummy_revert]; + llist_free(TT.current_hunk, do_line); + TT.current_hunk = NULL; + TT.state = 1; +done: + if (buf) { + buf->prev->next = NULL; + llist_free(buf, do_line); } - free(line); - return temp; + + return TT.state; } +// Read a patch file and find hunks, opening/creating/deleting files. +// Call apply_one_hunk() on each hunk. + +// state 0: Not in a hunk, look for +++. +// state 1: Found +++ file indicator, look for @@ +// state 2: In hunk: counting initial context lines +// state 3: In hunk: getting body + int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int patch_main(int argc UNUSED_PARAM, char **argv) { - struct stat saved_stat; - char *patch_line; - FILE *patch_file; - int patch_level; - int ret = 0; - char plus = '+'; - unsigned opt; - enum { - OPT_R = (1 << 2), - OPT_N = (1 << 3), - /*OPT_f = (1 << 4), ignored */ - /*OPT_E = (1 << 5), ignored, this is the default */ - /*OPT_g = (1 << 6), ignored */ - OPT_dry_run = (1 << 7) * ENABLE_LONG_OPTS, - }; - - xfunc_error_retval = 2; - { - const char *p = "-1"; - const char *i = "-"; /* compat */ -#if ENABLE_LONG_OPTS - static const char patch_longopts[] ALIGN1 = - "strip\0" Required_argument "p" - "input\0" Required_argument "i" - "reverse\0" No_argument "R" - "forward\0" No_argument "N" - /* "Assume user knows what [s]he is doing, do not ask any questions": */ - "force\0" No_argument "f" /*ignored*/ -# if ENABLE_DESKTOP - "remove-empty-files\0" No_argument "E" /*ignored*/ - /* "Controls actions when a file is under RCS or SCCS control, - * and does not exist or is read-only and matches the default version, - * or when a file is under ClearCase control and does not exist..." - * IOW: rather obscure option. - * But Gentoo's portage does use -g0 */ - "get\0" Required_argument "g" /*ignored*/ -# endif - "dry-run\0" No_argument "\xfd" -# if ENABLE_DESKTOP - "backup-if-mismatch\0" No_argument "\xfe" /*ignored*/ - "no-backup-if-mismatch\0" No_argument "\xff" /*ignored*/ -# endif - ; - applet_long_options = patch_longopts; -#endif - /* -f,-E,-g are ignored */ - opt = getopt32(argv, "p:i:RN""fEg:", &p, &i, NULL); - if (opt & OPT_R) - plus = '-'; - patch_level = xatoi(p); /* can be negative! */ - patch_file = xfopen_stdin(i); + int opts; + int reverse, state = 0; + char *oldname = NULL, *newname = NULL; + char *opt_p, *opt_i; + + INIT_TT(); + + opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i); + argv += optind; + reverse = opts & FLAG_REVERSE; + TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative! + TT.filein = TT.fileout = -1; + if (opts & FLAG_INPUT) { + TT.filepatch = xopen_stdin(opt_i); + } else { + if (argv[0] && argv[1]) { + TT.filepatch = xopen_stdin(argv[1]); + } + } + if (argv[0]) { + oldname = xstrdup(argv[0]); + newname = xstrdup(argv[0]); } - patch_line = xmalloc_fgetline(patch_file); - while (patch_line) { - FILE *src_stream; - FILE *dst_stream; - //char *old_filename; - char *new_filename; - char *backup_filename = NULL; - unsigned src_cur_line = 1; - unsigned dst_cur_line = 0; - unsigned dst_beg_line; - unsigned bad_hunk_count = 0; - unsigned hunk_count = 0; - smallint copy_trailing_lines_flag = 0; - - /* Skip everything upto the "---" marker - * No need to parse the lines "Only in ", and "diff " - */ - do { - /* Extract the filename used before the patch was generated */ - new_filename = extract_filename(patch_line, patch_level, "--- "); - // was old_filename above - patch_line = xmalloc_fgetline(patch_file); - if (!patch_line) goto quit; - } while (!new_filename); - free(new_filename); // "source" filename is irrelevant - - new_filename = extract_filename(patch_line, patch_level, "+++ "); - if (!new_filename) { - bb_error_msg_and_die("invalid patch"); + // Loop through the lines in the patch + for(;;) { + char *patchline; + + patchline = get_line(TT.filepatch); + if (!patchline) break; + + // Other versions of patch accept damaged patches, + // so we need to also. + if (!*patchline) { + free(patchline); + patchline = xstrdup(" "); } - /* Get access rights from the file to be patched */ - if (stat(new_filename, &saved_stat) != 0) { - char *slash = strrchr(new_filename, '/'); - if (slash) { - /* Create leading directories */ - *slash = '\0'; - bb_make_directory(new_filename, -1, FILEUTILS_RECUR); - *slash = '/'; + // Are we assembling a hunk? + if (state >= 2) { + if (*patchline==' ' || *patchline=='+' || *patchline=='-') { + dlist_add(&TT.current_hunk, patchline); + + if (*patchline != '+') TT.oldlen--; + if (*patchline != '-') TT.newlen--; + + // Context line? + if (*patchline==' ' && state==2) TT.context++; + else state=3; + + // If we've consumed all expected hunk lines, apply the hunk. + + if (!TT.oldlen && !TT.newlen) state = apply_one_hunk(); + continue; } - src_stream = NULL; - saved_stat.st_mode = 0644; - } else if (!(opt & OPT_dry_run)) { - backup_filename = xasprintf("%s.orig", new_filename); - xrename(new_filename, backup_filename); - src_stream = xfopen_for_read(backup_filename); - } else - src_stream = xfopen_for_read(new_filename); - - if (opt & OPT_dry_run) { - dst_stream = xfopen_for_write("/dev/null"); - } else { - dst_stream = xfopen_for_write(new_filename); - fchmod(fileno(dst_stream), saved_stat.st_mode); + fail_hunk(); + state = 0; + continue; } - printf("patching file %s\n", new_filename); - - /* Handle all hunks for this file */ - patch_line = xmalloc_fgets(patch_file); - while (patch_line) { - unsigned count; - unsigned src_beg_line; - unsigned hunk_offset_start; - unsigned src_last_line = 1; - unsigned dst_last_line = 1; - - if ((sscanf(patch_line, "@@ -%d,%d +%d,%d", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3) - && (sscanf(patch_line, "@@ -%d +%d,%d", &src_beg_line, &dst_beg_line, &dst_last_line) < 2) - ) { - /* No more hunks for this file */ - break; - } - if (plus != '+') { - /* reverse patch */ - unsigned tmp = src_last_line; - src_last_line = dst_last_line; - dst_last_line = tmp; - tmp = src_beg_line; - src_beg_line = dst_beg_line; - dst_beg_line = tmp; + // Open a new file? + if (!strncmp("--- ", patchline, 4) || !strncmp("+++ ", patchline, 4)) { + char *s, **name = reverse ? &newname : &oldname; + int i; + + if (*patchline == '+') { + name = reverse ? &oldname : &newname; + state = 1; } - hunk_count++; - - if (src_beg_line && dst_beg_line) { - /* Copy unmodified lines upto start of hunk */ - /* src_beg_line will be 0 if it's a new file */ - count = src_beg_line - src_cur_line; - if (copy_lines(src_stream, dst_stream, count)) { - bb_error_msg_and_die("bad src file"); + + finish_oldfile(); + + if (!argv[0]) { + free(*name); + // Trim date from end of filename (if any). We don't care. + for (s = patchline+4; *s && *s!='\t'; s++) + if (*s=='\\' && s[1]) s++; + i = atoi(s); + if (i>1900 && i<=1970) + *name = xstrdup("/dev/null"); + else { + *s = 0; + *name = xstrdup(patchline+4); } - src_cur_line += count; - dst_cur_line += count; - copy_trailing_lines_flag = 1; } - src_last_line += hunk_offset_start = src_cur_line; - dst_last_line += dst_cur_line; - - while (1) { - free(patch_line); - patch_line = xmalloc_fgets(patch_file); - if (patch_line == NULL) - break; /* EOF */ - if (!*patch_line) { - /* whitespace-damaged patch with "" lines */ - free(patch_line); - patch_line = xstrdup(" "); + + // We defer actually opening the file because svn produces broken + // patches that don't signal they want to create a new file the + // way the patch man page says, so you have to read the first hunk + // and _guess_. + + // Start a new hunk? Usually @@ -oldline,oldlen +newline,newlen @@ + // but a missing ,value means the value is 1. + } else if (state == 1 && !strncmp("@@ -", patchline, 4)) { + int i; + char *s = patchline+4; + + // Read oldline[,oldlen] +newline[,newlen] + + TT.oldlen = TT.newlen = 1; + TT.oldline = strtol(s, &s, 10); + if (*s == ',') TT.oldlen=strtol(s+1, &s, 10); + TT.newline = strtol(s+2, &s, 10); + if (*s == ',') TT.newlen = strtol(s+1, &s, 10); + + TT.context = 0; + state = 2; + + // If this is the first hunk, open the file. + if (TT.filein == -1) { + int oldsum, newsum, del = 0; + char *name; + + oldsum = TT.oldline + TT.oldlen; + newsum = TT.newline + TT.newlen; + + name = reverse ? oldname : newname; + + // We're deleting oldname if new file is /dev/null (before -p) + // or if new hunk is empty (zero context) after patching + if (!strcmp(name, "/dev/null") || !(reverse ? oldsum : newsum)) + { + name = reverse ? newname : oldname; + del++; } - if ((*patch_line != '-') && (*patch_line != '+') - && (*patch_line != ' ') - ) { - break; /* End of hunk */ + + // handle -p path truncation. + for (i=0, s = name; *s;) { + if ((option_mask32 & FLAG_PATHLEN) && TT.prefix == i) break; + if (*(s++)=='/') { + name = s; + i++; + } } - if (*patch_line != plus) { /* '-' or ' ' */ - char *src_line = NULL; - if (src_cur_line == src_last_line) - break; - if (src_stream) { - src_line = xmalloc_fgets(src_stream); - if (src_line) { - int diff = strcmp(src_line, patch_line + 1); - src_cur_line++; - free(src_line); - if (diff) - src_line = NULL; + + if (del) { + printf("removing %s\n", name); + xunlink(name); + state = 0; + // If we've got a file to open, do so. + } else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) { + // If the old file was null, we're creating a new one. + if (!strcmp(oldname, "/dev/null") || !oldsum) { + printf("creating %s\n", name); + s = strrchr(name, '/'); + if (s) { + *s = 0; + xmkpath(name, -1); + *s = '/'; } + TT.filein = xopen3(name, O_CREAT|O_EXCL|O_RDWR, 0666); + } else { + printf("patching file %s\n", name); + TT.filein = xopen(name, O_RDWR); } - /* Do not patch an already patched hunk with -N */ - if (src_line == 0 && (opt & OPT_N)) { - continue; - } - if (!src_line) { - bb_error_msg("hunk #%u FAILED at %u", hunk_count, hunk_offset_start); - bad_hunk_count++; - break; - } - if (*patch_line != ' ') { /* '-' */ - continue; - } + TT.fileout = copy_tempfile(TT.filein, name, &TT.tempname); + TT.linenum = 0; + TT.hunknum = 0; } - if (dst_cur_line == dst_last_line) - break; - fputs(patch_line + 1, dst_stream); - dst_cur_line++; - } /* end of while loop handling one hunk */ - } /* end of while loop handling one file */ - - /* Cleanup last patched file */ - if (copy_trailing_lines_flag) { - copy_lines(src_stream, dst_stream, (unsigned)(-1)); - } - if (src_stream) { - fclose(src_stream); - } - fclose(dst_stream); - if (bad_hunk_count) { - ret = 1; - bb_error_msg("%u out of %u hunk FAILED", bad_hunk_count, hunk_count); - } else { - /* It worked, we can remove the backup */ - if (backup_filename) { - unlink(backup_filename); - } - if (!(opt & OPT_dry_run) - && ((dst_cur_line == 0) || (dst_beg_line == 0)) - ) { - /* The new patched file is empty, remove it */ - xunlink(new_filename); - // /* old_filename and new_filename may be the same file */ - // unlink(old_filename); } + + TT.hunknum++; + + continue; } - free(backup_filename); - //free(old_filename); - free(new_filename); - } /* end of "while there are patch lines" */ - quit: - /* 0 = SUCCESS - * 1 = Some hunks failed - * 2 = More serious problems (exited earlier) - */ - return ret; + + // If we didn't continue above, discard this line. + free(patchline); + } + + finish_oldfile(); + + if (ENABLE_FEATURE_CLEAN_UP) { + close(TT.filepatch); + free(oldname); + free(newname); + } + + return TT.exitval; } diff --git a/editors/patch_bbox.c b/editors/patch_bbox.c new file mode 100644 index 000000000..78aa5fde8 --- /dev/null +++ b/editors/patch_bbox.c @@ -0,0 +1,306 @@ +/* vi: set sw=4 ts=4: */ +/* + * busybox patch applet to handle the unified diff format. + * Copyright (C) 2003 Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + * + * This applet is written to work with patches generated by GNU diff, + * where there is equivalent functionality busybox patch shall behave + * as per GNU patch. + * + * There is a SUSv3 specification for patch, however it looks to be + * incomplete, it doesnt even mention unified diff format. + * http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html + * + * Issues + * - Non-interactive + * - Patches must apply cleanly or patch (not just one hunk) will fail. + * - Reject file isnt saved + */ + +#include "libbb.h" + +static unsigned copy_lines(FILE *src_stream, FILE *dst_stream, unsigned lines_count) +{ + while (src_stream && lines_count) { + char *line; + line = xmalloc_fgets(src_stream); + if (line == NULL) { + break; + } + if (fputs(line, dst_stream) == EOF) { + bb_perror_msg_and_die("error writing to new file"); + } + free(line); + lines_count--; + } + return lines_count; +} + +/* If patch_level is -1 it will remove all directory names + * char *line must be greater than 4 chars + * returns NULL if the file doesnt exist or error + * returns malloc'ed filename + * NB: frees 1st argument! + */ +static char *extract_filename(char *line, int patch_level, const char *pat) +{ + char *temp = NULL, *filename_start_ptr = line + 4; + + if (strncmp(line, pat, 4) == 0) { + /* Terminate string at end of source filename */ + line[strcspn(line, "\t\n\r")] = '\0'; + + /* Skip over (patch_level) number of leading directories */ + while (patch_level--) { + temp = strchr(filename_start_ptr, '/'); + if (!temp) + break; + filename_start_ptr = temp + 1; + } + temp = xstrdup(filename_start_ptr); + } + free(line); + return temp; +} + +int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int patch_main(int argc UNUSED_PARAM, char **argv) +{ + struct stat saved_stat; + char *patch_line; + FILE *patch_file; + int patch_level; + int ret = 0; + char plus = '+'; + unsigned opt; + enum { + OPT_R = (1 << 2), + OPT_N = (1 << 3), + /*OPT_f = (1 << 4), ignored */ + /*OPT_E = (1 << 5), ignored, this is the default */ + /*OPT_g = (1 << 6), ignored */ + OPT_dry_run = (1 << 7) * ENABLE_LONG_OPTS, + }; + + xfunc_error_retval = 2; + { + const char *p = "-1"; + const char *i = "-"; /* compat */ +#if ENABLE_LONG_OPTS + static const char patch_longopts[] ALIGN1 = + "strip\0" Required_argument "p" + "input\0" Required_argument "i" + "reverse\0" No_argument "R" + "forward\0" No_argument "N" + /* "Assume user knows what [s]he is doing, do not ask any questions": */ + "force\0" No_argument "f" /*ignored*/ +# if ENABLE_DESKTOP + "remove-empty-files\0" No_argument "E" /*ignored*/ + /* "Controls actions when a file is under RCS or SCCS control, + * and does not exist or is read-only and matches the default version, + * or when a file is under ClearCase control and does not exist..." + * IOW: rather obscure option. + * But Gentoo's portage does use -g0 */ + "get\0" Required_argument "g" /*ignored*/ +# endif + "dry-run\0" No_argument "\xfd" +# if ENABLE_DESKTOP + "backup-if-mismatch\0" No_argument "\xfe" /*ignored*/ + "no-backup-if-mismatch\0" No_argument "\xff" /*ignored*/ +# endif + ; + applet_long_options = patch_longopts; +#endif + /* -f,-E,-g are ignored */ + opt = getopt32(argv, "p:i:RN""fEg:", &p, &i, NULL); + if (opt & OPT_R) + plus = '-'; + patch_level = xatoi(p); /* can be negative! */ + patch_file = xfopen_stdin(i); + } + + patch_line = xmalloc_fgetline(patch_file); + while (patch_line) { + FILE *src_stream; + FILE *dst_stream; + //char *old_filename; + char *new_filename; + char *backup_filename = NULL; + unsigned src_cur_line = 1; + unsigned dst_cur_line = 0; + unsigned dst_beg_line; + unsigned bad_hunk_count = 0; + unsigned hunk_count = 0; + smallint copy_trailing_lines_flag = 0; + + /* Skip everything upto the "---" marker + * No need to parse the lines "Only in ", and "diff " + */ + do { + /* Extract the filename used before the patch was generated */ + new_filename = extract_filename(patch_line, patch_level, "--- "); + // was old_filename above + patch_line = xmalloc_fgetline(patch_file); + if (!patch_line) goto quit; + } while (!new_filename); + free(new_filename); // "source" filename is irrelevant + + new_filename = extract_filename(patch_line, patch_level, "+++ "); + if (!new_filename) { + bb_error_msg_and_die("invalid patch"); + } + + /* Get access rights from the file to be patched */ + if (stat(new_filename, &saved_stat) != 0) { + char *slash = strrchr(new_filename, '/'); + if (slash) { + /* Create leading directories */ + *slash = '\0'; + bb_make_directory(new_filename, -1, FILEUTILS_RECUR); + *slash = '/'; + } + src_stream = NULL; + saved_stat.st_mode = 0644; + } else if (!(opt & OPT_dry_run)) { + backup_filename = xasprintf("%s.orig", new_filename); + xrename(new_filename, backup_filename); + src_stream = xfopen_for_read(backup_filename); + } else + src_stream = xfopen_for_read(new_filename); + + if (opt & OPT_dry_run) { + dst_stream = xfopen_for_write("/dev/null"); + } else { + dst_stream = xfopen_for_write(new_filename); + fchmod(fileno(dst_stream), saved_stat.st_mode); + } + + printf("patching file %s\n", new_filename); + + /* Handle all hunks for this file */ + patch_line = xmalloc_fgets(patch_file); + while (patch_line) { + unsigned count; + unsigned src_beg_line; + unsigned hunk_offset_start; + unsigned src_last_line = 1; + unsigned dst_last_line = 1; + + if ((sscanf(patch_line, "@@ -%d,%d +%d,%d", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3) + && (sscanf(patch_line, "@@ -%d +%d,%d", &src_beg_line, &dst_beg_line, &dst_last_line) < 2) + ) { + /* No more hunks for this file */ + break; + } + if (plus != '+') { + /* reverse patch */ + unsigned tmp = src_last_line; + src_last_line = dst_last_line; + dst_last_line = tmp; + tmp = src_beg_line; + src_beg_line = dst_beg_line; + dst_beg_line = tmp; + } + hunk_count++; + + if (src_beg_line && dst_beg_line) { + /* Copy unmodified lines upto start of hunk */ + /* src_beg_line will be 0 if it's a new file */ + count = src_beg_line - src_cur_line; + if (copy_lines(src_stream, dst_stream, count)) { + bb_error_msg_and_die("bad src file"); + } + src_cur_line += count; + dst_cur_line += count; + copy_trailing_lines_flag = 1; + } + src_last_line += hunk_offset_start = src_cur_line; + dst_last_line += dst_cur_line; + + while (1) { + free(patch_line); + patch_line = xmalloc_fgets(patch_file); + if (patch_line == NULL) + break; /* EOF */ + if (!*patch_line) { + /* whitespace-damaged patch with "" lines */ + free(patch_line); + patch_line = xstrdup(" "); + } + if ((*patch_line != '-') && (*patch_line != '+') + && (*patch_line != ' ') + ) { + break; /* End of hunk */ + } + if (*patch_line != plus) { /* '-' or ' ' */ + char *src_line = NULL; + if (src_cur_line == src_last_line) + break; + if (src_stream) { + src_line = xmalloc_fgets(src_stream); + if (src_line) { + int diff = strcmp(src_line, patch_line + 1); + src_cur_line++; + free(src_line); + if (diff) + src_line = NULL; + } + } + /* Do not patch an already patched hunk with -N */ + if (src_line == 0 && (opt & OPT_N)) { + continue; + } + if (!src_line) { + bb_error_msg("hunk #%u FAILED at %u", hunk_count, hunk_offset_start); + bad_hunk_count++; + break; + } + if (*patch_line != ' ') { /* '-' */ + continue; + } + } + if (dst_cur_line == dst_last_line) + break; + fputs(patch_line + 1, dst_stream); + dst_cur_line++; + } /* end of while loop handling one hunk */ + } /* end of while loop handling one file */ + + /* Cleanup last patched file */ + if (copy_trailing_lines_flag) { + copy_lines(src_stream, dst_stream, (unsigned)(-1)); + } + if (src_stream) { + fclose(src_stream); + } + fclose(dst_stream); + if (bad_hunk_count) { + ret = 1; + bb_error_msg("%u out of %u hunk FAILED", bad_hunk_count, hunk_count); + } else { + /* It worked, we can remove the backup */ + if (backup_filename) { + unlink(backup_filename); + } + if (!(opt & OPT_dry_run) + && ((dst_cur_line == 0) || (dst_beg_line == 0)) + ) { + /* The new patched file is empty, remove it */ + xunlink(new_filename); + // /* old_filename and new_filename may be the same file */ + // unlink(old_filename); + } + } + free(backup_filename); + //free(old_filename); + free(new_filename); + } /* end of "while there are patch lines" */ + quit: + /* 0 = SUCCESS + * 1 = Some hunks failed + * 2 = More serious problems (exited earlier) + */ + return ret; +} diff --git a/editors/sed.c b/editors/sed.c index 7af8f867a..6cf54afe9 100644 --- a/editors/sed.c +++ b/editors/sed.c @@ -61,6 +61,10 @@ #include "libbb.h" #include "xregex.h" +enum { + OPT_in_place = 1 << 0, +}; + /* Each sed command turns into one of these structures. */ typedef struct sed_cmd_s { /* Ordered by alignment requirements: currently 36 bytes on x86 */ @@ -938,8 +942,11 @@ static void process_files(void) if (matched) { /* once matched, "n,xxx" range is dead, disabling it */ - if (sed_cmd->beg_line > 0) + if (sed_cmd->beg_line > 0 + && !(option_mask32 & OPT_in_place) /* but not for -i */ + ) { sed_cmd->beg_line = -2; + } sed_cmd->in_match = !( /* has the ending line come, or is this a single address command? */ (sed_cmd->end_line ? @@ -985,6 +992,8 @@ static void process_files(void) } /* actual sedding */ + //bb_error_msg("pattern_space:'%s' next_line:'%s' cmd:%c", + //pattern_space, next_line, sed_cmd->cmd); switch (sed_cmd->cmd) { /* Print line number */ @@ -1111,10 +1120,16 @@ static void process_files(void) { int len; /* If no next line, jump to end of script and exit. */ + /* http://www.gnu.org/software/sed/manual/sed.html: + * "Most versions of sed exit without printing anything + * when the N command is issued on the last line of + * a file. GNU sed prints pattern space before exiting + * unless of course the -n command switch has been + * specified. This choice is by design." + */ if (next_line == NULL) { - free(next_line); - next_line = NULL; - goto discard_line; + //goto discard_line; + goto discard_commands; /* GNU behavior */ } /* Append next_line, read new next_line. */ len = strlen(pattern_space); @@ -1270,9 +1285,6 @@ static void add_cmd_block(char *cmdstr) int sed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int sed_main(int argc UNUSED_PARAM, char **argv) { - enum { - OPT_in_place = 1 << 0, - }; unsigned opt; llist_t *opt_e, *opt_f; int status = EXIT_SUCCESS; @@ -1292,6 +1304,7 @@ int sed_main(int argc UNUSED_PARAM, char **argv) opt_e = opt_f = NULL; opt_complementary = "e::f::" /* can occur multiple times */ "nn"; /* count -n */ + /* -i must be first, to match OPT_in_place definition */ opt = getopt32(argv, "irne:f:", &opt_e, &opt_f, &G.be_quiet); /* counter for -n */ //argc -= optind; diff --git a/findutils/grep.c b/findutils/grep.c index ac290a911..183d02606 100644 --- a/findutils/grep.c +++ b/findutils/grep.c @@ -461,15 +461,19 @@ static int grep_file(FILE *file) if (found) print_line(gl->pattern, strlen(gl->pattern), linenum, ':'); } else while (1) { + unsigned start = gl->matched_range.rm_so; unsigned end = gl->matched_range.rm_eo; + unsigned len = end - start; char old = line[end]; line[end] = '\0'; - print_line(line + gl->matched_range.rm_so, - end - gl->matched_range.rm_so, - linenum, ':'); + /* Empty match is not printed: try "echo test | grep -o ''" */ + if (len != 0) + print_line(line + start, len, linenum, ':'); if (old == '\0') break; line[end] = old; + if (len == 0) + end++; #if !ENABLE_EXTRA_COMPAT if (regexec(&gl->compiled_regex, line + end, 1, &gl->matched_range, REG_NOTBOL) != 0) diff --git a/include/libbb.h b/include/libbb.h index e2a8322b8..9e9d556f1 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -417,6 +417,7 @@ int xopen3(const char *pathname, int flags, int mode) FAST_FUNC; int open_or_warn(const char *pathname, int flags) FAST_FUNC; int open3_or_warn(const char *pathname, int flags, int mode) FAST_FUNC; int open_or_warn_stdin(const char *pathname) FAST_FUNC; +int xopen_stdin(const char *pathname) FAST_FUNC; void xrename(const char *oldpath, const char *newpath) FAST_FUNC; int rename_or_warn(const char *oldpath, const char *newpath) FAST_FUNC; off_t xlseek(int fd, off_t offset, int whence) FAST_FUNC; diff --git a/libbb/wfopen_input.c b/libbb/wfopen_input.c index 46ff7a6de..422a58ecf 100644 --- a/libbb/wfopen_input.c +++ b/libbb/wfopen_input.c @@ -4,7 +4,7 @@ * * Copyright (C) 2003 Manuel Novoa III * - * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ /* A number of applets need to open a file for reading, where the filename @@ -46,3 +46,11 @@ int FAST_FUNC open_or_warn_stdin(const char *filename) return fd; } + +int FAST_FUNC xopen_stdin(const char *filename) +{ + int fd = open_or_warn_stdin(filename); + if (fd >= 0) + return fd; + xfunc_die(); /* We already output an error message. */ +} diff --git a/shell/ash.c b/shell/ash.c index 0337a5535..7cc5a913a 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -4515,6 +4515,7 @@ clear_traps(void) INT_ON; } } + may_have_traps = 0; } /* Lives far away from here, needed for forkchild */ diff --git a/shell/ash_test/ash-redir/redir9.tests b/shell/ash_test/ash-redir/redir9.tests old mode 100644 new mode 100755 diff --git a/shell/ash_test/ash-signals/signal7.right b/shell/ash_test/ash-signals/signal7.right new file mode 100644 index 000000000..ba7453e42 --- /dev/null +++ b/shell/ash_test/ash-signals/signal7.right @@ -0,0 +1 @@ +Bug detected: 0 diff --git a/shell/ash_test/ash-signals/signal7.tests b/shell/ash_test/ash-signals/signal7.tests new file mode 100755 index 000000000..c2b1381f9 --- /dev/null +++ b/shell/ash_test/ash-signals/signal7.tests @@ -0,0 +1,18 @@ +bug() { + trap : exit + # Bug was causing sh to be run in subshell, + # as if this line is replaced with (sh -c ...; exit $?) & + # here: + sh -c 'echo REAL_CHILD=$$' & + echo PARENTS_IDEA_OF_CHILD=$! + wait # make sure bkgd shell completes +} + +bug | { +while read varval; do + eval $varval +done +test x"$REAL_CHILD" != x"" \ +&& test x"$REAL_CHILD" = x"$PARENTS_IDEA_OF_CHILD" +echo "Bug detected: $?" +} diff --git a/shell/hush.c b/shell/hush.c index 31ca22a2e..01bc96ce6 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -3901,8 +3901,6 @@ static void insert_bg_job(struct pipe *pi) if (G_interactive_fd) printf("[%d] %d %s\n", job->jobid, job->cmds[0].pid, job->cmdtext); - /* Last command's pid goes to $! */ - G.last_bg_pid = job->cmds[job->num_cmds - 1].pid; G.last_jobid = job->jobid; } @@ -4825,6 +4823,8 @@ static int run_list(struct pipe *pi) if (G.run_list_level == 1) insert_bg_job(pi); #endif + /* Last command's pid goes to $! */ + G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; G.last_exitcode = rcode = EXIT_SUCCESS; debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); } else { diff --git a/shell/hush_test/hush-trap/signal7.right b/shell/hush_test/hush-trap/signal7.right new file mode 100644 index 000000000..ba7453e42 --- /dev/null +++ b/shell/hush_test/hush-trap/signal7.right @@ -0,0 +1 @@ +Bug detected: 0 diff --git a/shell/hush_test/hush-trap/signal7.tests b/shell/hush_test/hush-trap/signal7.tests new file mode 100755 index 000000000..c2b1381f9 --- /dev/null +++ b/shell/hush_test/hush-trap/signal7.tests @@ -0,0 +1,18 @@ +bug() { + trap : exit + # Bug was causing sh to be run in subshell, + # as if this line is replaced with (sh -c ...; exit $?) & + # here: + sh -c 'echo REAL_CHILD=$$' & + echo PARENTS_IDEA_OF_CHILD=$! + wait # make sure bkgd shell completes +} + +bug | { +while read varval; do + eval $varval +done +test x"$REAL_CHILD" != x"" \ +&& test x"$REAL_CHILD" = x"$PARENTS_IDEA_OF_CHILD" +echo "Bug detected: $?" +} diff --git a/shell/shell_common.c b/shell/shell_common.c index 3114ff3f7..dc363e298 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -428,9 +428,14 @@ shell_builtin_ulimit(char **argv) val <<= l->factor_shift; } //bb_error_msg("opt %c val_str:'%s' val:%lld", opt_char, val_str, (long long)val); + /* from man bash: "If neither -H nor -S + * is specified, both the soft and hard + * limits are set. */ + if (!opts) + opts = OPT_hard + OPT_soft; if (opts & OPT_hard) limit.rlim_max = val; - if ((opts & OPT_soft) || opts == 0) + if (opts & OPT_soft) limit.rlim_cur = val; //bb_error_msg("setrlimit(%d, %lld, %lld)", l->cmd, (long long)limit.rlim_cur, (long long)limit.rlim_max); if (setrlimit(l->cmd, &limit) < 0) { diff --git a/testsuite/grep.tests b/testsuite/grep.tests index d4bf80d83..e0135dcbb 100755 --- a/testsuite/grep.tests +++ b/testsuite/grep.tests @@ -98,5 +98,9 @@ testing "grep -o does not loop forever" \ 'grep -o "[^/]*$"' \ "test\n" \ "" "/var/test\n" +testing "grep -o does not loop forever on zero-length match" \ + 'grep -o "" | head -n1' \ + "" \ + "" "test\n" exit $FAILCOUNT diff --git a/testsuite/mdev.tests b/testsuite/mdev.tests index c375fc774..a46929b3a 100755 --- a/testsuite/mdev.tests +++ b/testsuite/mdev.tests @@ -37,6 +37,16 @@ brw-rw---- 1 0 0 8,0 sda "" "" SKIP= +# continuing to use directory structure from prev test +optional STATIC FEATURE_MDEV_CONF FEATURE_LS_TIMESTAMPS FEATURE_LS_USERNAME +testing "mdev deletes /block/sda" \ + "env - PATH=$PATH ACTION=remove DEVPATH=/block/sda chroot mdev.testdir /mdev 2>&1; + ls -ln mdev.testdir/dev | $FILTER_LS" \ +"\ +" \ + "" "" +SKIP= + # continuing to use directory structure from prev test rm -rf mdev.testdir/dev/* echo ".* 1:1 666" >mdev.testdir/etc/mdev.conf diff --git a/testsuite/patch.tests b/testsuite/patch.tests index 749d936ea..e482304f6 100755 --- a/testsuite/patch.tests +++ b/testsuite/patch.tests @@ -1,13 +1,13 @@ #!/bin/sh # Copyright 2008 by Denys Vlasenko -# Licensed under GPL v2, see file LICENSE for details. +# Licensed under GPLv2, see file LICENSE in this source tree. . ./testing.sh -# testing "test name" "options" "expected result" "file input" "stdin" +# testing "test name" "command(s)" "expected result" "file input" "stdin" testing "patch with old_file == new_file" \ - 'patch; echo $?; cat input' \ + 'patch 2>&1; echo $?; cat input' \ "\ patching file input 0 @@ -29,7 +29,7 @@ zxc " \ testing "patch with nonexistent old_file" \ - 'patch; echo $?; cat input' \ + 'patch 2>&1; echo $?; cat input' \ "\ patching file input 0 @@ -51,7 +51,7 @@ zxc " \ testing "patch -R with nonexistent old_file" \ - 'patch -R; echo $?; cat input' \ + 'patch -R 2>&1; echo $?; cat input' \ "\ patching file input 0 @@ -75,9 +75,12 @@ zxc testing "patch detects already applied hunk" \ 'patch 2>&1; echo $?; cat input' \ "\ +Possibly reversed hunk 1 at 2 +Hunk 1 FAILED 1/1. + abc ++def + 123 patching file input -patch: hunk #1 FAILED at 1 -patch: 1 out of 1 hunk FAILED 1 abc def @@ -97,13 +100,15 @@ def 123 " \ -# Currently fails (erroneously appends second "456" line): -false && testing "patch detects already applied hunk" \ +testing "patch detects already applied hunk at the EOF" \ 'patch 2>&1; echo $?; cat input' \ "\ +Possibly reversed hunk 1 at 3 +Hunk 1 FAILED 1/1. + abc + 123 ++456 patching file input -patch: hunk #1 FAILED at 2 -patch: 1 out of 1 hunk FAILED 1 abc 123 @@ -123,6 +128,53 @@ abc +456 " \ +# testing "test name" "command(s)" "expected result" "file input" "stdin" +testing "patch -N ignores already applied hunk" \ + 'patch -N 2>&1; echo $?; cat input' \ +"\ +patching file input +0 +abc +def +123 +" \ +"\ +abc +def +123 +" \ +"\ +--- input ++++ input +@@ -1,2 +1,3 @@ + abc ++def + 123 +" \ + +# testing "test name" "command(s)" "expected result" "file input" "stdin" +testing "patch FILE PATCH" \ + 'cat >a.patch; patch input a.patch 2>&1; echo $?; cat input; rm a.patch' \ +"\ +patching file input +0 +abc +def +123 +" \ +"\ +abc +123 +" \ +"\ +--- foo.old ++++ foo +@@ -1,2 +1,3 @@ + abc ++def + 123 +" \ + rm input.orig 2>/dev/null exit $FAILCOUNT diff --git a/testsuite/sed.tests b/testsuite/sed.tests index 3301a25f8..61551e31c 100755 --- a/testsuite/sed.tests +++ b/testsuite/sed.tests @@ -80,10 +80,18 @@ test x"$SKIP_KNOWN_BUGS" = x"" && { # Query: how does this interact with no newline at EOF? testing "sed n (flushes pattern space, terminates early)" "sed -e 'n;p'" \ "a\nb\nb\nc\n" "" "a\nb\nc\n" -# N does _not_ flush pattern space, therefore c is still in there @ script end. -testing "sed N (doesn't flush pattern space when terminating)" "sed -e 'N;p'" \ - "a\nb\na\nb\nc\n" "" "a\nb\nc\n" } +# non-GNU sed: N does _not_ flush pattern space, therefore c is eaten @ script end +# GNU sed: N flushes pattern space, therefore c is printed too @ script end +testing "sed N (flushes pattern space (GNU behavior))" "sed -e 'N;p'" \ + "a\nb\na\nb\nc\n" "" "a\nb\nc\n" + +testing "sed N test2" "sed ':a;N;s/\n/ /;ta'" \ + "a b c\n" "" "a\nb\nc\n" + +testing "sed N test3" "sed 'N;s/\n/ /'" \ + "a b\nc\n" "" "a\nb\nc\n" + testing "sed address match newline" 'sed "/b/N;/b\\nc/i woo"' \ "a\nwoo\nb\nc\nd\n" "" "a\nb\nc\nd\n" @@ -270,11 +278,16 @@ testing "sed a cmd ended by double backslash" \ | two \\ ' -# fisrt three lines are deleted; 4th line is matched and printed by "2,3" and by "4" ranges +# first three lines are deleted; 4th line is matched and printed by "2,3" and by "4" ranges testing "sed with N skipping lines past ranges on next cmds" \ "sed -n '1{N;N;d};1p;2,3p;3p;4p'" \ "4\n4\n" "" "1\n2\n3\n4\n" +testing "sed -i with address modifies all files, not only first" \ + "cp input input2; sed -i -e '1s/foo/bar/' input input2 && cat input input2; rm input2" \ + "bar\nbar\n" "foo\n" "" + + # testing "description" "arguments" "result" "infile" "stdin" exit $FAILCOUNT diff --git a/util-linux/mdev.c b/util-linux/mdev.c index b4042c07e..cd6c1a89d 100644 --- a/util-linux/mdev.c +++ b/util-linux/mdev.c @@ -132,6 +132,7 @@ static void make_device(char *path, int delete) major = -1; } } + /* else: for delete, -1 still deletes the node, but < -1 suppresses that */ /* Determine device name, type, major and minor */ device_name = (char*) bb_basename(path); @@ -279,7 +280,7 @@ static void make_device(char *path, int delete) if (aliaslink == '!' && s == a+1) { val = st; /* "!": suppress node creation/deletion */ - major = -1; + major = -2; } else if (aliaslink == '>' || aliaslink == '=') { val = st; @@ -379,7 +380,7 @@ static void make_device(char *path, int delete) free(command); } - if (delete && major >= 0) { + if (delete && major >= -1) { if (ENABLE_FEATURE_MDEV_RENAME && alias) { if (aliaslink == '>') unlink(device_name); -- 2.25.1