1 |
/* vi: set sw=4 ts=4: */ |
/* Adapted from toybox's patch. */ |
2 |
/* |
|
3 |
* busybox patch applet to handle the unified diff format. |
/* vi: set sw=4 ts=4: |
|
* Copyright (C) 2003 Glenn McGrath |
|
4 |
* |
* |
5 |
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball. |
* patch.c - Apply a "universal" diff. |
6 |
* |
* |
7 |
* This applet is written to work with patches generated by GNU diff, |
* Copyright 2007 Rob Landley <rob@landley.net> |
|
* where there is equivalent functionality busybox patch shall behave |
|
|
* as per GNU patch. |
|
8 |
* |
* |
9 |
* There is a SUSv3 specification for patch, however it looks to be |
* see http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html |
10 |
* incomplete, it doesnt even mention unified diff format. |
* (But only does -u, because who still cares about "ed"?) |
|
* http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html |
|
11 |
* |
* |
12 |
* Issues |
* TODO: |
13 |
* - Non-interactive |
* -b backup |
14 |
* - Patches must apply cleanly or patch (not just one hunk) will fail. |
* -l treat all whitespace as a single space |
15 |
* - Reject file isnt saved |
* -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 |
|
* -E remove empty files --remove-empty-files |
21 |
|
* -f force (no questions asked) |
22 |
|
* -F fuzz (number, default 2) |
23 |
|
* [file] which file to patch |
24 |
|
|
25 |
|
USE_PATCH(NEWTOY(patch, USE_TOYBOX_DEBUG("x")"up#i:R", TOYFLAG_USR|TOYFLAG_BIN)) |
26 |
|
|
27 |
|
config PATCH |
28 |
|
bool "patch" |
29 |
|
default y |
30 |
|
help |
31 |
|
usage: patch [-i file] [-p depth] [-Ru] |
32 |
|
|
33 |
|
Apply a unified diff to one or more files. |
34 |
|
|
35 |
|
-i Input file (defaults=stdin) |
36 |
|
-p number of '/' to strip from start of file paths (default=all) |
37 |
|
-R Reverse patch. |
38 |
|
-u Ignored (only handles "unified" diffs) |
39 |
|
|
40 |
|
This version of patch only handles unified diffs, and only modifies |
41 |
|
a file when all all hunks to that file apply. Patch prints failed |
42 |
|
hunks to stderr, and exits with nonzero status if any hunks fail. |
43 |
|
|
44 |
|
A file compared against /dev/null (or with a date <= the epoch) is |
45 |
|
created/deleted as appropriate. |
46 |
|
*/ |
47 |
#include "libbb.h" |
#include "libbb.h" |
48 |
|
|
49 |
static unsigned copy_lines(FILE *src_stream, FILE *dst_stream, unsigned lines_count) |
struct double_list { |
50 |
|
struct double_list *next; |
51 |
|
struct double_list *prev; |
52 |
|
char *data; |
53 |
|
}; |
54 |
|
|
55 |
|
// Return the first item from the list, advancing the list (which must be called |
56 |
|
// as &list) |
57 |
|
static |
58 |
|
void *TOY_llist_pop(void *list) |
59 |
{ |
{ |
60 |
while (src_stream && lines_count) { |
// I'd use a void ** for the argument, and even accept the typecast in all |
61 |
char *line; |
// callers as documentation you need the &, except the stupid compiler |
62 |
line = xmalloc_fgets(src_stream); |
// would then scream about type-punned pointers. Screw it. |
63 |
if (line == NULL) { |
void **llist = (void **)list; |
64 |
break; |
void **next = (void **)*llist; |
65 |
} |
*llist = *next; |
66 |
if (fputs(line, dst_stream) == EOF) { |
|
67 |
bb_perror_msg_and_die("error writing to new file"); |
return (void *)next; |
68 |
|
} |
69 |
|
|
70 |
|
// Free all the elements of a linked list |
71 |
|
// if freeit!=NULL call freeit() on each element before freeing it. |
72 |
|
static |
73 |
|
void TOY_llist_free(void *list, void (*freeit)(void *data)) |
74 |
|
{ |
75 |
|
while (list) { |
76 |
|
void *pop = TOY_llist_pop(&list); |
77 |
|
if (freeit) freeit(pop); |
78 |
|
else free(pop); |
79 |
|
|
80 |
|
// End doubly linked list too. |
81 |
|
if (list==pop) break; |
82 |
|
} |
83 |
|
} |
84 |
|
//Override bbox's names |
85 |
|
#define llist_pop TOY_llist_pop |
86 |
|
#define llist_free TOY_llist_free |
87 |
|
|
88 |
|
// Add an entry to the end off a doubly linked list |
89 |
|
static |
90 |
|
struct double_list *dlist_add(struct double_list **list, char *data) |
91 |
|
{ |
92 |
|
struct double_list *line = xmalloc(sizeof(struct double_list)); |
93 |
|
|
94 |
|
line->data = data; |
95 |
|
if (*list) { |
96 |
|
line->next = *list; |
97 |
|
line->prev = (*list)->prev; |
98 |
|
(*list)->prev->next = line; |
99 |
|
(*list)->prev = line; |
100 |
|
} else *list = line->next = line->prev = line; |
101 |
|
|
102 |
|
return line; |
103 |
|
} |
104 |
|
|
105 |
|
// Ensure entire path exists. |
106 |
|
// If mode != -1 set permissions on newly created dirs. |
107 |
|
// Requires that path string be writable (for temporary null terminators). |
108 |
|
static |
109 |
|
void xmkpath(char *path, int mode) |
110 |
|
{ |
111 |
|
char *p, old; |
112 |
|
mode_t mask; |
113 |
|
int rc; |
114 |
|
struct stat st; |
115 |
|
|
116 |
|
for (p = path; ; p++) { |
117 |
|
if (!*p || *p == '/') { |
118 |
|
old = *p; |
119 |
|
*p = rc = 0; |
120 |
|
if (stat(path, &st) || !S_ISDIR(st.st_mode)) { |
121 |
|
if (mode != -1) { |
122 |
|
mask = umask(0); |
123 |
|
rc = mkdir(path, mode); |
124 |
|
umask(mask); |
125 |
|
} else rc = mkdir(path, 0777); |
126 |
|
} |
127 |
|
*p = old; |
128 |
|
if(rc) bb_perror_msg_and_die("mkpath '%s'", path); |
129 |
} |
} |
130 |
free(line); |
if (!*p) break; |
131 |
lines_count--; |
} |
132 |
|
} |
133 |
|
|
134 |
|
// Slow, but small. |
135 |
|
static |
136 |
|
char *get_rawline(int fd, long *plen, char end) |
137 |
|
{ |
138 |
|
char c, *buf = NULL; |
139 |
|
long len = 0; |
140 |
|
|
141 |
|
for (;;) { |
142 |
|
if (1>read(fd, &c, 1)) break; |
143 |
|
if (!(len & 63)) buf=xrealloc(buf, len+65); |
144 |
|
if ((buf[len++]=c) == end) break; |
145 |
|
} |
146 |
|
if (buf) buf[len]=0; |
147 |
|
if (plen) *plen = len; |
148 |
|
|
149 |
|
return buf; |
150 |
|
} |
151 |
|
|
152 |
|
static |
153 |
|
char *get_line(int fd) |
154 |
|
{ |
155 |
|
long len; |
156 |
|
char *buf = get_rawline(fd, &len, '\n'); |
157 |
|
|
158 |
|
if (buf && buf[--len]=='\n') buf[len]=0; |
159 |
|
|
160 |
|
return buf; |
161 |
|
} |
162 |
|
|
163 |
|
// Copy the rest of in to out and close both files. |
164 |
|
static |
165 |
|
void xsendfile(int in, int out) |
166 |
|
{ |
167 |
|
long len; |
168 |
|
char buf[4096]; |
169 |
|
|
170 |
|
if (in<0) return; |
171 |
|
for (;;) { |
172 |
|
len = safe_read(in, buf, 4096); |
173 |
|
if (len<1) break; |
174 |
|
xwrite(out, buf, len); |
175 |
} |
} |
|
return lines_count; |
|
176 |
} |
} |
177 |
|
|
178 |
/* If patch_level is -1 it will remove all directory names |
// Copy the rest of the data and replace the original with the copy. |
179 |
* char *line must be greater than 4 chars |
static |
180 |
* returns NULL if the file doesnt exist or error |
void replace_tempfile(int fdin, int fdout, char **tempname) |
181 |
* returns malloc'ed filename |
{ |
182 |
* NB: frees 1st argument! |
char *temp = xstrdup(*tempname); |
183 |
*/ |
|
184 |
static char *extract_filename(char *line, int patch_level, const char *pat) |
temp[strlen(temp)-6]=0; |
185 |
{ |
if (fdin != -1) { |
186 |
char *temp = NULL, *filename_start_ptr = line + 4; |
xsendfile(fdin, fdout); |
187 |
|
xclose(fdin); |
188 |
if (strncmp(line, pat, 4) == 0) { |
} |
189 |
/* Terminate string at end of source filename */ |
xclose(fdout); |
190 |
line[strcspn(line, "\t\n\r")] = '\0'; |
rename(*tempname, temp); |
191 |
|
free(*tempname); |
192 |
/* Skip over (patch_level) number of leading directories */ |
free(temp); |
193 |
while (patch_level--) { |
*tempname = NULL; |
194 |
temp = strchr(filename_start_ptr, '/'); |
} |
195 |
if (!temp) |
|
196 |
break; |
// Open a temporary file to copy an existing file into. |
197 |
filename_start_ptr = temp + 1; |
static |
198 |
|
int copy_tempfile(int fdin, char *name, char **tempname) |
199 |
|
{ |
200 |
|
struct stat statbuf; |
201 |
|
int fd; |
202 |
|
|
203 |
|
*tempname = xasprintf("%sXXXXXX", name); |
204 |
|
fd = mkstemp(*tempname); |
205 |
|
if(-1 == fd) bb_perror_msg_and_die("no temp file"); |
206 |
|
|
207 |
|
// Set permissions of output file |
208 |
|
fstat(fdin, &statbuf); |
209 |
|
fchmod(fd, statbuf.st_mode); |
210 |
|
|
211 |
|
return fd; |
212 |
|
} |
213 |
|
|
214 |
|
// Abort the copy and delete the temporary file. |
215 |
|
static |
216 |
|
void delete_tempfile(int fdin, int fdout, char **tempname) |
217 |
|
{ |
218 |
|
close(fdin); |
219 |
|
close(fdout); |
220 |
|
unlink(*tempname); |
221 |
|
free(*tempname); |
222 |
|
*tempname = NULL; |
223 |
|
} |
224 |
|
|
225 |
|
|
226 |
|
|
227 |
|
struct globals { |
228 |
|
char *infile; |
229 |
|
long prefix; |
230 |
|
|
231 |
|
struct double_list *current_hunk; |
232 |
|
long oldline, oldlen, newline, newlen; |
233 |
|
long linenum; |
234 |
|
int context, state, filein, fileout, filepatch, hunknum; |
235 |
|
char *tempname; |
236 |
|
|
237 |
|
// was toys.foo: |
238 |
|
int exitval; |
239 |
|
}; |
240 |
|
#define TT (*ptr_to_globals) |
241 |
|
#define INIT_TT() do { \ |
242 |
|
SET_PTR_TO_GLOBALS(xzalloc(sizeof(TT))); \ |
243 |
|
} while (0) |
244 |
|
|
245 |
|
|
246 |
|
#define FLAG_STR "Rup:i:Nx" |
247 |
|
/* FLAG_REVERSE must be == 1! Code uses this fact. */ |
248 |
|
#define FLAG_REVERSE (1 << 0) |
249 |
|
#define FLAG_u (1 << 1) |
250 |
|
#define FLAG_PATHLEN (1 << 2) |
251 |
|
#define FLAG_INPUT (1 << 3) |
252 |
|
#define FLAG_IGNORE (1 << 4) |
253 |
|
//non-standard: |
254 |
|
#define FLAG_DEBUG (1 << 5) |
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++; |
348 |
|
if (option_mask32 & FLAG_IGNORE) { |
349 |
|
dummy_revert = 1; |
350 |
|
reverse ^= 1; |
351 |
|
continue; |
352 |
|
} |
353 |
|
fdprintf(2,"Possibly reversed hunk %d at %ld\n", |
354 |
|
TT.hunknum, TT.linenum); |
355 |
|
} |
356 |
|
} |
357 |
|
plist = plist->next; |
358 |
|
} |
359 |
|
|
360 |
|
// Is this EOF? |
361 |
|
if (!data) { |
362 |
|
if (PATCH_DEBUG) fdprintf(2, "INEOF\n"); |
363 |
|
|
364 |
|
// Does this hunk need to match EOF? |
365 |
|
if (!plist && matcheof) break; |
366 |
|
|
367 |
|
// File ended before we found a place for this hunk. |
368 |
|
fail_hunk(); |
369 |
|
goto done; |
370 |
|
} else if (PATCH_DEBUG) fdprintf(2, "IN: %s\n", data); |
371 |
|
check = dlist_add(&buf, data); |
372 |
|
|
373 |
|
// Compare this line with next expected line of hunk. |
374 |
|
// todo: teach the strcmp() to ignore whitespace. |
375 |
|
|
376 |
|
// A match can fail because the next line doesn't match, or because |
377 |
|
// we hit the end of a hunk that needed EOF, and this isn't EOF. |
378 |
|
|
379 |
|
// If match failed, flush first line of buffered data and |
380 |
|
// recheck buffered data for a new match until we find one or run |
381 |
|
// out of buffer. |
382 |
|
|
383 |
|
for (;;) { |
384 |
|
if (!plist || strcmp(check->data, plist->data+1)) { |
385 |
|
// Match failed. Write out first line of buffered data and |
386 |
|
// recheck remaining buffered data for a new match. |
387 |
|
|
388 |
|
if (PATCH_DEBUG) |
389 |
|
fdprintf(2, "NOT: %s\n", plist->data); |
390 |
|
|
391 |
|
TT.state = 3; |
392 |
|
check = llist_pop(&buf); |
393 |
|
check->prev->next = buf; |
394 |
|
buf->prev = check->prev; |
395 |
|
do_line(check); |
396 |
|
plist = TT.current_hunk; |
397 |
|
|
398 |
|
// If we've reached the end of the buffer without confirming a |
399 |
|
// match, read more lines. |
400 |
|
if (check==buf) { |
401 |
|
buf = 0; |
402 |
|
break; |
403 |
|
} |
404 |
|
check = buf; |
405 |
|
} else { |
406 |
|
if (PATCH_DEBUG) |
407 |
|
fdprintf(2, "MAYBE: %s\n", plist->data); |
408 |
|
// This line matches. Advance plist, detect successful match. |
409 |
|
plist = plist->next; |
410 |
|
if (!plist && !matcheof) goto out; |
411 |
|
check = check->next; |
412 |
|
if (check == buf) break; |
413 |
|
} |
414 |
} |
} |
|
temp = xstrdup(filename_start_ptr); |
|
415 |
} |
} |
416 |
free(line); |
out: |
417 |
return temp; |
// We have a match. Emit changed data. |
418 |
|
TT.state = "-+"[reverse ^ dummy_revert]; |
419 |
|
llist_free(TT.current_hunk, do_line); |
420 |
|
TT.current_hunk = NULL; |
421 |
|
TT.state = 1; |
422 |
|
done: |
423 |
|
if (buf) { |
424 |
|
buf->prev->next = NULL; |
425 |
|
llist_free(buf, do_line); |
426 |
|
} |
427 |
|
|
428 |
|
return TT.state; |
429 |
} |
} |
430 |
|
|
431 |
|
// Read a patch file and find hunks, opening/creating/deleting files. |
432 |
|
// Call apply_one_hunk() on each hunk. |
433 |
|
|
434 |
|
// state 0: Not in a hunk, look for +++. |
435 |
|
// state 1: Found +++ file indicator, look for @@ |
436 |
|
// state 2: In hunk: counting initial context lines |
437 |
|
// state 3: In hunk: getting body |
438 |
|
|
439 |
int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
440 |
int patch_main(int argc UNUSED_PARAM, char **argv) |
int patch_main(int argc UNUSED_PARAM, char **argv) |
441 |
{ |
{ |
442 |
struct stat saved_stat; |
int opts; |
443 |
char *patch_line; |
int reverse, state = 0; |
444 |
FILE *patch_file; |
char *oldname = NULL, *newname = NULL; |
445 |
int patch_level; |
char *opt_p, *opt_i; |
446 |
int ret = 0; |
|
447 |
char plus = '+'; |
INIT_TT(); |
448 |
unsigned opt; |
|
449 |
enum { |
opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i); |
450 |
OPT_R = (1 << 2), |
argv += optind; |
451 |
OPT_N = (1 << 3), |
reverse = opts & FLAG_REVERSE; |
452 |
/*OPT_f = (1 << 4), ignored */ |
TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative! |
453 |
/*OPT_E = (1 << 5), ignored, this is the default */ |
TT.filein = TT.fileout = -1; |
454 |
/*OPT_g = (1 << 6), ignored */ |
if (opts & FLAG_INPUT) { |
455 |
OPT_dry_run = (1 << 7) * ENABLE_LONG_OPTS, |
TT.filepatch = xopen_stdin(opt_i); |
456 |
}; |
} else { |
457 |
|
if (argv[0] && argv[1]) { |
458 |
xfunc_error_retval = 2; |
TT.filepatch = xopen_stdin(argv[1]); |
|
{ |
|
|
const char *p = "-1"; |
|
|
const char *i = "-"; /* compat */ |
|
|
#if ENABLE_LONG_OPTS |
|
|
static const char patch_longopts[] ALIGN1 = |
|
|
"strip\0" Required_argument "p" |
|
|
"input\0" Required_argument "i" |
|
|
"reverse\0" No_argument "R" |
|
|
"forward\0" No_argument "N" |
|
|
/* "Assume user knows what [s]he is doing, do not ask any questions": */ |
|
|
"force\0" No_argument "f" /*ignored*/ |
|
|
# if ENABLE_DESKTOP |
|
|
"remove-empty-files\0" No_argument "E" /*ignored*/ |
|
|
/* "Controls actions when a file is under RCS or SCCS control, |
|
|
* and does not exist or is read-only and matches the default version, |
|
|
* or when a file is under ClearCase control and does not exist..." |
|
|
* IOW: rather obscure option. |
|
|
* But Gentoo's portage does use -g0 */ |
|
|
"get\0" Required_argument "g" /*ignored*/ |
|
|
# endif |
|
|
"dry-run\0" No_argument "\xfd" |
|
|
# if ENABLE_DESKTOP |
|
|
"backup-if-mismatch\0" No_argument "\xfe" /*ignored*/ |
|
|
"no-backup-if-mismatch\0" No_argument "\xff" /*ignored*/ |
|
|
# endif |
|
|
; |
|
|
applet_long_options = patch_longopts; |
|
|
#endif |
|
|
/* -f,-E,-g are ignored */ |
|
|
opt = getopt32(argv, "p:i:RN""fEg:", &p, &i, NULL); |
|
|
if (opt & OPT_R) |
|
|
plus = '-'; |
|
|
patch_level = xatoi(p); /* can be negative! */ |
|
|
patch_file = xfopen_stdin(i); |
|
|
} |
|
|
|
|
|
patch_line = xmalloc_fgetline(patch_file); |
|
|
while (patch_line) { |
|
|
FILE *src_stream; |
|
|
FILE *dst_stream; |
|
|
//char *old_filename; |
|
|
char *new_filename; |
|
|
char *backup_filename = NULL; |
|
|
unsigned src_cur_line = 1; |
|
|
unsigned dst_cur_line = 0; |
|
|
unsigned dst_beg_line; |
|
|
unsigned bad_hunk_count = 0; |
|
|
unsigned hunk_count = 0; |
|
|
smallint copy_trailing_lines_flag = 0; |
|
|
|
|
|
/* Skip everything upto the "---" marker |
|
|
* No need to parse the lines "Only in <dir>", and "diff <args>" |
|
|
*/ |
|
|
do { |
|
|
/* Extract the filename used before the patch was generated */ |
|
|
new_filename = extract_filename(patch_line, patch_level, "--- "); |
|
|
// was old_filename above |
|
|
patch_line = xmalloc_fgetline(patch_file); |
|
|
if (!patch_line) goto quit; |
|
|
} while (!new_filename); |
|
|
free(new_filename); // "source" filename is irrelevant |
|
|
|
|
|
new_filename = extract_filename(patch_line, patch_level, "+++ "); |
|
|
if (!new_filename) { |
|
|
bb_error_msg_and_die("invalid patch"); |
|
459 |
} |
} |
460 |
|
} |
461 |
|
if (argv[0]) { |
462 |
|
oldname = xstrdup(argv[0]); |
463 |
|
newname = xstrdup(argv[0]); |
464 |
|
} |
465 |
|
|
466 |
/* Get access rights from the file to be patched */ |
// Loop through the lines in the patch |
467 |
if (stat(new_filename, &saved_stat) != 0) { |
for(;;) { |
468 |
char *slash = strrchr(new_filename, '/'); |
char *patchline; |
469 |
if (slash) { |
|
470 |
/* Create leading directories */ |
patchline = get_line(TT.filepatch); |
471 |
*slash = '\0'; |
if (!patchline) break; |
472 |
bb_make_directory(new_filename, -1, FILEUTILS_RECUR); |
|
473 |
*slash = '/'; |
// Other versions of patch accept damaged patches, |
474 |
} |
// so we need to also. |
475 |
src_stream = NULL; |
if (!*patchline) { |
476 |
saved_stat.st_mode = 0644; |
free(patchline); |
477 |
} else if (!(opt & OPT_dry_run)) { |
patchline = xstrdup(" "); |
|
backup_filename = xasprintf("%s.orig", new_filename); |
|
|
xrename(new_filename, backup_filename); |
|
|
src_stream = xfopen_for_read(backup_filename); |
|
|
} else |
|
|
src_stream = xfopen_for_read(new_filename); |
|
|
|
|
|
if (opt & OPT_dry_run) { |
|
|
dst_stream = xfopen_for_write("/dev/null"); |
|
|
} else { |
|
|
dst_stream = xfopen_for_write(new_filename); |
|
|
fchmod(fileno(dst_stream), saved_stat.st_mode); |
|
478 |
} |
} |
479 |
|
|
480 |
printf("patching file %s\n", new_filename); |
// Are we assembling a hunk? |
481 |
|
if (state >= 2) { |
482 |
|
if (*patchline==' ' || *patchline=='+' || *patchline=='-') { |
483 |
|
dlist_add(&TT.current_hunk, patchline); |
484 |
|
|
485 |
|
if (*patchline != '+') TT.oldlen--; |
486 |
|
if (*patchline != '-') TT.newlen--; |
487 |
|
|
488 |
|
// Context line? |
489 |
|
if (*patchline==' ' && state==2) TT.context++; |
490 |
|
else state=3; |
491 |
|
|
492 |
|
// If we've consumed all expected hunk lines, apply the hunk. |
493 |
|
|
494 |
/* Handle all hunks for this file */ |
if (!TT.oldlen && !TT.newlen) state = apply_one_hunk(); |
495 |
patch_line = xmalloc_fgets(patch_file); |
continue; |
|
while (patch_line) { |
|
|
unsigned count; |
|
|
unsigned src_beg_line; |
|
|
unsigned hunk_offset_start; |
|
|
unsigned src_last_line = 1; |
|
|
unsigned dst_last_line = 1; |
|
|
|
|
|
if ((sscanf(patch_line, "@@ -%d,%d +%d,%d", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3) |
|
|
&& (sscanf(patch_line, "@@ -%d +%d,%d", &src_beg_line, &dst_beg_line, &dst_last_line) < 2) |
|
|
) { |
|
|
/* No more hunks for this file */ |
|
|
break; |
|
496 |
} |
} |
497 |
if (plus != '+') { |
fail_hunk(); |
498 |
/* reverse patch */ |
state = 0; |
499 |
unsigned tmp = src_last_line; |
continue; |
500 |
src_last_line = dst_last_line; |
} |
501 |
dst_last_line = tmp; |
|
502 |
tmp = src_beg_line; |
// Open a new file? |
503 |
src_beg_line = dst_beg_line; |
if (!strncmp("--- ", patchline, 4) || !strncmp("+++ ", patchline, 4)) { |
504 |
dst_beg_line = tmp; |
char *s, **name = reverse ? &newname : &oldname; |
505 |
|
int i; |
506 |
|
|
507 |
|
if (*patchline == '+') { |
508 |
|
name = reverse ? &oldname : &newname; |
509 |
|
state = 1; |
510 |
} |
} |
|
hunk_count++; |
|
511 |
|
|
512 |
if (src_beg_line && dst_beg_line) { |
finish_oldfile(); |
513 |
/* Copy unmodified lines upto start of hunk */ |
|
514 |
/* src_beg_line will be 0 if it's a new file */ |
if (!argv[0]) { |
515 |
count = src_beg_line - src_cur_line; |
free(*name); |
516 |
if (copy_lines(src_stream, dst_stream, count)) { |
// Trim date from end of filename (if any). We don't care. |
517 |
bb_error_msg_and_die("bad src file"); |
for (s = patchline+4; *s && *s!='\t'; s++) |
518 |
|
if (*s=='\\' && s[1]) s++; |
519 |
|
i = atoi(s); |
520 |
|
if (i>1900 && i<=1970) |
521 |
|
*name = xstrdup("/dev/null"); |
522 |
|
else { |
523 |
|
*s = 0; |
524 |
|
*name = xstrdup(patchline+4); |
525 |
} |
} |
|
src_cur_line += count; |
|
|
dst_cur_line += count; |
|
|
copy_trailing_lines_flag = 1; |
|
526 |
} |
} |
|
src_last_line += hunk_offset_start = src_cur_line; |
|
|
dst_last_line += dst_cur_line; |
|
527 |
|
|
528 |
while (1) { |
// We defer actually opening the file because svn produces broken |
529 |
free(patch_line); |
// patches that don't signal they want to create a new file the |
530 |
patch_line = xmalloc_fgets(patch_file); |
// way the patch man page says, so you have to read the first hunk |
531 |
if (patch_line == NULL) |
// and _guess_. |
532 |
break; /* EOF */ |
|
533 |
if (!*patch_line) { |
// Start a new hunk? Usually @@ -oldline,oldlen +newline,newlen @@ |
534 |
/* whitespace-damaged patch with "" lines */ |
// but a missing ,value means the value is 1. |
535 |
free(patch_line); |
} else if (state == 1 && !strncmp("@@ -", patchline, 4)) { |
536 |
patch_line = xstrdup(" "); |
int i; |
537 |
|
char *s = patchline+4; |
538 |
|
|
539 |
|
// Read oldline[,oldlen] +newline[,newlen] |
540 |
|
|
541 |
|
TT.oldlen = TT.newlen = 1; |
542 |
|
TT.oldline = strtol(s, &s, 10); |
543 |
|
if (*s == ',') TT.oldlen=strtol(s+1, &s, 10); |
544 |
|
TT.newline = strtol(s+2, &s, 10); |
545 |
|
if (*s == ',') TT.newlen = strtol(s+1, &s, 10); |
546 |
|
|
547 |
|
TT.context = 0; |
548 |
|
state = 2; |
549 |
|
|
550 |
|
// If this is the first hunk, open the file. |
551 |
|
if (TT.filein == -1) { |
552 |
|
int oldsum, newsum, del = 0; |
553 |
|
char *name; |
554 |
|
|
555 |
|
oldsum = TT.oldline + TT.oldlen; |
556 |
|
newsum = TT.newline + TT.newlen; |
557 |
|
|
558 |
|
name = reverse ? oldname : newname; |
559 |
|
|
560 |
|
// We're deleting oldname if new file is /dev/null (before -p) |
561 |
|
// or if new hunk is empty (zero context) after patching |
562 |
|
if (!strcmp(name, "/dev/null") || !(reverse ? oldsum : newsum)) |
563 |
|
{ |
564 |
|
name = reverse ? newname : oldname; |
565 |
|
del++; |
566 |
} |
} |
567 |
if ((*patch_line != '-') && (*patch_line != '+') |
|
568 |
&& (*patch_line != ' ') |
// handle -p path truncation. |
569 |
) { |
for (i=0, s = name; *s;) { |
570 |
break; /* End of hunk */ |
if ((option_mask32 & FLAG_PATHLEN) && TT.prefix == i) break; |
571 |
|
if (*(s++)=='/') { |
572 |
|
name = s; |
573 |
|
i++; |
574 |
|
} |
575 |
} |
} |
576 |
if (*patch_line != plus) { /* '-' or ' ' */ |
|
577 |
char *src_line = NULL; |
if (del) { |
578 |
if (src_cur_line == src_last_line) |
printf("removing %s\n", name); |
579 |
break; |
xunlink(name); |
580 |
if (src_stream) { |
state = 0; |
581 |
src_line = xmalloc_fgets(src_stream); |
// If we've got a file to open, do so. |
582 |
if (src_line) { |
} else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) { |
583 |
int diff = strcmp(src_line, patch_line + 1); |
// If the old file was null, we're creating a new one. |
584 |
src_cur_line++; |
if (!strcmp(oldname, "/dev/null") || !oldsum) { |
585 |
free(src_line); |
printf("creating %s\n", name); |
586 |
if (diff) |
s = strrchr(name, '/'); |
587 |
src_line = NULL; |
if (s) { |
588 |
|
*s = 0; |
589 |
|
xmkpath(name, -1); |
590 |
|
*s = '/'; |
591 |
} |
} |
592 |
|
TT.filein = xopen3(name, O_CREAT|O_EXCL|O_RDWR, 0666); |
593 |
|
} else { |
594 |
|
printf("patching file %s\n", name); |
595 |
|
TT.filein = xopen(name, O_RDWR); |
596 |
} |
} |
597 |
/* Do not patch an already patched hunk with -N */ |
TT.fileout = copy_tempfile(TT.filein, name, &TT.tempname); |
598 |
if (src_line == 0 && (opt & OPT_N)) { |
TT.linenum = 0; |
599 |
continue; |
TT.hunknum = 0; |
|
} |
|
|
if (!src_line) { |
|
|
bb_error_msg("hunk #%u FAILED at %u", hunk_count, hunk_offset_start); |
|
|
bad_hunk_count++; |
|
|
break; |
|
|
} |
|
|
if (*patch_line != ' ') { /* '-' */ |
|
|
continue; |
|
|
} |
|
600 |
} |
} |
|
if (dst_cur_line == dst_last_line) |
|
|
break; |
|
|
fputs(patch_line + 1, dst_stream); |
|
|
dst_cur_line++; |
|
|
} /* end of while loop handling one hunk */ |
|
|
} /* end of while loop handling one file */ |
|
|
|
|
|
/* Cleanup last patched file */ |
|
|
if (copy_trailing_lines_flag) { |
|
|
copy_lines(src_stream, dst_stream, (unsigned)(-1)); |
|
|
} |
|
|
if (src_stream) { |
|
|
fclose(src_stream); |
|
|
} |
|
|
fclose(dst_stream); |
|
|
if (bad_hunk_count) { |
|
|
ret = 1; |
|
|
bb_error_msg("%u out of %u hunk FAILED", bad_hunk_count, hunk_count); |
|
|
} else { |
|
|
/* It worked, we can remove the backup */ |
|
|
if (backup_filename) { |
|
|
unlink(backup_filename); |
|
|
} |
|
|
if (!(opt & OPT_dry_run) |
|
|
&& ((dst_cur_line == 0) || (dst_beg_line == 0)) |
|
|
) { |
|
|
/* The new patched file is empty, remove it */ |
|
|
xunlink(new_filename); |
|
|
// /* old_filename and new_filename may be the same file */ |
|
|
// unlink(old_filename); |
|
601 |
} |
} |
602 |
|
|
603 |
|
TT.hunknum++; |
604 |
|
|
605 |
|
continue; |
606 |
} |
} |
607 |
free(backup_filename); |
|
608 |
//free(old_filename); |
// If we didn't continue above, discard this line. |
609 |
free(new_filename); |
free(patchline); |
610 |
} /* end of "while there are patch lines" */ |
} |
611 |
quit: |
|
612 |
/* 0 = SUCCESS |
finish_oldfile(); |
613 |
* 1 = Some hunks failed |
|
614 |
* 2 = More serious problems (exited earlier) |
if (ENABLE_FEATURE_CLEAN_UP) { |
615 |
*/ |
close(TT.filepatch); |
616 |
return ret; |
free(oldname); |
617 |
|
free(newname); |
618 |
|
} |
619 |
|
|
620 |
|
return TT.exitval; |
621 |
} |
} |