chiark / gitweb /
TODO: update
[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 *found, lookup = { .ut_type = RUN_LVL };
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         if (!(found = getutxid(&lookup)))
70                 r = -errno;
71         else {
72                 int a, b;
73
74                 a = found->ut_pid & 0xFF;
75                 b = (found->ut_pid >> 8) & 0xFF;
76
77                 *runlevel = a;
78                 if (previous)
79                         *previous = b;
80
81                 r = 0;
82         }
83
84         endutxent();
85
86         return r;
87 }
88
89 static void init_timestamp(struct utmpx *store, usec_t t) {
90         assert(store);
91
92         zero(*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
208         assert(id);
209
210         init_timestamp(&store, 0);
211
212         store.ut_type = INIT_PROCESS;
213         store.ut_pid = pid;
214         store.ut_session = sid;
215
216         strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
217
218         if (line)
219                 strncpy(store.ut_line, path_get_file_name(line), sizeof(store.ut_line));
220
221         return write_entry_both(&store);
222 }
223
224 int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
225         struct utmpx lookup, store, store_wtmp, *found;
226
227         assert(id);
228
229         setutxent();
230
231         zero(lookup);
232         lookup.ut_type = INIT_PROCESS; /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
233         strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
234
235         if (!(found = getutxid(&lookup)))
236                 return 0;
237
238         if (found->ut_pid != pid)
239                 return 0;
240
241         memcpy(&store, found, sizeof(store));
242         store.ut_type = DEAD_PROCESS;
243         store.ut_exit.e_termination = code;
244         store.ut_exit.e_exit = status;
245
246         zero(store.ut_user);
247         zero(store.ut_host);
248         zero(store.ut_tv);
249
250         memcpy(&store_wtmp, &store, sizeof(store_wtmp));
251         /* wtmp wants the current time */
252         init_timestamp(&store_wtmp, 0);
253
254         return write_utmp_wtmp(&store, &store_wtmp);
255 }
256
257
258 int utmp_put_runlevel(int runlevel, int previous) {
259         struct utmpx store;
260         int r;
261
262         assert(runlevel > 0);
263
264         if (previous <= 0) {
265                 /* Find the old runlevel automatically */
266
267                 if ((r = utmp_get_runlevel(&previous, NULL)) < 0) {
268                         if (r != -ESRCH)
269                                 return r;
270
271                         previous = 0;
272                 }
273         }
274
275         if (previous == runlevel)
276                 return 0;
277
278         init_entry(&store, 0);
279
280         store.ut_type = RUN_LVL;
281         store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
282         strncpy(store.ut_user, "runlevel", sizeof(store.ut_user));
283
284         return write_entry_both(&store);
285 }
286
287 #define TIMEOUT_MSEC 50
288
289 static int write_to_terminal(const char *tty, const char *message) {
290         _cleanup_close_ int fd = -1;
291         const char *p;
292         size_t left;
293         usec_t end;
294
295         assert(tty);
296         assert(message);
297
298         fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC);
299         if (fd < 0 || !isatty(fd))
300                 return -errno;
301
302         p = message;
303         left = strlen(message);
304
305         end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC;
306
307         while (left > 0) {
308                 ssize_t n;
309                 struct pollfd pollfd = {
310                         .fd = fd,
311                         .events = POLLOUT,
312                 };
313                 usec_t t;
314                 int k;
315
316                 t = now(CLOCK_MONOTONIC);
317
318                 if (t >= end)
319                         return -ETIME;
320
321                 k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC);
322                 if (k < 0)
323                         return -errno;
324
325                 if (k == 0)
326                         return -ETIME;
327
328                 n = write(fd, p, left);
329                 if (n < 0) {
330                         if (errno == EAGAIN)
331                                 continue;
332
333                         return -errno;
334                 }
335
336                 assert((size_t) n <= left);
337
338                 p += n;
339                 left -= n;
340         }
341
342         return 0;
343 }
344
345 int utmp_wall(const char *message, bool (*match_tty)(const char *tty)) {
346         struct utmpx *u;
347         char date[FORMAT_TIMESTAMP_MAX];
348         char *text = NULL, *hn = NULL, *un = NULL, *tty = NULL;
349         int r;
350
351         if (!(hn = gethostname_malloc()) ||
352             !(un = getlogname_malloc())) {
353                 r = -ENOMEM;
354                 goto finish;
355         }
356
357         getttyname_harder(STDIN_FILENO, &tty);
358
359         if (asprintf(&text,
360                      "\a\r\n"
361                      "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
362                      "%s\r\n\r\n",
363                      un, hn,
364                      tty ? " on " : "", strempty(tty),
365                      format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
366                      message) < 0) {
367                 r = -ENOMEM;
368                 goto finish;
369         }
370
371         setutxent();
372
373         r = 0;
374
375         while ((u = getutxent())) {
376                 int q;
377                 const char *path;
378                 char *buf = NULL;
379
380                 if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
381                         continue;
382
383                 /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */
384                 if (path_startswith(u->ut_line, "/dev/"))
385                         path = u->ut_line;
386                 else {
387                         if (asprintf(&buf, "/dev/%.*s",
388                                      (int) sizeof(u->ut_line), u->ut_line) < 0) {
389                                 r = -ENOMEM;
390                                 goto finish;
391                         }
392
393                         path = buf;
394                 }
395
396                 if (!match_tty || match_tty(path))
397                         if ((q = write_to_terminal(path, text)) < 0)
398                                 r = q;
399
400                 free(buf);
401         }
402
403 finish:
404         free(hn);
405         free(un);
406         free(tty);
407         free(text);
408
409         return r;
410 }