We've been dragging along an antique version of Gnu patch as part of ape. It's a bulky program with built in remote code execution as part of ed-style diffs. I'd like to eventually kill it. Let's start by adding a replacement: diff 6fbb1acc8fa0b6655b14e8c46240a4a8d2d8c672 uncommitted --- /dev/null +++ b//sys/man/1/patch @@ -1,0 +1,57 @@ +.TH PATCH 1 +.SH NAME +patch \- apply diffs +.SH SYNOPSIS +.B patch +[ +.B -R +] +[ +.B -p +.I nstrip +] +[ +.B -f +.I maxfuzz +] +[ +.B patch... +] +.SH DESCRIPTION +.I Patch +will take a patch file in unified diff format, and apply it, +skipping leading and trailing junk. +If the file is not an exact match, it will search forwards and +backwards for the surrounding context up to +.I maxfuzz +lines. + +The following options are supported: +.TP +.I -R +Reverse direction of the patch. Addtions become removals, +and the new and old file names are swapped. +.TP +.I -p nstrip +Remove +.I nstrip +elements from the paths to the files. +.TP +.I -f maxfuzz +Controls how far +.I patch +searches for context when applying a patch. +.SH SEE ALSO +.IR diff (1) +.IR git/export (1) + +.SH BUGS +.PP +The output of +.IR diff -c +is not handled +.PP +Reject files and backups are not supported +.PP +All files are processed in memory, which limits +the files handled to 2 gigabytes.
whoops -- forgot to attach the acutal code. Attached, with some fixups for the manpage. diff 6fbb1acc8fa0b6655b14e8c46240a4a8d2d8c672 uncommitted --- /dev/null +++ b//sys/man/1/patch @@ -1,0 +1,58 @@ +.TH PATCH 1 +.SH NAME +patch \- apply diffs +.SH SYNOPSIS +.B patch +[ +.B -R +] +[ +.B -p +.I nstrip +] +[ +.B -f +.I maxfuzz +] +[ +.B patch... +] +.SH DESCRIPTION +.I Patch +will take a patch file in unified diff format, and apply it, +skipping leading and trailing junk. +If the file is not an exact match, it will search forwards and +backwards for the surrounding context up to +.I maxfuzz +lines. + +The following options are supported: +.TP +.I -R +Reverse direction of the patch. Addtions become removals, +and the new and old file names are swapped. +.TP +.I -p nstrip +Remove +.I nstrip +elements from the paths to the files. +.TP +.I -f maxfuzz +Controls how far +.I patch +searches for context when applying a patch. +If not specified, this defaults to 250 lines. +.SH SEE ALSO +.IR diff (1) +.IR git/export (1) + +.SH BUGS +.PP +The output of +.IR diff -c +is not handled. +.PP +Reject files and backups are not supported. +.PP +All files are processed in memory, which limits +the files handled to 2 gigabytes. --- /dev/null +++ b//sys/src/cmd/patch.c @@ -1,0 +1,548 @@ +#include <u.h> +#include <libc.h> +#include <ctype.h> +#include <bio.h> + +typedef struct Patch Patch; +typedef struct Hunk Hunk; +typedef struct Fbuf Fbuf; + +struct Patch { + char *name; + Hunk *hunk; + usize nhunk; +}; + +struct Hunk { + int lnum; + + char *oldpath; + int oldln; + int oldcnt; + int oldlen; + int oldsz; + char *old; + + char *newpath; + int newln; + int newcnt; + int newlen; + int newsz; + char *new; +}; + +struct Fbuf { + int *lines; + int nlines; + int lastln; + char *buf; + int len; +}; + +int strip; +int reverse; +int maxfuzz = 256; + +char* +readline(Biobuf *f, char *name, int *lnum) +{ + char *ln; + + if((ln = Brdstr(f, '\n', 0)) == nil) + sysfatal("read %s:%d: %r", name, *lnum); + *lnum += 1; + return ln; +} + +void * +emalloc(ulong n) +{ + void *v; + + v = mallocz(n, 1); + if(v == nil) + sysfatal("malloc: %r"); + setmalloctag(v, getcallerpc(&n)); + return v; +} + +void * +erealloc(void *v, ulong n) +{ + if(n == 0) + n++; + v = realloc(v, n); + if(v == nil) + sysfatal("malloc: %r"); + setmalloctag(v, getcallerpc(&n)); + return v; +} + +int +fileheader(char *s, char *pfx, char **name) +{ + char *p, *e; + int len, n; + + if((strncmp(s, pfx, strlen(pfx))) != 0) + return -1; + for(s += strlen(pfx); *s; s++) + if(!isspace(*s)) + break; + for(e = s; *e; e++) + if(isspace(*e)) + break; + if(s == e) + return -1; + n = strip; + for(p = s; *p && n > 0; p++){ + if(*p != '/') + continue; + s = p + 1; + n--; + } + len = (e - s) + 1; + *name = emalloc(len); + strecpy(*name, *name + len, s); + return 0; +} + +int +hunkheader(Hunk *h, char *s, char *oldpath, char *newpath, int lnum) +{ + char *e; + + memset(h, 0, sizeof(*h)); + h->lnum = lnum; + h->oldpath = strdup(oldpath); + h->newpath = strdup(newpath); + h->oldlen = 0; + h->oldsz = 32; + h->old = emalloc(h->oldsz); + h->newlen = 0; + h->newsz = 32; + h->new = emalloc(h->newsz); + if(strncmp(s, "@@ -", 4) != 0) + return -1; + e = s + 4; + h->oldln = strtol(e, &e, 10); + if(*e != ',') + return -1; + e++; + h->oldcnt = strtol(e, &e, 10); + while(*e == ' ' || *e == '\t') + e++; + if(*e != '+') + return -1; + e++; + h->newln = strtol(e, &e, 10); + if(e == s || *e != ',') + return -1; + e++; + h->newcnt = strtol(e, &e, 10); + if(e == s || *e != ' ') + return -1; + if(strncmp(e, " @@", 3) != 0) + return -1; + /* + * empty files have line number 0: keep that, + * otherwise adjust down. + */ + if(h->oldln > 0) + h->oldln--; + if(h->newln > 0) + h->newln--; + if(h->oldln < 0 || h->newln < 0 || h->oldcnt < 0 || h->newcnt < 0) + sysfatal("malformed hunk %s", s); + return 0; +} + +void +addnew(Hunk *h, char *ln) +{ + int n; + + ln++; + n = strlen(ln); + while(h->newlen + n >= h->newsz){ + h->newsz *= 2; + h->new = erealloc(h->new, h->newsz); + } + memcpy(h->new + h->newlen, ln, n); + h->newlen += n; +} + +void +addold(Hunk *h, char *ln) +{ + int n; + + ln++; + n = strlen(ln); + while(h->oldlen + n >= h->oldsz){ + h->oldsz *= 2; + h->old = erealloc(h->old, h->oldsz); + } + memcpy(h->old + h->oldlen, ln, n); + h->oldlen += n; +} + +void +addhunk(Patch *p, Hunk *h) +{ + p->hunk = erealloc(p->hunk, ++p->nhunk*sizeof(Hunk)); + p->hunk[p->nhunk-1] = *h; +} + +int +hunkcmp(void *a, void *b) +{ + int c; + + c = strcmp(((Hunk*)a)->oldpath, ((Hunk*)b)->oldpath); + if(c != 0) + return c; + return ((Hunk*)b)->oldln - ((Hunk*)a)->oldln; +} + +Patch* +parse(Biobuf *f, char *name) +{ + char *ln, *old, *new, **oldp, **newp; + int inbody, oldcnt, newcnt, lnum; + Patch *p; + Hunk h; + + ln = nil; + lnum = 0; + inbody = 0; + p = emalloc(sizeof(Patch)); + if(!reverse){ + oldp = &old; + newp = &new; + }else{ + oldp = &new; + newp = &old; + } +comment: + free(ln); + while((ln = readline(f, name, &lnum)) != nil){ + if(strncmp(ln, "--- ", 4) == 0) + goto patch; + free(ln); + } + sysfatal("%s: could not find start of patch", name); + +patch: + if(fileheader(ln, "--- ", oldp) == -1){ + if(inbody) + goto out; + else + goto comment; + } + free(ln); + ln = readline(f, name, &lnum); + if(fileheader(ln, "+++ ", newp) == -1){ + if(inbody) + goto out; + else + goto comment; + + } + free(ln); + ln = readline(f, name, &lnum); +hunk: + if(hunkheader(&h, ln, old, new, lnum) == -1) + goto comment; + free(ln); + + inbody = 1; + oldcnt = 0; + newcnt = 0; + while(1){ + if((ln = readline(f, name, &lnum)) == nil){ + if(oldcnt != h.oldcnt || newcnt != h.newcnt) + sysfatal("%s:%d: malformed hunk", name, lnum); + addhunk(p, &h); + break; + } + switch(ln[0]){ + default: + if(oldcnt != h.oldcnt || newcnt != h.newcnt) + sysfatal("%s:%d: malformed hunk", name, lnum); + addhunk(p, &h); + goto patch; + case '@': + addhunk(p, &h); + goto hunk; + case '-': + if(reverse) + addnew(&h, ln); + else + addold(&h, ln); + oldcnt++; + break; + case '+': + if(reverse) + addold(&h, ln); + else + addnew(&h, ln); + newcnt++; + break; + case ' ': + addold(&h, ln); + addnew(&h, ln); + oldcnt++; + newcnt++; + break; + } + } + +out: + qsort(p->hunk, p->nhunk, sizeof(Hunk), hunkcmp); + free(old); + free(new); + free(ln); + return p; +} + +int +rename(int fd, char *name) +{ + Dir st; + + nulldir(&st); + st.name = name; + return dirfwstat(fd, &st); +} + +void +blat(char *old, char *new, char *o, usize len) +{ + char *tmp; + int fd; + + if(strcmp(new, "/dev/null") == 0){ + if(len != 0) + sysfatal("diff modifies removed file"); + if(remove(old) == -1) + sysfatal("remove %s: %r", old); + return; + } + if((tmp = smprint("%s.tmp%d", new, getpid())) == nil) + sysfatal("smprint: %r"); + if((fd = create(tmp, OWRITE, 0666)) == -1) + sysfatal("open %s: %r", tmp); + if(write(fd, o, len) != len) + sysfatal("write %s: %r", tmp); + if(strcmp(old, "/dev/null") != 0 && remove(old) == -1) + sysfatal("remove %s: %r", old); + if(strcmp(new, old) != 0 && remove(new) == -1) + sysfatal("remove %s: %r", new); + if(rename(fd, new) == -1) + sysfatal("rename %s => %s: %r", tmp, new); + if(close(fd) == -1) + sysfatal("close %s: %r", tmp); + free(tmp); +} + +int +slurp(Fbuf *f, char *path) +{ + int n, i, fd, sz, len, nlines, linesz; + char *buf; + int *lines; + + if((fd = open(path, OREAD)) == -1) + sysfatal("open %s: %r", path); + sz = 8192; + len = 0; + buf = emalloc(sz); + while(1){ + if(len == sz){ + sz *= 2; + buf = erealloc(buf, sz); + } + n = read(fd, buf + len, sz - len); + if(n == 0) + break; + if(n == -1) + sysfatal("read %s: %r", path); + len += n; + } + + nlines = 0; + linesz = 32; + lines = emalloc(linesz*sizeof(int)); + lines[nlines++] = 0; + for(i = 0; i < len; i++){ + if(buf[i] != '\n') + continue; + if(nlines+1 == linesz){ + linesz *= 2; + lines = erealloc(lines, linesz*sizeof(int)); + } + lines[nlines++] = i+1; + } + f->len = len; + f->buf = buf; + f->lines = lines; + f->nlines = nlines; + f->lastln = 0; + return 0; +} + +char* +search(Fbuf *f, Hunk *h, char *fname) +{ + int ln, len, off, fuzz, nfuzz, scanning; + + scanning = 1; + len = h->oldlen; + nfuzz = (f->nlines < maxfuzz) ? f->nlines : maxfuzz; + for(fuzz = 0; scanning && fuzz <= nfuzz; fuzz++){ + scanning = 0; + ln = h->oldln - fuzz; + if(ln >= f->lastln){ + off = f->lines[ln]; + if(off + len > f->len) + continue; + scanning = 1; + if(memcmp(f->buf + off, h->old, h->oldlen) == 0){ + f->lastln = ln; + return f->buf + off; + } + } + ln = h->oldln + fuzz - 1; + if(ln <= f->nlines){ + off = f->lines[ln]; + if(off + len >= f->len) + continue; + scanning = 1; + if(memcmp(f->buf + off, h->old, h->oldlen) == 0){ + f->lastln = ln; + return f->buf + off; + } + } + } + sysfatal("%s:%d: unable to find patch offset", fname, h->lnum); + return nil; +} + +char* +append(char *o, int *sz, char *s, char *e) +{ + int n; + + n = (e - s); + o = erealloc(o, *sz + n); + memcpy(o + *sz, s, n); + *sz += n; + return o; +} + +int +apply(Patch *p, char *fname) +{ + char *o, *s, *e, *curfile; + int i, osz; + Hunk *h; + Fbuf f; + + e = nil; + o = nil; + osz = 0; + curfile = nil; + for(i = 0; i < p->nhunk; i++){ + h = &p->hunk[i]; + if(curfile == nil || strcmp(curfile, h->oldpath) != 0){ + if(slurp(&f, h->oldpath) == -1) + sysfatal("slurp %s: %r", h->oldpath); + curfile = h->oldpath; + e = f.buf; + } + s = e; + e = search(&f, h, fname); + o = append(o, &osz, s, e); + o = append(o, &osz, h->new, h->new + h->newlen); + e += h->oldlen; + if(i+1 == p->nhunk || strcmp(curfile, p->hunk[i+1].oldpath) != 0){ + o = append(o, &osz, e, f.buf + f.len); + blat(h->oldpath, h->newpath, o, osz); + osz = 0; + } + } + free(o); + return 0; +} + +void +freepatch(Patch *p) +{ + Hunk *h; + int i; + + for(i = 0; i < p->nhunk; i++){ + h = &p->hunk[i]; + free(h->oldpath); + free(h->newpath); + free(h->old); + free(h->new); + } + free(p->hunk); + free(p->name); + free(p); +} + +void +usage(void) +{ + fprint(2, "usage: %s [-R] [-p nstrip] [-f maxfuzz] [patch...]\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + Biobuf *f; + Patch *p; + int i; + + ARGBEGIN{ + case 'p': + strip = atoi(EARGF(usage())); + break; + case 'R': + reverse = 1; + break; + case 'f': + maxfuzz = atoi(EARGF(usage())); + break; + default: + usage(); + break; + }ARGEND; + + if(argc == 0){ + if((f = Bfdopen(0, OREAD)) == nil) + sysfatal("open stdin: %r"); + if((p = parse(f, "stdin")) == nil) + sysfatal("parse patch: %r"); + if(apply(p, "stdin") == -1) + sysfatal("apply stdin: %r"); + freepatch(p); + Bterm(f); + }else{ + for(i = 0; i < argc; i++){ + if((f = Bopen(argv[i], OREAD)) == nil) + sysfatal("open %s: %r", argv[i]); + if((p = parse(f, argv[i])) == nil) + sysfatal("parse patch: %r"); + if(apply(p, argv[i]) == -1) + sysfatal("apply %s: %r", argv[i]); + freepatch(p); + Bterm(f); + } + } + exits(nil); +}
[-- Attachment #1: Type: text/plain, Size: 889 bytes --] On Sun, 22 May 2022 19:09:40 -0400 ori@eigenstate.org wrote: > We've been dragging along an antique version of Gnu patch > as part of ape. It's a bulky program with built in remote > code execution as part of ed-style diffs. This will conflict with /rc/bin/patch and /sys/man/1/patch, which I'm all for removing. Attached is my edit of the man page: correct Synopsis patch italics clarify what patch does Addtions -> Additions fix table italics clarify -p in See Also, add comma in Bugs, use all bold for commands I can't seem to apply your diff with your program: cpu% touch patch patch.c cpu% lc 6.out* diff patch patch.c cpu% 6.out diff 6.out: diff:4: unable to find patch offset cpu% 6.out -p4 diff 6.out: open null: 'null' not found cpu% ape/patch -i diff patching file `patch' patching file `patch.c' cpu% I'm guessing /dev/null is special-cased in gnu patch. Thanks, Amavect [-- Attachment #2: patch.man --] [-- Type: application/x-troff-man, Size: 967 bytes --]
>Content-Type: application/x-troff-man
text/troff exists.
lines.
The following options are supported:
.\" …
.IR git/export (1)
.SH BUGS
In troff, blank lines are UB. They should be removed or replaced by
lines with just a period.
--
Humm
Quoth Amavect <amavect@gmail.com>: > On Sun, 22 May 2022 19:09:40 -0400 > ori@eigenstate.org wrote: > > > We've been dragging along an antique version of Gnu patch > > as part of ape. It's a bulky program with built in remote > > code execution as part of ed-style diffs. > > This will conflict with /rc/bin/patch and /sys/man/1/patch, > which I'm all for removing. done -- a59e61a6a4e11e0256da0d209afa38ccacd460a2 > Attached is my edit of the man page: > correct Synopsis patch italics > clarify what patch does > Addtions -> Additions > fix table italics > clarify -p > in See Also, add comma > in Bugs, use all bold for commands Taken, thanks. > I can't seem to apply your diff with your program: > cpu% touch patch patch.c > cpu% lc > 6.out* diff patch patch.c > cpu% 6.out diff > 6.out: diff:4: unable to find patch offset > cpu% 6.out -p4 diff > 6.out: open null: 'null' not found > cpu% ape/patch -i diff > patching file `patch' > patching file `patch.c' > cpu% > > I'm guessing /dev/null is special-cased in gnu patch. > Yeah, and there were a couple of other bugs around that, like handling paths starting with (or repeating) /. I also fixed the '--' ambiguity: Hunk headers start with '---', but if you have a line starting with '--', then diff -u can generate an output line that also starts with '---'; to disambiguate, you need to look ahead two lines for a line starting with '@@'. The code to handle it is an ugly goto-ridden mess :( new revision: diff 6fbb1acc8fa0b6655b14e8c46240a4a8d2d8c672 uncommitted --- /dev/null +++ b//sys/man/1/patch @@ -1,0 +1,58 @@ +.TH PATCH 1 +.SH NAME +patch \- apply diffs +.SH SYNOPSIS +.B patch +[ +.B -R +] +[ +.B -p +.I nstrip +] +[ +.B -f +.I maxfuzz +] +[ +.B patch... +] +.SH DESCRIPTION +.I Patch +will take a patch file in unified diff format, and apply it, +skipping leading and trailing junk. +If the file is not an exact match, it will search forwards and +backwards for the surrounding context up to +.I maxfuzz +lines. + +The following options are supported: +.TP +.I -R +Reverse direction of the patch. Addtions become removals, +and the new and old file names are swapped. +.TP +.I -p nstrip +Remove +.I nstrip +elements from the paths to the files. +.TP +.I -f maxfuzz +Controls how far +.I patch +searches for context when applying a patch. +If not specified, this defaults to 250 lines. +.SH SEE ALSO +.IR diff (1) +.IR git/export (1) + +.SH BUGS +.PP +The output of +.IR diff -c +is not handled. +.PP +Reject files and backups are not supported. +.PP +All files are processed in memory, which limits +the files handled to 2 gigabytes. --- /dev/null +++ b//sys/src/cmd/patch.c @@ -1,0 +1,617 @@ +#include <u.h> +#include <libc.h> +#include <ctype.h> +#include <bio.h> + +typedef struct Patch Patch; +typedef struct Hunk Hunk; +typedef struct Fbuf Fbuf; + +struct Patch { + char *name; + Hunk *hunk; + usize nhunk; +}; + +struct Hunk { + int lnum; + + char *oldpath; + int oldln; + int oldcnt; + int oldlen; + int oldsz; + char *old; + + char *newpath; + int newln; + int newcnt; + int newlen; + int newsz; + char *new; +}; + +struct Fbuf { + int *lines; + int nlines; + int lastln; + char *buf; + int len; +}; + +int strip; +int reverse; +int maxfuzz = 250; +int listpatch; +void (*addnew)(Hunk*, char*); +void (*addold)(Hunk*, char*); + +char* +readline(Biobuf *f, int *lnum) +{ + char *ln; + + if((ln = Brdstr(f, '\n', 0)) == nil) + return nil; + *lnum += 1; + return ln; +} + +void * +emalloc(ulong n) +{ + void *v; + + v = mallocz(n, 1); + if(v == nil) + sysfatal("malloc: %r"); + setmalloctag(v, getcallerpc(&n)); + return v; +} + +void * +erealloc(void *v, ulong n) +{ + if(n == 0) + n++; + v = realloc(v, n); + if(v == nil) + sysfatal("malloc: %r"); + setmalloctag(v, getcallerpc(&n)); + return v; +} + +int +fileheader(char *s, char *pfx, char **name) +{ + int len, n, nnull; + char *e; + + if((strncmp(s, pfx, strlen(pfx))) != 0) + return -1; + for(s += strlen(pfx); *s; s++) + if(!isspace(*s)) + break; + for(e = s; *e; e++) + if(isspace(*e)) + break; + if(s == e) + return -1; + nnull = strlen("/dev/null"); + if((e - s) != nnull || strncmp(s, "/dev/null", nnull) != 0){ + n = strip; + while(s != e && n > 0){ + while(s != e && *s == '/') + s++; + while(s != e && *s != '/') + s++; + n--; + } + while(*s == '/') + s++; + if(*s == '\0') + sysfatal("too many components stripped"); + } + len = (e - s) + 1; + *name = emalloc(len); + strecpy(*name, *name + len, s); + return 0; +} + +int +hunkheader(Hunk *h, char *s, char *oldpath, char *newpath, int lnum) +{ + char *e; + + memset(h, 0, sizeof(*h)); + h->lnum = lnum; + h->oldpath = strdup(oldpath); + h->newpath = strdup(newpath); + h->oldlen = 0; + h->oldsz = 32; + h->old = emalloc(h->oldsz); + h->newlen = 0; + h->newsz = 32; + h->new = emalloc(h->newsz); + if(strncmp(s, "@@ -", 4) != 0) + return -1; + e = s + 4; + h->oldln = strtol(e, &e, 10); + if(*e != ',') + return -1; + e++; + h->oldcnt = strtol(e, &e, 10); + while(*e == ' ' || *e == '\t') + e++; + if(*e != '+') + return -1; + e++; + h->newln = strtol(e, &e, 10); + if(e == s || *e != ',') + return -1; + e++; + h->newcnt = strtol(e, &e, 10); + if(e == s || *e != ' ') + return -1; + if(strncmp(e, " @@", 3) != 0) + return -1; + /* + * empty files have line number 0: keep that, + * otherwise adjust down. + */ + if(h->oldln > 0) + h->oldln--; + if(h->newln > 0) + h->newln--; + if(h->oldln < 0 || h->newln < 0 || h->oldcnt < 0 || h->newcnt < 0) + sysfatal("malformed hunk %s", s); + return 0; +} + +void +addnewfn(Hunk *h, char *ln) +{ + int n; + + ln++; + n = strlen(ln); + while(h->newlen + n >= h->newsz){ + h->newsz *= 2; + h->new = erealloc(h->new, h->newsz); + } + memcpy(h->new + h->newlen, ln, n); + h->newlen += n; +} + +void +addoldfn(Hunk *h, char *ln) +{ + int n; + + ln++; + n = strlen(ln); + while(h->oldlen + n >= h->oldsz){ + h->oldsz *= 2; + h->old = erealloc(h->old, h->oldsz); + } + memcpy(h->old + h->oldlen, ln, n); + h->oldlen += n; +} + +int +addmiss(Hunk *h, char *ln, int *nold, int *nnew) +{ + if(ln == nil) + return 1; + else if(ln[0] != '-' && ln[0] != '+') + return 0; + if(ln[0] == '-'){ + addold(h, ln); + *nold += 1; + }else{ + addnew(h, ln); + *nnew += 1; + } + return 1; +} + +void +addhunk(Patch *p, Hunk *h) +{ + p->hunk = erealloc(p->hunk, ++p->nhunk*sizeof(Hunk)); + p->hunk[p->nhunk-1] = *h; +} + +int +hunkcmp(void *a, void *b) +{ + int c; + + c = strcmp(((Hunk*)a)->oldpath, ((Hunk*)b)->oldpath); + if(c != 0) + return c; + return ((Hunk*)b)->oldln - ((Hunk*)a)->oldln; +} + +Patch* +parse(Biobuf *f, char *name) +{ + char *ln, *old, *new, *oldhdr, *newhdr, *hunkhdr, **oldp, **newp; + int inbody, oldcnt, newcnt, fromdash, lnum; + Patch *p; + Hunk h, hh; + + ln = nil; + lnum = 0; + inbody = 0; + fromdash = 0; + p = emalloc(sizeof(Patch)); + if(!reverse){ + oldp = &old; + newp = &new; + }else{ + oldp = &new; + newp = &old; + } +comment: + free(ln); + while((ln = readline(f, &lnum)) != nil){ + if(strncmp(ln, "--- ", 4) == 0) + goto patch; + free(ln); + } + sysfatal("%s: could not find start of patch", name); + +patch: + oldhdr = ln; + ln = nil; + newhdr = nil; + hunkhdr = nil; + if(fileheader(oldhdr, "--- ", oldp) == -1){ + if(!inbody) + goto comment; + else if(fromdash) + goto mishunk; + else + goto out; + } + if((newhdr = readline(f, &lnum)) == nil) + goto out; + if(fileheader(newhdr, "+++ ", newp) == -1){ + if(!inbody) + goto comment; + else if(fromdash) + goto mishunk; + else + goto out; + } + if((hunkhdr = readline(f, &lnum)) == nil) + goto out; +hunk: + if(hunkheader(&hh, hunkhdr, old, new, lnum) == -1){ + if(!inbody) + goto comment; + else if(!fromdash) + goto out; +mishunk: + if(!addmiss(&h, oldhdr, &oldcnt, &newcnt)) + goto out; + if(!addmiss(&h, newhdr, &oldcnt, &newcnt)) + goto out; + if(!addmiss(&h, hunkhdr, &oldcnt, &newcnt)) + goto out; + goto nextln; + } + if(inbody) + addhunk(p, &h); + h = hh; + inbody = 1; + oldcnt = 0; + newcnt = 0; + fromdash = 0; + free(ln); + free(oldhdr); + free(newhdr); + free(hunkhdr); + while(1){ +nextln: + if((ln = readline(f, &lnum)) == nil){ + if(oldcnt != h.oldcnt || newcnt != h.newcnt) + sysfatal("%s:%d: malformed hunk", name, lnum); + addhunk(p, &h); + break; + } + switch(ln[0]){ + default: + if(oldcnt != h.oldcnt || newcnt != h.newcnt) + sysfatal("%s:%d: malformed hunk", name, lnum); + addhunk(p, &h); + goto patch; + case '@': + addhunk(p, &h); + goto hunk; + case '-': + if(strncmp(ln, "--- ", 4) == 0){ + fromdash = 1; + goto patch; + } + addold(&h, ln); + oldcnt++; + break; + case '+': + addnew(&h, ln); + newcnt++; + break; + case ' ': + addold(&h, ln); + addnew(&h, ln); + oldcnt++; + newcnt++; + break; + } + } + +out: + qsort(p->hunk, p->nhunk, sizeof(Hunk), hunkcmp); + free(old); + free(new); + free(ln); + return p; +} + +int +rename(int fd, char *name) +{ + Dir st; + char *p; + + nulldir(&st); + if((p = strrchr(name, '/')) == nil) + st.name = name; + else + st.name = p + 1; + return dirfwstat(fd, &st); +} + +void +blat(char *old, char *new, char *o, usize len) +{ + char *tmp; + int fd; + + if(strcmp(new, "/dev/null") == 0){ + if(len != 0) + sysfatal("diff modifies removed file"); + if(remove(old) == -1) + sysfatal("removeold %s: %r", old); + return; + } + if((tmp = smprint("%s.tmp%d", new, getpid())) == nil) + sysfatal("smprint: %r"); + if((fd = create(tmp, OWRITE, 0666)) == -1) + sysfatal("open %s: %r", tmp); + if(write(fd, o, len) != len) + sysfatal("write %s: %r", tmp); + if(strcmp(old, "/dev/null") != 0 && remove(old) == -1) + sysfatal("remove %s: %r", old); + if(strcmp(new, old) == 0) + remove(new); + if(rename(fd, new) == -1) + sysfatal("create %s: %r", new); + if(close(fd) == -1) + sysfatal("close %s: %r", tmp); + free(tmp); +} + +int +slurp(Fbuf *f, char *path) +{ + int n, i, fd, sz, len, nlines, linesz; + char *buf; + int *lines; + + if((fd = open(path, OREAD)) == -1) + sysfatal("open %s: %r", path); + sz = 8192; + len = 0; + buf = emalloc(sz); + while(1){ + if(len == sz){ + sz *= 2; + buf = erealloc(buf, sz); + } + n = read(fd, buf + len, sz - len); + if(n == 0) + break; + if(n == -1) + sysfatal("read %s: %r", path); + len += n; + } + + nlines = 0; + linesz = 32; + lines = emalloc(linesz*sizeof(int)); + lines[nlines++] = 0; + for(i = 0; i < len; i++){ + if(buf[i] != '\n') + continue; + if(nlines+1 == linesz){ + linesz *= 2; + lines = erealloc(lines, linesz*sizeof(int)); + } + lines[nlines++] = i+1; + } + f->len = len; + f->buf = buf; + f->lines = lines; + f->nlines = nlines; + f->lastln = 0; + return 0; +} + +char* +search(Fbuf *f, Hunk *h, char *fname) +{ + int ln, len, off, fuzz, nfuzz, scanning; + + scanning = 1; + len = h->oldlen; + nfuzz = (f->nlines < maxfuzz) ? f->nlines : maxfuzz; + for(fuzz = 0; scanning && fuzz <= nfuzz; fuzz++){ + scanning = 0; + ln = h->oldln - fuzz; + if(ln >= f->lastln){ + off = f->lines[ln]; + if(off + len > f->len) + continue; + scanning = 1; + if(memcmp(f->buf + off, h->old, h->oldlen) == 0){ + f->lastln = ln; + return f->buf + off; + } + } + ln = h->oldln + fuzz - 1; + if(ln <= f->nlines){ + off = f->lines[ln]; + if(off + len >= f->len) + continue; + scanning = 1; + if(memcmp(f->buf + off, h->old, h->oldlen) == 0){ + f->lastln = ln; + return f->buf + off; + } + } + } + sysfatal("%s:%d: unable to find hunk offset in %s", fname, h->lnum, h->oldpath); + return nil; +} + +char* +append(char *o, int *sz, char *s, char *e) +{ + int n; + + n = (e - s); + o = erealloc(o, *sz + n); + memcpy(o + *sz, s, n); + *sz += n; + return o; +} + +int +apply(Patch *p, char *fname) +{ + char *o, *s, *e, *curfile; + int i, osz; + Hunk *h; + Fbuf f; + + e = nil; + o = nil; + osz = 0; + curfile = nil; + for(i = 0; i < p->nhunk; i++){ + h = &p->hunk[i]; + if(curfile == nil || strcmp(curfile, h->newpath) != 0){ + if(slurp(&f, h->oldpath) == -1) + sysfatal("slurp %s: %r", h->oldpath); + curfile = h->newpath; + e = f.buf; + } + s = e; + e = search(&f, h, fname); + o = append(o, &osz, s, e); + o = append(o, &osz, h->new, h->new + h->newlen); + e += h->oldlen; + if(i+1 == p->nhunk || strcmp(curfile, p->hunk[i+1].newpath) != 0){ + o = append(o, &osz, e, f.buf + f.len); + blat(h->oldpath, h->newpath, o, osz); + if(listpatch) + print("%s\n", h->newpath); + osz = 0; + } + } + free(o); + return 0; +} + +void +freepatch(Patch *p) +{ + Hunk *h; + int i; + + for(i = 0; i < p->nhunk; i++){ + h = &p->hunk[i]; + free(h->oldpath); + free(h->newpath); + free(h->old); + free(h->new); + } + free(p->hunk); + free(p->name); + free(p); +} + +void +usage(void) +{ + fprint(2, "usage: %s [-R] [-p nstrip] [-f maxfuzz] [patch...]\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + Biobuf *f; + Patch *p; + int i; + + ARGBEGIN{ + case 'p': + strip = atoi(EARGF(usage())); + break; + case 'R': + reverse++; + break; + case 'f': + maxfuzz = atoi(EARGF(usage())); + break; + case 'l': + listpatch++; + break; + default: + usage(); + break; + }ARGEND; + + if(reverse){ + addnew = addoldfn; + addold = addnewfn; + }else{ + addnew = addnewfn; + addold = addoldfn; + } + if(argc == 0){ + if((f = Bfdopen(0, OREAD)) == nil) + sysfatal("open stdin: %r"); + if((p = parse(f, "stdin")) == nil) + sysfatal("parse patch: %r"); + if(apply(p, "stdin") == -1) + sysfatal("apply stdin: %r"); + freepatch(p); + Bterm(f); + }else{ + for(i = 0; i < argc; i++){ + if((f = Bopen(argv[i], OREAD)) == nil) + sysfatal("open %s: %r", argv[i]); + if((p = parse(f, argv[i])) == nil) + sysfatal("parse patch: %r"); + if(apply(p, argv[i]) == -1) + sysfatal("apply %s: %r", argv[i]); + freepatch(p); + Bterm(f); + } + } + exits(nil); +}
Quoth ori@eigenstate.org:
> new revision:
spoke too soon; I broke multiple hunks.
[-- Attachment #1: Type: text/plain, Size: 1078 bytes --] On Mon, 23 May 2022 12:57:23 +0000 Humm <hummsmith42@gmail.com> wrote: > >Content-Type: application/x-troff-man > > text/troff exists. A man page is a specific kind of troff file. (do mime types even matter?) > > The following options are supported: > .\" … > .IR git/export (1) > > .SH BUGS > > In troff, blank lines are UB. They should be removed or replaced by > lines with just a period. > Is that true? I'm having trouble finding a reference. https://www.gnu.org/software/groff/manual/html_node/Basics.html Sometimes a new output line should be started even though the current line is not yet full; for example, at the end of a paragraph. To do this it is possible to cause a break, which starts a new output line. Some requests cause a break automatically, as normally do blank input lines and input lines beginning with a space. That blank line needs to be removed anyways since it adds extra space between the SEE ALSO section and BUGS section. Also fixed a typo: apply -> applies See attached. Thanks, Amavect [-- Attachment #2: patch.man --] [-- Type: application/x-troff-man, Size: 1023 bytes --]
Quoth Amavect: >On Mon, 23 May 2022 12:57:23 +0000 >Humm <hummsmith42@gmail.com> wrote: > >> >Content-Type: application/x-troff-man >> >> text/troff exists. > >A man page is a specific kind of troff file. Quoting RFC4263: >Optional parameters: >[…] > process: Human-readable additional information for formatting, > including environment variables, preprocessor arguments and > order, formatter arguments, and postprocessors. The parameter > value may need to be quoted or encoded as provided for by > [N4.RFC2045] as amended by [N5.RFC2231] and [N6.Errata]. > Generating implementations must not encode executable content > and other implementations must not attempt any execution or > other interpretation of the parameter value, as the parameter > value may be prose text. Implementations SHOULD present the > parameter (after reassembly of continuation parameters, etc.) > as information related to the media type, particularly if the > media content is not immediately available (e.g., as with > message/external-body composite media [N3.RFC2046]). As examples are provided: text/troff ; process="dformat | pic -n | troff -ms" and text/troff ; process="use pic -n then troff -ms" >(do mime types even matter?) Yes. Well-behaved MUAs decide what to do with stuff by looking at those. For troff: No. >> In troff, blank lines are UB. They should be removed or replaced by >> lines with just a period. >> > >Is that true? I'm having trouble finding a reference. While much documentation for specific troffs indeed says that blank lines have the same effect as `.sp 1`—they do implement that, after all—that behavior is not ubiquitous. A warning is given, for example, by mandoc. Quoting mandoc(1): >blank line in fill mode, using .sp > (mdoc) The meaning of blank input lines is only well-defined in > non-fill mode: In fill mode, line breaks of text input lines are not > supposed to be significant. However, for compatibility with groff, > blank lines in fill mode are formatted like sp requests. To request > a paragraph break, use Pp instead of a blank line. >https://www.gnu.org/software/groff/manual/html_node/Basics.html >Sometimes a new output line should be started even though the current >line is not yet full; for example, at the end of a paragraph. To do >this it is possible to cause a break, which starts a new output line. >Some requests cause a break automatically, as normally do blank input >lines and input lines beginning with a space. The groff docs are obnoxious in not telling you what is extension and what not or how portable something is. -- Humm
/sys/doc/troff.ms:/Blank umbraticus
Humm writes:
> by mandoc. Quoting mandoc(1):
Ignore mandoc. It's just a piss-poor partial reimplementation
of nroff.
[-- Attachment #1: Type: text/plain, Size: 772 bytes --] On Tue, 24 May 2022 16:39:28 +0000 Humm <hummsmith42@gmail.com> wrote: > Quoting RFC4263: Neat, but I have no clue how to configure process= in claws mail. Since application/x-troff-man is nonstandard, I'll just default it to text/troff and call it a day. > While much documentation for specific troffs indeed says that blank > lines have the same effect as `.sp 1`—they do implement that, after > all—that behavior is not ubiquitous. A warning is given, for > example, by mandoc. Quoting mandoc(1): There's disagreement from others, but we can all agree that .PP is the correct choice for new paragraphs. It has slightly shorter spacing than a blank line in page. Attached is the final revision for ori's patch man page. Thanks, Amavect [-- Attachment #2.1: Type: text/plain, Size: 306 bytes --] from postmaster@9front: The following attachment had content that we can't prove to be harmless. To avoid possible automatic execution, we changed the content headers. The original header was: Content-Type: text/troff Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=patch.man [-- Attachment #2.2: patch.man.suspect --] [-- Type: application/octet-stream, Size: 1027 bytes --] .TH PATCH 1 .SH NAME patch \- apply diffs .SH SYNOPSIS .B patch [ .B -R ] [ .B -p .I nstrip ] [ .B -f .I maxfuzz ] [ .I patch ... ] .SH DESCRIPTION .I Patch takes a patch file in unified diff format and applies the differences to the file tree. If the file is not an exact match, it will search forwards and backwards for the surrounding context up to .I maxfuzz lines. .PP The following options are supported: .TP .B -R Reverse direction of the patch. Additions become removals, and the new and old file names are swapped. .TP .BI -p \ nstrip Remove the prefix containing .I nstrip leading slashes from each file path in the diff file. .TP .BI -f \ maxfuzz Controls how far .I patch searches for context when applying a patch. If not specified, this defaults to 250 lines. .SH SEE ALSO .IR diff (1), .IR git/export (1) .SH BUGS .PP The output of .B diff -c is not handled. .PP Reject files and backups are not supported. .PP All files are processed in memory, which limits the files handled to 2 gigabytes on 32-bit systems.
[-- Attachment #1: Type: text/plain, Size: 12793 bytes --] Quoth ori@eigenstate.org: > Quoth ori@eigenstate.org: > > new revision: > > spoke too soon; I broke multiple hunks. > New revision. Tests, which can be applied to the regress suite, are attached. diff b75e549126641108880a24a4ff0b38171eb1a856 uncommitted --- /tmp/diff100000056523 +++ b//sys/man/1/patch @@ -1,0 +1,61 @@ +.TH PATCH 1 +.SH NAME +patch \- apply diffs +.SH SYNOPSIS +.B patch +[ +.B -lR +] +[ +.B -p +.I nstrip +] +[ +.B -f +.I maxfuzz +] +[ +.I patch ... +] +.SH DESCRIPTION +.I Patch +takes a patch file in unified diff format and applies the differences to the file tree. +If the file is not an exact match, it will search forwards and +backwards for the surrounding context up to +.I maxfuzz +lines. +.PP +The following options are supported: +.TP +.B -l +List files affected by the applied patches, one file per line. +.TP +.B -R +Reverse direction of the patch. Additions become removals, +and the new and old file names are swapped. +.TP +.BI -p \ nstrip +Remove the prefix containing +.I nstrip +leading slashes from each file path in the diff file. +.TP +.BI -f \ maxfuzz +Controls how far +.I patch +searches for context when applying a patch. +If not specified, this defaults to 250 lines. +.SH SEE ALSO +.IR diff (1), +.IR git/export (1) +.SH BUGS +.PP +The output of +.B diff -c +is not handled. +.PP +Reject files and backups are not supported. +.PP +All files are processed in memory, which limits +the files handled to 2 gigabytes on 32-bit systems. + + --- /tmp/diff100000056526 +++ b//sys/src/cmd/patch.c @@ -1,0 +1,621 @@ +#include <u.h> +#include <libc.h> +#include <ctype.h> +#include <bio.h> + +typedef struct Patch Patch; +typedef struct Hunk Hunk; +typedef struct Fbuf Fbuf; + +struct Patch { + char *name; + Hunk *hunk; + usize nhunk; +}; + +struct Hunk { + int lnum; + + char *oldpath; + int oldln; + int oldcnt; + int oldlen; + int oldsz; + char *old; + + char *newpath; + int newln; + int newcnt; + int newlen; + int newsz; + char *new; +}; + +struct Fbuf { + int *lines; + int nlines; + int lastln; + char *buf; + int len; +}; + +int strip; +int reverse; +int maxfuzz = 250; +int listpatch; +void (*addnew)(Hunk*, char*); +void (*addold)(Hunk*, char*); + +char* +readline(Biobuf *f, int *lnum) +{ + char *ln; + + if((ln = Brdstr(f, '\n', 0)) == nil) + return nil; + *lnum += 1; + return ln; +} + +void * +emalloc(ulong n) +{ + void *v; + + v = mallocz(n, 1); + if(v == nil) + sysfatal("malloc: %r"); + setmalloctag(v, getcallerpc(&n)); + return v; +} + +void * +erealloc(void *v, ulong n) +{ + if(n == 0) + n++; + v = realloc(v, n); + if(v == nil) + sysfatal("malloc: %r"); + setmalloctag(v, getcallerpc(&n)); + return v; +} + +int +fileheader(char *s, char *pfx, char **name) +{ + int len, n, nnull; + char *e; + + if((strncmp(s, pfx, strlen(pfx))) != 0) + return -1; + for(s += strlen(pfx); *s; s++) + if(!isspace(*s)) + break; + for(e = s; *e; e++) + if(isspace(*e)) + break; + if(s == e) + return -1; + nnull = strlen("/dev/null"); + if((e - s) != nnull || strncmp(s, "/dev/null", nnull) != 0){ + n = strip; + while(s != e && n > 0){ + while(s != e && *s == '/') + s++; + while(s != e && *s != '/') + s++; + n--; + } + while(*s == '/') + s++; + if(*s == '\0') + sysfatal("too many components stripped"); + } + len = (e - s) + 1; + *name = emalloc(len); + strecpy(*name, *name + len, s); + return 0; +} + +int +hunkheader(Hunk *h, char *s, char *oldpath, char *newpath, int lnum) +{ + char *e; + + memset(h, 0, sizeof(*h)); + h->lnum = lnum; + h->oldpath = strdup(oldpath); + h->newpath = strdup(newpath); + h->oldlen = 0; + h->oldsz = 32; + h->old = emalloc(h->oldsz); + h->newlen = 0; + h->newsz = 32; + h->new = emalloc(h->newsz); + if(strncmp(s, "@@ -", 4) != 0) + return -1; + e = s + 4; + h->oldln = strtol(e, &e, 10); + if(*e != ',') + return -1; + e++; + h->oldcnt = strtol(e, &e, 10); + while(*e == ' ' || *e == '\t') + e++; + if(*e != '+') + return -1; + e++; + h->newln = strtol(e, &e, 10); + if(e == s || *e != ',') + return -1; + e++; + h->newcnt = strtol(e, &e, 10); + if(e == s || *e != ' ') + return -1; + if(strncmp(e, " @@", 3) != 0) + return -1; + /* + * empty files have line number 0: keep that, + * otherwise adjust down. + */ + if(h->oldln > 0) + h->oldln--; + if(h->newln > 0) + h->newln--; + if(h->oldln < 0 || h->newln < 0 || h->oldcnt < 0 || h->newcnt < 0) + sysfatal("malformed hunk %s", s); + return 0; +} + +void +addnewfn(Hunk *h, char *ln) +{ + int n; + + ln++; + n = strlen(ln); + while(h->newlen + n >= h->newsz){ + h->newsz *= 2; + h->new = erealloc(h->new, h->newsz); + } + memcpy(h->new + h->newlen, ln, n); + h->newlen += n; +} + +void +addoldfn(Hunk *h, char *ln) +{ + int n; + + ln++; + n = strlen(ln); + while(h->oldlen + n >= h->oldsz){ + h->oldsz *= 2; + h->old = erealloc(h->old, h->oldsz); + } + memcpy(h->old + h->oldlen, ln, n); + h->oldlen += n; +} + +int +addmiss(Hunk *h, char *ln, int *nold, int *nnew) +{ + if(ln == nil) + return 1; + else if(ln[0] != '-' && ln[0] != '+') + return 0; + if(ln[0] == '-'){ + addold(h, ln); + *nold += 1; + }else{ + addnew(h, ln); + *nnew += 1; + } + return 1; +} + +void +addhunk(Patch *p, Hunk *h) +{ + p->hunk = erealloc(p->hunk, ++p->nhunk*sizeof(Hunk)); + p->hunk[p->nhunk-1] = *h; +} + +int +hunkcmp(void *a, void *b) +{ + int c; + + c = strcmp(((Hunk*)a)->oldpath, ((Hunk*)b)->oldpath); + if(c != 0) + return c; + return ((Hunk*)a)->oldln - ((Hunk*)b)->oldln; +} + +Patch* +parse(Biobuf *f, char *name) +{ + char *ln, *old, *new, *oldhdr, *newhdr, *hunkhdr, **oldp, **newp; + int inbody, oldcnt, newcnt, midhunk, lnum; + Patch *p; + Hunk h, hh; + + ln = nil; + lnum = 0; + inbody = 0; + midhunk = 0; + p = emalloc(sizeof(Patch)); + if(!reverse){ + oldp = &old; + newp = &new; + }else{ + oldp = &new; + newp = &old; + } +comment: + free(ln); + while((ln = readline(f, &lnum)) != nil){ + if(strncmp(ln, "--- ", 4) == 0) + goto patch; + free(ln); + } + sysfatal("%s: could not find start of patch", name); + +patch: + oldhdr = ln; + ln = nil; + newhdr = nil; + hunkhdr = nil; + if(fileheader(oldhdr, "--- ", oldp) == -1){ + if(!inbody) + goto comment; + else if(midhunk) + goto mishunk; + else + goto out; + } + if((newhdr = readline(f, &lnum)) == nil) + goto out; + if(fileheader(newhdr, "+++ ", newp) == -1){ + if(!inbody) + goto comment; + else if(midhunk) + goto mishunk; + else + goto out; + } + if((hunkhdr = readline(f, &lnum)) == nil) + goto out; +hunk: + if(hunkheader(&hh, hunkhdr, old, new, lnum) == -1){ + if(!inbody) + goto comment; + else if(!midhunk) + goto out; +mishunk: + if(!addmiss(&h, oldhdr, &oldcnt, &newcnt)) + goto out; + if(!addmiss(&h, newhdr, &oldcnt, &newcnt)) + goto out; + if(!addmiss(&h, hunkhdr, &oldcnt, &newcnt)) + goto out; + goto nextln; + } + if(midhunk) + addhunk(p, &h); + h = hh; + inbody = 1; + oldcnt = 0; + newcnt = 0; + midhunk = 0; + free(ln); + free(oldhdr); + free(newhdr); + free(hunkhdr); + oldhdr = nil; + newhdr = nil; + while(1){ +nextln: + if((ln = readline(f, &lnum)) == nil){ + if(oldcnt != h.oldcnt || newcnt != h.newcnt) + sysfatal("%s:%d: malformed hunk", name, lnum); + addhunk(p, &h); + break; + } + switch(ln[0]){ + default: + if(oldcnt != h.oldcnt || newcnt != h.newcnt) + sysfatal("%s:%d: malformed hunk", name, lnum); + addhunk(p, &h); + goto out; + case '@': + midhunk = 1; + hunkhdr = ln; + ln = nil; + goto hunk; + case '-': + if(strncmp(ln, "--- ", 4) == 0){ + midhunk = 1; + goto patch; + } + addold(&h, ln); + oldcnt++; + break; + case '+': + addnew(&h, ln); + newcnt++; + break; + case ' ': + addold(&h, ln); + addnew(&h, ln); + oldcnt++; + newcnt++; + break; + } + } + +out: + qsort(p->hunk, p->nhunk, sizeof(Hunk), hunkcmp); + free(old); + free(new); + free(ln); + return p; +} + +int +rename(int fd, char *name) +{ + Dir st; + char *p; + + nulldir(&st); + if((p = strrchr(name, '/')) == nil) + st.name = name; + else + st.name = p + 1; + return dirfwstat(fd, &st); +} + +void +blat(char *old, char *new, char *o, usize len) +{ + char *tmp; + int fd; + + if(strcmp(new, "/dev/null") == 0){ + if(len != 0) + sysfatal("diff modifies removed file"); + if(remove(old) == -1) + sysfatal("removeold %s: %r", old); + return; + } + if((tmp = smprint("%s.tmp%d", new, getpid())) == nil) + sysfatal("smprint: %r"); + if((fd = create(tmp, OWRITE, 0666)) == -1) + sysfatal("open %s: %r", tmp); + if(write(fd, o, len) != len) + sysfatal("write %s: %r", tmp); + if(strcmp(old, new) == 0 && remove(old) == -1) + sysfatal("remove %s: %r", old); + if(strcmp(new, old) == 0) + remove(new); + if(rename(fd, new) == -1) + sysfatal("create %s: %r", new); + if(close(fd) == -1) + sysfatal("close %s: %r", tmp); + free(tmp); +} + +int +slurp(Fbuf *f, char *path) +{ + int n, i, fd, sz, len, nlines, linesz; + char *buf; + int *lines; + + if((fd = open(path, OREAD)) == -1) + sysfatal("open %s: %r", path); + sz = 8192; + len = 0; + buf = emalloc(sz); + while(1){ + if(len == sz){ + sz *= 2; + buf = erealloc(buf, sz); + } + n = read(fd, buf + len, sz - len); + if(n == 0) + break; + if(n == -1) + sysfatal("read %s: %r", path); + len += n; + } + + nlines = 0; + linesz = 32; + lines = emalloc(linesz*sizeof(int)); + lines[nlines++] = 0; + for(i = 0; i < len; i++){ + if(buf[i] != '\n') + continue; + if(nlines+1 == linesz){ + linesz *= 2; + lines = erealloc(lines, linesz*sizeof(int)); + } + lines[nlines++] = i+1; + } + f->len = len; + f->buf = buf; + f->lines = lines; + f->nlines = nlines; + f->lastln = -1; + return 0; +} + +char* +search(Fbuf *f, Hunk *h, char *fname) +{ + int ln, len, off, fuzz, nfuzz, scanning; + + scanning = 1; + len = h->oldlen; + nfuzz = (f->nlines < maxfuzz) ? f->nlines : maxfuzz; + for(fuzz = 0; scanning && fuzz <= nfuzz; fuzz++){ + scanning = 0; + ln = h->oldln - fuzz; + if(ln > f->lastln){ + off = f->lines[ln]; + if(off + len > f->len) + continue; + scanning = 1; + if(memcmp(f->buf + off, h->old, h->oldlen) == 0){ + f->lastln = ln; + return f->buf + off; + } + } + ln = h->oldln + fuzz - 1; + if(ln <= f->nlines){ + off = f->lines[ln]; + if(off + len >= f->len) + continue; + scanning = 1; + if(memcmp(f->buf + off, h->old, h->oldlen) == 0){ + f->lastln = ln; + return f->buf + off; + } + } + } + sysfatal("%s:%d: unable to find hunk offset in %s", fname, h->lnum, h->oldpath); + return nil; +} + +char* +append(char *o, int *sz, char *s, char *e) +{ + int n; + + n = (e - s); + o = erealloc(o, *sz + n); + memcpy(o + *sz, s, n); + *sz += n; + return o; +} + +int +apply(Patch *p, char *fname) +{ + char *o, *s, *e, *curfile; + int i, osz; + Hunk *h; + Fbuf f; + + e = nil; + o = nil; + osz = 0; + curfile = nil; + for(i = 0; i < p->nhunk; i++){ + h = &p->hunk[i]; + if(curfile == nil || strcmp(curfile, h->newpath) != 0){ + if(slurp(&f, h->oldpath) == -1) + sysfatal("slurp %s: %r", h->oldpath); + curfile = h->newpath; + e = f.buf; + } + s = e; + e = search(&f, h, fname); + o = append(o, &osz, s, e); + o = append(o, &osz, h->new, h->new + h->newlen); + e += h->oldlen; + if(i+1 == p->nhunk || strcmp(curfile, p->hunk[i+1].newpath) != 0){ + o = append(o, &osz, e, f.buf + f.len); + blat(h->oldpath, h->newpath, o, osz); + if(listpatch) + print("%s\n", h->newpath); + osz = 0; + } + } + free(o); + return 0; +} + +void +freepatch(Patch *p) +{ + Hunk *h; + int i; + + for(i = 0; i < p->nhunk; i++){ + h = &p->hunk[i]; + free(h->oldpath); + free(h->newpath); + free(h->old); + free(h->new); + } + free(p->hunk); + free(p->name); + free(p); +} + +void +usage(void) +{ + fprint(2, "usage: %s [-R] [-p nstrip] [-f maxfuzz] [patch...]\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + Biobuf *f; + Patch *p; + int i; + + ARGBEGIN{ + case 'p': + strip = atoi(EARGF(usage())); + break; + case 'R': + reverse++; + break; + case 'f': + maxfuzz = atoi(EARGF(usage())); + break; + case 'l': + listpatch++; + break; + default: + usage(); + break; + }ARGEND; + + if(reverse){ + addnew = addoldfn; + addold = addnewfn; + }else{ + addnew = addnewfn; + addold = addoldfn; + } + if(argc == 0){ + if((f = Bfdopen(0, OREAD)) == nil) + sysfatal("open stdin: %r"); + if((p = parse(f, "stdin")) == nil) + sysfatal("parse patch: %r"); + if(apply(p, "stdin") == -1) + sysfatal("apply stdin: %r"); + freepatch(p); + Bterm(f); + }else{ + for(i = 0; i < argc; i++){ + if((f = Bopen(argv[i], OREAD)) == nil) + sysfatal("open %s: %r", argv[i]); + if((p = parse(f, argv[i])) == nil) + sysfatal("parse patch: %r"); + if(apply(p, argv[i]) == -1) + sysfatal("apply %s: %r", argv[i]); + freepatch(p); + Bterm(f); + } + } + exits(nil); +} [-- Attachment #2: regress.patch --] [-- Type: text/plain, Size: 4921 bytes --] diff f9f95b10dfcda3981a820a24e7929af6afbebad0 uncommitted --- /tmp/diff100000056765 +++ b/cmd/patch/basic.in @@ -1,0 +1,93 @@ +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +13 +12 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 --- /tmp/diff100000056768 +++ b/cmd/patch/create.expected @@ -1,0 +1,12 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 --- /tmp/diff100000056771 +++ b/cmd/patch/create.patch @@ -1,0 +1,15 @@ +--- /dev/null ++++ create.out +@@ -1,0 +1,12 @@ ++1 ++2 ++3 ++4 ++5 ++6 ++7 ++8 ++9 ++10 ++11 ++12 --- /tmp/diff100000056774 +++ b/cmd/patch/header.expected @@ -1,0 +1,100 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 --- /tmp/diff100000056777 +++ b/cmd/patch/header.in @@ -1,0 +1,93 @@ +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +13 +12 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 --- /tmp/diff100000056780 +++ b/cmd/patch/header.patch @@ -1,0 +1,53 @@ +diff should handle headers just fine, so +this file contains a few lines of header +and footer, with a couple of false starts, +consisting of lines starting with +--- some words +but no immediately following ++++ words +lines + +--- header.in ++++ header.out +@@ -1,3 +1,5 @@ ++1 ++2 + 3 + 4 + 5 +@@ -10,8 +12,8 @@ + 12 + 13 + 14 +-13 +-12 ++15 ++16 + 17 + 18 + 19 +@@ -35,6 +37,8 @@ + 37 + 38 + 39 ++40 ++41 + 42 + 43 + 44 +@@ -80,6 +84,7 @@ + 84 + 85 + 86 ++87 + 88 + 89 + 90 +@@ -91,3 +96,5 @@ + 96 + 97 + 98 ++99 ++100 +and here is the footer that should +also be ignored. --- /tmp/diff100000056783 +++ b/cmd/patch/mkfile @@ -1,0 +1,5 @@ +</$objtype/mkfile + +TEST=patch + +<../../regress --- /tmp/diff100000056786 +++ b/cmd/patch/multifile.patch @@ -1,0 +1,86 @@ +--- multifile2.in ++++ multifile2.out +@@ -1,21 +1,19 @@ +-77777 + 77778 + 77779 + 77780 + 77781 + 77782 +-77783 +-77784 +-77785 +-77786 +-77787 +-77788 + 77789 ++77788 ++77787 ++77786 ++77785 ++77784 ++77783 + 77790 + 77791 + 77792 + 77793 + 77794 +-77795 + 77796 + 77797 +--- multifile1.in ++++ multifile1.out +@@ -14,16 +14,6 @@ + 12 + 17 + 18 +-19 +-20 +-21 +-22 +-23 +-24 +-25 +-26 +-27 +-28 + 29 + 30 + 31 +@@ -32,20 +22,6 @@ + 34 + 35 + 36 +-37 +-38 +-39 +-42 +-43 +-44 +-45 +-46 +-47 +-48 +-49 +-50 +-51 +-52 + 53 + 54 + 55 +@@ -73,11 +49,11 @@ + 77 + 78 + 79 +-80 +-81 +-82 +-83 + 84 ++83 ++82 ++81 ++80 + 85 + 86 + 88 --- /tmp/diff100000056789 +++ b/cmd/patch/multifile1.expected @@ -1,0 +1,69 @@ +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +13 +12 +17 +18 +29 +30 +31 +32 +33 +34 +35 +36 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +84 +83 +82 +81 +80 +85 +86 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 --- /tmp/diff100000056792 +++ b/cmd/patch/multifile1.in @@ -1,0 +1,93 @@ +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +13 +12 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 --- /tmp/diff100000056795 +++ b/cmd/patch/multifile2.expected @@ -1,0 +1,19 @@ +77778 +77779 +77780 +77781 +77782 +77789 +77788 +77787 +77786 +77785 +77784 +77783 +77790 +77791 +77792 +77793 +77794 +77796 +77797 --- /tmp/diff100000056798 +++ b/cmd/patch/multifile2.in @@ -1,0 +1,21 @@ +77777 +77778 +77779 +77780 +77781 +77782 +77783 +77784 +77785 +77786 +77787 +77788 +77789 +77790 +77791 +77792 +77793 +77794 +77795 +77796 +77797 --- /tmp/diff100000056801 +++ b/cmd/patch/patch.rc @@ -1,0 +1,31 @@ +#!/bin/rc -e + +fn check{ + if(! cmp $1 $2){ + >[2=1] echo fail: $1 $2 + >[2=1] diff -u $1 $2 + exit mismatch + } + status=() +} + +fn checkpatch{ + rm -f $1.out + patch $1.patch + check $1.out $1.expected +} + +checkpatch basic +checkpatch header +checkpatch create + +seq 12 > delete.out +patch delete.patch +test ! -f delete.out + +rm -f multifile^(1 2)^.out +patch multifile.patch +check multifile1.out multifile1.expected +check multifile2.out multifile2.expected + +status=()