]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/utmp-wtmp.c
util-lib: split out IO related calls to io-util.[ch]
[thirdparty/systemd.git] / src / shared / utmp-wtmp.c
CommitLineData
d6c9574f 1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
e537352b
LP
2
3/***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
e537352b
LP
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 16 Lesser General Public License for more details.
e537352b 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
e537352b
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
e537352b 22#include <errno.h>
07630cea
LP
23#include <fcntl.h>
24#include <poll.h>
e537352b
LP
25#include <string.h>
26#include <sys/utsname.h>
ef2f1067 27#include <unistd.h>
07630cea 28#include <utmpx.h>
e537352b 29
3ffd4af2 30#include "fd-util.h"
07630cea 31#include "hostname-util.h"
e537352b 32#include "macro.h"
9eb977db 33#include "path-util.h"
07630cea 34#include "string-util.h"
288a74cc 35#include "terminal-util.h"
958b66ea 36#include "utmp-wtmp.h"
e537352b
LP
37
38int utmp_get_runlevel(int *runlevel, int *previous) {
b92bea5d 39 struct utmpx *found, lookup = { .ut_type = RUN_LVL };
e537352b
LP
40 int r;
41 const char *e;
42
43 assert(runlevel);
44
45 /* If these values are set in the environment this takes
46 * precedence. Presumably, sysvinit does this to work around a
47 * race condition that would otherwise exist where we'd always
48 * go to disk and hence might read runlevel data that might be
49 * very new and does not apply to the current script being
50 * executed. */
51
e7fb33ff
LP
52 e = getenv("RUNLEVEL");
53 if (e && e[0] > 0) {
e537352b
LP
54 *runlevel = e[0];
55
56 if (previous) {
57 /* $PREVLEVEL seems to be an Upstart thing */
58
e7fb33ff
LP
59 e = getenv("PREVLEVEL");
60 if (e && e[0] > 0)
e537352b
LP
61 *previous = e[0];
62 else
63 *previous = 0;
64 }
65
66 return 0;
67 }
68
69 if (utmpxname(_PATH_UTMPX) < 0)
70 return -errno;
71
72 setutxent();
73
e7fb33ff
LP
74 found = getutxid(&lookup);
75 if (!found)
e537352b
LP
76 r = -errno;
77 else {
78 int a, b;
79
80 a = found->ut_pid & 0xFF;
81 b = (found->ut_pid >> 8) & 0xFF;
82
4dd1de72
ZJS
83 *runlevel = a;
84 if (previous)
85 *previous = b;
86
87 r = 0;
e537352b
LP
88 }
89
90 endutxent();
91
92 return r;
93}
94
169c1bda 95static void init_timestamp(struct utmpx *store, usec_t t) {
e537352b
LP
96 assert(store);
97
871d7de4
LP
98 if (t <= 0)
99 t = now(CLOCK_REALTIME);
e537352b 100
871d7de4
LP
101 store->ut_tv.tv_sec = t / USEC_PER_SEC;
102 store->ut_tv.tv_usec = t % USEC_PER_SEC;
169c1bda
LP
103}
104
105static void init_entry(struct utmpx *store, usec_t t) {
b92bea5d 106 struct utsname uts = {};
169c1bda
LP
107
108 assert(store);
109
110 init_timestamp(store, t);
111
e537352b
LP
112 if (uname(&uts) >= 0)
113 strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
114
115 strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */
116 strncpy(store->ut_id, "~~", sizeof(store->ut_id));
117}
118
119static int write_entry_utmp(const struct utmpx *store) {
120 int r;
121
122 assert(store);
123
124 /* utmp is similar to wtmp, but there is only one entry for
125 * each entry type resp. user; i.e. basically a key/value
126 * table. */
127
128 if (utmpxname(_PATH_UTMPX) < 0)
129 return -errno;
130
131 setutxent();
132
133 if (!pututxline(store))
134 r = -errno;
135 else
136 r = 0;
137
138 endutxent();
139
140 return r;
141}
142
143static int write_entry_wtmp(const struct utmpx *store) {
144 assert(store);
145
146 /* wtmp is a simple append-only file where each entry is
863f3ce0 147 simply appended to the end; i.e. basically a log. */
e537352b
LP
148
149 errno = 0;
150 updwtmpx(_PATH_WTMPX, store);
151 return -errno;
152}
153
4743137a 154static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
e537352b
LP
155 int r, s;
156
4743137a
MS
157 r = write_entry_utmp(store_utmp);
158 s = write_entry_wtmp(store_wtmp);
e537352b
LP
159
160 if (r >= 0)
161 r = s;
162
163 /* If utmp/wtmp have been disabled, that's a good thing, hence
164 * ignore the errors */
165 if (r == -ENOENT)
166 r = 0;
167
168 return r;
169}
170
4743137a
MS
171static int write_entry_both(const struct utmpx *store) {
172 return write_utmp_wtmp(store, store);
173}
174
0ad26e09 175int utmp_put_shutdown(void) {
863f3ce0 176 struct utmpx store = {};
e537352b 177
0ad26e09 178 init_entry(&store, 0);
e537352b
LP
179
180 store.ut_type = RUN_LVL;
181 strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
182
183 return write_entry_both(&store);
184}
185
871d7de4 186int utmp_put_reboot(usec_t t) {
863f3ce0 187 struct utmpx store = {};
e537352b 188
871d7de4 189 init_entry(&store, t);
e537352b
LP
190
191 store.ut_type = BOOT_TIME;
55e39f40 192 strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
e537352b
LP
193
194 return write_entry_both(&store);
195}
196
44a6b1b6 197_pure_ static const char *sanitize_id(const char *id) {
169c1bda
LP
198 size_t l;
199
200 assert(id);
201 l = strlen(id);
202
203 if (l <= sizeof(((struct utmpx*) NULL)->ut_id))
204 return id;
205
206 return id + l - sizeof(((struct utmpx*) NULL)->ut_id);
207}
208
023a4f67 209int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) {
863f3ce0
TG
210 struct utmpx store = {
211 .ut_type = INIT_PROCESS,
212 .ut_pid = pid,
213 .ut_session = sid,
214 };
023a4f67 215 int r;
169c1bda
LP
216
217 assert(id);
218
0ad26e09 219 init_timestamp(&store, 0);
169c1bda 220
863f3ce0 221 /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
169c1bda
LP
222 strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
223
224 if (line)
2b6bf07d 225 strncpy(store.ut_line, basename(line), sizeof(store.ut_line));
169c1bda 226
023a4f67
LP
227 r = write_entry_both(&store);
228 if (r < 0)
229 return r;
230
231 if (ut_type == LOGIN_PROCESS || ut_type == USER_PROCESS) {
232 store.ut_type = LOGIN_PROCESS;
233 r = write_entry_both(&store);
234 if (r < 0)
235 return r;
236 }
237
238 if (ut_type == USER_PROCESS) {
239 store.ut_type = USER_PROCESS;
240 strncpy(store.ut_user, user, sizeof(store.ut_user)-1);
241 r = write_entry_both(&store);
242 if (r < 0)
243 return r;
244 }
245
246 return 0;
169c1bda
LP
247}
248
249int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
863f3ce0
TG
250 struct utmpx lookup = {
251 .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
252 }, store, store_wtmp, *found;
169c1bda
LP
253
254 assert(id);
255
256 setutxent();
257
863f3ce0 258 /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
169c1bda
LP
259 strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
260
e7fb33ff
LP
261 found = getutxid(&lookup);
262 if (!found)
169c1bda
LP
263 return 0;
264
265 if (found->ut_pid != pid)
266 return 0;
267
fa4ad7ce 268 memcpy(&store, found, sizeof(store));
169c1bda
LP
269 store.ut_type = DEAD_PROCESS;
270 store.ut_exit.e_termination = code;
271 store.ut_exit.e_exit = status;
272
273 zero(store.ut_user);
274 zero(store.ut_host);
275 zero(store.ut_tv);
276
4743137a
MS
277 memcpy(&store_wtmp, &store, sizeof(store_wtmp));
278 /* wtmp wants the current time */
279 init_timestamp(&store_wtmp, 0);
280
281 return write_utmp_wtmp(&store, &store_wtmp);
169c1bda
LP
282}
283
284
0ad26e09 285int utmp_put_runlevel(int runlevel, int previous) {
863f3ce0 286 struct utmpx store = {};
e537352b
LP
287 int r;
288
289 assert(runlevel > 0);
290
291 if (previous <= 0) {
292 /* Find the old runlevel automatically */
293
e7fb33ff
LP
294 r = utmp_get_runlevel(&previous, NULL);
295 if (r < 0) {
d7fc909d
LP
296 if (r != -ESRCH)
297 return r;
298
299 previous = 0;
300 }
e537352b
LP
301 }
302
4927fcae
LP
303 if (previous == runlevel)
304 return 0;
305
0ad26e09 306 init_entry(&store, 0);
e537352b
LP
307
308 store.ut_type = RUN_LVL;
309 store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
310 strncpy(store.ut_user, "runlevel", sizeof(store.ut_user));
311
312 return write_entry_both(&store);
313}
ef2f1067
LP
314
315#define TIMEOUT_MSEC 50
316
317static int write_to_terminal(const char *tty, const char *message) {
7fd1b19b 318 _cleanup_close_ int fd = -1;
ef2f1067
LP
319 const char *p;
320 size_t left;
321 usec_t end;
322
323 assert(tty);
324 assert(message);
325
e62d8c39
ZJS
326 fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC);
327 if (fd < 0 || !isatty(fd))
ef2f1067
LP
328 return -errno;
329
ef2f1067
LP
330 p = message;
331 left = strlen(message);
332
333 end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC;
334
335 while (left > 0) {
336 ssize_t n;
b92bea5d
ZJS
337 struct pollfd pollfd = {
338 .fd = fd,
339 .events = POLLOUT,
340 };
ef2f1067
LP
341 usec_t t;
342 int k;
343
344 t = now(CLOCK_MONOTONIC);
345
e62d8c39
ZJS
346 if (t >= end)
347 return -ETIME;
ef2f1067 348
e62d8c39
ZJS
349 k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC);
350 if (k < 0)
ef2f1067
LP
351 return -errno;
352
e62d8c39
ZJS
353 if (k == 0)
354 return -ETIME;
ef2f1067 355
e62d8c39
ZJS
356 n = write(fd, p, left);
357 if (n < 0) {
ef2f1067
LP
358 if (errno == EAGAIN)
359 continue;
360
e62d8c39 361 return -errno;
ef2f1067
LP
362 }
363
364 assert((size_t) n <= left);
365
366 p += n;
367 left -= n;
368 }
369
e62d8c39 370 return 0;
ef2f1067
LP
371}
372
99f710dd
DM
373int utmp_wall(
374 const char *message,
375 const char *username,
376 const char *origin_tty,
377 bool (*match_tty)(const char *tty, void *userdata),
378 void *userdata) {
379
380 _cleanup_free_ char *text = NULL, *hn = NULL, *un = NULL, *stdin_tty = NULL;
11620592 381 char date[FORMAT_TIMESTAMP_MAX];
e7fb33ff 382 struct utmpx *u;
ef2f1067 383 int r;
ef2f1067 384
e7fb33ff 385 hn = gethostname_malloc();
9003d9b0 386 if (!hn)
e7fb33ff 387 return -ENOMEM;
9003d9b0
ST
388 if (!username) {
389 un = getlogname_malloc();
390 if (!un)
391 return -ENOMEM;
392 }
ef2f1067 393
99f710dd
DM
394 if (!origin_tty) {
395 getttyname_harder(STDIN_FILENO, &stdin_tty);
396 origin_tty = stdin_tty;
397 }
ef2f1067
LP
398
399 if (asprintf(&text,
400 "\a\r\n"
11620592 401 "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
ef2f1067 402 "%s\r\n\r\n",
9003d9b0 403 un ?: username, hn,
99f710dd 404 origin_tty ? " on " : "", strempty(origin_tty),
11620592 405 format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
e7fb33ff
LP
406 message) < 0)
407 return -ENOMEM;
ef2f1067
LP
408
409 setutxent();
410
411 r = 0;
412
413 while ((u = getutxent())) {
e7fb33ff 414 _cleanup_free_ char *buf = NULL;
ef2f1067 415 const char *path;
e7fb33ff 416 int q;
ef2f1067
LP
417
418 if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
419 continue;
420
26888876 421 /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */
ef2f1067
LP
422 if (path_startswith(u->ut_line, "/dev/"))
423 path = u->ut_line;
424 else {
e7fb33ff
LP
425 if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0)
426 return -ENOMEM;
ef2f1067
LP
427
428 path = buf;
429 }
430
99f710dd 431 if (!match_tty || match_tty(path, userdata)) {
e7fb33ff
LP
432 q = write_to_terminal(path, text);
433 if (q < 0)
7af53310 434 r = q;
e7fb33ff 435 }
ef2f1067
LP
436 }
437
ef2f1067
LP
438 return r;
439}