Coverage Report

Created: 2022-04-19 08:24

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