diff --git a/src/apk.c b/src/apk.c
index 20cc95feb83a9212614895e94e27809b2f21ec26..4be4bc1b9c4a7f420d92d485a4c697035f9771cc 100644
--- a/src/apk.c
+++ b/src/apk.c
@@ -564,7 +564,7 @@ int main(int argc, char **argv)
 	}
 
 	apk_crypto_init();
-	apk_io_url_init();
+	apk_io_url_init(&ctx.out);
 	apk_io_url_set_timeout(60);
 	apk_io_url_set_redirect_callback(redirect_callback);
 
diff --git a/src/apk_io.h b/src/apk_io.h
index f15f16efe8bbce0499e45c9c0007d91eaea0dea5..142f4f7c68ef2722893021fd90311623d0cba56c 100644
--- a/src/apk_io.h
+++ b/src/apk_io.h
@@ -18,6 +18,8 @@
 #include "apk_atom.h"
 #include "apk_crypto.h"
 
+struct apk_out;
+
 int apk_make_dirs(int root_fd, const char *dirname, mode_t dirmode, mode_t parentmode);
 ssize_t apk_write_fully(int fd, const void *ptr, size_t size);
 
@@ -132,7 +134,7 @@ static inline int apk_istream_close_error(struct apk_istream *is, int r)
 	return apk_istream_close(is);
 }
 
-void apk_io_url_init(void);
+void apk_io_url_init(struct apk_out *out);
 void apk_io_url_set_timeout(int timeout);
 void apk_io_url_set_redirect_callback(void (*cb)(int, const char *));
 void apk_io_url_no_check_certificate(void);
