Commit Diff


commit - 55afbf19d730ecbbee0228be686ff3f5e57a13e8
commit + ad242220adab01ebd55a97a5701733d7256b2d32
blob - bbe1fafa84266d496fa4f4aacc7acb3f45f14ae7
blob + 2f31fc45cd1c490ae0c867d0db07e38ad57d3d1a
--- got/Makefile
+++ got/Makefile
@@ -6,7 +6,9 @@ SRCS=		got.c blame.c commit_graph.c delta.c diff.c dif
 		object_idset.c object_parse.c opentemp.c path.c pack.c \
 		privsep.c reference.c repository.c sha1.c worktree.c inflate.c
 
-CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
+GOT_LIBEXECDIR = ${HOME}/bin
+CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib \
+	-DGOT_LIBEXECDIR=${GOT_LIBEXECDIR}
 LDADD = -lutil -lz
 DPADD = ${LIBZ} ${LIBUTIL}
 .if defined(PROFILE)
blob - b44481ec74030fde08672be477030467b239550e
blob + be58cf339b920db7909cf38049c0b61c5d291788
--- got/got.c
+++ got/got.c
@@ -190,7 +190,8 @@ cmd_checkout(int argc, char *argv[])
 	argv += optind;
 
 #ifndef PROFILE
-	if (pledge("stdio rpath wpath cpath flock proc", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath flock proc exec sendfd", NULL)
+	    == -1)
 		err(1, "pledge");
 #endif
 	if (argc == 1) {
@@ -507,7 +508,8 @@ cmd_log(int argc, char *argv[])
 	const char *errstr;
 
 #ifndef PROFILE
-	if (pledge("stdio rpath wpath cpath flock proc", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath flock proc exec sendfd", NULL)
+	    == -1)
 		err(1, "pledge");
 #endif
 
@@ -623,8 +625,12 @@ done:
 	if (obj)
 		got_object_close(obj);
 	free(id);
-	if (repo)
-		got_repo_close(repo);
+	if (repo) {
+		const struct got_error *repo_error;
+		repo_error = got_repo_close(repo);
+		if (error == NULL)
+			error = repo_error;
+	}
 	return error;
 }
 
@@ -648,7 +654,8 @@ cmd_diff(int argc, char *argv[])
 	int ch;
 
 #ifndef PROFILE
-	if (pledge("stdio rpath wpath cpath flock proc", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath flock proc exec sendfd", NULL)
+	    == -1)
 		err(1, "pledge");
 #endif
 
@@ -731,8 +738,12 @@ done:
 		free(id1);
 	if (id2)
 		free(id2);
-	if (repo)
-		got_repo_close(repo);
+	if (repo) {
+		const struct got_error *repo_error;
+		repo_error = got_repo_close(repo);
+		if (error == NULL)
+			error = repo_error;
+	}
 	return error;
 }
 
@@ -755,7 +766,8 @@ cmd_blame(int argc, char *argv[])
 	int ch;
 
 #ifndef PROFILE
-	if (pledge("stdio rpath wpath cpath flock proc", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath flock proc exec sendfd", NULL)
+	    == -1)
 		err(1, "pledge");
 #endif
 
@@ -830,8 +842,12 @@ done:
 	free(repo_path);
 	free(cwd);
 	free(commit_id);
-	if (repo)
-		got_repo_close(repo);
+	if (repo) {
+		const struct got_error *repo_error;
+		repo_error = got_repo_close(repo);
+		if (error == NULL)
+			error = repo_error;
+	}
 	return error;
 }
 
blob - 3f8ef8cb512966482a3b4410b0c4b51505fa5063
blob + 2fd4ddd312ea5fb19ea1ae92f79f49a045bb517b
--- include/got_repository.h
+++ include/got_repository.h
@@ -18,7 +18,7 @@ struct got_repository;
 
 /* Open and close repositories. */
 const struct got_error *got_repo_open(struct got_repository**, const char *);
-void got_repo_close(struct got_repository*);
+const struct got_error *got_repo_close(struct got_repository*);
 
 /*
  * Obtain paths to various directories within a repository.
blob - /dev/null
blob + b1d1830b01b36ea576c29ee80aba577fc0adf4ae (mode 644)
--- /dev/null
+++ lib/got_lib_object_parse.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018 Stefan Sperling <stsp@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.
+ */
+
+struct got_commit_object *got_object_commit_alloc_partial(void);
+struct got_tree_entry *got_alloc_tree_entry_partial(void);
+const struct got_error *got_object_read_header_privsep(struct got_object**,
+    struct got_repository *repo, int);
+const struct got_error *got_object_read_blob_privsep(size_t *, int, int,
+    struct got_repository *repo);
+const struct got_error *got_object_read_commit_privsep(
+    struct got_commit_object **, struct got_object *, int,
+    struct got_repository *);
+const struct got_error *got_object_read_tree_privsep(struct got_tree_object **,
+    struct got_object *, int, struct got_repository *);
+
+const struct got_error *got_object_parse_commit(struct got_commit_object **,
+    char *, size_t);
+const struct got_error *got_object_parse_tree(struct got_tree_object **,
+    uint8_t *, size_t);
+const struct got_error *got_read_file_to_mem(uint8_t **, size_t *, FILE *);
+
+void got_object_tree_entry_close(struct got_tree_entry *);
blob - 67a687b619f5daf43ec02cb885bec570058db386
blob + 7a7a85a25f9d4f084f4143150b87d65af7e4485f
--- lib/got_lib_privsep.h
+++ lib/got_lib_privsep.h
@@ -24,18 +24,41 @@
  * if necessary. File descriptors are used in cases where this is impractical,
  * such as when accessing pack files or when transferring large blobs.
  *
- * We currently do not exec(2) after a fork(2).
- * To achieve fork+exec, relevant parts of our library functionality could
- * be made accessible via separate executables in a libexec directory.
+ * We exec(2) after a fork(2). Parts of our library functionality are
+ * accessible via separate executables in a libexec directory.
  */
 
+#define GOT_IMSG_FD_CHILD (STDERR_FILENO + 1)
+
+#ifndef GOT_LIBEXECDIR
+#define GOT_LIBEXECDIR /usr/libexec
+#endif
+
+/* Names of helper programs in libexec directory */
+#define GOT_PROG_READ_OBJECT	got-read-object
+#define GOT_PROG_READ_TREE	got-read-tree
+#define GOT_PROG_READ_COMMIT	got-read-commit
+#define GOT_PROG_READ_BLOB	got-read-blob
+
+#define GOT_STRINGIFY(x) #x
+#define GOT_STRINGVAL(x) GOT_STRINGIFY(x)
+
+/* Paths to helper programs in libexec directory */
+#define GOT_PATH_PROG_READ_OBJECT \
+	GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_OBJECT)
+#define GOT_PATH_PROG_READ_TREE \
+	GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_TREE)
+#define GOT_PATH_PROG_READ_COMMIT \
+	GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_COMMIT)
+#define GOT_PATH_PROG_READ_BLOB \
+	GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_BLOB)
+
 enum got_imsg_type {
 	/* An error occured while processing a request. */
 	GOT_IMSG_ERROR,
 
-	/* Messages for transmitting deltas and associated delta streams. */
-	GOT_IMSG_DELTA,
-	GOT_IMSG_DELTA_STREAM,
+	/* Stop the child process. */
+	GOT_IMSG_STOP,
 
 	/*
 	 * Messages concerned with read access to objects in a repository.
@@ -45,11 +68,19 @@ enum got_imsg_type {
 	 * separate process which runs under pledge("stdio").
 	 * This sandboxes our own repository parsing code, as well as zlib.
 	 */
+	GOT_IMSG_OBJECT_REQUEST,
 	GOT_IMSG_OBJECT,
+	GOT_IMSG_COMMIT_REQUEST,
 	GOT_IMSG_COMMIT,
+	GOT_IMSG_TREE_REQUEST,
 	GOT_IMSG_TREE,
 	GOT_IMSG_TREE_ENTRY,
+	GOT_IMSG_BLOB_REQUEST,
+	GOT_IMSG_BLOB_OUTFD,
 	GOT_IMSG_BLOB,
+	/* Messages for transmitting deltas and associated delta streams: */
+	GOT_IMSG_DELTA,
+	GOT_IMSG_DELTA_STREAM,
 };
 
 /* Structure for GOT_IMSG_ERROR. */
@@ -84,7 +115,10 @@ struct got_imsg_delta_stream {
 	 */
 };
 
