]> git.saurik.com Git - apple/securityd.git/blob - src/child.cpp
27b9debf25e2014e1978251b35cb33d8ae37b585
[apple/securityd.git] / src / child.cpp
1 /*
2 * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
7 *
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * file.
14 *
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
22 *
23 * @APPLE_LICENSE_HEADER_END@
24 */
25
26
27 //
28 // child - track a single child process and its belongings
29 //
30 #include "child.h"
31 #include <sys/wait.h>
32 #include <security_utilities/debugging.h>
33 #include <pthread.h>
34
35 kern_return_t
36 ucsp_server_handleSignal(mach_port_t sport,
37 mach_port_t task_port,
38 int signal_number)
39 {
40 try {
41 if (task_port != mach_task_self()) {
42 Syslog::error("handleSignal: recieved from someone other than myself");
43 } else {
44 ChildManager::childManager.handleSignal(signal_number);
45 }
46 } catch(...) {}
47 mach_port_deallocate(mach_task_self(), task_port);
48
49 return 0;
50 }
51
52 kern_return_t
53 ucsp_server_registerChild(mach_port_t sport,
54 mach_port_t rport,
55 mach_port_t task_port)
56 {
57 mach_port_t childPort = rport;
58 try {
59 pid_t pid;
60 kern_return_t kt = pid_for_task(task_port, &pid);
61 if (kt) {
62 Syslog::error("registerChild: pid_for_task returned: %d", kt);
63 } else {
64 ChildManager::childManager.registerChild(pid, childPort);
65 }
66 } catch(...) {}
67
68 if (childPort) {
69 // Dealloc the childPort unless registerChild set it to zero, which indicates it took over ownership.
70 mach_port_deallocate(mach_task_self(), childPort);
71 }
72
73 if (task_port)
74 mach_port_deallocate(mach_task_self(), task_port);
75
76 return 0;
77 }
78
79
80 //
81 // A Child object represents a UNIX process that was forked by us
82 // and may have some state associated with it.
83 // Although some children may be created per session (such as SecurityAgent instances)
84 // in general child processes are PerGlobal.
85 //
86 class Child
87 {
88 public:
89 Child(pid_t pid);
90 ~Child();
91
92 void waitStatus(int status);
93 int waitStatus() const { return mStatus; }
94
95 void childPort(mach_port_t &childPort);
96
97 // Return our childPort and transfer ownership of it.
98 mach_port_t childPort() { mach_port_t childPort = mChildPort; mChildPort = 0; return childPort; }
99
100 void eraseFromMap();
101 void insertInMap();
102
103 // Wait for the child to register or die. If it registered mChildPort will be non zero, it will be 0 if it didn't.
104 void waitForRegister();
105
106 private:
107 Mutex mLockInternal;
108 StLock<Mutex> mLock;
109 pid_t mPid;
110
111 // Service port for this child.
112 // This should only be nonzero while this object owns the port. Once it is retrieved ownership is handed off.
113 mach_port_t mChildPort;
114
115 int mStatus; // Exit status if child died.
116 bool mInMap;
117 };
118
119
120
121 //
122 // Construct a Child object.
123 //
124 Child::Child(pid_t pid) :
125 mLock(mLockInternal), mPid(pid), mChildPort(0), mStatus(0), mInMap(false)
126 {
127 }
128
129 Child::~Child()
130 {
131 try {
132 eraseFromMap();
133 if (mChildPort) {
134 // Dealloc our mChildPort someone else took over ownership.
135 mach_port_deallocate(mach_task_self(), mChildPort);
136 }
137 } catch (...) {}
138 }
139
140 void
141 Child::eraseFromMap()
142 {
143 if (mInMap)
144 ChildManager::childManager.eraseChild(mPid);
145 }
146
147 void
148 Child::insertInMap()
149 {
150 ChildManager::childManager.insertChild(mPid, this);
151 mInMap = true;
152 }
153
154 void
155 Child::waitStatus(int status)
156 {
157 mStatus = status;
158 mLock.unlock(); // Unlock the lock to unblock waitForRegister()
159 }
160
161 void
162 Child::childPort(mach_port_t &childPort)
163 {
164 mChildPort = childPort;
165 childPort = 0; // Tell our caller that we consumed the child port.
166
167 mLock.unlock(); // Unlock the lock to unblock waitForRegister()
168 }
169
170 void
171 Child::waitForRegister()
172 {
173 // Try to lock the lock again (this won't succeed until we get a register or wait result).
174 mLock.lock();
175 // Unlock as soon as we get the lock.
176 mLock.unlock();
177 }
178
179
180 //
181 // ChildManager - Singleton Child Manager class
182 //
183
184 // The signleton ChildManager.
185 ChildManager ChildManager::childManager;
186
187 ChildManager::ChildManager()
188 {
189 }
190
191 ChildManager::~ChildManager()
192 {
193 }
194
195 bool
196 ChildManager::forkChild(mach_port_t &outChildPort, int &outWaitStatus)
197 {
198 StLock<Mutex> lock(mLock, false);
199 pid_t pid;
200
201 /* Retry fork 10 times on failure after that just give up. */
202 for (int tries = 0; tries < 10; ++tries)
203 {
204 lock.lock(); // aquire the lock
205 pid = fork();
206 if (pid != pid_t(-1))
207 break;
208
209 /* Something went wrong. */
210 lock.unlock(); // Release the lock so we aren't holding it during the usleep below.
211
212 int err = errno;
213 if (err == EINTR)
214 continue;
215
216 if (err == EAGAIN || err == ENOMEM)
217 usleep(100 * tries);
218 else
219 UnixError::throwMe(err);
220 }
221
222 if (pid == 0)
223 {
224 // Child - return true.
225 return true;
226 }
227 else
228 {
229 Child child(pid);
230 child.insertInMap(); // Insert child into the map.
231 lock.unlock(); // Unlock as soon as we are done accessing mChildMap.
232
233 child.waitForRegister();
234
235 // Remove child from the map.
236 child.eraseFromMap();
237
238 // Transfer ownership of child's childPort to our caller.
239 outChildPort = child.childPort();
240
241 if (!outChildPort)
242 outWaitStatus = child.waitStatus();
243
244 // Parent - return false
245 return false;
246 }
247 }
248
249 void
250 ChildManager::handleSignal(int signal_number)
251 {
252 if (signal_number == SIGCHLD)
253 handleSigChild();
254 }
255
256 void
257 ChildManager::registerChild(pid_t pid, mach_port_t &childPort)
258 {
259 StLock<Mutex> _(mLock);
260 Child *child = findChild(pid);
261 if (child)
262 child->childPort(childPort);
263 }
264
265 // Assumes mLock is not locked.
266 void
267 ChildManager::eraseChild(pid_t pid)
268 {
269 StLock<Mutex> _(mLock);
270 mChildMap.erase(pid);
271 }
272
273 // Assumes mLock is already locked.
274 void
275 ChildManager::insertChild(pid_t pid, Child *child)
276 {
277 mChildMap[pid] = child;
278 }
279
280
281 //
282 // Private functions
283 //
284
285 Child *
286 ChildManager::findChild(pid_t pid)
287 {
288 ChildMap::iterator it = mChildMap.find(pid);
289 if (it == mChildMap.end())
290 return NULL;
291
292 return it->second;
293 }
294
295 void
296 ChildManager::handleSigChild()
297 {
298 for (int tries = 0; tries < 10; ++tries)
299 {
300 int status;
301 pid_t pid = waitpid(-1, &status, WNOHANG|WUNTRACED);
302 switch (pid)
303 {
304 case 0:
305 if (tries == 0)
306 Syslog::notice("Spurious SIGCHLD ignored");
307 break;
308 case -1:
309 {
310 int err = errno;
311 if (err == ECHILD)
312 {
313 if (tries == 0)
314 Syslog::notice("Spurious SIGCHLD ignored");
315 break;
316 }
317
318 if (err != EINTR)
319 {
320 Syslog::error("waitpid after SIGCHLD failed: %s", strerror(err));
321 break;
322 }
323 }
324 default:
325 if (WIFEXITED(status) || WIFSIGNALED(status))
326 {
327 if (WIFEXITED(status))
328 secdebug("child", "child with pid: %d exited: %d", pid, WEXITSTATUS(status));
329 else if (WCOREDUMP(status))
330 secdebug("child", "child with pid: %d terminated by signal: %d and dumped core", pid, WTERMSIG(status));
331 else
332 secdebug("child", "child with pid: %d terminated by signal: %d", pid, WTERMSIG(status));
333
334 waitStatusForPid(pid, status);
335 }
336 else if (WSTOPSIG(status))
337 {
338 secdebug("child", "child with pid: %d stopped by signal: %d", pid, WSTOPSIG(status));
339 }
340 else
341 {
342 Syslog::error("child with pid: %d bogus waitpid status: %d", pid, status);
343 }
344
345 tries = 1;
346 }
347 }
348 }
349
350 void
351 ChildManager::waitStatusForPid(pid_t pid, int status)
352 {
353 StLock<Mutex> _(mLock);
354 Child *child = findChild(pid);
355 if (child)
356 child->waitStatus(status);
357 }