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             __IOMIGMachPortPortCallback(CFMachPortRef port, void *msg, CFIndex size __unused, void *info);
67static Boolean          __NoMoreSenders(mach_msg_header_t *request, mach_msg_header_t *reply);
68
69
70static const CFRuntimeClass __IOMIGMachPortClass = {
71    0,                          // version
72    "IOMIGMachPort",          // className
73    NULL,                       // init
74    NULL,                       // copy
75    __IOMIGMachPortRelease,   // finalize
76    NULL,                       // equal
77    NULL,                       // hash
78    NULL,                       // copyFormattingDesc
79    NULL,
80    NULL,
81    NULL
82};
83
84static pthread_once_t           __IOMIGMachPortTypeInit   = PTHREAD_ONCE_INIT;
85static CFTypeID                 __IOMIGMachPortTypeID     = _kCFRuntimeNotATypeID;
86static CFMutableDictionaryRef   __ioPortCache             = NULL;
87static pthread_mutex_t          __ioPortCacheLock         = PTHREAD_MUTEX_INITIALIZER;
88
89//------------------------------------------------------------------------------
90// __IOMIGMachPortRegister
91//------------------------------------------------------------------------------
92void __IOMIGMachPortRegister(void)
93{
94    __IOMIGMachPortTypeID = _CFRuntimeRegisterClass(&__IOMIGMachPortClass);
95    __ioPortCache         = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
96}
97
98//------------------------------------------------------------------------------
99// __IOMIGMachPortRelease
100//------------------------------------------------------------------------------
101void __IOMIGMachPortRelease(CFTypeRef object)
102{
103    IOMIGMachPortRef migPort = (IOMIGMachPortRef)object;
104
105    if ( migPort->port ) {
106        CFMachPortInvalidate(migPort->port);
107        CFRelease(migPort->port);
108    }
109
110    if ( migPort->source ) {
111        CFRelease(migPort->source);
112    }
113
114    if ( migPort->dispatchSource ) {
115        dispatch_release(migPort->dispatchSource);
116    }
117}
118
119//------------------------------------------------------------------------------
120// IOMIGMachPortGetTypeID
121//------------------------------------------------------------------------------
122CFTypeID IOMIGMachPortGetTypeID(void)
123{
124    if ( _kCFRuntimeNotATypeID == __IOMIGMachPortTypeID )
125        pthread_once(&__IOMIGMachPortTypeInit, __IOMIGMachPortRegister);
126
127    return __IOMIGMachPortTypeID;
128}
129
130//------------------------------------------------------------------------------
131// IOMIGMachPortCreate
132//------------------------------------------------------------------------------
133IOMIGMachPortRef IOMIGMachPortCreate(CFAllocatorRef allocator, CFIndex maxMessageSize, mach_port_t port)
134{
135    IOMIGMachPortRef  migPort  = NULL;
136    void *              offset  = NULL;
137    uint32_t            size;
138
139    require(maxMessageSize > 0, exit);
140
141    /* allocate service */
142    size    = sizeof(__IOMIGMachPort) - sizeof(CFRuntimeBase);
143    migPort  = ( IOMIGMachPortRef)_CFRuntimeCreateInstance(allocator, IOMIGMachPortGetTypeID(), size, NULL);
144
145    require(migPort, exit);
146
147    offset = migPort;
148    bzero(offset + sizeof(CFRuntimeBase), size);
149
150    CFMachPortContext context = {0, migPort, NULL, NULL, NULL};
151    migPort->port = (port != MACH_PORT_NULL) ?
152            CFMachPortCreateWithPort(allocator, port, __IOMIGMachPortPortCallback, &context, NULL) :
153            CFMachPortCreate(allocator, __IOMIGMachPortPortCallback, &context, NULL);
154
155    require(migPort->port, exit);
156
157    migPort->maxMessageSize = maxMessageSize;
158
159    return migPort;
160
161exit:
162    if ( migPort )
163        CFRelease(migPort);
164
165    return NULL;
166}
167
168//------------------------------------------------------------------------------
169// IOMIGMachPortScheduleWithDispatchQueue
170//------------------------------------------------------------------------------
171void IOMIGMachPortScheduleWithDispatchQueue(IOMIGMachPortRef migPort, dispatch_queue_t queue)
172{
173
174    mach_port_t port = CFMachPortGetPort(migPort->port);
175
176    migPort->dispatchQueue = queue;
177
178    require(migPort->dispatchQueue, exit);
179
180    // init the sources
181    if ( !migPort->dispatchSource ) {
182        migPort->dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, port, 0, migPort->dispatchQueue);
183        require(migPort->dispatchSource, exit);
184
185        dispatch_source_set_event_handler(migPort->dispatchSource, ^{
186            CFRetain(migPort);
187            mach_msg_size_t size = migPort->maxMessageSize + MAX_TRAILER_SIZE;
188            mach_msg_header_t *msg = (mach_msg_header_t *)CFAllocatorAllocate(CFGetAllocator(migPort), size, 0);
189            msg->msgh_size = size;
190            for (;;) {
191                msg->msgh_bits = 0;
192                msg->msgh_local_port = port;
193                msg->msgh_remote_port = MACH_PORT_NULL;
194                msg->msgh_id = 0;
195                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);
196                if (MACH_MSG_SUCCESS == ret) break;
197                if (MACH_RCV_TOO_LARGE != ret) goto inner_exit;
198                uint32_t newSize = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
199                msg = CFAllocatorReallocate(CFGetAllocator(migPort), msg, newSize, 0);
200                msg->msgh_size = newSize;
201            }
202
203            __IOMIGMachPortPortCallback(migPort->port, msg, msg->msgh_size, migPort);
204
205        inner_exit:
206            CFAllocatorDeallocate(kCFAllocatorSystemDefault, msg);
207            CFRelease(migPort);
208        });
209    }
210
211    dispatch_resume(migPort->dispatchSource);
212
213exit:
214    return;
215}
216
217//------------------------------------------------------------------------------
218// IOMIGMachPortUnscheduleFromDispatchQueue
219//------------------------------------------------------------------------------
220void IOMIGMachPortUnscheduleFromDispatchQueue(IOMIGMachPortRef migPort, dispatch_queue_t queue)
221{
222    if ( !queue || !migPort->dispatchQueue)
223        return;
224
225    if ( queue != migPort->dispatchQueue )
226        return;
227
228    migPort->dispatchQueue = NULL;
229
230    if ( migPort->dispatchSource ) {
231        dispatch_release(migPort->dispatchSource);
232        migPort->dispatchSource = NULL;
233    }
234}
235
236//------------------------------------------------------------------------------
237// IOMIGMachPortScheduleWithRunLoop
238//------------------------------------------------------------------------------
239void IOMIGMachPortScheduleWithRunLoop(IOMIGMachPortRef migPort, CFRunLoopRef runLoop, CFStringRef runLoopMode)
240{
241    migPort->runLoop        = runLoop;
242    migPort->runLoopMode    = runLoopMode;
243
244    require(migPort->runLoop, exit);
245    require(migPort->runLoopMode, exit);
246
247    // init the sources
248    if ( !migPort->source ) {
249        migPort->source = CFMachPortCreateRunLoopSource(CFGetAllocator(migPort), migPort->port, 1);
250        require(migPort->source, exit);
251    }
252
253    CFRunLoopAddSource(runLoop, migPort->source, runLoopMode);
254
255exit:
256    return;
257}
258
259//------------------------------------------------------------------------------
260// IOMIGMachPortUnscheduleFromRunLoop
261//------------------------------------------------------------------------------
262void IOMIGMachPortUnscheduleFromRunLoop(IOMIGMachPortRef migPort, CFRunLoopRef runLoop, CFStringRef runLoopMode)
263{
264    if ( !runLoop || !runLoopMode || !migPort->runLoop || !migPort->runLoopMode)
265        return;
266
267    if ( !CFEqual(runLoop, migPort->runLoop) || !CFEqual(runLoopMode, migPort->runLoopMode) )
268        return;
269
270    migPort->runLoop     = NULL;
271    migPort->runLoopMode = NULL;
272
273    if ( migPort->source )
274        CFRunLoopRemoveSource(runLoop, migPort->source, runLoopMode);
275}
276
277//------------------------------------------------------------------------------
278// IOMIGMachPortGetPort
279//------------------------------------------------------------------------------
280mach_port_t IOMIGMachPortGetPort(IOMIGMachPortRef migPort)
281{
282    return CFMachPortGetPort(migPort->port);
283}
284
285//------------------------------------------------------------------------------
286// IOMIGMachPortRegisterDemuxCallback
287//------------------------------------------------------------------------------
288void IOMIGMachPortRegisterDemuxCallback(IOMIGMachPortRef migPort, IOMIGMachPortDemuxCallback callback, void *refcon)
289{
290    migPort->demuxCallback = callback;
291    migPort->demuxRefcon   = refcon;
292}
293
294//------------------------------------------------------------------------------
295// IOMIGMachPortRegisterTerminationCallback
296//------------------------------------------------------------------------------
297void IOMIGMachPortRegisterTerminationCallback(IOMIGMachPortRef migPort, IOMIGMachPortTerminationCallback callback, void *refcon)
298{
299    migPort->terminationCallback = callback;
300    migPort->terminationRefcon   = refcon;
301}
302
303//------------------------------------------------------------------------------
304// __IOMIGMachPortPortCallback
305//------------------------------------------------------------------------------
306void __IOMIGMachPortPortCallback(CFMachPortRef port __unused, void *msg, CFIndex size __unused, void *info)
307{
308    IOMIGMachPortRef  migPort      = (IOMIGMachPortRef)info;
309    mig_reply_error_t * bufRequest  = msg;
310    mig_reply_error_t * bufReply    = NULL;
311    mach_msg_return_t   mr;
312    int                 options;
313
314    require(migPort, exit);
315    CFRetain(migPort);
316
317    bufReply = CFAllocatorAllocate(NULL, migPort->maxMessageSize, 0);
318    require(bufReply, exit);
319
320    // let's see if we have no more senders
321    if ( __NoMoreSenders(&bufRequest->Head, &bufReply->Head) ) {
322        if ( migPort->terminationCallback )
323            (*migPort->terminationCallback)(migPort, migPort->terminationRefcon);
324        else {
325            goto exit;
326        }
327    } else {
328        if ( migPort->demuxCallback )
329            (*migPort->demuxCallback)(migPort, &bufRequest->Head, &bufReply->Head, migPort->demuxRefcon);
330        else {
331            mach_msg_destroy(&bufRequest->Head);
332            goto exit;
333        }
334    }
335
336    if (!(bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) &&
337        (bufReply->RetCode != KERN_SUCCESS)) {
338
339        //This return code is a little tricky -- it appears that the
340        //demux routine found an error of some sort, but since that
341        //error would not normally get returned either to the local
342        //user or the remote one, we pretend it's ok.
343        require(bufReply->RetCode != MIG_NO_REPLY, exit);
344
345        // destroy any out-of-line data in the request buffer but don't destroy
346        // the reply port right (since we need that to send an error message).
347
348        bufRequest->Head.msgh_remote_port = MACH_PORT_NULL;
349        mach_msg_destroy(&bufRequest->Head);
350    }
351
352    if (bufReply->Head.msgh_remote_port == MACH_PORT_NULL) {
353        //no reply port, so destroy the reply
354        if (bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) {
355            mach_msg_destroy(&bufReply->Head);
356        }
357
358        goto exit;
359    }
360
361    // send reply.
362    // We don't want to block indefinitely because the migPort
363    // isn't receiving messages from the reply port.
364    // If we have a send-once right for the reply port, then
365    // this isn't a concern because the send won't block.
366    // If we have a send right, we need to use MACH_SEND_TIMEOUT.
367    // To avoid falling off the kernel's fast RPC path unnecessarily,
368    // we only supply MACH_SEND_TIMEOUT when absolutely necessary.
369
370    options = MACH_SEND_MSG;
371    if (MACH_MSGH_BITS_REMOTE(bufReply->Head.msgh_bits) != MACH_MSG_TYPE_MOVE_SEND_ONCE) {
372       options |= MACH_SEND_TIMEOUT;
373    }
374
375    mr = mach_msg(&bufReply->Head,
376              options,
377              bufReply->Head.msgh_size,
378              0,
379              MACH_PORT_NULL,
380              MACH_MSG_TIMEOUT_NONE,
381              MACH_PORT_NULL);
382
383
384    // Has a message error occurred?
385    switch (mr) {
386        case MACH_SEND_INVALID_DEST:
387        case MACH_SEND_TIMED_OUT:
388            // the reply can't be delivered, so destroy it
389            mach_msg_destroy(&bufReply->Head);
390            break;
391
392        default :
393            // Includes success case.
394            break;
395    }
396
397exit:
398    if ( bufReply )
399        CFAllocatorDeallocate(NULL, bufReply);
400
401    if ( migPort )
402        CFRelease(migPort);
403}
404
405//------------------------------------------------------------------------------
406// __NoMoreSenders
407//------------------------------------------------------------------------------
408Boolean __NoMoreSenders(mach_msg_header_t *request, mach_msg_header_t *reply)
409{
410	mach_no_senders_notification_t	*Request = (mach_no_senders_notification_t *)request;
411	mig_reply_error_t               *Reply   = (mig_reply_error_t *)reply;
412
413	reply->msgh_bits        = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request->msgh_bits), 0);
414	reply->msgh_remote_port = request->msgh_remote_port;
415	reply->msgh_size        = sizeof(mig_reply_error_t);	// Minimal size: update as needed
416	reply->msgh_local_port  = MACH_PORT_NULL;
417	reply->msgh_id          = request->msgh_id + 100;
418
419	if ((Request->not_header.msgh_id > MACH_NOTIFY_LAST) ||
420	    (Request->not_header.msgh_id < MACH_NOTIFY_FIRST)) {
421		Reply->NDR     = NDR_record;
422		Reply->RetCode = MIG_BAD_ID;
423		return FALSE;	// if this is not a notification message
424	}
425
426	switch (Request->not_header.msgh_id) {
427		case MACH_NOTIFY_NO_SENDERS :
428			Reply->Head.msgh_bits		= 0;
429			Reply->Head.msgh_remote_port	= MACH_PORT_NULL;
430			Reply->RetCode			= KERN_SUCCESS;
431			return TRUE;
432		default :
433			break;
434	}
435
436	Reply->NDR     = NDR_record;
437	Reply->RetCode = MIG_BAD_ID;
438	return FALSE;	// if this is not a notification we are handling
439}
440
441//------------------------------------------------------------------------------
442//IOMIGMachPortCacheAdd
443//------------------------------------------------------------------------------
444void IOMIGMachPortCacheAdd(mach_port_t port, CFTypeRef server)
445{
446    pthread_mutex_lock(&__ioPortCacheLock);
447
448    CFDictionarySetValue(__ioPortCache, (void *)port, server);
449
450    pthread_mutex_unlock(&__ioPortCacheLock);
451}
452
453//------------------------------------------------------------------------------
454// IOMIGMachPortCacheRemove
455//------------------------------------------------------------------------------
456void IOMIGMachPortCacheRemove(mach_port_t port)
457{
458    pthread_mutex_lock(&__ioPortCacheLock);
459
460    CFDictionaryRemoveValue(__ioPortCache, (void *)port);
461
462    pthread_mutex_unlock(&__ioPortCacheLock);
463}
464
465//------------------------------------------------------------------------------
466// IOMIGMachPortCacheCopy
467//------------------------------------------------------------------------------
468CFTypeRef IOMIGMachPortCacheCopy(mach_port_t port)
469{
470    CFTypeRef server;
471
472    pthread_mutex_lock(&__ioPortCacheLock);
473
474    server = (CFTypeRef)CFDictionaryGetValue(__ioPortCache, (void *)port);
475    if ( server ) CFRetain(server);
476
477    pthread_mutex_unlock(&__ioPortCacheLock);
478
479    return server;
480}
481
482