]>
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 | ||
1a9403b7 A |
83 | /* Increment our write location */ |
84 | aslr->w += nbytes; | |
85 | aslr->w[0] = '\0'; | |
ad3c9f2a A |
86 | |
87 | /* One line at a time */ | |
1a9403b7 A |
88 | for (p = aslr->buf; p < aslr->w; p = s + 1) { |
89 | /* Find null or \n */ | |
ad3c9f2a | 90 | for (s=p; *s && *s != '\n'; s++); |
1a9403b7 | 91 | |
ad3c9f2a A |
92 | if (*s == '\n') { |
93 | *s='\0'; | |
1a9403b7 A |
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 */ | |
ad3c9f2a | 98 | asl_log((aslclient)aslr->asl, (aslmsg)aslr->msg, aslr->level, "%s", p); |
1a9403b7 A |
99 | } else { |
100 | /* We reached the end of the buffer, move this chunk to the start. */ | |
ad3c9f2a A |
101 | memmove(aslr->buf, p, BUF_SIZE - (p - aslr->buf)); |
102 | aslr->w = aslr->buf + (s - p); | |
103 | break; | |
ad3c9f2a A |
104 | } |
105 | } | |
1a9403b7 A |
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 | } | |
ad3c9f2a A |
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 | ||
126 | static 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 | ||
134 | static 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 | ||
154 | static 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 | ||
170 | static 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 | ||
184 | static 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) { | |
1a9403b7 | 205 | size_t new_n = 1 << (fls(descriptor) + 1); |
ad3c9f2a A |
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; | |
1a9403b7 | 212 | memset(redirect_descriptors + n_redirect_descriptors, 0, (new_n - n_redirect_descriptors) * sizeof(*redirect_descriptors)); |
ad3c9f2a A |
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 | ||
255 | int 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 | } |