-/* Structure for GOT_IMSG_OBJECT data. */
+/*
+ * Structure for GOT_IMSG_TREE_REQUEST, GOT_IMSG_COMMIT_REQUEST,
+ * and GOT_IMSG_OBJECT data.
+ */
 struct got_imsg_object {
 	/* These fields are the same as in struct got_object. */
 	int type;
@@ -132,7 +166,13 @@ struct got_imsg_blob {
 	size_t size;
 };
 
+const struct got_error *got_privsep_send_stop(int);
+const struct got_error *got_privsep_recv_imsg(struct imsg *, struct imsgbuf *,
+    size_t);
 void got_privsep_send_error(struct imsgbuf *, const struct got_error *);
+const struct got_error *got_privsep_send_obj_req(struct imsgbuf *, int,
+    struct got_object *);
+const struct got_error *got_privsep_send_blob_req(struct imsgbuf *, int, int);
 const struct got_error *got_privsep_send_obj(struct imsgbuf *,
     struct got_object *, int);
 const struct got_error *got_privsep_recv_obj(struct got_object **,
blob - 24122e83d302d604ba05b7698aaad60bc5ea8e16
blob + ea058702e40e67e713aa1321c05e22edb72d9700
--- lib/got_lib_repository.h
+++ lib/got_lib_repository.h
@@ -44,6 +44,11 @@ struct got_object_cache {
 	int cache_miss;
 };
 
+struct got_privsep_child {
+	int imsg_fd;
+	pid_t pid;
+};
+
 struct got_repository {
 	char *path;
 	char *path_git_dir;
@@ -54,7 +59,17 @@ struct got_repository {
 	/* Open file handles for pack files. */
 	struct got_pack packs[GOT_PACK_CACHE_SIZE];
 
-	/* Caches for opened objects. */
+	/* Handles to child processes for reading pack files. */
+	struct got_privsep_child pack_privsep_children[GOT_PACK_CACHE_SIZE];
+
+	/* Handles to child processes for reading loose objects. */
+	 struct got_privsep_child privsep_children[4];
+#define GOT_REPO_PRIVSEP_CHILD_OBJECT	0
+#define GOT_REPO_PRIVSEP_CHILD_COMMIT	1
+#define GOT_REPO_PRIVSEP_CHILD_TREE	2
+#define GOT_REPO_PRIVSEP_CHILD_BLOB	3
+
+	/* Caches for open objects. */
 	struct got_object_cache objcache;
 	struct got_object_cache treecache;
 	struct got_object_cache commitcache;
blob - e8d569b12eb5b9f74cbe5fdf362abe1b0c79c118
blob + 11b768f43c29b5eed657ffb5684b11607d28a63b
--- lib/object.c
+++ lib/object.c
@@ -45,8 +45,8 @@
 #include "got_lib_path.h"
 #include "got_lib_inflate.h"
 #include "got_lib_object.h"
-#include "got_lib_object_parse.h"
 #include "got_lib_privsep.h"
+#include "got_lib_object_parse.h"
 #include "got_lib_repository.h"
 
 #ifndef MIN
@@ -194,7 +194,7 @@ got_object_open(struct got_object **obj, struct got_re
 		if (*obj == NULL)
 			err = got_error(GOT_ERR_NO_OBJ);
 	} else {
-		err = got_object_read_header_privsep(obj, fd);
+		err = got_object_read_header_privsep(obj, repo, fd);
 		if (err)
 			goto done;
 		memcpy((*obj)->id.sha1, id->sha1, SHA1_DIGEST_LENGTH);
@@ -296,7 +296,7 @@ got_object_commit_open(struct got_commit_object **comm
 		err = open_loose_object(&fd, obj, repo);
 		if (err)
 			return err;
-		err = got_object_read_commit_privsep(commit, obj, fd);
+		err = got_object_read_commit_privsep(commit, obj, fd, repo);
 		close(fd);
 	}
 
@@ -337,7 +337,7 @@ got_object_tree_open(struct got_tree_object **tree,
 		err = open_loose_object(&fd, obj, repo);
 		if (err)
 			return err;
-		err = got_object_read_tree_privsep(tree, obj, fd);
+		err = got_object_read_tree_privsep(tree, obj, fd, repo);
 		close(fd);
 	}
 
@@ -420,7 +420,7 @@ got_object_blob_open(struct got_blob_object **blob,
 			goto done;
 		}
 
-		err = got_object_read_blob_privsep(&size, outfd, infd);
+		err = got_object_read_blob_privsep(&size, outfd, infd, repo);
 		close(infd);
 		if (err)
 			goto done;
blob - 3b98216fb5dfe9e4e4624f8f9e53ed461cea01e9
blob + d4f209240efa15b01cc928f166088b0c268144ef
--- lib/object_parse.c
+++ lib/object_parse.c
@@ -32,6 +32,7 @@
 #include <limits.h>
 #include <imsg.h>
 #include <time.h>
+#include <unistd.h>
 
 #include "got_error.h"
 #include "got_object.h"
@@ -40,10 +41,11 @@
 
 #include "got_lib_sha1.h"
 #include "got_lib_delta.h"
+#include "got_lib_privsep.h"
 #include "got_lib_pack.h"
 #include "got_lib_inflate.h"
 #include "got_lib_object.h"
-#include "got_lib_privsep.h"
+#include "got_lib_repository.h"
 
 #ifndef nitems
 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
@@ -88,164 +90,56 @@ got_object_qid_free(struct got_object_qid *qid)
 }
 
 static const struct got_error *
-parse_object_header(struct got_object **obj, char *buf, size_t len)
+request_object(struct got_object **obj, struct got_repository *repo, int fd)
 {
-	const char *obj_tags[] = {
-		GOT_OBJ_TAG_COMMIT,
-		GOT_OBJ_TAG_TREE,
-		GOT_OBJ_TAG_BLOB
-	};
-	const int obj_types[] = {
-		GOT_OBJ_TYPE_COMMIT,
-		GOT_OBJ_TYPE_TREE,
-		GOT_OBJ_TYPE_BLOB,
-	};
-	int type = 0;
-	size_t size = 0, hdrlen = 0;
-	int i;
-	char *p = strchr(buf, '\0');
+	const struct got_error *err = NULL;
+	struct imsgbuf ibuf;
 
-	if (p == NULL)
-		return got_error(GOT_ERR_BAD_OBJ_HDR);
+	imsg_init(&ibuf,
+	    repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_OBJECT].imsg_fd);
 
-	hdrlen = strlen(buf) + 1 /* '\0' */;
-
-	for (i = 0; i < nitems(obj_tags); i++) {
-		const char *tag = obj_tags[i];
-		size_t tlen = strlen(tag);
-		const char *errstr;
-
-		if (strncmp(buf, tag, tlen) != 0)
-			continue;
-
-		type = obj_types[i];
-		if (len <= tlen)
-			return got_error(GOT_ERR_BAD_OBJ_HDR);
-		size = strtonum(buf + tlen, 0, LONG_MAX, &errstr);
-		if (errstr != NULL)
-			return got_error(GOT_ERR_BAD_OBJ_HDR);
-		break;
-	}
-
-	if (type == 0)
-		return got_error(GOT_ERR_BAD_OBJ_HDR);
-
-	*obj = calloc(1, sizeof(**obj));
-	if (*obj == NULL)
-		return got_error_from_errno();
-	(*obj)->type = type;
-	(*obj)->hdrlen = hdrlen;
-	(*obj)->size = size;
-	return NULL;
-}
-
-static const struct got_error *
-read_object_header(struct got_object **obj, int fd)
-{
-	const struct got_error *err;
-	struct got_zstream_buf zb;
-	char *buf;
-	const size_t zbsize = 64;
-	size_t outlen, totlen;
-	int nbuf = 1;
-
-	buf = malloc(zbsize);
-	if (buf == NULL)
-		return got_error_from_errno();
-
-	err = got_inflate_init(&zb, buf, zbsize);
+	err = got_privsep_send_obj_req(&ibuf, fd, NULL);
 	if (err)
-		return err;
-
-	totlen = 0;
-	do {
-		err = got_inflate_read_fd(&zb, fd, &outlen);
-		if (err)
-			goto done;
-		if (outlen == 0)
-			break;
-		totlen += outlen;
-		if (strchr(zb.outbuf, '\0') == NULL) {
-			char *newbuf;
-			nbuf++;
-			newbuf = recallocarray(buf, nbuf - 1, nbuf, zbsize);
-			if (newbuf == NULL) {
-				err = got_error_from_errno();
-				goto done;
-			}
-			buf = newbuf;
-			zb.outbuf = newbuf + totlen;
-			zb.outlen = (nbuf * zbsize) - totlen;
-		}
-	} while (strchr(zb.outbuf, '\0') == NULL);
-
-	err = parse_object_header(obj, buf, totlen);
+		goto done;
+	err = got_privsep_recv_obj(obj, &ibuf);
 done:
-	free(buf);
-	got_inflate_end(&zb);
+	imsg_clear(&ibuf);
 	return err;
 }
 
 static void
-read_object_header_privsep_child(int obj_fd, int imsg_fds[2])
+exec_privsep_child(int imsg_fds[2], const char *path)
 {
-	const struct got_error *err = NULL;
-	struct got_object *obj = NULL;
-	struct imsgbuf ibuf;
-	int status = 0;
-
-	setproctitle("read object header");
 	close(imsg_fds[0]);
-	imsg_init(&ibuf, imsg_fds[1]);
 
-	/* revoke access to most system calls */
-	if (pledge("stdio", NULL) == -1) {
-		err = got_error_from_errno();
-		goto done;
+	if (dup2(imsg_fds[1], GOT_IMSG_FD_CHILD) == -1) {
+		fprintf(stderr, "%s: %s\n", getprogname(),
+		    strerror(errno));
+		_exit(1);
 	}
+	if (closefrom(GOT_IMSG_FD_CHILD + 1) == -1) {
+		fprintf(stderr, "%s: %s\n", getprogname(),
+		    strerror(errno));
+		_exit(1);
+	}
 
-	err = read_object_header(&obj, obj_fd);
-	if (err)
-		goto done;
-
-	err = got_privsep_send_obj(&ibuf, obj, 0);
-done:
-	if (obj)
-		got_object_close(obj);
-	if (err) {
-		got_privsep_send_error(&ibuf, err);
-		status = 1;
+	if (execl(path, path, (char *)NULL) == -1) {
+		fprintf(stderr, "%s: %s: %s\n", getprogname(), path,
+		    strerror(errno));
+		_exit(1);
 	}
-	close(obj_fd);
-	imsg_clear(&ibuf);
-	close(imsg_fds[1]);
-	_exit(status);
 }
 
-static const struct got_error *
-wait_for_child(pid_t pid)
-{
-	int child_status;
-
-	waitpid(pid, &child_status, 0);
-
-	if (!WIFEXITED(child_status))
-		return got_error(GOT_ERR_PRIVSEP_DIED);
-
-	if (WEXITSTATUS(child_status) != 0)
-		return got_error(GOT_ERR_PRIVSEP_EXIT);
-
-	return NULL;
-}
-
 const struct got_error *
-got_object_read_header_privsep(struct got_object **obj, int fd)
+got_object_read_header_privsep(struct got_object **obj,
+    struct got_repository *repo, int obj_fd)
 {
-	struct imsgbuf parent_ibuf;
 	int imsg_fds[2];
-	const struct got_error *err = NULL, *err_child = NULL;
 	pid_t pid;
 
+	if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_OBJECT].imsg_fd != -1)
+		return request_object(obj, repo, obj_fd);
+
 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
 		return got_error_from_errno();
 
@@ -253,17 +147,16 @@ got_object_read_header_privsep(struct got_object **obj
 	if (pid == -1)
 		return got_error_from_errno();
 	else if (pid == 0) {
-		read_object_header_privsep_child(fd, imsg_fds);
+		exec_privsep_child(imsg_fds, GOT_PATH_PROG_READ_OBJECT);
 		/* not reached */
 	}
 
 	close(imsg_fds[1]);
-	imsg_init(&parent_ibuf, imsg_fds[0]);
-	err = got_privsep_recv_obj(obj, &parent_ibuf);
-	imsg_clear(&parent_ibuf);
-	err_child = wait_for_child(pid);
-	close(imsg_fds[0]);
-	return err ? err : err_child;
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_OBJECT].imsg_fd =
+	    imsg_fds[0];
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_OBJECT].pid = pid;
+
+	return request_object(obj, repo, obj_fd);
 }
 
 struct got_commit_object *
@@ -532,8 +425,8 @@ done:
 	return err;
 }
 
-static void
-tree_entry_close(struct got_tree_entry *te)
+void
+got_object_tree_entry_close(struct got_tree_entry *te)
 {
 	free(te->id);
 	free(te->name);
@@ -554,7 +447,7 @@ got_object_tree_close(struct got_tree_object *tree)
 	while (!SIMPLEQ_EMPTY(&tree->entries.head)) {
 		te = SIMPLEQ_FIRST(&tree->entries.head);
 		SIMPLEQ_REMOVE_HEAD(&tree->entries.head, entry);
-		tree_entry_close(te);
+		got_object_tree_entry_close(te);
 	}
 
 	free(tree);
@@ -622,7 +515,7 @@ parse_tree_entry(struct got_tree_entry **te, size_t *e
 	*elen += SHA1_DIGEST_LENGTH;
 done:
 	if (err) {
-		tree_entry_close(*te);
+		got_object_tree_entry_close(*te);
 		*te = NULL;
 	}
 	return err;
@@ -661,8 +554,8 @@ got_object_parse_tree(struct got_tree_object **tree, u
 	return NULL;
 }
 
-static const struct got_error *
-read_to_mem(uint8_t **outbuf, size_t *outlen, FILE *f)
+const struct got_error *
+got_read_file_to_mem(uint8_t **outbuf, size_t *outlen, FILE *f)
 {
 	const struct got_error *err = NULL;
 	static const size_t blocksize = 512;
@@ -711,88 +604,35 @@ done:
 }
 
 static const struct got_error *
-read_commit_object(struct got_commit_object **commit, struct got_object *obj,
-    FILE *f)
+request_commit(struct got_commit_object **commit, struct got_repository *repo,
+    struct got_object *obj, int fd)
 {
 	const struct got_error *err = NULL;
-	size_t len;
-	uint8_t *p;
+	struct imsgbuf ibuf;
 
-	if (obj->flags & GOT_OBJ_FLAG_PACKED)
-		err = read_to_mem(&p, &len, f);
-	else
-		err = got_inflate_to_mem(&p, &len, f);
-	if (err)
-		return err;
-
-	if (len < obj->hdrlen + obj->size) {
-		err = got_error(GOT_ERR_BAD_OBJ_DATA);
-		goto done;
-	}
-
-	/* Skip object header. */
-	len -= obj->hdrlen;
-	err = got_object_parse_commit(commit, p + obj->hdrlen, len);
-	free(p);
-done:
-	return err;
-}
-
-static void
-read_commit_object_privsep_child(struct got_object *obj, int obj_fd,
-    int imsg_fds[2])
-{
-	const struct got_error *err = NULL;
-	struct got_commit_object *commit = NULL;
-	struct imsgbuf ibuf;
-	FILE *f = NULL;
-	int status = 0;
-
-	setproctitle("read commit object");
-	close(imsg_fds[0]);
-	imsg_init(&ibuf, imsg_fds[1]);
+	imsg_init(&ibuf, 
+	    repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_COMMIT].imsg_fd);
 
-	/* revoke access to most system calls */
-	if (pledge("stdio", NULL) == -1) {
-		err = got_error_from_errno();
-		goto done;
-	}
-
-	f = fdopen(obj_fd, "rb");
-	if (f == NULL) {
-		err = got_error_from_errno();
-		close(obj_fd);
-		goto done;
-	}
-
-	err = read_commit_object(&commit, obj, f);
+	err = got_privsep_send_obj_req(&ibuf, fd,obj);
 	if (err)
 		goto done;
 
-	err = got_privsep_send_commit(&ibuf, commit);
+	err = got_privsep_recv_commit(commit, &ibuf);
 done:
-	if (commit)
-		got_object_commit_close(commit);
-	if (err) {
-		got_privsep_send_error(&ibuf, err);
-		status = 1;
-	}
-	if (f)
-		fclose(f);
 	imsg_clear(&ibuf);
-	close(imsg_fds[1]);
-	_exit(status);
+	return err;
 }
 
 const struct got_error *
 got_object_read_commit_privsep(struct got_commit_object **commit,
-    struct got_object *obj, int fd)
+    struct got_object *obj, int obj_fd, struct got_repository *repo)
 {
-	const struct got_error *err = NULL, *err_child = NULL;
-	struct imsgbuf parent_ibuf;
 	int imsg_fds[2];
 	pid_t pid;
 
+	if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_COMMIT].imsg_fd != -1)
+		return request_commit(commit, repo, obj, obj_fd);
+
 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
 		return got_error_from_errno();
 
