123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- /* Adapted from toybox's patch. */
- /* vi: set sw=4 ts=4:
- *
- * patch.c - Apply a "universal" diff.
- *
- * 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
- * -N ignore already applied
- * -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
- *
- * -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 (9.6 kb)"
- 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"
- 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))
- {
- while (list) {
- void *pop = TOY_llist_pop(&list);
- if (freeit) freeit(pop);
- else free(pop);
- // End doubly linked list too.
- if (list == pop) break;
- }
- }
- // 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);
- }
- 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);
- }
- 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_simple_perror_msg_and_die("no temp file");
- // Set permissions of output file
- fstat(fdin, &statbuf);
- fchmod(fd, statbuf.st_mode);
- return fd;
- }
- // Abort the copy and delete the temporary file.
- static
- void delete_tempfile(int fdin, int fdout, char **tempname)
- {
- 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, 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)
- //bbox had: "p:i:RN"
- #define FLAG_STR "Rup:i:x"
- /* 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)
- //non-standard:
- #define FLAG_DEBUG (1 << 4)
- // 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;
- TOY_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;
- // 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) == 0) {
- if (!backwarn) {
- fdprintf(2,"Possibly reversed hunk %d at %ld\n",
- TT.hunknum, TT.linenum);
- backwarn++;
- }
- }
- 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;
- // 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 = TOY_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];
- TOY_llist_free(TT.current_hunk, do_line);
- TT.current_hunk = NULL;
- TT.state = 1;
- done:
- if (buf) {
- buf->prev->next = NULL;
- TOY_llist_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
- 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;
- INIT_TT();
- opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i);
- reverse = opts & FLAG_REVERSE;
- TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative!
- if (opts & FLAG_INPUT) TT.filepatch = xopen(opt_i, O_RDONLY);
- TT.filein = TT.fileout = -1;
- // 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(" ");
- }
- // 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;
- }
- fail_hunk();
- state = 0;
- continue;
- }
- // 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;
- }
- free(*name);
- finish_oldfile();
- // 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?
- } else if (state == 1 && !strncmp("@@ -", patchline, 4)) {
- int i;
- i = sscanf(patchline+4, "%ld,%ld +%ld,%ld", &TT.oldline,
- &TT.oldlen, &TT.newline, &TT.newlen);
- if (i != 4)
- bb_error_msg_and_die("corrupt hunk %d at %ld", TT.hunknum, TT.linenum);
- TT.context = 0;
- state = 2;
- // If this is the first hunk, open the file.
- if (TT.filein == -1) {
- int oldsum, newsum, del = 0;
- char *s, *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") == 0 || !(reverse ? oldsum : newsum)) {
- name = reverse ? newname : oldname;
- del++;
- }
- // 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 (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") == 0 || !oldsum) {
- printf("creating %s\n", name);
- s = strrchr(name, '/');
- if (s) {
- *s = 0;
- xmkpath(name, -1);
- *s = '/';
- }
- TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR);
- } else {
- printf("patching file %s\n", name);
- TT.filein = xopen(name, O_RDWR);
- }
- TT.fileout = copy_tempfile(TT.filein, name, &TT.tempname);
- 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) {
- close(TT.filepatch);
- free(oldname);
- free(newname);
- }
- return TT.exitval;
- }
|