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 21 : int pager_open(PagerFlags flags) {
86 21 : _cleanup_close_pair_ int fd[2] = { -1, -1 }, exe_name_pipe[2] = { -1, -1 };
87 21 : _cleanup_strv_free_ char **pager_args = NULL;
88 : const char *pager, *less_opts;
89 : int r;
90 :
91 21 : if (flags & PAGER_DISABLE)
92 0 : return 0;
93 :
94 21 : if (pager_pid > 0)
95 0 : return 1;
96 :
97 21 : if (terminal_is_dumb())
98 21 : 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 289 : void pager_close(void) {
235 :
236 289 : if (pager_pid <= 0)
237 289 : 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 4 : bool pager_have(void) {
258 4 : 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 : }
|