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