chiark / gitweb /
c9b986fc08480f77696685050a2b5277fcf1539f
[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                 *runlevel = a;
81                 if (previous)
82                         *previous = b;
83
84                 r = 0;
85         }
86
87         endutxent();
88
89         return r;
90 }
91
92 static void init_timestamp(struct utmpx *store, usec_t t) {
93         assert(store);
94
95         zero(*store);
96
97         if (t <= 0)
98                 t = now(CLOCK_REALTIME);
99
100         store->ut_tv.tv_sec = t / USEC_PER_SEC;
101         store->ut_tv.tv_usec = t % USEC_PER_SEC;
102 }
103
104 static void init_entry(struct utmpx *store, usec_t t) {
105         struct utsname uts;
106
107         assert(store);
108
109         init_timestamp(store, t);
110
111         zero(uts);
112
113         if (uname(&uts) >= 0)
114                 strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
115
116         strncpy(store->ut_line, "~", sizeof(store->ut_line));  /* or ~~ ? */
117         strncpy(store->ut_id, "~~", sizeof(store->ut_id));
118 }
119
120 static int write_entry_utmp(const struct utmpx *store) {
121         int r;
122
123         assert(store);
124
125         /* utmp is similar to wtmp, but there is only one entry for
126          * each entry type resp. user; i.e. basically a key/value
127          * table. */
128
129         if (utmpxname(_PATH_UTMPX) < 0)
130                 return -errno;
131
132         setutxent();
133
134         if (!pututxline(store))
135                 r = -errno;
136         else
137                 r = 0;
138
139         endutxent();
140
141         return r;
142 }
143
144 static int write_entry_wtmp(const struct utmpx *store) {
145         assert(store);
146
147         /* wtmp is a simple append-only file where each entry is
148         simply appended to * the end; i.e. basically a log. */
149
150         errno = 0;
151         updwtmpx(_PATH_WTMPX, store);
152         return -errno;
153 }
154
155 static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
156         int r, s;
157
158         r = write_entry_utmp(store_utmp);
159         s = write_entry_wtmp(store_wtmp);
160
161         if (r >= 0)
162                 r = s;
163
164         /* If utmp/wtmp have been disabled, that's a good thing, hence
165          * ignore the errors */
166         if (r == -ENOENT)
167                 r = 0;
168
169         return r;
170 }
171
172 static int write_entry_both(const struct utmpx *store) {
173         return write_utmp_wtmp(store, store);
174 }
175
176 int utmp_put_shutdown(void) {
177         struct utmpx store;
178
179         init_entry(&store, 0);
180
181         store.ut_type = RUN_LVL;
182         strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
183
184         return write_entry_both(&store);
185 }
186
187 int utmp_put_reboot(usec_t t) {
188         struct utmpx store;
189
190         init_entry(&store, t);
191
192         store.ut_type = BOOT_TIME;
193         strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
194
195         return write_entry_both(&store);
196 }
197
198 static const char *sanitize_id(const char *id) {
199         size_t l;
200
201         assert(id);
202         l = strlen(id);
203
204         if (l <= sizeof(((struct utmpx*) NULL)->ut_id))
205                 return id;
206
207         return id + l - sizeof(((struct utmpx*) NULL)->ut_id);
208 }
209
210 int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line) {
211         struct utmpx store;
212
213         assert(id);
214
215         init_timestamp(&store, 0);
216
217         store.ut_type = INIT_PROCESS;
218         store.ut_pid = pid;
219         store.ut_session = sid;
220
221         strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
222
223         if (line)
224                 strncpy(store.ut_line, path_get_file_name(line), sizeof(store.ut_line));
225
226         return write_entry_both(&store);
227 }
228
229 int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
230         struct utmpx lookup, store, store_wtmp, *found;
231
232         assert(id);
233
234         setutxent();
235
236         zero(lookup);
237         lookup.ut_type = INIT_PROCESS; /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
238         strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
239
240         if (!(found = getutxid(&lookup)))
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                 if ((r = utmp_get_runlevel(&previous, NULL)) < 0) {
273                         if (r != -ESRCH)
274                                 return r;
275
276                         previous = 0;
277                 }
278         }
279
280         if (previous == runlevel)
281                 return 0;
282
283         init_entry(&store, 0);
284
285         store.ut_type = RUN_LVL;
286         store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
287         strncpy(store.ut_user, "runlevel", sizeof(store.ut_user));
288
289         return write_entry_both(&store);
290 }
291
292 #define TIMEOUT_MSEC 50
293
294 static int write_to_terminal(const char *tty, const char *message) {
295         int fd, r;
296         const char *p;
297         size_t left;
298         usec_t end;
299
300         assert(tty);
301         assert(message);
302
303         if ((fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC)) < 0)
304                 return -errno;
305
306         if (!isatty(fd)) {
307                 r = -errno;
308                 goto finish;
309         }
310
311         p = message;
312         left = strlen(message);
313
314         end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC;
315
316         while (left > 0) {
317                 ssize_t n;
318                 struct pollfd pollfd;
319                 usec_t t;
320                 int k;
321
322                 t = now(CLOCK_MONOTONIC);
323
324                 if (t >= end) {
325                         r = -ETIME;
326                         goto finish;
327                 }
328
329                 zero(pollfd);
330                 pollfd.fd = fd;
331                 pollfd.events = POLLOUT;
332
333                 if ((k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC)) < 0)
334                         return -errno;
335
336                 if (k <= 0) {
337                         r = -ETIME;
338                         goto finish;
339                 }
340
341                 if ((n = write(fd, p, left)) < 0) {
342
343                         if (errno == EAGAIN)
344                                 continue;
345
346                         r = -errno;
347                         goto finish;
348                 }
349
350                 assert((size_t) n <= left);
351
352                 p += n;
353                 left -= n;
354         }
355
356         r = 0;
357
358 finish:
359         close_nointr_nofail(fd);
360
361         return r;
362 }
363
364 int utmp_wall(const char *message, bool (*match_tty)(const char *tty)) {
365         struct utmpx *u;
366         char date[FORMAT_TIMESTAMP_MAX];
367         char *text = NULL, *hn = NULL, *un = NULL, *tty = NULL;
368         int r;
369
370         if (!(hn = gethostname_malloc()) ||
371             !(un = getlogname_malloc())) {
372                 r = -ENOMEM;
373                 goto finish;
374         }
375
376         getttyname_harder(STDIN_FILENO, &tty);
377
378         if (asprintf(&text,
379                      "\a\r\n"
380                      "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
381                      "%s\r\n\r\n",
382                      un, hn,
383                      tty ? " on " : "", strempty(tty),
384                      format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
385                      message) < 0) {
386                 r = -ENOMEM;
387                 goto finish;
388         }
389
390         setutxent();
391
392         r = 0;
393
394         while ((u = getutxent())) {
395                 int q;
396                 const char *path;
397                 char *buf = NULL;
398
399                 if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
400                         continue;
401
402                 /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */
403                 if (path_startswith(u->ut_line, "/dev/"))
404                         path = u->ut_line;
405                 else {
406                         if (asprintf(&buf, "/dev/%.*s",
407                                      (int) sizeof(u->ut_line), u->ut_line) < 0) {
408                                 r = -ENOMEM;
409                                 goto finish;
410                         }
411
412                         path = buf;
413                 }
414
415                 if (!match_tty || match_tty(path))
416                         if ((q = write_to_terminal(path, text)) < 0)
417                                 r = q;
418
419                 free(buf);
420         }
421
422 finish:
423         free(hn);
424         free(un);
425         free(tty);
426         free(text);
427
428         return r;
429 }