1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010 Lennart Poettering
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.
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.
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/>.
25 #include <sys/utsname.h>
31 #include "path-util.h"
32 #include "terminal-util.h"
33 #include "hostname-util.h"
34 #include "utmp-wtmp.h"
36 int utmp_get_runlevel(int *runlevel, int *previous) {
37 struct utmpx *found, lookup = { .ut_type = RUN_LVL };
43 /* If these values are set in the environment this takes
44 * precedence. Presumably, sysvinit does this to work around a
45 * race condition that would otherwise exist where we'd always
46 * go to disk and hence might read runlevel data that might be
47 * very new and does not apply to the current script being
50 e = getenv("RUNLEVEL");
55 /* $PREVLEVEL seems to be an Upstart thing */
57 e = getenv("PREVLEVEL");
67 if (utmpxname(_PATH_UTMPX) < 0)
72 found = getutxid(&lookup);
78 a = found->ut_pid & 0xFF;
79 b = (found->ut_pid >> 8) & 0xFF;
93 static void init_timestamp(struct utmpx *store, usec_t t) {
97 t = now(CLOCK_REALTIME);
99 store->ut_tv.tv_sec = t / USEC_PER_SEC;
100 store->ut_tv.tv_usec = t % USEC_PER_SEC;
103 static void init_entry(struct utmpx *store, usec_t t) {
104 struct utsname uts = {};
108 init_timestamp(store, t);
110 if (uname(&uts) >= 0)
111 strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
113 strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */
114 strncpy(store->ut_id, "~~", sizeof(store->ut_id));
117 static int write_entry_utmp(const struct utmpx *store) {
122 /* utmp is similar to wtmp, but there is only one entry for
123 * each entry type resp. user; i.e. basically a key/value
126 if (utmpxname(_PATH_UTMPX) < 0)
131 if (!pututxline(store))
141 static int write_entry_wtmp(const struct utmpx *store) {
144 /* wtmp is a simple append-only file where each entry is
145 simply appended to the end; i.e. basically a log. */
148 updwtmpx(_PATH_WTMPX, store);
152 static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
155 r = write_entry_utmp(store_utmp);
156 s = write_entry_wtmp(store_wtmp);
161 /* If utmp/wtmp have been disabled, that's a good thing, hence
162 * ignore the errors */
169 static int write_entry_both(const struct utmpx *store) {
170 return write_utmp_wtmp(store, store);
173 int utmp_put_shutdown(void) {
174 struct utmpx store = {};
176 init_entry(&store, 0);
178 store.ut_type = RUN_LVL;
179 strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
181 return write_entry_both(&store);
184 int utmp_put_reboot(usec_t t) {
185 struct utmpx store = {};
187 init_entry(&store, t);
189 store.ut_type = BOOT_TIME;
190 strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
192 return write_entry_both(&store);
195 _pure_ static const char *sanitize_id(const char *id) {
201 if (l <= sizeof(((struct utmpx*) NULL)->ut_id))
204 return id + l - sizeof(((struct utmpx*) NULL)->ut_id);
207 int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line) {
208 struct utmpx store = {
209 .ut_type = INIT_PROCESS,
216 init_timestamp(&store, 0);
218 /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
219 strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
222 strncpy(store.ut_line, basename(line), sizeof(store.ut_line));
224 return write_entry_both(&store);
227 int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
228 struct utmpx lookup = {
229 .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
230 }, store, store_wtmp, *found;
236 /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */
237 strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
239 found = getutxid(&lookup);
243 if (found->ut_pid != pid)
246 memcpy(&store, found, sizeof(store));
247 store.ut_type = DEAD_PROCESS;
248 store.ut_exit.e_termination = code;
249 store.ut_exit.e_exit = status;
255 memcpy(&store_wtmp, &store, sizeof(store_wtmp));
256 /* wtmp wants the current time */
257 init_timestamp(&store_wtmp, 0);
259 return write_utmp_wtmp(&store, &store_wtmp);
263 int utmp_put_runlevel(int runlevel, int previous) {
264 struct utmpx store = {};
267 assert(runlevel > 0);
270 /* Find the old runlevel automatically */
272 r = utmp_get_runlevel(&previous, NULL);
281 if (previous == runlevel)
284 init_entry(&store, 0);
286 store.ut_type = RUN_LVL;
287 store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
288 strncpy(store.ut_user, "runlevel", sizeof(store.ut_user));
290 return write_entry_both(&store);
293 #define TIMEOUT_MSEC 50
295 static int write_to_terminal(const char *tty, const char *message) {
296 _cleanup_close_ int fd = -1;
304 fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC);
305 if (fd < 0 || !isatty(fd))
309 left = strlen(message);
311 end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC;
315 struct pollfd pollfd = {
322 t = now(CLOCK_MONOTONIC);
327 k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC);
334 n = write(fd, p, left);
342 assert((size_t) n <= left);
351 int utmp_wall(const char *message, const char *username, bool (*match_tty)(const char *tty)) {
352 _cleanup_free_ char *text = NULL, *hn = NULL, *un = NULL, *tty = NULL;
353 char date[FORMAT_TIMESTAMP_MAX];
357 hn = gethostname_malloc();
361 un = getlogname_malloc();
366 getttyname_harder(STDIN_FILENO, &tty);
370 "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
373 tty ? " on " : "", strempty(tty),
374 format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
382 while ((u = getutxent())) {
383 _cleanup_free_ char *buf = NULL;
387 if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
390 /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */
391 if (path_startswith(u->ut_line, "/dev/"))
394 if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0)
400 if (!match_tty || match_tty(path)) {
401 q = write_to_terminal(path, text);