notify_client.c   [plain text]


/*
 * Copyright (c) 2003-2012 Apple Inc. All rights reserved.
 *
 * @APPLE_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. 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_LICENSE_HEADER_END@
 */

#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <sys/ipc.h>
#include <sys/signal.h>
#include <sys/syslimits.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <sys/mman.h>
#include <sys/fcntl.h>
#include <sys/time.h>
#include <bootstrap_priv.h>
#include <errno.h>
#include <stdatomic.h>
#include <os/alloc_once_private.h>
#include <os/lock_private.h>
#include <os/reason_private.h>
#include <os/variant_private.h>
#include <TargetConditionals.h>
#include <AvailabilityMacros.h>
#include <Block.h>
#include <dispatch/dispatch.h>
#include <dispatch/private.h>
#include <xpc/private.h>
#include <_simple.h>

#include <mach-o/dyld_priv.h> // _dyld_is_memory_immutable

#include "libnotify.h"

#include "notify.h"
#include "notify_internal.h"
#include "notify_ipc.h"
#include "notify_private.h"
#include "notify_probes.h"

#ifdef DEBUG
#define DEBUG_REGISTRATION		0x00000001
#define DEBUG_NOTIFICATION		0x00000002
#define DEBUG_RETAIN_RELEASE	0x00000004
#define DEBUG_CANCEL			0x00000008
#define DEBUG_GET_STATE			0x00000010
#define DEBUG_SEND_NO_BLOCK		0x00000020
#define DEBUG_NODES				0x00000040
#define DEBUG_API				0x00000080
#define DEBUG_USER				0x80000000
#define DEBUG_ALL				0xffffffff
static uint32_t _libnotify_debug = DEBUG_ALL;
#endif /* DEBUG */

#define EVENT_INIT       0
#define EVENT_REGEN      1

#define SELF_PREFIX "self."
#define SELF_PREFIX_LEN 5

#define COMMON_SELF_PORT_KEY "self.com.apple.system.notify.common"

#define MULTIPLE_REGISTRATION_WARNING_TRIGGER 500

#define NID_UNSET 0xffffffffffffffffL
#define NID_CALLED_ONCE 0xfffffffffffffffeL

// If connection to notifyd tries, we retry a total of
// NOTIFY_SERVER_RETRY_NUMBER times, waiting in between each attempt for
// NOTIFY_SERVER_RETRY_WAIT_US microseconds.
#define NOTIFY_SERVER_RETRY_WAIT_US 100000
#define NOTIFY_SERVER_RETRY_NUMBER 50

/*
 * Details about registrations, tokens, dispatch (NOTIFY_OPT_DISPATCH), IPC versions, and etc.
 *
 * In the first versions of the client/server protocol (ipc versions 0 and 1), the integer
 * token representing a registration was generated by notifyd and returned to the client
 * as an out parameter in the MIG registration call.
 *
 * If a client uses the old protocols, then each registration in the client goes straight to
 * the server.  If the client registers for signals, file descriptor writes, or whatever,
 * that's all handled by the server.
 *
 * The current version (ipc version 2) has better performance.  The client now generates
 * a unique token (simply incremented by 1 for each new registration) and sends it to
 * notifyd in a MIG simpleroutine.  notifyd internally uses a 64 bit ID for each registration
 * that's formed from the client's PID (high-order 32 bits) and the client-provided integer
 * (low-order 32 bits).
 *
 * With the advent of libdispatch, there was an opportunity to further improve performance
 * and reduce the load on notifyd.  The client library (this source file) checks if the
 * client process is multithreaded, or at least can be multithreaded, with a call to the
 * _dispatch_is_multithreaded() routine.  This tells us if we can use dispatch in the client.
 *
 * If the client can use dispatch (NOTIFY_OPT_DISPATCH gets set in globals->client_opts),
 * then all registrations use a single shared mach port (globals->notify_common_port).
 * The client creates a dispatch source (globals->notify_dispatch_source) that handles all
 * notifications from the server.  The handler routine looks up the client's registration
 * for the received notification and sends a signal, writes on a file descriptor, or sends
 * a mach message as required.  It's all done locally in the client process.
 *
 * Many clients register several times for the same notification name.  That's sometimes
 * due to bad code, but it may be legitimate as well.  For example, different libraries or
 * frameworks may register independently, or different threads in a client process may
 * each require a registration for the same name.  When dispatch is available, client
 * registrations for the same name are coalesced.  The library still generates a new
 * token (and an underlying token data structure) for each registration, but only the
 * first registration is actually sent to notifyd.  Subsequent registrations are simply
 * chained to the first in a linked list.  When the globals->notify_dispatch_source
 * handler processes a notification from the server, it traverses the linked list and
 * forwards the notification to each.
 *
 * Note that it is possible for the client to still have multiple registrations with
 * notifyd, even when coalescing.  Polled registrations (memory or plain) must be handled
 * by notifyd, so these registration types are not coalesced.  Also, a client might start
 * out single threaded and appear not to be dispatch-safe, but them become multi-threaded
 * later on.  Early registrations would go individually to notifyd, while later
 * registrations would be coalesced.
 *
 * The library uses both locking and refcounting of data structures.  A mutex in the
 * library globals (globals->notify_lock) is used when accessing mutable values, hash
 * tables, and lists that are found in the global data (returned by _notify_globals()).
 * Data structure instances (name_node_t and registration_node_t types) are refcounted.
 * name_node_t instances have locks to protect their data.  registration_node_t
 * instances do not, mostly to save memory.  Most operations on them can either be done
 * atomically, or are within the scope of some other lock.
 */

typedef struct
{
	uint64_t name_id;
	TAILQ_HEAD(, __registration_node_s) coalesced;
	struct __registration_node_s *coalesce_base;
	char *name;
	os_unfair_lock lock;
	atomic_uint_fast32_t refcount;
	uint32_t coalesce_base_token;
	bool has_been_warned;
	bool needs_free;
} name_node_t;

/*
 * Data structure behind a client's token.
 * The library exports tokens (integers) to users of the library, so
 * notify_register_...() gives the client an int value that represents
 * a registration.
 *
 * If the client registers multiple times for the same name and we can
 * use dispatch in the library, then the duplicate registration creates
 * a new client-side registration, but not a new registration with the
 * server.  Multiple registrations are chained in a linked list.  The
 * base registration is retained for each coalesced registration.
 */
typedef struct __registration_node_s
{
	TAILQ_ENTRY(__registration_node_s) registration_coalesced_entry;

	atomic_uint_fast32_t refcount;
	uint32_t token;
	uint32_t flags;

	/* shared memory slot and value at that location when we last did notify_check() */
	uint32_t slot;
	uint32_t val;

	/* client-facing parts of a notification */
	int fd;
	int signal_or_xtra_mp;
	mach_port_t mp;
	dispatch_queue_t queue;
	notify_handler_t block;

	/* client_id is the value returned from notifyd for registrations using IPC version 0 */
	uint32_t client_id;

	/* state value and timestamp when we set it - used to regenerate if notifyd restarts */
	uint64_t set_state_val;
	uint64_t set_state_time;

	/* path monitoring */
	char *path;
	int path_flags;

	/* name table node for this registration */
	name_node_t *name_node;
} registration_node_t;


/* FORWARD */
static void _notify_lib_server_restart_handler(void *ctxt);
static void notify_retain_mach_port(notify_globals_t globals, mach_port_t mp, int flags);
static void _notify_dispatch_handle(void *context);
static void registration_node_release(registration_node_t *r);
static void registration_node_release_locked(notify_globals_t globals, registration_node_t *r);
static void notify_release_file_descriptor_locked(notify_globals_t globals, int fd);
static void notify_release_mach_port_locked(notify_globals_t globals, mach_port_t mp, uint32_t flags);
static uint32_t notify_register_coalesced_registration(const char *name, int flags, int *out_token, notify_globals_t globals, mach_port_t extra_mp);

// TSAN doesn't know about os_unfair_lock_with_options
#if defined(__has_feature)
#if __has_feature(thread_sanitizer)
#define TSAN_SAFE_LOCK(x) os_unfair_lock_lock(x)
#else
#define TSAN_SAFE_LOCK(x) os_unfair_lock_lock_with_options(x, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION)
#endif // __has_feature(thread_sanitizer)
#else
#define TSAN_SAFE_LOCK(x) os_unfair_lock_lock(x)
#endif // defined(__clang__)

#ifdef DEBUG_MUTEX
#define mutex_lock(s,x,f,l) \
_notify_client_log(ASL_LEVEL_NOTICE, "attempting mutex lock %s %p from %s:%u", s, x, f, l); \
TSAN_SAFE_LOCK(x); \
_notify_client_log(ASL_LEVEL_NOTICE, "acquired mutex lock %s %p from %s:%u", s, x, f, l);
#define mutex_unlock(s,x,f,l) \
_notify_client_log(ASL_LEVEL_NOTICE, "dropping mutex lock %s %p from %s:%u", s, x, f, l); \
os_unfair_lock_unlock(x);
#else
#define mutex_lock(s,x,f,l) TSAN_SAFE_LOCK(x)
#define mutex_unlock(s,x,f,l) os_unfair_lock_unlock(x)
#endif

// returns the result after the decrement
inline static int32_t
atomic_refcount_release(atomic_uint_fast32_t *val)
{
	int32_t result = os_atomic_dec(val, release);
	// Crash right away if we underrun our refcounts
	// as this indicates a bug in our logic
	assert(result >= 0);
	// result == 0 means that this was the last reference so any changes
	// made to the refcounted object need to be visible to all threads
	// at this point. c.f. "Release-Acquire ordering" at
    // http://en.cppreference.com/w/cpp/atomic/memory_order
	if (result == 0) os_atomic_thread_fence(acquire);
	return result;
}

// returns the result after the increment
inline static int32_t
atomic_increment32(atomic_uint_fast32_t *val)
{
	return os_atomic_inc(val, relaxed);
}

inline static void
name_node_retain(name_node_t *node)
{
	atomic_increment32(&node->refcount);
}

inline static void
registration_node_retain(registration_node_t *reg)
{
	atomic_increment32(&reg->refcount);
}

inline static uint32_t
client_opts(notify_globals_t globals)
{
	return os_atomic_load(&globals->client_opts, relaxed);
}

__printflike(2, 3)
static void
_notify_client_log(int level, const char *fmt, ...)
{
	va_list ap;
	char *msg = NULL;

	va_start(ap, fmt);
	vasprintf(&msg, fmt, ap);
	va_end(ap);

	if (msg != NULL)
	{
		_simple_asl_log(level, "com.apple.notify", msg);
#ifdef DEBUG_VERBOSE
				fprintf(stderr, "thread %p: %s\n", (void *)pthread_self(), msg);
#endif
	}

	free(msg);
}


#if !TARGET_OS_SIMULATOR && !TARGET_OS_OSX

static const uint32_t LAUNCHD_PID = 1;

// do this so we don't have to link against libtrace
__printflike(1, 2)
static void
simulate_crash(char *fmt, ...)
{
	char *desc = NULL;
	va_list ap;
	int len;

	if (getpid() == LAUNCHD_PID) {
		return;
	}

	va_start(ap, fmt);
	len = vasprintf(&desc, fmt, ap);
	va_end(ap);

	if (desc == NULL) {
		return;
	}

	os_fault_with_payload(OS_REASON_LIBSYSTEM, OS_REASON_LIBSYSTEM_CODE_FAULT,
			desc, len + 1, desc, 0);
	free(desc);
}

#define REPORT_BAD_BEHAVIOR(...)							\
	if(os_variant_has_internal_diagnostics("libnotify.simulate_crash"))		\
	{										\
		simulate_crash(__VA_ARGS__);						\
	} else {									\
		_notify_client_log(ASL_LEVEL_ERR, __VA_ARGS__);				\
	}										\
	(void)0 // This allows ; after the macro and the compiler will optimize it out

#else /* !TARGET_OS_SIMULATOR && !TARGET_OS_OSX */

#define REPORT_BAD_BEHAVIOR(...) _notify_client_log(ASL_LEVEL_ERR, __VA_ARGS__)

#endif /* !TARGET_OS_SIMULATOR && !TARGET_OS_OSX */

static char *
_notify_strdup_if_mutable(const char *str, bool *needs_free)
{
	size_t size = strlen(str) + 1;
	if (!_dyld_is_memory_immutable(str, size)) {
		char *clone = (char *)malloc(size);
		if (clone) {
			memcpy(clone, str, size);
			if (needs_free) *needs_free = true;
		}
		return clone;
	}
	if (needs_free) *needs_free = false;
	return (char *)str;
}

#pragma mark -
#pragma mark globals

#define INITIAL_TOKEN_ID 1

#define NUM_LOGGED_WRONG_CANARY 3

#if 64 < CANARY_COUNT
#error CANARY_COUNT too large to be represented in bitvector in _check_canary()
#endif
#if CANARY_COUNT < NUM_LOGGED_WRONG_CANARY
#error NUM_LOGGED_WRONG_CANARY too large; set equal to or lower than CANARY_COUNT
#endif

static const uint64_t canary_const = 0xAAAAaaaaAAAAaaaaULL;

/*
 * Initialization of global variables. Called once per process.
 */
static void
_notify_init_globals(void * /* notify_globals_t */ _globals)
{
	notify_globals_t globals = _globals;

	for (uint64_t idx = 0; idx < CANARY_COUNT; idx += 1) {
		globals->canary[idx] = canary_const;
	}
	globals->notify_lock = OS_UNFAIR_LOCK_INIT;
	os_atomic_store(&globals->token_id, INITIAL_TOKEN_ID, relaxed);
	globals->notify_common_token = -1;
	globals->check_lock = OS_UNFAIR_LOCK_INIT;
	_nc_table_init(&globals->name_node_table, offsetof(name_node_t, name));
	_nc_table_init_n(&globals->registration_table, offsetof(registration_node_t, token));

	_notify_lib_notify_state_init(&globals->self_state, NOTIFY_STATE_USE_LOCKS);
}

