318 lines
6.0 KiB
C
318 lines
6.0 KiB
C
/*
|
|
* 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 <signal.h>
|
|
|
|
#include "c.h"
|
|
#include "xalloc.h"
|
|
#include "nls.h"
|
|
#include "ttyutils.h"
|
|
#include "pager.h"
|
|
|
|
#define NULL_DEVICE "/dev/null"
|
|
|
|
static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
|
|
|
|
struct child_process {
|
|
const char **argv;
|
|
pid_t pid;
|
|
int in;
|
|
int out;
|
|
int err;
|
|
|
|
int org_err;
|
|
int org_out;
|
|
struct sigaction orig_sigint;
|
|
struct sigaction orig_sighup;
|
|
struct sigaction orig_sigterm;
|
|
struct sigaction orig_sigquit;
|
|
struct sigaction orig_sigpipe;
|
|
|
|
unsigned no_stdin:1;
|
|
void (*preexec_cb)(void);
|
|
};
|
|
static struct child_process pager_process;
|
|
|
|
static inline void close_pair(int fd[2])
|
|
{
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
}
|
|
|
|
static int start_command(struct child_process *cmd)
|
|
{
|
|
int need_in;
|
|
int fdin[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], STDIN_FILENO);
|
|
close_pair(fdin);
|
|
} else if (cmd->in > 0) {
|
|
dup2(cmd->in, STDIN_FILENO);
|
|
close(cmd->in);
|
|
}
|
|
|
|
cmd->preexec_cb();
|
|
execvp(cmd->argv[0], (char *const*) cmd->argv);
|
|
errexec(cmd->argv[0]);
|
|
}
|
|
|
|
if (cmd->pid < 0) {
|
|
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, ex;
|
|
|
|
FD_ZERO(&in);
|
|
FD_SET(STDIN_FILENO, &in);
|
|
ex = in;
|
|
|
|
select(STDIN_FILENO + 1, &in, NULL, &ex, NULL);
|
|
|
|
if (setenv("LESS", "FRSX", 0) != 0)
|
|
warn(_("failed to set the %s environment variable"), "LESS");
|
|
}
|
|
|
|
static void wait_for_pager(void)
|
|
{
|
|
if (pager_process.pid == 0)
|
|
return;
|
|
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
/* signal EOF to pager */
|
|
close(STDOUT_FILENO);
|
|
close(STDERR_FILENO);
|
|
finish_command(&pager_process);
|
|
}
|
|
|
|
static void wait_for_pager_signal(int signo)
|
|
{
|
|
wait_for_pager();
|
|
raise(signo);
|
|
}
|
|
|
|
static int has_command(const char *cmd)
|
|
{
|
|
const char *path;
|
|
char *p, *s;
|
|
int rc = 0;
|
|
|
|
if (!cmd)
|
|
goto done;
|
|
if (*cmd == '/') {
|
|
rc = access(cmd, X_OK) == 0;
|
|
goto done;
|
|
}
|
|
|
|
path = getenv("PATH");
|
|
if (!path)
|
|
goto done;
|
|
p = xstrdup(path);
|
|
if (!p)
|
|
goto done;
|
|
|
|
for(s = strtok(p, ":"); s; s = strtok(NULL, ":")) {
|
|
int fd = open(s, O_RDONLY|O_CLOEXEC);
|
|
if (fd < 0)
|
|
continue;
|
|
rc = faccessat(fd, cmd, X_OK, 0) == 0;
|
|
close(fd);
|
|
if (rc)
|
|
break;
|
|
}
|
|
free(p);
|
|
done:
|
|
/*fprintf(stderr, "has PAGER %s rc=%d\n", cmd, rc);*/
|
|
return rc;
|
|
}
|
|
|
|
static void __setup_pager(void)
|
|
{
|
|
const char *pager = getenv("PAGER");
|
|
struct sigaction sa;
|
|
|
|
if (!isatty(STDOUT_FILENO))
|
|
return;
|
|
|
|
if (!pager)
|
|
pager = "less";
|
|
else if (!*pager || !strcmp(pager, "cat"))
|
|
return;
|
|
|
|
if (!has_command(pager))
|
|
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, STDOUT_FILENO);
|
|
if (isatty(STDERR_FILENO))
|
|
dup2(pager_process.in, STDERR_FILENO);
|
|
close(pager_process.in);
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_handler = wait_for_pager_signal;
|
|
|
|
/* this makes sure that the parent terminates after the pager */
|
|
sigaction(SIGINT, &sa, &pager_process.orig_sigint);
|
|
sigaction(SIGHUP, &sa, &pager_process.orig_sighup);
|
|
sigaction(SIGTERM, &sa, &pager_process.orig_sigterm);
|
|
sigaction(SIGQUIT, &sa, &pager_process.orig_sigquit);
|
|
sigaction(SIGPIPE, &sa, &pager_process.orig_sigpipe);
|
|
}
|
|
|
|
/* Setup pager and redirects output to the $PAGER. The pager is closed at exit.
|
|
*/
|
|
void pager_redirect(void)
|
|
{
|
|
if (pager_process.pid)
|
|
return; /* already running */
|
|
|
|
__setup_pager();
|
|
|
|
atexit(wait_for_pager);
|
|
}
|
|
|
|
/* Setup pager and redirect output, the pager may be closed by pager_close().
|
|
*/
|
|
void pager_open(void)
|
|
{
|
|
if (pager_process.pid)
|
|
return; /* already running */
|
|
|
|
pager_process.org_out = dup(STDOUT_FILENO);
|
|
pager_process.org_err = dup(STDERR_FILENO);
|
|
|
|
__setup_pager();
|
|
}
|
|
|
|
/* Close pager and restore original std{out,err}.
|
|
*/
|
|
void pager_close(void)
|
|
{
|
|
if (pager_process.pid == 0)
|
|
return;
|
|
|
|
wait_for_pager();
|
|
|
|
/* restore original output */
|
|
dup2(pager_process.org_out, STDOUT_FILENO);
|
|
dup2(pager_process.org_err, STDERR_FILENO);
|
|
|
|
close(pager_process.org_out);
|
|
close(pager_process.org_err);
|
|
|
|
/* restore original segnals setting */
|
|
sigaction(SIGINT, &pager_process.orig_sigint, NULL);
|
|
sigaction(SIGHUP, &pager_process.orig_sighup, NULL);
|
|
sigaction(SIGTERM, &pager_process.orig_sigterm, NULL);
|
|
sigaction(SIGQUIT, &pager_process.orig_sigquit, NULL);
|
|
sigaction(SIGPIPE, &pager_process.orig_sigpipe, NULL);
|
|
|
|
memset(&pager_process, 0, sizeof(pager_process));
|
|
}
|
|
|
|
#ifdef TEST_PROGRAM_PAGER
|
|
|
|
#define MAX 255
|
|
|
|
int main(int argc __attribute__ ((__unused__)),
|
|
char *argv[] __attribute__ ((__unused__)))
|
|
{
|
|
int i;
|
|
|
|
pager_redirect();
|
|
for (i = 0; i < MAX; i++)
|
|
printf("%d\n", i);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
#endif /* TEST_PROGRAM_PAGER */
|