Currently, patch calls /bin/ed to support ed-formatted diffs, which
means that patch needs to pledge proc and exec. As discussed, it would
be a huge benefit if we can remove that.
Our patch implementation and GNU patch are already very restrictive in
which patch lines are sent to ed, so don't expect a full-blown ed here:
- Address ranges must be supplied in absolute line numbers. No search
regular expressions, no relative positioning etc.
- Allowed commands are a, c, d, i, and s.
- s only works for one address, not a range.
- s only works with /\.\././ (want to change this to /.//)
- No support for binary files, because our patch lacks that in general
If you ever happened to work with ed-formatted diffs, no news so far.
What's new is this:
- Invalid address ranges lead to fatal instead of silently accepting
them. This behaviour is in sync with unified diff handling now. If
garbage at the end of a diff file starts with a number, it might be
affected.
- No ed output on your console, because... there is no ed running. :P
- The ed code also uses plan A or plan B now. The code benefits from
this because it renders the needed scratch file of /bin/ed obsolete.
I decided to store the ed code in its own file to reduce complexity
in pch.c. If ed-diffs ever become obsolete, simply remove ed.{c,h}.
So... anyone with ed-diffs out there? This diff wants some tests and
reviews.
Tobias
PS: No, I won't send this in ed-format.
PPS: Yes, I tried and it applies just fine after "touch ed.{c,h}". ;)
Index: Makefile
===================================================================
RCS file: /cvs/src/usr.bin/patch/Makefile,v
retrieving revision 1.4
diff -u -p -u -p -r1.4 Makefile
--- Makefile 16 May 2005 15:22:46 -0000 1.4
+++ Makefile 11 Oct 2015 09:43:59 -0000
@@ -1,6 +1,6 @@
# $OpenBSD: Makefile,v 1.4 2005/05/16 15:22:46 espie Exp $
PROG= patch
-SRCS= patch.c pch.c inp.c util.c backupfile.c mkpath.c
+SRCS= patch.c pch.c inp.c util.c backupfile.c mkpath.c ed.c
.include <bsd.prog.mk>
Index: ed.c
===================================================================
RCS file: ed.c
diff -N ed.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ed.c 11 Oct 2015 09:43:59 -0000
@@ -0,0 +1,335 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2015 Tobias Stoeckmann <[email protected]>
+ *
+ * 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 finit 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 */
+
+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 (pgets(buf, sizeof buf, pfp) == NULL)
+ 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--;
+ }
+ fsm = (fsm == FSM_C) ? FSM_I : FSM_CMD;
+ break;
+ case FSM_S:
+ cline->subst++;
+ fsm = FSM_CMD;
+ break;
+ default:
+ break;
+ }
+
+ continue;
+ }
+
+ if (strcmp(buf, ".\n") == 0) {
+ fsm = FSM_CMD;
+ continue;
+ }
+
+ if (fsm == FSM_A) {
+ nline = create_line(linepos);
+ if (cline == NULL)
+ LIST_INSERT_HEAD(&head, nline, entries);
+ else
+ LIST_INSERT_AFTER(cline, nline, entries);
+ cline = nline;
+ line_count++;
+ } else if (fsm == FSM_I) {
+ nline = create_line(linepos);
+ if (cline == NULL) {
+ LIST_INSERT_HEAD(&head, nline, entries);
+ cline = nline;
+ } else
+ LIST_INSERT_BEFORE(cline, nline, entries);
+ line_count++;
+ }
+ }
+
+ 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;
+
+ /* 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 (strcmp(++p, "/^\\.\\././\n") != 0)
+ fatal("unsupported substitution at "
+ "line %ld: %s", p_input_line, buf);
+ fsm = FSM_S;
+ min_addr = 1;
+ break;
+ default:
+ return -1;
+ /* NOTREACHED */
+ }
+
+ 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;
+}
+
+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 (pgets(buf, sizeof buf, pfp) == NULL)
+ 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;
+}
Index: ed.h
===================================================================
RCS file: ed.h
diff -N ed.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ed.h 11 Oct 2015 09:43:59 -0000
@@ -0,0 +1,19 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2015 Tobias Stoeckmann <[email protected]>
+ *
+ * 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);
Index: patch.c
===================================================================
RCS file: /cvs/src/usr.bin/patch/patch.c,v
retrieving revision 1.59
diff -u -p -u -p -r1.59 patch.c
--- patch.c 9 Oct 2015 01:37:08 -0000 1.59
+++ patch.c 11 Oct 2015 09:43:59 -0000
@@ -43,6 +43,7 @@
#include "inp.h"
#include "backupfile.h"
#include "pathnames.h"
+#include "ed.h"
mode_t filemode = 0644;
@@ -147,7 +148,7 @@ main(int argc, char *argv[])
const char *tmpdir;
char *v;
- if (pledge("stdio rpath wpath cpath tmppath fattr proc exec", NULL) ==
-1)
+ if (pledge("stdio rpath wpath cpath tmppath fattr", NULL) == -1)
perror("pledge");
setvbuf(stdout, NULL, _IOLBF, 0);
@@ -218,11 +219,6 @@ main(int argc, char *argv[])
if (outname == NULL)
outname = xstrdup(filearg[0]);
- /* for ed script just up and do it and exit */
- if (diff_type == ED_DIFF) {
- do_ed_script();
- continue;
- }
/* initialize the patched file */
if (!skip_rest_of_patch)
init_output(TMPOUTNAME);
@@ -233,6 +229,12 @@ main(int argc, char *argv[])
/* 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 */
Index: pch.c
===================================================================
RCS file: /cvs/src/usr.bin/patch/pch.c,v
retrieving revision 1.53
diff -u -p -u -p -r1.53 pch.c
--- pch.c 31 Jul 2015 00:24:14 -0000 1.53
+++ pch.c 11 Oct 2015 09:44:00 -0000
@@ -45,6 +45,9 @@
/* 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 */
@@ -53,7 +56,6 @@ static LINENUM p_repl_lines; /* # lines
static LINENUM p_end = -1; /* last line in hunk */
static LINENUM p_max; /* max allowed value of p_end */
static LINENUM p_context = 3; /* # of context lines */
-static LINENUM p_input_line = 0; /* current line # from patch file */
static char **p_line = NULL;/* the text of the hunk */
static short *p_len = NULL; /* length of each line */
static char *p_char = NULL; /* +, -, and ! */
@@ -66,18 +68,14 @@ static LINENUM p_sline; /* and the line
static LINENUM p_hunk_beg; /* line number of current hunk */
static LINENUM p_efake = -1; /* end of faked up lines--don't free */
static LINENUM p_bfake = -1; /* beg of faked up lines */
-static FILE *pfp = NULL; /* patch file pointer */
static char *bestguess = NULL; /* guess at correct filename */
static void grow_hunkmax(void);
static int intuit_diff_type(void);
-static void next_intuit_at(off_t, LINENUM);
static void skip_to(off_t, LINENUM);
-static char *pgets(char *, int, FILE *);
static char *best_name(const struct file_name *, bool);
static char *posix_name(const struct file_name *, bool);
static size_t num_components(const char *);
-static LINENUM strtolinenum(char *, char **);
/*
* Prepare to look for the next patch in the patch file.
@@ -411,7 +409,7 @@ scan_exit:
/*
* Remember where this patch ends so we know where to start up again.
*/
-static void
+void
next_intuit_at(off_t file_pos, LINENUM file_line)
{
p_base = file_pos;
@@ -1151,7 +1149,7 @@ hunk_done:
/*
* Input a line from the patch file, worrying about indentation.
*/
-static char *
+char *
pgets(char *bf, int sz, FILE *fp)
{
char *s, *ret = fgets(bf, sz, fp);
@@ -1370,82 +1368,6 @@ pch_hunk_beg(void)
}
/*
- * Apply an ed script by feeding ed itself.
- */
-void
-do_ed_script(void)
-{
- char *t;
- off_t beginning_of_this_line;
- FILE *pipefp = NULL;
-
- if (!skip_rest_of_patch) {
- if (copy_file(filearg[0], TMPOUTNAME) < 0) {
- unlink(TMPOUTNAME);
- fatal("can't create temp file %s", TMPOUTNAME);
- }
- snprintf(buf, sizeof buf, "%s%s%s", _PATH_ED,
- verbose ? " " : " -s ", TMPOUTNAME);
- pipefp = popen(buf, "w");
- }
- for (;;) {
- beginning_of_this_line = ftello(pfp);
- if (pgets(buf, sizeof buf, pfp) == NULL) {
- next_intuit_at(beginning_of_this_line, p_input_line);
- break;
- }
- p_input_line++;
- for (t = buf; isdigit((unsigned char)*t) || *t == ','; t++)
- ;
- /* POSIX defines allowed commands as {a,c,d,i,s} */
- if (isdigit((unsigned char)*buf) &&
- *t != '\0' && strchr("acdis", *t) != NULL) {
- if (pipefp != NULL)
- fputs(buf, pipefp);
- if (*t == 's') {
- for (;;) {
- bool continued = false;
- t = buf + strlen(buf) - 1;
- while (--t >= buf && *t == '\\')
- continued = !continued;
- if (!continued ||
- pgets(buf, sizeof buf, pfp) == NULL)
- break;
- if (pipefp != NULL)
- fputs(buf, pipefp);
- }
- } else if (*t != 'd') {
- while (pgets(buf, sizeof buf, pfp) != NULL) {
- p_input_line++;
- if (pipefp != NULL)
- fputs(buf, pipefp);
- if (strEQ(buf, ".\n"))
- break;
- }
- }
- } else {
- next_intuit_at(beginning_of_this_line, p_input_line);
- break;
- }
- }
- if (pipefp == NULL)
- return;
- fprintf(pipefp, "w\n");
- fprintf(pipefp, "q\n");
- fflush(pipefp);
- pclose(pipefp);
- ignore_signals();
- if (!check_only) {
- if (move_file(TMPOUTNAME, outname) < 0) {
- toutkeep = true;
- chmod(TMPOUTNAME, filemode);
- } else
- chmod(outname, filemode);
- }
- set_signals(1);
-}
-
-/*
* Choose the name of the file to be patched based on POSIX rules.
* NOTE: the POSIX rules are amazingly stupid and we only follow them
* if the user specified --posix or set POSIXLY_CORRECT.
@@ -1556,7 +1478,7 @@ num_components(const char *path)
* character that is not a digit in ENDPTR. If conversion is not
* possible, call fatal.
*/
-static LINENUM
+LINENUM
strtolinenum(char *nptr, char **endptr)
{
LINENUM rv;
Index: pch.h
===================================================================
RCS file: /cvs/src/usr.bin/patch/pch.h,v
retrieving revision 1.9
diff -u -p -u -p -r1.9 pch.h
--- pch.h 31 Oct 2003 20:20:45 -0000 1.9
+++ pch.h 11 Oct 2015 09:44:00 -0000
@@ -53,4 +53,9 @@ LINENUM pch_context(void);
LINENUM pch_hunk_beg(void);
char pch_char(LINENUM);
char *pfetch(LINENUM);
-void do_ed_script(void);
+char *pgets(char *, int, FILE *);
+void next_intuit_at(off_t, LINENUM);
+LINENUM strtolinenum(char *, char **);
+
+extern FILE *pfp;
+extern LINENUM p_input_line;