]>
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 | { | |
61 | debug("machsrv", "%p preparing service for \"%s\"", this, name); | |
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. | |
72 | debug("machsrv", "%p destroyed", this); | |
73 | } | |
74 | ||
75 | ||
76 | // | |
77 | // Utility access | |
78 | // | |
79 | void MachServer::notifyIfDead(Port port) const | |
80 | { | |
81 | port.requestNotify(mServerPort, MACH_NOTIFY_DEAD_NAME, true); | |
82 | } | |
83 | ||
84 | ||
85 | // | |
86 | // Initiate service. | |
87 | // This call will take control of the current thread and use it to service | |
88 | // incoming requests. The thread will not be released until an error happens. | |
89 | // We may also be creating additional threads to service concurrent requests | |
90 | // as appropriate. | |
91 | // @@@ Additional threads are not being reaped at this point. | |
92 | // @@@ Msg-errors in additional threads are not acted upon. | |
93 | // | |
94 | void MachServer::run(size_t maxSize, mach_msg_options_t options) | |
95 | { | |
96 | // establish server-global (thread-shared) parameters | |
97 | mMaxSize = maxSize; | |
98 | mMsgOptions = options; | |
99 | ||
100 | // establish the thread pool state | |
101 | // (don't need managerLock since we're the only thread as of yet) | |
102 | idleCount = workerCount = 1; | |
103 | nextCheckTime = Time::now() + workerTimeout; | |
104 | leastIdleWorkers = 1; | |
105 | highestWorkerCount = 1; | |
106 | ||
107 | // run server loop in initial (immortal) thread | |
108 | runServerThread(false); | |
109 | ||
110 | // primary server thread exited somehow (not currently possible) | |
111 | assert(false); | |
112 | } | |
113 | ||
114 | ||
115 | // | |
116 | // This is the core of a server thread at work. It takes over the thread until | |
117 | // something makes it exit normally. Then it returns. Errors cause exceptions. | |
118 | // This code is loosely based on mach_msg_server.c, but is drifting away for | |
119 | // various reasons of flexibility and resilience. | |
120 | // | |
121 | extern "C" boolean_t cdsa_notify_server(mach_msg_header_t *in, mach_msg_header_t *out); | |
122 | ||
123 | void MachServer::runServerThread(bool doTimeout) | |
124 | { | |
125 | // allocate request/reply buffers | |
126 | Message bufRequest(mMaxSize); | |
127 | Message bufReply(mMaxSize); | |
128 | ||
129 | // all exits from runServerThread are through exceptions or "goto exit" | |
130 | try { | |
131 | // register as a worker thread | |
132 | debug("machsrv", "%p starting service on port %d", this, int(mServerPort)); | |
133 | perThread().server = this; | |
134 | ||
135 | for (;;) { | |
136 | // process all pending timers | |
137 | while (processTimer()) ; | |
138 | ||
139 | // check for worker idle timeout | |
140 | { StLock<Mutex> _(managerLock); | |
141 | // record idle thread low-water mark in scan interval | |
142 | if (idleCount < leastIdleWorkers) | |
143 | leastIdleWorkers = idleCount; | |
144 | ||
145 | // perform self-timeout processing | |
146 | if (doTimeout) { | |
147 | if (workerCount > maxWorkerCount) { | |
148 | debug("machsrv", "%p too many threads; reaping immediately", this); | |
149 | break; | |
150 | } | |
151 | Time::Absolute rightNow = Time::now(); | |
152 | if (rightNow >= nextCheckTime) { // reaping period complete; process | |
153 | uint32 idlers = leastIdleWorkers; | |
154 | debug("machsrv", "%p end of reaping period: %ld (min) idle of %ld total", | |
155 | this, idlers, workerCount); | |
156 | nextCheckTime = rightNow + workerTimeout; | |
157 | leastIdleWorkers = INT_MAX; | |
158 | if (idlers > 1) | |
159 | break; | |
160 | } | |
161 | } | |
162 | } | |
163 | ||
164 | // release deferred-release memory | |
165 | releaseDeferredAllocations(); | |
166 | ||
167 | // determine next timeout, or zero for infinity | |
168 | bool indefinite = false; | |
169 | Time::Interval timeout; | |
170 | { StLock<Mutex> _(managerLock); | |
171 | if (timers.empty()) { | |
172 | if (doTimeout) | |
173 | timeout = workerTimeout; | |
174 | else | |
175 | indefinite = true; | |
176 | } else { | |
177 | timeout = doTimeout | |
178 | ? min(workerTimeout, timers.next() - Time::now()) | |
179 | : timers.next() - Time::now(); | |
180 | } | |
181 | } | |
182 | ||
183 | // receive next IPC request (or wait for timeout) | |
184 | switch (mach_msg_return_t mr = indefinite ? | |
185 | mach_msg_overwrite_trap(bufRequest, | |
186 | MACH_RCV_MSG | mMsgOptions, | |
187 | 0, mMaxSize, mPortSet, | |
188 | MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, | |
189 | (mach_msg_header_t *) 0, 0) | |
190 | : | |
191 | mach_msg_overwrite_trap(bufRequest, | |
192 | MACH_RCV_MSG | MACH_RCV_TIMEOUT | mMsgOptions, | |
193 | 0, mMaxSize, mPortSet, | |
194 | mach_msg_timeout_t(timeout.mSeconds()), MACH_PORT_NULL, | |
195 | (mach_msg_header_t *) 0, 0)) { | |
196 | case MACH_MSG_SUCCESS: | |
197 | // process received request message below | |
198 | break; | |
199 | case MACH_RCV_TIMED_OUT: | |
200 | // back to top for time-related processing | |
201 | continue; | |
202 | case MACH_RCV_TOO_LARGE: | |
203 | // the kernel destroyed the request | |
204 | continue; | |
205 | case MACH_RCV_INTERRUPTED: | |
206 | // receive interrupted, try again | |
207 | continue; | |
208 | default: | |
209 | Error::throwMe(mr); | |
210 | } | |
211 | ||
212 | // process received message | |
213 | if (bufRequest.msgId() >= MACH_NOTIFY_FIRST && | |
214 | bufRequest.msgId() <= MACH_NOTIFY_LAST) { | |
215 | // mach kernel notification message | |
216 | // we assume this is quick, so no thread arbitration here | |
217 | cdsa_notify_server(bufRequest, bufReply); | |
218 | } else { | |
219 | // normal request message | |
220 | { StLock<Mutex> _(managerLock); idleCount--; } | |
221 | debug("machsrvreq", | |
222 | "servicing port %d request id=%d", | |
223 | bufRequest.localPort().port(), bufRequest.msgId()); | |
224 | if (bufRequest.localPort() == mServerPort) { // primary | |
225 | handle(bufRequest, bufReply); | |
226 | } else { | |
227 | for (HandlerSet::const_iterator it = mHandlers.begin(); | |
228 | it != mHandlers.end(); it++) | |
229 | if (bufRequest.localPort() == (*it)->port()) | |
230 | (*it)->handle(bufRequest, bufReply); | |
231 | } | |
232 | debug("machsrvreq", "request complete"); | |
233 | { StLock<Mutex> _(managerLock); idleCount++; } | |
234 | } | |
235 | ||
236 | // process reply generated by handler | |
237 | if (!(bufReply.bits() & MACH_MSGH_BITS_COMPLEX) && | |
238 | bufReply.returnCode() != KERN_SUCCESS) { | |
239 | if (bufReply.returnCode() == MIG_NO_REPLY) | |
240 | continue; | |
241 | // don't destroy the reply port right, so we can send an error message | |
242 | bufRequest.remotePort(MACH_PORT_NULL); | |
243 | mach_msg_destroy(bufRequest); | |
244 | } | |
245 | ||
246 | if (bufReply.remotePort() == MACH_PORT_NULL) { | |
247 | // no reply port, so destroy the reply | |
248 | if (bufReply.bits() & MACH_MSGH_BITS_COMPLEX) | |
249 | bufReply.destroy(); | |
250 | continue; | |
251 | } | |
252 | ||
253 | /* | |
254 | * We don't want to block indefinitely because the client | |
255 | * isn't receiving messages from the reply port. | |
256 | * If we have a send-once right for the reply port, then | |
257 | * this isn't a concern because the send won't block. | |
258 | * If we have a send right, we need to use MACH_SEND_TIMEOUT. | |
259 | * To avoid falling off the kernel's fast RPC path unnecessarily, | |
260 | * we only supply MACH_SEND_TIMEOUT when absolutely necessary. | |
261 | */ | |
262 | switch (mach_msg_return_t mr = mach_msg_overwrite_trap(bufReply, | |
263 | (MACH_MSGH_BITS_REMOTE(bufReply.bits()) == | |
264 | MACH_MSG_TYPE_MOVE_SEND_ONCE) ? | |
265 | MACH_SEND_MSG | mMsgOptions : | |
266 | MACH_SEND_MSG | MACH_SEND_TIMEOUT | mMsgOptions, | |
267 | bufReply.length(), 0, MACH_PORT_NULL, | |
268 | 0, MACH_PORT_NULL, NULL, 0)) { | |
269 | case MACH_MSG_SUCCESS: | |
270 | break; | |
271 | case MACH_SEND_INVALID_DEST: | |
272 | case MACH_SEND_TIMED_OUT: | |
273 | /* the reply can't be delivered, so destroy it */ | |
274 | mach_msg_destroy(bufRequest); | |
275 | break; | |
276 | default: | |
277 | Error::throwMe(mr); | |
278 | } | |
279 | } | |
280 | perThread().server = NULL; | |
281 | debug("machsrv", "%p ending service on port %d", this, int(mServerPort)); | |
282 | ||
283 | } catch (...) { | |
284 | perThread().server = NULL; | |
285 | debug("machsrv", "%p aborted by exception (port %d)", this, int(mServerPort)); | |
286 | throw; | |
287 | } | |
288 | } | |
289 | ||
290 | ||
291 | // | |
292 | // Manage subsidiary ports | |
293 | // | |
294 | void MachServer::add(Handler &handler) | |
295 | { | |
296 | assert(mHandlers.find(&handler) == mHandlers.end()); | |
297 | assert(handler.port() != MACH_PORT_NULL); | |
298 | mHandlers.insert(&handler); | |
299 | mPortSet += handler.port(); | |
300 | } | |
301 | ||
302 | void MachServer::remove(Handler &handler) | |
303 | { | |
304 | assert(mHandlers.find(&handler) != mHandlers.end()); | |
305 | mHandlers.erase(&handler); | |
306 | mPortSet -= handler.port(); | |
307 | } | |
308 | ||
309 | ||
310 | // | |
311 | // Implement a Handler that sends no reply | |
312 | // | |
313 | boolean_t MachServer::NoReplyHandler::handle(mach_msg_header_t *in, mach_msg_header_t *out) | |
314 | { | |
315 | // set up reply message to be valid (enough) and read "do not send reply" | |
316 | out->msgh_bits = 0; | |
317 | out->msgh_remote_port = MACH_PORT_NULL; | |
318 | out->msgh_size = sizeof(mig_reply_error_t); | |
319 | ((mig_reply_error_t *)out)->RetCode = MIG_NO_REPLY; | |
320 | ||
321 | // call input-only handler | |
322 | return handle(in); | |
323 | } | |
324 | ||
325 | ||
326 | // | |
327 | // Register a memory block for deferred release. | |
328 | // | |
329 | void MachServer::releaseWhenDone(CssmAllocator &alloc, void *memory) | |
330 | { | |
331 | if (memory) { | |
332 | set<Allocation> &releaseSet = perThread().deferredAllocations; | |
333 | assert(releaseSet.find(Allocation(memory, alloc)) == releaseSet.end()); | |
334 | debug("machsrvmem", "%p register %p for release with %p", | |
335 | this, memory, &alloc); | |
336 | releaseSet.insert(Allocation(memory, alloc)); | |
337 | } | |
338 | } | |
339 | ||
340 | ||
341 | // | |
342 | // Run through the accumulated deferred allocations and release them. | |
343 | // This is done automatically on every pass through the server loop; | |
344 | // it must be called by subclasses that implement their loop in some | |
345 | // other way. | |
346 | // @@@X Needs to be thread local | |
347 | // | |
348 | void MachServer::releaseDeferredAllocations() | |
349 | { | |
350 | set<Allocation> &releaseSet = perThread().deferredAllocations; | |
351 | for (set<Allocation>::iterator it = releaseSet.begin(); it != releaseSet.end(); it++) { | |
352 | debug("machsrvmem", "%p release %p with %p", this, it->addr, it->allocator); | |
353 | it->allocator->free(it->addr); | |
354 | } | |
355 | releaseSet.erase(releaseSet.begin(), releaseSet.end()); | |
356 | } | |
357 | ||
358 | ||
359 | // | |
360 | // The handler function calls this if it realizes that it might be blocked | |
361 | // (or doing something that takes a long time). We respond by ensuring that | |
362 | // at least one more thread is ready to serve requests. | |
363 | // | |
364 | void MachServer::longTermActivity() | |
365 | { | |
366 | StLock<Mutex> _(managerLock); | |
367 | if (idleCount == 0 && workerCount < maxWorkerCount) { | |
368 | // spawn a new thread of activity that shares in the server main loop | |
369 | (new LoadThread(*this))->run(); | |
370 | } | |
371 | } | |
372 | ||
373 | void MachServer::LoadThread::action() | |
374 | { | |
375 | //@@@ race condition?! can server exit before helpers thread gets here? | |
376 | ||
377 | // register the worker thread and go | |
378 | server.addThread(this); | |
379 | try { | |
380 | server.runServerThread(true); | |
381 | } catch (...) { | |
382 | // fell out of server loop by error. Let the thread go quietly | |
383 | } | |
384 | server.removeThread(this); | |
385 | } | |
386 | ||
387 | void MachServer::addThread(Thread *thread) | |
388 | { | |
389 | StLock<Mutex> _(managerLock); | |
390 | workerCount++; | |
391 | idleCount++; | |
392 | debug("machsrv", "%p adding worker thread (%ld workers, %ld idle)", | |
393 | this, workerCount, idleCount); | |
394 | workers.insert(thread); | |
395 | } | |
396 | ||
397 | void MachServer::removeThread(Thread *thread) | |
398 | { | |
399 | StLock<Mutex> _(managerLock); | |
400 | workerCount--; | |
401 | idleCount--; | |
402 | debug("machsrv", "%p removing worker thread (%ld workers, %ld idle)", | |
403 | this, workerCount, idleCount); | |
404 | workers.erase(thread); | |
405 | } | |
406 | ||
407 | ||
408 | // | |
409 | // Timer management | |
410 | // | |
411 | bool MachServer::processTimer() | |
412 | { | |
413 | Timer *top; | |
414 | { StLock<Mutex> _(managerLock); // could have multiple threads trying this | |
415 | if (!(top = static_cast<Timer *>(timers.pop(Time::now())))) | |
416 | return false; // nothing (more) to be done now | |
417 | } // drop lock; work has been retrieved | |
418 | debug("machsrvtime", "%p timer %p executing at %.3f", | |
419 | this, top, Time::now().internalForm()); | |
420 | try { | |
421 | top->action(); | |
422 | debug("machsrvtime", "%p timer %p done", this, top); | |
423 | } catch (...) { | |
424 | debug("machsrvtime", "%p server timer %p failed with exception", this, top); | |
425 | } | |
426 | return true; | |
427 | } | |
428 | ||
429 | void MachServer::setTimer(Timer *timer, Time::Absolute when) | |
430 | { | |
431 | StLock<Mutex> _(managerLock); | |
432 | timers.schedule(timer, when); | |
433 | } | |
434 | ||
435 | void MachServer::clearTimer(Timer *timer) | |
436 | { | |
437 | StLock<Mutex> _(managerLock); | |
438 | if (timer->scheduled()) | |
439 | timers.unschedule(timer); | |
440 | } | |
441 | ||
442 | ||
443 | // | |
444 | // Notification hooks and shims. Defaults do nothing. | |
445 | // | |
446 | void cdsa_mach_notify_dead_name(mach_port_t, mach_port_name_t port) | |
447 | { MachServer::active().notifyDeadName(port); } | |
448 | ||
449 | void MachServer::notifyDeadName(Port) { } | |
450 | ||
451 | void cdsa_mach_notify_port_deleted(mach_port_t, mach_port_name_t port) | |
452 | { MachServer::active().notifyPortDeleted(port); } | |
453 | ||
454 | void MachServer::notifyPortDeleted(Port) { } | |
455 | ||
456 | void cdsa_mach_notify_port_destroyed(mach_port_t, mach_port_name_t port) | |
457 | { MachServer::active().notifyPortDestroyed(port); } | |
458 | ||
459 | void MachServer::notifyPortDestroyed(Port) { } | |
460 | ||
461 | void cdsa_mach_notify_send_once(mach_port_t) | |
462 | { MachServer::active().notifySendOnce(); } | |
463 | ||
464 | void MachServer::notifySendOnce() { } | |
465 | ||
466 | void cdsa_mach_notify_no_senders(mach_port_t) | |
467 | { /* legacy handler - not used by system */ } | |
468 | ||
469 | ||
470 | } // end namespace MachPlusPlus | |
471 | ||
472 | } // end namespace Security |