This is xnu-11215.1.10. See this file in:
/*
 * Copyright (c) 1998-2021 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

#include <IOKit/IORPC.h>
#include <IOKit/IOKitServer.h>
#include <IOKit/IOKitKeysPrivate.h>
#include <IOKit/IOKernelReportStructs.h>
#include <IOKit/IOUserClient.h>
#include <IOKit/IOService.h>
#include <IOKit/IORegistryEntry.h>
#include <IOKit/IOCatalogue.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/IOBufferMemoryDescriptor.h>
#include <IOKit/IOSubMemoryDescriptor.h>
#include <IOKit/IOMultiMemoryDescriptor.h>
#include <IOKit/IOMapper.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOHibernatePrivate.h>
#include <IOKit/IOBSD.h>
#include <IOKit/system.h>
#include "IOServicePrivate.h"
#include <IOKit/IOUserServer.h>
#include <IOKit/IOInterruptEventSource.h>
#include <IOKit/IOTimerEventSource.h>
#include <IOKit/pwr_mgt/RootDomain.h>
#include <IOKit/pwr_mgt/IOPowerConnection.h>
#include <libkern/c++/OSAllocation.h>
#include <libkern/c++/OSKext.h>
#include <libkern/c++/OSSharedPtr.h>
#include <libkern/OSDebug.h>
#include <libkern/Block.h>
#include <kern/cs_blobs.h>
#include <kern/thread_call.h>
#include <os/atomic_private.h>
#include <sys/proc.h>
#include <sys/reboot.h>
#include <sys/codesign.h>
#include "IOKitKernelInternal.h"

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <DriverKit/IODispatchQueue.h>
#include <DriverKit/OSObject.h>
#include <DriverKit/OSAction.h>
#include <DriverKit/IODispatchSource.h>
#include <DriverKit/IOInterruptDispatchSource.h>
#include <DriverKit/IOService.h>
#include <DriverKit/IOMemoryDescriptor.h>
#include <DriverKit/IOBufferMemoryDescriptor.h>
#include <DriverKit/IOMemoryMap.h>
#include <DriverKit/IODataQueueDispatchSource.h>
#include <DriverKit/IOServiceNotificationDispatchSource.h>
#include <DriverKit/IOServiceStateNotificationDispatchSource.h>
#include <DriverKit/IOEventLink.h>
#include <DriverKit/IOWorkGroup.h>
#include <DriverKit/IOUserServer.h>

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <System/IODataQueueDispatchSourceShared.h>

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

TUNABLE(SInt64, gIODKDebug, "dk", kIODKEnable);

#if DEBUG || DEVELOPMENT
TUNABLE(bool, disable_dext_crash_reboot, "disable_dext_crash_reboot", 0);
#endif /* DEBUG || DEVELOPMENT */

extern bool restore_boot;

static OSString       * gIOSystemStateSleepDescriptionKey;
static const OSSymbol * gIOSystemStateSleepDescriptionReasonKey;
static const OSSymbol * gIOSystemStateSleepDescriptionHibernateStateKey;

static OSString       * gIOSystemStateWakeDescriptionKey;
static const OSSymbol * gIOSystemStateWakeDescriptionWakeReasonKey;
static const OSSymbol * gIOSystemStateWakeDescriptionContinuousTimeOffsetKey;

static OSString       * gIOSystemStateHaltDescriptionKey;
static const OSSymbol * gIOSystemStateHaltDescriptionHaltStateKey;

static OSString       * gIOSystemStatePowerSourceDescriptionKey;
static const OSSymbol * gIOSystemStatePowerSourceDescriptionACAttachedKey;

extern bool gInUserspaceReboot;

extern void iokit_clear_registered_ports(task_t task);

static IORPCMessage *
IORPCMessageFromMachReply(IORPCMessageMach * msg);

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

struct IOPStrings;

class OSUserMetaClass : public OSObject
{
	OSDeclareDefaultStructors(OSUserMetaClass);
public:
	const OSSymbol    * name;
	const OSMetaClass * meta;
	OSUserMetaClass   * superMeta;

	queue_chain_t       link;

	OSClassDescription * description;
	IOPStrings * queueNames;
	uint32_t     methodCount;
	uint64_t   * methods;

	virtual void free() override;
	virtual kern_return_t Dispatch(const IORPC rpc) APPLE_KEXT_OVERRIDE;
};
OSDefineMetaClassAndStructors(OSUserMetaClass, OSObject);

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

class IOUserService : public IOService
{
	friend class IOService;

	OSDeclareDefaultStructors(IOUserService)

	virtual bool
	start(IOService * provider) APPLE_KEXT_OVERRIDE;
};

OSDefineMetaClassAndStructors(IOUserService, IOService)

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

class IOUserUserClient : public IOUserClient
{
	OSDeclareDefaultStructors(IOUserUserClient);
public:
	task_t          fTask;
	OSDictionary  * fWorkGroups;
	OSDictionary  * fEventLinks;
	IOLock        * fLock;

	IOReturn                   setTask(task_t task);
	IOReturn                   eventlinkConfigurationTrap(void * p1, void * p2, void * p3, void * p4, void * p5, void * p6);
	IOReturn                   workgroupConfigurationTrap(void * p1, void * p2, void * p3, void * p4, void * p5, void * p6);

	virtual bool           init( OSDictionary * dictionary ) APPLE_KEXT_OVERRIDE;
	virtual void           free() APPLE_KEXT_OVERRIDE;
	virtual void           stop(IOService * provider) APPLE_KEXT_OVERRIDE;
	virtual IOReturn       clientClose(void) APPLE_KEXT_OVERRIDE;
	virtual IOReturn       setProperties(OSObject * properties) APPLE_KEXT_OVERRIDE;
	virtual IOReturn       externalMethod(uint32_t selector, IOExternalMethodArguments * args,
	    IOExternalMethodDispatch * dispatch, OSObject * target, void * reference) APPLE_KEXT_OVERRIDE;
	virtual IOReturn           clientMemoryForType(UInt32 type,
	    IOOptionBits * options,
	    IOMemoryDescriptor ** memory) APPLE_KEXT_OVERRIDE;
	virtual IOExternalTrap * getTargetAndTrapForIndex( IOService **targetP, UInt32 index ) APPLE_KEXT_OVERRIDE;
};

OSDefineMetaClassAndStructors(IOUserServerCheckInToken, OSObject);
OSDefineMetaClassAndStructors(_IOUserServerCheckInCancellationHandler, OSObject);

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


bool
IOUserService::start(IOService * provider)
{
	bool     ok = true;
	IOReturn ret;

	ret = Start(provider);
	if (kIOReturnSuccess != ret) {
		return false;
	}

	return ok;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#undef super

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

struct IODispatchQueue_IVars {
	IOUserServer * userServer;
	IODispatchQueue   * queue;
	queue_chain_t  link;
	uint64_t       tid;

	mach_port_t    serverPort;
};

struct OSAction_IVars {
	OSObject             * target;
	uint64_t               targetmsgid;
	uint64_t               msgid;
	IOUserServer         * userServer;
	OSActionAbortedHandler abortedHandler;
	OSString             * typeName;
	void                 * reference;
	size_t                 referenceSize;
	bool                   aborted;
};

struct IOWorkGroup_IVars {
	IOUserServer * userServer;
	OSString * name;
	IOUserUserClient * userClient;
};

struct IOEventLink_IVars {
	IOUserServer * userServer;
	OSString * name;
	IOUserUserClient * userClient;
};

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IOService::GetRegistryEntryID_Impl(
	uint64_t * registryEntryID)
{
	IOReturn ret = kIOReturnSuccess;

	*registryEntryID = getRegistryEntryID();

	return ret;
}

kern_return_t
IOService::SetName_Impl(
	const char * name)
{
	IOReturn ret = kIOReturnSuccess;

	setName(name);

	return ret;
}

kern_return_t
IOService::CopyName_Impl(
	OSString ** name)
{
	const OSString * str = copyName();
	*name = __DECONST(OSString *, str);
	return str ? kIOReturnSuccess : kIOReturnError;
}


kern_return_t
IOService::Start_Impl(
	IOService * provider)
{
	IOReturn ret = kIOReturnSuccess;
	return ret;
}


IOReturn
IOService::UpdateReport_Impl(OSData *channels, uint32_t action,
    uint32_t *outElementCount,
    uint64_t offset, uint64_t capacity,
    IOMemoryDescriptor *buffer)
{
	return kIOReturnUnsupported;
}

IOReturn
IOService::ConfigureReport_Impl(OSData *channels, uint32_t action, uint32_t *outCount)
{
	return kIOReturnUnsupported;
}

// adapt old signature of configureReport to the iig-friendly signature of ConfigureReport
IOReturn
IOService::_ConfigureReport(IOReportChannelList    *channelList,
    IOReportConfigureAction action,
    void                   *result,
    void                   *destination)
{
	if (action != kIOReportEnable && action != kIOReportGetDimensions && action != kIOReportDisable) {
		return kIOReturnUnsupported;
	}
	static_assert(sizeof(IOReportChannelList) == 8);
	static_assert(sizeof(IOReportChannel) == 16);
	unsigned int size_of_channels;
	bool overflow = os_mul_and_add_overflow(channelList->nchannels, sizeof(IOReportChannel), sizeof(IOReportChannelList), &size_of_channels);
	if (overflow) {
		return kIOReturnOverrun;
	}
	OSSharedPtr<OSData> sp_channels(OSData::withBytesNoCopy(channelList, size_of_channels), libkern::no_retain);
	if (!sp_channels) {
		return kIOReturnNoMemory;
	}
	int *resultp = (int*) result;
	uint32_t count = 0;
	IOReturn r = ConfigureReport(sp_channels.get(), action, &count);
	int new_result;
	overflow = os_add_overflow(*resultp, count, &new_result);
	if (overflow) {
		return kIOReturnOverrun;
	}
	*resultp = new_result;
	return r;
}

// adapt old signature of updateReport to the iig-friendly signature of UpdateReport
IOReturn
IOService::_UpdateReport(IOReportChannelList      *channelList,
    IOReportUpdateAction      action,
    void                     *result,
    void                     *destination)
{
	if (action != kIOReportCopyChannelData) {
		return kIOReturnUnsupported;
	}
	unsigned int size_of_channels;
	bool overflow = os_mul_and_add_overflow(channelList->nchannels, sizeof(IOReportChannel), sizeof(IOReportChannelList), &size_of_channels);
	if (overflow) {
		return kIOReturnOverrun;
	}
	OSSharedPtr<OSData> sp_channels(OSData::withBytesNoCopy(channelList, size_of_channels), libkern::no_retain);
	if (!sp_channels) {
		return kIOReturnNoMemory;
	}
	int *resultp = (int*) result;
	uint32_t count = 0;
	auto buffer = (IOBufferMemoryDescriptor*) destination;
	uint64_t length = buffer->getLength();
	buffer->setLength(buffer->getCapacity());
	IOReturn r = UpdateReport(sp_channels.get(), action, &count, length, buffer->getCapacity() - length, buffer);
	int new_result;
	overflow = os_add_overflow(*resultp, count, &new_result);
	size_t new_length;
	overflow = overflow || os_mul_and_add_overflow(count, sizeof(IOReportElement), length, &new_length);
	if (overflow || new_length > buffer->getCapacity()) {
		buffer->setLength(length);
		return kIOReturnOverrun;
	}
	*resultp = new_result;
	buffer->setLength(new_length);
	return r;
}


IOReturn
IOService::SetLegend_Impl(OSArray *legend, bool is_public)
{
	bool ok = setProperty(kIOReportLegendKey, legend);
	ok = ok && setProperty(kIOReportLegendPublicKey, is_public);
	return ok ? kIOReturnSuccess : kIOReturnError;
}


kern_return_t
IOService::RegisterService_Impl()
{
	IOReturn ret = kIOReturnSuccess;
	bool started;

	IOUserServer *us = (typeof(us))thread_iokit_tls_get(0);
	if (reserved != NULL && reserved->uvars != NULL && reserved->uvars->userServer == us) {
		started = reserved->uvars->started;
	} else {
		// assume started
		started = true;
	}

	if (OSDynamicCast(IOUserServer, this) != NULL || started) {
		registerService(kIOServiceAsynchronous);
	} else {
		assert(reserved != NULL && reserved->uvars != NULL);
		reserved->uvars->deferredRegisterService = true;
	}

	return ret;
}

kern_return_t
IOService::CopyDispatchQueue_Impl(
	const char * name,
	IODispatchQueue ** queue)
{
	IODispatchQueue * result;
	IOService  * service;
	IOReturn     ret;
	uint32_t index;

	if (!reserved->uvars) {
		return kIOReturnError;
	}

	if (!reserved->uvars->queueArray) {
		// CopyDispatchQueue should not be called after the service has stopped
		return kIOReturnError;
	}

	ret = kIOReturnNotFound;
	index = -1U;
	if (!strcmp("Default", name)) {
		index = 0;
	} else if (reserved->uvars->userMeta
	    && reserved->uvars->userMeta->queueNames) {
		index = reserved->uvars->userServer->stringArrayIndex(reserved->uvars->userMeta->queueNames, name);
		if (index != -1U) {
			index++;
		}
	}
	if (index == -1U) {
		if ((service = getProvider())) {
			ret = service->CopyDispatchQueue(name, queue);
		}
	} else {
		result = reserved->uvars->queueArray[index];
		if (result) {
			result->retain();
			*queue = result;
			ret = kIOReturnSuccess;
		}
	}

	return ret;
}

kern_return_t
IOService::CreateDefaultDispatchQueue_Impl(
	IODispatchQueue ** queue)
{
	return kIOReturnError;
}

kern_return_t
IOService::CoreAnalyticsSendEvent_Impl(
	uint64_t       options,
	OSString     * eventName,
	OSDictionary * eventPayload)
{
	kern_return_t ret;

	if (NULL == gIOCoreAnalyticsSendEventProc) {
		// perhaps save for later?
		return kIOReturnNotReady;
	}

	ret = (*gIOCoreAnalyticsSendEventProc)(options, eventName, eventPayload);

	return ret;
}

kern_return_t
IOService::SetDispatchQueue_Impl(
	const char * name,
	IODispatchQueue * queue)
{
	IOReturn ret = kIOReturnSuccess;
	uint32_t index;

	if (!reserved->uvars) {
		return kIOReturnError;
	}

	if (kIODKLogSetup & gIODKDebug) {
		DKLOG(DKS "::SetDispatchQueue(%s)\n", DKN(this), name);
	}
	queue->ivars->userServer = reserved->uvars->userServer;
	index = -1U;
	if (!strcmp("Default", name)) {
		index = 0;
	} else if (reserved->uvars->userMeta
	    && reserved->uvars->userMeta->queueNames) {
		index = reserved->uvars->userServer->stringArrayIndex(reserved->uvars->userMeta->queueNames, name);
		if (index != -1U) {
			index++;
		}
	}
	if (index == -1U) {
		ret = kIOReturnBadArgument;
	} else {
		reserved->uvars->queueArray[index] = queue;
		queue->retain();
	}

	return ret;
}

IOService *
IOService::GetProvider() const
{
	return getProvider();
}

kern_return_t
IOService::SetProperties_Impl(
	OSDictionary * properties)
{
	IOUserServer   * us;
	OSDictionary   * dict;
	IOReturn         ret;

	us = (typeof(us))thread_iokit_tls_get(0);
	dict = OSDynamicCast(OSDictionary, properties);
	if (NULL == us) {
		if (!dict) {
			return kIOReturnBadArgument;
		}
		bool ok __block = true;
		dict->iterateObjects(^bool (const OSSymbol * key, OSObject * value) {
			ok = setProperty(key, value);
			return !ok;
		});
		ret = ok ? kIOReturnSuccess : kIOReturnNotWritable;
		return ret;
	}

	ret = setProperties(properties);

	if (kIOReturnUnsupported == ret) {
		if (dict && reserved->uvars && (reserved->uvars->userServer == us)) {
			ret = runPropertyActionBlock(^IOReturn (void) {
				OSDictionary   * userProps;
				IOReturn         ret;

				userProps = OSDynamicCast(OSDictionary, getProperty(gIOUserServicePropertiesKey));
				if (userProps) {
				        userProps = (typeof(userProps))userProps->copyCollection();
				} else {
				        userProps = OSDictionary::withCapacity(4);
				}
				if (!userProps) {
				        ret = kIOReturnNoMemory;
				} else {
				        bool ok = userProps->merge(dict);
				        if (ok) {
				                ok = setProperty(gIOUserServicePropertiesKey, userProps);
					}
				        OSSafeReleaseNULL(userProps);
				        ret = ok ? kIOReturnSuccess : kIOReturnNotWritable;
				}
				return ret;
			});
		}
	}

	return ret;
}

kern_return_t
IOService::RemoveProperty_Impl(OSString * propertyName)
{
	IOUserServer * us  = (IOUserServer *)thread_iokit_tls_get(0);
	IOReturn       ret = kIOReturnUnsupported;

	if (NULL == propertyName) {
		return kIOReturnUnsupported;
	}
	if (NULL == us) {
		removeProperty(propertyName);
		return kIOReturnSuccess;
	}
	if (reserved && reserved->uvars && reserved->uvars->userServer == us) {
		ret = runPropertyActionBlock(^IOReturn (void) {
			OSDictionary * userProps;
			userProps = OSDynamicCast(OSDictionary, getProperty(gIOUserServicePropertiesKey));
			if (userProps) {
			        userProps = (OSDictionary *)userProps->copyCollection();
			        if (!userProps) {
			                return kIOReturnNoMemory;
				}
			        userProps->removeObject(propertyName);
			        bool ok = setProperty(gIOUserServicePropertiesKey, userProps);
			        OSSafeReleaseNULL(userProps);
			        return ok ? kIOReturnSuccess : kIOReturnNotWritable;
			} else {
			        return kIOReturnNotFound;
			}
		});
	}
	return ret;
}

kern_return_t
IOService::CopyProperties_Local(
	OSDictionary ** properties)
{
	OSDictionary * props;
	OSDictionary * userProps;

	props = dictionaryWithProperties();
	userProps = OSDynamicCast(OSDictionary, props->getObject(gIOUserServicePropertiesKey));
	if (userProps) {
		props->merge(userProps);
		props->removeObject(gIOUserServicePropertiesKey);
	}

	*properties = props;

	return props ? kIOReturnSuccess : kIOReturnNoMemory;
}

kern_return_t
IOService::CopyProperties_Impl(
	OSDictionary ** properties)
{
	return CopyProperties_Local(properties);
}

kern_return_t
IOService::RequireMaxBusStall_Impl(
	uint64_t u64ns)
{
	IOReturn ret;
	UInt32   ns;

	if (os_convert_overflow(u64ns, &ns)) {
		return kIOReturnBadArgument;
	}
	ret = requireMaxBusStall(ns);

	return ret;
}

#if PRIVATE_WIFI_ONLY
kern_return_t
IOService::UserSetProperties_Impl(
	OSContainer * properties)
{
	return kIOReturnUnsupported;
}

kern_return_t
IOService::SendIOMessageServicePropertyChange_Impl(void)
{
	return messageClients(kIOMessageServicePropertyChange);
}
#endif /* PRIVATE_WIFI_ONLY */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IOMemoryDescriptor::_CopyState_Impl(
	_IOMDPrivateState * state)
{
	IOReturn ret;

	state->length = _length;
	state->options = _flags;

	ret = kIOReturnSuccess;

	return ret;
}

kern_return_t
IOMemoryDescriptor::GetLength(uint64_t * returnLength)
{
	*returnLength = getLength();

	return kIOReturnSuccess;
}

kern_return_t
IOMemoryDescriptor::CreateMapping_Impl(
	uint64_t options,
	uint64_t address,
	uint64_t offset,
	uint64_t length,
	uint64_t alignment,
	IOMemoryMap ** map)
{
	IOReturn          ret;
	IOMemoryMap     * resultMap;
	IOOptionBits      koptions;
	mach_vm_address_t atAddress;

	ret       = kIOReturnSuccess;
	koptions  = 0;
	resultMap = NULL;

	if (kIOMemoryMapFixedAddress & options) {
		atAddress   = address;
		koptions    = 0;
	} else {
		switch (kIOMemoryMapGuardedMask & options) {
		default:
		case kIOMemoryMapGuardedDefault:
			koptions |= kIOMapGuardedSmall;
			break;
		case kIOMemoryMapGuardedNone:
			break;
		case kIOMemoryMapGuardedSmall:
			koptions |= kIOMapGuardedSmall;
			break;
		case kIOMemoryMapGuardedLarge:
			koptions |= kIOMapGuardedLarge;
			break;
		}
		atAddress   = 0;
		koptions   |= kIOMapAnywhere;
	}

	if ((kIOMemoryMapReadOnly & options) || (kIODirectionOut == getDirection())) {
		if (!reserved || (current_task() != reserved->creator)) {
			koptions   |= kIOMapReadOnly;
		}
	}

	switch (0xFF00 & options) {
	case kIOMemoryMapCacheModeDefault:
		koptions |= kIOMapDefaultCache;
		break;
	case kIOMemoryMapCacheModeInhibit:
		koptions |= kIOMapInhibitCache;
		break;
	case kIOMemoryMapCacheModeCopyback:
		koptions |= kIOMapCopybackCache;
		break;
	case kIOMemoryMapCacheModeWriteThrough:
		koptions |= kIOMapWriteThruCache;
		break;
	case kIOMemoryMapCacheModeRealTime:
		koptions |= kIOMapRealTimeCache;
		break;
	default:
		ret = kIOReturnBadArgument;
	}

	if (kIOReturnSuccess == ret) {
		resultMap = createMappingInTask(current_task(), atAddress, koptions, offset, length);
		if (!resultMap) {
			ret = kIOReturnError;
		}
	}

	*map = resultMap;

	return ret;
}

