busybox/editors/patch.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4:
   2 *
   3 * Apply a "universal" diff.
   4 * Adapted from toybox's patch implementation.
   5 *
   6 * Copyright 2007 Rob Landley <rob@landley.net>
   7 *
   8 * see http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html
   9 * (But only does -u, because who still cares about "ed"?)
  10 *
  11 * TODO:
  12 * -b backup
  13 * -l treat all whitespace as a single space
  14 * -d chdir first
  15 * -D define wrap #ifdef and #ifndef around changes
  16 * -o outfile output here instead of in place
  17 * -r rejectfile write rejected hunks to this file
  18 *
  19 * -f force (no questions asked)
  20 * -F fuzz (number, default 2)
  21 * [file] which file to patch
  22 */
  23//config:config PATCH
  24//config:       bool "patch (9.4 kb)"
  25//config:       default y
  26//config:       help
  27//config:       Apply a unified diff formatted patch.
  28
  29//applet:IF_PATCH(APPLET(patch, BB_DIR_USR_BIN, BB_SUID_DROP))
  30
  31//kbuild:lib-$(CONFIG_PATCH) += patch.o
  32
  33//usage:#define patch_trivial_usage
  34//usage:       "[OPTIONS] [ORIGFILE [PATCHFILE]]"
  35//usage:#define patch_full_usage "\n\n"
  36//usage:       "        -p N    Strip N leading components from file names"
  37//usage:     "\n        -i DIFF Read DIFF instead of stdin"
  38//usage:     "\n        -R      Reverse patch"
  39//usage:     "\n        -N      Ignore already applied patches"
  40//usage:     "\n        -E      Remove output files if they become empty"
  41//usage:        IF_LONG_OPTS(
  42//usage:     "\n        --dry-run       Don't actually change files"
  43//usage:        )
  44/* -u "interpret as unified diff" is supported but not documented: this info is not useful for --help */
  45//usage:
  46//usage:#define patch_example_usage
  47//usage:       "$ patch -p1 < example.diff\n"
  48//usage:       "$ patch -p0 -i example.diff"
  49
  50#include "libbb.h"
  51
  52#define PATCH_DEBUG  0
  53
  54// libbb candidate?
  55
  56struct double_list {
  57        struct double_list *next;
  58        struct double_list *prev;
  59        char *data;
  60};
  61
  62// Free all the elements of a linked list
  63// Call freeit() on each element before freeing it.
  64static void dlist_free(struct double_list *list, void (*freeit)(void *data))
  65{
  66        while (list) {
  67                void *pop = list;
  68                list = list->next;
  69                freeit(pop);
  70                // Bail out also if list is circular.
  71                if (list == pop) break;
  72        }
  73}
  74
  75// Add an entry before "list" element in (circular) doubly linked list
  76static struct double_list *dlist_add(struct double_list **list, char *data)
  77{
  78        struct double_list *llist;
  79        struct double_list *line = xmalloc(sizeof(*line));
  80
  81        line->data = data;
  82        llist = *list;
  83        if (llist) {
  84                struct double_list *p;
  85                line->next = llist;
  86                p = line->prev = llist->prev;
  87                // (list is circular, we assume p is never NULL)
  88                p->next = line;
  89                llist->prev = line;
  90        } else
  91                *list = line->next = line->prev = line;
  92
  93        return line;
  94}
  95
  96
  97struct globals {
  98        char *infile;
  99        long prefix;
 100
 101        struct double_list *current_hunk;
 102
 103        long oldline, oldlen, newline, newlen;
 104        long linenum;
 105        int context, state, hunknum;
 106        int filein, fileout;
 107        char *tempname;
 108
 109        int exitval;
 110};
 111#define TT (*ptr_to_globals)
 112#define INIT_TT() do { \
 113        SET_PTR_TO_GLOBALS(xzalloc(sizeof(TT))); \
 114} while (0)
 115
 116
 117#define FLAG_STR "Rup:i:NEfg"
 118/* FLAG_REVERSE must be == 1! Code uses this fact. */
 119#define FLAG_REVERSE  (1 << 0)
 120#define FLAG_u        (1 << 1)
 121#define FLAG_PATHLEN  (1 << 2)
 122#define FLAG_INPUT    (1 << 3)
 123#define FLAG_IGNORE   (1 << 4)
 124#define FLAG_RMEMPTY  (1 << 5)
 125#define FLAG_f_unused (1 << 6)
 126#define FLAG_g_unused (1 << 7)
 127#define FLAG_dry_run  ((1 << 8) * ENABLE_LONG_OPTS)
 128
 129
 130// Dispose of a line of input, either by writing it out or discarding it.
 131
 132// state < 2: just free
 133// state = 2: write whole line to stderr
 134// state = 3: write whole line to fileout
 135// state > 3: write line+1 to fileout when *line != state
 136
 137static void do_line(void *data)
 138{
 139        struct double_list *dlist = data;
 140
 141        if (TT.state>1 && *dlist->data != TT.state)
 142                fdprintf(TT.state == 2 ? 2 : TT.fileout,
 143                        "%s\n", dlist->data+(TT.state>3 ? 1 : 0));
 144
 145        if (PATCH_DEBUG) fdprintf(2, "DO %d: %s\n", TT.state, dlist->data);
 146
 147        free(dlist->data);
 148        free(dlist);
 149}
 150
 151static void finish_oldfile(void)
 152{
 153        if (TT.tempname) {
 154                // Copy the rest of the data and replace the original with the copy.
 155                char *temp;
 156
 157                if (TT.filein != -1) {
 158                        bb_copyfd_eof(TT.filein, TT.fileout);
 159                        xclose(TT.filein);
 160                }
 161                xclose(TT.fileout);
 162
 163                if (!ENABLE_LONG_OPTS || TT.tempname[0]) { /* not --dry-run? */
 164                        temp = xstrdup(TT.tempname);
 165                        temp[strlen(temp) - 6] = '\0';
 166                        rename(TT.tempname, temp);
 167                        free(temp);
 168                        free(TT.tempname);
 169                }
 170
 171                TT.tempname = NULL;
 172        }
 173        TT.fileout = TT.filein = -1;
 174}
 175
 176static void fail_hunk(void)
 177{
 178        if (!TT.current_hunk) return;
 179
 180        fdprintf(2, "Hunk %d FAILED %ld/%ld.\n", TT.hunknum, TT.oldline, TT.newline);
 181        TT.exitval = 1;
 182
 183        // If we got to this point, we've seeked to the end.  Discard changes to
 184        // this file and advance to next file.
 185
 186        TT.state = 2;
 187        TT.current_hunk->prev->next = NULL;
 188        dlist_free(TT.current_hunk, do_line);
 189        TT.current_hunk = NULL;
 190
 191        // Abort the copy and delete the temporary file.
 192        close(TT.filein);
 193        close(TT.fileout);
 194        if (!ENABLE_LONG_OPTS || TT.tempname[0]) { /* not --dry-run? */
 195                unlink(TT.tempname);
 196                free(TT.tempname);
 197        }
 198        TT.tempname = NULL;
 199
 200        TT.state = 0;
 201}
 202
 203// Given a hunk of a unified diff, make the appropriate change to the file.
 204// This does not use the location information, but instead treats a hunk
 205// as a sort of regex.  Copies data from input to output until it finds
 206// the change to be made, then outputs the changed data and returns.
 207// (Finding EOF first is an error.)  This is a single pass operation, so
 208// multiple hunks must occur in order in the file.
 209
 210static int apply_one_hunk(void)
 211{
 212        struct double_list *plist, *buf = NULL, *check;
 213        int matcheof = 0, reverse = option_mask32 & FLAG_REVERSE, backwarn = 0;
 214        /* Do we try "dummy" revert to check whether
 215         * to silently skip this hunk? Used to implement -N.
 216         */
 217        int dummy_revert = 0;
 218
 219        // Break doubly linked list so we can use singly linked traversal function.
 220        TT.current_hunk->prev->next = NULL;
 221
 222        // Match EOF if there aren't as many ending context lines as beginning
 223        for (plist = TT.current_hunk; plist; plist = plist->next) {
 224                if (plist->data[0]==' ') matcheof++;
 225                else matcheof = 0;
 226                if (PATCH_DEBUG) fdprintf(2, "HUNK:%s\n", plist->data);
 227        }
 228        matcheof = !matcheof || matcheof < TT.context;
 229
 230        if (PATCH_DEBUG) fdprintf(2,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N');
 231
 232        // Loop through input data searching for this hunk.  Match all context
 233        // lines and all lines to be removed until we've found the end of a
 234        // complete hunk.
 235        plist = TT.current_hunk;
 236        buf = NULL;
 237        if (reverse ? TT.oldlen : TT.newlen) for (;;) {
 238//FIXME: this performs 1-byte reads:
 239                char *data = xmalloc_reads(TT.filein, NULL);
 240
 241                TT.linenum++;
 242
 243                // Figure out which line of hunk to compare with next.  (Skip lines
 244                // of the hunk we'd be adding.)
 245                while (plist && *plist->data == "+-"[reverse]) {
 246                        if (data && strcmp(data, plist->data+1) == 0) {
 247                                if (!backwarn) {
 248                                        backwarn = TT.linenum;
 249                                        if (option_mask32 & FLAG_IGNORE) {
 250                                                dummy_revert = 1;
 251                                                reverse ^= 1;
 252                                                continue;
 253                                        }
 254                                }
 255                        }
 256                        plist = plist->next;
 257                }
 258
 259                // Is this EOF?
 260                if (!data) {
 261                        if (PATCH_DEBUG) fdprintf(2, "INEOF\n");
 262
 263                        // Does this hunk need to match EOF?
 264                        if (!plist && matcheof) break;
 265
 266                        if (backwarn)
 267                                fdprintf(2,"Possibly reversed hunk %d at %ld\n",
 268                                        TT.hunknum, TT.linenum);
 269
 270                        // File ended before we found a place for this hunk.
 271                        fail_hunk();
 272                        goto done;
 273                }
 274
 275                if (PATCH_DEBUG) fdprintf(2, "IN: %s\n", data);
 276                check = dlist_add(&buf, data);
 277
 278                // Compare this line with next expected line of hunk.
 279                // todo: teach the strcmp() to ignore whitespace.
 280
 281                // A match can fail because the next line doesn't match, or because
 282                // we hit the end of a hunk that needed EOF, and this isn't EOF.
 283
 284                // If match failed, flush first line of buffered data and
 285                // recheck buffered data for a new match until we find one or run
 286                // out of buffer.
 287
 288                for (;;) {
 289                        while (plist && *plist->data == "+-"[reverse]) {
 290                                if (strcmp(check->data, plist->data+1) == 0
 291                                 && !backwarn
 292                                ) {
 293                                        backwarn = TT.linenum;
 294                                        if (option_mask32 & FLAG_IGNORE) {
 295                                                dummy_revert = 1;
 296                                                reverse ^= 1;
 297                                        }
 298                                }
 299                                plist = plist->next;
 300                        }
 301                        if (!plist || strcmp(check->data, plist->data+1)) {
 302                                // Match failed.  Write out first line of buffered data and
 303                                // recheck remaining buffered data for a new match.
 304
 305                                if (PATCH_DEBUG)
 306                                        fdprintf(2, "NOT: %s\n", plist ? plist->data : "EOF");
 307
 308                                TT.state = 3;
 309                                check = buf;
 310                                buf = buf->next;
 311                                check->prev->next = buf;
 312                                buf->prev = check->prev;
 313                                do_line(check);
 314                                plist = TT.current_hunk;
 315
 316                                // If we've reached the end of the buffer without confirming a
 317                                // match, read more lines.
 318                                if (check == buf) {
 319                                        buf = NULL;
 320                                        break;
 321                                }
 322                                check = buf;
 323                        } else {
 324                                if (PATCH_DEBUG)
 325                                        fdprintf(2, "MAYBE: %s\n", plist->data);
 326                                // This line matches.  Advance plist, detect successful match.
 327                                plist = plist->next;
 328                                if (!plist && !matcheof) goto out;
 329                                check = check->next;
 330                                if (check == buf) break;
 331                        }
 332                }
 333        }
 334out:
 335        // We have a match.  Emit changed data.
 336        TT.state = "-+"[reverse ^ dummy_revert];
 337        dlist_free(TT.current_hunk, do_line);
 338        TT.current_hunk = NULL;
 339        TT.state = 1;
 340done:
 341        if (buf) {
 342                buf->prev->next = NULL;
 343                dlist_free(buf, do_line);
 344        }
 345
 346        return TT.state;
 347}
 348
 349// Read a patch file and find hunks, opening/creating/deleting files.
 350// Call apply_one_hunk() on each hunk.
 351
 352// state 0: Not in a hunk, look for +++.
 353// state 1: Found +++ file indicator, look for @@
 354// state 2: In hunk: counting initial context lines
 355// state 3: In hunk: getting body
 356// Like GNU patch, we don't require a --- line before the +++, and
 357// also allow the --- after the +++ line.
 358
 359int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 360int patch_main(int argc UNUSED_PARAM, char **argv)
 361{
 362        int opts;
 363        int reverse, state = 0;
 364        char *oldname = NULL, *newname = NULL;
 365        char *opt_p, *opt_i;
 366        long oldlen = oldlen; /* for compiler */
 367        long newlen = newlen; /* for compiler */
 368
 369#if ENABLE_LONG_OPTS
 370        static const char patch_longopts[] ALIGN1 =
 371                "reverse\0"               No_argument       "R"
 372                "unified\0"               No_argument       "u"
 373                "strip\0"                 Required_argument "p"
 374                "input\0"                 Required_argument "i"
 375                "forward\0"               No_argument       "N"
 376# if ENABLE_DESKTOP
 377                "remove-empty-files\0"    No_argument       "E" /*ignored*/
 378                /* "debug"                Required_argument "x" */
 379# endif
 380                /* "Assume user knows what [s]he is doing, do not ask any questions": */
 381                "force\0"                 No_argument       "f" /*ignored*/
 382# if ENABLE_DESKTOP
 383                /* "Controls actions when a file is under RCS or SCCS control,
 384                 * and does not exist or is read-only and matches the default version,
 385                 * or when a file is under ClearCase control and does not exist..."
 386                 * IOW: rather obscure option.
 387                 * But Gentoo's portage does use -g0
 388                 */
 389                "get\0"                   Required_argument "g" /*ignored*/
 390# endif
 391                "dry-run\0"               No_argument       "\xfd"
 392# if ENABLE_DESKTOP
 393                "backup-if-mismatch\0"    No_argument       "\xfe" /*ignored*/
 394                "no-backup-if-mismatch\0" No_argument       "\xff" /*ignored*/
 395# endif
 396                ;
 397#endif
 398
 399        INIT_TT();
 400
 401#if ENABLE_LONG_OPTS
 402        opts = getopt32long(argv, FLAG_STR, patch_longopts, &opt_p, &opt_i);
 403#else
 404        opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i);
 405#endif
 406        //bb_error_msg_and_die("opts:%x", opts);
 407
 408        argv += optind;
 409        reverse = opts & FLAG_REVERSE;
 410        TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative!
 411        TT.filein = TT.fileout = -1;
 412        if (opts & FLAG_INPUT) {
 413                xmove_fd(xopen_stdin(opt_i), STDIN_FILENO);
 414        } else {
 415                if (argv[0] && argv[1]) {
 416                        xmove_fd(xopen_stdin(argv[1]), STDIN_FILENO);
 417                }
 418        }
 419
 420        // Loop through the lines in the patch
 421        for(;;) {
 422                char *patchline;
 423
 424                patchline = xmalloc_fgetline(stdin);
 425                if (!patchline) break;
 426
 427                // Other versions of patch accept damaged patches,
 428                // so we need to also.
 429                if (!*patchline) {
 430                        free(patchline);
 431                        patchline = xstrdup(" ");
 432                }
 433
 434                // Are we assembling a hunk?
 435                if (state >= 2) {
 436                        if (*patchline==' ' || *patchline=='+' || *patchline=='-') {
 437                                dlist_add(&TT.current_hunk, patchline);
 438
 439                                if (*patchline != '+') oldlen--;
 440                                if (*patchline != '-') newlen--;
 441
 442                                // Context line?
 443                                if (*patchline==' ' && state==2) TT.context++;
 444                                else state=3;
 445
 446                                // If we've consumed all expected hunk lines, apply the hunk.
 447
 448                                if (!oldlen && !newlen) state = apply_one_hunk();
 449                                continue;
 450                        }
 451                        fail_hunk();
 452                        state = 0;
 453                        continue;
 454                }
 455
 456                // Open a new file?
 457                if (is_prefixed_with(patchline, "--- ") || is_prefixed_with(patchline, "+++ ")) {
 458                        char *s, **name = reverse ? &newname : &oldname;
 459                        int i;
 460
 461                        if (*patchline == '+') {
 462                                name = reverse ? &oldname : &newname;
 463                                state = 1;
 464                        }
 465
 466                        finish_oldfile();
 467
 468                        if (!argv[0]) {
 469                                free(*name);
 470                                // Trim date from end of filename (if any).  We don't care.
 471                                for (s = patchline+4; *s && *s!='\t'; s++)
 472                                        if (*s=='\\' && s[1]) s++;
 473                                i = atoi(s);
 474                                if (i>1900 && i<=1970)
 475                                        *name = xstrdup("/dev/null");
 476                                else {
 477                                        *s = 0;
 478                                        *name = xstrdup(patchline+4);
 479                                }
 480                        }
 481
 482                        // We defer actually opening the file because svn produces broken
 483                        // patches that don't signal they want to create a new file the
 484                        // way the patch man page says, so you have to read the first hunk
 485                        // and _guess_.
 486
 487                // Start a new hunk?  Usually @@ -oldline,oldlen +newline,newlen @@
 488                // but a missing ,value means the value is 1.
 489                } else if (state == 1 && is_prefixed_with(patchline, "@@ -")) {
 490                        int i;
 491                        char *s = patchline+4;
 492
 493                        // Read oldline[,oldlen] +newline[,newlen]
 494
 495                        TT.oldlen = oldlen = TT.newlen = newlen = 1;
 496                        TT.oldline = strtol(s, &s, 10);
 497                        if (*s == ',') TT.oldlen = oldlen = strtol(s+1, &s, 10);
 498                        TT.newline = strtol(s+2, &s, 10);
 499                        if (*s == ',') TT.newlen = newlen = strtol(s+1, &s, 10);
 500
 501                        if (oldlen < 1 && newlen < 1)
 502                                bb_error_msg_and_die("Really? %s", patchline);
 503
 504                        TT.context = 0;
 505                        state = 2;
 506
 507                        // If the --- line is missing or malformed, either oldname
 508                        // or (for -R) newname could be NULL -- but not both.  Like
 509                        // GNU patch, proceed based on the +++ line, and avoid SEGVs.
 510                        if (!oldname)
 511                                oldname = xstrdup("MISSING_FILENAME");
 512                        if (!newname)
 513                                newname = xstrdup("MISSING_FILENAME");
 514
 515                        // If this is the first hunk, open the file.
 516                        if (TT.filein == -1) {
 517                                int oldsum, newsum, empty = 0;
 518                                char *name;
 519
 520                                oldsum = TT.oldline + oldlen;
 521                                newsum = TT.newline + newlen;
 522
 523                                name = reverse ? oldname : newname;
 524
 525                                // We're deleting oldname if new file is /dev/null (before -p)
 526                                // or if new hunk is empty (zero context) after patching
 527                                if (strcmp(name, "/dev/null") == 0 || !(reverse ? oldsum : newsum)) {
 528                                        name = reverse ? newname : oldname;
 529                                        empty = 1;
 530                                }
 531
 532                                // Handle -p path truncation.
 533                                for (i = 0, s = name; *s;) {
 534                                        if ((option_mask32 & FLAG_PATHLEN) && TT.prefix == i)
 535                                                break;
 536                                        if (*s++ != '/')
 537                                                continue;
 538                                        while (*s == '/')
 539                                                s++;
 540                                        i++;
 541                                        name = s;
 542                                }
 543                                // If "patch FILE_TO_PATCH", completely ignore name from patch
 544                                if (argv[0])
 545                                        name = argv[0];
 546
 547                                if (empty) {
 548                                        // File is empty after the patches have been applied
 549                                        state = 0;
 550                                        if (option_mask32 & FLAG_RMEMPTY) {
 551                                                // If flag -E or --remove-empty-files is set
 552                                                printf("removing %s\n", name);
 553                                                if (!(opts & FLAG_dry_run))
 554                                                        xunlink(name);
 555                                        } else {
 556                                                printf("patching file %s\n", name);
 557                                                if (!(opts & FLAG_dry_run))
 558                                                        xclose(xopen(name, O_WRONLY | O_TRUNC));
 559                                        }
 560                                // If we've got a file to open, do so.
 561                                } else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) {
 562                                        struct stat statbuf;
 563
 564                                        // If the old file was null, we're creating a new one.
 565                                        if (strcmp(oldname, "/dev/null") == 0 || !oldsum) {
 566                                                printf("creating %s\n", name);
 567                                                if (!(opts & FLAG_dry_run)) {
 568                                                        s = strrchr(name, '/');
 569                                                        if (s) {
 570                                                                *s = '\0';
 571                                                                bb_make_directory(name, -1, FILEUTILS_RECUR);
 572                                                                *s = '/';
 573                                                        }
 574                                                        TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR);
 575                                                } else {
 576                                                        TT.filein = xopen("/dev/null", O_RDONLY);
 577                                                }
 578                                        } else {
 579                                                printf("patching file %s\n", name);
 580                                                TT.filein = xopen(name, O_RDONLY);
 581                                        }
 582
 583                                        if (!(opts & FLAG_dry_run)) {
 584                                                TT.tempname = xasprintf("%sXXXXXX", name);
 585                                                TT.fileout = xmkstemp(TT.tempname);
 586                                                // Set permissions of output file
 587                                                fstat(TT.filein, &statbuf);
 588                                                fchmod(TT.fileout, statbuf.st_mode);
 589                                        } else {
 590                                                TT.tempname = (char*)"";
 591                                                TT.fileout = xopen("/dev/null", O_WRONLY);
 592                                        }
 593                                        TT.linenum = 0;
 594                                        TT.hunknum = 0;
 595                                }
 596                        }
 597
 598                        TT.hunknum++;
 599
 600                        continue;
 601                }
 602
 603                // If we didn't continue above, discard this line.
 604                free(patchline);
 605        }
 606
 607        finish_oldfile();
 608
 609        if (ENABLE_FEATURE_CLEAN_UP) {
 610                free(oldname);
 611                free(newname);
 612        }
 613
 614        return TT.exitval;
 615}
 616