From ef02e2d5ef63fac8195006e65d006a7b2e6fcd08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Rolim?= Date: Thu, 1 Oct 2020 03:12:02 -0300 Subject: [PATCH] Add pager implementation, use in gemi. This also required a small change in where gemi outputs the page's contents, to avoid requiring multiple wait_for_pager() calls in the code. This also allows it to show the redirect page's contents. --- gemi.c | 36 ++++++++++++++++++++++++------- makefile | 2 +- pager.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pager.h | 17 +++++++++++++++ 4 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 pager.c create mode 100644 pager.h diff --git a/gemi.c b/gemi.c index 3534a99..39553b6 100644 --- a/gemi.c +++ b/gemi.c @@ -8,6 +8,7 @@ #include "mmap_file.h" #include "read_certs.h" #include "gemini.h" +#include "pager.h" #define GEMINI_REQUEST 1024 @@ -18,6 +19,7 @@ static void usage(bool fail) "Usage: gemi [options] \n" "Options:\n" " -b: browse mode (experimental)\n" + " -p: use pager: value of PAGER (default is less)\n" " -n: don't strip header\n" " -d: debug\n" " -h: show this dialog\n" @@ -30,15 +32,19 @@ static void usage(bool fail) int main(int argc, char **argv) { int rv = EXIT_FAILURE; - bool debug = false, no_strip = false, browse = false; + bool browse = false, pager = false; + bool debug = false, no_strip = false; int redirections = 0, redirections_pos = 0; int c; - while ((c = getopt(argc, argv, "+bndhr:")) != -1) { + while ((c = getopt(argc, argv, "+bpndhr:")) != -1) { switch (c) { case 'b': browse = true; break; + case 'p': + pager = true; + break; case 'n': no_strip = true; break; @@ -123,12 +129,20 @@ int main(int argc, char **argv) signal(SIGPIPE, SIG_IGN); - // writes directly into stdout + FILE *output_stream = stdout; + struct pager_proc pager_info; + if (pager) { + if (launch_pager(&pager_info) < 0) { + return rv; + } + output_stream = pager_info.file; + } + struct mmap_file output; if (browse) { output = create_mmap_from_file(NULL, PROT_MEM); } else { - output = create_mmap_from_FILE(stdout, "w"); + output = create_mmap_from_FILE(output_stream, "w"); } if (ERROR_MMAP(output)) { return rv; @@ -151,6 +165,16 @@ int main(int argc, char **argv) goto early_out; } + // generic way of outputting data: + // - if using FILE backend, offset is 0 and nothing happens + // - if using memory backend, offset is used + // data output should happen _before_ dealing with links + fwrite(output.data, 1, output.offset, output_stream); + // pager must always be closed before exec'ing into self + // if redirect_link exists, should also kill pager + // XXX: add some flag to not kill pager when performing redirection? + if (pager) wait_for_pager(pager_info, (bool)redirect_link); + if (redirect_link) { redirections += 1; // redirect link was stored in callback @@ -182,10 +206,6 @@ int main(int argc, char **argv) execvp(progpath, new_argv); } - // generic way of outputting data: - // - if using FILE backend, offset is 0 and nothing happens - // - if using memory backend, offset is used - fwrite(output.data, 1, output.offset, stdout); if (browse) { struct gemini_link_node *head = NULL; int n = get_links_from_gmi((char *)output.data, &head); diff --git a/makefile b/makefile index b4edbcf..1fc2096 100644 --- a/makefile +++ b/makefile @@ -12,7 +12,7 @@ INC += -Iextern BASEENCODEOBJS = extern/libbaseencode/base64.o extern/libbaseencode/base32.o PURROBJS = socket.o urls.o files.o comm.o formats.o encrypt.o mmap_file.o -PURROBJS += read_certs.o gemini.o +PURROBJS += read_certs.o gemini.o pager.o LIBSOBJS = $(BASEENCODEOBJS) $(PURROBJS) HEADERS = purr.h mmap_file.h read_certs.h gemini.h diff --git a/pager.c b/pager.c new file mode 100644 index 0000000..682e8a8 --- /dev/null +++ b/pager.c @@ -0,0 +1,65 @@ +#define _POSIX_C_SOURCE 200112L /* fdopen */ +#include +#include +#include +#include +#include + +#include "pager.h" + +int launch_pager(struct pager_proc *p) +{ + int rv = -1; + int pipes[2]; + + if (pipe(pipes) < 0) { + perror("pipe()"); + return rv; + } + + char *pager = getenv("PAGER"); + if (pager == NULL || *pager == 0) { + pager = "less"; + } + char *const pager_cmd[] = {pager, NULL}; + + p->pid = fork(); + if (p->pid < 0) { + perror("fork()"); + return rv; + } + + if (p->pid == 0) { + close(pipes[1]); + dup2(pipes[0], STDIN_FILENO); + if (execvp(pager, pager_cmd) < 0) { + perror("execvp()"); + return rv; + } + } else { + close(pipes[0]); + p->file = fdopen(pipes[1], "w"); + if (p->file == NULL) { + perror("fdopen()"); + return rv; + } + rv = 0; + } + + return rv; +} + +int wait_for_pager(struct pager_proc p, bool should_kill) +{ + int status = 0; + fclose(p.file); + + if (should_kill) { + sleep(1); + kill(p.pid, SIGTERM); + } + + waitpid(p.pid, &status, 0); + + return status; +} diff --git a/pager.h b/pager.h new file mode 100644 index 0000000..c6405f9 --- /dev/null +++ b/pager.h @@ -0,0 +1,17 @@ +#ifndef __PAGER_H_ +#define __PAGER_H_ + +#include +#include +#include +#include + +struct pager_proc { + FILE *file; + pid_t pid; +}; + +int launch_pager(struct pager_proc *); +int wait_for_pager(struct pager_proc, bool); + +#endif // __PAGER_H_