Branch data 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 : : }
|