xref: /linux/tools/lib/subcmd/pager.c (revision a06c3fad49a50d5d5eb078f93e70f4d3eca5d5a5)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <sys/select.h>
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <signal.h>
7 #include <sys/ioctl.h>
8 #include "pager.h"
9 #include "run-command.h"
10 #include "sigchain.h"
11 #include "subcmd-config.h"
12 
13 /*
14  * This is split up from the rest of git so that we can do
15  * something different on Windows.
16  */
17 
18 static int spawned_pager;
19 static int pager_columns;
20 
21 void pager_init(const char *pager_env)
22 {
23 	subcmd_config.pager_env = pager_env;
24 }
25 
26 static const char *forced_pager;
27 
28 void force_pager(const char *pager)
29 {
30 	forced_pager = pager;
31 }
32 
33 static void pager_preexec(void)
34 {
35 	/*
36 	 * Work around bug in "less" by not starting it until we
37 	 * have real input
38 	 */
39 	fd_set in;
40 	fd_set exception;
41 
42 	FD_ZERO(&in);
43 	FD_ZERO(&exception);
44 	FD_SET(0, &in);
45 	FD_SET(0, &exception);
46 	select(1, &in, NULL, &exception, NULL);
47 
48 	setenv("LESS", "FRSX", 0);
49 }
50 
51 static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
52 static struct child_process pager_process;
53 
54 static void wait_for_pager(void)
55 {
56 	fflush(stdout);
57 	fflush(stderr);
58 	/* signal EOF to pager */
59 	close(1);
60 	close(2);
61 	finish_command(&pager_process);
62 }
63 
64 static void wait_for_pager_signal(int signo)
65 {
66 	wait_for_pager();
67 	sigchain_pop(signo);
68 	raise(signo);
69 }
70 
71 void setup_pager(void)
72 {
73 	const char *pager = getenv(subcmd_config.pager_env);
74 	struct winsize sz;
75 
76 	if (forced_pager)
77 		pager = forced_pager;
78 	if (!isatty(1) && !forced_pager)
79 		return;
80 	if (ioctl(1, TIOCGWINSZ, &sz) == 0)
81 		pager_columns = sz.ws_col;
82 	if (!pager)
83 		pager = getenv("PAGER");
84 	if (!(pager || access("/usr/bin/pager", X_OK)))
85 		pager = "/usr/bin/pager";
86 	if (!(pager || access("/usr/bin/less", X_OK)))
87 		pager = "/usr/bin/less";
88 	if (!pager)
89 		pager = "cat";
90 	if (!*pager || !strcmp(pager, "cat"))
91 		return;
92 
93 	spawned_pager = 1; /* means we are emitting to terminal */
94 
95 	/* spawn the pager */
96 	pager_argv[2] = pager;
97 	pager_process.argv = pager_argv;
98 	pager_process.in = -1;
99 	pager_process.preexec_cb = pager_preexec;
100 
101 	if (start_command(&pager_process))
102 		return;
103 
104 	/* original process continues, but writes to the pipe */
105 	dup2(pager_process.in, 1);
106 	if (isatty(2))
107 		dup2(pager_process.in, 2);
108 	close(pager_process.in);
109 
110 	/* this makes sure that the parent terminates after the pager */
111 	sigchain_push_common(wait_for_pager_signal);
112 	atexit(wait_for_pager);
113 }
114 
115 int pager_in_use(void)
116 {
117 	return spawned_pager;
118 }
119 
120 int pager_get_columns(void)
121 {
122 	char *s;
123 
124 	s = getenv("COLUMNS");
125 	if (s)
126 		return atoi(s);
127 
128 	return (pager_columns ? pager_columns : 80) - 2;
129 }
130