ntpd: default to FEATURE_NTP_AUTH=y
[oweals/busybox.git] / editors / patch.c
index 4fadbb8fe991d4357e6ae457bb2f6de048064b5b..eca6bc5f6c4f941f306eb26ac5178ac6906fb3c6 100644 (file)
  * -F fuzz (number, default 2)
  * [file] which file to patch
  */
-
-//applet:IF_PATCH(APPLET(patch, _BB_DIR_USR_BIN, _BB_SUID_DROP))
-
-//kbuild:lib-$(CONFIG_PATCH) += patch.o
-
 //config:config PATCH
-//config:      bool "patch"
+//config:      bool "patch (9.1 kb)"
 //config:      default y
 //config:      help
-//config:        Apply a unified diff formatted patch.
+//config:      Apply a unified diff formatted patch.
+
+//applet:IF_PATCH(APPLET(patch, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_PATCH) += patch.o
 
 //usage:#define patch_trivial_usage
 //usage:       "[OPTIONS] [ORIGFILE [PATCHFILE]]"
 //usage:#define patch_full_usage "\n\n"
-//usage:       IF_LONG_OPTS(
-//usage:       "       -p,--strip N            Strip N leading components from file names"
-//usage:     "\n       -i,--input DIFF         Read DIFF instead of stdin"
-//usage:     "\n       -R,--reverse            Reverse patch"
-//usage:     "\n       -N,--forward            Ignore already applied patches"
-//usage:     "\n       --dry-run               Don't actually change files"
-//usage:     "\n       -E,--remove-empty-files Remove output files if they become empty"
-//usage:       )
-//usage:       IF_NOT_LONG_OPTS(
 //usage:       "       -p N    Strip N leading components from file names"
 //usage:     "\n       -i DIFF Read DIFF instead of stdin"
 //usage:     "\n       -R      Reverse patch"
 //usage:     "\n       -N      Ignore already applied patches"
 //usage:     "\n       -E      Remove output files if they become empty"
+//usage:       IF_LONG_OPTS(
+//usage:     "\n       --dry-run       Don't actually change files"
 //usage:       )
+/* -u "interpret as unified diff" is supported but not documented: this info is not useful for --help */
 //usage:
 //usage:#define patch_example_usage
 //usage:       "$ patch -p1 < example.diff\n"
 
 #include "libbb.h"
 
+#define PATCH_DEBUG  0
+
+// libbb candidate?
+
 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)
-{
-       // 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))
+// Call freeit() on each element before freeing it.
+static void dlist_free(struct double_list *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;
+               void *pop = list;
+               list = list->next;
+               freeit(pop);
+               // Bail out also if list is circular.
+               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)
+// Add an entry before "list" element in (circular) doubly linked list
+static struct double_list *dlist_add(struct double_list **list, char *data)
 {
-       struct double_list *line = xmalloc(sizeof(struct double_list));
+       struct double_list *llist;
+       struct double_list *line = xmalloc(sizeof(*line));
 
        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;
+       llist = *list;
+       if (llist) {
+               struct double_list *p;
+               line->next = llist;
+               p = line->prev = llist->prev;
+               // (list is circular, we assume p is never NULL)
+               p->next = line;
+               llist->prev = line;
+       } else
+               *list = line->next = line->prev = line;
 
        return line;
 }
 
 
-
 struct globals {
        char *infile;
        long prefix;
 
        struct double_list *current_hunk;
+
        long oldline, oldlen, newline, newlen;
        long linenum;
-       int context, state, filein, fileout, hunknum;
+       int context, state, hunknum;
+       int filein, fileout;
        char *tempname;
 
-       // was toys.foo:
        int exitval;
 };
 #define TT (*ptr_to_globals)
@@ -133,16 +114,18 @@ struct globals {
 } while (0)
 
 
-#define FLAG_STR "Rup:i:NEx"
+#define FLAG_STR "Rup:i:NEfg"
 /* 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)
-#define FLAG_RMEMPTY (1 << 5)
-//non-standard:
-#define FLAG_DEBUG   (1 << 6)
+#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)
+#define FLAG_RMEMPTY  (1 << 5)
+#define FLAG_f_unused (1 << 6)
+#define FLAG_g_unused (1 << 7)
+#define FLAG_dry_run  ((1 << 8) * ENABLE_LONG_OPTS)
+
 
 // Dispose of a line of input, either by writing it out or discarding it.
 
@@ -151,8 +134,6 @@ struct globals {
 // 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 = data;
@@ -179,12 +160,14 @@ static void finish_oldfile(void)
                }
                xclose(TT.fileout);
 
-               temp = xstrdup(TT.tempname);
-               temp[strlen(temp) - 6] = '\0';
-               rename(TT.tempname, temp);
-               free(temp);
+               if (!ENABLE_LONG_OPTS || TT.tempname[0]) { /* not --dry-run? */
+                       temp = xstrdup(TT.tempname);
+                       temp[strlen(temp) - 6] = '\0';
+                       rename(TT.tempname, temp);
+                       free(temp);
+                       free(TT.tempname);
+               }
 
