chiark / gitweb /
bus: add convenience calls for method replies, too
[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 lookup, *found;
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         zero(lookup);
70         lookup.ut_type = RUN_LVL;
71
72         if (!(found = getutxid(&lookup)))
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         zero(uts);
112
113         if (uname(&uts) >= 0)
114                 strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
115
116         strncpy(store->ut_line, "~", sizeof(store->ut_line));  /* or ~~ ? */
117         strncpy(store->ut_id, "~~", sizeof(store->ut_id));
118 }
119
120 static int write_entry_utmp(const struct utmpx *store) {
121         int r;
122
123         assert(store);
124
125         /* utmp is similar to wtmp, but there is only one entry for
126          * each entry type resp. user; i.e. basically a key/value
127          * table. */
128
129         if (utmpxname(_PATH_UTMPX) < 0)
130                 return -errno;
131
132         setutxent();
133
134         if (!pututxline(store))
135                 r = -errno;
136         else
137                 r = 0;
138
139         endutxent();
140
141         return r;
142 }
143
144 static int write_entry_wtmp(const struct utmpx *store) {
145         assert(store);
146
147         /* wtmp is a simple append-only file where each entry is
148         simply appended to * the end; i.e. basically a log. */
149
150         errno = 0;
151         updwtmpx(_PATH_WTMPX, store);
152         return -errno;
153 }
154
155 static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) {
156         int r, s;
157
158         r = write_entry_utmp(store_utmp);
159         s = write_entry_wtmp(store_wtmp);
160
161         if (r >= 0)
162                 r = s;
163
164         /* If utmp/wtmp have been disabled, that's a good thing, hence
165          * ignore the errors */
166         if (r == -ENOENT)
167                 r = 0;
168
169         return r;
170 }
171
172 static int write_entry_both(const struct utmpx *store) {
173         return write_utmp_wtmp(store, store);
174 }
175
176 int utmp_put_shutdown(void) {
177         struct utmpx store;
178
179         init_entry(&store, 0);
180
181         store.ut_type = RUN_LVL;
182         strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
183
184         return write_entry_both(&store);
185 }
186
187 int utmp_put_reboot(usec_t t) {
188         struct utmpx store;
189
190         init_entry(&store, t);
191
192         store.ut_type = BOOT_TIME;
193         strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
194
195         return write_entry_both(&store);
196 }
197
198 static const char *sanitize_id(const char *id) {
199         size_t l;
200
201         assert(id);
202         l = strlen(id);
203
204         if (l <= sizeof(((struct utmpx*) NULL)->ut_id))
205                 return id;
206
207         return id + l - sizeof(((struct utmpx*) NULL)->ut_id);
208 }
209
210 int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line) {
211         struct utmpx store;
212
213         assert(id);
214
215         init_timestamp(&store, 0);
216
217         store.ut_type = INIT_PROCESS;
218         store.ut_pid = pid;
219         store.ut_session = sid;
220
221         strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id));
222
223         if (line)
224                 strncpy(store.ut_line, path_get_file_name(line), sizeof(store.ut_line));
225
226         return write_entry_both(&store);
227 }
228
229 int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) {
230         struct utmpx lookup, store, store_wtmp, *found;
231
232         assert(id);
233
234         setutxent();
235
236         zero(lookup);
237         lookup.ut_type = INIT_PROCESS; /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */
238         strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id));
239
240         if (!(found = getutxid(&lookup)))
241                 return 0;
242
243         if (found->ut_pid != pid)
244                 return 0;
245
246         memcpy(&store, found, sizeof(store));
247         store.ut_type = DEAD_PROCESS;
248         store.ut_exit.e_termination = code;
249         store.ut_exit.e_exit = status;
250
251         zero(store.ut_user);
252         zero(store.ut_host);
253         zero(store.ut_tv);
254
255         memcpy(&store_wtmp, &store, sizeof(store_wtmp));
256         /* wtmp wants the current time */
257         init_timestamp(&store_wtmp, 0);
258
259         return write_utmp_wtmp(&store, &store_wtmp);
260 }
261
262
263 int utmp_put_runlevel(int runlevel, int previous) {
264         struct utmpx store;
265         int r;
266
267         assert(runlevel > 0);
268
269         if (previous <= 0) {
270                 /* Find the old runlevel automatically */
271
272                 if ((r = utmp_get_runlevel(&previous, NULL)) < 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         int _cleanup_close_ 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                 usec_t t;
316                 int k;
317
318                 t = now(CLOCK_MONOTONIC);
319
320                 if (t >= end)
321                         return -ETIME;
322
323                 zero(pollfd);
324                 pollfd.fd = fd;
325                 pollfd.events = POLLOUT;
326
327                 k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC);
328                 if (k < 0)
329                         return -errno;
330
331                 if (k == 0)
332                         return -ETIME;
333
334                 n = write(fd, p, left);
335                 if (n < 0) {
336                         if (errno == EAGAIN)
337                                 continue;
338
339                         return -errno;
340                 }
341
342                 assert((size_t) n <= left);
343
344                 p += n;
345                 left -= n;
346         }
347
348         return 0;
349 }
350
351 int utmp_wall(const char *message, bool (*match_tty)(const char *tty)) {
352         struct utmpx *u;
353         char date[FORMAT_TIMESTAMP_MAX];
354         char *text = NULL, *hn = NULL, *un = NULL, *tty = NULL;
355         int r;
356
357         if (!(hn = gethostname_malloc()) ||
358             !(un = getlogname_malloc())) {
359                 r = -ENOMEM;
360                 goto finish;
361         }
362
363         getttyname_harder(STDIN_FILENO, &tty);
364
365         if (asprintf(&text,
366                      "\a\r\n"
367                      "Broadcast message from %s@%s%s%s (%s):\r\n\r\n"
368                      "%s\r\n\r\n",
369                      un, hn,
370                      tty ? " on " : "", strempty(tty),
371                      format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)),
372                      message) < 0) {
373                 r = -ENOMEM;
374                 goto finish;
375         }
376
377         setutxent();
378
379         r = 0;
380
381         while ((u = getutxent())) {
382                 int q;
383                 const char *path;
384                 char *buf = NULL;
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",
394                                      (int) sizeof(u->ut_line), u->ut_line) < 0) {
395                                 r = -ENOMEM;
396                                 goto finish;
397                         }
398
399                         path = buf;
400                 }
401
402                 if (!match_tty || match_tty(path))
403                         if ((q = write_to_terminal(path, text)) < 0)
404                                 r = q;
405
406                 free(buf);
407         }
408
409 finish:
410         free(hn);
411         free(un);
412         free(tty);
413         free(text);
414
415         return r;
416 }