Commit Diff


commit - 55aa433f8f1327733b5f8695d0f211c8c3ab8077
commit + f63b8f7342aefba6b3dac50d6790981987c8faa8
blob - a8bb49116b2901be99e92fbcce1554a3d848d710
blob + 688094dbafe4a14e728cc4f0f42953cc5c52aacb
--- downloads.c
+++ downloads.c
@@ -79,7 +79,7 @@ end:
 	wrap_page(&downloadwin, download_cols);
 }
 
-void
+struct download *
 enqueue_download(uint32_t id, const char *path, int buffer)
 {
 	struct download *d;
@@ -93,6 +93,8 @@ enqueue_download(uint32_t id, const char *path, int bu
 	d->buffer = buffer;
 
 	STAILQ_INSERT_HEAD(&downloads, d, entries);
+
+	return d;
 }
 
 void
blob - 3d7a767702543a6d9cd2ab23a78ee3b5f600f7a1
blob + 2309904c0b78c1bf3d1042531cd40b17c2442cbd
--- fs.c
+++ fs.c
@@ -33,30 +33,21 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "fs.h"
 #include "pages.h"
-#include "telescope.h"
+#include "parser.h"
 #include "session.h"
+#include "telescope.h"
 #include "utils.h"
 
+#include "fs.h"
+
+#ifndef nitems
+#define nitems(x)  (sizeof(x) / sizeof(x[0]))
+#endif
+
 static void		 die(void) __attribute__((__noreturn__));
-static void		 send_file(uint32_t, FILE *);
-static void		 handle_get(struct imsg*, size_t);
 static int		 select_non_dot(const struct dirent *);
 static int		 select_non_dotdot(const struct dirent *);
-static void		 handle_get_file(struct imsg*, size_t);
-static void		 handle_misc(struct imsg *, size_t);
-static void		 handle_bookmark_page(struct imsg*, size_t);
-static void		 handle_save_cert(struct imsg*, size_t);
-static void		 handle_update_cert(struct imsg*, size_t);
-static void		 handle_file_open(struct imsg*, size_t);
-static void		 handle_session_start(struct imsg*, size_t);
-static void		 handle_session_tab(struct imsg*, size_t);
-static void		 handle_session_tab_hist(struct imsg*, size_t);
-static void		 handle_session_end(struct imsg*, size_t);
-static void		 handle_hist(struct imsg *, size_t);
-static void		 handle_dispatch_imsg(int, short, void*);
-static int		 fs_send_ui(int, uint32_t, int, const void *, uint16_t);
 static size_t		 join_path(char*, const char*, const char*, size_t);
 static void		 getenv_default(char*, const char*, const char*, size_t);
 static void		 mkdirs(const char*, mode_t);
@@ -64,11 +55,8 @@ static void		 init_paths(void);
 static void		 load_last_session(void);
 static void		 load_hist(void);
 static int		 last_time_crashed(void);
-static void		 load_certs(void);
+static void		 load_certs(struct ohash *);
 
-static struct imsgev		*iev_ui;
-static FILE			*session;
-
 /*
  * Where to store user data.  These are all equal to ~/.telescope if
  * it exists.
@@ -84,342 +72,225 @@ char		bookmark_file[PATH_MAX];
 char		known_hosts_file[PATH_MAX], known_hosts_tmp[PATH_MAX];
 char		crashed_file[PATH_MAX];
 char		session_file[PATH_MAX];
-static char	history_file[PATH_MAX];
+char		history_file[PATH_MAX];
 
-static imsg_handlerfn *handlers[] = {
-	[IMSG_GET] = handle_get,
-	[IMSG_GET_FILE] = handle_get_file,
-	[IMSG_QUIT] = handle_misc,
-	[IMSG_INIT] = handle_misc,
-	[IMSG_BOOKMARK_PAGE] = handle_bookmark_page,
-	[IMSG_SAVE_CERT] = handle_save_cert,
-	[IMSG_UPDATE_CERT] = handle_update_cert,
-	[IMSG_FILE_OPEN] = handle_file_open,
-	[IMSG_SESSION_START] = handle_session_start,
-	[IMSG_SESSION_TAB] = handle_session_tab,
-	[IMSG_SESSION_TAB_HIST] = handle_session_tab_hist,
-	[IMSG_SESSION_END] = handle_session_end,
-	[IMSG_HIST_ITEM] = handle_hist,
-	[IMSG_HIST_END] = handle_hist,
-};
-
 static void __attribute__((__noreturn__))
 die(void)
 {
 	abort(); 		/* TODO */
 }
 
-static void
-send_file(uint32_t peerid, FILE *f)
+static int
+select_non_dot(const struct dirent *d)
 {
-	ssize_t	 r;
-	char	 buf[BUFSIZ];
+	return strcmp(d->d_name, ".");
+}
 
-	for (;;) {
-		r = fread(buf, 1, sizeof(buf), f);
-		if (r != 0)
-			fs_send_ui(IMSG_BUF, peerid, -1, buf, r);
-		if (r != sizeof(buf))
-			break;
-	}
-	fs_send_ui(IMSG_EOF, peerid, -1, NULL, 0);
-	fclose(f);
+static int
+select_non_dotdot(const struct dirent *d)
+{
+	return strcmp(d->d_name, ".") && strcmp(d->d_name, "..");
 }
 
 static void