-               free(TT.tempname);
                TT.tempname = NULL;
        }
        TT.fileout = TT.filein = -1;
@@ -193,7 +176,6 @@ static void finish_oldfile(void)
 static void fail_hunk(void)
 {
        if (!TT.current_hunk) return;
-       TT.current_hunk->prev->next = NULL;
 
        fdprintf(2, "Hunk %d FAILED %ld/%ld.\n", TT.hunknum, TT.oldline, TT.newline);
        TT.exitval = 1;
@@ -202,14 +184,17 @@ static void fail_hunk(void)
        // this file and advance to next file.
 
        TT.state = 2;
-       llist_free(TT.current_hunk, do_line);
+       TT.current_hunk->prev->next = NULL;
+       dlist_free(TT.current_hunk, do_line);
        TT.current_hunk = NULL;
 
        // Abort the copy and delete the temporary file.
        close(TT.filein);
        close(TT.fileout);
-       unlink(TT.tempname);
-       free(TT.tempname);
+       if (!ENABLE_LONG_OPTS || TT.tempname[0]) { /* not --dry-run? */
+               unlink(TT.tempname);
+               free(TT.tempname);
+       }
        TT.tempname = NULL;
 
        TT.state = 0;
@@ -240,7 +225,7 @@ static int apply_one_hunk(void)
                else matcheof = 0;
                if (PATCH_DEBUG) fdprintf(2, "HUNK:%s\n", plist->data);
        }