static inline void
_check_canary(notify_globals_t globals) {
	uint64_t ne_bits = 0;
	size_t cnt = 0;
	uint64_t wrong[NUM_LOGGED_WRONG_CANARY];
	for (size_t idx = 0; idx < CANARY_COUNT; idx += 1) {
		uint64_t canary = globals->canary[idx];
		if (os_unlikely(canary_const != canary)) {
			ne_bits |= (1ULL << idx);
			if (cnt == 0) {
				memset(wrong, 0, sizeof(wrong));
			}
			if (cnt < NUM_LOGGED_WRONG_CANARY) {
				wrong[cnt] = canary;
				cnt += 1;
			}
		}
	}
	if (ne_bits != 0) {
		REPORT_BAD_BEHAVIOR("BUG IN LIBNOTIFY CLIENT: internal data structure corrupted [0x%04llx, 0x%llx, 0x%llx, 0x%llx]]", ne_bits, wrong[0], wrong[1], wrong[2]);
		// Reset canary to (1) avoid further reports of this corruption, and (2) detect the next corruption.
		for (uint64_t idx = 0; idx < CANARY_COUNT; idx += 1) {
			globals->canary[idx] = canary_const;
		}
	}
}

__attribute__((__pure__))
static inline notify_globals_t
_notify_globals(void)
{
	notify_globals_t globals = (notify_globals_t)os_alloc_once(OS_ALLOC_ONCE_KEY_LIBSYSTEM_NOTIFY,
			sizeof(struct notify_globals_s), &_notify_init_globals);

	static uintptr_t _globals_lookup_counter = 0;
	_globals_lookup_counter += 1;
	if ((_globals_lookup_counter & 0x3) == 0) {
		_check_canary(globals);
	}
	return globals;
}

#pragma mark -
#pragma mark name_node_t

#ifdef NOTDEF
static void
name_node_dump(int level, name_node_t *n)
{
	if (n == NULL)
	{
		_notify_client_log(level, "name_node_t NULL\n");
		return;
	}

	_notify_client_log(level, "name_node_t %p name=%s name_id=%llu refcount=%d coalesce_base_token=%u coalesce_base=%p\n", (n->name == NULL) ? "NULL" : n->name, n->name_id, n->refcount, n->coalesce_base_token, n->coalesce_base);
}
#endif

// must be called with the global lock held
static name_node_t *
name_node_for_name_locked(notify_globals_t globals, const char *name, uint64_t nid, bool create)
{
	os_unfair_lock_assert_owner(&globals->notify_lock);

	if (name == NULL) return NULL;

	name_node_t *n = _nc_table_find(&globals->name_node_table, name);
	if (n != NULL)
	{
		name_node_retain(n);
	}
	else if (create)
	{
		n = (name_node_t *)calloc(1, sizeof(name_node_t));
		if (n == NULL)
		{
#ifdef DEBUG
			_notify_client_log(ASL_LEVEL_ERR, "name_node_for_name name %s calloc failed errno %d [%s]\n", name, errno, strerror(errno));
#endif
			goto done;
		}

		n->name = _notify_strdup_if_mutable(name, &n->needs_free);
		if (n->name == NULL)
		{
			free(n);
			n = NULL;
			goto done;
		}

		os_atomic_store(&n->refcount, 1, relaxed);
		n->name_id = nid;
		TAILQ_INIT(&n->coalesced);
		n->coalesce_base_token = NOTIFY_TOKEN_INVALID;
		n->lock = OS_UNFAIR_LOCK_INIT;
		n->has_been_warned = false;

		_nc_table_insert(&globals->name_node_table, &n->name);
	}

done:

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_NODES)
	{
		if (n == NULL) _notify_client_log(ASL_LEVEL_NOTICE, "name_node_for_name name %s returning NULL\n", name);
		else _notify_client_log(ASL_LEVEL_NOTICE, "name_node_for_name name %s refcount %d %p\n", n->name, n->refcount, n);
	}
#endif

	return n;
}

static name_node_t *
name_node_for_name(const char *name, uint64_t nid, bool create)
{
	notify_globals_t globals = _notify_globals();
	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);
	name_node_t *node = name_node_for_name_locked(globals, name, nid, create);
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
	return node;
}

// must be called with the global lock held
static void
name_node_delete_locked(notify_globals_t globals, name_node_t *n)
{
	os_unfair_lock_assert_owner(&globals->notify_lock);

	/* refcount is zero, free the node */
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_NODES) _notify_client_log(ASL_LEVEL_NOTICE, "name_node_release name %s refcount %d %p FREE", n->name, n->refcount, n);
#endif

	_nc_table_delete(&globals->name_node_table, n->name);
	if (n->needs_free) {
		free(n->name);
	}
	n->name = NULL;
	free(n);
}

// must be called with the global lock held
static void
name_node_release_locked(notify_globals_t globals, name_node_t *n)
{
	os_unfair_lock_assert_owner(&globals->notify_lock);

	if (n == NULL) return;

	if (atomic_refcount_release(&n->refcount) > 0)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_NODES) _notify_client_log(ASL_LEVEL_NOTICE, "%s name %s refcount %d %p", __func__, n->name, n->refcount, n);
#endif
		return;
	}
	name_node_delete_locked(globals, n);
}

static void
name_node_unlock_and_release(name_node_t *n)
{
	if (n == NULL) return;

    mutex_unlock(n->name, &n->lock, __func__, __LINE__);
    notify_globals_t globals = _notify_globals();
    mutex_lock("global", &globals->notify_lock, __func__, __LINE__);
	if (atomic_refcount_release(&n->refcount) > 0)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_NODES) _notify_client_log(ASL_LEVEL_NOTICE, "%s name %s refcount %d %p", __func__, n->name, n->refcount, n);
#endif
        mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
		return;
	}
    name_node_delete_locked(globals, n);
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
}

// We avoid an extra retain here if the base registration was just created
static void
name_node_add_coalesced_registration_locked(name_node_t *n, registration_node_t *r, bool skip_retain)
{
	if (n == NULL) return;
	if (r == NULL) return;
    
    os_unfair_lock_assert_owner(&n->lock);

	if (!skip_retain && n->coalesce_base) registration_node_retain(n->coalesce_base);
	TAILQ_INSERT_TAIL(&n->coalesced, r, registration_coalesced_entry);
}

// must be called with the global lock held
static void
name_node_remove_coalesced_registration_locked(notify_globals_t globals, name_node_t *n, registration_node_t *r)
{
	os_unfair_lock_assert_owner(&globals->notify_lock);

	if (n == NULL) return;
	if (r == NULL) return;

	mutex_lock(n->name, &n->lock, __func__, __LINE__);
	TAILQ_REMOVE(&n->coalesced, r, registration_coalesced_entry);
	mutex_unlock(n->name, &n->lock, __func__, __LINE__);

	registration_node_release_locked(globals, n->coalesce_base);
}

static void
name_node_set_nid_locked(name_node_t *n, uint64_t nid)
{
    os_unfair_lock_assert_owner(&n->lock);
	n->name_id = nid;
}

static void
name_node_set_nid(name_node_t *n, uint64_t nid)
{
    if (n == NULL) return;
    
    mutex_lock(n->name, &n->lock, __func__, __LINE__);
	name_node_set_nid_locked(n, nid);
    mutex_unlock(n->name, &n->lock, __func__, __LINE__);
}

#pragma mark -
#pragma mark registration_node_t

static registration_node_t *
registration_node_find(uint32_t token)
{
	notify_globals_t globals = _notify_globals();

	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);
	registration_node_t *r = _nc_table_find_n(&globals->registration_table, token);
	if (r != NULL) registration_node_retain(r);
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_NODES) _notify_client_log(ASL_LEVEL_NOTICE, "registration_node_find token %u refcount %d -> %p", token, r ? r->refcount : -1, r);
#endif

	return r;
}

/*
 * Tells the caller if a value is a valid token number.
 * Internal coalesce_base registration tokens are reported as invalid,
 * since clients should not be mucking around with them.
 */
bool
notify_is_valid_token(int val)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif
	bool valid = true;
	notify_globals_t globals = _notify_globals();

	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);
	registration_node_t *r = _nc_table_find_n(&globals->registration_table, val);
	if (r == NULL) valid = false;
	else if (r->flags & NOTIFY_FLAG_COALESCE_BASE) valid = false;
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return valid;
}

// must be called with the global lock held
static void
registration_node_free_locked(notify_globals_t globals, registration_node_t *r)
{
	os_unfair_lock_assert_owner(&globals->notify_lock);

	name_node_t *n = r->name_node;

	if (r->flags & NOTIFY_FLAG_COALESCED)
	{
		name_node_remove_coalesced_registration_locked(globals, n, r);
	}
	else if (r->flags & NOTIFY_FLAG_COALESCE_BASE)
	{
		mutex_lock(n->name, &n->lock, __func__, __LINE__);
		n->coalesce_base_token = NOTIFY_TOKEN_INVALID;
		n->coalesce_base = NULL;
		mutex_unlock(n->name, &n->lock, __func__, __LINE__);

		/* cancel the registration with notifyd */
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_CANCEL) _notify_client_log(ASL_LEVEL_NOTICE, "_notify_server_cancel_2 token %u", r->token);
#endif
		// hold lock across async server call
		(void)_notify_server_cancel_2(globals->notify_server_port, r->token);
	}

	notify_release_file_descriptor_locked(globals, r->fd);
	notify_release_mach_port_locked(globals, r->mp, r->flags);
	if(((r->flags & NOTIFY_TYPE_MASK) == NOTIFY_TYPE_COMMON_PORT) && (r->signal_or_xtra_mp != MACH_PORT_NULL)) {
		notify_release_mach_port_locked(globals, (mach_port_t)r->signal_or_xtra_mp, r->flags | NOTIFY_FLAG_RELEASE_SEND);
	}
	free(r->path);

	if (r->block != NULL) dispatch_async_f(r->queue, r->block, (dispatch_function_t)_Block_release);
	r->block = NULL;

	if (r->queue != NULL) dispatch_release(r->queue);
	r->queue = NULL;

	free(r);

	name_node_release_locked(globals, n);
}

// must be called with the global lock held
static void
registration_node_delete_locked(notify_globals_t globals, registration_node_t *r)
{
	os_unfair_lock_assert_owner(&globals->notify_lock);

	/* refcount is zero, free the node */

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_NODES) _notify_client_log(ASL_LEVEL_NOTICE, "%s token %u refcount %d flags 0x%08x %p FREE", __func__, r->token, r->refcount, r->flags, r);
#endif

	_nc_table_delete_n(&globals->registration_table, r->token);

	uint32_t reg_token = r->token;
	uint32_t reg_flags = r->flags;
	registration_node_free_locked(globals, r);

	if (reg_flags & NOTIFY_FLAG_SELF)
	{
		/*
		 * _notify_lib_cancel fails quietly if self_state is NULL
		 * We let it fail quietly.
		 */
		_notify_lib_cancel(&globals->self_state, NOTIFY_CLIENT_SELF, reg_token);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return;
	}

	if (reg_flags & NOTIFY_FLAG_COALESCE_BASE)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_CANCEL) _notify_client_log(ASL_LEVEL_NOTICE, "notify_cancel token %d NOTIFY_FLAG_COALESCE_BASE", reg_token);
#endif
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return;
	}
	kern_return_t kstatus = KERN_SUCCESS;
	if ((reg_flags & NOTIFY_FLAG_COALESCED) == 0)
	{
		// hold lock across async server call
		kstatus = _notify_server_cancel_2(globals->notify_server_port, reg_token);
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_CANCEL) _notify_client_log(ASL_LEVEL_NOTICE, "notify_cancel token %d reg_node %p has been cancelled", reg_token, r);
#endif

	if ((kstatus == MIG_SERVER_DIED) || (kstatus == MACH_SEND_INVALID_DEST))
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return;
	}
	else if (kstatus != KERN_SUCCESS)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		_notify_client_log(ASL_LEVEL_ERR, "<- %s [%d] _notify_server_cancel_2 failed: 0x%08x\n", __func__, __LINE__ + 2, kstatus);
		return;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
}

static void
registration_node_release(registration_node_t *r)
{
	notify_globals_t globals = _notify_globals();
	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);
	registration_node_release_locked(globals, r);
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
}

static void
registration_node_release_locked(notify_globals_t globals, registration_node_t *r)
{
	os_unfair_lock_assert_owner(&globals->notify_lock);

	if (r == NULL) return;

	if (atomic_refcount_release(&r->refcount) > 0)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_NODES) _notify_client_log(ASL_LEVEL_NOTICE, "%s token %u refcount %d flags 0x%08x %p", __func__, r->token, r->refcount, r->flags, r);
#endif

		return;
	}
	registration_node_delete_locked(globals, r);
}

#if !TARGET_OS_SIMULATOR
static bool
shm_attach(uint32_t size)
{
	int32_t shmfd;
	notify_globals_t globals = _notify_globals();
	void *shm_base = NULL;
	bool result = true;

	shmfd = shm_open(SHM_ID, O_RDONLY, 0);
	if (shmfd == -1)
	{
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed on line %d with errno %d", __func__, __LINE__, errno);
		return false;
	}

	shm_base = mmap(NULL, size, PROT_READ, MAP_SHARED, shmfd, 0);
	if (shm_base == MAP_FAILED) {
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed on line %d with errno %d", __func__, __LINE__, errno);
		result = false;
	} else {
		globals->shm_base = shm_base;
		result = true;
	}

	close(shmfd);

	return result;
}
#endif /* TARGET_OS_SIMULATOR */

