]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/utmp-wtmp.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / shared / utmp-wtmp.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
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
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
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
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <poll.h>
24 #include <stddef.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/time.h>
29 #include <sys/utsname.h>
30 #include <unistd.h>
31 #include <utmpx.h>
32
33 #include "alloc-util.h"
34 #include "fd-util.h"
35 #include "hostname-util.h"
36 #include "macro.h"
37 #include "path-util.h"
38 #include "string-util.h"
39 #include "terminal-util.h"
40 #include "time-util.h"
41 #include "user-util.h"
42 #include "util.h"
43 #include "utmp-wtmp.h"
44
45 int utmp_get_runlevel(int *runlevel, int *previous) {
46 struct utmpx *found, lookup = { .ut_type = RUN_LVL };
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
59 e = getenv("RUNLEVEL");
60 if (e && e[0] > 0) {
61 *runlevel = e[0];
62
63 if (previous) {
64 /* $PREVLEVEL seems to be an Upstart thing */
65
66 e = getenv("PREVLEVEL");
67 if (e && e[0] > 0)
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
81 found = getutxid(&lookup);
82 if (!found)
83 r = -errno;
84 else {
85 int a, b;
86
87 a = found->ut_pid & 0xFF;
88 b = (found->ut_pid >> 8) & 0xFF;
89
90 *runlevel = a;
91 if (previous)
92 *previous = b;
93
94 r = 0;
95 }
96
97 endutxent();
98
99 return r;
100 }
101
102 static void init_timestamp(struct utmpx *store, usec_t t) {
103 assert(store);
104
105 if (t <= 0)
106 t = now(CLOCK_REALTIME);
107
108 store->ut_tv.tv_sec = t / USEC_PER_SEC;
109 store->ut_tv.tv_usec = t % USEC_PER_SEC;
110 }
111
112 static void init_entry(struct utmpx *store, usec_t t) {
113 struct utsname uts = {};
114
115 assert(store);
116
117 init_timestamp(store, t);
118
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
126 static 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
150 static int write_entry_wtmp(const struct utmpx *store) {
151 assert(store);
152
153 /* wtmp is a simple append-only file where each entry is
154 simply appended to the end; i.e. basically a log. */
155
156 errno = 0;
157 updwtmpx(_PATH_WTMPX, store);
158 return -errno;
159 }
160
161 static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
162 int r, s;
163
164 r = write_entry_utmp(store_utmp);
165 s = write_entry_wtmp(store_wtmp);
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
178 static int write_entry_both(const struct utmpx *store) {
179 return write_utmp_wtmp(store, store);
180 }
181
182 int utmp_put_shutdown(void) {
183 struct utmpx store = {};
184
185 init_entry(&store, 0);
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
193 int utmp_put_reboot(usec_t t) {
194 struct utmpx store = {};
195
196 init_entry(&store, t);
197
198 store.ut_type = BOOT_TIME;
199 strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
200
201 return write_entry_both(&store);
202 }
203
204 _pure_ static const char *sanitize_id(const char *id) {
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
216 int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) {
217 struct utmpx store = {
218 .ut_type = INIT_PROCESS,
219 .ut_pid = pid,
220 .ut_session = sid,
221 };
222 int r;
223
224 assert(id);
225
226 init_timestamp(&store, 0);
227
228 /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
229 strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
230
231 if (line)
232 strncpy(store.ut_line, basename(line), sizeof(store.ut_line));
233
234 r = write_entry_both(&store);
235 if (r < 0)
236 return r;
237
238 if (IN_SET(ut_type, LOGIN_PROCESS, USER_PROCESS)) {
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;
254 }
255
256 int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
257 struct utmpx lookup = {
258 .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
259 }, store, store_wtmp, *found;
260
261 assert(id);
262
263 setutxent();
264
265 /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
266 strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
267
268 found = getutxid(&lookup);
269 if (!found)
270 return 0;
271
272 if (found->ut_pid != pid)
273 return 0;
274
275 memcpy(&store, found, sizeof(store));
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
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);
289 }
290
291
292 int utmp_put_runlevel(int runlevel, int previous) {
293 struct utmpx store = {};
294 int r;
295
296 assert(runlevel > 0);
297
298 if (previous <= 0) {
299 /* Find the old runlevel automatically */
300
301 r = utmp_get_runlevel(&previous, NULL);
302 if (r < 0) {
303 if (r != -ESRCH)
304 return r;
305
306 previous = 0;
307 }
308 }
309
310 if (previous == runlevel)
311 return 0;
312
313 init_entry(&store, 0);
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 }
321
322 #define TIMEOUT_MSEC 50
323
324 static int write_to_terminal(const char *tty, const char *message) {
325 _cleanup_close_ int fd = -1;
326 const char *p;
327 size_t left;
328 usec_t end;
329
330 assert(tty);
331 assert(message);
332
333 fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC);
334 if (fd < 0 || !isatty(fd))
335 return -errno;
336
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;
344 struct pollfd pollfd = {
345 .fd = fd,
346 .events = POLLOUT,
347 };
348 usec_t t;
349 int k;
350
351 t = now(CLOCK_MONOTONIC);
352
353 if (t >= end)
354 return -ETIME;
355
356 k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC);
357 if (k < 0)
358 return -errno;
359
360 if (k == 0)
361 return -ETIME;
362
363 n = write(fd, p, left);
364 if (n < 0) {
365 if (errno == EAGAIN)
366 continue;
367
368 return -errno;
369 }
370
371 assert((size_t) n <= left);
372
373 p += n;
374 left -= n;
375 }
376
377 return 0;
378 }
379
380 int 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;
388 char date[FORMAT_TIMESTAMP_MAX];
389 struct utmpx *u;
390 int r;
391
392 hn = gethostname_malloc();
393 if (!hn)
394 return -ENOMEM;
395 if (!username) {
396 un = getlogname_malloc();
397 if (!un)
398 return -ENOMEM;
399 }
400
401 if (!origin_tty) {
402 getttyname_harder(STDIN_FILENO, &stdin_tty);
403 origin_tty = stdin_tty;
404 }
405
406 if (asprintf(&text,
407 "\a\r\n"
408 "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
409 "%s\r\n\r\n",
410 un ?: username, hn,
411 origin_tty ? " on " : "", strempty(origin_tty),
412 format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
413 message) < 0)
414 return -ENOMEM;
415
416 setutxent();
417
418 r = 0;
419
420 while ((u = getutxent())) {
421 _cleanup_free_ char *buf = NULL;
422 const char *path;
423 int q;
424
425 if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
426 continue;
427
428 /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */
429 if (path_startswith(u->ut_line, "/dev/"))
430 path = u->ut_line;
431 else {
432 if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0)
433 return -ENOMEM;
434
435 path = buf;
436 }
437
438 if (!match_tty || match_tty(path, userdata)) {
439 q = write_to_terminal(path, text);
440 if (q < 0)
441 r = q;
442 }
443 }
444
445 return r;
446 }