Imported Upstream version 1.15.1
[deb_xorg-server.git] / hw / xquartz / console_redirect.c
CommitLineData
a09e091a
JB
1/*
2 * Copyright (c) 2011-2012 Apple Inc. All rights reserved.
3 *
4 * Permission is hereby granted, free of charge, to any person
5 * obtaining a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT
19 * HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *
24 * Except as contained in this notice, the name(s) of the above
25 * copyright holders shall not be used in advertising or otherwise to
26 * promote the sale, use or other dealings in this Software without
27 * prior written authorization.
28 */
29
30#ifdef HAVE_DIX_CONFIG_H
31#include <dix-config.h>
32#else
33#define DEBUG_CONSOLE_REDIRECT 1
34#define HAVE_LIBDISPATCH 1
35#endif
36
37#include <assert.h>
38#include <unistd.h>
39#include <stdio.h>
40#include <string.h>
41#include <stdlib.h>
42#include <sys/types.h>
43#include <sys/event.h>
44#include <asl.h>
45#include <errno.h>
46#include <fcntl.h>
47
48#include "console_redirect.h"
49
50#define BUF_SIZE 512
51
52#ifdef HAVE_LIBDISPATCH
53#include <dispatch/dispatch.h>
54
55static dispatch_queue_t redirect_serial_q;
56static dispatch_group_t read_source_group;
57#else
58#include <pthread.h>
59
60static pthread_t redirect_pthread;
61static pthread_mutex_t redirect_fds_lock = PTHREAD_MUTEX_INITIALIZER;
62
63static int kq;
64
65/* Notifications to our reader thread */
66#define ASL_REDIRECT_TERMINATE ((void *)(uintptr_t)1)
67#endif
68
69typedef struct {
70 int level;
71 aslclient asl;
72 aslmsg msg;
73
74 /* Buffered reading */
75 char *buf;
76 char *w;
77
78#ifdef HAVE_LIBDISPATCH
79 dispatch_source_t read_source;
80#endif
81} asl_redirect;
82
83static asl_redirect *redirect_fds = NULL;
84static int n_redirect_fds = 0;
85
86/* Read from the FD until there is no more to read and redirect to ASL.
87 * Preconditions:
88 * 1: pthread_mutex_lock lock is held (pthreads) or called
89 * from the appropriate serial queue for operating on
90 * redirect_fds
91 * 2: fd corresponds to a valid entry in redirect_fds
92 *
93 * Return values:
94 * If the pipe is closed, EOF is returned regardless of how many bytes
95 * were processed. If the pipe is still open, the number of read bytes
96 * is returned.
97 */
98static inline int
99_read_redirect(int fd, int flush)
100{
101 int total_read = 0;
102 int nbytes;
103 asl_redirect *aslr = &redirect_fds[fd];
104
105 while ((nbytes =
106 read(fd, aslr->w,
107 BUF_SIZE - (aslr->w - aslr->buf) - 1)) > 0) {
108 char *s, *p;
109
110 /* Increment our returned number read */
111 total_read += nbytes;
112
113 /* Increment our write location */
114 aslr->w += nbytes;
115 aslr->w[0] = '\0';
116
117 /* One line at a time */
118 for (p = aslr->buf; p < aslr->w; p = s + 1) {
119 // Find null or \n
120 for (s = p; *s && *s != '\n'; s++) ;
121 if (*s == '\n') {
122 *s = '\0';
123 }
124
125 if (s < aslr->w || aslr->buf == p) {
126 /* Either the first of multiple messages or one message which is larger than our buffer */
127 asl_log(aslr->asl, aslr->msg, aslr->level, "%s", p);
128 }
129 else {
130 /* We reached the end of the buffer, move this chunk to the start. */
131 memmove(aslr->buf, p, BUF_SIZE - (p - aslr->buf));
132 aslr->w = aslr->buf + (s - p);
133 break;
134 }
135 }
136
137 if (p == aslr->w) {
138 /* Start writing at the beginning in the case where we flushed */
139 aslr->w = aslr->buf;
140 }
141 }
142
143 /* Flush if requested or we're at EOF */
144 if (flush || nbytes == 0) {
145 if (aslr->w > aslr->buf) {
146 *aslr->w = '\0';
147 asl_log(aslr->asl, aslr->msg, aslr->level, "%s", aslr->buf);
148 }
149 }
150
151 if (nbytes == 0)
152 return EOF;
153 return total_read;
154}
155
156#ifdef HAVE_LIBDISPATCH
157static void
158read_from_source(void *_source)
159{
160 dispatch_source_t source = (dispatch_source_t)_source;
161 int fd = dispatch_source_get_handle(source);
162 if (_read_redirect(fd, 0) == EOF) {
163 dispatch_source_cancel(source);
164 }
165}
166
167static void
168cancel_source(void *_source)
169{
170 dispatch_source_t source = (dispatch_source_t)_source;
171 int fd = dispatch_source_get_handle(source);
172 asl_redirect *aslr = &redirect_fds[fd];
173
174 /* Flush the buffer */
175 _read_redirect(fd, 1);
176
177 close(fd);
178 free(aslr->buf);
179 memset(aslr, 0, sizeof(*aslr));
180 dispatch_release(source);
181 dispatch_group_leave(read_source_group);
182}
183
184#else /* !HAVE_LIBDISPATCH */
185static void *
186redirect_thread(void *ctx __unused)
187{
188 struct kevent ev;
189 int n;
190
191 while (1) {
192 n = kevent(kq, NULL, 0, &ev, 1, NULL);
193
194 /* Bail on errors */
195 if (n < 0) {
196 asl_log(NULL, NULL, ASL_LEVEL_ERR, "kevent failure: %s",
197 strerror(errno));
198 break;
199 }
200
201 /* This should not happen */
202 if (n == 0)
203 continue;
204
205 switch (ev.filter) {
206 case EVFILT_READ:
207 pthread_mutex_lock(&redirect_fds_lock);
208 {
209 int fd = ev.ident;
210 int close_fd = 0;
211 asl_redirect *aslr = &redirect_fds[fd];
212
213 if (fd < 0 || fd >= n_redirect_fds || aslr->buf == NULL) {
214 asl_log(NULL, NULL, ASL_LEVEL_ERR,
215 "Unexpected file descriptor: %d",
216 fd);
217 goto next;
218 }
219
220 if (ev.flags & EV_EOF) {
221 close_fd = 1;
222 if (EOF != _read_redirect(fd, 1)) {
223 asl_log(
224 NULL, NULL, ASL_LEVEL_ERR,
225 "kevent reported EOF on %d, but read doesn't concur.",
226 fd);
227 }
228 }
229 else {
230 close_fd = (EOF == _read_redirect(fd, 0));
231 }
232
233 if (close_fd) {
234 EV_SET(&ev, fd, EVFILT_READ, EV_DELETE, 0, 0, 0);
235 kevent(kq, &ev, 1, NULL, 0, NULL);
236 close(fd);
237 free(aslr->buf);
238 memset(aslr, 0, sizeof(*aslr));
239 }
240 }
241next:
242 pthread_mutex_unlock(&redirect_fds_lock);
243
244 case EVFILT_TIMER:
245 if (ev.udata == ASL_REDIRECT_TERMINATE)
246 return NULL;
247
248 default:
249 ;
250 ;
251 }
252 }
253
254 return NULL;
255}
256#endif
257
258static void
259redirect_atexit(void)
260{
261 /* stdout is linebuffered, so flush the buffer */
262 if (redirect_fds[STDOUT_FILENO].buf)
263 fflush(stdout);
264
265#ifdef HAVE_LIBDISPATCH
266 {
267 int i;
268
269 /* Cancel all of our dispatch sources, so they flush to ASL */
270 for (i = 0; i < n_redirect_fds; i++)
271 if (redirect_fds[i].read_source)
272 dispatch_source_cancel(redirect_fds[i].read_source);
273
274 /* Wait at least three seconds for our sources to flush to ASL */
275 dispatch_group_wait(read_source_group,
276 dispatch_time(DISPATCH_TIME_NOW, 3LL *
277 NSEC_PER_SEC));
278 }
279#else
280 {
281 struct kevent ev;
282
283 /* Tell our reader thread it is time to pack up and go home */
284 EV_SET(&ev, 0, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 0,
285 ASL_REDIRECT_TERMINATE);
286 kevent(kq, &ev, 1, NULL, 0, NULL);
287
288 pthread_join(redirect_pthread, NULL);
289 }
290#endif
291}
292
293#ifdef HAVE_LIBDISPATCH
294static void
295xq_asl_init(void *ctx __unused)
296#else
297static void
298xq_asl_init(void)
299#endif
300{
301 assert((redirect_fds = calloc(16, sizeof(*redirect_fds))) != NULL);
302 n_redirect_fds = 16;
303
304#ifdef HAVE_LIBDISPATCH
305 redirect_serial_q = dispatch_queue_create("com.apple.asl-redirect", NULL);
306 assert(redirect_serial_q != NULL);
307
308 read_source_group = dispatch_group_create();
309 assert(read_source_group != NULL);
310#else
311 assert((kq = kqueue()) != -1);
312 assert(pthread_create(&redirect_pthread, NULL, redirect_thread,
313 NULL) == 0);
314#endif
315
316 atexit(redirect_atexit);
317}
318
319#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
320#define fls(v) xq_fls(v)
321
322static inline int fls(int value) {
323 unsigned int b, v;
324
325 v = *((unsigned int *)&value);
326
327 for(b=0 ; v ; v >>= 1 , b++);
328
329 return b;
330}
331#endif
332
333int
334xq_asl_log_fd(aslclient asl, aslmsg msg, int level, int fd)
335{
336#ifdef HAVE_LIBDISPATCH
337 int err __block = 0;
338 static dispatch_once_t once_control;
339 dispatch_once_f(&once_control, NULL, xq_asl_init);
340#else
341 int err = 0;
342 static pthread_once_t once_control = PTHREAD_ONCE_INIT;
343 assert(pthread_once(&once_control, xq_asl_init) == 0);
344#endif
345
346 if (fd < 0)
347 return EBADF;
348
349#ifdef HAVE_LIBDISPATCH
350#define BLOCK_DONE return
351 dispatch_sync(redirect_serial_q, ^
352#else
353#define BLOCK_DONE goto done
354 assert(pthread_mutex_lock(&redirect_fds_lock) == 0);
355#endif
356 {
357 /* Reallocate if we need more space */
358 if (fd >= n_redirect_fds) {
359 size_t new_n = 1 << (fls(fd) + 1);
360 asl_redirect *new_array =
361 realloc(redirect_fds, new_n *
362 sizeof(*redirect_fds));
363 if (!new_array) {
364 err = errno;
365 BLOCK_DONE;
366 }
367 redirect_fds = new_array;
368 memset(redirect_fds + n_redirect_fds, 0, (new_n -
369 n_redirect_fds) * sizeof(*redirect_fds));
370 n_redirect_fds = new_n;
371 }
372
373 /* If we're already listening on it, return error. */
374 if (redirect_fds[fd].buf != NULL) {
375 err = EBADF;
376 BLOCK_DONE;
377 }
378
379 /* Initialize our buffer */
380 redirect_fds[fd].buf = (char *)malloc(BUF_SIZE);
381 if (redirect_fds[fd].buf == NULL) {
382 err = errno;
383 BLOCK_DONE;
384 }
385 redirect_fds[fd].w = redirect_fds[fd].buf;
386
387 /* Store our ASL settings */
388 redirect_fds[fd].level = level;
389 redirect_fds[fd].asl = asl;
390 redirect_fds[fd].msg = msg;
391
392 /* Don't block on reads from this fd */
393 fcntl(fd, F_SETFL,
394 O_NONBLOCK);
395
396 /* Start listening */
397#ifdef HAVE_LIBDISPATCH
398 {
399 dispatch_source_t read_source =
400 dispatch_source_create(
401 DISPATCH_SOURCE_TYPE_READ, fd, 0,
402 redirect_serial_q);
403 redirect_fds[fd].read_source = read_source;
404 dispatch_set_context(read_source, read_source);
405 dispatch_source_set_event_handler_f(read_source,
406 read_from_source);
407 dispatch_source_set_cancel_handler_f(read_source,
408 cancel_source);
409 dispatch_group_enter(read_source_group);
410 dispatch_resume(read_source);
411 }
412#else
413 {
414 struct kevent ev;
415 EV_SET(&ev, fd, EVFILT_READ, EV_ADD, 0, 0, 0);
416 kevent(kq, &ev, 1, NULL, 0, NULL);
417 }
418#endif
419 }
420#ifdef HAVE_LIBDISPATCH
421 );
422#else
423done:
424 assert(pthread_mutex_unlock(&redirect_fds_lock) == 0);
425#endif
426#undef BLOCK_DONE
427
428 return err;
429}
430
431int
432xq_asl_capture_fd(aslclient asl, aslmsg msg, int level, int fd)
433{
434 int pipepair[2];
435
436 /* Create pipe */
437 if (pipe(pipepair) == -1)
438 return errno;
439
440 /* Close the read fd but not the write fd on exec */
441 if (fcntl(pipepair[0], F_SETFD, FD_CLOEXEC) == -1)
442 return errno;
443
444 /* Replace the existing fd */
445 if (dup2(pipepair[1], fd) == -1) {
446 close(pipepair[0]);
447 close(pipepair[1]);
448 return errno;
449 }
450
451 /* If we capture STDOUT_FILENO, make sure we linebuffer stdout */
452 if (fd == STDOUT_FILENO)
453 setlinebuf(stdout);
454
455 /* Close the duplicate fds since they've been reassigned */
456 close(pipepair[1]);
457
458 /* Hand off the read end of our pipe to xq_asl_log_fd */
459 return xq_asl_log_fd(asl, msg, level, pipepair[0]);
460}
461
462#ifdef DEBUG_CONSOLE_REDIRECT
463int
464main(int argc __unused, char * *argv __unused)
465{
466 xq_asl_capture_fd(NULL, NULL, ASL_LEVEL_NOTICE, STDOUT_FILENO);
467 xq_asl_capture_fd(NULL, NULL, ASL_LEVEL_ERR, STDERR_FILENO);
468
469 fprintf(stderr, "TEST ERR1\n");
470 fprintf(stdout, "TEST OUT1\n");
471 fprintf(stderr, "TEST ERR2\n");
472 fprintf(stdout, "TEST OUT2\n");
473 system("/bin/echo SYST OUT");
474 system("/bin/echo SYST ERR >&2");
475 fprintf(stdout, "TEST OUT3\n");
476 fprintf(stdout, "TEST OUT4\n");
477 fprintf(stderr, "TEST ERR3\n");
478 fprintf(stderr, "TEST ERR4\n");
479
480 exit(0);
481}
482#endif