#ifdef NOTDEF
static void
shm_detach(void)
{
	if (shm_base != NULL)
	{
		shmdt(shm_base);
		shm_base = NULL;
	}
}
#endif

/*
 * _notify_lib_init is called for each new registration (event = EVENT_INIT).
 * It is also called to re-initialize when the library has detected that
 * notifyd has restarted (event = EVENT_REGEN).
 */
static uint32_t
_notify_lib_init_locked(notify_globals_t globals, uint32_t event)
{
	__block kern_return_t kstatus;
	uint32_t status;

	/* notifyd sets NOTIFY_OPT_DISABLE to avoid re-entrancy issues */
	if (client_opts(globals) & NOTIFY_OPT_DISABLE) return NOTIFY_STATUS_OPT_DISABLE;

	/* We need to hold the global lock here otherwise "first" is racy */
	/* But then we don't need dispatch_once -- clean this up in 39829810 */
	os_unfair_lock_assert_owner(&globals->notify_lock);
	
	/* Look up the notifyd server port just once. */
	kstatus = KERN_SUCCESS;
	dispatch_once(&globals->notify_server_port_once, ^{
		kstatus = bootstrap_look_up2(bootstrap_port, NOTIFY_SERVICE_NAME, &globals->notify_server_port, 0, BOOTSTRAP_PRIVILEGED_SERVER);
	});

	if ((kstatus != KERN_SUCCESS) || !(MACH_PORT_VALID(globals->notify_server_port)))
	{
		return NOTIFY_STATUS_SERVER_NOT_FOUND;
	}

	/*
	 * _dispatch_is_multithreaded() tells us if it is safe to use dispatch queues for
	 * a shared port for all registrations (NOTIFY_OPT_DISPATCH), and to watch for notifyd
	 * exiting / restarting (NOTIFY_OPT_REGEN).
	 */
	if (_dispatch_is_multithreaded()) {
		os_atomic_or(&globals->client_opts, NOTIFY_OPT_DISPATCH | NOTIFY_OPT_REGEN, relaxed);
	}

	/*
	 * Look up the server's PID and supported IPC version on the first call,
	 * and on a regeneration event (when the server has restarted).
	 */
	pid_t last_pid = globals->notify_server_pid;
	if ((last_pid == 0) || (event == EVENT_REGEN))
	{
		uint32_t version;
		pid_t new_pid;

		globals->notify_server_pid = 0;

		kstatus = _notify_server_checkin(globals->notify_server_port, &version, (uint32_t *)&new_pid, (int *)&status);
		if (kstatus != KERN_SUCCESS)
		{
		    if (kstatus == MACH_SEND_INVALID_DEST) {
			status = NOTIFY_STATUS_SERVER_NOT_FOUND;
		    } else {
			status = NOTIFY_STATUS_SERVER_CHECKIN_FAILED;
		    }
		}
		if (status != NOTIFY_STATUS_OK)
		{
			return status;
		}

		/*
		 * protocol versions below 3 aren't supported anymore
		 */
		if (version < NOTIFY_IPC_VERSION_MIN_SUPPORTED) {
			NOTIFY_INTERNAL_CRASH(version, "Unsupported protocol version");
		}
		globals->notify_server_pid = new_pid;

		if ((new_pid == last_pid) && (event == EVENT_REGEN))
		{
			return NOTIFY_STATUS_NO_REGEN_NEEDED;
		}

		if (globals->server_proc_source != NULL)
		{
			dispatch_source_cancel(globals->server_proc_source);
			dispatch_release(globals->server_proc_source);
			globals->server_proc_source = NULL;
		}

		if (MACH_PORT_VALID(globals->notify_common_port)) {
			if (globals->notify_dispatch_source) {
				dispatch_source_cancel(globals->notify_dispatch_source);
				dispatch_release(globals->notify_dispatch_source);
				globals->notify_dispatch_source = NULL;
			}
			globals->notify_common_port = MACH_PORT_NULL;
		}
	}


	/*
	 * Create a source (DISPATCH_SOURCE_TYPE_PROC) to invoke _notify_lib_regenerate if notifyd restarts.
	 * Available in IPC version 2.
	 */
	if ((globals->server_proc_source == NULL) && (client_opts(globals) & NOTIFY_OPT_REGEN) && (globals->notify_server_pid != 0))
	{
		globals->server_proc_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)globals->notify_server_pid, DISPATCH_PROC_EXIT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
		dispatch_source_set_event_handler_f(globals->server_proc_source, _notify_lib_server_restart_handler);
		dispatch_resume(globals->server_proc_source);
	}

	/*
	 * Create the shared multiplex port if NOTIFY_OPT_DISPATCH is set.
	 */
	if ((client_opts(globals) & NOTIFY_OPT_DISPATCH) &&
	    ((globals->notify_common_port == MACH_PORT_NULL) || (globals->notify_common_port == MACH_PORT_DEAD)))
	{
		mach_port_t mp;

		// kstatus is the status of the mig call and status is the status of the server-side routine
		kstatus = _notify_generate_common_port(globals->notify_server_port, &status, &mp);
		if (kstatus != KERN_SUCCESS)
		{
		    if (kstatus == MACH_SEND_INVALID_DEST) {
			status = NOTIFY_STATUS_SERVER_NOT_FOUND;
		    } else {
			status = NOTIFY_STATUS_SERVER_CHECKIN_FAILED;
		    }
		}
		if (status != NOTIFY_STATUS_OK)
		{
			return status;
		}

		globals->notify_common_port = mp;

		globals->notify_dispatch_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, mp, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
		dispatch_set_context(globals->notify_dispatch_source, globals);
		dispatch_source_set_event_handler_f(globals->notify_dispatch_source, _notify_dispatch_handle);
		dispatch_source_set_cancel_handler(globals->notify_dispatch_source, ^{
			mach_port_destruct(mach_task_self(), mp, 0, 0);
		});

		dispatch_resume(globals->notify_dispatch_source);
	}

	return NOTIFY_STATUS_OK;
}

static uint32_t
_notify_lib_init(notify_globals_t globals, uint32_t event) {
	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);
	uint32_t ret = _notify_lib_init_locked(globals, event);
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
	return ret;
}

/* Reset all internal state at fork */
/* Used in Libsystem without header declaration */
void
_notify_fork_child(void)
{
	notify_globals_t globals = _notify_globals();

	_notify_init_globals(globals);

	/*
	 * Expressly disable notify in the child side of a fork if it had
	 * been initialized in the parent. Using notify in the child process
	 * can lead to deadlock (see <rdar://problem/11498014>).
	 *
	 * Also disable notify in the forked child of a multi-threaded parent that
	 * used dispatch, since notify will use dispatch, and that will blow up.
	 */
	if (globals->notify_server_port != MACH_PORT_NULL ||
		_dispatch_is_fork_of_multithreaded_parent())
	{
		os_atomic_store(&globals->client_opts, NOTIFY_OPT_DISABLE, relaxed);
	}

	_notify_lib_notify_state_init(&globals->self_state, NOTIFY_STATE_USE_LOCKS);
	globals->notify_server_port = MACH_PORT_NULL;
	globals->notify_server_pid = 0;

	globals->fd_count = 0;
	globals->fd_clnt = NULL;
	globals->fd_srv = NULL;
	globals->fd_refcount = NULL;

	globals->mp_size = 0;
	globals->mp_count = 0;
	globals->mp_list = NULL;

	globals->shm_base = NULL;
}

/*
 * Create the registration structure associated with a client's token.
 * Will create a name_node_t structure for the name if one does not exist.
 * Note that this routine is NOT used to create the "base" for non-polled
 * coalesced registrations.  That's done by base_registration_create().
 */
static uint32_t
client_registration_create_base( __attribute__((nonnull)) const char * name, uint64_t nid, uint32_t token, uint32_t cid, uint32_t slot, uint32_t flags, int sig, int fd, mach_port_t mp, bool create_base)
{
	name_node_t *name_node = NULL;
	registration_node_t *reg_node;
	uint32_t warn_count = 0;
	notify_globals_t globals = _notify_globals();

	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);

	/* should never happen, but check if the registration exists */
	reg_node = _nc_table_find_n(&globals->registration_table, token);
	if (reg_node != NULL) goto client_registration_create_fail;

	name_node = name_node_for_name_locked(globals, name, nid, true);
	if (name_node == NULL) goto client_registration_create_fail;
	mutex_lock(name_node->name, &name_node->lock, __func__, __LINE__);

	reg_node = (registration_node_t *)calloc(1, sizeof(registration_node_t));
	if (reg_node == NULL)
	{
#ifdef DEBUG
		_notify_client_log(ASL_LEVEL_ERR, "client_registration_create name %s calloc failed errno %d [%s]\n", name, errno, strerror(errno));
#endif
		name_node_unlock_and_release(name_node);
		goto client_registration_create_fail;
	}

	os_atomic_store(&reg_node->refcount, 1, relaxed);
	reg_node->token = token;
	_nc_table_insert_n(&globals->registration_table, &reg_node->token);

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_NODES) _notify_client_log(ASL_LEVEL_NOTICE, "client_registration_create token %u refcount %d -> %p", token, reg_node->refcount, reg_node);
#endif

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_REGISTRATION)
	{
		if (nid == NID_UNSET) _notify_client_log(ASL_LEVEL_NOTICE, "client_registration_create reg_node %p name %s name_node %p nid NID_UNSET token %u flags 0x%08x", reg_node, name, name_node, token, flags);
		else if (nid == NID_CALLED_ONCE) _notify_client_log(ASL_LEVEL_NOTICE, "client_registration_create reg_node %p name %s name_node %p nid NID_CALLED_ONCE token %u flags 0x%08x", reg_node, name, name_node, token, flags);
		else _notify_client_log(ASL_LEVEL_NOTICE, "client_registration_create reg_node %p name %s name_node %p nid %llu token %u flags 0x%08x", reg_node, name, name_node, nid, token, flags);
	}
#endif

	/* no need to regenerate a coalesced registration */
	if (flags & NOTIFY_FLAG_COALESCED) flags &= ~NOTIFY_FLAG_REGEN;

	reg_node->flags = flags;
	reg_node->slot = slot;
	reg_node->val = 0;
	reg_node->fd = fd;
	reg_node->mp = mp;
	reg_node->signal_or_xtra_mp = sig;
	reg_node->client_id = cid;

	/*
	 * Registration nodes retain their name node, so in theory we should
	 * retain name_node here.  However, name_node_for_name() retains
	 * the name node, so we just make the assignment here and skip
	 * calling name_node_release() before returning from this routine.
	 */
	reg_node->name_node = name_node;

	/*
	 * If dispatch is available, add this registration to the name node's dispatch list.
	 * Doesn't apply to polled types (memory and plain).
	 */
	if ((client_opts(globals) & NOTIFY_OPT_DISPATCH) && (flags & NOTIFY_FLAG_COALESCED))
	{
		bool delivered = notify_is_type(flags, NOTIFY_TYPE_PORT) ||
				notify_is_type(flags, NOTIFY_TYPE_FILE) ||
				notify_is_type(flags, NOTIFY_TYPE_SIGNAL) ||
				notify_is_type(flags, NOTIFY_TYPE_XPC_EVENT) ||
				notify_is_type(flags, NOTIFY_TYPE_COMMON_PORT);
		if (delivered) {
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_REGISTRATION) _notify_client_log(ASL_LEVEL_NOTICE, "Add coalesced registration %p to name %s name node %p base %p", reg_node, name, name_node, name_node->coalesce_base);
#endif
			name_node_add_coalesced_registration_locked(name_node, reg_node, create_base);
		}
	}

	warn_count = os_atomic_load(&name_node->refcount, relaxed);
    
	if ((!name_node->has_been_warned) && (warn_count == MULTIPLE_REGISTRATION_WARNING_TRIGGER))
	{
		name_node->has_been_warned = true;
		REPORT_BAD_BEHAVIOR("notify name \"%s\" has been registered %d times - this may be a leak", name, warn_count);
	}

	mutex_unlock(name_node->name, &name_node->lock, __func__, __LINE__);
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
	return NOTIFY_STATUS_OK;

client_registration_create_fail:
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
	return NOTIFY_STATUS_CLIENT_REG_FAILED;
}

static uint32_t
client_registration_create(const char *name, uint64_t nid, uint32_t token, uint32_t cid, uint32_t slot, uint32_t flags, int sig, int fd, mach_port_t mp)
{
	return client_registration_create_base(name, nid, token, cid, slot, flags, sig, fd, mp, false);
}

/*
 * N.B. base_registration_create is called from notify_register_mach_port(), which holds the global lock
 */
