123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- /* vi: set sw=4 ts=4:
- *
- * Apply a "universal" diff.
- * Adapted from toybox's patch implementation.
- *
- * Copyright 2007 Rob Landley <rob@landley.net>
- *
- * see http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html
- * (But only does -u, because who still cares about "ed"?)
- *
- * 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
- * --dry-run (regression!)
- *
- * -f force (no questions asked)
- * -F fuzz (number, default 2)
- * [file] which file to patch
- */
- //config:config PATCH
- //config: bool "patch"
- //config: default y
- //config: help
- //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" - TODO */
- //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: )
- /* -u "interpret as unified diff" is supported but not documented: this info is not useful for --help */
- /* -x "debug" is supported but does nothing */
- //usage:
- //usage:#define patch_example_usage
- //usage: "$ patch -p1 < example.diff\n"
- //usage: "$ patch -p0 -i example.diff"
- #include "libbb.h"
- // libbb candidate?
- struct double_list {
- struct double_list *next;
- struct double_list *prev;
- char *data;
- };
- // Free all the elements of a linked list
- // Call freeit() on each element before freeing it.
- static void dlist_free(struct double_list *list, void (*freeit)(void *data))
- {
- while (list) {
- void *pop = list;
- list = list->next;
- freeit(pop);
- // Bail out also if list is circular.
- if (list == pop) break;
- }
- }
- // 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 *llist;
- struct double_list *line = xmalloc(sizeof(*line));
- line->data = data;
- 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, hunknum;
- int filein, fileout;
- char *tempname;
- 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:NEx"
- /* 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)
- /* Enable this bit and use -x for debug output: */
- #define FLAG_DEBUG (0 << 6)
- // 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 = 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(dlist);
- }
- static void finish_oldfile(void)
- {
- if (TT.tempname) {
- // Copy the rest of the data and replace the original with the copy.
- char *temp;
- if (TT.filein != -1) {
- bb_copyfd_eof(TT.filein, TT.fileout);
- xclose(TT.filein);
- }
- xclose(TT.fileout);
- temp = xstrdup(TT.tempname);
- temp[strlen(temp) - 6] = '\0';
- rename(TT.tempname, temp);
- free(temp);
- free(TT.tempname);
- TT.tempname = NULL;
- }
- TT.fileout = TT.filein = -1;
- }
- static void fail_hunk(void)
- {
- if (!TT.current_hunk) return;
- 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;
- 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);
- TT.tempname = NULL;
- 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 || 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 (reverse ? TT.oldlen : TT.newlen) for (;;) {
- 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 (!backwarn) {
- backwarn = TT.linenum;
- if (option_mask32 & FLAG_IGNORE) {
- dummy_revert = 1;
- reverse ^= 1;
- continue;
- }
- }
- }
- plist = plist->next;
- }
- // Is this EOF?
- if (!data) {
- if (PATCH_DEBUG) fdprintf(2, "INEOF\n");
- // Does this hunk need to match EOF?
- if (!plist && matcheof) break;
- if (backwarn)
- fdprintf(2,"Possibly reversed hunk %d at %ld\n",
- TT.hunknum, TT.linenum);
- // File ended before we found a place for this hunk.
- fail_hunk();
- goto done;
- }
- 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 = buf;
- buf = buf->next;
- 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 = NULL;
- 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];
- dlist_free(TT.current_hunk, do_line);
- TT.current_hunk = NULL;
- TT.state = 1;
- done:
- if (buf) {
- buf->prev->next = NULL;
- dlist_free(buf, do_line);
- }
- 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
- // 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)
- {
- int opts;
- int reverse, state = 0;
- char *oldname = NULL, *newname = NULL;
- char *opt_p, *opt_i;
- long oldlen = oldlen; /* for compiler */
- long newlen = newlen; /* for compiler */
- 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) {
- xmove_fd(xopen_stdin(opt_i), STDIN_FILENO);
- } else {
- if (argv[0] && argv[1]) {
- 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(;;) {
- char *patchline;
- patchline = xmalloc_fgetline(stdin);
- if (!patchline) break;
- // Other versions of patch accept damaged patches,
- // so we need to also.
- if (!*patchline) {
- free(patchline);
- patchline = xstrdup(" ");
- }
- // Are we assembling a hunk?
- if (state >= 2) {
- if (*patchline==' ' || *patchline=='+' || *patchline=='-') {
- dlist_add(&TT.current_hunk, patchline);
- if (*patchline != '+') oldlen--;
- if (*patchline != '-') newlen--;
- // Context line?
- if (*patchline==' ' && state==2) TT.context++;
- else state=3;
- // If we've consumed all expected hunk lines, apply the hunk.
- if (!oldlen && !newlen) state = apply_one_hunk();
- continue;
- }
- fail_hunk();
- state = 0;
- continue;
- }
- // Open a new file?
- if (is_prefixed_with(patchline, "--- ") || is_prefixed_with(patchline, "+++ ")) {
- char *s, **name = reverse ? &newname : &oldname;
- int i;
- if (*patchline == '+') {
- name = reverse ? &oldname : &newname;
- state = 1;
- }
- 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);
- }
- }
- // 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 && is_prefixed_with(patchline, "@@ -")) {
- int i;
- char *s = patchline+4;
- // Read oldline[,oldlen] +newline[,newlen]
- TT.oldlen = oldlen = TT.newlen = newlen = 1;
- TT.oldline = strtol(s, &s, 10);
- if (*s == ',') TT.oldlen = oldlen = strtol(s+1, &s, 10);
- TT.newline = strtol(s+2, &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 + 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)) {
- name = reverse ? newname : oldname;
- empty++;
- }
- // 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 (empty) {
- // File is empty after the patches have been applied
- state = 0;
- if (option_mask32 & FLAG_RMEMPTY) {
- // If flag -E or --remove-empty-files is set
- printf("removing %s\n", name);
- xunlink(name);
- } else {
- printf("patching file %s\n", name);
- 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) {
- printf("creating %s\n", name);
- 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 {
- 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);
- TT.linenum = 0;
- TT.hunknum = 0;
- }
- }
- TT.hunknum++;
- continue;
- }
- // If we didn't continue above, discard this line.
- free(patchline);
- }
- finish_oldfile();
- if (ENABLE_FEATURE_CLEAN_UP) {
- free(oldname);
- free(newname);
- }
- return TT.exitval;
- }
|