]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/pager.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / shared / pager.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
1968a360
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
1968a360
LP
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 15 Lesser General Public License for more details.
1968a360 16
5430f7f2 17 You should have received a copy of the GNU Lesser General Public License
1968a360
LP
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
a8fbdf54
TA
21#include <errno.h>
22#include <signal.h>
23#include <stddef.h>
24#include <stdint.h>
25#include <stdio.h>
1968a360 26#include <stdlib.h>
1968a360
LP
27#include <string.h>
28#include <sys/prctl.h>
07630cea 29#include <unistd.h>
1968a360 30
07630cea 31#include "copy.h"
3ffd4af2 32#include "fd-util.h"
8752c575 33#include "locale-util.h"
a8fbdf54 34#include "log.h"
1968a360 35#include "macro.h"
3ffd4af2 36#include "pager.h"
07630cea 37#include "process-util.h"
ce30c8dc 38#include "signal-util.h"
07630cea 39#include "string-util.h"
57c9e047 40#include "strv.h"
07630cea 41#include "terminal-util.h"
1968a360
LP
42
43static pid_t pager_pid = 0;
44
919ce0b7 45noreturn static void pager_fallback(void) {
6a7c676c 46 int r;
46e65dcc 47
1c876927 48 r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (uint64_t) -1, 0);
6a7c676c
LP
49 if (r < 0) {
50 log_error_errno(r, "Internal pager failed: %m");
4a8e40eb
MS
51 _exit(EXIT_FAILURE);
52 }
46e65dcc 53
4a8e40eb
MS
54 _exit(EXIT_SUCCESS);
55}
56
a45e7bb4
MS
57static int stored_stdout = -1;
58static int stored_stderr = -1;
59static bool stdout_redirected = false;
60static bool stderr_redirected = false;
61
ea4b98e6 62int pager_open(bool no_pager, bool jump_to_end) {
bcbd61db 63 _cleanup_close_pair_ int fd[2] = { -1, -1 };
1968a360
LP
64 const char *pager;
65 pid_t parent_pid;
66
ea4b98e6
AK
67 if (no_pager)
68 return 0;
69
1968a360 70 if (pager_pid > 0)
f89a3b6f 71 return 1;
1968a360 72
ac96418b 73 if (terminal_is_dumb())
f89a3b6f 74 return 0;
729e3769 75
bcbd61db
LP
76 pager = getenv("SYSTEMD_PAGER");
77 if (!pager)
78 pager = getenv("PAGER");
79
80 /* If the pager is explicitly turned off, honour it */
57c9e047 81 if (pager && STR_IN_SET(pager, "", "cat"))
bcbd61db
LP
82 return 0;
83
d13b5227
LP
84 /* Determine and cache number of columns/lines before we spawn the pager so that we get the value from the
85 * actual tty */
bcbd61db 86 (void) columns();
d13b5227 87 (void) lines();
1968a360 88
d262e99e 89 if (pipe2(fd, O_CLOEXEC) < 0)
4a62c710 90 return log_error_errno(errno, "Failed to create pager pipe: %m");
1968a360 91
df0ff127 92 parent_pid = getpid_cached();
1968a360
LP
93
94 pager_pid = fork();
bcbd61db 95 if (pager_pid < 0)
65359589 96 return log_error_errno(errno, "Failed to fork pager: %m");
1968a360
LP
97
98 /* In the child start the pager */
99 if (pager_pid == 0) {
a1b4e6e9 100 const char* less_opts, *less_charset;
1968a360 101
ce30c8dc
LP
102 (void) reset_all_signal_handlers();
103 (void) reset_signal_mask();
104
bcbd61db 105 (void) dup2(fd[0], STDIN_FILENO);
3d94f76c 106 safe_close_pair(fd);
1968a360 107
a1b4e6e9 108 /* Initialize a good set of less options */
f366d58d
JD
109 less_opts = getenv("SYSTEMD_LESS");
110 if (!less_opts)
111 less_opts = "FRSXMK";
1b12a7b5 112 if (jump_to_end)
63c372cb 113 less_opts = strjoina(less_opts, " +G");
0357fa0d
ZJS
114 if (setenv("LESS", less_opts, 1) < 0)
115 _exit(EXIT_FAILURE);
1968a360 116
a1b4e6e9
LP
117 /* Initialize a good charset for less. This is
118 * particularly important if we output UTF-8
119 * characters. */
120 less_charset = getenv("SYSTEMD_LESSCHARSET");
121 if (!less_charset && is_locale_utf8())
122 less_charset = "utf-8";
0357fa0d
ZJS
123 if (less_charset &&
124 setenv("LESSCHARSET", less_charset, 1) < 0)
125 _exit(EXIT_FAILURE);
a1b4e6e9 126
1968a360
LP
127 /* Make sure the pager goes away when the parent dies */
128 if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
129 _exit(EXIT_FAILURE);
130
131 /* Check whether our parent died before we were able
132 * to set the death signal */
133 if (getppid() != parent_pid)
134 _exit(EXIT_SUCCESS);
135
136 if (pager) {
137 execlp(pager, pager, NULL);
138 execl("/bin/sh", "sh", "-c", pager, NULL);
139 }
140
141 /* Debian's alternatives command for pagers is
142 * called 'pager'. Note that we do not call
143 * sensible-pagers here, since that is just a
144 * shell script that implements a logic that
145 * is similar to this one anyway, but is
146 * Debian-specific. */
147 execlp("pager", "pager", NULL);
148
149 execlp("less", "less", NULL);
150 execlp("more", "more", NULL);
1968a360 151
4a8e40eb
MS
152 pager_fallback();
153 /* not reached */
1968a360
LP
154 }
155
156 /* Return in the parent */
a45e7bb4
MS
157 stored_stdout = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 3);
158 if (dup2(fd[1], STDOUT_FILENO) < 0) {
159 stored_stdout = safe_close(stored_stdout);
4a62c710 160 return log_error_errno(errno, "Failed to duplicate pager pipe: %m");
a45e7bb4
MS
161 }
162 stdout_redirected = true;
163
164 stored_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3);
165 if (dup2(fd[1], STDERR_FILENO) < 0) {
166 stored_stderr = safe_close(stored_stderr);
8b5264aa 167 return log_error_errno(errno, "Failed to duplicate pager pipe: %m");
a45e7bb4
MS
168 }
169 stderr_redirected = true;
1968a360 170
f89a3b6f 171 return 1;
1968a360
LP
172}
173
174void pager_close(void) {
175
176 if (pager_pid <= 0)
177 return;
178
179 /* Inform pager that we are done */
a45e7bb4 180 (void) fflush(stdout);
77018a8c
MS
181 if (stdout_redirected)
182 if (stored_stdout < 0 || dup2(stored_stdout, STDOUT_FILENO) < 0)
183 (void) close(STDOUT_FILENO);
a45e7bb4
MS
184 stored_stdout = safe_close(stored_stdout);
185 (void) fflush(stderr);
77018a8c
MS
186 if (stderr_redirected)
187 if (stored_stderr < 0 || dup2(stored_stderr, STDERR_FILENO) < 0)
188 (void) close(STDERR_FILENO);
a45e7bb4
MS
189 stored_stderr = safe_close(stored_stderr);
190 stdout_redirected = stderr_redirected = false;
8b5264aa 191
74ca738f 192 (void) kill(pager_pid, SIGCONT);
c73d180d 193 (void) wait_for_terminate(pager_pid, NULL);
1968a360
LP
194 pager_pid = 0;
195}
f89a3b6f
LP
196
197bool pager_have(void) {
198 return pager_pid > 0;
199}
78002a67
ZJS
200
201int show_man_page(const char *desc, bool null_stdio) {
202 const char *args[4] = { "man", NULL, NULL, NULL };
203 char *e = NULL;
204 pid_t pid;
205 size_t k;
206 int r;
207 siginfo_t status;
208
209 k = strlen(desc);
210
211 if (desc[k-1] == ')')
212 e = strrchr(desc, '(');
213
214 if (e) {
215 char *page = NULL, *section = NULL;
216
217 page = strndupa(desc, e - desc);
218 section = strndupa(e + 1, desc + k - e - 2);
219
220 args[1] = section;
221 args[2] = page;
222 } else
223 args[1] = desc;
224
225 pid = fork();
4a62c710
MS
226 if (pid < 0)
227 return log_error_errno(errno, "Failed to fork: %m");
78002a67
ZJS
228
229 if (pid == 0) {
230 /* Child */
ce30c8dc
LP
231
232 (void) reset_all_signal_handlers();
233 (void) reset_signal_mask();
234
78002a67
ZJS
235 if (null_stdio) {
236 r = make_null_stdio();
237 if (r < 0) {
da927ba9 238 log_error_errno(r, "Failed to kill stdio: %m");
78002a67
ZJS
239 _exit(EXIT_FAILURE);
240 }
241 }
242
243 execvp(args[0], (char**) args);
56f64d95 244 log_error_errno(errno, "Failed to execute man: %m");
78002a67
ZJS
245 _exit(EXIT_FAILURE);
246 }
247
248 r = wait_for_terminate(pid, &status);
249 if (r < 0)
250 return r;
251
252 log_debug("Exit code %i status %i", status.si_code, status.si_status);
253 return status.si_status;
254}