Files
Arquivotheca.Solaris-2.5/cmd/patch/patch.c
seta75D 7c4988eac0 Init
2021-10-11 19:38:01 -03:00

988 lines
23 KiB
C
Executable File

#ident "@(#)patch.c 1.2 95/09/07 SMI"
/*
* Copyright (c) 1995, Sun Microsystems, Inc.
* All Rights Reserved.
*
* This module contains IBM CONFIDENTIAL code. -- (IBM
* Confidential Restricted when combined with the aggregated
* modules for this product)
* OBJECT CODE ONLY SOURCE MATERIALS
* (C) COPYRIGHT International Business Machines Corp. 1993
* All Rights Reserved
*
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*
* (c) Copyright 1990, 1991, 1992, 1993 OPEN SOFTWARE FOUNDATION, INC.
* ALL RIGHTS RESERVED
*
* OSF/1 1.2
*
* Copyright 1986, Larry Wall
*
* This program may be copied as long as you don't try to make any
* money off of it, or pretend that you wrote it.
*/
/*
* File: patch.c
* Date: Sun Feb 12 18:48:18 PST 1995
*
* Description:
*
* patch - a program to apply diffs to original files
*
* Modifications:
* $Log$
*/
/*
* Include files:
*/
#include "common.h"
/*
* Global variables
*/
struct stat filestat; /* file statistics area */
/* Temp buffers for character conversion */
char *buf; /* general purpose buffer */
wchar_t *wbuf; /* general purpose buffer */
/* Flags */
bool verbose = TRUE; /* Tell all */
bool reverse = FALSE; /* Reverse patch files */
bool skip_rest_of_patch = FALSE; /* Set on error */
int strippath = INT_MAX; /* # path components to strip */
int cdiff_type = 0; /* diff type from cmd line */
int diff_type = 0; /* working diff type */
int dont_sync = FALSE; /* Assume we sync files */
long max_input;
/*
* Local variables.
*/
static LINENUM last_offset = 0;
static bool force = FALSE;
static bool noreverse = FALSE;
static bool canonicalize = FALSE;
static bool saveorig = FALSE;
static wchar_t if_defined[128] = { 0 }; /* #ifdef xyzzy */
static wchar_t if_ndefined[128] = { 0 }; /* #ifndef xyzzy */
static wchar_t else_defined[] = { L"#else\n" }; /* #else */
static wchar_t end_defined[128] = { 0 }; /* #endif xyzzy */
static char *patch_file = NULL;
static char *output_file = NULL;
static char *reject_file = NULL;
static char *input_file = NULL;
static LINENUM delta;
static int reject_written = 0;
/* To keep the compiler quiet... */
void main(int, char **);
/*
* Function: void usage(char *)
*
* Description:
*
* Print problem and usage message on stderr and exit with error.
*
* Inputs:
* problem -> Message to print.
*/
static void
usage(char *problem)
{
say("patch: %s.\n", problem);
fatal(gettext("Usage:\tpatch [-blNR] [-c|-e|-n] [-d dir]"
" [-D define] [-i patchfile]\\\n"
"\t [-o outfile] [-p num] [-r rejectfile] [file]\n"));
exit(ABORT_EXIT_VALUE);
}
/*
* Function: void reinitialize(void)
*
* Description:
*
* Prepare to find the next patch to do in the patch file.
*/
static void
reinitialize(void)
{
last_offset = 0;
delta = 0;
reverse = FALSE;
skip_rest_of_patch = FALSE;
}
/*
* Function: void process_arguments(int, char **)
*
* Description:
*
* Process commmand line arguments.
*/
static void
process_arguments(int argc, char **argv)
{
int c;
while ((c = getopt(argc, argv, ":bcd:D:ei:lnNo:p:r:Rs")) != -1) {
switch (c) {
case 'b': /* Save copy of originals */
saveorig = TRUE;
break;
case 'c': /* Interpret patch as context diff */
if (cdiff_type && cdiff_type != CONTEXT_DIFF) {
usage(gettext("-c, -e, and -n are mutually"
" exclusive options"));
/* NOTREACHED */
}
cdiff_type = CONTEXT_DIFF;
break;
case 'd': /* Chdir to optarg before processing */
if (chdir(optarg) < 0) {
fatal(gettext("cd to path %s failed.\n"),
optarg);
}
break;
case 'D': /* #Ifdef changes */
if (!isalpha(*optarg)) {
fatal(gettext("Argument to -D"
" is not an identifier.\n"));
}
(void) mbstowcs(wbuf, optarg,
sizeof (wbuf) / sizeof (wchar_t));
wcscpy(if_defined, L"#ifdef ");
(void) mbstowcs(&if_defined[wcslen(if_defined)],
optarg, 120);
wcscat(if_defined, L"\n");
wcscpy(if_ndefined, L"#ifndef ");
(void) mbstowcs(&if_ndefined[wcslen(if_ndefined)],
optarg, 120);
wcscat(if_ndefined, L"\n");
wcscpy(end_defined, L"#endif /* ");
(void) mbstowcs(&end_defined[wcslen(end_defined)],
optarg, 115);
wcscat(end_defined, L" */\n");
break;
case 'e': /* Interpret patch as ed script */
if (cdiff_type && cdiff_type != ED_DIFF) {
usage(gettext("-c, -e, and -n are mutually"
" exclusive options"));
/* NOTREACHED */
}
cdiff_type = ED_DIFF;
break;
case 'i': /* Read patch from file rather than stdin */
patch_file = optarg; /* patch input file */
break;
case 'l': /* Cause any blank sequences to match */
canonicalize = TRUE;
break;
case 'n': /* Interpret patch as normal diff */
if (cdiff_type && cdiff_type != NORMAL_DIFF) {
usage(gettext("-c, -e, and -n are mutually"
" exclusive options"));
/* NOTREACHED */
}
cdiff_type = NORMAL_DIFF;
break;
case 'N': /* Ignore previously applied patches */
noreverse = TRUE;
break;
case 'o': /* Output to named file */
/*
* If an empty string is passed I.E 'patch -o ""'
* we accept this to mean stdout too.
*/
if (*optarg == NULL)
output_file = "-";
else
output_file = optarg;
break;
case 'p': /* delete specified # components from paths */
if (!isdigit(*optarg)) {
strippath = 0;
optind--;
} else {
strippath = atoi(optarg);
}
break;
case 'r': /* Send rejects to named file */
reject_file = optarg;
break;
case 'R': /* reverse patch */
reverse = TRUE;
break;
case 's': /* Silent operation */
verbose = FALSE;
break;
case ':':
if (optopt == 'p') {
strippath = 0;
break;
}
default: /* None of the above */
usage(gettext("Invalid options"));
break;
}
}
/*
* Get file name if specified. Otherwise name is derived from
* patch file.
*/
if (argv[optind] != NULL) {
(void) mbstowcs(wbuf, argv[optind], max_input);
if (argv[optind + 1] != NULL) {
usage(gettext("Too many file arguments"));
}
if ((input_file = fetchname(wbuf, 0, 0)) == NULL) {
fatal(gettext("patch: Could not open \n"),
argv[optind]);
}
}
}
/*
* Function: void similar(wchar_t *, wchar_t *)
*
* Description:
*
* Do two lines match with canonicalized white space?
*
* Inputs:
* a -> A pointer to the first wide character string.
* b -> A pointer to the second wide character string.
*
* Returns:
* TRUE -> Lines compare favorably.
* FALSE -> Lines do not compare.
*/
static bool
similar(wchar_t *a, wchar_t *b)
{
int len = wcslen(b);
while (len) {
if (iswspace(*b)) { /* whitespace (or \n) to match? */
/* no corresponding whitespace? */
if (!iswspace(*a))
return (FALSE);
/* skip pattern whitespace */
while (len && iswspace(*b) && *b != '\n') {
b++;
len--;
}
/* skip target whitespace */
while (iswspace(*a) && *a != '\n')
a++;
/* should end in sync */
if (*a == '\n' || *b == '\n')
return (*a == *b);
/*
* Othewise start matching non-whitespace
* characters again (if any characters
* are left.
*/
continue;
} else if (*a++ != *b++) {
/*
* Non-whitespace chars did not match
* so lines are not similar.
*/
return (FALSE);
}
/*
* Non-whitespace chars matched
* so lines are still similar.
*/
len--;
}
/* Lines match */
return (TRUE);
}
/*
* Function: bool patch_match(file_info *, hunk_info *, LINENUM)
*
* Description:
*
* Does the hunk patterns match starting at line offset?
*
* Inputs:
* winfo -> A pointer to the file descriptor.
* hunk -> A pointer to the patch descriptor.
* offset -> Offset to start checking.
*/
static bool
patch_match(file_info *winfo, hunk_info *hunk, LINENUM offset)
{
wchar_t *patch_line, *input_line;
LINENUM pline;
for (pline = 0; pline < hunk->file1_lines; pline++) {
patch_line = hunk->lines[pline] + 2;
if ((input_line = fetch_line(winfo, offset + pline)) == NULL)
return (FALSE);
if (canonicalize) {
if (!similar(input_line, patch_line))
return (FALSE);
} else if (wcsncmp(input_line, patch_line, wcslen(patch_line)))
return (FALSE);
}
return (TRUE);
}
/*
* Function: LINENUM locate_hunk(file_info *, hunk_info *, LINENUM)
*
* Description:
*
* Attempt to find the right place to apply this hunk of patch.
* Fuzz is the current extent of the search. Most the time this
* is 0, but for misplaced patches our caller keeps expanding the
* search by one line. We take advantage of this and only check
* the extremes since we know every position in the middle has already
* failed.
*
* Inputs:
* winfo -> A pointer to the file descriptor.
* hunk -> A pointer to the patch descriptor.
* fuzz -> +/- Offset to check at.
*
* Returns:
* line number of match or -1 if no match was found yet.
*/
static LINENUM
locate_hunk(file_info *winfo, hunk_info *hunk, LINENUM fuzz)
{
LINENUM first_guess, max_pos_offset, max_neg_offset;
first_guess = hunk->file1_start + delta + last_offset - 1;
/* null range matches always */
if (hunk->file1_lines == 0)
return (first_guess);
max_pos_offset = first_guess + fuzz;
max_neg_offset = first_guess - fuzz;
/* do not try lines < 0 */
if (max_neg_offset < 0)
max_neg_offset = 0;
if (max_pos_offset < 0)
max_pos_offset = 0;
/* do not try lines > number of lines in file */
if ((max_neg_offset + hunk->file1_lines) > winfo->line_count)
max_neg_offset = winfo->line_count - hunk->file1_lines;
if ((max_pos_offset + hunk->file1_lines) > winfo->line_count)
max_pos_offset = winfo->line_count - hunk->file1_lines;
/*
* because of the way searches are expanded we only need
* to check the extremes... If fuzz is 0 then we don't
* even need to check both since they are identical.
*/
if (fuzz && patch_match(winfo, hunk, max_neg_offset)) {
last_offset = delta + hunk->file1_start - max_neg_offset - 1;
return (max_neg_offset);
}
if (patch_match(winfo, hunk, max_pos_offset)) {
last_offset = delta + hunk->file1_start - max_pos_offset - 1;
return (max_pos_offset);
}
return (-1);
}
/*
* Function: LINENUM check_hunk_location(file_info *, hunk_info *, int)
*
* Description:
*
*
* Inputs:
* winfo -> A pointer to the file descriptor.
* hunk -> A pointer to the patch descriptor.
* hunk_count -> Current hunk number.
*
* Returns:
* line number of match or -1 if no match was found.
*/
static LINENUM
check_hunk_location(file_info *winfo, hunk_info *hunk, int hunk_count)
{
LINENUM where, fuzz;
fuzz = 0;
do {
where = locate_hunk(winfo, hunk, fuzz);
if (hunk_count == 1 && where == -1 && !force) {
/*
* dwim for reversed patch?
* for normal diffs swapping a pure delete will always
* work and this is not what we want...
*/
if (diff_type == NORMAL_DIFF && hunk->file2_lines == 0)
continue;
pch_swap(hunk);
reverse = !reverse;
/* try again */
where = locate_hunk(winfo, hunk, fuzz);
/* didn't find it swapped */
if (where == -1) {
pch_swap(hunk);
reverse = !reverse;
} else if (noreverse) {
pch_swap(hunk);
reverse = !reverse;
say(gettext("Ignoring previously applied "
"(or reversed) patch.\n"));
skip_rest_of_patch = TRUE;
} else {
if (reverse) {
ask(gettext("Reversed (or previously "
"applied) patch detected! "
"Assume -R [%s] "),
nl_langinfo(YESSTR));
} else {
ask(gettext(
"Unreversed (or previously "
"applied) patch detected! "
"Ignore -R? [%s] "),
nl_langinfo(YESSTR));
}
if (rpmatch(buf) == 0) {
/* no */
ask(gettext("Apply anyway? [%s] "),
nl_langinfo(NOSTR));
if (rpmatch(buf) != 1) {
skip_rest_of_patch = TRUE;
}
where = NULL;
reverse = !reverse;
pch_swap(hunk);
}
}
}
} while (!skip_rest_of_patch && where == -1 && ++fuzz <= MAXFUZZ);
return (where);
}
/*
* Function: void abort_hunk(hunk_info *, file_info *)
*
* Description:
*
* We did not find the pattern, dump out the hunk so the user
* can handle it.
*
* Inputs:
* hunk -> A pointer to the patch descriptor.
* reject_info -> A pointer to the file descriptor.
*/
static void
abort_hunk(hunk_info *hunk, file_info *reject_info)
{
LINENUM i;
reject_written = 1;
insert_line(reject_info, L"***************\n", reject_info->line_count);
(void) sprintf(buf, "*** %ld,%ld ****\n", hunk->file1_start,
hunk->file1_start + hunk->file1_lines - 1);
(void) mbstowcs(wbuf, buf, max_input);
insert_line(reject_info, wbuf, reject_info->line_count);
for (i = 0; i < hunk->file1_lines; i++) {
insert_line(reject_info, hunk->lines[i],
reject_info->line_count);
}
(void) sprintf(buf, "--- %ld,%ld ----\n", hunk->file2_start,
hunk->file2_start + hunk->file2_lines - 1);
(void) mbstowcs(wbuf, buf, max_input);
insert_line(reject_info, wbuf, reject_info->line_count);
for (; i < hunk->line_count; i++) {
insert_line(reject_info, hunk->lines[i],
reject_info->line_count);
}
}
/*
* Function: void apply_hunk(file_info *, hunk_info *, LINENUM)
*
* Description:
*
* We found where to apply it (we hope), so do it.
*
* Hunk has already been error checked so we don't
* check for malformed expressions here.
*
* The hunk is applied in 2 phases:
* 1) Process the original lines (these include deletions, context lines,
* and change lines) we simply delete deletions and change lines.
* 2) Process the new lines (these include, additions, context lines,
* and change lines) we simply add additions and change lines.
*
* The tricky part here is to properly account for #ifdef lines and
* lines that would normally be deleted when ifdefs are required and
* to not account so if they are not. The array deltas is used to
* accomplish this. It tracks the number of additional lines that need
* to be added to the line count at each sync point (context line) for
* each context line in the second phase of application.
*
* Inputs:
* winfo -> A pointer to the file descriptor.
* hunk -> A pointer to the patch descriptor.
* offset -> Offset to start checking.
*/
static void
apply_hunk(file_info *winfo, hunk_info *hunk, LINENUM where)
{
LINENUM i, wh;
wchar_t *line;
wchar_t in_define = 0;
LINENUM *deltas;
LINENUM context_line;
deltas = allocate(sizeof (LINENUM) * (hunk->line_count + 1));
/* file1 processing */
wh = where;
context_line = 0;
for (i = 0; i < hunk->file1_lines; i++) {
line = hunk->lines[i];
switch (*line) {
case L' ': /* Context line */
if (*if_defined) {
/* Put out #endif if needed */
if (in_define) {
insert_line(winfo, end_defined, wh++);
deltas[context_line]++;
delta++;
in_define = 0;
}
context_line++;
}
wh++;
break;
case L'-': /* Delete line */
if (*if_defined) {
/* Put out #ifndef if needed */
if (!in_define) {
in_define = *line;
insert_line(winfo, if_ndefined, wh++);
deltas[context_line]++;
delta++;
}
wh++;
} else {
delete_line(winfo, wh);
delta--;
}
break;
case L'!': /* Change line */
if (*if_defined) {
/* Put out #else if needed */
if (!in_define) {
in_define = *line;
insert_line(winfo, else_defined, wh++);
deltas[context_line]++;
delta++;
}
/*
* Deltas are accounted for at matching change
*/
wh++;
} else {
delete_line(winfo, wh);
delta--;
}
break;
}
}
/* Put out #endif if needed */
if (*if_defined && in_define) {
insert_line(winfo, end_defined, wh++);
deltas[context_line]++;
delta++;
in_define = 0;
}
/* Finish adding lines */
wh = where;
context_line = 0;
for (i = hunk->file1_lines; i < hunk->line_count; i++) {
line = hunk->lines[i];
switch (*line) {
case L' ': /* Context line */
wh += deltas[context_line++];
if (*if_defined && in_define == L'+') {
/* Put out #endif */
insert_line(winfo, end_defined, wh++);
delta++;
in_define = 0;
}
wh++;
break;
case L'+': /* Add line */
if (*if_defined) {
/* Put out #ifdef if needed */
if (!in_define) {
in_define = *line;
insert_line(winfo, if_defined, wh++);
delta++;
}
}
insert_line(winfo, line + 2, wh++);
delta++;
break;
case L'!': /* Change line */
if (*if_defined) {
/* Put out #ifdef if needed */
if (!in_define) {
in_define = *line;
insert_line(winfo, if_defined, wh++);
delta++;
}
}
insert_line(winfo, line + 2, wh++);
delta++;
break;
}
}
/* Put out final #endif if needed */
if (*if_defined && in_define == L'+') {
insert_line(winfo, end_defined, wh++);
delta++;
}
in_define = 0;
/* Free up our temp space */
free(deltas);
}
/*
* Function: void cleanup(void)
*
* Description:
*
* Called on exit. Writes out any files that have not already
* been written to disk.
*
* Inputs:
* info -> A pointer to the file descriptor.
* data -> A pointer to the wide character string to add.
*/
void
cleanup(void)
{
unsigned long i;
if (dont_sync == FALSE) {
for (i = 0; i < opened_file_descriptors; i++) {
file_info *info;
if (opened_files[i]->flags & UPDATE_ON_EXIT) {
(void) open_file(opened_files[i]->name, 0);
sync_file(opened_files[i], NULL);
close_file(opened_files[i]);
}
}
}
for (i = 0; i < opened_file_descriptors; i++) {
unlink(opened_files[i]->temp_file);
}
}
/*
* Function: void ask_for_name(void)
*
* Description:
*
* Ask for a file to patch since none could be intuited.
*
* Returns:
*
* Name of a file that exists.
*/
static char *
ask_for_name(void)
{
struct stat statbuf;
char *name = NULL;
while (name == NULL) {
ask(gettext("File to patch: "));
if (*buf != '\n') {
(void) mbstowcs(wbuf, buf, max_input);
/* Process potential file name */
name = fetchname(wbuf, 0, FALSE);
}
if (name == NULL || lstat(name, &statbuf) == -1) {
ask(gettext("No file found -- skip this patch? [%s] "),
nl_langinfo(NOSTR));
if (rpmatch(buf) != 1) { /* not yes */
continue;
}
if (verbose)
say(gettext("Skipping patch...\n"));
skip_rest_of_patch = TRUE;
return (NULL);
}
}
return (savestr(name));
}
/*
* Function: void main(int, char **)
*
* Description:
*
* Process arguments and apply a set of diffs as appropriate.
*/
void
main(int argc, char **argv)
{
int winfo, pinfo, reject_info;
hunk_info *hunk;
LINENUM where;
struct stat statbuf;
char *workname, *reject_name;
int hunk_count;
int failed = 0;
int failtotal = 0;
/* Set locale environment variables local definitions */
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it wasn't */
#endif
(void) textdomain(TEXT_DOMAIN);
/*
* setup common buffers
*/
max_input = MAX_INPUT;
buf = allocate((max_input + 1) * sizeof (char));
wbuf = allocate((max_input + 1) * sizeof (wchar_t));
/* parse switches */
process_arguments(argc, argv);
/* make sure we clean up /tmp and sync files in case of disaster */
set_signals();
(void) atexit(cleanup);
/* For each patch in file open file, apply patch */
for (pinfo = open_patch_file(patch_file);
there_is_another_patch(opened_files[pinfo], &workname);
reinitialize()) {
if (!skip_rest_of_patch) {
if (input_file == NULL) {
/*
* No file was specified on command line so we
* take the file name from patch file (if_any)
*/
if (workname == NULL) {
/* No file name in patch file either! */
input_file = ask_for_name();
if (input_file == NULL)
continue;
workname = input_file;
}
} else {
/*
* file name was specified on command line or
* user was prompted for it so the name never
* changes
*/
workname = input_file;
}
winfo = open_file(workname, 0);
opened_files[winfo]->flags |= UPDATE_ON_EXIT;
if (saveorig && (output_file == 0))
opened_files[winfo]->flags |= SAVE_ORIGINAL;
}
/* for ed script just up and do it and exit */
if (diff_type == ED_DIFF) {
if (!skip_rest_of_patch)
do_ed_script(opened_files[winfo],
opened_files[pinfo]);
goto next;
}
/* Open up reject file */
if (reject_file != NULL) {
reject_name = reject_file;
} else {
(void) sprintf(buf, "%s.rej", workname);
reject_name = savestr(buf);
}
/* get an empty file */
reject_info = open_file(reject_name, 1);
/* apply each hunk of patch to file */
failed = hunk_count = 0;
while ((hunk = another_hunk(opened_files[pinfo])) != NULL) {
hunk_count++;
where = check_hunk_location(opened_files[winfo], hunk, hunk_count);
if (skip_rest_of_patch) {
abort_hunk(hunk, opened_files[reject_info]);
failed++;
if (verbose) {
say(gettext("Hunk #%d ignored at "
"line %ld.\n"), hunk_count, where);
}
} else if (where == (LINENUM) -1) {
abort_hunk(hunk, opened_files[reject_info]);
failed++;
if (verbose) {
say(gettext("Hunk #%d failed at "
"line %ld.\n"), hunk_count,
hunk->file1_start);
}
} else {
apply_hunk(opened_files[winfo], hunk, where);
if (verbose) {
if (last_offset == 1L) {
say(gettext("Hunk #%d succeeded"
" at line %ld (offset 1"
" line)\n"), hunk_count,
where - delta);
} else if (last_offset > 1L) {
say(gettext("Hunk #%d succeeded"
" at %ld (offset %ld lines)"
"\n"), hunk_count,
where - delta,
last_offset);
}
}
}
free_hunk(hunk);
}
/* and put the output where desired */
ignore_signals();
/* If patch failed write out stats */
if (failed) {
opened_files[reject_info]->flags |= UPDATE_ON_EXIT;
failtotal = FAIL_EXIT_VALUE;
if (skip_rest_of_patch) {
say(gettext("%d out of %d hunks "
"ignored: saving rejects to %s\n"),
failed, hunk_count, reject_name);
} else {
say(gettext("%d out of %d hunks "
"failed: saving rejects to %s\n"),
failed, hunk_count, reject_name);
}
}
/*
* If an output has been specified then all output goes to
* that file and the original files are not touched. We also
* Flush out the intermediate results after each patch in this
* case since the spec says so...
*/
close_file(opened_files[reject_info]);
next: if (output_file) {
/*
* We also need to create a .orig file if asked to
* and file exists already...
*/
if (saveorig && (strcmp(output_file, "-") != 0)) {
saveorig = 0;
if (lstat(output_file, &statbuf) != -1) {
(void) sprintf(buf, "%s.orig",
output_file);
(void) rename(output_file, buf);
}
}
opened_files[winfo]->flags &= ~UPDATE_ON_EXIT;
sync_file(opened_files[winfo], output_file);
}
close_file(opened_files[winfo]);
}
if (reject_written)
exit(REJECT_EXIT_VALUE);
exit(failtotal);
}