static uint32_t
base_registration_create_locked(notify_globals_t globals, __attribute__((nonnull))  const char *name, uint64_t nid, uint32_t token, uint32_t flags, mach_port_t mp)
{
	os_unfair_lock_assert_owner(&globals->notify_lock);

	name_node_t *name_node = NULL;
	registration_node_t *reg_node;

	/* should never happen, but check if the registration exists */
	reg_node = _nc_table_find_n(&globals->registration_table, token);
	if (reg_node != NULL) return NOTIFY_STATUS_DOUBLE_REG;

	reg_node = (registration_node_t *)calloc(1, sizeof(registration_node_t));
	if (reg_node == NULL)
	{
#ifdef DEBUG
		_notify_client_log(ASL_LEVEL_ERR, "base_registration_create name %s calloc failed errno %d [%s]\n", name, errno, strerror(errno));
#endif
		return NOTIFY_STATUS_ALLOC_FAILED;
	}

	name_node = name_node_for_name_locked(globals, name, nid, true);
	if (name_node == NULL)
	{
		free(reg_node);
		return NOTIFY_STATUS_NEW_NAME_FAILED;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_REGISTRATION)
	{
		if (nid == NID_UNSET) _notify_client_log(ASL_LEVEL_NOTICE, "%s reg_node %p name %s name_node %p nid NID_UNSET token %u flags 0x%08x", __func__, reg_node, name, name_node, token, flags);
		else if (nid == NID_CALLED_ONCE) _notify_client_log(ASL_LEVEL_NOTICE, "%s reg_node %p name %s name_node %p nid NID_CALLED_ONCE token %u flags 0x%08x", __func__, reg_node, name, name_node, token, flags);
		else _notify_client_log(ASL_LEVEL_NOTICE, "%s reg_node %p name %s name_node %p nid %llu token %u flags 0x%08x", __func__, reg_node, name, name_node, nid, token, flags);
	}
#endif

	os_atomic_store(&reg_node->refcount, 1, relaxed);
	reg_node->token = token;
	reg_node->client_id = token;
	reg_node->flags = flags | NOTIFY_FLAG_COALESCE_BASE;
	reg_node->mp = mp;
	reg_node->val = 0;
	reg_node->slot = SLOT_NONE;
	reg_node->fd = FD_NONE;
	reg_node->signal_or_xtra_mp = SIGNAL_NONE;
	reg_node->name_node = name_node;

	/*
	 * Registration nodes retain their name node, so in theory we should
	 * retain name_node here.  However, name_node_for_name() retains
	 * the name node, so we just make the assignment here and skip
	 * calling name_node_release() before returning from this routine.
	 */
	name_node->coalesce_base = reg_node;
	name_node->coalesce_base_token = token;

	_nc_table_insert_n(&globals->registration_table, &reg_node->token);
	return NOTIFY_STATUS_OK;
}

static bool
check_name_access(char *name, uid_t uid)
{
	char str[64];
	size_t len;

	/* root may do anything */
	if (uid == 0) return true;

	/* if name does not have "user.uid." as a prefix, it is not a user-protected namespace */
	if (strncmp(name, USER_PROTECTED_UID_PREFIX, USER_PROTECTED_UID_PREFIX_LEN))
	{
		return true;
	}

	snprintf(str, sizeof(str) - 1, "%s%d", USER_PROTECTED_UID_PREFIX, uid);
	len = strlen(str);

	/* user <uid> may access user.uid.<uid> or a subtree name */
	return ((!strncmp(name, str, len)) && ((name[len] == '\0') || (name[len] == '.')));

}

static void
_notify_lib_regenerate_registration(registration_node_t *r)
{
	uint32_t type;
	int status;
	int new_slot = SLOT_NONE;
	kern_return_t kstatus;
	uint64_t new_nid = NID_UNSET;
	size_t pathlen;
	char *name = r->name_node->name;

	notify_globals_t globals = _notify_globals();

	if (r->flags & NOTIFY_FLAG_SELF) return;
	if ((r->flags & NOTIFY_FLAG_REGEN) == 0) return;

	if(!check_name_access(name, geteuid()))
	{
		REPORT_BAD_BEHAVIOR("BUG IN LIBNOTIFY CLIENT: registration held for restricted name %s with process uid %d",
				    name, geteuid());
	}

	pathlen = 0;
	if (r->path != NULL) pathlen = strlen(r->path) + 1;
	type = r->flags & NOTIFY_TYPE_MASK;

	int remaining_retries = NOTIFY_SERVER_RETRY_NUMBER;
	do {
		kstatus = _notify_server_regenerate(globals->notify_server_port, (caddr_t)name, r->token, type, MACH_PORT_NULL, (type == NOTIFY_TYPE_SIGNAL) ? r->signal_or_xtra_mp : 0,
				r->slot, r->set_state_val, r->set_state_time, r->path, (mach_msg_type_number_t)pathlen,
				r->path_flags, &new_slot, &new_nid, &status);
		if (kstatus == KERN_SUCCESS) {
			break;
		}
		usleep(NOTIFY_SERVER_RETRY_WAIT_US);
	} while (remaining_retries-- > 0);

	assert(kstatus == KERN_SUCCESS);

	if(status != NOTIFY_STATUS_OK && status != NOTIFY_STATUS_DUP_CLIENT &&
	   status != NOTIFY_STATUS_NO_REGEN_NEEDED)
	{
		REPORT_BAD_BEHAVIOR("Libnotify: _notify_server_regnerate failed for name %s with status %d", name, status);
	}


	r->slot = new_slot;
	r->name_node->name_id = new_nid;
}

/*
 * Invoked when server has died.
 * Regenerates all registrations and state.
 */
static uint32_t
_notify_lib_regenerate_locked(notify_globals_t globals)
{
	uint32_t result;

	if ((client_opts(globals) & NOTIFY_OPT_REGEN) == 0) return NOTIFY_STATUS_OK;

	/* if _notify_lib_init_lockec returns an error, regeneration is unnecessary */
	result = _notify_lib_init_locked(globals, EVENT_REGEN);

	if (result == NOTIFY_STATUS_NO_REGEN_NEEDED) {
		return NOTIFY_STATUS_OK;
	}

	if (result == NOTIFY_STATUS_OK) {
		_nc_table_foreach_n(&globals->registration_table, ^bool(void *reg) {
			_notify_lib_regenerate_registration(reg);
			return true;
		});
	}

	return result;
}

/*
 * Invoked when server has died.
 * Regenerates all registrations and state.
 */
static void
_notify_lib_server_restart_handler(void *ctxt __unused)
{
	notify_globals_t globals = _notify_globals();
	
	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);
	(void)_notify_lib_regenerate_locked(globals);
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);

	return;
}

/*
 * Get the up to date shm_base if the server PID (shared memory slot 0)
 * has changed.
 */
static inline uint32_t
regenerate_check(notify_globals_t globals)
{
	uint32_t result = NOTIFY_STATUS_OK;

	if ((client_opts(globals) & NOTIFY_OPT_REGEN) == 0) return NOTIFY_STATUS_OK;

	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);
	if ((globals->shm_base != NULL) && ((pid_t)globals->shm_base[0] != globals->notify_server_pid))
	{
		result = _notify_lib_regenerate_locked(globals);
	}
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);

	return result;
}

/* notify_lock is required in notify_retain_file_descriptor */
static void
notify_retain_file_descriptor(int clnt, int srv)
{
	uint32_t x, i;

	notify_globals_t globals = _notify_globals();

	if (clnt < 0) return;
	if (srv < 0) return;

	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);

	x = SLOT_NONE;
	for (i = 0; (i < globals->fd_count) && (x == SLOT_NONE); i++)
	{
		if (globals->fd_clnt[i] == clnt) x = i;
	}

	if (x != SLOT_NONE)
	{
		globals->fd_refcount[x]++;
		mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
		return;
	}

	x = globals->fd_count;
	globals->fd_count++;

	globals->fd_clnt = (int *)reallocf(globals->fd_clnt, globals->fd_count * sizeof(int));
	globals->fd_srv = (int *)reallocf(globals->fd_srv, globals->fd_count * sizeof(int));
	globals->fd_refcount = (int *)reallocf(globals->fd_refcount, globals->fd_count * sizeof(int));

	if ((globals->fd_clnt == NULL) || (globals->fd_srv == NULL) || (globals->fd_refcount == NULL))
	{
#ifdef DEBUG
		_notify_client_log(ASL_LEVEL_ERR, "notify_retain_file_descriptor reallocf failed errno %d [%s]\n", errno, strerror(errno));
#endif
		free(globals->fd_clnt);
		globals->fd_clnt = NULL;
		free(globals->fd_srv);
		globals->fd_srv = NULL;
		free(globals->fd_refcount);
		globals->fd_refcount = NULL;
		globals->fd_count = 0;
	}
	else
	{
		globals->fd_clnt[x] = clnt;
		globals->fd_srv[x] = srv;
		globals->fd_refcount[x] = 1;
	}

	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
}

// must be called with the global lock held
static void
notify_release_file_descriptor_locked(notify_globals_t globals, int fd)
{
	os_unfair_lock_assert_owner(&globals->notify_lock);

	uint32_t x, i, j;

	if (fd < 0) return;

	x = SLOT_NONE;
	for (i = 0; (i < globals->fd_count) && (x == SLOT_NONE); i++)
	{
		if (globals->fd_clnt[i] == fd) x = i;
	}

	if (x == SLOT_NONE)
	{
		return;
	}

	if (globals->fd_refcount[x] > 0) globals->fd_refcount[x]--;
	if (globals->fd_refcount[x] > 0)
	{
		return;
	}

	close(globals->fd_clnt[x]);
	close(globals->fd_srv[x]);

	if (globals->fd_count == 1)
	{
		free(globals->fd_clnt);
		globals->fd_clnt = NULL;
		free(globals->fd_srv);
		globals->fd_srv = NULL;
		free(globals->fd_refcount);
		globals->fd_refcount = NULL;
		globals->fd_count = 0;
		return;
	}

	for (i = x + 1, j = x; i < globals->fd_count; i++, j++)
	{
		globals->fd_clnt[j] = globals->fd_clnt[i];
		globals->fd_srv[j] = globals->fd_srv[i];
		globals->fd_refcount[j] = globals->fd_refcount[i];
	}

	globals->fd_count--;

	globals->fd_clnt = (int *)reallocf(globals->fd_clnt, globals->fd_count * sizeof(int));
	globals->fd_srv = (int *)reallocf(globals->fd_srv, globals->fd_count * sizeof(int));
	globals->fd_refcount = (int *)reallocf(globals->fd_refcount, globals->fd_count * sizeof(int));

	if ((globals->fd_clnt == NULL) || (globals->fd_srv == NULL) || (globals->fd_refcount == NULL))
	{
#ifdef DEBUG
		_notify_client_log(ASL_LEVEL_ERR, "notify_release_file_descriptor reallocf failed errno %d [%s]\n", errno, strerror(errno));
#endif
		free(globals->fd_clnt);
		globals->fd_clnt = NULL;
		free(globals->fd_srv);
		globals->fd_srv = NULL;
		free(globals->fd_refcount);
		globals->fd_refcount = NULL;
		globals->fd_count = 0;
	}

}

/* notify_lock is required in notify_retain_mach_port */
static void
notify_retain_mach_port(notify_globals_t globals, mach_port_t mp, int flags)
{
	if (mp == MACH_PORT_NULL) return;
	if (flags & _NOTIFY_COMMON_PORT) return;

	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);

	struct mp_entry *entries = globals->mp_list;

	for (uint32_t i = 0; i < globals->mp_count; i++)
	{
		if (entries[i].mpl_port_name == mp) {
			entries[i].mpl_refs++;
			mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
			return;
		}
	}

	uint32_t x = globals->mp_count;
	globals->mp_count++;

	if (x >= globals->mp_size) {
		if (globals->mp_size < 4) {
			globals->mp_size = 4;
		} else {
			globals->mp_size *= 2;
		}
		entries = reallocf(entries, globals->mp_size * sizeof(struct mp_entry));
		globals->mp_list = entries;
		if (os_unlikely(entries == NULL)) {
			NOTIFY_CLIENT_CRASH(0, "Unable to allocate port array: "
					"possible notification registration leak");
		}
	}

	entries[x].mpl_port_name = mp;
	entries[x].mpl_refs = 1;
	entries[x].mpl_mine = ((flags & NOTIFY_REUSE) == 0);

	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
}

// must be called with the global lock held
static void
notify_release_mach_port_locked(notify_globals_t globals, mach_port_t mp, uint32_t flags)
{
	struct mp_entry *entries = globals->mp_list;
	uint32_t x = SLOT_NONE;

	os_unfair_lock_assert_owner(&globals->notify_lock);

	if (mp == MACH_PORT_NULL) return;
	if (mp == globals->notify_common_port) return;

	for (uint32_t i = 0; i < globals->mp_count; i++)
	{
		if (entries[i].mpl_port_name == mp) {
			x = i;
			break;
		}
	}

	if (x == SLOT_NONE)
	{
		return;
	}

	if (entries[x].mpl_refs > 1) {
		entries[x].mpl_refs--;
		return;
	}

	if (entries[x].mpl_mine)
	{
		int uref_mod = 0;
		if (flags & NOTIFY_FLAG_RELEASE_SEND) uref_mod = -1;
		mach_port_destruct(mach_task_self(), mp, uref_mod, 0);
	}
	else if (flags & NOTIFY_FLAG_RELEASE_SEND)
	{
		/* multiplexed registration holds a send right in Libnotify */
		mach_port_deallocate(mach_task_self(), mp);
	}

	// swap removed element with the last one in the array
	globals->mp_count--;
	if (x != globals->mp_count) {
		entries[x] = entries[globals->mp_count];
	}

	if (globals->mp_count == 0)
	{
		free(entries);
		globals->mp_count = globals->mp_size = 0;
		globals->mp_list = NULL;
	}
	else if (globals->mp_size > 4 && globals->mp_count <= globals->mp_size / 4)
	{
		entries = realloc(entries, sizeof(struct mp_entry) * globals->mp_size / 2);
		if (entries) {
			globals->mp_list = entries;
			globals->mp_size /= 2;
		}
	}
}

