]> git.ipfire.org Git - thirdparty/systemd.git/blob - 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
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
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
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
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
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <poll.h>
25 #include <string.h>
26 #include <sys/utsname.h>
27 #include <unistd.h>
28 #include <utmpx.h>
29
30 #include "hostname-util.h"
31 #include "macro.h"
32 #include "path-util.h"
33 #include "string-util.h"
34 #include "terminal-util.h"
35 #include "utmp-wtmp.h"
36
37 int utmp_get_runlevel(int *runlevel, int *previous) {
38 struct utmpx *found, lookup = { .ut_type = RUN_LVL };
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
51 e = getenv("RUNLEVEL");
52 if (e && e[0] > 0) {
53 *runlevel = e[0];
54
55 if (previous) {
56 /* $PREVLEVEL seems to be an Upstart thing */
57
58 e = getenv("PREVLEVEL");
59 if (e && e[0] > 0)
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
73 found = getutxid(&lookup);
74 if (!found)
75 r = -errno;
76 else {
77 int a, b;
78
79 a = found->ut_pid & 0xFF;
80 b = (found->ut_pid >> 8) & 0xFF;
81
82 *runlevel = a;
83 if (previous)
84 *previous = b;
85
86 r = 0;
87 }
88
89 endutxent();
90
91 return r;
92 }
93
94 static void init_timestamp(struct utmpx *store, usec_t t) {
95 assert(store);
96
97 if (t <= 0)
98 t = now(CLOCK_REALTIME);
99
100 store->ut_tv.tv_sec = t / USEC_PER_SEC;
101 store->ut_tv.tv_usec = t % USEC_PER_SEC;
102 }
103
104 static void init_entry(struct utmpx *store, usec_t t) {
105 struct utsname uts = {};
106
107 assert(store);
108
109 init_timestamp(store, t);
110
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
118 static 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
142 static int write_entry_wtmp(const struct utmpx *store) {
143 assert(store);
144
145 /* wtmp is a simple append-only file where each entry is
146 simply appended to the end; i.e. basically a log. */
147
148 errno = 0;
149 updwtmpx(_PATH_WTMPX, store);
150 return -errno;
151 }
152
153 static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
154 int r, s;
155
156 r = write_entry_utmp(store_utmp);
157 s = write_entry_wtmp(store_wtmp);
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
170 static int write_entry_both(const struct utmpx *store) {
171 return write_utmp_wtmp(store, store);
172 }
173
174 int utmp_put_shutdown(void) {
175 struct utmpx store = {};
176
177 init_entry(&store, 0);
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
185 int utmp_put_reboot(usec_t t) {
186 struct utmpx store = {};
187
188 init_entry(&store, t);
189
190 store.ut_type = BOOT_TIME;
191 strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
192
193 return write_entry_both(&store);
194 }
195
196 _pure_ static const char *sanitize_id(const char *id) {
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
208 int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) {
209 struct utmpx store = {
210 .ut_type = INIT_PROCESS,
211 .ut_pid = pid,
212 .ut_session = sid,
213 };
214 int r;
215
216 assert(id);
217
218 init_timestamp(&store, 0);
219
220 /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
221 strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
222
223 if (line)
224 strncpy(store.ut_line, basename(line), sizeof(store.ut_line));
225
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;
246 }
247
248 int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
249 struct utmpx lookup = {
250 .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
251 }, store, store_wtmp, *found;
252
253 assert(id);
254
255 setutxent();
256
257 /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
258 strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
259
260 found = getutxid(&lookup);
261 if (!found)
262 return 0;
263
264 if (found->ut_pid != pid)
265 return 0;
266
267 memcpy(&store, found, sizeof(store));
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
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);
281 }
282
283
284 int utmp_put_runlevel(int runlevel, int previous) {
285 struct utmpx store = {};
286 int r;
287
288 assert(runlevel > 0);
289
290 if (previous <= 0) {
291 /* Find the old runlevel automatically */
292
293 r = utmp_get_runlevel(&previous, NULL);
294 if (r < 0) {
295 if (r != -ESRCH)
296 return r;
297
298 previous = 0;
299 }
300 }
301
302 if (previous == runlevel)
303 return 0;
304
305 init_entry(&store, 0);
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 }
313
314 #define TIMEOUT_MSEC 50
315
316 static int write_to_terminal(const char *tty, const char *message) {
317 _cleanup_close_ int fd = -1;
318 const char *p;
319 size_t left;
320 usec_t end;
321
322 assert(tty);
323 assert(message);
324
325 fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC);
326 if (fd < 0 || !isatty(fd))
327 return -errno;
328
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;
336 struct pollfd pollfd = {
337 .fd = fd,
338 .events = POLLOUT,
339 };
340 usec_t t;
341 int k;
342
343 t = now(CLOCK_MONOTONIC);
344
345 if (t >= end)
346 return -ETIME;
347
348 k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC);
349 if (k < 0)
350 return -errno;
351
352 if (k == 0)
353 return -ETIME;
354
355 n = write(fd, p, left);
356 if (n < 0) {
357 if (errno == EAGAIN)
358 continue;
359
360 return -errno;
361 }
362
363 assert((size_t) n <= left);
364
365 p += n;
366 left -= n;
367 }
368
369 return 0;
370 }
371
372 int 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;
380 char date[FORMAT_TIMESTAMP_MAX];
381 struct utmpx *u;
382 int r;
383
384 hn = gethostname_malloc();
385 if (!hn)
386 return -ENOMEM;
387 if (!username) {
388 un = getlogname_malloc();
389 if (!un)
390 return -ENOMEM;
391 }
392
393 if (!origin_tty) {
394 getttyname_harder(STDIN_FILENO, &stdin_tty);
395 origin_tty = stdin_tty;
396 }
397
398 if (asprintf(&text,
399 "\a\r\n"
400 "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
401 "%s\r\n\r\n",
402 un ?: username, hn,
403 origin_tty ? " on " : "", strempty(origin_tty),
404 format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
405 message) < 0)
406 return -ENOMEM;
407
408 setutxent();
409
410 r = 0;
411
412 while ((u = getutxent())) {
413 _cleanup_free_ char *buf = NULL;
414 const char *path;
415 int q;
416
417 if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
418 continue;
419
420 /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */
421 if (path_startswith(u->ut_line, "/dev/"))
422 path = u->ut_line;
423 else {
424 if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0)
425 return -ENOMEM;
426
427 path = buf;
428 }
429
430 if (!match_tty || match_tty(path, userdata)) {
431 q = write_to_terminal(path, text);
432 if (q < 0)
433 r = q;
434 }
435 }
436
437 return r;
438 }