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