1/*
2 * Copyright (c) 2013 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_client.h>
33#include <asl_private.h>
34#include <errno.h>
35#include <fcntl.h>
36#include <dispatch/dispatch.h>
37
38#define BUF_SIZE 512
39
40static dispatch_queue_t redirect_serial_q;
41static dispatch_group_t read_source_group;
42
43typedef struct
44{
45    int level;
46    asl_client_t *asl;
47    asl_msg_t *msg;
48
49    /* Buffered reading */
50    char *buf;
51    char *w;
52
53    dispatch_source_t read_source;
54} asl_redirect_t;
55
56static asl_redirect_t *redirect_descriptors = NULL;
57static int n_redirect_descriptors = 0;
58
59/*
60 * Read from the FD until there is no more to read and redirect to ASL.
61 * Preconditions:
62 *      1: called from the appropriate serial queue for operating on
63 *         redirect_descriptors
64 *      2: descriptor corresponds to a valid entry in redirect_descriptors
65 *
66 * Return values:
67 *      If the pipe is closed, EOF is returned regardless of how many bytes
68 *      were processed.  If the pipe is still open, the number of read bytes
69 *      is returned.
70 */
71static inline int
72_read_redirect(int descriptor, int flush)
73{
74    int total_read = 0;
75    int nbytes;
76    asl_redirect_t *aslr = &redirect_descriptors[descriptor];
77
78    while ((nbytes = read(descriptor, aslr->w, BUF_SIZE - (aslr->w - aslr->buf) - 1)) > 0)
79	{
80        char *s, *p;
81
82        /* Increment our returned number read */
83        total_read += nbytes;
84
85        /* Increment our write location */
86        aslr->w += nbytes;
87        aslr->w[0] = '\0';
88
89        /* One line at a time */
90        for (p = aslr->buf; p < aslr->w; p = s + 1)
91		{
92            /* Find null or \n */
93            for (s = p; *s && *s != '\n'; s++);
94
95            if (*s == '\n') *s='\0';
96
97            if (s < aslr->w || aslr->buf == p)
98			{
99                /* Either the first of multiple messages or one message which is larger than our buffer */
100                asl_log((aslclient)aslr->asl, (aslmsg)aslr->msg, aslr->level, "%s", p);
101            }
102			else
103			{
104                /* We reached the end of the buffer, move this chunk to the start. */
105                memmove(aslr->buf, p, BUF_SIZE - (p - aslr->buf));
106                aslr->w = aslr->buf + (s - p);
107                break;
108            }
109        }
110
111        if (p == aslr->w)
112		{
113            /* Start writing at the beginning in the case where we cleared the buffer */
114            aslr->w = aslr->buf;
115        }
116    }
117
118    /* Flush if requested or we're at EOF */
119    if (flush || nbytes == 0)
120	{
121        if (aslr->w > aslr->buf)
122		{
123            *aslr->w = '\0';
124            asl_log((aslclient)aslr->asl, (aslmsg)aslr->msg, aslr->level, "%s", aslr->buf);
125        }
126    }
127
128    if (nbytes == 0) return EOF;
129    return total_read;
130}
131
132static void
133read_from_source(void *_source)
134{
135    dispatch_source_t source = (dispatch_source_t)_source;
136    int descriptor = dispatch_source_get_handle(source);
137    if (_read_redirect(descriptor, 0) == EOF)  dispatch_source_cancel(source);
138}
139
140static void
141cancel_source(void *_source)
142{
143    dispatch_source_t source = (dispatch_source_t)_source;
144    int descriptor = dispatch_source_get_handle(source);
145    asl_redirect_t *aslr = &redirect_descriptors[descriptor];
146
147    /* Flush the buffer */
148    _read_redirect(descriptor, 1);
149
150    close(descriptor);
151
152    asl_client_release(aslr->asl);
153    asl_msg_release(aslr->msg);
154    free(aslr->buf);
155
156    memset(aslr, 0, sizeof(*aslr));
157    dispatch_release(source);
158    dispatch_group_leave(read_source_group);
159}
160
161__attribute__ ((visibility ("hidden")))
162void
163_asl_redirect_fork_child(void)
164{
165    if (redirect_descriptors) {
166        free(redirect_descriptors);
167        n_redirect_descriptors = 0;
168        redirect_descriptors = NULL;
169    }
170}
171
172static void
173redirect_atexit(void)
174{
175    int i;
176
177    /* stdout is linebuffered, so flush the buffer */
178    if (redirect_descriptors[STDOUT_FILENO].buf) fflush(stdout);
179
180    /* Cancel all of our dispatch sources, so they flush to ASL */
181    for (i = 0; i < n_redirect_descriptors; i++) {
182        if (redirect_descriptors[i].read_source)
183            dispatch_source_cancel(redirect_descriptors[i].read_source);
184    }
185
186    /* Wait at least three seconds for our sources to flush to ASL */
187    dispatch_group_wait(read_source_group, dispatch_time(DISPATCH_TIME_NOW, 3LL * NSEC_PER_SEC));
188}
189
190static void
191asl_descriptor_init(void *ctx __unused)
192{
193    assert((redirect_descriptors = calloc(16, sizeof(*redirect_descriptors))) != NULL);
194    n_redirect_descriptors = 16;
195
196    redirect_serial_q = dispatch_queue_create("com.apple.asl-redirect", NULL);
197    assert(redirect_serial_q != NULL);
198
199    read_source_group = dispatch_group_create();
200    assert(read_source_group != NULL);
201
202    atexit(redirect_atexit);
203}
204
205static int
206asl_log_from_descriptor(aslclient ac, aslmsg am, int level, int descriptor)
207{
208    int err __block = 0;
209    static dispatch_once_t once_control;
210    dispatch_once_f(&once_control, NULL, asl_descriptor_init);
211    asl_client_t *asl = (asl_client_t *)ac;
212    asl_msg_t *msg = (asl_msg_t *)am;
213
214    if (descriptor < 0) return EBADF;
215
216    if (msg != NULL)
217	{
218        msg = asl_msg_copy(msg);
219        if (msg == NULL) return ENOMEM;
220    }
221
222    dispatch_sync(redirect_serial_q, ^{
223        dispatch_source_t read_source;
224
225        /* Reallocate if we need more space */
226        if (descriptor >= n_redirect_descriptors)
227		{
228            size_t new_n = 1 << (fls(descriptor) + 1);
229            asl_redirect_t *new_array = realloc(redirect_descriptors, new_n * sizeof(*redirect_descriptors));
230            if (!new_array)
231			{
232                err = errno;
233                return;
234            }
235
236            redirect_descriptors = new_array;
237            memset(redirect_descriptors + n_redirect_descriptors, 0, (new_n - n_redirect_descriptors) * sizeof(*redirect_descriptors));
238            n_redirect_descriptors = new_n;
239        }
240
241        /* If we're already listening on it, return error. */
242        if (redirect_descriptors[descriptor].buf != NULL)
243		{
244            err = EBADF;
245            return;
246        }
247
248        /* Initialize our buffer */
249        redirect_descriptors[descriptor].buf = (char *)malloc(BUF_SIZE);
250        if (redirect_descriptors[descriptor].buf == NULL)
251		{
252            err = errno;
253            return;
254        }
255
256        redirect_descriptors[descriptor].w = redirect_descriptors[descriptor].buf;
257
258        /* Store our ASL settings */
259        redirect_descriptors[descriptor].level = level;
260        redirect_descriptors[descriptor].asl = asl_client_retain(asl);
261        redirect_descriptors[descriptor].msg = msg;
262
263        /* Don't block on reads from this descriptor */
264        (void)fcntl(descriptor, F_SETFL, O_NONBLOCK);
265
266        /* Start listening */
267        read_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, descriptor, 0, redirect_serial_q);
268        redirect_descriptors[descriptor].read_source = read_source;
269        dispatch_set_context(read_source, read_source);
270        dispatch_source_set_event_handler_f(read_source, read_from_source);
271        dispatch_source_set_cancel_handler_f(read_source, cancel_source);
272        dispatch_group_enter(read_source_group);
273        dispatch_resume(read_source);
274    });
275
276    if (err) asl_msg_release(msg);
277
278    return err;
279}
280
281int
282asl_log_descriptor(aslclient ac, aslmsg am, int level, int descriptor, uint32_t fd_type)
283{
284    int pipepair[2];
285    int retval;
286    int oerrno = errno;
287
288    if (fd_type == ASL_LOG_DESCRIPTOR_READ) return asl_log_from_descriptor(ac, am, level, descriptor);
289
290    assert(fd_type == ASL_LOG_DESCRIPTOR_WRITE);
291
292    /* Create pipe */
293    if (pipe(pipepair) == -1)
294	{
295        retval = errno;
296        errno = oerrno;
297        return retval;
298    }
299
300    /* Close the read descriptor but not the write descriptor on exec */
301    if (fcntl(pipepair[0], F_SETFD, FD_CLOEXEC) == -1)
302	{
303        retval = errno;
304        errno = oerrno;
305        return retval;
306    }
307
308    /* Replace the existing descriptor */
309    if (dup2(pipepair[1], descriptor) == -1)
310	{
311        close(pipepair[0]);
312        close(pipepair[1]);
313        retval = errno;
314        errno = oerrno;
315        return retval;
316    }
317
318    /* If we capture STDOUT_FILENO, make sure we linebuffer stdout */
319    if (descriptor == STDOUT_FILENO) setlinebuf(stdout);
320
321    /* Close the duplicate descriptors since they've been reassigned */
322    close(pipepair[1]);
323
324    /* Hand off the read end of our pipe to asl_log_descriptor */
325    return asl_log_from_descriptor(ac, am, level, pipepair[0]);
326}
327