From: Joerg Sonnenberger Date: Fri, 24 Sep 2004 18:44:28 +0000 (+0000) Subject: Add GPL-free patch. This is the extended version of Larry Wall's original X-Git-Tag: v2.0.1~10089 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/48d201a5a8c1dab4aa7166b0812594c101fc43c3 Add GPL-free patch. This is the extended version of Larry Wall's original patch. Obtained-from: OpenBSD Submitted-by: Sascha Wildner --- diff --git a/usr.bin/patch/Makefile b/usr.bin/patch/Makefile new file mode 100644 index 0000000000..b6219ea02f --- /dev/null +++ b/usr.bin/patch/Makefile @@ -0,0 +1,9 @@ +# $OpenBSD: Makefile,v 1.3 2003/07/18 02:00:09 deraadt Exp $ +# $DragonFly: src/usr.bin/patch/Makefile,v 1.1 2004/09/24 18:44:28 joerg Exp $ + +PROG= patch +SRCS= patch.c pch.c inp.c util.c backupfile.c + +WARNS?= 3 + +.include diff --git a/usr.bin/patch/README b/usr.bin/patch/README new file mode 100644 index 0000000000..1df5dcdcd7 --- /dev/null +++ b/usr.bin/patch/README @@ -0,0 +1,126 @@ +$DragonFly: src/usr.bin/patch/Attic/README,v 1.1 2004/09/24 18:44:28 joerg Exp $ +####################################################################### + +this version modified to fit in with the 386bsd release. +this isn't gnu software, so we're not obligated to give +you the original sources -- if you want them, get them +from prep.ai.mit.edu:pub/gnu/patch-2.0.12u8.tar.z + + -- cgd + +####################################################################### + + +This version of patch contains modifications made by the Free Software +Foundation, summarized in the file ChangeLog. Primarily they are to +support the unified context diff format that GNU diff can produce, and +to support making GNU Emacs-style backup files. They also include +fixes for some bugs. + +There are two GNU variants of patch: this one, which retains Larry +Wall's interactive Configure script and has patchlevels starting with +`12u'; and another one that has a GNU-style non-interactive configure +script and accepts long-named options, and has patchlevels starting +with `12g'. Unlike the 12g variant, the 12u variant contains no +copylefted code, for the paranoid. The two variants are otherwise the +same. They should be available from the same places. + +The FSF is distributing this version of patch independently because as +of this writing, Larry Wall has not released a new version of patch +since mid-1988. I have heard that he has been too busy working on +other things, like Perl. + +Here is a wish list of some projects to improve patch: + +1. Correctly handle files and patchfiles that contain NUL characters. +This is hard to do straightforwardly; it would be less work to +adopt a kind of escape encoding internally. +Let ESC be a "control prefix". ESC @ stands for NUL. ESC [ stands for ESC. +You need to crunch this when reading input (replace fgets), +and when writing the output file (replace fputs), +but otherwise everything can go along as it does now. +Be careful to handle reject files correctly; +I think they are currently created using `write', not `fputs'. + +2. Correctly handle patches produced by GNU diff for files that do +not end with a newline. + +Please send bug reports for this version of patch to +bug-gnu-utils@prep.ai.mit.edu as well as to Larry Wall (lwall@netlabs.com). + --djm@gnu.ai.mit.edu (David MacKenzie) + + Patch Kit, Version 2.0 + + Copyright (c) 1988, 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. +-------------------------------------------------------------------------- + +Please read all the directions below before you proceed any further, and +then follow them carefully. Failure to do so may void your warranty. :-) + +After you have unpacked your kit, you should have all the files listed +in MANIFEST. + +Installation + +1) Run Configure. This will figure out various things about your system. + Some things Configure will figure out for itself, other things it will + ask you about. It will then proceed to make config.h, config.sh, and + Makefile. + + You might possibly have to trim # comments from the front of Configure + if your sh doesn't handle them, but all other # comments will be taken + care of. + + If you don't have sh, you'll have to rip the prototype of config.h out + of Configure and generate the defines by hand. + +2) Glance through config.h to make sure system dependencies are correct. + Most of them should have been taken care of by running the + Configure script. + + If you have any additional changes to make to the C definitions, they + can be done in the Makefile, or in config.h. Bear in mind that they may + get undone next time you run Configure. + +3) make + + This will attempt to make patch in the current directory. + +4) make install + + This will put patch into a public directory (normally /usr/local/bin). + It will also try to put the man pages in a reasonable place. It will not + nroff the man page, however. + +5) Read the manual entry before running patch. + +6) IMPORTANT! Help save the world! Communicate any problems and + suggested patches to me, lwall@netlabs.com (Larry Wall), + so we can keep the world in sync. If you have a problem, there's + someone else out there who either has had or will have the same problem. + + If possible, send in patches such that the patch program will apply them. + Context diffs are the best, then normal diffs. Don't send ed scripts-- + I've probably changed my copy since the version you have. + + Watch for patch patches in comp.sources.bugs. Patches will generally be + in a form usable by the patch program. Your current patch level + is shown in patchlevel.h. diff --git a/usr.bin/patch/backupfile.c b/usr.bin/patch/backupfile.c new file mode 100644 index 0000000000..bee45160c6 --- /dev/null +++ b/usr.bin/patch/backupfile.c @@ -0,0 +1,237 @@ +/* + * $OpenBSD: backupfile.c,v 1.18 2004/08/05 21:47:24 deraadt Exp $ + * $DragonFly: src/usr.bin/patch/backupfile.c,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * backupfile.c -- make Emacs style backup file names Copyright (C) 1990 Free + * Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * without restriction. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* + * David MacKenzie . Some algorithms adapted from GNU Emacs. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "backupfile.h" + + +#define ISDIGIT(c) (isascii (c) && isdigit (c)) + +/* Which type of backup file names are generated. */ +enum backup_type backup_type = none; + +/* + * The extension added to file names to produce a simple (as opposed to + * numbered) backup file name. + */ +char *simple_backup_suffix = "~"; + +static char *concat(const char *, const char *); +static char *make_version_name(const char *, int); +static int max_backup_version(const char *, const char *); +static int version_number(const char *, const char *, int); +static int argmatch(const char *, const char **); +static void invalid_arg(const char *, const char *, int); + +/* + * Return the name of the new backup file for file FILE, allocated with + * malloc. Return 0 if out of memory. FILE must not end with a '/' unless it + * is the root directory. Do not call this function if backup_type == none. + */ +char * +find_backup_file_name(const char *file) +{ + char *dir, *base_versions; + int highest_backup; + + if (backup_type == simple) + return concat(file, simple_backup_suffix); + base_versions = concat(basename(file), ".~"); + if (base_versions == NULL) + return NULL; + dir = dirname(file); + if (dir == NULL) { + free(base_versions); + return NULL; + } + highest_backup = max_backup_version(base_versions, dir); + free(base_versions); + if (backup_type == numbered_existing && highest_backup == 0) + return concat(file, simple_backup_suffix); + return make_version_name(file, highest_backup + 1); +} + +/* + * Return the number of the highest-numbered backup file for file FILE in + * directory DIR. If there are no numbered backups of FILE in DIR, or an + * error occurs reading DIR, return 0. FILE should already have ".~" appended + * to it. + */ +static int +max_backup_version(const char *file, const char *dir) +{ + DIR *dirp; + struct dirent *dp; + int highest_version, this_version, file_name_length; + + dirp = opendir(dir); + if (dirp == NULL) + return 0; + + highest_version = 0; + file_name_length = strlen(file); + + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_namlen <= file_name_length) + continue; + + this_version = version_number(file, dp->d_name, file_name_length); + if (this_version > highest_version) + highest_version = this_version; + } + closedir(dirp); + return highest_version; +} + +/* + * Return a string, allocated with malloc, containing "FILE.~VERSION~". + * Return 0 if out of memory. + */ +static char * +make_version_name(const char *file, int version) +{ + char *backup_name; + + if (asprintf(&backup_name, "%s.~%d~", file, version) == -1) + return NULL; + return backup_name; +} + +/* + * If BACKUP is a numbered backup of BASE, return its version number; + * otherwise return 0. BASE_LENGTH is the length of BASE. BASE should + * already have ".~" appended to it. + */ +static int +version_number(const char *base, const char *backup, int base_length) +{ + int version; + const char *p; + + version = 0; + if (!strncmp(base, backup, base_length) && ISDIGIT(backup[base_length])) { + for (p = &backup[base_length]; ISDIGIT(*p); ++p) + version = version * 10 + *p - '0'; + if (p[0] != '~' || p[1]) + version = 0; + } + return version; +} + +/* + * Return the newly-allocated concatenation of STR1 and STR2. If out of + * memory, return 0. + */ +static char * +concat(const char *str1, const char *str2) +{ + char *newstr; + + if (asprintf(&newstr, "%s%s", str1, str2) == -1) + return NULL; + return newstr; +} + +/* + * If ARG is an unambiguous match for an element of the null-terminated array + * OPTLIST, return the index in OPTLIST of the matched element, else -1 if it + * does not match any element or -2 if it is ambiguous (is a prefix of more + * than one element). + */ +static int +argmatch(const char *arg, const char **optlist) +{ + int i; /* Temporary index in OPTLIST. */ + size_t arglen; /* Length of ARG. */ + int matchind = -1; /* Index of first nonexact match. */ + int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */ + + arglen = strlen(arg); + + /* Test all elements for either exact match or abbreviated matches. */ + for (i = 0; optlist[i]; i++) { + if (!strncmp(optlist[i], arg, arglen)) { + if (strlen(optlist[i]) == arglen) + /* Exact match found. */ + return i; + else if (matchind == -1) + /* First nonexact match found. */ + matchind = i; + else + /* Second nonexact match found. */ + ambiguous = 1; + } + } + if (ambiguous) + return -2; + else + return matchind; +} + +/* + * Error reporting for argmatch. KIND is a description of the type of entity + * that was being matched. VALUE is the invalid value that was given. PROBLEM + * is the return value from argmatch. + */ +static void +invalid_arg(const char *kind, const char *value, int problem) +{ + fprintf(stderr, "patch: "); + if (problem == -1) + fprintf(stderr, "invalid"); + else /* Assume -2. */ + fprintf(stderr, "ambiguous"); + fprintf(stderr, " %s `%s'\n", kind, value); +} + +static const char *backup_args[] = { + "never", "simple", "nil", "existing", "t", "numbered", 0 +}; + +static enum backup_type backup_types[] = { + simple, simple, numbered_existing, + numbered_existing, numbered, numbered +}; + +/* + * Return the type of backup indicated by VERSION. Unique abbreviations are + * accepted. + */ +enum backup_type +get_version(const char *version) +{ + int i; + + if (version == NULL || *version == '\0') + return numbered_existing; + i = argmatch(version, backup_args); + if (i >= 0) + return backup_types[i]; + invalid_arg("version control type", version, i); + exit(2); +} diff --git a/usr.bin/patch/backupfile.h b/usr.bin/patch/backupfile.h new file mode 100644 index 0000000000..408f61c158 --- /dev/null +++ b/usr.bin/patch/backupfile.h @@ -0,0 +1,41 @@ +/* + * $OpenBSD: backupfile.h,v 1.6 2003/07/28 18:35:36 otto Exp $ + * $DragonFly: src/usr.bin/patch/backupfile.h,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * backupfile.h -- declarations for making Emacs style backup file names + * Copyright (C) 1990 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * without restriction. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* When to make backup files. */ +enum backup_type { + /* Never make backups. */ + none, + + /* Make simple backups of every file. */ + simple, + + /* + * Make numbered backups of files that already have numbered backups, + * and simple backups of the others. + */ + numbered_existing, + + /* Make numbered backups of every file. */ + numbered +}; + +extern enum backup_type backup_type; +extern char *simple_backup_suffix; + +char *find_backup_file_name(const char *file); +enum backup_type get_version(const char *vers); diff --git a/usr.bin/patch/common.h b/usr.bin/patch/common.h new file mode 100644 index 0000000000..06d9d8dca5 --- /dev/null +++ b/usr.bin/patch/common.h @@ -0,0 +1,117 @@ +/* + * $OpenBSD: common.h,v 1.25 2003/10/31 20:20:45 millert Exp $ + * $DragonFly: src/usr.bin/patch/common.h,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * 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 + +#define DEBUGGING + +/* constants */ + +#define MAXHUNKSIZE 100000 /* is this enough lines? */ +#define INITHUNKMAX 125 /* initial dynamic allocation size */ +#define MAXLINELEN 8192 +#define BUFFERSIZE 1024 + +#define SCCSPREFIX "s." +#define GET "get -e %s" +#define SCCSDIFF "get -p %s | diff - %s >/dev/null" + +#define RCSSUFFIX ",v" +#define CHECKOUT "co -l %s" +#define RCSDIFF "rcsdiff %s > /dev/null" + +#define ORIGEXT ".orig" +#define REJEXT ".rej" + +/* handy definitions */ + +#define strNE(s1,s2) (strcmp(s1, s2)) +#define strEQ(s1,s2) (!strcmp(s1, s2)) +#define strnNE(s1,s2,l) (strncmp(s1, s2, l)) +#define strnEQ(s1,s2,l) (!strncmp(s1, s2, l)) + +/* typedefs */ + +typedef long LINENUM; /* must be signed */ + +/* globals */ + +extern int filemode; + +extern char buf[MAXLINELEN];/* general purpose buffer */ + +extern bool using_plan_a; /* try to keep everything in memory */ +extern bool out_of_mem; /* ran out of memory in plan a */ + +#define MAXFILEC 2 + +extern char *filearg[MAXFILEC]; +extern bool ok_to_create_file; +extern char *outname; +extern char *origprae; + +extern char *TMPOUTNAME; +extern char *TMPINNAME; +extern char *TMPREJNAME; +extern char *TMPPATNAME; +extern bool toutkeep; +extern bool trejkeep; + +#ifdef DEBUGGING +extern int debug; +#endif + +extern bool force; +extern bool batch; +extern bool verbose; +extern bool reverse; +extern bool noreverse; +extern bool skip_rest_of_patch; +extern int strippath; +extern bool canonicalize; +/* TRUE if -C was specified on command line. */ +extern bool check_only; +extern bool warn_on_invalid_line; +extern bool last_line_missing_eol; + + +#define CONTEXT_DIFF 1 +#define NORMAL_DIFF 2 +#define ED_DIFF 3 +#define NEW_CONTEXT_DIFF 4 +#define UNI_DIFF 5 + +extern int diff_type; +extern char *revision; /* prerequisite revision, if any */ +extern LINENUM input_lines; /* how long is input file in lines */ + +extern int posix; + diff --git a/usr.bin/patch/inp.c b/usr.bin/patch/inp.c new file mode 100644 index 0000000000..5da11b3911 --- /dev/null +++ b/usr.bin/patch/inp.c @@ -0,0 +1,472 @@ +/* + * $OpenBSD: inp.c,v 1.32 2004/08/05 21:47:24 deraadt Exp $ + * $DragonFly: src/usr.bin/patch/inp.c,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "util.h" +#include "pch.h" +#include "inp.h" + + +/* Input-file-with-indexable-lines abstract type */ + +static off_t i_size; /* size of the input file */ +static char *i_womp; /* plan a buffer for entire file */ +static char **i_ptr; /* pointers to lines in i_womp */ + +static int tifd = -1; /* plan b virtual string array */ +static char *tibuf[2]; /* plan b buffers */ +static LINENUM tiline[2] = {-1, -1}; /* 1st line in each buffer */ +static LINENUM lines_per_buf; /* how many lines per buffer */ +static int tireclen; /* length of records in tmp file */ + +static bool rev_in_string(const char *); +static bool reallocate_lines(size_t *); + +/* returns false if insufficient memory */ +static bool plan_a(const char *); + +static void plan_b(const char *); + +/* New patch--prepare to edit another file. */ + +void +re_input(void) +{ + if (using_plan_a) { + i_size = 0; + free(i_ptr); + i_ptr = NULL; + if (i_womp != NULL) { + munmap(i_womp, i_size); + i_womp = NULL; + } + } else { + using_plan_a = true; /* maybe the next one is smaller */ + close(tifd); + tifd = -1; + free(tibuf[0]); + free(tibuf[1]); + tibuf[0] = tibuf[1] = NULL; + tiline[0] = tiline[1] = -1; + tireclen = 0; + } +} + +/* Constuct the line index, somehow or other. */ + +void +scan_input(const char *filename) +{ + if (!plan_a(filename)) + plan_b(filename); + if (verbose) { + say("Patching file %s using Plan %s...\n", filename, + (using_plan_a ? "A" : "B")); + } +} + +static bool +reallocate_lines(size_t *lines_allocated) +{ + char **p; + size_t new_size; + + new_size = *lines_allocated * 3 / 2; + p = realloc(i_ptr, (new_size + 2) * sizeof(char *)); + if (p == NULL) { /* shucks, it was a near thing */ + munmap(i_womp, i_size); + i_womp = NULL; + free(i_ptr); + i_ptr = NULL; + *lines_allocated = 0; + return false; + } + *lines_allocated = new_size; + i_ptr = p; + return true; +} + +/* Try keeping everything in memory. */ + +static bool +plan_a(const char *filename) +{ + int ifd, statfailed; + char *p, *s, lbuf[MAXLINELEN]; + LINENUM iline; + struct stat filestat; + off_t i; + ptrdiff_t sz; + size_t lines_allocated; + +#ifdef DEBUGGING + if (debug & 8) + return false; +#endif + + if (filename == NULL || *filename == '\0') + return false; + + statfailed = stat(filename, &filestat); + if (statfailed && ok_to_create_file) { + if (verbose) + say("(Creating file %s...)\n", filename); + + /* + * in check_patch case, we still display `Creating file' even + * though we're not. The rule is that -C should be as similar + * to normal patch behavior as possible + */ + if (check_only) + return true; + makedirs(filename, true); + close(creat(filename, 0666)); + statfailed = stat(filename, &filestat); + } + if (statfailed && check_only) + fatal("%s not found, -C mode, can't probe further\n", filename); + /* For nonexistent or read-only files, look for RCS or SCCS versions. */ + if (statfailed || + /* No one can write to it. */ + (filestat.st_mode & 0222) == 0 || + /* I can't write to it. */ + ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) { + char *cs = NULL, *filebase, *filedir; + struct stat cstat; + + filebase = basename(filename); + filedir = dirname(filename); + + /* Leave room in lbuf for the diff command. */ + s = lbuf + 20; + +#define try(f, a1, a2, a3) \ + (snprintf(s, sizeof lbuf - 20, f, a1, a2, a3), stat(s, &cstat) == 0) + + if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) || + try("%s/RCS/%s%s", filedir, filebase, "") || + try("%s/%s%s", filedir, filebase, RCSSUFFIX)) { + snprintf(buf, sizeof buf, CHECKOUT, filename); + snprintf(lbuf, sizeof lbuf, RCSDIFF, filename); + cs = "RCS"; + } else if (try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) || + try("%s/%s%s", filedir, SCCSPREFIX, filebase)) { + snprintf(buf, sizeof buf, GET, s); + snprintf(lbuf, sizeof lbuf, SCCSDIFF, s, filename); + cs = "SCCS"; + } else if (statfailed) + fatal("can't find %s\n", filename); + /* + * else we can't write to it but it's not under a version + * control system, so just proceed. + */ + if (cs) { + if (!statfailed) { + if ((filestat.st_mode & 0222) != 0) + /* The owner can write to it. */ + fatal("file %s seems to be locked " + "by somebody else under %s\n", + filename, cs); + /* + * It might be checked out unlocked. See if + * it's safe to check out the default version + * locked. + */ + if (verbose) + say("Comparing file %s to default " + "%s version...\n", + filename, cs); + if (system(lbuf)) + fatal("can't check out file %s: " + "differs from default %s version\n", + filename, cs); + } + if (verbose) + say("Checking out file %s from %s...\n", + filename, cs); + if (system(buf) || stat(filename, &filestat)) + fatal("can't check out file %s from %s\n", + filename, cs); + } + } + filemode = filestat.st_mode; + if (!S_ISREG(filemode)) + fatal("%s is not a normal file--can't patch\n", filename); + i_size = filestat.st_size; + if (out_of_mem) { + set_hunkmax(); /* make sure dynamic arrays are allocated */ + out_of_mem = false; + return false; /* force plan b because plan a bombed */ + } + if (i_size > SIZE_MAX) { + say("block too large to mmap\n"); + return false; + } + if ((ifd = open(filename, O_RDONLY)) < 0) + pfatal("can't open file %s", filename); + + i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0); + if (i_womp == MAP_FAILED) { + perror("mmap failed"); + i_womp = NULL; + close(ifd); + return false; + } + + close(ifd); + if (i_size) + madvise(i_womp, i_size, MADV_SEQUENTIAL); + + /* estimate the number of lines */ + lines_allocated = i_size / 25; + if (lines_allocated < 100) + lines_allocated = 100; + + if (!reallocate_lines(&lines_allocated)) + return false; + + /* now scan the buffer and build pointer array */ + iline = 1; + i_ptr[iline] = i_womp; + /* test for NUL too, to maintain the behavior of the original code */ + for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) { + if (*s == '\n') { + if (iline == (LINENUM)lines_allocated) { + if (!reallocate_lines(&lines_allocated)) + return false; + } + /* these are NOT NUL terminated */ + i_ptr[++iline] = s + 1; + } + } + /* if the last line contains no EOL, append one */ + if (i_size > 0 && i_womp[i_size - 1] != '\n') { + last_line_missing_eol = true; + /* fix last line */ + sz = s - i_ptr[iline]; + p = malloc(sz + 1); + if (p == NULL) { + free(i_ptr); + i_ptr = NULL; + munmap(i_womp, i_size); + i_womp = NULL; + return false; + } + + memcpy(p, i_ptr[iline], sz); + p[sz] = '\n'; + i_ptr[iline] = p; + /* count the extra line and make it point to some valid mem */ + i_ptr[++iline] = ""; + } else + last_line_missing_eol = false; + + input_lines = iline - 1; + + /* now check for revision, if any */ + + if (revision != NULL) { + if (!rev_in_string(i_womp)) { + if (force) { + if (verbose) + say("Warning: this file doesn't appear " + "to be the %s version--patching anyway.\n", + revision); + } else if (batch) { + fatal("this file doesn't appear to be the " + "%s version--aborting.\n", + revision); + } else { + ask("This file doesn't appear to be the " + "%s version--patch anyway? [n] ", + revision); + if (*buf != 'y') + fatal("aborted\n"); + } + } else if (verbose) + say("Good. This file appears to be the %s version.\n", + revision); + } + return true; /* plan a will work */ +} + +/* Keep (virtually) nothing in memory. */ + +static void +plan_b(const char *filename) +{ + FILE *ifp; + int i = 0, j, maxlen = 1; + char *p; + bool found_revision = (revision == NULL); + + using_plan_a = false; + if ((ifp = fopen(filename, "r")) == NULL) + pfatal("can't open file %s", filename); + (void) unlink(TMPINNAME); + if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0) + pfatal("can't open file %s", TMPINNAME); + while (fgets(buf, sizeof buf, ifp) != NULL) { + if (revision != NULL && !found_revision && rev_in_string(buf)) + found_revision = true; + if ((i = strlen(buf)) > maxlen) + maxlen = i; /* find longest line */ + } + last_line_missing_eol = i > 0 && buf[i - 1] != '\n'; + if (last_line_missing_eol && maxlen == i) + maxlen++; + + if (revision != NULL) { + if (!found_revision) { + if (force) { + if (verbose) + say("Warning: this file doesn't appear " + "to be the %s version--patching anyway.\n", + revision); + } else if (batch) { + fatal("this file doesn't appear to be the " + "%s version--aborting.\n", + revision); + } else { + ask("This file doesn't appear to be the %s " + "version--patch anyway? [n] ", + revision); + if (*buf != 'y') + fatal("aborted\n"); + } + } else if (verbose) + say("Good. This file appears to be the %s version.\n", + revision); + } + fseek(ifp, 0L, SEEK_SET); /* rewind file */ + lines_per_buf = BUFFERSIZE / maxlen; + tireclen = maxlen; + tibuf[0] = malloc(BUFFERSIZE + 1); + if (tibuf[0] == NULL) + fatal("out of memory\n"); + tibuf[1] = malloc(BUFFERSIZE + 1); + if (tibuf[1] == NULL) + fatal("out of memory\n"); + for (i = 1;; i++) { + p = tibuf[0] + maxlen * (i % lines_per_buf); + if (i % lines_per_buf == 0) /* new block */ + if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE) + pfatal("can't write temp file"); + if (fgets(p, maxlen + 1, ifp) == NULL) { + input_lines = i - 1; + if (i % lines_per_buf != 0) + if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE) + pfatal("can't write temp file"); + break; + } + j = strlen(p); + /* These are '\n' terminated strings, so no need to add a NUL */ + if (j == 0 || p[j - 1] != '\n') + p[j] = '\n'; + } + fclose(ifp); + close(tifd); + if ((tifd = open(TMPINNAME, O_RDONLY)) < 0) + pfatal("can't reopen file %s", TMPINNAME); +} + +/* + * Fetch a line from the input file, \n terminated, not necessarily \0. + */ +char * +ifetch(LINENUM line, int whichbuf) +{ + if (line < 1 || line > input_lines) { + if (warn_on_invalid_line) { + say("No such line %ld in input file, ignoring\n", line); + warn_on_invalid_line = false; + } + return NULL; + } + if (using_plan_a) + return i_ptr[line]; + else { + LINENUM offline = line % lines_per_buf; + LINENUM baseline = line - offline; + + if (tiline[0] == baseline) + whichbuf = 0; + else if (tiline[1] == baseline) + whichbuf = 1; + else { + tiline[whichbuf] = baseline; + + lseek(tifd, (off_t) (baseline / lines_per_buf * + BUFFERSIZE), SEEK_SET); + + if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0) + pfatal("error reading tmp file %s", TMPINNAME); + } + return tibuf[whichbuf] + (tireclen * offline); + } +} + +/* + * True if the string argument contains the revision number we want. + */ +static bool +rev_in_string(const char *string) +{ + const char *s; + int patlen; + + if (revision == NULL) + return true; + patlen = strlen(revision); + if (strnEQ(string, revision, patlen) && isspace(string[patlen])) + return true; + for (s = string; *s; s++) { + if (isspace(*s) && strnEQ(s + 1, revision, patlen) && + isspace(s[patlen + 1])) { + return true; + } + } + return false; +} diff --git a/usr.bin/patch/inp.h b/usr.bin/patch/inp.h new file mode 100644 index 0000000000..d0a90f736a --- /dev/null +++ b/usr.bin/patch/inp.h @@ -0,0 +1,34 @@ +/* + * $OpenBSD: inp.h,v 1.8 2003/08/15 08:00:51 otto Exp $ + * $DragonFly: src/usr.bin/patch/inp.h,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * 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 + */ + +void re_input(void); +void scan_input(const char *); +char *ifetch(LINENUM, int); diff --git a/usr.bin/patch/patch.1 b/usr.bin/patch/patch.1 new file mode 100644 index 0000000000..550eb92c1d --- /dev/null +++ b/usr.bin/patch/patch.1 @@ -0,0 +1,643 @@ +.\" $OpenBSD: patch.1,v 1.17 2003/10/31 20:20:45 millert Exp $ +.\" $DragonFly: src/usr.bin/patch/patch.1,v 1.1 2004/09/24 18:44:28 joerg Exp $ +.\" +.\" 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. +.\" +.Dd July 23, 2003 +.Dt PATCH 1 +.Os +.Sh NAME +.Nm patch +.Nd apply a diff file to an original +.Sh SYNOPSIS +.Nm patch +.Op Cm options +.Op Ar origfile Op Ar patchfile +.Nm patch +.Pf \*(Lt Ar patchfile +.Sh DESCRIPTION +.Nm +will take a patch file containing any of the four forms of difference +listing produced by the +.Xr diff 1 +program and apply those differences to an original file, +producing a patched version. +If +.Ar patchfile +is omitted, or is a hyphen, the patch will be read from the standard input. +.Pp +.Nm +will attempt to determine the type of the diff listing, unless over-ruled by a +.Fl c , +.Fl e , +.Fl n , +or +.Fl u +option. +Context diffs (old-style, new-style, and unified) and +normal diffs are applied directly by the +.Nm +program itself, whereas ed diffs are simply fed to the +.Xr ed 1 +editor via a pipe. +.Pp +If the +.Ar patchfile +contains more than one patch, +.Nm +will try to apply each of them as if they came from separate patch files. +This means, among other things, that it is assumed that the name of the file +to patch must be determined for each diff listing, and that the garbage before +each diff listing will be examined for interesting things such as file names +and revision level (see the section on +.Sx Filename Determination +below). +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl b , Fl Fl backup +Save a backup copy of the file before it is modified. +By default the original file is saved with a backup extension of +.Qq .orig +unless the file already has a numbered backup, in which case a numbered +backup is made. +This is equivalent to specifying +.Qo Fl V Ar existing Qc . +This option is currently the default but that will change in a future release. +.It Fl B , Fl Fl prefix +Causes the next argument to be interpreted as a prefix to the backup file +name. +If this argument is specified, any argument to +.Fl z +will be ignored. +.It Fl c , Fl Fl context +Forces +.Nm +to interpret the patch file as a context diff. +.It Fl C , Fl Fl check +Checks that the patch would apply cleanly, but does not modify anything. +.It Fl d , Fl Fl directory +Causes +.Nm +to interpret the next argument as a directory, and +.Xr cd 1 +to it before doing anything else. +.It Fl D , Fl Fl ifdef +Causes +.Nm +to use the +.Qq #ifdef...#endif +construct to mark changes. +The argument following will be used as the differentiating symbol. +Note that, unlike the C compiler, there must be a space between the +.Fl D +and the argument. +.It Fl e , Fl Fl ed +Forces +.Nm +to interpret the patch file as an +.Xr ed 1 +script. +.It Fl E , Fl Fl remove-empty-files +Causes +.Nm +to remove output files that are empty after the patches have been applied. +This option is useful when applying patches that create or remove files. +.It Fl f , Fl Fl force +Forces +.Nm +to assume that the user knows exactly what he or she is doing, and to not +ask any questions. +It assumes the following: +skip patches for which a file to patch can't be found; +patch files even though they have the wrong version for the +.Qq Prereq: +line in the patch; +and assume that patches are not reversed even if they look like they are. +This option does not suppress commentary; use +.Fl s +for that. +.It Xo +.Fl F Ns Aq Ar number , +.Fl Fl fuzz Aq Ar number +.Xc +Sets the maximum fuzz factor. +This option only applies to context diffs, and causes +.Nm +to ignore up to that many lines in looking for places to install a hunk. +Note that a larger fuzz factor increases the odds of a faulty patch. +The default fuzz factor is 2, and it may not be set to more than +the number of lines of context in the context diff, ordinarily 3. +.It Fl i , Fl Fl input +Causes the next argument to be interpreted as the input file name +(i.e. a patchfile). +This option may be specified multiple times. +.It Fl l , Fl Fl ignore-whitespace +Causes the pattern matching to be done loosely, in case the tabs and +spaces have been munged in your input file. +Any sequence of whitespace in the pattern line will match any sequence +in the input file. +Normal characters must still match exactly. +Each line of the context must still match a line in the input file. +.It Fl n , Fl Fl normal +Forces +.Nm +to interpret the patch file as a normal diff. +.It Fl N , Fl Fl forward +Causes +.Nm +to ignore patches that it thinks are reversed or already applied. +See also +.Fl R . +.It Fl o , Fl Fl output +Causes the next argument to be interpreted as the output file name. +.It Xo +.Fl p Ns Aq Ar number , +.Fl Fl strip Aq Ar number +.Xc +Sets the pathname strip count, +which controls how pathnames found in the patch file are treated, +in case you keep your files in a different directory than the person who sent +out the patch. +The strip count specifies how many slashes are to be stripped from +the front of the pathname. +(Any intervening directory names also go away.) +For example, supposing the file name in the patch file was +.Pa /u/howard/src/blurfl/blurfl.c : +.Pp +Setting +.Fl p Ns Ar 0 +gives the entire pathname unmodified. +.Pp +.Fl p Ns Ar 1 +gives +.Pp +.D1 Pa u/howard/src/blurfl/blurfl.c +.Pp +without the leading slash. +.Pp +.Fl p Ns Ar 4 +gives +.Pp +.D1 Pa blurfl/blurfl.c +.Pp +Not specifying +.Fl p +at all just gives you +.Pa blurfl.c , +unless all of the directories in the leading path +.Pq Pa u/howard/src/blurfl +exist and that path is relative, +in which case you get the entire pathname unmodified. +Whatever you end up with is looked for either in the current directory, +or the directory specified by the +.Fl d +option. +.It Fl r , Fl Fl reject-file +Causes the next argument to be interpreted as the reject file name. +.It Fl R , Fl Fl reverse +Tells +.Nm +that this patch was created with the old and new files swapped. +(Yes, I'm afraid that does happen occasionally, human nature being what it +is.) +.Nm +will attempt to swap each hunk around before applying it. +Rejects will come out in the swapped format. +The +.Fl R +option will not work with ed diff scripts because there is too little +information to reconstruct the reverse operation. +.Pp +If the first hunk of a patch fails, +.Nm +will reverse the hunk to see if it can be applied that way. +If it can, you will be asked if you want to have the +.Fl R +option set. +If it can't, the patch will continue to be applied normally. +(Note: this method cannot detect a reversed patch if it is a normal diff +and if the first command is an append (i.e. it should have been a delete) +since appends always succeed, due to the fact that a null context will match +anywhere. +Luckily, most patches add or change lines rather than delete them, so most +reversed normal diffs will begin with a delete, which will fail, triggering +the heuristic.) +.It Xo +.Fl s , Fl Fl quiet , +.Fl Fl silent +.Xc +Makes +.Nm +do its work silently, unless an error occurs. +.It Fl t , Fl Fl batch +Similar to +.Fl f , +in that it suppresses questions, but makes some different assumptions: +skip patches for which a file to patch can't be found (the same as +.Fl f ) ; +skip patches for which the file has the wrong version for the +.Qq Prereq: +line in the patch; +and assume that patches are reversed if they look like they are. +.It Fl u , Fl Fl unified +Forces +.Nm +to interpret the patch file as a unified context diff (a unidiff). +.It Fl v , Fl Fl version +Causes +.Nm +to print out its revision header and patch level. +.It Fl V , Fl Fl version-control +Causes the next argument to be interpreted as a method for creating +backup file names. +The type of backups made can also be given in the +.Ev PATCH_VERSION_CONTROL +or +.Ev VERSION_CONTROL +environment variables, which are overridden by this option. +The +.Fl B +option overrides this option, causing the prefix to always be used for +making backup file names. +The values of the +.Ev PATCH_VERSION_CONTROL +and +.Ev VERSION_CONTROL +environment variables and the argument to the +.Fl V +option are like the GNU Emacs +.Dq version-control +variable; they also recognize synonyms that are more descriptive. +The valid values are (unique abbreviations are accepted): +.Bl -tag -width Ds -offset indent +.It t , numbered +Always make numbered backups. +.It nil , existing +Make numbered backups of files that already have them, +simple backups of the others. +.It never , simple +Always make simple backups. +.El +.It Xo +.Fl x Ns Aq Ar number , +.Fl Fl debug Aq Ar number +.Xc +Sets internal debugging flags, and is of interest only to +.Nm +patchers. +.It Fl z , Fl Fl suffix +Causes the next argument to be interpreted as the backup extension, to be +used in place of +.Qq .orig . +.It Fl Fl posix +Enables strict +.St -p1003.2 +conformance, specifically: +.Bl -enum +.It +Backup files are not created unless the +.Fl b +option is specified. +.It +If unspecified, the file name used is the first of the old, new and +index files that exists. +.El +.El +.Ss Patch Application +.Nm +will try to skip any leading garbage, apply the diff, +and then skip any trailing garbage. +Thus you could feed an article or message containing a +diff listing to +.Nm patch , +and it should work. +If the entire diff is indented by a consistent amount, +this will be taken into account. +.Pp +With context diffs, and to a lesser extent with normal diffs, +.Nm +can detect when the line numbers mentioned in the patch are incorrect, +and will attempt to find the correct place to apply each hunk of the patch. +As a first guess, it takes the line number mentioned for the hunk, plus or +minus any offset used in applying the previous hunk. +If that is not the correct place, +.Nm +will scan both forwards and backwards for a set of lines matching the context +given in the hunk. +First +.Nm +looks for a place where all lines of the context match. +If no such place is found, and it's a context diff, and the maximum fuzz factor +is set to 1 or more, then another scan takes place ignoring the first and last +line of context. +If that fails, and the maximum fuzz factor is set to 2 or more, +the first two and last two lines of context are ignored, +and another scan is made. +.Pq The default maximum fuzz factor is 2. +.Pp +If +.Nm +cannot find a place to install that hunk of the patch, it will put the hunk +out to a reject file, which normally is the name of the output file plus +.Qq .rej . +(Note that the rejected hunk will come out in context diff form whether the +input patch was a context diff or a normal diff. +If the input was a normal diff, many of the contexts will simply be null.) +The line numbers on the hunks in the reject file may be different than +in the patch file: they reflect the approximate location patch thinks the +failed hunks belong in the new file rather than the old one. +.Pp +As each hunk is completed, you will be told whether the hunk succeeded or +failed, and which line (in the new file) +.Nm +thought the hunk should go on. +If this is different from the line number specified in the diff, +you will be told the offset. +A single large offset MAY be an indication that a hunk was installed in the +wrong place. +You will also be told if a fuzz factor was used to make the match, in which +case you should also be slightly suspicious. +.Ss Filename Determination +If no original file is specified on the command line, +.Nm +will try to figure out from the leading garbage what the name of the file +to edit is. +When checking a prospective file name, pathname components are stripped +as specified by the +.Fl p +option and the file's existence and writability are checked relative +to the current working directory (or the directory specified by the +.Fl d +option). +.Pp +If the diff is a context or unified diff, +.Nm +is able to determine the old and new file names from the diff header. +For context diffs, the +.Dq old +file is specified in the line beginning with +.Qq *** +and the +.Dq new +file is specified in the line beginning with +.Qq --- . +For a unified diff, the +.Dq old +file is specified in the line beginning with +.Qq --- +and the +.Dq new +file is specified in the line beginning with +.Qq +++ . +If there is an +.Qq Index: +line in the leading garbage (regardless of the diff type), +.Nm +will use the file name from that line as the +.Dq index +file. +.Pp +.Nm +will choose the file name by performing the following steps, with the first +match used: +.Bl -enum +.It +If +.Nm +is operating in strict +.St -p1003.2 +mode, the first of the +.Dq old , +.Dq new +and +.Dq index +file names that exist is used. +Otherwise, +.Nm +will examine either the +.Dq old +and +.Dq new +file names or, for a non-context diff, the +.Dq index +file name, and choose the file name with the fewest path components, +the shortest basename, and the shortest total file name length (in that order). +.It +If no file exists, +.Nm +checks for the existence of the files in an SCCS or RCS directory +(using the appropriate prefix or suffix) using the criteria specified +above. +If found, +.Nm +will attempt to get or check out the file. +.It +If no suitable file was found to patch, the patch file is a context or +unified diff, and the old file was zero length, the new file name is +created and used. +.It +If the file name still cannot be determined, +.Nm +will prompt the user for the file name to use. +.El +.Pp +Additionally, if the leading garbage contains a +.Qq Prereq:\ \& +line, +.Nm +will take the first word from the prerequisites line (normally a version +number) and check the input file to see if that word can be found. +If not, +.Nm +will ask for confirmation before proceeding. +.Pp +The upshot of all this is that you should be able to say, while in a news +interface, the following: +.Pp +.Dl | patch -d /usr/src/local/blurfl +.Pp +and patch a file in the blurfl directory directly from the article containing +the patch. +.Ss Backup Files +By default, the patched version is put in place of the original, with +the original file backed up to the same name with the extension +.Qq .orig , +or as specified by the +.Fl B , +.Fl V , +or +.Fl z +options. +The extension used for making backup files may also be specified in the +.Ev SIMPLE_BACKUP_SUFFIX +environment variable, which is overridden by the options above. +.Pp +If the backup file is a symbolic or hard link to the original file, +.Nm +creates a new backup file name by changing the first lowercase letter +in the last component of the file's name into uppercase. +If there are no more lowercase letters in the name, +it removes the first character from the name. +It repeats this process until it comes up with a +backup file that does not already exist or is not linked to the original file. +.Pp +You may also specify where you want the output to go with the +.Fl o +option; if that file already exists, it is backed up first. +.Ss Notes For Patch Senders +There are several things you should bear in mind if you are going to +be sending out patches: +.Pp +First, you can save people a lot of grief by keeping a +.Pa patchlevel.h +file which is patched to increment the patch level as the first diff in the +patch file you send out. +If you put a +.Qq Prereq: +line in with the patch, it won't let them apply +patches out of order without some warning. +.Pp +Second, make sure you've specified the file names right, either in a +context diff header, or with an +.Qq Index: +line. +If you are patching something in a subdirectory, be sure to tell the patch +user to specify a +.Fl p +option as needed. +.Pp +Third, you can create a file by sending out a diff that compares a +null file to the file you want to create. +This will only work if the file you want to create doesn't exist already in +the target directory. +.Pp +Fourth, take care not to send out reversed patches, since it makes people wonder +whether they already applied the patch. +.Pp +Fifth, while you may be able to get away with putting 582 diff listings into +one file, it is probably wiser to group related patches into separate files in +case something goes haywire. +.Sh ENVIRONMENT +.Bl -tag -width "PATCH_VERSION_CONTROL" -compact +.It Ev POSIXLY_CORRECT +When set, +.Nm +behaves as if the +.Fl Fl posix +option has been specified. +.It Ev SIMPLE_BACKUP_SUFFIX +Extension to use for backup file names instead of +.Qq .orig . +.It Ev TMPDIR +Directory to put temporary files in; default is +.Pa /tmp . +.It Ev PATCH_VERSION_CONTROL +Selects when numbered backup files are made. +.It Ev VERSION_CONTROL +Same as +.Ev PATCH_VERSION_CONTROL . +.El +.Sh FILES +.Bl -tag -width "$TMPDIR/patch*" -compact +.It Pa $TMPDIR/patch* +.Nm +temporary files +.It Pa /dev/tty +used to read input when +.Nm +prompts the user +.El +.Sh DIAGNOSTICS +Too many to list here, but generally indicative that +.Nm +couldn't parse your patch file. +.Pp +The message +.Qq Hmm... +indicates that there is unprocessed text in the patch file and that +.Nm +is attempting to intuit whether there is a patch in that text and, if so, +what kind of patch it is. +.Pp +The +.Nm +utility exits with one of the following values: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It \&0 +Successful completion. +.It \&1 +One or more lines were written to a reject file. +.It \*[Gt]\&1 +An error occurred. +.El +.Pp +When applying a set of patches in a loop it behooves you to check this +exit status so you don't apply a later patch to a partially patched file. +.Sh SEE ALSO +.Xr diff 1 +.Sh AUTHORS +.An Larry Wall +with many other contributors. +.Sh CAVEATS +.Nm +cannot tell if the line numbers are off in an ed script, and can only detect +bad line numbers in a normal diff when it finds a +.Qq change +or a +.Qq delete +command. +A context diff using fuzz factor 3 may have the same problem. +Until a suitable interactive interface is added, you should probably do +a context diff in these cases to see if the changes made sense. +Of course, compiling without errors is a pretty good indication that the patch +worked, but not always. +.Pp +.Nm +usually produces the correct results, even when it has to do a lot of +guessing. +However, the results are guaranteed to be correct only when the patch is +applied to exactly the same version of the file that the patch was +generated from. +.Sh BUGS +Could be smarter about partial matches, excessively deviant offsets and +swapped code, but that would take an extra pass. +.Pp +Check patch mode +.Pq Fl C +will fail if you try to check several patches in succession that build on +each other. +The entire +.Nm +code would have to be restructured to keep temporary files around so that it +can handle this situation. +.Pp +If code has been duplicated (for instance with #ifdef OLDCODE ... #else ... +#endif), +.Nm +is incapable of patching both versions, and, if it works at all, will likely +patch the wrong one, and tell you that it succeeded to boot. +.Pp +If you apply a patch you've already applied, +.Nm +will think it is a reversed patch, and offer to un-apply the patch. +This could be construed as a feature. diff --git a/usr.bin/patch/patch.c b/usr.bin/patch/patch.c new file mode 100644 index 0000000000..ef052109c0 --- /dev/null +++ b/usr.bin/patch/patch.c @@ -0,0 +1,973 @@ +/* + * $OpenBSD: patch.c,v 1.41 2004/07/09 19:13:46 otto Exp $ + * $DragonFly: src/usr.bin/patch/patch.c,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "util.h" +#include "pch.h" +#include "inp.h" +#include "backupfile.h" +#include "pathnames.h" + +int filemode = 0644; + +char buf[MAXLINELEN]; /* general purpose buffer */ + +bool using_plan_a = true; /* try to keep everything in memory */ +bool out_of_mem = false; /* ran out of memory in plan a */ + +#define MAXFILEC 2 + +char *filearg[MAXFILEC]; +bool ok_to_create_file = false; +char *outname = NULL; +char *origprae = NULL; +char *TMPOUTNAME; +char *TMPINNAME; +char *TMPREJNAME; +char *TMPPATNAME; +bool toutkeep = false; +bool trejkeep = false; +bool warn_on_invalid_line; +bool last_line_missing_eol; + +#ifdef DEBUGGING +int debug = 0; +#endif + +bool force = false; +bool batch = false; +bool verbose = true; +bool reverse = false; +bool noreverse = false; +bool skip_rest_of_patch = false; +int strippath = 957; +bool canonicalize = false; +bool check_only = false; +int diff_type = 0; +char *revision = NULL; /* prerequisite revision, if any */ +LINENUM input_lines = 0; /* how long is input file in lines */ +int posix = 0; /* strict POSIX mode? */ + +static void reinitialize_almost_everything(void); +static void get_some_switches(void); +static LINENUM locate_hunk(LINENUM); +static void abort_hunk(void); +static void apply_hunk(LINENUM); +static void init_output(const char *); +static void init_reject(const char *); +static void copy_till(LINENUM, bool); +static void spew_output(void); +static void dump_line(LINENUM, bool); +static bool patch_match(LINENUM, LINENUM, LINENUM); +static bool similar(const char *, const char *, int); +static void usage(void); + +/* true if -E was specified on command line. */ +static bool remove_empty_files = false; + +/* true if -R was specified on command line. */ +static bool reverse_flag_specified = false; + +/* buffer holding the name of the rejected patch file. */ +static char rejname[NAME_MAX + 1]; + +/* buffer for stderr */ +static char serrbuf[BUFSIZ]; + +/* how many input lines have been irretractibly output */ +static LINENUM last_frozen_line = 0; + +static int Argc; /* guess */ +static char **Argv; +static int Argc_last; /* for restarting plan_b */ +static char **Argv_last; + +static FILE *ofp = NULL; /* output file pointer */ +static FILE *rejfp = NULL; /* reject file pointer */ + +static int filec = 0; /* how many file arguments? */ +static LINENUM last_offset = 0; +static LINENUM maxfuzz = 2; + +/* patch using ifdef, ifndef, etc. */ +static bool do_defines = false; +/* #ifdef xyzzy */ +static char if_defined[128]; +/* #ifndef xyzzy */ +static char not_defined[128]; +/* #else */ +static const char else_defined[] = "#else\n"; +/* #endif xyzzy */ +static char end_defined[128]; + + +/* Apply a set of diffs as appropriate. */ + +int +main(int argc, char *argv[]) +{ + int error = 0, hunk, failed, patch_seen = 0, i, fd; + LINENUM where = 0, newwhere, fuzz, mymaxfuzz; + const char *tmpdir; + char *v; + + setbuf(stderr, serrbuf); + for (i = 0; i < MAXFILEC; i++) + filearg[i] = NULL; + + /* Cons up the names of the temporary files. */ + if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0') + tmpdir = _PATH_TMP; + for (i = strlen(tmpdir) - 1; i > 0 && tmpdir[i] == '/'; i--) + ; + i++; + if (asprintf(&TMPOUTNAME, "%.*s/patchoXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPOUTNAME)) < 0) + pfatal("can't create %s", TMPOUTNAME); + close(fd); + + if (asprintf(&TMPINNAME, "%.*s/patchiXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPINNAME)) < 0) + pfatal("can't create %s", TMPINNAME); + close(fd); + + if (asprintf(&TMPREJNAME, "%.*s/patchrXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPREJNAME)) < 0) + pfatal("can't create %s", TMPREJNAME); + close(fd); + + if (asprintf(&TMPPATNAME, "%.*s/patchpXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPPATNAME)) < 0) + pfatal("can't create %s", TMPPATNAME); + close(fd); + + v = getenv("SIMPLE_BACKUP_SUFFIX"); + if (v) + simple_backup_suffix = v; + else + simple_backup_suffix = ORIGEXT; + + /* parse switches */ + Argc = argc; + Argv = argv; + get_some_switches(); + + if (backup_type == none) { + if ((v = getenv("PATCH_VERSION_CONTROL")) == NULL) + v = getenv("VERSION_CONTROL"); + if (v != NULL || !posix) + backup_type = get_version(v); /* OK to pass NULL. */ + } + + /* 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 */ + + patch_seen = true; + 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); + + /* initialize reject file */ + init_reject(TMPREJNAME); + + /* 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 previously applied) patch detected! %s -R.", + reverse ? "R" : "Unr", + reverse ? "Assuming" : "Ignoring"); + } else { + ask("%seversed (or previously applied) patch detected! %s -R? [y] ", + reverse ? "R" : "Unr", + 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 */ + fclose(ofp); + 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(); + + /* and put the output where desired */ + ignore_signals(); + if (!skip_rest_of_patch) { + struct stat statbuf; + char *realout = outname; + + if (!check_only) { + if (move_file(TMPOUTNAME, outname) < 0) { + toutkeep = true; + realout = TMPOUTNAME; + chmod(TMPOUTNAME, filemode); + } else + chmod(outname, filemode); + + if (remove_empty_files && + stat(realout, &statbuf) == 0 && + statbuf.st_size == 0) { + if (verbose) + say("Removing %s (empty after patching).\n", + realout); + unlink(realout); + } + } + } + fclose(rejfp); + rejfp = NULL; + if (failed) { + error = 1; + if (*rejname == '\0') { + if (strlcpy(rejname, outname, + sizeof(rejname)) >= sizeof(rejname)) + fatal("filename %s is too long\n", outname); + if (strlcat(rejname, REJEXT, + sizeof(rejname)) >= sizeof(rejname)) + fatal("filename %s is too long\n", outname); + } + if (skip_rest_of_patch) { + say("%d out of %d hunks ignored--saving rejects to %s\n", + failed, hunk, rejname); + } else { + say("%d out of %d hunks failed--saving rejects to %s\n", + failed, hunk, rejname); + } + if (!check_only && move_file(TMPREJNAME, rejname) < 0) + trejkeep = true; + } + set_signals(1); + } + my_exit(error); + /* NOTREACHED */ +} + +/* Prepare to find the next patch to do in the patch file. */ + +static void +reinitialize_almost_everything(void) +{ + re_patch(); + re_input(); + + input_lines = 0; + last_frozen_line = 0; + + filec = 0; + if (!out_of_mem) { + free(filearg[0]); + filearg[0] = NULL; + } + + free(outname); + outname = NULL; + + last_offset = 0; + diff_type = 0; + + free(revision); + revision = NULL; + + reverse = reverse_flag_specified; + skip_rest_of_patch = false; + + get_some_switches(); +} + +/* Process switches and filenames. */ + +static void +get_some_switches(void) +{ + const char *options = "b::B:cCd:D:eEfF:i:lnNo:p:r:RstuvV:x:z:"; + static struct option longopts[] = { + {"backup", no_argument, 0, 'b'}, + {"batch", no_argument, 0, 't'}, + {"check", no_argument, 0, 'C'}, + {"context", no_argument, 0, 'c'}, + {"debug", required_argument, 0, 'x'}, + {"directory", required_argument, 0, 'd'}, + {"ed", no_argument, 0, 'e'}, + {"force", no_argument, 0, 'f'}, + {"forward", no_argument, 0, 'N'}, + {"fuzz", required_argument, 0, 'F'}, + {"ifdef", required_argument, 0, 'D'}, + {"input", required_argument, 0, 'i'}, + {"ignore-whitespace", no_argument, 0, 'l'}, + {"normal", no_argument, 0, 'n'}, + {"output", required_argument, 0, 'o'}, + {"prefix", required_argument, 0, 'B'}, + {"quiet", no_argument, 0, 's'}, + {"reject-file", required_argument, 0, 'r'}, + {"remove-empty-files", no_argument, 0, 'E'}, + {"reverse", no_argument, 0, 'R'}, + {"silent", no_argument, 0, 's'}, + {"strip", required_argument, 0, 'p'}, + {"suffix", required_argument, 0, 'z'}, + {"unified", no_argument, 0, 'u'}, + {"version", no_argument, 0, 'v'}, + {"version-control", required_argument, 0, 'V'}, + {"posix", no_argument, &posix, 1}, + {NULL, 0, 0, 0} + }; + int ch; + + rejname[0] = '\0'; + Argc_last = Argc; + Argv_last = Argv; + if (!Argc) + return; + optreset = optind = 1; + while ((ch = getopt_long(Argc, Argv, options, longopts, NULL)) != -1) { + switch (ch) { + case 'b': + if (backup_type == none) + backup_type = numbered_existing; + if (optarg == NULL) + break; + if (verbose) + say("Warning, the ``-b suffix'' option has been" + " obsoleted by the -z option.\n"); + /* FALLTHROUGH */ + case 'z': + /* must directly follow 'b' case for backwards compat */ + simple_backup_suffix = savestr(optarg); + break; + case 'B': + origprae = savestr(optarg); + break; + case 'c': + diff_type = CONTEXT_DIFF; + break; + case 'C': + check_only = true; + break; + case 'd': + if (chdir(optarg) < 0) + pfatal("can't cd to %s", optarg); + break; + case 'D': + do_defines = true; + if (!isalpha(*optarg) && *optarg != '_') + fatal("argument to -D is not an identifier\n"); + snprintf(if_defined, sizeof if_defined, + "#ifdef %s\n", optarg); + snprintf(not_defined, sizeof not_defined, + "#ifndef %s\n", optarg); + snprintf(end_defined, sizeof end_defined, + "#endif /* %s */\n", optarg); + break; + case 'e': + diff_type = ED_DIFF; + break; + case 'E': + remove_empty_files = true; + break; + case 'f': + force = true; + break; + case 'F': + maxfuzz = atoi(optarg); + break; + case 'i': + if (++filec == MAXFILEC) + fatal("too many file arguments\n"); + filearg[filec] = savestr(optarg); + break; + case 'l': + canonicalize = true; + break; + case 'n': + diff_type = NORMAL_DIFF; + break; + case 'N': + noreverse = true; + break; + case 'o': + outname = savestr(optarg); + break; + case 'p': + strippath = atoi(optarg); + break; + case 'r': + if (strlcpy(rejname, optarg, + sizeof(rejname)) >= sizeof(rejname)) + fatal("argument for -r is too long\n"); + break; + case 'R': + reverse = true; + reverse_flag_specified = true; + break; + case 's': + verbose = false; + break; + case 't': + batch = true; + break; + case 'u': + diff_type = UNI_DIFF; + break; + case 'v': + version(); + break; + case 'V': + backup_type = get_version(optarg); + break; +#ifdef DEBUGGING + case 'x': + debug = atoi(optarg); + break; +#endif + default: + if (ch != '\0') + usage(); + break; + } + } + Argc -= optind; + Argv += optind; + + if (Argc > 0) { + filearg[0] = savestr(*Argv++); + Argc--; + while (Argc > 0) { + if (++filec == MAXFILEC) + fatal("too many file arguments\n"); + filearg[filec] = savestr(*Argv++); + Argc--; + } + } + + if (getenv("POSIXLY_CORRECT") != NULL) + posix = 1; +} + +static void +usage(void) +{ + fprintf(stderr, +"usage: patch [-bcCeEflnNRstuv] [-B backup-prefix] [-d directory] [-D symbol]\n" +" [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]\n" +" [-r rej-name] [-V {numbered,existing,simple}] [-z backup-ext]\n" +" [origfile [patchfile]]\n"); + my_exit(EXIT_SUCCESS); +} + +/* + * Attempt to find the right place to apply this hunk of patch. + */ +static LINENUM +locate_hunk(LINENUM fuzz) +{ + LINENUM first_guess = pch_first() + last_offset; + LINENUM offset; + LINENUM pat_lines = pch_ptrn_lines(); + LINENUM max_pos_offset = input_lines - first_guess - pat_lines + 1; + LINENUM max_neg_offset = first_guess - last_frozen_line - 1 + pch_context(); + + if (pat_lines == 0) { /* null range matches always */ + if (verbose && fuzz == 0 && (diff_type == CONTEXT_DIFF + || diff_type == NEW_CONTEXT_DIFF + || diff_type == UNI_DIFF)) { + say("Empty context always matches.\n"); + } + if (diff_type == CONTEXT_DIFF + || diff_type == NEW_CONTEXT_DIFF + || diff_type == UNI_DIFF) { + if (fuzz == 0) + return (input_lines == 0 ? first_guess : 0); + } else + return (first_guess); + } + if (max_neg_offset >= first_guess) /* do not try lines < 0 */ + max_neg_offset = first_guess - 1; + if (first_guess <= input_lines && patch_match(first_guess, 0, fuzz)) + return first_guess; + for (offset = 1; ; offset++) { + bool check_after = (offset <= max_pos_offset); + bool check_before = (offset <= max_neg_offset); + + if (check_after && patch_match(first_guess, offset, fuzz)) { +#ifdef DEBUGGING + if (debug & 1) + say("Offset changing from %ld to %ld\n", + last_offset, offset); +#endif + last_offset = offset; + return first_guess + offset; + } else if (check_before && patch_match(first_guess, -offset, fuzz)) { +#ifdef DEBUGGING + if (debug & 1) + say("Offset changing from %ld to %ld\n", + last_offset, -offset); +#endif + last_offset = -offset; + return first_guess - offset; + } else if (!check_before && !check_after) + return 0; + } +} + +/* We did not find the pattern, dump out the hunk so they can handle it. */ + +static void +abort_hunk(void) +{ + LINENUM i; + const LINENUM pat_end = pch_end(); + /* + * add in last_offset to guess the same as the previous successful + * hunk + */ + const LINENUM oldfirst = pch_first() + last_offset; + const LINENUM newfirst = pch_newfirst() + last_offset; + const LINENUM oldlast = oldfirst + pch_ptrn_lines() - 1; + const LINENUM newlast = newfirst + pch_repl_lines() - 1; + const char *stars = (diff_type >= NEW_CONTEXT_DIFF ? " ****" : ""); + const char *minuses = (diff_type >= NEW_CONTEXT_DIFF ? " ----" : " -----"); + + fprintf(rejfp, "***************\n"); + for (i = 0; i <= pat_end; i++) { + switch (pch_char(i)) { + case '*': + if (oldlast < oldfirst) + fprintf(rejfp, "*** 0%s\n", stars); + else if (oldlast == oldfirst) + fprintf(rejfp, "*** %ld%s\n", oldfirst, stars); + else + fprintf(rejfp, "*** %ld,%ld%s\n", oldfirst, + oldlast, stars); + break; + case '=': + if (newlast < newfirst) + fprintf(rejfp, "--- 0%s\n", minuses); + else if (newlast == newfirst) + fprintf(rejfp, "--- %ld%s\n", newfirst, minuses); + else + fprintf(rejfp, "--- %ld,%ld%s\n", newfirst, + newlast, minuses); + break; + case '\n': + fprintf(rejfp, "%s", pfetch(i)); + break; + case ' ': + case '-': + case '+': + case '!': + fprintf(rejfp, "%c %s", pch_char(i), pfetch(i)); + break; + default: + fatal("fatal internal error in abort_hunk\n"); + } + } +} + +/* We found where to apply it (we hope), so do it. */ + +static void +apply_hunk(LINENUM where) +{ + LINENUM old = 1; + const LINENUM lastline = pch_ptrn_lines(); + LINENUM new = lastline + 1; +#define OUTSIDE 0 +#define IN_IFNDEF 1 +#define IN_IFDEF 2 +#define IN_ELSE 3 + int def_state = OUTSIDE; + const LINENUM pat_end = pch_end(); + + 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 void +spew_output(void) +{ +#ifdef DEBUGGING + if (debug & 256) + say("il=%ld lfl=%ld\n", input_lines, last_frozen_line); +#endif + if (input_lines) + copy_till(input_lines, true); /* dump remainder of file */ + fclose(ofp); + ofp = NULL; +} + +/* + * 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; + short 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, int len) +{ + while (len) { + if (isspace(*b)) { /* whitespace (or \n) to match? */ + if (!isspace(*a)) /* no corresponding whitespace? */ + return false; + while (len && isspace(*b) && *b != '\n') + b++, len--; /* skip pattern whitespace */ + while (isspace(*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 */ +} diff --git a/usr.bin/patch/pathnames.h b/usr.bin/patch/pathnames.h new file mode 100644 index 0000000000..241760a4d8 --- /dev/null +++ b/usr.bin/patch/pathnames.h @@ -0,0 +1,14 @@ +/* + * $OpenBSD: pathnames.h,v 1.1 2003/07/29 20:10:17 millert Exp $ + * $DragonFly: src/usr.bin/patch/pathnames.h,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * Placed in the public domain by Todd C. Miller + * on July 29, 2003. + */ + +#include + +#define _PATH_ED "/bin/ed" +#define _PATH_MKDIR "/bin/mkdir" diff --git a/usr.bin/patch/pch.c b/usr.bin/patch/pch.c new file mode 100644 index 0000000000..b9c0bf59ab --- /dev/null +++ b/usr.bin/patch/pch.c @@ -0,0 +1,1551 @@ +/* + * $OpenBSD: pch.c,v 1.35 2004/08/05 21:47:24 deraadt Exp $ + * $DragonFly: src/usr.bin/patch/pch.c,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "util.h" +#include "pch.h" +#include "pathnames.h" + +/* Patch (diff listing) abstract type. */ + +static long p_filesize; /* size of the patch file */ +static LINENUM p_first; /* 1st line number */ +static LINENUM p_newfirst; /* 1st line number of replacement */ +static LINENUM p_ptrn_lines; /* # lines in pattern */ +static LINENUM p_repl_lines; /* # lines in replacement text */ +static LINENUM p_end = -1; /* last line in hunk */ +static LINENUM p_max; /* max allowed value of p_end */ +static LINENUM p_context = 3; /* # of context lines */ +static LINENUM p_input_line = 0; /* current line # from patch file */ +static char **p_line = NULL;/* the text of the hunk */ +static short *p_len = NULL; /* length of each line */ +static char *p_char = NULL; /* +, -, and ! */ +static int hunkmax = INITHUNKMAX; /* size of above arrays to begin with */ +static int p_indent; /* indent to patch */ +static LINENUM p_base; /* where to intuit this time */ +static LINENUM p_bline; /* line # of p_base */ +static LINENUM p_start; /* where intuit found a patch */ +static LINENUM p_sline; /* and the line number for it */ +static LINENUM p_hunk_beg; /* line number of current hunk */ +static LINENUM p_efake = -1; /* end of faked up lines--don't free */ +static LINENUM p_bfake = -1; /* beg of faked up lines */ +static FILE *pfp = NULL; /* patch file pointer */ +static char *bestguess = NULL; /* guess at correct filename */ + +static void grow_hunkmax(void); +static int intuit_diff_type(void); +static void next_intuit_at(LINENUM, LINENUM); +static void skip_to(LINENUM, LINENUM); +static char *pgets(char *, int, FILE *); +static char *best_name(const struct file_name *, bool); +static char *posix_name(const struct file_name *, bool); +static size_t num_components(const char *); + +/* + * Prepare to look for the next patch in the patch file. + */ +void +re_patch(void) +{ + p_first = 0; + p_newfirst = 0; + p_ptrn_lines = 0; + p_repl_lines = 0; + p_end = (LINENUM) - 1; + p_max = 0; + p_indent = 0; +} + +/* + * Open the patch file at the beginning of time. + */ +void +open_patch_file(const char *filename) +{ + struct stat filestat; + + if (filename == NULL || *filename == '\0' || strEQ(filename, "-")) { + pfp = fopen(TMPPATNAME, "w"); + if (pfp == NULL) + pfatal("can't create %s", TMPPATNAME); + while (fgets(buf, sizeof buf, stdin) != NULL) + fputs(buf, pfp); + fclose(pfp); + filename = TMPPATNAME; + } + pfp = fopen(filename, "r"); + if (pfp == NULL) + pfatal("patch file %s not found", filename); + fstat(fileno(pfp), &filestat); + p_filesize = filestat.st_size; + next_intuit_at(0L, 1L); /* start at the beginning */ + set_hunkmax(); +} + +/* + * Make sure our dynamically realloced tables are malloced to begin with. + */ +void +set_hunkmax(void) +{ + if (p_line == NULL) + p_line = malloc((size_t) hunkmax * sizeof(char *)); + if (p_len == NULL) + p_len = malloc((size_t) hunkmax * sizeof(short)); + if (p_char == NULL) + p_char = malloc((size_t) hunkmax * sizeof(char)); +} + +/* + * Enlarge the arrays containing the current hunk of patch. + */ +static void +grow_hunkmax(void) +{ + int new_hunkmax; + char **new_p_line; + short *new_p_len; + char *new_p_char; + + new_hunkmax = hunkmax * 2; + + if (p_line == NULL || p_len == NULL || p_char == NULL) + fatal("Internal memory allocation error\n"); + + new_p_line = realloc(p_line, new_hunkmax * sizeof(char *)); + if (new_p_line == NULL) + free(p_line); + + new_p_len = realloc(p_len, new_hunkmax * sizeof(short)); + if (new_p_len == NULL) + free(p_len); + + new_p_char = realloc(p_char, new_hunkmax * sizeof(char)); + if (new_p_char == NULL) + free(p_char); + + p_char = new_p_char; + p_len = new_p_len; + p_line = new_p_line; + + if (p_line != NULL && p_len != NULL && p_char != NULL) { + hunkmax = new_hunkmax; + return; + } + + if (!using_plan_a) + fatal("out of memory\n"); + out_of_mem = true; /* whatever is null will be allocated again */ + /* from within plan_a(), of all places */ +} + +/* True if the remainder of the patch file contains a diff of some sort. */ + +bool +there_is_another_patch(void) +{ + bool exists = false; + + if (p_base != 0L && p_base >= p_filesize) { + if (verbose) + say("done\n"); + return false; + } + if (verbose) + say("Hmm..."); + diff_type = intuit_diff_type(); + if (!diff_type) { + if (p_base != 0L) { + if (verbose) + say(" Ignoring the trailing garbage.\ndone\n"); + } else + say(" I can't seem to find a patch in there anywhere.\n"); + return false; + } + if (verbose) + say(" %sooks like %s to me...\n", + (p_base == 0L ? "L" : "The next patch l"), + diff_type == UNI_DIFF ? "a unified diff" : + diff_type == CONTEXT_DIFF ? "a context diff" : + diff_type == NEW_CONTEXT_DIFF ? "a new-style context diff" : + diff_type == NORMAL_DIFF ? "a normal diff" : + "an ed script"); + if (p_indent && verbose) + say("(Patch is indented %d space%s.)\n", p_indent, + p_indent == 1 ? "" : "s"); + skip_to(p_start, p_sline); + while (filearg[0] == NULL) { + if (force || batch) { + say("No file to patch. Skipping...\n"); + filearg[0] = savestr(bestguess); + skip_rest_of_patch = true; + return true; + } + ask("File to patch: "); + if (*buf != '\n') { + free(bestguess); + bestguess = savestr(buf); + filearg[0] = fetchname(buf, &exists, 0); + } + if (!exists) { + ask("No file found--skip this patch? [n] "); + if (*buf != 'y') + continue; + if (verbose) + say("Skipping patch...\n"); + free(filearg[0]); + filearg[0] = fetchname(bestguess, &exists, 0); + skip_rest_of_patch = true; + return true; + } + } + return true; +} + +/* Determine what kind of diff is in the remaining part of the patch file. */ + +static int +intuit_diff_type(void) +{ + long this_line = 0, previous_line; + long first_command_line = -1; + LINENUM fcl_line = -1; + bool last_line_was_command = false, this_is_a_command = false; + bool stars_last_line = false, stars_this_line = false; + char *s, *t; + int indent, retval; + struct file_name names[MAX_FILE]; + + memset(names, 0, sizeof(names)); + ok_to_create_file = false; + fseek(pfp, p_base, SEEK_SET); + p_input_line = p_bline - 1; + for (;;) { + previous_line = this_line; + last_line_was_command = this_is_a_command; + stars_last_line = stars_this_line; + this_line = ftell(pfp); + indent = 0; + p_input_line++; + if (fgets(buf, sizeof buf, pfp) == NULL) { + if (first_command_line >= 0L) { + /* nothing but deletes!? */ + p_start = first_command_line; + p_sline = fcl_line; + retval = ED_DIFF; + goto scan_exit; + } else { + p_start = this_line; + p_sline = p_input_line; + retval = 0; + goto scan_exit; + } + } + for (s = buf; *s == ' ' || *s == '\t' || *s == 'X'; s++) { + if (*s == '\t') + indent += 8 - (indent % 8); + else + indent++; + } + for (t = s; isdigit(*t) || *t == ','; t++) + ; + this_is_a_command = (isdigit(*s) && + (*t == 'd' || *t == 'c' || *t == 'a')); + if (first_command_line < 0L && this_is_a_command) { + first_command_line = this_line; + fcl_line = p_input_line; + p_indent = indent; /* assume this for now */ + } + if (!stars_last_line && strnEQ(s, "*** ", 4)) + names[OLD_FILE].path = fetchname(s + 4, + &names[OLD_FILE].exists, strippath); + else if (strnEQ(s, "--- ", 4)) + names[NEW_FILE].path = fetchname(s + 4, + &names[NEW_FILE].exists, strippath); + else if (strnEQ(s, "+++ ", 4)) + /* pretend it is the old name */ + names[OLD_FILE].path = fetchname(s + 4, + &names[OLD_FILE].exists, strippath); + else if (strnEQ(s, "Index:", 6)) + names[INDEX_FILE].path = fetchname(s + 6, + &names[INDEX_FILE].exists, strippath); + else if (strnEQ(s, "Prereq:", 7)) { + for (t = s + 7; isspace(*t); t++) + ; + revision = savestr(t); + for (t = revision; *t && !isspace(*t); t++) + ; + *t = '\0'; + if (*revision == '\0') { + free(revision); + revision = NULL; + } + } + if ((!diff_type || diff_type == ED_DIFF) && + first_command_line >= 0L && + strEQ(s, ".\n")) { + p_indent = indent; + p_start = first_command_line; + p_sline = fcl_line; + retval = ED_DIFF; + goto scan_exit; + } + if ((!diff_type || diff_type == UNI_DIFF) && strnEQ(s, "@@ -", 4)) { + if (strnEQ(s + 4, "0,0", 3)) + ok_to_create_file = true; + p_indent = indent; + p_start = this_line; + p_sline = p_input_line; + retval = UNI_DIFF; + goto scan_exit; + } + stars_this_line = strnEQ(s, "********", 8); + if ((!diff_type || diff_type == CONTEXT_DIFF) && stars_last_line && + strnEQ(s, "*** ", 4)) { + if (atol(s + 4) == 0) + ok_to_create_file = true; + /* + * If this is a new context diff the character just + * before the newline is a '*'. + */ + while (*s != '\n') + s++; + p_indent = indent; + p_start = previous_line; + p_sline = p_input_line - 1; + retval = (*(s - 1) == '*' ? NEW_CONTEXT_DIFF : CONTEXT_DIFF); + goto scan_exit; + } + if ((!diff_type || diff_type == NORMAL_DIFF) && + last_line_was_command && + (strnEQ(s, "< ", 2) || strnEQ(s, "> ", 2))) { + p_start = previous_line; + p_sline = p_input_line - 1; + p_indent = indent; + retval = NORMAL_DIFF; + goto scan_exit; + } + } +scan_exit: + if (retval == UNI_DIFF) { + /* unswap old and new */ + struct file_name tmp = names[OLD_FILE]; + names[OLD_FILE] = names[NEW_FILE]; + names[NEW_FILE] = tmp; + } + if (filearg[0] == NULL) { + if (posix) + filearg[0] = posix_name(names, ok_to_create_file); + else { + /* Ignore the Index: name for context diffs, like GNU */ + if (names[OLD_FILE].path != NULL || + names[NEW_FILE].path != NULL) { + free(names[INDEX_FILE].path); + names[INDEX_FILE].path = NULL; + } + filearg[0] = best_name(names, ok_to_create_file); + } + } + + free(bestguess); + bestguess = NULL; + if (filearg[0] != NULL) + bestguess = savestr(filearg[0]); + else if (!ok_to_create_file) { + /* + * We don't want to create a new file but we need a + * filename to set bestguess. Avoid setting filearg[0] + * so the file is not created automatically. + */ + if (posix) + bestguess = posix_name(names, true); + else + bestguess = best_name(names, true); + } + free(names[OLD_FILE].path); + free(names[NEW_FILE].path); + free(names[INDEX_FILE].path); + return retval; +} + +/* + * Remember where this patch ends so we know where to start up again. + */ +static void +next_intuit_at(LINENUM file_pos, LINENUM file_line) +{ + p_base = file_pos; + p_bline = file_line; +} + +/* + * Basically a verbose fseek() to the actual diff listing. + */ +static void +skip_to(LINENUM file_pos, LINENUM file_line) +{ + char *ret; + + if (p_base > file_pos) + fatal("Internal error: seek %ld>%ld\n", p_base, file_pos); + if (verbose && p_base < file_pos) { + fseek(pfp, p_base, SEEK_SET); + say("The text leading up to this was:\n--------------------------\n"); + while (ftell(pfp) < file_pos) { + ret = fgets(buf, sizeof buf, pfp); + if (ret == NULL) + fatal("Unexpected end of file\n"); + say("|%s", buf); + } + say("--------------------------\n"); + } else + fseek(pfp, file_pos, SEEK_SET); + p_input_line = file_line - 1; +} + +/* Make this a function for better debugging. */ +static void +malformed(void) +{ + fatal("malformed patch at line %ld: %s", p_input_line, buf); + /* about as informative as "Syntax error" in C */ +} + +/* + * True if the line has been discarded (i.e. it is a line saying + * "\ No newline at end of file".) + */ +static bool +remove_special_line(void) +{ + int c; + + c = fgetc(pfp); + if (c == '\\') { + do { + c = fgetc(pfp); + } while (c != EOF && c != '\n'); + + return true; + } + if (c != EOF) + fseek(pfp, -1L, SEEK_CUR); + + return false; +} + +/* + * True if there is more of the current diff listing to process. + */ +bool +another_hunk(void) +{ + long line_beginning; /* file pos of the current line */ + LINENUM repl_beginning; /* index of --- line */ + LINENUM fillcnt; /* #lines of missing ptrn or repl */ + LINENUM fillsrc; /* index of first line to copy */ + LINENUM filldst; /* index of first missing line */ + bool ptrn_spaces_eaten; /* ptrn was slightly misformed */ + bool repl_could_be_missing; /* no + or ! lines in this hunk */ + bool repl_missing; /* we are now backtracking */ + long repl_backtrack_position; /* file pos of first repl line */ + LINENUM repl_patch_line; /* input line number for same */ + LINENUM ptrn_copiable; /* # of copiable lines in ptrn */ + char *s, *ret; + int context = 0; + + while (p_end >= 0) { + if (p_end == p_efake) + p_end = p_bfake; /* don't free twice */ + else + free(p_line[p_end]); + p_end--; + } + p_efake = -1; + + p_max = hunkmax; /* gets reduced when --- found */ + if (diff_type == CONTEXT_DIFF || diff_type == NEW_CONTEXT_DIFF) { + line_beginning = ftell(pfp); + repl_beginning = 0; + fillcnt = 0; + fillsrc = 0; + filldst = 0; + ptrn_spaces_eaten = false; + repl_could_be_missing = true; + repl_missing = false; + repl_backtrack_position = 0; + repl_patch_line = 0; + ptrn_copiable = 0; + + ret = pgets(buf, sizeof buf, pfp); + p_input_line++; + if (ret == NULL || strnNE(buf, "********", 8)) { + next_intuit_at(line_beginning, p_input_line); + return false; + } + p_context = 100; + p_hunk_beg = p_input_line + 1; + while (p_end < p_max) { + line_beginning = ftell(pfp); + ret = pgets(buf, sizeof buf, pfp); + p_input_line++; + if (ret == NULL) { + if (p_max - p_end < 4) { + /* assume blank lines got chopped */ + strlcpy(buf, " \n", sizeof buf); + } else { + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + fatal("unexpected end of file in patch\n"); + } + } + p_end++; + if (p_end >= hunkmax) + fatal("Internal error: hunk larger than hunk " + "buffer size"); + p_char[p_end] = *buf; + p_line[p_end] = NULL; + switch (*buf) { + case '*': + if (strnEQ(buf, "********", 8)) { + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } else + fatal("unexpected end of hunk " + "at line %ld\n", + p_input_line); + } + if (p_end != 0) { + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + fatal("unexpected *** at line %ld: %s", + p_input_line, buf); + } + context = 0; + p_line[p_end] = savestr(buf); + if (out_of_mem) { + p_end--; + return false; + } + for (s = buf; *s && !isdigit(*s); s++) + ; + if (!*s) + malformed(); + if (strnEQ(s, "0,0", 3)) + memmove(s, s + 2, strlen(s + 2) + 1); + p_first = (LINENUM) atol(s); + while (isdigit(*s)) + s++; + if (*s == ',') { + for (; *s && !isdigit(*s); s++) + ; + if (!*s) + malformed(); + p_ptrn_lines = ((LINENUM) atol(s)) - p_first + 1; + } else if (p_first) + p_ptrn_lines = 1; + else { + p_ptrn_lines = 0; + p_first = 1; + } + + /* we need this much at least */ + p_max = p_ptrn_lines + 6; + while (p_max >= hunkmax) + grow_hunkmax(); + p_max = hunkmax; + break; + case '-': + if (buf[1] == '-') { + if (repl_beginning || + (p_end != p_ptrn_lines + 1 + + (p_char[p_end - 1] == '\n'))) { + if (p_end == 1) { + /* + * `old' lines were omitted; + * set up to fill them in + * from 'new' context lines. + */ + p_end = p_ptrn_lines + 1; + fillsrc = p_end + 1; + filldst = 1; + fillcnt = p_ptrn_lines; + } else { + if (repl_beginning) { + if (repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + fatal("duplicate \"---\" at line %ld--check line numbers at line %ld\n", + p_input_line, p_hunk_beg + repl_beginning); + } else { + fatal("%s \"---\" at line %ld--check line numbers at line %ld\n", + (p_end <= p_ptrn_lines + ? "Premature" + : "Overdue"), + p_input_line, p_hunk_beg); + } + } + } + repl_beginning = p_end; + repl_backtrack_position = ftell(pfp); + repl_patch_line = p_input_line; + p_line[p_end] = savestr(buf); + if (out_of_mem) { + p_end--; + return false; + } + p_char[p_end] = '='; + for (s = buf; *s && !isdigit(*s); s++) + ; + if (!*s) + malformed(); + p_newfirst = (LINENUM) atol(s); + while (isdigit(*s)) + s++; + if (*s == ',') { + for (; *s && !isdigit(*s); s++) + ; + if (!*s) + malformed(); + p_repl_lines = ((LINENUM) atol(s)) - + p_newfirst + 1; + } else if (p_newfirst) + p_repl_lines = 1; + else { + p_repl_lines = 0; + p_newfirst = 1; + } + p_max = p_repl_lines + p_end; + if (p_max > MAXHUNKSIZE) + fatal("hunk too large (%ld lines) at line %ld: %s", + p_max, p_input_line, buf); + while (p_max >= hunkmax) + grow_hunkmax(); + if (p_repl_lines != ptrn_copiable && + (p_context != 0 || p_repl_lines != 1)) + repl_could_be_missing = false; + break; + } + goto change_line; + case '+': + case '!': + repl_could_be_missing = false; + change_line: + if (buf[1] == '\n' && canonicalize) + strlcpy(buf + 1, " \n", sizeof buf - 1); + if (!isspace(buf[1]) && buf[1] != '>' && + buf[1] != '<' && + repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + if (context >= 0) { + if (context < p_context) + p_context = context; + context = -1000; + } + p_line[p_end] = savestr(buf + 2); + if (out_of_mem) { + p_end--; + return false; + } + if (p_end == p_ptrn_lines) { + if (remove_special_line()) { + int len; + + len = strlen(p_line[p_end]) - 1; + (p_line[p_end])[len] = 0; + } + } + break; + case '\t': + case '\n': /* assume the 2 spaces got eaten */ + if (repl_beginning && repl_could_be_missing && + (!ptrn_spaces_eaten || + diff_type == NEW_CONTEXT_DIFF)) { + repl_missing = true; + goto hunk_done; + } + p_line[p_end] = savestr(buf); + if (out_of_mem) { + p_end--; + return false; + } + if (p_end != p_ptrn_lines + 1) { + ptrn_spaces_eaten |= (repl_beginning != 0); + context++; + if (!repl_beginning) + ptrn_copiable++; + p_char[p_end] = ' '; + } + break; + case ' ': + if (!isspace(buf[1]) && + repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + context++; + if (!repl_beginning) + ptrn_copiable++; + p_line[p_end] = savestr(buf + 2); + if (out_of_mem) { + p_end--; + return false; + } + break; + default: + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + malformed(); + } + /* set up p_len for strncmp() so we don't have to */ + /* assume null termination */ + if (p_line[p_end]) + p_len[p_end] = strlen(p_line[p_end]); + else + p_len[p_end] = 0; + } + +hunk_done: + if (p_end >= 0 && !repl_beginning) + fatal("no --- found in patch at line %ld\n", pch_hunk_beg()); + + if (repl_missing) { + + /* reset state back to just after --- */ + p_input_line = repl_patch_line; + for (p_end--; p_end > repl_beginning; p_end--) + free(p_line[p_end]); + fseek(pfp, repl_backtrack_position, SEEK_SET); + + /* redundant 'new' context lines were omitted - set */ + /* up to fill them in from the old file context */ + if (!p_context && p_repl_lines == 1) { + p_repl_lines = 0; + p_max--; + } + fillsrc = 1; + filldst = repl_beginning + 1; + fillcnt = p_repl_lines; + p_end = p_max; + } else if (!p_context && fillcnt == 1) { + /* the first hunk was a null hunk with no context */ + /* and we were expecting one line -- fix it up. */ + while (filldst < p_end) { + p_line[filldst] = p_line[filldst + 1]; + p_char[filldst] = p_char[filldst + 1]; + p_len[filldst] = p_len[filldst + 1]; + filldst++; + } +#if 0 + repl_beginning--; /* this doesn't need to be fixed */ +#endif + p_end--; + p_first++; /* do append rather than insert */ + fillcnt = 0; + p_ptrn_lines = 0; + } + if (diff_type == CONTEXT_DIFF && + (fillcnt || (p_first > 1 && ptrn_copiable > 2 * p_context))) { + if (verbose) + say("%s\n%s\n%s\n", + "(Fascinating--this is really a new-style context diff but without", + "the telltale extra asterisks on the *** line that usually indicate", + "the new style...)"); + diff_type = NEW_CONTEXT_DIFF; + } + /* if there were omitted context lines, fill them in now */ + if (fillcnt) { + p_bfake = filldst; /* remember where not to free() */ + p_efake = filldst + fillcnt - 1; + while (fillcnt-- > 0) { + while (fillsrc <= p_end && p_char[fillsrc] != ' ') + fillsrc++; + if (fillsrc > p_end) + fatal("replacement text or line numbers mangled in hunk at line %ld\n", + p_hunk_beg); + p_line[filldst] = p_line[fillsrc]; + p_char[filldst] = p_char[fillsrc]; + p_len[filldst] = p_len[fillsrc]; + fillsrc++; + filldst++; + } + while (fillsrc <= p_end && fillsrc != repl_beginning && + p_char[fillsrc] != ' ') + fillsrc++; +#ifdef DEBUGGING + if (debug & 64) + printf("fillsrc %ld, filldst %ld, rb %ld, e+1 %ld\n", + fillsrc, filldst, repl_beginning, p_end + 1); +#endif + if (fillsrc != p_end + 1 && fillsrc != repl_beginning) + malformed(); + if (filldst != p_end + 1 && filldst != repl_beginning) + malformed(); + } + if (p_line[p_end] != NULL) { + if (remove_special_line()) { + p_len[p_end] -= 1; + (p_line[p_end])[p_len[p_end]] = 0; + } + } + } else if (diff_type == UNI_DIFF) { + long line_beginning = ftell(pfp); /* file pos of the current line */ + LINENUM fillsrc; /* index of old lines */ + LINENUM filldst; /* index of new lines */ + char ch; + + ret = pgets(buf, sizeof buf, pfp); + p_input_line++; + if (ret == NULL || strnNE(buf, "@@ -", 4)) { + next_intuit_at(line_beginning, p_input_line); + return false; + } + s = buf + 4; + if (!*s) + malformed(); + p_first = (LINENUM) atol(s); + while (isdigit(*s)) + s++; + if (*s == ',') { + p_ptrn_lines = (LINENUM) atol(++s); + while (isdigit(*s)) + s++; + } else + p_ptrn_lines = 1; + if (*s == ' ') + s++; + if (*s != '+' || !*++s) + malformed(); + p_newfirst = (LINENUM) atol(s); + while (isdigit(*s)) + s++; + if (*s == ',') { + p_repl_lines = (LINENUM) atol(++s); + while (isdigit(*s)) + s++; + } else + p_repl_lines = 1; + if (*s == ' ') + s++; + if (*s != '@') + malformed(); + if (!p_ptrn_lines) + p_first++; /* do append rather than insert */ + p_max = p_ptrn_lines + p_repl_lines + 1; + while (p_max >= hunkmax) + grow_hunkmax(); + fillsrc = 1; + filldst = fillsrc + p_ptrn_lines; + p_end = filldst + p_repl_lines; + snprintf(buf, sizeof buf, "*** %ld,%ld ****\n", p_first, + p_first + p_ptrn_lines - 1); + p_line[0] = savestr(buf); + if (out_of_mem) { + p_end = -1; + return false; + } + p_char[0] = '*'; + snprintf(buf, sizeof buf, "--- %ld,%ld ----\n", p_newfirst, + p_newfirst + p_repl_lines - 1); + p_line[filldst] = savestr(buf); + if (out_of_mem) { + p_end = 0; + return false; + } + p_char[filldst++] = '='; + p_context = 100; + context = 0; + p_hunk_beg = p_input_line + 1; + while (fillsrc <= p_ptrn_lines || filldst <= p_end) { + line_beginning = ftell(pfp); + ret = pgets(buf, sizeof buf, pfp); + p_input_line++; + if (ret == NULL) { + if (p_max - filldst < 3) { + /* assume blank lines got chopped */ + strlcpy(buf, " \n", sizeof buf); + } else { + fatal("unexpected end of file in patch\n"); + } + } + if (*buf == '\t' || *buf == '\n') { + ch = ' '; /* assume the space got eaten */ + s = savestr(buf); + } else { + ch = *buf; + s = savestr(buf + 1); + } + if (out_of_mem) { + while (--filldst > p_ptrn_lines) + free(p_line[filldst]); + p_end = fillsrc - 1; + return false; + } + switch (ch) { + case '-': + if (fillsrc > p_ptrn_lines) { + free(s); + p_end = filldst - 1; + malformed(); + } + p_char[fillsrc] = ch; + p_line[fillsrc] = s; + p_len[fillsrc++] = strlen(s); + if (fillsrc > p_ptrn_lines) { + if (remove_special_line()) { + p_len[fillsrc - 1] -= 1; + s[p_len[fillsrc - 1]] = 0; + } + } + break; + case '=': + ch = ' '; + /* FALL THROUGH */ + case ' ': + if (fillsrc > p_ptrn_lines) { + free(s); + while (--filldst > p_ptrn_lines) + free(p_line[filldst]); + p_end = fillsrc - 1; + malformed(); + } + context++; + p_char[fillsrc] = ch; + p_line[fillsrc] = s; + p_len[fillsrc++] = strlen(s); + s = savestr(s); + if (out_of_mem) { + while (--filldst > p_ptrn_lines) + free(p_line[filldst]); + p_end = fillsrc - 1; + return false; + } + if (fillsrc > p_ptrn_lines) { + if (remove_special_line()) { + p_len[fillsrc - 1] -= 1; + s[p_len[fillsrc - 1]] = 0; + } + } + /* FALL THROUGH */ + case '+': + if (filldst > p_end) { + free(s); + while (--filldst > p_ptrn_lines) + free(p_line[filldst]); + p_end = fillsrc - 1; + malformed(); + } + p_char[filldst] = ch; + p_line[filldst] = s; + p_len[filldst++] = strlen(s); + if (fillsrc > p_ptrn_lines) { + if (remove_special_line()) { + p_len[filldst - 1] -= 1; + s[p_len[filldst - 1]] = 0; + } + } + break; + default: + p_end = filldst; + malformed(); + } + if (ch != ' ' && context > 0) { + if (context < p_context) + p_context = context; + context = -1000; + } + } /* while */ + } else { /* normal diff--fake it up */ + char hunk_type; + int i; + LINENUM min, max; + long line_beginning = ftell(pfp); + + p_context = 0; + ret = pgets(buf, sizeof buf, pfp); + p_input_line++; + if (ret == NULL || !isdigit(*buf)) { + next_intuit_at(line_beginning, p_input_line); + return false; + } + p_first = (LINENUM) atol(buf); + for (s = buf; isdigit(*s); s++) + ; + if (*s == ',') { + p_ptrn_lines = (LINENUM) atol(++s) - p_first + 1; + while (isdigit(*s)) + s++; + } else + p_ptrn_lines = (*s != 'a'); + hunk_type = *s; + if (hunk_type == 'a') + p_first++; /* do append rather than insert */ + min = (LINENUM) atol(++s); + for (; isdigit(*s); s++) + ; + if (*s == ',') + max = (LINENUM) atol(++s); + else + max = min; + if (hunk_type == 'd') + min++; + p_end = p_ptrn_lines + 1 + max - min + 1; + if (p_end > MAXHUNKSIZE) + fatal("hunk too large (%ld lines) at line %ld: %s", + p_end, p_input_line, buf); + while (p_end >= hunkmax) + grow_hunkmax(); + p_newfirst = min; + p_repl_lines = max - min + 1; + snprintf(buf, sizeof buf, "*** %ld,%ld\n", p_first, + p_first + p_ptrn_lines - 1); + p_line[0] = savestr(buf); + if (out_of_mem) { + p_end = -1; + return false; + } + p_char[0] = '*'; + for (i = 1; i <= p_ptrn_lines; i++) { + ret = pgets(buf, sizeof buf, pfp); + p_input_line++; + if (ret == NULL) + fatal("unexpected end of file in patch at line %ld\n", + p_input_line); + if (*buf != '<') + fatal("< expected at line %ld of patch\n", + p_input_line); + p_line[i] = savestr(buf + 2); + if (out_of_mem) { + p_end = i - 1; + return false; + } + p_len[i] = strlen(p_line[i]); + p_char[i] = '-'; + } + + if (remove_special_line()) { + p_len[i - 1] -= 1; + (p_line[i - 1])[p_len[i - 1]] = 0; + } + if (hunk_type == 'c') { + ret = pgets(buf, sizeof buf, pfp); + p_input_line++; + if (ret == NULL) + fatal("unexpected end of file in patch at line %ld\n", + p_input_line); + if (*buf != '-') + fatal("--- expected at line %ld of patch\n", + p_input_line); + } + snprintf(buf, sizeof(buf), "--- %ld,%ld\n", min, max); + p_line[i] = savestr(buf); + if (out_of_mem) { + p_end = i - 1; + return false; + } + p_char[i] = '='; + for (i++; i <= p_end; i++) { + ret = pgets(buf, sizeof buf, pfp); + p_input_line++; + if (ret == NULL) + fatal("unexpected end of file in patch at line %ld\n", + p_input_line); + if (*buf != '>') + fatal("> expected at line %ld of patch\n", + p_input_line); + p_line[i] = savestr(buf + 2); + if (out_of_mem) { + p_end = i - 1; + return false; + } + p_len[i] = strlen(p_line[i]); + p_char[i] = '+'; + } + + if (remove_special_line()) { + p_len[i - 1] -= 1; + (p_line[i - 1])[p_len[i - 1]] = 0; + } + } + if (reverse) /* backwards patch? */ + if (!pch_swap()) + say("Not enough memory to swap next hunk!\n"); +#ifdef DEBUGGING + if (debug & 2) { + int i; + char special; + + for (i = 0; i <= p_end; i++) { + if (i == p_ptrn_lines) + special = '^'; + else + special = ' '; + fprintf(stderr, "%3d %c %c %s", i, p_char[i], + special, p_line[i]); + fflush(stderr); + } + } +#endif + if (p_end + 1 < hunkmax)/* paranoia reigns supreme... */ + p_char[p_end + 1] = '^'; /* add a stopper for apply_hunk */ + return true; +} + +/* + * Input a line from the patch file, worrying about indentation. + */ +static char * +pgets(char *bf, int sz, FILE *fp) +{ + char *s, *ret = fgets(bf, sz, fp); + int indent = 0; + + if (p_indent && ret != NULL) { + for (s = buf; + indent < p_indent && (*s == ' ' || *s == '\t' || *s == 'X'); + s++) { + if (*s == '\t') + indent += 8 - (indent % 7); + else + indent++; + } + if (buf != s && strlcpy(buf, s, sizeof(buf)) >= sizeof(buf)) + fatal("buffer too small in pgets()\n"); + } + return ret; +} + +/* + * Reverse the old and new portions of the current hunk. + */ +bool +pch_swap(void) +{ + char **tp_line; /* the text of the hunk */ + short *tp_len; /* length of each line */ + char *tp_char; /* +, -, and ! */ + LINENUM i; + LINENUM n; + bool blankline = false; + char *s; + + i = p_first; + p_first = p_newfirst; + p_newfirst = i; + + /* make a scratch copy */ + + tp_line = p_line; + tp_len = p_len; + tp_char = p_char; + p_line = NULL; /* force set_hunkmax to allocate again */ + p_len = NULL; + p_char = NULL; + set_hunkmax(); + if (p_line == NULL || p_len == NULL || p_char == NULL) { + + free(p_line); + p_line = tp_line; + free(p_len); + p_len = tp_len; + free(p_char); + p_char = tp_char; + return false; /* not enough memory to swap hunk! */ + } + /* now turn the new into the old */ + + i = p_ptrn_lines + 1; + if (tp_char[i] == '\n') { /* account for possible blank line */ + blankline = true; + i++; + } + if (p_efake >= 0) { /* fix non-freeable ptr range */ + if (p_efake <= i) + n = p_end - i + 1; + else + n = -i; + p_efake += n; + p_bfake += n; + } + for (n = 0; i <= p_end; i++, n++) { + p_line[n] = tp_line[i]; + p_char[n] = tp_char[i]; + if (p_char[n] == '+') + p_char[n] = '-'; + p_len[n] = tp_len[i]; + } + if (blankline) { + i = p_ptrn_lines + 1; + p_line[n] = tp_line[i]; + p_char[n] = tp_char[i]; + p_len[n] = tp_len[i]; + n++; + } + if (p_char[0] != '=') + fatal("Malformed patch at line %ld: expected '=' found '%c'\n", + p_input_line, p_char[0]); + p_char[0] = '*'; + for (s = p_line[0]; *s; s++) + if (*s == '-') + *s = '*'; + + /* now turn the old into the new */ + + if (p_char[0] != '*') + fatal("Malformed patch at line %ld: expected '*' found '%c'\n", + p_input_line, p_char[0]); + tp_char[0] = '='; + for (s = tp_line[0]; *s; s++) + if (*s == '*') + *s = '-'; + for (i = 0; n <= p_end; i++, n++) { + p_line[n] = tp_line[i]; + p_char[n] = tp_char[i]; + if (p_char[n] == '-') + p_char[n] = '+'; + p_len[n] = tp_len[i]; + } + + if (i != p_ptrn_lines + 1) + fatal("Malformed patch at line %ld: expected %ld lines, " + "got %ld\n", + p_input_line, p_ptrn_lines + 1, i); + + i = p_ptrn_lines; + p_ptrn_lines = p_repl_lines; + p_repl_lines = i; + + free(tp_line); + free(tp_len); + free(tp_char); + + return true; +} + +/* + * Return the specified line position in the old file of the old context. + */ +LINENUM +pch_first(void) +{ + return p_first; +} + +/* + * Return the number of lines of old context. + */ +LINENUM +pch_ptrn_lines(void) +{ + return p_ptrn_lines; +} + +/* + * Return the probable line position in the new file of the first line. + */ +LINENUM +pch_newfirst(void) +{ + return p_newfirst; +} + +/* + * Return the number of lines in the replacement text including context. + */ +LINENUM +pch_repl_lines(void) +{ + return p_repl_lines; +} + +/* + * Return the number of lines in the whole hunk. + */ +LINENUM +pch_end(void) +{ + return p_end; +} + +/* + * Return the number of context lines before the first changed line. + */ +LINENUM +pch_context(void) +{ + return p_context; +} + +/* + * Return the length of a particular patch line. + */ +short +pch_line_len(LINENUM line) +{ + return p_len[line]; +} + +/* + * Return the control character (+, -, *, !, etc) for a patch line. + */ +char +pch_char(LINENUM line) +{ + return p_char[line]; +} + +/* + * Return a pointer to a particular patch line. + */ +char * +pfetch(LINENUM line) +{ + return p_line[line]; +} + +/* + * Return where in the patch file this hunk began, for error messages. + */ +LINENUM +pch_hunk_beg(void) +{ + return p_hunk_beg; +} + +/* + * Apply an ed script by feeding ed itself. + */ +void +do_ed_script(void) +{ + char *t; + long beginning_of_this_line; + FILE *pipefp; + + pipefp = NULL; + if (!skip_rest_of_patch) { + if (copy_file(filearg[0], TMPOUTNAME) < 0) { + unlink(TMPOUTNAME); + fatal("can't create temp file %s", TMPOUTNAME); + } + snprintf(buf, sizeof buf, "%s%s%s", _PATH_ED, + verbose ? " " : " -s ", TMPOUTNAME); + pipefp = popen(buf, "w"); + } + for (;;) { + beginning_of_this_line = ftell(pfp); + if (pgets(buf, sizeof buf, pfp) == NULL) { + next_intuit_at(beginning_of_this_line, p_input_line); + break; + } + p_input_line++; + for (t = buf; isdigit(*t) || *t == ','; t++) + ; + /* POSIX defines allowed commands as {a,c,d,i,s} */ + if (isdigit(*buf) && (*t == 'a' || *t == 'c' || *t == 'd' || + *t == 'i' || *t == 's')) { + if (pipefp != NULL) + fputs(buf, pipefp); + if (*t != 'd') { + while (pgets(buf, sizeof buf, pfp) != NULL) { + p_input_line++; + if (pipefp != NULL) + fputs(buf, pipefp); + if (strEQ(buf, ".\n")) + break; + } + } + } else { + next_intuit_at(beginning_of_this_line, p_input_line); + break; + } + } + if (pipefp == NULL) + return; + fprintf(pipefp, "w\n"); + fprintf(pipefp, "q\n"); + fflush(pipefp); + pclose(pipefp); + ignore_signals(); + if (!check_only) { + if (move_file(TMPOUTNAME, outname) < 0) { + toutkeep = true; + chmod(TMPOUTNAME, filemode); + } else + chmod(outname, filemode); + } + set_signals(1); +} + +/* + * Choose the name of the file to be patched based on POSIX rules. + * NOTE: the POSIX rules are amazingly stupid and we only follow them + * if the user specified --posix or set POSIXLY_CORRECT. + */ +static char * +posix_name(const struct file_name *names, bool assume_exists) +{ + char *path = NULL; + int i; + + /* + * POSIX states that the filename will be chosen from one + * of the old, new and index names (in that order) if + * the file exists relative to CWD after -p stripping. + */ + for (i = 0; i < MAX_FILE; i++) { + if (names[i].path != NULL && names[i].exists) { + path = names[i].path; + break; + } + } + if (path == NULL && !assume_exists) { + /* + * No files found, look for something we can checkout from + * RCS/SCCS dirs. Same order as above. + */ + for (i = 0; i < MAX_FILE; i++) { + if (names[i].path != NULL && + (path = checked_in(names[i].path)) != NULL) + break; + } + /* + * Still no match? Check to see if the diff could be creating + * a new file. + */ + if (path == NULL && ok_to_create_file && + names[NEW_FILE].path != NULL) + path = names[NEW_FILE].path; + } + + return path ? savestr(path) : NULL; +} + +/* + * Choose the name of the file to be patched based the "best" one + * available. + */ +static char * +best_name(const struct file_name *names, bool assume_exists) +{ + size_t min_components, min_baselen, min_len, tmp; + char *best = NULL; + int i; + + /* + * The "best" name is the one with the fewest number of path + * components, the shortest basename length, and the shortest + * overall length (in that order). We only use the Index: file + * if neither of the old or new files could be intuited from + * the diff header. + */ + min_components = min_baselen = min_len = SIZE_MAX; + for (i = INDEX_FILE; i >= OLD_FILE; i--) { + if (names[i].path == NULL || + (!names[i].exists && !assume_exists)) + continue; + if ((tmp = num_components(names[i].path)) > min_components) + continue; + min_components = tmp; + if ((tmp = strlen(basename(names[i].path))) > min_baselen) + continue; + min_baselen = tmp; + if ((tmp = strlen(names[i].path)) > min_len) + continue; + min_len = tmp; + best = names[i].path; + } + if (best == NULL) { + /* + * No files found, look for something we can checkout from + * RCS/SCCS dirs. Logic is identical to that above... + */ + min_components = min_baselen = min_len = SIZE_MAX; + for (i = INDEX_FILE; i >= OLD_FILE; i--) { + if (names[i].path == NULL || + checked_in(names[i].path) == NULL) + continue; + if ((tmp = num_components(names[i].path)) > min_components) + continue; + min_components = tmp; + if ((tmp = strlen(basename(names[i].path))) > min_baselen) + continue; + min_baselen = tmp; + if ((tmp = strlen(names[i].path)) > min_len) + continue; + min_len = tmp; + best = names[i].path; + } + /* + * Still no match? Check to see if the diff could be creating + * a new file. + */ + if (best == NULL && ok_to_create_file && + names[NEW_FILE].path != NULL) + best = names[NEW_FILE].path; + } + + return best ? savestr(best) : NULL; +} + +static size_t +num_components(const char *path) +{ + size_t n; + const char *cp; + + for (n = 0, cp = path; (cp = strchr(cp, '/')) != NULL; n++, cp++) { + while (*cp == '/') + cp++; /* skip consecutive slashes */ + } + return n; +} diff --git a/usr.bin/patch/pch.h b/usr.bin/patch/pch.h new file mode 100644 index 0000000000..0bd01334ad --- /dev/null +++ b/usr.bin/patch/pch.h @@ -0,0 +1,58 @@ +/* + * $OpenBSD: pch.h,v 1.9 2003/10/31 20:20:45 millert Exp $ + * $DragonFly: src/usr.bin/patch/pch.h,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * 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 + */ + +#define OLD_FILE 0 +#define NEW_FILE 1 +#define INDEX_FILE 2 +#define MAX_FILE 3 + +struct file_name { + char *path; + bool exists; +}; + +void re_patch(void); +void open_patch_file(const char *); +void set_hunkmax(void); +bool there_is_another_patch(void); +bool another_hunk(void); +bool pch_swap(void); +char *pfetch(LINENUM); +short pch_line_len(LINENUM); +LINENUM pch_first(void); +LINENUM pch_ptrn_lines(void); +LINENUM pch_newfirst(void); +LINENUM pch_repl_lines(void); +LINENUM pch_end(void); +LINENUM pch_context(void); +LINENUM pch_hunk_beg(void); +char pch_char(LINENUM); +void do_ed_script(void); diff --git a/usr.bin/patch/util.c b/usr.bin/patch/util.c new file mode 100644 index 0000000000..b2b6a415ad --- /dev/null +++ b/usr.bin/patch/util.c @@ -0,0 +1,424 @@ +/* + * $OpenBSD: util.c,v 1.28 2004/08/05 21:47:24 deraadt Exp $ + * $DragonFly: src/usr.bin/patch/util.c,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "util.h" +#include "backupfile.h" +#include "pathnames.h" + + +/* Rename a file, copying it if necessary. */ + +int +move_file(const char *from, const char *to) +{ + int fromfd; + ssize_t i; + + /* to stdout? */ + + if (strEQ(to, "-")) { +#ifdef DEBUGGING + if (debug & 4) + say("Moving %s to stdout.\n", from); +#endif + fromfd = open(from, O_RDONLY); + if (fromfd < 0) + pfatal("internal error, can't reopen %s", from); + while ((i = read(fromfd, buf, sizeof buf)) > 0) + if (write(STDOUT_FILENO, buf, i) != i) + pfatal("write failed"); + close(fromfd); + return 0; + } + if (backup_file(to) < 0) { + say("Can't backup %s, output is in %s: %s\n", to, from, + strerror(errno)); + return -1; + } +#ifdef DEBUGGING + if (debug & 4) + say("Moving %s to %s.\n", from, to); +#endif + if (rename(from, to) < 0) { + if (errno != EXDEV || copy_file(from, to) < 0) { + say("Can't create %s, output is in %s: %s\n", + to, from, strerror(errno)); + return -1; + } + } + return 0; +} + +/* Backup the original file. */ + +int +backup_file(const char *orig) +{ + struct stat filestat; + char bakname[MAXPATHLEN], *s, *simplename; + dev_t orig_device; + ino_t orig_inode; + + if (backup_type == none || stat(orig, &filestat) != 0) + return 0; /* nothing to do */ + orig_device = filestat.st_dev; + orig_inode = filestat.st_ino; + + if (origprae) { + if (strlcpy(bakname, origprae, sizeof(bakname)) >= sizeof(bakname) || + strlcat(bakname, orig, sizeof(bakname)) >= sizeof(bakname)) + fatal("filename %s too long for buffer\n", origprae); + } else { + if ((s = find_backup_file_name(orig)) == NULL) + fatal("out of memory\n"); + if (strlcpy(bakname, s, sizeof(bakname)) >= sizeof(bakname)) + fatal("filename %s too long for buffer\n", s); + free(s); + } + + if ((simplename = strrchr(bakname, '/')) != NULL) + simplename = simplename + 1; + else + simplename = bakname; + + /* + * Find a backup name that is not the same file. Change the + * first lowercase char into uppercase; if that isn't + * sufficient, chop off the first char and try again. + */ + while (stat(bakname, &filestat) == 0 && + orig_device == filestat.st_dev && orig_inode == filestat.st_ino) { + /* Skip initial non-lowercase chars. */ + for (s = simplename; *s && !islower(*s); s++) + ; + if (*s) + *s = toupper(*s); + else + memmove(simplename, simplename + 1, + strlen(simplename + 1) + 1); + } +#ifdef DEBUGGING + if (debug & 4) + say("Moving %s to %s.\n", orig, bakname); +#endif + if (rename(orig, bakname) < 0) { + if (errno != EXDEV || copy_file(orig, bakname) < 0) + return -1; + } + return 0; +} + +/* + * Copy a file. + */ +int +copy_file(const char *from, const char *to) +{ + int tofd, fromfd; + ssize_t i; + + tofd = open(to, O_CREAT|O_TRUNC|O_WRONLY, 0666); + if (tofd < 0) + return -1; + fromfd = open(from, O_RDONLY, 0); + if (fromfd < 0) + pfatal("internal error, can't reopen %s", from); + while ((i = read(fromfd, buf, sizeof buf)) > 0) + if (write(tofd, buf, i) != i) + pfatal("write to %s failed", to); + close(fromfd); + close(tofd); + return 0; +} + +/* + * Allocate a unique area for a string. + */ +char * +savestr(const char *s) +{ + char *rv; + + if (!s) + s = "Oops"; + rv = strdup(s); + if (rv == NULL) { + if (using_plan_a) + out_of_mem = true; + else + fatal("out of memory\n"); + } + return rv; +} + +/* + * Vanilla terminal output (buffered). + */ +void +say(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fflush(stderr); +} + +/* + * Terminal output, pun intended. + */ +void +fatal(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "patch: **** "); + vfprintf(stderr, fmt, ap); + va_end(ap); + my_exit(2); +} + +/* + * Say something from patch, something from the system, then silence . . . + */ +void +pfatal(const char *fmt, ...) +{ + va_list ap; + int errnum = errno; + + fprintf(stderr, "patch: **** "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(errnum)); + my_exit(2); +} + +/* + * Get a response from the user via /dev/tty + */ +void +ask(const char *fmt, ...) +{ + va_list ap; + ssize_t nr = 0; + static int ttyfd = -1; + + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + fflush(stdout); + if (ttyfd < 0) + ttyfd = open(_PATH_TTY, O_RDONLY); + if (ttyfd >= 0) { + if ((nr = read(ttyfd, buf, sizeof(buf))) > 0 && + buf[nr - 1] == '\n') + buf[nr - 1] = '\0'; + } + if (ttyfd < 0 || nr <= 0) { + /* no tty or error reading, pretend user entered 'return' */ + putchar('\n'); + buf[0] = '\0'; + } +} + +/* + * How to handle certain events when not in a critical region. + */ +void +set_signals(int reset) +{ + static sig_t hupval, intval; + + if (!reset) { + hupval = signal(SIGHUP, SIG_IGN); + if (hupval != SIG_IGN) + hupval = (sig_t) my_exit; + intval = signal(SIGINT, SIG_IGN); + if (intval != SIG_IGN) + intval = (sig_t) my_exit; + } + signal(SIGHUP, hupval); + signal(SIGINT, intval); +} + +/* + * How to handle certain events when in a critical region. + */ +void +ignore_signals(void) +{ + signal(SIGHUP, SIG_IGN); + signal(SIGINT, SIG_IGN); +} + +/* + * Make sure we'll have the directories to create a file. If `striplast' is + * true, ignore the last element of `filename'. + */ + +void +makedirs(const char *filename, bool striplast) +{ + char *tmpbuf; + + if ((tmpbuf = strdup(filename)) == NULL) + fatal("out of memory\n"); + + if (striplast) { + char *s = strrchr(tmpbuf, '/'); + if (s == NULL) + return; /* nothing to be done */ + *s = '\0'; + } + if (snprintf(buf, sizeof(buf), "%s -p %s", _PATH_MKDIR, tmpbuf) + >= (int)sizeof(buf)) + fatal("buffer too small to hold %.20s...\n", tmpbuf); + + if (system(buf)) + pfatal("%.40s failed", buf); +} + +/* + * Make filenames more reasonable. + */ +char * +fetchname(const char *at, bool *exists, int strip_leading) +{ + char *fullname, *name, *t; + int sleading; + struct stat filestat; + + if (at == NULL || *at == '\0') + return NULL; + while (isspace(*at)) + at++; +#ifdef DEBUGGING + if (debug & 128) + say("fetchname %s %d\n", at, strip_leading); +#endif + /* So files can be created by diffing against /dev/null. */ + if (strnEQ(at, _PATH_DEVNULL, sizeof(_PATH_DEVNULL) - 1)) + return NULL; + name = fullname = t = savestr(at); + + /* Strip off up to `strip_leading' path components and NUL terminate. */ + for (sleading = strip_leading; *t != '\0' && !isspace(*t); t++) { + if (t[0] == '/' && t[1] != '/' && t[1] != '\0') + if (--sleading >= 0) + name = t + 1; + } + *t = '\0'; + + /* + * If no -p option was given (957 is the default value!), we were + * given a relative pathname, and the leading directories that we + * just stripped off all exist, put them back on. + */ + if (strip_leading == 957 && name != fullname && *fullname != '/') { + name[-1] = '\0'; + if (stat(fullname, &filestat) == 0 && S_ISDIR(filestat.st_mode)) { + name[-1] = '/'; + name = fullname; + } + } + name = savestr(name); + free(fullname); + + *exists = stat(name, &filestat) == 0; + return name; +} + +/* + * Takes the name returned by fetchname and looks in RCS/SCCS directories + * for a checked in version. + */ +char * +checked_in(char *file) +{ + char *filebase, *filedir, tmpbuf[MAXPATHLEN]; + struct stat filestat; + + filebase = basename(file); + filedir = dirname(file); + +#define try(f, a1, a2, a3) \ +(snprintf(tmpbuf, sizeof tmpbuf, f, a1, a2, a3), stat(tmpbuf, &filestat) == 0) + + if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) || + try("%s/RCS/%s%s", filedir, filebase, "") || + try("%s/%s%s", filedir, filebase, RCSSUFFIX) || + try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) || + try("%s/%s%s", filedir, SCCSPREFIX, filebase)) + return file; + + return NULL; +} + +void +version(void) +{ + fprintf(stderr, "Patch version 2.0-12u8-OpenBSD\n"); + my_exit(EXIT_SUCCESS); +} + +/* + * Exit with cleanup. + */ +void +my_exit(int status) +{ + unlink(TMPINNAME); + if (!toutkeep) + unlink(TMPOUTNAME); + if (!trejkeep) + unlink(TMPREJNAME); + unlink(TMPPATNAME); + exit(status); +} diff --git a/usr.bin/patch/util.h b/usr.bin/patch/util.h new file mode 100644 index 0000000000..3c99a6b460 --- /dev/null +++ b/usr.bin/patch/util.h @@ -0,0 +1,50 @@ +/* + * $OpenBSD: util.h,v 1.13 2004/08/05 21:47:24 deraadt Exp $ + * $DragonFly: src/usr.bin/patch/util.h,v 1.1 2004/09/24 18:44:28 joerg Exp $ + */ + +/* + * 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 + */ + +char *fetchname(const char *, bool *, int); +char *checked_in(char *); +int backup_file(const char *); +int move_file(const char *, const char *); +int copy_file(const char *, const char *); +void say(const char *, ...) + __attribute__((__format__(__printf__, 1, 2))); +void fatal(const char *, ...) + __attribute__((__format__(__printf__, 1, 2))); +void pfatal(const char *, ...) + __attribute__((__format__(__printf__, 1, 2))); +void ask(const char *, ...) + __attribute__((__format__(__printf__, 1, 2))); +char *savestr(const char *); +void set_signals(int); +void ignore_signals(void); +void makedirs(const char *, bool); +void version(void); +void my_exit(int) __attribute__((noreturn));