aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/patch
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/patch')
-rw-r--r--usr.bin/patch/CVS/Entries16
-rw-r--r--usr.bin/patch/CVS/Repository1
-rw-r--r--usr.bin/patch/CVS/Root1
-rw-r--r--usr.bin/patch/Makefile6
-rw-r--r--usr.bin/patch/backupfile.c237
-rw-r--r--usr.bin/patch/backupfile.h38
-rw-r--r--usr.bin/patch/common.h111
-rw-r--r--usr.bin/patch/ed.c336
-rw-r--r--usr.bin/patch/ed.h19
-rw-r--r--usr.bin/patch/inp.c430
-rw-r--r--usr.bin/patch/inp.h31
-rw-r--r--usr.bin/patch/mkpath.c77
-rw-r--r--usr.bin/patch/patch.1680
-rw-r--r--usr.bin/patch/patch.c1064
-rw-r--r--usr.bin/patch/pch.c1522
-rw-r--r--usr.bin/patch/pch.h60
-rw-r--r--usr.bin/patch/util.c425
-rw-r--r--usr.bin/patch/util.h51
18 files changed, 5105 insertions, 0 deletions
diff --git a/usr.bin/patch/CVS/Entries b/usr.bin/patch/CVS/Entries
new file mode 100644
index 0000000..aab1841
--- /dev/null
+++ b/usr.bin/patch/CVS/Entries
@@ -0,0 +1,16 @@
+/Makefile/1.5/Fri Oct 16 07:33:47 2015//
+/backupfile.c/1.21/Tue Nov 26 13:19:07 2013//
+/backupfile.h/1.6/Mon Jul 28 18:35:36 2003//
+/common.h/1.30/Mon Dec 2 22:17:32 2019//
+/ed.c/1.4/Mon Dec 2 22:17:32 2019//
+/ed.h/1.1/Fri Oct 16 07:33:47 2015//
+/inp.c/1.49/Fri Jun 28 13:35:02 2019//
+/inp.h/1.8/Fri Aug 15 08:00:51 2003//
+/mkpath.c/1.4/Tue May 20 01:25:23 2014//
+/patch.1/1.32/Fri Jun 22 15:37:15 2018//
+/patch.c/1.69/Mon Dec 2 22:17:32 2019//
+/pch.c/1.62/Mon Dec 2 22:23:19 2019//
+/pch.h/1.13/Wed Dec 11 20:10:17 2019//
+/util.c/1.45/Mon Dec 2 22:17:32 2019//
+/util.h/1.18/Sat Aug 17 14:25:06 2019//
+D
diff --git a/usr.bin/patch/CVS/Repository b/usr.bin/patch/CVS/Repository
new file mode 100644
index 0000000..f6f2ebb
--- /dev/null
+++ b/usr.bin/patch/CVS/Repository
@@ -0,0 +1 @@
+src/usr.bin/patch
diff --git a/usr.bin/patch/CVS/Root b/usr.bin/patch/CVS/Root
new file mode 100644
index 0000000..3811072
--- /dev/null
+++ b/usr.bin/patch/CVS/Root
@@ -0,0 +1 @@
+/cvs
diff --git a/usr.bin/patch/Makefile b/usr.bin/patch/Makefile
new file mode 100644
index 0000000..7ff9ec2
--- /dev/null
+++ b/usr.bin/patch/Makefile
@@ -0,0 +1,6 @@
+# $OpenBSD: Makefile,v 1.5 2015/10/16 07:33:47 tobias Exp $
+
+PROG= patch
+SRCS= patch.c pch.c inp.c util.c backupfile.c mkpath.c ed.c
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/patch/backupfile.c b/usr.bin/patch/backupfile.c
new file mode 100644
index 0000000..243d00e
--- /dev/null
+++ b/usr.bin/patch/backupfile.c
@@ -0,0 +1,237 @@
+/* $OpenBSD: backupfile.c,v 1.21 2013/11/26 13:19:07 deraadt 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 <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 (c) && isdigit (c))
+
+/* Which type of backup file names are generated. */
+enum backup_type backup_type = none;
+
+/*
+ * The extension added to file names to produce a simple (as opposed to
+ * numbered) backup file name.
+ */
+char *simple_backup_suffix = "~";
+
+static char *concat(const char *, const char *);
+static char *make_version_name(const char *, int);
+static int max_backup_version(const char *, const char *);
+static int version_number(const char *, const char *, 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 *path, *base_versions;
+ int highest_backup;
+
+ if (backup_type == simple)
+ return concat(file, simple_backup_suffix);
+ path = strdup(file);
+ if (path == NULL)
+ return NULL;
+ base_versions = concat(basename(path), ".~");
+ if (base_versions == NULL) {
+ free(path);
+ return NULL;
+ }
+ highest_backup = max_backup_version(base_versions, dirname(path));
+ free(base_versions);
+ free(path);
+ 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 (strlen(dp->d_name) <= 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((unsigned char)backup[base_length])) {
+ for (p = &backup[base_length]; ISDIGIT((unsigned char)*p); ++p)
+ version = version * 10 + *p - '0';
+ if (p[0] != '~' || p[1])
+ version = 0;
+ }
+ return version;
+}
+
+/*
+ * Return the newly-allocated concatenation of STR1 and STR2. If out of
+ * memory, return 0.
+ */
+static char *
+concat(const char *str1, const char *str2)
+{
+ char *newstr;
+
+ if (asprintf(&newstr, "%s%s", str1, str2) == -1)
+ return NULL;
+ return newstr;
+}
+
+/*
+ * If ARG is an unambiguous match for an element of the null-terminated array
+ * OPTLIST, return the index in OPTLIST of the matched element, else -1 if it
+ * does not match any element or -2 if it is ambiguous (is a prefix of more
+ * than one element).
+ */
+static int
+argmatch(const char *arg, const char **optlist)
+{
+ int i; /* Temporary index in OPTLIST. */
+ size_t arglen; /* Length of ARG. */
+ int matchind = -1; /* Index of first nonexact match. */
+ int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */
+
+ arglen = strlen(arg);
+
+ /* Test all elements for either exact match or abbreviated matches. */
+ for (i = 0; optlist[i]; i++) {
+ if (!strncmp(optlist[i], arg, arglen)) {
+ if (strlen(optlist[i]) == arglen)
+ /* Exact match found. */
+ return i;
+ else if (matchind == -1)
+ /* First nonexact match found. */
+ matchind = i;
+ else
+ /* Second nonexact match found. */
+ ambiguous = 1;
+ }
+ }
+ if (ambiguous)
+ return -2;
+ else
+ return matchind;
+}
+
+/*
+ * Error reporting for argmatch. KIND is a description of the type of entity
+ * that was being matched. VALUE is the invalid value that was given. PROBLEM
+ * is the return value from argmatch.
+ */
+static void
+invalid_arg(const char *kind, const char *value, int problem)
+{
+ fprintf(stderr, "patch: ");
+ if (problem == -1)
+ fprintf(stderr, "invalid");
+ else /* Assume -2. */
+ fprintf(stderr, "ambiguous");
+ fprintf(stderr, " %s `%s'\n", kind, value);
+}
+
+static const char *backup_args[] = {
+ "never", "simple", "nil", "existing", "t", "numbered", 0
+};
+
+static enum backup_type backup_types[] = {
+ simple, simple, numbered_existing,
+ numbered_existing, numbered, numbered
+};
+
+/*
+ * Return the type of backup indicated by VERSION. Unique abbreviations are
+ * accepted.
+ */
+enum backup_type
+get_version(const char *version)
+{
+ int i;
+
+ if (version == NULL || *version == '\0')
+ return numbered_existing;
+ i = argmatch(version, backup_args);
+ if (i >= 0)
+ return backup_types[i];
+ invalid_arg("version control type", version, i);
+ exit(2);
+}
diff --git a/usr.bin/patch/backupfile.h b/usr.bin/patch/backupfile.h
new file mode 100644
index 0000000..7c20ddb
--- /dev/null
+++ b/usr.bin/patch/backupfile.h
@@ -0,0 +1,38 @@
+/* $OpenBSD: backupfile.h,v 1.6 2003/07/28 18:35:36 otto Exp $ */
+
+/*
+ * backupfile.h -- declarations for making Emacs style backup file names
+ * Copyright (C) 1990 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * without restriction.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/* When to make backup files. */
+enum backup_type {
+ /* Never make backups. */
+ none,
+
+ /* Make simple backups of every file. */
+ simple,
+
+ /*
+ * Make numbered backups of files that already have numbered backups,
+ * and simple backups of the others.
+ */
+ numbered_existing,
+
+ /* Make numbered backups of every file. */
+ numbered
+};
+
+extern enum backup_type backup_type;
+extern char *simple_backup_suffix;
+
+char *find_backup_file_name(const char *file);
+enum backup_type get_version(const char *version);
diff --git a/usr.bin/patch/common.h b/usr.bin/patch/common.h
new file mode 100644
index 0000000..8165d5b
--- /dev/null
+++ b/usr.bin/patch/common.h
@@ -0,0 +1,111 @@
+/* $OpenBSD: common.h,v 1.30 2019/12/02 22:17:32 jca 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 <limits.h>
+#include <stdbool.h>
+
+#define DEBUGGING
+
+/* constants */
+
+#define MAXHUNKSIZE 100000 /* is this enough lines? */
+#define INITHUNKMAX 125 /* initial dynamic allocation size */
+#define INITLINELEN 8192
+#define BUFFERSIZE 1024
+#define LINENUM_MAX LONG_MAX
+
+#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; /* general purpose buffer */
+extern size_t bufsz; /* general purpose buffer size */
+
+extern bool using_plan_a; /* try to keep everything in memory */
+extern bool out_of_mem; /* ran out of memory in plan a */
+
+#define MAXFILEC 2
+
+extern char *filearg[MAXFILEC];
+extern bool ok_to_create_file;
+extern char *outname;
+extern char *origprae;
+
+extern char *TMPOUTNAME;
+extern char *TMPINNAME;
+extern char *TMPREJNAME;
+extern char *TMPPATNAME;
+extern bool toutkeep;
+extern bool trejkeep;
+
+#ifdef DEBUGGING
+extern int debug;
+#endif
+
+extern bool force;
+extern bool batch;
+extern bool verbose;
+extern bool reverse;
+extern bool noreverse;
+extern bool skip_rest_of_patch;
+extern int strippath;
+extern bool canonicalize;
+/* TRUE if -C was specified on command line. */
+extern bool check_only;
+extern bool warn_on_invalid_line;
+extern bool last_line_missing_eol;
+
+
+#define CONTEXT_DIFF 1
+#define NORMAL_DIFF 2
+#define ED_DIFF 3
+#define NEW_CONTEXT_DIFF 4
+#define UNI_DIFF 5
+
+extern int diff_type;
+extern char *revision; /* prerequisite revision, if any */
+extern LINENUM input_lines; /* how long is input file in lines */
+
+extern int posix;
+
diff --git a/usr.bin/patch/ed.c b/usr.bin/patch/ed.c
new file mode 100644
index 0000000..3b83cb3
--- /dev/null
+++ b/usr.bin/patch/ed.c
@@ -0,0 +1,336 @@
+/* $OpenBSD: ed.c,v 1.4 2019/12/02 22:17:32 jca Exp $ */
+
+/*
+ * Copyright (c) 2015 Tobias Stoeckmann <tobias@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+#include "util.h"
+#include "pch.h"
+#include "inp.h"
+
+/* states of finite state machine */
+#define FSM_CMD 1
+#define FSM_A 2
+#define FSM_C 3
+#define FSM_D 4
+#define FSM_I 5
+#define FSM_S 6
+
+#define SRC_INP 1 /* line's origin is input file */
+#define SRC_PCH 2 /* line's origin is patch file */
+
+#define S_PATTERN "/.//"
+
+static void init_lines(void);
+static void free_lines(void);
+static struct ed_line *get_line(LINENUM);
+static struct ed_line *create_line(off_t);
+static int valid_addr(LINENUM, LINENUM);
+static int get_command(void);
+static void write_lines(char *);
+
+LIST_HEAD(ed_head, ed_line) head;
+struct ed_line {
+ LIST_ENTRY(ed_line) entries;
+ int src;
+ unsigned long subst;
+ union {
+ LINENUM lineno;
+ off_t seek;
+ } pos;
+};
+
+static LINENUM first_addr;
+static LINENUM second_addr;
+static LINENUM line_count;
+static struct ed_line *cline; /* current line */
+
+void
+do_ed_script(void)
+{
+ off_t linepos;
+ struct ed_line *nline;
+ LINENUM i, range;
+ int fsm;
+
+ init_lines();
+ cline = NULL;
+ fsm = FSM_CMD;
+
+ for (;;) {
+ linepos = ftello(pfp);
+ if (pgetline(&buf, &bufsz, pfp) == -1)
+ break;
+ p_input_line++;
+
+ if (fsm == FSM_CMD) {
+ if ((fsm = get_command()) == -1)
+ break;
+
+ switch (fsm) {
+ case FSM_C:
+ case FSM_D:
+ /* delete lines in specified range */
+ if (second_addr == -1)
+ range = 1;
+ else
+ range = second_addr - first_addr + 1;
+ for (i = 0; i < range; i++) {
+ nline = LIST_NEXT(cline, entries);
+ LIST_REMOVE(cline, entries);
+ free(cline);
+ cline = nline;
+ line_count--;
+ }
+ cline = get_line(first_addr - 1);
+ fsm = (fsm == FSM_C) ? FSM_A : FSM_CMD;
+ break;
+ case FSM_S:
+ cline->subst++;
+ fsm = FSM_CMD;
+ break;
+ default:
+ break;
+ }
+
+ continue;
+ }
+
+ if (strcmp(buf, ".\n") == 0) {
+ fsm = FSM_CMD;
+ continue;
+ }
+
+ nline = create_line(linepos);
+ if (cline == NULL)
+ LIST_INSERT_HEAD(&head, nline, entries);
+ else if (fsm == FSM_A)
+ LIST_INSERT_AFTER(cline, nline, entries);
+ else
+ LIST_INSERT_BEFORE(cline, nline, entries);
+ cline = nline;
+ line_count++;
+ fsm = FSM_A;
+ }
+
+ next_intuit_at(linepos, p_input_line);
+
+ if (skip_rest_of_patch) {
+ free_lines();
+ return;
+ }
+
+ write_lines(TMPOUTNAME);
+ free_lines();
+
+ ignore_signals();
+ if (!check_only) {
+ if (move_file(TMPOUTNAME, outname) < 0) {
+ toutkeep = true;
+ chmod(TMPOUTNAME, filemode);
+ } else
+ chmod(outname, filemode);
+ }
+ set_signals(1);
+}
+
+static int
+get_command(void)
+{
+ char *p;
+ LINENUM min_addr;
+ int fsm;
+
+ min_addr = 0;
+ fsm = -1;
+ p = buf;
+
+ /* maybe garbage encountered at end of patch */
+ if (!isdigit((unsigned char)*p))
+ return -1;
+
+ first_addr = strtolinenum(buf, &p);
+ second_addr = (*p == ',') ? strtolinenum(p + 1, &p) : -1;
+
+ switch (*p++) {
+ case 'a':
+ if (second_addr != -1)
+ fatal("invalid address at line %ld: %s",
+ p_input_line, buf);
+ fsm = FSM_A;
+ break;
+ case 'c':
+ fsm = FSM_C;
+ min_addr = 1;
+ break;
+ case 'd':
+ fsm = FSM_D;
+ min_addr = 1;
+ break;
+ case 'i':
+ if (second_addr != -1)
+ fatal("invalid address at line %ld: %s",
+ p_input_line, buf);
+ fsm = FSM_I;
+ break;
+ case 's':
+ if (second_addr != -1)
+ fatal("unsupported address range at line %ld: %s",
+ p_input_line, buf);
+ if (strncmp(p, S_PATTERN, sizeof(S_PATTERN) - 1) != 0)
+ fatal("unsupported substitution at "
+ "line %ld: %s", p_input_line, buf);
+ p += sizeof(S_PATTERN) - 1;
+ fsm = FSM_S;
+ min_addr = 1;
+ break;
+ default:
+ return -1;
+ /* NOTREACHED */
+ }
+
+ if (*p != '\n')
+ return -1;
+
+ if (!valid_addr(first_addr, min_addr) ||
+ (second_addr != -1 && !valid_addr(second_addr, first_addr)))
+ fatal("invalid address at line %ld: %s", p_input_line, buf);
+
+ cline = get_line(first_addr);
+
+ return fsm;
+}
+
+static void
+write_lines(char *filename)
+{
+ FILE *ofp;
+ char *p;
+ struct ed_line *line;
+ off_t linepos;
+
+ linepos = ftello(pfp);
+ ofp = fopen(filename, "w");
+ if (ofp == NULL)
+ pfatal("can't create %s", filename);
+
+ LIST_FOREACH(line, &head, entries) {
+ if (line->src == SRC_INP) {
+ p = ifetch(line->pos.lineno, 0);
+ /* Note: string is not NUL terminated. */
+ for (; *p != '\n'; p++)
+ if (line->subst != 0)
+ line->subst--;
+ else
+ putc(*p, ofp);
+ putc('\n', ofp);
+ } else if (line->src == SRC_PCH) {
+ fseeko(pfp, line->pos.seek, SEEK_SET);
+ if (pgetline(&buf, &bufsz, pfp) == -1)
+ fatal("unexpected end of file");
+ p = buf;
+ if (line->subst != 0)
+ for (; *p != '\0' && *p != '\n'; p++)
+ if (line->subst-- == 0)
+ break;
+ fputs(p, ofp);
+ if (strchr(p, '\n') == NULL)
+ putc('\n', ofp);
+ }
+ }
+ fclose(ofp);
+
+ /* restore patch file position to match p_input_line */
+ fseeko(pfp, linepos, SEEK_SET);
+}
+
+/* initialize list with input file */
+static void
+init_lines(void)
+{
+ struct ed_line *line;
+ LINENUM i;
+
+ LIST_INIT(&head);
+ for (i = input_lines; i > 0; i--) {
+ line = malloc(sizeof(*line));
+ if (line == NULL)
+ fatal("cannot allocate memory");
+ line->src = SRC_INP;
+ line->subst = 0;
+ line->pos.lineno = i;
+ LIST_INSERT_HEAD(&head, line, entries);
+ }
+ line_count = input_lines;
+}
+
+static void
+free_lines(void)
+{
+ struct ed_line *line;
+
+ while (!LIST_EMPTY(&head)) {
+ line = LIST_FIRST(&head);
+ LIST_REMOVE(line, entries);
+ free(line);
+ }
+}
+
+static struct ed_line *
+get_line(LINENUM lineno)
+{
+ struct ed_line *line;
+ LINENUM i;
+
+ if (lineno == 0)
+ return NULL;
+
+ i = 0;
+ LIST_FOREACH(line, &head, entries)
+ if (++i == lineno)
+ return line;
+
+ return NULL;
+}
+
+static struct ed_line *
+create_line(off_t seek)
+{
+ struct ed_line *line;
+
+ line = malloc(sizeof(*line));
+ if (line == NULL)
+ fatal("cannot allocate memory");
+ line->src = SRC_PCH;
+ line->subst = 0;
+ line->pos.seek = seek;
+
+ return line;
+}
+
+static int
+valid_addr(LINENUM lineno, LINENUM min)
+{
+ return lineno >= min && lineno <= line_count;
+}
diff --git a/usr.bin/patch/ed.h b/usr.bin/patch/ed.h
new file mode 100644
index 0000000..86bf9b9
--- /dev/null
+++ b/usr.bin/patch/ed.h
@@ -0,0 +1,19 @@
+/* $OpenBSD: ed.h,v 1.1 2015/10/16 07:33:47 tobias Exp $ */
+
+/*
+ * Copyright (c) 2015 Tobias Stoeckmann <tobias@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+void do_ed_script(void);
diff --git a/usr.bin/patch/inp.c b/usr.bin/patch/inp.c
new file mode 100644
index 0000000..3583814
--- /dev/null
+++ b/usr.bin/patch/inp.c
@@ -0,0 +1,430 @@
+/* $OpenBSD: inp.c,v 1.49 2019/06/28 13:35:02 deraadt 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/stat.h>
+#include <sys/mman.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdint.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 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 size_t lines_per_buf; /* how many lines per buffer */
+static size_t tibuflen; /* plan b buffer length */
+static size_t 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) {
+ free(i_ptr);
+ i_ptr = NULL;
+ if (i_womp != NULL) {
+ munmap(i_womp, i_size);
+ i_womp = NULL;
+ }
+ i_size = 0;
+ } 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_allocatedp)
+{
+ char **p;
+ size_t new_size;
+
+ new_size = *lines_allocatedp * 3 / 2;
+ p = reallocarray(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_allocatedp = 0;
+ return false;
+ }
+ *lines_allocatedp = 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;
+ 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) {
+ int fd;
+
+ 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);
+ if ((fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0666)) != -1)
+ close(fd);
+
+ statfailed = stat(filename, &filestat);
+ }
+ if (statfailed)
+ fatal("can't find %s\n", filename);
+ 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)) == -1)
+ pfatal("can't open file %s", filename);
+
+ if (i_size) {
+ 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;
+ }
+ } else {
+ i_womp = NULL;
+ }
+
+ 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] = "";
+ } else
+ last_line_missing_eol = false;
+
+ input_lines = iline - 1;
+
+ /* now check for revision, if any */
+
+ if (revision != NULL) {
+ if (i_womp == NULL || !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, len, maxlen = 1;
+ char *lbuf = NULL, *p;
+ bool found_revision = (revision == NULL);
+
+ using_plan_a = false;
+ if ((ifp = fopen(filename, "r")) == NULL)
+ pfatal("can't open file %s", filename);
+ (void) unlink(TMPINNAME);
+ if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) == -1)
+ pfatal("can't open file %s", TMPINNAME);
+ while ((p = fgetln(ifp, &len)) != NULL) {
+ if (p[len - 1] == '\n')
+ p[len - 1] = '\0';
+ else {
+ /* EOF without EOL, copy and add the NUL */
+ if ((lbuf = malloc(len + 1)) == NULL)
+ fatal("out of memory\n");
+ memcpy(lbuf, p, len);
+ lbuf[len] = '\0';
+ p = lbuf;
+
+ last_line_missing_eol = true;
+ len++;
+ }
+ if (revision != NULL && !found_revision && rev_in_string(p))
+ found_revision = true;
+ if (len > maxlen)
+ maxlen = len; /* find longest line */
+ }
+ free(lbuf);
+ if (ferror(ifp))
+ pfatal("can't read file %s", filename);
+
+ 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 */
+ tireclen = maxlen;
+ tibuflen = maxlen > BUFFERSIZE ? maxlen : BUFFERSIZE;
+ lines_per_buf = tibuflen / maxlen;
+ tibuf[0] = malloc(tibuflen + 1);
+ if (tibuf[0] == NULL)
+ fatal("out of memory\n");
+ tibuf[1] = malloc(tibuflen + 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], tibuflen) !=
+ (ssize_t) tibuflen)
+ 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], tibuflen) !=
+ (ssize_t) tibuflen)
+ 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)) == -1)
+ 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 *
+ tibuflen), SEEK_SET) == -1)
+ pfatal("cannot seek in the temporary input file");
+
+ if (read(tifd, tibuf[whichbuf], tibuflen)
+ != (ssize_t) tibuflen)
+ 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/usr.bin/patch/inp.h b/usr.bin/patch/inp.h
new file mode 100644
index 0000000..aa66208
--- /dev/null
+++ b/usr.bin/patch/inp.h
@@ -0,0 +1,31 @@
+/* $OpenBSD: inp.h,v 1.8 2003/08/15 08:00:51 otto Exp $ */
+
+/*
+ * patch - a program to apply diffs to original files
+ *
+ * Copyright 1986, Larry Wall
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following condition is met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this condition and the following disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * -C option added in 1998, original code by Marc Espie, based on FreeBSD
+ * behaviour
+ */
+
+void re_input(void);
+void scan_input(const char *);
+char *ifetch(LINENUM, int);
diff --git a/usr.bin/patch/mkpath.c b/usr.bin/patch/mkpath.c
new file mode 100644
index 0000000..63d53ec
--- /dev/null
+++ b/usr.bin/patch/mkpath.c
@@ -0,0 +1,77 @@
+/* $OpenBSD: mkpath.c,v 1.4 2014/05/20 01:25:23 guenther 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/types.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+
+#include "common.h"
+#include "util.h"
+
+/* 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)) {
+ warnc(ENOTDIR, "%s", path);
+ return (-1);
+ }
+
+ *slash = '/';
+ }
+
+ return (0);
+}
+
diff --git a/usr.bin/patch/patch.1 b/usr.bin/patch/patch.1
new file mode 100644
index 0000000..8d915b8
--- /dev/null
+++ b/usr.bin/patch/patch.1
@@ -0,0 +1,680 @@
+.\" $OpenBSD: patch.1,v 1.32 2018/06/22 15:37:15 zhuk 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 $Mdocdate: June 22 2018 $
+.Dt PATCH 1
+.Os
+.Sh NAME
+.Nm patch
+.Nd apply a diff file to an original
+.Sh SYNOPSIS
+.Nm patch
+.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 patch
+.Pf \*(Lt Ar patchfile
+.Sh DESCRIPTION
+.Nm
+will take a patch file containing any of the four forms of difference
+listing produced by the
+.Xr diff 1
+program and apply those differences to an original file,
+producing a patched version.
+If
+.Ar patchfile
+is omitted, or is a hyphen, the patch will be read from the standard input.
+.Pp
+.Nm
+will attempt to determine the type of the diff listing, unless overruled by a
+.Fl c ,
+.Fl e ,
+.Fl n ,
+or
+.Fl u
+option.
+.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 , Fl Fl dry-run
+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 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-2008
+conformance, specifically:
+.Bl -enum
+.It
+Backup files are not created unless the
+.Fl b
+option is specified.
+.It
+If unspecified, the file name used is the first of the old, new and
+index files that exists.
+.El
+.El
+.Ss Patch Application
+.Nm
+will try to skip any leading garbage, apply the diff,
+and then skip any trailing garbage.
+Thus you could feed an article or message containing a
+diff listing to
+.Nm patch ,
+and it should work.
+If the entire diff is indented by a consistent amount,
+this will be taken into account.
+.Pp
+With context diffs, and to a lesser extent with normal diffs,
+.Nm
+can detect when the line numbers mentioned in the patch are incorrect,
+and will attempt to find the correct place to apply each hunk of the patch.
+As a first guess, it takes the line number mentioned for the hunk, plus or
+minus any offset used in applying the previous hunk.
+If that is not the correct place,
+.Nm
+will scan both forwards and backwards for a set of lines matching the context
+given in the hunk.
+First
+.Nm
+looks for a place where all lines of the context match.
+If no such place is found, and it's a context diff, and the maximum fuzz factor
+is set to 1 or more, then another scan takes place ignoring the first and last
+line of context.
+If that fails, and the maximum fuzz factor is set to 2 or more,
+the first two and last two lines of context are ignored,
+and another scan is made.
+.Pq The default maximum fuzz factor is 2.
+.Pp
+If
+.Nm
+cannot find a place to install that hunk of the patch, it will put the hunk
+out to a reject file, which normally is the name of the output file plus
+.Qq .rej .
+(Note that the rejected hunk will come out in context diff form whether the
+input patch was a context diff or a normal diff.
+If the input was a normal diff, many of the contexts will simply be null.)
+The line numbers on the hunks in the reject file may be different than
+in the patch file: they reflect the approximate location patch thinks the
+failed hunks belong in the new file rather than the old one.
+.Pp
+As each hunk is completed, you will be told whether the hunk succeeded or
+failed, and which line (in the new file)
+.Nm
+thought the hunk should go on.
+If this is different from the line number specified in the diff,
+you will be told the offset.
+A single large offset MAY be an indication that a hunk was installed in the
+wrong place.
+You will also be told if a fuzz factor was used to make the match, in which
+case you should also be slightly suspicious.
+.Ss Filename Determination
+If no original file is specified on the command line,
+.Nm
+will try to figure out from the leading garbage what the name of the file
+to edit is.
+When checking a prospective file name, pathname components are stripped
+as specified by the
+.Fl p
+option and the file's existence and writability are checked relative
+to the current working directory (or the directory specified by the
+.Fl d
+option).
+.Pp
+If the diff is a context or unified diff,
+.Nm
+is able to determine the old and new file names from the diff header.
+For context diffs, the
+.Dq old
+file is specified in the line beginning with
+.Qq ***
+and the
+.Dq new
+file is specified in the line beginning with
+.Qq --- .
+For a unified diff, the
+.Dq old
+file is specified in the line beginning with
+.Qq ---
+and the
+.Dq new
+file is specified in the line beginning with
+.Qq +++ .
+If there is an
+.Qq Index:
+line in the leading garbage (regardless of the diff type),
+.Nm
+will use the file name from that line as the
+.Dq index
+file.
+.Pp
+.Nm
+will choose the file name by performing the following steps, with the first
+match used:
+.Bl -enum
+.It
+If
+.Nm
+is operating in strict
+.St -p1003.1-2008
+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 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 EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It 0
+Successful completion.
+.It 1
+One or more lines were written to a reject file.
+.It \*(Gt1
+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 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.
+.Sh SEE ALSO
+.Xr diff 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification,
+except as detailed above for the
+.Fl -posix
+option.
+.Pp
+The flags
+.Op Fl BCEFfstVvxz
+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/usr.bin/patch/patch.c b/usr.bin/patch/patch.c
new file mode 100644
index 0000000..088ea1f
--- /dev/null
+++ b/usr.bin/patch/patch.c
@@ -0,0 +1,1064 @@
+/* $OpenBSD: patch.c,v 1.69 2019/12/02 22:17:32 jca 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>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <ctype.h>
+#include <getopt.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "common.h"
+#include "util.h"
+#include "pch.h"
+#include "inp.h"
+#include "backupfile.h"
+#include "ed.h"
+
+mode_t filemode = 0644;
+
+char *buf; /* general purpose buffer */
+size_t bufsz; /* general purpose buffer size */
+
+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 void spew_output(void);
+static void dump_line(LINENUM, bool);
+static bool patch_match(LINENUM, LINENUM, LINENUM);
+static bool similar(const char *, const char *, int);
+static __dead 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[PATH_MAX];
+
+/* 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;
+ bool patch_seen;
+ LINENUM where = 0, newwhere, fuzz, mymaxfuzz;
+ const char *tmpdir;
+ char *v;
+
+ if (pledge("stdio rpath wpath cpath tmppath fattr", NULL) == -1) {
+ perror("pledge");
+ my_exit(2);
+ }
+
+ bufsz = INITLINELEN;
+ if ((buf = malloc(bufsz)) == NULL)
+ pfatal("allocating input buffer");
+ buf[0] = '\0';
+
+ setvbuf(stdout, NULL, _IOLBF, 0);
+ setvbuf(stderr, NULL, _IOLBF, 0);
+ 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)) == -1)
+ pfatal("can't create %s", TMPOUTNAME);
+ close(fd);
+
+ if (asprintf(&TMPINNAME, "%.*s/patchiXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPINNAME)) == -1)
+ pfatal("can't create %s", TMPINNAME);
+ close(fd);
+
+ if (asprintf(&TMPREJNAME, "%.*s/patchrXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPREJNAME)) == -1)
+ pfatal("can't create %s", TMPREJNAME);
+ close(fd);
+
+ if (asprintf(&TMPPATNAME, "%.*s/patchpXXXXXXXXXX", i, tmpdir) == -1)
+ fatal("cannot allocate memory");
+ if ((fd = mkstemp(TMPPATNAME)) == -1)
+ 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);
+
+ patch_seen = false;
+ for (open_patch_file(filearg[1]); there_is_another_patch();
+ reinitialize_almost_everything()) {
+ /* for each patch in patch file */
+
+ patch_seen = true;
+
+ warn_on_invalid_line = true;
+
+ if (outname == NULL)
+ outname = xstrdup(filearg[0]);
+
+ /* 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]);
+
+ /* for ed script just up and do it and exit */
+ if (diff_type == ED_DIFF) {
+ do_ed_script();
+ continue;
+ }
+
+ /* from here on, open no standard i/o files, because malloc */
+ /* might misfire and we can't catch it easily */
+
+ /* apply each hunk of patch */
+ hunk = 0;
+ failed = 0;
+ out_of_mem = false;
+ while (another_hunk()) {
+ hunk++;
+ fuzz = 0;
+ mymaxfuzz = pch_context();
+ if (maxfuzz < mymaxfuzz)
+ mymaxfuzz = maxfuzz;
+ if (!skip_rest_of_patch) {
+ do {
+ where = locate_hunk(fuzz);
+ if (hunk == 1 && where == 0 && !force) {
+ /* dwim for reversed patch? */
+ if (!pch_swap()) {
+ if (fuzz == 0)
+ say("Not enough memory to try swapped hunk! Assuming unswapped.\n");
+ continue;
+ }
+ reverse = !reverse;
+ /* try again */
+ where = locate_hunk(fuzz);
+ if (where == 0) {
+ /* didn't find it swapped */
+ if (!pch_swap())
+ /* put it back to normal */
+ fatal("lost hunk on alloc error!\n");
+ reverse = !reverse;
+ } else if (noreverse) {
+ if (!pch_swap())
+ /* put it back to normal */
+ fatal("lost hunk on alloc error!\n");
+ reverse = !reverse;
+ say("Ignoring previously applied (or reversed) patch.\n");
+ skip_rest_of_patch = true;
+ } else if (batch) {
+ if (verbose)
+ say("%seversed (or previously applied) patch detected! %s -R.",
+ reverse ? "R" : "Unr",
+ reverse ? "Assuming" : "Ignoring");
+ } else {
+ ask("%seversed (or previously applied) patch detected! %s -R? [y] ",
+ reverse ? "R" : "Unr",
+ reverse ? "Assume" : "Ignore");
+ if (*buf == 'n') {
+ ask("Apply anyway? [n] ");
+ if (*buf != 'y')
+ skip_rest_of_patch = true;
+ where = 0;
+ reverse = !reverse;
+ if (!pch_swap())
+ /* put it back to normal */
+ fatal("lost hunk on alloc error!\n");
+ }
+ }
+ }
+ } while (!skip_rest_of_patch && where == 0 &&
+ ++fuzz <= mymaxfuzz);
+
+ if (skip_rest_of_patch) { /* just got decided */
+ fclose(ofp);
+ ofp = NULL;
+ }
+ }
+ newwhere = pch_newfirst() + last_offset;
+ if (skip_rest_of_patch) {
+ abort_hunk();
+ failed++;
+ if (verbose)
+ say("Hunk #%d ignored at %ld.\n",
+ hunk, newwhere);
+ } else if (where == 0) {
+ abort_hunk();
+ failed++;
+ if (verbose)
+ say("Hunk #%d failed at %ld.\n",
+ hunk, newwhere);
+ } else {
+ apply_hunk(where);
+ if (verbose) {
+ say("Hunk #%d succeeded at %ld",
+ hunk, newwhere);
+ if (fuzz != 0)
+ say(" with fuzz %ld", fuzz);
+ if (last_offset)
+ say(" (offset %ld line%s)",
+ last_offset,
+ last_offset == 1L ? "" : "s");
+ say(".\n");
+ }
+ }
+ }
+
+ if (out_of_mem && using_plan_a) {
+ Argc = Argc_last;
+ Argv = Argv_last;
+ say("\n\nRan out of memory using Plan A--trying again...\n\n");
+ if (ofp)
+ fclose(ofp);
+ ofp = NULL;
+ if (rejfp)
+ fclose(rejfp);
+ rejfp = NULL;
+ continue;
+ }
+ if (hunk == 0)
+ fatal("Internal error: hunk should not be 0\n");
+
+ /* finish spewing out the new file */
+ if (!skip_rest_of_patch)
+ spew_output();
+
+ /* and put the output where desired */
+ ignore_signals();
+ if (!skip_rest_of_patch) {
+ struct stat statbuf;
+ char *realout = outname;
+
+ if (!check_only) {
+ if (move_file(TMPOUTNAME, outname) < 0) {
+ toutkeep = true;
+ realout = TMPOUTNAME;
+ chmod(TMPOUTNAME, filemode);
+ } else
+ chmod(outname, filemode);
+
+ if (remove_empty_files &&
+ stat(realout, &statbuf) == 0 &&
+ statbuf.st_size == 0) {
+ if (verbose)
+ say("Removing %s (empty after patching).\n",
+ realout);
+ unlink(realout);
+ }
+ }
+ }
+ fclose(rejfp);
+ rejfp = NULL;
+ if (failed) {
+ error = 1;
+ if (*rejname == '\0') {
+ if (strlcpy(rejname, outname,
+ sizeof(rejname)) >= sizeof(rejname))
+ fatal("filename %s is too long\n", outname);
+ if (strlcat(rejname, REJEXT,
+ sizeof(rejname)) >= sizeof(rejname))
+ fatal("filename %s is too long\n", outname);
+ }
+ if (!check_only)
+ say("%d out of %d hunks %s--saving rejects to %s\n",
+ failed, hunk, skip_rest_of_patch ? "ignored" : "failed", rejname);
+ else
+ say("%d out of %d hunks %s\n",
+ failed, hunk, skip_rest_of_patch ? "ignored" : "failed");
+ if (!check_only && move_file(TMPREJNAME, rejname) < 0)
+ trejkeep = true;
+ }
+ set_signals(1);
+ }
+
+ if (!patch_seen)
+ error = 2;
+
+ 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'},
+ {"dry-run", no_argument, 0, 'C'},
+ {"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 = xstrdup(optarg);
+ break;
+ case 'B':
+ origprae = xstrdup(optarg);
+ break;
+ case 'c':
+ diff_type = CONTEXT_DIFF;
+ break;
+ case 'C':
+ check_only = true;
+ break;
+ case 'd':
+ if (chdir(optarg) == -1)
+ 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] = xstrdup(optarg);
+ break;
+ case 'l':
+ canonicalize = true;
+ break;
+ case 'n':
+ diff_type = NORMAL_DIFF;
+ break;
+ case 'N':
+ noreverse = true;
+ break;
+ case 'o':
+ outname = xstrdup(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] = xstrdup(*Argv++);
+ Argc--;
+ while (Argc > 0) {
+ if (++filec == MAXFILEC)
+ fatal("too many file arguments\n");
+ filearg[filec] = xstrdup(*Argv++);
+ Argc--;
+ }
+ }
+
+ if (getenv("POSIXLY_CORRECT") != NULL)
+ posix = 1;
+}
+
+static __dead 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(2);
+}
+
+/*
+ * 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 void
+spew_output(void)
+{
+#ifdef DEBUGGING
+ if (debug & 256)
+ say("il=%ld lfl=%ld\n", input_lines, last_frozen_line);
+#endif
+ if (input_lines)
+ copy_till(input_lines, true); /* dump remainder of file */
+ fclose(ofp);
+ ofp = NULL;
+}
+
+/*
+ * Copy one line from input to output.
+ */
+static void
+dump_line(LINENUM line, bool write_newline)
+{
+ char *s;
+
+ s = ifetch(line, 0);
+ if (s == NULL)
+ return;
+ /* Note: string is not NUL terminated. */
+ for (; *s != '\n'; s++)
+ putc(*s, ofp);
+ if (write_newline)
+ putc('\n', ofp);
+}
+
+/*
+ * Does the patch pattern match at line base+offset?
+ */
+static bool
+patch_match(LINENUM base, LINENUM offset, LINENUM fuzz)
+{
+ LINENUM pline = 1 + fuzz;
+ LINENUM iline;
+ LINENUM pat_lines = pch_ptrn_lines() - fuzz;
+ const char *ilineptr;
+ const char *plineptr;
+ short plinelen;
+
+ for (iline = base + offset + fuzz; pline <= pat_lines; pline++, iline++) {
+ ilineptr = ifetch(iline, offset >= 0);
+ if (ilineptr == NULL)
+ return false;
+ plineptr = pfetch(pline);
+ plinelen = pch_line_len(pline);
+ if (canonicalize) {
+ if (!similar(ilineptr, plineptr, plinelen))
+ return false;
+ } else if (strnNE(ilineptr, plineptr, plinelen))
+ return false;
+ if (iline == input_lines) {
+ /*
+ * We are looking at the last line of the file.
+ * If the file has no eol, the patch line should
+ * not have one either and vice-versa. Note that
+ * plinelen > 0.
+ */
+ if (last_line_missing_eol) {
+ if (plineptr[plinelen - 1] == '\n')
+ return false;
+ } else {
+ if (plineptr[plinelen - 1] != '\n')
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/*
+ * Do two lines match with canonicalized white space?
+ */
+static bool
+similar(const char *a, const char *b, int len)
+{
+ while (len) {
+ if (isspace((unsigned char)*b)) { /* whitespace (or \n) to match? */
+ if (!isspace((unsigned char)*a))
+ return false; /* no corresponding whitespace */
+ 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/usr.bin/patch/pch.c b/usr.bin/patch/pch.c
new file mode 100644
index 0000000..89db753
--- /dev/null
+++ b/usr.bin/patch/pch.c
@@ -0,0 +1,1522 @@
+/* $OpenBSD: pch.c,v 1.62 2019/12/02 22:23:19 jca 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 <sys/stat.h>
+
+#include <ctype.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "util.h"
+#include "pch.h"
+
+/* Patch (diff listing) abstract type. */
+
+FILE *pfp = NULL; /* patch file pointer */
+LINENUM p_input_line = 0; /* current line # from patch file */
+
+static off_t 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 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 off_t p_base; /* where to intuit this time */
+static LINENUM p_bline; /* line # of p_base */
+static off_t 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 char *bestguess = NULL; /* guess at correct filename */
+
+static void grow_hunkmax(void);
+static int intuit_diff_type(void);
+static void skip_to(off_t, LINENUM);
+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 (getline(&buf, &bufsz, stdin) != -1)
+ fputs(buf, pfp);
+ fclose(pfp);
+ filename = TMPPATNAME;
+ }
+ pfp = fopen(filename, "r");
+ if (pfp == NULL)
+ pfatal("patch file %s not found", filename);
+ if (fstat(fileno(pfp), &filestat))
+ pfatal("can't stat %s", filename);
+ p_filesize = filestat.st_size;
+ next_intuit_at(0, 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 = reallocarray(p_line, new_hunkmax, sizeof(char *));
+ if (new_p_line == NULL)
+ free(p_line);
+
+ new_p_len = reallocarray(p_len, new_hunkmax, sizeof(short));
+ if (new_p_len == NULL)
+ free(p_len);
+
+ new_p_char = recallocarray(p_char, hunkmax, 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 != 0 && 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 != 0) {
+ 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 == 0 ? "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] = xstrdup(bestguess);
+ skip_rest_of_patch = true;
+ return true;
+ }
+ ask("File to patch: ");
+ if (*buf != '\n') {
+ free(bestguess);
+ bestguess = xstrdup(buf);
+ filearg[0] = fetchname(buf, &exists, 0);
+ }
+ if (!exists) {
+ int def_skip = *bestguess == '\0';
+ ask("No file found--skip this patch? [%c] ",
+ def_skip ? 'y' : 'n');
+ if (*buf == 'n' || (!def_skip && *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)
+{
+ off_t this_line = 0, previous_line;
+ off_t 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];
+ int piece_of_git = 0;
+
+ memset(names, 0, sizeof(names));
+ ok_to_create_file = false;
+ fseeko(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 = ftello(pfp);
+ indent = 0;
+ p_input_line++;
+ if (getline(&buf, &bufsz, pfp) == -1) {
+ if (first_command_line >= 0) {
+ /* 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 < 0 && 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)) {
+ size_t off = 4;
+ if (piece_of_git && strippath == 957 &&
+ strnEQ(s, "--- a/", 6))
+ off = 6;
+ names[NEW_FILE].path = fetchname(s + off,
+ &names[NEW_FILE].exists, strippath);
+ } else if (strnEQ(s, "+++ ", 4)) {
+ /* pretend it is the old name */
+ size_t off = 4;
+ if (piece_of_git && strippath == 957 &&
+ strnEQ(s, "+++ b/", 6))
+ off = 6;
+ names[OLD_FILE].path = fetchname(s + off,
+ &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 = xstrdup(t);
+ for (t = revision;
+ *t && !isspace((unsigned char)*t); t++)
+ ;
+ *t = '\0';
+ if (*revision == '\0') {
+ free(revision);
+ revision = NULL;
+ }
+ } else if (strnEQ(s, "diff --git a/", 13))
+ piece_of_git = 1;
+ if ((!diff_type || diff_type == ED_DIFF) &&
+ first_command_line >= 0 &&
+ 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 (strtolinenum(s + 4, &s) == 0)
+ ok_to_create_file = true;
+ /*
+ * If this is a new context diff the character just
+ * at the end of the line is a '*'.
+ */
+ while (*s && *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 = xstrdup(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.
+ */
+void
+next_intuit_at(off_t file_pos, LINENUM file_line)
+{
+ p_base = file_pos;
+ p_bline = file_line;
+}
+
+/*
+ * Basically a verbose fseeko() to the actual diff listing.
+ */
+static void
+skip_to(off_t file_pos, LINENUM file_line)
+{
+ int ret;
+
+ if (p_base > file_pos)
+ fatal("Internal error: seek %lld>%lld\n",
+ (long long)p_base, (long long)file_pos);
+ if (verbose && p_base < file_pos) {
+ fseeko(pfp, p_base, SEEK_SET);
+ say("The text leading up to this was:\n--------------------------\n");
+ while (ftello(pfp) < file_pos) {
+ ret = getline(&buf, &bufsz, pfp);
+ if (ret == -1)
+ fatal("Unexpected end of file\n");
+ say("|%s", buf);
+ }
+ say("--------------------------\n");
+ } else
+ fseeko(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)
+ fseeko(pfp, -1, SEEK_CUR);
+
+ return false;
+}
+
+/*
+ * True if there is more of the current diff listing to process.
+ */
+bool
+another_hunk(void)
+{
+ off_t 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 */
+ off_t 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;
+ int context = 0;
+ int ret;
+
+ 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 = ftello(pfp);
+ repl_beginning = 0;
+ fillcnt = 0;
+ fillsrc = 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 = pgetline(&buf, &bufsz, pfp);
+ p_input_line++;
+ if (ret == -1 || 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 = ftello(pfp);
+ ret = pgetline(&buf, &bufsz, pfp);
+ p_input_line++;
+ if (ret == -1) {
+ if (p_max - p_end < 4) {
+ /* assume blank lines got chopped */
+ strlcpy(buf, " \n", bufsz);
+ } 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 = strtolinenum(s, &s);
+ if (*s == ',') {
+ for (; *s && !isdigit((unsigned char)*s); s++)
+ ;
+ if (!*s)
+ malformed();
+ p_ptrn_lines = strtolinenum(s, &s) - p_first + 1;
+ if (p_ptrn_lines < 0)
+ malformed();
+ } else if (p_first)
+ p_ptrn_lines = 1;
+ else {
+ p_ptrn_lines = 0;
+ p_first = 1;
+ }
+ if (p_first >= LINENUM_MAX - p_ptrn_lines ||
+ p_ptrn_lines >= LINENUM_MAX - 6)
+ malformed();
+
+ /* 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 = ftello(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 = strtolinenum(s, &s);
+ if (*s == ',') {
+ for (; *s && !isdigit((unsigned char)*s); s++)
+ ;
+ if (!*s)
+ malformed();
+ p_repl_lines = strtolinenum(s, &s) -
+ p_newfirst + 1;
+ if (p_repl_lines < 0)
+ malformed();
+ } else if (p_newfirst)
+ p_repl_lines = 1;
+ else {
+ p_repl_lines = 0;
+ p_newfirst = 1;
+ }
+ if (p_newfirst >= LINENUM_MAX - p_repl_lines ||
+ p_repl_lines >= LINENUM_MAX - p_end)
+ malformed();
+ 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", bufsz - 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]);
+ fseeko(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) {
+ off_t line_beginning = ftello(pfp); /* file pos of the current line */
+ LINENUM fillsrc; /* index of old lines */
+ LINENUM filldst; /* index of new lines */
+ char ch;
+
+ ret = pgetline(&buf, &bufsz, pfp);
+ p_input_line++;
+ if (ret == -1 || strnNE(buf, "@@ -", 4)) {
+ next_intuit_at(line_beginning, p_input_line);
+ return false;
+ }
+ s = buf + 4;
+ if (!*s)
+ malformed();
+ p_first = strtolinenum(s, &s);
+ if (*s == ',') {
+ p_ptrn_lines = strtolinenum(s + 1, &s);
+ } else
+ p_ptrn_lines = 1;
+ if (*s == ' ')
+ s++;
+ if (*s != '+' || !*++s)
+ malformed();
+ p_newfirst = strtolinenum(s, &s);
+ if (*s == ',') {
+ p_repl_lines = strtolinenum(s + 1, &s);
+ } else
+ p_repl_lines = 1;
+ if (*s == ' ')
+ s++;
+ if (*s != '@')
+ malformed();
+ if (p_first >= LINENUM_MAX - p_ptrn_lines ||
+ p_newfirst > LINENUM_MAX - p_repl_lines ||
+ p_ptrn_lines >= LINENUM_MAX - p_repl_lines - 1)
+ malformed();
+ if (!p_ptrn_lines)
+ p_first++; /* do append rather than insert */
+ p_max = p_ptrn_lines + p_repl_lines + 1;
+ while (p_max >= hunkmax)
+ grow_hunkmax();
+ fillsrc = 1;
+ filldst = fillsrc + p_ptrn_lines;
+ p_end = filldst + p_repl_lines;
+ snprintf(buf, bufsz, "*** %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, bufsz, "--- %ld,%ld ----\n", p_newfirst,
+ p_newfirst + p_repl_lines - 1);
+ p_line[filldst] = savestr(buf);
+ if (out_of_mem) {
+ p_end = 0;
+ return false;
+ }
+ p_char[filldst++] = '=';
+ p_context = 100;
+ context = 0;
+ p_hunk_beg = p_input_line + 1;
+ while (fillsrc <= p_ptrn_lines || filldst <= p_end) {
+ line_beginning = ftello(pfp);
+ ret = pgetline(&buf, &bufsz, pfp);
+ p_input_line++;
+ if (ret == -1) {
+ if (p_max - filldst < 3) {
+ /* assume blank lines got chopped */
+ strlcpy(buf, " \n", bufsz);
+ } else {
+ fatal("unexpected end of file in patch\n");
+ }
+ }
+ if (*buf == '\t' || *buf == '\n') {
+ ch = ' '; /* assume the space got eaten */
+ s = savestr(buf);
+ } else {
+ ch = *buf;
+ s = savestr(buf + 1);
+ }
+ if (out_of_mem) {
+ while (--filldst > p_ptrn_lines)
+ free(p_line[filldst]);
+ p_end = fillsrc - 1;
+ return false;
+ }
+ switch (ch) {
+ case '-':
+ if (fillsrc > p_ptrn_lines) {
+ free(s);
+ p_end = filldst - 1;
+ malformed();
+ }
+ p_char[fillsrc] = ch;
+ p_line[fillsrc] = s;
+ p_len[fillsrc++] = strlen(s);
+ if (fillsrc > p_ptrn_lines) {
+ if (remove_special_line()) {
+ p_len[fillsrc - 1] -= 1;
+ s[p_len[fillsrc - 1]] = 0;
+ }
+ }
+ break;
+ case '=':
+ ch = ' ';
+ /* FALL THROUGH */
+ case ' ':
+ if (fillsrc > p_ptrn_lines) {
+ free(s);
+ while (--filldst > p_ptrn_lines)
+ free(p_line[filldst]);
+ p_end = fillsrc - 1;
+ malformed();
+ }
+ context++;
+ p_char[fillsrc] = ch;
+ p_line[fillsrc] = s;
+ p_len[fillsrc++] = strlen(s);
+ s = savestr(s);
+ if (out_of_mem) {
+ while (--filldst > p_ptrn_lines)
+ free(p_line[filldst]);
+ p_end = fillsrc - 1;
+ return false;
+ }
+ if (fillsrc > p_ptrn_lines) {
+ if (remove_special_line()) {
+ p_len[fillsrc - 1] -= 1;
+ s[p_len[fillsrc - 1]] = 0;
+ }
+ }
+ /* FALL THROUGH */
+ case '+':
+ if (filldst > p_end) {
+ free(s);
+ while (--filldst > p_ptrn_lines)
+ free(p_line[filldst]);
+ p_end = fillsrc - 1;
+ malformed();
+ }
+ p_char[filldst] = ch;
+ p_line[filldst] = s;
+ p_len[filldst++] = strlen(s);
+ if (fillsrc > p_ptrn_lines) {
+ if (remove_special_line()) {
+ p_len[filldst - 1] -= 1;
+ s[p_len[filldst - 1]] = 0;
+ }
+ }
+ break;
+ default:
+ p_end = filldst;
+ malformed();
+ }
+ if (ch != ' ' && context > 0) {
+ if (context < p_context)
+ p_context = context;
+ context = -1000;
+ }
+ } /* while */
+ } else { /* normal diff--fake it up */
+ char hunk_type;
+ int i;
+ LINENUM min, max;
+ off_t line_beginning = ftello(pfp);
+
+ p_context = 0;
+ ret = pgetline(&buf, &bufsz, pfp);
+ p_input_line++;
+ if (ret == -1 || !isdigit((unsigned char)*buf)) {
+ next_intuit_at(line_beginning, p_input_line);
+ return false;
+ }
+ p_first = strtolinenum(buf, &s);
+ if (*s == ',') {
+ p_ptrn_lines = strtolinenum(s + 1, &s) - p_first + 1;
+ if (p_ptrn_lines < 0)
+ malformed();
+ } else
+ p_ptrn_lines = (*s != 'a');
+ if (p_first >= LINENUM_MAX - p_ptrn_lines)
+ malformed();
+ hunk_type = *s;
+ if (hunk_type == 'a')
+ p_first++; /* do append rather than insert */
+ min = strtolinenum(s + 1, &s);
+ if (*s == ',')
+ max = strtolinenum(s + 1, &s);
+ else
+ max = min;
+ if (min < 0 || min > max || max - min == LINENUM_MAX)
+ malformed();
+ if (hunk_type == 'd')
+ min++;
+ p_newfirst = min;
+ p_repl_lines = max - min + 1;
+ if (p_newfirst > LINENUM_MAX - p_repl_lines ||
+ p_ptrn_lines >= LINENUM_MAX - p_repl_lines - 1)
+ malformed();
+ p_end = p_ptrn_lines + p_repl_lines + 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();
+ snprintf(buf, bufsz, "*** %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 = pgetline(&buf, &bufsz, pfp);
+ p_input_line++;
+ if (ret == -1)
+ 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 = pgetline(&buf, &bufsz, pfp);
+ p_input_line++;
+ if (ret == -1)
+ 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, bufsz, "--- %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 = pgetline(&buf, &bufsz, pfp);
+ p_input_line++;
+ if (ret == -1)
+ 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.
+ */
+int
+pgetline(char **bf, size_t *sz, FILE *fp)
+{
+ char *s;
+ int indent = 0;
+ int ret;
+
+ ret = getline(bf, sz, fp);
+
+ if (p_indent && ret != -1) {
+ 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, bufsz) >= bufsz)
+ fatal("buffer too small in pgetline()\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;
+}
+
+/*
+ * 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, 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 ? xstrdup(path) : NULL;
+}
+
+static char *
+compare_names(const struct file_name *names, bool assume_exists)
+{
+ size_t min_components, min_baselen, min_len, tmp;
+ char *best = NULL;
+ char *path;
+ 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--) {
+ path = names[i].path;
+ if (path == NULL || (!names[i].exists && !assume_exists))
+ continue;
+ if ((tmp = num_components(path)) > min_components)
+ continue;
+ if (tmp < min_components) {
+ min_components = tmp;
+ best = path;
+ }
+ if ((tmp = strlen(basename(path))) > min_baselen)
+ continue;
+ if (tmp < min_baselen) {
+ min_baselen = tmp;
+ best = path;
+ }
+ if ((tmp = strlen(path)) > min_len)
+ continue;
+ min_len = tmp;
+ best = path;
+ }
+ return best;
+}
+
+/*
+ * 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)
+{
+ char *best;
+
+ best = compare_names(names, assume_exists);
+
+ /* No match? Check to see if the diff could be creating a new file. */
+ if (best == NULL && ok_to_create_file)
+ best = names[NEW_FILE].path;
+
+ return best ? xstrdup(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;
+}
+
+/*
+ * Convert number at NPTR into LINENUM and save address of first
+ * character that is not a digit in ENDPTR. If conversion is not
+ * possible, call fatal.
+ */
+LINENUM
+strtolinenum(char *nptr, char **endptr)
+{
+ LINENUM rv;
+ char c;
+ char *p;
+ const char *errstr;
+
+ for (p = nptr; isdigit((unsigned char)*p); p++)
+ ;
+
+ if (p == nptr)
+ malformed();
+
+ c = *p;
+ *p = '\0';
+
+ rv = strtonum(nptr, 0, LINENUM_MAX, &errstr);
+ if (errstr != NULL)
+ fatal("invalid line number at line %ld: `%s' is %s\n",
+ p_input_line, nptr, errstr);
+
+ *p = c;
+ *endptr = p;
+
+ return rv;
+}
diff --git a/usr.bin/patch/pch.h b/usr.bin/patch/pch.h
new file mode 100644
index 0000000..3367056
--- /dev/null
+++ b/usr.bin/patch/pch.h
@@ -0,0 +1,60 @@
+/* $OpenBSD: pch.h,v 1.13 2019/12/11 20:10:17 jca 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;
+};
+
+extern FILE *pfp;
+extern LINENUM p_input_line;
+
+void re_patch(void);
+void open_patch_file(const char *);
+void set_hunkmax(void);
+bool there_is_another_patch(void);
+void next_intuit_at(off_t, LINENUM);
+bool another_hunk(void);
+int pgetline(char **, size_t *, FILE *);
+bool pch_swap(void);
+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);
+short pch_line_len(LINENUM);
+char pch_char(LINENUM);
+char *pfetch(LINENUM);
+LINENUM pch_hunk_beg(void);
+LINENUM strtolinenum(char *, char **);
diff --git a/usr.bin/patch/util.c b/usr.bin/patch/util.c
new file mode 100644
index 0000000..f079b51
--- /dev/null
+++ b/usr.bin/patch/util.c
@@ -0,0 +1,425 @@
+/* $OpenBSD: util.c,v 1.45 2019/12/02 22:17:32 jca 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/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.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"
+
+/* 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 == -1)
+ pfatal("internal error, can't reopen %s", from);
+ while ((i = read(fromfd, buf, bufsz)) > 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) == -1) {
+ 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[PATH_MAX], *s, *simplename;
+ dev_t orig_device;
+ ino_t orig_inode;
+
+ if (backup_type == none || stat(orig, &filestat) != 0)
+ return 0; /* nothing to do */
+ orig_device = filestat.st_dev;
+ orig_inode = filestat.st_ino;
+
+ if (origprae) {
+ if (strlcpy(bakname, origprae, sizeof(bakname)) >= sizeof(bakname) ||
+ strlcat(bakname, orig, sizeof(bakname)) >= sizeof(bakname))
+ fatal("filename %s too long for buffer\n", origprae);
+ } else {
+ if ((s = find_backup_file_name(orig)) == NULL)
+ fatal("out of memory\n");
+ if (strlcpy(bakname, s, sizeof(bakname)) >= sizeof(bakname))
+ fatal("filename %s too long for buffer\n", s);
+ free(s);
+ }
+
+ if ((simplename = strrchr(bakname, '/')) != NULL)
+ simplename = simplename + 1;
+ else
+ simplename = bakname;
+
+ /*
+ * Find a backup name that is not the same file. Change the
+ * first lowercase char into uppercase; if that isn't
+ * sufficient, chop off the first char and try again.
+ */
+ while (stat(bakname, &filestat) == 0 &&
+ orig_device == filestat.st_dev && orig_inode == filestat.st_ino) {
+ /* Skip initial non-lowercase chars. */
+ for (s = simplename; *s && !islower((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) == -1) {
+ 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 == -1)
+ return -1;
+ fromfd = open(from, O_RDONLY, 0);
+ if (fromfd == -1)
+ pfatal("internal error, can't reopen %s", from);
+ while ((i = read(fromfd, buf, bufsz)) > 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;
+}
+
+/*
+ * Allocate a unique area for a string. Call fatal if out of memory.
+ */
+char *
+xstrdup(const char *s)
+{
+ char *rv;
+
+ if (!s)
+ s = "Oops";
+ rv = strdup(s);
+ if (rv == NULL)
+ fatal("out of memory\n");
+ return rv;
+}
+
+/*
+ * Vanilla terminal output (buffered).
+ */
+void
+say(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ fflush(stdout);
+}
+
+/*
+ * 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;
+ 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, bufsz)) > 0 &&
+ buf[nr - 1] == '\n')
+ buf[nr - 1] = '\0';
+ }
+ if (ttyfd == -1 || 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_sigexit;
+ intval = signal(SIGINT, SIG_IGN);
+ if (intval != SIG_IGN)
+ intval = my_sigexit;
+ }
+ 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) {
+ free(tmpbuf);
+ 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;
+}
+
+void
+version(void)
+{
+ fprintf(stderr, "Patch version 2.0-12u8-OpenBSD\n");
+ my_exit(EXIT_SUCCESS);
+}
+
+void
+my_cleanup(void)
+{
+ unlink(TMPINNAME);
+ if (!toutkeep)
+ unlink(TMPOUTNAME);
+ if (!trejkeep)
+ unlink(TMPREJNAME);
+ unlink(TMPPATNAME);
+}
+
+/*
+ * Exit with cleanup.
+ */
+void
+my_exit(int status)
+{
+ my_cleanup();
+ exit(status);
+}
+
+/*
+ * Exit with cleanup, from a signal handler.
+ */
+void
+my_sigexit(int signo)
+{
+ my_cleanup();
+ _exit(2);
+}
diff --git a/usr.bin/patch/util.h b/usr.bin/patch/util.h
new file mode 100644
index 0000000..a21929b
--- /dev/null
+++ b/usr.bin/patch/util.h
@@ -0,0 +1,51 @@
+/* $OpenBSD: util.h,v 1.18 2019/08/17 14:25:06 deraadt 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);
+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 *);
+char *xstrdup(const char *);
+void set_signals(int);
+void ignore_signals(void);
+void makedirs(const char *, bool);
+void version(void);
+void my_exit(int) __attribute__((noreturn));
+void my_sigexit(int) __attribute__((noreturn));
+
+/* in mkpath.c */
+extern int mkpath(char *);