diff options
author | Érico Rolim <erico.erc@gmail.com> | 2020-10-21 18:52:51 -0300 |
---|---|---|
committer | Harry Jeffery <harry@exec64.co.uk> | 2020-12-02 00:11:51 +0000 |
commit | 82960d3c41601652860843c1040acaf7860d79d0 (patch) | |
tree | 2f195c6d263fdeb2b6d98b4950c72f4c0785b1b7 | |
parent | 489421b9e763865881df614a3ef550b289409677 (diff) | |
download | imv-82960d3c41601652860843c1040acaf7860d79d0.tar.gz |
imv: don't rely on glibc's lack of FILE sync on exit.
This issue was spotted on musl, where killing imv with the Q shortcut
while it was waiting for image paths in stdin led to the application
hanging until stdin received an EOF manually with ^D, since the stdin
stream was locked by the fgets() function.
Switching to pipes and a helper thread allows us to use read() for
reading from STDIN_FILENO while still taking advantage of the fgets()
logic for handling newlines and buffer size. read() is a thread
cancellation point (fgets() is only an optional one), which makes it
possible to cancel the helper thread cleanly and force the thread using
fgets() to exit when the write-end of the pipe it's reading from is
closed.
-rw-r--r-- | src/imv.c | 59 |
1 files changed, 55 insertions, 4 deletions
@@ -129,6 +129,9 @@ struct imv { /* read paths from stdin, as opposed to image data */ bool paths_from_stdin; + /* FILE to use when reading paths from stdin */ + FILE *stdin_pipe; + /* scale up / down images to match window, or actual size */ enum scaling_mode scaling_mode; @@ -738,6 +741,24 @@ static bool parse_initial_pan(struct imv *imv, const char *pan_params) return true; } +static void *pipe_stdin(void *data) +{ + int *fd = data; + while (1) { + char buf[PIPE_BUF]; + ssize_t err = read(STDIN_FILENO, buf, PIPE_BUF); + if (err > 0) { + /* writes up to PIPE_BUF are atomic */ + write(*fd, buf, err); + } else if (err == 0 || errno != EINTR) { + /* break if EOF or actual read error */ + break; + } + } + + return NULL; +} + static void *load_paths_from_stdin(void *data) { struct imv *imv = data; @@ -745,7 +766,7 @@ static void *load_paths_from_stdin(void *data) imv_log(IMV_INFO, "Reading paths from stdin...\n"); char buf[PATH_MAX]; - while (fgets(buf, sizeof(buf), stdin) != NULL) { + while (fgets(buf, sizeof(buf), imv->stdin_pipe) != NULL) { size_t len = strlen(buf); if (buf[len-1] == '\n') { buf[--len] = 0; @@ -894,10 +915,25 @@ int imv_run(struct imv *imv) /* if loading paths from stdin, kick off a thread to do that - we'll receive * events back via internal events */ + int *stdin_pipe_fds = NULL; + pthread_t pipe_stdin_thread; + pthread_t load_paths_thread; if (imv->paths_from_stdin) { - pthread_t thread; - pthread_create(&thread, NULL, load_paths_from_stdin, imv); - pthread_detach(thread); + /* this array is allocated on the heap because it's passed to pthread_create */ + stdin_pipe_fds = calloc(2, sizeof *stdin_pipe_fds); + + if (pipe(stdin_pipe_fds)) { + /* if pipe creation fails, we should exit */ + free(stdin_pipe_fds); + return 1; + } + + imv->stdin_pipe = fdopen(stdin_pipe_fds[0], "re"); + + if (pthread_create(&load_paths_thread, NULL, load_paths_from_stdin, imv) + || pthread_create(&pipe_stdin_thread, NULL, pipe_stdin, stdin_pipe_fds + 1)) { + return 1; + } } if (imv->starting_path) { @@ -1102,6 +1138,21 @@ int imv_run(struct imv *imv) puts(imv_navigator_at(imv->navigator, i)); } + if (imv->paths_from_stdin) { + /* stop the thread before closing the pipe */ + pthread_cancel(pipe_stdin_thread); + pthread_join(pipe_stdin_thread, NULL); + + /* will cause the thread running load_paths_from_stdin() to exit */ + close(stdin_pipe_fds[1]); + free(stdin_pipe_fds); + + /* join the other thread to avoid a race when closing imv->stdin_pipe */ + pthread_join(load_paths_thread, NULL); + + fclose(imv->stdin_pipe); + } + return 0; } |