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