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