]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lib: add pager functionality
authorDavidlohr Bueso <dave@gnu.org>
Mon, 23 Apr 2012 09:51:29 +0000 (11:51 +0200)
committerKarel Zak <kzak@redhat.com>
Mon, 23 Apr 2012 09:52:39 +0000 (11:52 +0200)
When some program' output exceeds the terminal's dimensions, it is a nice
feature to call a pager that acts as calling 'less' to allow better user
navigation. This patch adds this functionality, based on what perf and git
have (ie: git log).

Signed-off-by: Davidlohr Bueso <dave@gnu.org>
include/pager.h [new file with mode: 0644]
lib/Makefile.am
lib/pager.c [new file with mode: 0644]

diff --git a/include/pager.h b/include/pager.h
new file mode 100644 (file)
index 0000000..9ca42eb
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef UTIL_LINUX_PAGER
+#define UTIL_LINUX_PAGER
+
+void setup_pager(void);
+
+#endif
index fc967fcbdfc87920d264569c084abcce1fa5f57b..aed0ecd835b9f9214acb9e7c2e192a05ded9ae57 100644 (file)
@@ -9,6 +9,7 @@ noinst_PROGRAMS = \
        test_fileutils \
        test_ismounted \
        test_mangle \
+       test_pager \
        test_procutils \
        test_randutils \
        test_strutils \
@@ -37,6 +38,7 @@ test_procutils_SOURCES = procutils.c
 if LINUX
 test_cpuset_SOURCES = cpuset.c
 test_sysfs_SOURCES = sysfs.c at.c
+test_pager_SOURCES = pager.c
 test_sysfs_CFLAGS = -DTEST_PROGRAM_SYSFS
 
 test_loopdev_SOURCES = \
diff --git a/lib/pager.c b/lib/pager.c
new file mode 100644 (file)
index 0000000..1bc4134
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * Based on linux-perf/git scm
+ *
+ * Some modifications and simplifications for util-linux
+ * by Davidlohr Bueso <dave@xxxxxxx> - March 2012.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "c.h"
+#include "xalloc.h"
+#include "nls.h"
+
+static struct child_process pager_process;
+static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
+
+struct child_process {
+       const char **argv;
+       pid_t pid;
+       int in;
+       int out;
+       int err;
+       unsigned no_stdin:1;
+       void (*preexec_cb)(void);
+};
+
+static inline void close_pair(int fd[2])
+{
+       close(fd[0]);
+       close(fd[1]);
+}
+
+static inline void dup_devnull(int to)
+{
+       int fd = open("/dev/null", O_RDWR);
+       dup2(fd, to);
+       close(fd);
+}
+
+static int start_command(struct child_process *cmd)
+{
+       int need_in, need_out, need_err;
+       int fdin[2], fdout[2], fderr[2];
+
+       /*
+        * In case of errors we must keep the promise to close FDs
+        * that have been passed in via ->in and ->out.
+        */
+       need_in = !cmd->no_stdin && cmd->in < 0;
+       if (need_in) {
+               if (pipe(fdin) < 0) {
+                       if (cmd->out > 0)
+                               close(cmd->out);
+                       return -1;
+               }
+               cmd->in = fdin[1];
+       }
+
+       fflush(NULL);
+       cmd->pid = fork();
+       if (!cmd->pid) {
+               if (need_in) {
+                       dup2(fdin[0], 0);
+                       close_pair(fdin);
+               } else if (cmd->in) {
+                       dup2(cmd->in, 0);
+                       close(cmd->in);
+               }
+
+               cmd->preexec_cb();
+               execvp(cmd->argv[0], (char *const*) cmd->argv);
+               exit(127); /* cmd not found */
+       }
+
+       if (cmd->pid < 0) {
+               int err = errno;
+               if (need_in)
+                       close_pair(fdin);
+               else if (cmd->in)
+                       close(cmd->in);
+
+               return -1;
+       }
+
+       if (need_in)
+               close(fdin[0]);
+       else if (cmd->in)
+               close(cmd->in);
+       return 0;
+}
+
+static int wait_or_whine(pid_t pid)
+{
+       for (;;) {
+               int status, code;
+               pid_t waiting = waitpid(pid, &status, 0);
+
+               if (waiting < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       err(EXIT_FAILURE, _("waitpid failed (%s)"), strerror(errno));
+               }
+               if (waiting != pid)
+                       return -1;
+               if (WIFSIGNALED(status))
+                       return -1;
+
+               if (!WIFEXITED(status))
+                       return -1;
+               code = WEXITSTATUS(status);
+               switch (code) {
+               case 127:
+                       return -1;
+               case 0:
+                       return 0;
+               default:
+                       return -1;
+               }
+       }
+}
+
+static int finish_command(struct child_process *cmd)
+{
+       return wait_or_whine(cmd->pid);
+}
+
+static void pager_preexec(void)
+{
+       /*
+        * Work around bug in "less" by not starting it until we
+        * have real input
+        */
+       fd_set in;
+
+       FD_ZERO(&in);
+       FD_SET(0, &in);
+       select(1, &in, NULL, &in, NULL);
+
+       setenv("LESS", "FRSX", 0);
+}
+
+static void wait_for_pager(void)
+{
+       fflush(stdout);
+       fflush(stderr);
+       /* signal EOF to pager */
+       close(1);
+       close(2);
+       finish_command(&pager_process);
+}
+
+static void wait_for_pager_signal(int signo)
+{
+       wait_for_pager();
+       raise(signo);
+}
+
+void setup_pager(void)
+{
+       const char *pager = getenv("PAGER");
+
+       if (!isatty(1))
+               return;
+
+       if (!pager)
+               pager = "less";
+       else if (!*pager || !strcmp(pager, "cat"))
+               return;
+
+       /* spawn the pager */
+       pager_argv[2] = pager;
+       pager_process.argv = pager_argv;
+       pager_process.in = -1;
+       pager_process.preexec_cb = pager_preexec;
+
+       if (start_command(&pager_process))
+               return;
+
+       /* original process continues, but writes to the pipe */
+       dup2(pager_process.in, 1);
+       if (isatty(2))
+               dup2(pager_process.in, 2);
+       close(pager_process.in);
+
+       /* this makes sure that the parent terminates after the pager */
+       signal(SIGINT, wait_for_pager_signal);
+       signal(SIGHUP, wait_for_pager_signal);
+       signal(SIGTERM, wait_for_pager_signal);
+       signal(SIGQUIT, wait_for_pager_signal);
+       signal(SIGPIPE, wait_for_pager_signal);
+
+       atexit(wait_for_pager);
+}
+
+#ifdef TEST_PROGRAM
+
+#define MAX 255
+
+int main(int argc, char *argv[])
+{
+       int i;
+
+       setup_pager();
+       for (i = 0; i < MAX; i++)
+               printf("%d\n", i);
+       return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */