chiark / gitweb /
bus-dump: fix two minor memory leaks
[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         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 }