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