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