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         stdout = NULL;
157
158         fclose(stderr);
159         stderr = NULL;
160
161         kill(pager_pid, SIGCONT);
162         (void) wait_for_terminate(pager_pid, NULL);
163         pager_pid = 0;
164 }
165
166 bool pager_have(void) {
167         return pager_pid > 0;
168 }
169
170 /// UNNEEDED by elogind
171 #if 0
172 int show_man_page(const char *desc, bool null_stdio) {
173         const char *args[4] = { "man", NULL, NULL, NULL };
174         char *e = NULL;
175         pid_t pid;
176         size_t k;
177         int r;
178         siginfo_t status;
179
180         k = strlen(desc);
181
182         if (desc[k-1] == ')')
183                 e = strrchr(desc, '(');
184
185         if (e) {
186                 char *page = NULL, *section = NULL;
187
188                 page = strndupa(desc, e - desc);
189                 section = strndupa(e + 1, desc + k - e - 2);
190
191                 args[1] = section;
192                 args[2] = page;
193         } else
194                 args[1] = desc;
195
196         pid = fork();
197         if (pid < 0)
198                 return log_error_errno(errno, "Failed to fork: %m");
199
200         if (pid == 0) {
201                 /* Child */
202
203                 (void) reset_all_signal_handlers();
204                 (void) reset_signal_mask();
205
206                 if (null_stdio) {
207                         r = make_null_stdio();
208                         if (r < 0) {
209                                 log_error_errno(r, "Failed to kill stdio: %m");
210                                 _exit(EXIT_FAILURE);
211                         }
212                 }
213
214                 execvp(args[0], (char**) args);
215                 log_error_errno(errno, "Failed to execute man: %m");
216                 _exit(EXIT_FAILURE);
217         }
218
219         r = wait_for_terminate(pid, &status);
220         if (r < 0)
221                 return r;
222
223         log_debug("Exit code %i status %i", status.si_code, status.si_status);
224         return status.si_status;
225 }
226 #endif // 0