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"); +}