1/* patch.c - Apply a "universal" diff. 2 * 3 * Copyright 2007 Rob Landley <rob@landley.net> 4 * 5 * see http://opengroup.org/onlinepubs/9699919799/utilities/patch.html 6 * (But only does -u, because who still cares about "ed"?) 7 * 8 * TODO: 9 * -b backup 10 * -N ignore already applied 11 * -d chdir first 12 * -D define wrap #ifdef and #ifndef around changes 13 * -o outfile output here instead of in place 14 * -r rejectfile write rejected hunks to this file 15 * 16 * -E remove empty files --remove-empty-files 17 * -F fuzz (number, default 2) 18 * [file] which file to patch 19 20USE_PATCH(NEWTOY(patch, "(no-backup-if-mismatch)(dry-run)"USE_TOYBOX_DEBUG("x")"g#fulp#d:i:Rs(quiet)", TOYFLAG_USR|TOYFLAG_BIN)) 21 22config PATCH 23 bool "patch" 24 default y 25 help 26 usage: patch [-d DIR] [-i file] [-p depth] [-Rlsu] [--dry-run] 27 28 Apply a unified diff to one or more files. 29 30 -d Modify files in DIR 31 -i Input file (default=stdin) 32 -l Loose match (ignore whitespace) 33 -p Number of '/' to strip from start of file paths (default=all) 34 -R Reverse patch 35 -s Silent except for errors 36 -u Ignored (only handles "unified" diffs) 37 --dry-run Don't change files, just confirm patch applies 38 39 This version of patch only handles unified diffs, and only modifies 40 a file when all hunks to that file apply. Patch prints failed hunks 41 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/deleted as appropriate. 45*/ 46 47#define FOR_patch 48#include "toys.h" 49 50GLOBALS( 51 char *i, *d; 52 long p, g; 53 54 struct double_list *current_hunk; 55 long oldline, oldlen, newline, newlen; 56 long linenum; 57 int context, state, filein, fileout, filepatch, hunknum; 58 char *tempname; 59) 60 61// Dispose of a line of input, either by writing it out or discarding it. 62 63// state < 2: just free 64// state = 2: write whole line to stderr 65// state = 3: write whole line to fileout 66// state > 3: write line+1 to fileout when *line != state 67 68static void do_line(void *data) 69{ 70 struct double_list *dlist = (struct double_list *)data; 71 72 if (TT.state>1 && *dlist->data != TT.state) { 73 char *s = dlist->data+(TT.state>3); 74 int i = TT.state == 2 ? 2 : TT.fileout; 75 76 xwrite(i, s, strlen(s)); 77 xwrite(i, "\n", 1); 78 } 79 80 if (FLAG(x)) fprintf(stderr, "DO %d: %s\n", TT.state, dlist->data); 81 82 free(dlist->data); 83 free(data); 84} 85 86static void finish_oldfile(void) 87{ 88 if (TT.tempname) replace_tempfile(TT.filein, TT.fileout, &TT.tempname); 89 TT.fileout = TT.filein = -1; 90} 91 92static void fail_hunk(void) 93{ 94 if (!TT.current_hunk) return; 95 96 fprintf(stderr, "Hunk %d FAILED %ld/%ld.\n", 97 TT.hunknum, TT.oldline, TT.newline); 98 toys.exitval = 1; 99 100 // If we got to this point, we've seeked to the end. Discard changes to 101 // this file and advance to next file. 102 103 TT.state = 2; 104 llist_traverse(TT.current_hunk, do_line); 105 TT.current_hunk = NULL; 106 if (!FLAG(dry_run)) delete_tempfile(TT.filein, TT.fileout, &TT.tempname); 107 TT.state = 0; 108} 109 110// Compare ignoring whitespace. Just returns 0/1, no > or < 111static int loosecmp(char *aa, char *bb) 112{ 113 int a = 0, b = 0; 114 115 for (;;) { 116 while (isspace(aa[a])) a++; 117 while (isspace(bb[b])) b++; 118 if (aa[a] != bb[b]) return 1; 119 if (!aa[a]) return 0; 120 a++, b++; 121 } 122} 123 124// Given a hunk of a unified diff, make the appropriate change to the file. 125// This does not use the location information, but instead treats a hunk 126// as a sort of regex. Copies data from input to output until it finds 127// the change to be made, then outputs the changed data and returns. 128// (Finding EOF first is an error.) This is a single pass operation, so 129// multiple hunks must occur in order in the file. 130 131static int apply_one_hunk(void) 132{ 133 struct double_list *plist, *buf = NULL, *check; 134 int matcheof, trailing = 0, reverse = FLAG(R), backwarn = 0; 135 int (*lcmp)(char *aa, char *bb); 136 137 lcmp = FLAG(l) ? (void *)loosecmp : (void *)strcmp; 138 dlist_terminate(TT.current_hunk); 139 140 // Match EOF if there aren't as many ending context lines as beginning 141 for (plist = TT.current_hunk; plist; plist = plist->next) { 142 if (plist->data[0]==' ') trailing++; 143 else trailing = 0; 144 if (FLAG(x)) fprintf(stderr, "HUNK:%s\n", plist->data); 145 } 146 matcheof = !trailing || trailing < TT.context; 147 148 if (FLAG(x)) fprintf(stderr,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N'); 149 150 // Loop through input data searching for this hunk. Match all context 151 // lines and all lines to be removed until we've found the end of a 152 // complete hunk. 153 plist = TT.current_hunk; 154 buf = NULL; 155 156 for (;;) { 157 char *data = get_line(TT.filein); 158 159 TT.linenum++; 160 // Figure out which line of hunk to compare with next. (Skip lines 161 // of the hunk we'd be adding.) 162 while (plist && *plist->data == "+-"[reverse]) { 163 if (data && !lcmp(data, plist->data+1)) 164 if (!backwarn) backwarn = TT.linenum; 165 plist = plist->next; 166 } 167 168 // Is this EOF? 169 if (!data) { 170 if (FLAG(x)) fprintf(stderr, "INEOF\n"); 171 172 // Does this hunk need to match EOF? 173 if (!plist && matcheof) break; 174 175 if (backwarn && !FLAG(s)) 176 fprintf(stderr, "Possibly reversed hunk %d at %ld\n", 177 TT.hunknum, TT.linenum); 178 179 // File ended before we found a place for this hunk. 180 fail_hunk(); 181 goto done; 182 } else if (FLAG(x)) fprintf(stderr, "IN: %s\n", data); 183 check = dlist_add(&buf, data); 184 185 // Compare this line with next expected line of hunk. 186 187 // A match can fail because the next line doesn't match, or because 188 // we hit the end of a hunk that needed EOF, and this isn't EOF. 189 190 // If match failed, flush first line of buffered data and 191 // recheck buffered data for a new match until we find one or run 192 // out of buffer. 193 194 for (;;) { 195 if (!plist || lcmp(check->data, plist->data+1)) { 196 // Match failed. Write out first line of buffered data and 197 // recheck remaining buffered data for a new match. 198 199 if (FLAG(x)) { 200 int bug = 0; 201 202 if (!plist) fprintf(stderr, "NULL plist\n"); 203 else { 204 while (plist->data[bug] == check->data[bug]) bug++; 205 fprintf(stderr, "NOT(%d:%d!=%d): %s\n", bug, plist->data[bug], 206 check->data[bug], plist->data); 207 } 208 } 209 210 // If this hunk must match start of file, fail if it didn't. 211 if (!TT.context || trailing>TT.context) { 212 fail_hunk(); 213 goto done; 214 } 215 216 TT.state = 3; 217 do_line(check = dlist_pop(&buf)); 218 plist = TT.current_hunk; 219 220 // If we've reached the end of the buffer without confirming a 221 // match, read more lines. 222 if (!buf) break; 223 check = buf; 224 } else { 225 if (FLAG(x)) fprintf(stderr, "MAYBE: %s\n", plist->data); 226 // This line matches. Advance plist, detect successful match. 227 plist = plist->next; 228 if (!plist && !matcheof) goto out; 229 check = check->next; 230 if (check == buf) break; 231 } 232 } 233 } 234out: 235 // We have a match. Emit changed data. 236 TT.state = "-+"[reverse]; 237 llist_traverse(TT.current_hunk, do_line); 238 TT.current_hunk = NULL; 239 TT.state = 1; 240done: 241 if (buf) { 242 dlist_terminate(buf); 243 llist_traverse(buf, do_line); 244 } 245 246 return TT.state; 247} 248 249// read a filename that has been quoted or escaped 250char *unquote_file(char *filename) { 251 char *s = filename, *result, *t, *u; 252 int quote = 0, ch; 253 254 // quoted and escaped filenames are larger than the original 255 result = xmalloc(strlen(filename) + 1); 256 t = result; 257 if (*s == '"') { 258 s++; 259 quote = 1; 260 } 261 for (; *s && !(quote && *s == '"' && !s[1]); s++) { 262 // don't accept escape sequences unless the filename is quoted 263 if (quote && *s == '\\' && s[1]) { 264 if (s[1] >= '0' && s[1] < '8') { 265 *t++ = strtoul(s + 1, &u, 8); 266 s = u - 1; 267 } else { 268 ch = unescape(s[1]); 269 *t++ = ch ? ch : s[1]; 270 s++; 271 } 272 } else *t++ = *s; 273 } 274 *t = 0; 275 return result; 276} 277 278// Read a patch file and find hunks, opening/creating/deleting files. 279// Call apply_one_hunk() on each hunk. 280 281// state 0: Not in a hunk, look for +++. 282// state 1: Found +++ file indicator, look for @@ 283// state 2: In hunk: counting initial context lines 284// state 3: In hunk: getting body 285 286void patch_main(void) 287{ 288 int reverse = FLAG(R), state = 0, patchlinenum = 0, strip = 0; 289 char *oldname = NULL, *newname = NULL; 290 291 if (TT.i) TT.filepatch = xopenro(TT.i); 292 TT.filein = TT.fileout = -1; 293 294 if (TT.d) xchdir(TT.d); 295 296 // Loop through the lines in the patch 297 for (;;) { 298 char *patchline; 299 300 patchline = get_line(TT.filepatch); 301 if (!patchline) break; 302 303 // Other versions of patch accept damaged patches, so we need to also. 304 if (strip || !patchlinenum++) { 305 int len = strlen(patchline); 306 if (patchline[len-1] == '\r') { 307 if (!strip && !FLAG(s)) fprintf(stderr, "Removing DOS newlines\n"); 308 strip = 1; 309 patchline[len-1]=0; 310 } 311 } 312 if (!*patchline) { 313 free(patchline); 314 patchline = xstrdup(" "); 315 } 316 317 // Are we assembling a hunk? 318 if (state >= 2) { 319 if (*patchline==' ' || *patchline=='+' || *patchline=='-') { 320 dlist_add(&TT.current_hunk, patchline); 321 322 if (*patchline != '+') TT.oldlen--; 323 if (*patchline != '-') TT.newlen--; 324 325 // Context line? 326 if (*patchline==' ' && state==2) TT.context++; 327 else state=3; 328 329 // If we've consumed all expected hunk lines, apply the hunk. 330 if (!TT.oldlen && !TT.newlen) state = apply_one_hunk(); 331 continue; 332 } 333 dlist_terminate(TT.current_hunk); 334 fail_hunk(); 335 state = 0; 336 continue; 337 } 338 339 // Open a new file? 340 if (!strncmp("--- ", patchline, 4) || !strncmp("+++ ", patchline, 4)) { 341 char *s, **name = &oldname; 342 int i; 343 344 if (*patchline == '+') { 345 name = &newname; 346 state = 1; 347 } 348 349 free(*name); 350 finish_oldfile(); 351 352 // Trim date from end of filename (if any). We don't care. 353 for (s = patchline+4; *s && *s!='\t'; s++); 354 i = atoi(s); 355 if (i>1900 && i<=1970) *name = xstrdup("/dev/null"); 356 else { 357 *s = 0; 358 *name = unquote_file(patchline+4); 359 } 360 361 // We defer actually opening the file because svn produces broken 362 // patches that don't signal they want to create a new file the 363 // way the patch man page says, so you have to read the first hunk 364 // and _guess_. 365 366 // Start a new hunk? Usually @@ -oldline,oldlen +newline,newlen @@ 367 // but a missing ,value means the value is 1. 368 } else if (state == 1 && !strncmp("@@ -", patchline, 4)) { 369 int i; 370 char *s = patchline+4; 371 372 // Read oldline[,oldlen] +newline[,newlen] 373 374 TT.oldlen = TT.newlen = 1; 375 TT.oldline = strtol(s, &s, 10); 376 if (*s == ',') TT.oldlen=strtol(s+1, &s, 10); 377 TT.newline = strtol(s+2, &s, 10); 378 if (*s == ',') TT.newlen = strtol(s+1, &s, 10); 379 380 TT.context = 0; 381 state = 2; 382 383 // If this is the first hunk, open the file. 384 if (TT.filein == -1) { 385 int oldsum, newsum, del = 0; 386 char *name; 387 388 oldsum = TT.oldline + TT.oldlen; 389 newsum = TT.newline + TT.newlen; 390 391 name = reverse ? oldname : newname; 392 393 // We're deleting oldname if new file is /dev/null (before -p) 394 // or if new hunk is empty (zero context) after patching 395 if (!strcmp(name, "/dev/null") || !(reverse ? oldsum : newsum)) 396 { 397 name = reverse ? newname : oldname; 398 del++; 399 } 400 401 // handle -p path truncation. 402 for (i = 0, s = name; *s;) { 403 if (FLAG(p) && TT.p == i) break; 404 if (*s++ != '/') continue; 405 while (*s == '/') s++; 406 name = s; 407 i++; 408 } 409 410 if (del) { 411 if (!FLAG(s)) printf("removing %s\n", name); 412 xunlink(name); 413 state = 0; 414 // If we've got a file to open, do so. 415 } else if (!FLAG(p) || i <= TT.p) { 416 // If the old file was null, we're creating a new one. 417 if ((!strcmp(oldname, "/dev/null") || !oldsum) && access(name, F_OK)) 418 { 419 if (!FLAG(s)) printf("creating %s\n", name); 420 if (mkpath(name)) perror_exit("mkpath %s", name); 421 TT.filein = xcreate(name, O_CREAT|O_EXCL|O_RDWR, 0666); 422 } else { 423 if (!FLAG(s)) printf("patching %s\n", name); 424 TT.filein = xopenro(name); 425 } 426 if (FLAG(dry_run)) TT.fileout = xopen("/dev/null", O_RDWR); 427 else TT.fileout = copy_tempfile(TT.filein, name, &TT.tempname); 428 TT.linenum = 0; 429 TT.hunknum = 0; 430 } 431 } 432 433 TT.hunknum++; 434 435 continue; 436 } 437 438 // If we didn't continue above, discard this line. 439 free(patchline); 440 } 441 442 finish_oldfile(); 443 444 if (CFG_TOYBOX_FREE) { 445 close(TT.filepatch); 446 free(oldname); 447 free(newname); 448 } 449} 450