chiark / gitweb /
coccinelle: O_NDELAY → O_NONBLOCK
[elogind.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_NONBLOCK|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 }