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