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