]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/pager.c
core/namespace: rework the return semantics of clone_device_node yet again
[thirdparty/systemd.git] / src / shared / pager.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
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
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
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
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <signal.h>
23 #include <stddef.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/prctl.h>
29 #include <unistd.h>
30
31 #include "copy.h"
32 #include "fd-util.h"
33 #include "locale-util.h"
34 #include "log.h"
35 #include "macro.h"
36 #include "pager.h"
37 #include "process-util.h"
38 #include "signal-util.h"
39 #include "string-util.h"
40 #include "strv.h"
41 #include "terminal-util.h"
42
43 static pid_t pager_pid = 0;
44
45 static int stored_stdout = -1;
46 static int stored_stderr = -1;
47 static bool stdout_redirected = false;
48 static bool stderr_redirected = false;
49
50 _noreturn_ static void pager_fallback(void) {
51 int r;
52
53 r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (uint64_t) -1, 0);
54 if (r < 0) {
55 log_error_errno(r, "Internal pager failed: %m");
56 _exit(EXIT_FAILURE);
57 }
58
59 _exit(EXIT_SUCCESS);
60 }
61
62 int pager_open(bool no_pager, bool jump_to_end) {
63 _cleanup_close_pair_ int fd[2] = { -1, -1 };
64 const char *pager;
65 int r;
66
67 if (no_pager)
68 return 0;
69
70 if (pager_pid > 0)
71 return 1;
72
73 if (terminal_is_dumb())
74 return 0;
75
76 if (!is_main_thread())
77 return -EPERM;
78
79 pager = getenv("SYSTEMD_PAGER");
80 if (!pager)
81 pager = getenv("PAGER");
82
83 /* If the pager is explicitly turned off, honour it */
84 if (pager && STR_IN_SET(pager, "", "cat"))
85 return 0;
86
87 /* Determine and cache number of columns/lines before we spawn the pager so that we get the value from the
88 * actual tty */
89 (void) columns();
90 (void) lines();
91
92 if (pipe2(fd, O_CLOEXEC) < 0)
93 return log_error_errno(errno, "Failed to create pager pipe: %m");
94
95 r = safe_fork("(pager)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pager_pid);
96 if (r < 0)
97 return r;
98 if (r == 0) {
99 const char* less_opts, *less_charset;
100
101 /* In the child start the pager */
102
103 (void) dup2(fd[0], STDIN_FILENO);
104 safe_close_pair(fd);
105
106 /* Initialize a good set of less options */
107 less_opts = getenv("SYSTEMD_LESS");
108 if (!less_opts)
109 less_opts = "FRSXMK";
110 if (jump_to_end)
111 less_opts = strjoina(less_opts, " +G");
112 if (setenv("LESS", less_opts, 1) < 0)
113 _exit(EXIT_FAILURE);
114
115 /* Initialize a good charset for less. This is
116 * particularly important if we output UTF-8
117 * characters. */
118 less_charset = getenv("SYSTEMD_LESSCHARSET");
119 if (!less_charset && is_locale_utf8())
120 less_charset = "utf-8";
121 if (less_charset &&
122 setenv("LESSCHARSET", less_charset, 1) < 0)
123 _exit(EXIT_FAILURE);
124
125 if (pager) {
126 execlp(pager, pager, NULL);
127 execl("/bin/sh", "sh", "-c", pager, NULL);
128 }
129
130 /* Debian's alternatives command for pagers is
131 * called 'pager'. Note that we do not call
132 * sensible-pagers here, since that is just a
133 * shell script that implements a logic that
134 * is similar to this one anyway, but is
135 * Debian-specific. */
136 execlp("pager", "pager", NULL);
137
138 execlp("less", "less", NULL);
139 execlp("more", "more", NULL);
140
141 pager_fallback();
142 /* not reached */
143 }
144
145 /* Return in the parent */
146 stored_stdout = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 3);
147 if (dup2(fd[1], STDOUT_FILENO) < 0) {
148 stored_stdout = safe_close(stored_stdout);
149 return log_error_errno(errno, "Failed to duplicate pager pipe: %m");
150 }
151 stdout_redirected = true;
152
153 stored_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3);
154 if (dup2(fd[1], STDERR_FILENO) < 0) {
155 stored_stderr = safe_close(stored_stderr);
156 return log_error_errno(errno, "Failed to duplicate pager pipe: %m");
157 }
158 stderr_redirected = true;
159
160 return 1;
161 }
162
163 void pager_close(void) {
164
165 if (pager_pid <= 0)
166 return;
167
168 /* Inform pager that we are done */
169 (void) fflush(stdout);
170 if (stdout_redirected)
171 if (stored_stdout < 0 || dup2(stored_stdout, STDOUT_FILENO) < 0)
172 (void) close(STDOUT_FILENO);
173 stored_stdout = safe_close(stored_stdout);
174 (void) fflush(stderr);
175 if (stderr_redirected)
176 if (stored_stderr < 0 || dup2(stored_stderr, STDERR_FILENO) < 0)
177 (void) close(STDERR_FILENO);
178 stored_stderr = safe_close(stored_stderr);
179 stdout_redirected = stderr_redirected = false;
180
181 (void) kill(pager_pid, SIGCONT);
182 (void) wait_for_terminate(pager_pid, NULL);
183 pager_pid = 0;
184 }
185
186 bool pager_have(void) {
187 return pager_pid > 0;
188 }
189
190 int show_man_page(const char *desc, bool null_stdio) {
191 const char *args[4] = { "man", NULL, NULL, NULL };
192 char *e = NULL;
193 pid_t pid;
194 size_t k;
195 int r;
196
197 k = strlen(desc);
198
199 if (desc[k-1] == ')')
200 e = strrchr(desc, '(');
201
202 if (e) {
203 char *page = NULL, *section = NULL;
204
205 page = strndupa(desc, e - desc);
206 section = strndupa(e + 1, desc + k - e - 2);
207
208 args[1] = section;
209 args[2] = page;
210 } else
211 args[1] = desc;
212
213 r = safe_fork("(man)", FORK_RESET_SIGNALS|FORK_DEATHSIG|(null_stdio ? FORK_NULL_STDIO : 0)|FORK_LOG, &pid);
214 if (r < 0)
215 return r;
216 if (r == 0) {
217 /* Child */
218 execvp(args[0], (char**) args);
219 log_error_errno(errno, "Failed to execute man: %m");
220 _exit(EXIT_FAILURE);
221 }
222
223 return wait_for_terminate_and_check(NULL, pid, 0);
224 }