1/*
2 * Copyright (c) 2011 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <assert.h>
25#include <unistd.h>
26#include <stdio.h>
27#include <string.h>
28#include <stdlib.h>
29#include <sys/types.h>
30#include <sys/event.h>
31#include <asl.h>
32#include <asl_private.h>
33#include <errno.h>
34#include <fcntl.h>
35#include <dispatch/dispatch.h>
36
37/* asl.c */
38__private_extern__ void asl_client_release(asl_client_t *asl);
39__private_extern__ asl_client_t *asl_client_retain(asl_client_t *asl);
40
41#define BUF_SIZE 512
42
43static dispatch_queue_t redirect_serial_q;
44static dispatch_group_t read_source_group;
45
46typedef struct {
47    int level;
48    asl_client_t *asl;
49    asl_msg_t *msg;
50
51    /* Buffered reading */
52    char *buf;
53    char *w;
54
55    dispatch_source_t read_source;
56} asl_redirect_t;
57
58static asl_redirect_t *redirect_descriptors = NULL;
59static int n_redirect_descriptors = 0;
60
61/* Read from the FD until there is no more to read and redirect to ASL.
62 * Preconditions:
63 *      1: called from the appropriate serial queue for operating on
64 *         redirect_descriptors
65 *      2: descriptor corresponds to a valid entry in redirect_descriptors
66 *
67 * Return values:
68 *      If the pipe is closed, EOF is returned regardless of how many bytes
69 *      were processed.  If the pipe is still open, the number of read bytes
70 *      is returned.
71 */
72static inline int _read_redirect(int descriptor, int flush) {
73    int total_read = 0;
74    int nbytes;
75    asl_redirect_t *aslr = &redirect_descriptors[descriptor];
76
77    while ((nbytes = read(descriptor, aslr->w, BUF_SIZE - (aslr->w - aslr->buf) - 1)) > 0) {
78        char *s, *p;
79
80        /* Increment our returned number read */
81        total_read += nbytes;
82
83        /* Increment our write location */
84        aslr->w += nbytes;
85        aslr->w[0] = '\0';
86
87        /* One line at a time */
88        for (p = aslr->buf; p < aslr->w; p = s + 1) {
89            /* Find null or \n */
90            for (s=p; *s && *s != '\n'; s++);
91
92            if (*s == '\n') {
93                *s='\0';
94            }
95
96            if (s < aslr->w || aslr->buf == p) {
97                /* Either the first of multiple messages or one message which is larger than our buffer */
98                asl_log((aslclient)aslr->asl, (aslmsg)aslr->msg, aslr->level, "%s", p);
99            } else {
100                /* We reached the end of the buffer, move this chunk to the start. */
101                memmove(aslr->buf, p, BUF_SIZE - (p - aslr->buf));
102                aslr->w = aslr->buf + (s - p);
103                break;
104            }
105        }
106
107        if (p == aslr->w) {
108            /* Start writing at the beginning in the case where we cleared the buffer */
109            aslr->w = aslr->buf;
110        }
111    }
112
113    /* Flush if requested or we're at EOF */
114    if (flush || nbytes == 0) {
115        if (aslr->w > aslr->buf) {
116            *aslr->w = '\0';
117            asl_log((aslclient)aslr->asl, (aslmsg)aslr->msg, aslr->level, "%s", aslr->buf);
118        }
119    }
120
121    if (nbytes == 0)
122        return EOF;
123    return total_read;
124}
125
126static void read_from_source(void *_source) {
127    dispatch_source_t source = (dispatch_source_t)_source;
128    int descriptor = dispatch_source_get_handle(source);
129    if (_read_redirect(descriptor, 0) == EOF) {
130        dispatch_source_cancel(source);
131    }
132}
133
134static void cancel_source(void *_source) {
135    dispatch_source_t source = (dispatch_source_t)_source;
136    int descriptor = dispatch_source_get_handle(source);
137    asl_redirect_t *aslr = &redirect_descriptors[descriptor];
138
139    /* Flush the buffer */
140    _read_redirect(descriptor, 1);
141
142    close(descriptor);
143
144    asl_client_release(aslr->asl);
145    asl_msg_release(aslr->msg);
146    free(aslr->buf);
147
148    memset(aslr, 0, sizeof(*aslr));
149    dispatch_release(source);
150    dispatch_group_leave(read_source_group);
151}
152
153
154static void redirect_atexit(void) {
155    int i;
156
157    /* stdout is linebuffered, so flush the buffer */
158    if (redirect_descriptors[STDOUT_FILENO].buf)
159        fflush(stdout);
160
161    /* Cancel all of our dispatch sources, so they flush to ASL */
162    for (i=0; i < n_redirect_descriptors; i++)
163        if (redirect_descriptors[i].read_source)
164            dispatch_source_cancel(redirect_descriptors[i].read_source);
165
166    /* Wait at least three seconds for our sources to flush to ASL */
167    dispatch_group_wait(read_source_group, dispatch_time(DISPATCH_TIME_NOW, 3LL * NSEC_PER_SEC));
168}
169
170static void asl_descriptor_init(void *ctx __unused)
171{
172    assert((redirect_descriptors = calloc(16, sizeof(*redirect_descriptors))) != NULL);
173    n_redirect_descriptors = 16;
174
175    redirect_serial_q = dispatch_queue_create("com.apple.asl-redirect", NULL);
176    assert(redirect_serial_q != NULL);
177
178    read_source_group = dispatch_group_create();
179    assert(read_source_group != NULL);
180
181    atexit(redirect_atexit);
182}
183
184static int asl_log_from_descriptor(aslclient ac, aslmsg am, int level, int descriptor) {
185    int err __block = 0;
186    static dispatch_once_t once_control;
187    dispatch_once_f(&once_control, NULL, asl_descriptor_init);
188    asl_client_t *asl = (asl_client_t *)ac;
189    asl_msg_t *msg = (asl_msg_t *)am;
190
191    if (descriptor < 0)
192        return EBADF;
193
194    if (msg != NULL) {
195        msg = asl_msg_copy(msg);
196        if (msg == NULL)
197            return ENOMEM;
198    }
199
200    dispatch_sync(redirect_serial_q, ^{
201        dispatch_source_t read_source;
202
203        /* Reallocate if we need more space */
204        if (descriptor >= n_redirect_descriptors) {
205            size_t new_n = 1 << (fls(descriptor) + 1);
206            asl_redirect_t *new_array = realloc(redirect_descriptors, new_n * sizeof(*redirect_descriptors));
207            if (!new_array) {
208                err = errno;
209                return;
210            }
211            redirect_descriptors = new_array;
212            memset(redirect_descriptors + n_redirect_descriptors, 0, (new_n - n_redirect_descriptors) * sizeof(*redirect_descriptors));
213            n_redirect_descriptors = new_n;
214        }
215
216        /* If we're already listening on it, return error. */
217        if (redirect_descriptors[descriptor].buf != NULL) {
218            err = EBADF;
219            return;
220        }
221
222        /* Initialize our buffer */
223        redirect_descriptors[descriptor].buf = (char *)malloc(BUF_SIZE);
224        if (redirect_descriptors[descriptor].buf == NULL) {
225            err = errno;
226            return;
227        }
228        redirect_descriptors[descriptor].w = redirect_descriptors[descriptor].buf;
229
230        /* Store our ASL settings */
231        redirect_descriptors[descriptor].level = level;
232        redirect_descriptors[descriptor].asl = asl_client_retain(asl);
233        redirect_descriptors[descriptor].msg = msg;
234
235        /* Don't block on reads from this descriptor */
236        (void)fcntl(descriptor, F_SETFL, O_NONBLOCK);
237
238        /* Start listening */
239        read_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, descriptor, 0, redirect_serial_q);
240        redirect_descriptors[descriptor].read_source = read_source;
241        dispatch_set_context(read_source, read_source);
242        dispatch_source_set_event_handler_f(read_source, read_from_source);
243        dispatch_source_set_cancel_handler_f(read_source, cancel_source);
244        dispatch_group_enter(read_source_group);
245        dispatch_resume(read_source);
246    });
247
248    if (err) {
249        asl_msg_release(msg);
250    }
251
252    return err;
253}
254
255int asl_log_descriptor(aslclient ac, aslmsg am, int level, int descriptor, uint32_t fd_type) {
256    int pipepair[2];
257    int retval;
258    int oerrno = errno;
259
260    if (fd_type == ASL_LOG_DESCRIPTOR_READ)
261        return asl_log_from_descriptor(ac, am, level, descriptor);
262
263    assert(fd_type == ASL_LOG_DESCRIPTOR_WRITE);
264
265    /* Create pipe */
266    if (pipe(pipepair) == -1) {
267        retval = errno;
268        errno = oerrno;
269        return retval;
270    }
271
272    /* Close the read descriptor but not the write descriptor on exec */
273    if (fcntl(pipepair[0], F_SETFD, FD_CLOEXEC) == -1) {
274        retval = errno;
275        errno = oerrno;
276        return retval;
277    }
278
279    /* Replace the existing descriptor */
280    if (dup2(pipepair[1], descriptor) == -1) {
281        close(pipepair[0]);
282        close(pipepair[1]);
283        retval = errno;
284        errno = oerrno;
285        return retval;
286    }
287
288    /* If we capture STDOUT_FILENO, make sure we linebuffer stdout */
289    if (descriptor == STDOUT_FILENO)
290        setlinebuf(stdout);
291
292    /* Close the duplicate descriptors since they've been reassigned */
293    close(pipepair[1]);
294
295    /* Hand off the read end of our pipe to asl_log_descriptor */
296    return asl_log_from_descriptor(ac, am, level, pipepair[0]);
297}
298