3 * Apply a "universal" diff.
4 * Adapted from toybox's patch implementation.
6 * Copyright 2007 Rob Landley <rob@landley.net>
8 * see http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html
9 * (But only does -u, because who still cares about "ed"?)
13 * -l treat all whitespace as a single space
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!)
20 * -f force (no questions asked)
21 * -F fuzz (number, default 2)
22 * [file] which file to patch
26 //config: bool "patch"
29 //config: Apply a unified diff formatted patch.
31 //applet:IF_PATCH(APPLET(patch, BB_DIR_USR_BIN, BB_SUID_DROP))
33 //kbuild:lib-$(CONFIG_PATCH) += patch.o
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"
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"
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 */
56 //usage:#define patch_example_usage
57 //usage: "$ patch -p1 < example.diff\n"
58 //usage: "$ patch -p0 -i example.diff"
66 struct double_list
*next
;
67 struct double_list
*prev
;
71 // Free all the elements of a linked list
72 // Call freeit() on each element before freeing it.
73 static void dlist_free(struct double_list
*list
, void (*freeit
)(void *data
))
79 // Bail out also if list is circular.
80 if (list
== pop
) break;
84 // Add an entry before "list" element in (circular) doubly linked list
85 static struct double_list
*dlist_add(struct double_list
**list
, char *data
)
87 struct double_list
*llist
;
88 struct double_list
*line
= xmalloc(sizeof(*line
));
93 struct double_list
*p
;
95 p
= line
->prev
= llist
->prev
;
96 // (list is circular, we assume p is never NULL)
100 *list
= line
->next
= line
->prev
= line
;
110 struct double_list
*current_hunk
;
112 long oldline
, oldlen
, newline
, newlen
;
114 int context
, state
, hunknum
;
120 #define TT (*ptr_to_globals)
121 #define INIT_TT() do { \
122 SET_PTR_TO_GLOBALS(xzalloc(sizeof(TT))); \
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)
137 // Dispose of a line of input, either by writing it out or discarding it.
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
144 #define PATCH_DEBUG (option_mask32 & FLAG_DEBUG)
146 static void do_line(void *data
)
148 struct double_list
*dlist
= data
;
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));
154 if (PATCH_DEBUG
) fdprintf(2, "DO %d: %s\n", TT
.state
, dlist
->data
);
160 static void finish_oldfile(void)
163 // Copy the rest of the data and replace the original with the copy.
166 if (TT
.filein
!= -1) {
167 bb_copyfd_eof(TT
.filein
, TT
.fileout
);
172 temp
= xstrdup(TT
.tempname
);
173 temp
[strlen(temp
) - 6] = '\0';
174 rename(TT
.tempname
, temp
);
180 TT
.fileout
= TT
.filein
= -1;
183 static void fail_hunk(void)
185 if (!TT
.current_hunk
) return;
187 fdprintf(2, "Hunk %d FAILED %ld/%ld.\n", TT
.hunknum
, TT
.oldline
, TT
.newline
);
190 // If we got to this point, we've seeked to the end. Discard changes to
191 // this file and advance to next file.
194 TT
.current_hunk
->prev
->next
= NULL
;
195 dlist_free(TT
.current_hunk
, do_line
);
196 TT
.current_hunk
= NULL
;
198 // Abort the copy and delete the temporary file.
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.
215 static int apply_one_hunk(void)
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.
222 int dummy_revert
= 0;
224 // Break doubly linked list so we can use singly linked traversal function.
225 TT
.current_hunk
->prev
->next
= NULL
;
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
++;
231 if (PATCH_DEBUG
) fdprintf(2, "HUNK:%s\n", plist
->data
);
233 matcheof
= !matcheof
|| matcheof
< TT
.context
;
235 if (PATCH_DEBUG
) fdprintf(2,"MATCHEOF=%c\n", matcheof
? 'Y' : 'N');
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
240 plist
= TT
.current_hunk
;
242 if (reverse
? TT
.oldlen
: TT
.newlen
) for (;;) {
243 char *data
= xmalloc_reads(TT
.filein
, NULL
);
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)) {
252 backwarn
= TT
.linenum
;
253 if (option_mask32
& FLAG_IGNORE
) {
265 if (PATCH_DEBUG
) fdprintf(2, "INEOF\n");
267 // Does this hunk need to match EOF?
268 if (!plist
&& matcheof
) break;
271 fdprintf(2,"Possibly reversed hunk %d at %ld\n",
272 TT
.hunknum
, TT
.linenum
);
274 // File ended before we found a place for this hunk.
279 if (PATCH_DEBUG
) fdprintf(2, "IN: %s\n", data
);
280 check
= dlist_add(&buf
, data
);
282 // Compare this line with next expected line of hunk.
283 // todo: teach the strcmp() to ignore whitespace.
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.
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
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.
298 fdprintf(2, "NOT: %s\n", plist
->data
);
303 check
->prev
->next
= buf
;
304 buf
->prev
= check
->prev
;
306 plist
= TT
.current_hunk
;
308 // If we've reached the end of the buffer without confirming a
309 // match, read more lines.
317 fdprintf(2, "MAYBE: %s\n", plist
->data
);
318 // This line matches. Advance plist, detect successful match.
320 if (!plist
&& !matcheof
) goto out
;
322 if (check
== buf
) break;
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
;
334 buf
->prev
->next
= NULL
;
335 dlist_free(buf
, do_line
);
341 // Read a patch file and find hunks, opening/creating/deleting files.
342 // Call apply_one_hunk() on each hunk.
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.
351 int patch_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
352 int patch_main(int argc UNUSED_PARAM
, char **argv
)
355 int reverse
, state
= 0;
356 char *oldname
= NULL
, *newname
= NULL
;
358 long oldlen
= oldlen
; /* for compiler */
359 long newlen
= newlen
; /* for compiler */
363 opts
= getopt32(argv
, FLAG_STR
, &opt_p
, &opt_i
);
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
);
371 if (argv
[0] && argv
[1]) {
372 xmove_fd(xopen_stdin(argv
[1]), STDIN_FILENO
);
376 // Loop through the lines in the patch
380 patchline
= xmalloc_fgetline(stdin
);
381 if (!patchline
) break;
383 // Other versions of patch accept damaged patches,
384 // so we need to also.
387 patchline
= xstrdup(" ");
390 // Are we assembling a hunk?
392 if (*patchline
==' ' || *patchline
=='+' || *patchline
=='-') {
393 dlist_add(&TT
.current_hunk
, patchline
);
395 if (*patchline
!= '+') oldlen
--;
396 if (*patchline
!= '-') newlen
--;
399 if (*patchline
==' ' && state
==2) TT
.context
++;
402 // If we've consumed all expected hunk lines, apply the hunk.
404 if (!oldlen
&& !newlen
) state
= apply_one_hunk();
413 if (is_prefixed_with(patchline
, "--- ") || is_prefixed_with(patchline
, "+++ ")) {
414 char *s
, **name
= reverse
? &newname
: &oldname
;
417 if (*patchline
== '+') {
418 name
= reverse
? &oldname
: &newname
;
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
++;
430 if (i
>1900 && i
<=1970)
431 *name
= xstrdup("/dev/null");
434 *name
= xstrdup(patchline
+4);
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
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
, "@@ -")) {
447 char *s
= patchline
+4;
449 // Read oldline[,oldlen] +newline[,newlen]
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);
457 if (oldlen
< 1 && newlen
< 1)
458 bb_error_msg_and_die("Really? %s", patchline
);
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.
467 oldname
= xstrdup("MISSING_FILENAME");
469 newname
= xstrdup("MISSING_FILENAME");
471 // If this is the first hunk, open the file.
472 if (TT
.filein
== -1) {
473 int oldsum
, newsum
, empty
= 0;
476 oldsum
= TT
.oldline
+ oldlen
;
477 newsum
= TT
.newline
+ newlen
;
479 name
= reverse
? oldname
: newname
;
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
;
488 // Handle -p path truncation.
489 for (i
= 0, s
= name
; *s
;) {
490 if ((option_mask32
& FLAG_PATHLEN
) && TT
.prefix
== i
)
499 // If "patch FILE_TO_PATCH", completely ignore name from patch
504 // File is empty after the patches have been applied
506 if (option_mask32
& FLAG_RMEMPTY
) {
507 // If flag -E or --remove-empty-files is set
508 printf("removing %s\n", name
);
511 printf("patching file %s\n", name
);
512 xclose(xopen(name
, O_WRONLY
| O_TRUNC
));
514 // If we've got a file to open, do so.
515 } else if (!(option_mask32
& FLAG_PATHLEN
) || i
<= TT
.prefix
) {
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
, '/');
524 bb_make_directory(name
, -1, FILEUTILS_RECUR
);
527 TT
.filein
= xopen(name
, O_CREAT
|O_EXCL
|O_RDWR
);
529 printf("patching file %s\n", name
);
530 TT
.filein
= xopen(name
, O_RDONLY
);
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
);
549 // If we didn't continue above, discard this line.
555 if (ENABLE_FEATURE_CLEAN_UP
) {