chiark / gitweb /
Prep v221: Update and clean up build system to sync with upstream
[elogind.git] / src / shared / utmp-wtmp.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 <utmpx.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <sys/utsname.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <poll.h>
29
30 #include "macro.h"
31 #include "path-util.h"
32 #include "terminal-util.h"
33 #include "hostname-util.h"
34 #include "utmp-wtmp.h"
35
36 int utmp_get_runlevel(int *runlevel, int *previous) {
37         struct utmpx *found, lookup = { .ut_type = RUN_LVL };
38         int r;
39         const char *e;
40
41         assert(runlevel);
42
43         /* If these values are set in the environment this takes
44          * precedence. Presumably, sysvinit does this to work around a
45          * race condition that would otherwise exist where we'd always
46          * go to disk and hence might read runlevel data that might be
47          * very new and does not apply to the current script being
48          * executed. */
49
50         e = getenv("RUNLEVEL");
51         if (e && e[0] > 0) {
52                 *runlevel = e[0];
53
54                 if (previous) {
55                         /* $PREVLEVEL seems to be an Upstart thing */
56
57                         e = getenv("PREVLEVEL");
58                         if (e && e[0] > 0)
59                                 *previous = e[0];
60                         else
61                                 *previous = 0;
62                 }
63
64                 return 0;
65         }
66
67         if (utmpxname(_PATH_UTMPX) < 0)
68                 return -errno;
69
70         setutxent();
71
72         found = getutxid(&lookup);
73         if (!found)
74                 r = -errno;
75         else {
76                 int a, b;
77
78                 a = found->ut_pid & 0xFF;
79                 b = (found->ut_pid >> 8) & 0xFF;
80
81                 *runlevel = a;
82                 if (previous)
83                         *previous = b;
84
85                 r = 0;
86         }
87
88         endutxent();
89
90         return r;
91 }
92
93 static void init_timestamp(struct utmpx *store, usec_t t) {
94         assert(store);
95
96         if (t <= 0)
97                 t = now(CLOCK_REALTIME);
98
99         store->ut_tv.tv_sec = t / USEC_PER_SEC;
100         store->ut_tv.tv_usec = t % USEC_PER_SEC;
101 }
102
103 static void init_entry(struct utmpx *store, usec_t t) {
104         struct utsname uts = {};
105
106         assert(store);
107
108         init_timestamp(store, t);
109
110         if (uname(&uts) >= 0)
111                 strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
112
113         strncpy(store->ut_line, "~", sizeof(store->ut_line));  /* or ~~ ? */
114         strncpy(store->ut_id, "~~", sizeof(store->ut_id));
115 }
116
117 static int write_entry_utmp(const struct utmpx *store) {
118         int r;
119
120         assert(store);
121
122         /* utmp is similar to wtmp, but there is only one entry for
123          * each entry type resp. user; i.e. basically a key/value
124          * table. */
125
126         if (utmpxname(_PATH_UTMPX) < 0)
127                 return -errno;
128
129         setutxent();
130
131         if (!pututxline(store))
132                 r = -errno;
133         else
134                 r = 0;
135
136         endutxent();
137
138         return r;
139 }
140
141 static int write_entry_wtmp(const struct utmpx *store) {
142         assert(store);
143
144         /* wtmp is a simple append-only file where each entry is
145         simply appended to the end; i.e. basically a log. */
146
147         errno = 0;
148         updwtmpx(_PATH_WTMPX, store);
149         return -errno;
150 }
151
152 static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
153         int r, s;
154
155         r = write_entry_utmp(store_utmp);
156         s = write_entry_wtmp(store_wtmp);
157
158         if (r >= 0)
159                 r = s;
160
161         /* If utmp/wtmp have been disabled, that's a good thing, hence
162          * ignore the errors */
163         if (r == -ENOENT)
164                 r = 0;
165
166         return r;
167 }
168
169 static int write_entry_both(const struct utmpx *store) {
170         return write_utmp_wtmp(store, store);
171 }
172
173 int utmp_put_shutdown(void) {
174         struct utmpx store = {};
175
176         init_entry(&store, 0);
177
178         store.ut_type = RUN_LVL;
179         strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
180
181         return write_entry_both(&store);
182 }
183
184 int utmp_put_reboot(usec_t t) {
185         struct utmpx store = {};
186
187         init_entry(&store, t);
188
189         store.ut_type = BOOT_TIME;
190         strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
191
192         return write_entry_both(&store);
193 }
194
195 _pure_ static const char *sanitize_id(const char *id) {
196         size_t l;
197
198         assert(id);
199         l = strlen(id);
200
201         if (l <= sizeof(((struct utmpx*) NULL)->ut_id))
202                 return id;
203
204         return id + l - sizeof(((struct utmpx*) NULL)->ut_id);
205 }
206
207 int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line) {
208         struct utmpx store = {
209                 .ut_type = INIT_PROCESS,
210                 .ut_pid = pid,
211                 .ut_session = sid,
212         };
213
214         assert(id);
215
216         init_timestamp(&store, 0);
217
218         /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
219         strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
220
221         if (line)
222                 strncpy(store.ut_line, basename(line), sizeof(store.ut_line));
223
224         return write_entry_both(&store);
225 }
226
227 int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
228         struct utmpx lookup = {
229                 .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
230         }, store, store_wtmp, *found;
231
232         assert(id);
233
234         setutxent();
235
236         /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
237         strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
238
239         found = getutxid(&lookup);
240         if (!found)
241                 return 0;
242
243         if (found->ut_pid != pid)
244                 return 0;
245
246         memcpy(&store, found, sizeof(store));
247         store.ut_type = DEAD_PROCESS;
248         store.ut_exit.e_termination = code;
249         store.ut_exit.e_exit = status;
250
251         zero(store.ut_user);
252         zero(store.ut_host);
253         zero(store.ut_tv);
254
255         memcpy(&store_wtmp, &store, sizeof(store_wtmp));
256         /* wtmp wants the current time */
257         init_timestamp(&store_wtmp, 0);
258
259         return write_utmp_wtmp(&store, &store_wtmp);
260 }
261
262
263 int utmp_put_runlevel(int runlevel, int previous) {
264         struct utmpx store = {};
265         int r;
266
267         assert(runlevel > 0);
268
269         if (previous <= 0) {
270                 /* Find the old runlevel automatically */
271
272                 r = utmp_get_runlevel(&previous, NULL);
273                 if (r < 0) {
274                         if (r != -ESRCH)
275                                 return r;
276
277                         previous = 0;
278                 }
279         }
280
281         if (previous == runlevel)
282                 return 0;
283
284         init_entry(&store, 0);
285
286         store.ut_type = RUN_LVL;
287         store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
288         strncpy(store.ut_user, "runlevel", sizeof(store.ut_user));
289
290         return write_entry_both(&store);
291 }
292
293 #define TIMEOUT_MSEC 50
294
295 static int write_to_terminal(const char *tty, const char *message) {
296         _cleanup_close_ int fd = -1;
297         const char *p;
298         size_t left;
299         usec_t end;
300
301         assert(tty);
302         assert(message);
303
304         fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC);
305         if (fd < 0 || !isatty(fd))
306                 return -errno;
307
308         p = message;
309         left = strlen(message);
310
311         end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC;
312
313         while (left > 0) {
314                 ssize_t n;
315                 struct pollfd pollfd = {
316                         .fd = fd,
317                         .events = POLLOUT,
318                 };
319                 usec_t t;
320                 int k;
321
322                 t = now(CLOCK_MONOTONIC);
323
324                 if (t >= end)
325                         return -ETIME;
326
327                 k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC);
328                 if (k < 0)
329                         return -errno;
330
331                 if (k == 0)
332                         return -ETIME;
333
334                 n = write(fd, p, left);
335                 if (n < 0) {
336                         if (errno == EAGAIN)
337                                 continue;
338
339                         return -errno;
340                 }
341
342                 assert((size_t) n <= left);
343
344                 p += n;
345                 left -= n;
346         }
347
348         return 0;
349 }
350
351 int utmp_wall(const char *message, const char *username, bool (*match_tty)(const char *tty)) {
352         _cleanup_free_ char *text = NULL, *hn = NULL, *un = NULL, *tty = NULL;
353         char date[FORMAT_TIMESTAMP_MAX];
354         struct utmpx *u;
355         int r;
356
357         hn = gethostname_malloc();
358         if (!hn)
359                 return -ENOMEM;
360         if (!username) {
361                 un = getlogname_malloc();
362                 if (!un)
363                         return -ENOMEM;
364         }
365
366         getttyname_harder(STDIN_FILENO, &tty);
367
368         if (asprintf(&text,
369                      "\a\r\n"
370                      "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
371                      "%s\r\n\r\n",
372                      un ?: username, hn,
373                      tty ? " on " : "", strempty(tty),
374                      format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
375                      message) < 0)
376                 return -ENOMEM;
377
378         setutxent();
379
380         r = 0;
381
382         while ((u = getutxent())) {
383                 _cleanup_free_ char *buf = NULL;
384                 const char *path;
385                 int q;
386
387                 if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
388                         continue;
389
390                 /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */
391                 if (path_startswith(u->ut_line, "/dev/"))
392                         path = u->ut_line;
393                 else {
394                         if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0)
395                                 return -ENOMEM;
396
397                         path = buf;
398                 }
399
400                 if (!match_tty || match_tty(path)) {
401                         q = write_to_terminal(path, text);
402                         if (q < 0)
403                                 r = q;
404                 }
405         }
406
407         return r;
408 }