summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavidlohr Bueso2012-04-23 11:51:29 +0200
committerKarel Zak2012-04-23 11:52:39 +0200
commit35717a57ac290490c464451a57c960266da3c5d4 (patch)
tree911976e0aca59e705def5e20a8c79bb1753a19f8
parentnamei: fix relative symlinks evaluation (diff)
downloadkernel-qcow2-util-linux-35717a57ac290490c464451a57c960266da3c5d4.tar.gz
kernel-qcow2-util-linux-35717a57ac290490c464451a57c960266da3c5d4.tar.xz
kernel-qcow2-util-linux-35717a57ac290490c464451a57c960266da3c5d4.zip
lib: add pager functionality
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>
-rw-r--r--include/pager.h6
-rw-r--r--lib/Makefile.am2
-rw-r--r--lib/pager.c214
3 files changed, 222 insertions, 0 deletions
diff --git a/include/pager.h b/include/pager.h
new file mode 100644
index 000000000..9ca42eb35
--- /dev/null
+++ b/include/pager.h
@@ -0,0 +1,6 @@
+#ifndef UTIL_LINUX_PAGER
+#define UTIL_LINUX_PAGER
+
+void setup_pager(void);
+
+#endif
diff --git a/lib/Makefile.am b/lib/Makefile.am
index fc967fcbd..aed0ecd83 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -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
index 000000000..1bc4134cf
--- /dev/null
+++ b/lib/pager.c
@@ -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 */