chiark / gitweb /
Do not print invalid UTF-8 in error messages
[elogind.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 }