1/*
2 * Copyright (c) 2003-2020 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29#include <mach/mach_types.h>
30#include <mach/mach_host.h>
31
32#include <kern/kern_types.h>
33#include <kern/ipc_kobject.h>
34#include <kern/host_notify.h>
35
36#include <kern/queue.h>
37
38#include "mach/host_notify_reply.h"
39
40struct host_notify_entry {
41 queue_chain_t entries;
42 ipc_port_t port;
43 ipc_port_request_index_t index;
44};
45
46LCK_GRP_DECLARE(host_notify_lock_grp, "host_notify");
47LCK_MTX_DECLARE(host_notify_lock, &host_notify_lock_grp);
48
49static KALLOC_TYPE_DEFINE(host_notify_zone,
50 struct host_notify_entry, KT_DEFAULT);
51
52static queue_head_t host_notify_queue[HOST_NOTIFY_TYPE_MAX + 1] = {
53 QUEUE_HEAD_INITIALIZER(host_notify_queue[HOST_NOTIFY_CALENDAR_CHANGE]),
54 QUEUE_HEAD_INITIALIZER(host_notify_queue[HOST_NOTIFY_CALENDAR_SET]),
55};
56
57static mach_msg_id_t host_notify_replyid[HOST_NOTIFY_TYPE_MAX + 1] = {
58 HOST_CALENDAR_CHANGED_REPLYID,
59 HOST_CALENDAR_SET_REPLYID,
60};
61
62kern_return_t
63host_request_notification(
64 host_t host,
65 host_flavor_t notify_type,
66 ipc_port_t port)
67{
68 host_notify_t entry;
69 kern_return_t kr;
70
71 if (host == HOST_NULL) {
72 return KERN_INVALID_ARGUMENT;
73 }
74
75 if (!IP_VALID(port)) {
76 return KERN_INVALID_CAPABILITY;
77 }
78
79 if (notify_type > HOST_NOTIFY_TYPE_MAX || notify_type < 0) {
80 return KERN_INVALID_ARGUMENT;
81 }
82
83 entry = zalloc_flags(host_notify_zone, Z_WAITOK | Z_ZERO | Z_NOFAIL);
84 entry->port = port;
85
86again:
87 lck_mtx_lock(lck: &host_notify_lock);
88
89 ip_mq_lock(port);
90 if (ip_active(port)) {
91 kr = ipc_port_request_hnotify_alloc(port, hnotify: entry, indexp: &entry->index);
92 } else {
93 kr = KERN_INVALID_CAPABILITY;
94 }
95
96 if (kr == KERN_SUCCESS) {
97 /*
98 * Preserve original ABI of host-notify ports being immovable
99 * as a side effect of being a kobject.
100 *
101 * Unlike the original ABI, multiple registrations
102 * for the same port are now allowed.
103 */
104 port->ip_immovable_receive = true;
105 enqueue_tail(que: &host_notify_queue[notify_type], elt: &entry->entries);
106 }
107
108 lck_mtx_unlock(lck: &host_notify_lock);
109
110 if (kr == KERN_NO_SPACE) {
111 kr = ipc_port_request_grow(port);
112 /* port unlocked */
113 if (kr == KERN_SUCCESS) {
114 goto again;
115 }
116 } else {
117 ip_mq_unlock(port);
118 }
119
120 if (kr != KERN_SUCCESS) {
121 zfree(host_notify_zone, entry);
122 }
123
124 return kr;
125}
126
127void
128host_notify_cancel(host_notify_t entry)
129{
130 ipc_port_t port;
131
132 lck_mtx_lock(lck: &host_notify_lock);
133 remqueue(elt: (queue_entry_t)entry);
134 port = entry->port;
135 lck_mtx_unlock(lck: &host_notify_lock);
136
137 zfree(host_notify_zone, entry);
138 ipc_port_release_sonce(port);
139}
140
141static void
142host_notify_all(
143 host_flavor_t notify_type,
144 mach_msg_header_t *msg,
145 mach_msg_size_t msg_size)
146{
147 queue_head_t send_queue = QUEUE_HEAD_INITIALIZER(send_queue);
148 queue_entry_t e;
149 host_notify_t entry;
150 ipc_port_t port;
151
152 lck_mtx_lock(lck: &host_notify_lock);
153
154 qe_foreach_safe(e, &host_notify_queue[notify_type]) {
155 entry = (host_notify_t)e;
156 port = entry->port;
157
158 ip_mq_lock(port);
159 if (ip_active(port)) {
160 ipc_port_request_cancel(port, IPR_HOST_NOTIFY,
161 index: entry->index);
162 remqueue(elt: e);
163 enqueue_tail(que: &send_queue, elt: e);
164 } else {
165 /*
166 * leave the entry in place,
167 * we're racing with ipc_port_dnnotify()
168 * which will call host_notify_cancel().
169 */
170 }
171 ip_mq_unlock(port);
172 }
173
174 lck_mtx_unlock(lck: &host_notify_lock);
175
176 if (queue_empty(&send_queue)) {
177 return;
178 }
179
180 msg->msgh_bits =
181 MACH_MSGH_BITS_SET(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0, 0, 0);
182 msg->msgh_local_port = MACH_PORT_NULL;
183 msg->msgh_voucher_port = MACH_PORT_NULL;
184 msg->msgh_id = host_notify_replyid[notify_type];
185
186 qe_foreach_safe(e, &send_queue) {
187 entry = (host_notify_t)e;
188 port = entry->port;
189
190 zfree(host_notify_zone, entry);
191
192 msg->msgh_remote_port = port;
193 (void)mach_msg_send_from_kernel_proper(msg, send_size: msg_size);
194 }
195}
196
197void
198host_notify_calendar_change(void)
199{
200 __Request__host_calendar_changed_t msg;
201
202 host_notify_all(HOST_NOTIFY_CALENDAR_CHANGE, msg: &msg.Head, msg_size: sizeof(msg));
203}
204
205void
206host_notify_calendar_set(void)
207{
208 __Request__host_calendar_set_t msg;
209
210 host_notify_all(HOST_NOTIFY_CALENDAR_SET, msg: &msg.Head, msg_size: sizeof(msg));
211}
212