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/>.
26 #include <sys/utsname.h>
32 #include "path-util.h"
33 #include "utmp-wtmp.h"
35 int utmp_get_runlevel(int *runlevel, int *previous) {
36 struct utmpx lookup, *found;
42 /* If these values are set in the environment this takes
43 * precedence. Presumably, sysvinit does this to work around a
44 * race condition that would otherwise exist where we'd always
45 * go to disk and hence might read runlevel data that might be
46 * very new and does not apply to the current script being
49 if ((e = getenv("RUNLEVEL")) && e[0] > 0) {
53 /* $PREVLEVEL seems to be an Upstart thing */
55 if ((e = getenv("PREVLEVEL")) && e[0] > 0)
64 if (utmpxname(_PATH_UTMPX) < 0)
70 lookup.ut_type = RUN_LVL;
72 if (!(found = getutxid(&lookup)))
77 a = found->ut_pid & 0xFF;
78 b = (found->ut_pid >> 8) & 0xFF;
92 static void init_timestamp(struct utmpx *store, usec_t t) {
98 t = now(CLOCK_REALTIME);
100 store->ut_tv.tv_sec = t / USEC_PER_SEC;
101 store->ut_tv.tv_usec = t % USEC_PER_SEC;
104 static void init_entry(struct utmpx *store, usec_t t) {
109 init_timestamp(store, t);
113 if (uname(&uts) >= 0)
114 strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
116 strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */
117 strncpy(store->ut_id, "~~", sizeof(store->ut_id));
120 static int write_entry_utmp(const struct utmpx *store) {
125 /* utmp is similar to wtmp, but there is only one entry for
126 * each entry type resp. user; i.e. basically a key/value
129 if (utmpxname(_PATH_UTMPX) < 0)
134 if (!pututxline(store))
144 static int write_entry_wtmp(const struct utmpx *store) {
147 /* wtmp is a simple append-only file where each entry is
148 simply appended to * the end; i.e. basically a log. */
151 updwtmpx(_PATH_WTMPX, store);
155 static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
158 r = write_entry_utmp(store_utmp);
159 s = write_entry_wtmp(store_wtmp);
164 /* If utmp/wtmp have been disabled, that's a good thing, hence
165 * ignore the errors */
172 static int write_entry_both(const struct utmpx *store) {
173 return write_utmp_wtmp(store, store);
176 int utmp_put_shutdown(void) {
179 init_entry(&store, 0);
181 store.ut_type = RUN_LVL;
182 strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
184 return write_entry_both(&store);
187 int utmp_put_reboot(usec_t t) {
190 init_entry(&store, t);
192 store.ut_type = BOOT_TIME;
193 strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
195 return write_entry_both(&store);
198 static const char *sanitize_id(const char *id) {
204 if (l <= sizeof(((struct utmpx*) NULL)->ut_id))
207 return id + l - sizeof(((struct utmpx*) NULL)->ut_id);
210 int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line) {
215 init_timestamp(&store, 0);
217 store.ut_type = INIT_PROCESS;
219 store.ut_session = sid;
221 strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
224 strncpy(store.ut_line, path_get_file_name(line), sizeof(store.ut_line));
226 return write_entry_both(&store);
229 int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
230 struct utmpx lookup, store, store_wtmp, *found;
237 lookup.ut_type = INIT_PROCESS; /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
238 strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
240 if (!(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) {
267 assert(runlevel > 0);
270 /* Find the old runlevel automatically */
272 if ((r = utmp_get_runlevel(&previous, NULL)) < 0) {
280 if (previous == runlevel)
283 init_entry(&store, 0);
285 store.ut_type = RUN_LVL;
286 store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
287 strncpy(store.ut_user, "runlevel", sizeof(store.ut_user));
289 return write_entry_both(&store);
292 #define TIMEOUT_MSEC 50
294 static int write_to_terminal(const char *tty, const char *message) {
295 int _cleanup_close_ fd = -1;
303 fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC);
304 if (fd < 0 || !isatty(fd))
308 left = strlen(message);
310 end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC;
314 struct pollfd pollfd;
318 t = now(CLOCK_MONOTONIC);
325 pollfd.events = POLLOUT;
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, bool (*match_tty)(const char *tty)) {
353 char date[FORMAT_TIMESTAMP_MAX];
354 char *text = NULL, *hn = NULL, *un = NULL, *tty = NULL;
357 if (!(hn = gethostname_malloc()) ||
358 !(un = getlogname_malloc())) {
363 getttyname_harder(STDIN_FILENO, &tty);
367 "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
370 tty ? " on " : "", strempty(tty),
371 format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
381 while ((u = getutxent())) {
386 if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
389 /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */
390 if (path_startswith(u->ut_line, "/dev/"))
393 if (asprintf(&buf, "/dev/%.*s",
394 (int) sizeof(u->ut_line), u->ut_line) < 0) {
402 if (!match_tty || match_tty(path))
403 if ((q = write_to_terminal(path, text)) < 0)