/* SPI */
void
notify_set_options(uint32_t opts)
{
	notify_globals_t globals = _notify_globals();

	/* NOTIFY_OPT_DISABLE can be unset with NOTIFY_OPT_ENABLE */
	if (client_opts(globals) & NOTIFY_OPT_DISABLE)
	{
		if (!(opts & NOTIFY_OPT_ENABLE))
		{
			globals->saved_opts |= opts;
			return;
		}
		
		/* re-enable by swapping in the saved server port and saved opts*/
		mutex_lock("global", &globals->notify_lock, __func__, __LINE__);

		os_atomic_store(&globals->client_opts, globals->saved_opts, relaxed);
		globals->notify_server_port = globals->saved_server_port;

		mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
		return;
	}

	/*
	 * A client can disable the library even if the server port has already been fetched.
	 * Note that this could race with another thread making a Libnotify call.
	 */
	if (opts & NOTIFY_OPT_DISABLE)
	{
		mutex_lock("global", &globals->notify_lock, __func__, __LINE__);

		globals->saved_opts = os_atomic_xchg(&globals->client_opts, NOTIFY_OPT_DISABLE, relaxed);

		globals->saved_server_port = globals->notify_server_port;
		globals->notify_server_port = MACH_PORT_NULL;

		mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
		return;
	}

	os_atomic_or(&globals->client_opts, opts, relaxed);

	/* call _notify_lib_init to create ports / dispatch sources as required */
	_notify_lib_init(globals, EVENT_INIT);
}

/**
 * Check if process has root access entitlement and should claim root access
 * from notifyd.
 */
static bool
should_claim_root_access(void)
{
	static dispatch_once_t once;
	static bool has_root_entitlement;
	dispatch_once(&once, ^{
		xpc_object_t entitlement = xpc_copy_entitlement_for_token(ROOT_ENTITLEMENT_KEY, NULL);
		if (entitlement == XPC_BOOL_TRUE) has_root_entitlement = true;
		if (entitlement) xpc_release(entitlement);
	});
	return has_root_entitlement;
}

/*
 * PUBLIC API
 */



/*
 * notify_post is a very simple API, but the implementation is
 * more complex to try to optimize the time it takes.
 *
 * The server - notifyd - keeps a unique ID number for each key
 * in the namespace.  Although it's reasonably fast to call
 * _notify_server_post_4 (a MIG simpleroutine), the MIG call
 * allocates VM and copies the name string.  It's much faster to
 * call using the ID number.  The problem is mapping from name to
 * ID number.  The name table keeps track of all registered names
 * (in the client), but the registration calls are simpleroutines,
 * except for notify_register_check.  notify_register_check saves
 * the name ID in the name table, but the other routines set it
 * to NID_UNSET.
 *
 * In notify_post, we check if the name is known.  If it is not,
 * then the client is doing a "cold call".  There may be no
 * clients for this name anywhere on the system.  In this case
 * we simply send the name.  We take the allocate/copy cost, but
 * the latency is still not too bad since we use a simpleroutine.
 *
 * If the name in registered and the ID number is known, we send
 * the ID using a simpleroutine.  This is very fast.
 *
 * If the name is registered but the ID number is NID_UNSET, we
 * send the name (as in a "cold call".  It *might* just be that
 * this client process just posts once, and we don't want to incur
 * any addition cost.  The ID number is reset to NID_CALLED_ONCE.
 *
 * If the client posts the same name again (the ID number is
 * NID_CALLED_ONCE) we do a synchronous call to notifyd, sending
 * the name string and getting back the name ID, whcih we save
 * in the name table.  This is simply a zero/one/many heuristic:
 * If the client posts the same name more than once, we make the
 * guess that it's going to do it more frequently, and it's worth
 * the time it takes to fetch the ID from notifyd.
 */
uint32_t
notify_post(const char *name)
{
	NOTIFY_POST(name);
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	kern_return_t kstatus;
	uint32_t status;
	name_node_t *n;
	uint64_t nid = UINT64_MAX;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}


	if (name == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_NAME;
	}

	if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN))
	{
		_notify_lib_post(&globals->self_state, name, 0, 0);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_OK;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	/* See if we have a name ID for this name. */
	n = name_node_for_name(name, NID_UNSET, false);
	if (n != NULL)
	{
        mutex_lock(n->name, &n->lock, __func__, __LINE__);
		if (n->name_id == NID_UNSET)
		{
			/* First post goes using the name string. */
			kstatus = _notify_server_post_4(globals->notify_server_port, (caddr_t)name, should_claim_root_access());
			if (kstatus != KERN_SUCCESS)
			{
				name_node_unlock_and_release(n);
#ifdef DEBUG
				if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif

				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
						NOTIFY_STATUS_SERVER_POST_4_FAILED, kstatus, __LINE__);
				return NOTIFY_STATUS_FAILED;
			}

			name_node_set_nid_locked(n, NID_CALLED_ONCE);
		}
		else if (n->name_id == NID_CALLED_ONCE)
		{
			status = NOTIFY_STATUS_SERVER_POST_2_FAILED;
			/* Post and fetch the name ID.  Slow, but subsequent posts will be very fast. */
			kstatus = _notify_server_post_2(globals->notify_server_port, (caddr_t)name, &nid, (int32_t *)&status, should_claim_root_access());
			if (kstatus != KERN_SUCCESS || (status != NOTIFY_STATUS_OK && status != NOTIFY_STATUS_NO_NID))
			{
				name_node_unlock_and_release(n);
#ifdef DEBUG
				if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
						    status, kstatus, __LINE__);
				return NOTIFY_STATUS_FAILED;
			}

			if (status == NOTIFY_STATUS_NO_NID) {
				// This means that notifyd doesn't have a nid for
				// this name. By setting this to NID_UNSET, the
				// next post will be by name, and then the one
				// after that will attempt to query the nid again.
				name_node_set_nid_locked(n, NID_UNSET);
			} else {
				name_node_set_nid_locked(n, nid);
			}
		}
		else
		{
			/* We have the name ID.  Do an async post using the name ID.  Very fast. */
			kstatus = _notify_server_post_3(globals->notify_server_port, n->name_id, should_claim_root_access());
			if (kstatus != KERN_SUCCESS)
			{
				name_node_unlock_and_release(n);
#ifdef DEBUG
				if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
						    NOTIFY_STATUS_SERVER_POST_3_FAILED, kstatus, __LINE__);
				return NOTIFY_STATUS_FAILED;
			}
		}

		name_node_unlock_and_release(n);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_OK;
	}

	/* Do an async post using the name string. Fast (but not as fast as using name ID). */
	kstatus = _notify_server_post_4(globals->notify_server_port, (caddr_t)name, should_claim_root_access());
	if (kstatus != KERN_SUCCESS)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
				NOTIFY_STATUS_SERVER_POST_4_FAILED, kstatus, __LINE__);
		return NOTIFY_STATUS_FAILED;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
}



static void
_notify_dispatch_local_notification(registration_node_t *r)
{
	if (r == NULL) return;
	if (r->queue == NULL) return;
	if (r->block == NULL) return;

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_NOTIFICATION)
	{
		if (r->flags & NOTIFY_FLAG_COALESCED) _notify_client_log(ASL_LEVEL_NOTICE, "coalesced notification for token %d (name %s)", r->token, r->name_node->name);
		else if (r->flags & NOTIFY_FLAG_COALESCE_BASE) _notify_client_log(ASL_LEVEL_NOTICE, "coalesced base notification for token %d (name %s)", r->token, r->name_node->name);
		else _notify_client_log(ASL_LEVEL_NOTICE, "dispatch notification for token %d (name %s)", r->token, r->name_node->name);
	}
#endif

	if (r->flags & NOTIFY_FLAG_SUSPENDED)
	{
		r->flags |= NOTIFY_FLAG_DEFERRED_POST;
		return;
	}

	/*
	 * If the block calls notify_cancel, the node can get trashed, so
	 * we keep anything we need from the block (properly retained and released)
	 * in local variables.  Concurrent notify_cancel() calls in the block are safe.
	 */
	int token = r->token;
	notify_handler_t theblock = Block_copy(r->block);
	dispatch_queue_t thequeue = r->queue;
	dispatch_retain(thequeue);

	/*
	 * If DTrace probes are currently active that deal with the delivery of a
	 * notification, then the name needs to live until the end of the delivery.
	 * It is freed after delivery.
	 */
	char *name = NULL;
	if ((NOTIFY_DELIVER_START_ENABLED() || NOTIFY_DELIVER_END_ENABLED()) && r && r->name_node && r->name_node->name)
	{
		name = strdup(r->name_node->name);
	}

	// notify_dispatch_source runs on a DISPATCH_QUEUE_PRIORITY_HIGH queue (IN
	// QoS). Dispatching directly from that queue to the client queue taints the
	// block with IN qos. Instead we create a detached wrapper and let the block
	// run with the priority of the target queue hierachy. This is the same
	// behaviour as with dispatch sources.
	// <rdar://problem/38438633>
	dispatch_block_t detached_block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
		bool valid = notify_is_valid_token(token);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_NOTIFICATION) _notify_client_log(ASL_LEVEL_NOTICE, "-> dispatch_async token %d (%svalid) registration node %p", token, valid ? "" : "in", r);
#endif
		/* check if the token is still valid: it may have been cancelled */
		if (name) NOTIFY_DELIVER_START(name);
		if (valid) theblock(token);
		if (name) NOTIFY_DELIVER_END(name);
		_Block_release(theblock);
		dispatch_release(thequeue);

		if (name) free(name);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_NOTIFICATION) _notify_client_log(ASL_LEVEL_NOTICE, "<- dispatch_async token %d", token);
#endif
	});

	dispatch_async(thequeue, detached_block);
	Block_release(detached_block);
}

static void
_notify_dispatch_handle(void *context)
{
	notify_globals_t globals = context;
	mach_port_t port = globals->notify_common_port;
	name_node_t *n;
	registration_node_t *r;
	int token;
	mach_msg_empty_rcv_t msg;
	kern_return_t status;

	if (port == MACH_PORT_NULL) return;

	for (int retries = 5; retries-- > 0; ) {
		memset(&msg, 0, sizeof(msg));

		/*
		 * The dispatch source watching our multiplexed mach port has fired.
		 * Read the message that is waiting and get the token ID from the mach header.
		 */
		status = mach_msg(&msg.header, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, sizeof(msg), port, 0, MACH_PORT_NULL);
		if (status != KERN_SUCCESS) return;

		token = msg.header.msgh_id;

#ifdef DEBUG
		if (_libnotify_debug & DEBUG_NOTIFICATION) _notify_client_log(ASL_LEVEL_NOTICE, "_notify_dispatch_handle token %d", token);
#endif

		r = registration_node_find(token);
		if (r == NULL) continue;

		n = r->name_node;
		if (n == NULL)
		{
			/* should not happen */
			registration_node_release(r);
			continue;
		}

		mutex_lock(n->name, &n->lock, __func__, __LINE__);

		if (r->flags & NOTIFY_FLAG_COALESCE_BASE)
		{
			registration_node_t *x;
			TAILQ_FOREACH(x, &n->coalesced, registration_coalesced_entry)
			{
				if (x != r) _notify_dispatch_local_notification(x);
			}
		}
		else
		{
			_notify_dispatch_local_notification(r);
		}

		mutex_unlock(n->name, &n->lock, __func__, __LINE__);

		registration_node_release(r);
	}
}

#pragma mark -
#pragma mark registration

static uint32_t
_notify_register_dispatch_with_extra_mp(const char *name, int *out_token, dispatch_queue_t queue, notify_handler_t handler, mach_port_t extra_mp)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	uint32_t status;
	registration_node_t *r;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (queue == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_NULL_INPUT;
	}

	if (handler == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_NULL_INPUT;
	}

	/* client is using dispatch: enable local demux / dispatch and regeneration */
	notify_set_options(NOTIFY_OPT_DISPATCH | NOTIFY_OPT_REGEN);

	status = notify_register_coalesced_registration(name, NOTIFY_REUSE | _NOTIFY_COMMON_PORT, out_token, globals, extra_mp);
	if (status != NOTIFY_STATUS_OK)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	r = registration_node_find(*out_token);
	if (r == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, NOTIFY_STATUS_TOKEN_NOT_FOUND, __LINE__);
		return NOTIFY_STATUS_FAILED;
	}

	r->queue = queue;
	dispatch_retain(r->queue);
	r->block = Block_copy(handler);

	registration_node_release(r);

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
}

uint32_t
notify_register_dispatch(const char *name, int *out_token, dispatch_queue_t queue, notify_handler_t handler)
{
	return _notify_register_dispatch_with_extra_mp(name, out_token, queue, handler, MACH_PORT_NULL);
}

/* note this does not get self names */
static uint32_t
notify_register_mux_fd(const char *name, int *out_token, int rfd, int wfd)
{
	uint32_t status;
	registration_node_t *r;
	int val;
	notify_globals_t globals = _notify_globals();

	if (globals->notify_common_port == MACH_PORT_NULL) return NOTIFY_STATUS_COMMON_PORT_NULL;

	status = notify_register_coalesced_registration(name, NOTIFY_REUSE | _NOTIFY_COMMON_PORT, out_token, globals, MACH_PORT_NULL);
	
	if (status != NOTIFY_STATUS_OK)
	{
		return status;
	}
	r = registration_node_find(*out_token);
	if (r == NULL)
	{
		return NOTIFY_STATUS_TOKEN_NOT_FOUND;
	}

	r->token = *out_token;
	r->fd = rfd;
	r->queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
	dispatch_retain(r->queue);
	val = htonl(r->token);
	r->block = (notify_handler_t)Block_copy(^(int unused){ write(wfd, &val, sizeof(val)); });

	registration_node_release(r);

	return NOTIFY_STATUS_OK;
}

