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