@@ -800,101 +640,48 @@ got_object_read_commit_privsep(struct got_commit_objec
 	if (pid == -1)
 		return got_error_from_errno();
 	else if (pid == 0) {
-		read_commit_object_privsep_child(obj, fd, imsg_fds);
+		exec_privsep_child(imsg_fds, GOT_PATH_PROG_READ_COMMIT);
 		/* not reached */
 	}
 
 	close(imsg_fds[1]);
-	imsg_init(&parent_ibuf, imsg_fds[0]);
-	err = got_privsep_recv_commit(commit, &parent_ibuf);
-	imsg_clear(&parent_ibuf);
-	err_child = wait_for_child(pid);
-	close(imsg_fds[0]);
-	return err ? err : err_child;
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_COMMIT].imsg_fd =
+	    imsg_fds[0];
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_COMMIT].pid = pid;
+
+	return request_commit(commit, repo, obj, obj_fd);
 }
 
 static const struct got_error *
-read_tree_object(struct got_tree_object **tree, struct got_object *obj, FILE *f)
+request_tree(struct got_tree_object **tree, struct got_repository *repo,
+    struct got_object *obj, int fd)
 {
 	const struct got_error *err = NULL;
-	size_t len;
-	uint8_t *p;
-
-	if (obj->flags & GOT_OBJ_FLAG_PACKED)
-		err = read_to_mem(&p, &len, f);
-	else
-		err = got_inflate_to_mem(&p, &len, f);
-	if (err)
-		return err;
-
-	if (len < obj->hdrlen + obj->size) {
-		err = got_error(GOT_ERR_BAD_OBJ_DATA);
-		goto done;
-	}
-
-	/* Skip object header. */
-	len -= obj->hdrlen;
-	err = got_object_parse_tree(tree, p + obj->hdrlen, len);
-	free(p);
-done:
-	return err;
-}
-
-static void
-read_tree_object_privsep_child(struct got_object *obj, int obj_fd,
-    int imsg_fds[2])
-{
-	const struct got_error *err = NULL;
-	struct got_tree_object *tree = NULL;
 	struct imsgbuf ibuf;
-	FILE *f = NULL;
-	int status = 0;
 
-	setproctitle("read tree object");
-	close(imsg_fds[0]);
-	imsg_init(&ibuf, imsg_fds[1]);
+	imsg_init(&ibuf, 
+	    repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TREE].imsg_fd);
 
