chiark / gitweb /
54790947ba7d82eb678e73b1d99fd888bbe6094b
[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 <sys/types.h>
23 #include <fcntl.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <sys/prctl.h>
28
29 #include "pager.h"
30 #include "util.h"
31 #include "macro.h"
32
33 static pid_t pager_pid = 0;
34
35 noreturn static void pager_fallback(void) {
36         ssize_t n;
37
38         do {
39                 n = splice(STDIN_FILENO, NULL, STDOUT_FILENO, NULL, 64*1024, 0);
40         } while (n > 0);
41
42         if (n < 0) {
43                 log_error("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                 log_error("Failed to create pager pipe: %m");
72                 return -errno;
73         }
74
75         parent_pid = getpid();
76
77         pager_pid = fork();
78         if (pager_pid < 0) {
79                 r = -errno;
80                 log_error("Failed to fork pager: %m");
81                 safe_close_pair(fd);
82                 return r;
83         }
84
85         /* In the child start the pager */
86         if (pager_pid == 0) {
87                 const char* less_opts;
88
89                 dup2(fd[0], STDIN_FILENO);
90                 safe_close_pair(fd);
91
92                 less_opts = getenv("SYSTEMD_LESS");
93                 if (!less_opts)
94                         less_opts = "FRSXMK";
95                 if (jump_to_end)
96                         less_opts = strappenda(less_opts, " +G");
97                 setenv("LESS", less_opts, 1);
98
99                 /* Make sure the pager goes away when the parent dies */
100                 if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
101                         _exit(EXIT_FAILURE);
102
103                 /* Check whether our parent died before we were able
104                  * to set the death signal */
105                 if (getppid() != parent_pid)
106                         _exit(EXIT_SUCCESS);
107
108                 if (pager) {
109                         execlp(pager, pager, NULL);
110                         execl("/bin/sh", "sh", "-c", pager, NULL);
111                 }
112
113                 /* Debian's alternatives command for pagers is
114                  * called 'pager'. Note that we do not call
115                  * sensible-pagers here, since that is just a
116                  * shell script that implements a logic that
117                  * is similar to this one anyway, but is
118                  * Debian-specific. */
119                 execlp("pager", "pager", NULL);
120
121                 execlp("less", "less", NULL);
122                 execlp("more", "more", NULL);
123
124                 pager_fallback();
125                 /* not reached */
126         }
127
128         /* Return in the parent */
129         if (dup2(fd[1], STDOUT_FILENO) < 0) {
130                 log_error("Failed to duplicate pager pipe: %m");
131                 return -errno;
132         }
133
134         safe_close_pair(fd);
135         return 1;
136 }
137
138 void pager_close(void) {
139
140         if (pager_pid <= 0)
141                 return;
142
143         /* Inform pager that we are done */
144         fclose(stdout);
145         kill(pager_pid, SIGCONT);
146         wait_for_terminate(pager_pid, NULL);
147         pager_pid = 0;
148 }
149
150 bool pager_have(void) {
151         return pager_pid > 0;
152 }
153
154 int show_man_page(const char *desc, bool null_stdio) {
155         const char *args[4] = { "man", NULL, NULL, NULL };
156         char *e = NULL;
157         pid_t pid;
158         size_t k;
159         int r;
160         siginfo_t status;
161
162         k = strlen(desc);
163
164         if (desc[k-1] == ')')
165                 e = strrchr(desc, '(');
166
167         if (e) {
168                 char *page = NULL, *section = NULL;
169
170                 page = strndupa(desc, e - desc);
171                 section = strndupa(e + 1, desc + k - e - 2);
172
173                 args[1] = section;
174                 args[2] = page;
175         } else
176                 args[1] = desc;
177
178         pid = fork();
179         if (pid < 0) {
180                 log_error("Failed to fork: %m");
181                 return -errno;
182         }
183
184         if (pid == 0) {
185                 /* Child */
186                 if (null_stdio) {
187                         r = make_null_stdio();
188                         if (r < 0) {
189                                 log_error("Failed to kill stdio: %s", strerror(-r));
190                                 _exit(EXIT_FAILURE);
191                         }
192                 }
193
194                 execvp(args[0], (char**) args);
195                 log_error("Failed to execute man: %m");
196                 _exit(EXIT_FAILURE);
197         }
198
199         r = wait_for_terminate(pid, &status);
200         if (r < 0)
201                 return r;
202
203         log_debug("Exit code %i status %i", status.si_code, status.si_status);
204         return status.si_status;
205 }