]> git.saurik.com Git - apple/launchd.git/blob - launchd/src/IPC.c
b8e4dd61f9e9142604dc5b6772daf6b8980e307b
[apple/launchd.git] / launchd / src / IPC.c
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 }
241 return CFDataCreate(NULL, aValue, strlen(aValue) + 1); /* aValue + null */
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)
316 aResult = CFDataCreateWithBytesNoCopy(NULL, "", 1, kCFAllocatorNull);
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;
368 uint8_t *aBuffer = CFAllocatorAllocate(NULL, aPortNameSize, 0);
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 }