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