uint32_t
notify_register_check(const char *name, int *out_token)
{
#if TARGET_OS_SIMULATOR
	return notify_register_plain(name, out_token);
#else
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	kern_return_t kstatus;
	uint32_t status, token;
	uint64_t nid;
	int slot;
	int32_t shmsize;
	uint32_t cid;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (name == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_NAME;
	}

	if (out_token == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_NULL_INPUT;
	}

	*out_token = -1;

	if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN))
	{
		token = atomic_increment32(&globals->token_id);
		status = _notify_lib_register_plain(&globals->self_state, name, NOTIFY_CLIENT_SELF, token, SLOT_NONE, 0, 0, &nid);
		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}

		cid = token;

		status = client_registration_create(name, nid, token, cid, SLOT_NONE,
					NOTIFY_FLAG_SELF | NOTIFY_TYPE_PLAIN, SIGNAL_NONE, FD_NONE, MACH_PORT_NULL);

		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}

		*out_token = token;
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_OK;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	token = atomic_increment32(&globals->token_id);
	cid = token;
	kstatus = _notify_server_register_check_2(globals->notify_server_port, (caddr_t)name, token, &shmsize, &slot, &nid, (int32_t *)&status);

	if (kstatus != KERN_SUCCESS)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif

		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
				NOTIFY_STATUS_REG_CHECK_2_FAILED, kstatus, __LINE__);
		return NOTIFY_STATUS_FAILED;
	}

	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (shmsize != -1)
	{
		mutex_lock("global", &globals->notify_lock, __func__, __LINE__);
		if (globals->shm_base == NULL)
		{
			if (!shm_attach(shmsize))
			{
#ifdef DEBUG
				if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
				mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
				return NOTIFY_STATUS_FAILED;
			}

			if (globals->shm_base == NULL)
			{
#ifdef DEBUG
				if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
				mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__,
							NOTIFY_STATUS_SHM_BASE_REMAINS_NULL, __LINE__);
				return NOTIFY_STATUS_FAILED;
			}
		}
		mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);

		status = client_registration_create(name, nid, token, cid, slot,
				NOTIFY_TYPE_MEMORY | NOTIFY_FLAG_REGEN, SIGNAL_NONE, FD_NONE, MACH_PORT_NULL);
	}
	else
	{
		status = client_registration_create(name, nid, token, cid, SLOT_NONE,
				NOTIFY_TYPE_PLAIN | NOTIFY_FLAG_REGEN, SIGNAL_NONE, FD_NONE, MACH_PORT_NULL);
	}

	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	*out_token = token;
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
#endif /* TARGET_OS_SIMULATOR */
}

uint32_t
notify_register_plain(const char *name, int *out_token)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	kern_return_t kstatus;
	uint32_t status;
	uint64_t nid;
	int token;
	uint32_t cid;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (name == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_NAME;
	}

	if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN))
	{
		token = atomic_increment32(&globals->token_id);
		status = _notify_lib_register_plain(&globals->self_state, name, NOTIFY_CLIENT_SELF, token, SLOT_NONE, 0, 0, &nid);
		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}

		cid = token;

		status = client_registration_create(name, nid, token, cid, SLOT_NONE,
				NOTIFY_FLAG_SELF | NOTIFY_TYPE_PLAIN, SIGNAL_NONE, FD_NONE, MACH_PORT_NULL);

		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE,
					"<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}

		*out_token = token;
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_OK;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	token = atomic_increment32(&globals->token_id);
	cid = token;
	kstatus = _notify_server_register_plain_2(globals->notify_server_port, (caddr_t)name, token);
	if (kstatus != KERN_SUCCESS)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__, NOTIFY_STATUS_REG_PLAIN_2_FAILED, kstatus, __LINE__);
		return NOTIFY_STATUS_FAILED;
	}


	status = client_registration_create(name, NID_UNSET, token, cid, SLOT_NONE,
			NOTIFY_TYPE_PLAIN | NOTIFY_FLAG_REGEN, SIGNAL_NONE, FD_NONE, MACH_PORT_NULL);

	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE,
				"<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	*out_token = token;
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
}

uint32_t
notify_register_signal(const char *name, int sig, int *out_token)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	kern_return_t kstatus;
	uint32_t status;
	uint64_t nid;
	int token;
	uint32_t cid;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (name == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_NAME;
	}

	if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN))
	{
		token = atomic_increment32(&globals->token_id);
		status = _notify_lib_register_signal(&globals->self_state, name, NOTIFY_CLIENT_SELF, token, sig, 0, 0, &nid);
		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}

		cid = token;
		status = client_registration_create(name, nid, token, cid, SLOT_NONE,
						    NOTIFY_FLAG_SELF | NOTIFY_TYPE_SIGNAL, sig, FD_NONE, MACH_PORT_NULL);

		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE,
					"<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}

		*out_token = token;
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_OK;
	}

	if (client_opts(globals) & NOTIFY_OPT_DISPATCH)
	{
		status = notify_register_dispatch(name, out_token, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int unused){ kill(getpid(), sig); });
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	token = atomic_increment32(&globals->token_id);
	cid = token;
	kstatus = _notify_server_register_signal_2(globals->notify_server_port, (caddr_t)name, token, sig);
	if (kstatus != KERN_SUCCESS)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__, NOTIFY_STATUS_REG_SIGNAL_2_FAILED, kstatus, __LINE__);
		return NOTIFY_STATUS_FAILED;
	}

	status = client_registration_create(name, NID_UNSET, token, cid, SLOT_NONE,
			NOTIFY_TYPE_SIGNAL | NOTIFY_FLAG_REGEN, sig, FD_NONE, MACH_PORT_NULL);

	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	*out_token = token;
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
}

// Assumes notify_port already exists
static uint32_t
notify_register_mach_port_self(const char *name, mach_port_name_t *notify_port, int flags, int *out_token, notify_globals_t globals)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	assert(globals);
	assert(name);

	kern_return_t kstatus;
	uint32_t token;
	uint64_t nid;
	uint32_t status;
	uint32_t cid;
	bool mine = false;

	if ((flags & NOTIFY_REUSE) == 0)
	{
		/* caller wants a new port: create a new port with a receive right */
		mine = true;
		kstatus = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, notify_port);
		if (kstatus != KERN_SUCCESS)
		{
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
					    NOTIFY_STATUS_MACH_PORT_ALLOC_FAILED, kstatus, __LINE__);
			return NOTIFY_STATUS_FAILED;
		}
	}
	else if (!MACH_PORT_VALID(*notify_port))
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_PORT;
	}

	/* we need to donate a Send right to the client */
	kstatus = mach_port_insert_right(mach_task_self(), *notify_port, *notify_port, MACH_MSG_TYPE_MAKE_SEND);
	if (kstatus != KERN_SUCCESS)
	{
		if (mine) mach_port_destruct(mach_task_self(), *notify_port, 0, 0);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_PORT;
	}

	token = atomic_increment32(&globals->token_id);
	status = _notify_lib_register_mach_port(&globals->self_state, name, NOTIFY_CLIENT_SELF, token, *notify_port, 0, 0, &nid);
	if (status != NOTIFY_STATUS_OK)
	{
		if (mine) mach_port_destruct(mach_task_self(), *notify_port, 0, 0);
		if (IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif

		return status;
	}

	cid = token;
	status = client_registration_create(name, nid, token, cid, SLOT_NONE,
					    NOTIFY_FLAG_SELF | NOTIFY_TYPE_PORT, SIGNAL_NONE, FD_NONE, *notify_port);

	if (status != NOTIFY_STATUS_OK)
	{
		if (mine) mach_port_destruct(mach_task_self(), *notify_port, 0, 0);
		_notify_lib_cancel(&globals->self_state, NOTIFY_CLIENT_SELF, token);
		if (IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE,
								     "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	*out_token = token;
	notify_retain_mach_port(globals, *notify_port, flags);
	NOTIFY_REGISTER_MACH_PORT(name, *notify_port, flags, token);

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
}

// Assumes no dispatch
// Assumes notify_port already exists
// Assumes not self
static uint32_t
notify_register_mach_port_no_dispatch(const char *name, mach_port_name_t *notify_port, int flags, int *out_token, notify_globals_t globals)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	assert(globals);
	assert(name);
	assert(strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN));

	uint32_t token;
	uint32_t cid;
	kern_return_t kstatus;
	uint32_t status;
	bool mine = false;

	if (((flags & NOTIFY_REUSE) != 0) && !MACH_PORT_VALID(*notify_port))
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_PORT;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
			return status;
		}
	}

	token = atomic_increment32(&globals->token_id);
	cid = token;

	if ((flags & NOTIFY_REUSE) == 0) {
		mine = true;
		uint32_t status = NOTIFY_STATUS_OK;
		*notify_port = MACH_PORT_NULL;
		kstatus = _notify_server_register_mach_port_3(globals->notify_server_port, (caddr_t)name, token, &status, notify_port);
		if (status != NOTIFY_STATUS_OK) {
			assert(*notify_port == MACH_PORT_NULL);
			if (IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	} else {
		kstatus = _notify_server_register_mach_port_2(globals->notify_server_port, (caddr_t)name, token, *notify_port);
	}

	if (kstatus != KERN_SUCCESS)
	{
		if (mine) mach_port_destruct(mach_task_self(), *notify_port, 0, 0);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif

		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
				    NOTIFY_STATUS_REG_MACH_PORT_2_FAILED, kstatus, __LINE__);
		return NOTIFY_STATUS_FAILED;
	}

	status = client_registration_create_base(name, NID_UNSET, token, cid, SLOT_NONE,
						 NOTIFY_TYPE_PORT, SIGNAL_NONE, FD_NONE, *notify_port, false);

	if(status != NOTIFY_STATUS_OK)
	{
		if (mine) mach_port_destruct(mach_task_self(), *notify_port, 0, 0);
		if (IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	notify_retain_mach_port(globals, *notify_port, flags);
	NOTIFY_REGISTER_MACH_PORT(name, *notify_port, flags, token);

	if(out_token) {
		*out_token = token;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
}

static uint32_t
notify_register_coalesced_registration(const char *name, int flags, int *out_token, notify_globals_t globals, mach_port_t extra_mp)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	assert(globals);
	assert(name);

	if(!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) {
		return notify_register_mach_port_self(name, &globals->notify_common_port, flags, out_token, globals);
	}

	uint32_t status;
	name_node_t *n;
	bool create_base = false;
	uint32_t token;
	uint32_t cid;
	kern_return_t kstatus;
	uint32_t tflags = NOTIFY_TYPE_COMMON_PORT | NOTIFY_FLAG_REGEN;

	/* initialize if necessary */
	if ((globals->notify_server_port == MACH_PORT_NULL) ||
	    !MACH_PORT_VALID(globals->notify_common_port))
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			if (status != NOTIFY_STATUS_OPT_DISABLE) {
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	/*
	 * Dance to protect n->coalesce_base.
	 *
	 * We take globals->notify_lock here.  That prevents any other thread
	 * from looking up and/or creating a name node.  We hold the lock
	 * until the name_node (n) is created if necessary, and n->coalesce_base
	 * has been set if it was NULL. The code below is the only code that
	 * assigns a value to n->coalesce_base, so even if some other thread has
	 * the name node, they won't clobber the value.
	 */
	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);

	n = _nc_table_find(&globals->name_node_table, name);
	if (n == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_REGISTRATION) _notify_client_log(ASL_LEVEL_NOTICE, "%s [%d]: name table find %s -> NULL", __func__, __LINE__, name);
#endif
		create_base = true;
	}
	else if (n->coalesce_base == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_REGISTRATION) _notify_client_log(ASL_LEVEL_NOTICE, "%s [%d]: name table find %s -> %p, coalesce_base = NULL", __func__, __LINE__, name, n);
#endif
		create_base = true;
	}
#ifdef DEBUG
	else
	{
		if (_libnotify_debug & DEBUG_REGISTRATION) _notify_client_log(ASL_LEVEL_NOTICE, "%s [%d]: name table find %s -> %p, coalesce_base = %p", __func__, __LINE__, name, n, n->coalesce_base);
	}
#endif

	if (create_base)
	{
		/* base of coalesced registrations gets a private token */
		token = atomic_increment32(&globals->token_id);

		kstatus = _notify_server_register_common_port(globals->notify_server_port, (caddr_t)name, token);
		if (kstatus != KERN_SUCCESS)
		{
			mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
					    NOTIFY_STATUS_REG_MACH_PORT_2_FAILED, kstatus, __LINE__);
			return NOTIFY_STATUS_FAILED;
		}

		status = base_registration_create_locked(globals, name, NID_UNSET, token, tflags,
							 globals->notify_common_port);
		if (status != NOTIFY_STATUS_OK)
		{
			mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
			if (IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif

			return status;
		}

	}

	/* we will need a pointer to the name node below - look it up while we still have the global lock */
	n = _nc_table_find(&globals->name_node_table, name);

	if(!create_base){
		registration_node_retain(n->coalesce_base);
	}

	/* release globals->notify_lock here */
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);

	/* caller's token */
	token = atomic_increment32(&globals->token_id);
	cid = token;
	tflags |= NOTIFY_FLAG_COALESCED;

	/*
	 * If this call to notify_register_mach_port created the coalesce_base registration,
	 * we avoid an extra retain here. It is created with a refcount of 1, and it would get retained
	 * by the coalesced registration created in client_registration_create_base below.  We
	 * want the refcount to be equal to the number of registrations in the coalesce list.
	 * We pass create_base down to client_registration_create_base to indicate that it should avoid
	 * the extra retain -- see name_node_add_coalesced_registration()
	 */
	status = client_registration_create_base(name, NID_UNSET, token, cid, SLOT_NONE,
						 tflags, MACH_PORT_VALID(extra_mp) ? extra_mp : MACH_PORT_NULL, FD_NONE, globals->notify_server_port, create_base);

	if(!create_base){
		registration_node_release(n->coalesce_base);
	}

	if(status != NOTIFY_STATUS_OK){
		if (IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	notify_retain_mach_port(globals, globals->notify_server_port, flags);

	if (out_token) {
		*out_token = token;
	}

	return NOTIFY_STATUS_OK;
}

static void notify_mach_port(int token, mach_port_t port, notify_globals_t globals) {
	mach_msg_empty_send_t msg;
	kern_return_t kstatus;

	/* send empty message to the port with msgh_id = token; */
	memset(&msg, 0, sizeof(msg));
	msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSGH_BITS_ZERO);
	msg.header.msgh_remote_port = port;
	msg.header.msgh_local_port = MACH_PORT_NULL;
	msg.header.msgh_size = sizeof(mach_msg_empty_send_t);
	msg.header.msgh_id = token;

	kstatus = mach_msg(&msg.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, msg.header.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
	if (kstatus == MACH_SEND_TIMED_OUT)
	{
		dispatch_once(&globals->make_background_send_queue_once, ^{
			globals->background_send_queue = dispatch_queue_create("com.apple.notify.background.local.notification", NULL);
		});

		if (globals->background_send_queue != NULL) dispatch_async(globals->background_send_queue, ^{
			mach_msg_empty_send_t msg;

			/* send empty message to the port with msgh_id = token; */
			memset(&msg, 0, sizeof(msg));
			msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSGH_BITS_ZERO);
			msg.header.msgh_remote_port = port;
			msg.header.msgh_local_port = MACH_PORT_NULL;
			msg.header.msgh_size = sizeof(mach_msg_empty_send_t);
			msg.header.msgh_id = token;

			mach_msg(&msg.header, MACH_SEND_MSG, msg.header.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
		});
	}
}

static uint32_t
notify_register_mach_port_via_dispatch(const char *name, mach_port_name_t *notify_port, int flags, int *out_token, notify_globals_t globals)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	bool mine = false;
	uint32_t return_status;
	kern_return_t kstatus;
	mach_port_t port;

	if ((flags & NOTIFY_REUSE) == 0)
	{
		/* caller wants a new port: create a new port with a receive right */
		mine = true;
		kstatus = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, notify_port);
		if (kstatus != KERN_SUCCESS)
		{
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
					    NOTIFY_STATUS_MACH_PORT_ALLOC_FAILED, kstatus, __LINE__);
			return NOTIFY_STATUS_FAILED;
		}
	}

	port = *notify_port;

	if (!MACH_PORT_VALID(port))
	{
		return NOTIFY_STATUS_INVALID_PORT;
	}

	/*
	 * If we use dispatch to forward notifications to this port, the the library needs the send right.
	 */
	kstatus = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
	if (kstatus != KERN_SUCCESS)
	{
		if (mine) mach_port_destruct(mach_task_self(), port, 0, 0);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_PORT;
	}

	return_status = _notify_register_dispatch_with_extra_mp(name, out_token, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
						 ^(int token){
		notify_mach_port(token, port, globals);
	}, *notify_port);

	if(return_status != NOTIFY_STATUS_OK && mine) {
		mach_port_destruct(mach_task_self(), port, 0, 0);
	}

	if (return_status == NOTIFY_STATUS_OK) {
		notify_retain_mach_port(globals, *notify_port, flags);
		NOTIFY_REGISTER_MACH_PORT(name, *notify_port, flags, *out_token);
	}


