Patch command.
diff --git a/lib/lib.c b/lib/lib.c
index 547fe40..510b830 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -606,12 +606,57 @@
 void xsendfile(int in, int out)
 {
 	long len;
+	char buf[4096];
 
 	if (in<0) return;
 	for (;;) {
-		len = xread(in, toybuf, sizeof(toybuf));
+		len = xread(in, buf, 4096);
 		if (len<1) break;
-		xwrite(out, toybuf, len);
+		xwrite(out, buf, len);
 	}
-	xclose(in);
+}
+
+// Open a temporary file to copy an existing file into.
+int copy_tempfile(int fdin, char *name, char **tempname)
+{
+	struct stat statbuf;
+	int fd;
+
+	*tempname = xstrndup(name, strlen(name)+6);
+	strcat(*tempname,"XXXXXX");
+	if(-1 == (fd = mkstemp(*tempname))) error_exit("no temp file");
+
+	// Set permissions of output file
+
+	fstat(fdin, &statbuf);
+	fchmod(fd, statbuf.st_mode);
+
+	return fd;
+}
+
+// Abort the copy and delete the temporary file.
+void delete_tempfile(int fdin, int fdout, char **tempname)
+{
+	close(fdin);
+	close(fdout);
+	unlink(*tempname);
+	free(*tempname);
+	*tempname = NULL;
+}
+
+// Copy the rest of the data and replace the original with the copy.
+void replace_tempfile(int fdin, int fdout, char **tempname)
+{
+	char *temp = xstrdup(*tempname);
+
+	temp[strlen(temp)-6]=0;
+	if (fdin != -1) {
+		xsendfile(fdin, fdout);
+		xclose(fdin);
+	}
+	xclose(fdout);
+	rename(*tempname, temp);
+	free(*tempname);
+	free(temp);
+	*tempname = NULL;
 }
diff --git a/lib/lib.h b/lib/lib.h
index 063c5d0..fdead96 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -84,6 +84,9 @@
 char *get_rawline(int fd, long *plen);
 char *get_line(int fd);
 void xsendfile(int in, int out);
