chiark / gitweb /
journald: use a bit more cleanup magic!
[elogind.git] / src / journal / journald-stream.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 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 <fcntl.h>
23 #include <unistd.h>
24 #include <stddef.h>
25 #include <sys/epoll.h>
26
27 #ifdef HAVE_SELINUX
28 #include <selinux/selinux.h>
29 #endif
30
31 #include "socket-util.h"
32 #include "selinux-util.h"
33 #include "journald-server.h"
34 #include "journald-stream.h"
35 #include "journald-syslog.h"
36 #include "journald-kmsg.h"
37 #include "journald-console.h"
38
39 #define STDOUT_STREAMS_MAX 4096
40
41 typedef enum StdoutStreamState {
42         STDOUT_STREAM_IDENTIFIER,
43         STDOUT_STREAM_UNIT_ID,
44         STDOUT_STREAM_PRIORITY,
45         STDOUT_STREAM_LEVEL_PREFIX,
46         STDOUT_STREAM_FORWARD_TO_SYSLOG,
47         STDOUT_STREAM_FORWARD_TO_KMSG,
48         STDOUT_STREAM_FORWARD_TO_CONSOLE,
49         STDOUT_STREAM_RUNNING
50 } StdoutStreamState;
51
52 struct StdoutStream {
53         Server *server;
54         StdoutStreamState state;
55
56         int fd;
57
58         struct ucred ucred;
59 #ifdef HAVE_SELINUX
60         security_context_t security_context;
61 #endif
62
63         char *identifier;
64         char *unit_id;
65         int priority;
66         bool level_prefix:1;
67         bool forward_to_syslog:1;
68         bool forward_to_kmsg:1;
69         bool forward_to_console:1;
70
71         char buffer[LINE_MAX+1];
72         size_t length;
73
74         LIST_FIELDS(StdoutStream, stdout_stream);
75 };
76
77 static int stdout_stream_log(StdoutStream *s, const char *p) {
78         struct iovec iovec[N_IOVEC_META_FIELDS + 5];
79         _cleanup_free_ char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_identifier = NULL;
80         unsigned n = 0;
81         int priority;
82         char *label = NULL;
83         size_t label_len = 0;
84
85         assert(s);
86         assert(p);
87
88         if (isempty(p))
89                 return 0;
90
91         priority = s->priority;
92
93         if (s->level_prefix)
94                 syslog_parse_priority((char**) &p, &priority, false);
95
96         if (s->forward_to_syslog || s->server->forward_to_syslog)
97                 server_forward_syslog(s->server, syslog_fixup_facility(priority), s->identifier, p, &s->ucred, NULL);
98
99         if (s->forward_to_kmsg || s->server->forward_to_kmsg)
100                 server_forward_kmsg(s->server, priority, s->identifier, p, &s->ucred);
101
102         if (s->forward_to_console || s->server->forward_to_console)
103                 server_forward_console(s->server, priority, s->identifier, p, &s->ucred);
104
105         IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=stdout");
106
107         if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0)
108                 IOVEC_SET_STRING(iovec[n++], syslog_priority);
109
110         if (priority & LOG_FACMASK)
111                 if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0)
112                         IOVEC_SET_STRING(iovec[n++], syslog_facility);
113
114         if (s->identifier) {
115                 syslog_identifier = strappend("SYSLOG_IDENTIFIER=", s->identifier);
116                 if (syslog_identifier)
117                         IOVEC_SET_STRING(iovec[n++], syslog_identifier);
118         }
119
120         message = strappend("MESSAGE=", p);
121         if (message)
122                 IOVEC_SET_STRING(iovec[n++], message);
123
124 #ifdef HAVE_SELINUX
125         if (s->security_context) {
126                 label = (char*) s->security_context;
127                 label_len = strlen((char*) s->security_context);
128         }
129 #endif
130
131         server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, label, label_len, s->unit_id, priority, 0);
132         return 0;
133 }
134
135 static int stdout_stream_line(StdoutStream *s, char *p) {
136         int r;
137
138         assert(s);
139         assert(p);
140
141         p = strstrip(p);
142
143         switch (s->state) {
144
145         case STDOUT_STREAM_IDENTIFIER:
146                 if (isempty(p))
147                         s->identifier = NULL;
148                 else  {
149                         s->identifier = strdup(p);
150                         if (!s->identifier)
151                                 return log_oom();
152                 }
153
154                 s->state = STDOUT_STREAM_UNIT_ID;
155                 return 0;
156
157         case STDOUT_STREAM_UNIT_ID:
158                 if (s->ucred.uid == 0) {
159                         if (isempty(p))
160                                 s->unit_id = NULL;
161                         else  {
162                                 s->unit_id = strdup(p);
163                                 if (!s->unit_id)
164                                         return log_oom();
165                         }
166                 }
167
168                 s->state = STDOUT_STREAM_PRIORITY;
169                 return 0;
170
171         case STDOUT_STREAM_PRIORITY:
172                 r = safe_atoi(p, &s->priority);
173                 if (r < 0 || s->priority < 0 || s->priority > 999) {
174                         log_warning("Failed to parse log priority line.");
175                         return -EINVAL;
176                 }
177
178                 s->state = STDOUT_STREAM_LEVEL_PREFIX;
179                 return 0;
180
181         case STDOUT_STREAM_LEVEL_PREFIX:
182                 r = parse_boolean(p);
183                 if (r < 0) {
184                         log_warning("Failed to parse level prefix line.");
185                         return -EINVAL;
186                 }
187
188                 s->level_prefix = !!r;
189                 s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG;
190                 return 0;
191
192         case STDOUT_STREAM_FORWARD_TO_SYSLOG:
193                 r = parse_boolean(p);
194                 if (r < 0) {
195                         log_warning("Failed to parse forward to syslog line.");
196                         return -EINVAL;
197                 }
198
199                 s->forward_to_syslog = !!r;
200                 s->state = STDOUT_STREAM_FORWARD_TO_KMSG;
201                 return 0;
202
203         case STDOUT_STREAM_FORWARD_TO_KMSG:
204                 r = parse_boolean(p);
205                 if (r < 0) {
206                         log_warning("Failed to parse copy to kmsg line.");
207                         return -EINVAL;
208                 }
209
210                 s->forward_to_kmsg = !!r;
211                 s->state = STDOUT_STREAM_FORWARD_TO_CONSOLE;
212                 return 0;
213
214         case STDOUT_STREAM_FORWARD_TO_CONSOLE:
215                 r = parse_boolean(p);
216                 if (r < 0) {
217                         log_warning("Failed to parse copy to console line.");
218                         return -EINVAL;
219                 }
220
221                 s->forward_to_console = !!r;
222                 s->state = STDOUT_STREAM_RUNNING;
223                 return 0;
224
225         case STDOUT_STREAM_RUNNING:
226                 return stdout_stream_log(s, p);
227         }
228
229         assert_not_reached("Unknown stream state");
230 }
231
232 static int stdout_stream_scan(StdoutStream *s, bool force_flush) {
233         char *p;
234         size_t remaining;
235         int r;
236
237         assert(s);
238
239         p = s->buffer;
240         remaining = s->length;
241         for (;;) {
242                 char *end;
243                 size_t skip;
244
245                 end = memchr(p, '\n', remaining);
246                 if (end)
247                         skip = end - p + 1;
248                 else if (remaining >= sizeof(s->buffer) - 1) {
249                         end = p + sizeof(s->buffer) - 1;
250                         skip = remaining;
251                 } else
252                         break;
253
254                 *end = 0;
255
256                 r = stdout_stream_line(s, p);
257                 if (r < 0)
258                         return r;
259
260                 remaining -= skip;
261                 p += skip;
262         }
263
264         if (force_flush && remaining > 0) {
265                 p[remaining] = 0;
266                 r = stdout_stream_line(s, p);
267                 if (r < 0)
268                         return r;
269
270                 p += remaining;
271                 remaining = 0;
272         }
273
274         if (p > s->buffer) {
275                 memmove(s->buffer, p, remaining);
276                 s->length = remaining;
277         }
278
279         return 0;
280 }
281
282 int stdout_stream_process(StdoutStream *s) {
283         ssize_t l;
284         int r;
285
286         assert(s);
287
288         l = read(s->fd, s->buffer+s->length, sizeof(s->buffer)-1-s->length);
289         if (l < 0) {
290
291                 if (errno == EAGAIN)
292                         return 0;
293
294                 log_warning("Failed to read from stream: %m");
295                 return -errno;
296         }
297
298         if (l == 0) {
299                 r = stdout_stream_scan(s, true);
300                 if (r < 0)
301                         return r;
302
303                 return 0;
304         }
305
306         s->length += l;
307         r = stdout_stream_scan(s, false);
308         if (r < 0)
309                 return r;
310
311         return 1;
312
313 }
314
315 void stdout_stream_free(StdoutStream *s) {
316         assert(s);
317
318         if (s->server) {
319                 assert(s->server->n_stdout_streams > 0);
320                 s->server->n_stdout_streams --;
321                 LIST_REMOVE(stdout_stream, s->server->stdout_streams, s);
322         }
323
324         if (s->fd >= 0) {
325                 if (s->server)
326                         epoll_ctl(s->server->epoll_fd, EPOLL_CTL_DEL, s->fd, NULL);
327
328                 close_nointr_nofail(s->fd);
329         }
330
331 #ifdef HAVE_SELINUX
332         if (s->security_context)
333                 freecon(s->security_context);
334 #endif
335
336         free(s->identifier);
337         free(s->unit_id);
338         free(s);
339 }
340
341 int stdout_stream_new(Server *s) {
342         StdoutStream *stream;
343         int fd, r;
344         socklen_t len;
345         struct epoll_event ev;
346
347         assert(s);
348
349         fd = accept4(s->stdout_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
350         if (fd < 0) {
351                 if (errno == EAGAIN)
352                         return 0;
353
354                 log_error("Failed to accept stdout connection: %m");
355                 return -errno;
356         }
357
358         if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) {
359                 log_warning("Too many stdout streams, refusing connection.");
360                 close_nointr_nofail(fd);
361                 return 0;
362         }
363
364         stream = new0(StdoutStream, 1);
365         if (!stream) {
366                 close_nointr_nofail(fd);
367                 return log_oom();
368         }
369
370         stream->fd = fd;
371
372         len = sizeof(stream->ucred);
373         if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &stream->ucred, &len) < 0) {
374                 log_error("Failed to determine peer credentials: %m");
375                 r = -errno;
376                 goto fail;
377         }
378
379 #ifdef HAVE_SELINUX
380         if (use_selinux()) {
381                 if (getpeercon(fd, &stream->security_context) < 0 && errno != ENOPROTOOPT)
382                         log_error("Failed to determine peer security context: %m");
383         }
384 #endif
385
386         if (shutdown(fd, SHUT_WR) < 0) {
387                 log_error("Failed to shutdown writing side of socket: %m");
388                 r = -errno;
389                 goto fail;
390         }
391
392         zero(ev);
393         ev.data.ptr = stream;
394         ev.events = EPOLLIN;
395         if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
396                 log_error("Failed to add stream to event loop: %m");
397                 r = -errno;
398                 goto fail;
399         }
400
401         stream->server = s;
402         LIST_PREPEND(stdout_stream, s->stdout_streams, stream);
403         s->n_stdout_streams ++;
404
405         return 0;
406
407 fail:
408         stdout_stream_free(stream);
409         return r;
410 }
411
412 int server_open_stdout_socket(Server *s) {
413         int r;
414         struct epoll_event ev;
415
416         assert(s);
417
418         if (s->stdout_fd < 0) {
419                 union sockaddr_union sa = {
420                         .un.sun_family = AF_UNIX,
421                         .un.sun_path = "/run/systemd/journal/stdout",
422                 };
423
424                 s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
425                 if (s->stdout_fd < 0) {
426                         log_error("socket() failed: %m");
427                         return -errno;
428                 }
429
430                 unlink(sa.un.sun_path);
431
432                 r = bind(s->stdout_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path));
433                 if (r < 0) {
434                         log_error("bind() failed: %m");
435                         return -errno;
436                 }
437
438                 chmod(sa.un.sun_path, 0666);
439
440                 if (listen(s->stdout_fd, SOMAXCONN) < 0) {
441                         log_error("listen() failed: %m");
442                         return -errno;
443                 }
444         } else
445                 fd_nonblock(s->stdout_fd, 1);
446
447         zero(ev);
448         ev.events = EPOLLIN;
449         ev.data.fd = s->stdout_fd;
450         if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->stdout_fd, &ev) < 0) {
451                 log_error("Failed to add stdout server fd to epoll object: %m");
452                 return -errno;
453         }
454
455         return 0;
456 }