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