+int copy_tempfile(int fdin, char *name, char **tempname);
+void delete_tempfile(int fdin, int fdout, char **tempname);
+void replace_tempfile(int fdin, int fdout, char **tempname);
 
 // getmountlist.c
 struct mtab_list {
diff --git a/toys/patch.c b/toys/patch.c
new file mode 100644
index 0000000..1ad983c
--- /dev/null
+++ b/toys/patch.c
@@ -0,0 +1,224 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * patch.c - Apply a "universal" diff.
+ *
+ * SUSv3 at http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html
+ * but who cares about "ed"?
+ *
+ * -u ignored
+ * -R reverse (remove applied hunks, apply removed hunks)
+ * -p num remove this many slashes from start of path (default = all)
+ *
+ * TODO:
+ * -b backup
+ * -l treat all whitespace as a single space
+ * -N ignore already applied
+ * -d chdir first
+ * -D define wrap #ifdef and #ifndef around changes
+ * -i patchfile apply patch from filename rather than stdin
+ * -o outfile output here instead of in place
+ * -r rejectfile write rejected hunks to this file
+ *
+ * -E remove empty files --remove-empty-files
+ * -f force (no questions asked)
+ * -F fuzz (number, default 2)
+ * [file] which file to patch
+ */
+
+#include "toys.h"
+
+#define TT toy.patch
+
+#define FLAG_REVERSE 1
+#define FLAG_PATHLEN 4
+
+static void do_line(void *data)
+{
+	struct double_list *dlist = (struct double_list *)data;
+
+	if (TT.state && *dlist->data != TT.state)
+		fdprintf(TT.fileout, "%s\n", dlist->data+(TT.state>1 ? 1 : 0));
+	free(dlist->data);
+	free(dlist);
+}
+
+
+static void dlist_add(struct double_list **list, char *data)
+{
+	struct double_list *line = xmalloc(sizeof(struct double_list));
+
+	line->data = data;
+	if (*list) {
+		line->next = *list;
+		line->prev = (*list)->prev;
+		(*list)->prev->next = line;
+		(*list)->prev = line;
+	} else *list = line->next = line->prev = line;
+}
+
+static void apply_hunk(void)
+{
+	struct double_list *plist, *temp, *buf;
+	int i = 0, backwards = 0, reverse = toys.optflags & FLAG_REVERSE;
+
+	TT.state = 0;
+
+	if (!TT.plines) return;
+	temp = buf = NULL;
+
+	// Hunk is complete, break doubly linked list so we can use singly linked
+	// traversal function.
+	TT.plines->prev->next = NULL;
+
+	// Trim extra context lines, if any.  If there aren't as many ending
+	// context lines as beginning lines, this isn't a valid hunk.
+	for (plist = TT.plines; plist; plist = plist->next) {
+		if (plist->data[0]==' ') {
+			if (i<TT.context) temp = plist;
+			i++;
+		} else i = 0;
+	}
+	if (i < TT.context) goto fail_hunk;
+	llist_free(temp->next, do_line);
+	temp->next = NULL;
+
+	// Search for a place to apply this hunk
+	plist = TT.plines;
+	buf = NULL;
+	i = 0;
+	for (;;) {
+		char *data = get_line(TT.filein);
+		TT.linenum++;
+
+		// If the file ended before we found a home for this hunk, fail.
+		if (!data) break;
+
+		dlist_add(&buf, data);
+		if (!backwards && *plist->data == "+-"[reverse]) {
+			backwards = 1;
+			if (!strcmp(data, plist->data+1))
+				fdprintf(1,"Possibly reversed hunk at %ld\n", TT.linenum);
+		}
+		while (*plist->data == "+-"[reverse]) plist = plist->next;
+		if (strcmp(data, plist->data+1)) {     // Ignore whitespace?
+			// Hunk doesn't go here, flush accumulated buffer so far.
+
+			buf->prev->next = NULL;
+			TT.state = 1;
+			llist_free(buf, do_line);
+			buf = NULL;
+			plist = TT.plines;
+		} else {
+			plist = plist->next;
+			if (!plist) {
+				// Got it.  Emit changed data.
+				TT.state = "-+"[reverse];
+				llist_free(TT.plines, do_line);
+				TT.plines = NULL;
+				buf->prev->next = NULL;
+				TT.state = 0;
+				llist_free(buf, do_line);
+				return;
+			}
+		}
+	}
+fail_hunk:
+	printf("Hunk FAILED.\n");
+
+	// If we got to this point, we've seeked to the end.  Discard changes to
+	// this file and advance to next file.
+
+	TT.state = 0;
+	llist_free(TT.plines, do_line);
+	TT.plines = 0;
+	if (buf) {
+		buf->prev->next = NULL;
+		llist_free(buf, do_line);
+	}
+	delete_tempfile(TT.filein, TT.fileout, &TT.tempfile);
+	TT.filein = -1;
+}
+
+// state 0: Not in a hunk, look for +++.
+// state 1: Found +++ file indicator, look for @@
+// state 2: In hunk: counting initial context lines
+// state 3: In hunk: getting body
+
+void patch_main(void)
+{
+	if (TT.infile) TT.filepatch = xopen(TT.infile, O_RDONLY);
+	else TT.filepatch = 0;
+	TT.filein = TT.fileout = -1;
+
+	// Loop through the lines in the patch
+	for(;;) {
+		char *patchline;
+
+		patchline = get_line(TT.filepatch);
+		if (!patchline) break;
+
+		// Are we processing a hunk?
+		if (TT.state >= 2) {
+			// Context line?
+			if (*patchline==' ' || *patchline=='+' || *patchline=='-') {
+				dlist_add(&TT.plines, patchline);
+
+				if (*patchline==' ' && TT.state==2) TT.context++;
+				else TT.state=3;
+
+				continue;
+			}
+		}
+
+		// If we have a hunk at this point, it's ready to apply.
+		apply_hunk();
+			
+		// Open a new file?
+		if (!strncmp("+++ ", patchline, 4)) {
+			int i;
+			char *s, *start;
+
+			// Finish old file.
+			if (TT.tempfile)
+				replace_tempfile(TT.filein, TT.fileout, &TT.tempfile);
+
+			// Trim date from end of filename (if any).  We don't care.
+			for (s = patchline+4; *s && *s!='\t'; s++)
+				if (*s=='\\' && s[1]) s++;
+			*s = i = 0;
+			for (s = start = patchline+4; *s;) {
+				if ((toys.optflags & FLAG_PATHLEN) && TT.prefix == i) break;
+				if (*(s++)=='/') {
+					start = s;
+					i++;
+				}
+			}
+
+			// If we've got a file to open, do so.
+			if (!(toys.optflags & FLAG_PATHLEN) || i <= TT.prefix) {
+				printf("patching %s\n", start);
+				TT.filein = xopen(start, O_RDWR);
+				TT.fileout = copy_tempfile(TT.filein, start, &TT.tempfile);
+				TT.state = 1;
+				TT.context = 0;
+				TT.linenum = 0;
+			}
+
+		// Start a new hunk?
+		} else if (TT.filein!=-1 && !strncmp("@@ ", patchline, 3)) {
+			TT.context = 0;
+			TT.state = 2;
+			sscanf(patchline+3, "%ld,%ld %ld,%ld", &TT.oldline,
+				&TT.oldlen, &TT.newline, &TT.newlen);
+			continue;
+		}
+
+		// This line is noise, discard it.
+		free(patchline);
+	}
+
+	// Flush pending hunk and flush data
+	apply_hunk();
+	if (TT.tempfile) replace_tempfile(TT.filein, TT.fileout, &TT.tempfile);
+	close(TT.filepatch);
+}
diff --git a/toys/toylist.h b/toys/toylist.h
index 9b99202..61e89c1 100644
--- a/toys/toylist.h
+++ b/toys/toylist.h
@@ -72,9 +72,9 @@
 	long prefix;
 
 	struct double_list *plines, *flines;
-	long oldline, oldlen, newline, newlen;
-	int context, state;
-	int filein, fileout, filepatch;
+	long oldline, oldlen, newline, newlen, linenum;
+	int context, state, filein, fileout, filepatch;
+	char *tempfile;
 };
 
 struct sleep_data {