+/*
+ * Routine: ipc_kobject_make_send_lazy_alloc_port
+ * Purpose:
+ * Make a send once for a kobject port.
+ *
+ * A location owning this port is passed in port_store.
+ * If no port exists, a port is made lazily.
+ *
+ * A send right is made for the port, and if this is the first one
+ * (possibly not for the first time), then the no-more-senders
+ * notification is rearmed.
+ *
+ * When a notification is armed, the kobject must donate
+ * one of its references to the port. It is expected
+ * the no-more-senders notification will consume this reference.
+ *
+ * Returns:
+ * TRUE if a notification was armed
+ * FALSE else
+ *
+ * Conditions:
+ * Nothing is locked, memory can be allocated.
+ * The caller must be able to donate a kobject reference to the port.
+ */
+boolean_t
+ipc_kobject_make_send_lazy_alloc_port(
+ ipc_port_t *port_store,
+ ipc_kobject_t kobject,
+ ipc_kobject_type_t type,
+ ipc_kobject_alloc_options_t alloc_opts,
+ boolean_t __ptrauth_only should_ptrauth,
+ uint64_t __ptrauth_only ptrauth_discriminator)
+{
+ ipc_port_t port, previous, __ptrauth_only port_addr;
+ boolean_t rc = FALSE;
+
+ port = os_atomic_load(port_store, dependency);
+
+#if __has_feature(ptrauth_calls)
+ /* If we're on a ptrauth system and this port is signed, authenticate and strip the pointer */
+ if (should_ptrauth && IP_VALID(port)) {
+ port = ptrauth_auth_data(port,
+ ptrauth_key_process_independent_data,
+ ptrauth_blend_discriminator(port_store, ptrauth_discriminator));
+ }
+#endif // __has_feature(ptrauth_calls)
+
+ if (!IP_VALID(port)) {
+ port = ipc_kobject_alloc_port(kobject, type,
+ IPC_KOBJECT_ALLOC_MAKE_SEND | IPC_KOBJECT_ALLOC_NSREQUEST | alloc_opts);
+
+#if __has_feature(ptrauth_calls)
+ if (should_ptrauth) {
+ port_addr = ptrauth_sign_unauthenticated(port,
+ ptrauth_key_process_independent_data,
+ ptrauth_blend_discriminator(port_store, ptrauth_discriminator));
+ } else {
+ port_addr = port;
+ }
+#else
+ port_addr = port;
+#endif // __has_feature(ptrauth_calls)
+
+ if (os_atomic_cmpxchgv(port_store, IP_NULL, port_addr, &previous, release)) {
+ return TRUE;
+ }
+
+ // undo what ipc_kobject_alloc_port() did above
+ port->ip_nsrequest = IP_NULL;
+ port->ip_mscount = 0;
+ port->ip_sorights = 0;
+ port->ip_srights = 0;
+ ip_release(port);
+ ip_release(port);
+ ipc_port_dealloc_kernel(port);
+
+ port = previous;
+ }
+
+ ip_lock(port);
+ ipc_port_make_send_locked(port);
+ if (port->ip_srights == 1) {
+ ipc_port_make_sonce_locked(port);
+ assert(port->ip_nsrequest == IP_NULL);
+ port->ip_nsrequest = port;
+ rc = TRUE;
+ }
+ ip_unlock(port);
+
+ return rc;
+}
+
+/*
+ * Routine: ipc_kobject_make_send_lazy_alloc_labeled_port
+ * Purpose:
+ * Make a send once for a kobject port.
+ *
+ * A location owning this port is passed in port_store.
+ * If no port exists, a port is made lazily.
+ *
+ * A send right is made for the port, and if this is the first one
+ * (possibly not for the first time), then the no-more-senders
+ * notification is rearmed.
+ *
+ * When a notification is armed, the kobject must donate
+ * one of its references to the port. It is expected
+ * the no-more-senders notification will consume this reference.
+ *
+ * Returns:
+ * TRUE if a notification was armed
+ * FALSE else
+ *
+ * Conditions:
+ * Nothing is locked, memory can be allocated.
+ * The caller must be able to donate a kobject reference to the port.
+ */
+boolean_t
+ipc_kobject_make_send_lazy_alloc_labeled_port(
+ ipc_port_t *port_store,
+ ipc_kobject_t kobject,
+ ipc_kobject_type_t type,
+ ipc_label_t label)
+{
+ ipc_port_t port, previous;
+ boolean_t rc = FALSE;
+
+ port = os_atomic_load(port_store, dependency);
+
+ if (!IP_VALID(port)) {
+ port = ipc_kobject_alloc_labeled_port(kobject, type, label,
+ IPC_KOBJECT_ALLOC_MAKE_SEND | IPC_KOBJECT_ALLOC_NSREQUEST);
+ if (os_atomic_cmpxchgv(port_store, IP_NULL, port, &previous, release)) {
+ return TRUE;
+ }
+
+ // undo what ipc_kobject_alloc_port() did above
+ port->ip_nsrequest = IP_NULL;
+ port->ip_mscount = 0;
+ port->ip_sorights = 0;
+ port->ip_srights = 0;
+ ip_release(port);
+ ip_release(port);
+ zfree(ipc_kobject_label_zone, port->ip_kolabel);
+ port->ip_object.io_bits &= ~IO_BITS_KOLABEL;
+ port->ip_kolabel = NULL;
+ ipc_port_dealloc_kernel(port);
+
+ port = previous;
+ assert(ip_is_kolabeled(port));
+ }
+
+ ip_lock(port);
+ ipc_port_make_send_locked(port);
+ if (port->ip_srights == 1) {
+ ipc_port_make_sonce_locked(port);
+ assert(port->ip_nsrequest == IP_NULL);
+ port->ip_nsrequest = port;
+ rc = TRUE;
+ }
+ ip_unlock(port);
+
+ return rc;
+}
+
+