-handle_get(struct imsg *imsg, size_t datalen)
+send_dir(struct tab *tab, const char *path)
 {
-	const char	*bpath = "bookmarks.gmi";
-	char		 path[PATH_MAX];
-	FILE		*f;
-	const char	*data, *p;
-	size_t		 i;
-	struct page {
-		const char	*name;
-		const char	*path;
-		const uint8_t	*data;
-		size_t		 len;
-	} pages[] = {
-		{"about",	NULL,	about_about,	about_about_len},
-		{"blank",	NULL,	about_blank,	about_blank_len},
-		{"bookmarks",	bpath,	bookmarks,	bookmarks_len},
-		{"crash",	NULL,	about_crash,	about_crash_len},
-		{"help",	NULL,	about_help,	about_help_len},
-		{"license",	NULL,	about_license,	about_license_len},
-		{"new",		NULL,	about_new,	about_new_len},
-	}, *page = NULL;
+	struct dirent	**names;
+	int		(*selector)(const struct dirent *) = select_non_dot;
+	int		  i, len;
 
-	data = imsg->data;
-	if (data[datalen-1] != '\0') /* make sure it's NUL-terminated */
-		die();
-	if ((data = strchr(data, ':')) == NULL)
-		goto notfound;
-	data++;
+#if notyet
+	/*
+	 * need something to fake a redirect
+	 */
 
-	for (i = 0; i < sizeof(pages)/sizeof(pages[0]); ++i)
-		if (!strcmp(data, pages[i].name)) {
-			page = &pages[i];
-			break;
-		}
-
-	if (page == NULL)
-		goto notfound;
-
-	strlcpy(path, data_path_base, sizeof(path));
-	strlcat(path, "/", sizeof(path));
-	if (page->path != NULL)
-		strlcat(path, page->path, sizeof(path));
-	else {
-		strlcat(path, "pages/about_", sizeof(path));
-		strlcat(path, page->name, sizeof(path));
-		strlcat(path, ".gmi", sizeof(path));
+	if (!has_suffix(path, "/")) {
+		if (asprintf(&s, "%s/", path) == -1)
+			die();
+		send_hdr(peerid, 30, s);
+		free(s);
+		return;
 	}
+#endif
 
-	if ((f = fopen(path, "r")) == NULL) {
-		fs_send_ui(IMSG_BUF, imsg->hdr.peerid, -1,
-		    page->data, page->len);
-		fs_send_ui(IMSG_EOF, imsg->hdr.peerid, -1,
-		    NULL, 0);
+	if (!strcmp(path, "/"))
+		selector = select_non_dotdot;
+
+	if ((len = scandir(path, &names, selector, alphasort)) == -1) {
+		load_page_from_str(tab, "# failure reading the directory\n");
 		return;
 	}
 
-	send_file(imsg->hdr.peerid, f);
-	return;
+	parser_init(tab, gemtext_initparser);
+	parser_parsef(tab, "# Index of %s\n\n", path);
 
-notfound:
-	p = "# not found!\n";
-	fs_send_ui(IMSG_BUF, imsg->hdr.peerid, -1, p, strlen(p));
-	fs_send_ui(IMSG_EOF, imsg->hdr.peerid, -1, NULL, 0);
-}
+	for (i = 0; i < len; ++i) {
+		const char *sufx = "";
 
-static inline void
-send_hdr(uint32_t peerid, int code, const char *meta)
-{
-	fs_send_ui(IMSG_GOT_CODE, peerid, -1, &code, sizeof(code));
-	fs_send_ui(IMSG_GOT_META, peerid, -1, meta, strlen(meta)+1);
+		if (names[i]->d_type == DT_DIR)
+			sufx = "/";
+
+		parser_parsef(tab, "=> %s%s\n", names[i]->d_name, sufx);
+	}
+
+	parser_free(tab);
+	free(names);
 }
 
-static inline void
-send_errno(uint32_t peerid, int code, const char *str, int no)
+static int
+is_dir(FILE *fp)
 {
-	char *s;
+	struct stat sb;
 
-	if (asprintf(&s, "%s: %s", str, strerror(no)) == -1)
-		s = NULL;
+	if (fstat(fileno(fp), &sb) == -1)
+		return 0;
 
-	send_hdr(peerid, code, s == NULL ? str : s);
-	free(s);
+	return S_ISDIR(sb.st_mode);
 }
 
-static inline const char *
+static parserinit
 file_type(const char *path)
 {
 	struct mapping {
 		const char	*ext;
-		const char	*mime;
+		parserinit	 fn;
 	} ms[] = {
-		{"diff",	"text/x-patch"},
-		{"gemini",	"text/gemini"},
-		{"gmi",		"text/gemini"},
-		{"markdown",	"text/plain"},
-		{"md",		"text/plain"},
-		{"patch",	"text/x-patch"},
-		{"txt",		"text/plain"},
+		{"diff",	textpatch_initparser},
+		{"gemini",	gemtext_initparser},
+		{"gmi",		gemtext_initparser},
+		{"markdown",	textplain_initparser},
+		{"md",		textplain_initparser},
+		{"patch",	gemtext_initparser},
 		{NULL, NULL},
 	}, *m;
 	char *dot;
 
 	if ((dot = strrchr(path, '.')) == NULL)
-		return NULL;
+		return textplain_initparser;
 
 	dot++;
 
 	for (m = ms; m->ext != NULL; ++m)
 		if (!strcmp(m->ext, dot))
-			return m->mime;
+			return m->fn;
 
-	return NULL;
+	return textplain_initparser;
 }
 
-static int
-select_non_dot(const struct dirent *d)
+void
+fs_load_url(struct tab *tab, const char *url)
 {
-	return strcmp(d->d_name, ".");
-}
-
-static int
-select_non_dotdot(const struct dirent *d)
-{
-	return strcmp(d->d_name, ".") && strcmp(d->d_name, "..");
-}
+	const char	*bpath = "bookmarks.gmi", *fallback = "# Not found\n";
+	parserinit	 initfn = gemtext_initparser;
+	char		 path[PATH_MAX];
+	FILE		*fp = NULL;
+	size_t		 i;
+	char		 buf[BUFSIZ];
+	struct page {
+		const char	*name;
+		const char	*path;
+		const uint8_t	*data;
+		size_t		 len;
+	} pages[] = {
+		{"about",	NULL,	about_about,	about_about_len},
+		{"blank",	NULL,	about_blank,	about_blank_len},
+		{"bookmarks",	bpath,	bookmarks,	bookmarks_len},
+		{"crash",	NULL,	about_crash,	about_crash_len},
+		{"help",	NULL,	about_help,	about_help_len},
+		{"license",	NULL,	about_license,	about_license_len},
+		{"new",		NULL,	about_new,	about_new_len},
+	}, *page = NULL;
 
-static inline void
-send_dir(uint32_t peerid, const char *path)
-{
-	struct dirent	**names;
-	struct evbuffer	 *ev;
-	char		 *s;
-	int		(*selector)(const struct dirent *) = select_non_dot;
-	int		  i, len, no;
+	if (!strncmp(url, "about:", 6)) {
+		url += 6;
 
-	if (!has_suffix(path, "/")) {
-		if (asprintf(&s, "%s/", path) == -1)
-			die();
-		send_hdr(peerid, 30, s);
-		free(s);
-		return;
-	}
+		for (i = 0; page == NULL && i < nitems(pages); ++i) {
+			if (!strcmp(url, pages[i].name))
+				page = &pages[i];
+		}
 
-	if (!strcmp(path, "/"))
-		selector = select_non_dotdot;
+		if (page == NULL)
+			goto done;
 
-	if ((ev = evbuffer_new()) == NULL ||
-	    (len = scandir(path, &names, selector, alphasort)) == -1) {
-		no = errno;
-		evbuffer_free(ev);
-		send_errno(peerid, 40, "failure reading the directory", no);
-		return;
-	}
+		strlcpy(path, data_path_base, sizeof(path));
+		strlcat(path, "/", sizeof(path));
+		if (page->path != NULL)
+			strlcat(path, page->path, sizeof(path));
+		else {
+			strlcat(path, "page/about_", sizeof(path));
+			strlcat(path, page->name, sizeof(path));
+			strlcat(path, ".gmi", sizeof(path));
+		}
 
-	evbuffer_add_printf(ev, "# Index of %s\n\n", path);
-	for (i = 0; i < len; ++i) {
-		evbuffer_add_printf(ev, "=> %s", names[i]->d_name);
-		if (names[i]->d_type == DT_DIR)
-			evbuffer_add(ev, "/", 1);
-		evbuffer_add(ev, "\n", 1);
-	}
+		fallback = page->data;
+	} else if (!strncmp(url, "file://", 7)) {
+		url += 7;
+		strlcpy(path, url, sizeof(path));
+		initfn = file_type(url);
+	} else
+		goto done;
 
-	send_hdr(peerid, 20, "text/gemini");
-	fs_send_ui(IMSG_BUF, peerid, -1,
-	    EVBUFFER_DATA(ev), EVBUFFER_LENGTH(ev));
-	fs_send_ui(IMSG_EOF, peerid, -1, NULL, 0);
+	if ((fp = fopen(path, "r")) == NULL)
+		goto done;
 
-	evbuffer_free(ev);
-	free(names);
-}
-
-static void
-handle_get_file(struct imsg *imsg, size_t datalen)
-{
-	struct stat	 sb;
-	FILE		*f;
-	char		*data;
-	const char	*meta = NULL;
-
-	data = imsg->data;
-	data[datalen-1] = '\0';
-
-	if ((f = fopen(data, "r")) == NULL) {
-		send_errno(imsg->hdr.peerid, 51, "can't open", errno);
-		return;
+	if (is_dir(fp)) {
+		fclose(fp);
+		send_dir(tab, path);
+		goto done;
 	}
 
-	if (fstat(fileno(f), &sb) == -1) {
-		send_errno(imsg->hdr.peerid, 40, "fstat", errno);
-		return;
-	}
+	parser_init(tab, initfn);
+	for (;;) {
+		size_t r;
 
-	if (S_ISDIR(sb.st_mode)) {
-		fclose(f);
-		send_dir(imsg->hdr.peerid, data);
-		return;
+		r = fread(buf, 1, sizeof(buf), fp);
+		if (!parser_parse(tab, buf, r))
+			break;
+		if (r != sizeof(buf))
+			break;
 	}
+	parser_free(tab);
 
-	if ((meta = file_type(data)) == NULL) {
-		fclose(f);
-		send_hdr(imsg->hdr.peerid, 51,
-		    "don't know how to visualize this file");
-		return;
-	}
-
-	send_hdr(imsg->hdr.peerid, 20, meta);
-	send_file(imsg->hdr.peerid, f);
+done:
+	if (fp != NULL)
+		fclose(fp);
+	else
+		load_page_from_str(tab, fallback);
 }
 
-static void
-handle_misc(struct imsg *imsg, size_t datalen)
+int
+bookmark_page(const char *url)
 {
-	switch (imsg->hdr.type) {
-	case IMSG_INIT:
-		load_certs();
-		load_hist();
-		load_last_session();
-		break;
+	FILE *f;
 
-	case IMSG_QUIT:
-		if (!safe_mode)
-			unlink(crashed_file);
-		event_loopbreak();
-		break;
-
-	default:
-		die();
-	}
+	if ((f = fopen(bookmark_file, "a")) == NULL)
+		return -1;
+	fprintf(f, "=> %s\n", url);
+	fclose(f);
+	return 0;
 }
 
-static void
-handle_bookmark_page(struct imsg *imsg, size_t datalen)
+int
+save_cert(const struct tofu_entry *e)
 {
-	char	*data;
-	int	 res;
-	FILE	*f;
+	FILE *f;
 
-	data = imsg->data;
-	if (data[datalen-1] != '\0')
-		die();
-
-	if ((f = fopen(bookmark_file, "a")) == NULL) {
-		res = errno;
-		goto end;
-	}
-	fprintf(f, "=> %s\n", data);
+	if ((f = fopen(known_hosts_file, "a")) == NULL)
+		return -1;
+	fprintf(f, "%s %s %d\n", e->domain, e->hash, e->verified);
 	fclose(f);
-
-	res = 0;
-end:
-	fs_send_ui(IMSG_BOOKMARK_OK, 0, -1, &res, sizeof(res));
+	return 0;
 }
 
-static void
-handle_save_cert(struct imsg *imsg, size_t datalen)
+int
+update_cert(const struct tofu_entry *e)
 {
-	struct tofu_entry	 e;
-	FILE			*f;
-	int			 res;
-
-	/* TODO: traverse the file to avoid duplications? */
-
-	if (datalen != sizeof(e))
-		die();
-	memcpy(&e, imsg->data, datalen);
-
-	if ((f = fopen(known_hosts_file, "a")) == NULL) {
-		res = errno;
-		goto end;
-	}
-	fprintf(f, "%s %s %d\n", e.domain, e.hash, e.verified);
-	fclose(f);
-
-	res = 0;
-end:
-	fs_send_ui(IMSG_SAVE_CERT_OK, imsg->hdr.peerid, -1,
-	    &res, sizeof(res));
-}
-
-static void
-handle_update_cert(struct imsg *imsg, size_t datalen)
-{
 	FILE	*tmp, *f;
-	struct	 tofu_entry entry;
 	char	 sfn[PATH_MAX], *line = NULL, *t;
 	size_t	 l, linesize = 0;
 	ssize_t	 linelen;
-	int	 fd, e, res = 0;
+	int	 fd, err;
 
-	if (datalen != sizeof(entry))
-		die();
-	memcpy(&entry, imsg->data, datalen);
-
 	strlcpy(sfn, known_hosts_tmp, sizeof(sfn));
 	if ((fd = mkstemp(sfn)) == -1 ||
 	    (tmp = fdopen(fd, "w")) == NULL) {
@@ -427,190 +298,41 @@ handle_update_cert(struct imsg *imsg, size_t datalen)
 			unlink(sfn);
 			close(fd);
 		}
-		res = 0;
-		goto end;
+		return -1;
 	}
 
 	if ((f = fopen(known_hosts_file, "r")) == NULL) {
 		unlink(sfn);
 		fclose(tmp);
-                res = 0;
-		goto end;
+		return -1;
 	}
 
-	l = strlen(entry.domain);
+	l = strlen(e->domain);
 	while ((linelen = getline(&line, &linesize, f)) != -1) {
-		if ((t = strstr(line, entry.domain)) != NULL &&
+		if ((t = strstr(line, e->domain)) != NULL &&
 		    (line[l] == ' ' || line[l] == '\t'))
 			continue;
 		/* line has a trailing \n */
 		fprintf(tmp, "%s", line);
 	}
-	fprintf(tmp, "%s %s %d\n", entry.domain, entry.hash, entry.verified);
+	fprintf(tmp, "%s %s %d\n", e->domain, e->hash, e->verified);
 
 	free(line);
-	e = ferror(tmp);
+	err = ferror(tmp);
 
 	fclose(tmp);
 	fclose(f);
 
-	if (e) {
+	if (err) {
 		unlink(sfn);
-		res = 0;
-		goto end;
+		return -1;
 	}
 
-	res = rename(sfn, known_hosts_file) != -1;
-
-end:
-	fs_send_ui(IMSG_UPDATE_CERT_OK, imsg->hdr.peerid, -1,
-	    &res, sizeof(res));
-}
-
-static void
-handle_file_open(struct imsg *imsg, size_t datalen)
-{
-	char	*path, *e;
-	int	 fd;
-
-	path = imsg->data;
-	if (path[datalen-1] != '\0')
-		die();
-
-	if ((fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0644)) == -1) {
-		e = strerror(errno);
-		fs_send_ui(IMSG_FILE_OPENED, imsg->hdr.peerid, -1,
-		    e, strlen(e)+1);
-	} else
-		fs_send_ui(IMSG_FILE_OPENED, imsg->hdr.peerid, fd,
-		    NULL, 0);
-}
-
-static void
-handle_session_start(struct imsg *imsg, size_t datalen)
-{
-	if (datalen != 0)
-		die();
-
-	if ((session = fopen(session_file, "w")) == NULL)
-		die();
-}
-
-static void
-handle_session_tab(struct imsg *imsg, size_t datalen)
-{
-	struct session_tab	tab;
-
-	if (session == NULL)
-		die();
-
-	if (datalen != sizeof(tab))
-		die();
-
-	memcpy(&tab, imsg->data, sizeof(tab));
-	if (tab.uri[sizeof(tab.uri)-1] != '\0' ||
-	    tab.title[sizeof(tab.title)-1] != '\0')
-		die();
-
-	fprintf(session, "%s ", tab.uri);
-
-	if (tab.flags & TAB_CURRENT)
-		fprintf(session, "current,");
-	if (tab.flags & TAB_KILLED)
-		fprintf(session, "killed,");
-
-	fprintf(session, "top=%zu,cur=%zu %s\n", tab.top_line,
-	    tab.current_line, tab.title);
-}
-
-static void
-handle_session_tab_hist(struct imsg *imsg, size_t datalen)
-{
-	struct session_tab_hist th;
-
-	if (session == NULL)
-		die();
-
-	if (datalen != sizeof(th))
-		die();
-
-	memcpy(&th, imsg->data, sizeof(th));
-	if (th.uri[sizeof(th.uri)-1] != '\0')
-		die();
-
-	fprintf(session, "%s %s\n", th.future ? ">" : "<", th.uri);
-}
-
-static void
-handle_session_end(struct imsg *imsg, size_t datalen)
-{
-	if (session == NULL)
-		die();
-	fclose(session);
-	session = NULL;
-}
-
-static void
-handle_hist(struct imsg *imsg, size_t datalen)
-{
-	static FILE *hist;
-	struct histitem hi;
-
-	switch (imsg->hdr.type) {
-	case IMSG_HIST_ITEM:
-		if (hist == NULL) {
-			if ((hist = fopen(history_file, "a")) == NULL)
-				return;
-		}
-		if (datalen != sizeof(hi))
-			abort();
-		memcpy(&hi, imsg->data, sizeof(hi));
-		fprintf(hist, "%lld %s\n", (long long)hi.ts, hi.uri);
-		break;
-
-	case IMSG_HIST_END:
-		if (hist == NULL)
-			return;
-		fclose(hist);
-		hist = NULL;
-		break;
-
-	default:
-		abort();
-	}
-}
-
-static void
-handle_dispatch_imsg(int fd, short ev, void *d)
-{
-	struct imsgev	*iev = d;
-	int		 e;
-
-	if (dispatch_imsg(iev, ev, handlers, sizeof(handlers)) == -1) {
-		/*
-		 * This should leave a ~/.cache/telescope/crashed file to
-		 * trigger about:crash on next run.  Unfortunately, if
-		 * the main process dies the fs sticks around and
-		 * doesn't notice that the fd was closed.  Why EV_READ
-		 * is not triggered when a fd is closed on the other end?
-		 */
-		e = errno;
-		if ((fd = open(crashed_file, O_CREAT|O_TRUNC|O_WRONLY, 0600))
-		    == -1)
-			err(1, "open");
-		close(fd);
-		errx(1, "connection closed: %s", strerror(e));
-	}
+	if (rename(sfn, known_hosts_file))
+		return -1;
+	return 0;
 }
 
-static int
-fs_send_ui(int type, uint32_t peerid, int fd, const void *data,
-    uint16_t datalen)
-{
-	return imsg_compose_event(iev_ui, type, peerid, 0, fd,
-	    data, datalen);
-}
-
 static size_t
 join_path(char *buf, const char *lhs, const char *rhs, size_t buflen)
 {
@@ -733,80 +455,89 @@ fs_init(void)
 }
 
 /*
- * Parse a line of the session file.  The format is:
+ * Parse a line of the session file and restores it.  The format is:
  *
  *	URL [flags,...] [title]\n
  */
-static void
-parse_session_line(char *line)
+static inline struct tab *
+parse_session_line(char *line, struct tab **ct)
 {
-	struct session_tab tab;
+	struct tab *tab;
 	char *s, *t, *ap;
+	const char *uri, *title = "";
+	int current = 0, killed = 0;
+	size_t top_line = 0, current_line = 0;
 
-	memset(&tab, 0, sizeof(tab));
-
+	uri = line;
 	if ((s = strchr(line, ' ')) == NULL)
-		return;
+		return NULL;
 
 	*s++ = '\0';
 
-	if (strlcpy(tab.uri, line, sizeof(tab.uri)) >= sizeof(tab.uri))
-		return;
-
 	if ((t = strchr(s, ' ')) != NULL) {
 		*t++ = '\0';
-
-		/* don't worry about cached title truncation */
-		strlcpy(tab.title, t, sizeof(tab.title));
+		title = t;
 	}
 
 	while ((ap = strsep(&s, ",")) != NULL) {
 		if (!strcmp(ap, "current"))
-			tab.flags |= TAB_CURRENT;
+			current = 1;
 		else if (!strcmp(ap, "killed"))
-			tab.flags |= TAB_KILLED;
+			killed = 1;
 		else if (has_prefix(ap, "top="))
-			tab.top_line = strtonum(ap+4, 0, UINT32_MAX, NULL);
+			top_line = strtonum(ap+4, 0, UINT32_MAX, NULL);
 		else if (has_prefix(ap, "cur="))
-			tab.current_line = strtonum(ap+4, 0, UINT32_MAX, NULL);
+			current_line = strtonum(ap+4, 0, UINT32_MAX, NULL);
 	}
 
-	if (tab.top_line > tab.current_line) {
-		tab.top_line = 0;
-		tab.current_line = 0;
+	if (top_line > current_line) {
+		top_line = 0;
+		current_line = 0;
 	}
 
-	fs_send_ui(IMSG_SESSION_TAB, 0, -1, &tab, sizeof(tab));
+	if ((tab = new_tab(uri, NULL, NULL)) == NULL)
+		die();
+	tab->hist_cur->line_off = top_line;
+	tab->hist_cur->current_off = current_line;
+	strlcpy(tab->buffer.page.title, title, sizeof(tab->buffer.page.title));
+
+	if (current)
+		*ct = tab;
+	else if (killed)
+		kill_tab(tab, 1);
+
+	return tab;
 }
 
 static inline void
-sendhist(const char *uri, int future)
+sendhist(struct tab *tab, const char *uri, int future)
 {
-	struct session_tab_hist sth;
+	struct hist *h;
 
-	memset(&sth, 0, sizeof(sth));
-	sth.future = future;
+	if ((h = calloc(1, sizeof(*h))) == NULL)
+		die();
+	strlcpy(h->h, uri, sizeof(h->h));
 
-	if (strlcpy(sth.uri, uri, sizeof(sth.uri)) >= sizeof(sth.uri))
-		return;
-
-	fs_send_ui(IMSG_SESSION_TAB_HIST, 0, -1, &sth, sizeof(sth));
+	if (future)
+		hist_push(&tab->hist, h);
+	else
+		hist_add_before(&tab->hist, tab->hist_cur, h);
 }
 
 static void
 load_last_session(void)
 {
+	struct tab	*tab = NULL, *ct = NULL;
 	FILE		*session;
 	size_t		 linesize = 0;
 	ssize_t		 linelen;
-	int		 first_time = 0;
 	int		 future;
 	char		*nl, *s, *line = NULL;
 
 	if ((session = fopen(session_file, "r")) == NULL) {
-		/* first time? */
-		first_time = 1;
-		goto end;
+		new_tab("about:new", NULL, NULL);
+		switch_to_tab(new_tab("about:help", NULL, NULL));
+		return;
 	}
 
 	while ((linelen = getline(&line, &linesize, session)) != -1) {
@@ -816,27 +547,22 @@ load_last_session(void)
 		if (*line == '<' || *line == '>') {
 			future = *line == '>';
 			s = line+1;
-			if (*s != ' ')
+			if (*s != ' ' || tab == NULL)
 				continue;
-			sendhist(++s, future);
+			sendhist(tab, ++s, future);
 		} else {
-			parse_session_line(line);
+			tab = parse_session_line(line, &ct);
 		}
 	}
 
 	fclose(session);
 	free(line);
 
-	if (last_time_crashed()) {
-		struct session_tab tab;
-		memset(&tab, 0, sizeof(tab));
-		tab.flags = TAB_CURRENT;
-		strlcpy(tab.uri, "about:crash", sizeof(tab.uri));
-		fs_send_ui(IMSG_SESSION_TAB, 0, -1, &tab, sizeof(tab));
-	}
+	if (ct != NULL)
+		switch_to_tab(ct);
 
-end:
-	fs_send_ui(IMSG_SESSION_END, 0, -1, &first_time, sizeof(first_time));
+	if (last_time_crashed())
+		switch_to_tab(new_tab("about:crash", NULL, NULL));
 }
 
 static void
@@ -850,7 +576,7 @@ load_hist(void)
 	struct histitem	 hi;
 
 	if ((hist = fopen(history_file, "r")) == NULL)
-		goto end;
+		return;
 
 	while ((linelen = getline(&line, &linesize, hist)) != -1) {
 		if ((nl = strchr(line, '\n')) != NULL)
@@ -867,37 +593,21 @@ load_hist(void)
 		if (strlcpy(hi.uri, spc, sizeof(hi.uri)) >= sizeof(hi.uri))
 			continue;
 
-		fs_send_ui(IMSG_HIST_ITEM, 0, -1, &hi, sizeof(hi));
+		history_push(&hi);
 	}
 
 	fclose(hist);
 	free(line);
-end:
-	fs_send_ui(IMSG_HIST_END, 0, -1, NULL, 0);
+
+	history_sort();
 }
 
 int
-fs_main(void)
+fs_load_state(struct ohash *certs)
 {
-	setproctitle("fs");
-
-	fs_init();
-
-	event_init();
-
-	/* Setup pipe and event handler to the main process */
-	if ((iev_ui = malloc(sizeof(*iev_ui))) == NULL)
-		die();
-	imsg_init(&iev_ui->ibuf, 3);
-	iev_ui->handler = handle_dispatch_imsg;
-	iev_ui->events = EV_READ;
-	event_set(&iev_ui->ev, iev_ui->ibuf.fd, iev_ui->events,
-	    iev_ui->handler, iev_ui);
-	event_add(&iev_ui->ev, NULL);
-
-	sandbox_fs_process();
-
-	event_dispatch();
+	load_certs(certs);
+	load_hist();
+	load_last_session();
 	return 0;
 }
 
@@ -962,33 +672,37 @@ parse_khost_line(char *line, char *tmp[3])
 }
 
 static void
-load_certs(void)
+load_certs(struct ohash *certs)
 {
 	char		*tmp[3], *line = NULL;
 	const char	*errstr;
 	size_t		 lineno = 0, linesize = 0;
 	ssize_t		 linelen;
 	FILE		*f;
-	struct tofu_entry e;
+	struct tofu_entry *e;
 
 	if ((f = fopen(known_hosts_file, "r")) == NULL)
 		return;
 
+	if ((e = calloc(1, sizeof(*e))) == NULL) {
+		fclose(f);
+		return;
+	}
+
 	while ((linelen = getline(&line, &linesize, f)) != -1) {
 		lineno++;
 
-		memset(&e, 0, sizeof(e));
 		if (parse_khost_line(line, tmp)) {
-			strlcpy(e.domain, tmp[0], sizeof(e.domain));
-			strlcpy(e.hash, tmp[1], sizeof(e.hash));
+			strlcpy(e->domain, tmp[0], sizeof(e->domain));
+			strlcpy(e->hash, tmp[1], sizeof(e->hash));
 
-			e.verified = strtonum(tmp[2], 0, 1, &errstr);
+			e->verified = strtonum(tmp[2], 0, 1, &errstr);
 			if (errstr != NULL)
 				errx(1, "%s:%zu verification for %s is %s: %s",
 				    known_hosts_file, lineno,
-				    e.domain, errstr, tmp[2]);
+				    e->domain, errstr, tmp[2]);
 
-			fs_send_ui(IMSG_TOFU, 0, -1, &e, sizeof(e));
+			tofu_add(certs, e);
 		} else {
 			warnx("%s:%zu invalid entry",
 			    known_hosts_file, lineno);
@@ -999,4 +713,3 @@ load_certs(void)
 	fclose(f);
 	return;
 }
-
blob - 6337fed6e6cfa36597a25e71245cf9b17eec0d16
blob + da0257e65eb443f4bdfca3f75baa41cce5e60076
--- include/fs.h
+++ include/fs.h
@@ -17,10 +17,9 @@
 #ifndef FS_H
 #define FS_H
 
-#include "compat.h"
+struct tab;
+struct tofu_entry;
 
-#include <limits.h>
-
 extern char	config_path_base[PATH_MAX];
 extern char	data_path_base[PATH_MAX];
 extern char	cache_path_base[PATH_MAX];
@@ -32,9 +31,14 @@ extern char	bookmark_file[PATH_MAX];
 extern char	known_hosts_file[PATH_MAX], known_hosts_tmp[PATH_MAX];
 extern char	crashed_file[PATH_MAX];
 extern char	session_file[PATH_MAX];
+extern char	history_file[PATH_MAX];
 
 int		 fs_init(void);
-int		 fs_main(void);
 int		 lock_session(void);
+void		 fs_load_url(struct tab *, const char *);
+int		 bookmark_page(const char *);
+int		 save_cert(const struct tofu_entry *e);
+int		 update_cert(const struct tofu_entry *e);
+int		 fs_load_state(struct ohash *);
 
 #endif
blob - 6a667eb7b16f9dad1e16d5bf2c992fa983d7a215
blob + e84ebaa3ff51abebb6b2a55dbf935db3ccc15b46
--- include/telescope.h
+++ include/telescope.h
@@ -144,6 +144,8 @@ struct vline {
 struct parser;
 
 typedef int	(*printfn)(void *, const char *, ...);
+
+typedef void	(*parserinit)(struct parser *);
 
 typedef int	(*parsechunkfn)(struct parser *, const char *, size_t);
 typedef int	(*parserfreefn)(struct parser *);
@@ -162,7 +164,7 @@ struct parser {
 #define PARSER_IN_PRE	2
 #define PARSER_IN_PATCH_HDR 4
 	int		 flags;
-	void		(*init)(struct parser *);
+	parserinit	 init;
 	parsechunkfn	 parse;
 	parserfreefn	 free;
 	parserserial	 serialize;
@@ -308,7 +310,7 @@ struct download {
 };
 
 void		 recompute_downloads(void);
-void		 enqueue_download(uint32_t, const char *, int);
+struct download	*enqueue_download(uint32_t, const char *, int);
 void		 dequeue_first_download(void);
 struct download	*download_by_id(uint32_t);
 
@@ -334,7 +336,6 @@ void		 parseconfig(const char *, int);
 /* sandbox.c */
 void		 sandbox_net_process(void);
 void		 sandbox_ui_process(void);
-void		 sandbox_fs_process(void);
 
 /* telescope.c */
 extern int operating;
@@ -350,11 +351,9 @@ void		 load_url(struct tab *, const char *, const char
 void		 load_url_in_tab(struct tab *, const char *, const char *, int);
 int		 load_previous_page(struct tab*);
 int		 load_next_page(struct tab*);
-void		 add_to_bookmarks(const char*);
 void		 write_buffer(const char *, struct tab *);
 void		 humanify_url(const char *, char *, size_t);
 int		 ui_send_net(int, uint32_t, const void *, uint16_t);
-int		 ui_send_fs(int, uint32_t, const void *, uint16_t);
 
 /* tofu.c */
 void			 tofu_init(struct ohash*, unsigned int, ptrdiff_t);
blob - 555f1d665c6d73a02a51cb9379bf25597b553d06
blob + f12756a98c82f0afcdb1f648e668b0c48b3355bd
--- minibuffer.c
+++ minibuffer.c
@@ -16,10 +16,13 @@
 
 #include "compat.h"
 
+#include <errno.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
+#include "fs.h"
 #include "minibuffer.h"
 #include "session.h"
 #include "ui.h"
@@ -331,9 +334,11 @@ void
 bp_select(void)
 {
 	exit_minibuffer();
-	if (*ministate.buf != '\0')
-		add_to_bookmarks(ministate.buf);
-	else
+	if (*ministate.buf != '\0') {
+		if (!bookmark_page(ministate.buf))
+			message("failed to bookmark page: %s",
+			    strerror(errno));
+	} else
 		message("Abort.");
 }
 
blob - 0f2c57ee4215b0e220bf6d9e0f1a1f0ae42ae4c6
blob + 87cfa487e43b3a8c143408f5da36fcf7a17f766b
--- sandbox.c
+++ sandbox.c
@@ -16,13 +16,14 @@
 
 #include "compat.h"
 
+#include <limits.h>
+
 #include "fs.h"
 #include "telescope.h"
 
 #ifdef __OpenBSD__
 
 # include <errno.h>
-# include <limits.h>
 # include <stdlib.h>
 # include <string.h>
 # include <unistd.h>
@@ -37,13 +38,6 @@ sandbox_net_process(void)
 void
 sandbox_ui_process(void)
 {
-	if (pledge("stdio tty unix recvfd", NULL) == -1)
-		err(1, "pledge");
-}
-
-void
-sandbox_fs_process(void)
-{
 	char path[PATH_MAX];
 
 	if (unveil("/tmp", "rwc") == -1)
@@ -63,7 +57,7 @@ sandbox_fs_process(void)
 	if (unveil(cache_path_base, "rwc") == -1)
 		err(1, "unveil(%s)", cache_path_base);
 
-	if (pledge("stdio rpath wpath cpath sendfd", NULL) == -1)
+	if (pledge("stdio rpath wpath cpath unix tty", NULL) == -1)
 		err(1, "pledge");
 }
 
@@ -212,45 +206,11 @@ sandbox_net_process(void)
 void
 sandbox_ui_process(void)
 {
-	if (landlock_no_fs() == -1)
-		err(1, "landlock");
-}
-
-void
-sandbox_fs_process(void)
-{
-	int fd, rwc;
-	char path[PATH_MAX];
-
 	/*
-	 * XXX: at build-time we found landlock.h but we've just
-	 * realized it's not available on this kernel, so do nothing.
+	 * Needs to be able to read files *and* execute programs,
+	 * can't be sandboxed.
 	 */
-	if ((fd = open_landlock()) == -1)
-		return;
-
-	rwc =	LANDLOCK_ACCESS_FS_READ_FILE	|
-		LANDLOCK_ACCESS_FS_READ_DIR	|
-		LANDLOCK_ACCESS_FS_WRITE_FILE	|
-		LANDLOCK_ACCESS_FS_MAKE_DIR	|
-		LANDLOCK_ACCESS_FS_MAKE_REG;
-
-	if (landlock_unveil(fd, "/tmp", rwc) == -1)
-		err(1, "landlock_unveil(/tmp)");
-
-	strlcpy(path, getenv("HOME"), sizeof(path));
-	strlcat(path, "/Downloads", sizeof(path));
-	if (landlock_unveil(fd, path, rwc) == -1 && errno != ENOENT)
-		err(1, "landlock_unveil(%s)", path);
-
-	if (landlock_unveil(fd, config_path_base, rwc) == -1)
-		err(1, "landlock_unveil(%s)", config_path_base);
-
-	if (landlock_unveil(fd, data_path_base, rwc) == -1)
-		err(1, "landlock_unveil(%s)", data_path_base);
-
-	if (landlock_unveil(fd, cache_path_base, rwc) == -1)
-		err(1, "landlock_unveil(%s)", cache_path_base);
+	return;
 }
 
 #else
@@ -269,10 +229,4 @@ sandbox_ui_process(void)
 	return;
 }
 
-void
-sandbox_fs_process(void)
-{
-	return;
-}
-
 #endif
blob - d5cdb78eff0b21b41f3eaaab0448c30f63689e41
blob + b5bd2aaf1318f0aded094a8b90bd8a7519ff04d4
--- session.c
+++ session.c
@@ -17,6 +17,7 @@
 #include "compat.h"
 
 #include <errno.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -161,26 +162,23 @@ stop_tab(struct tab *tab)
 }
 
 static inline void
-sendtab(struct tab *tab, int killed)
+savetab(FILE *fp, struct tab *tab, int killed)
 {
-	struct session_tab	 st;
-	struct session_tab_hist	 sth;
-	struct hist		*h;
-	int			 future;
+	struct hist	*h;
+	size_t		 top_line, current_line;
+	int		 future;
 
-	memset(&st, 0, sizeof(st));
+	get_scroll_position(tab, &top_line, &current_line);
 
+	fprintf(fp, "%s ", tab->hist_cur->h);
 	if (tab == current_tab)
-		st.flags |= TAB_CURRENT;
+		fprintf(fp, "current,");
 	if (killed)
-		st.flags |= TAB_KILLED;
+		fprintf(fp, "killed,");
 
-	get_scroll_position(tab, &st.top_line, &st.current_line);
+	fprintf(fp, "top=%zu,cur=%zu %s\n", top_line, current_line,
+	    tab->buffer.page.title);
 
-	strlcpy(st.uri, tab->hist_cur->h, sizeof(st.uri));
-	strlcpy(st.title, tab->buffer.page.title, sizeof(st.title));
-	ui_send_fs(IMSG_SESSION_TAB, 0, &st, sizeof(st));
-
 	future = 0;
 	TAILQ_FOREACH(h, &tab->hist.head, entries) {
 		if (h == tab->hist_cur) {
@@ -188,33 +186,33 @@ sendtab(struct tab *tab, int killed)
 			continue;
 		}
 
-		memset(&sth, 0, sizeof(sth));
-		strlcpy(sth.uri, h->h, sizeof(sth.uri));
-		sth.future = future;
-		ui_send_fs(IMSG_SESSION_TAB_HIST, 0, &sth, sizeof(sth));
+		fprintf(fp, "%s %s\n", future ? ">" : "<", h->h);
 	}
-
 }
 
 void
 save_session(void)
 {
+	FILE			*session, *hist;
 	struct tab		*tab;
-	struct histitem		 hi;
 	size_t			 i;
 
 	if (safe_mode)
 		return;
 
-	ui_send_fs(IMSG_SESSION_START, 0, NULL, 0);
+	if ((session = fopen(session_file, "w")) == NULL)
+		return;
 
 	TAILQ_FOREACH(tab, &tabshead, tabs)
-		sendtab(tab, 0);
+		savetab(session, tab, 0);
 	TAILQ_FOREACH(tab, &ktabshead, tabs)
-		sendtab(tab, 1);
+		savetab(session, tab, 1);
 
-	ui_send_fs(IMSG_SESSION_END, 0, NULL, 0);
+	fclose(session);
 
+	if ((hist = fopen(history_file, "a")) == NULL)
+		return;
+
 	if (history.dirty) {
 		for (i = 0; i < history.len && history.dirty > 0; ++i) {
 			if (!history.items[i].dirty)
@@ -222,14 +220,14 @@ save_session(void)
 			history.dirty--;
 			history.items[i].dirty = 0;
 
-			memset(&hi, 0, sizeof(hi));
-			hi.ts = history.items[i].ts;
-			strlcpy(hi.uri, history.items[i].uri, sizeof(hi.uri));
-			ui_send_fs(IMSG_HIST_ITEM, 0, &hi, sizeof(hi));
+			fprintf(hist, "%lld %s\n",
+			    (long long)history.items[i].ts,
+			    history.items[i].uri);
 		}
-		ui_send_fs(IMSG_HIST_END, 0, NULL, 0);
 		history.dirty = 0;
 	}
+
+	fclose(hist);
 }
 
 void
blob - 9061f38f9535cc4211f5549e5de4c14c681030d1
blob + be43e355c74f7332ed707c95332953fb45ee1e2d
--- telescope.c
+++ telescope.c
@@ -21,7 +21,9 @@
 #include <sys/wait.h>
 
 #include <errno.h>
+#include <fcntl.h>
 #include <getopt.h>
+#include <limits.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -64,7 +66,7 @@ int			 operating;
  */
 int			safe_mode;
 
-static struct imsgev	*iev_fs, *iev_net;
+static struct imsgev	*iev_net;
 
 struct tabshead		 tabshead = TAILQ_HEAD_INITIALIZER(tabshead);
 struct tabshead		 ktabshead = TAILQ_HEAD_INITIALIZER(ktabshead);
@@ -72,7 +74,6 @@ struct proxylist	 proxies = TAILQ_HEAD_INITIALIZER(pro
 
 enum telescope_process {
 	PROC_UI,
-	PROC_FS,
 	PROC_NET,
 };
 
@@ -117,15 +118,8 @@ static void		 handle_imsg_got_code(struct imsg *, size
 static void		 handle_imsg_got_meta(struct imsg *, size_t);
 static void		 handle_maybe_save_page(int, struct tab *);
 static void		 handle_save_page_path(const char *, struct tab *);
-static void		 handle_imsg_file_opened(struct imsg *, size_t);
 static void		 handle_imsg_buf(struct imsg *, size_t);
 static void		 handle_imsg_eof(struct imsg *, size_t);
-static void		 handle_imsg_tofu(struct imsg *, size_t);
-static void		 handle_imsg_bookmark_ok(struct imsg *, size_t);
-static void		 handle_imsg_save_cert_ok(struct imsg *, size_t);
-static void		 handle_imsg_update_cert_ok(struct imsg *, size_t);
-static void		 handle_imsg_session(struct imsg *, size_t);
-static void		 handle_imsg_history(struct imsg *, size_t);
 static void		 handle_dispatch_imsg(int, short, void *);
 static int		 load_about_url(struct tab *, const char *);
 static int		 load_file_url(struct tab *, const char *);
@@ -136,7 +130,6 @@ static int		 load_via_proxy(struct tab *, const char *
 			     struct proxy *);
 static int		 make_request(struct tab *, struct get_req *, int,
 			     const char *);
-static int		 make_fs_request(struct tab *, int, const char *);
 static int		 do_load_url(struct tab *, const char *, const char *, int);
 static pid_t		 start_child(enum telescope_process, const char *, int);
 static void		 send_url(const char *);
@@ -161,16 +154,6 @@ static imsg_handlerfn *handlers[] = {
 	[IMSG_GOT_META] = handle_imsg_got_meta,
 	[IMSG_BUF] = handle_imsg_buf,
 	[IMSG_EOF] = handle_imsg_eof,
-	[IMSG_TOFU] = handle_imsg_tofu,
-	[IMSG_BOOKMARK_OK] = handle_imsg_bookmark_ok,
-	[IMSG_SAVE_CERT_OK] = handle_imsg_save_cert_ok,
-	[IMSG_UPDATE_CERT_OK] = handle_imsg_update_cert_ok,
-	[IMSG_FILE_OPENED] = handle_imsg_file_opened,
-	[IMSG_SESSION_TAB] = handle_imsg_session,
-	[IMSG_SESSION_TAB_HIST] = handle_imsg_session,
-	[IMSG_SESSION_END] = handle_imsg_session,
-	[IMSG_HIST_ITEM] = handle_imsg_history,
-	[IMSG_HIST_END] = handle_imsg_history,
 };
 
 static struct ohash	certs;
@@ -253,7 +236,7 @@ handle_imsg_check_cert(struct imsg *imsg, size_t datal
 		}
 		strlcpy(e->hash, hash, sizeof(e->hash));
 		tofu_add(&certs, e);
-		ui_send_fs(IMSG_SAVE_CERT, tab->id, e, sizeof(*e));
+		save_cert(e);
 	} else
 		tofu_res = !strcmp(hash, e->hash);
 
@@ -332,8 +315,8 @@ handle_maybe_save_new_cert(int accept, struct tab *tab
 		strlcat(e->domain, port, sizeof(e->domain));
 	}
 	strlcpy(e->hash, tab->cert, sizeof(e->hash));
-	ui_send_fs(IMSG_UPDATE_CERT, 0, e, sizeof(*e));
 
+	update_cert(e);
 	tofu_update(&certs, e);
 
 	tab->trust = TS_TRUSTED;
@@ -471,6 +454,9 @@ handle_maybe_save_page(int dosave, struct tab *tab)
 static void
 handle_save_page_path(const char *path, struct tab *tab)
 {
+	struct download *d;
+	int fd;
+
 	if (path == NULL) {
 		stop_tab(tab);
 		return;
@@ -478,39 +464,21 @@ handle_save_page_path(const char *path, struct tab *ta
 
 	ui_show_downloads_pane();
 
-	enqueue_download(tab->id, path, 0);
-	ui_send_fs(IMSG_FILE_OPEN, tab->id, path, strlen(path)+1);
+	d = enqueue_download(tab->id, path, 0);
 
 	/*
 	 * Change this tab id, the old one is associated with the
 	 * download now.
 	 */
 	tab->id = tab_new_id();
-}
-
-static void
-handle_imsg_file_opened(struct imsg *imsg, size_t datalen)
-{
-	struct download	*d;
-	const char	*e;
 
-	/*
-	 * There are no reason we shouldn't be able to find the
-	 * required download.
-	 */
-	if ((d = download_by_id(imsg->hdr.peerid)) == NULL)
-		die();
-
-	if (imsg->fd == -1) {
-		e = imsg->data;
-		if (e[datalen-1] != '\0')
-			die();
-		message("Can't open file %s: %s", d->path, e);
-	} else if (d->buffer) {
+	if ((fd = open(path, O_WRONLY|O_TRUNC|O_CREAT, 0644)) == -1)
+		message("Can't open file %s: %s", d->path, strerror(errno));
+	else if (d->buffer) {
 		FILE *fp;
 		int r;
 
-		if ((fp = fdopen(imsg->fd, "w")) != NULL) {
+		if ((fp = fdopen(fd, "w")) != NULL) {
 			r = parser_serialize(current_tab, fp);
 			if (!r)
 				message("Failed to save the page.");
@@ -519,118 +487,12 @@ handle_imsg_file_opened(struct imsg *imsg, size_t data
 
 		dequeue_first_download();
 	} else {
-		d->fd = imsg->fd;
+		d->fd = fd;
 		ui_send_net(IMSG_PROCEED, d->id, NULL, 0);
-	}
-}
-
-static void
-handle_imsg_session(struct imsg *imsg, size_t datalen)
-{
-	static struct tab	*curr;
-	static struct tab	*tab;
-	struct session_tab	 st;
-	struct session_tab_hist	 sth;
-	struct hist		*h;
-	int			 first_time;
-
-	/*
-	 * The fs process tried to send tabs after it has announced
-	 * that he's done.  Something fishy is going on, better die.
-	 */
-	if (operating)
-		die();
-
-	switch (imsg->hdr.type) {
-	case IMSG_SESSION_TAB:
-		if (datalen != sizeof(st))
-			die();
-
-		memcpy(&st, imsg->data, sizeof(st));
-		if ((tab = new_tab(st.uri, NULL, NULL)) == NULL)
-			die();
-		tab->hist_cur->line_off = st.top_line;
-		tab->hist_cur->current_off = st.current_line;
-		strlcpy(tab->buffer.page.title, st.title,
-		    sizeof(tab->buffer.page.title));
-		if (st.flags & TAB_CURRENT)
-			curr = tab;
-		if (st.flags & TAB_KILLED)
-			kill_tab(tab, 1);
-		break;
-
-	case IMSG_SESSION_TAB_HIST:
-		if (tab == NULL || datalen != sizeof(sth))
-			die();
-
-		memcpy(&sth, imsg->data, sizeof(sth));
-		if (sth.uri[sizeof(sth.uri)-1] != '\0')
-			die();
-
-		if ((h = calloc(1, sizeof(*h))) == NULL)
-			die();
-		strlcpy(h->h, sth.uri, sizeof(h->h));
-
-		if (sth.future)
-			hist_push(&tab->hist, h);
-		else
-			hist_add_before(&tab->hist, tab->hist_cur, h);
-		break;
-
-	case IMSG_SESSION_END:
-		if (datalen != sizeof(first_time))
-			die();
-		memcpy(&first_time, imsg->data, sizeof(first_time));
-		if (first_time) {
-			new_tab("about:new", NULL, NULL);
-			curr = new_tab("about:help", NULL, NULL);
-		}
-
-		operating = 1;
-		if (curr != NULL)
-			switch_to_tab(curr);
-		if (has_url || TAILQ_EMPTY(&tabshead))
-			new_tab(url, NULL, NULL);
-		ui_main_loop();
-		break;
-
-	default:
-		die();
 	}
 }
 
 static void
-handle_imsg_history(struct imsg *imsg, size_t datalen)
-{
-	struct histitem hi;
-
-	/*
-	 * The fs process tried to send history item after it
-	 * has announced that it's done.  Something fishy is
-	 * going on, better die
-	 */
-	if (operating)
-		die();
-
-	switch (imsg->hdr.type) {
-	case IMSG_HIST_ITEM:
-		if (datalen != sizeof(hi))
-			die();
-
-		memcpy(&hi, imsg->data, sizeof(hi));
-		history_push(&hi);
-		break;
-
-	case IMSG_HIST_END:
-		history_sort();
-		break;
-
-	default:
-		die();
-	}
-}
-
-static void
 handle_imsg_buf(struct imsg *imsg, size_t datalen)
 {
 	struct tab	*tab = NULL;
@@ -677,67 +539,6 @@ handle_imsg_eof(struct imsg *imsg, size_t datalen)
 }
 
 static void
-handle_imsg_tofu(struct imsg *imsg, size_t datalen)
-{
-	struct tofu_entry *e;
-
-	if (operating)
-		die();
-
-	if ((e = calloc(1, sizeof(*e))) == NULL)
-		die();
-
-	if (datalen != sizeof(*e))
-		die();
-	memcpy(e, imsg->data, sizeof(*e));
-	if (e->domain[sizeof(e->domain)-1] != '\0' ||
-	    e->hash[sizeof(e->hash)-1] != '\0')
-		die();
-	tofu_add(&certs, e);
-}
-
-static void
-handle_imsg_bookmark_ok(struct imsg *imsg, size_t datalen)
-{
-	int res;
-
-	if (datalen != sizeof(res))
-		die();
-
-	memcpy(&res, imsg->data, sizeof(res));
-	if (res == 0)
-		message("Added to bookmarks!");
-	else
-		message("Failed to add to bookmarks: %s",
-		    strerror(res));
-}
-
-static void
-handle_imsg_save_cert_ok(struct imsg *imsg, size_t datalen)
-{
-	int res;
-
-	if (datalen != sizeof(res))
-		die();
-	memcpy(&res, imsg->data, datalen);
-	if (res != 0)
-		message("Failed to save the cert for: %s",
-		    strerror(res));
-}
-
-static void
-handle_imsg_update_cert_ok(struct imsg *imsg, size_t datalen)
-{
-	int res;
-
-	if (datalen != sizeof(res))
-		die();
-	memcpy(&res, imsg->data, datalen);
-	if (!res)
-		message("Failed to update the certificate");
-}
-
-static void
 handle_dispatch_imsg(int fd, short ev, void *d)
 {
 	struct imsgev	*iev = d;
@@ -749,16 +550,21 @@ handle_dispatch_imsg(int fd, short ev, void *d)
 static int
 load_about_url(struct tab *tab, const char *url)
 {
-	tab->trust = TS_UNKNOWN;
-	parser_init(tab, gemtext_initparser);
-	return make_fs_request(tab, IMSG_GET, url);
+	tab->trust = TS_TRUSTED;
+	fs_load_url(tab, url);
+	ui_on_tab_refresh(tab);
+	ui_on_tab_loaded(tab);
+	return 0;
 }
 
 static int
 load_file_url(struct tab *tab, const char *url)
 {
-	tab->trust = TS_UNKNOWN;
-	return make_fs_request(tab, IMSG_GET_FILE, tab->uri.path);
+	tab->trust = TS_TRUSTED;
+	fs_load_url(tab, url);
+	ui_on_tab_refresh(tab);
+	ui_on_tab_loaded(tab);
+	return 0;
 }
 
 static int
@@ -914,21 +720,6 @@ make_request(struct tab *tab, struct get_req *req, int
 	return 1;
 }
 
-static int
-make_fs_request(struct tab *tab, int type, const char *r)
-{
-	stop_tab(tab);
-	tab->id = tab_new_id();
-
-	ui_send_fs(type, tab->id, r, strlen(r)+1);
-
-	/*
-	 * So load_{about,file}_url can `return make_fs_request` and
-	 * do_load_url is happy.
-	 */
-	return 1;
-}
-
 void
 gopher_send_search_req(struct tab *tab, const char *text)
 {
@@ -1106,20 +897,18 @@ load_next_page(struct tab *tab)
 }
 
 void
-add_to_bookmarks(const char *str)
-{
-	ui_send_fs(IMSG_BOOKMARK_PAGE, 0,
-	    str, strlen(str)+1);
-}
-
-void
 write_buffer(const char *path, struct tab *tab)
 {
+	FILE *fp;
+
 	if (path == NULL)
 		return;
 
-	enqueue_download(tab->id, path, 1);
-	ui_send_fs(IMSG_FILE_OPEN, tab->id, path, strlen(path)+1);
+	if ((fp = fopen(path, "w")) == NULL)
+		return;
+	if (!parser_serialize(tab, fp))
+		message("Failed to save the page.");
+	fclose(fp);
 }
 
 /*
@@ -1186,9 +975,6 @@ start_child(enum telescope_process p, const char *argv
 	switch (p) {
 	case PROC_UI:
 		errx(1, "Can't start ui process");
-	case PROC_FS:
-		argv[argc++] = "-Tf";
-		break;
 	case PROC_NET:
 		argv[argc++] = "-Tn";
 		break;
@@ -1234,13 +1020,6 @@ ui_send_net(int type, uint32_t peerid, const void *dat
 	    datalen);
 }
 
-int
-ui_send_fs(int type, uint32_t peerid, const void *data, uint16_t datalen)
-{
-	return imsg_compose_event(iev_fs, type, peerid, 0, -1, data,
-	    datalen);
-}
-
 static void __attribute__((noreturn))
 usage(int r)
 {
@@ -1253,10 +1032,10 @@ usage(int r)
 int
 main(int argc, char * const *argv)
 {
-	struct imsgev	 net_ibuf, fs_ibuf;
+	struct imsgev	 net_ibuf;
 	pid_t		 pid;
 	int		 control_fd;
-	int		 pipe2net[2], pipe2fs[2];
+	int		 pipe2net[2];
 	int		 ch, configtest = 0, fail = 0;
 	int		 proc = -1;
 	int		 sessionfd = -1;
@@ -1271,8 +1050,6 @@ main(int argc, char * const *argv)
 	if (getenv("NO_COLOR") != NULL)
 		enable_colors = 0;
 
-	fs_init();
-
 	while ((ch = getopt_long(argc, argv, opts, longopts, NULL)) != -1) {
 		switch (ch) {
 		case 'C':
@@ -1291,9 +1068,6 @@ main(int argc, char * const *argv)
 			break;
 		case 'T':
 			switch (*optarg) {
-			case 'f':
-				proc = PROC_FS;
-				break;
 			case 'n':
 				proc = PROC_NET;
 				break;
@@ -1317,8 +1091,6 @@ main(int argc, char * const *argv)
 	if (proc != -1) {
 		if (argc > 0)
 			usage(1);
-		else if (proc == PROC_FS)
-			return fs_main();
 		else if (proc == PROC_NET)
 			return net_main();
 		else
@@ -1330,6 +1102,8 @@ main(int argc, char * const *argv)
 		humanify_url(argv[0], url, sizeof(url));
 	}
 
+	fs_init();
+
 	/* setup keys before reading the config */
 	TAILQ_INIT(&global_map.m);
 	global_map.unhandled_input = global_key_unbound;
@@ -1357,13 +1131,6 @@ main(int argc, char * const *argv)
 	}
 
 	/* Start children. */
-	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe2fs) == -1)
-		err(1, "socketpair");
-	start_child(PROC_FS, argv0, pipe2fs[1]);
-	imsg_init(&fs_ibuf.ibuf, pipe2fs[0]);
-	iev_fs = &fs_ibuf;
-	iev_fs->handler = handle_dispatch_imsg;
-
 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe2net) == -1)
 		err(1, "socketpair");
 	start_child(PROC_NET, argv0, pipe2net[1]);
@@ -1390,12 +1157,7 @@ main(int argc, char * const *argv)
 	/* Setup event handler for the autosave */
 	autosave_init();
 
-	/* Setup event handlers for pipes to fs/net */
-	iev_fs->events = EV_READ;
-	event_set(&iev_fs->ev, iev_fs->ibuf.fd, iev_fs->events,
-	    iev_fs->handler, iev_fs);
-	event_add(&iev_fs->ev, NULL);
-
+	/* Setup event handlers for pipes to net */
 	iev_net->events = EV_READ;
 	event_set(&iev_net->ev, iev_net->ibuf.fd, iev_net->events,
 	    iev_net->handler, iev_net);
@@ -1403,14 +1165,14 @@ main(int argc, char * const *argv)
 
 	if (ui_init()) {
 		sandbox_ui_process();
-		ui_send_fs(IMSG_INIT, 0, NULL, 0);
-		event_dispatch();
+		fs_load_state(&certs);
+		operating = 1;
+		switch_to_tab(current_tab);
+		ui_main_loop();
 		ui_end();
 	}
 
-	ui_send_fs(IMSG_QUIT, 0, NULL, 0);
 	ui_send_net(IMSG_QUIT, 0, NULL, 0);
-	imsg_flush(&iev_fs->ibuf);
 	imsg_flush(&iev_net->ibuf);
 
 	/* wait for children to terminate */
@@ -1423,6 +1185,9 @@ main(int argc, char * const *argv)
 			warnx("child terminated; signal %d", WTERMSIG(status));
 	} while (pid != -1 || (pid == -1 && errno == EINTR));
 
+	if (!safe_mode)
+		unlink(crashed_file);
+
 	if (!safe_mode && close(sessionfd) == -1)
 		err(1, "close(sessionfd = %d)", sessionfd);
 
blob - fe12f904031d9c35398cc59c25425e3f231b0d08
blob + 605da51f8dec8e5d9ea30481d277de1ba61cce50
--- ui.c
+++ ui.c
@@ -1200,6 +1200,8 @@ ui_main_loop(void)
 
 	switch_to_tab(current_tab);
 	rearrange_windows();
+
+	event_dispatch();
 }
 
 void