-       matcheof = matcheof < TT.context;
+       matcheof = !matcheof || matcheof < TT.context;
 
        if (PATCH_DEBUG) fdprintf(2,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N');
 
@@ -249,15 +234,16 @@ static int apply_one_hunk(void)
        // complete hunk.
        plist = TT.current_hunk;
        buf = NULL;
-       if (TT.context) for (;;) {
-               char *data = xmalloc_reads(TT.filein, NULL, NULL);
+       if (reverse ? TT.oldlen : TT.newlen) for (;;) {
+//FIXME: this performs 1-byte reads:
+               char *data = xmalloc_reads(TT.filein, NULL);
 
                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 (data && strcmp(data, plist->data+1) == 0) {
                                if (!backwarn) {
                                        backwarn = TT.linenum;
                                        if (option_mask32 & FLAG_IGNORE) {
@@ -300,15 +286,28 @@ static int apply_one_hunk(void)
                // out of buffer.
 
                for (;;) {
+                       while (plist && *plist->data == "+-"[reverse]) {
+                               if (strcmp(check->data, plist->data+1) == 0
+                                && !backwarn
+                               ) {
+                                       backwarn = TT.linenum;
+                                       if (option_mask32 & FLAG_IGNORE) {
+                                               dummy_revert = 1;
+                                               reverse ^= 1;
+                                       }
+                               }
+                               plist = plist->next;
+                       }
                        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);
+                                       fdprintf(2, "NOT: %s\n", plist ? plist->data : "EOF");
 
                                TT.state = 3;
-                               check = llist_pop(&buf);
+                               check = buf;
+                               buf = buf->next;
                                check->prev->next = buf;
                                buf->prev = check->prev;
                                do_line(check);
@@ -335,13 +334,13 @@ static int apply_one_hunk(void)
 out:
        // We have a match.  Emit changed data.
        TT.state = "-+"[reverse ^ dummy_revert];
-       llist_free(TT.current_hunk, do_line);
+       dlist_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);
+               dlist_free(buf, do_line);
        }
 
        return TT.state;
@@ -354,6 +353,8 @@ done:
 // state 1: Found +++ file indicator, look for @@
 // state 2: In hunk: counting initial context lines
 // state 3: In hunk: getting body
+// Like GNU patch, we don't require a --- line before the +++, and
+// also allow the --- after the +++ line.
 
 int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int patch_main(int argc UNUSED_PARAM, char **argv)
@@ -362,10 +363,48 @@ int patch_main(int argc UNUSED_PARAM, char **argv)
        int reverse, state = 0;
        char *oldname = NULL, *newname = NULL;
        char *opt_p, *opt_i;
+       long oldlen = oldlen; /* for compiler */
+       long newlen = newlen; /* for compiler */
+
+#if ENABLE_LONG_OPTS
+       static const char patch_longopts[] ALIGN1 =
+               "reverse\0"               No_argument       "R"
+               "unified\0"               No_argument       "u"
+               "strip\0"                 Required_argument "p"
+               "input\0"                 Required_argument "i"
+               "forward\0"               No_argument       "N"
+# if ENABLE_DESKTOP
+               "remove-empty-files\0"    No_argument       "E" /*ignored*/
+               /* "debug"                Required_argument "x" */
+# endif
+               /* "Assume user knows what [s]he is doing, do not ask any questions": */
+               "force\0"                 No_argument       "f" /*ignored*/
+# if ENABLE_DESKTOP
+               /* "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
+               ;
+#endif
 
        INIT_TT();
 
+#if ENABLE_LONG_OPTS
+       opts = getopt32long(argv, FLAG_STR, patch_longopts, &opt_p, &opt_i);
+#else
        opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i);
+#endif
+       //bb_error_msg_and_die("opts:%x", opts);
+
        argv += optind;
        reverse = opts & FLAG_REVERSE;
        TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative!
@@ -377,10 +416,6 @@ int patch_main(int argc UNUSED_PARAM, char **argv)
                        xmove_fd(xopen_stdin(argv[1]), STDIN_FILENO);
                }
        }
-       if (argv[0]) {
-               oldname = xstrdup(argv[0]);
-               newname = xstrdup(argv[0]);
-       }
 
        // Loop through the lines in the patch
        for(;;) {
@@ -401,8 +436,8 @@ int patch_main(int argc UNUSED_PARAM, char **argv)
                        if (*patchline==' ' || *patchline=='+' || *patchline=='-') {
                                dlist_add(&TT.current_hunk, patchline);
 
-                               if (*patchline != '+') TT.oldlen--;
-                               if (*patchline != '-') TT.newlen--;
+                               if (*patchline != '+') oldlen--;
+                               if (*patchline != '-') newlen--;
 
                                // Context line?
                                if (*patchline==' ' && state==2) TT.context++;
@@ -410,7 +445,7 @@ int patch_main(int argc UNUSED_PARAM, char **argv)
 
                                // If we've consumed all expected hunk lines, apply the hunk.
 
-                               if (!TT.oldlen && !TT.newlen) state = apply_one_hunk();
+                               if (!oldlen && !newlen) state = apply_one_hunk();
                                continue;
                        }
                        fail_hunk();
@@ -419,7 +454,7 @@ int patch_main(int argc UNUSED_PARAM, char **argv)
                }
 
                // Open a new file?
-               if (!strncmp("--- ", patchline, 4) || !strncmp("+++ ", patchline, 4)) {
+               if (is_prefixed_with(patchline, "--- ") || is_prefixed_with(patchline, "+++ ")) {
                        char *s, **name = reverse ? &newname : &oldname;
                        int i;
 
@@ -451,47 +486,63 @@ int patch_main(int argc UNUSED_PARAM, char **argv)
 
                // 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)) {
+               } else if (state == 1 && is_prefixed_with(patchline, "@@ -")) {
                        int i;
                        char *s = patchline+4;
 
                        // Read oldline[,oldlen] +newline[,newlen]
 
-                       TT.oldlen = TT.newlen = 1;
+                       TT.oldlen = oldlen = TT.newlen = newlen = 1;
                        TT.oldline = strtol(s, &s, 10);
-                       if (*s == ',') TT.oldlen=strtol(s+1, &s, 10);
+                       if (*s == ',') TT.oldlen = oldlen = strtol(s+1, &s, 10);
                        TT.newline = strtol(s+2, &s, 10);
-                       if (*s == ',') TT.newlen = strtol(s+1, &s, 10);
+                       if (*s == ',') TT.newlen = newlen = strtol(s+1, &s, 10);
+
+                       if (oldlen < 1 && newlen < 1)
+                               bb_error_msg_and_die("Really? %s", patchline);
 
                        TT.context = 0;
                        state = 2;
 
+                       // If the --- line is missing or malformed, either oldname
+                       // or (for -R) newname could be NULL -- but not both.  Like
+                       // GNU patch, proceed based on the +++ line, and avoid SEGVs.
+                       if (!oldname)
+                               oldname = xstrdup("MISSING_FILENAME");
+                       if (!newname)
+                               newname = xstrdup("MISSING_FILENAME");
+
                        // If this is the first hunk, open the file.
                        if (TT.filein == -1) {
                                int oldsum, newsum, empty = 0;
                                char *name;
 
-                               oldsum = TT.oldline + TT.oldlen;
-                               newsum = TT.newline + TT.newlen;
+                               oldsum = TT.oldline + oldlen;
+                               newsum = TT.newline + 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))
-                               {
+                               if (strcmp(name, "/dev/null") == 0 || !(reverse ? oldsum : newsum)) {
                                        name = reverse ? newname : oldname;
-                                       empty++;
+                                       empty = 1;
                                }
 
-                               // handle -p path truncation.
-                               for (i=0, s = name; *s;) {
-                                       if ((option_mask32 & FLAG_PATHLEN) && TT.prefix == i) break;
-                                       if (*(s++)=='/') {
-                                               name = s;
-                                               i++;
-                                       }
+                               // Handle -p path truncation.
+                               for (i = 0, s = name; *s;) {
+                                       if ((option_mask32 & FLAG_PATHLEN) && TT.prefix == i)
+                                               break;
+                                       if (*s++ != '/')
+                                               continue;
+                                       while (*s == '/')
+                                               s++;
+                                       i++;
+                                       name = s;
                                }
+                               // If "patch FILE_TO_PATCH", completely ignore name from patch
+                               if (argv[0])
+                                       name = argv[0];
 
                                if (empty) {
                                        // File is empty after the patches have been applied
@@ -499,36 +550,46 @@ int patch_main(int argc UNUSED_PARAM, char **argv)
                                        if (option_mask32 & FLAG_RMEMPTY) {
                                                // If flag -E or --remove-empty-files is set
                                                printf("removing %s\n", name);
-                                               xunlink(name);
+                                               if (!(opts & FLAG_dry_run))
+                                                       xunlink(name);
                                        } else {
                                                printf("patching file %s\n", name);
-                                               xclose(xopen(name, O_WRONLY | O_TRUNC));
+                                               if (!(opts & FLAG_dry_run))
+                                                       xclose(xopen(name, O_WRONLY | O_TRUNC));
                                        }
                                // If we've got a file to open, do so.
                                } else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) {
                                        struct stat statbuf;
 
                                        // If the old file was null, we're creating a new one.
-                                       if (!strcmp(oldname, "/dev/null") || !oldsum) {
+                                       if (strcmp(oldname, "/dev/null") == 0 || !oldsum) {
                                                printf("creating %s\n", name);
-                                               s = strrchr(name, '/');
-                                               if (s) {
-                                                       *s = 0;
-                                                       bb_make_directory(name, -1, FILEUTILS_RECUR);
-                                                       *s = '/';
+                                               if (!(opts & FLAG_dry_run)) {
+                                                       s = strrchr(name, '/');
+                                                       if (s) {
+                                                               *s = '\0';
+                                                               bb_make_directory(name, -1, FILEUTILS_RECUR);
+                                                               *s = '/';
+                                                       }
+                                                       TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR);
+                                               } else {
+                                                       TT.filein = xopen("/dev/null", O_RDONLY);
                                                }
-                                               TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR);
                                        } else {
                                                printf("patching file %s\n", name);
                                                TT.filein = xopen(name, O_RDONLY);
                                        }
 
-                                       TT.tempname = xasprintf("%sXXXXXX", name);
-                                       TT.fileout = xmkstemp(TT.tempname);
-                                       // Set permissions of output file
-                                       fstat(TT.filein, &statbuf);
-                                       fchmod(TT.fileout, statbuf.st_mode);
-
+                                       if (!(opts & FLAG_dry_run)) {
+                                               TT.tempname = xasprintf("%sXXXXXX", name);
+                                               TT.fileout = xmkstemp(TT.tempname);
+                                               // Set permissions of output file
+                                               fstat(TT.filein, &statbuf);
+                                               fchmod(TT.fileout, statbuf.st_mode);
+                                       } else {
+                                               TT.tempname = (char*)"";
+                                               TT.fileout = xopen("/dev/null", O_WRONLY);
+                                       }
                                        TT.linenum = 0;
                                        TT.hunknum = 0;
                                }