]>
Commit | Line | Data |
---|---|---|
bac41a7b A |
1 | /* |
2 | * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. | |
3 | * | |
4 | * The contents of this file constitute Original Code as defined in and are | |
5 | * subject to the Apple Public Source License Version 1.2 (the 'License'). | |
6 | * You may not use this file except in compliance with the License. Please obtain | |
7 | * a copy of the License at http://www.apple.com/publicsource and read it before | |
8 | * using this file. | |
9 | * | |
10 | * This Original Code and all software distributed under the License are | |
11 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS | |
12 | * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT | |
13 | * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | |
14 | * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the | |
15 | * specific language governing rights and limitations under the License. | |
16 | */ | |
17 | ||
18 | ||
19 | // | |
20 | // machserver - C++ shell for writing Mach 3 servers | |
21 | // | |
22 | #include "machserver.h" | |
23 | #include <servers/bootstrap.h> | |
24 | #include <mach/kern_return.h> | |
25 | #include <mach/message.h> | |
26 | #include <mach/mig_errors.h> | |
27 | #include "mach_notify.h" | |
28 | #include <Security/debugging.h> | |
29 | ||
30 | #if defined(USECFCURRENTTIME) | |
31 | # include <CoreFoundation/CFDate.h> | |
32 | #else | |
33 | # include <sys/time.h> | |
34 | #endif | |
35 | ||
36 | namespace Security { | |
37 | namespace MachPlusPlus { | |
38 | ||
39 | ||
40 | // | |
41 | // Global per-thread information | |
42 | // | |
43 | ModuleNexus< ThreadNexus<MachServer::PerThread> > MachServer::thread; | |
44 | ||
45 | ||
46 | // | |
47 | // Create a server object. | |
48 | // The resulting object is not "active", and any number of server objects | |
49 | // can be in this "prepared" state at the same time. | |
50 | // | |
51 | MachServer::MachServer(const char *name) | |
52 | : mServerPort(name, bootstrap) | |
53 | { setup(name); } | |
54 | ||
55 | MachServer::MachServer(const char *name, const Bootstrap &boot) | |
56 | : bootstrap(boot), mServerPort(name, bootstrap) | |
57 | { setup(name); } | |
58 | ||
59 | void MachServer::setup(const char *name) | |
60 | { | |
df0e469f | 61 | secdebug("machsrv", "%p preparing service for \"%s\"", this, name); |
bac41a7b A |
62 | workerTimeout = 60 * 2; // 2 minutes default timeout |
63 | maxWorkerCount = 100; // sanity check limit | |
64 | ||
65 | mPortSet += mServerPort; | |
66 | } | |
67 | ||
68 | MachServer::~MachServer() | |
69 | { | |
70 | // The ReceivePort members will clean themselves up. | |
71 | // The bootstrap server will clear us from its map when our receive port dies. | |
df0e469f | 72 | secdebug("machsrv", "%p destroyed", this); |
bac41a7b A |
73 | } |
74 | ||
75 | ||
76 | // | |
29654253 A |
77 | // Add and remove extra listening ports. |
78 | // Messages directed to those ports are dispatched through the main handler. | |
79 | // To get automatic call-out to another handler, use the Handler class. | |
bac41a7b | 80 | // |
29654253 | 81 | void MachServer::add(Port receiver) |
bac41a7b | 82 | { |
df0e469f | 83 | secdebug("machsrv", "adding port %d to primary dispatch", receiver.port()); |
29654253 A |
84 | mPortSet += receiver; |
85 | } | |
86 | ||
87 | void MachServer::remove(Port receiver) | |
88 | { | |
df0e469f | 89 | secdebug("machsrv", "removing port %d from primary dispatch", receiver.port()); |
29654253 A |
90 | mPortSet -= receiver; |
91 | } | |
92 | ||
93 | ||
94 | // | |
95 | // Register for mach port notifications | |
96 | // | |
97 | void MachServer::notifyIfDead(Port port, bool doNotify) const | |
98 | { | |
99 | if (doNotify) | |
100 | port.requestNotify(mServerPort, MACH_NOTIFY_DEAD_NAME, true); | |
101 | else | |
102 | port.cancelNotify(MACH_NOTIFY_DEAD_NAME); | |
103 | } | |
104 | ||
105 | void MachServer::notifyIfUnused(Port port, bool doNotify) const | |
106 | { | |
107 | if (doNotify) | |
108 | port.requestNotify(port, MACH_NOTIFY_NO_SENDERS, true); | |
109 | else | |
110 | port.cancelNotify(MACH_NOTIFY_NO_SENDERS); | |
bac41a7b A |
111 | } |
112 | ||
113 | ||
114 | // | |
115 | // Initiate service. | |
116 | // This call will take control of the current thread and use it to service | |
29654253 A |
117 | // incoming requests. The thread will not be released until an error happens, which |
118 | // will cause an exception to be thrown. In other words, this never returns normally. | |
bac41a7b A |
119 | // We may also be creating additional threads to service concurrent requests |
120 | // as appropriate. | |
bac41a7b A |
121 | // @@@ Msg-errors in additional threads are not acted upon. |
122 | // | |
123 | void MachServer::run(size_t maxSize, mach_msg_options_t options) | |
124 | { | |
125 | // establish server-global (thread-shared) parameters | |
126 | mMaxSize = maxSize; | |
127 | mMsgOptions = options; | |
128 | ||
129 | // establish the thread pool state | |
130 | // (don't need managerLock since we're the only thread as of yet) | |
131 | idleCount = workerCount = 1; | |
132 | nextCheckTime = Time::now() + workerTimeout; | |
133 | leastIdleWorkers = 1; | |
134 | highestWorkerCount = 1; | |
135 | ||
136 | // run server loop in initial (immortal) thread | |
137 | runServerThread(false); | |
138 | ||
139 | // primary server thread exited somehow (not currently possible) | |
140 | assert(false); | |
141 | } | |
142 | ||
143 | ||
144 | // | |
145 | // This is the core of a server thread at work. It takes over the thread until | |
29654253 A |
146 | // (a) an error occurs, throwing an exception |
147 | // (b) low-load timeout happens, causing a normal return (doTimeout only) | |
bac41a7b A |
148 | // This code is loosely based on mach_msg_server.c, but is drifting away for |
149 | // various reasons of flexibility and resilience. | |
150 | // | |
151 | extern "C" boolean_t cdsa_notify_server(mach_msg_header_t *in, mach_msg_header_t *out); | |
152 | ||
153 | void MachServer::runServerThread(bool doTimeout) | |
154 | { | |
155 | // allocate request/reply buffers | |
156 | Message bufRequest(mMaxSize); | |
157 | Message bufReply(mMaxSize); | |
158 | ||
29654253 | 159 | // all exits from runServerThread are through exceptions |
bac41a7b A |
160 | try { |
161 | // register as a worker thread | |
df0e469f | 162 | secdebug("machsrv", "%p starting service on port %d", this, int(mServerPort)); |
bac41a7b A |
163 | perThread().server = this; |
164 | ||
165 | for (;;) { | |
166 | // process all pending timers | |
167 | while (processTimer()) ; | |
168 | ||
169 | // check for worker idle timeout | |
170 | { StLock<Mutex> _(managerLock); | |
171 | // record idle thread low-water mark in scan interval | |
172 | if (idleCount < leastIdleWorkers) | |
173 | leastIdleWorkers = idleCount; | |
174 | ||
175 | // perform self-timeout processing | |
176 | if (doTimeout) { | |
177 | if (workerCount > maxWorkerCount) { | |
df0e469f | 178 | secdebug("machsrv", "%p too many threads; reaping immediately", this); |
bac41a7b A |
179 | break; |
180 | } | |
181 | Time::Absolute rightNow = Time::now(); | |
182 | if (rightNow >= nextCheckTime) { // reaping period complete; process | |
183 | uint32 idlers = leastIdleWorkers; | |
df0e469f | 184 | secdebug("machsrv", "%p end of reaping period: %ld (min) idle of %ld total", |
bac41a7b A |
185 | this, idlers, workerCount); |
186 | nextCheckTime = rightNow + workerTimeout; | |
187 | leastIdleWorkers = INT_MAX; | |
188 | if (idlers > 1) | |
189 | break; | |
190 | } | |
191 | } | |
192 | } | |
193 | ||
194 | // release deferred-release memory | |
195 | releaseDeferredAllocations(); | |
196 | ||
197 | // determine next timeout, or zero for infinity | |
198 | bool indefinite = false; | |
199 | Time::Interval timeout; | |
200 | { StLock<Mutex> _(managerLock); | |
201 | if (timers.empty()) { | |
202 | if (doTimeout) | |
203 | timeout = workerTimeout; | |
204 | else | |
205 | indefinite = true; | |
206 | } else { | |
207 | timeout = doTimeout | |
208 | ? min(workerTimeout, timers.next() - Time::now()) | |
209 | : timers.next() - Time::now(); | |
210 | } | |
211 | } | |
212 | ||
213 | // receive next IPC request (or wait for timeout) | |
214 | switch (mach_msg_return_t mr = indefinite ? | |
29654253 | 215 | mach_msg_overwrite(bufRequest, |
bac41a7b A |
216 | MACH_RCV_MSG | mMsgOptions, |
217 | 0, mMaxSize, mPortSet, | |
218 | MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, | |
219 | (mach_msg_header_t *) 0, 0) | |
220 | : | |
29654253 | 221 | mach_msg_overwrite(bufRequest, |
bac41a7b A |
222 | MACH_RCV_MSG | MACH_RCV_TIMEOUT | mMsgOptions, |
223 | 0, mMaxSize, mPortSet, | |
224 | mach_msg_timeout_t(timeout.mSeconds()), MACH_PORT_NULL, | |
225 | (mach_msg_header_t *) 0, 0)) { | |
226 | case MACH_MSG_SUCCESS: | |
227 | // process received request message below | |
228 | break; | |
229 | case MACH_RCV_TIMED_OUT: | |
230 | // back to top for time-related processing | |
231 | continue; | |
232 | case MACH_RCV_TOO_LARGE: | |
233 | // the kernel destroyed the request | |
234 | continue; | |
235 | case MACH_RCV_INTERRUPTED: | |
236 | // receive interrupted, try again | |
237 | continue; | |
238 | default: | |
239 | Error::throwMe(mr); | |
240 | } | |
241 | ||
242 | // process received message | |
243 | if (bufRequest.msgId() >= MACH_NOTIFY_FIRST && | |
244 | bufRequest.msgId() <= MACH_NOTIFY_LAST) { | |
245 | // mach kernel notification message | |
246 | // we assume this is quick, so no thread arbitration here | |
247 | cdsa_notify_server(bufRequest, bufReply); | |
248 | } else { | |
249 | // normal request message | |
250 | { StLock<Mutex> _(managerLock); idleCount--; } | |
df0e469f | 251 | secdebug("machsrvreq", |
bac41a7b A |
252 | "servicing port %d request id=%d", |
253 | bufRequest.localPort().port(), bufRequest.msgId()); | |
29654253 A |
254 | |
255 | // try subsidiary handlers first | |
256 | bool handled = false; | |
257 | for (HandlerSet::const_iterator it = mHandlers.begin(); | |
258 | it != mHandlers.end(); it++) | |
259 | if (bufRequest.localPort() == (*it)->port()) { | |
260 | (*it)->handle(bufRequest, bufReply); | |
261 | handled = true; | |
262 | } | |
263 | if (!handled) { | |
264 | // unclaimed, send to main handler | |
bac41a7b | 265 | handle(bufRequest, bufReply); |
bac41a7b | 266 | } |
29654253 | 267 | |
df0e469f | 268 | secdebug("machsrvreq", "request complete"); |
bac41a7b A |
269 | { StLock<Mutex> _(managerLock); idleCount++; } |
270 | } | |
271 | ||
272 | // process reply generated by handler | |
273 | if (!(bufReply.bits() & MACH_MSGH_BITS_COMPLEX) && | |
274 | bufReply.returnCode() != KERN_SUCCESS) { | |
275 | if (bufReply.returnCode() == MIG_NO_REPLY) | |
276 | continue; | |
277 | // don't destroy the reply port right, so we can send an error message | |
278 | bufRequest.remotePort(MACH_PORT_NULL); | |
279 | mach_msg_destroy(bufRequest); | |
280 | } | |
281 | ||
282 | if (bufReply.remotePort() == MACH_PORT_NULL) { | |
283 | // no reply port, so destroy the reply | |
284 | if (bufReply.bits() & MACH_MSGH_BITS_COMPLEX) | |
285 | bufReply.destroy(); | |
286 | continue; | |
287 | } | |
288 | ||
289 | /* | |
290 | * We don't want to block indefinitely because the client | |
291 | * isn't receiving messages from the reply port. | |
292 | * If we have a send-once right for the reply port, then | |
293 | * this isn't a concern because the send won't block. | |
294 | * If we have a send right, we need to use MACH_SEND_TIMEOUT. | |
295 | * To avoid falling off the kernel's fast RPC path unnecessarily, | |
296 | * we only supply MACH_SEND_TIMEOUT when absolutely necessary. | |
297 | */ | |
29654253 | 298 | switch (mach_msg_return_t mr = mach_msg_overwrite(bufReply, |
bac41a7b A |
299 | (MACH_MSGH_BITS_REMOTE(bufReply.bits()) == |
300 | MACH_MSG_TYPE_MOVE_SEND_ONCE) ? | |
301 | MACH_SEND_MSG | mMsgOptions : | |
302 | MACH_SEND_MSG | MACH_SEND_TIMEOUT | mMsgOptions, | |
303 | bufReply.length(), 0, MACH_PORT_NULL, | |
304 | 0, MACH_PORT_NULL, NULL, 0)) { | |
305 | case MACH_MSG_SUCCESS: | |
306 | break; | |
307 | case MACH_SEND_INVALID_DEST: | |
308 | case MACH_SEND_TIMED_OUT: | |
309 | /* the reply can't be delivered, so destroy it */ | |
310 | mach_msg_destroy(bufRequest); | |
311 | break; | |
312 | default: | |
313 | Error::throwMe(mr); | |
314 | } | |
315 | } | |
316 | perThread().server = NULL; | |
df0e469f | 317 | secdebug("machsrv", "%p ending service on port %d", this, int(mServerPort)); |
bac41a7b A |
318 | |
319 | } catch (...) { | |
320 | perThread().server = NULL; | |
df0e469f | 321 | secdebug("machsrv", "%p aborted by exception (port %d)", this, int(mServerPort)); |
bac41a7b A |
322 | throw; |
323 | } | |
324 | } | |
325 | ||
326 | ||
327 | // | |
328 | // Manage subsidiary ports | |
329 | // | |
330 | void MachServer::add(Handler &handler) | |
331 | { | |
332 | assert(mHandlers.find(&handler) == mHandlers.end()); | |
333 | assert(handler.port() != MACH_PORT_NULL); | |
334 | mHandlers.insert(&handler); | |
335 | mPortSet += handler.port(); | |
336 | } | |
337 | ||
338 | void MachServer::remove(Handler &handler) | |
339 | { | |
340 | assert(mHandlers.find(&handler) != mHandlers.end()); | |
341 | mHandlers.erase(&handler); | |
342 | mPortSet -= handler.port(); | |
343 | } | |
344 | ||
345 | ||
346 | // | |
347 | // Implement a Handler that sends no reply | |
348 | // | |
349 | boolean_t MachServer::NoReplyHandler::handle(mach_msg_header_t *in, mach_msg_header_t *out) | |
350 | { | |
351 | // set up reply message to be valid (enough) and read "do not send reply" | |
352 | out->msgh_bits = 0; | |
353 | out->msgh_remote_port = MACH_PORT_NULL; | |
354 | out->msgh_size = sizeof(mig_reply_error_t); | |
355 | ((mig_reply_error_t *)out)->RetCode = MIG_NO_REPLY; | |
356 | ||
357 | // call input-only handler | |
358 | return handle(in); | |
359 | } | |
360 | ||
361 | ||
362 | // | |
363 | // Register a memory block for deferred release. | |
364 | // | |
365 | void MachServer::releaseWhenDone(CssmAllocator &alloc, void *memory) | |
366 | { | |
367 | if (memory) { | |
368 | set<Allocation> &releaseSet = perThread().deferredAllocations; | |
369 | assert(releaseSet.find(Allocation(memory, alloc)) == releaseSet.end()); | |
df0e469f | 370 | secdebug("machsrvmem", "%p register %p for release with %p", |
bac41a7b A |
371 | this, memory, &alloc); |
372 | releaseSet.insert(Allocation(memory, alloc)); | |
373 | } | |
374 | } | |
375 | ||
376 | ||
377 | // | |
378 | // Run through the accumulated deferred allocations and release them. | |
379 | // This is done automatically on every pass through the server loop; | |
380 | // it must be called by subclasses that implement their loop in some | |
381 | // other way. | |
382 | // @@@X Needs to be thread local | |
383 | // | |
384 | void MachServer::releaseDeferredAllocations() | |
385 | { | |
386 | set<Allocation> &releaseSet = perThread().deferredAllocations; | |
387 | for (set<Allocation>::iterator it = releaseSet.begin(); it != releaseSet.end(); it++) { | |
df0e469f | 388 | secdebug("machsrvmem", "%p release %p with %p", this, it->addr, it->allocator); |
bac41a7b A |
389 | it->allocator->free(it->addr); |
390 | } | |
391 | releaseSet.erase(releaseSet.begin(), releaseSet.end()); | |
392 | } | |
393 | ||
394 | ||
395 | // | |
396 | // The handler function calls this if it realizes that it might be blocked | |
397 | // (or doing something that takes a long time). We respond by ensuring that | |
398 | // at least one more thread is ready to serve requests. | |
399 | // | |
400 | void MachServer::longTermActivity() | |
401 | { | |
402 | StLock<Mutex> _(managerLock); | |
403 | if (idleCount == 0 && workerCount < maxWorkerCount) { | |
404 | // spawn a new thread of activity that shares in the server main loop | |
405 | (new LoadThread(*this))->run(); | |
406 | } | |
407 | } | |
408 | ||
409 | void MachServer::LoadThread::action() | |
410 | { | |
411 | //@@@ race condition?! can server exit before helpers thread gets here? | |
412 | ||
413 | // register the worker thread and go | |
414 | server.addThread(this); | |
415 | try { | |
416 | server.runServerThread(true); | |
417 | } catch (...) { | |
418 | // fell out of server loop by error. Let the thread go quietly | |
419 | } | |
420 | server.removeThread(this); | |
421 | } | |
422 | ||
423 | void MachServer::addThread(Thread *thread) | |
424 | { | |
425 | StLock<Mutex> _(managerLock); | |
426 | workerCount++; | |
427 | idleCount++; | |
df0e469f | 428 | secdebug("machsrv", "%p adding worker thread (%ld workers, %ld idle)", |
bac41a7b A |
429 | this, workerCount, idleCount); |
430 | workers.insert(thread); | |
431 | } | |
432 | ||
433 | void MachServer::removeThread(Thread *thread) | |
434 | { | |
435 | StLock<Mutex> _(managerLock); | |
436 | workerCount--; | |
437 | idleCount--; | |
df0e469f | 438 | secdebug("machsrv", "%p removing worker thread (%ld workers, %ld idle)", |
bac41a7b A |
439 | this, workerCount, idleCount); |
440 | workers.erase(thread); | |
441 | } | |
442 | ||
443 | ||
444 | // | |
445 | // Timer management | |
446 | // | |
447 | bool MachServer::processTimer() | |
448 | { | |
449 | Timer *top; | |
450 | { StLock<Mutex> _(managerLock); // could have multiple threads trying this | |
451 | if (!(top = static_cast<Timer *>(timers.pop(Time::now())))) | |
452 | return false; // nothing (more) to be done now | |
453 | } // drop lock; work has been retrieved | |
df0e469f | 454 | secdebug("machsrvtime", "%p timer %p executing at %.3f", |
bac41a7b A |
455 | this, top, Time::now().internalForm()); |
456 | try { | |
457 | top->action(); | |
df0e469f | 458 | secdebug("machsrvtime", "%p timer %p done", this, top); |
bac41a7b | 459 | } catch (...) { |
df0e469f | 460 | secdebug("machsrvtime", "%p server timer %p failed with exception", this, top); |
bac41a7b A |
461 | } |
462 | return true; | |
463 | } | |
464 | ||
465 | void MachServer::setTimer(Timer *timer, Time::Absolute when) | |
466 | { | |
467 | StLock<Mutex> _(managerLock); | |
468 | timers.schedule(timer, when); | |
469 | } | |
470 | ||
471 | void MachServer::clearTimer(Timer *timer) | |
472 | { | |
473 | StLock<Mutex> _(managerLock); | |
474 | if (timer->scheduled()) | |
475 | timers.unschedule(timer); | |
476 | } | |
477 | ||
478 | ||
479 | // | |
480 | // Notification hooks and shims. Defaults do nothing. | |
481 | // | |
482 | void cdsa_mach_notify_dead_name(mach_port_t, mach_port_name_t port) | |
483 | { MachServer::active().notifyDeadName(port); } | |
484 | ||
485 | void MachServer::notifyDeadName(Port) { } | |
486 | ||
487 | void cdsa_mach_notify_port_deleted(mach_port_t, mach_port_name_t port) | |
488 | { MachServer::active().notifyPortDeleted(port); } | |
489 | ||
490 | void MachServer::notifyPortDeleted(Port) { } | |
491 | ||
492 | void cdsa_mach_notify_port_destroyed(mach_port_t, mach_port_name_t port) | |
493 | { MachServer::active().notifyPortDestroyed(port); } | |
494 | ||
495 | void MachServer::notifyPortDestroyed(Port) { } | |
496 | ||
29654253 A |
497 | void cdsa_mach_notify_send_once(mach_port_t port) |
498 | { MachServer::active().notifySendOnce(port); } | |
499 | ||
500 | void MachServer::notifySendOnce(Port) { } | |
bac41a7b | 501 | |
29654253 A |
502 | void cdsa_mach_notify_no_senders(mach_port_t port, mach_port_mscount_t count) |
503 | { MachServer::active().notifyNoSenders(port, count); } | |
bac41a7b | 504 | |
29654253 | 505 | void MachServer::notifyNoSenders(Port, mach_port_mscount_t) { } |
bac41a7b A |
506 | |
507 | ||
508 | } // end namespace MachPlusPlus | |
509 | ||
510 | } // end namespace Security |