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