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