]> git.saurik.com Git - apple/xnu.git/blob - bsd/nfs/nfs_upcall.c
b719f88a0b581eab4b7bd908a77c6ad01f06880f
[apple/xnu.git] / bsd / nfs / nfs_upcall.c
1 /*
2 * Copyright (c) 2011-2014 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 <nfs/nfs_conf.h>
30 #if CONFIG_NFS_SERVER
31
32 #include <stdint.h>
33 #include <sys/param.h>
34 #include <sys/mount_internal.h>
35 #include <sys/malloc.h>
36 #include <sys/queue.h>
37
38 #include <libkern/libkern.h>
39 #include <libkern/OSAtomic.h>
40 #include <kern/debug.h>
41 #include <kern/thread.h>
42
43 #include <nfs/rpcv2.h>
44 #include <nfs/nfsproto.h>
45 #include <nfs/nfs.h>
46
47 #ifdef NFS_UC_DEBUG
48 #define DPRINT(fmt, ...) printf(fmt,## __VA_ARGS__)
49 #else
50 #define DPRINT(fmt, ...)
51 #endif
52
53 struct nfsrv_uc_arg {
54 TAILQ_ENTRY(nfsrv_uc_arg) nua_svcq;
55 socket_t nua_so;
56 struct nfsrv_sock *nua_slp;
57 int nua_waitflag; /* Should always be MBUF_DONTWAIT */
58 uint32_t nua_flags;
59 uint32_t nua_qi;
60 };
61 #define NFS_UC_QUEUED 0x0001
62
63 #define NFS_UC_HASH_SZ 7
64 #define NFS_UC_HASH(x) ((((uint32_t)(uintptr_t)(x)) >> 3) % nfsrv_uc_thread_count)
65
66 TAILQ_HEAD(nfsrv_uc_q, nfsrv_uc_arg);
67
68 static struct nfsrv_uc_queue {
69 lck_mtx_t *ucq_lock;
70 struct nfsrv_uc_q ucq_queue[1];
71 thread_t ucq_thd;
72 uint32_t ucq_flags;
73 } nfsrv_uc_queue_tbl[NFS_UC_HASH_SZ];
74 #define NFS_UC_QUEUE_SLEEPING 0x0001
75
76 static lck_grp_t *nfsrv_uc_group;
77 static lck_mtx_t *nfsrv_uc_shutdown_lock;
78 static volatile int nfsrv_uc_shutdown = 0;
79 static int32_t nfsrv_uc_thread_count;
80
81 extern kern_return_t thread_terminate(thread_t);
82
83 #ifdef NFS_UC_Q_DEBUG
84 int nfsrv_uc_use_proxy = 1;
85 uint32_t nfsrv_uc_queue_limit;
86 uint32_t nfsrv_uc_queue_max_seen;
87 volatile uint32_t nfsrv_uc_queue_count;
88 #endif
89
90 /*
91 * Thread that dequeues up-calls and runs the nfsrv_rcv routine
92 */
93 static void
94 nfsrv_uc_thread(void *arg, wait_result_t wr __unused)
95 {
96 int qi = (int)(uintptr_t)arg;
97 int error;
98 struct nfsrv_uc_arg *ep = NULL;
99 struct nfsrv_uc_queue *myqueue = &nfsrv_uc_queue_tbl[qi];
100
101 DPRINT("nfsrv_uc_thread %d started\n", qi);
102 while (!nfsrv_uc_shutdown) {
103 lck_mtx_lock(myqueue->ucq_lock);
104
105 while (!nfsrv_uc_shutdown && TAILQ_EMPTY(myqueue->ucq_queue)) {
106 myqueue->ucq_flags |= NFS_UC_QUEUE_SLEEPING;
107 error = msleep(myqueue, myqueue->ucq_lock, PSOCK, "nfsd_upcall_handler", NULL);
108 myqueue->ucq_flags &= ~NFS_UC_QUEUE_SLEEPING;
109 if (error) {
110 printf("nfsrv_uc_thread received error %d\n", error);
111 }
112 }
113 if (nfsrv_uc_shutdown) {
114 lck_mtx_unlock(myqueue->ucq_lock);
115 break;
116 }
117
118
119 ep = TAILQ_FIRST(myqueue->ucq_queue);
120 DPRINT("nfsrv_uc_thread:%d dequeue %p from %p\n", qi, ep, myqueue);
121
122 TAILQ_REMOVE(myqueue->ucq_queue, ep, nua_svcq);
123
124 ep->nua_flags &= ~NFS_UC_QUEUED;
125
126 lck_mtx_unlock(myqueue->ucq_lock);
127
128 #ifdef NFS_UC_Q_DEBUG
129 OSDecrementAtomic(&nfsrv_uc_queue_count);
130 #endif
131
132 DPRINT("calling nfsrv_rcv for %p\n", (void *)ep->nua_slp);
133 nfsrv_rcv(ep->nua_so, (void *)ep->nua_slp, ep->nua_waitflag);
134 }
135
136 lck_mtx_lock(nfsrv_uc_shutdown_lock);
137 nfsrv_uc_thread_count--;
138 wakeup(&nfsrv_uc_thread_count);
139 lck_mtx_unlock(nfsrv_uc_shutdown_lock);
140
141 thread_terminate(current_thread());
142 }
143
144 /*
145 * Dequeue a closed nfsrv_sock if needed from the up-call queue.
146 * Call from nfsrv_zapsock
147 */
148 void
149 nfsrv_uc_dequeue(struct nfsrv_sock *slp)
150 {
151 struct nfsrv_uc_arg *ap = slp->ns_ua;
152 struct nfsrv_uc_queue *myqueue = &nfsrv_uc_queue_tbl[ap->nua_qi];
153
154 /*
155 * We assume that the socket up-calls have been stop and the socket
156 * is shutting down so no need for acquiring the lock to check that
157 * the flag is cleared.
158 */
159 if (ap == NULL || (ap->nua_flags & NFS_UC_QUEUED) == 0) {
160 return;
161 }
162 /* If we're queued we might race with nfsrv_uc_thread */
163 lck_mtx_lock(myqueue->ucq_lock);
164 if (ap->nua_flags & NFS_UC_QUEUED) {
165 printf("nfsrv_uc_dequeue remove %p\n", ap);
166 TAILQ_REMOVE(myqueue->ucq_queue, ap, nua_svcq);
167 ap->nua_flags &= ~NFS_UC_QUEUED;
168 #ifdef NFS_UC_Q_DEBUG
169 OSDecrementAtomic(&nfsrv_uc_queue_count);
170 #endif
171 }
172 FREE(slp->ns_ua, M_TEMP);
173 slp->ns_ua = NULL;
174 lck_mtx_unlock(myqueue->ucq_lock);
175 }
176
177 /*
178 * Allocate and initialize globals for nfsrv_sock up-call support.
179 */
180 void
181 nfsrv_uc_init(void)
182 {
183 int i;
184
185 nfsrv_uc_group = lck_grp_alloc_init("nfs_upcall_locks", LCK_GRP_ATTR_NULL);
186 for (i = 0; i < NFS_UC_HASH_SZ; i++) {
187 TAILQ_INIT(nfsrv_uc_queue_tbl[i].ucq_queue);
188 nfsrv_uc_queue_tbl[i].ucq_lock = lck_mtx_alloc_init(nfsrv_uc_group, LCK_ATTR_NULL);
189 nfsrv_uc_queue_tbl[i].ucq_thd = THREAD_NULL;
190 nfsrv_uc_queue_tbl[i].ucq_flags = 0;
191 }
192 nfsrv_uc_shutdown_lock = lck_mtx_alloc_init(nfsrv_uc_group, LCK_ATTR_NULL);
193 }
194
195 /*
196 * Start up-call threads to service nfsrv_sock(s)
197 * Called from the first call of nfsrv_uc_addsock
198 */
199 static void
200 nfsrv_uc_start(void)
201 {
202 int32_t i;
203 int error;
204
205 #ifdef NFS_UC_Q_DEBUG
206 if (!nfsrv_uc_use_proxy) {
207 return;
208 }
209 #endif
210 DPRINT("nfsrv_uc_start\n");
211
212 /* Wait until previous shutdown finishes */
213 lck_mtx_lock(nfsrv_uc_shutdown_lock);
214 while (nfsrv_uc_shutdown || nfsrv_uc_thread_count > 0) {
215 msleep(&nfsrv_uc_thread_count, nfsrv_uc_shutdown_lock, PSOCK, "nfsd_upcall_shutdown_wait", NULL);
216 }
217
218 /* Start up-call threads */
219 for (i = 0; i < NFS_UC_HASH_SZ; i++) {
220 error = kernel_thread_start(nfsrv_uc_thread, (void *)(uintptr_t)i, &nfsrv_uc_queue_tbl[nfsrv_uc_thread_count].ucq_thd);
221 if (!error) {
222 nfsrv_uc_thread_count++;
223 } else {
224 printf("nfsd: Could not start nfsrv_uc_thread: %d\n", error);
225 }
226 }
227 if (nfsrv_uc_thread_count == 0) {
228 printf("nfsd: Could not start nfsd proxy up-call service. Falling back\n");
229 goto out;
230 }
231
232 out:
233 #ifdef NFS_UC_Q_DEBUG
234 nfsrv_uc_queue_count = 0ULL;
235 nfsrv_uc_queue_max_seen = 0ULL;
236 #endif
237 lck_mtx_unlock(nfsrv_uc_shutdown_lock);
238 }
239
240 /*
241 * Stop the up-call threads.
242 * Called from nfsrv_uc_cleanup.
243 */
244 static void
245 nfsrv_uc_stop(void)
246 {
247 int32_t i;
248 int32_t thread_count = nfsrv_uc_thread_count;
249
250 DPRINT("Entering nfsrv_uc_stop\n");
251
252 /* Signal up-call threads to stop */
253 nfsrv_uc_shutdown = 1;
254 for (i = 0; i < thread_count; i++) {
255 lck_mtx_lock(nfsrv_uc_queue_tbl[i].ucq_lock);
256 wakeup(&nfsrv_uc_queue_tbl[i]);
257 lck_mtx_unlock(nfsrv_uc_queue_tbl[i].ucq_lock);
258 }
259
260 /* Wait until they are done shutting down */
261 lck_mtx_lock(nfsrv_uc_shutdown_lock);
262 while (nfsrv_uc_thread_count > 0) {
263 msleep(&nfsrv_uc_thread_count, nfsrv_uc_shutdown_lock, PSOCK, "nfsd_upcall_shutdown_stop", NULL);
264 }
265
266 /* Deallocate old threads */
267 for (i = 0; i < nfsrv_uc_thread_count; i++) {
268 if (nfsrv_uc_queue_tbl[i].ucq_thd != THREAD_NULL) {
269 thread_deallocate(nfsrv_uc_queue_tbl[i].ucq_thd);
270 }
271 nfsrv_uc_queue_tbl[i].ucq_thd = THREAD_NULL;
272 }
273
274 /* Enable restarting */
275 nfsrv_uc_shutdown = 0;
276 lck_mtx_unlock(nfsrv_uc_shutdown_lock);
277 }
278
279 /*
280 * Shutdown up-calls for nfsrv_socks.
281 * Make sure nothing is queued on the up-call queues
282 * Shutdown the up-call threads
283 * Called from nfssvc_cleanup.
284 */
285 void
286 nfsrv_uc_cleanup(void)
287 {
288 int i;
289
290 DPRINT("Entering nfsrv_uc_cleanup\n");
291
292 /*
293 * Every thing should be dequeued at this point or will be as sockets are closed
294 * but to be safe, we'll make sure.
295 */
296 for (i = 0; i < NFS_UC_HASH_SZ; i++) {
297 struct nfsrv_uc_queue *queue = &nfsrv_uc_queue_tbl[i];
298
299 lck_mtx_lock(queue->ucq_lock);
300 while (!TAILQ_EMPTY(queue->ucq_queue)) {
301 struct nfsrv_uc_arg *ep = TAILQ_FIRST(queue->ucq_queue);
302 TAILQ_REMOVE(queue->ucq_queue, ep, nua_svcq);
303 ep->nua_flags &= ~NFS_UC_QUEUED;
304 }
305 lck_mtx_unlock(queue->ucq_lock);
306 }
307
308 nfsrv_uc_stop();
309 }
310
311 /*
312 * This is the nfs up-call routine for server sockets.
313 * We used to set nfsrv_rcv as the up-call routine, but
314 * recently that seems like we are doing to much work for
315 * the interface thread, so we just queue the arguments
316 * that we would have gotten for nfsrv_rcv and let a
317 * worker thread dequeue them and pass them on to nfsrv_rcv.
318 */
319 static void
320 nfsrv_uc_proxy(socket_t so, void *arg, int waitflag)
321 {
322 struct nfsrv_uc_arg *uap = (struct nfsrv_uc_arg *)arg;
323 int qi = uap->nua_qi;
324 struct nfsrv_uc_queue *myqueue = &nfsrv_uc_queue_tbl[qi];
325
326 lck_mtx_lock(myqueue->ucq_lock);
327 DPRINT("nfsrv_uc_proxy called for %p (%p)\n", uap, uap->nua_slp);
328 DPRINT("\tUp-call queued on %d for wakeup of %p\n", qi, myqueue);
329 if (uap == NULL || uap->nua_flags & NFS_UC_QUEUED) {
330 lck_mtx_unlock(myqueue->ucq_lock);
331 return; /* Already queued or freed */
332 }
333
334 uap->nua_so = so;
335 uap->nua_waitflag = waitflag;
336
337 TAILQ_INSERT_TAIL(myqueue->ucq_queue, uap, nua_svcq);
338
339 uap->nua_flags |= NFS_UC_QUEUED;
340 if (myqueue->ucq_flags & NFS_UC_QUEUE_SLEEPING) {
341 wakeup(myqueue);
342 }
343
344 #ifdef NFS_UC_Q_DEBUG
345 {
346 uint32_t count = OSIncrementAtomic(&nfsrv_uc_queue_count);
347
348 /* This is a bit racey but just for debug */
349 if (count > nfsrv_uc_queue_max_seen) {
350 nfsrv_uc_queue_max_seen = count;
351 }
352
353 if (nfsrv_uc_queue_limit && count > nfsrv_uc_queue_limit) {
354 panic("nfsd up-call queue limit exceeded\n");
355 }
356 }
357 #endif
358 lck_mtx_unlock(myqueue->ucq_lock);
359 }
360
361
362 /*
363 * Set the up-call routine on the socket associated with the passed in
364 * nfsrv_sock.
365 * Assumes nfsd_mutex is held.
366 */
367 void
368 nfsrv_uc_addsock(struct nfsrv_sock *slp, int start)
369 {
370 int on = 1;
371 struct nfsrv_uc_arg *arg;
372
373 if (start && nfsrv_uc_thread_count == 0) {
374 nfsrv_uc_start();
375 }
376
377 /*
378 * We don't take a lock since once we're up nfsrv_uc_thread_count does
379 * not change until shutdown and then we should not be adding sockets to
380 * generate up-calls.
381 */
382 if (nfsrv_uc_thread_count) {
383 MALLOC(arg, struct nfsrv_uc_arg *, sizeof(struct nfsrv_uc_arg), M_TEMP, M_WAITOK | M_ZERO);
384 if (arg == NULL) {
385 goto direct;
386 }
387
388 slp->ns_ua = arg;
389 arg->nua_slp = slp;
390 arg->nua_qi = NFS_UC_HASH(slp);
391
392 sock_setupcall(slp->ns_so, nfsrv_uc_proxy, arg);
393 } else {
394 direct:
395 slp->ns_ua = NULL;
396 DPRINT("setting nfsrv_rcv up-call\n");
397 sock_setupcall(slp->ns_so, nfsrv_rcv, slp);
398 }
399
400 /* just playin' it safe */
401 sock_setsockopt(slp->ns_so, SOL_SOCKET, SO_UPCALLCLOSEWAIT, &on, sizeof(on));
402
403 return;
404 }
405
406 #endif /* CONFIG_NFS_SERVER */