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