#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return return_status;
}

/*
 * Port ownership rules:
 * - notify common port is a pure receive right, with no send rights in our space
 *
 * - if the client doesn't pass NOTIFY_REUSE then we make a receive right that
 *   we track in the mach port references (notify_retain_mach_port, mpl_mine).
 *
 * - if we forward notifications to a local port, the library owns a send right
 *   tracked in the the registration with NOTIFY_FLAG_RELEASE_SEND
 *
 * - self notifications own a send right, tracked by the client_t structure
 */
uint32_t
notify_register_mach_port(const char *name, mach_port_name_t *notify_port, int flags, int *out_token)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	uint32_t status;

	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (name == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_NAME;
	}

	if (notify_port == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_PORT;
	}

	if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN))
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return notify_register_mach_port_self(name, notify_port, flags, out_token, globals);
	}
	else if ((client_opts(globals) & NOTIFY_OPT_DISPATCH) && ((flags & NOTIFY_NO_DISPATCH) == 0))
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return notify_register_mach_port_via_dispatch(name, notify_port, flags, out_token, globals);
	}
	else
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return notify_register_mach_port_no_dispatch(name, notify_port, flags, out_token, globals);
	}
}

uint32_t
notify_register_file_descriptor(const char *name, int *notify_fd, int flags, int *out_token)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	uint32_t i, status;
	uint64_t nid;
	int token, mine, fdpair[2];
	fileport_t fileport;
	kern_return_t kstatus;
	uint32_t cid = -1;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{

		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	mine = 0;

	if (name == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_register_file_descriptor name=NULL\n");
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_NAME;
	}

	if (notify_fd == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_register_file_descriptor notify_fd=NULL\n");
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_FILE;
	}

	if ((flags & NOTIFY_REUSE) == 0)
	{
		if (pipe(fdpair) < 0)
		{
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_register_file_descriptor %s [non-reused[ pipe failed errno=%d [%s]\n, name, errno, strerror(errno)");
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__,
					NOTIFY_STATUS_PIPE_FAILED, __LINE__);
			return NOTIFY_STATUS_FAILED;
		}

		mine = 1;
		*notify_fd = fdpair[0];
	}
	else
	{
		/* check the file descriptor - it must be one of "ours" */
		for (i = 0; i < globals->fd_count; i++)
		{
			if (globals->fd_clnt[i] == *notify_fd) break;
		}

		if (i >= globals->fd_count)
		{
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_register_file_descriptor %s [reused] file not found\n", name);
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return NOTIFY_STATUS_INVALID_FILE;
		}

		fdpair[0] = globals->fd_clnt[i];
		fdpair[1] = globals->fd_srv[i];
	}

	if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN))
	{
		token = atomic_increment32(&globals->token_id);
		status = _notify_lib_register_file_descriptor(&globals->self_state, name, NOTIFY_CLIENT_SELF, token, fdpair[1], 0, 0, &nid);
		if (status != NOTIFY_STATUS_OK)
		{
			if (mine == 1)
			{
				close(fdpair[0]);
				close(fdpair[1]);
			}

			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_register_file_descriptor %s [self] _notify_lib_register_file_descriptor failed status=%u\n", name, status);
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}

		cid = token;
		status = client_registration_create(name, nid, token, cid, SLOT_NONE,
				NOTIFY_FLAG_SELF | NOTIFY_TYPE_FILE, SIGNAL_NONE, *notify_fd, MACH_PORT_NULL);

		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE,
					"<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}

		*out_token = token;
		notify_retain_file_descriptor(fdpair[0], fdpair[1]);

#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_OK;
	}

	if (client_opts(globals) & NOTIFY_OPT_DISPATCH)
	{
		/*
		 * Use dispatch to do a write() on fdpair[1] when notified.
		 */
		status = notify_register_mux_fd(name, out_token, fdpair[0], fdpair[1]);
		if (status != NOTIFY_STATUS_OK)
		{
			if (mine == 1)
			{
				close(fdpair[0]);
				close(fdpair[1]);
			}

			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_register_file_descriptor %s notify_register_mux_fd failed status=%u\n", name, status);
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}

		notify_retain_file_descriptor(fdpair[0], fdpair[1]);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_OK;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			if (mine == 1)
			{
				close(fdpair[0]);
				close(fdpair[1]);
			}

			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_register_file_descriptor %s _notify_lib_init failed status=%u\n", name, status);
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	/* send fdpair[1] (the sender's fd) to notifyd using a fileport */
	fileport = MACH_PORT_NULL;
	if (fileport_makeport(fdpair[1], (fileport_t *)&fileport) < 0)
	{
		if (mine == 1)
		{
			close(fdpair[0]);
			close(fdpair[1]);
		}

#ifdef DEBUG
		if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_register_file_descriptor %s fileport_makeport failed errno=%d [%s]\n", name, errno, strerror(errno));
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__,
				NOTIFY_STATUS_FILEPORT_MAKEPORT_FAILED, __LINE__);
		return NOTIFY_STATUS_FAILED;
	}

	token = atomic_increment32(&globals->token_id);
	kstatus = _notify_server_register_file_descriptor_2(globals->notify_server_port, (caddr_t)name, token, (mach_port_t)fileport);
	if (kstatus != KERN_SUCCESS)
	{
		if (mine == 1)
		{
			close(fdpair[0]);
			close(fdpair[1]);
		}

#ifdef DEBUG
		if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_register_file_descriptor %s  _notify_server_register_file_descriptor[_2] failed status=0x%08xu\n", name, kstatus);
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
				    NOTIFY_STATUS_REG_FD_2_FAILED, kstatus, __LINE__);
		return NOTIFY_STATUS_FAILED;
	}

	status = client_registration_create(name, NID_UNSET, token, cid, SLOT_NONE, NOTIFY_TYPE_FILE,
			SIGNAL_NONE, *notify_fd, MACH_PORT_NULL);

	if (status != NOTIFY_STATUS_OK)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE,
				"<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	*out_token = token;
	notify_retain_file_descriptor(fdpair[0], fdpair[1]);

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
}

uint32_t
notify_check(int token, int *check)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	kern_return_t kstatus;
	uint32_t status = NOTIFY_STATUS_OK;
	registration_node_t *r;
	uint32_t tid;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (check == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_check check=NULL\n", token);
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_NULL_INPUT;
	}

	r = registration_node_find(token);
	if (r == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_check token %d registration node not found\n", token);
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_TOKEN;
	}

	if (r->flags & NOTIFY_FLAG_SELF)
	{
		/* _notify_lib_check returns NOTIFY_STATUS_NULL_INPUT if self_state is NULL */
		status = _notify_lib_check(&globals->self_state, NOTIFY_CLIENT_SELF, token, check);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_check token %d self registration _notify_lib_check status %u check %d\n", token, status, *check);
		if ((_libnotify_debug & DEBUG_USER) && (status != NOTIFY_STATUS_NULL_INPUT)) _notify_client_log(ASL_LEVEL_ERR, "notify_check token %d [self] _notify_lib_check failed status=%u\n", token, status);
#endif
		goto release_and_return;
	}

	if (notify_is_type(r->flags, NOTIFY_TYPE_MEMORY))
	{
		if (globals->shm_base == NULL)
		{
			status = NOTIFY_STATUS_SHM_BASE_NULL;
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_check token %d [memory] globals->shm_base is NULL\n", token);
#endif
			goto release_and_return;
		}

		*check = 0;

		mutex_lock("check_lock", &globals->check_lock, __func__, __LINE__);
		if (r->val != globals->shm_base[r->slot])
		{
			*check = 1;
			r->val = globals->shm_base[r->slot];
		}
		mutex_unlock("check_lock", &globals->check_lock, __func__, __LINE__);


		status = NOTIFY_STATUS_OK;
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_check token %d shared memory check status %u check %d\n", token, status, *check);
#endif
		goto release_and_return;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_check token %d in not a shared memory registration\n", token);
#endif

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != 0)
		{
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_check token %d [non-memory] _notify_lib_init failed status=%u\n", token, status);
#endif
			goto release_and_return;
		}
	}

	tid = token;
	if (r->flags & NOTIFY_FLAG_COALESCED) tid = r->name_node->coalesce_base_token;

	kstatus = _notify_server_check(globals->notify_server_port, tid, check, (int32_t *)&status);
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_check token %d [%d] _notify_server_check kstatus 0x%08x status %u check %d\n", token, tid, kstatus, status, *check);
#endif
	if (kstatus != KERN_SUCCESS)
	{
		status = NOTIFY_STATUS_SERVER_CHECK_FAILED;
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_USER) _notify_client_log(ASL_LEVEL_ERR, "notify_check token %d [non-memory] _notify_server_check failed kstatus=0x%08x status=%u\n", token, kstatus, status);
#endif
	}

release_and_return:

	NOTIFY_CHECK(token, *check);

	registration_node_release(r);

	if(IS_INTERNAL_ERROR(status))
	{
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
		status = NOTIFY_STATUS_FAILED;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d] status %u check %d\n", __func__, __LINE__ + 2, status, *check);
#endif
	return status;
}

/* Used in Libinfo without header declaration */
uint32_t
notify_peek(int token, uint32_t *val)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	registration_node_t *r;
	uint32_t status;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	r = registration_node_find(token);
	if (r == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_TOKEN;
	}

	if (r->flags & NOTIFY_FLAG_SELF)
	{
		/* _notify_lib_peek returns NOTIFY_STATUS_NULL_INPUT if self_state is NULL */
		status = _notify_lib_peek(&globals->self_state, NOTIFY_CLIENT_SELF, token, (int *)val);
		goto release_and_return;
	}

	status = NOTIFY_STATUS_INVALID_REQUEST;

	if (notify_is_type(r->flags, NOTIFY_TYPE_MEMORY))
	{
		if (globals->shm_base == NULL)
		{
			status = NOTIFY_STATUS_SHM_BASE_NULL;
			goto release_and_return;
		}

		*val = globals->shm_base[r->slot];
		status = NOTIFY_STATUS_OK;
	}

release_and_return:

	registration_node_release(r);

	if(IS_INTERNAL_ERROR(status))
	{
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
		status = NOTIFY_STATUS_FAILED;
	}
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return status;
}

