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