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