]> Zhao Yanbai Git Server - minix.git/commitdiff
original netbsd patch
authorBen Gras <ben@minix3.org>
Tue, 22 Jun 2010 00:41:00 +0000 (00:41 +0000)
committerBen Gras <ben@minix3.org>
Tue, 22 Jun 2010 00:41:00 +0000 (00:41 +0000)
14 files changed:
commands/patch/Makefile [new file with mode: 0644]
commands/patch/backupfile.c [new file with mode: 0644]
commands/patch/backupfile.h [new file with mode: 0644]
commands/patch/common.h [new file with mode: 0644]
commands/patch/inp.c [new file with mode: 0644]
commands/patch/inp.h [new file with mode: 0644]
commands/patch/mkpath.c [new file with mode: 0644]
commands/patch/patch.1 [new file with mode: 0644]
commands/patch/patch.c [new file with mode: 0644]
commands/patch/pathnames.h [new file with mode: 0644]
commands/patch/pch.c [new file with mode: 0644]
commands/patch/pch.h [new file with mode: 0644]
commands/patch/util.c [new file with mode: 0644]
commands/patch/util.h [new file with mode: 0644]

diff --git a/commands/patch/Makefile b/commands/patch/Makefile
new file mode 100644 (file)
index 0000000..07fa576
--- /dev/null
@@ -0,0 +1,8 @@
+#      $NetBSD: Makefile,v 1.9 2008/09/19 18:33:34 joerg Exp $
+#      $OpenBSD: Makefile,v 1.4 2005/05/16 15:22:46 espie Exp $
+#      $DragonFly: src/usr.bin/patch/Makefile,v 1.8 2008/08/10 23:50:12 joerg Exp $
+
+PROG=  patch
+SRCS=  patch.c pch.c inp.c util.c backupfile.c mkpath.c
+
+.include <bsd.prog.mk>
diff --git a/commands/patch/backupfile.c b/commands/patch/backupfile.c
new file mode 100644 (file)
index 0000000..59a2de9
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * $OpenBSD: backupfile.c,v 1.19 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/backupfile.c,v 1.5 2008/08/11 00:05:06 joerg Exp $
+ * $NetBSD: backupfile.c,v 1.14 2008/09/19 18:33:34 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 <djm@ai.mit.edu>. Some algorithms adapted from GNU Emacs.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: backupfile.c,v 1.14 2008/09/19 18:33:34 joerg Exp $");
+
+#include <ctype.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "backupfile.h"
+
+
+#define ISDIGIT(c) (isascii ((unsigned char)c) && isdigit ((unsigned char)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.
+ */
+const 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 *, size_t);
+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, *tmp_file;
+       int     highest_backup;
+
+       if (backup_type == simple)
+               return concat(file, simple_backup_suffix);
+       tmp_file = strdup(file);
+       if (tmp_file == NULL)
+               return NULL;
+       base_versions = concat(basename(tmp_file), ".~");
+       free(tmp_file);
+       if (base_versions == NULL)
+               return NULL;
+       tmp_file = strdup(file);
+       if (tmp_file == NULL) {
+               free(base_versions);
+               return NULL;
+       }
+       dir = dirname(tmp_file);
+       if (dir == NULL) {
+               free(base_versions);
+               free(tmp_file);
+               return NULL;
+       }
+       highest_backup = max_backup_version(base_versions, dir);
+       free(base_versions);
+       free(tmp_file);
+       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;
+       size_t  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, size_t 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/commands/patch/backupfile.h b/commands/patch/backupfile.h
new file mode 100644 (file)
index 0000000..d146c3a
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * $OpenBSD: backupfile.h,v 1.6 2003/07/28 18:35:36 otto Exp $
+ * $DragonFly: src/usr.bin/patch/backupfile.h,v 1.3 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: backupfile.h,v 1.6 2008/09/19 18:33:34 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 const char      *simple_backup_suffix;
+
+char           *find_backup_file_name(const char *file);
+enum backup_type get_version(const char *version);
diff --git a/commands/patch/common.h b/commands/patch/common.h
new file mode 100644 (file)
index 0000000..c2402d8
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * $OpenBSD: common.h,v 1.26 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/common.h,v 1.5 2008/08/10 23:50:12 joerg Exp $
+ * $NetBSD: common.h,v 1.19 2008/09/19 18:33:34 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 <sys/types.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#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 mode_t  filemode;
+
+extern char    buf[MAXLINELEN];/* general purpose buffer */
+extern size_t  buf_len;
+
+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/commands/patch/inp.c b/commands/patch/inp.c
new file mode 100644 (file)
index 0000000..7bb2de1
--- /dev/null
@@ -0,0 +1,486 @@
+/*
+ * $OpenBSD: inp.c,v 1.34 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/inp.c,v 1.6 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: inp.c,v 1.19 2008/09/19 18:33:34 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 <sys/cdefs.h>
+__RCSID("$NetBSD: inp.c,v 1.19 2008/09/19 18:33:34 joerg Exp $");
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <ctype.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 char    empty_line[] = { '\0' };
+
+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;
+       }
+}
+
+/* Construct 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];
+       struct stat     filestat;
+       off_t           i;
+       ptrdiff_t       sz;
+       size_t          iline, 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())) {
+               const char      *cs = NULL, *filebase, *filedir;
+               struct stat     cstat;
+               char *tmp_filename1, *tmp_filename2;
+
+               tmp_filename1 = strdup(filename);
+               tmp_filename2 = strdup(filename);
+               if (tmp_filename1 == NULL || tmp_filename2 == NULL)
+                       fatal("strdupping filename");
+               filebase = basename(tmp_filename1);
+               filedir = dirname(tmp_filename2);
+
+               /* 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, buf_len, 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, buf_len, GET, s);
+                       snprintf(lbuf, sizeof lbuf, SCCSDIFF, s, filename);
+                       cs = "SCCS";
+               } else if (statfailed)
+                       fatal("can't find %s\n", filename);
+
+               free(tmp_filename1);
+               free(tmp_filename2);
+
+               /*
+                * 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 == 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] = empty_line;
+       } 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;
+       size_t  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);
+       unlink(TMPINNAME);
+       if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0)
+               pfatal("can't open file %s", TMPINNAME);
+       while (fgets(buf, buf_len, 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;
+
+                       if (lseek(tifd, (off_t) (baseline / lines_per_buf *
+                           BUFFERSIZE), SEEK_SET) < 0)
+                               pfatal("cannot seek in the temporary input file");
+
+                       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;
+       size_t          patlen;
+
+       if (revision == NULL)
+               return true;
+       patlen = strlen(revision);
+       if (strnEQ(string, revision, patlen) && isspace((unsigned char)string[patlen]))
+               return true;
+       for (s = string; *s; s++) {
+               if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) &&
+                   isspace((unsigned char)s[patlen + 1])) {
+                       return true;
+               }
+       }
+       return false;
+}
diff --git a/commands/patch/inp.h b/commands/patch/inp.h
new file mode 100644 (file)
index 0000000..d010526
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * $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 $
+ * $NetBSD: inp.h,v 1.10 2008/09/19 18:33:34 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/commands/patch/mkpath.c b/commands/patch/mkpath.c
new file mode 100644 (file)
index 0000000..3d18036
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ *     $OpenBSD: mkpath.c,v 1.2 2005/06/20 07:14:06 otto Exp $
+ *     $DragonFly: src/usr.bin/patch/mkpath.c,v 1.1 2007/09/29 23:11:10 swildner Exp $
+ *     $NetBSD: mkpath.c,v 1.1 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * Copyright (c) 1983, 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: mkpath.c,v 1.1 2008/09/19 18:33:34 joerg Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+
+int    mkpath(char *);
+
+/* Code taken directly from mkdir(1).
+
+ * mkpath -- create directories.
+ *     path     - path
+ */
+int
+mkpath(char *path)
+{
+       struct stat sb;
+       char *slash;
+       int done = 0;
+
+       slash = path;
+
+       while (!done) {
+               slash += strspn(slash, "/");
+               slash += strcspn(slash, "/");
+
+               done = (*slash == '\0');
+               *slash = '\0';
+
+               if (stat(path, &sb)) {
+                       if (errno != ENOENT || (mkdir(path, 0777) &&
+                           errno != EEXIST)) {
+                               warn("%s", path);
+                               return (-1);
+                       }
+               } else if (!S_ISDIR(sb.st_mode)) {
+                       warnx("%s: %s", path, strerror(ENOTDIR));
+                       return (-1);
+               }
+
+               *slash = '/';
+       }
+
+       return (0);
+}
+
diff --git a/commands/patch/patch.1 b/commands/patch/patch.1
new file mode 100644 (file)
index 0000000..b07efdc
--- /dev/null
@@ -0,0 +1,700 @@
+.\"    $OpenBSD: patch.1,v 1.22 2008/06/06 20:44:00 jmc Exp $
+.\"    $DragonFly: src/usr.bin/patch/patch.1,v 1.10 2008/08/18 19:15:55 joerg Exp $
+.\"    $NetBSD: patch.1,v 1.13 2008/09/19 18:33:34 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 August 18, 2008
+.Dt PATCH 1
+.Os
+.Sh NAME
+.Nm patch
+.Nd apply a diff file to an original
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl bCcEeflNnRstuv
+.Op Fl B Ar backup-prefix
+.Op Fl D Ar symbol
+.Op Fl d Ar directory
+.Op Fl F Ar max-fuzz
+.Op Fl i Ar patchfile
+.Op Fl o Ar out-file
+.Op Fl p Ar strip-count
+.Op Fl r Ar rej-name
+.Op Fl V Cm t | nil | never
+.Op Fl x Ar number
+.Op Fl z Ar backup-ext
+.Op Fl Fl posix
+.Op Ar origfile Op Ar patchfile
+.Ek
+.Nm
+.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 Xo
+.Fl B Ar backup-prefix ,
+.Fl Fl prefix Ar backup-prefix
+.Xc
+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 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 Cm existing Qc .
+This option is currently the default, unless
+.Fl -posix
+is specified.
+.It Fl C , Fl Fl check
+Checks that the patch would apply cleanly, but does not modify anything.
+.It Fl c , Fl Fl context
+Forces
+.Nm
+to interpret the patch file as a context diff.
+.It Xo
+.Fl D Ar symbol ,
+.Fl Fl ifdef Ar symbol
+.Xc
+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 Xo
+.Fl d Ar directory ,
+.Fl Fl directory Ar directory
+.Xc
+Causes
+.Nm
+to interpret the next argument as a directory,
+and change the working directory to it before doing anything else.
+.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 e , Fl Fl ed
+Forces
+.Nm
+to interpret the patch file as an
+.Xr ed 1
+script.
+.It Xo
+.Fl F Ar max-fuzz ,
+.Fl Fl fuzz Ar max-fuzz
+.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 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 i Ar patchfile ,
+.Fl Fl input Ar patchfile
+.Xc
+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 forward
+Causes
+.Nm
+to ignore patches that it thinks are reversed or already applied.
+See also
+.Fl R .
+.It Fl n , Fl Fl normal
+Forces
+.Nm
+to interpret the patch file as a normal diff.
+.It Xo
+.Fl o Ar out-file ,
+.Fl Fl output Ar out-file
+.Xc
+Causes the next argument to be interpreted as the output file name.
+.It Xo
+.Fl p Ar strip-count ,
+.Fl Fl strip Ar strip-count
+.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 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 r Ar rej-name ,
+.Fl Fl reject-file Ar rej-name
+.Xc
+Causes the next argument to be interpreted as the reject file name.
+.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 Xo
+.Fl V Cm t | nil | never ,
+.Fl Fl version-control Cm t | nil | never
+.Xc
+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 Cm t , numbered
+Always make numbered backups.
+.It Cm nil , existing
+Make numbered backups of files that already have them,
+simple backups of the others.
+.It Cm never , simple
+Always make simple backups.
+.El
+.It Fl v , Fl Fl version
+Causes
+.Nm
+to print out its revision header and patch level.
+.It Xo
+.Fl x Ar number ,
+.Fl Fl debug Ar number
+.Xc
+Sets internal debugging flags, and is of interest only to
+.Nm
+patchers.
+.It Xo
+.Fl z Ar backup-ext ,
+.Fl Fl suffix Ar backup-ext
+.Xc
+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.1-2004
+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 ,
+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.1-2004
+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 STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2004
+specification
+(except as detailed above for the
+.Fl -posix
+option),
+though the presence of
+.Nm
+itself is optional.
+.Pp
+The flags
+.Op Fl CEfstuvBFVxz
+and
+.Op Fl -posix
+are extensions to that specification.
+.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/commands/patch/patch.c b/commands/patch/patch.c
new file mode 100644 (file)
index 0000000..072a2e8
--- /dev/null
@@ -0,0 +1,1064 @@
+/*
+ * $OpenBSD: patch.c,v 1.45 2007/04/18 21:52:24 sobrado Exp $
+ * $DragonFly: src/usr.bin/patch/patch.c,v 1.10 2008/08/10 23:39:56 joerg Exp $
+ * $NetBSD: patch.c,v 1.27 2008/09/19 18:33:34 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 <sys/cdefs.h>
+__RCSID("$NetBSD: patch.c,v 1.27 2008/09/19 18:33:34 joerg Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "util.h"
+#include "pch.h"
+#include "inp.h"
+#include "backupfile.h"
+#include "pathnames.h"
+
+mode_t         filemode = 0644;
+
+char           buf[MAXLINELEN];        /* general purpose buffer */
+size_t         buf_len = sizeof(buf);
+
+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_context_hunk(void);
+static void    rej_line(int, 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 bool    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, 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 */
+
+               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 */
+                                       if (ferror(ofp) || fclose(ofp)) {
+                                               say("Error writing %s\n",
+                                                   TMPOUTNAME);
+                                               error = 1;
+                                       }
+                                       ofp = NULL;
+                               }
+                       }
+                       newwhere = pch_newfirst() + last_offset;
+                       if (skip_rest_of_patch) {
+                               abort_hunk();
+                               failed++;
+                               if (verbose)
+                                       say("Hunk #%d ignored at %ld.\n",
+                                           hunk, newwhere);
+                       } else if (where == 0) {
+                               abort_hunk();
+                               failed++;
+                               if (verbose)
+                                       say("Hunk #%d failed at %ld.\n",
+                                           hunk, newwhere);
+                       } else {
+                               apply_hunk(where);
+                               if (verbose) {
+                                       say("Hunk #%d succeeded at %ld",
+                                           hunk, newwhere);
+                                       if (fuzz != 0)
+                                               say(" with fuzz %ld", fuzz);
+                                       if (last_offset)
+                                               say(" (offset %ld line%s)",
+                                                   last_offset,
+                                                   last_offset == 1L ? "" : "s");
+                                       say(".\n");
+                               }
+                       }
+               }
+
+               if (out_of_mem && using_plan_a) {
+                       Argc = Argc_last;
+                       Argv = Argv_last;
+                       say("\n\nRan out of memory using Plan A--trying again...\n\n");
+                       if (ofp)
+                               fclose(ofp);
+                       ofp = NULL;
+                       if (rejfp)
+                               fclose(rejfp);
+                       rejfp = NULL;
+                       continue;
+               }
+               if (hunk == 0)
+                       fatal("Internal error: hunk should not be 0\n");
+
+               /* finish spewing out the new file */
+               if (!skip_rest_of_patch && !spew_output()) {
+                       say("Can't write %s\n", TMPOUTNAME);
+                       error = 1;
+               }
+
+               /* and put the output where desired */
+               ignore_signals();
+               if (!skip_rest_of_patch) {
+                       struct stat     statbuf;
+                       char    *realout = outname;
+
+                       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);
+                               }
+                       }
+               }
+               if (ferror(rejfp) || fclose(rejfp)) {
+                       say("Error writing %s\n", rejname);
+                       error = 1;
+               }
+               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((unsigned char)*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 symbol] [-d directory]\n"
+"             [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]\n"
+"             [-r rej-name] [-V t | nil | never] [-x number] [-z backup-ext]\n"
+"             [--posix] [origfile [patchfile]]\n"
+"       patch <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");
+               }
+               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_context_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_context_hunk\n");
+               }
+       }
+}
+
+static void
+rej_line(int ch, LINENUM i)
+{
+       size_t len;
+       const char *line = pfetch(i);
+
+       len = strlen(line);
+
+       fprintf(rejfp, "%c%s", ch, line);
+       if (len == 0 || line[len-1] != '\n')
+               fprintf(rejfp, "\n\\ No newline at end of file\n");
+}
+
+static void
+abort_hunk(void)
+{
+       LINENUM         i, j, split;
+       int             ch1, ch2;
+       const LINENUM   pat_end = pch_end();
+       const LINENUM   oldfirst = pch_first() + last_offset;
+       const LINENUM   newfirst = pch_newfirst() + last_offset;
+
+       if (diff_type != UNI_DIFF) {
+               abort_context_hunk();
+               return;
+       }
+       split = -1;
+       for (i = 0; i <= pat_end; i++) {
+               if (pch_char(i) == '=') {
+                       split = i;
+                       break;
+               }
+       }
+       if (split == -1) {
+               fprintf(rejfp, "malformed hunk: no split found\n");
+               return;
+       }
+       i = 0;
+       j = split + 1;
+       fprintf(rejfp, "@@ -%ld,%ld +%ld,%ld @@\n",
+           pch_ptrn_lines() ? oldfirst : 0,
+           pch_ptrn_lines(), newfirst, pch_repl_lines());
+       while (i < split || j <= pat_end) {
+               ch1 = i < split ? pch_char(i) : -1;
+               ch2 = j <= pat_end ? pch_char(j) : -1;
+               if (ch1 == '-') {
+                       rej_line('-', i);
+                       i++;
+               } else if (ch1 == ' ' && ch2 == ' ') {
+                       rej_line(' ', i);
+                       i++;
+                       j++;
+               } else if (ch1 == '!' && ch2 == '!') {
+                       while (i < split && ch1 == '!') {
+                               rej_line('-', i);
+                               i++;
+                               ch1 = i < split ? pch_char(i) : -1;
+                       }
+                       while (j <= pat_end && ch2 == '!') {
+                               rej_line('+', j);
+                               j++;
+                               ch2 = j <= pat_end ? pch_char(j) : -1;
+                       }
+               } else if (ch1 == '*') {
+                       i++;
+               } else if (ch2 == '+' || ch2 == ' ') {
+                       rej_line(ch2, j);
+                       j++;
+               } else {
+                       fprintf(rejfp, "internal error on (%ld %ld %ld)\n",
+                           i, split, j);
+                       rej_line(ch1, i);
+                       rej_line(ch2, j);
+                       return;
+               }
+       }
+}
+
+/* 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 bool
+spew_output(void)
+{
+       int rv;
+
+#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 */
+       rv = ferror(ofp) == 0 && fclose(ofp) == 0;
+       ofp = NULL;
+       return rv;
+}
+
+/*
+ * Copy one line from input to output.
+ */
+static void
+dump_line(LINENUM line, bool write_newline)
+{
+       char    *s;
+
+       s = ifetch(line, 0);
+       if (s == NULL)
+               return;
+       /* Note: string is not NUL terminated. */
+       for (; *s != '\n'; s++)
+               putc(*s, ofp);
+       if (write_newline)
+               putc('\n', ofp);
+}
+
+/*
+ * Does the patch pattern match at line base+offset?
+ */
+static bool
+patch_match(LINENUM base, LINENUM offset, LINENUM fuzz)
+{
+       LINENUM         pline = 1 + fuzz;
+       LINENUM         iline;
+       LINENUM         pat_lines = pch_ptrn_lines() - fuzz;
+       const char      *ilineptr;
+       const char      *plineptr;
+       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((unsigned char)*b)) {       /* whitespace (or \n) to match? */
+                       if (!isspace((unsigned char)*a))        /* no corresponding whitespace? */
+                               return false;
+                       while (len && isspace((unsigned char)*b) && *b != '\n')
+                               b++, len--;     /* skip pattern whitespace */
+                       while (isspace((unsigned char)*a) && *a != '\n')
+                               a++;    /* skip target whitespace */
+                       if (*a == '\n' || *b == '\n')
+                               return (*a == *b);      /* should end in sync */
+               } else if (*a++ != *b++)        /* match non-whitespace chars */
+                       return false;
+               else
+                       len--;  /* probably not necessary */
+       }
+       return true;            /* actually, this is not reached */
+       /* since there is always a \n */
+}
diff --git a/commands/patch/pathnames.h b/commands/patch/pathnames.h
new file mode 100644 (file)
index 0000000..e9d3ccb
--- /dev/null
@@ -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.2 2008/08/11 00:04:12 joerg Exp $
+ * $NetBSD: pathnames.h,v 1.1 2008/09/19 18:33:34 joerg Exp $
+ */
+
+/*
+ * Placed in the public domain by Todd C. Miller <Todd.Miller@courtesan.com>
+ * on July 29, 2003.
+ */
+
+#include <paths.h>
+
+#define        _PATH_ED                "/bin/ed"
diff --git a/commands/patch/pch.c b/commands/patch/pch.c
new file mode 100644 (file)
index 0000000..6dcf3ae
--- /dev/null
@@ -0,0 +1,1555 @@
+/*
+ * $OpenBSD: pch.c,v 1.37 2007/09/02 15:19:33 deraadt Exp $
+ * $DragonFly: src/usr.bin/patch/pch.c,v 1.6 2008/08/10 23:35:40 joerg Exp $
+ * $NetBSD: pch.c,v 1.23 2008/09/19 18:33:34 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 <sys/cdefs.h>
+__RCSID("$NetBSD: pch.c,v 1.23 2008/09/19 18:33:34 joerg Exp $");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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, buf_len, stdin) != NULL)
+                       fputs(buf, pfp);
+               if (ferror(pfp) || fclose(pfp))
+                       pfatal("can't write %s", TMPPATNAME);
+               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 = calloc((size_t) hunkmax, sizeof(char *));
+       if (p_len == NULL)
+               p_len = calloc((size_t) hunkmax, sizeof(short));
+       if (p_char == NULL)
+               p_char = calloc((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, buf_len, 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((unsigned char)*t) || *t == ','; t++)
+                       ;
+               this_is_a_command = (isdigit((unsigned char)*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((unsigned char)*t); t++)
+                               ;
+                       revision = savestr(t);
+                       for (t = revision; *t && !isspace((unsigned char)*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, buf_len, 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, buf_len, 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, buf_len, pfp);
+                       p_input_line++;
+                       if (ret == NULL) {
+                               if (p_max - p_end < 4) {
+                                       /* assume blank lines got chopped */
+                                       strlcpy(buf, "  \n", buf_len);
+                               } 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((unsigned char)*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((unsigned char)*s))
+                                       s++;
+                               if (*s == ',') {
+                                       for (; *s && !isdigit((unsigned char)*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((unsigned char)*s); s++)
+                                               ;
+                                       if (!*s)
+                                               malformed();
+                                       p_newfirst = (LINENUM) atol(s);
+                                       while (isdigit((unsigned char)*s))
+                                               s++;
+                                       if (*s == ',') {
+                                               for (; *s && !isdigit((unsigned char)*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", buf_len - 1);
+                               if (!isspace((unsigned char)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((unsigned char)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) {
+               LINENUM fillold;        /* index of old lines */
+               LINENUM fillnew;        /* index of new lines */
+               char    ch;
+
+               line_beginning = ftell(pfp); /* file pos of the current line */
+               ret = pgets(buf, buf_len, 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((unsigned char)*s))
+                       s++;
+               if (*s == ',') {
+                       p_ptrn_lines = (LINENUM) atol(++s);
+                       while (isdigit((unsigned char)*s))
+                               s++;
+               } else
+                       p_ptrn_lines = 1;
+               if (*s == ' ')
+                       s++;
+               if (*s != '+' || !*++s)
+                       malformed();
+               p_newfirst = (LINENUM) atol(s);
+               while (isdigit((unsigned char)*s))
+                       s++;
+               if (*s == ',') {
+                       p_repl_lines = (LINENUM) atol(++s);
+                       while (isdigit((unsigned char)*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();
+               fillold = 1;
+               fillnew = fillold + p_ptrn_lines;
+               p_end = fillnew + p_repl_lines;
+               snprintf(buf, buf_len, "*** %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, buf_len, "--- %ld,%ld ----\n", p_newfirst,
+                   p_newfirst + p_repl_lines - 1);
+               p_line[fillnew] = savestr(buf);
+               if (out_of_mem) {
+                       p_end = 0;
+                       return false;
+               }
+               p_char[fillnew++] = '=';
+               p_context = 100;
+               context = 0;
+               p_hunk_beg = p_input_line + 1;
+               while (fillold <= p_ptrn_lines || fillnew <= p_end) {
+                       line_beginning = ftell(pfp);
+                       ret = pgets(buf, buf_len, pfp);
+                       p_input_line++;
+                       if (ret == NULL) {
+                               if (p_max - fillnew < 3) {
+                                       /* assume blank lines got chopped */
+                                       strlcpy(buf, " \n", buf_len);
+                               } 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 (--fillnew > p_ptrn_lines)
+                                       free(p_line[fillnew]);
+                               p_end = fillold - 1;
+                               return false;
+                       }
+                       switch (ch) {
+                       case '-':
+                               if (fillold > p_ptrn_lines) {
+                                       free(s);
+                                       p_end = fillnew - 1;
+                                       malformed();
+                               }
+                               p_char[fillold] = ch;
+                               p_line[fillold] = s;
+                               p_len[fillold++] = strlen(s);
+                               if (fillold > p_ptrn_lines) {
+                                       if (remove_special_line()) {
+                                               p_len[fillold - 1] -= 1;
+                                               s[p_len[fillold - 1]] = 0;
+                                       }
+                               }
+                               break;
+                       case '=':
+                               ch = ' ';
+                               /* FALL THROUGH */
+                       case ' ':
+                               if (fillold > p_ptrn_lines) {
+                                       free(s);
+                                       while (--fillnew > p_ptrn_lines)
+                                               free(p_line[fillnew]);
+                                       p_end = fillold - 1;
+                                       malformed();
+                               }
+                               context++;
+                               p_char[fillold] = ch;
+                               p_line[fillold] = s;
+                               p_len[fillold++] = strlen(s);
+                               s = savestr(s);
+                               if (out_of_mem) {
+                                       while (--fillnew > p_ptrn_lines)
+                                               free(p_line[fillnew]);
+                                       p_end = fillold - 1;
+                                       return false;
+                               }
+                               if (fillold > p_ptrn_lines) {
+                                       if (remove_special_line()) {
+                                               p_len[fillold - 1] -= 1;
+                                               s[p_len[fillold - 1]] = 0;
+                                       }
+                               }
+                               /* FALL THROUGH */
+                       case '+':
+                               if (fillnew > p_end) {
+                                       free(s);
+                                       while (--fillnew > p_ptrn_lines)
+                                               free(p_line[fillnew]);
+                                       p_end = fillold - 1;
+                                       malformed();
+                               }
+                               p_char[fillnew] = ch;
+                               p_line[fillnew] = s;
+                               p_len[fillnew++] = strlen(s);
+                               if (fillold > p_ptrn_lines) {
+                                       if (remove_special_line()) {
+                                               p_len[fillnew - 1] -= 1;
+                                               s[p_len[fillnew - 1]] = 0;
+                                       }
+                               }
+                               break;
+                       default:
+                               p_end = fillnew;
+                               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;
+
+               line_beginning = ftell(pfp);
+               p_context = 0;
+               ret = pgets(buf, buf_len, pfp);
+               p_input_line++;
+               if (ret == NULL || !isdigit((unsigned char)*buf)) {
+                       next_intuit_at(line_beginning, p_input_line);
+                       return false;
+               }
+               p_first = (LINENUM) atol(buf);
+               for (s = buf; isdigit((unsigned char)*s); s++)
+                       ;
+               if (*s == ',') {
+                       p_ptrn_lines = (LINENUM) atol(++s) - p_first + 1;
+                       while (isdigit((unsigned char)*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((unsigned char)*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, buf_len, "*** %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, buf_len, 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, buf_len, 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, buf_len, "--- %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, buf_len, 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, buf_len) >= buf_len)
+                       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 = 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, buf_len, "%s%s%s", _PATH_ED,
+                   verbose ? " " : " -s ", TMPOUTNAME);
+               pipefp = popen(buf, "w");
+       }
+       for (;;) {
+               beginning_of_this_line = ftell(pfp);
+               if (pgets(buf, buf_len, pfp) == NULL) {
+                       next_intuit_at(beginning_of_this_line, p_input_line);
+                       break;
+               }
+               p_input_line++;
+               for (t = buf; isdigit((unsigned char)*t) || *t == ','; t++)
+                       ;
+               /* POSIX defines allowed commands as {a,c,d,i,s} */
+               if (isdigit((unsigned char)*buf) && (*t == 'a' || *t == 'c' ||
+                   *t == 'd' || *t == 'i' || *t == 's')) {
+                       if (pipefp != NULL)
+                               fputs(buf, pipefp);
+                       if (*t != 'd') {
+                               while (pgets(buf, buf_len, 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/commands/patch/pch.h b/commands/patch/pch.h
new file mode 100644 (file)
index 0000000..a2d45a7
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * $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 $
+ * $NetBSD: pch.h,v 1.10 2008/09/19 18:33:34 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/commands/patch/util.c b/commands/patch/util.c
new file mode 100644 (file)
index 0000000..bc5b260
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ * $OpenBSD: util.c,v 1.32 2006/03/11 19:41:30 otto Exp $
+ * $DragonFly: src/usr.bin/patch/util.c,v 1.9 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: util.c,v 1.24 2008/09/19 18:33:34 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 <sys/cdefs.h>
+__RCSID("$NetBSD: util.c,v 1.24 2008/09/19 18:33:34 joerg Exp $");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#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, buf_len)) > 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 */
+       /*
+        * If the user used zero prefixes or suffixes, then
+        * he doesn't want backups.  Yet we have to remove
+        * orig to break possible hardlinks.
+        */
+       if ((origprae && *origprae == 0) || *simple_backup_suffix == 0) {
+               unlink(orig);
+               return 0;
+       }
+       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((unsigned char)*s); s++)
+                       ;
+               if (*s)
+                       *s = toupper((unsigned char)*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, buf_len)) > 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, buf_len)) > 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 = my_exit;
+               intval = signal(SIGINT, SIG_IGN);
+               if (intval != SIG_IGN)
+                       intval = 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 (mkpath(tmpbuf) != 0)
+               pfatal("creation of %s failed", tmpbuf);
+       free(tmpbuf);
+}
+
+/*
+ * Make filenames more reasonable.
+ */
+char *
+fetchname(const char *at, bool *exists, int strip_leading)
+{
+       char            *fullname, *name, *t;
+       int             sleading, tab;
+       struct stat     filestat;
+
+       if (at == NULL || *at == '\0')
+               return NULL;
+       while (isspace((unsigned char)*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);
+
+       tab = strchr(t, '\t') != NULL;
+       /* Strip off up to `strip_leading' path components and NUL terminate. */
+       for (sleading = strip_leading; *t != '\0' && ((tab && *t != '\t') ||
+           !isspace((unsigned char)*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-NetBSD\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/commands/patch/util.h b/commands/patch/util.h
new file mode 100644 (file)
index 0000000..222136c
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * $OpenBSD: util.h,v 1.15 2005/06/20 07:14:06 otto Exp $
+ * $DragonFly: src/usr.bin/patch/util.h,v 1.2 2007/09/29 23:11:10 swildner Exp $
+ * $NetBSD: util.h,v 1.11 2008/09/19 18:33:34 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));
+
+/* in mkpath.c */
+extern int mkpath(char *);