Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <errno.h>
4 : : #include <signal.h>
5 : : #include <stddef.h>
6 : : #include <stdint.h>
7 : : #include <stdio.h>
8 : : #include <stdlib.h>
9 : : #include <string.h>
10 : : #include <sys/prctl.h>
11 : : #include <unistd.h>
12 : :
13 : : #include "copy.h"
14 : : #include "fd-util.h"
15 : : #include "fileio.h"
16 : : #include "io-util.h"
17 : : #include "locale-util.h"
18 : : #include "log.h"
19 : : #include "macro.h"
20 : : #include "pager.h"
21 : : #include "process-util.h"
22 : : #include "rlimit-util.h"
23 : : #include "signal-util.h"
24 : : #include "string-util.h"
25 : : #include "strv.h"
26 : : #include "terminal-util.h"
27 : : #include "util.h"
28 : :
29 : : static pid_t pager_pid = 0;
30 : :
31 : : static int stored_stdout = -1;
32 : : static int stored_stderr = -1;
33 : : static bool stdout_redirected = false;
34 : : static bool stderr_redirected = false;
35 : :
36 : 0 : _noreturn_ static void pager_fallback(void) {
37 : : int r;
38 : :
39 : 0 : r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (uint64_t) -1, 0);
40 [ # # ]: 0 : if (r < 0) {
41 [ # # ]: 0 : log_error_errno(r, "Internal pager failed: %m");
42 : 0 : _exit(EXIT_FAILURE);
43 : : }
44 : :
45 : 0 : _exit(EXIT_SUCCESS);
46 : : }
47 : :
48 : 0 : static int no_quit_on_interrupt(int exe_name_fd, const char *less_opts) {
49 : 0 : _cleanup_fclose_ FILE *file = NULL;
50 : 0 : _cleanup_free_ char *line = NULL;
51 : : int r;
52 : :
53 [ # # ]: 0 : assert(exe_name_fd >= 0);
54 [ # # ]: 0 : assert(less_opts);
55 : :
56 : : /* This takes ownership of exe_name_fd */
57 : 0 : file = fdopen(exe_name_fd, "r");
58 [ # # ]: 0 : if (!file) {
59 : 0 : safe_close(exe_name_fd);
60 [ # # ]: 0 : return log_error_errno(errno, "Failed to create FILE object: %m");
61 : : }
62 : :
63 : : /* Find the last line */
64 : 0 : for (;;) {
65 [ # # # ]: 0 : _cleanup_free_ char *t = NULL;
66 : :
67 : 0 : r = read_line(file, LONG_LINE_MAX, &t);
68 [ # # ]: 0 : if (r < 0)
69 [ # # ]: 0 : return log_error_errno(r, "Failed to read from socket: %m");
70 [ # # ]: 0 : if (r == 0)
71 : 0 : break;
72 : :
73 : 0 : free_and_replace(line, t);
74 : : }
75 : :
76 : : /* We only treat "less" specially.
77 : : * Return true whenever option K is *not* set. */
78 [ # # # # ]: 0 : r = streq_ptr(line, "less") && !strchr(less_opts, 'K');
79 : :
80 [ # # ]: 0 : log_debug("Pager executable is \"%s\", options \"%s\", quit_on_interrupt: %s",
81 : : strnull(line), less_opts, yes_no(!r));
82 : 0 : return r;
83 : : }
84 : :
85 : 83 : int pager_open(PagerFlags flags) {
86 : 83 : _cleanup_close_pair_ int fd[2] = { -1, -1 }, exe_name_pipe[2] = { -1, -1 };
87 : 83 : _cleanup_strv_free_ char **pager_args = NULL;
88 : : const char *pager, *less_opts;
89 : : int r;
90 : :
91 [ - + ]: 83 : if (flags & PAGER_DISABLE)
92 : 0 : return 0;
93 : :
94 [ - + ]: 83 : if (pager_pid > 0)
95 : 0 : return 1;
96 : :
97 [ + - ]: 83 : if (terminal_is_dumb())
98 : 83 : return 0;
99 : :
100 [ # # ]: 0 : if (!is_main_thread())
101 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Pager invoked from wrong thread.");
102 : :
103 : 0 : pager = getenv("SYSTEMD_PAGER");
104 [ # # ]: 0 : if (!pager)
105 : 0 : pager = getenv("PAGER");
106 : :
107 [ # # ]: 0 : if (pager) {
108 : 0 : pager_args = strv_split(pager, WHITESPACE);
109 [ # # ]: 0 : if (!pager_args)
110 : 0 : return log_oom();
111 : :
112 : : /* If the pager is explicitly turned off, honour it */
113 [ # # # # ]: 0 : if (strv_isempty(pager_args) || strv_equal(pager_args, STRV_MAKE("cat")))
114 : 0 : return 0;
115 : : }
116 : :
117 : : /* Determine and cache number of columns/lines before we spawn the pager so that we get the value from the
118 : : * actual tty */
119 : 0 : (void) columns();
120 : 0 : (void) lines();
121 : :
122 [ # # ]: 0 : if (pipe2(fd, O_CLOEXEC) < 0)
123 [ # # ]: 0 : return log_error_errno(errno, "Failed to create pager pipe: %m");
124 : :
125 : : /* This is a pipe to feed the name of the executed pager binary into the parent */
126 [ # # ]: 0 : if (pipe2(exe_name_pipe, O_CLOEXEC) < 0)
127 [ # # ]: 0 : return log_error_errno(errno, "Failed to create exe_name pipe: %m");
128 : :
129 : : /* Initialize a good set of less options */
130 : 0 : less_opts = getenv("SYSTEMD_LESS");
131 [ # # ]: 0 : if (!less_opts)
132 : 0 : less_opts = "FRSXMK";
133 [ # # ]: 0 : if (flags & PAGER_JUMP_TO_END)
134 [ # # # # : 0 : less_opts = strjoina(less_opts, " +G");
# # # # #
# # # ]
135 : :
136 : 0 : r = safe_fork("(pager)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pager_pid);
137 [ # # ]: 0 : if (r < 0)
138 : 0 : return r;
139 [ # # ]: 0 : if (r == 0) {
140 : : const char *less_charset, *exe;
141 : :
142 : : /* In the child start the pager */
143 : :
144 [ # # ]: 0 : if (dup2(fd[0], STDIN_FILENO) < 0) {
145 [ # # ]: 0 : log_error_errno(errno, "Failed to duplicate file descriptor to STDIN: %m");
146 : 0 : _exit(EXIT_FAILURE);
147 : : }
148 : :
149 : 0 : safe_close_pair(fd);
150 : :
151 [ # # ]: 0 : if (setenv("LESS", less_opts, 1) < 0) {
152 [ # # ]: 0 : log_error_errno(errno, "Failed to set environment variable LESS: %m");
153 : 0 : _exit(EXIT_FAILURE);
154 : : }
155 : :
156 : : /* Initialize a good charset for less. This is
157 : : * particularly important if we output UTF-8
158 : : * characters. */
159 : 0 : less_charset = getenv("SYSTEMD_LESSCHARSET");
160 [ # # # # ]: 0 : if (!less_charset && is_locale_utf8())
161 : 0 : less_charset = "utf-8";
162 [ # # # # ]: 0 : if (less_charset &&
163 : 0 : setenv("LESSCHARSET", less_charset, 1) < 0) {
164 [ # # ]: 0 : log_error_errno(errno, "Failed to set environment variable LESSCHARSET: %m");
165 : 0 : _exit(EXIT_FAILURE);
166 : : }
167 : :
168 [ # # ]: 0 : if (pager_args) {
169 : 0 : r = loop_write(exe_name_pipe[1], pager_args[0], strlen(pager_args[0]) + 1, false);
170 [ # # ]: 0 : if (r < 0) {
171 [ # # ]: 0 : log_error_errno(r, "Failed to write pager name to socket: %m");
172 : 0 : _exit(EXIT_FAILURE);
173 : : }
174 : :
175 : 0 : execvp(pager_args[0], pager_args);
176 [ # # # # ]: 0 : log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
177 : : "Failed to execute '%s', using fallback pagers: %m", pager_args[0]);
178 : : }
179 : :
180 : : /* Debian's alternatives command for pagers is
181 : : * called 'pager'. Note that we do not call
182 : : * sensible-pagers here, since that is just a
183 : : * shell script that implements a logic that
184 : : * is similar to this one anyway, but is
185 : : * Debian-specific. */
186 [ # # ]: 0 : FOREACH_STRING(exe, "pager", "less", "more") {
187 : 0 : r = loop_write(exe_name_pipe[1], exe, strlen(exe) + 1, false);
188 [ # # ]: 0 : if (r < 0) {
189 [ # # ]: 0 : log_error_errno(r, "Failed to write pager name to socket: %m");
190 : 0 : _exit(EXIT_FAILURE);
191 : : }
192 : 0 : execlp(exe, exe, NULL);
193 [ # # # # ]: 0 : log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
194 : : "Failed to execute '%s', using next fallback pager: %m", exe);
195 : : }
196 : :
197 : 0 : r = loop_write(exe_name_pipe[1], "(built-in)", strlen("(built-in)") + 1, false);
198 [ # # ]: 0 : if (r < 0) {
199 [ # # ]: 0 : log_error_errno(r, "Failed to write pager name to socket: %m");
200 : 0 : _exit(EXIT_FAILURE);
201 : : }
202 : : /* Close pipe to signal the parent to start sending data */
203 : 0 : safe_close_pair(exe_name_pipe);
204 : 0 : pager_fallback();
205 : : /* not reached */
206 : : }
207 : :
208 : : /* Return in the parent */
209 : 0 : stored_stdout = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 3);
210 [ # # ]: 0 : if (dup2(fd[1], STDOUT_FILENO) < 0) {
211 : 0 : stored_stdout = safe_close(stored_stdout);
212 [ # # ]: 0 : return log_error_errno(errno, "Failed to duplicate pager pipe: %m");
213 : : }
214 : 0 : stdout_redirected = true;
215 : :
216 : 0 : stored_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3);
217 [ # # ]: 0 : if (dup2(fd[1], STDERR_FILENO) < 0) {
218 : 0 : stored_stderr = safe_close(stored_stderr);
219 [ # # ]: 0 : return log_error_errno(errno, "Failed to duplicate pager pipe: %m");
220 : : }
221 : 0 : stderr_redirected = true;
222 : :
223 : 0 : exe_name_pipe[1] = safe_close(exe_name_pipe[1]);
224 : :
225 : 0 : r = no_quit_on_interrupt(TAKE_FD(exe_name_pipe[0]), less_opts);
226 [ # # ]: 0 : if (r < 0)
227 : 0 : return r;
228 [ # # ]: 0 : if (r > 0)
229 : 0 : (void) ignore_signals(SIGINT, -1);
230 : :
231 : 0 : return 1;
232 : : }
233 : :
234 : 1155 : void pager_close(void) {
235 : :
236 [ + - ]: 1155 : if (pager_pid <= 0)
237 : 1155 : return;
238 : :
239 : : /* Inform pager that we are done */
240 : 0 : (void) fflush(stdout);
241 [ # # ]: 0 : if (stdout_redirected)
242 [ # # # # ]: 0 : if (stored_stdout < 0 || dup2(stored_stdout, STDOUT_FILENO) < 0)
243 : 0 : (void) close(STDOUT_FILENO);
244 : 0 : stored_stdout = safe_close(stored_stdout);
245 : 0 : (void) fflush(stderr);
246 [ # # ]: 0 : if (stderr_redirected)
247 [ # # # # ]: 0 : if (stored_stderr < 0 || dup2(stored_stderr, STDERR_FILENO) < 0)
248 : 0 : (void) close(STDERR_FILENO);
249 : 0 : stored_stderr = safe_close(stored_stderr);
250 : 0 : stdout_redirected = stderr_redirected = false;
251 : :
252 : 0 : (void) kill(pager_pid, SIGCONT);
253 : 0 : (void) wait_for_terminate(pager_pid, NULL);
254 : 0 : pager_pid = 0;
255 : : }
256 : :
257 : 16 : bool pager_have(void) {
258 : 16 : return pager_pid > 0;
259 : : }
260 : :
261 : 0 : int show_man_page(const char *desc, bool null_stdio) {
262 : 0 : const char *args[4] = { "man", NULL, NULL, NULL };
263 : 0 : char *e = NULL;
264 : : pid_t pid;
265 : : size_t k;
266 : : int r;
267 : :
268 : 0 : k = strlen(desc);
269 : :
270 [ # # ]: 0 : if (desc[k-1] == ')')
271 : 0 : e = strrchr(desc, '(');
272 : :
273 [ # # ]: 0 : if (e) {
274 : 0 : char *page = NULL, *section = NULL;
275 : :
276 : 0 : page = strndupa(desc, e - desc);
277 : 0 : section = strndupa(e + 1, desc + k - e - 2);
278 : :
279 : 0 : args[1] = section;
280 : 0 : args[2] = page;
281 : : } else
282 : 0 : args[1] = desc;
283 : :
284 [ # # ]: 0 : r = safe_fork("(man)", FORK_RESET_SIGNALS|FORK_DEATHSIG|(null_stdio ? FORK_NULL_STDIO : 0)|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
285 [ # # ]: 0 : if (r < 0)
286 : 0 : return r;
287 [ # # ]: 0 : if (r == 0) {
288 : : /* Child */
289 : 0 : execvp(args[0], (char**) args);
290 [ # # ]: 0 : log_error_errno(errno, "Failed to execute man: %m");
291 : 0 : _exit(EXIT_FAILURE);
292 : : }
293 : :
294 : 0 : return wait_for_terminate_and_check(NULL, pid, 0);
295 : : }
|