-	/* revoke access to most system calls */
-	if (pledge("stdio", NULL) == -1) {
-		err = got_error_from_errno();
-		goto done;
-	}
-
-	f = fdopen(obj_fd, "rb");
-	if (f == NULL) {
-		err = got_error_from_errno();
-		close(obj_fd);
-		goto done;
-	}
-
-	err = read_tree_object(&tree, obj, f);
+	err = got_privsep_send_obj_req(&ibuf, fd,obj);
 	if (err)
 		goto done;
 
-	err = got_privsep_send_tree(&ibuf, tree);
+	err = got_privsep_recv_tree(tree, &ibuf);
 done:
-	if (tree)
-		got_object_tree_close(tree);
-	if (err) {
-		got_privsep_send_error(&ibuf, err);
-		status = 1;
-	}
-	if (f)
-		fclose(f);
 	imsg_clear(&ibuf);
-	close(imsg_fds[1]);
-	_exit(status);
+	return err;
 }
 
 const struct got_error *
 got_object_read_tree_privsep(struct got_tree_object **tree,
-    struct got_object *obj, int fd)
+    struct got_object *obj, int obj_fd, struct got_repository *repo)
 {
-	const struct got_error *err = NULL, *err_child = NULL;
-	struct imsgbuf parent_ibuf;
 	int imsg_fds[2];
 	pid_t pid;
 
+	if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TREE].imsg_fd != -1)
+		return request_tree(tree, repo, obj, obj_fd);
+
 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
 		return got_error_from_errno();
 
@@ -902,69 +689,57 @@ got_object_read_tree_privsep(struct got_tree_object **
 	if (pid == -1)
 		return got_error_from_errno();
 	else if (pid == 0) {
-		read_tree_object_privsep_child(obj, fd, imsg_fds);
+		exec_privsep_child(imsg_fds, GOT_PATH_PROG_READ_TREE);
 		/* not reached */
 	}
 
 	close(imsg_fds[1]);
-	imsg_init(&parent_ibuf, imsg_fds[0]);
-	err = got_privsep_recv_tree(tree, &parent_ibuf);
-	imsg_clear(&parent_ibuf);
-	err_child = wait_for_child(pid);
-	close(imsg_fds[0]);
-	return err ? err : err_child;
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TREE].imsg_fd =
+	    imsg_fds[0];
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TREE].pid = pid;
+
+	return request_tree(tree, repo, obj, obj_fd);
 }
 
 static const struct got_error *
-read_blob_object_privsep_child(int outfd, int infd, int imsg_fds[2])
+request_blob(size_t *size, int outfd, int infd, struct got_repository *repo)
 {
 	const struct got_error *err = NULL;
 	struct imsgbuf ibuf;
-	int status = 0;
-	size_t size;
-	FILE *infile = NULL;
+	int outfd_child;
 
-	setproctitle("read blob object");
-	close(imsg_fds[0]);
-	imsg_init(&ibuf, imsg_fds[1]);
+	outfd_child = dup(outfd);
+	if (outfd_child == -1)
+		return got_error_from_errno();
 
-	/* revoke access to most system calls */
-	if (pledge("stdio", NULL) == -1) {
-		err = got_error_from_errno();
-		goto done;
-	}
+	imsg_init(&ibuf, 
+	    repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_BLOB].imsg_fd);
 
-	infile = fdopen(infd, "rb");
-	if (infile == NULL) {
-		err = got_error_from_errno();
-		close(infd);
+	err = got_privsep_send_blob_req(&ibuf, outfd_child, infd);
+	if (err)
 		goto done;
-	}
-	err = got_inflate_to_fd(&size, infile, outfd);
-	fclose(infile);
+
+	err = got_privsep_recv_blob(size, &ibuf);
 	if (err)
 		goto done;
 
-	err = got_privsep_send_blob(&ibuf, size);
+	if (lseek(outfd, SEEK_SET, 0) == -1)
+		err = got_error_from_errno();
 done:
-	if (err) {
-		got_privsep_send_error(&ibuf, err);
-		status = 1;
-	}
-	close(outfd);
 	imsg_clear(&ibuf);
-	close(imsg_fds[1]);
-	_exit(status);
+	return err;
 }
 
 const struct got_error *
-got_object_read_blob_privsep(size_t *size, int outfd, int infd)
+got_object_read_blob_privsep(size_t *size, int outfd, int infd,
+    struct got_repository *repo)
 {
-	struct imsgbuf parent_ibuf;
 	int imsg_fds[2];
-	const struct got_error *err = NULL, *err_child = NULL;
 	pid_t pid;
 
+	if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_BLOB].imsg_fd != -1)
+		return request_blob(size, outfd, infd, repo);
+
 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
 		return got_error_from_errno();
 
@@ -972,17 +747,14 @@ got_object_read_blob_privsep(size_t *size, int outfd, 
 	if (pid == -1)
 		return got_error_from_errno();
 	else if (pid == 0) {
-		read_blob_object_privsep_child(outfd, infd, imsg_fds);
+		exec_privsep_child(imsg_fds, GOT_PATH_PROG_READ_BLOB);
 		/* not reached */
 	}
 
 	close(imsg_fds[1]);
-	imsg_init(&parent_ibuf, imsg_fds[0]);
-	err = got_privsep_recv_blob(size, &parent_ibuf);
-	imsg_clear(&parent_ibuf);
-	err_child = wait_for_child(pid);
-	close(imsg_fds[0]);
-	if (lseek(outfd, SEEK_SET, 0) == -1)
-		err = got_error_from_errno();
-	return err ? err : err_child;
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_BLOB].imsg_fd =
+	    imsg_fds[0];
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_BLOB].pid = pid;
+
+	return request_blob(size, outfd, infd, repo);
 }
blob - 7b2c957fd762f6d53cbcf3927a6b89c82d207f12
blob + e01100b024d9a4e2277b55413469962af808b8c4
--- lib/privsep.c
+++ lib/privsep.c
@@ -87,8 +87,8 @@ read_imsg(struct imsgbuf *ibuf)
 	return NULL;
 }
 
