]>
Commit | Line | Data |
---|---|---|
ad3c9f2a A |
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 | ||
43 | static dispatch_queue_t redirect_serial_q; | |
44 | static dispatch_group_t read_source_group; | |
45 | ||
46 | typedef 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 | ||
58 | static asl_redirect_t *redirect_descriptors = NULL; | |
59 | static 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 | */ | |
72 | static 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 | nbytes += (aslr->w - aslr->buf); | |
84 | aslr->buf[nbytes] = '\0'; | |
85 | ||
86 | /* One line at a time */ | |
87 | for (p=aslr->buf; *p && (p - aslr->buf) < nbytes; p = s + 1) { | |
88 | // Find null or \n | |
89 | for (s=p; *s && *s != '\n'; s++); | |
90 | if (*s == '\n') { | |
91 | *s='\0'; | |
92 | asl_log((aslclient)aslr->asl, (aslmsg)aslr->msg, aslr->level, "%s", p); | |
93 | } else if (aslr->buf != p) { | |
94 | memmove(aslr->buf, p, BUF_SIZE - (p - aslr->buf)); | |
95 | aslr->w = aslr->buf + (s - p); | |
96 | break; | |
97 | } else if (nbytes == BUF_SIZE - 1) { | |
98 | asl_log((aslclient)aslr->asl, (aslmsg)aslr->msg, aslr->level, "%s", p); | |
99 | aslr->w = aslr->buf; | |
100 | break; | |
101 | } | |
102 | } | |
103 | } | |
104 | ||
105 | /* Flush if requested or we're at EOF */ | |
106 | if (flush || nbytes == 0) { | |
107 | if (aslr->w > aslr->buf) { | |
108 | *aslr->w = '\0'; | |
109 | asl_log((aslclient)aslr->asl, (aslmsg)aslr->msg, aslr->level, "%s", aslr->buf); | |
110 | } | |
111 | } | |
112 | ||
113 | if (nbytes == 0) | |
114 | return EOF; | |
115 | return total_read; | |
116 | } | |
117 | ||
118 | static void read_from_source(void *_source) { | |
119 | dispatch_source_t source = (dispatch_source_t)_source; | |
120 | int descriptor = dispatch_source_get_handle(source); | |
121 | if (_read_redirect(descriptor, 0) == EOF) { | |
122 | dispatch_source_cancel(source); | |
123 | } | |
124 | } | |
125 | ||
126 | static void cancel_source(void *_source) { | |
127 | dispatch_source_t source = (dispatch_source_t)_source; | |
128 | int descriptor = dispatch_source_get_handle(source); | |
129 | asl_redirect_t *aslr = &redirect_descriptors[descriptor]; | |
130 | ||
131 | /* Flush the buffer */ | |
132 | _read_redirect(descriptor, 1); | |
133 | ||
134 | close(descriptor); | |
135 | ||
136 | asl_client_release(aslr->asl); | |
137 | asl_msg_release(aslr->msg); | |
138 | free(aslr->buf); | |
139 | ||
140 | memset(aslr, 0, sizeof(*aslr)); | |
141 | dispatch_release(source); | |
142 | dispatch_group_leave(read_source_group); | |
143 | } | |
144 | ||
145 | ||
146 | static void redirect_atexit(void) { | |
147 | int i; | |
148 | ||
149 | /* stdout is linebuffered, so flush the buffer */ | |
150 | if (redirect_descriptors[STDOUT_FILENO].buf) | |
151 | fflush(stdout); | |
152 | ||
153 | /* Cancel all of our dispatch sources, so they flush to ASL */ | |
154 | for (i=0; i < n_redirect_descriptors; i++) | |
155 | if (redirect_descriptors[i].read_source) | |
156 | dispatch_source_cancel(redirect_descriptors[i].read_source); | |
157 | ||
158 | /* Wait at least three seconds for our sources to flush to ASL */ | |
159 | dispatch_group_wait(read_source_group, dispatch_time(DISPATCH_TIME_NOW, 3LL * NSEC_PER_SEC)); | |
160 | } | |
161 | ||
162 | static void asl_descriptor_init(void *ctx __unused) | |
163 | { | |
164 | assert((redirect_descriptors = calloc(16, sizeof(*redirect_descriptors))) != NULL); | |
165 | n_redirect_descriptors = 16; | |
166 | ||
167 | redirect_serial_q = dispatch_queue_create("com.apple.asl-redirect", NULL); | |
168 | assert(redirect_serial_q != NULL); | |
169 | ||
170 | read_source_group = dispatch_group_create(); | |
171 | assert(read_source_group != NULL); | |
172 | ||
173 | atexit(redirect_atexit); | |
174 | } | |
175 | ||
176 | static int asl_log_from_descriptor(aslclient ac, aslmsg am, int level, int descriptor) { | |
177 | int err __block = 0; | |
178 | static dispatch_once_t once_control; | |
179 | dispatch_once_f(&once_control, NULL, asl_descriptor_init); | |
180 | asl_client_t *asl = (asl_client_t *)ac; | |
181 | asl_msg_t *msg = (asl_msg_t *)am; | |
182 | ||
183 | if (descriptor < 0) | |
184 | return EBADF; | |
185 | ||
186 | if (msg != NULL) { | |
187 | msg = asl_msg_copy(msg); | |
188 | if (msg == NULL) | |
189 | return ENOMEM; | |
190 | } | |
191 | ||
192 | dispatch_sync(redirect_serial_q, ^{ | |
193 | dispatch_source_t read_source; | |
194 | ||
195 | /* Reallocate if we need more space */ | |
196 | if (descriptor >= n_redirect_descriptors) { | |
197 | size_t new_n = 1 << (ffs(descriptor) + 1); | |
198 | asl_redirect_t *new_array = realloc(redirect_descriptors, new_n * sizeof(*redirect_descriptors)); | |
199 | if (!new_array) { | |
200 | err = errno; | |
201 | return; | |
202 | } | |
203 | redirect_descriptors = new_array; | |
204 | memset(redirect_descriptors + n_redirect_descriptors, 0, new_n - n_redirect_descriptors); | |
205 | n_redirect_descriptors = new_n; | |
206 | } | |
207 | ||
208 | /* If we're already listening on it, return error. */ | |
209 | if (redirect_descriptors[descriptor].buf != NULL) { | |
210 | err = EBADF; | |
211 | return; | |
212 | } | |
213 | ||
214 | /* Initialize our buffer */ | |
215 | redirect_descriptors[descriptor].buf = (char *)malloc(BUF_SIZE); | |
216 | if (redirect_descriptors[descriptor].buf == NULL) { | |
217 | err = errno; | |
218 | return; | |
219 | } | |
220 | redirect_descriptors[descriptor].w = redirect_descriptors[descriptor].buf; | |
221 | ||
222 | /* Store our ASL settings */ | |
223 | redirect_descriptors[descriptor].level = level; | |
224 | redirect_descriptors[descriptor].asl = asl_client_retain(asl); | |
225 | redirect_descriptors[descriptor].msg = msg; | |
226 | ||
227 | /* Don't block on reads from this descriptor */ | |
228 | (void)fcntl(descriptor, F_SETFL, O_NONBLOCK); | |
229 | ||
230 | /* Start listening */ | |
231 | read_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, descriptor, 0, redirect_serial_q); | |
232 | redirect_descriptors[descriptor].read_source = read_source; | |
233 | dispatch_set_context(read_source, read_source); | |
234 | dispatch_source_set_event_handler_f(read_source, read_from_source); | |
235 | dispatch_source_set_cancel_handler_f(read_source, cancel_source); | |
236 | dispatch_group_enter(read_source_group); | |
237 | dispatch_resume(read_source); | |
238 | }); | |
239 | ||
240 | if (err) { | |
241 | asl_msg_release(msg); | |
242 | } | |
243 | ||
244 | return err; | |
245 | } | |
246 | ||
247 | int asl_log_descriptor(aslclient ac, aslmsg am, int level, int descriptor, uint32_t fd_type) { | |
248 | int pipepair[2]; | |
249 | int retval; | |
250 | int oerrno = errno; | |
251 | ||
252 | if (fd_type == ASL_LOG_DESCRIPTOR_READ) | |
253 | return asl_log_from_descriptor(ac, am, level, descriptor); | |
254 | ||
255 | assert(fd_type == ASL_LOG_DESCRIPTOR_WRITE); | |
256 | ||
257 | /* Create pipe */ | |
258 | if (pipe(pipepair) == -1) { | |
259 | retval = errno; | |
260 | errno = oerrno; | |
261 | return retval; | |
262 | } | |
263 | ||
264 | /* Close the read descriptor but not the write descriptor on exec */ | |
265 | if (fcntl(pipepair[0], F_SETFD, FD_CLOEXEC) == -1) { | |
266 | retval = errno; | |
267 | errno = oerrno; | |
268 | return retval; | |
269 | } | |
270 | ||
271 | /* Replace the existing descriptor */ | |
272 | if (dup2(pipepair[1], descriptor) == -1) { | |
273 | close(pipepair[0]); | |
274 | close(pipepair[1]); | |
275 | retval = errno; | |
276 | errno = oerrno; | |
277 | return retval; | |
278 | } | |
279 | ||
280 | /* If we capture STDOUT_FILENO, make sure we linebuffer stdout */ | |
281 | if (descriptor == STDOUT_FILENO) | |
282 | setlinebuf(stdout); | |
283 | ||
284 | /* Close the duplicate descriptors since they've been reassigned */ | |
285 | close(pipepair[1]); | |
286 | ||
287 | /* Hand off the read end of our pipe to asl_log_descriptor */ | |
288 | return asl_log_from_descriptor(ac, am, level, pipepair[0]); | |
289 | } |