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