]>
Commit | Line | Data |
---|---|---|
e91b9f68 A |
1 | /** |
2 | * IPC.c - System Starter IPC routines | |
3 | * Wilfredo Sanchez | wsanchez@opensource.apple.com | |
4 | * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu | |
5 | * $Apple$ | |
6 | ** | |
7 | * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved. | |
8 | * | |
9 | * @APPLE_LICENSE_HEADER_START@ | |
10 | * | |
11 | * Portions Copyright (c) 1999 Apple Computer, Inc. All Rights | |
12 | * Reserved. This file contains Original Code and/or Modifications of | |
13 | * Original Code as defined in and that are subject to the Apple Public | |
14 | * Source License Version 1.1 (the "License"). You may not use this file | |
15 | * except in compliance with the License. Please obtain a copy of the | |
16 | * License at http://www.apple.com/publicsource and read it before using | |
17 | * this file. | |
18 | * | |
19 | * The Original Code and all software distributed under the License are | |
20 | * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
21 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
22 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
23 | * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the | |
24 | * License for the specific language governing rights and limitations | |
25 | * under the License. | |
26 | * | |
27 | * @APPLE_LICENSE_HEADER_END@ | |
28 | **/ | |
29 | ||
30 | #include <sys/wait.h> | |
31 | #include <mach/mach.h> | |
32 | #include <mach/message.h> | |
33 | #include <mach/mach_error.h> | |
34 | #include <servers/bootstrap.h> | |
35 | #include <CoreFoundation/CoreFoundation.h> | |
36 | #include <syslog.h> | |
37 | #include "IPC.h" | |
38 | #include "StartupItems.h" | |
39 | #include "SystemStarter.h" | |
40 | #include "SystemStarterIPC.h" | |
41 | ||
42 | /* Structure to pass StartupContext and anItem to the termination handler. */ | |
43 | typedef struct TerminationContextStorage { | |
44 | StartupContext aStartupContext; | |
45 | CFMutableDictionaryRef anItem; | |
46 | } *TerminationContext; | |
47 | ||
48 | /** | |
49 | * A CFMachPort invalidation callback that records the termination of | |
50 | * a startup item task. Stops the current run loop to give system_starter | |
51 | * another go at running items. | |
52 | **/ | |
53 | static void | |
54 | startupItemTerminated(CFMachPortRef aMachPort, void *anInfo) | |
55 | { | |
56 | TerminationContext aTerminationContext = (TerminationContext) anInfo; | |
57 | ||
58 | if (aMachPort) { | |
59 | mach_port_deallocate(mach_task_self(), CFMachPortGetPort(aMachPort)); | |
60 | } | |
61 | if (aTerminationContext && aTerminationContext->anItem) { | |
62 | pid_t aPID = 0; | |
63 | pid_t rPID = 0; | |
64 | int aStatus = 0; | |
65 | CFMutableDictionaryRef anItem = aTerminationContext->anItem; | |
66 | StartupContext aStartupContext = aTerminationContext->aStartupContext; | |
67 | ||
68 | /* Get the exit status */ | |
69 | if (anItem) { | |
70 | aPID = StartupItemGetPID(anItem); | |
71 | if (aPID > 0) | |
72 | rPID = waitpid(aPID, &aStatus, 0); | |
73 | } | |
74 | if (aStartupContext) { | |
75 | --aStartupContext->aRunningCount; | |
76 | ||
77 | /* Record the item's status */ | |
78 | if (aStartupContext->aStatusDict) { | |
79 | StartupItemExit(aStartupContext->aStatusDict, anItem, (WIFEXITED(aStatus) && WEXITSTATUS(aStatus) == 0)); | |
80 | if (aStatus) { | |
81 | CF_syslog(LOG_WARNING, CFSTR("%@ (%d) did not complete successfully"), CFDictionaryGetValue(anItem, CFSTR("Description")), aPID); | |
82 | } else { | |
83 | CF_syslog(LOG_DEBUG, CFSTR("Finished %@ (%d)"), CFDictionaryGetValue(anItem, CFSTR("Description")), aPID); | |
84 | } | |
85 | } | |
86 | /* | |
87 | * If the item failed to start, then add it to the | |
88 | * failed list | |
89 | */ | |
90 | if (WEXITSTATUS(aStatus) || WTERMSIG(aStatus) || WCOREDUMP(aStatus)) { | |
91 | CFDictionarySetValue(anItem, kErrorKey, kErrorReturnNonZero); | |
92 | AddItemToFailedList(aStartupContext, anItem); | |
93 | } | |
94 | /* | |
95 | * Remove the item from the waiting list regardless | |
96 | * if it was successful or it failed. | |
97 | */ | |
98 | RemoveItemFromWaitingList(aStartupContext, anItem); | |
99 | } | |
100 | } | |
101 | if (aTerminationContext) | |
102 | free(aTerminationContext); | |
103 | } | |
104 | ||
105 | void | |
106 | MonitorStartupItem(StartupContext aStartupContext, CFMutableDictionaryRef anItem) | |
107 | { | |
108 | pid_t aPID = StartupItemGetPID(anItem); | |
109 | if (anItem && aPID > 0) { | |
110 | mach_port_t aPort; | |
111 | kern_return_t aResult; | |
112 | CFMachPortContext aContext; | |
113 | CFMachPortRef aMachPort; | |
114 | CFRunLoopSourceRef aSource; | |
115 | TerminationContext aTerminationContext = (TerminationContext) malloc(sizeof(struct TerminationContextStorage)); | |
116 | ||
117 | aTerminationContext->aStartupContext = aStartupContext; | |
118 | aTerminationContext->anItem = anItem; | |
119 | ||
120 | aContext.version = 0; | |
121 | aContext.info = aTerminationContext; | |
122 | aContext.retain = 0; | |
123 | aContext.release = 0; | |
124 | ||
125 | if ((aResult = task_for_pid(mach_task_self(), aPID, &aPort)) != KERN_SUCCESS) | |
126 | goto out_bad; | |
127 | ||
128 | if (!(aMachPort = CFMachPortCreateWithPort(NULL, aPort, NULL, &aContext, NULL))) | |
129 | goto out_bad; | |
130 | ||
131 | if (!(aSource = CFMachPortCreateRunLoopSource(NULL, aMachPort, 0))) { | |
132 | CFRelease(aMachPort); | |
133 | goto out_bad; | |
134 | } | |
135 | CFMachPortSetInvalidationCallBack(aMachPort, startupItemTerminated); | |
136 | CFRunLoopAddSource(CFRunLoopGetCurrent(), aSource, kCFRunLoopCommonModes); | |
137 | CFRelease(aSource); | |
138 | CFRelease(aMachPort); | |
139 | return; | |
140 | out_bad: | |
141 | /* | |
142 | * The assumption is something failed, the task already | |
143 | * terminated. | |
144 | */ | |
145 | startupItemTerminated(NULL, aTerminationContext); | |
146 | } | |
147 | } | |
148 | ||
149 | /** | |
150 | * Returns a reference to an item based on tokens passed in an IPC message. | |
151 | * This is useful for figuring out which item the message came from. | |
152 | * | |
153 | * Currently two tokens are supported: | |
154 | * kIPCProcessIDKey - the pid of the running startup script | |
155 | * kIPCServiceNameKey - a name of a service that the item provides. This | |
156 | * takes precedence over the pid key when both are present. | |
157 | **/ | |
158 | static CFMutableDictionaryRef | |
159 | itemFromIPCMessage(StartupContext aStartupContext, CFDictionaryRef anIPCMessage) | |
160 | { | |
161 | CFMutableDictionaryRef anItem = NULL; | |
162 | ||
163 | if (aStartupContext && anIPCMessage) { | |
164 | CFStringRef aServiceName = CFDictionaryGetValue(anIPCMessage, kIPCServiceNameKey); | |
165 | CFIndex aPID = 0; | |
166 | CFNumberRef aPIDNumber = CFDictionaryGetValue(anIPCMessage, kIPCProcessIDKey); | |
167 | ||
168 | if (aServiceName && CFGetTypeID(aServiceName) == CFStringGetTypeID()) { | |
169 | anItem = StartupItemListGetProvider(aStartupContext->aWaitingList, aServiceName); | |
170 | } else if (aPIDNumber && | |
171 | CFGetTypeID(aPIDNumber) == CFNumberGetTypeID() && | |
172 | CFNumberGetValue(aPIDNumber, kCFNumberCFIndexType, &aPID)) { | |
173 | anItem = StartupItemWithPID(aStartupContext->aWaitingList, aPID); | |
174 | } | |
175 | } | |
176 | return anItem; | |
177 | } | |
178 | ||
179 | /** | |
180 | * Displays a message on the console. | |
181 | * aConsoleMessage will be localized according to the dictionary in the specified item. | |
182 | * Running tems may be specified by their current process id. Items may also be specified | |
183 | * by one of the service names they provide. | |
184 | **/ | |
185 | static void | |
186 | consoleMessage(StartupContext aStartupContext, CFDictionaryRef anIPCMessage) | |
187 | { | |
188 | if (aStartupContext && anIPCMessage) { | |
189 | CFStringRef aConsoleMessage = CFDictionaryGetValue(anIPCMessage, kIPCConsoleMessageKey); | |
190 | ||
191 | if (aConsoleMessage && CFGetTypeID(aConsoleMessage) == CFStringGetTypeID()) { | |
192 | CF_syslog(LOG_INFO, CFSTR("%@"), aConsoleMessage); | |
193 | } | |
194 | } | |
195 | } | |
196 | ||
197 | /** | |
198 | * Records the success or failure or a particular service. | |
199 | * If no service name is specified, but a pid is, then all services provided | |
200 | * by the item are flagged. | |
201 | **/ | |
202 | static void | |
203 | statusMessage(StartupContext aStartupContext, CFDictionaryRef anIPCMessage) | |
204 | { | |
205 | if (anIPCMessage && aStartupContext && aStartupContext->aStatusDict) { | |
206 | CFMutableDictionaryRef anItem = itemFromIPCMessage(aStartupContext, anIPCMessage); | |
207 | CFStringRef aServiceName = CFDictionaryGetValue(anIPCMessage, kIPCServiceNameKey); | |
208 | CFBooleanRef aStatus = CFDictionaryGetValue(anIPCMessage, kIPCStatusKey); | |
209 | ||
210 | if (anItem && aStatus && | |
211 | CFGetTypeID(aStatus) == CFBooleanGetTypeID() && | |
212 | (!aServiceName || CFGetTypeID(aServiceName) == CFStringGetTypeID())) { | |
213 | StartupItemSetStatus(aStartupContext->aStatusDict, anItem, aServiceName, CFBooleanGetValue(aStatus), TRUE); | |
214 | } | |
215 | } | |
216 | } | |
217 | ||
218 | /** | |
219 | * Queries one of several configuration settings. | |
220 | */ | |
221 | static CFDataRef | |
222 | queryConfigSetting(StartupContext aStartupContext, CFDictionaryRef anIPCMessage) | |
223 | { | |
224 | char *aValue = ""; | |
225 | ||
226 | if (anIPCMessage) { | |
227 | CFStringRef aSetting = CFDictionaryGetValue(anIPCMessage, kIPCConfigSettingKey); | |
228 | ||
229 | if (aSetting && CFGetTypeID(aSetting) == CFStringGetTypeID()) { | |
230 | if (CFEqual(aSetting, kIPCConfigSettingVerboseFlag)) { | |
231 | aValue = gVerboseFlag ? "-YES-" : "-NO-"; | |
232 | } else if (CFEqual(aSetting, kIPCConfigSettingNetworkUp)) { | |
233 | Boolean aNetworkUpFlag = FALSE; | |
234 | if (aStartupContext && aStartupContext->aStatusDict) { | |
235 | aNetworkUpFlag = CFDictionaryContainsKey(aStartupContext->aStatusDict, CFSTR("Network")); | |
236 | } | |
237 | aValue = aNetworkUpFlag ? "-YES-" : "-NO-"; | |
238 | } | |
239 | } | |
240 | } | |
aa59983a | 241 | return CFDataCreate(NULL, (const UInt8 *)aValue, strlen(aValue) + 1); /* aValue + null */ |
e91b9f68 A |
242 | } |
243 | ||
244 | static void *handleIPCMessage(void *aMsgParam, CFIndex aMessageSize __attribute__((unused)), CFAllocatorRef anAllocator __attribute__((unused)), void *aMachPort) { | |
245 | SystemStarterIPCMessage *aMessage = (SystemStarterIPCMessage *) aMsgParam; | |
246 | SystemStarterIPCMessage *aReplyMessage = NULL; | |
247 | ||
248 | CFDataRef aResult = NULL; | |
249 | CFDataRef aData = NULL; | |
250 | ||
251 | if (aMessage->aHeader.msgh_bits & MACH_MSGH_BITS_COMPLEX) { | |
252 | syslog(LOG_WARNING, "Ignoring out-of-line IPC message"); | |
253 | return NULL; | |
254 | } else { | |
255 | mach_msg_security_trailer_t *aSecurityTrailer = (mach_msg_security_trailer_t *) | |
256 | ((uint8_t *) aMessage + round_msg(sizeof(SystemStarterIPCMessage) + aMessage->aByteLength)); | |
257 | ||
258 | /* | |
259 | * CFRunLoop includes the format 0 message trailer with the | |
260 | * passed message. | |
261 | */ | |
262 | if (aSecurityTrailer->msgh_trailer_type == MACH_MSG_TRAILER_FORMAT_0 && | |
263 | aSecurityTrailer->msgh_sender.val[0] != 0) { | |
264 | syslog(LOG_WARNING, "Ignoring IPC message sent from uid %d", aSecurityTrailer->msgh_sender.val[0]); | |
265 | return NULL; | |
266 | } | |
267 | } | |
268 | ||
269 | if (aMessage->aProtocol != kIPCProtocolVersion) { | |
270 | syslog(LOG_WARNING, "Unsupported IPC protocol version number: %d. Message ignored", aMessage->aProtocol); | |
271 | return NULL; | |
272 | } | |
273 | aData = CFDataCreateWithBytesNoCopy(NULL, | |
274 | (uint8_t *) aMessage + sizeof(SystemStarterIPCMessage), | |
275 | aMessage->aByteLength, | |
276 | kCFAllocatorNull); | |
277 | /* | |
278 | * Dispatch the IPC message. | |
279 | */ | |
280 | if (aData) { | |
281 | StartupContext aStartupContext = NULL; | |
282 | CFStringRef anErrorString = NULL; | |
283 | CFDictionaryRef anIPCMessage = (CFDictionaryRef) CFPropertyListCreateFromXMLData(NULL, aData, kCFPropertyListImmutable, &anErrorString); | |
284 | ||
285 | CF_syslog(LOG_DEBUG, CFSTR("IPC message = %@"), anIPCMessage); | |
286 | ||
287 | if (aMachPort) { | |
288 | CFMachPortContext aMachPortContext; | |
289 | CFMachPortGetContext((CFMachPortRef) aMachPort, &aMachPortContext); | |
290 | aStartupContext = (StartupContext) aMachPortContext.info; | |
291 | } | |
292 | if (anIPCMessage && CFGetTypeID(anIPCMessage) == CFDictionaryGetTypeID()) { | |
293 | /* switch on the type of the IPC message */ | |
294 | CFStringRef anIPCMessageType = CFDictionaryGetValue(anIPCMessage, kIPCMessageKey); | |
295 | if (anIPCMessageType && CFGetTypeID(anIPCMessageType) == CFStringGetTypeID()) { | |
296 | if (CFEqual(anIPCMessageType, kIPCConsoleMessage)) { | |
297 | consoleMessage(aStartupContext, anIPCMessage); | |
298 | } else if (CFEqual(anIPCMessageType, kIPCStatusMessage)) { | |
299 | statusMessage(aStartupContext, anIPCMessage); | |
300 | } else if (CFEqual(anIPCMessageType, kIPCQueryMessage)) { | |
301 | aResult = queryConfigSetting(aStartupContext, anIPCMessage); | |
302 | } | |
303 | } | |
304 | } else { | |
305 | CF_syslog(LOG_ERR, CFSTR("Unable to parse IPC message: %@"), anErrorString); | |
306 | } | |
307 | CFRelease(aData); | |
308 | } else { | |
309 | syslog(LOG_ERR, "Out of memory. Could not allocate space for IPC message"); | |
310 | } | |
311 | ||
312 | /* | |
313 | * Generate a Mach message for the result data. | |
314 | */ | |
315 | if (!aResult) | |
aa59983a | 316 | aResult = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)"", 1, kCFAllocatorNull); |
e91b9f68 A |
317 | if (aResult) { |
318 | CFIndex aDataSize = CFDataGetLength(aResult); | |
319 | CFIndex aReplyMessageSize = round_msg(sizeof(SystemStarterIPCMessage) + aDataSize + 3); | |
320 | aReplyMessage = CFAllocatorAllocate(kCFAllocatorSystemDefault, aReplyMessageSize, 0); | |
321 | if (aReplyMessage) { | |
322 | aReplyMessage->aHeader.msgh_id = -1 * (SInt32) aMessage->aHeader.msgh_id; | |
323 | aReplyMessage->aHeader.msgh_size = aReplyMessageSize; | |
324 | aReplyMessage->aHeader.msgh_remote_port = aMessage->aHeader.msgh_remote_port; | |
325 | aReplyMessage->aHeader.msgh_local_port = MACH_PORT_NULL; | |
326 | aReplyMessage->aHeader.msgh_reserved = 0; | |
327 | aReplyMessage->aHeader.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0); | |
328 | aReplyMessage->aBody.msgh_descriptor_count = 0; | |
329 | aReplyMessage->aProtocol = kIPCProtocolVersion; | |
330 | aReplyMessage->aByteLength = CFDataGetLength(aResult); | |
331 | memmove((uint8_t *) aReplyMessage + sizeof(SystemStarterIPCMessage), | |
332 | CFDataGetBytePtr(aResult), | |
333 | CFDataGetLength(aResult)); | |
334 | } | |
335 | CFRelease(aResult); | |
336 | } | |
337 | if (!aReplyMessage) { | |
338 | syslog(LOG_ERR, "Out of memory. Could not allocate IPC result"); | |
339 | } | |
340 | return aReplyMessage; | |
341 | } | |
342 | ||
343 | ||
344 | static mach_port_t | |
345 | getIPCPort(void *anInfo) | |
346 | { | |
347 | return anInfo ? CFMachPortGetPort((CFMachPortRef) anInfo) : MACH_PORT_NULL; | |
348 | } | |
349 | ||
350 | CFRunLoopSourceRef | |
351 | CreateIPCRunLoopSource(CFStringRef aPortName, StartupContext aStartupContext) | |
352 | { | |
353 | CFRunLoopSourceRef aSource = NULL; | |
354 | CFMachPortRef aMachPort = NULL; | |
355 | CFMachPortContext aContext; | |
356 | kern_return_t aKernReturn = KERN_FAILURE; | |
357 | ||
358 | aContext.version = 0; | |
359 | aContext.info = (void *) aStartupContext; | |
360 | aContext.retain = 0; | |
361 | aContext.release = 0; | |
362 | aContext.copyDescription = 0; | |
363 | aMachPort = CFMachPortCreate(NULL, NULL, &aContext, NULL); | |
364 | ||
365 | if (aMachPort && aPortName) { | |
366 | CFIndex aPortNameLength = CFStringGetLength(aPortName); | |
367 | CFIndex aPortNameSize = CFStringGetMaximumSizeForEncoding(aPortNameLength, kCFStringEncodingUTF8) + 1; | |
aa59983a | 368 | char *aBuffer = CFAllocatorAllocate(NULL, aPortNameSize, 0); |
e91b9f68 A |
369 | if (aBuffer && CFStringGetCString(aPortName, |
370 | aBuffer, | |
371 | aPortNameSize, | |
372 | kCFStringEncodingUTF8)) { | |
373 | mach_port_t aBootstrapPort; | |
374 | task_get_bootstrap_port(mach_task_self(), &aBootstrapPort); | |
375 | aKernReturn = bootstrap_register(aBootstrapPort, aBuffer, CFMachPortGetPort(aMachPort)); | |
376 | } | |
377 | if (aBuffer) | |
378 | CFAllocatorDeallocate(NULL, aBuffer); | |
379 | } | |
380 | if (aMachPort && aKernReturn == KERN_SUCCESS) { | |
381 | CFRunLoopSourceContext1 aSourceContext; | |
382 | aSourceContext.version = 1; | |
383 | aSourceContext.info = aMachPort; | |
384 | aSourceContext.retain = CFRetain; | |
385 | aSourceContext.release = CFRelease; | |
386 | aSourceContext.copyDescription = CFCopyDescription; | |
387 | aSourceContext.equal = CFEqual; | |
388 | aSourceContext.hash = CFHash; | |
389 | aSourceContext.getPort = getIPCPort; | |
390 | aSourceContext.perform = (void *) handleIPCMessage; | |
391 | aSource = CFRunLoopSourceCreate(NULL, 0, (CFRunLoopSourceContext *) & aSourceContext); | |
392 | } | |
393 | if (aMachPort && (!aSource || aKernReturn != KERN_SUCCESS)) { | |
394 | CFMachPortInvalidate(aMachPort); | |
395 | CFRelease(aMachPort); | |
396 | aMachPort = NULL; | |
397 | } | |
398 | return aSource; | |
399 | } |