/* Used by Libc, cctools, configd, kext_tools, Libnotify, and libresolv without header declaration */
uint32_t
notify_monitor_file(int token, char *path, int flags)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	kern_return_t kstatus;
	uint32_t status;
	registration_node_t *r;
	char *dup;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (path == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_REQUEST;
	}

	r = registration_node_find(token);
	if (r == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_TOKEN;
	}

	if (r->flags & NOTIFY_FLAG_SELF)
	{
		registration_node_release(r);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_REQUEST;
	}

	/* can only monitor one path with a token */
	if (r->path != NULL)
	{
		registration_node_release(r);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_REQUEST;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			registration_node_release(r);

			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	dup = strdup(path);
	if (dup == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__,
				NOTIFY_STATUS_STRDUP_FAILED, __LINE__);
		return NOTIFY_STATUS_FAILED;
	}

	{
		int xtoken = token;
		int len = (uint32_t)(strlen(path) + 1);
		if (r->flags & NOTIFY_FLAG_COALESCED) xtoken = r->name_node->coalesce_base_token;

		kstatus = _notify_server_monitor_file_2(globals->notify_server_port, xtoken, path, len, flags);
	}

	r->path = dup;
	r->path_flags = flags;

	registration_node_release(r);

	if (kstatus != KERN_SUCCESS)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
				    NOTIFY_STATUS_STRDUP_FAILED, kstatus, __LINE__);
		return NOTIFY_STATUS_SERVER_MONITOR_FILE_2_FAILED;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
}

/* Used by kext_tools without header declaration */
uint32_t
notify_get_event(int token, int *ev, char *buf, int *len)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	static bool report_deprecated_once = true;

	if(report_deprecated_once)
	{
		REPORT_BAD_BEHAVIOR("Libnotify client using deprecated function notify_get_event; this function does nothing");
		report_deprecated_once = false;
	}

	if (ev != NULL) *ev = 0;
	if (len != NULL) *len = 0;

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
}

uint32_t
notify_get_state(int token, uint64_t *state)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	kern_return_t kstatus;
	uint32_t status;
	registration_node_t *r;
	uint64_t nid;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{

		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	r = registration_node_find(token);
	if (r == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_TOKEN;
	}

	if (r->name_node == NULL)
	{
		registration_node_release(r);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_TOKEN;
	}

	if (r->flags & NOTIFY_FLAG_SELF)
	{
		/* _notify_lib_get_state returns NOTIFY_STATUS_NULL_INPUT if self_state is NULL */
		status = _notify_lib_get_state(&globals->self_state, r->name_node->name_id, state, 0, 0);
		registration_node_release(r);

		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			registration_node_release(r);

			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	{
		name_node_t *name_node = r->name_node;
		int xtoken = token;
		if (r->flags & NOTIFY_FLAG_COALESCED) xtoken = name_node->coalesce_base_token;

		// This is a race, but it is safe. The worst case is that nid is looked-up and set twice.
		mutex_lock(name_node->name, &name_node->lock, __func__, __LINE__);
		nid = name_node->name_id;
		mutex_unlock(name_node->name, &name_node->lock, __func__, __LINE__);

		if ((nid == NID_UNSET) || (nid == NID_CALLED_ONCE))
		{
			kstatus = _notify_server_get_state_3(globals->notify_server_port, xtoken, state, (uint64_t *)&nid, (int32_t *)&status);
			if ((kstatus == KERN_SUCCESS) && (status == NOTIFY_STATUS_OK)) name_node_set_nid(name_node, nid);
		}
		else
		{
			kstatus = _notify_server_get_state_2(globals->notify_server_port, nid, state, (int32_t *)&status);
		}
	}

	registration_node_release(r);

	if (kstatus != KERN_SUCCESS)
	{
		status = NOTIFY_STATUS_SERVER_GET_STATE_2_FAILED;
	}


	if(IS_INTERNAL_ERROR(status))
	{
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__, status, kstatus, __LINE__);
		status = NOTIFY_STATUS_FAILED;
	}
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return status;
}

uint32_t
notify_set_state(int token, uint64_t state)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	kern_return_t kstatus;
	uint32_t status;
	registration_node_t *r;
	uint64_t nid;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	r = registration_node_find(token);
	if (r == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_TOKEN;
	}

	if (r->name_node == NULL)
	{
		registration_node_release(r);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_TOKEN;
	}

	if (r->flags & NOTIFY_FLAG_SELF)
	{
		/* _notify_lib_set_state returns NOTIFY_STATUS_NULL_INPUT if self_state is NULL */
		status = _notify_lib_set_state(&globals->self_state, r->name_node->name_id, state, 0, 0);
		registration_node_release(r);

		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			registration_node_release(r);

			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	status = NOTIFY_STATUS_OK;

	{
		name_node_t *name_node = r->name_node;
		int xtoken = token;
		if (r->flags & NOTIFY_FLAG_COALESCED) xtoken = name_node->coalesce_base_token;

		// This is a race, but it is safe. The worst case is that nid is looked-up and set twice.
		mutex_lock(name_node->name, &name_node->lock, __func__, __LINE__);
		nid = name_node->name_id;
		mutex_unlock(name_node->name, &name_node->lock, __func__, __LINE__);

		if ((nid == NID_UNSET) || (nid == NID_CALLED_ONCE))
		{
			kstatus = _notify_server_set_state_3(globals->notify_server_port, xtoken, state, (uint64_t *)&nid, (int32_t *)&status, should_claim_root_access());
			if ((kstatus == KERN_SUCCESS) && (status == NOTIFY_STATUS_OK)) name_node_set_nid(name_node, nid);
		}
		else
		{
			status = NOTIFY_STATUS_OK;
			kstatus = _notify_server_set_state_2(globals->notify_server_port, nid, state, should_claim_root_access());
		}
	}

	if ((kstatus == KERN_SUCCESS) && (status == NOTIFY_STATUS_OK))
	{
		r->set_state_time = mach_absolute_time();
		r->set_state_val = state;
	}

	registration_node_release(r);
	if (kstatus != KERN_SUCCESS)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__,
				NOTIFY_STATUS_SERVER_SET_STATE_2_FAILED, kstatus, __LINE__);
		return NOTIFY_STATUS_FAILED;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return NOTIFY_STATUS_OK;
}

uint32_t
notify_cancel(int token)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	registration_node_t *r;
	notify_globals_t globals = _notify_globals();
	uint32_t status;

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	mutex_lock("global", &globals->notify_lock, __func__, __LINE__);

	r = _nc_table_find_n(&globals->registration_table, token);
	if (r == NULL)
	{
		mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_TOKEN;
	}

	if (!(r->flags & NOTIFY_FLAG_CANCELED)) {
		r->flags |= NOTIFY_FLAG_CANCELED;
		registration_node_release_locked(globals, r);
	}
	mutex_unlock("global", &globals->notify_lock, __func__, __LINE__);
	// Always return OK here as there's nothing a client can do if the server call failed
	return NOTIFY_STATUS_OK;
}

uint32_t
notify_suspend(int token)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	registration_node_t *r;
	uint32_t status;
	kern_return_t kstatus = KERN_SUCCESS;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	r = registration_node_find(token);
	if (r == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_TOKEN;
	}

	if (r->flags & NOTIFY_FLAG_SELF)
	{
		_notify_lib_suspend(&globals->self_state, NOTIFY_CLIENT_SELF, r->token);
		registration_node_release(r);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_OK;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			registration_node_release(r);

			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}


	mutex_lock(r->name_node->name, &r->name_node->lock, __func__, __LINE__);
	if (r->flags & NOTIFY_FLAG_COALESCED)
	{
		r->flags |= NOTIFY_FLAG_SUSPENDED;
		mutex_unlock(r->name_node->name, &r->name_node->lock, __func__, __LINE__);
	} else {
		mutex_unlock(r->name_node->name, &r->name_node->lock, __func__, __LINE__);
		kstatus = _notify_server_suspend(globals->notify_server_port, token, (int32_t *)&status);
	}

	registration_node_release(r);

	if (kstatus != KERN_SUCCESS) status = NOTIFY_STATUS_SERVER_SUSPEND_FAILED;

	if(IS_INTERNAL_ERROR(status))
	{
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__, status, kstatus, __LINE__);
		status = NOTIFY_STATUS_FAILED;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return status;
}

uint32_t
notify_resume(int token)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	registration_node_t *r;
	uint32_t status;
	kern_return_t kstatus = KERN_SUCCESS;
	notify_globals_t globals = _notify_globals();

	status = regenerate_check(globals);
	if (status != NOTIFY_STATUS_OK)
	{
		if(IS_INTERNAL_ERROR(status))
		{
			REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
			status = NOTIFY_STATUS_FAILED;
		}
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return status;
	}

	r = registration_node_find(token);
	if (r == NULL)
	{
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_INVALID_TOKEN;
	}

	if (r->flags & NOTIFY_FLAG_SELF)
	{
		_notify_lib_resume(&globals->self_state, NOTIFY_CLIENT_SELF, r->token);
		registration_node_release(r);
#ifdef DEBUG
		if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
		return NOTIFY_STATUS_OK;
	}

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			registration_node_release(r);

			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	mutex_lock(r->name_node->name, &r->name_node->lock, __func__, __LINE__);
	if (r->flags & NOTIFY_FLAG_COALESCED)
	{
		bool deferred_post = r->flags & NOTIFY_FLAG_DEFERRED_POST;
		r->flags &= ~(NOTIFY_FLAG_SUSPENDED | NOTIFY_FLAG_DEFERRED_POST);
		mutex_unlock(r->name_node->name, &r->name_node->lock, __func__, __LINE__);

		if (deferred_post)
		{
			_notify_dispatch_local_notification(r);
		}

	} else {
		mutex_unlock(r->name_node->name, &r->name_node->lock, __func__, __LINE__);
		kstatus = _notify_server_resume(globals->notify_server_port, token, (int32_t *)&status);
	}

	registration_node_release(r);

	if (kstatus != KERN_SUCCESS) status = NOTIFY_STATUS_SERVER_RESUME_FAILED;

	if(IS_INTERNAL_ERROR(status))
	{
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d (%d) on line %d", __func__, status, kstatus, __LINE__);
		status = NOTIFY_STATUS_FAILED;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return status;
}

uint32_t
notify_suspend_pid(pid_t pid)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	uint32_t status = NOTIFY_STATUS_OK;
	kern_return_t kstatus;
	notify_globals_t globals = _notify_globals();

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	kstatus = _notify_server_suspend_pid(globals->notify_server_port, pid);

	if(kstatus != KERN_SUCCESS)
	{
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, kstatus, __LINE__);
		status = NOTIFY_STATUS_FAILED;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return status;
}

uint32_t
notify_resume_pid(pid_t pid)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	uint32_t status = NOTIFY_STATUS_OK;
	kern_return_t kstatus;
	notify_globals_t globals = _notify_globals();

	if (globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{

			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}
#ifdef DEBUG
			if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
			return status;
		}
	}

	kstatus = _notify_server_resume_pid(globals->notify_server_port, pid);

	if(kstatus != KERN_SUCCESS)
	{
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, kstatus, __LINE__);
		status = NOTIFY_STATUS_FAILED;
	}

#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return status;
}

/* Deprecated SPI */
uint32_t
notify_simple_post(const char *name)
{
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "-> %s\n", __func__);
#endif

	static bool report_deprecated_once = true;

	if(report_deprecated_once)
	{
		REPORT_BAD_BEHAVIOR("Libnotify client using deprecated function notify_simple_post, use notify_post instead");
		report_deprecated_once = false;
	}

	uint32_t status = notify_post(name);
#ifdef DEBUG
	if (_libnotify_debug & DEBUG_API) _notify_client_log(ASL_LEVEL_NOTICE, "<- %s [%d]\n", __func__, __LINE__ + 2);
#endif
	return status;
}


uint32_t
notify_dump_status(const char *filepath)
{
	notify_globals_t globals;
	uint32_t status;
	uint32_t kstatus;
	int fileport_status;
	int file_descriptor;
	fileport_t fileport;

	globals = _notify_globals();

	if(globals->notify_server_port == MACH_PORT_NULL)
	{
		status = _notify_lib_init(globals, EVENT_INIT);
		if (status != NOTIFY_STATUS_OK)
		{
			if(IS_INTERNAL_ERROR(status))
			{
				REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d", __func__, status, __LINE__);
				status = NOTIFY_STATUS_FAILED;
			}

			return status;
		}
	}


	file_descriptor = creat(filepath,  S_IWUSR);
	if(file_descriptor < 0)
	{
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d (errno: %d)", __func__, NOTIFY_STATUS_FAILED, __LINE__, errno);
		return NOTIFY_STATUS_FAILED;
	}


	fileport = FILEPORT_NULL;
	fileport_status = fileport_makeport(file_descriptor, &fileport);
	if(fileport_status < 0)
	{
		close(file_descriptor);
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d (fileport_status: %d)", __func__, NOTIFY_STATUS_FAILED, __LINE__, fileport_status);
		return NOTIFY_STATUS_FAILED;
	}


	kstatus = _notify_server_dump(globals->notify_server_port, (mach_port_t)fileport);
	close(file_descriptor);
	if(kstatus != KERN_SUCCESS)
	{
		REPORT_BAD_BEHAVIOR("Libnotify: %s failed with code %d on line %d (kstatus: %d)", __func__, NOTIFY_STATUS_FAILED, __LINE__, kstatus);
		return NOTIFY_STATUS_FAILED;
	}


	return NOTIFY_STATUS_OK;
}