shithub: riscv

Download patch

ref: 07e162091149adcd7b8ed39d97e07018159ffda0
parent: 761bf6c3477b2bef675c20aad954acae02b923ed
author: Ori Bernstein <ori@eigenstate.org>
date: Fri Jun 3 21:56:01 EDT 2022

patch: implement a new, simpler patch program to replace ape/patch

ape/patch is a giant, ugly ball of code from almost 25 years ago,
which has not and will likely never been updated or maintained.

the world has since settled on unified diffs, and we just need a
simple program that can parse and apply them.

--- /dev/null
+++ b/sys/src/cmd/patch.c
@@ -1,0 +1,611 @@
+#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;
+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);
+	h->oldcnt = 1;
+	if(*e == ','){
+		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)
+		return -1;
+	h->newcnt = 1;
+	if(*e == ','){
+		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, **oldp, **newp;
+	int oldcnt, newcnt, lnum;
+	Patch *p;
+	Hunk h;
+
+	ln = nil;
+	lnum = 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);
+	}
+	if(p->nhunk == 0)
+		sysfatal("%s: could not find start of patch", name);
+	goto out;
+
+patch:
+	if(fileheader(ln, "--- ", oldp) == -1)
+		goto comment;
+	free(ln);
+
+	if((ln = readline(f, &lnum)) == nil)
+		goto out;
+	if(fileheader(ln, "+++ ", newp) == -1)
+		goto comment;
+	free(ln);
+
+	if((ln = readline(f, &lnum)) == nil)
+		goto out;
+hunk:
+	oldcnt = 0;
+	newcnt = 0;
+	if(hunkheader(&h, ln, old, new, lnum) == -1)
+		goto comment;
+	free(ln);
+
+	while(1){
+		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:
+			sysfatal("%s:%d: malformed hunk2", name, lnum);
+			goto out;
+		case '-':
+			addold(&h, ln);
+			oldcnt++;
+			break;
+		case '+':
+			addnew(&h, ln);
+			newcnt++;
+			break;
+		case ' ':
+			addold(&h, ln);
+			addnew(&h, ln);
+			oldcnt++;
+			newcnt++;
+			break;
+		}
+		free(ln);
+		if(oldcnt > h.oldcnt || newcnt > h.newcnt)
+			sysfatal("%s:%d: malformed hunk", name, lnum);
+		if(oldcnt < h.oldcnt || newcnt < h.newcnt)
+			continue;
+
+		addhunk(p, &h);
+		if((ln = readline(f, &lnum)) == nil)
+			goto out;
+		if(strncmp(ln, "--- ", 4) == 0)
+			goto patch;
+		if(strncmp(ln, "@@ ", 3) == 0)
+			goto hunk;
+		goto comment;
+	}
+
+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);
+}
+
+int
+mkpath(char *path)
+{
+	char *p, buf[ERRMAX];
+	int f;
+	
+	if(*path == '\0')
+		return 0;
+	for(p = strchr(path+1, '/'); p != nil; p = strchr(p+1, '/')){
+		*p = '\0';
+		if(access(path, AEXIST) != 0){
+			if((f = create(path, OREAD, DMDIR | 0777)) == -1){
+				rerrstr(buf, sizeof(buf));
+				if(strstr(buf, "exist") == nil)
+					return -1;
+			}
+			close(f);
+		}
+		*p = '/';
+	}
+	return 0;
+}
+
+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(mkpath(new) == -1)
+		sysfatal("mkpath %s: %r", new);
+	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(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 < 250) ? f->nlines : 250;
+	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(strcmp(h->newpath, "/dev/null") == 0)
+				print("%s\n", h->oldpath);
+			else
+				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] [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;
+	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);
+}