-static const struct got_error *
-recv_one_imsg(struct imsg *imsg, struct imsgbuf *ibuf, size_t min_datalen)
+const struct got_error *
+got_privsep_recv_imsg(struct imsg *imsg, struct imsgbuf *ibuf, size_t min_datalen)
 {
 	const struct got_error *err;
 	ssize_t n;
@@ -174,9 +174,87 @@ flush_imsg(struct imsgbuf *ibuf)
 		return got_error_from_errno();
 
 	return NULL;
+}
+
+const struct got_error *
+got_privsep_send_stop(int fd)
+{
+	const struct got_error *err = NULL;
+	struct imsgbuf ibuf;
+
+	imsg_init(&ibuf, fd);
+
+	if (imsg_compose(&ibuf, GOT_IMSG_STOP, 0, 0, -1, NULL, 0) == -1)
+		return got_error_from_errno();
+
+	err = flush_imsg(&ibuf);
+	imsg_clear(&ibuf);
+	return err;
+}
+
+const struct got_error *
+got_privsep_send_obj_req(struct imsgbuf *ibuf, int fd, struct got_object *obj)
+{
+	struct got_imsg_object iobj, *iobjp = NULL;
+	size_t iobj_size = 0;
+	int imsg_code = GOT_IMSG_OBJECT_REQUEST;
+
+	if (obj) {
+		switch (obj->type) {
+		case GOT_OBJ_TYPE_TREE:
+			imsg_code = GOT_IMSG_TREE_REQUEST;
+			break;
+		case GOT_OBJ_TYPE_COMMIT:
+			imsg_code = GOT_IMSG_COMMIT_REQUEST;
+			break;
+		default:
+			return got_error(GOT_ERR_OBJ_TYPE);
+		}
+
+		iobj.type = obj->type;
+		iobj.flags = obj->flags;
+		iobj.hdrlen = obj->hdrlen;
+		iobj.size = obj->size;
+		iobj.ndeltas = 0;
+
+		iobjp = &iobj;
+		iobj_size = sizeof(iobj);
+	}
+
+	if (imsg_compose(ibuf, imsg_code, 0, 0, fd, iobjp, iobj_size) == -1)
+		return got_error_from_errno();
+
+	return flush_imsg(ibuf);
 }
 
 const struct got_error *
