source: MondoRescue/branches/3.3/mindi-busybox/editors/patch.c@ 3625

Last change on this file since 3625 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 14.7 KB
Line 
1/* vi: set sw=4 ts=4:
2 *
3 * Apply a "universal" diff.
4 * Adapted from toybox's patch implementation.
5 *
6 * Copyright 2007 Rob Landley <rob@landley.net>
7 *
8 * see http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html
9 * (But only does -u, because who still cares about "ed"?)
10 *
11 * TODO:
12 * -b backup
13 * -l treat all whitespace as a single space
14 * -d chdir first
15 * -D define wrap #ifdef and #ifndef around changes
16 * -o outfile output here instead of in place
17 * -r rejectfile write rejected hunks to this file
18 * --dry-run (regression!)
19 *
20 * -f force (no questions asked)
21 * -F fuzz (number, default 2)
22 * [file] which file to patch
23 */
24
25//config:config PATCH
26//config: bool "patch"
27//config: default y
28//config: help
29//config: Apply a unified diff formatted patch.
30
31//applet:IF_PATCH(APPLET(patch, BB_DIR_USR_BIN, BB_SUID_DROP))
32
33//kbuild:lib-$(CONFIG_PATCH) += patch.o
34
35//usage:#define patch_trivial_usage
36//usage: "[OPTIONS] [ORIGFILE [PATCHFILE]]"
37//usage:#define patch_full_usage "\n\n"
38//usage: IF_LONG_OPTS(
39//usage: " -p,--strip N Strip N leading components from file names"
40//usage: "\n -i,--input DIFF Read DIFF instead of stdin"
41//usage: "\n -R,--reverse Reverse patch"
42//usage: "\n -N,--forward Ignore already applied patches"
43/*usage: "\n --dry-run Don't actually change files" - TODO */
44//usage: "\n -E,--remove-empty-files Remove output files if they become empty"
45//usage: )
46//usage: IF_NOT_LONG_OPTS(
47//usage: " -p N Strip N leading components from file names"
48//usage: "\n -i DIFF Read DIFF instead of stdin"
49//usage: "\n -R Reverse patch"
50//usage: "\n -N Ignore already applied patches"
51//usage: "\n -E Remove output files if they become empty"
52//usage: )
53/* -u "interpret as unified diff" is supported but not documented: this info is not useful for --help */
54/* -x "debug" is supported but does nothing */
55//usage:
56//usage:#define patch_example_usage
57//usage: "$ patch -p1 < example.diff\n"
58//usage: "$ patch -p0 -i example.diff"
59
60#include "libbb.h"
61
62
63// libbb candidate?
64
65struct double_list {
66 struct double_list *next;
67 struct double_list *prev;
68 char *data;
69};
70
71// Free all the elements of a linked list
72// Call freeit() on each element before freeing it.
73static void dlist_free(struct double_list *list, void (*freeit)(void *data))
74{
75 while (list) {
76 void *pop = list;
77 list = list->next;
78 freeit(pop);
79 // Bail out also if list is circular.
80 if (list == pop) break;
81 }
82}
83
84// Add an entry before "list" element in (circular) doubly linked list
85static struct double_list *dlist_add(struct double_list **list, char *data)
86{
87 struct double_list *llist;
88 struct double_list *line = xmalloc(sizeof(*line));
89
90 line->data = data;
91 llist = *list;
92 if (llist) {
93 struct double_list *p;
94 line->next = llist;
95 p = line->prev = llist->prev;
96 // (list is circular, we assume p is never NULL)
97 p->next = line;
98 llist->prev = line;
99 } else
100 *list = line->next = line->prev = line;
101
102 return line;
103}
104
105
106struct globals {
107 char *infile;
108 long prefix;
109
110 struct double_list *current_hunk;
111
112 long oldline, oldlen, newline, newlen;
113 long linenum;
114 int context, state, hunknum;
115 int filein, fileout;
116 char *tempname;
117
118 int exitval;
119};
120#define TT (*ptr_to_globals)
121#define INIT_TT() do { \
122 SET_PTR_TO_GLOBALS(xzalloc(sizeof(TT))); \
123} while (0)
124
125
126#define FLAG_STR "Rup:i:NEx"
127/* FLAG_REVERSE must be == 1! Code uses this fact. */
128#define FLAG_REVERSE (1 << 0)
129#define FLAG_u (1 << 1)
130#define FLAG_PATHLEN (1 << 2)
131#define FLAG_INPUT (1 << 3)
132#define FLAG_IGNORE (1 << 4)
133#define FLAG_RMEMPTY (1 << 5)
134/* Enable this bit and use -x for debug output: */
135#define FLAG_DEBUG (0 << 6)
136
137// Dispose of a line of input, either by writing it out or discarding it.
138
139// state < 2: just free
140// state = 2: write whole line to stderr
141// state = 3: write whole line to fileout
142// state > 3: write line+1 to fileout when *line != state
143
144#define PATCH_DEBUG (option_mask32 & FLAG_DEBUG)
145
146static void do_line(void *data)
147{
148 struct double_list *dlist = data;
149
150 if (TT.state>1 && *dlist->data != TT.state)
151 fdprintf(TT.state == 2 ? 2 : TT.fileout,
152 "%s\n", dlist->data+(TT.state>3 ? 1 : 0));
153
154 if (PATCH_DEBUG) fdprintf(2, "DO %d: %s\n", TT.state, dlist->data);
155
156 free(dlist->data);
157 free(dlist);
158}
159
160static void finish_oldfile(void)
161{
162 if (TT.tempname) {
163 // Copy the rest of the data and replace the original with the copy.
164 char *temp;
165
166 if (TT.filein != -1) {
167 bb_copyfd_eof(TT.filein, TT.fileout);
168 xclose(TT.filein);
169 }
170 xclose(TT.fileout);
171
172 temp = xstrdup(TT.tempname);
173 temp[strlen(temp) - 6] = '\0';
174 rename(TT.tempname, temp);
175 free(temp);
176
177 free(TT.tempname);
178 TT.tempname = NULL;
179 }
180 TT.fileout = TT.filein = -1;
181}
182
183static void fail_hunk(void)
184{
185 if (!TT.current_hunk) return;
186
187 fdprintf(2, "Hunk %d FAILED %ld/%ld.\n", TT.hunknum, TT.oldline, TT.newline);
188 TT.exitval = 1;
189
190 // If we got to this point, we've seeked to the end. Discard changes to
191 // this file and advance to next file.
192
193 TT.state = 2;
194 TT.current_hunk->prev->next = NULL;
195 dlist_free(TT.current_hunk, do_line);
196 TT.current_hunk = NULL;
197
198 // Abort the copy and delete the temporary file.
199 close(TT.filein);
200 close(TT.fileout);
201 unlink(TT.tempname);
202 free(TT.tempname);
203 TT.tempname = NULL;
204
205 TT.state = 0;
206}
207
208// Given a hunk of a unified diff, make the appropriate change to the file.
209// This does not use the location information, but instead treats a hunk
210// as a sort of regex. Copies data from input to output until it finds
211// the change to be made, then outputs the changed data and returns.
212// (Finding EOF first is an error.) This is a single pass operation, so
213// multiple hunks must occur in order in the file.
214
215static int apply_one_hunk(void)
216{
217 struct double_list *plist, *buf = NULL, *check;
218 int matcheof = 0, reverse = option_mask32 & FLAG_REVERSE, backwarn = 0;
219 /* Do we try "dummy" revert to check whether
220 * to silently skip this hunk? Used to implement -N.
221 */
222 int dummy_revert = 0;
223
224 // Break doubly linked list so we can use singly linked traversal function.
225 TT.current_hunk->prev->next = NULL;
226
227 // Match EOF if there aren't as many ending context lines as beginning
228 for (plist = TT.current_hunk; plist; plist = plist->next) {
229 if (plist->data[0]==' ') matcheof++;
230 else matcheof = 0;
231 if (PATCH_DEBUG) fdprintf(2, "HUNK:%s\n", plist->data);
232 }
233 matcheof = !matcheof || matcheof < TT.context;
234
235 if (PATCH_DEBUG) fdprintf(2,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N');
236
237 // Loop through input data searching for this hunk. Match all context
238 // lines and all lines to be removed until we've found the end of a
239 // complete hunk.
240 plist = TT.current_hunk;
241 buf = NULL;
242 if (reverse ? TT.oldlen : TT.newlen) for (;;) {
243 char *data = xmalloc_reads(TT.filein, NULL);
244
245 TT.linenum++;
246
247 // Figure out which line of hunk to compare with next. (Skip lines
248 // of the hunk we'd be adding.)
249 while (plist && *plist->data == "+-"[reverse]) {
250 if (data && !strcmp(data, plist->data+1)) {
251 if (!backwarn) {
252 backwarn = TT.linenum;
253 if (option_mask32 & FLAG_IGNORE) {
254 dummy_revert = 1;
255 reverse ^= 1;
256 continue;
257 }
258 }
259 }
260 plist = plist->next;
261 }
262
263 // Is this EOF?
264 if (!data) {
265 if (PATCH_DEBUG) fdprintf(2, "INEOF\n");
266
267 // Does this hunk need to match EOF?
268 if (!plist && matcheof) break;
269
270 if (backwarn)
271 fdprintf(2,"Possibly reversed hunk %d at %ld\n",
272 TT.hunknum, TT.linenum);
273
274 // File ended before we found a place for this hunk.
275 fail_hunk();
276 goto done;
277 }
278
279 if (PATCH_DEBUG) fdprintf(2, "IN: %s\n", data);
280 check = dlist_add(&buf, data);
281
282 // Compare this line with next expected line of hunk.
283 // todo: teach the strcmp() to ignore whitespace.
284
285 // A match can fail because the next line doesn't match, or because
286 // we hit the end of a hunk that needed EOF, and this isn't EOF.
287
288 // If match failed, flush first line of buffered data and
289 // recheck buffered data for a new match until we find one or run
290 // out of buffer.
291
292 for (;;) {
293 if (!plist || strcmp(check->data, plist->data+1)) {
294 // Match failed. Write out first line of buffered data and
295 // recheck remaining buffered data for a new match.
296
297 if (PATCH_DEBUG)
298 fdprintf(2, "NOT: %s\n", plist->data);
299
300 TT.state = 3;
301 check = buf;
302 buf = buf->next;
303 check->prev->next = buf;
304 buf->prev = check->prev;
305 do_line(check);
306 plist = TT.current_hunk;
307
308 // If we've reached the end of the buffer without confirming a
309 // match, read more lines.
310 if (check == buf) {
311 buf = NULL;
312 break;
313 }
314 check = buf;
315 } else {
316 if (PATCH_DEBUG)
317 fdprintf(2, "MAYBE: %s\n", plist->data);
318 // This line matches. Advance plist, detect successful match.
319 plist = plist->next;
320 if (!plist && !matcheof) goto out;
321 check = check->next;
322 if (check == buf) break;
323 }
324 }
325 }
326out:
327 // We have a match. Emit changed data.
328 TT.state = "-+"[reverse ^ dummy_revert];
329 dlist_free(TT.current_hunk, do_line);
330 TT.current_hunk = NULL;
331 TT.state = 1;
332done:
333 if (buf) {
334 buf->prev->next = NULL;
335 dlist_free(buf, do_line);
336 }
337
338 return TT.state;
339}
340
341// Read a patch file and find hunks, opening/creating/deleting files.
342// Call apply_one_hunk() on each hunk.
343
344// state 0: Not in a hunk, look for +++.
345// state 1: Found +++ file indicator, look for @@
346// state 2: In hunk: counting initial context lines
347// state 3: In hunk: getting body
348// Like GNU patch, we don't require a --- line before the +++, and
349// also allow the --- after the +++ line.
350
351int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
352int patch_main(int argc UNUSED_PARAM, char **argv)
353{
354 int opts;
355 int reverse, state = 0;
356 char *oldname = NULL, *newname = NULL;
357 char *opt_p, *opt_i;
358 long oldlen = oldlen; /* for compiler */
359 long newlen = newlen; /* for compiler */
360
361 INIT_TT();
362
363 opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i);
364 argv += optind;
365 reverse = opts & FLAG_REVERSE;
366 TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative!
367 TT.filein = TT.fileout = -1;
368 if (opts & FLAG_INPUT) {
369 xmove_fd(xopen_stdin(opt_i), STDIN_FILENO);
370 } else {
371 if (argv[0] && argv[1]) {
372 xmove_fd(xopen_stdin(argv[1]), STDIN_FILENO);
373 }
374 }
375
376 // Loop through the lines in the patch
377 for(;;) {
378 char *patchline;
379
380 patchline = xmalloc_fgetline(stdin);
381 if (!patchline) break;
382
383 // Other versions of patch accept damaged patches,
384 // so we need to also.
385 if (!*patchline) {
386 free(patchline);
387 patchline = xstrdup(" ");
388 }
389
390 // Are we assembling a hunk?
391 if (state >= 2) {
392 if (*patchline==' ' || *patchline=='+' || *patchline=='-') {
393 dlist_add(&TT.current_hunk, patchline);
394
395 if (*patchline != '+') oldlen--;
396 if (*patchline != '-') newlen--;
397
398 // Context line?
399 if (*patchline==' ' && state==2) TT.context++;
400 else state=3;
401
402 // If we've consumed all expected hunk lines, apply the hunk.
403
404 if (!oldlen && !newlen) state = apply_one_hunk();
405 continue;
406 }
407 fail_hunk();
408 state = 0;
409 continue;
410 }
411
412 // Open a new file?
413 if (is_prefixed_with(patchline, "--- ") || is_prefixed_with(patchline, "+++ ")) {
414 char *s, **name = reverse ? &newname : &oldname;
415 int i;
416
417 if (*patchline == '+') {
418 name = reverse ? &oldname : &newname;
419 state = 1;
420 }
421
422 finish_oldfile();
423
424 if (!argv[0]) {
425 free(*name);
426 // Trim date from end of filename (if any). We don't care.
427 for (s = patchline+4; *s && *s!='\t'; s++)
428 if (*s=='\\' && s[1]) s++;
429 i = atoi(s);
430 if (i>1900 && i<=1970)
431 *name = xstrdup("/dev/null");
432 else {
433 *s = 0;
434 *name = xstrdup(patchline+4);
435 }
436 }
437
438 // We defer actually opening the file because svn produces broken
439 // patches that don't signal they want to create a new file the
440 // way the patch man page says, so you have to read the first hunk
441 // and _guess_.
442
443 // Start a new hunk? Usually @@ -oldline,oldlen +newline,newlen @@
444 // but a missing ,value means the value is 1.
445 } else if (state == 1 && is_prefixed_with(patchline, "@@ -")) {
446 int i;
447 char *s = patchline+4;
448
449 // Read oldline[,oldlen] +newline[,newlen]
450
451 TT.oldlen = oldlen = TT.newlen = newlen = 1;
452 TT.oldline = strtol(s, &s, 10);
453 if (*s == ',') TT.oldlen = oldlen = strtol(s+1, &s, 10);
454 TT.newline = strtol(s+2, &s, 10);
455 if (*s == ',') TT.newlen = newlen = strtol(s+1, &s, 10);
456
457 if (oldlen < 1 && newlen < 1)
458 bb_error_msg_and_die("Really? %s", patchline);
459
460 TT.context = 0;
461 state = 2;
462
463 // If the --- line is missing or malformed, either oldname
464 // or (for -R) newname could be NULL -- but not both. Like
465 // GNU patch, proceed based on the +++ line, and avoid SEGVs.
466 if (!oldname)
467 oldname = xstrdup("MISSING_FILENAME");
468 if (!newname)
469 newname = xstrdup("MISSING_FILENAME");
470
471 // If this is the first hunk, open the file.
472 if (TT.filein == -1) {
473 int oldsum, newsum, empty = 0;
474 char *name;
475
476 oldsum = TT.oldline + oldlen;
477 newsum = TT.newline + newlen;
478
479 name = reverse ? oldname : newname;
480
481 // We're deleting oldname if new file is /dev/null (before -p)
482 // or if new hunk is empty (zero context) after patching
483 if (!strcmp(name, "/dev/null") || !(reverse ? oldsum : newsum)) {
484 name = reverse ? newname : oldname;
485 empty = 1;
486 }
487
488 // Handle -p path truncation.
489 for (i = 0, s = name; *s;) {
490 if ((option_mask32 & FLAG_PATHLEN) && TT.prefix == i)
491 break;
492 if (*s++ != '/')
493 continue;
494 while (*s == '/')
495 s++;
496 i++;
497 name = s;
498 }
499 // If "patch FILE_TO_PATCH", completely ignore name from patch
500 if (argv[0])
501 name = argv[0];
502
503 if (empty) {
504 // File is empty after the patches have been applied
505 state = 0;
506 if (option_mask32 & FLAG_RMEMPTY) {
507 // If flag -E or --remove-empty-files is set
508 printf("removing %s\n", name);
509 xunlink(name);
510 } else {
511 printf("patching file %s\n", name);
512 xclose(xopen(name, O_WRONLY | O_TRUNC));
513 }
514 // If we've got a file to open, do so.
515 } else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) {
516 struct stat statbuf;
517
518 // If the old file was null, we're creating a new one.
519 if (!strcmp(oldname, "/dev/null") || !oldsum) {
520 printf("creating %s\n", name);
521 s = strrchr(name, '/');
522 if (s) {
523 *s = 0;
524 bb_make_directory(name, -1, FILEUTILS_RECUR);
525 *s = '/';
526 }
527 TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR);
528 } else {
529 printf("patching file %s\n", name);
530 TT.filein = xopen(name, O_RDONLY);
531 }
532
533 TT.tempname = xasprintf("%sXXXXXX", name);
534 TT.fileout = xmkstemp(TT.tempname);
535 // Set permissions of output file
536 fstat(TT.filein, &statbuf);
537 fchmod(TT.fileout, statbuf.st_mode);
538
539 TT.linenum = 0;
540 TT.hunknum = 0;
541 }
542 }
543
544 TT.hunknum++;
545
546 continue;
547 }
548
549 // If we didn't continue above, discard this line.
550 free(patchline);
551 }
552
553 finish_oldfile();
554
555 if (ENABLE_FEATURE_CLEAN_UP) {
556 free(oldname);
557 free(newname);
558 }
559
560 return TT.exitval;
561}
Note: See TracBrowser for help on using the repository browser.