Bump to version 1.22.1
[platform/upstream/busybox.git] / editors / patch.c
index 4d1425e..13785ef 100644 (file)
-/* vi: set sw=4 ts=4: */
-/*
- *  busybox patch applet to handle the unified diff format.
- *  Copyright (C) 2003 Glenn McGrath <bug1@iinet.net.au>
+/* vi: set sw=4 ts=4:
  *
- *  Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ * Apply a "universal" diff.
+ * Adapted from toybox's patch implementation.
  *
- *  This applet is written to work with patches generated by GNU diff,
- *  where there is equivalent functionality busybox patch shall behave
- *  as per GNU patch.
+ * Copyright 2007 Rob Landley <rob@landley.net>
  *
- *  There is a SUSv3 specification for patch, however it looks to be
- *  incomplete, it doesnt even mention unified diff format.
- *  http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html
+ * see http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html
+ * (But only does -u, because who still cares about "ed"?)
  *
- *  Issues
- *   - Non-interactive
- *   - Patches must apply cleanly or patch (not just one hunk) will fail.
- *   - Reject file isnt saved
+ * TODO:
+ * -b backup
+ * -l treat all whitespace as a single space
+ * -d chdir first
+ * -D define wrap #ifdef and #ifndef around changes
+ * -o outfile output here instead of in place
+ * -r rejectfile write rejected hunks to this file
+ * --dry-run (regression!)
+ *
+ * -f force (no questions asked)
+ * -F fuzz (number, default 2)
+ * [file] which file to patch
  */
 
-#include <getopt.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include "busybox.h"
+//config:config PATCH
+//config:      bool "patch"
+//config:      default y
+//config:      help
+//config:        Apply a unified diff formatted patch.
 
