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 <tob...@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 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 <tob...@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);
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;

Reply via email to