]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/utmp-wtmp.c
utmp: make sure we don't write the utmp reboot record twice on each boot
[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 <sys/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 zero(*store);
96
97 if (t <= 0)
98 t = now(CLOCK_REALTIME);
99
100 store->ut_tv.tv_sec = t / USEC_PER_SEC;
101 store->ut_tv.tv_usec = t % USEC_PER_SEC;
102 }
103
104 static void init_entry(struct utmpx *store, usec_t t) {
105 struct utsname uts = {};
106
107 assert(store);
108
109 init_timestamp(store, t);
110
111 if (uname(&uts) >= 0)
112 strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
113
114 strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */
115 strncpy(store->ut_id, "~~", sizeof(store->ut_id));
116 }
117
118 static int write_entry_utmp(const struct utmpx *store) {
119 int r;
120
121 assert(store);
122
123 /* utmp is similar to wtmp, but there is only one entry for
124 * each entry type resp. user; i.e. basically a key/value
125 * table. */
126
127 if (utmpxname(_PATH_UTMPX) < 0)
128 return -errno;
129
130 setutxent();
131
132 if (!pututxline(store))
133 r = -errno;
134 else
135 r = 0;
136
137 endutxent();
138
139 return r;
140 }
141
142 static int write_entry_wtmp(const struct utmpx *store) {
143 assert(store);
144
145 /* wtmp is a simple append-only file where each entry is
146 simply appended to * the end; i.e. basically a log. */
147
148 errno = 0;
149 updwtmpx(_PATH_WTMPX, store);
150 return -errno;
151 }
152
153 static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
154 int r, s;
155
156 r = write_entry_utmp(store_utmp);
157 s = write_entry_wtmp(store_wtmp);
158
159 if (r >= 0)
160 r = s;
161
162 /* If utmp/wtmp have been disabled, that's a good thing, hence
163 * ignore the errors */
164 if (r == -ENOENT)
165 r = 0;
166
167 return r;
168 }
169
170 static int write_entry_both(const struct utmpx *store) {
171 return write_utmp_wtmp(store, store);
172 }
173
174 int utmp_put_shutdown(void) {
175 struct utmpx store;
176
177 init_entry(&store, 0);
178
179 store.ut_type = RUN_LVL;
180 strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
181
182 return write_entry_both(&store);
183 }
184
185 int utmp_put_reboot(usec_t t) {
186 struct utmpx store;
187
188 init_entry(&store, t);
189
190 store.ut_type = BOOT_TIME;
191 strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
192
193 return write_entry_both(&store);
194 }
195
196 _pure_ static const char *sanitize_id(const char *id) {
197 size_t l;
198
199 assert(id);
200 l = strlen(id);
201
202 if (l <= sizeof(((struct utmpx*) NULL)->ut_id))
203 return id;
204
205 return id + l - sizeof(((struct utmpx*) NULL)->ut_id);
206 }
207
208 int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line) {
209 struct utmpx store;
210
211 assert(id);
212
213 init_timestamp(&store, 0);
214
215 store.ut_type = INIT_PROCESS;
216 store.ut_pid = pid;
217 store.ut_session = sid;
218
219 strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
220
221 if (line)
222 strncpy(store.ut_line, basename(line), sizeof(store.ut_line));
223
224 return write_entry_both(&store);
225 }
226
227 int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
228 struct utmpx lookup, store, store_wtmp, *found;
229
230 assert(id);
231
232 setutxent();
233
234 zero(lookup);
235 lookup.ut_type = INIT_PROCESS; /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
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, 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 un = getlogname_malloc();
358 if (!hn || !un)
359 return -ENOMEM;
360
361 getttyname_harder(STDIN_FILENO, &tty);
362
363 if (asprintf(&text,
364 "\a\r\n"
365 "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
366 "%s\r\n\r\n",
367 un, hn,
368 tty ? " on " : "", strempty(tty),
369 format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
370 message) < 0)
371 return -ENOMEM;
372
373 setutxent();
374
375 r = 0;
376
377 while ((u = getutxent())) {
378 _cleanup_free_ char *buf = NULL;
379 const char *path;
380 int q;
381
382 if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0)
383 continue;
384
385 /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */
386 if (path_startswith(u->ut_line, "/dev/"))
387 path = u->ut_line;
388 else {
389 if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0)
390 return -ENOMEM;
391
392 path = buf;
393 }
394
395 if (!match_tty || match_tty(path)) {
396 q = write_to_terminal(path, text);
397 if (q < 0)
398 r = q;
399 }
400 }
401
402 return r;
403 }