chiark / gitweb /
pager: set $LESSCHARSET when we output UTF8 chars
[elogind.git] / src / shared / pager.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <fcntl.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <sys/prctl.h>
27
28 #include "pager.h"
29 #include "util.h"
30 #include "process-util.h"
31 #include "macro.h"
32 #include "terminal-util.h"
33 #include "signal-util.h"
34 #include "copy.h"
35
36 static pid_t pager_pid = 0;
37
38 noreturn static void pager_fallback(void) {
39         int r;
40
41         r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (off_t) -1, false);
42         if (r < 0) {
43                 log_error_errno(r, "Internal pager failed: %m");
44                 _exit(EXIT_FAILURE);
45         }
46
47         _exit(EXIT_SUCCESS);
48 }
49
50 int pager_open(bool jump_to_end) {
51         int fd[2];
52         const char *pager;
53         pid_t parent_pid;
54         int r;
55
56         if (pager_pid > 0)
57                 return 1;
58
59         if ((pager = getenv("SYSTEMD_PAGER")) || (pager = getenv("PAGER")))
60                 if (!*pager || streq(pager, "cat"))
61                         return 0;
62
63         if (!on_tty())
64                 return 0;
65
66         /* Determine and cache number of columns before we spawn the
67          * pager so that we get the value from the actual tty */
68         columns();
69
70         if (pipe(fd) < 0)
71                 return log_error_errno(errno, "Failed to create pager pipe: %m");
72
73         parent_pid = getpid();
74
75         pager_pid = fork();
76         if (pager_pid < 0) {
77                 r = -errno;
78                 log_error_errno(errno, "Failed to fork pager: %m");
79                 safe_close_pair(fd);
80                 return r;
81         }
82
83         /* In the child start the pager */
84         if (pager_pid == 0) {
85                 const char* less_opts, *less_charset;
86
87                 (void) reset_all_signal_handlers();
88                 (void) reset_signal_mask();
89
90                 dup2(fd[0], STDIN_FILENO);
91                 safe_close_pair(fd);
92
93                 /* Initialize a good set of less options */
94                 less_opts = getenv("SYSTEMD_LESS");
95                 if (!less_opts)
96                         less_opts = "FRSXMK";
97                 if (jump_to_end)
98                         less_opts = strjoina(less_opts, " +G");
99                 setenv("LESS", less_opts, 1);
100
101                 /* Initialize a good charset for less. This is
102                  * particularly important if we output UTF-8
103                  * characters. */
104                 less_charset = getenv("SYSTEMD_LESSCHARSET");
105                 if (!less_charset && is_locale_utf8())
106                         less_charset = "utf-8";
107                 if (less_charset)
108                         setenv("LESSCHARSET", less_charset, 1);
109
110                 /* Make sure the pager goes away when the parent dies */
111                 if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
112                         _exit(EXIT_FAILURE);
113
114                 /* Check whether our parent died before we were able
115                  * to set the death signal */
116                 if (getppid() != parent_pid)
117                         _exit(EXIT_SUCCESS);
118
119                 if (pager) {
120                         execlp(pager, pager, NULL);
121                         execl("/bin/sh", "sh", "-c", pager, NULL);
122                 }
123
124                 /* Debian's alternatives command for pagers is
125                  * called 'pager'. Note that we do not call
126                  * sensible-pagers here, since that is just a
127                  * shell script that implements a logic that
128                  * is similar to this one anyway, but is
129                  * Debian-specific. */
130                 execlp("pager", "pager", NULL);
131
132                 execlp("less", "less", NULL);
133                 execlp("more", "more", NULL);
134
135                 pager_fallback();
136                 /* not reached */
137         }
138
139         /* Return in the parent */
140         if (dup2(fd[1], STDOUT_FILENO) < 0)
141                 return log_error_errno(errno, "Failed to duplicate pager pipe: %m");
142         if (dup2(fd[1], STDERR_FILENO) < 0)
143                 return log_error_errno(errno, "Failed to duplicate pager pipe: %m");
144
145         safe_close_pair(fd);
146         return 1;
147 }
148
149 void pager_close(void) {
150
151         if (pager_pid <= 0)
152                 return;
153
154         /* Inform pager that we are done */
155         fclose(stdout);
156 #if defined(__GLIBC__)
157         stdout = NULL;
158 #endif // in musl-libc this is a const
159
160         fclose(stderr);
161 #if defined(__GLIBC__)
162         stderr = NULL;
163 #endif // in musl-libc this is a const
164
165         kill(pager_pid, SIGCONT);
166         (void) wait_for_terminate(pager_pid, NULL);
167         pager_pid = 0;
168 }
169
170 bool pager_have(void) {
171         return pager_pid > 0;
172 }
173
174 /// UNNEEDED by elogind
175 #if 0
176 int show_man_page(const char *desc, bool null_stdio) {
177         const char *args[4] = { "man", NULL, NULL, NULL };
178         char *e = NULL;
179         pid_t pid;
180         size_t k;
181         int r;
182         siginfo_t status;
183
184         k = strlen(desc);
185
186         if (desc[k-1] == ')')
187                 e = strrchr(desc, '(');
188
189         if (e) {
190                 char *page = NULL, *section = NULL;
191
192                 page = strndupa(desc, e - desc);
193                 section = strndupa(e + 1, desc + k - e - 2);
194
195                 args[1] = section;
196                 args[2] = page;
197         } else
198                 args[1] = desc;
199
200         pid = fork();
201         if (pid < 0)
202                 return log_error_errno(errno, "Failed to fork: %m");
203
204         if (pid == 0) {
205                 /* Child */
206
207                 (void) reset_all_signal_handlers();
208                 (void) reset_signal_mask();
209
210                 if (null_stdio) {
211                         r = make_null_stdio();
212                         if (r < 0) {
213                                 log_error_errno(r, "Failed to kill stdio: %m");
214                                 _exit(EXIT_FAILURE);
215                         }
216                 }
217
218                 execvp(args[0], (char**) args);
219                 log_error_errno(errno, "Failed to execute man: %m");
220                 _exit(EXIT_FAILURE);
221         }
222
223         r = wait_for_terminate(pid, &status);
224         if (r < 0)
225                 return r;
226
227         log_debug("Exit code %i status %i", status.si_code, status.si_status);
228         return status.si_status;
229 }
230 #endif // 0