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