-static unsigned int copy_lines(FILE *src_stream, FILE *dest_stream, const unsigned int lines_count)
-{
-       unsigned int i = 0;
+//applet:IF_PATCH(APPLET(patch, BB_DIR_USR_BIN, BB_SUID_DROP))
 
-       while (src_stream && (i < lines_count)) {
-               char *line;
-               line = xmalloc_fgets(src_stream);
-               if (line == NULL) {
-                       break;
-               }
-               if (fputs(line, dest_stream) == EOF) {
-                       bb_perror_msg_and_die("error writing to new file");
-               }
-               free(line);
+//kbuild:lib-$(CONFIG_PATCH) += patch.o
+
+//usage:#define patch_trivial_usage
+//usage:       "[OPTIONS] [ORIGFILE [PATCHFILE]]"
+//usage:#define patch_full_usage "\n\n"
+//usage:       IF_LONG_OPTS(
+//usage:       "       -p,--strip N            Strip N leading components from file names"
+//usage:     "\n       -i,--input DIFF         Read DIFF instead of stdin"
+//usage:     "\n       -R,--reverse            Reverse patch"
+//usage:     "\n       -N,--forward            Ignore already applied patches"
+/*usage:     "\n       --dry-run               Don't actually change files" - TODO */
+//usage:     "\n       -E,--remove-empty-files Remove output files if they become empty"
+//usage:       )
+//usage:       IF_NOT_LONG_OPTS(
+//usage:       "       -p N    Strip N leading components from file names"
+//usage:     "\n       -i DIFF Read DIFF instead of stdin"
+//usage:     "\n       -R      Reverse patch"
+//usage:     "\n       -N      Ignore already applied patches"
+//usage:     "\n       -E      Remove output files if they become empty"
+//usage:       )
+/* -u "interpret as unified diff" is supported but not documented: this info is not useful for --help */
+/* -x "debug" is supported but does nothing */
+//usage:
+//usage:#define patch_example_usage
+//usage:       "$ patch -p1 < example.diff\n"
+//usage:       "$ patch -p0 -i example.diff"
+
+#include "libbb.h"
+
+
+// libbb candidate?
 
-               i++;
+struct double_list {
+       struct double_list *next;
+       struct double_list *prev;
+       char *data;
+};
+
+// Free all the elements of a linked list
+// Call freeit() on each element before freeing it.
+static void dlist_free(struct double_list *list, void (*freeit)(void *data))
+{
+       while (list) {
+               void *pop = list;
+               list = list->next;
+               freeit(pop);
+               // Bail out also if list is circular.
+               if (list == pop) break;
        }
-       return i;
 }
 
-/* If patch_level is -1 it will remove all directory names
- * char *line must be greater than 4 chars
- * returns NULL if the file doesnt exist or error
- * returns malloc'ed filename
- */
+// Add an entry before "list" element in (circular) doubly linked list
+static struct double_list *dlist_add(struct double_list **list, char *data)
+{
+       struct double_list *llist;
+       struct double_list *line = xmalloc(sizeof(*line));
+
+       line->data = data;
+       llist = *list;
+       if (llist) {
+               struct double_list *p;
+               line->next = llist;
+               p = line->prev = llist->prev;
+               // (list is circular, we assume p is never NULL)
+               p->next = line;
+               llist->prev = line;
+       } else
+               *list = line->next = line->prev = line;
+
+       return line;
+}
+
 
-static char *extract_filename(char *line, int patch_level)
+struct globals {
+       char *infile;
+       long prefix;
+
+       struct double_list *current_hunk;
+
+       long oldline, oldlen, newline, newlen;
+       long linenum;
+       int context, state, hunknum;
+       int filein, fileout;
+       char *tempname;
+
+       int exitval;
+};
+#define TT (*ptr_to_globals)
+#define INIT_TT() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(TT))); \
+} while (0)
+
+
+#define FLAG_STR "Rup:i:NEx"
+/* FLAG_REVERSE must be == 1! Code uses this fact. */
+#define FLAG_REVERSE (1 << 0)
+#define FLAG_u       (1 << 1)
+#define FLAG_PATHLEN (1 << 2)
+#define FLAG_INPUT   (1 << 3)
+#define FLAG_IGNORE  (1 << 4)
+#define FLAG_RMEMPTY (1 << 5)
+/* Enable this bit and use -x for debug output: */
+#define FLAG_DEBUG   (0 << 6)
+
+// Dispose of a line of input, either by writing it out or discarding it.
+
+// state < 2: just free
+// state = 2: write whole line to stderr
+// state = 3: write whole line to fileout
+// state > 3: write line+1 to fileout when *line != state
+
+#define PATCH_DEBUG (option_mask32 & FLAG_DEBUG)
+
+static void do_line(void *data)
 {
-       char *temp, *filename_start_ptr = line + 4;
-       int i;
+       struct double_list *dlist = data;
 
-       /* Terminate string at end of source filename */
-       temp = strchr(filename_start_ptr, '\t');
-       if (temp) *temp = 0;
+       if (TT.state>1 && *dlist->data != TT.state)
+               fdprintf(TT.state == 2 ? 2 : TT.fileout,
+                       "%s\n", dlist->data+(TT.state>3 ? 1 : 0));
 
-       /* skip over (patch_level) number of leading directories */
-       for (i = 0; i < patch_level; i++) {
-               if(!(temp = strchr(filename_start_ptr, '/'))) break;
-               filename_start_ptr = temp + 1;
-       }
+       if (PATCH_DEBUG) fdprintf(2, "DO %d: %s\n", TT.state, dlist->data);
 
-       return xstrdup(filename_start_ptr);
+       free(dlist->data);
+       free(dlist);
 }
 
-static int file_doesnt_exist(const char *filename)
+static void finish_oldfile(void)
 {
-       struct stat statbuf;
-       return stat(filename, &statbuf);
+       if (TT.tempname) {
+               // Copy the rest of the data and replace the original with the copy.
+               char *temp;
+
+               if (TT.filein != -1) {
+                       bb_copyfd_eof(TT.filein, TT.fileout);
+                       xclose(TT.filein);
+               }
+               xclose(TT.fileout);
+
+               temp = xstrdup(TT.tempname);
+               temp[strlen(temp) - 6] = '\0';
+               rename(TT.tempname, temp);
+               free(temp);
+
+               free(TT.tempname);
+               TT.tempname = NULL;
+       }
+       TT.fileout = TT.filein = -1;
 }
 
-int patch_main(int argc, char **argv);
-int patch_main(int argc, char **argv)
+static void fail_hunk(void)
 {
-       int patch_level = -1;
-       char *patch_line;
-       int ret;
-       FILE *patch_file = NULL;
-
-       {
-               char *p, *i;
-               ret = getopt32(argc, argv, "p:i:", &p, &i);
-               if (ret & 1)
-                       patch_level = xatol_range(p, -1, USHRT_MAX);
-               if (ret & 2) {
-                       patch_file = xfopen(i, "r");
-               } else {
-                       patch_file = stdin;
-               }
-               ret = 0;
+       if (!TT.current_hunk) return;
+
+       fdprintf(2, "Hunk %d FAILED %ld/%ld.\n", TT.hunknum, TT.oldline, TT.newline);
+       TT.exitval = 1;
+
+       // If we got to this point, we've seeked to the end.  Discard changes to
+       // this file and advance to next file.
+
+       TT.state = 2;
+       TT.current_hunk->prev->next = NULL;
+       dlist_free(TT.current_hunk, do_line);
+       TT.current_hunk = NULL;
+
+       // Abort the copy and delete the temporary file.
+       close(TT.filein);
+       close(TT.fileout);
+       unlink(TT.tempname);
+       free(TT.tempname);
+       TT.tempname = NULL;
+
+       TT.state = 0;
+}
+
+// Given a hunk of a unified diff, make the appropriate change to the file.
+// This does not use the location information, but instead treats a hunk
+// as a sort of regex.  Copies data from input to output until it finds
+// the change to be made, then outputs the changed data and returns.
+// (Finding EOF first is an error.)  This is a single pass operation, so
+// multiple hunks must occur in order in the file.
+
+static int apply_one_hunk(void)
+{
+       struct double_list *plist, *buf = NULL, *check;
+       int matcheof = 0, reverse = option_mask32 & FLAG_REVERSE, backwarn = 0;
+       /* Do we try "dummy" revert to check whether
+        * to silently skip this hunk? Used to implement -N.
+        */
+       int dummy_revert = 0;
+
+       // Break doubly linked list so we can use singly linked traversal function.
+       TT.current_hunk->prev->next = NULL;
+
+       // Match EOF if there aren't as many ending context lines as beginning
+       for (plist = TT.current_hunk; plist; plist = plist->next) {
+               if (plist->data[0]==' ') matcheof++;
+               else matcheof = 0;
+               if (PATCH_DEBUG) fdprintf(2, "HUNK:%s\n", plist->data);
        }
+       matcheof = !matcheof || matcheof < TT.context;
 
-       patch_line = xmalloc_fgets(patch_file);
-       while (patch_line) {
-               FILE *src_stream;
-               FILE *dst_stream;
-               char *original_filename;
-               char *new_filename;
-               char *backup_filename;
-               unsigned int src_cur_line = 1;
-               unsigned int dest_cur_line = 0;
-               unsigned int dest_beg_line;
-               unsigned int bad_hunk_count = 0;
-               unsigned int hunk_count = 0;
-               char copy_trailing_lines_flag = 0;
-
-               /* Skip everything upto the "---" marker
-                * No need to parse the lines "Only in <dir>", and "diff <args>"
-                */
-               while (patch_line && strncmp(patch_line, "--- ", 4) != 0) {
-                       free(patch_line);
-                       patch_line = xmalloc_fgets(patch_file);
-               }
-               /* FIXME: patch_line NULL check?? */
+       if (PATCH_DEBUG) fdprintf(2,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N');
 
-               /* Extract the filename used before the patch was generated */
-               original_filename = extract_filename(patch_line, patch_level);
-               free(patch_line);
+       // Loop through input data searching for this hunk.  Match all context
+       // lines and all lines to be removed until we've found the end of a
+       // complete hunk.
+       plist = TT.current_hunk;
+       buf = NULL;
+       if (reverse ? TT.oldlen : TT.newlen) for (;;) {
+               char *data = xmalloc_reads(TT.filein, NULL);
 
-               patch_line = xmalloc_fgets(patch_file);
-               /* FIXME: NULL check?? */
-               if (strncmp(patch_line, "+++ ", 4) != 0) {
-                       ret = 2;
-                       bb_error_msg("invalid patch");
-                       continue;
-               }
-               new_filename = extract_filename(patch_line, patch_level);
-               free(patch_line);
-
-               if (file_doesnt_exist(new_filename)) {
-                       char *line_ptr;
-                       /* Create leading directories */
-                       line_ptr = strrchr(new_filename, '/');
-                       if (line_ptr) {
-                               *line_ptr = '\0';
-                               bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
-                               *line_ptr = '/';
-                       }
-                       dst_stream = xfopen(new_filename, "w+");
-                       backup_filename = NULL;
-               } else {
-                       backup_filename = xmalloc(strlen(new_filename) + 6);
-                       strcpy(backup_filename, new_filename);
-                       strcat(backup_filename, ".orig");
-                       if (rename(new_filename, backup_filename) == -1) {
-                               bb_perror_msg_and_die("cannot create file %s",
-                                               backup_filename);
+               TT.linenum++;
+
+               // Figure out which line of hunk to compare with next.  (Skip lines
+               // of the hunk we'd be adding.)
+               while (plist && *plist->data == "+-"[reverse]) {
+                       if (data && !strcmp(data, plist->data+1)) {
+                               if (!backwarn) {
+                                       backwarn = TT.linenum;
+                                       if (option_mask32 & FLAG_IGNORE) {
+                                               dummy_revert = 1;
+                                               reverse ^= 1;
+                                               continue;
+                                       }
+                               }
                        }
-                       dst_stream = xfopen(new_filename, "w");
+                       plist = plist->next;
                }
 
-               if ((backup_filename == NULL) || file_doesnt_exist(original_filename)) {
-                       src_stream = NULL;
-               } else {
-                       if (strcmp(original_filename, new_filename) == 0) {
-                               src_stream = xfopen(backup_filename, "r");
-                       } else {
-                               src_stream = xfopen(original_filename, "r");
-                       }
+               // Is this EOF?
+               if (!data) {
+                       if (PATCH_DEBUG) fdprintf(2, "INEOF\n");
+
+                       // Does this hunk need to match EOF?
+                       if (!plist && matcheof) break;
+
+                       if (backwarn)
+                               fdprintf(2,"Possibly reversed hunk %d at %ld\n",
+                                       TT.hunknum, TT.linenum);
+
+                       // File ended before we found a place for this hunk.
+                       fail_hunk();
+                       goto done;
                }
 
-               printf("patching file %s\n", new_filename);
-
-               /* Handle each hunk */
-               patch_line = xmalloc_fgets(patch_file);
-               while (patch_line) {
-                       unsigned int count;
-                       unsigned int src_beg_line;
-                       unsigned int unused;
-                       unsigned int hunk_offset_start = 0;
-                       int hunk_error = 0;
-
-                       /* This bit should be improved */
-                       if ((sscanf(patch_line, "@@ -%d,%d +%d,%d @@", &src_beg_line, &unused, &dest_beg_line, &unused) != 4) &&
-                               (sscanf(patch_line, "@@ -%d,%d +%d @@", &src_beg_line, &unused, &dest_beg_line) != 3) &&
-                               (sscanf(patch_line, "@@ -%d +%d,%d @@", &src_beg_line, &dest_beg_line, &unused) != 3)) {
-                               /* No more hunks for this file */
-                               break;
-                       }
-                       free(patch_line);
-                       hunk_count++;
-
-                       if (src_beg_line && dest_beg_line) {
-                               /* Copy unmodified lines upto start of hunk */
-                               /* src_beg_line will be 0 if its a new file */
-                               count = src_beg_line - src_cur_line;
-                               if (copy_lines(src_stream, dst_stream, count) != count) {
-                                       bb_error_msg_and_die("bad src file");
-                               }
-                               src_cur_line += count;
-                               dest_cur_line += count;
-                               copy_trailing_lines_flag = 1;
-                       }
-                       hunk_offset_start = src_cur_line;
-
-                       while ((patch_line = xmalloc_fgets(patch_file)) != NULL) {
-                               if ((*patch_line == '-') || (*patch_line == ' ')) {
-                                       char *src_line = NULL;
-                                       if (src_stream) {
-                                               src_line = xmalloc_fgets(src_stream);
-                                               if (!src_line) {
-                                                       hunk_error++;
-                                                       break;
-                                               } else {
-                                                       src_cur_line++;
-                                               }
-                                               if (strcmp(src_line, patch_line + 1) != 0) {
-                                                       bb_error_msg("hunk #%d FAILED at %d", hunk_count, hunk_offset_start);
-                                                       hunk_error++;
-                                                       free(patch_line);
-                                                       /* Probably need to find next hunk, etc... */
-                                                       /* but for now we just bail out */
-                                                       patch_line = NULL;
-                                                       break;
-                                               }
-                                               free(src_line);
-                                       }
-                                       if (*patch_line == ' ') {
-                                               fputs(patch_line + 1, dst_stream);
-                                               dest_cur_line++;
-                                       }
-                               } else if (*patch_line == '+') {
-                                       fputs(patch_line + 1, dst_stream);
-                                       dest_cur_line++;
-                               } else {
+               if (PATCH_DEBUG) fdprintf(2, "IN: %s\n", data);
+               check = dlist_add(&buf, data);
+
+               // Compare this line with next expected line of hunk.
+               // todo: teach the strcmp() to ignore whitespace.
+
+               // A match can fail because the next line doesn't match, or because
+               // we hit the end of a hunk that needed EOF, and this isn't EOF.
+
+               // If match failed, flush first line of buffered data and
+               // recheck buffered data for a new match until we find one or run
+               // out of buffer.
+
+               for (;;) {
+                       if (!plist || strcmp(check->data, plist->data+1)) {
+                               // Match failed.  Write out first line of buffered data and
+                               // recheck remaining buffered data for a new match.
+
+                               if (PATCH_DEBUG)
+                                       fdprintf(2, "NOT: %s\n", plist->data);
+
+                               TT.state = 3;
+                               check = buf;
+                               buf = buf->next;
+                               check->prev->next = buf;
+                               buf->prev = check->prev;
+                               do_line(check);
+                               plist = TT.current_hunk;
+
+                               // If we've reached the end of the buffer without confirming a
+                               // match, read more lines.
+                               if (check == buf) {
+                                       buf = NULL;
                                        break;
                                }
-                               free(patch_line);
-                       }
-                       if (hunk_error) {
-                               bad_hunk_count++;
+                               check = buf;
+                       } else {
+                               if (PATCH_DEBUG)
+                                       fdprintf(2, "MAYBE: %s\n", plist->data);
+                               // This line matches.  Advance plist, detect successful match.
+                               plist = plist->next;
+                               if (!plist && !matcheof) goto out;
+                               check = check->next;
+                               if (check == buf) break;
                        }
                }
+       }
+out:
+       // We have a match.  Emit changed data.
+       TT.state = "-+"[reverse ^ dummy_revert];
+       dlist_free(TT.current_hunk, do_line);
+       TT.current_hunk = NULL;
+       TT.state = 1;
+done:
+       if (buf) {
+               buf->prev->next = NULL;
+               dlist_free(buf, do_line);
+       }
+
+       return TT.state;
+}
+
+// Read a patch file and find hunks, opening/creating/deleting files.
+// Call apply_one_hunk() on each hunk.
+
+// state 0: Not in a hunk, look for +++.
+// state 1: Found +++ file indicator, look for @@
+// state 2: In hunk: counting initial context lines
+// state 3: In hunk: getting body
+
+int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int patch_main(int argc UNUSED_PARAM, char **argv)
+{
+       int opts;
+       int reverse, state = 0;
+       char *oldname = NULL, *newname = NULL;
+       char *opt_p, *opt_i;
+       long oldlen = oldlen; /* for compiler */
+       long newlen = newlen; /* for compiler */
 
-               /* Cleanup last patched file */
-               if (copy_trailing_lines_flag) {
-                       copy_lines(src_stream, dst_stream, -1);
+       INIT_TT();
+
+       opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i);
+       argv += optind;
+       reverse = opts & FLAG_REVERSE;
+       TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative!
+       TT.filein = TT.fileout = -1;
+       if (opts & FLAG_INPUT) {
+               xmove_fd(xopen_stdin(opt_i), STDIN_FILENO);
+       } else {
+               if (argv[0] && argv[1]) {
+                       xmove_fd(xopen_stdin(argv[1]), STDIN_FILENO);
                }
-               if (src_stream) {
-                       fclose(src_stream);
+       }
+       if (argv[0]) {
+               oldname = xstrdup(argv[0]);
+               newname = xstrdup(argv[0]);
+       }
+
+       // Loop through the lines in the patch
+       for(;;) {
+               char *patchline;
+
+               patchline = xmalloc_fgetline(stdin);
+               if (!patchline) break;
+
+               // Other versions of patch accept damaged patches,
+               // so we need to also.
+               if (!*patchline) {
+                       free(patchline);
+                       patchline = xstrdup(" ");
                }
-               if (dst_stream) {
-                       fclose(dst_stream);
+
+               // Are we assembling a hunk?
+               if (state >= 2) {
+                       if (*patchline==' ' || *patchline=='+' || *patchline=='-') {
+                               dlist_add(&TT.current_hunk, patchline);
+
+                               if (*patchline != '+') oldlen--;
+                               if (*patchline != '-') newlen--;
+
+                               // Context line?
+                               if (*patchline==' ' && state==2) TT.context++;
+                               else state=3;
+
+                               // If we've consumed all expected hunk lines, apply the hunk.
+
+                               if (!oldlen && !newlen) state = apply_one_hunk();
+                               continue;
+                       }
+                       fail_hunk();
+                       state = 0;
+                       continue;
                }
-               if (bad_hunk_count) {
-                       if (!ret) {
-                               ret = 1;
+
+               // Open a new file?
+               if (!strncmp("--- ", patchline, 4) || !strncmp("+++ ", patchline, 4)) {
+                       char *s, **name = reverse ? &newname : &oldname;
+                       int i;
+
+                       if (*patchline == '+') {
+                               name = reverse ? &oldname : &newname;
+                               state = 1;
                        }
-                       bb_error_msg("%d out of %d hunk FAILED", bad_hunk_count, hunk_count);
-               } else {
-                       /* It worked, we can remove the backup */
-                       if (backup_filename) {
-                               unlink(backup_filename);
+
+                       finish_oldfile();
+
+                       if (!argv[0]) {
+                               free(*name);
+                               // Trim date from end of filename (if any).  We don't care.
+                               for (s = patchline+4; *s && *s!='\t'; s++)
+                                       if (*s=='\\' && s[1]) s++;
+                               i = atoi(s);
+                               if (i>1900 && i<=1970)
+                                       *name = xstrdup("/dev/null");
+                               else {
+                                       *s = 0;
+                                       *name = xstrdup(patchline+4);
+                               }
                        }
-                       if ((dest_cur_line == 0) || (dest_beg_line == 0)) {
-                               /* The new patched file is empty, remove it */
-                               xunlink(new_filename);
-                               if (strcmp(new_filename, original_filename) != 0)
-                                       xunlink(original_filename);
+
+                       // We defer actually opening the file because svn produces broken
+                       // patches that don't signal they want to create a new file the
+                       // way the patch man page says, so you have to read the first hunk
+                       // and _guess_.
+
+               // Start a new hunk?  Usually @@ -oldline,oldlen +newline,newlen @@
+               // but a missing ,value means the value is 1.
+               } else if (state == 1 && !strncmp("@@ -", patchline, 4)) {
+                       int i;
+                       char *s = patchline+4;
+
+                       // Read oldline[,oldlen] +newline[,newlen]
+
+                       TT.oldlen = oldlen = TT.newlen = newlen = 1;
+                       TT.oldline = strtol(s, &s, 10);
+                       if (*s == ',') TT.oldlen = oldlen = strtol(s+1, &s, 10);
+                       TT.newline = strtol(s+2, &s, 10);
+                       if (*s == ',') TT.newlen = newlen = strtol(s+1, &s, 10);
+
+                       if (oldlen < 1 && newlen < 1)
+                               bb_error_msg_and_die("Really? %s", patchline);
+
+                       TT.context = 0;
+                       state = 2;
+
+                       // If this is the first hunk, open the file.
+                       if (TT.filein == -1) {
+                               int oldsum, newsum, empty = 0;
+                               char *name;
+
+                               oldsum = TT.oldline + oldlen;
+                               newsum = TT.newline + newlen;
+
+                               name = reverse ? oldname : newname;
+
+                               // We're deleting oldname if new file is /dev/null (before -p)
+                               // or if new hunk is empty (zero context) after patching
+                               if (!strcmp(name, "/dev/null") || !(reverse ? oldsum : newsum)) {
+                                       name = reverse ? newname : oldname;
+                                       empty++;
+                               }
+
+                               // handle -p path truncation.
+                               for (i = 0, s = name; *s;) {
+                                       if ((option_mask32 & FLAG_PATHLEN) && TT.prefix == i)
+                                               break;
+                                       if (*s++ != '/')
+                                               continue;
+                                       while (*s == '/')
+                                               s++;
+                                       i++;
+                                       name = s;
+                               }
+
+                               if (empty) {
+                                       // File is empty after the patches have been applied
+                                       state = 0;
+                                       if (option_mask32 & FLAG_RMEMPTY) {
+                                               // If flag -E or --remove-empty-files is set
+                                               printf("removing %s\n", name);
+                                               xunlink(name);
+                                       } else {
+                                               printf("patching file %s\n", name);
+                                               xclose(xopen(name, O_WRONLY | O_TRUNC));
+                                       }
+                               // If we've got a file to open, do so.
+                               } else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) {
+                                       struct stat statbuf;
+
+                                       // If the old file was null, we're creating a new one.
+                                       if (!strcmp(oldname, "/dev/null") || !oldsum) {
+                                               printf("creating %s\n", name);
+                                               s = strrchr(name, '/');
+                                               if (s) {
+                                                       *s = 0;
+                                                       bb_make_directory(name, -1, FILEUTILS_RECUR);
+                                                       *s = '/';
+                                               }
+                                               TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR);
+                                       } else {
+                                               printf("patching file %s\n", name);
+                                               TT.filein = xopen(name, O_RDONLY);
+                                       }
+
+                                       TT.tempname = xasprintf("%sXXXXXX", name);
+                                       TT.fileout = xmkstemp(TT.tempname);
+                                       // Set permissions of output file
+                                       fstat(TT.filein, &statbuf);
+                                       fchmod(TT.fileout, statbuf.st_mode);
+
+                                       TT.linenum = 0;
+                                       TT.hunknum = 0;
+                               }
                        }
+
+                       TT.hunknum++;
+
+                       continue;
                }
+
+               // If we didn't continue above, discard this line.
+               free(patchline);
        }
 
-       /* 0 = SUCCESS
-        * 1 = Some hunks failed
-        * 2 = More serious problems
-        */
-       return ret;
+       finish_oldfile();
+
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               free(oldname);
+               free(newname);
+       }
+
+       return TT.exitval;
 }