+got_privsep_send_blob_req(struct imsgbuf *ibuf, int outfd, int infd)
+{
+	const struct got_error *err = NULL;
+
+	if (imsg_compose(ibuf, GOT_IMSG_BLOB_REQUEST, 0, 0, infd, NULL, 0)
+	    == -1) {
+		close(infd);
+		close(outfd);
+		return got_error_from_errno();
+	}
+
+	err = flush_imsg(ibuf);
+	if (err) {
+		close(outfd);
+		return err;
+	}
+
+	if (imsg_compose(ibuf, GOT_IMSG_BLOB_OUTFD, 0, 0, outfd, NULL, 0)
+	    == -1) {
+		close(outfd);
+		return got_error_from_errno();
+	}
+
+	return flush_imsg(ibuf);
+}
+
+const struct got_error *
 got_privsep_send_obj(struct imsgbuf *ibuf, struct got_object *obj, int ndeltas)
 {
 	struct got_imsg_object iobj;
@@ -211,7 +289,7 @@ got_privsep_recv_obj(struct got_object **obj, struct i
 
 	*obj = NULL;
 
-	err = recv_one_imsg(&imsg, ibuf, min_datalen);
+	err = got_privsep_recv_imsg(&imsg, ibuf, min_datalen);
 	if (err)
 		return err;
 
@@ -326,7 +404,7 @@ got_privsep_recv_commit(struct got_commit_object **com
 
 	*commit = NULL;
 
-	err = recv_one_imsg(&imsg, ibuf, min_datalen);
+	err = got_privsep_recv_imsg(&imsg, ibuf, min_datalen);
 	if (err)
 		return err;
 
@@ -641,7 +719,7 @@ got_privsep_recv_blob(size_t *size, struct imsgbuf *ib
 	struct got_imsg_blob iblob;
 	size_t datalen;
 
-	err = recv_one_imsg(&imsg, ibuf, 0);
+	err = got_privsep_recv_imsg(&imsg, ibuf, 0);
 	if (err)
 		return err;
 
blob - 59b728c96939fc5eb660e65375c48497faa5fb0d
blob + 7ebf16fd18566adf4b16ba0bb1c5c0d7bde1ea09
--- lib/repository.c
+++ lib/repository.c
@@ -14,8 +14,11 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <sys/types.h>
 #include <sys/queue.h>
+#include <sys/uio.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 
 #include <limits.h>
 #include <stdlib.h>
@@ -25,6 +28,8 @@
 #include <zlib.h>
 #include <errno.h>
 #include <libgen.h>
+#include <stdint.h>
+#include <imsg.h>
 
 #include "got_error.h"
 #include "got_reference.h"
@@ -40,6 +45,7 @@
 #include "got_lib_repository.h"
 #include "got_lib_worktree.h"
 #include "got_lib_object_idcache.h"
+#include "got_lib_privsep.h"
 
 #ifndef nitems
 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
@@ -391,7 +397,7 @@ got_repo_open(struct got_repository **repop, const cha
 	struct got_repository *repo = NULL;
 	const struct got_error *err = NULL;
 	char *abspath, *normpath = NULL;
-	int tried_root = 0;
+	int i, tried_root = 0;
 
 	*repop = NULL;
 
@@ -408,6 +414,11 @@ got_repo_open(struct got_repository **repop, const cha
 		goto done;
 	}
 
+	for (i = 0; i < nitems(repo->privsep_children); i++) {
+		repo->privsep_children[i].imsg_fd = -1;
+		repo->privsep_children[i].pid = 0;
+	}
+
 	repo->objcache.type = GOT_OBJECT_CACHE_TYPE_OBJ;
 	repo->objcache.size = GOT_OBJECT_CACHE_SIZE_OBJ;
 	repo->objcache.idcache = got_object_idcache_alloc(repo->objcache.size);
@@ -460,7 +471,7 @@ got_repo_open(struct got_repository **repop, const cha
 	} while (path);
 done:
 	if (err)
-		got_repo_close(repo);
+		err = got_repo_close(repo);
 	else
 		*repop = repo;
 	free(abspath);
@@ -516,9 +527,26 @@ void check_refcount(struct got_object_id *id, void *da
 }
 #endif
 
-void
+static const struct got_error *
+wait_for_child(pid_t pid)
+{
+	int child_status;
+
+	waitpid(pid, &child_status, 0);
+
+	if (!WIFEXITED(child_status))
+		return got_error(GOT_ERR_PRIVSEP_DIED);
+
+	if (WEXITSTATUS(child_status) != 0)
+		return got_error(GOT_ERR_PRIVSEP_EXIT);
+
+	return NULL;
+}
+
+const struct got_error *
 got_repo_close(struct got_repository *repo)
 {
+	const struct got_error *err = NULL, *child_err;
 	int i;
 
 	for (i = 0; i < nitems(repo->packidx_cache); i++) {
@@ -554,7 +582,19 @@ got_repo_close(struct got_repository *repo)
 		got_object_idcache_free(repo->treecache.idcache);
 	if (repo->commitcache.idcache)
 		got_object_idcache_free(repo->commitcache.idcache);
+
+	for (i = 0; i < nitems(repo->privsep_children); i++) {
+		if (repo->privsep_children[i].imsg_fd == -1)
+			continue;
+		err = got_privsep_send_stop(repo->privsep_children[i].imsg_fd);
+		child_err = wait_for_child(repo->privsep_children[i].pid);
+		if (child_err && err == NULL)
+			err = child_err;
+		close(repo->privsep_children[i].imsg_fd);
+	}
 	free(repo);
+
+	return err;
 }
 
 const struct got_error *
blob - ee47b5d17c6579fcc0d5a562dbb73b3a98a3da3b
blob + 2ee86e361958414c674a933d5a2e1494ea250b7d
--- regress/repository/Makefile
+++ regress/repository/Makefile
@@ -6,7 +6,9 @@ SRCS = path.c repository.c error.c reference.c object.
 	pack.c privsep.c delta.c fileindex.c worktree.c inflate.c \
 	repository_test.c
 
-CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
+GOT_LIBEXECDIR = ${HOME}/bin
+CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib \
+	-DGOT_LIBEXECDIR=${GOT_LIBEXECDIR}
 LDADD = -lutil -lz
 DEBUG = -O0 -g
 CFLAGS += -Werror -Wall -Wstrict-prototypes -Wunused-variable
blob - 8b5fcb740be61c3312fcb4ccff3900cb9df69a77
blob + d5fe6d3ef1904416bca7e896855adc989a99b553
--- regress/repository/repository_test.c
+++ regress/repository/repository_test.c
@@ -457,7 +457,7 @@ main(int argc, char *argv[])
 	const char *repo_path;
 	int ch;
 
-	if (pledge("stdio rpath wpath cpath proc", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath proc exec sendfd", NULL) == -1)
 		err(1, "pledge");
 
 	while ((ch = getopt(argc, argv, "v")) != -1) {
blob - 4a2eb3aad111c9c4ebda609be120828e2da69773
blob + 181971230c2c141420272c6cad96af4dabed1dab
--- regress/worktree/Makefile
+++ regress/worktree/Makefile
@@ -5,7 +5,9 @@ SRCS = worktree.c repository.c object.c object_idcache
 	object_parse.c opentemp.c path.c error.c reference.c sha1.c pack.c \
 	privsep.c delta.c inflate.c fileindex.c worktree_test.c
 
-CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
+GOT_LIBEXECDIR = ${HOME}/bin
+CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib \
+	-DGOT_LIBEXECDIR=${GOT_LIBEXECDIR}
 LDADD = -lutil -lz
 DEBUG = -O0 -g
 CFLAGS += -Werror -Wall -Wstrict-prototypes -Wunused-variable
blob - a1d1e49fe8037949908f248557e97cfb381a9757
blob + c6ece3a0c62118b22c4b7acc12dbef54dbb5e14a
--- regress/worktree/worktree_test.c
+++ regress/worktree/worktree_test.c
@@ -390,7 +390,8 @@ main(int argc, char *argv[])
 	const char *repo_path;
 	int ch;
 
-	if (pledge("stdio rpath wpath cpath flock proc", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath flock proc exec sendfd", NULL)
+	    == -1)
 		err(1, "pledge");
 
 	while ((ch = getopt(argc, argv, "v")) != -1) {
blob - /dev/null
blob + 261f1cc59455aad5fbbd415b6933ecf56a74f571 (mode 644)
--- /dev/null
+++ libexec/got-read-blob/Makefile
@@ -0,0 +1,31 @@
+.PATH:${.CURDIR}/../../lib
+
+PROG=		got-read-blob
+SRCS=		got-read-blob.c delta.c error.c inflate.c object_parse.c \
+		privsep.c sha1.c
+
+CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+.if defined(PROFILE)
+CC = gcc
+CPPFLAGS += -DPROFILE
+DEBUG = -O0 -pg
+.else
+DEBUG = -O0 -g
+.endif
+CFLAGS += -Werror -Wall -Wstrict-prototypes -Wunused-variable
+#CFLAGS += -DGOT_PACK_NO_MMAP
+#CFLAGS += -DGOT_NO_OBJ_CACHE
+
+# For now, default to installing binary in ~/bin
+LIBEXECDIR = ${HOME}/bin
+GROUP!=id -g -n
+install:
+	${INSTALL} ${INSTALL_COPY} -o ${USER} -g ${GROUP} \
+	-m ${BINMODE} ${PROG} ${LIBEXECDIR}/${PROG}
+
+# Don't install man pages yet
+NOMAN = Yes
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 2302e711eaaf23075b4319ef22ba5cc36caefdff (mode 644)
--- /dev/null
+++ libexec/got-read-blob/got-read-blob.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2018 Stefan Sperling <stsp@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/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/limits.h>
+
+#include <stdint.h>
+#include <imsg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sha1.h>
+#include <zlib.h>
+
+#include "got_error.h"
+#include "got_object.h"
+
+#include "got_lib_delta.h"
+#include "got_lib_inflate.h"
+#include "got_lib_object.h"
+#include "got_lib_object_parse.h"
+#include "got_lib_privsep.h"
+
+int
+main(int argc, char *argv[])
+{
+	const struct got_error *err = NULL;
+	struct imsgbuf ibuf;
+	size_t datalen;
+
+	imsg_init(&ibuf, GOT_IMSG_FD_CHILD);
+
+	/* revoke access to most system calls */
+	if (pledge("stdio recvfd", NULL) == -1) {
+		err = got_error_from_errno();
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
+
+	while (1) {
+		struct imsg imsg, imsg_outfd;
+		FILE *f = NULL;
+		size_t size;
+	
+		imsg.fd = -1;
+		imsg_outfd.fd = -1;
+
+		err = got_privsep_recv_imsg(&imsg, &ibuf, 0);
+		if (err) {
+			if (imsg.hdr.len == 0)
+				err = NULL;
+			break;
+		}
+
+		if (imsg.hdr.type == GOT_IMSG_STOP)
+			break;
+
+		if (imsg.hdr.type != GOT_IMSG_BLOB_REQUEST) {
+			err = got_error(GOT_ERR_PRIVSEP_MSG);
+			goto done;
+		}
+
+		datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+		if (datalen != 0) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+		if (imsg.fd == -1) {
+			err = got_error(GOT_ERR_PRIVSEP_NO_FD);
+			goto done;
+		}
+
+		err = got_privsep_recv_imsg(&imsg_outfd, &ibuf, 0);
+		if (err) {
+			if (imsg.hdr.len == 0)
+				err = NULL;
+			break;
+		}
+
+		if (imsg_outfd.hdr.type == GOT_IMSG_STOP)
+			break;
+
+		if (imsg_outfd.hdr.type != GOT_IMSG_BLOB_OUTFD) {
+			err = got_error(GOT_ERR_PRIVSEP_MSG);
+			goto done;
+		}
+
+		datalen = imsg_outfd.hdr.len - IMSG_HEADER_SIZE;
+		if (datalen != 0) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+		if (imsg_outfd.fd == -1) {
+			err = got_error(GOT_ERR_PRIVSEP_NO_FD);
+			goto done;
+		}
+
+		f = fdopen(imsg.fd, "rb");
+		if (f == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+
+		err = got_inflate_to_fd(&size, f, imsg_outfd.fd);
+		if (err)
+			goto done;
+
+		err = got_privsep_send_blob(&ibuf, size);
+done:
+		if (f)
+			fclose(f);
+		else if (imsg.fd != -1)
+			close(imsg.fd);
+		if (imsg_outfd.fd != -1)
+			close(imsg_outfd.fd);
+		imsg_free(&imsg);
+		imsg_free(&imsg_outfd);
+		if (err) {
+			got_privsep_send_error(&ibuf, err);
+			break;
+		}
+	}
+
+	imsg_clear(&ibuf);
+	if (err)
+		fprintf(stderr, "%s: %s\n", getprogname(), err->msg);
+	close(GOT_IMSG_FD_CHILD);
+	return err ? 1 : 0;
+}
blob - /dev/null
blob + fe091982cd9735bf9abd120934bf0c1a6417b8d4 (mode 644)
--- /dev/null
+++ libexec/got-read-commit/Makefile
@@ -0,0 +1,31 @@
+.PATH:${.CURDIR}/../../lib
+
+PROG=		got-read-commit
+SRCS=		got-read-commit.c delta.c error.c inflate.c object_parse.c \
+		privsep.c sha1.c
+
+CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+.if defined(PROFILE)
+CC = gcc
+CPPFLAGS += -DPROFILE
+DEBUG = -O0 -pg
+.else
+DEBUG = -O0 -g
+.endif
+CFLAGS += -Werror -Wall -Wstrict-prototypes -Wunused-variable
+#CFLAGS += -DGOT_PACK_NO_MMAP
+#CFLAGS += -DGOT_NO_OBJ_CACHE
+
+# For now, default to installing binary in ~/bin
+LIBEXECDIR = ${HOME}/bin
+GROUP!=id -g -n
+install:
+	${INSTALL} ${INSTALL_COPY} -o ${USER} -g ${GROUP} \
+	-m ${BINMODE} ${PROG} ${LIBEXECDIR}/${PROG}
+
+# Don't install man pages yet
+NOMAN = Yes
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 178dd961e87c303cc67120aaa8d9878f9fb741b2 (mode 644)
--- /dev/null
+++ libexec/got-read-commit/got-read-commit.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2018 Stefan Sperling <stsp@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/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/limits.h>
+
+#include <stdint.h>
+#include <imsg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sha1.h>
+#include <zlib.h>
+
+#include "got_error.h"
+#include "got_object.h"
+
+#include "got_lib_delta.h"
+#include "got_lib_inflate.h"
+#include "got_lib_object.h"
+#include "got_lib_object_parse.h"
+#include "got_lib_privsep.h"
+
+static const struct got_error *
+read_commit_object(struct got_commit_object **commit, struct got_object *obj,
+    FILE *f)
+{
+	const struct got_error *err = NULL;
+	size_t len;
+	uint8_t *p;
+
+	if (obj->flags & GOT_OBJ_FLAG_PACKED)
+		err = got_read_file_to_mem(&p, &len, f);
+	else
+		err = got_inflate_to_mem(&p, &len, f);
+	if (err)
+		return err;
+
+	if (len < obj->hdrlen + obj->size) {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+
+	/* Skip object header. */
+	len -= obj->hdrlen;
+	err = got_object_parse_commit(commit, p + obj->hdrlen, len);
+	free(p);
+done:
+	return err;
+}
+
+int
+main(int argc, char *argv[])
+{
+	const struct got_error *err = NULL;
+	struct got_commit_object *commit = NULL;
+	struct imsgbuf ibuf;
+	size_t datalen;
+
+	imsg_init(&ibuf, GOT_IMSG_FD_CHILD);
+
+	/* revoke access to most system calls */
+	if (pledge("stdio recvfd", NULL) == -1) {
+		err = got_error_from_errno();
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
+
+	while (1) {
+		struct imsg imsg;
+		struct got_imsg_object iobj;
+		FILE *f = NULL;
+		struct got_object *obj = NULL;
+	
+		err = got_privsep_recv_imsg(&imsg, &ibuf, 0);
+		if (err) {
+			if (imsg.hdr.len == 0)
+				err = NULL;
+			break;
+		}
+
+		if (imsg.hdr.type == GOT_IMSG_STOP)
+			break;
+
+		if (imsg.hdr.type != GOT_IMSG_COMMIT_REQUEST) {
+			err = got_error(GOT_ERR_PRIVSEP_MSG);
+			goto done;
+		}
+
+		datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+		if (datalen != sizeof(iobj)) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+
+		memcpy(&iobj, imsg.data, sizeof(iobj));
+		if (iobj.type != GOT_OBJ_TYPE_COMMIT) {
+			err = got_error(GOT_ERR_OBJ_TYPE);
+			goto done;
+		}
+
+		if (imsg.fd == -1) {
+			err = got_error(GOT_ERR_PRIVSEP_NO_FD);
+			goto done;
+		}
+
+		obj = calloc(1, sizeof(*obj));
+		if (obj == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		obj->type = iobj.type;
+		obj->hdrlen = iobj.hdrlen;
+		obj->size = iobj.size;
+
+		/* Always assume file offset zero. */
+		f = fdopen(imsg.fd, "rb");
+		if (f == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+
+		err = read_commit_object(&commit, obj, f);
+		if (err)
+			goto done;
+
+		err = got_privsep_send_commit(&ibuf, commit);
+done:
+		if (f)
+			fclose(f);
+		else if (imsg.fd != -1)
+			close(imsg.fd);
+		imsg_free(&imsg);
+		if (obj)
+			got_object_close(obj);
+		if (err) {
+			got_privsep_send_error(&ibuf, err);
+			break;
+		}
+	}
+
+	imsg_clear(&ibuf);
+	if (err)
+		fprintf(stderr, "%s: %s\n", getprogname(), err->msg);
+	close(GOT_IMSG_FD_CHILD);
+	return err ? 1 : 0;
+}
blob - /dev/null
blob + 71a0ac872de0db8584350f20eb5986bdd75fc46f (mode 644)
--- /dev/null
+++ libexec/got-read-object/Makefile
@@ -0,0 +1,31 @@
+.PATH:${.CURDIR}/../../lib
+
+PROG=		got-read-object
+SRCS=		got-read-object.c delta.c error.c inflate.c object_parse.c \
+		privsep.c sha1.c
+
+CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+.if defined(PROFILE)
+CC = gcc
+CPPFLAGS += -DPROFILE
+DEBUG = -O0 -pg
+.else
+DEBUG = -O0 -g
+.endif
+CFLAGS += -Werror -Wall -Wstrict-prototypes -Wunused-variable
+#CFLAGS += -DGOT_PACK_NO_MMAP
+#CFLAGS += -DGOT_NO_OBJ_CACHE
+
+# For now, default to installing binary in ~/bin
+LIBEXECDIR = ${HOME}/bin
+GROUP!=id -g -n
+install:
+	${INSTALL} ${INSTALL_COPY} -o ${USER} -g ${GROUP} \
+	-m ${BINMODE} ${PROG} ${LIBEXECDIR}/${PROG}
+
+# Don't install man pages yet
+NOMAN = Yes
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 0911e80092f384fa5eec6a65e28f5b24b5849066 (mode 644)
--- /dev/null
+++ libexec/got-read-object/got-read-object.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2018 Stefan Sperling <stsp@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/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/limits.h>
+
+#include <stdint.h>
+#include <imsg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sha1.h>
+#include <zlib.h>
+
+#include "got_error.h"
+#include "got_object.h"
+
+#include "got_lib_delta.h"
+#include "got_lib_inflate.h"
+#include "got_lib_object.h"
+#include "got_lib_privsep.h"
+
+#ifndef nitems
+#define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
+#endif
+
+#define GOT_OBJ_TAG_COMMIT	"commit"
+#define GOT_OBJ_TAG_TREE	"tree"
+#define GOT_OBJ_TAG_BLOB	"blob"
+
+static const struct got_error *
+parse_object_header(struct got_object **obj, char *buf, size_t len)
+{
+	const char *obj_tags[] = {
+		GOT_OBJ_TAG_COMMIT,
+		GOT_OBJ_TAG_TREE,
+		GOT_OBJ_TAG_BLOB
+	};
+	const int obj_types[] = {
+		GOT_OBJ_TYPE_COMMIT,
+		GOT_OBJ_TYPE_TREE,
+		GOT_OBJ_TYPE_BLOB,
+	};
+	int type = 0;
+	size_t size = 0, hdrlen = 0;
+	int i;
+	char *p = strchr(buf, '\0');
+
+	if (p == NULL)
+		return got_error(GOT_ERR_BAD_OBJ_HDR);
+
+	hdrlen = strlen(buf) + 1 /* '\0' */;
+
+	for (i = 0; i < nitems(obj_tags); i++) {
+		const char *tag = obj_tags[i];
+		size_t tlen = strlen(tag);
+		const char *errstr;
+
+		if (strncmp(buf, tag, tlen) != 0)
+			continue;
+
+		type = obj_types[i];
+		if (len <= tlen)
+			return got_error(GOT_ERR_BAD_OBJ_HDR);
+		size = strtonum(buf + tlen, 0, LONG_MAX, &errstr);
+		if (errstr != NULL)
+			return got_error(GOT_ERR_BAD_OBJ_HDR);
+		break;
+	}
+
+	if (type == 0)
+		return got_error(GOT_ERR_BAD_OBJ_HDR);
+
+	*obj = calloc(1, sizeof(**obj));
+	if (*obj == NULL)
+		return got_error_from_errno();
+	(*obj)->type = type;
+	(*obj)->hdrlen = hdrlen;
+	(*obj)->size = size;
+	return NULL;
+}
+
+static const struct got_error *
+read_object_header(struct got_object **obj, int fd)
+{
+	const struct got_error *err;
+	struct got_zstream_buf zb;
+	char *buf;
+	const size_t zbsize = 64;
+	size_t outlen, totlen;
+	int nbuf = 1;
+
+	buf = malloc(zbsize);
+	if (buf == NULL)
+		return got_error_from_errno();
+
+	err = got_inflate_init(&zb, buf, zbsize);
+	if (err)
+		return err;
+
+	totlen = 0;
+	do {
+		err = got_inflate_read_fd(&zb, fd, &outlen);
+		if (err)
+			goto done;
+		if (outlen == 0)
+			break;
+		totlen += outlen;
+		if (strchr(zb.outbuf, '\0') == NULL) {
+			char *newbuf;
+			nbuf++;
+			newbuf = recallocarray(buf, nbuf - 1, nbuf, zbsize);
+			if (newbuf == NULL) {
+				err = got_error_from_errno();
+				goto done;
+			}
+			buf = newbuf;
+			zb.outbuf = newbuf + totlen;
+			zb.outlen = (nbuf * zbsize) - totlen;
+		}
+	} while (strchr(zb.outbuf, '\0') == NULL);
+
+	err = parse_object_header(obj, buf, totlen);
+done:
+	free(buf);
+	got_inflate_end(&zb);
+	return err;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+	const struct got_error *err = NULL;
+	struct got_object *obj = NULL;
+	struct imsg imsg;
+	struct imsgbuf ibuf;
+	size_t datalen;
+
+	imsg_init(&ibuf, GOT_IMSG_FD_CHILD);
+
+	/* revoke access to most system calls */
+	if (pledge("stdio recvfd", NULL) == -1) {
+		err = got_error_from_errno();
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
+
+	while (1) {
+		err = got_privsep_recv_imsg(&imsg, &ibuf, 0);
+		if (err) {
+			if (imsg.hdr.len == 0)
+				err = NULL;
+			break;
+		}
+
+		if (imsg.hdr.type == GOT_IMSG_STOP)
+			break;
+
+		if (imsg.hdr.type != GOT_IMSG_OBJECT_REQUEST) {
+			err = got_error(GOT_ERR_PRIVSEP_MSG);
+			goto done;
+		}
+
+		datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+		if (datalen != 0) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+
+		err = read_object_header(&obj, imsg.fd);
+		if (err)
+			goto done;
+
+		err = got_privsep_send_obj(&ibuf, obj, 0);
+done:
+		close(imsg.fd);
+		imsg_free(&imsg);
+		if (obj)
+			got_object_close(obj);
+		if (err) {
+			got_privsep_send_error(&ibuf, err);
+			break;
+		}
+	}
+
+	imsg_clear(&ibuf);
+	if (err)
+		fprintf(stderr, "%s: %s\n", getprogname(), err->msg);
+	close(GOT_IMSG_FD_CHILD);
+	return err ? 1 : 0;
+}
blob - /dev/null
blob + c6079e3ec721fb0b95f0662d62d4e6b2275372f1 (mode 644)
--- /dev/null
+++ libexec/got-read-tree/Makefile
@@ -0,0 +1,31 @@
+.PATH:${.CURDIR}/../../lib
+
+PROG=		got-read-tree
+SRCS=		got-read-tree.c delta.c error.c inflate.c object_parse.c \
+		privsep.c sha1.c
+
+CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+.if defined(PROFILE)
+CC = gcc
+CPPFLAGS += -DPROFILE
+DEBUG = -O0 -pg
+.else
+DEBUG = -O0 -g
+.endif
+CFLAGS += -Werror -Wall -Wstrict-prototypes -Wunused-variable
+#CFLAGS += -DGOT_PACK_NO_MMAP
+#CFLAGS += -DGOT_NO_OBJ_CACHE
+
+# For now, default to installing binary in ~/bin
+LIBEXECDIR = ${HOME}/bin
+GROUP!=id -g -n
+install:
+	${INSTALL} ${INSTALL_COPY} -o ${USER} -g ${GROUP} \
+	-m ${BINMODE} ${PROG} ${LIBEXECDIR}/${PROG}
+
+# Don't install man pages yet
+NOMAN = Yes
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 43696a70aa71949a1566e43e2e43d3f9b0076481 (mode 644)
--- /dev/null
+++ libexec/got-read-tree/got-read-tree.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2018 Stefan Sperling <stsp@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/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/limits.h>
+
+#include <stdint.h>
+#include <imsg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sha1.h>
+#include <zlib.h>
+
+#include "got_error.h"
+#include "got_object.h"
+
+#include "got_lib_delta.h"
+#include "got_lib_inflate.h"
+#include "got_lib_object.h"
+#include "got_lib_object_parse.h"
+#include "got_lib_privsep.h"
+
+static const struct got_error *
+read_tree_object(struct got_tree_object **tree, struct got_object *obj, FILE *f)
+{
+	const struct got_error *err = NULL;
+	size_t len;
+	uint8_t *p;
+
+	if (obj->flags & GOT_OBJ_FLAG_PACKED)
+		err = got_read_file_to_mem(&p, &len, f);
+	else
+		err = got_inflate_to_mem(&p, &len, f);
+	if (err)
+		return err;
+
+	if (len < obj->hdrlen + obj->size) {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+
+	/* Skip object header. */
+	len -= obj->hdrlen;
+	err = got_object_parse_tree(tree, p + obj->hdrlen, len);
+	free(p);
+done:
+	return err;
+}
+
+int
+main(int argc, char *argv[])
+{
+	const struct got_error *err = NULL;
+	struct got_tree_object *tree = NULL;
+	struct imsgbuf ibuf;
+	size_t datalen;
+
+	imsg_init(&ibuf, GOT_IMSG_FD_CHILD);
+
+	/* revoke access to most system calls */
+	if (pledge("stdio recvfd", NULL) == -1) {
+		err = got_error_from_errno();
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
+
+	while (1) {
+		struct imsg imsg;
+		struct got_imsg_object iobj;
+		FILE *f = NULL;
+		struct got_object *obj = NULL;
+	
+		err = got_privsep_recv_imsg(&imsg, &ibuf, 0);
+		if (err) {
+			if (imsg.hdr.len == 0)
+				err = NULL;
+			break;
+		}
+
+		if (imsg.hdr.type == GOT_IMSG_STOP)
+			break;
+
+		if (imsg.hdr.type != GOT_IMSG_TREE_REQUEST) {
+			err = got_error(GOT_ERR_PRIVSEP_MSG);
+			goto done;
+		}
+
+		datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+		if (datalen != sizeof(iobj)) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+
+		memcpy(&iobj, imsg.data, sizeof(iobj));
+		if (iobj.type != GOT_OBJ_TYPE_TREE) {
+			err = got_error(GOT_ERR_OBJ_TYPE);
+			goto done;
+		}
+
+		if (imsg.fd == -1) {
+			err = got_error(GOT_ERR_PRIVSEP_NO_FD);
+			goto done;
+		}
+
+		obj = calloc(1, sizeof(*obj));
+		if (obj == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		obj->type = iobj.type;
+		obj->hdrlen = iobj.hdrlen;
+		obj->size = iobj.size;
+
+		/* Always assume file offset zero. */
+		f = fdopen(imsg.fd, "rb");
+		if (f == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+
+		err = read_tree_object(&tree, obj, f);
+		if (err)
+			goto done;
+
+		err = got_privsep_send_tree(&ibuf, tree);
+done:
+		if (f)
+			fclose(f);
+		else if (imsg.fd != -1)
+			close(imsg.fd);
+		imsg_free(&imsg);
+		if (obj)
+			got_object_close(obj);
+		if (err) {
+			got_privsep_send_error(&ibuf, err);
+			break;
+		}
+	}
+
+	imsg_clear(&ibuf);
+	if (err)
+		fprintf(stderr, "%s: %s\n", getprogname(), err->msg);
+	close(GOT_IMSG_FD_CHILD);
+	return err ? 1 : 0;
+}
blob - ab8d78df0d3be8f6a5d681827f8c9ee32971fd07
blob + a3a05e3992bcc9bdc949b5bad8d348bdbad84b8d
--- tog/Makefile
+++ tog/Makefile
@@ -7,7 +7,9 @@ SRCS=		tog.c blame.c commit_graph.c delta.c diff.c dif
 		privsep.c reference.c repository.c sha1.c worktree.c \
 		utf8.c inflate.c
 
-CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
+GOT_LIBEXECDIR = ${HOME}/bin
+CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib \
+	-DGOT_LIBEXECDIR=${GOT_LIBEXECDIR}
 LDADD = -lpanel -lncursesw -lutil -lz -lpthread
 DPADD = ${LIBZ} ${LIBUTIL}
 .if defined(PROFILE)
blob - 1588ddcc15f3d237c38309143807c857ea2fa96d
blob + d302f3408ca7c7d8b22d2bb3b9c1a45545986874
--- tog/tog.c
+++ tog/tog.c
@@ -1398,7 +1398,8 @@ cmd_log(int argc, char *argv[])
 	struct tog_view *view;
 
 #ifndef PROFILE
-	if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
+	    == -1)
 		err(1, "pledge");
 #endif
 
@@ -1753,7 +1754,8 @@ cmd_diff(int argc, char *argv[])
 	struct tog_view *view;
 
 #ifndef PROFILE
-	if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
+	    == -1)
 		err(1, "pledge");
 #endif
 
@@ -2485,7 +2487,8 @@ cmd_blame(int argc, char *argv[])
 	struct tog_view *view;
 
 #ifndef PROFILE
-	if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
+	    == -1)
 		err(1, "pledge");
 #endif
 
@@ -3051,7 +3054,8 @@ cmd_tree(int argc, char *argv[])
 	struct tog_view *view;
 
 #ifndef PROFILE
-	if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
+	    == -1)
 		err(1, "pledge");
 #endif