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