chiark / gitweb /
2598c1eaa4a86d395063644dc32e9b05f451128e
[elogind.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 #if 0 /// UNNEEDED by elogind
191 int show_man_page(const char *desc, bool null_stdio) {
192         const char *args[4] = { "man", NULL, NULL, NULL };
193         char *e = NULL;
194         pid_t pid;
195         size_t k;
196         int r;
197
198         k = strlen(desc);
199
200         if (desc[k-1] == ')')
201                 e = strrchr(desc, '(');
202
203         if (e) {
204                 char *page = NULL, *section = NULL;
205
206                 page = strndupa(desc, e - desc);
207                 section = strndupa(e + 1, desc + k - e - 2);
208
209                 args[1] = section;
210                 args[2] = page;
211         } else
212                 args[1] = desc;
213
214         r = safe_fork("(man)", FORK_RESET_SIGNALS|FORK_DEATHSIG|(null_stdio ? FORK_NULL_STDIO : 0)|FORK_LOG, &pid);
215         if (r < 0)
216                 return r;
217         if (r == 0) {
218                 /* Child */
219                 execvp(args[0], (char**) args);
220                 log_error_errno(errno, "Failed to execute man: %m");
221                 _exit(EXIT_FAILURE);
222         }
223
224         return wait_for_terminate_and_check(NULL, pid, 0);
225 }
226 #endif // 0