1/*
2 * Copyright (c) 2009 Apple, Inc.  All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*
25 *  IOMIGMachPort.c
26 *
27 *  Created by Rob Yepez on 1/29/09.
28 *  Copyright 2008 Apple Inc.. All rights reserved.
29 *
30 */
31
32#include <AssertMacros.h>
33#include <CoreFoundation/CFRuntime.h>
34
35#include <pthread.h>
36#include <mach/mach.h>
37#include <mach/mach_time.h>
38#include <servers/bootstrap.h>
39#include "IOMIGMachPort.h"
40
41typedef struct __IOMIGMachPort {
42    CFRuntimeBase                       cfBase;   // base CFType information
43
44    CFRunLoopRef                        runLoop;
45    CFStringRef                         runLoopMode;
46
47    dispatch_queue_t                    dispatchQueue;
48    dispatch_source_t                   dispatchSource;
49
50    CFMachPortRef                       port;
51    CFRunLoopSourceRef                  source;
52    CFIndex                             maxMessageSize;
53
54    IOMIGMachPortDemuxCallback          demuxCallback;
55    void *                              demuxRefcon;
56
57    IOMIGMachPortTerminationCallback    terminationCallback;
58    void *                              terminationRefcon;
59
60
61} __IOMIGMachPort, *__IOMIGMachPortRef;
62
63
64static void             __IOMIGMachPortRelease(CFTypeRef object);
65static void             __IOMIGMachPortRegister(void);
66static void             __IOMIGMachPortSourceCallback(void * info);
67static void             __IOMIGMachPortPortCallback(CFMachPortRef port, void *msg, CFIndex size __unused, void *info);
68static Boolean          __NoMoreSenders(mach_msg_header_t *request, mach_msg_header_t *reply);
69
70
71static const CFRuntimeClass __IOMIGMachPortClass = {
72    0,                          // version
73    "IOMIGMachPort",          // className
74    NULL,                       // init
75    NULL,                       // copy
76    __IOMIGMachPortRelease,   // finalize
77    NULL,                       // equal
78    NULL,                       // hash
79    NULL,                       // copyFormattingDesc
80    NULL,
81    NULL,
82    NULL
83};
84
85static pthread_once_t           __IOMIGMachPortTypeInit   = PTHREAD_ONCE_INIT;
86static CFTypeID                 __IOMIGMachPortTypeID     = _kCFRuntimeNotATypeID;
87static CFMutableDictionaryRef   __ioPortCache             = NULL;
88static pthread_mutex_t          __ioPortCacheLock         = PTHREAD_MUTEX_INITIALIZER;
89
90//------------------------------------------------------------------------------
91// __IOMIGMachPortRegister
92//------------------------------------------------------------------------------
93void __IOMIGMachPortRegister(void)
94{
95    __ioPortCache         = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
96    __IOMIGMachPortTypeID = _CFRuntimeRegisterClass(&__IOMIGMachPortClass);
97}
98
99//------------------------------------------------------------------------------
100// __IOMIGMachPortRelease
101//------------------------------------------------------------------------------
102void __IOMIGMachPortRelease(CFTypeRef object)
103{
104    IOMIGMachPortRef migPort = (IOMIGMachPortRef)object;
105
106    if ( migPort->port ) {
107        CFMachPortInvalidate(migPort->port);
108        CFRelease(migPort->port);
109    }
110
111    if ( migPort->source ) {
112        CFRelease(migPort->source);
113    }
114
115    if ( migPort->dispatchSource ) {
116        dispatch_release(migPort->dispatchSource);
117    }
118}
119
120//------------------------------------------------------------------------------
121// IOMIGMachPortGetTypeID
122//------------------------------------------------------------------------------
123CFTypeID IOMIGMachPortGetTypeID(void)
124{
125    if ( _kCFRuntimeNotATypeID == __IOMIGMachPortTypeID )
126        pthread_once(&__IOMIGMachPortTypeInit, __IOMIGMachPortRegister);
127
128    return __IOMIGMachPortTypeID;
129}
130
131//------------------------------------------------------------------------------
132// IOMIGMachPortCreate
133//------------------------------------------------------------------------------
134IOMIGMachPortRef IOMIGMachPortCreate(CFAllocatorRef allocator, CFIndex maxMessageSize, mach_port_t port)
135{
136    IOMIGMachPortRef  migPort  = NULL;
137    void *              offset  = NULL;
138    uint32_t            size;
139
140    require(maxMessageSize > 0, exit);
141
142    /* allocate service */
143    size    = sizeof(__IOMIGMachPort) - sizeof(CFRuntimeBase);
144    migPort  = ( IOMIGMachPortRef)_CFRuntimeCreateInstance(allocator, IOMIGMachPortGetTypeID(), size, NULL);
145
146    require(migPort, exit);
147
148    offset = migPort;
149    bzero(offset + sizeof(CFRuntimeBase), size);
150
151    CFMachPortContext context = {0, migPort, NULL, NULL, NULL};
152    migPort->port = (port != MACH_PORT_NULL) ?
153            CFMachPortCreateWithPort(allocator, port, __IOMIGMachPortPortCallback, &context, NULL) :
154            CFMachPortCreate(allocator, __IOMIGMachPortPortCallback, &context, NULL);
155
156    require(migPort->port, exit);
157
158    migPort->maxMessageSize = maxMessageSize;
159
160    return migPort;
161
162exit:
163    if ( migPort )
164        CFRelease(migPort);
165
166    return NULL;
167}
168
169//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
170// __IOMIGMachPortSourceCallback
171//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
172void __IOMIGMachPortSourceCallback(void * info)
173{
174    IOMIGMachPortRef migPort = (IOMIGMachPortRef)info;
175
176    CFRetain(migPort);
177
178    mach_port_t         port    = CFMachPortGetPort(migPort->port);
179    mach_msg_size_t     size    = migPort->maxMessageSize + MAX_TRAILER_SIZE;
180    mach_msg_header_t * msg     = (mach_msg_header_t *)CFAllocatorAllocate(CFGetAllocator(migPort), size, 0);
181
182    msg->msgh_size = size;
183    for (;;) {
184        msg->msgh_bits = 0;
185        msg->msgh_local_port = port;
186        msg->msgh_remote_port = MACH_PORT_NULL;
187        msg->msgh_id = 0;
188        kern_return_t ret = mach_msg(msg, MACH_RCV_MSG|MACH_RCV_LARGE|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, 0, MACH_PORT_NULL);
189        if (MACH_MSG_SUCCESS == ret) break;
190        if (MACH_RCV_TOO_LARGE != ret) goto inner_exit;
191        uint32_t newSize = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
192        msg = CFAllocatorReallocate(CFGetAllocator(migPort), msg, newSize, 0);
193        msg->msgh_size = newSize;
194    }
195
196    __IOMIGMachPortPortCallback(migPort->port, msg, msg->msgh_size, migPort);
197
198inner_exit:
199    CFAllocatorDeallocate(kCFAllocatorSystemDefault, msg);
200    CFRelease(migPort);
201}
202
203
204//------------------------------------------------------------------------------
205// IOMIGMachPortScheduleWithDispatchQueue
206//------------------------------------------------------------------------------
207void IOMIGMachPortScheduleWithDispatchQueue(IOMIGMachPortRef migPort, dispatch_queue_t queue)
208{
209
210    mach_port_t port = CFMachPortGetPort(migPort->port);
211
212    migPort->dispatchQueue = queue;
213
214    require(migPort->dispatchQueue, exit);
215
216    // init the sources
217    if ( !migPort->dispatchSource ) {
218        migPort->dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, port, 0, migPort->dispatchQueue);
219        require(migPort->dispatchSource, exit);
220
221        dispatch_set_context(migPort->dispatchSource, migPort);
222        dispatch_source_set_event_handler_f(migPort->dispatchSource, __IOMIGMachPortSourceCallback);
223    }
224
225    dispatch_resume(migPort->dispatchSource);
226
227exit:
228    return;
229}
230
231//------------------------------------------------------------------------------
232// IOMIGMachPortUnscheduleFromDispatchQueue
233//------------------------------------------------------------------------------
234void IOMIGMachPortUnscheduleFromDispatchQueue(IOMIGMachPortRef migPort, dispatch_queue_t queue)
235{
236    if ( !queue || !migPort->dispatchQueue)
237        return;
238
239    if ( queue != migPort->dispatchQueue )
240        return;
241
242    migPort->dispatchQueue = NULL;
243
244    if ( migPort->dispatchSource ) {
245        dispatch_release(migPort->dispatchSource);
246        migPort->dispatchSource = NULL;
247    }
248}
249
250//------------------------------------------------------------------------------
251// IOMIGMachPortScheduleWithRunLoop
252//------------------------------------------------------------------------------
253void IOMIGMachPortScheduleWithRunLoop(IOMIGMachPortRef migPort, CFRunLoopRef runLoop, CFStringRef runLoopMode)
254{
255    migPort->runLoop        = runLoop;
256    migPort->runLoopMode    = runLoopMode;
257
258    require(migPort->runLoop, exit);
259    require(migPort->runLoopMode, exit);
260
261    // init the sources
262    if ( !migPort->source ) {
263        migPort->source = CFMachPortCreateRunLoopSource(CFGetAllocator(migPort), migPort->port, 1);
264        require(migPort->source, exit);
265    }
266
267    CFRunLoopAddSource(runLoop, migPort->source, runLoopMode);
268
269exit:
270    return;
271}
272
273//------------------------------------------------------------------------------
274// IOMIGMachPortUnscheduleFromRunLoop
275//------------------------------------------------------------------------------
276void IOMIGMachPortUnscheduleFromRunLoop(IOMIGMachPortRef migPort, CFRunLoopRef runLoop, CFStringRef runLoopMode)
277{
278    if ( !runLoop || !runLoopMode || !migPort->runLoop || !migPort->runLoopMode)
279        return;
280
281    if ( !CFEqual(runLoop, migPort->runLoop) || !CFEqual(runLoopMode, migPort->runLoopMode) )
282        return;
283
284    migPort->runLoop     = NULL;
285    migPort->runLoopMode = NULL;
286
287    if ( migPort->source )
288        CFRunLoopRemoveSource(runLoop, migPort->source, runLoopMode);
289}
290
291//------------------------------------------------------------------------------
292// IOMIGMachPortGetPort
293//------------------------------------------------------------------------------
294mach_port_t IOMIGMachPortGetPort(IOMIGMachPortRef migPort)
295{
296    return CFMachPortGetPort(migPort->port);
297}
298
299//------------------------------------------------------------------------------
300// IOMIGMachPortRegisterDemuxCallback
301//------------------------------------------------------------------------------
302void IOMIGMachPortRegisterDemuxCallback(IOMIGMachPortRef migPort, IOMIGMachPortDemuxCallback callback, void *refcon)
303{
304    migPort->demuxCallback = callback;
305    migPort->demuxRefcon   = refcon;
306}
307
308//------------------------------------------------------------------------------
309// IOMIGMachPortRegisterTerminationCallback
310//------------------------------------------------------------------------------
311void IOMIGMachPortRegisterTerminationCallback(IOMIGMachPortRef migPort, IOMIGMachPortTerminationCallback callback, void *refcon)
312{
313    migPort->terminationCallback = callback;
314    migPort->terminationRefcon   = refcon;
315}
316
317//------------------------------------------------------------------------------
318// __IOMIGMachPortPortCallback
319//------------------------------------------------------------------------------
320void __IOMIGMachPortPortCallback(CFMachPortRef port __unused, void *msg, CFIndex size __unused, void *info)
321{
322    IOMIGMachPortRef  migPort      = (IOMIGMachPortRef)info;
323    mig_reply_error_t * bufRequest  = msg;
324    mig_reply_error_t * bufReply    = NULL;
325    mach_msg_return_t   mr;
326    int                 options;
327
328    require(migPort, exit);
329    CFRetain(migPort);
330
331    bufReply = CFAllocatorAllocate(NULL, migPort->maxMessageSize, 0);
332    require(bufReply, exit);
333
334    // let's see if we have no more senders
335    if ( __NoMoreSenders(&bufRequest->Head, &bufReply->Head) ) {
336        if ( migPort->terminationCallback )
337            (*migPort->terminationCallback)(migPort, migPort->terminationRefcon);
338        else {
339            goto exit;
340        }
341    } else {
342        if ( migPort->demuxCallback )
343            (*migPort->demuxCallback)(migPort, &bufRequest->Head, &bufReply->Head, migPort->demuxRefcon);
344        else {
345            mach_msg_destroy(&bufRequest->Head);
346            goto exit;
347        }
348    }
349
350    if (!(bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) &&
351        (bufReply->RetCode != KERN_SUCCESS)) {
352
353        //This return code is a little tricky -- it appears that the
354        //demux routine found an error of some sort, but since that
355        //error would not normally get returned either to the local
356        //user or the remote one, we pretend it's ok.
357        require(bufReply->RetCode != MIG_NO_REPLY, exit);
358
359        // destroy any out-of-line data in the request buffer but don't destroy
360        // the reply port right (since we need that to send an error message).
361
362        bufRequest->Head.msgh_remote_port = MACH_PORT_NULL;
363        mach_msg_destroy(&bufRequest->Head);
364    }
365
366    if (bufReply->Head.msgh_remote_port == MACH_PORT_NULL) {
367        //no reply port, so destroy the reply
368        if (bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) {
369            mach_msg_destroy(&bufReply->Head);
370        }
371
372        goto exit;
373    }
374
375    // send reply.
376    // We don't want to block indefinitely because the migPort
377    // isn't receiving messages from the reply port.
378    // If we have a send-once right for the reply port, then
379    // this isn't a concern because the send won't block.
380    // If we have a send right, we need to use MACH_SEND_TIMEOUT.
381    // To avoid falling off the kernel's fast RPC path unnecessarily,
382    // we only supply MACH_SEND_TIMEOUT when absolutely necessary.
383
384    options = MACH_SEND_MSG;
385    if (MACH_MSGH_BITS_REMOTE(bufReply->Head.msgh_bits) != MACH_MSG_TYPE_MOVE_SEND_ONCE) {
386       options |= MACH_SEND_TIMEOUT;
387    }
388
389    mr = mach_msg(&bufReply->Head,
390              options,
391              bufReply->Head.msgh_size,
392              0,
393              MACH_PORT_NULL,
394              MACH_MSG_TIMEOUT_NONE,
395              MACH_PORT_NULL);
396
397
398    // Has a message error occurred?
399    switch (mr) {
400        case MACH_SEND_INVALID_DEST:
401        case MACH_SEND_TIMED_OUT:
402            // the reply can't be delivered, so destroy it
403            mach_msg_destroy(&bufReply->Head);
404            break;
405
406        default :
407            // Includes success case.
408            break;
409    }
410
411exit:
412    if ( bufReply )
413        CFAllocatorDeallocate(NULL, bufReply);
414
415    if ( migPort )
416        CFRelease(migPort);
417}
418
419//------------------------------------------------------------------------------
420// __NoMoreSenders
421//------------------------------------------------------------------------------
422Boolean __NoMoreSenders(mach_msg_header_t *request, mach_msg_header_t *reply)
423{
424	mach_no_senders_notification_t	*Request = (mach_no_senders_notification_t *)request;
425	mig_reply_error_t               *Reply   = (mig_reply_error_t *)reply;
426
427	reply->msgh_bits        = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request->msgh_bits), 0);
428	reply->msgh_remote_port = request->msgh_remote_port;
429	reply->msgh_size        = sizeof(mig_reply_error_t);	// Minimal size: update as needed
430	reply->msgh_local_port  = MACH_PORT_NULL;
431	reply->msgh_id          = request->msgh_id + 100;
432
433	if ((Request->not_header.msgh_id > MACH_NOTIFY_LAST) ||
434	    (Request->not_header.msgh_id < MACH_NOTIFY_FIRST)) {
435		Reply->NDR     = NDR_record;
436		Reply->RetCode = MIG_BAD_ID;
437		return FALSE;	// if this is not a notification message
438	}
439
440	switch (Request->not_header.msgh_id) {
441		case MACH_NOTIFY_NO_SENDERS :
442			Reply->Head.msgh_bits		= 0;
443			Reply->Head.msgh_remote_port	= MACH_PORT_NULL;
444			Reply->RetCode			= KERN_SUCCESS;
445			return TRUE;
446		default :
447			break;
448	}
449
450	Reply->NDR     = NDR_record;
451	Reply->RetCode = MIG_BAD_ID;
452	return FALSE;	// if this is not a notification we are handling
453}
454
455//------------------------------------------------------------------------------
456//IOMIGMachPortCacheAdd
457//------------------------------------------------------------------------------
458void IOMIGMachPortCacheAdd(mach_port_t port, CFTypeRef server)
459{
460    pthread_mutex_lock(&__ioPortCacheLock);
461
462    CFDictionarySetValue(__ioPortCache, (void *)port, server);
463
464    pthread_mutex_unlock(&__ioPortCacheLock);
465}
466
467//------------------------------------------------------------------------------
468// IOMIGMachPortCacheRemove
469//------------------------------------------------------------------------------
470void IOMIGMachPortCacheRemove(mach_port_t port)
471{
472    pthread_mutex_lock(&__ioPortCacheLock);
473
474    CFDictionaryRemoveValue(__ioPortCache, (void *)port);
475
476    pthread_mutex_unlock(&__ioPortCacheLock);
477}
478
479//------------------------------------------------------------------------------
480// IOMIGMachPortCacheCopy
481//------------------------------------------------------------------------------
482CFTypeRef IOMIGMachPortCacheCopy(mach_port_t port)
483{
484    CFTypeRef server;
485
486    pthread_mutex_lock(&__ioPortCacheLock);
487
488    server = (CFTypeRef)CFDictionaryGetValue(__ioPortCache, (void *)port);
489    if ( server ) CFRetain(server);
490
491    pthread_mutex_unlock(&__ioPortCacheLock);
492
493    return server;
494}
495
496