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