diff --git a/src/apk_process.h b/src/apk_process.h
index ed647be05648b6ccc837cec51a830954a13cecd7..eba482a6bc8abec93e5b4c54183bf037b5e9ad9e 100644
--- a/src/apk_process.h
+++ b/src/apk_process.h
@@ -23,6 +23,7 @@ struct apk_process {
 	struct apk_out *out;
 	struct apk_istream *is;
 	apk_blob_t is_blob;
+	int status;
 	unsigned int is_eof : 1;
 	struct buf {
 		uint16_t len;
@@ -35,5 +36,6 @@ pid_t apk_process_fork(struct apk_process *p);
 int apk_process_spawn(struct apk_process *p, const char *path, char * const* argv, char * const* env);
 int apk_process_run(struct apk_process *p);
 int apk_process_cleanup(struct apk_process *p);
+struct apk_istream *apk_process_istream(char * const* argv, struct apk_out *out, const char *argv0);
 
 #endif
diff --git a/src/context.c b/src/context.c
index 19e8138345f08344a31386b3ebac5acaa6d806a4..fd5f9ee93e5a2a47e60ab6bd52a5f047198da496 100644
--- a/src/context.c
+++ b/src/context.c
@@ -66,7 +66,7 @@ int apk_ctx_prepare(struct apk_ctx *ac)
 		ac->root_set = 1;
 		if (!ac->uvol) ac->uvol = ERR_PTR(-APKE_UVOL_ROOT);
 	}
-	if (!IS_ERR(ac->uvol) && access(ac->uvol, X_OK) != 0)
+	if (!IS_ERR(ac->uvol) && (ac->uvol[0] != '/' || access(ac->uvol, X_OK) != 0))
 		ac->uvol = ERR_PTR(-APKE_UVOL_NOT_AVAILABLE);
 
 	ac->root_fd = openat(AT_FDCWD, ac->root, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
diff --git a/src/io_url_libfetch.c b/src/io_url_libfetch.c
index 67f9664fe8d3f0f366f1f84a8945c499a0772cc1..7a9fa91c5974214f56a22eac58a99f755437ce51 100644
--- a/src/io_url_libfetch.c
+++ b/src/io_url_libfetch.c
@@ -172,7 +172,7 @@ static void apk_io_url_fini(void)
 	fetchConnectionCacheClose();
 }
 
-void apk_io_url_init(void)
+void apk_io_url_init(struct apk_out *out)
 {
 	fetchConnectionCacheInit(32, 4);
 	atexit(apk_io_url_fini);
diff --git a/src/io_url_wget.c b/src/io_url_wget.c
index d22f04270023b383a3250b22d1ab02348aeff9d7..d7e58ae5acdff392d1c0fd04708b147ffc1bfa76 100644
--- a/src/io_url_wget.c
+++ b/src/io_url_wget.c
@@ -7,39 +7,17 @@
  * SPDX-License-Identifier: GPL-2.0-only
  */
 
-#include <spawn.h>
-#include <unistd.h>
-#include <sys/wait.h>
 #include "apk_io.h"
+#include "apk_process.h"
 
 static char wget_timeout[16];
 static char wget_no_check_certificate;
+static struct apk_out *wget_out;
 
-static int wget_translate_status(int status)
-{
-	if (!WIFEXITED(status)) return -EFAULT;
-	switch (WEXITSTATUS(status)) {
-	case 0: return 0;
-	case 3: return -EIO;
-	case 4: return -ENETUNREACH;
-	case 5: return -EACCES;
-	case 6: return -EACCES;
-	case 7: return -EPROTO;
-	default: return -APKE_REMOTE_IO;
-	}
-}
-
-struct apk_wget_istream {
-	struct apk_istream is;
-	int fd;
-	pid_t pid;
-};
-
-static int wget_spawn(const char *url, pid_t *pid, int *fd)
+struct apk_istream *apk_io_url_istream(const char *url, time_t since)
 {
-	int i = 0, r, pipefds[2];
-	posix_spawn_file_actions_t act;
 	char *argv[16];
+	int i = 0;
 
 	argv[i++] = "wget";
 	argv[i++] = "-q";
@@ -51,84 +29,7 @@ static int wget_spawn(const char *url, pid_t *pid, int *fd)
 	argv[i++] = "-";
 	argv[i++] = 0;
 
-	if (pipe2(pipefds, O_CLOEXEC) != 0) return -errno;
-
-	posix_spawn_file_actions_init(&act);
-	posix_spawn_file_actions_adddup2(&act, pipefds[1], STDOUT_FILENO);
-	r = posix_spawnp(pid, "wget", &act, 0, argv, environ);
-	posix_spawn_file_actions_destroy(&act);
-	if (r != 0) return -r;
-	close(pipefds[1]);
-	*fd = pipefds[0];
-	return 0;
-}
-
-static int wget_check_exit(struct apk_wget_istream *wis)
-{
-	int status;
-
-	if (wis->pid == 0) return apk_istream_error(&wis->is, 0);
-	if (waitpid(wis->pid, &status, 0) == wis->pid) {
-		wis->pid = 0;
-		return apk_istream_error(&wis->is, wget_translate_status(status));
-	}
-	return 0;
-}
-
-static void wget_get_meta(struct apk_istream *is, struct apk_file_meta *meta)
-{
-}
-
-static ssize_t wget_read(struct apk_istream *is, void *ptr, size_t size)
-{
-	struct apk_wget_istream *wis = container_of(is, struct apk_wget_istream, is);
-	ssize_t r;
-
-	r = read(wis->fd, ptr, size);
-	if (r < 0) return -errno;
-	if (r == 0) return wget_check_exit(wis);
-	return r;
-}
-
-static int wget_close(struct apk_istream *is)
-{
-	int r = is->err;
-	struct apk_wget_istream *wis = container_of(is, struct apk_wget_istream, is);
-
-	while (wis->pid != 0)
-		wget_check_exit(wis);
-
-	close(wis->fd);
-	free(wis);
-	return r < 0 ? r : 0;
-}
-
-static const struct apk_istream_ops wget_istream_ops = {
-	.get_meta = wget_get_meta,
-	.read = wget_read,
-	.close = wget_close,
-};
-
-struct apk_istream *apk_io_url_istream(const char *url, time_t since)
-{
-	struct apk_wget_istream *wis;
-	int r;
-
-	wis = malloc(sizeof(*wis) + apk_io_bufsize);
-	if (wis == NULL) return ERR_PTR(-ENOMEM);
-
-	*wis = (struct apk_wget_istream) {
-		.is.ops = &wget_istream_ops,
-		.is.buf = (uint8_t *)(wis + 1),
-		.is.buf_size = apk_io_bufsize,
-	};
-	r = wget_spawn(url, &wis->pid, &wis->fd);
-	if (r != 0) {
-		free(wis);
-		return ERR_PTR(r);
-	}
-
-	return &wis->is;
+	return apk_process_istream(argv, wget_out, "wget");
 }
 
 void apk_io_url_no_check_certificate(void)
@@ -145,6 +46,6 @@ void apk_io_url_set_redirect_callback(void (*cb)(int, const char *))
 {
 }
 
-void apk_io_url_init(void)
+void apk_io_url_init(struct apk_out *out)
 {
 }
diff --git a/src/process.c b/src/process.c
index 8a3ef3fb8de419f2788e05e4b4e902d4264342ca..11eccfcc078d5d0d6ef5b72ca43d20d0168986a0 100644
--- a/src/process.c
+++ b/src/process.c
@@ -107,7 +107,7 @@ int apk_process_spawn(struct apk_process *p, const char *path, char * const* arg
 	posix_spawn_file_actions_adddup2(&act, p->pipe_stdin[0], STDIN_FILENO);
 	posix_spawn_file_actions_adddup2(&act, p->pipe_stdout[1], STDOUT_FILENO);
 	posix_spawn_file_actions_adddup2(&act, p->pipe_stderr[1], STDERR_FILENO);
-	r = posix_spawn(&p->pid, path, &act, 0, argv, env ?: environ);
+	r = posix_spawnp(&p->pid, path, &act, 0, argv, env ?: environ);
 	posix_spawn_file_actions_destroy(&act);
 
 	close_fd(&p->pipe_stdin[0]);
@@ -116,7 +116,7 @@ int apk_process_spawn(struct apk_process *p, const char *path, char * const* arg
 	return -r;
 }
 
-int apk_process_run(struct apk_process *p)
+static int apk_process_handle(struct apk_process *p, bool break_on_stdout)
 {
 	struct pollfd fds[3] = {
 		{ .fd = p->pipe_stdout[0], .events = POLLIN },
@@ -126,7 +126,7 @@ int apk_process_run(struct apk_process *p)
 
 	while (fds[0].fd >= 0 || fds[1].fd >= 0 || fds[2].fd >= 0) {
 		if (poll(fds, ARRAY_SIZE(fds), -1) <= 0) continue;
-		if (fds[0].revents) {
+		if (fds[0].revents && !break_on_stdout) {
 			if (!buf_process(&p->buf_stdout, p->pipe_stdout[0], p->out, NULL, p->argv0)) {
 				fds[0].fd = -1;
 				close_fd(&p->pipe_stdout[0]);
@@ -163,26 +163,112 @@ int apk_process_run(struct apk_process *p)
 			close_fd(&p->pipe_stdin[1]);
 			fds[2].fd = -1;
 		}
+		if (fds[0].revents && break_on_stdout) return 1;
 	}
 	return apk_process_cleanup(p);
 }
 
-int apk_process_cleanup(struct apk_process *p)
+int apk_process_run(struct apk_process *p)
 {
-	char buf[APK_EXIT_STATUS_MAX_SIZE];
-	int status = 0;
+	return apk_process_handle(p, false);
+}
 
-	if (p->is) apk_istream_close(p->is);
-	close_fd(&p->pipe_stdin[1]);
-	close_fd(&p->pipe_stdout[0]);
-	close_fd(&p->pipe_stderr[0]);
+int apk_process_cleanup(struct apk_process *p)
+{
+	if (p->pid != 0) {
+		char buf[APK_EXIT_STATUS_MAX_SIZE];
+		if (p->is) apk_istream_close(p->is);
+		close_fd(&p->pipe_stdin[1]);
+		close_fd(&p->pipe_stdout[0]);
+		close_fd(&p->pipe_stderr[0]);
 
-	while (waitpid(p->pid, &status, 0) < 0 && errno == EINTR);
+		while (waitpid(p->pid, &p->status, 0) < 0 && errno == EINTR);
+		p->pid = 0;
 
-	if (apk_exit_status_str(status, buf, sizeof buf)) {
-		apk_err(p->out, "%s: %s", p->argv0, buf);
-		return -1;
+		if (apk_exit_status_str(p->status, buf, sizeof buf))
+			apk_err(p->out, "%s: %s", p->argv0, buf);
 	}
+	if (!WIFEXITED(p->status) || WEXITSTATUS(p->status) != 0) return -1;
 	if (p->is && !p->is_eof) return -2;
 	return 0;
 }
+
+static int process_translate_status(int status)
+{
+	if (!WIFEXITED(status)) return -EFAULT;
+	// Assume wget like return code
+	switch (WEXITSTATUS(status)) {
+	case 0: return 0;
+	case 3: return -EIO;
+	case 4: return -ENETUNREACH;
+	case 5: return -EACCES;
+	case 6: return -EACCES;
+	case 7: return -EPROTO;
+	default: return -APKE_REMOTE_IO;
+	}
+}
+
+struct apk_process_istream {
+	struct apk_istream is;
+	struct apk_process proc;
+};
+
+static void process_get_meta(struct apk_istream *is, struct apk_file_meta *meta)
+{
+}
+
+static ssize_t process_read(struct apk_istream *is, void *ptr, size_t size)
+{
+	struct apk_process_istream *pis = container_of(is, struct apk_process_istream, is);
+	ssize_t r;
+
+	r = apk_process_handle(&pis->proc, true);
+	if (r <= 0) return process_translate_status(pis->proc.status);
+
+	r = read(pis->proc.pipe_stdout[0], ptr, size);
+	if (r < 0) return -errno;
+	return r;
+}
+
+static int process_close(struct apk_istream *is)
+{
+	int r = is->err;
+	struct apk_process_istream *pis = container_of(is, struct apk_process_istream, is);
+
+	if (apk_process_cleanup(&pis->proc) < 0 && r >= 0)
+		r = process_translate_status(pis->proc.status);
+	free(pis);
+
+	return r < 0 ? r : 0;
+}
+
+static const struct apk_istream_ops process_istream_ops = {
+	.get_meta = process_get_meta,
+	.read = process_read,
+	.close = process_close,
+};
+
+struct apk_istream *apk_process_istream(char * const* argv, struct apk_out *out, const char *argv0)
+{
+	struct apk_process_istream *pis;
+	int r;
+
+	pis = malloc(sizeof(*pis) + apk_io_bufsize);
+	if (pis == NULL) return ERR_PTR(-ENOMEM);
+
+	*pis = (struct apk_process_istream) {
+		.is.ops = &process_istream_ops,
+		.is.buf = (uint8_t *)(pis + 1),
+		.is.buf_size = apk_io_bufsize,
+	};
+	r = apk_process_init(&pis->proc, argv0, out, NULL);
+	if (r != 0) goto err;
+
+	r = apk_process_spawn(&pis->proc, argv[0], argv, NULL);
+	if (r != 0) goto err;
+
+	return &pis->is;
+err:
+	free(pis);
+	return ERR_PTR(r);
+}
diff --git a/test/process-istream.sh b/test/process-istream.sh
new file mode 100755
index 0000000000000000000000000000000000000000..c73aaafdf928f78b2b6784a5564070958e225a52
--- /dev/null
+++ b/test/process-istream.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+case "$1" in
+ok)
+	echo "hello"
+	echo "stderr text" 1>&2
+	sleep 0.2
+	echo "hello again"
+	echo "stderr again" 1>&2
+	exit 0;;
+fail)
+	echo "hello"
+	echo "stderr text" 1>&2
+	exit 10;;
+esac
+
+exit 1
diff --git a/test/unit/process_test.c b/test/unit/process_test.c
index 4c9a638c9fa1c8a5ea3bc35ce008bcf4a3d38dee..d7bca7b7e770abdedddaabf51d8bb26d0259b9f5 100644
--- a/test/unit/process_test.c
+++ b/test/unit/process_test.c
@@ -127,4 +127,33 @@ APK_TEST(pid_input_full) {
 		"test3: success\n");
 }
 
-// FIXME: add test for subprocess _istream
+static void test_process_istream(int rc, char *arg, const char *expect_err, const char *expect_out)
+{
+	struct cached_out co;
+	char out[256], *argv[] = { "../process-istream.sh", arg, NULL };
+
+	open_out(&co);
+	struct apk_istream *is = apk_process_istream(argv, &co.out, "process-istream");
+	assert_ptr_ok(is);
+
+	int n = apk_istream_read_max(is, out, sizeof out);
+	assert_int_equal(rc, apk_istream_close(is));
+
+	assert_output_equal(&co, expect_err, "");
+	assert_int_equal(strlen(expect_out), n);
+	assert_memory_equal(expect_out, out, n);
+}
+
+APK_TEST(pid_istream_ok) {
+	test_process_istream(0, "ok",
+		"process-istream: stderr text\n"
+		"process-istream: stderr again\n",
+		"hello\nhello again\n");
+}
+
+APK_TEST(pid_istream_fail) {
+	test_process_istream(-APKE_REMOTE_IO, "fail",
+		"process-istream: stderr text\n"
+		"ERROR: process-istream: exited with error 10\n",
+		"hello\n");
+}