| /* vi: set sw=4 ts=4: */ |
| /* |
| * patch.c - Apply a "universal" diff. |
| * |
| * SUSv3 at http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html |
| * but who cares about "ed"? |
| * |
| * -u ignored |
| * -R reverse (remove applied hunks, apply removed hunks) |
| * -p num remove this many slashes from start of path (default = all) |
| * |
| * 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 |
| * -i patchfile apply patch from filename rather than stdin |
| * -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 |
| */ |
| |
| #include "toys.h" |
| |
| #define TT toy.patch |
| |
| #define FLAG_REVERSE 1 |
| #define FLAG_PATHLEN 4 |
| |
| static void do_line(void *data) |
| { |
| struct double_list *dlist = (struct double_list *)data; |
| |
| if (TT.state && *dlist->data != TT.state) |
| fdprintf(TT.state == 2 ? 2: TT.fileout, |
| "%s\n", dlist->data+(TT.state>2 ? 1 : 0)); |
| free(dlist->data); |
| free(dlist); |
| } |
| |
| |
| static void 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; |
| } |
| |
| static void apply_hunk(void) |
| { |
| struct double_list *plist, *temp, *buf; |
| int i = 0, backwards = 0, reverse = toys.optflags & FLAG_REVERSE; |
| |
| TT.state = 0; |
| |
| if (!TT.plines) return; |
| temp = buf = NULL; |
| |
| // Hunk is complete, break doubly linked list so we can use singly linked |
| // traversal function. |
| TT.plines->prev->next = NULL; |
| |
| // Trim extra context lines, if any. If there aren't as many ending |
| // context lines as beginning lines, this isn't a valid hunk. |
| for (plist = TT.plines; plist; plist = plist->next) { |
| if (plist->data[0]==' ') { |
| if (i<TT.context) temp = plist; |
| i++; |
| } else i = 0; |
| } |
| if (i < TT.context) goto fail_hunk; |
| if (temp) { |
| llist_free(temp->next, do_line); |
| temp->next = NULL; |
| } |
| |
| // Search for a place to apply this hunk. Match all context lines and |
| // lines to be removed. |
| plist = TT.plines; |
| buf = NULL; |
| i = 0; |
| if (TT.context) for (;;) { |
| char *data = get_line(TT.filein); |
| TT.linenum++; |
| |
| // If the file ended before we found a home for this hunk, fail. |
| if (!data) goto fail_hunk; |
| |
| dlist_add(&buf, data); |
| if (!backwards && *plist->data == "+-"[reverse]) { |
| backwards = 1; |
| if (!strcmp(data, plist->data+1)) |
| fdprintf(1,"Possibly reversed hunk at %ld\n", TT.linenum); |
| } |
| while (*plist->data == "+-"[reverse]) plist = plist->next; |
| if (strcmp(data, plist->data+1)) { // Ignore whitespace? |
| // Hunk doesn't go here, flush accumulated buffer so far. |
| |
| buf->prev->next = NULL; |
| TT.state = 1; |
| llist_free(buf, do_line); |
| buf = NULL; |
| plist = TT.plines; |
| } else { |
| plist = plist->next; |
| if (!plist) break; |
| } |
| } |
| |
| // Got it. Emit changed data. |
| TT.state = "-+"[reverse]; |
| llist_free(TT.plines, do_line); |
| TT.plines = NULL; |
| TT.state = 0; |
| if (buf) { |
| buf->prev->next = NULL; |
| llist_free(buf, do_line); |
| } |
| return; |
| |
| fail_hunk: |
| printf("Hunk FAILED.\n"); |
| |
| // 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; |
| llist_free(TT.plines, do_line); |
| TT.plines = 0; |
| if (buf) { |
| buf->prev->next = NULL; |
| llist_free(buf, do_line); |
| } |
| delete_tempfile(TT.filein, TT.fileout, &TT.tempname); |
| TT.filein = -1; |
| TT.state = 0; |
| } |
| |
| // 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 |
| |
| void patch_main(void) |
| { |
| if (TT.infile) TT.filepatch = xopen(TT.infile, O_RDONLY); |
| else TT.filepatch = 0; |
| TT.filein = TT.fileout = -1; |
| |
| // Loop through the lines in the patch |
| for(;;) { |
| char *patchline; |
| |
| patchline = get_line(TT.filepatch); |
| if (!patchline) break; |
| |
| // Are we processing a hunk? |
| if (TT.state >= 2) { |
| // Context line? |
| if (*patchline==' ' || *patchline=='+' || *patchline=='-') { |
| dlist_add(&TT.plines, patchline); |
| |
| if (*patchline==' ' && TT.state==2) TT.context++; |
| else TT.state=3; |
| |
| continue; |
| } |
| } |
| |
| // If we have a hunk at this point, it's ready to apply. |
| apply_hunk(); |
| |
| // Open a new file? |
| if (!strncmp("--- ", patchline, 4)) { |
| char *s; |
| free(TT.oldname); |
| |
| // 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++; |
| *s = 0; |
| |
| TT.oldname = xstrdup(patchline+4); |
| } else if (!strncmp("+++ ", patchline, 4)) { |
| int i = 0, del = 0; |
| char *s, *start; |
| |
| // Finish old file. |
| if (TT.tempname) |
| replace_tempfile(TT.filein, TT.fileout, &TT.tempname); |
| |
| // 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++; |
| *s = 0; |
| |
| |
| // If new file is null (before -p trim), we're deleting oldname |
| start = patchline+4; |
| if (!strcmp(start, "/dev/null")) { |
| start = TT.oldname; |
| del++; |
| } else start = patchline+4; |
| |
| // handle -p path truncation. |
| for (s = start; *s;) { |
| if ((toys.optflags & FLAG_PATHLEN) && TT.prefix == i) break; |
| if (*(s++)=='/') { |
| start = s; |
| i++; |
| } |
| } |
| |
| if (del) xunlink(TT.oldname); |
| // If we've got a file to open, do so. |
| else if (!(toys.optflags & FLAG_PATHLEN) || i <= TT.prefix) { |
| // If the old file was null, we're creating a new one. |
| if (!strcmp(TT.oldname, "/dev/null")) { |
| printf("creating %s\n", start); |
| s = strrchr(start, '/'); |
| if (s) { |
| *s = 0; |
| xmkpath(start, -1); |
| *s = '/'; |
| } |
| TT.filein = xcreate(start, O_CREAT|O_RDWR, 0666); |
| } else { |
| printf("patching %s\n", start); |
| TT.filein = xopen(start, O_RDWR); |
| } |
| TT.fileout = copy_tempfile(TT.filein, start, &TT.tempname); |
| TT.state = 1; |
| TT.context = 0; |
| TT.linenum = 0; |
| } |
| |
| // Start a new hunk? |
| } else if (TT.filein!=-1 && !strncmp("@@ ", patchline, 3)) { |
| TT.context = 0; |
| TT.state = 2; |
| sscanf(patchline+3, "%ld,%ld %ld,%ld", &TT.oldline, |
| &TT.oldlen, &TT.newline, &TT.newlen); |
| // Don't free it. |
| continue; |
| } |
| |
| // This line is noise, discard it. |
| free(patchline); |
| } |
| |
| // Flush pending hunk and flush data |
| apply_hunk(); |
| if (TT.tempname) replace_tempfile(TT.filein, TT.fileout, &TT.tempname); |
| if (CFG_TOYBOX_FREE) { |
| close(TT.filepatch); |
| free(TT.oldname); |
| } |
| } |