(root)/
util-linux-2.39/
lib/
pager.c
       1  /*
       2   * SPDX-License-Identifier: GPL-2.0-or-later
       3   *
       4   * This file may be redistributed under the terms of the GNU Public
       5   * License.
       6   *
       7   * Based on linux-perf/git scm
       8   *
       9   * Some modifications and simplifications for util-linux
      10   * by Davidlohr Bueso <dave@xxxxxxx> - March 2012.
      11   */
      12  
      13  #include <unistd.h>
      14  #include <stdlib.h>
      15  #include <string.h>
      16  #include <err.h>
      17  #include <sys/types.h>
      18  #include <sys/stat.h>
      19  #include <sys/wait.h>
      20  #include <signal.h>
      21  
      22  #include "c.h"
      23  #include "xalloc.h"
      24  #include "nls.h"
      25  #include "ttyutils.h"
      26  #include "pager.h"
      27  
      28  #define NULL_DEVICE	"/dev/null"
      29  
      30  static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
      31  
      32  struct child_process {
      33  	const char **argv;
      34  	pid_t pid;
      35  	int in;
      36  	int out;
      37  	int err;
      38  
      39  	int org_err;
      40  	int org_out;
      41  	struct sigaction orig_sigint;
      42  	struct sigaction orig_sighup;
      43  	struct sigaction orig_sigterm;
      44  	struct sigaction orig_sigquit;
      45  	struct sigaction orig_sigpipe;
      46  
      47  	unsigned no_stdin:1;
      48  	void (*preexec_cb)(void);
      49  };
      50  static struct child_process pager_process;
      51  
      52  static inline void close_pair(int fd[2])
      53  {
      54  	close(fd[0]);
      55  	close(fd[1]);
      56  }
      57  
      58  static int start_command(struct child_process *cmd)
      59  {
      60  	int need_in;
      61  	int fdin[2];
      62  
      63  	/*
      64  	 * In case of errors we must keep the promise to close FDs
      65  	 * that have been passed in via ->in and ->out.
      66  	 */
      67  	need_in = !cmd->no_stdin && cmd->in < 0;
      68  	if (need_in) {
      69  		if (pipe(fdin) < 0) {
      70  			if (cmd->out > 0)
      71  				close(cmd->out);
      72  			return -1;
      73  		}
      74  		cmd->in = fdin[1];
      75  	}
      76  
      77  	fflush(NULL);
      78  	cmd->pid = fork();
      79  	if (!cmd->pid) {
      80  		if (need_in) {
      81  			dup2(fdin[0], STDIN_FILENO);
      82  			close_pair(fdin);
      83  		} else if (cmd->in > 0) {
      84  			dup2(cmd->in, STDIN_FILENO);
      85  			close(cmd->in);
      86  		}
      87  
      88  		cmd->preexec_cb();
      89  		execvp(cmd->argv[0], (char *const*) cmd->argv);
      90  		errexec(cmd->argv[0]);
      91  	}
      92  
      93  	if (cmd->pid < 0) {
      94  		if (need_in)
      95  			close_pair(fdin);
      96  		else if (0 <= cmd->in)
      97  			close(cmd->in);
      98  		return -1;
      99  	}
     100  
     101  	if (need_in)
     102  		close(fdin[0]);
     103  	else if (0 <= cmd->in)
     104  		close(cmd->in);
     105  	return 0;
     106  }
     107  
     108  static int wait_or_whine(pid_t pid)
     109  {
     110  	for (;;) {
     111  		int status, code;
     112  		pid_t waiting = waitpid(pid, &status, 0);
     113  
     114  		if (waiting < 0) {
     115  			if (errno == EINTR)
     116  				continue;
     117  			ul_sig_err(EXIT_FAILURE, "waitpid failed");
     118  		}
     119  		if (waiting != pid)
     120  			return -1;
     121  		if (WIFSIGNALED(status))
     122  			return -1;
     123  
     124  		if (!WIFEXITED(status))
     125  			return -1;
     126  		code = WEXITSTATUS(status);
     127  		switch (code) {
     128  		case 127:
     129  			return -1;
     130  		case 0:
     131  			return 0;
     132  		default:
     133  			return -1;
     134  		}
     135  	}
     136  }
     137  
     138  static int finish_command(struct child_process *cmd)
     139  {
     140  	return wait_or_whine(cmd->pid);
     141  }
     142  
     143  static void pager_preexec(void)
     144  {
     145  	/*
     146  	 * Work around bug in "less" by not starting it until we
     147  	 * have real input
     148  	 */
     149  	fd_set in, ex;
     150  
     151  	FD_ZERO(&in);
     152  	FD_SET(STDIN_FILENO, &in);
     153  	ex = in;
     154  
     155  	select(STDIN_FILENO + 1, &in, NULL, &ex, NULL);
     156  
     157  	if (setenv("LESS", "FRSX", 0) != 0)
     158  		warn(_("failed to set the %s environment variable"), "LESS");
     159  }
     160  
     161  static void wait_for_pager(void)
     162  {
     163  	if (pager_process.pid == 0)
     164  		return;
     165  
     166  	/* signal EOF to pager */
     167  	close(STDOUT_FILENO);
     168  	close(STDERR_FILENO);
     169  	finish_command(&pager_process);
     170  }
     171  
     172  static void wait_for_pager_signal(int signo)
     173  {
     174  	wait_for_pager();
     175  	raise(signo);
     176  }
     177  
     178  static int has_command(const char *cmd)
     179  {
     180  	const char *path;
     181  	char *p, *s;
     182  	int rc = 0;
     183  
     184  	if (!cmd)
     185  		goto done;
     186  	if (*cmd == '/') {
     187  		rc = access(cmd, X_OK) == 0;
     188  		goto done;
     189  	}
     190  
     191  	path = getenv("PATH");
     192  	if (!path)
     193  		goto done;
     194  	p = xstrdup(path);
     195  	if (!p)
     196  		goto done;
     197  
     198  	for(s = strtok(p, ":"); s; s = strtok(NULL, ":")) {
     199  		int fd = open(s, O_RDONLY|O_CLOEXEC);
     200  		if (fd < 0)
     201  			continue;
     202  		rc = faccessat(fd, cmd, X_OK, 0) == 0;
     203  		close(fd);
     204  		if (rc)
     205  			break;
     206  	}
     207  	free(p);
     208  done:
     209  	/*fprintf(stderr, "has PAGER %s rc=%d\n", cmd, rc);*/
     210  	return rc;
     211  }
     212  
     213  static void __setup_pager(void)
     214  {
     215  	const char *pager = getenv("PAGER");
     216  	struct sigaction sa;
     217  
     218  	if (!isatty(STDOUT_FILENO))
     219  		return;
     220  
     221  	if (!pager)
     222  		pager = "less";
     223  	else if (!*pager || !strcmp(pager, "cat"))
     224  		return;
     225  
     226  	if (!has_command(pager))
     227  		return;
     228  
     229  	/* spawn the pager */
     230  	pager_argv[2] = pager;
     231  	pager_process.argv = pager_argv;
     232  	pager_process.in = -1;
     233  	pager_process.preexec_cb = pager_preexec;
     234  
     235  	if (start_command(&pager_process))
     236  		return;
     237  
     238  	/* original process continues, but writes to the pipe */
     239  	dup2(pager_process.in, STDOUT_FILENO);
     240  	setvbuf(stdout, NULL, _IOLBF, 0);
     241  	if (isatty(STDERR_FILENO)) {
     242  		dup2(pager_process.in, STDERR_FILENO);
     243  		setvbuf(stderr, NULL, _IOLBF, 0);
     244  	}
     245  	close(pager_process.in);
     246  
     247  	memset(&sa, 0, sizeof(sa));
     248  	sa.sa_handler = wait_for_pager_signal;
     249  
     250  	/* this makes sure that the parent terminates after the pager */
     251  	sigaction(SIGINT,  &sa, &pager_process.orig_sigint);
     252  	sigaction(SIGHUP,  &sa, &pager_process.orig_sighup);
     253  	sigaction(SIGTERM, &sa, &pager_process.orig_sigterm);
     254  	sigaction(SIGQUIT, &sa, &pager_process.orig_sigquit);
     255  	sigaction(SIGPIPE, &sa, &pager_process.orig_sigpipe);
     256  }
     257  
     258  /* Setup pager and redirects output to the $PAGER. The pager is closed at exit.
     259   */
     260  void pager_redirect(void)
     261  {
     262  	if (pager_process.pid)
     263  		return;		/* already running */
     264  
     265  	__setup_pager();
     266  
     267  	atexit(wait_for_pager);
     268  }
     269  
     270  /* Setup pager and redirect output, the pager may be closed by pager_close().
     271   */
     272  void pager_open(void)
     273  {
     274  	if (pager_process.pid)
     275  		return;		/* already running */
     276  
     277  	pager_process.org_out = dup(STDOUT_FILENO);
     278  	pager_process.org_err = dup(STDERR_FILENO);
     279  
     280  	__setup_pager();
     281  }
     282  
     283  /* Close pager and restore original std{out,err}.
     284   */
     285  void pager_close(void)
     286  {
     287  	if (pager_process.pid == 0)
     288  		return;
     289  
     290  	wait_for_pager();
     291  
     292  	/* restore original output */
     293  	dup2(pager_process.org_out, STDOUT_FILENO);
     294  	dup2(pager_process.org_err, STDERR_FILENO);
     295  
     296  	close(pager_process.org_out);
     297  	close(pager_process.org_err);
     298  
     299  	/* restore original segnals setting */
     300  	sigaction(SIGINT,  &pager_process.orig_sigint, NULL);
     301  	sigaction(SIGHUP,  &pager_process.orig_sighup, NULL);
     302  	sigaction(SIGTERM, &pager_process.orig_sigterm, NULL);
     303  	sigaction(SIGQUIT, &pager_process.orig_sigquit, NULL);
     304  	sigaction(SIGPIPE, &pager_process.orig_sigpipe, NULL);
     305  
     306  	memset(&pager_process, 0, sizeof(pager_process));
     307  }
     308  
     309  #ifdef TEST_PROGRAM_PAGER
     310  
     311  #define MAX 255
     312  
     313  int main(int argc __attribute__ ((__unused__)),
     314  	 char *argv[] __attribute__ ((__unused__)))
     315  {
     316  	int i;
     317  
     318  	pager_redirect();
     319  	for (i = 0; i < MAX; i++)
     320  		printf("%d\n", i);
     321  	return EXIT_SUCCESS;
     322  }
     323  #endif /* TEST_PROGRAM_PAGER */