/*
* patch - a program to apply diffs to original files
*
* Copyright 1986, Larry Wall
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following condition is met:
* 1. Redistributions of source code must retain the above copyright notice,
* this condition and the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* -C option added in 1998, original code by Marc Espie, based on FreeBSD
* behaviour
*/
#include <sys/cdefs.h>
__RCSID("$NetBSD: patch.c,v 1.35 2024/07/12 15:48:39 manu Exp $");
/* make sure we clean up /tmp in case of disaster */
set_signals(0);
for (open_patch_file(filearg[1]); there_is_another_patch();
reinitialize_almost_everything()) {
/* for each patch in patch file */
warn_on_invalid_line = true;
if (outname == NULL)
outname = savestr(filearg[0]);
/* for ed script just up and do it and exit */
if (diff_type == ED_DIFF) {
do_ed_script();
continue;
}
/* initialize the patched file */
if (!skip_rest_of_patch)
init_output(TMPOUTNAME);
/* find out where all the lines are */
if (!skip_rest_of_patch)
scan_input(filearg[0]);
/* from here on, open no standard i/o files, because malloc */
/* might misfire and we can't catch it easily */
/* apply each hunk of patch */
hunk = 0;
failed = 0;
out_of_mem = false;
while (another_hunk()) {
hunk++;
fuzz = 0;
mymaxfuzz = pch_context();
if (maxfuzz < mymaxfuzz)
mymaxfuzz = maxfuzz;
if (!skip_rest_of_patch) {
do {
where = locate_hunk(fuzz);
if (hunk == 1 && where == 0 && !force) {
/* dwim for reversed patch? */
if (!pch_swap()) {
if (fuzz == 0)
say("Not enough memory to try swapped hunk! Assuming unswapped.\n");
continue;
}
reverse = !reverse;
/* try again */
where = locate_hunk(fuzz);
if (where == 0) {
/* didn't find it swapped */
if (!pch_swap())
/* put it back to normal */
fatal("lost hunk on alloc error!\n");
reverse = !reverse;
} else if (noreverse) {
if (!pch_swap())
/* put it back to normal */
fatal("lost hunk on alloc error!\n");
reverse = !reverse;
say("Ignoring previously applied (or reversed) patch.\n");
skip_rest_of_patch = true;
} else if (batch) {
if (verbose)
say("%seversed (or %spreviously applied) patch detected! %s -R.",
reverse ? "R" : "Unr",
reverse ? "" : "not ",
reverse ? "Assuming" : "Ignoring");
} else {
ask("%seversed (or %spreviously applied) patch detected! %s -R? [y] ",
reverse ? "R" : "Unr",
reverse ? "" : "not ",
reverse ? "Assume" : "Ignore");
if (*buf == 'n') {
ask("Apply anyway? [n] ");
if (*buf != 'y')
skip_rest_of_patch = true;
where = 0;
reverse = !reverse;
if (!pch_swap())
/* put it back to normal */
fatal("lost hunk on alloc error!\n");
}
}
}
} while (!skip_rest_of_patch && where == 0 &&
++fuzz <= mymaxfuzz);
if (skip_rest_of_patch) { /* just got decided */
if (ferror(ofp) || fclose(ofp)) {
say("Error writing %s\n",
TMPOUTNAME);
error = 1;
}
ofp = NULL;
}
}
newwhere = pch_newfirst() + last_offset;
if (skip_rest_of_patch) {
abort_hunk();
failed++;
if (verbose)
say("Hunk #%d ignored at %ld.\n",
hunk, newwhere);
} else if (where == 0) {
abort_hunk();
failed++;
if (verbose)
say("Hunk #%d failed at %ld.\n",
hunk, newwhere);
} else {
apply_hunk(where);
if (verbose) {
say("Hunk #%d succeeded at %ld",
hunk, newwhere);
if (fuzz != 0)
say(" with fuzz %ld", fuzz);
if (last_offset)
say(" (offset %ld line%s)",
last_offset,
last_offset == 1L ? "" : "s");
say(".\n");
}
}
}
if (out_of_mem && using_plan_a) {
Argc = Argc_last;
Argv = Argv_last;
say("\n\nRan out of memory using Plan A--trying again...\n\n");
if (ofp)
fclose(ofp);
ofp = NULL;
if (rejfp)
fclose(rejfp);
rejfp = NULL;
continue;
}
if (hunk == 0)
fatal("Internal error: hunk should not be 0\n");
/* finish spewing out the new file */
if (!skip_rest_of_patch && !spew_output()) {
say("Can't write %s\n", TMPOUTNAME);
error = 1;
}
/* and put the output where desired */
ignore_signals();
if (!skip_rest_of_patch) {
struct stat statbuf;
char *realout = outname;
where--;
while (pch_char(new) == '=' || pch_char(new) == '\n')
new++;
while (old <= lastline) {
if (pch_char(old) == '-') {
copy_till(where + old - 1, false);
if (do_defines) {
if (def_state == OUTSIDE) {
fputs(not_defined, ofp);
def_state = IN_IFNDEF;
} else if (def_state == IN_IFDEF) {
fputs(else_defined, ofp);
def_state = IN_ELSE;
}
fputs(pfetch(old), ofp);
}
last_frozen_line++;
old++;
} else if (new > pat_end) {
break;
} else if (pch_char(new) == '+') {
copy_till(where + old - 1, false);
if (do_defines) {
if (def_state == IN_IFNDEF) {
fputs(else_defined, ofp);
def_state = IN_ELSE;
} else if (def_state == OUTSIDE) {
fputs(if_defined, ofp);
def_state = IN_IFDEF;
}
}
fputs(pfetch(new), ofp);
new++;
} else if (pch_char(new) != pch_char(old)) {
say("Out-of-sync patch, lines %ld,%ld--mangled text or line numbers, maybe?\n",
pch_hunk_beg() + old,
pch_hunk_beg() + new);
#ifdef DEBUGGING
say("oldchar = '%c', newchar = '%c'\n",
pch_char(old), pch_char(new));
#endif
my_exit(2);
} else if (pch_char(new) == '!') {
copy_till(where + old - 1, false);
if (do_defines) {
fputs(not_defined, ofp);
def_state = IN_IFNDEF;
}
while (pch_char(old) == '!') {
if (do_defines) {
fputs(pfetch(old), ofp);
}
last_frozen_line++;
old++;
}
if (do_defines) {
fputs(else_defined, ofp);
def_state = IN_ELSE;
}
while (pch_char(new) == '!') {
fputs(pfetch(new), ofp);
new++;
}
} else {
if (pch_char(new) != ' ')
fatal("Internal error: expected ' '\n");
old++;
new++;
if (do_defines && def_state != OUTSIDE) {
fputs(end_defined, ofp);
def_state = OUTSIDE;
}
}
}
if (new <= pat_end && pch_char(new) == '+') {
copy_till(where + old - 1, false);
if (do_defines) {
if (def_state == OUTSIDE) {
fputs(if_defined, ofp);
def_state = IN_IFDEF;
} else if (def_state == IN_IFNDEF) {
fputs(else_defined, ofp);
def_state = IN_ELSE;
}
}
while (new <= pat_end && pch_char(new) == '+') {
fputs(pfetch(new), ofp);
new++;
}
}
if (do_defines && def_state != OUTSIDE) {
fputs(end_defined, ofp);
}
}
/*
* Open the new file.
*/
static void
init_output(const char *name)
{
ofp = fopen(name, "w");
if (ofp == NULL)
pfatal("can't create %s", name);
}
/*
* Open a file to put hunks we can't locate.
*/
static void
init_reject(const char *name)
{
rejfp = fopen(name, "w");
if (rejfp == NULL)
pfatal("can't create %s", name);
}
/*
* Copy input file to output, up to wherever hunk is to be applied.
* If endoffile is true, treat the last line specially since it may
* lack a newline.
*/
static void
copy_till(LINENUM lastline, bool endoffile)
{
if (last_frozen_line > lastline)
fatal("misordered hunks! output would be garbled\n");
while (last_frozen_line < lastline) {
if (++last_frozen_line == lastline && endoffile)
dump_line(last_frozen_line, !last_line_missing_eol);
else
dump_line(last_frozen_line, true);
}
}
/*
* Finish copying the input file to the output file.
*/
static bool
spew_output(void)
{
int rv;
/*
* Copy one line from input to output.
*/
static void
dump_line(LINENUM line, bool write_newline)
{
char *s;
s = ifetch(line, 0);
if (s == NULL)
return;
/* Note: string is not NUL terminated. */
for (; *s != '\n'; s++)
putc(*s, ofp);
if (write_newline)
putc('\n', ofp);
}
/*
* Does the patch pattern match at line base+offset?
*/
static bool
patch_match(LINENUM base, LINENUM offset, LINENUM fuzz)
{
LINENUM pline = 1 + fuzz;
LINENUM iline;
LINENUM pat_lines = pch_ptrn_lines() - fuzz;
const char *ilineptr;
const char *plineptr;
ssize_t plinelen;
for (iline = base + offset + fuzz; pline <= pat_lines; pline++, iline++) {
ilineptr = ifetch(iline, offset >= 0);
if (ilineptr == NULL)
return false;
plineptr = pfetch(pline);
plinelen = pch_line_len(pline);
if (canonicalize) {
if (!similar(ilineptr, plineptr, plinelen))
return false;
} else if (strnNE(ilineptr, plineptr, plinelen))
return false;
if (iline == input_lines) {
/*
* We are looking at the last line of the file.
* If the file has no eol, the patch line should
* not have one either and vice-versa. Note that
* plinelen > 0.
*/
if (last_line_missing_eol) {
if (plineptr[plinelen - 1] == '\n')
return false;
} else {
if (plineptr[plinelen - 1] != '\n')
return false;
}
}
}
return true;
}
/*
* Do two lines match with canonicalized white space?
*/
static bool
similar(const char *a, const char *b, ssize_t len)
{
while (len) {
if (isspace((unsigned char)*b)) { /* whitespace (or \n) to match? */
if (!isspace((unsigned char)*a)) /* no corresponding whitespace? */
return false;
while (len && isspace((unsigned char)*b) && *b != '\n')
b++, len--; /* skip pattern whitespace */
while (isspace((unsigned char)*a) && *a != '\n')
a++; /* skip target whitespace */
if (*a == '\n' || *b == '\n')
return (*a == *b); /* should end in sync */
} else if (*a++ != *b++) /* match non-whitespace chars */
return false;
else
len--; /* probably not necessary */
}
return true; /* actually, this is not reached */
/* since there is always a \n */
}