kern_return_t
IOMemoryDescriptor::CreateSubMemoryDescriptor_Impl(
	uint64_t memoryDescriptorCreateOptions,
	uint64_t offset,
	uint64_t length,
	IOMemoryDescriptor * ofDescriptor,
	IOMemoryDescriptor ** memory)
{
	IOReturn             ret;
	IOMemoryDescriptor * iomd;
	IOByteCount          mdOffset;
	IOByteCount          mdLength;
	IOByteCount          mdEnd;

	if (!ofDescriptor) {
		return kIOReturnBadArgument;
	}
	if (memoryDescriptorCreateOptions & ~kIOMemoryDirectionOutIn) {
		return kIOReturnBadArgument;
	}
	if (os_convert_overflow(offset, &mdOffset)) {
		return kIOReturnBadArgument;
	}
	if (os_convert_overflow(length, &mdLength)) {
		return kIOReturnBadArgument;
	}
	if (os_add_overflow(mdOffset, mdLength, &mdEnd)) {
		return kIOReturnBadArgument;
	}
	if (mdEnd > ofDescriptor->getLength()) {
		return kIOReturnBadArgument;
	}

	iomd = IOSubMemoryDescriptor::withSubRange(
		ofDescriptor, mdOffset, mdLength, (IOOptionBits) memoryDescriptorCreateOptions);

	if (iomd) {
		ret = kIOReturnSuccess;
		*memory = iomd;
	} else {
		ret = kIOReturnNoMemory;
		*memory = NULL;
	}

	return ret;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IOMemoryDescriptor::CreateWithMemoryDescriptors_Impl(
	uint64_t memoryDescriptorCreateOptions,
	uint32_t withDescriptorsCount,
	IOMemoryDescriptor ** const withDescriptors,
	IOMemoryDescriptor ** memory)
{
	IOReturn             ret;
	IOMemoryDescriptor * iomd;

	if (!withDescriptors) {
		return kIOReturnBadArgument;
	}
	if (!withDescriptorsCount) {
		return kIOReturnBadArgument;
	}
	if (memoryDescriptorCreateOptions & ~kIOMemoryDirectionOutIn) {
		return kIOReturnBadArgument;
	}

	for (unsigned int idx = 0; idx < withDescriptorsCount; idx++) {
		if (NULL == withDescriptors[idx]) {
			return kIOReturnBadArgument;
		}
	}

	iomd = IOMultiMemoryDescriptor::withDescriptors(withDescriptors, withDescriptorsCount,
	    (IODirection) memoryDescriptorCreateOptions, false);

	if (iomd) {
		ret = kIOReturnSuccess;
		*memory = iomd;
	} else {
		ret = kIOReturnNoMemory;
		*memory = NULL;
	}

	return ret;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * * * * * * * * * * */

kern_return_t
IOUserClient::CreateMemoryDescriptorFromClient_Impl(
	uint64_t memoryDescriptorCreateOptions,
	uint32_t segmentsCount,
	const IOAddressSegment segments[32],
	IOMemoryDescriptor ** memory)
{
	IOReturn             ret;
	IOMemoryDescriptor * iomd;
	IOOptionBits         mdOptions;
	IOUserUserClient   * me;
	IOAddressRange     * ranges;

	me = OSDynamicCast(IOUserUserClient, this);
	if (!me) {
		return kIOReturnBadArgument;
	}
	if (!me->fTask) {
		return kIOReturnNotReady;
	}

	mdOptions = kIOMemoryThreadSafe;
	if (kIOMemoryDirectionOut & memoryDescriptorCreateOptions) {
		mdOptions |= kIODirectionOut;
	}
	if (kIOMemoryDirectionIn & memoryDescriptorCreateOptions) {
		mdOptions |= kIODirectionIn;
	}
	if (!(kIOMemoryDisableCopyOnWrite & memoryDescriptorCreateOptions)) {
		mdOptions |= kIOMemoryMapCopyOnWrite;
	}

	static_assert(sizeof(IOAddressRange) == sizeof(IOAddressSegment));
	ranges = __DECONST(IOAddressRange *, &segments[0]);

	iomd = IOMemoryDescriptor::withAddressRanges(
		ranges, segmentsCount,
		mdOptions, me->fTask);

	if (iomd) {
		ret = kIOReturnSuccess;
		*memory = iomd;
	} else {
		ret = kIOReturnNoMemory;
		*memory = NULL;
	}

	return ret;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IOMemoryMap::_CopyState_Impl(
	_IOMemoryMapPrivateState * state)
{
	IOReturn ret;

	state->offset  = fOffset;
	state->length  = getLength();
	state->address = getAddress();
	state->options = getMapOptions();

	ret = kIOReturnSuccess;

	return ret;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IOBufferMemoryDescriptor::Create_Impl(
	uint64_t options,
	uint64_t capacity,
	uint64_t alignment,
	IOBufferMemoryDescriptor ** memory)
{
	IOReturn ret;
	IOOptionBits                 bmdOptions;
	IOBufferMemoryDescriptor   * bmd;
	IOMemoryDescriptorReserved * reserved;

	if (options & ~((uint64_t) kIOMemoryDirectionOutIn)) {
		// no other options currently defined
		return kIOReturnBadArgument;
	}
	bmdOptions = (options & kIOMemoryDirectionOutIn) | kIOMemoryKernelUserShared | kIOMemoryThreadSafe;
	bmd = IOBufferMemoryDescriptor::inTaskWithOptions(
		kernel_task, bmdOptions, capacity, alignment);

	*memory = bmd;

	if (!bmd) {
		return kIOReturnNoMemory;
	}

	reserved = bmd->getKernelReserved();
	reserved->creator = current_task();
	task_reference(reserved->creator);

	ret = kIOReturnSuccess;

	return ret;
}

kern_return_t
IOBufferMemoryDescriptor::SetLength_Impl(
	uint64_t length)
{
	setLength(length);
	return kIOReturnSuccess;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IODMACommand::Create_Impl(
	IOService * device,
	uint64_t options,
	const IODMACommandSpecification * specification,
	IODMACommand ** command)
{
	IOReturn ret;
	IODMACommand   * dma;
	IODMACommand::SegmentOptions segmentOptions;
	IOMapper             * mapper;

	if (options & ~((uint64_t) kIODMACommandCreateNoOptions)) {
		// no other options currently defined
		return kIOReturnBadArgument;
	}

	if (os_convert_overflow(specification->maxAddressBits, &segmentOptions.fNumAddressBits)) {
		return kIOReturnBadArgument;
	}
	segmentOptions.fMaxSegmentSize            = 0;
	segmentOptions.fMaxTransferSize           = 0;
	segmentOptions.fAlignment                 = 1;
	segmentOptions.fAlignmentLength           = 1;
	segmentOptions.fAlignmentInternalSegments = 1;
	segmentOptions.fStructSize                = sizeof(segmentOptions);

	mapper = IOMapper::copyMapperForDevice(device);

	dma = IODMACommand::withSpecification(
		kIODMACommandOutputHost64,
		&segmentOptions,
		kIODMAMapOptionDextOwner |
		kIODMAMapOptionMapped,
		mapper,
		NULL);

	OSSafeReleaseNULL(mapper);
	*command = dma;

	if (!dma) {
		return kIOReturnNoMemory;
	}
	ret = kIOReturnSuccess;

	return ret;
}

#define fInternalState reserved

kern_return_t
IODMACommand::PrepareForDMA_Impl(
	uint64_t options,
	IOMemoryDescriptor * memory,
	uint64_t offset,
	uint64_t length,
	uint64_t * flags,
	uint32_t * segmentsCount,
	IOAddressSegment * segments)
{
	IOReturn ret;
	uint64_t lflags, mdFlags;
	UInt32   numSegments;
	UInt64   genOffset;

	if (options & ~((uint64_t) kIODMACommandPrepareForDMANoOptions)) {
		// no other options currently defined
		return kIOReturnBadArgument;
	}

	if (memory == NULL) {
		return kIOReturnBadArgument;
	}

	assert(fInternalState->fDextLock);
	IOLockLock(fInternalState->fDextLock);

	// uses IOMD direction
	ret = memory->prepare();
	if (kIOReturnSuccess != ret) {
		goto exit;
	}

	ret = setMemoryDescriptor(memory, false);
	if (kIOReturnSuccess != ret) {
		memory->complete();
		goto exit;
	}

	ret = prepare(offset, length);
	if (kIOReturnSuccess != ret) {
		clearMemoryDescriptor(false);
		memory->complete();
		goto exit;
	}

	static_assert(sizeof(IODMACommand::Segment64) == sizeof(IOAddressSegment));

	numSegments = *segmentsCount;
	genOffset   = 0;
	ret = genIOVMSegments(&genOffset, segments, &numSegments);

	if (kIOReturnSuccess != ret) {
		clearMemoryDescriptor(true);
		memory->complete();
		goto exit;
	}

	mdFlags = fMemory->getFlags();
	lflags  = 0;
	if (kIODirectionOut & mdFlags) {
		lflags |= kIOMemoryDirectionOut;
	}
	if (kIODirectionIn & mdFlags) {
		lflags |= kIOMemoryDirectionIn;
	}
	*flags = lflags;
	*segmentsCount = numSegments;

exit:
	IOLockUnlock(fInternalState->fDextLock);

	return ret;
}

kern_return_t
IODMACommand::CompleteDMA_Impl(
	uint64_t options)
{
	IOReturn ret, completeRet;
	IOMemoryDescriptor * md;

	if (options & ~((uint64_t) kIODMACommandCompleteDMANoOptions)) {
		// no other options currently defined
		return kIOReturnBadArgument;
	}

	assert(fInternalState->fDextLock);
	IOLockLock(fInternalState->fDextLock);

	if (!fInternalState->fPrepared) {
		ret = kIOReturnNotReady;
		goto exit;
	}

	md = __DECONST(IOMemoryDescriptor *, fMemory);
	if (md) {
		md->retain();
	}

	ret = clearMemoryDescriptor(true);

	if (md) {
		completeRet = md->complete();
		OSSafeReleaseNULL(md);
		if (kIOReturnSuccess == ret) {
			ret = completeRet;
		}
	}
exit:
	IOLockUnlock(fInternalState->fDextLock);

	return ret;
}

kern_return_t
IODMACommand::GetPreparation_Impl(
	uint64_t * offset,
	uint64_t * length,
	IOMemoryDescriptor ** memory)
{
	IOReturn ret;
	IOMemoryDescriptor * md;

	if (!fActive) {
		return kIOReturnNotReady;
	}

	ret = getPreparedOffsetAndLength(offset, length);
	if (kIOReturnSuccess != ret) {
		return ret;
	}

	if (memory) {
		md = __DECONST(IOMemoryDescriptor *, fMemory);
		*memory = md;
		if (!md) {
			ret = kIOReturnNotReady;
		} else {
			md->retain();
		}
	}
	return ret;
}

kern_return_t
IODMACommand::PerformOperation_Impl(
	uint64_t options,
	uint64_t dmaOffset,
	uint64_t length,
	uint64_t dataOffset,
	IOMemoryDescriptor * data)
{
	IOReturn ret;
	OSDataAllocation<uint8_t> buffer;
	UInt64 copiedDMA;
	IOByteCount mdOffset, mdLength, copied;

	if (options & ~((uint64_t)
	    (kIODMACommandPerformOperationOptionRead
	    | kIODMACommandPerformOperationOptionWrite
	    | kIODMACommandPerformOperationOptionZero))) {
		// no other options currently defined
		return kIOReturnBadArgument;
	}

	if (!fActive) {
		return kIOReturnNotReady;
	}
	if (os_convert_overflow(dataOffset, &mdOffset)) {
		return kIOReturnBadArgument;
	}
	if (os_convert_overflow(length, &mdLength)) {
		return kIOReturnBadArgument;
	}
	if (length > fMemory->getLength()) {
		return kIOReturnBadArgument;
	}
	buffer = OSDataAllocation<uint8_t>(length, OSAllocateMemory);
	if (!buffer) {
		return kIOReturnNoMemory;
	}

	switch (options) {
	case kIODMACommandPerformOperationOptionZero:
		bzero(buffer.data(), length);
		copiedDMA = writeBytes(dmaOffset, buffer.data(), length);
		if (copiedDMA != length) {
			ret = kIOReturnUnderrun;
			break;
		}
		ret = kIOReturnSuccess;
		break;

	case kIODMACommandPerformOperationOptionRead:
	case kIODMACommandPerformOperationOptionWrite:

		if (!data) {
			ret = kIOReturnBadArgument;
			break;
		}
		if (length > data->getLength()) {
			ret = kIOReturnBadArgument;
			break;
		}
		if (kIODMACommandPerformOperationOptionWrite == options) {
			copied = data->readBytes(mdOffset, buffer.data(), mdLength);
			if (copied != mdLength) {
				ret = kIOReturnUnderrun;
				break;
			}
			copiedDMA = writeBytes(dmaOffset, buffer.data(), length);
			if (copiedDMA != length) {
				ret = kIOReturnUnderrun;
				break;
			}
		} else {       /* kIODMACommandPerformOperationOptionRead */
			copiedDMA = readBytes(dmaOffset, buffer.data(), length);
			if (copiedDMA != length) {
				ret = kIOReturnUnderrun;
				break;
			}
			copied = data->writeBytes(mdOffset, buffer.data(), mdLength);
			if (copied != mdLength) {
				ret = kIOReturnUnderrun;
				break;
			}
		}
		ret = kIOReturnSuccess;
		break;
	default:
		ret = kIOReturnBadArgument;
		break;
	}

	return ret;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

static kern_return_t
OSActionCreateWithTypeNameInternal(OSObject * target, uint64_t targetmsgid, uint64_t msgid, size_t referenceSize, OSString * typeName, bool fromKernel, OSAction ** action)
{
	OSAction * inst = NULL;
	void * reference = NULL; // must release
	const OSSymbol *sym = NULL; // must release
	OSObject *obj = NULL; // must release
	const OSMetaClass *actionMetaClass = NULL; // do not release
	kern_return_t ret;

	if (fromKernel && typeName) {
		/* The action is being constructed in the kernel with a type name */
		sym = OSSymbol::withString(typeName);
		actionMetaClass = OSMetaClass::getMetaClassWithName(sym);
		if (actionMetaClass && actionMetaClass->getSuperClass() == OSTypeID(OSAction)) {
			obj = actionMetaClass->alloc();
			if (!obj) {
				ret = kIOReturnNoMemory;
				goto finish;
			}
			inst = OSDynamicCast(OSAction, obj);
			obj = NULL; // prevent release
			assert(inst); // obj is a subclass of OSAction so the dynamic cast should always work
		} else {
			DKLOG("Attempted to create action object with type \"%s\" which does not inherit from OSAction\n", typeName->getCStringNoCopy());
			ret = kIOReturnBadArgument;
			goto finish;
		}
	} else {
		inst = OSTypeAlloc(OSAction);
		if (!inst) {
			ret = kIOReturnNoMemory;
			goto finish;
		}
	}

	if (referenceSize != 0) {
		reference = IONewZeroData(uint8_t, referenceSize);
		if (reference == NULL) {
			ret = kIOReturnNoMemory;
			goto finish;
		}
	}

	inst->ivars = IONewZero(OSAction_IVars, 1);
	if (!inst->ivars) {
		ret = kIOReturnNoMemory;
		goto finish;
	}
	if (target) {
		target->retain();
		if (!fromKernel && !OSDynamicCast(IOService, target)) {
			IOUserServer * us;
			us = (typeof(us))thread_iokit_tls_get(0);
			inst->ivars->userServer = OSDynamicCast(IOUserServer, us);
			assert(inst->ivars->userServer);
			inst->ivars->userServer->retain();
		}
	}
	inst->ivars->target        = target;
	inst->ivars->targetmsgid   = targetmsgid;
	inst->ivars->msgid         = msgid;

	inst->ivars->reference     = reference;
	inst->ivars->referenceSize = referenceSize;
	reference = NULL; // prevent release

	if (typeName) {
		typeName->retain();
	}
	inst->ivars->typeName      = typeName;

	*action = inst;
	inst = NULL; // prevent release
	ret = kIOReturnSuccess;

finish:
	OSSafeReleaseNULL(obj);
	OSSafeReleaseNULL(sym);
	OSSafeReleaseNULL(inst);
	if (reference) {
		IODeleteData(reference, uint8_t, referenceSize);
	}

	return ret;
}

kern_return_t
OSAction::Create(OSAction_Create_Args)
{
	return OSAction::CreateWithTypeName(target, targetmsgid, msgid, referenceSize, NULL, action);
}

kern_return_t
OSAction::CreateWithTypeName(OSAction_CreateWithTypeName_Args)
{
	return OSActionCreateWithTypeNameInternal(target, targetmsgid, msgid, referenceSize, typeName, true, action);
}

kern_return_t
OSAction::Create_Impl(
	OSObject * target,
	uint64_t targetmsgid,
	uint64_t msgid,
	size_t referenceSize,
	OSAction ** action)
{
	return OSAction::CreateWithTypeName_Impl(target, targetmsgid, msgid, referenceSize, NULL, action);
}

kern_return_t
OSAction::CreateWithTypeName_Impl(
	OSObject * target,
	uint64_t targetmsgid,
	uint64_t msgid,
	size_t referenceSize,
	OSString * typeName,
	OSAction ** action)
{
	return OSActionCreateWithTypeNameInternal(target, targetmsgid, msgid, referenceSize, typeName, false, action);
}

void
OSAction::free()
{
	if (ivars) {
		if (ivars->abortedHandler) {
			Block_release(ivars->abortedHandler);
			ivars->abortedHandler = NULL;
		}
		OSSafeReleaseNULL(ivars->target);
		OSSafeReleaseNULL(ivars->typeName);
		OSSafeReleaseNULL(ivars->userServer);
		if (ivars->reference) {
			assert(ivars->referenceSize > 0);
			IODeleteData(ivars->reference, uint8_t, ivars->referenceSize);
		}
		IOSafeDeleteNULL(ivars, OSAction_IVars, 1);
	}
	return super::free();
}

void *
OSAction::GetReference()
{
	assert(ivars && ivars->referenceSize && ivars->reference);
	return ivars->reference;
}

kern_return_t
OSAction::SetAbortedHandler(OSActionAbortedHandler handler)
{
	ivars->abortedHandler = Block_copy(handler);
	return kIOReturnSuccess;
}

void
OSAction::Aborted_Impl(void)
{
	if (!os_atomic_cmpxchg(&ivars->aborted, false, true, relaxed)) {
		// already aborted
		return;
	}
	if (ivars->abortedHandler) {
		ivars->abortedHandler();
	}
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

struct IODispatchSource_IVars {
	queue_chain_t           link;
	IODispatchSource      * source;
	IOUserServer          * server;
	IODispatchQueue_IVars * queue;
	bool                    enabled;
};

bool
IODispatchSource::init()
{
	if (!super::init()) {
		return false;
	}

	ivars = IOMallocType(IODispatchSource_IVars);

	ivars->source = this;

	return true;
}

void
IODispatchSource::free()
{
	IOFreeType(ivars, IODispatchSource_IVars);
	super::free();
}

kern_return_t
IODispatchSource::SetEnable_Impl(
	bool enable)
{
	return SetEnableWithCompletion(enable, NULL);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

struct IOInterruptDispatchSource_IVars {
	IOService    * provider;
	uint32_t       intIndex;
	uint32_t       flags;
	int            interruptType;
	IOSimpleLock * lock;
	thread_t       waiter;
	uint64_t       count;
	uint64_t       time;
	OSAction     * action;
	bool           enable;
	bool           canceled;
};

static void
IOInterruptDispatchSourceInterrupt(OSObject * target, void * refCon,
    IOService * nub, int source )
{
	IOInterruptDispatchSource_IVars * ivars = (typeof(ivars))refCon;
	IOInterruptState is;

	is = IOSimpleLockLockDisableInterrupt(ivars->lock);
	ivars->count++;
	ivars->time = (kIOInterruptSourceContinuousTime & ivars->flags)
	    ? mach_continuous_time() : mach_absolute_time();
	if (ivars->waiter) {
		thread_wakeup_thread((event_t) ivars, ivars->waiter);
		ivars->waiter = NULL;
	}
	if (kIOInterruptTypeLevel & ivars->interruptType) {
		ivars->provider->disableInterrupt(ivars->intIndex);
	}
	IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
}

kern_return_t
IOInterruptDispatchSource::Create_Impl(
	IOService * provider,
	uint32_t indexAndFlags,
	IODispatchQueue * queue,
	IOInterruptDispatchSource ** source)
{
	IOReturn ret;
	IOInterruptDispatchSource * inst;
	uint32_t index;
	uint32_t flags;

	index = indexAndFlags & kIOInterruptSourceIndexMask;
	flags = indexAndFlags & ~kIOInterruptSourceIndexMask;

	inst = OSTypeAlloc(IOInterruptDispatchSource);
	if (!inst->init()) {
		inst->free();
		return kIOReturnNoMemory;
	}

	inst->ivars->lock = IOSimpleLockAlloc();

	ret = provider->getInterruptType(index, &inst->ivars->interruptType);
	if (kIOReturnSuccess != ret) {
		OSSafeReleaseNULL(inst);
		return ret;
	}
	ret = provider->registerInterrupt(index, inst, IOInterruptDispatchSourceInterrupt, inst->ivars);
	if (kIOReturnSuccess == ret) {
		inst->ivars->intIndex = index;
		inst->ivars->flags    = flags;
		inst->ivars->provider = provider;
		inst->ivars->provider->retain();
		*source = inst;
	}
	return ret;
}

kern_return_t
IOInterruptDispatchSource::GetInterruptType_Impl(
	IOService * provider,
	uint32_t index,
	uint64_t * interruptType)
{
	IOReturn ret;
	int      type;

	*interruptType = 0;
	ret = provider->getInterruptType(index, &type);
	if (kIOReturnSuccess == ret) {
		*interruptType = type;
	}

	return ret;
}

bool
IOInterruptDispatchSource::init()
{
	if (!super::init()) {
		return false;
	}
	ivars = IOMallocType(IOInterruptDispatchSource_IVars);

	return true;
}

void
IOInterruptDispatchSource::free()
{
	if (ivars && ivars->provider) {
		(void) ivars->provider->unregisterInterrupt(ivars->intIndex);
		ivars->provider->release();
	}

	if (ivars && ivars->lock) {
		IOSimpleLockFree(ivars->lock);
	}

	IOFreeType(ivars, IOInterruptDispatchSource_IVars);

	super::free();
}

kern_return_t
IOInterruptDispatchSource::SetHandler_Impl(
	OSAction * action)
{
	IOReturn ret;
	OSAction * oldAction;

	oldAction = (typeof(oldAction))ivars->action;
	if (oldAction && OSCompareAndSwapPtr(oldAction, NULL, &ivars->action)) {
		oldAction->release();
	}
	action->retain();
	ivars->action = action;

	ret = kIOReturnSuccess;

	return ret;
}

kern_return_t
IOInterruptDispatchSource::SetEnableWithCompletion_Impl(
	bool enable,
	IODispatchSourceCancelHandler handler)
{
	IOReturn ret;
	IOInterruptState is;

	if (enable == ivars->enable) {
		return kIOReturnSuccess;
	}

	if (enable) {
		is = IOSimpleLockLockDisableInterrupt(ivars->lock);
		ivars->enable = enable;
		IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
		ret = ivars->provider->enableInterrupt(ivars->intIndex);
	} else {
		ret = ivars->provider->disableInterrupt(ivars->intIndex);
		is = IOSimpleLockLockDisableInterrupt(ivars->lock);
		ivars->enable = enable;
		IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
	}

	return ret;
}

kern_return_t
IOInterruptDispatchSource::Cancel_Impl(
	IODispatchSourceCancelHandler handler)
{
	IOInterruptState is;
	IOService * provider;

	is = IOSimpleLockLockDisableInterrupt(ivars->lock);
	ivars->canceled = true;
	if (ivars->waiter) {
		thread_wakeup_thread((event_t) ivars, ivars->waiter);
		ivars->waiter = NULL;
	}
	provider = ivars->provider;
	if (provider) {
		ivars->provider = NULL;
	}
	IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);

	if (provider) {
		(void) provider->unregisterInterrupt(ivars->intIndex);
		provider->release();
	}

	return kIOReturnSuccess;
}

kern_return_t
IOInterruptDispatchSource::CheckForWork_Impl(
	const IORPC rpc,
	bool synchronous)
{
	IOReturn         ret = kIOReturnNotReady;
	IOInterruptState is;
	bool             willWait;
	bool             canceled;
	wait_result_t    waitResult;
	uint64_t         icount;
	uint64_t         itime;
	thread_t         self;

	self = current_thread();
	icount = 0;
	do {
		willWait = false;
		is = IOSimpleLockLockDisableInterrupt(ivars->lock);
		canceled = ivars->canceled;
		if (!canceled) {
			if ((icount = ivars->count)) {
				itime = ivars->time;
				ivars->count = 0;
				waitResult = THREAD_AWAKENED;
			} else if (synchronous) {
				assert(NULL == ivars->waiter);
				ivars->waiter = self;
				waitResult = assert_wait((event_t) ivars, THREAD_INTERRUPTIBLE);
			}
			willWait = (synchronous && (waitResult == THREAD_WAITING));
			if (willWait && (kIOInterruptTypeLevel & ivars->interruptType) && ivars->enable) {
				IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
				ivars->provider->enableInterrupt(ivars->intIndex);
			} else {
				IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
			}
		} else {
			IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
		}
		if (willWait) {
			waitResult = thread_block(THREAD_CONTINUE_NULL);
			if (THREAD_INTERRUPTED == waitResult) {
				is = IOSimpleLockLockDisableInterrupt(ivars->lock);
				ivars->waiter = NULL;
				IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
				canceled = true;
				break;
			}
		}
	} while (synchronous && !icount && !canceled);

	if (icount && ivars->action) {
		ret = InterruptOccurred(rpc, ivars->action, icount, itime);
	}

	return ret;
}

void
IOInterruptDispatchSource::InterruptOccurred_Impl(
	OSAction * action,
	uint64_t count,
	uint64_t time)
{
}

kern_return_t
IOInterruptDispatchSource::GetLastInterrupt_Impl(
	uint64_t  * pCount,
	uint64_t  * pTime)
{
	IOInterruptState is;
	uint64_t count, time;

	is = IOSimpleLockLockDisableInterrupt(ivars->lock);
	count = ivars->count;
	time  = ivars->time;
	IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);

	if (pCount) {
		*pCount = count;
	}
	if (pTime) {
		*pTime = time;
	}
	return kIOReturnSuccess;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

enum {
	kIOServiceNotificationTypeCount = kIOServiceNotificationTypeLast + 1,
};

struct IOServiceNotificationDispatchSource_IVars {
	OSObject     * serverName;
	OSAction     * action;
	IOLock       * lock;
	IONotifier   * notifier;
	OSDictionary * interestNotifiers;
	OSBoundedArray<OSArray *, kIOServiceNotificationTypeCount> pending;
	bool           enable;
};

kern_return_t
IOServiceNotificationDispatchSource::Create_Impl(
	OSDictionary * matching,
	uint64_t options,
	IODispatchQueue * queue,
	IOServiceNotificationDispatchSource ** notification)
{
	IOUserServer * us;
	IOReturn       ret;
	IOServiceNotificationDispatchSource * inst;

	inst = OSTypeAlloc(IOServiceNotificationDispatchSource);
	if (!inst->init()) {
		OSSafeReleaseNULL(inst);
		return kIOReturnNoMemory;
	}

	us = (typeof(us))thread_iokit_tls_get(0);
	assert(OSDynamicCast(IOUserServer, us));
	if (!us) {
		OSSafeReleaseNULL(inst);
		return kIOReturnError;
	}
	inst->ivars->serverName = us->copyProperty(gIOUserServerNameKey);
	if (!inst->ivars->serverName) {
		OSSafeReleaseNULL(inst);
		return kIOReturnNoMemory;
	}

	inst->ivars->lock    = IOLockAlloc();
	if (!inst->ivars->lock) {
		OSSafeReleaseNULL(inst);
		return kIOReturnNoMemory;
	}
	for (uint32_t idx = 0; idx < kIOServiceNotificationTypeCount; idx++) {
		inst->ivars->pending[idx] = OSArray::withCapacity(4);
		if (!inst->ivars->pending[idx]) {
			OSSafeReleaseNULL(inst);
			return kIOReturnNoMemory;
		}
	}
	inst->ivars->interestNotifiers = OSDictionary::withCapacity(4);
	if (!inst->ivars->interestNotifiers) {
		OSSafeReleaseNULL(inst);
		return kIOReturnNoMemory;
	}

	inst->ivars->notifier = IOService::addMatchingNotification(gIOMatchedNotification, matching, 0 /*priority*/,
	    ^bool (IOService * newService, IONotifier * notifier) {
		bool         notifyReady = false;
		IONotifier * interest;
		OSObject   * serverName;
		bool         okToUse;

		serverName = newService->copyProperty(gIOUserServerNameKey);
		okToUse = (serverName && inst->ivars->serverName->isEqualTo(serverName));
		OSSafeReleaseNULL(serverName);
		if (!okToUse) {
		        OSObject * prop;
		        OSObject * str;

		        if (!newService->reserved->uvars || !newService->reserved->uvars->userServer) {
		                return false;
			}
		        str = OSString::withCStringNoCopy(kIODriverKitAllowsPublishEntitlementsKey);
		        if (!str) {
		                return false;
			}
		        okToUse = newService->reserved->uvars->userServer->checkEntitlements(str, NULL, NULL);
		        if (!okToUse) {
		                if (kIODKLogSetup & gIODKDebug) {
		                        DKLOG(DKS ": publisher entitlements check failed\n", DKN(newService));
				}
		                return false;
			}
		        prop = newService->copyProperty(kIODriverKitPublishEntitlementsKey);
		        if (!prop) {
		                return false;
			}
		        okToUse = us->checkEntitlements(prop, NULL, NULL);
		        if (!okToUse) {
		                if (kIODKLogSetup & gIODKDebug) {
		                        DKLOG(DKS ": subscriber entitlements check failed\n", DKN(newService));
				}
		                return false;
			}
		}

		IOLockLock(inst->ivars->lock);
		notifyReady = (0 == inst->ivars->pending[kIOServiceNotificationTypeMatched]->getCount());
		inst->ivars->pending[kIOServiceNotificationTypeMatched]->setObject(newService);
		IOLockUnlock(inst->ivars->lock);

		interest = newService->registerInterest(gIOGeneralInterest,
		^IOReturn (uint32_t messageType, IOService * provider,
		void * messageArgument, size_t argSize) {
			IONotifier * interest;
			bool         notifyReady = false;

			switch (messageType) {
			case kIOMessageServiceIsTerminated:
				IOLockLock(inst->ivars->lock);
				notifyReady = (0 == inst->ivars->pending[kIOServiceNotificationTypeTerminated]->getCount());
				inst->ivars->pending[kIOServiceNotificationTypeTerminated]->setObject(provider);
				if (inst->ivars->interestNotifiers != NULL) {
				        interest = (typeof(interest))inst->ivars->interestNotifiers->getObject((const OSSymbol *) newService);
				        assert(interest);
				        interest->remove();
				        inst->ivars->interestNotifiers->removeObject((const OSSymbol *) newService);
				}
				IOLockUnlock(inst->ivars->lock);
				break;
			default:
				break;
			}
			if (notifyReady && inst->ivars->action) {
			        inst->ServiceNotificationReady(inst->ivars->action);
			}
			return kIOReturnSuccess;
		});
		if (interest) {
		        IOLockLock(inst->ivars->lock);
		        inst->ivars->interestNotifiers->setObject((const OSSymbol *) newService, interest);
		        IOLockUnlock(inst->ivars->lock);
		}
		if (notifyReady) {
		        if (inst->ivars->action) {
		                inst->ServiceNotificationReady(inst->ivars->action);
			}
		}
		return false;
	});

	if (!inst->ivars->notifier) {
		OSSafeReleaseNULL(inst);
		ret = kIOReturnError;
	}

	*notification = inst;
	ret = kIOReturnSuccess;

	return ret;
}

kern_return_t
IOServiceNotificationDispatchSource::CopyNextNotification_Impl(
	uint64_t * type,
	IOService ** service,
	uint64_t * options)
{
	IOService * next;
	uint32_t    idx;

	IOLockLock(ivars->lock);
	for (idx = 0; idx < kIOServiceNotificationTypeCount; idx++) {
		next = (IOService *) ivars->pending[idx]->getObject(0);
		if (next) {
			next->retain();
			ivars->pending[idx]->removeObject(0);
			break;
		}
	}
	IOLockUnlock(ivars->lock);

	if (idx == kIOServiceNotificationTypeCount) {
		idx = kIOServiceNotificationTypeNone;
	}
	*type    = idx;
	*service = next;
	*options = 0;

	return kIOReturnSuccess;
}

bool
IOServiceNotificationDispatchSource::init()
{
	if (!super::init()) {
		return false;
	}
	ivars = IOMallocType(IOServiceNotificationDispatchSource_IVars);

	return true;
}

void
IOServiceNotificationDispatchSource::free()
{
	if (ivars) {
		if (ivars->notifier) {
			ivars->notifier->remove();
			ivars->notifier = NULL;
		}
		if (ivars->interestNotifiers) {
			OSDictionary * savedInterestNotifiers = NULL;

			// the lock is always initialized first, so it should exist
			assert(ivars->lock);

			// Prevent additional changes to interestNotifiers
			IOLockLock(ivars->lock);
			savedInterestNotifiers = ivars->interestNotifiers;
			ivars->interestNotifiers = NULL;
			IOLockUnlock(ivars->lock);

			// Remove all interest notifiers
			savedInterestNotifiers->iterateObjects(^bool (const OSSymbol * key, OSObject * object) {
				IONotifier * interest = (typeof(interest))object;
				interest->remove();
				return false;
			});
			OSSafeReleaseNULL(savedInterestNotifiers);
		}
		for (uint32_t idx = 0; idx < kIOServiceNotificationTypeCount; idx++) {
			OSSafeReleaseNULL(ivars->pending[idx]);
		}
		if (ivars->lock) {
			IOLockFree(ivars->lock);
			ivars->lock = NULL;
		}
		OSSafeReleaseNULL(ivars->serverName);
		IOFreeType(ivars, IOServiceNotificationDispatchSource_IVars);
	}

	super::free();
}

kern_return_t
IOServiceNotificationDispatchSource::SetHandler_Impl(
	OSAction * action)
{
	IOReturn ret;
	bool     notifyReady;

	notifyReady = false;

	IOLockLock(ivars->lock);
	OSSafeReleaseNULL(ivars->action);
	action->retain();
	ivars->action = action;
	if (action) {
		for (uint32_t idx = 0; idx < kIOServiceNotificationTypeCount; idx++) {
			notifyReady = (ivars->pending[idx]->getCount());
			if (notifyReady) {
				break;
			}
		}
	}
	IOLockUnlock(ivars->lock);

	if (notifyReady) {
		ServiceNotificationReady(action);
	}
	ret = kIOReturnSuccess;

	return ret;
}

kern_return_t
IOServiceNotificationDispatchSource::SetEnableWithCompletion_Impl(
	bool enable,
	IODispatchSourceCancelHandler handler)
{
	if (enable == ivars->enable) {
		return kIOReturnSuccess;
	}

	IOLockLock(ivars->lock);
	ivars->enable = enable;
	IOLockUnlock(ivars->lock);

	return kIOReturnSuccess;
}

kern_return_t
IOServiceNotificationDispatchSource::Cancel_Impl(
	IODispatchSourceCancelHandler handler)
{
	return kIOReturnUnsupported;
}

kern_return_t
IOServiceNotificationDispatchSource::CheckForWork_Impl(
	const IORPC rpc,
	bool synchronous)
{
	return kIOReturnNotReady;
}

kern_return_t
IOServiceNotificationDispatchSource::DeliverNotifications(IOServiceNotificationBlock block)
{
	return kIOReturnUnsupported;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

OSDictionary *
IOService::CreatePropertyMatchingDictionary(const char * key, OSObjectPtr value, OSDictionary * matching)
{
	OSDictionary   * result;
	const OSSymbol * keySym;

	keySym = OSSymbol::withCString(key);
	result = propertyMatching(keySym, (const OSObject *) value, matching);
	OSSafeReleaseNULL(keySym);

	return result;
}

OSDictionary *
IOService::CreatePropertyMatchingDictionary(const char * key, const char * stringValue, OSDictionary * matching)
{
	OSDictionary * result;
	OSString     * value;

	value = OSString::withCString(stringValue);
	result = CreatePropertyMatchingDictionary(key, value, matching);
	OSSafeReleaseNULL(value);

	return result;
}

OSDictionary *
IOService::CreateKernelClassMatchingDictionary(OSString * className, OSDictionary * matching)
{
	if (!className) {
		return NULL;
	}
	if (!matching) {
		matching = OSDictionary::withCapacity(2);
		if (!matching) {
			return NULL;
		}
	}
	matching->setObject(kIOProviderClassKey, className);

	return matching;
}

OSDictionary *
IOService::CreateKernelClassMatchingDictionary(const char * className, OSDictionary * matching)
{
	OSDictionary * result;
	OSString     * string;

	string = OSString::withCString(className);
	result = CreateKernelClassMatchingDictionary(string, matching);
	OSSafeReleaseNULL(string);

	return result;
}

OSDictionary *
IOService::CreateUserClassMatchingDictionary(OSString * className, OSDictionary * matching)
{
	return CreatePropertyMatchingDictionary(kIOUserClassKey, className, matching);
}

OSDictionary *
IOService::CreateUserClassMatchingDictionary(const char * className, OSDictionary * matching)
{
	return CreatePropertyMatchingDictionary(kIOUserClassKey, className, matching);
}

OSDictionary *
IOService::CreateNameMatchingDictionary(OSString * serviceName, OSDictionary * matching)
{
	if (!serviceName) {
		return NULL;
	}
	if (!matching) {
		matching = OSDictionary::withCapacity(2);
		if (!matching) {
			return NULL;
		}
	}
	matching->setObject(kIONameMatchKey, serviceName);

	return matching;
}

OSDictionary *
IOService::CreateNameMatchingDictionary(const char * serviceName, OSDictionary * matching)
{
	OSDictionary * result;
	OSString     * string;

	string = OSString::withCString(serviceName);
	result = CreateNameMatchingDictionary(string, matching);
	OSSafeReleaseNULL(string);

	return result;
}



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IOUserServer::waitInterruptTrap(void * p1, void * p2, void * p3, void * p4, void * p5, void * p6)
{
	IOReturn         ret = kIOReturnBadArgument;
	IOInterruptState is;
	IOInterruptDispatchSource * interrupt;
	IOInterruptDispatchSource_IVars * ivars;
	IOInterruptDispatchSourcePayload payload;

	bool             willWait;
	bool             canceled;
	wait_result_t    waitResult;
	thread_t         self;

	OSObject * object;

	object = iokit_lookup_object_with_port_name((mach_port_name_t)(uintptr_t)p1, IKOT_UEXT_OBJECT, current_task());

	if (!object) {
		return kIOReturnBadArgument;
	}
	if (!(interrupt = OSDynamicCast(IOInterruptDispatchSource, object))) {
		ret = kIOReturnBadArgument;
	} else {
		self = current_thread();
		ivars = interrupt->ivars;
		payload.count = 0;
		do {
			willWait = false;
			is = IOSimpleLockLockDisableInterrupt(ivars->lock);
			canceled = ivars->canceled;
			if (!canceled) {
				if ((payload.count = ivars->count)) {
					payload.time = ivars->time;
					ivars->count = 0;
					waitResult = THREAD_AWAKENED;
				} else {
					assert(NULL == ivars->waiter);
					ivars->waiter = self;
					waitResult = assert_wait((event_t) ivars, THREAD_INTERRUPTIBLE);
				}
				willWait = (waitResult == THREAD_WAITING);
				if (willWait && (kIOInterruptTypeLevel & ivars->interruptType) && ivars->enable) {
					IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
					ivars->provider->enableInterrupt(ivars->intIndex);
				} else {
					IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
				}
			} else {
				IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
			}
			if (willWait) {
				waitResult = thread_block(THREAD_CONTINUE_NULL);
				if (THREAD_INTERRUPTED == waitResult) {
					is = IOSimpleLockLockDisableInterrupt(ivars->lock);
					ivars->waiter = NULL;
					IOSimpleLockUnlockEnableInterrupt(ivars->lock, is);
					canceled = true;
					break;
				}
			}
		} while (!payload.count && !canceled);
		ret = (payload.count ? kIOReturnSuccess : kIOReturnAborted);
	}

	if (kIOReturnSuccess == ret) {
		int copyerr = copyout(&payload, (user_addr_t) p2, sizeof(payload));
		if (copyerr) {
			ret = kIOReturnVMError;
		}
	}

	object->release();

	return ret;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IOUserServer::Create_Impl(
	const char * name,
	uint64_t tag,
	uint64_t options,
	OSString * bundleID,
	IOUserServer ** server)
{
	IOReturn          ret;
	IOUserServer    * us;
	const OSSymbol  * sym;
	OSNumber        * serverTag;
	io_name_t         rname;
	OSKext          * kext;

	us = (typeof(us))thread_iokit_tls_get(0);
	assert(OSDynamicCast(IOUserServer, us));
	if (kIODKLogSetup & gIODKDebug) {
		DKLOG(DKS "::Create(" DKS ") %p\n", DKN(us), name, tag, us);
	}
	if (!us) {
		return kIOReturnError;
	}

	if (bundleID) {
		kext = OSKext::lookupKextWithIdentifier(bundleID->getCStringNoCopy());
		if (kext) {
			us->setTaskLoadTag(kext);
			us->setDriverKitUUID(kext);
			us->setDriverKitStatistics(kext);
			OSKext::OSKextLogDriverKitInfoLoad(kext);
			OSSafeReleaseNULL(kext);
		} else {
			DKLOG(DKS "::Create(" DKS "): could not find OSKext for %s\n", DKN(us), name, tag, bundleID->getCStringNoCopy());
		}

		us->fAllocationName = kern_allocation_name_allocate(bundleID->getCStringNoCopy(), 0);
		assert(us->fAllocationName);
	}

	sym       = OSSymbol::withCString(name);
	serverTag = OSNumber::withNumber(tag, 64);

	us->setProperty(gIOUserServerNameKey, (OSObject *) sym);
	us->setProperty(gIOUserServerTagKey, serverTag);

	serverTag->release();
	OSSafeReleaseNULL(sym);

	snprintf(rname, sizeof(rname), "IOUserServer(%s-0x%qx)", name, tag);
	us->setName(rname);

	us->retain();
	*server = us;
	ret = kIOReturnSuccess;

	return ret;
}

kern_return_t
IOUserServer::RegisterService_Impl()
{
	kern_return_t ret = IOService::RegisterService_Impl();

	return ret;
}

kern_return_t
IOUserServer::Exit_Impl(
	const char * reason)
{
	return kIOReturnUnsupported;
}

kern_return_t
IOUserServer::LoadModule_Impl(
	const char * path)
{
	return kIOReturnUnsupported;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IODispatchQueue::Create_Impl(
	const char * name,
	uint64_t options,
	uint64_t priority,
	IODispatchQueue ** queue)
{
	IODispatchQueue * result;
	IOUserServer    * us;

	result = OSTypeAlloc(IODispatchQueue);
	if (!result) {
		return kIOReturnNoMemory;
	}
	if (!result->init()) {
		OSSafeReleaseNULL(result);
		return kIOReturnNoMemory;
	}

	*queue = result;

	if (!strcmp("Root", name)) {
		us = (typeof(us))thread_iokit_tls_get(0);
		assert(OSDynamicCast(IOUserServer, us));
		us->setRootQueue(result);
	}

	if (kIODKLogSetup & gIODKDebug) {
		DKLOG("IODispatchQueue::Create %s %p\n", name, result);
	}

	return kIOReturnSuccess;
}

kern_return_t
IODispatchQueue::SetPort_Impl(
	mach_port_t port)
{
	if (MACH_PORT_NULL != ivars->serverPort) {
		return kIOReturnNotReady;
	}
	ivars->serverPort = ipc_port_copy_send_mqueue(port);
	if (ivars->serverPort == MACH_PORT_NULL) {
		return kIOReturnBadArgument;
	}
	return kIOReturnSuccess;
}

bool
IODispatchQueue::init()
{
	ivars = IOMallocType(IODispatchQueue_IVars);
	ivars->queue = this;

	return true;
}

void
IODispatchQueue::free()
{
	if (ivars && ivars->serverPort) {
		ipc_port_release_send(ivars->serverPort);
		ivars->serverPort = MACH_PORT_NULL;
	}
	IOFreeType(ivars, IODispatchQueue_IVars);
	super::free();
}

bool
IODispatchQueue::OnQueue()
{
	return false;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


kern_return_t
OSMetaClassBase::Dispatch(IORPC rpc)
{
	return kIOReturnUnsupported;
}

kern_return_t
OSMetaClassBase::Invoke(IORPC rpc)
{
	IOReturn          ret = kIOReturnUnsupported;
	OSMetaClassBase * object;
	OSAction        * action;
	IOService       * service;
	IOUserServer    * us;
	IORPCMessage    * message;

	assert(rpc.sendSize >= (sizeof(IORPCMessageMach) + sizeof(IORPCMessage)));
	message = rpc.kernelContent;
	if (!message) {
		return kIOReturnIPCError;
	}
	message->flags |= kIORPCMessageKernel;

	us = NULL;
	if (!(kIORPCMessageLocalHost & message->flags)) {
		us = OSDynamicCast(IOUserServer, this);
		if (!us) {
			IOEventLink * eventLink = NULL;
			IOWorkGroup * workgroup = NULL;

			if ((action = OSDynamicCast(OSAction, this))) {
				object = IOUserServer::target(action, message);
			} else {
				object = this;
			}
			if ((service = OSDynamicCast(IOService, object))
			    && service->reserved->uvars) {
				// xxx other classes
				us = service->reserved->uvars->userServer;
			} else if (action) {
				us = action->ivars->userServer;
			} else if ((eventLink = OSDynamicCast(IOEventLink, object))) {
				us = eventLink->ivars->userServer;
			} else if ((workgroup = OSDynamicCast(IOWorkGroup, object))) {
				us = workgroup->ivars->userServer;
			}
		}
	}
	if (us) {
		message->flags |= kIORPCMessageRemote;
		ret = us->rpc(rpc);
		if (kIOReturnSuccess != ret) {
			if (kIODKLogIPC & gIODKDebug) {
				DKLOG("OSMetaClassBase::Invoke user 0x%x\n", ret);
			}
		}
	} else {
		if (kIODKLogIPC & gIODKDebug) {
			DKLOG("OSMetaClassBase::Invoke kernel %s 0x%qx\n", getMetaClass()->getClassName(), message->msgid);
		}
		void * prior = thread_iokit_tls_get(0);
		thread_iokit_tls_set(0, NULL);
		ret = Dispatch(rpc);
		thread_iokit_tls_set(0, prior);
	}

	return ret;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

struct IOPStrings {
	uint32_t     dataSize;
	uint32_t     count;
	const char   strings[0];
};

kern_return_t
OSUserMetaClass::Dispatch(IORPC rpc)
{
	if (meta) {
		return const_cast<OSMetaClass *>(meta)->Dispatch(rpc);
	} else {
		return kIOReturnUnsupported;
	}
}

void
OSUserMetaClass::free()
{
	if (queueNames) {
		IOFreeData(queueNames, sizeof(IOPStrings) + queueNames->dataSize * sizeof(char));
		queueNames = NULL;
	}
	if (description) {
		IOFreeData(description, description->descriptionSize);
		description = NULL;
	}
	IODeleteData(methods, uint64_t, 2 * methodCount);
	if (meta) {
		meta->releaseMetaClass();
	}
	if (name) {
		name->release();
	}
	OSObject::free();
}

/*
 * Sets the loadTag of the associated OSKext
 * in the dext task.
 * NOTE: different instances of the same OSKext
 * (so same BounleID but different tasks)
 * will have the same loadTag.
 */
void
IOUserServer::setTaskLoadTag(OSKext *kext)
{
	task_t owningTask;
	uint32_t loadTag, prev_taskloadTag;

	owningTask = this->fOwningTask;
	if (!owningTask) {
		printf("%s: fOwningTask not found\n", __FUNCTION__);
		return;
	}

	loadTag = kext->getLoadTag();
	prev_taskloadTag = set_task_loadTag(owningTask, loadTag);
	if (prev_taskloadTag) {
		printf("%s: found the task loadTag already set to %u (set to %u)\n",
		    __FUNCTION__, prev_taskloadTag, loadTag);
	}
}

/*
 * Sets the OSKext uuid as the uuid of the userspace
 * dext executable.
 */
void
IOUserServer::setDriverKitUUID(OSKext *kext)
{
	task_t task;
	proc_t p;
	uuid_t p_uuid, k_uuid;
	OSData *k_data_uuid;
	OSData *new_uuid;
	uuid_string_t       uuid_string = "";

	task = this->fOwningTask;
	if (!task) {
		printf("%s: fOwningTask not found\n", __FUNCTION__);
		return;
	}

	p = (proc_t)(get_bsdtask_info(task));
	if (!p) {
		printf("%s: proc not found\n", __FUNCTION__);
		return;
	}
	proc_getexecutableuuid(p, p_uuid, sizeof(p_uuid));

	k_data_uuid = kext->copyUUID();
	if (k_data_uuid) {
		memcpy(&k_uuid, k_data_uuid->getBytesNoCopy(), sizeof(k_uuid));
		OSSafeReleaseNULL(k_data_uuid);
		if (uuid_compare(k_uuid, p_uuid) != 0) {
			printf("%s: uuid not matching\n", __FUNCTION__);
		}
		return;
	}

	uuid_unparse(p_uuid, uuid_string);
	new_uuid = OSData::withValue(p_uuid);
	kext->setDriverKitUUID(new_uuid);
}

void
IOUserServer::setDriverKitStatistics(OSKext *kext)
{
	OSDextStatistics * statistics = kext->copyDextStatistics();
	if (statistics == NULL) {
		panic("Kext %s was not a DriverKit OSKext", kext->getIdentifierCString());
	}
	fStatistics = statistics;
}

IOReturn
IOUserServer::setCheckInToken(IOUserServerCheckInToken *token)
{
	IOReturn ret = kIOReturnError;
	if (token != NULL && fCheckInToken == NULL) {
		token->retain();
		fCheckInToken = token;
		ret = fCheckInToken->complete();
		iokit_clear_registered_ports(fOwningTask);
	} else {
		printf("%s: failed to set check in token. token=%p, fCheckInToken=%p\n", __FUNCTION__, token, fCheckInToken);
	}
	return ret;
}

bool
IOUserServer::serviceMatchesCheckInToken(IOUserServerCheckInToken *token)
{
	if (token != NULL) {
		bool result = token == fCheckInToken;
		return result;
	} else {
		printf("%s: null check in token\n", __FUNCTION__);
		return false;
	}
}

// entitlements - dict of entitlements to check
// prop - string - if present return true
//      - array of strings - if any present return true
//      - array of arrays of strings - in each leaf array all must be present
//                                   - if any top level array succeeds return true
// consumes one reference of prop
bool
IOUserServer::checkEntitlements(
	OSDictionary * entitlements, OSObject * prop,
	IOService * provider, IOService * dext)
{
	OSDictionary * matching;

	if (!prop) {
		return true;
	}
	if (!entitlements) {
		OSSafeReleaseNULL(prop);
		return false;
	}

	matching = NULL;
	if (dext) {
		matching = dext->dictionaryWithProperties();
		if (!matching) {
			OSSafeReleaseNULL(prop);
			return false;
		}
	}

	bool allPresent __block = false;
	prop->iterateObjects(^bool (OSObject * object) {
		allPresent = false;
		object->iterateObjects(^bool (OSObject * object) {
			OSString * string;
			OSObject * value;
			string = OSDynamicCast(OSString, object);
			value = entitlements->getObject(string);
			if (matching && value) {
			        matching->setObject(string, value);
			}
			allPresent = (NULL != value);
			// early terminate if not found
			return !allPresent;
		});
		// early terminate if found
		return allPresent;
	});

	if (allPresent && matching && provider) {
		allPresent = provider->matchPropertyTable(matching);
	}

	OSSafeReleaseNULL(matching);
	OSSafeReleaseNULL(prop);

	return allPresent;
}

bool
IOUserServer::checkEntitlements(OSObject * prop, IOService * provider, IOService * dext)
{
	return checkEntitlements(fEntitlements, prop, provider, dext);
}

bool
IOUserServer::checkEntitlements(IOService * provider, IOService * dext)
{
	OSObject     * prop;
	bool           ok;

	if (!fOwningTask) {
		return false;
	}

	prop = provider->copyProperty(gIOServiceDEXTEntitlementsKey);
	ok = checkEntitlements(fEntitlements, prop, provider, dext);
	if (!ok) {
		DKLOG(DKS ": provider entitlements check failed\n", DKN(dext));
	}
	if (ok) {
		prop = dext->copyProperty(gIOServiceDEXTEntitlementsKey);
		ok = checkEntitlements(fEntitlements, prop, NULL, NULL);
		if (!ok) {
			DKLOG(DKS ": family entitlements check failed\n", DKN(dext));
		}
	}

	return ok;
}

IOReturn
IOUserServer::exit(const char * reason)
{
	DKLOG("%s::exit(%s)\n", getName(), reason);
	Exit(reason);
	return kIOReturnSuccess;
}

IOReturn
IOUserServer::kill(const char * reason)
{
	IOReturn ret = kIOReturnError;
	if (fOwningTask != NULL) {
		DKLOG("%s::kill(%s)\n", getName(), reason);
		task_bsdtask_kill(fOwningTask);
		ret = kIOReturnSuccess;
	}
	return ret;
}

OSObjectUserVars *
IOUserServer::varsForObject(OSObject * obj)
{
	IOService * service;

	if ((service = OSDynamicCast(IOService, obj))) {
		return service->reserved->uvars;
	}

	return NULL;
}

IOPStrings *
IOUserServer::copyInStringArray(const char * string, uint32_t userSize)
{
	IOPStrings * array;
	vm_size_t    alloc;
	size_t       len;
	const char * end;
	OSBoundedPtr<const char> cstr;

	if (userSize <= 1) {
		return NULL;
	}

	if (os_add_overflow(sizeof(IOPStrings), userSize, &alloc)) {
		assert(false);
		return NULL;
	}
	if (alloc > 16384) {
		assert(false);
		return NULL;
	}
	array = (typeof(array))IOMallocData(alloc);
	if (!array) {
		return NULL;
	}
	array->dataSize = userSize;
	bcopy(string, (void *) &array->strings[0], userSize);

	array->count = 0;
	end =  &array->strings[array->dataSize];
	cstr = OSBoundedPtr<const char>(&array->strings[0], &array->strings[0], end);
	while ((len = (unsigned char)cstr[0])) {
		cstr++;
		if ((cstr + len) >= end) {
			break;
		}
		cstr += len;
		array->count++;
	}
	if (len) {
		IOFreeData(array, alloc);
		array = NULL;
	}

	return array;
}

uint32_t
IOUserServer::stringArrayIndex(IOPStrings * array, const char * look)
{
	uint32_t     idx;
	size_t       len, llen;
	OSBoundedPtr<const char> cstr;
	const char * end;

	idx  = 0;
	end  =  &array->strings[array->dataSize];
	cstr = OSBoundedPtr<const char>(&array->strings[0], &array->strings[0], end);

	llen = strlen(look);
	while ((len = (unsigned char)cstr[0])) {
		cstr++;
		if ((cstr + len) >= end) {
			break;
		}
		if ((len == llen) && !strncmp(cstr.discard_bounds(), look, len)) {
			return idx;
		}
		cstr += len;
		idx++;
	}

	return -1U;
}
#define kIODispatchQueueStopped ((IODispatchQueue *) -1L)

IODispatchQueue *
IOUserServer::queueForObject(OSObject * obj, uint64_t msgid)
{
	IODispatchQueue  * queue;
	OSObjectUserVars * uvars;
	uint64_t           option;

	uvars = varsForObject(obj);
	if (!uvars) {
		return NULL;
	}
	if (!uvars->queueArray) {
		if (uvars->stopped) {
			return kIODispatchQueueStopped;
		}
		return NULL;
	}
	queue = uvars->queueArray[0];

	if (uvars->userMeta
	    && uvars->userMeta->methods) {
		uint32_t idx, baseIdx;
		uint32_t lim;
		// bsearch
		for (baseIdx = 0, lim = uvars->userMeta->methodCount; lim; lim >>= 1) {
			idx = baseIdx + (lim >> 1);
			if (msgid == uvars->userMeta->methods[idx]) {
				option = uvars->userMeta->methods[uvars->userMeta->methodCount + idx];
				option &= 0xFF;
				if (option < uvars->userMeta->queueNames->count) {
					queue = uvars->queueArray[option + 1];
				}
				break;
			} else if (msgid > uvars->userMeta->methods[idx]) {
				// move right
				baseIdx += (lim >> 1) + 1;
				lim--;
			}
			// else move left
		}
	}
	return queue;
}

IOReturn
IOUserServer::objectInstantiate(OSObject * obj, IORPC rpc, IORPCMessage * message)
{
	IOReturn         ret;
	OSString       * str;
	OSObject       * prop;
	IOService      * service;

	OSAction       * action;
	OSObject       * target;
	uint32_t         queueCount, queueAlloc;
	const char     * resultClassName;
	uint64_t         resultFlags;

	mach_msg_size_t    replySize;
	uint32_t           methodCount;
	const uint64_t   * methods;
	IODispatchQueue  * queue;
	OSUserMetaClass  * userMeta;
	OSObjectUserVars * uvars;
	uint32_t           idx;
	ipc_port_t         sendPort;
	bool               serviceInactive;

	OSObject_Instantiate_Rpl_Content * reply;
	IODispatchQueue ** unboundedQueueArray = NULL;
	queueCount      = 0;
	methodCount     = 0;
	methods         = NULL;
	str             = NULL;
	prop            = NULL;
	userMeta        = NULL;
	resultClassName = NULL;
	resultFlags     = 0;
	ret = kIOReturnUnsupportedMode;

	service = OSDynamicCast(IOService, obj);
	action = OSDynamicCast(OSAction, obj);
	if (!service) {
		// xxx other classes hosted
		resultFlags |= kOSObjectRPCKernel;
		resultFlags |= kOSObjectRPCRemote;
	} else {
		serviceInactive = false;
		if (service->lockForArbitration()) {
			if (service->isInactive() && (service->__state[1] & kIOServiceStartState) == 0) {
				serviceInactive = true;
			}
			service->unlockForArbitration();
		}
		if (serviceInactive) {
			DKLOG(DKS "::instantiate inactive\n", DKN(service));
			return kIOReturnOffline;
		}
		prop = service->copyProperty(gIOUserClassKey);
		str = OSDynamicCast(OSString, prop);
		if (!service->reserved->uvars) {
			resultFlags |= kOSObjectRPCRemote;
			resultFlags |= kOSObjectRPCKernel;
		} else if (this != service->reserved->uvars->userServer) {
			// remote, use base class
			resultFlags |= kOSObjectRPCRemote;
		}
		if (service->reserved->uvars && service->reserved->uvars->userServer) {
			if (!str) {
				DKLOG("no IOUserClass defined for " DKS "\n", DKN(service));
				OSSafeReleaseNULL(prop);
				return kIOReturnError;
			}
			IOLockLock(service->reserved->uvars->userServer->fLock);
			userMeta = (typeof(userMeta))service->reserved->uvars->userServer->fClasses->getObject(str);
			IOLockUnlock(service->reserved->uvars->userServer->fLock);
		}
	}
	if (!str && !userMeta) {
		const OSMetaClass * meta;
		meta = obj->getMetaClass();
		IOLockLock(fLock);
		if (action) {
			str = action->ivars->typeName;
			if (str) {
				userMeta = (typeof(userMeta))fClasses->getObject(str);
			}
		}
		while (meta && !userMeta) {
			str = (OSString *) meta->getClassNameSymbol();
			userMeta = (typeof(userMeta))fClasses->getObject(str);
			if (!userMeta) {
				meta = meta->getSuperClass();
			}
		}
		IOLockUnlock(fLock);
	}
	if (str) {
		if (!userMeta) {
			IOLockLock(fLock);
			userMeta = (typeof(userMeta))fClasses->getObject(str);
			IOLockUnlock(fLock);
		}
		if (kIODKLogSetup & gIODKDebug) {
			DKLOG("userMeta %s %p\n", str->getCStringNoCopy(), userMeta);
		}
		if (userMeta) {
			if (kOSObjectRPCRemote & resultFlags) {
				if (!action) {
					/* Special case: For OSAction subclasses, do not use the superclass */
					while (userMeta && !(kOSClassCanRemote & userMeta->description->flags)) {
						userMeta = userMeta->superMeta;
					}
				}
				if (userMeta) {
					resultClassName = userMeta->description->name;
					ret = kIOReturnSuccess;
				}
			} else {
				service->reserved->uvars->userMeta = userMeta;
				queueAlloc = 1;
				if (userMeta->queueNames) {
					queueAlloc += userMeta->queueNames->count;
				}
				unboundedQueueArray = IONewZero(IODispatchQueue *, queueAlloc);
				service->reserved->uvars->queueArray =
				    OSBoundedArrayRef<IODispatchQueue *>(unboundedQueueArray, queueAlloc);
				resultClassName = str->getCStringNoCopy();
				ret = kIOReturnSuccess;
			}
		} else if (kIODKLogSetup & gIODKDebug) {
			DKLOG("userMeta %s was not found in fClasses\n", str->getCStringNoCopy());
			IOLockLock(fLock);
			fClasses->iterateObjects(^bool (const OSSymbol * key, OSObject * val) {
				DKLOG(" fClasses[\"%s\"] => %p\n", key->getCStringNoCopy(), val);
				return false;
			});
			IOLockUnlock(fLock);
		}
	}
	OSSafeReleaseNULL(prop);

	IORPCMessageMach * machReply = rpc.reply;
	replySize = sizeof(OSObject_Instantiate_Rpl);

	if ((kIOReturnSuccess == ret) && (kOSObjectRPCRemote & resultFlags)) {
		target = obj;
		if (action) {
			if (action->ivars->referenceSize) {
				resultFlags |= kOSObjectRPCKernel;
			} else {
				resultFlags &= ~kOSObjectRPCKernel;
				if (action->ivars->target) {
					target = action->ivars->target;
					queueCount = 1;
					queue = queueForObject(target, action->ivars->targetmsgid);
					if (!queue && action->ivars->userServer) {
						queue = action->ivars->userServer->fRootQueue;
					}
					idx = 0;
					sendPort = NULL;
					if (queue && (kIODispatchQueueStopped != queue)) {
						sendPort = ipc_port_copy_send_mqueue(queue->ivars->serverPort);
					}
					replySize = sizeof(OSObject_Instantiate_Rpl)
					    + queueCount * sizeof(machReply->objects[0])
					    + 2 * methodCount * sizeof(reply->methods[0]);
					if (replySize > rpc.replySize) {
						assert(false);
						return kIOReturnIPCError;
					}
					machReply->objects[idx].type        = MACH_MSG_PORT_DESCRIPTOR;
					machReply->objects[idx].disposition = MACH_MSG_TYPE_MOVE_SEND;
					machReply->objects[idx].name        = sendPort;
					machReply->objects[idx].pad2        = 0;
					machReply->objects[idx].pad_end     = 0;
				}
			}
		} else {
			uvars = varsForObject(target);
			if (uvars && uvars->userMeta) {
				queueCount = 1;
				if (uvars->userMeta->queueNames) {
					queueCount += uvars->userMeta->queueNames->count;
				}
				methods = &uvars->userMeta->methods[0];
				methodCount = uvars->userMeta->methodCount;
				replySize = sizeof(OSObject_Instantiate_Rpl)
				    + queueCount * sizeof(machReply->objects[0])
				    + 2 * methodCount * sizeof(reply->methods[0]);
				if (replySize > rpc.replySize) {
					assert(false);
					return kIOReturnIPCError;
				}
				for (idx = 0; idx < queueCount; idx++) {
					queue = uvars->queueArray[idx];
					sendPort = NULL;
					if (queue) {
						sendPort = ipc_port_copy_send_mqueue(queue->ivars->serverPort);
					}
					machReply->objects[idx].type        = MACH_MSG_PORT_DESCRIPTOR;
					machReply->objects[idx].disposition = MACH_MSG_TYPE_MOVE_SEND;
					machReply->objects[idx].name        = sendPort;
					machReply->objects[idx].pad2        = 0;
					machReply->objects[idx].pad_end     = 0;
				}
			}
		}
	}

	if (kIODKLogIPC & gIODKDebug) {
		DKLOG("instantiate object %s with user class %s\n", obj->getMetaClass()->getClassName(), str ? str->getCStringNoCopy() : "(null)");
	}

	if (kIOReturnSuccess != ret) {
		DKLOG("%s: no user class found\n", str ? str->getCStringNoCopy() : obj->getMetaClass()->getClassName());
		resultClassName = "unknown";
	}

	machReply->msgh.msgh_id                    = kIORPCVersionCurrentReply;
	machReply->msgh.msgh_size                  = replySize;
	machReply->msgh_body.msgh_descriptor_count = queueCount;

	reply = (typeof(reply))IORPCMessageFromMachReply(machReply);
	if (!reply) {
		return kIOReturnIPCError;
	}
	if (methodCount) {
		bcopy(methods, &reply->methods[0], methodCount * 2 * sizeof(reply->methods[0]));
	}
	reply->__hdr.msgid       = OSObject_Instantiate_ID;
	reply->__hdr.flags       = kIORPCMessageOneway;
	reply->__hdr.objectRefs  = 0;
	reply->__pad             = 0;
	reply->flags             = resultFlags;
	strlcpy(reply->classname, resultClassName, sizeof(reply->classname));
	reply->__result          = ret;

	ret = kIOReturnSuccess;

	return ret;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

IOReturn
IOUserServer::kernelDispatch(OSObject * obj, IORPC rpc)
{
	IOReturn       ret;
	IORPCMessage * message;

	message = rpc.kernelContent;
	if (!message) {
		return kIOReturnIPCError;
	}

	if (OSObject_Instantiate_ID == message->msgid) {
		ret = objectInstantiate(obj, rpc, message);
		if (kIOReturnSuccess != ret) {
			DKLOG("%s: instantiate failed 0x%x\n", obj->getMetaClass()->getClassName(), ret);
		}
	} else {
		if (kIODKLogIPC & gIODKDebug) {
			DKLOG("%s::Dispatch kernel 0x%qx\n", obj->getMetaClass()->getClassName(), message->msgid);
		}
		ret = obj->Dispatch(rpc);
		if (kIODKLogIPC & gIODKDebug) {
			DKLOG("%s::Dispatch kernel 0x%qx result 0x%x\n", obj->getMetaClass()->getClassName(), message->msgid, ret);
		}
	}

	return ret;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

OSObject *
IOUserServer::target(OSAction * action, IORPCMessage * message)
{
	OSObject * object;

	if (message->msgid != action->ivars->msgid) {
		return action;
	}
	object = action->ivars->target;
	if (!object) {
		return action;
	}
	message->msgid      = action->ivars->targetmsgid;
	message->objects[0] = (OSObjectRef) object;
	if (kIORPCMessageRemote & message->flags) {
		object->retain();
#ifndef __clang_analyzer__
		// Hide the release of 'action' from the clang static analyzer to suppress
		// an overrelease diagnostic. The analyzer doesn't have a way to express the
		// non-standard contract of this method, which is that it releases 'action' when
		// the message flags have kIORPCMessageRemote set.
		action->release();
#endif
	}
	if (kIODKLogIPC & gIODKDebug) {
		DKLOG("TARGET %s msg 0x%qx from 0x%qx\n", object->getMetaClass()->getClassName(), message->msgid, action->ivars->msgid);
	}

	return object;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
uext_server(ipc_port_t receiver, ipc_kmsg_t requestkmsg, ipc_kmsg_t * pReply)
{
	kern_return_t      ret;
	OSObject         * object;
	IOUserServer     * server;

	object = IOUserServer::copyObjectForSendRight(receiver, IKOT_UEXT_OBJECT);
	server = OSDynamicCast(IOUserServer, object);
	if (!server) {
		OSSafeReleaseNULL(object);
		return KERN_INVALID_NAME;
	}

	IORPCMessage * message = (typeof(message))ikm_udata_from_header(requestkmsg);

	ret = server->server(requestkmsg, message, pReply);
	object->release();

	return ret;
}

/*
 * Chosen to hit kalloc zones (as opposed to the VM).
 * doesn't include the trailer size which ipc_kmsg_alloc() will add
 */
#define MAX_UEXT_REPLY_SIZE     0x17c0
static_assert(MAX_UEXT_REPLY_SIZE + MAX_TRAILER_SIZE <= KALLOC_SAFE_ALLOC_SIZE);

kern_return_t
IOUserServer::server(ipc_kmsg_t requestkmsg, IORPCMessage * message, ipc_kmsg_t * pReply)
{
	kern_return_t      ret;
	mach_msg_size_t    replyAlloc;
	ipc_kmsg_t         replykmsg;
	IORPCMessageMach * msgin;
	IORPCMessageMach * msgout;
	IORPCMessage     * reply;
	uint32_t           replySize;
	OSObject         * object;
	OSAction         * action;
	bool               oneway;
	uint64_t           msgid;

	msgin   = (typeof(msgin))ikm_header(requestkmsg);
	replyAlloc = 0;
	msgout = NULL;
	replykmsg = NULL;

	if (msgin->msgh.msgh_size < (sizeof(IORPCMessageMach) + sizeof(IORPCMessage))) {
		if (kIODKLogIPC & gIODKDebug) {
			DKLOG("UEXT notify %o\n", msgin->msgh.msgh_id);
		}
		return KERN_NOT_SUPPORTED;
	}

	if (!(MACH_MSGH_BITS_COMPLEX & msgin->msgh.msgh_bits)) {
		msgin->msgh_body.msgh_descriptor_count = 0;
	}
	if (!message) {
		return kIOReturnIPCError;
	}
	if (message->objectRefs == 0) {
		return kIOReturnIPCError;
	}
	ret = copyInObjects(msgin, message, msgin->msgh.msgh_size, true, false);
	if (kIOReturnSuccess != ret) {
		if (kIODKLogIPC & gIODKDebug) {
			DKLOG("UEXT copyin(0x%x) %x\n", ret, msgin->msgh.msgh_id);
		}
		// release objects and ports
		consumeObjects(msgin, message, msgin->msgh.msgh_size);
		copyInObjects(msgin, message, msgin->msgh.msgh_size, false, true);
		return KERN_NOT_SUPPORTED;
	}

	if (msgin->msgh_body.msgh_descriptor_count < 1) {
		return KERN_NOT_SUPPORTED;
	}
	object = (OSObject *) message->objects[0];
	msgid = message->msgid;
	message->flags &= ~kIORPCMessageKernel;
	message->flags |= kIORPCMessageRemote;

	if ((action = OSDynamicCast(OSAction, object))) {
		object = target(action, message);
		msgid  = message->msgid;
	}

	oneway = (0 != (kIORPCMessageOneway & message->flags));
	assert(oneway || (MACH_PORT_NULL != msgin->msgh.msgh_local_port));

	replyAlloc = oneway ? 0 : MAX_UEXT_REPLY_SIZE;




	if (replyAlloc) {
		/*
		 * Same as:
		 *    ipc_kmsg_alloc(MAX_UEXT_REPLY_SIZE_MACH, MAX_UEXT_REPLY_SIZE_MESSAGE,
		 *        IPC_KMSG_ALLOC_KERNEL | IPC_KMSG_ALLOC_ZERO | IPC_KMSG_ALLOC_LINEAR |
		 *        IPC_KMSG_ALLOC_NOFAIL);
		 */
		replykmsg = ipc_kmsg_alloc_uext_reply(MAX_UEXT_REPLY_SIZE);
		msgout = (typeof(msgout))ikm_header(replykmsg);
	}

	IORPC rpc = { .message = msgin, .reply = msgout, .sendSize = msgin->msgh.msgh_size, .replySize = replyAlloc, .kernelContent = message };

	if (object) {
		kern_allocation_name_t prior;
		bool                   setAllocationName;

		setAllocationName = (NULL != fAllocationName);
		if (setAllocationName) {
			prior = thread_set_allocation_name(fAllocationName);
		}
		thread_iokit_tls_set(0, this);
		ret = kernelDispatch(object, rpc);
		thread_iokit_tls_set(0, NULL);
		if (setAllocationName) {
			thread_set_allocation_name(prior);
		}
	} else {
		ret = kIOReturnBadArgument;
	}

	// release objects
	consumeObjects(msgin, message, msgin->msgh.msgh_size);

	// release ports
	copyInObjects(msgin, message, msgin->msgh.msgh_size, false, true);

	if (!oneway) {
		if (kIOReturnSuccess == ret) {
			replySize = msgout->msgh.msgh_size;
			reply = IORPCMessageFromMachReply(msgout);
			if (!reply) {
				ret = kIOReturnIPCError;
			} else {
				ret = copyOutObjects(msgout, reply, replySize, (kIORPCVersionCurrentReply == msgout->msgh.msgh_id) /* =>!InvokeReply */);
			}
		}
		if (kIOReturnSuccess != ret) {
			IORPCMessageErrorReturnContent * errorMsg;

			msgout->msgh_body.msgh_descriptor_count = 0;
			msgout->msgh.msgh_id                    = kIORPCVersionCurrentReply;
			errorMsg = (typeof(errorMsg))IORPCMessageFromMachReply(msgout);
			errorMsg->hdr.msgid      = message->msgid;
			errorMsg->hdr.flags      = kIORPCMessageOneway | kIORPCMessageError;
			errorMsg->hdr.objectRefs = 0;
			errorMsg->result         = ret;
			errorMsg->pad            = 0;
			replySize                = sizeof(IORPCMessageErrorReturn);
		}

		msgout->msgh.msgh_bits = MACH_MSGH_BITS_COMPLEX |
		    MACH_MSGH_BITS_SET(MACH_MSGH_BITS_LOCAL(msgin->msgh.msgh_bits) /*remote*/, 0 /*local*/, 0, 0);

		msgout->msgh.msgh_remote_port  = msgin->msgh.msgh_local_port;
		msgout->msgh.msgh_local_port   = MACH_PORT_NULL;
		msgout->msgh.msgh_voucher_port = (mach_port_name_t) 0;
		msgout->msgh.msgh_reserved     = 0;
		msgout->msgh.msgh_size         = replySize;
	}

	*pReply = replykmsg;
	return KERN_SUCCESS;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

static inline uint32_t
MAX_OBJECT_COUNT(IORPCMessageMach *mach, size_t size, IORPCMessage *message __unused)
{
	assert(mach->msgh.msgh_size == size);
	size_t used_size;
	size_t remaining_size;
	if (os_mul_and_add_overflow(
		    mach->msgh_body.msgh_descriptor_count,
		    sizeof(mach_msg_port_descriptor_t),
		    sizeof(mach->msgh) + sizeof(mach->msgh_body) + offsetof(IORPCMessage, objects[0]),
		    &used_size)) {
		return 0;
	}
	if (os_sub_overflow(size, used_size, &remaining_size)) {
		return 0;
	}
	return (uint32_t)(remaining_size / sizeof(OSObjectRef));
}

#pragma pack(push, 4)
struct UEXTTrapReply {
	uint64_t replySize;
	IORPCMessage replyMessage;
};
#pragma pack(pop)

kern_return_t
IOUserServerUEXTTrap(OSObject * object, void * p1, void * p2, void * p3, void * p4, void * p5, void * p6)
{
	const user_addr_t msg              = (uintptr_t) p1;
	size_t            inSize           = (uintptr_t) p2;
	user_addr_t       out              = (uintptr_t) p3;
	size_t            outSize          = (uintptr_t) p4;
	mach_port_name_t  objectName1      = (mach_port_name_t)(uintptr_t) p5;
	size_t            totalSize;
	OSObject        * objectArg1;

	IORPCMessageMach *  mach;
	mach_msg_port_descriptor_t * descs;

#pragma pack(4)
	struct {
		uint32_t                   pad;
		IORPCMessageMach           mach;
		mach_msg_port_descriptor_t objects[2];
		IOTrapMessageBuffer        buffer;
	} buffer;
#pragma pack()

	IOReturn           ret;
	OSAction         * action;
	int                copyerr;
	IORPCMessage     * message;
	IORPCMessage     * reply;
	IORPC              rpc;
	uint64_t           refs;
	uint32_t           maxObjectCount;
	size_t             copySize;
	UEXTTrapReply    * replyHdr;
	uintptr_t          p;

	bzero(&buffer, sizeof(buffer));

	p = (typeof(p)) & buffer.buffer[0];
	if (os_add_overflow(inSize, outSize, &totalSize)) {
		return kIOReturnMessageTooLarge;
	}
	if (totalSize > sizeof(buffer.buffer)) {
		return kIOReturnMessageTooLarge;
	}
	if (inSize < sizeof(IORPCMessage)) {
		return kIOReturnIPCError;
	}
	copyerr = copyin(msg, &buffer.buffer[0], inSize);
	if (copyerr) {
		return kIOReturnVMError;
	}

	message = (typeof(message))p;
	refs    = message->objectRefs;
	if ((refs > 2) || !refs) {
		return kIOReturnUnsupported;
	}
	if (!(kIORPCMessageSimpleReply & message->flags)) {
		return kIOReturnUnsupported;
	}
	message->flags &= ~(kIORPCMessageKernel | kIORPCMessageRemote);

	descs = (typeof(descs))(p - refs * sizeof(*descs));
	mach  = (typeof(mach))(p - refs * sizeof(*descs) - sizeof(*mach));

	mach->msgh.msgh_id   = kIORPCVersionCurrent;
	mach->msgh.msgh_size = (mach_msg_size_t) (sizeof(IORPCMessageMach) + refs * sizeof(*descs) + inSize); // totalSize was checked
	mach->msgh_body.msgh_descriptor_count = ((mach_msg_size_t) refs);

	rpc.message   = mach;
	rpc.sendSize  = mach->msgh.msgh_size;
	rpc.reply     = (IORPCMessageMach *) (p + inSize);
	rpc.replySize = ((uint32_t) (sizeof(buffer.buffer) - inSize));    // inSize was checked
	rpc.kernelContent = message;

	message->objects[0] = 0;
	if ((action = OSDynamicCast(OSAction, object))) {
		maxObjectCount = MAX_OBJECT_COUNT(rpc.message, rpc.sendSize, message);
		if (refs > maxObjectCount) {
			return kIOReturnBadArgument;
		}
		if (refs < 2) {
			DKLOG("invalid refs count %qd in message id 0x%qx\n", refs, message->msgid);
			return kIOReturnBadArgument;
		}
		object = IOUserServer::target(action, message);
		message->objects[1] = (OSObjectRef) action;
		if (kIODKLogIPC & gIODKDebug) {
			DKLOG("%s::Dispatch(trap) kernel 0x%qx\n", object->getMetaClass()->getClassName(), message->msgid);
		}
		ret = object->Dispatch(rpc);
	} else {
		objectArg1 = NULL;
		if (refs > 1) {
			if (objectName1) {
				objectArg1 = iokit_lookup_uext_ref_current_task(objectName1);
				if (!objectArg1) {
					return kIOReturnIPCError;
				}
			}
			message->objects[1] = (OSObjectRef) objectArg1;
		}
		if (kIODKLogIPC & gIODKDebug) {
			DKLOG("%s::Dispatch(trap) kernel 0x%qx\n", object->getMetaClass()->getClassName(), message->msgid);
		}
		ret = object->Dispatch(rpc);
		if (kIODKLogIPC & gIODKDebug) {
			DKLOG("%s::Dispatch(trap) kernel 0x%qx 0x%x\n", object->getMetaClass()->getClassName(), message->msgid, ret);
		}
		OSSafeReleaseNULL(objectArg1);

		if (kIOReturnSuccess == ret) {
			if (rpc.reply->msgh_body.msgh_descriptor_count) {
				return kIOReturnIPCError;
			}
			reply = IORPCMessageFromMachReply(rpc.reply);
			if (!reply) {
				return kIOReturnIPCError;
			}
			copySize = rpc.reply->msgh.msgh_size - (((uintptr_t) reply) - ((uintptr_t) rpc.reply)) + sizeof(uint64_t);
			if (copySize > outSize) {
				return kIOReturnIPCError;
			}
			replyHdr = (UEXTTrapReply *) ((uintptr_t)reply - sizeof(uint64_t));
			replyHdr->replySize = copySize;
			copyerr = copyout(replyHdr, out, copySize);
			if (copyerr) {
				return kIOReturnVMError;
			}
		}
	}

	return ret;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

IOReturn
IOUserServer::rpc(IORPC rpc)
{
	if (isInactive() && !fRootQueue) {
		return kIOReturnOffline;
	}

	IOReturn           ret;
	IORPCMessage     * message;
	IORPCMessageMach * mach;
	mach_msg_id_t      machid;
	uint32_t           sendSize, replySize;
	bool               oneway;
	uint64_t           msgid;
	IODispatchQueue  * queue;
	IOService        * service;
	ipc_port_t         port;
	ipc_port_t         sendPort;

	queue    = NULL;
	port     = NULL;
	sendPort = NULL;

	mach      = rpc.message;
	sendSize  = rpc.sendSize;
	replySize = rpc.replySize;

	assert(sendSize >= (sizeof(IORPCMessageMach) + sizeof(IORPCMessage)));

	message = rpc.kernelContent;
	if (!message) {
		return kIOReturnIPCError;
	}
	msgid   = message->msgid;
	machid  = (msgid >> 32);

	if (mach->msgh_body.msgh_descriptor_count < 1) {
		return kIOReturnNoMedia;
	}

	IOLockLock(gIOUserServerLock);
	if ((service = OSDynamicCast(IOService, (OSObject *) message->objects[0]))) {
		queue = queueForObject(service, msgid);
	}
	if (!queue) {
		queue = fRootQueue;
	}
	if (queue && (kIODispatchQueueStopped != queue)) {
		port = queue->ivars->serverPort;
	}
	if (port) {
		sendPort = ipc_port_copy_send_mqueue(port);
	}
	IOLockUnlock(gIOUserServerLock);
	if (!sendPort) {
		return kIOReturnNotReady;
	}

	oneway = (0 != (kIORPCMessageOneway & message->flags));

	ret = copyOutObjects(mach, message, sendSize, false);

	mach->msgh.msgh_bits = MACH_MSGH_BITS_COMPLEX |
	    MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, (oneway ? 0 : MACH_MSG_TYPE_MAKE_SEND_ONCE));
	mach->msgh.msgh_remote_port  = sendPort;
	mach->msgh.msgh_local_port   = (oneway ? MACH_PORT_NULL : mig_get_reply_port());
	mach->msgh.msgh_id           = kIORPCVersionCurrent;
	mach->msgh.msgh_reserved     = 0;

	boolean_t message_moved;

	if (oneway) {
		ret = kernel_mach_msg_send(&mach->msgh, sendSize,
		    MACH_SEND_KERNEL_DEFAULT, 0, &message_moved);
	} else {
		assert(replySize >= (sizeof(IORPCMessageMach) + sizeof(IORPCMessage)));
		ret = kernel_mach_msg_rpc(&mach->msgh, sendSize, replySize, FALSE, &message_moved);
	}

	ipc_port_release_send(sendPort);

	if (MACH_MSG_SUCCESS != ret) {
		if (kIODKLogIPC & gIODKDebug) {
			DKLOG("mach_msg() failed 0x%x\n", ret);
		}
		if (!message_moved) {
			// release ports
			copyInObjects(mach, message, sendSize, false, true);
		}
	}

	if ((KERN_SUCCESS == ret) && !oneway) {
		if (kIORPCVersionCurrentReply != mach->msgh.msgh_id) {
			ret = (MACH_NOTIFY_SEND_ONCE == mach->msgh.msgh_id) ? MIG_SERVER_DIED : MIG_REPLY_MISMATCH;
		} else if ((replySize = mach->msgh.msgh_size) < (sizeof(IORPCMessageMach) + sizeof(IORPCMessage))) {
//				printf("BAD REPLY SIZE\n");
			ret = MIG_BAD_ARGUMENTS;
		} else {
			if (!(MACH_MSGH_BITS_COMPLEX & mach->msgh.msgh_bits)) {
				mach->msgh_body.msgh_descriptor_count = 0;
			}
			message = IORPCMessageFromMachReply(mach);
			if (!message) {
				ret = kIOReturnIPCError;
			} else if (message->msgid != msgid) {
//					printf("BAD REPLY ID\n");
				ret = MIG_BAD_ARGUMENTS;
			} else {
				bool isError = (0 != (kIORPCMessageError & message->flags));
				ret = copyInObjects(mach, message, replySize, !isError, true);
				if (kIOReturnSuccess != ret) {
					if (kIODKLogIPC & gIODKDebug) {
						DKLOG("rpc copyin(0x%x) %x\n", ret, mach->msgh.msgh_id);
					}
					if (!isError) {
						consumeObjects(mach, message, replySize);
						copyInObjects(mach, message, replySize, false, true);
					}
					return KERN_NOT_SUPPORTED;
				}
				if (isError) {
					IORPCMessageErrorReturnContent * errorMsg = (typeof(errorMsg))message;
					ret = errorMsg->result;
				}
			}
		}
	}

	return ret;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

static IORPCMessage *
IORPCMessageFromMachReply(IORPCMessageMach * msg)
{
	mach_msg_size_t              idx, count;
	mach_msg_port_descriptor_t * desc;
	mach_msg_port_descriptor_t * maxDesc;
	size_t                       size, msgsize;
	bool                         upgrade;
	bool                         reply = true;

	msgsize = msg->msgh.msgh_size;
	count   = msg->msgh_body.msgh_descriptor_count;
	desc    = &msg->objects[0];
	maxDesc = (typeof(maxDesc))(((uintptr_t) msg) + msgsize);
	upgrade = (msg->msgh.msgh_id != (reply ? kIORPCVersionCurrentReply : kIORPCVersionCurrent));

	if (upgrade) {
		OSReportWithBacktrace("obsolete message");
		return NULL;
	}

	for (idx = 0; idx < count; idx++) {
		if (desc >= maxDesc) {
			return NULL;
		}
		switch (desc->type) {
		case MACH_MSG_PORT_DESCRIPTOR:
			size = sizeof(mach_msg_port_descriptor_t);
			break;
		case MACH_MSG_OOL_DESCRIPTOR:
			size = sizeof(mach_msg_ool_descriptor_t);
			break;
		default:
			return NULL;
		}
		desc = (typeof(desc))(((uintptr_t) desc) + size);
	}
	return (IORPCMessage *)(uintptr_t) desc;
}

ipc_port_t
IOUserServer::copySendRightForObject(OSObject * object, ipc_kobject_type_t type)
{
	ipc_port_t port;
	ipc_port_t sendPort = NULL;
	ipc_kobject_t kobj;

	port = iokit_port_for_object(object, type, &kobj);
	if (port) {
		sendPort = ipc_kobject_make_send(port, kobj, type);
		iokit_release_port(port);
	}

	return sendPort;
}

OSObject *
IOUserServer::copyObjectForSendRight(ipc_port_t port, ipc_kobject_type_t type)
{
	OSObject * object;
	object = iokit_lookup_io_object(port, type);
	return object;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

// Create a vm_map_copy_t or kalloc'ed data for memory
// to be copied out. ipc will free after the copyout.

static kern_return_t
copyoutkdata(const void * data, vm_size_t len, void ** buf)
{
	kern_return_t       err;
	vm_map_copy_t       copy;

	err = vm_map_copyin( kernel_map, CAST_USER_ADDR_T(data), len,
	    false /* src_destroy */, &copy);

	assert( err == KERN_SUCCESS );
	if (err == KERN_SUCCESS) {
		*buf = (char *) copy;
	}

	return err;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

IOReturn
IOUserServer::copyOutObjects(IORPCMessageMach * mach, IORPCMessage * message,
    size_t size, bool consume)
{
	uint64_t           refs;
	uint32_t           idx, maxObjectCount;
	ipc_port_t         port;
	OSObject         * object;
	size_t             descsize;
	mach_msg_port_descriptor_t * desc;
	mach_msg_ool_descriptor_t  * ool;
	vm_map_copy_t                copy;
	void                       * address;
	mach_msg_size_t              length;
	kern_return_t                kr;
	OSSerialize                * s;

	refs           = message->objectRefs;
	maxObjectCount = MAX_OBJECT_COUNT(mach, size, message);
//	assert(refs <= mach->msgh_body.msgh_descriptor_count);
//	assert(refs <= maxObjectCount);
	if (refs > mach->msgh_body.msgh_descriptor_count) {
		return kIOReturnBadArgument;
	}
	if (refs > maxObjectCount) {
		return kIOReturnBadArgument;
	}

	desc = &mach->objects[0];
	for (idx = 0; idx < refs; idx++) {
		object = (OSObject *) message->objects[idx];

		switch (desc->type) {
		case MACH_MSG_PORT_DESCRIPTOR:
			descsize = sizeof(mach_msg_port_descriptor_t);
			port = NULL;
			if (object) {
#if DEVELOPMENT || DEBUG
				if (kIODKLogIPC & gIODKDebug) {
					IOMemoryDescriptor * iomd = OSDynamicCast(IOMemoryDescriptor, object);
					if (iomd != NULL && (iomd->getFlags() & kIOMemoryThreadSafe) == 0) {
						OSReportWithBacktrace("IOMemoryDescriptor %p was created without kIOMemoryThreadSafe flag", iomd);
					}
				}
#endif /* DEVELOPMENT || DEBUG */

				port = copySendRightForObject(object, IKOT_UEXT_OBJECT);
				if (!port) {
					break;
				}
				if (consume) {
					object->release();
				}
				message->objects[idx] = 0;
			}
//		    desc->type        = MACH_MSG_PORT_DESCRIPTOR;
			desc->disposition = MACH_MSG_TYPE_MOVE_SEND;
			desc->name        = port;
			desc->pad2        = 0;
			desc->pad_end     = 0;
			break;

		case MACH_MSG_OOL_DESCRIPTOR:
			descsize = sizeof(mach_msg_ool_descriptor_t);

			length = 0;
			address = NULL;
			if (object) {
				s = OSSerialize::binaryWithCapacity(4096);
				assert(s);
				if (!s) {
					break;
				}
				s->setIndexed(true);
				if (!object->serialize(s)) {
					assert(false);
					descsize = -1UL;
					s->release();
					break;
				}
				length = s->getLength();
				kr = copyoutkdata(s->text(), length, &address);
				s->release();
				if (KERN_SUCCESS != kr) {
					descsize = -1UL;
					address = NULL;
					length = 0;
				}
				if (consume) {
					object->release();
				}
				message->objects[idx] = 0;
			}
			ool = (typeof(ool))desc;
//		    ool->type        = MACH_MSG_OOL_DESCRIPTOR;
			ool->deallocate  = false;
			ool->copy        = MACH_MSG_PHYSICAL_COPY;
			ool->size        = length;
			ool->address     = address;
			break;

		default:
			descsize = -1UL;
			break;
		}
		if (-1UL == descsize) {
			break;
		}
		desc = (typeof(desc))(((uintptr_t) desc) + descsize);
	}

	if (idx >= refs) {
		return kIOReturnSuccess;
	}

	desc = &mach->objects[0];
	while (idx--) {
		switch (desc->type) {
		case MACH_MSG_PORT_DESCRIPTOR:
			descsize = sizeof(mach_msg_port_descriptor_t);
			port = desc->name;
			if (port) {
				ipc_port_release_send(port);
			}
			break;

		case MACH_MSG_OOL_DESCRIPTOR:
			descsize = sizeof(mach_msg_ool_descriptor_t);
			ool = (typeof(ool))desc;
			copy = (vm_map_copy_t) ool->address;
			if (copy) {
				vm_map_copy_discard(copy);
			}
			break;

		default:
			descsize = -1UL;
			break;
		}
		if (-1UL == descsize) {
			break;
		}
		desc = (typeof(desc))(((uintptr_t) desc) + descsize);
	}

	return kIOReturnBadArgument;
}

IOReturn
IOUserServer::copyInObjects(IORPCMessageMach * mach, IORPCMessage * message,
    size_t size, bool copyObjects, bool consumePorts)
{
	uint64_t           refs;
	uint32_t           idx, maxObjectCount;
	ipc_port_t         port;
	OSObject         * object;
	size_t                       descsize;
	mach_msg_port_descriptor_t * desc;
	mach_msg_ool_descriptor_t  * ool;
	vm_map_address_t             copyoutdata;
	kern_return_t                kr;

	refs           = message->objectRefs;
	maxObjectCount = MAX_OBJECT_COUNT(mach, size, message);
//	assert(refs <= mach->msgh_body.msgh_descriptor_count);
//	assert(refs <= maxObjectCount);
	if (refs > mach->msgh_body.msgh_descriptor_count) {
		return kIOReturnBadArgument;
	}
	if (refs > maxObjectCount) {
		return kIOReturnBadArgument;
	}

	for (idx = 0; idx < refs; idx++) {
		message->objects[idx] = (OSObjectRef) NULL;
	}

	desc = &mach->objects[0];
	for (idx = 0; idx < mach->msgh_body.msgh_descriptor_count; idx++) {
		bool isObjectPort = idx < refs;

		switch (desc->type) {
		case MACH_MSG_PORT_DESCRIPTOR:
			descsize = sizeof(mach_msg_port_descriptor_t);

			object = NULL;
			port = desc->name;
			if (port) {
				if (isObjectPort && copyObjects) {
					object = copyObjectForSendRight(port, IKOT_UEXT_OBJECT);
					if (!object) {
						descsize = -1UL;
						break;
					}
				}
				if (consumePorts) {
					ipc_port_release_send(port);
					desc->name = MACH_PORT_NULL;
				}
			}
			break;

		case MACH_MSG_OOL_DESCRIPTOR:
			descsize = sizeof(mach_msg_ool_descriptor_t);
			ool = (typeof(ool))desc;

			object = NULL;
			if (isObjectPort && copyObjects && ool->size && ool->address) {
				kr = vm_map_copyout(kernel_map, &copyoutdata, (vm_map_copy_t) ool->address);
				if (KERN_SUCCESS == kr) {
					object = OSUnserializeXML((const char *) copyoutdata, ool->size);
					kr = vm_deallocate(kernel_map, copyoutdata, ool->size);
					assert(KERN_SUCCESS == kr);
					// vm_map_copyout() has consumed the vm_map_copy_t in the message
					ool->size = 0;
					ool->address = NULL;
				}
				if (!object) {
					descsize = -1UL;
					break;
				}
			}
			break;

		default:
			descsize = -1UL;
			break;
		}
		if (-1UL == descsize) {
			break;
		}
		if (isObjectPort && copyObjects) {
			message->objects[idx] = (OSObjectRef) object;
		}
		desc = (typeof(desc))(((uintptr_t) desc) + descsize);
	}

	if (idx >= refs) {
		return kIOReturnSuccess;
	}

	while (idx--) {
		object = (OSObject *) message->objects[idx];
		OSSafeReleaseNULL(object);
		message->objects[idx] = 0;
	}

	return kIOReturnBadArgument;
}

IOReturn
IOUserServer::consumeObjects(IORPCMessageMach *mach, IORPCMessage * message, size_t messageSize)
{
	uint64_t    refs, idx;
	OSObject  * object;

	refs   = message->objectRefs;
	uint32_t maxObjectCount = MAX_OBJECT_COUNT(mach, messageSize, message);
	if (refs > mach->msgh_body.msgh_descriptor_count) {
		return kIOReturnBadArgument;
	}
	if (refs > maxObjectCount) {
		return kIOReturnBadArgument;
	}

	for (idx = 0; idx < refs; idx++) {
		object = (OSObject *) message->objects[idx];
		if (object) {
			object->release();
			message->objects[idx] = 0;
		}
	}

	return kIOReturnSuccess;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

bool
IOUserServer::finalize(IOOptionBits options)
{
	OSArray   * services;

	if (kIODKLogSetup & gIODKDebug) {
		DKLOG("%s::finalize(%p)\n", getName(), this);
	}

	IOLockLock(gIOUserServerLock);
	OSSafeReleaseNULL(fRootQueue);
	IOLockUnlock(gIOUserServerLock);

	services = NULL;
	IOLockLock(fLock);
	if (fServices) {
		services = OSArray::withArray(fServices);
	}
	IOLockUnlock(fLock);

	IOOptionBits terminateFlags = kIOServiceTerminateNeedWillTerminate | kIOServiceTerminateWithRematch;
	if (fCheckInToken) {
		bool can_rematch = fCheckInToken->dextTerminate();
		if (can_rematch) {
			terminateFlags |= kIOServiceTerminateWithRematchCurrentDext;
		} else {
			DKLOG("%s::finalize(%p) dext was replaced, do not rematch current dext\n", getName(), this);
		}
	} else {
		terminateFlags |= kIOServiceTerminateWithRematchCurrentDext;
		DKLOG("%s::finalize(%p) could not find fCheckInToken\n", getName(), this);
	}

	if (services) {
		services->iterateObjects(^bool (OSObject * obj) {
			int         service __unused;       // hide outer defn
			IOService * nextService;
			IOService * provider;
			bool        started = false;

			nextService = (IOService *) obj;
			if (kIODKLogSetup & gIODKDebug) {
			        DKLOG("%s::terminate(" DKS ")\n", getName(), DKN(nextService));
			}
			if (nextService->reserved->uvars) {
			        IOUserClient * nextUserClient = OSDynamicCast(IOUserClient, nextService);
			        provider = nextService->getProvider();
			        if (nextUserClient) {
			                nextUserClient->setTerminateDefer(provider, false);
				}
			        started = nextService->reserved->uvars->started;
			        nextService->reserved->uvars->serverDied = true;

			        serviceDidStop(nextService, provider);
			        if (provider != NULL && (terminateFlags & kIOServiceTerminateWithRematchCurrentDext) == 0) {
			                provider->resetRematchProperties();
				}
			        if (started) {
			                nextService->terminate(terminateFlags);
				}
			}
			if (!started) {
			        DKLOG("%s::terminate(" DKS ") server exit before start()\n", getName(), DKN(nextService));
			        serviceStop(nextService, NULL);
			}
			return false;
		});
		services->release();
	}

	return IOUserClient::finalize(options);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#undef super
#define super IOUserClient2022

OSDefineMetaClassAndStructors(IOUserServer, IOUserClient2022)

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

IOUserClient * IOUserServer::withTask(task_t owningTask)
{
	IOUserServer * inst;

	assert(owningTask == current_task());
	if (!task_is_driver(owningTask)) {
		DKLOG("IOUserServer may only be created with driver tasks\n");
		return NULL;
	}

	inst = new IOUserServer;
	if (inst && !inst->init()) {
		inst->release();
		inst = NULL;
		return inst;
	}
	OS_ANALYZER_SUPPRESS("82033761") inst->PMinit();

	inst->fOwningTask = current_task();
	task_reference(inst->fOwningTask);

	inst->fEntitlements = IOUserClient::copyClientEntitlements(inst->fOwningTask);

	if (!(kIODKDisableEntitlementChecking & gIODKDebug)) {
		proc_t p;
		pid_t  pid;
		const char * name;
		p = (proc_t)get_bsdtask_info(inst->fOwningTask);
		if (p) {
			name = proc_best_name(p);
			pid = proc_pid(p);
		} else {
			name = "unknown";
			pid = 0;
		}

		if (inst->fEntitlements == NULL) {
#if DEVELOPMENT || DEBUG
			panic("entitlements are missing for %s[%d]\n", name, pid);
#else
			DKLOG("entitlements are missing for %s[%d]\n", name, pid);
#endif /* DEVELOPMENT || DEBUG */
		}


		const char * dextTeamID = csproc_get_teamid(p);
		if (dextTeamID != NULL) {
			inst->fTeamIdentifier = OSString::withCString(dextTeamID);
			DKLOG("%s[%d] has team identifier %s\n", name, pid, dextTeamID);
		}

		if (!IOCurrentTaskHasEntitlement(gIODriverKitEntitlementKey->getCStringNoCopy())) {
			IOLog(kIODriverKitEntitlementKey " entitlement check failed for %s[%d]\n", name, pid);
			inst->release();
			inst = NULL;
			return inst;
		}
	}

	/* Mark the current task's space as eligible for uext object ports */
	iokit_label_dext_task(inst->fOwningTask);

	inst->fLock     = IOLockAlloc();
	inst->fServices = OSArray::withCapacity(4);
	inst->fClasses  = OSDictionary::withCapacity(16);
	inst->fClasses->setOptions(OSCollection::kSort, OSCollection::kSort);
	inst->fPlatformDriver = task_get_platform_binary(inst->fOwningTask);
	if (csproc_get_validation_category(current_proc(), &inst->fCSValidationCategory) != KERN_SUCCESS) {
		inst->fCSValidationCategory = CS_VALIDATION_CATEGORY_INVALID;
	}

	inst->setProperty(kIOUserClientDefaultLockingKey, kOSBooleanTrue);
	inst->setProperty(kIOUserClientDefaultLockingSetPropertiesKey, kOSBooleanTrue);
	inst->setProperty(kIOUserClientDefaultLockingSingleThreadExternalMethodKey, kOSBooleanTrue);
	//requirement for gIODriverKitEntitlementKey is enforced elsewhere conditionally
	inst->setProperty(kIOUserClientEntitlementsKey, kOSBooleanFalse);

	return inst;
}

static bool gIOUserServerLeakObjects = false;

bool
IOUserServer::shouldLeakObjects()
{
	return gIOUserServerLeakObjects;
}

void
IOUserServer::beginLeakingObjects()
{
	gIOUserServerLeakObjects = true;
}

bool
IOUserServer::isPlatformDriver()
{
	return fPlatformDriver;
}

int
IOUserServer::getCSValidationCategory()
{
	return fCSValidationCategory;
}


struct IOUserServerRecordExitReasonContext {
	task_t task;
	os_reason_t reason;
};

static bool
IOUserServerRecordExitReasonMatch(const OSObject *obj, void * context)
{
	IOUserServerRecordExitReasonContext * ctx = (IOUserServerRecordExitReasonContext *)context;
	IOUserServer * us = OSDynamicCast(IOUserServer, obj);
	if (us == NULL) {
		return false;
	}

	if (us->fOwningTask == ctx->task) {
		assert(us->fTaskCrashReason == OS_REASON_NULL);
		assert(ctx->reason != OS_REASON_NULL);
		os_reason_ref(ctx->reason);
		us->fTaskCrashReason = ctx->reason;
		return true;
	}

	return false;
}

extern "C" void
IOUserServerRecordExitReason(task_t task, os_reason_t reason)
{
	IOUserServerRecordExitReasonContext ctx { task, reason };
	IOUserServer::gMetaClass.applyToInstances(IOUserServerRecordExitReasonMatch, &ctx);
}

IOReturn
IOUserServer::clientClose(void)
{
	OSArray   * services;
	bool __block unexpectedExit = false;

	if (kIODKLogSetup & gIODKDebug) {
		DKLOG("%s::clientClose(%p)\n", getName(), this);
	}
	services = NULL;
	IOLockLock(fLock);
	if (fServices) {
		services = OSArray::withArray(fServices);
	}
	IOLockUnlock(fLock);

	// if this was a an expected exit, termination and stop should have detached at this
	// point, so send any provider still attached and not owned by this user server
	// the ClientCrashed() notification
	if (services) {
		services->iterateObjects(^bool (OSObject * obj) {
			int         service __unused;       // hide outer defn
			IOService * nextService;
			IOService * provider;

			nextService = (IOService *) obj;
			if (nextService->isInactive()) {
			        return false;
			}
			if (nextService->reserved && nextService->reserved->uvars && nextService->reserved->uvars->started) {
			        unexpectedExit = true;
			}
			provider = nextService->getProvider();
			if (provider
			&& (!provider->reserved->uvars || (provider->reserved->uvars->userServer != this))) {
			        if (kIODKLogSetup & gIODKDebug) {
			                DKLOG(DKS "::ClientCrashed(" DKS ")\n", DKN(provider), DKN(nextService));
				}
			        if (unexpectedExit) {
			                provider->unregisterAllInterrupts();
				}
			        provider->ClientCrashed(nextService, 0);
			}
			return false;
		});
		services->release();
	}

	if (unexpectedExit &&
	    !gInUserspaceReboot &&
	    (fTaskCrashReason != OS_REASON_NULL && fTaskCrashReason->osr_namespace != OS_REASON_JETSAM && fTaskCrashReason->osr_namespace != OS_REASON_RUNNINGBOARD) &&
	    fStatistics != NULL) {
		OSDextCrashPolicy policy = fStatistics->recordCrash();
		bool allowPanic;
#if DEVELOPMENT || DEBUG
		allowPanic = !restore_boot && fPlatformDriver && fEntitlements->getObject(gIODriverKitTestDriverEntitlementKey) != kOSBooleanTrue && !disable_dext_crash_reboot;
#else
		allowPanic = !restore_boot && fPlatformDriver;
#endif /* DEVELOPMENT || DEBUG */

		if (policy == kOSDextCrashPolicyReboot && allowPanic) {
			panic("Driver %s has crashed too many times\n", getName());
		}
	}

	terminate();
	return kIOReturnSuccess;
}

IOReturn
IOUserServer::setProperties(OSObject * properties)
{
	IOReturn kr = kIOReturnUnsupported;
	return kr;
}

void
IOUserServer::stop(IOService * provider)
{
	if (fOwningTask) {
		task_deallocate(fOwningTask);
		fOwningTask = TASK_NULL;
	}

	PMstop();

	IOServicePH::serverRemove(this);

	OSSafeReleaseNULL(fRootQueue);

	if (fInterruptLock) {
		IOSimpleLockFree(fInterruptLock);
	}
}

void
IOUserServer::free()
{
	OSSafeReleaseNULL(fEntitlements);
	OSSafeReleaseNULL(fClasses);
	if (fOwningTask) {
		task_deallocate(fOwningTask);
		fOwningTask = TASK_NULL;
	}
	if (fLock) {
		IOLockFree(fLock);
	}
	OSSafeReleaseNULL(fServices);
	OSSafeReleaseNULL(fCheckInToken);
	OSSafeReleaseNULL(fStatistics);
	OSSafeReleaseNULL(fTeamIdentifier);
	if (fAllocationName) {
		kern_allocation_name_release(fAllocationName);
		fAllocationName = NULL;
	}
	if (fTaskCrashReason != OS_REASON_NULL) {
		os_reason_free(fTaskCrashReason);
	}
	IOUserClient::free();
}

IOReturn
IOUserServer::registerClass(OSClassDescription * desc, uint32_t size, OSUserMetaClass ** pCls)
{
	OSUserMetaClass * cls;
	const OSSymbol  * sym;
	uint64_t        * methodOptions;
	const char      * queueNames;
	uint32_t          methodOptionsEnd, queueNamesEnd;
	IOReturn          ret = kIOReturnSuccess;

	if (size < sizeof(OSClassDescription)) {
		assert(false);
		return kIOReturnBadArgument;
	}

	if (kIODKLogSetup & gIODKDebug) {
		DKLOG("%s::registerClass %s, %d, %d\n", getName(), desc->name, desc->queueNamesSize, desc->methodNamesSize);
	}

	if (desc->descriptionSize != size) {
		assert(false);
		return kIOReturnBadArgument;
	}
	if (os_add_overflow(desc->queueNamesOffset, desc->queueNamesSize, &queueNamesEnd)) {
		assert(false);
		return kIOReturnBadArgument;
	}
	if (queueNamesEnd > size) {
		assert(false);
		return kIOReturnBadArgument;
	}
	if (os_add_overflow(desc->methodOptionsOffset, desc->methodOptionsSize, &methodOptionsEnd)) {
		assert(false);
		return kIOReturnBadArgument;
	}
	if (methodOptionsEnd > size) {
		assert(false);
		return kIOReturnBadArgument;
	}
	// overlaps?
	if ((desc->queueNamesOffset >= desc->methodOptionsOffset) && (desc->queueNamesOffset < methodOptionsEnd)) {
		assert(false);
		return kIOReturnBadArgument;
	}
	if ((queueNamesEnd >= desc->methodOptionsOffset) && (queueNamesEnd < methodOptionsEnd)) {
		assert(false);
		return kIOReturnBadArgument;
	}

	if (desc->methodOptionsSize & ((2 * sizeof(uint64_t)) - 1)) {
		assert(false);
		return kIOReturnBadArgument;
	}
	if (sizeof(desc->name) == strnlen(desc->name, sizeof(desc->name))) {
		assert(false);
		return kIOReturnBadArgument;
	}
	if (sizeof(desc->superName) == strnlen(desc->superName, sizeof(desc->superName))) {
		assert(false);
		return kIOReturnBadArgument;
	}

	cls = OSTypeAlloc(OSUserMetaClass);
	assert(cls);
	if (!cls) {
		return kIOReturnNoMemory;
	}

	cls->description = (typeof(cls->description))IOMallocData(size);
	assert(cls->description);
	if (!cls->description) {
		assert(false);
		cls->release();
		return kIOReturnNoMemory;
	}
	bcopy(desc, cls->description, size);

	cls->methodCount = desc->methodOptionsSize / (2 * sizeof(uint64_t));
	cls->methods = IONewData(uint64_t, 2 * cls->methodCount);
	if (!cls->methods) {
		assert(false);
		cls->release();
		return kIOReturnNoMemory;
	}

	methodOptions = (typeof(methodOptions))(((uintptr_t) desc) + desc->methodOptionsOffset);
	bcopy(methodOptions, cls->methods, 2 * cls->methodCount * sizeof(uint64_t));

	queueNames = (typeof(queueNames))(((uintptr_t) desc) + desc->queueNamesOffset);
	cls->queueNames = copyInStringArray(queueNames, desc->queueNamesSize);

	sym = OSSymbol::withCString(desc->name);
	assert(sym);
	if (!sym) {
		assert(false);
		cls->release();
		return kIOReturnNoMemory;
	}

	cls->name = sym;
	cls->meta = OSMetaClass::copyMetaClassWithName(sym);
	IOLockLock(fLock);
	cls->superMeta = OSDynamicCast(OSUserMetaClass, fClasses->getObject(desc->superName));
	if (fClasses->getObject(sym) != NULL) {
		/* class with this name exists */
		ret = kIOReturnBadArgument;
	} else {
		if (fClasses->setObject(sym, cls)) {
			*pCls = cls;
		} else {
			/* could not add class to fClasses */
			ret = kIOReturnNoMemory;
		}
	}
	IOLockUnlock(fLock);
	cls->release();
	return ret;
}

IOReturn
IOUserServer::registerClass(OSClassDescription * desc, uint32_t size, OSSharedPtr<OSUserMetaClass>& pCls)
{
	OSUserMetaClass* pClsRaw = NULL;
	IOReturn result = registerClass(desc, size, &pClsRaw);
	if (result == kIOReturnSuccess) {
		pCls.reset(pClsRaw, OSRetain);
	}
	return result;
}

IOReturn
IOUserServer::setRootQueue(IODispatchQueue * queue)
{
	assert(!fRootQueue);
	if (fRootQueue) {
		return kIOReturnStillOpen;
	}
	queue->retain();
	fRootQueue = queue;

	return kIOReturnSuccess;
}


IOReturn
IOUserServer::externalMethod(uint32_t selector, IOExternalMethodArgumentsOpaque * args)
{
	static const IOExternalMethodDispatch2022 dispatchArray[] = {
		[kIOUserServerMethodRegisterClass] = {
			.function                 = &IOUserServer::externalMethodRegisterClass,
			.checkScalarInputCount    = 0,
			.checkStructureInputSize  = kIOUCVariableStructureSize,
			.checkScalarOutputCount   = 2,
			.checkStructureOutputSize = 0,
			.allowAsync               = false,
			.checkEntitlement         = NULL,
		},
		[kIOUserServerMethodStart] = {
			.function                 = &IOUserServer::externalMethodStart,
			.checkScalarInputCount    = 1,
			.checkStructureInputSize  = 0,
			.checkScalarOutputCount   = 1,
			.checkStructureOutputSize = 0,
			.allowAsync               = false,
			.checkEntitlement         = NULL,
		},
	};

	return dispatchExternalMethod(selector, args, dispatchArray, sizeof(dispatchArray) / sizeof(dispatchArray[0]), this, NULL);
}

IOReturn
IOUserServer::externalMethodRegisterClass(OSObject * target, void * reference, IOExternalMethodArguments * args)
{
	IOReturn ret = kIOReturnBadArgument;
	mach_port_name_t portname;

	IOUserServer * me = (typeof(me))target;

	OSUserMetaClass * cls;
	if (!args->structureInputSize) {
		return kIOReturnBadArgument;
	}

	ret = me->registerClass((OSClassDescription *) args->structureInput, args->structureInputSize, &cls);
	if (kIOReturnSuccess == ret) {
		portname = iokit_make_send_right(me->fOwningTask, cls, IKOT_UEXT_OBJECT);
		assert(portname);
		args->scalarOutput[0] = portname;
		args->scalarOutput[1] = kOSObjectRPCRemote;
	}

	return ret;
}

IOReturn
IOUserServer::externalMethodStart(OSObject * target, void * reference, IOExternalMethodArguments * args)
{
	mach_port_name_t portname = 0;
	IOReturn ret = kIOReturnSuccess;

	IOUserServer * me = (typeof(me))target;

	if (!(kIODKDisableCheckInTokenVerification & gIODKDebug)) {
		mach_port_name_t checkInPortName = ((typeof(checkInPortName))args->scalarInput[0]);
		OSObject * obj = iokit_lookup_object_with_port_name(checkInPortName, IKOT_IOKIT_IDENT, me->fOwningTask);
		IOUserServerCheckInToken * retrievedToken = OSDynamicCast(IOUserServerCheckInToken, obj);
		if (retrievedToken != NULL) {
			ret = me->setCheckInToken(retrievedToken);
		} else {
			ret = kIOReturnBadArgument;
		}
		OSSafeReleaseNULL(obj);
	}
	if (ret == kIOReturnSuccess) {
		portname = iokit_make_send_right(me->fOwningTask, me, IKOT_UEXT_OBJECT);
		assert(portname);
	}
	args->scalarOutput[0] = portname;
	return ret;
}
IOExternalTrap *
IOUserServer::getTargetAndTrapForIndex( IOService **targetP, UInt32 index )
{
	static const OSBoundedArray<IOExternalTrap, 1> trapTemplate = {{
									       { NULL, (IOTrap) & IOUserServer::waitInterruptTrap},
								       }};
	if (index >= trapTemplate.size()) {
		return NULL;
	}
	*targetP = this;
	return (IOExternalTrap *)&trapTemplate[index];
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

IOReturn
IOUserServer::serviceAttach(IOService * service, IOService * provider)
{
	IOReturn           ret;
	OSObjectUserVars * vars;
	OSObject         * prop;
	OSString         * str;
	OSSymbol const*   bundleID;
	char               execPath[1024];

	vars = IOMallocType(OSObjectUserVars);
	service->reserved->uvars = vars;

	vars->userServer = this;
	vars->userServer->retain();
	vars->uvarsLock = IOLockAlloc();
	IOLockLock(fLock);
	if (-1U == fServices->getNextIndexOfObject(service, 0)) {
		fServices->setObject(service);

		// Add to IOAssociatedServices
		OSObject * serviceArrayObj = copyProperty(gIOAssociatedServicesKey);
		OSArray * serviceArray = OSDynamicCast(OSArray, serviceArrayObj);
		if (!serviceArray) {
			serviceArray = OSArray::withCapacity(0);
		} else {
			serviceArray = OSDynamicCast(OSArray, serviceArray->copyCollection());
			assert(serviceArray != NULL);
		}

		OSNumber * registryEntryNumber = OSNumber::withNumber(service->getRegistryEntryID(), 64);
		serviceArray->setObject(registryEntryNumber);
		setProperty(gIOAssociatedServicesKey, serviceArray);
		OSSafeReleaseNULL(registryEntryNumber);
		OSSafeReleaseNULL(serviceArray);
		OSSafeReleaseNULL(serviceArrayObj);

		// populate kIOUserClassesKey

		OSUserMetaClass * userMeta;
		OSArray         * classesArray;
		const OSString  * str2;

		classesArray = OSArray::withCapacity(4);
		prop = service->copyProperty(gIOUserClassKey);
		str2 = OSDynamicCast(OSString, prop);
		userMeta = (typeof(userMeta))service->reserved->uvars->userServer->fClasses->getObject(str2);
		while (str2 && userMeta) {
			classesArray->setObject(str2);
			userMeta = userMeta->superMeta;
			if (userMeta) {
				str2 = userMeta->name;
			}
		}
		service->setProperty(gIOUserClassesKey, classesArray);
		OSSafeReleaseNULL(classesArray);
		OSSafeReleaseNULL(prop);
	}
	IOLockUnlock(fLock);

	prop = service->copyProperty(gIOUserClassKey);
	str = OSDynamicCast(OSString, prop);
	if (str) {
		service->setName(str);
	}
	OSSafeReleaseNULL(prop);

	prop = service->copyProperty(gIOModuleIdentifierKey);
	bundleID = OSDynamicCast(OSSymbol, prop);
	if (bundleID) {
		execPath[0] = 0;
		bool ok = OSKext::copyUserExecutablePath(bundleID, execPath, sizeof(execPath));
		if (ok) {
			ret = LoadModule(execPath);
			if (kIODKLogSetup & gIODKDebug) {
				DKLOG("%s::LoadModule 0x%x %s\n", getName(), ret, execPath);
			}
		}
	}
	OSSafeReleaseNULL(prop);

	ret = kIOReturnSuccess;

	return ret;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

IOReturn
IOUserServer::serviceNewUserClient(IOService * service, task_t owningTask, void * securityID,
    uint32_t type, OSDictionary * properties, IOUserClient ** handler)
{
	IOReturn           ret;
	IOUserClient     * uc;
	IOUserUserClient * userUC;
	OSDictionary     * entitlements;
	OSObject         * prop;
	OSObject         * bundleID;
	bool               ok = false;

	entitlements = IOUserClient::copyClientEntitlements(owningTask);
	if (!entitlements) {
		entitlements = OSDictionary::withCapacity(8);
	}
	if (entitlements) {
		if (kIOReturnSuccess == clientHasPrivilege((void *) owningTask, kIOClientPrivilegeAdministrator)) {
			entitlements->setObject(kIODriverKitUserClientEntitlementAdministratorKey, kOSBooleanTrue);
		}
		OSString * creatorName = IOCopyLogNameForPID(proc_selfpid());
		if (creatorName) {
			entitlements->setObject(kIOUserClientCreatorKey, creatorName);
			OSSafeReleaseNULL(creatorName);
		}
	}

	*handler = NULL;
	ret = service->_NewUserClient(type, entitlements, &uc);
	if (kIOReturnSuccess != ret) {
		OSSafeReleaseNULL(entitlements);
		return ret;
	}
	userUC = OSDynamicCast(IOUserUserClient, uc);
	if (!userUC) {
		uc->terminate(kIOServiceTerminateNeedWillTerminate);
		uc->setTerminateDefer(service, false);
		OSSafeReleaseNULL(uc);
		OSSafeReleaseNULL(entitlements);
		return kIOReturnUnsupported;
	}
	userUC->setTask(owningTask);

	if (!(kIODKDisableEntitlementChecking & gIODKDebug)) {
		do {
			bool checkiOS3pEntitlements;

			// check if client has com.apple.private.driverkit.driver-access and the required entitlements match the driver's entitlements
			if (entitlements && (prop = entitlements->getObject(gIODriverKitRequiredEntitlementsKey))) {
				prop->retain();
				ok = checkEntitlements(fEntitlements, prop, NULL, NULL);
				if (ok) {
					break;
				} else {
					DKLOG(DKS ":UC failed required entitlement check\n", DKN(userUC));
				}
			}

#if XNU_TARGET_OS_IOS
			checkiOS3pEntitlements = !fPlatformDriver;
			if (checkiOS3pEntitlements && fTeamIdentifier == NULL) {
				DKLOG("warning: " DKS " does not have a team identifier\n", DKN(this));
			}
#else
			checkiOS3pEntitlements = false;
#endif
			if (checkiOS3pEntitlements) {
				// App must have com.apple.developer.driverkit.communicates-with-drivers
				ok = entitlements && entitlements->getObject(gIODriverKitUserClientEntitlementCommunicatesWithDriversKey) == kOSBooleanTrue;
				if (ok) {
					// check team ID
					const char * clientTeamID = csproc_get_teamid(current_proc());
					bool sameTeam = fTeamIdentifier != NULL && clientTeamID != NULL && strncmp(fTeamIdentifier->getCStringNoCopy(), clientTeamID, CS_MAX_TEAMID_LEN) == 0;

					if (sameTeam) {
						ok = true;
					} else {
						// different team IDs, dext must have com.apple.developer.driverkit.allow-third-party-userclients
						ok = fEntitlements && fEntitlements->getObject(gIODriverKitUserClientEntitlementAllowThirdPartyUserClientsKey) == kOSBooleanTrue;
					}
					if (!ok) {
						DKLOG(DKS ":UC failed team ID check. client team=%s, driver team=%s\n", DKN(userUC), clientTeamID ? clientTeamID : "(null)", fTeamIdentifier ? fTeamIdentifier->getCStringNoCopy() : "(null)");
					}
				} else {
					DKLOG(DKS ":UC entitlement check failed, app does not have %s entitlement\n", DKN(userUC), gIODriverKitUserClientEntitlementCommunicatesWithDriversKey->getCStringNoCopy());
				}

				// When checking iOS 3rd party entitlements, do not fall through to other entitlement checks
				break;
			}

			// first party dexts and third party macOS dexts

			// check if driver has com.apple.developer.driverkit.allow-any-userclient-access
			if (fEntitlements && fEntitlements->getObject(gIODriverKitUserClientEntitlementAllowAnyKey)) {
				ok = true;
				break;
			}

			// check if client has com.apple.developer.driverkit.userclient-access and its value matches the bundle ID of the service
			bundleID = service->copyProperty(gIOModuleIdentifierKey);
			ok = (entitlements
			    && bundleID
			    && (prop = entitlements->getObject(gIODriverKitUserClientEntitlementsKey)));
			if (ok) {
				bool found __block = false;
				ok = prop->iterateObjects(^bool (OSObject * object) {
					found = object->isEqualTo(bundleID);
					return found;
				});
				ok = found;
			} else {
				OSString * bundleIDStr = OSDynamicCast(OSString, bundleID);
				DKLOG(DKS ":UC failed userclient-access check, needed bundle ID %s\n", DKN(userUC), bundleIDStr ? bundleIDStr->getCStringNoCopy() : "(null)");
			}
			OSSafeReleaseNULL(bundleID);
		} while (false);

		if (ok) {
			prop = userUC->copyProperty(gIOServiceDEXTEntitlementsKey);
			ok = checkEntitlements(entitlements, prop, NULL, NULL);
		}

		if (!ok) {
			DKLOG(DKS ":UC entitlements check failed\n", DKN(userUC));
			uc->terminate(kIOServiceTerminateNeedWillTerminate);
			uc->setTerminateDefer(service, false);
			OSSafeReleaseNULL(uc);
			OSSafeReleaseNULL(entitlements);
			return kIOReturnNotPermitted;
		}
	}

	OSSafeReleaseNULL(entitlements);
	*handler = userUC;

	return ret;
}

IOReturn
IOUserServer::serviceNewUserClient(IOService * service, task_t owningTask, void * securityID,
    uint32_t type, OSDictionary * properties, OSSharedPtr<IOUserClient>& handler)
{
	IOUserClient* handlerRaw = NULL;
	IOReturn result = serviceNewUserClient(service, owningTask, securityID, type, properties, &handlerRaw);
	handler.reset(handlerRaw, OSNoRetain);
	return result;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

static IOPMPowerState
    sPowerStates[] = {
	{   .version                = kIOPMPowerStateVersion1,
	    .capabilityFlags        = 0,
	    .outputPowerCharacter   = 0,
	    .inputPowerRequirement  = 0},
	{   .version                = kIOPMPowerStateVersion1,
	    .capabilityFlags        = kIOPMLowPower,
	    .outputPowerCharacter   = kIOPMLowPower,
	    .inputPowerRequirement  = kIOPMLowPower},
	{   .version                = kIOPMPowerStateVersion1,
	    .capabilityFlags        = kIOPMPowerOn,
	    .outputPowerCharacter   = kIOPMPowerOn,
	    .inputPowerRequirement  = kIOPMPowerOn},
};

enum {
	kUserServerMaxPowerState    = 2
};

IOReturn
IOUserServer::serviceJoinPMTree(IOService * service)
{
	IOReturn    ret;
	IOService * pmProvider;
	bool        joinTree;

	if (service->reserved->uvars->userServerPM) {
		return kIOReturnSuccess;
	}

	if (!fRootNotifier) {
		ret = registerPowerDriver(this, sPowerStates, sizeof(sPowerStates) / sizeof(sPowerStates[0]));
		assert(kIOReturnSuccess == ret);
		IOServicePH::serverAdd(this);
		fRootNotifier = true;
	}

	joinTree = false;
	if (!(kIODKDisablePM & gIODKDebug) && !service->pm_vars) {
		kern_return_t  kr;
		OSDictionary * props;
		kr = service->CopyProperties_Local(&props);
		if (kIOReturnSuccess == kr) {
			if (props->getObject(kIOPMResetPowerStateOnWakeKey) == kOSBooleanTrue) {
				service->setProperty(kIOPMResetPowerStateOnWakeKey, kOSBooleanTrue);
			}
			OSSafeReleaseNULL(props);
		}
		service->PMinit();
		ret = service->registerPowerDriver(this, sPowerStates, sizeof(sPowerStates) / sizeof(sPowerStates[0]));
		assert(kIOReturnSuccess == ret);
		joinTree = true;
	}

	pmProvider = service;
	while (pmProvider && !pmProvider->inPlane(gIOPowerPlane)) {
		pmProvider = pmProvider->getProvider();
	}
	if (!pmProvider) {
		pmProvider = getPMRootDomain();
	}
	if (pmProvider) {
		IOService * entry;
		OSObject  * prop;
		OSObject  * nextProp;
		OSString  * str;

		entry = pmProvider;
		prop  = NULL;
		do {
			nextProp = entry->copyProperty("non-removable");
			if (nextProp) {
				OSSafeReleaseNULL(prop);
				prop = nextProp;
			}
			entry = entry->getProvider();
		} while (entry);
		if (prop) {
			str = OSDynamicCast(OSString, prop);
			if (str && str->isEqualTo("yes")) {
				pmProvider = NULL;
			}
			prop->release();
		}
	}

	if (!(kIODKDisablePM & gIODKDebug) && pmProvider) {
		IOLockLock(fLock);
		service->reserved->uvars->powerState = true;
		IOLockUnlock(fLock);

		if (joinTree) {
			pmProvider->joinPMtree(service);
			service->reserved->uvars->userServerPM = true;
			service->reserved->uvars->resetPowerOnWake = service->propertyExists(kIOPMResetPowerStateOnWakeKey);
		}
	}

	service->registerInterestedDriver(this);
	return kIOReturnSuccess;
}

IOReturn
IOUserServer::setPowerState(unsigned long state, IOService * service)
{
	if (kIODKLogPM & gIODKDebug) {
		DKLOG(DKS "::setPowerState(%ld) %d\n", DKN(service), state, fSystemPowerAck);
	}
	return kIOPMAckImplied;
}


IOReturn
IOUserServer::serviceSetPowerState(IOService * controllingDriver, IOService * service, IOPMPowerFlags flags, unsigned long state)
{
	IOReturn ret;
	bool sendIt = false;

	IOLockLock(fLock);
	if (service->reserved->uvars) {
		if (!fSystemOff && !(kIODKDisablePM & gIODKDebug)) {
			OSDictionary * wakeDescription;
			OSObject     * prop;
			char           wakeReasonString[128];

			wakeDescription = OSDictionary::withCapacity(4);
			if (wakeDescription) {
				wakeReasonString[0] = 0;
				getPMRootDomain()->copyWakeReasonString(wakeReasonString, sizeof(wakeReasonString));

				if (wakeReasonString[0]) {
					prop = OSString::withCString(&wakeReasonString[0]);
					wakeDescription->setObject(gIOSystemStateWakeDescriptionWakeReasonKey, prop);
					OSSafeReleaseNULL(prop);
				}
#if defined(__arm__) || defined(__arm64__)
				prop = OSNumber::withNumber(ml_get_conttime_offset(), sizeof(uint64_t) * CHAR_BIT);
				wakeDescription->setObject(gIOSystemStateWakeDescriptionContinuousTimeOffsetKey, prop);
				OSSafeReleaseNULL(prop);
#endif /* defined(__arm__) || defined(__arm64__) */
				getSystemStateNotificationService()->StateNotificationItemSet(gIOSystemStateWakeDescriptionKey, wakeDescription);
				OSSafeReleaseNULL(wakeDescription);
			}

			service->reserved->uvars->willPower = true;
			service->reserved->uvars->willPowerState = state;
			service->reserved->uvars->controllingDriver = controllingDriver;
			sendIt = true;
		} else {
			service->reserved->uvars->willPower = false;
		}
	}
	IOLockUnlock(fLock);

	if (sendIt) {
		if (kIODKLogPM & gIODKDebug) {
			DKLOG(DKS "::serviceSetPowerState(%ld) %d\n", DKN(service), state, fSystemPowerAck);
		}
		ret = service->SetPowerState((uint32_t) flags);
		if (kIOReturnSuccess == ret) {
			return 20 * 1000 * 1000;
		} else {
			IOLockLock(fLock);
			service->reserved->uvars->willPower = false;
			IOLockUnlock(fLock);
		}
	}

	return kIOPMAckImplied;
}

IOReturn
IOUserServer::powerStateWillChangeTo(IOPMPowerFlags flags, unsigned long state, IOService * service)
{
	return kIOPMAckImplied;
}

IOReturn
IOUserServer::powerStateDidChangeTo(IOPMPowerFlags flags, unsigned long state, IOService * service)
{
	unsigned int idx;
	bool         pmAck;

	pmAck = false;
	IOLockLock(fLock);
	idx = fServices->getNextIndexOfObject(service, 0);
	if (-1U == idx) {
		IOLockUnlock(fLock);
		return kIOPMAckImplied;
	}

	service->reserved->uvars->powerState = (0 != state);
	bool allPowerStates __block = service->reserved->uvars->powerState;
	if (!allPowerStates) {
		// any service on?
		fServices->iterateObjects(^bool (OSObject * obj) {
			int         service __unused;       // hide outer defn
			IOService * nextService;
			nextService = (IOService *) obj;
			allPowerStates = nextService->reserved->uvars->powerState;
			// early terminate if true
			return allPowerStates;
		});
	}
	if (kIODKLogPM & gIODKDebug) {
		DKLOG(DKS "::powerStateDidChangeTo(%ld) %d, %d\n", DKN(service), state, allPowerStates, fSystemPowerAck);
	}
	if (!allPowerStates && (pmAck = fSystemPowerAck)) {
		fSystemPowerAck = false;
		fSystemOff      = true;
	}
	IOLockUnlock(fLock);

	if (pmAck) {
		IOServicePH::serverAck(this);
	}

	return kIOPMAckImplied;
}

bool
IOUserServer::checkPMReady()
{
	bool __block ready = true;

	IOLockLock(fLock);
	// Check if any services have not completely joined the PM tree (i.e.
	// addPowerChild has not compeleted).
	fServices->iterateObjects(^bool (OSObject * obj) {
		IOPowerConnection *conn;
		IOService *service = (IOService *) obj;
		IORegistryEntry *parent = service->getParentEntry(gIOPowerPlane);
		if ((conn = OSDynamicCast(IOPowerConnection, parent))) {
		        if (!conn->getReadyFlag()) {
		                ready = false;
		                return true;
			}
		}
		return false;
	});
	IOLockUnlock(fLock);

	return ready;
}

kern_return_t
IOService::JoinPMTree_Impl(void)
{
	if (!reserved->uvars || !reserved->uvars->userServer) {
		return kIOReturnNotReady;
	}
	return reserved->uvars->userServer->serviceJoinPMTree(this);
}

kern_return_t
IOService::SetPowerState_Impl(
	uint32_t powerFlags)
{
	if (kIODKLogPM & gIODKDebug) {
		DKLOG(DKS "::SetPowerState(%d), %d\n", DKN(this), powerFlags, reserved->uvars->willPower);
	}
	if (reserved->uvars
	    && reserved->uvars->userServer
	    && reserved->uvars->willPower) {
		IOReturn ret;
		reserved->uvars->willPower = false;
		ret = reserved->uvars->controllingDriver->setPowerState(reserved->uvars->willPowerState, this);
		if (kIOPMAckImplied == ret) {
			acknowledgeSetPowerState();
		}
		return kIOReturnSuccess;
	}
	return kIOReturnNotReady;
}

kern_return_t
IOService::ChangePowerState_Impl(
	uint32_t powerFlags)
{
	switch (powerFlags) {
	case kIOServicePowerCapabilityOff:
		changePowerStateToPriv(0);
		break;
	case kIOServicePowerCapabilityLow:
		changePowerStateToPriv(1);
		break;
	case kIOServicePowerCapabilityOn:
		changePowerStateToPriv(2);
		break;
	default:
		return kIOReturnBadArgument;
	}

	return kIOReturnSuccess;
}

kern_return_t
IOService::_ClaimSystemWakeEvent_Impl(
	IOService          * device,
	uint64_t             flags,
	const char         * reason,
	OSContainer        * details)
{
	IOPMrootDomain * rootDomain;
	IOOptionBits     pmFlags;

	rootDomain = getPMRootDomain();
	if (!rootDomain) {
		return kIOReturnNotReady;
	}
	if (os_convert_overflow(flags, &pmFlags)) {
		return kIOReturnBadArgument;
	}
	rootDomain->claimSystemWakeEvent(device, pmFlags, reason, details);

	return kIOReturnSuccess;
}

kern_return_t
IOService::Create_Impl(
	IOService * provider,
	const char * propertiesKey,
	IOService ** result)
{
	OSObject       * inst;
	IOService      * service;
	OSString       * str;
	const OSSymbol * sym;
	OSObject       * prop = NULL;
	OSObject       * moduleIdentifier = NULL;
	OSObject       * userServerName = NULL;
	OSDictionary   * properties = NULL;
	OSDictionary   * copyProperties = NULL;
	kern_return_t    ret;

	if (provider != this) {
		return kIOReturnUnsupported;
	}

	ret = kIOReturnUnsupported;
	inst = NULL;
	service = NULL;

	prop = copyProperty(propertiesKey);
	properties = OSDynamicCast(OSDictionary, prop);
	if (!properties) {
		ret = kIOReturnBadArgument;
		goto finish;
	}
	copyProperties = OSDynamicCast(OSDictionary, properties->copyCollection());
	if (!copyProperties) {
		ret = kIOReturnNoMemory;
		goto finish;
	}
	moduleIdentifier = copyProperty(gIOModuleIdentifierKey);
	if (moduleIdentifier) {
		copyProperties->setObject(gIOModuleIdentifierKey, moduleIdentifier);
	}
	userServerName = reserved->uvars->userServer->copyProperty(gIOUserServerNameKey);
	if (userServerName) {
		copyProperties->setObject(gIOUserServerNameKey, userServerName);
	}

	str = OSDynamicCast(OSString, copyProperties->getObject(gIOClassKey));
	if (!str) {
		ret = kIOReturnBadArgument;
		goto finish;
	}
	sym = OSSymbol::withString(str);
	if (sym) {
		inst = OSMetaClass::allocClassWithName(sym);
		service = OSDynamicCast(IOService, inst);
		if (service && service->init(copyProperties) && service->attach(this)) {
			reserved->uvars->userServer->serviceAttach(service, this);
			service->reserved->uvars->started = true;
			ret = kIOReturnSuccess;
			*result = service;
		}
		OSSafeReleaseNULL(sym);
	}

finish:
	OSSafeReleaseNULL(prop);
	OSSafeReleaseNULL(copyProperties);
	OSSafeReleaseNULL(moduleIdentifier);
	OSSafeReleaseNULL(userServerName);
	if (kIOReturnSuccess != ret) {
		OSSafeReleaseNULL(inst);
	}

	return ret;
}

kern_return_t
IOService::Terminate_Impl(
	uint64_t options)
{
	IOUserServer * us;

	if (options) {
		return kIOReturnUnsupported;
	}

	us = (typeof(us))thread_iokit_tls_get(0);
	if (us && (!reserved->uvars
	    || (reserved->uvars->userServer != us))) {
		return kIOReturnNotPermitted;
	}
	terminate(kIOServiceTerminateNeedWillTerminate);

	return kIOReturnSuccess;
}

kern_return_t
IOService::NewUserClient_Impl(
	uint32_t type,
	IOUserClient ** userClient)
{
	return kIOReturnError;
}

kern_return_t
IOService::_NewUserClient_Impl(
	uint32_t type,
	OSDictionary * entitlements,
	IOUserClient ** userClient)
{
	return kIOReturnError;
}

kern_return_t
IOService::SearchProperty_Impl(
	const char * name,
	const char * plane,
	uint64_t options,
	OSContainer ** property)
{
	OSObject   * object __block;
	IOService  * provider;
	IOOptionBits regOptions;

	if (kIOServiceSearchPropertyParents & options) {
		regOptions = kIORegistryIterateParents | kIORegistryIterateRecursively;
	} else {
		regOptions = 0;
	}

	object = copyProperty(name, IORegistryEntry::getPlane(plane), regOptions);

	if (NULL == object) {
		for (provider = this; provider; provider = provider->getProvider()) {
			provider->runPropertyActionBlock(^IOReturn (void) {
				OSDictionary * userProps;
				object = provider->getProperty(name);
				if (!object
				&& (userProps = OSDynamicCast(OSDictionary, provider->getProperty(gIOUserServicePropertiesKey)))) {
				        object = userProps->getObject(name);
				}
				if (object) {
				        object->retain();
				}
				return kIOReturnSuccess;
			});
			if (object || !(kIORegistryIterateParents & options)) {
				break;
			}
		}
	}

	*property = object;

	return object ? kIOReturnSuccess : kIOReturnNotFound;
}

kern_return_t
IOService::StringFromReturn_Impl(
	IOReturn retval,
	OSString ** str)
{
	OSString *obj = OSString::withCString(stringFromReturn(retval));
	*str = obj;
	return obj ? kIOReturnSuccess : kIOReturnError;
}

#if PRIVATE_WIFI_ONLY
const char *
IOService::StringFromReturn(
	IOReturn retval)
{
	return stringFromReturn(retval);
}
#endif /* PRIVATE_WIFI_ONLY */

kern_return_t
IOService::CopyProviderProperties_Impl(
	OSArray * propertyKeys,
	OSArray ** properties)
{
	IOReturn    ret;
	OSArray   * result;
	IOService * provider;

	result = OSArray::withCapacity(8);
	if (!result) {
		return kIOReturnNoMemory;
	}

	ret = kIOReturnSuccess;
	for (provider = this; provider; provider = provider->getProvider()) {
		OSObject     * obj;
		OSDictionary * props;

		obj = provider->copyProperty(gIOSupportedPropertiesKey);
		props = OSDynamicCast(OSDictionary, obj);
		if (!props) {
			OSSafeReleaseNULL(obj);
			props = provider->dictionaryWithProperties();
		}
		if (!props) {
			ret = kIOReturnNoMemory;
			break;
		}

		bool __block addClass = true;
		if (propertyKeys) {
			OSDictionary * retProps;
			retProps = OSDictionary::withCapacity(4);
			addClass = false;
			if (!retProps) {
				ret = kIOReturnNoMemory;
				OSSafeReleaseNULL(props);
				break;
			}
			propertyKeys->iterateObjects(^bool (OSObject * _key) {
				OSString * key = OSDynamicCast(OSString, _key);
				if (gIOClassKey->isEqualTo(key)) {
				        addClass = true;
				        return false;
				}
				retProps->setObject(key, props->getObject(key));
				return false;
			});
			OSSafeReleaseNULL(props);
			props = retProps;
		}
		if (addClass) {
			OSArray * classes = OSArray::withCapacity(8);
			if (!classes) {
				OSSafeReleaseNULL(props);
				ret = kIOReturnNoMemory;
				break;
			}
			for (const OSMetaClass * meta = provider->getMetaClass(); meta; meta = meta->getSuperClass()) {
				classes->setObject(meta->getClassNameSymbol());
			}
			props->setObject(gIOClassKey, classes);
			OSSafeReleaseNULL(classes);
		}
		bool ok = result->setObject(props);
		props->release();
		if (!ok) {
			ret = kIOReturnNoMemory;
			break;
		}
	}
	if (kIOReturnSuccess != ret) {
		OSSafeReleaseNULL(result);
	}
	*properties = result;
	return ret;
}

IOReturn
IOService::AdjustBusy_Impl(int32_t delta)
{
	adjustBusy(delta);
	return kIOReturnSuccess;
}

IOReturn
IOService::GetBusyState_Impl(uint32_t *busyState)
{
	*busyState = getBusyState();
	return kIOReturnSuccess;
}

void
IOUserServer::systemPower(bool powerOff, bool hibernate)
{
	OSArray * services;
	{
		OSDictionary * sleepDescription;
		OSObject     * prop;

		sleepDescription = OSDictionary::withCapacity(4);
		if (sleepDescription) {
			prop = getPMRootDomain()->copyProperty(kRootDomainSleepReasonKey);
			if (prop) {
				sleepDescription->setObject(gIOSystemStateSleepDescriptionReasonKey, prop);
				OSSafeReleaseNULL(prop);
			}
			prop = getPMRootDomain()->copyProperty(kIOHibernateStateKey);
			if (prop) {
				sleepDescription->setObject(gIOSystemStateSleepDescriptionHibernateStateKey, prop);
				OSSafeReleaseNULL(prop);
			}
			if (hibernate) {
				uint32_t correctHibernateState = kIOSystemStateSleepDescriptionHibernateStateHibernating;
				OSData *correctHibernateStateData = OSData::withValue(correctHibernateState);
				assert(correctHibernateStateData != NULL);
				sleepDescription->setObject(gIOSystemStateSleepDescriptionHibernateStateKey, correctHibernateStateData);
				OSSafeReleaseNULL(correctHibernateStateData);
			}
			getSystemStateNotificationService()->StateNotificationItemSet(gIOSystemStateSleepDescriptionKey, sleepDescription);
			OSSafeReleaseNULL(sleepDescription);
		}
	}

	IOLockLock(fLock);

	services = OSArray::withArray(fServices);

	bool allPowerStates __block = 0;
	// any service on?
	fServices->iterateObjects(^bool (OSObject * obj) {
		int         service __unused;       // hide outer defn
		IOService * nextService;
		nextService = (IOService *) obj;
		allPowerStates = nextService->reserved->uvars->powerState;
		// early terminate if true
		return allPowerStates;
	});

	if (kIODKLogPM & gIODKDebug) {
		DKLOG("%s::powerOff(%d) %d\n", getName(), powerOff, allPowerStates);
	}

	if (powerOff) {
		fSystemPowerAck = allPowerStates;
		if (!fSystemPowerAck) {
			fSystemOff = true;
		}
		IOLockUnlock(fLock);

		if (!fSystemPowerAck) {
			IOServicePH::serverAck(this);
		} else {
			if (services) {
				services->iterateObjects(^bool (OSObject * obj) {
					int         service __unused;       // hide outer defn
					IOService * nextService;
					nextService = (IOService *) obj;
					if (kIODKLogPM & gIODKDebug) {
					        DKLOG("changePowerStateWithOverrideTo(" DKS ", %d)\n", DKN(nextService), 0);
					}
					nextService->reserved->uvars->powerOverride = nextService->reserved->uvars->userServerPM ? kUserServerMaxPowerState : nextService->getPowerState();
					nextService->changePowerStateWithOverrideTo(0, 0);
					return false;
				});
			}
		}
	} else {
		fSystemOff = false;
		IOLockUnlock(fLock);
		if (services) {
			services->iterateObjects(^bool (OSObject * obj) {
				int         service __unused;       // hide outer defn
				IOService * nextService;
				nextService = (IOService *) obj;
				if (-1U != nextService->reserved->uvars->powerOverride) {
				        if (kIODKLogPM & gIODKDebug) {
				                DKLOG("%schangePowerStateWithOverrideTo(" DKS ", %d)\n", nextService->reserved->uvars->resetPowerOnWake ? "!" : "", DKN(nextService), nextService->reserved->uvars->powerOverride);
					}
				        if (!nextService->reserved->uvars->resetPowerOnWake) {
				                nextService->changePowerStateWithOverrideTo(nextService->reserved->uvars->powerOverride, 0);
					}
				        nextService->reserved->uvars->powerOverride = -1U;
				}
				return false;
			});
		}
	}
	OSSafeReleaseNULL(services);
}


void
IOUserServer::systemHalt(int howto)
{
	OSArray * services;

	if (true || (kIODKLogPM & gIODKDebug)) {
		DKLOG("%s::systemHalt()\n", getName());
	}

	{
		OSDictionary * haltDescription;
		OSNumber     * state;
		uint64_t       haltStateFlags;

		haltDescription = OSDictionary::withCapacity(4);
		if (haltDescription) {
			haltStateFlags = 0;
			if (RB_HALT & howto) {
				haltStateFlags |= kIOServiceHaltStatePowerOff;
			} else {
				haltStateFlags |= kIOServiceHaltStateRestart;
			}
			state = OSNumber::withNumber(haltStateFlags, 64);
			haltDescription->setObject(gIOSystemStateHaltDescriptionHaltStateKey, state);
			getSystemStateNotificationService()->StateNotificationItemSet(gIOSystemStateHaltDescriptionKey, haltDescription);

			OSSafeReleaseNULL(state);
			OSSafeReleaseNULL(haltDescription);
		}
	}

	IOLockLock(fLock);
	services = OSArray::withArray(fServices);
	IOLockUnlock(fLock);

	if (services) {
		services->iterateObjects(^bool (OSObject * obj) {
			int         service __unused;       // hide outer defn
			IOService  * nextService;
			IOService  * provider;
			IOOptionBits terminateOptions;
			bool         root;

			nextService = (IOService *) obj;
			provider = nextService->getProvider();
			if (!provider) {
			        DKLOG("stale service " DKS " found, skipping termination\n", DKN(nextService));
			        return false;
			}
			root = (NULL == provider->getProperty(gIOUserServerNameKey, gIOServicePlane));
			if (true || (kIODKLogPM & gIODKDebug)) {
			        DKLOG("%d: terminate(" DKS ")\n", root, DKN(nextService));
			}
			if (!root) {
			        return false;
			}
			terminateOptions = kIOServiceRequired | kIOServiceTerminateNeedWillTerminate;
			if (!nextService->terminate(terminateOptions)) {
			        IOLog("failed to terminate service %s-0x%llx\n", nextService->getName(), nextService->getRegistryEntryID());
			}
			return false;
		});
	}
	OSSafeReleaseNULL(services);
}

void
IOUserServer::powerSourceChanged(bool acAttached)
{
	OSDictionary * powerSourceDescription;

	powerSourceDescription = OSDictionary::withCapacity(4);
	if (!powerSourceDescription) {
		return;
	}
	powerSourceDescription->setObject(gIOSystemStatePowerSourceDescriptionACAttachedKey, acAttached ? kOSBooleanTrue : kOSBooleanFalse);
	getSystemStateNotificationService()->StateNotificationItemSet(gIOSystemStatePowerSourceDescriptionKey, powerSourceDescription);

	OSSafeReleaseNULL(powerSourceDescription);
}

IOReturn
IOUserServer::serviceStarted(IOService * service, IOService * provider, bool result)
{
	IOReturn    ret;

	DKLOG(DKS "::start(" DKS ") %s\n", DKN(service), DKN(provider), result ? "ok" : "fail");

	if (!result) {
		ret = kIOReturnSuccess;
		return ret;
	}

	ret = serviceJoinPMTree(service);

	service->reserved->uvars->started = true;

	if (service->reserved->uvars->deferredRegisterService) {
		service->registerService(kIOServiceAsynchronous | kIOServiceDextRequirePowerForMatching);
		service->reserved->uvars->deferredRegisterService = false;
	}

	return kIOReturnSuccess;
}


IOReturn
IOUserServer::serviceOpen(IOService * provider, IOService * client)
{
	OSObjectUserVars * uvars;
	IOReturn ret;

	IOLockLock(client->reserved->uvars->uvarsLock);
	uvars = client->reserved->uvars;
	if (uvars->willTerminate || uvars->stopped) {
		DKLOG(DKS "- " DKS " blocked attempt to open " DKS "\n", DKN(this), DKN(client), DKN(provider));
		ret = kIOReturnBadArgument;
	} else {
		if (!uvars->openProviders) {
			uvars->openProviders = OSArray::withObjects((const OSObject **) &provider, 1);
		} else if (-1U == uvars->openProviders->getNextIndexOfObject(provider, 0)) {
			uvars->openProviders->setObject(provider);
		}
		ret = kIOReturnSuccess;
	}

	IOLockUnlock(client->reserved->uvars->uvarsLock);

	return ret;
}

IOReturn
IOUserServer::serviceClose(IOService * provider, IOService * client)
{
	OSObjectUserVars * uvars;
	unsigned int       idx;
	IOReturn           ret;

	IOLockLock(client->reserved->uvars->uvarsLock);
	uvars = client->reserved->uvars;
	if (!uvars->openProviders) {
		ret = kIOReturnNotOpen;
		goto finish;
	}
	idx = uvars->openProviders->getNextIndexOfObject(provider, 0);
	if (-1U == idx) {
		ret = kIOReturnNotOpen;
		goto finish;
	}
	uvars->openProviders->removeObject(idx);
	if (!uvars->openProviders->getCount()) {
		OSSafeReleaseNULL(uvars->openProviders);
	}

	ret = kIOReturnSuccess;

finish:
	IOLockUnlock(client->reserved->uvars->uvarsLock);

	return ret;
}


IOReturn
IOUserServer::serviceStop(IOService * service, IOService *)
{
	IOReturn           ret;
	uint32_t           idx;
	bool               pmAck;
	OSObjectUserVars * uvars;
	pmAck = false;
	IOLockLock(fLock);
	idx = fServices->getNextIndexOfObject(service, 0);
	if (-1U != idx) {
		fServices->removeObject(idx);

		// Remove the service from IOAssociatedServices
		OSObject * serviceArrayObj = copyProperty(gIOAssociatedServicesKey);
		OSArray * serviceArray = OSDynamicCast(OSArray, serviceArrayObj);
		assert(serviceArray != NULL);

		serviceArray = OSDynamicCast(OSArray, serviceArray->copyCollection());
		assert(serviceArray != NULL);

		// Index should be the same as it was in fServices
		OSNumber * __assert_only registryEntryID = OSDynamicCast(OSNumber, serviceArray->getObject(idx));
		assert(registryEntryID);

		// ensure it is the right service
		assert(registryEntryID->unsigned64BitValue() == service->getRegistryEntryID());
		serviceArray->removeObject(idx);

		setProperty(gIOAssociatedServicesKey, serviceArray);
		OSSafeReleaseNULL(serviceArray);
		OSSafeReleaseNULL(serviceArrayObj);

		uvars = service->reserved->uvars;
		uvars->stopped = true;
		uvars->powerState = 0;

		bool allPowerStates __block = 0;
		// any service on?
		fServices->iterateObjects(^bool (OSObject * obj) {
			int         service __unused;       // hide outer defn
			IOService * nextService;
			nextService = (IOService *) obj;
			allPowerStates = nextService->reserved->uvars->powerState;
			// early terminate if true
			return allPowerStates;
		});

		if (!allPowerStates && (pmAck = fSystemPowerAck)) {
			fSystemPowerAck = false;
			fSystemOff      = true;
		}
	}
	IOLockUnlock(fLock);
	if (pmAck) {
		IOServicePH::serverAck(this);
	}

	if (-1U == idx) {
		return kIOReturnSuccess;
	}

	(void) service->deRegisterInterestedDriver(this);
	if (uvars->userServerPM) {
		service->PMstop();
	}

	ret = kIOReturnSuccess;
	return ret;
}

void
IOUserServer::serviceFree(IOService * service)
{
	OSObjectUserVars * uvars;
	uint32_t idx, queueAlloc;
	IODispatchQueue ** unboundedQueueArray = NULL;

	uvars = service->reserved->uvars;
	if (!uvars) {
		return;
	}
	if (uvars->queueArray && uvars->userMeta) {
		queueAlloc = 1;
		if (uvars->userMeta->queueNames) {
			queueAlloc += uvars->userMeta->queueNames->count;
		}
		for (idx = 0; idx < queueAlloc; idx++) {
			OSSafeReleaseNULL(uvars->queueArray[idx]);
		}
		unboundedQueueArray = uvars->queueArray.data();
		IOSafeDeleteNULL(unboundedQueueArray, IODispatchQueue *, queueAlloc);
		uvars->queueArray = OSBoundedArrayRef<IODispatchQueue *>();
	}
	OSSafeReleaseNULL(uvars->userServer);
	IOLockFree(uvars->uvarsLock);
	IOFreeType(service->reserved->uvars, OSObjectUserVars);
}

void
IOUserServer::serviceWillTerminate(IOService * client, IOService * provider, IOOptionBits options)
{
	IOReturn ret;
	bool     willTerminate;

	willTerminate = false;
	IOLockLock(client->reserved->uvars->uvarsLock);
	if (!client->reserved->uvars->serverDied
	    && !client->reserved->uvars->willTerminate) {
		client->reserved->uvars->willTerminate = true;
		willTerminate = true;
	}
	IOLockUnlock(client->reserved->uvars->uvarsLock);

	if (willTerminate) {
		if (provider->isInactive() || IOServicePH::serverSlept()) {
			client->Stop_async(provider);
			ret = kIOReturnOffline;
		} else {
			ret = client->Stop(provider);
		}
		if (kIOReturnSuccess != ret) {
			IOUserServer::serviceDidStop(client, provider);
			ret = kIOReturnSuccess;
		}
	}
}

void
IOUserServer::serviceDidTerminate(IOService * client, IOService * provider, IOOptionBits options, bool * defer)
{
	IOLockLock(client->reserved->uvars->uvarsLock);
	client->reserved->uvars->didTerminate = true;
	if (!client->reserved->uvars->serverDied
	    && !client->reserved->uvars->stopped) {
		*defer = true;
	}
	IOLockUnlock(client->reserved->uvars->uvarsLock);
}

void
IOUserServer::serviceDidStop(IOService * client, IOService * provider)
{
	bool complete;
	OSArray * closeArray;

	complete = false;
	closeArray = NULL;

	IOLockLock(client->reserved->uvars->uvarsLock);
	if (client->reserved->uvars
	    && client->reserved->uvars->willTerminate
	    && !client->reserved->uvars->stopped) {
		client->reserved->uvars->stopped = true;
		complete = client->reserved->uvars->didTerminate;
	}

	if (client->reserved->uvars) {
		closeArray = client->reserved->uvars->openProviders;
		client->reserved->uvars->openProviders = NULL;
	}
	IOLockUnlock(client->reserved->uvars->uvarsLock);

	if (closeArray) {
		closeArray->iterateObjects(^bool (OSObject * obj) {
			IOService * toClose;
			toClose = OSDynamicCast(IOService, obj);
			if (toClose) {
			        DKLOG(DKS ":force close (" DKS ")\n", DKN(client), DKN(toClose));
			        toClose->close(client);
			}
			return false;
		});
		closeArray->release();
	}

	if (complete) {
		bool defer = false;
		client->didTerminate(provider, 0, &defer);
	}
}

kern_return_t
IOService::ClientCrashed_Impl(
	IOService * client,
	uint64_t    options)
{
	return kIOReturnUnsupported;
}

kern_return_t
IOService::Stop_Impl(
	IOService * provider)
{
	IOUserServer::serviceDidStop(this, provider);

	return kIOReturnSuccess;
}

void
IOService::Stop_async_Impl(
	IOService * provider)
{
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#undef super
#define super IOUserClient

OSDefineMetaClassAndStructors(IOUserUserClient, IOUserClient)

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

bool
IOUserUserClient::init(OSDictionary * properties)
{
	if (!super::init(properties)) {
		return false;
	}

	fWorkGroups = OSDictionary::withCapacity(0);
	if (fWorkGroups == NULL) {
		return false;
	}

	fEventLinks = OSDictionary::withCapacity(0);
	if (fEventLinks == NULL) {
		return false;
	}

	fLock = IOLockAlloc();

	return true;
}

void
IOUserUserClient::free()
{
	OSSafeReleaseNULL(fWorkGroups);
	OSSafeReleaseNULL(fEventLinks);
	if (fLock) {
		IOLockFree(fLock);
	}

	super::free();
}

IOReturn
IOUserUserClient::setTask(task_t task)
{
	task_reference(task);
	fTask = task;

	return kIOReturnSuccess;
}

void
IOUserUserClient::stop(IOService * provider)
{
	if (fTask) {
		task_deallocate(fTask);
		fTask = NULL;
	}
	super::stop(provider);
}

IOReturn
IOUserUserClient::clientClose(void)
{
	terminate(kIOServiceTerminateNeedWillTerminate);
	return kIOReturnSuccess;
}

IOReturn
IOUserUserClient::setProperties(OSObject * properties)
{
	IOReturn ret = kIOReturnUnsupported;
	return ret;
}

// p1 - name of object
// p2 - length of object name
// p3 - mach port name

kern_return_t
IOUserUserClient::eventlinkConfigurationTrap(void * p1, void * p2, void * p3, void * p4, void * p5, void * p6)
{
	user_addr_t userObjectName = (user_addr_t)p1;
	mach_port_name_t portName = (mach_port_name_t)(uintptr_t)p3;
	mach_port_t port = MACH_PORT_NULL;
	ipc_kobject_type_t portType;
	char eventlinkName[kIOEventLinkMaxNameLength + 1] = {0};
	size_t eventLinkNameLen;
	OSString * eventlinkNameStr = NULL; // must release
	IOEventLink * eventLink = NULL; // do not release
	kern_return_t ret;

	ret = copyinstr(userObjectName, &eventlinkName[0], sizeof(eventlinkName), &eventLinkNameLen);
	if (ret != kIOReturnSuccess) {
		goto finish;
	}

	// ensure string length matches trap argument
	if (eventLinkNameLen != (size_t)p2 + 1) {
		ret = kIOReturnBadArgument;
		goto finish;
	}

	eventlinkNameStr = OSString::withCStringNoCopy(eventlinkName);
	if (eventlinkNameStr == NULL) {
		ret = kIOReturnNoMemory;
		goto finish;
	}

	IOLockLock(fLock);
	eventLink = OSDynamicCast(IOEventLink, fEventLinks->getObject(eventlinkNameStr));
	if (eventLink) {
		eventLink->retain();
	}
	IOLockUnlock(fLock);

	if (eventLink == NULL) {
		ret = kIOReturnNotFound;
		goto finish;
	}

	port = iokit_lookup_raw_current_task(portName, &portType);

	if (port == NULL) {
		ret = kIOReturnNotFound;
		goto finish;
	}

	if (portType != IKOT_EVENTLINK) {
		ret = kIOReturnBadArgument;
		goto finish;
	}

	ret = eventLink->SetEventlinkPort(port);
	if (ret != kIOReturnSuccess) {
		if (kIODKLogSetup & gIODKDebug) {
			DKLOG(DKS " %s SetEventlinkPort() returned %x\n", DKN(this), eventlinkNameStr->getCStringNoCopy(), ret);
		}
		goto finish;
	}

finish:
	if (port != NULL) {
		iokit_release_port_send(port);
	}

	OSSafeReleaseNULL(eventlinkNameStr);
	OSSafeReleaseNULL(eventLink);

	return ret;
}

kern_return_t
IOUserUserClient::workgroupConfigurationTrap(void * p1, void * p2, void * p3, void * p4, void * p5, void * p6)
{
	user_addr_t userObjectName = (user_addr_t)p1;
	mach_port_name_t portName = (mach_port_name_t)(uintptr_t)p3;
	mach_port_t port = MACH_PORT_NULL;
	ipc_kobject_type_t portType;
	char workgroupName[kIOWorkGroupMaxNameLength + 1] = {0};
	size_t workgroupNameLen;
	OSString * workgroupNameStr = NULL; // must release
	IOWorkGroup * workgroup = NULL; // do not release
	kern_return_t ret;

	ret = copyinstr(userObjectName, &workgroupName[0], sizeof(workgroupName), &workgroupNameLen);
	if (ret != kIOReturnSuccess) {
		goto finish;
	}

	// ensure string length matches trap argument
	if (workgroupNameLen != (size_t)p2 + 1) {
		ret = kIOReturnBadArgument;
		goto finish;
	}

	workgroupNameStr = OSString::withCStringNoCopy(workgroupName);
	if (workgroupNameStr == NULL) {
		ret = kIOReturnNoMemory;
		goto finish;
	}

	IOLockLock(fLock);
	workgroup = OSDynamicCast(IOWorkGroup, fWorkGroups->getObject(workgroupNameStr));
	if (workgroup) {
		workgroup->retain();
	}
	IOLockUnlock(fLock);

	if (workgroup == NULL) {
		ret = kIOReturnNotFound;
		goto finish;
	}

	port = iokit_lookup_raw_current_task(portName, &portType);

	if (port == NULL) {
		ret = kIOReturnNotFound;
		goto finish;
	}

	if (portType != IKOT_WORK_INTERVAL) {
		ret = kIOReturnBadArgument;
		goto finish;
	}

	ret = workgroup->SetWorkGroupPort(port);
	if (ret != kIOReturnSuccess) {
		if (kIODKLogSetup & gIODKDebug) {
			DKLOG(DKS " %s SetWorkGroupPort() returned %x\n", DKN(this), workgroupNameStr->getCStringNoCopy(), ret);
		}
		goto finish;
	}

finish:

	if (port != NULL) {
		iokit_release_port_send(port);
	}

	OSSafeReleaseNULL(workgroupNameStr);
	OSSafeReleaseNULL(workgroup);

	return ret;
}

IOExternalTrap *
IOUserUserClient::getTargetAndTrapForIndex( IOService **targetP, UInt32 index )
{
	static const OSBoundedArray<IOExternalTrap, 2> trapTemplate = {{
									       { NULL, (IOTrap) & IOUserUserClient::eventlinkConfigurationTrap},
									       { NULL, (IOTrap) & IOUserUserClient::workgroupConfigurationTrap},
								       }};
	if (index >= trapTemplate.size()) {
		return NULL;
	}
	*targetP = this;
	return (IOExternalTrap *)&trapTemplate[index];
}

kern_return_t
IOUserClient::CopyClientEntitlements_Impl(OSDictionary ** entitlements)
{
	return kIOReturnUnsupported;
};

struct IOUserUserClientActionRef {
	OSAsyncReference64 asyncRef;
};

void
IOUserClient::KernelCompletion_Impl(
	OSAction * action,
	IOReturn status,
	const unsigned long long * asyncData,
	uint32_t asyncDataCount)
{
	IOUserUserClientActionRef * ref;

	ref = (typeof(ref))action->GetReference();

	IOUserClient::sendAsyncResult64(ref->asyncRef, status, (io_user_reference_t *) asyncData, asyncDataCount);
}

kern_return_t
IOUserClient::_ExternalMethod_Impl(
	uint64_t selector,
	const unsigned long long * scalarInput,
	uint32_t scalarInputCount,
	OSData * structureInput,
	IOMemoryDescriptor * structureInputDescriptor,
	unsigned long long * scalarOutput,
	uint32_t * scalarOutputCount,
	uint64_t structureOutputMaximumSize,
	OSData ** structureOutput,
	IOMemoryDescriptor * structureOutputDescriptor,
	OSAction * completion)
{
	return kIOReturnUnsupported;
}

IOReturn
IOUserUserClient::clientMemoryForType(UInt32 type,
    IOOptionBits * koptions,
    IOMemoryDescriptor ** kmemory)
{
	IOReturn             kr;
	uint64_t             options;
	IOMemoryDescriptor * memory;

	kr = CopyClientMemoryForType(type, &options, &memory);

	*koptions = 0;
	*kmemory  = NULL;
	if (kIOReturnSuccess != kr) {
		return kr;
	}

	if (kIOUserClientMemoryReadOnly & options) {
		*koptions |= kIOMapReadOnly;
	}
	*kmemory = memory;

	return kr;
}

IOReturn
IOUserUserClient::externalMethod(uint32_t selector, IOExternalMethodArguments * args,
    IOExternalMethodDispatch * dispatch, OSObject * target, void * reference)
{
	IOReturn   kr;
	OSData   * structureInput;
	OSData   * structureOutput;
	size_t     copylen;
	uint64_t   structureOutputSize;
	OSAction                  * action;
	IOUserUserClientActionRef * ref;
	mach_port_t wake_port = MACH_PORT_NULL;

	kr             = kIOReturnUnsupported;
	structureInput = NULL;
	action         = NULL;
	ref            = NULL;

	if (args->structureInputSize) {
		structureInput = OSData::withBytesNoCopy((void *) args->structureInput, args->structureInputSize);
	}

	if (MACH_PORT_NULL != args->asyncWakePort) {
		// this retain is for the OSAction to release
		wake_port = ipc_port_make_send_mqueue(args->asyncWakePort);
		kr = CreateActionKernelCompletion(sizeof(IOUserUserClientActionRef), &action);
		assert(KERN_SUCCESS == kr);
		ref = (typeof(ref))action->GetReference();
		bcopy(args->asyncReference, &ref->asyncRef[0], args->asyncReferenceCount * sizeof(ref->asyncRef[0]));
		kr = action->SetAbortedHandler(^(void) {
			IOUserUserClientActionRef * ref;
			IOReturn ret;

			ref = (typeof(ref))action->GetReference();
			ret = releaseAsyncReference64(ref->asyncRef);
			assert(kIOReturnSuccess == ret);
			bzero(&ref->asyncRef[0], sizeof(ref->asyncRef));
		});
		assert(KERN_SUCCESS == kr);
	}

	if (args->structureVariableOutputData) {
		structureOutputSize = kIOUserClientVariableStructureSize;
	} else if (args->structureOutputDescriptor) {
		structureOutputSize = args->structureOutputDescriptor->getLength();
	} else {
		structureOutputSize = args->structureOutputSize;
	}

	kr = _ExternalMethod(selector, &args->scalarInput[0], args->scalarInputCount,
	    structureInput, args->structureInputDescriptor,
	    args->scalarOutput, &args->scalarOutputCount,
	    structureOutputSize, &structureOutput, args->structureOutputDescriptor,
	    action);

	OSSafeReleaseNULL(structureInput);
	OSSafeReleaseNULL(action);

	if (kr == kIOReturnSuccess && structureOutput) {
		if (args->structureVariableOutputData) {
			*args->structureVariableOutputData = structureOutput;
		} else {
			copylen = structureOutput->getLength();
			if (copylen > args->structureOutputSize) {
				kr = kIOReturnBadArgument;
			} else {
				bcopy((const void *) structureOutput->getBytesNoCopy(), args->structureOutput, copylen);
				args->structureOutputSize = (uint32_t) copylen;
			}
			OSSafeReleaseNULL(structureOutput);
		}
	}

	if (kIOReturnSuccess != kr) {
		// mig will destroy any async port
		return kr;
	}

	// We must never return error after this point in order to preserve MIG ownership semantics
	assert(kr == kIOReturnSuccess);
	if (MACH_PORT_NULL != wake_port) {
		// this release is for the mig created send right
		iokit_release_port_send(wake_port);
	}

	return kr;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

extern IORecursiveLock               * gDriverKitLaunchLock;
extern OSSet                         * gDriverKitLaunches;

_IOUserServerCheckInCancellationHandler *
IOUserServerCheckInToken::setCancellationHandler(IOUserServerCheckInCancellationHandler handler,
    void* handlerArgs)
{
	_IOUserServerCheckInCancellationHandler * handlerObj = _IOUserServerCheckInCancellationHandler::withHandler(handler, handlerArgs);
	if (!handlerObj) {
		goto finish;
	}

	IORecursiveLockLock(gDriverKitLaunchLock);

	if (fState == kIOUserServerCheckInCanceled) {
		// Send cancel notification if we set the handler after this was canceled
		handlerObj->call(this);
	} else if (fState == kIOUserServerCheckInPending) {
		fHandlers->setObject(handlerObj);
	}

	IORecursiveLockUnlock(gDriverKitLaunchLock);

finish:
	return handlerObj;
}

void
IOUserServerCheckInToken::removeCancellationHandler(_IOUserServerCheckInCancellationHandler * handler)
{
	IORecursiveLockLock(gDriverKitLaunchLock);

	fHandlers->removeObject(handler);

	IORecursiveLockUnlock(gDriverKitLaunchLock);
}

void
IOUserServerCheckInToken::cancel()
{
	IORecursiveLockLock(gDriverKitLaunchLock);

	if (fState == kIOUserServerCheckInPending) {
		fState = kIOUserServerCheckInCanceled;
		if (gDriverKitLaunches != NULL) {
			// Remove pending launch from list, if we have not shut down yet.
			gDriverKitLaunches->removeObject(this);
		}

		fHandlers->iterateObjects(^bool (OSObject * obj){
			_IOUserServerCheckInCancellationHandler * handlerObj = OSDynamicCast(_IOUserServerCheckInCancellationHandler, obj);
			if (handlerObj) {
			        handlerObj->call(this);
			}
			return false;
		});
		fHandlers->flushCollection();
	}

	IORecursiveLockUnlock(gDriverKitLaunchLock);
}

IOReturn
IOUserServerCheckInToken::complete()
{
	IOReturn ret;
	IORecursiveLockLock(gDriverKitLaunchLock);

	if (fState == kIOUserServerCheckInCanceled) {
		ret = kIOReturnError;
	} else {
		ret = kIOReturnSuccess;
	}

	if (fState == kIOUserServerCheckInPending && --fPendingCount == 0) {
		fState = kIOUserServerCheckInComplete;
		if (gDriverKitLaunches != NULL) {
			// Remove pending launch from list, if we have not shut down yet.
			gDriverKitLaunches->removeObject(this);
		}

		// No need to hold on to the cancellation handlers
		fHandlers->flushCollection();
	}

	IORecursiveLockUnlock(gDriverKitLaunchLock);
	return ret;
}

bool
IOUserServerCheckInToken::init(const OSSymbol * serverName, OSNumber * serverTag, OSKext *driverKext, OSData *serverDUI)
{
	if (!OSObject::init()) {
		return false;
	}

	if (!serverName) {
		return false;
	}
	fServerName = serverName;
	fServerName->retain();

	if (!serverTag) {
		return false;
	}
	fServerTag = serverTag;
	fServerTag->retain();

	fHandlers = OSSet::withCapacity(0);
	if (!fHandlers) {
		return false;
	}

	fState = kIOUserServerCheckInPending;
	fPendingCount = 1;

	fKextBundleID = NULL;
	fNeedDextDec = false;

	fExecutableName = NULL;

	if (driverKext) {
		fExecutableName = OSDynamicCast(OSSymbol, driverKext->getBundleExecutable());

		if (fExecutableName) {
			fExecutableName->retain();
		}

		/*
		 * We need to keep track of how many dexts we have started.
		 * For every new dext we are going to create a new token, and
		 * we consider the token creation as the initial step to
		 * create a dext as it is the data structure that will back up
		 * the userspace dance to start a dext.
		 * We later have to decrement only once per token.
		 * If no error occurs we consider the finalize() call on IOUserServer
		 * as the moment in which we do not consider the dext "alive" anymore;
		 * however in case of errors we will still need to decrement the count
		 * otherwise upgrades of the dext will never make progress.
		 */
		if (OSKext::incrementDextLaunchCount(driverKext, serverDUI)) {
			/*
			 * If fKext holds a pointer,
			 * it is the indication that a decrements needs
			 * to be called.
			 */
			fNeedDextDec = true;
			fKextBundleID = OSDynamicCast(OSString, driverKext->getIdentifier());
			fKextBundleID->retain();
		} else {
			return false;
		}
	}

	return true;
}

/*
 * Returns if the dext can be re-used
 * for matching.
 */
bool
IOUserServerCheckInToken::dextTerminate(void)
{
	bool ret = true;

	if (fNeedDextDec == true) {
		/*
		 * We can decrement DextLaunchCount only
		 * once per token.
		 */
		ret = !(OSKext::decrementDextLaunchCount(fKextBundleID));
		fNeedDextDec = false;
	}

	return ret;
}

void
IOUserServerCheckInToken::free()
{
	OSSafeReleaseNULL(fServerName);
	OSSafeReleaseNULL(fServerTag);
	OSSafeReleaseNULL(fExecutableName);
	OSSafeReleaseNULL(fHandlers);
	if (fKextBundleID != NULL) {
		dextTerminate();
		OSSafeReleaseNULL(fKextBundleID);
	}

	OSObject::free();
}

const OSSymbol *
IOUserServerCheckInToken::copyServerName() const
{
	fServerName->retain();
	return fServerName;
}

OSNumber *
IOUserServerCheckInToken::copyServerTag() const
{
	fServerTag->retain();
	return fServerTag;
}

IOUserServer *
IOUserServer::launchUserServer(OSString * bundleID, const OSSymbol * serverName, OSNumber * serverTag, bool reuseIfExists, IOUserServerCheckInToken ** resultToken, OSData *serverDUI)
{
	IOUserServer *me = NULL;
	IOUserServerCheckInToken * token = NULL;
	OSDictionary * matching = NULL;  // must release
	OSKext * driverKext = NULL; // must release
	OSDextStatistics * driverStatistics = NULL; // must release
	bool reslide = false;

	/* TODO: Check we are looking for same dextID
	 * and if it is not the same
	 * restart the matching process.
	 */
	driverKext = OSKext::lookupDextWithIdentifier(bundleID, serverDUI);
	if (driverKext != NULL) {
		driverStatistics = driverKext->copyDextStatistics();
		if (driverStatistics == NULL) {
			panic("Kext %s was not a DriverKit OSKext", bundleID->getCStringNoCopy());
		}
		IOLog("Driver %s has crashed %zu time(s)\n", bundleID->getCStringNoCopy(), driverStatistics->getCrashCount());
		reslide = driverStatistics->getCrashCount() > 0;
	} else {
		DKLOG("Could not find OSKext for %s\n", bundleID->getCStringNoCopy());
		*resultToken = NULL;
		return NULL;
	}

	IORecursiveLockLock(gDriverKitLaunchLock);

	if (gDriverKitLaunches == NULL) {
		// About to shut down, don't launch anything
		goto finish;
	}

	if (reuseIfExists) {
		const char * serverNameCStr;
		const char * bundleIDCStr;
		const char * endOrgCStr;

		serverNameCStr = serverName->getCStringNoCopy();
		bundleIDCStr = bundleID->getCStringNoCopy();
		(endOrgCStr = strchr(bundleIDCStr, '.')) && (endOrgCStr = strchr(endOrgCStr + 1, '.'));
		reuseIfExists = endOrgCStr && (0 == strncmp(bundleIDCStr, serverNameCStr, endOrgCStr + 1 - bundleIDCStr));
		if (!reuseIfExists) {
			IOLog(kIOUserServerNameKey " \"%s\" not correct organization for bundleID \"%s\"\n", serverNameCStr, bundleIDCStr);
		}
	}

	// Find existing server
	if (reuseIfExists) {
		token = IOUserServerCheckInToken::findExistingToken(serverName);
		if (token) {
			// Launch in progress, return token
			goto finish;
		} else {
			// Check if launch completed
			matching = IOService::serviceMatching(gIOUserServerClassKey);
			if (!matching) {
				goto finish;
			}
			IOService::propertyMatching(gIOUserServerNameKey, serverName, matching);
			IOService * service = IOService::copyMatchingService(matching);
			IOUserServer * userServer = OSDynamicCast(IOUserServer, service);
			if (userServer) {
				// found existing user server
				me = userServer;
				goto finish;
			} else {
				OSSafeReleaseNULL(service);
			}
		}
	}

	// No existing server, request launch
	token = new IOUserServerCheckInToken;
	if (!token) {
		goto finish;
	}

	/*
	 * TODO: If the init fails because the personalities are not up to date
	 * restart the whole matching process.
	 */
	if (token && !token->init(serverName, serverTag, driverKext, serverDUI)) {
		IOLog("Could not initialize token\n");
		OSSafeReleaseNULL(token);
		goto finish;
	}

	/*
	 * If the launch fails at any point terminate() will
	 * be called on this IOUserServer.
	 */
	gDriverKitLaunches->setObject(token);
	OSKext::requestDaemonLaunch(bundleID, (OSString *)serverName, serverTag, reslide ? kOSBooleanTrue : kOSBooleanFalse, token, serverDUI);

finish:
	IORecursiveLockUnlock(gDriverKitLaunchLock);
	OSSafeReleaseNULL(matching);
	OSSafeReleaseNULL(driverStatistics);
	OSSafeReleaseNULL(driverKext);

	if (resultToken) {
		*resultToken = token;
	} else {
		OSSafeReleaseNULL(token);
	}

	return me;
}

/*
 * IOUserServerCheckInTokens are used to track dext launches. They have three possible states:
 *
 * - Pending: A dext launch is pending
 * - Canceled: Dext launch failed
 * - Complete: Dext launch is complete
 *
 * A token can be shared among multiple IOServices that are waiting for dexts if the IOUserServerName
 * is the same. This allows dexts to be reused and host multiple services. All pending tokens are stored
 * in gDriverKitLaunches and we check here before creating a new token when launching a dext.
 *
 * A token starts in the pending state with a pending count of 1. When we reuse a token, we increase the
 * pending count of the token.
 *
 * The token is sent to userspace as a mach port through kernelmanagerd/driverkitd to the dext. The dext then
 * uses that token to check in to the kernel. If any part of the dext launch failed (dext crashed, kmd crashed, etc.)
 * we get a no-senders notification for the token in the kernel and the token goes into the Canceled state.
 *
 * Once the dext checks in to the kernel, we decrement the pending count for the token. When the pending count reaches
 * 0, the token goes into the Complete state. So if the token is in the Complete state, there are no kernel matching threads
 * waiting on the dext to check in.
 */

IOUserServerCheckInToken *
IOUserServerCheckInToken::findExistingToken(const OSSymbol * serverName)
{
	IOUserServerCheckInToken * __block result = NULL;

	IORecursiveLockLock(gDriverKitLaunchLock);
	if (gDriverKitLaunches == NULL) {
		goto finish;
	}

	gDriverKitLaunches->iterateObjects(^(OSObject * obj) {
		IOUserServerCheckInToken * token = OSDynamicCast(IOUserServerCheckInToken, obj);
		if (token) {
		        // Check if server name matches
		        const OSSymbol * tokenServerName = token->fServerName;
		        if (tokenServerName->isEqualTo(serverName)) {
		                assert(token->fState == kIOUserServerCheckInPending);
		                token->fPendingCount++;
		                result = token;
		                result->retain();
			}
		}
		return result != NULL;
	});

finish:
	IORecursiveLockUnlock(gDriverKitLaunchLock);
	return result;
}

void
IOUserServerCheckInToken::cancelAll()
{
	OSSet * tokensToCancel;

	IORecursiveLockLock(gDriverKitLaunchLock);
	tokensToCancel = gDriverKitLaunches;
	gDriverKitLaunches = NULL;


	tokensToCancel->iterateObjects(^(OSObject *obj) {
		IOUserServerCheckInToken * token = OSDynamicCast(IOUserServerCheckInToken, obj);
		if (token) {
		        token->cancel();
		}
		return false;
	});

	IORecursiveLockUnlock(gDriverKitLaunchLock);

	OSSafeReleaseNULL(tokensToCancel);
}

void
_IOUserServerCheckInCancellationHandler::call(IOUserServerCheckInToken * token)
{
	fHandler(token, fHandlerArgs);
}

_IOUserServerCheckInCancellationHandler *
_IOUserServerCheckInCancellationHandler::withHandler(IOUserServerCheckInCancellationHandler handler, void * args)
{
	_IOUserServerCheckInCancellationHandler * handlerObj = NULL;
	if (!handler) {
		goto finish;
	}

	handlerObj = new _IOUserServerCheckInCancellationHandler;
	if (!handlerObj) {
		goto finish;
	}

	handlerObj->fHandler = handler;
	handlerObj->fHandlerArgs = args;

finish:
	return handlerObj;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

struct IOServiceStateNotificationDispatchSource_IVars {
	IOLock                       * fLock;
	IOService                    * fStateNotification;
	IOStateNotificationListenerRef fListener;
	OSAction                     * fAction;
	bool                           fEnable;
	bool                           fArmed;
};

kern_return_t
IOServiceStateNotificationDispatchSource::Create_Impl(IOService * service, OSArray * items,
    IODispatchQueue * queue, IOServiceStateNotificationDispatchSource ** outSource)
{
	kern_return_t kr;
	IOServiceStateNotificationDispatchSource * source;

	source = OSTypeAlloc(IOServiceStateNotificationDispatchSource);
	source->init();

	source->ivars->fStateNotification = service;
	kr = service->stateNotificationListenerAdd(items, &source->ivars->fListener, ^kern_return_t () {
		OSAction * action;

		action = NULL;
		IOLockLock(source->ivars->fLock);
		if (source->ivars->fArmed && source->ivars->fAction) {
		        source->ivars->fArmed = false;
		        action = source->ivars->fAction;
		        action->retain();
		}
		IOLockUnlock(source->ivars->fLock);
		if (action) {
		        source->StateNotificationReady(action);
		        OSSafeReleaseNULL(action);
		}
		return kIOReturnSuccess;
	});

	if (kIOReturnSuccess != kr) {
		OSSafeReleaseNULL(source);
	}
	*outSource = source;

	return kr;
}


bool
IOServiceStateNotificationDispatchSource::init()
{
	if (!IODispatchSource::init()) {
		return false;
	}
	ivars = IOMallocType(IOServiceStateNotificationDispatchSource_IVars);
	if (!ivars) {
		return false;
	}
	ivars->fLock = IOLockAlloc();
	if (!ivars->fLock) {
		return false;
	}
	ivars->fArmed = true;

	return true;
}

void
IOServiceStateNotificationDispatchSource::free()
{
	if (ivars) {
		if (ivars->fListener) {
			ivars->fStateNotification->stateNotificationListenerRemove(ivars->fListener);
		}
		if (ivars->fLock) {
			IOLockFree(ivars->fLock);
		}
		IOFreeType(ivars, IOServiceStateNotificationDispatchSource_IVars);
	}
	IODispatchSource::free();
}

kern_return_t
IOServiceStateNotificationDispatchSource::SetHandler_Impl(OSAction * action)
{
	IOReturn ret;
	bool     notifyReady;

	notifyReady = false;

	IOLockLock(ivars->fLock);
	action->retain();
	OSSafeReleaseNULL(ivars->fAction);
	ivars->fAction = action;
	if (action) {
		notifyReady = true;
	}
	IOLockUnlock(ivars->fLock);

	if (notifyReady) {
		StateNotificationReady(action);
	}
	ret = kIOReturnSuccess;

	return ret;
}

kern_return_t
IOServiceStateNotificationDispatchSource::SetEnableWithCompletion_Impl(
	bool enable,
	IODispatchSourceCancelHandler handler)
{
	if (enable == ivars->fEnable) {
		return kIOReturnSuccess;
	}

	IOLockLock(ivars->fLock);
	ivars->fEnable = enable;
	IOLockUnlock(ivars->fLock);

	return kIOReturnSuccess;
}

kern_return_t
IOServiceStateNotificationDispatchSource::Cancel_Impl(
	IODispatchSourceCancelHandler handler)
{
	return kIOReturnUnsupported;
}

kern_return_t
IOServiceStateNotificationDispatchSource::StateNotificationBegin_Impl(void)
{
	IOLockLock(ivars->fLock);
	ivars->fArmed = true;
	IOLockUnlock(ivars->fLock);

	return kIOReturnSuccess;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <IOKit/IOServiceStateNotificationEventSource.h>

OSDefineMetaClassAndStructors(IOServiceStateNotificationEventSource, IOEventSource)
OSMetaClassDefineReservedUnused(IOServiceStateNotificationEventSource, 0);
OSMetaClassDefineReservedUnused(IOServiceStateNotificationEventSource, 1);
OSMetaClassDefineReservedUnused(IOServiceStateNotificationEventSource, 2);
OSMetaClassDefineReservedUnused(IOServiceStateNotificationEventSource, 3);
OSMetaClassDefineReservedUnused(IOServiceStateNotificationEventSource, 4);
OSMetaClassDefineReservedUnused(IOServiceStateNotificationEventSource, 5);
OSMetaClassDefineReservedUnused(IOServiceStateNotificationEventSource, 6);
OSMetaClassDefineReservedUnused(IOServiceStateNotificationEventSource, 7);

OSPtr<IOServiceStateNotificationEventSource>
IOServiceStateNotificationEventSource::serviceStateNotificationEventSource(IOService *service,
    OSArray * items,
    ActionBlock inAction)
{
	kern_return_t kr;
	IOServiceStateNotificationEventSource * source;

	source = OSTypeAlloc(IOServiceStateNotificationEventSource);
	if (source && !source->init(service, NULL)) {
		OSSafeReleaseNULL(source);
	}

	if (!source) {
		return nullptr;
	}

	source->fStateNotification = service;
	kr = service->stateNotificationListenerAdd(items, &source->fListener, ^kern_return_t () {
		if (!source->workLoop) {
		        return kIOReturnSuccess;
		}
		source->workLoop->runActionBlock(^IOReturn (void) {
			source->fArmed = true;
			return kIOReturnSuccess;
		});
		source->signalWorkAvailable();
		return kIOReturnSuccess;
	});

	if (kIOReturnSuccess != kr) {
		OSSafeReleaseNULL(source);
	}

	if (source) {
		source->setActionBlock((IOEventSource::ActionBlock) inAction);
	}

	return source;
}

void
IOServiceStateNotificationEventSource::free()
{
	if (fListener) {
		fStateNotification->stateNotificationListenerRemove(fListener);
	}
	IOEventSource::free();
}

void
IOServiceStateNotificationEventSource::enable()
{
	fEnable = true;
}

void
IOServiceStateNotificationEventSource::disable()
{
	fEnable = false;
}

void
IOServiceStateNotificationEventSource::setWorkLoop(IOWorkLoop *inWorkLoop)
{
	IOEventSource::setWorkLoop(inWorkLoop);
}

bool
IOServiceStateNotificationEventSource::checkForWork()
{
	ActionBlock intActionBlock = (ActionBlock) actionBlock;

	if (fArmed) {
		fArmed = false;
		(intActionBlock)();
	}

	return false;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

OSDefineMetaClassAndStructors(IOSystemStateNotification, IOService);

class IOStateNotificationItem : public OSObject
{
	OSDeclareDefaultStructors(IOStateNotificationItem);

public:
	virtual bool init() override;

	OSDictionary * fSchema;
	OSDictionary * fValue;
	OSSet        * fListeners;
};
OSDefineMetaClassAndStructors(IOStateNotificationItem, OSObject);


class IOStateNotificationListener : public OSObject
{
	OSDeclareDefaultStructors(IOStateNotificationListener);

public:
	virtual bool init() override;
	virtual void free() override;

	IOStateNotificationHandler fHandler;
};
OSDefineMetaClassAndStructors(IOStateNotificationListener, OSObject);


bool
IOStateNotificationItem::init()
{
	return OSObject::init();
}

bool
IOStateNotificationListener::init()
{
	return OSObject::init();
}

void
IOStateNotificationListener::free()
{
	if (fHandler) {
		Block_release(fHandler);
	}
	OSObject::free();
}


struct IOServiceStateChangeVars {
	IOLock       * fLock;
	OSDictionary * fItems;
};

IOService *
IOSystemStateNotification::initialize(void)
{
	IOSystemStateNotification * me;
	IOServiceStateChangeVars  * vars;

	me = OSTypeAlloc(IOSystemStateNotification);
	me->init();
	vars = IOMallocType(IOServiceStateChangeVars);
	me->reserved->svars = vars;
	vars->fLock  = IOLockAlloc();
	vars->fItems = OSDictionary::withCapacity(16);
	{
		kern_return_t ret;

		gIOSystemStateSleepDescriptionKey = (OSString *)OSSymbol::withCStringNoCopy(kIOSystemStateSleepDescriptionKey);
		gIOSystemStateSleepDescriptionHibernateStateKey = OSSymbol::withCStringNoCopy(kIOSystemStateSleepDescriptionHibernateStateKey);
		gIOSystemStateSleepDescriptionReasonKey = OSSymbol::withCStringNoCopy(kIOSystemStateSleepDescriptionReasonKey);

		ret = me->StateNotificationItemCreate(gIOSystemStateSleepDescriptionKey, NULL);
		assert(kIOReturnSuccess == ret);

		gIOSystemStateWakeDescriptionKey = (OSString *)OSSymbol::withCStringNoCopy(kIOSystemStateWakeDescriptionKey);
		gIOSystemStateWakeDescriptionWakeReasonKey = OSSymbol::withCStringNoCopy(kIOSystemStateWakeDescriptionWakeReasonKey);
		gIOSystemStateWakeDescriptionContinuousTimeOffsetKey = OSSymbol::withCStringNoCopy(kIOSystemStateWakeDescriptionContinuousTimeOffsetKey);

		ret = me->StateNotificationItemCreate(gIOSystemStateWakeDescriptionKey, NULL);
		assert(kIOReturnSuccess == ret);

		gIOSystemStateHaltDescriptionKey = (OSString *)OSSymbol::withCStringNoCopy(kIOSystemStateHaltDescriptionKey);
		gIOSystemStateHaltDescriptionHaltStateKey = OSSymbol::withCStringNoCopy(kIOSystemStateHaltDescriptionHaltStateKey);

		ret = me->StateNotificationItemCreate(gIOSystemStateHaltDescriptionKey, NULL);
		assert(kIOReturnSuccess == ret);

		gIOSystemStatePowerSourceDescriptionKey = (OSString *)OSSymbol::withCStringNoCopy(kIOSystemStatePowerSourceDescriptionKey);
		gIOSystemStatePowerSourceDescriptionACAttachedKey = OSSymbol::withCStringNoCopy(kIOSystemStatePowerSourceDescriptionACAttachedKey);

		ret = me->StateNotificationItemCreate(gIOSystemStatePowerSourceDescriptionKey, NULL);
		assert(kIOReturnSuccess == ret);
	}

	return me;
}

bool
IOSystemStateNotification::serializeProperties(OSSerialize * s) const
{
	IOServiceStateChangeVars * ivars = reserved->svars;

	bool ok;
	OSDictionary * result;

	result = OSDictionary::withCapacity(16);

	IOLockLock(ivars->fLock);
	ivars->fItems->iterateObjects(^bool (const OSSymbol * key, OSObject * object) {
		IOStateNotificationItem * item;

		item = (typeof(item))object;
		if (!item->fValue) {
		        return false;
		}
		result->setObject(key, item->fValue);
		return false;
	});
	IOLockUnlock(ivars->fLock);

	ok = result->serialize(s);
	OSSafeReleaseNULL(result);

	return ok;
}

kern_return_t
IOSystemStateNotification::setProperties(OSObject * properties)
{
	kern_return_t  kr;
	OSDictionary * dict;
	OSDictionary * schema;
	OSDictionary * value;
	OSString     * itemName;

	dict = OSDynamicCast(OSDictionary, properties);
	if (!dict) {
		return kIOReturnBadArgument;
	}

	if (!IOCurrentTaskHasEntitlement(kIOSystemStateEntitlement)) {
		return kIOReturnNotPermitted;
	}

	if ((schema = OSDynamicCast(OSDictionary, dict->getObject(kIOStateNotificationItemCreateKey)))) {
		itemName = OSDynamicCast(OSString, schema->getObject(kIOStateNotificationNameKey));
		kr = StateNotificationItemCreate(itemName, schema);
	} else if ((value = OSDynamicCast(OSDictionary, dict->getObject(kIOStateNotificationItemSetKey)))) {
		itemName = OSDynamicCast(OSString, value->getObject(kIOStateNotificationNameKey));
		itemName->retain();
		value->removeObject(kIOStateNotificationNameKey);
		kr = StateNotificationItemSet(itemName, value);
		itemName->release();
	} else {
		kr = kIOReturnError;
	}

	return kr;
}

kern_return_t
IOService::CopySystemStateNotificationService_Impl(IOService ** outService)
{
	IOService * service;

	service = getSystemStateNotificationService();
	service->retain();
	*outService = service;

	return kIOReturnSuccess;
}

IOStateNotificationItem *
IOService::stateNotificationItemCopy(OSString * itemName, OSDictionary * schema)
{
	IOServiceStateChangeVars * ivars = reserved->svars;

	const OSSymbol          * name;
	IOStateNotificationItem * item;

	name = OSSymbol::withString(itemName);

	IOLockLock(ivars->fLock);
	if ((item = (typeof(item))ivars->fItems->getObject(name))) {
		item->retain();
	} else {
		item = OSTypeAlloc(IOStateNotificationItem);
		item->init();
		item->fListeners = OSSet::withCapacity(16);

		if (schema) {
			schema->retain();
		} else {
			schema = OSDictionary::withCapacity(8);
		}
		schema->setObject(kIOStateNotificationNameKey, name);
		item->fSchema = schema;
		ivars->fItems->setObject(name, item);
	}
	IOLockUnlock(ivars->fLock);

	OSSafeReleaseNULL(name);

	return item;
}

kern_return_t
IOService::StateNotificationItemCreate_Impl(OSString * itemName, OSDictionary * schema)
{
	IOStateNotificationItem * item;

	item = stateNotificationItemCopy(itemName, schema);
	if (!item) {
		return kIOReturnNoMemory;
	}
	item->release();

	return kIOReturnSuccess;
}

kern_return_t
IOService::StateNotificationItemSet_Impl(OSString * itemName, OSDictionary * value)
{
	IOServiceStateChangeVars * ivars = reserved->svars;

	OSSet                   * listeners;
	IOStateNotificationItem * item;

	value->retain();
	IOLockLock(ivars->fLock);
	item = (typeof(item))ivars->fItems->getObject(itemName);
	OSSafeReleaseNULL(item->fValue);
	item->fValue = value;
	listeners = NULL;
	if (item->fListeners->getCount()) {
		listeners = OSSet::withSet(item->fListeners);
	}
	IOLockUnlock(ivars->fLock);

	if (listeners) {
		listeners->iterateObjects(^bool (OSObject * object) {
			IOStateNotificationListener * listener;

			listener = (typeof(listener))object;
			listener->fHandler();
			return false;
		});
		OSSafeReleaseNULL(listeners);
	}

	return kIOReturnSuccess;
}

kern_return_t
IOService::StateNotificationItemCopy_Impl(OSString * itemName, OSDictionary ** outValue)
{
	IOServiceStateChangeVars * ivars = reserved->svars;

	kern_return_t              ret;
	IOStateNotificationItem  * item;
	OSDictionary             * value;

	IOLockLock(ivars->fLock);
	item = (typeof(item))ivars->fItems->getObject(itemName);
	if (item) {
		value = item->fValue;
	} else {
		value = NULL;
	}
	if (!value) {
		ret = kIOReturnNotFound;
	} else {
		value->retain();
		ret = kIOReturnSuccess;
	}
	IOLockUnlock(ivars->fLock);

	*outValue = value;

	return ret;
}

kern_return_t
IOService::stateNotificationListenerAdd(OSArray * items,
    IOStateNotificationListenerRef * outRef,
    IOStateNotificationHandler handler)
{
	IOServiceStateChangeVars * ivars = reserved->svars;

	kern_return_t                 kr __block;
	IOStateNotificationListener * listener;

	listener = OSTypeAlloc(IOStateNotificationListener);
	listener->init();
	listener->fHandler = Block_copy(handler);

	kr = kIOReturnSuccess;
	items->iterateObjects(^bool (OSObject * object) {
		OSString                * itemName;
		IOStateNotificationItem * item;

		itemName = OSDynamicCast(OSString, object);
		if (!itemName) {
		        kr = kIOReturnBadArgument;
		        return true;
		}
		item = stateNotificationItemCopy(itemName, NULL);
		if (!item) {
		        kr = kIOReturnNoMemory;
		        return true;
		}
		IOLockLock(ivars->fLock);
		item->fListeners->setObject(listener);
		IOLockUnlock(ivars->fLock);
		item->release();
		return false;
	});

	if (kIOReturnSuccess != kr) {
		stateNotificationListenerRemove(listener);
		OSSafeReleaseNULL(listener);
	}
	*outRef = listener;

	return kr;
}


kern_return_t
IOService::stateNotificationListenerRemove(IOStateNotificationListenerRef ref)
{
	IOServiceStateChangeVars * ivars = reserved->svars;

	IOStateNotificationListener * listener;
	kern_return_t                 kr;

	kr = kIOReturnSuccess;
	listener = (typeof(listener))ref;

	IOLockLock(ivars->fLock);
	ivars->fItems->iterateObjects(^bool (const OSSymbol * key, OSObject * object) {
		IOStateNotificationItem * item;

		item = (typeof(item))object;
		item->fListeners->removeObject(listener);
		return false;
	});
	IOLockUnlock(ivars->fLock);

	return kr;
}



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IOWorkGroup::Create_Impl(OSString * name, IOUserClient * userClient, IOWorkGroup ** workgroup)
{
	IOWorkGroup * inst = NULL;
	IOUserUserClient * uc = NULL;
	kern_return_t ret = kIOReturnError;
	IOUserServer * us;

	if (name == NULL) {
		ret = kIOReturnBadArgument;
		goto finish;
	}

	if (name->getLength() > kIOWorkGroupMaxNameLength) {
		ret = kIOReturnBadArgument;
		goto finish;
	}

	uc = OSDynamicCast(IOUserUserClient, userClient);
	if (uc == NULL) {
		ret = kIOReturnBadArgument;
		goto finish;
	}

	inst = OSTypeAlloc(IOWorkGroup);
	if (!inst->init()) {
		inst->free();
		inst = NULL;
		ret = kIOReturnNoMemory;
		goto finish;
	}

	us = (typeof(us))thread_iokit_tls_get(0);
	inst->ivars->userServer = OSDynamicCast(IOUserServer, us);

	if (inst->ivars->userServer == NULL) {
		ret = kIOReturnBadArgument;
		goto finish;
	}
	inst->ivars->userServer->retain();

	inst->ivars->name = name;
	inst->ivars->name->retain();

	inst->ivars->userClient = uc; // no retain

	IOLockLock(uc->fLock);
	uc->fWorkGroups->setObject(name, inst);
	IOLockUnlock(uc->fLock);
	ret = kIOReturnSuccess;

finish:
	if (ret != kIOReturnSuccess) {
		OSSafeReleaseNULL(inst);
	} else {
		*workgroup = inst;
	}

	return ret;
}

kern_return_t
IOWorkGroup::InvalidateKernel_Impl(IOUserClient * client)
{
	IOUserUserClient * uc = OSDynamicCast(IOUserUserClient, client);

	if (uc == NULL) {
		return kIOReturnBadArgument;
	}

	if (uc != ivars->userClient) {
		return kIOReturnBadArgument;
	}

	IOLockLock(uc->fLock);
	uc->fWorkGroups->removeObject(ivars->name);
	IOLockUnlock(uc->fLock);

	return kIOReturnSuccess;
}

kern_return_t
IOWorkGroup::SetWorkGroupPort_Impl(mach_port_t port)
{
	return kIOReturnUnsupported;
}

bool
IOWorkGroup::init()
{
	if (!OSObject::init()) {
		return false;
	}
	ivars = IOMallocType(IOWorkGroup_IVars);

	return true;
}

void
IOWorkGroup::free()
{
	if (ivars) {
		OSSafeReleaseNULL(ivars->userServer);
		OSSafeReleaseNULL(ivars->name);
		IOFreeType(ivars, IOWorkGroup_IVars);
	}

	OSObject::free();
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

kern_return_t
IOEventLink::Create_Impl(OSString * name, IOUserClient * userClient, IOEventLink ** eventlink)
{
	IOEventLink * inst = NULL;
	IOUserUserClient * uc = NULL;
	IOUserServer * us;
	kern_return_t ret = kIOReturnError;

	if (name == NULL) {
		ret = kIOReturnBadArgument;
		goto finish;
	}

	if (name->getLength() > kIOEventLinkMaxNameLength) {
		ret = kIOReturnBadArgument;
		goto finish;
	}

	uc = OSDynamicCast(IOUserUserClient, userClient);
	if (uc == NULL) {
		ret = kIOReturnBadArgument;
		goto finish;
	}

	inst = OSTypeAlloc(IOEventLink);
	if (!inst->init()) {
		inst->free();
		inst = NULL;
		ret = kIOReturnNoMemory;
		goto finish;
	}

	us = (typeof(us))thread_iokit_tls_get(0);
	inst->ivars->userServer = OSDynamicCast(IOUserServer, us);

	if (inst->ivars->userServer == NULL) {
		ret = kIOReturnBadArgument;
		goto finish;
	}
	inst->ivars->userServer->retain();

	inst->ivars->name = name;
	inst->ivars->name->retain();

	inst->ivars->userClient = uc; // no retain

	IOLockLock(uc->fLock);
	uc->fEventLinks->setObject(name, inst);
	IOLockUnlock(uc->fLock);

	ret = kIOReturnSuccess;

finish:
	if (ret != kIOReturnSuccess) {
		OSSafeReleaseNULL(inst);
	} else {
		*eventlink = inst;
	}

	return ret;
}

kern_return_t
IOEventLink::InvalidateKernel_Impl(IOUserClient * client)
{
	IOUserUserClient * uc = OSDynamicCast(IOUserUserClient, client);

	if (uc == NULL) {
		return kIOReturnBadArgument;
	}

	if (uc != ivars->userClient) {
		return kIOReturnBadArgument;
	}

	IOLockLock(uc->fLock);
	uc->fEventLinks->removeObject(ivars->name);
	IOLockUnlock(uc->fLock);

	return kIOReturnSuccess;
}

bool
IOEventLink::init()
{
	if (!OSObject::init()) {
		return false;
	}
	ivars = IOMallocType(IOEventLink_IVars);

	return true;
}

void
IOEventLink::free()
{
	if (ivars) {
		OSSafeReleaseNULL(ivars->userServer);
		OSSafeReleaseNULL(ivars->name);
		IOFreeType(ivars, IOEventLink_IVars);
	}

	OSObject::free();
}

kern_return_t
IOEventLink::SetEventlinkPort_Impl(mach_port_t port __unused)
{
	return kIOReturnUnsupported;
}