+ parse_stackshot(PARSE_STACKSHOT_WAITINFO_CSEG, ssbuf, sslen, @{cseg_expected_threadid_key: @(thread_id)});
+ });
+}
+
+static void
+srp_send(
+ mach_port_t send_port,
+ mach_port_t reply_port,
+ mach_port_t msg_port)
+{
+ kern_return_t ret = 0;
+
+ struct test_msg {
+ mach_msg_header_t header;
+ mach_msg_body_t body;
+ mach_msg_port_descriptor_t port_descriptor;
+ };
+ struct test_msg send_msg = {
+ .header = {
+ .msgh_remote_port = send_port,
+ .msgh_local_port = reply_port,
+ .msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND,
+ reply_port ? MACH_MSG_TYPE_MAKE_SEND_ONCE : 0,
+ MACH_MSG_TYPE_MOVE_SEND,
+ MACH_MSGH_BITS_COMPLEX),
+ .msgh_id = 0x100,
+ .msgh_size = sizeof(send_msg),
+ },
+ .body = {
+ .msgh_descriptor_count = 1,
+ },
+ .port_descriptor = {
+ .name = msg_port,
+ .disposition = MACH_MSG_TYPE_MOVE_RECEIVE,
+ .type = MACH_MSG_PORT_DESCRIPTOR,
+ },
+ };
+
+ if (msg_port == MACH_PORT_NULL) {
+ send_msg.body.msgh_descriptor_count = 0;
+ }
+
+ ret = mach_msg(&(send_msg.header),
+ MACH_SEND_MSG |
+ MACH_SEND_TIMEOUT |
+ MACH_SEND_OVERRIDE |
+ (reply_port ? MACH_SEND_SYNC_OVERRIDE : 0),
+ send_msg.header.msgh_size,
+ 0,
+ MACH_PORT_NULL,
+ 10000,
+ 0);
+
+ T_ASSERT_MACH_SUCCESS(ret, "client mach_msg");
+}
+
+T_HELPER_DECL(srp_client,
+ "Client used for the special_reply_port test")
+{
+ pid_t ppid = getppid();
+ dispatch_semaphore_t can_continue = dispatch_semaphore_create(0);
+ dispatch_queue_t dq = dispatch_queue_create("client_signalqueue", NULL);
+ dispatch_source_t sig_src;
+
+ mach_msg_return_t mr;
+ mach_port_t service_port;
+ mach_port_t conn_port;
+ mach_port_t special_reply_port;
+ mach_port_options_t opts = {
+ .flags = MPO_INSERT_SEND_RIGHT,
+ };
+
+ signal(SIGUSR1, SIG_IGN);
+ sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq);
+
+ dispatch_source_set_event_handler(sig_src, ^{
+ dispatch_semaphore_signal(can_continue);
+ });
+ dispatch_activate(sig_src);
+
+ /* lookup the mach service port for the parent */
+ kern_return_t kr = bootstrap_look_up(bootstrap_port,
+ SRP_SERVICE_NAME, &service_port);
+ T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "client bootstrap_look_up");
+
+ /* create the send-once right (special reply port) and message to send to the server */
+ kr = mach_port_construct(mach_task_self(), &opts, 0ull, &conn_port);
+ T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_construct");
+
+ special_reply_port = thread_get_special_reply_port();
+ T_QUIET; T_ASSERT_TRUE(MACH_PORT_VALID(special_reply_port), "get_thread_special_reply_port");
+
+ /* send the message with the special reply port */
+ srp_send(service_port, special_reply_port, conn_port);
+
+ /* signal the parent to continue */
+ kill(ppid, SIGUSR1);
+
+ struct {
+ mach_msg_header_t header;
+ mach_msg_body_t body;
+ mach_msg_port_descriptor_t port_descriptor;
+ } rcv_msg = {
+ .header =
+ {
+ .msgh_remote_port = MACH_PORT_NULL,
+ .msgh_local_port = special_reply_port,
+ .msgh_size = sizeof(rcv_msg),
+ },
+ };
+
+ /* wait on the reply from the parent (that we will never receive) */
+ mr = mach_msg(&(rcv_msg.header),
+ (MACH_RCV_MSG | MACH_RCV_SYNC_WAIT),
+ 0,
+ rcv_msg.header.msgh_size,
+ special_reply_port,
+ MACH_MSG_TIMEOUT_NONE,
+ service_port);
+
+ /* not expected to execute as parent will SIGKILL client... */
+ T_LOG("client process exiting after sending message to parent (server)");
+}
+
+/*
+ * Tests the stackshot wait info plumbing for synchronous IPC that doesn't use kevent on the server.
+ *
+ * (part 1): tests the scenario where a client sends a request that includes a special reply port
+ * to a server that doesn't receive the message and doesn't copy the send-once right
+ * into its address space as a result. for this case the special reply port is enqueued
+ * in a port and we check which task has that receive right and use that info. (rdar://60440338)
+ * (part 2): tests the scenario where a client sends a request that includes a special reply port
+ * to a server that receives the message and copies in the send-once right, but doesn't
+ * reply to the client. for this case the special reply port is copied out and the kernel
+ * stashes the info about which task copied out the send once right. (rdar://60440592)
+ */
+T_DECL(special_reply_port, "test that tasks using special reply ports have correct waitinfo")
+{
+ dispatch_semaphore_t can_continue = dispatch_semaphore_create(0);
+ dispatch_queue_t dq = dispatch_queue_create("signalqueue", NULL);
+ dispatch_source_t sig_src;
+ char path[PATH_MAX];
+ uint32_t path_size = sizeof(path);
+ T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath");
+ char *client_args[] = { path, "-n", "srp_client", NULL };
+ pid_t client_pid;
+ int sp_ret;
+ kern_return_t kr;
+ struct scenario scenario = {
+ .name = "srp",
+ .quiet = false,
+ .flags = (STACKSHOT_THREAD_WAITINFO | STACKSHOT_KCDATA_FORMAT),
+ };
+ mach_port_t port;
+
+ /* setup the signal handler in the parent (server) */
+ T_LOG("setup sig handlers");
+ signal(SIGUSR1, SIG_IGN);
+ sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq);
+
+ dispatch_source_set_event_handler(sig_src, ^{
+ dispatch_semaphore_signal(can_continue);