2 * Copyright (c) 2011-2014 Apple Inc. All rights reserved.
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
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.
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
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.
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
29 #include <nfs/nfs_conf.h>
33 #include <sys/param.h>
34 #include <sys/mount_internal.h>
35 #include <sys/malloc.h>
36 #include <sys/queue.h>
38 #include <libkern/libkern.h>
39 #include <libkern/OSAtomic.h>
40 #include <kern/debug.h>
41 #include <kern/thread.h>
43 #include <nfs/rpcv2.h>
44 #include <nfs/nfsproto.h>
48 #define DPRINT(fmt, ...) printf(fmt,## __VA_ARGS__)
50 #define DPRINT(fmt, ...)
54 TAILQ_ENTRY(nfsrv_uc_arg
) nua_svcq
;
56 struct nfsrv_sock
*nua_slp
;
57 int nua_waitflag
; /* Should always be MBUF_DONTWAIT */
61 #define NFS_UC_QUEUED 0x0001
63 #define NFS_UC_HASH_SZ 7
64 #define NFS_UC_HASH(x) ((((uint32_t)(uintptr_t)(x)) >> 3) % nfsrv_uc_thread_count)
66 TAILQ_HEAD(nfsrv_uc_q
, nfsrv_uc_arg
);
68 static struct nfsrv_uc_queue
{
70 struct nfsrv_uc_q ucq_queue
[1];
73 } nfsrv_uc_queue_tbl
[NFS_UC_HASH_SZ
];
74 #define NFS_UC_QUEUE_SLEEPING 0x0001
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
;
81 extern kern_return_t
thread_terminate(thread_t
);
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
;
91 * Thread that dequeues up-calls and runs the nfsrv_rcv routine
94 nfsrv_uc_thread(void *arg
, wait_result_t wr __unused
)
96 int qi
= (int)(uintptr_t)arg
;
98 struct nfsrv_uc_arg
*ep
= NULL
;
99 struct nfsrv_uc_queue
*myqueue
= &nfsrv_uc_queue_tbl
[qi
];
101 DPRINT("nfsrv_uc_thread %d started\n", qi
);
102 while (!nfsrv_uc_shutdown
) {
103 lck_mtx_lock(myqueue
->ucq_lock
);
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
;
110 printf("nfsrv_uc_thread received error %d\n", error
);
113 if (nfsrv_uc_shutdown
) {
114 lck_mtx_unlock(myqueue
->ucq_lock
);
119 ep
= TAILQ_FIRST(myqueue
->ucq_queue
);
120 DPRINT("nfsrv_uc_thread:%d dequeue %p from %p\n", qi
, ep
, myqueue
);
122 TAILQ_REMOVE(myqueue
->ucq_queue
, ep
, nua_svcq
);
124 ep
->nua_flags
&= ~NFS_UC_QUEUED
;
126 lck_mtx_unlock(myqueue
->ucq_lock
);
128 #ifdef NFS_UC_Q_DEBUG
129 OSDecrementAtomic(&nfsrv_uc_queue_count
);
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
);
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
);
141 thread_terminate(current_thread());
145 * Dequeue a closed nfsrv_sock if needed from the up-call queue.
146 * Call from nfsrv_zapsock
149 nfsrv_uc_dequeue(struct nfsrv_sock
*slp
)
151 struct nfsrv_uc_arg
*ap
= slp
->ns_ua
;
152 struct nfsrv_uc_queue
*myqueue
= &nfsrv_uc_queue_tbl
[ap
->nua_qi
];
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.
159 if (ap
== NULL
|| (ap
->nua_flags
& NFS_UC_QUEUED
) == 0) {
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
);
172 FREE(slp
->ns_ua
, M_TEMP
);
174 lck_mtx_unlock(myqueue
->ucq_lock
);
178 * Allocate and initialize globals for nfsrv_sock up-call support.
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;
192 nfsrv_uc_shutdown_lock
= lck_mtx_alloc_init(nfsrv_uc_group
, LCK_ATTR_NULL
);
196 * Start up-call threads to service nfsrv_sock(s)
197 * Called from the first call of nfsrv_uc_addsock
205 #ifdef NFS_UC_Q_DEBUG
206 if (!nfsrv_uc_use_proxy
) {
210 DPRINT("nfsrv_uc_start\n");
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
);
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
);
222 nfsrv_uc_thread_count
++;
224 printf("nfsd: Could not start nfsrv_uc_thread: %d\n", error
);
227 if (nfsrv_uc_thread_count
== 0) {
228 printf("nfsd: Could not start nfsd proxy up-call service. Falling back\n");
233 #ifdef NFS_UC_Q_DEBUG
234 nfsrv_uc_queue_count
= 0ULL;
235 nfsrv_uc_queue_max_seen
= 0ULL;
237 lck_mtx_unlock(nfsrv_uc_shutdown_lock
);
241 * Stop the up-call threads.
242 * Called from nfsrv_uc_cleanup.
248 int32_t thread_count
= nfsrv_uc_thread_count
;
250 DPRINT("Entering nfsrv_uc_stop\n");
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
);
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
);
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
);
271 nfsrv_uc_queue_tbl
[i
].ucq_thd
= THREAD_NULL
;
274 /* Enable restarting */
275 nfsrv_uc_shutdown
= 0;
276 lck_mtx_unlock(nfsrv_uc_shutdown_lock
);
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.
286 nfsrv_uc_cleanup(void)
290 DPRINT("Entering nfsrv_uc_cleanup\n");
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.
296 for (i
= 0; i
< NFS_UC_HASH_SZ
; i
++) {
297 struct nfsrv_uc_queue
*queue
= &nfsrv_uc_queue_tbl
[i
];
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
;
305 lck_mtx_unlock(queue
->ucq_lock
);
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.
320 nfsrv_uc_proxy(socket_t so
, void *arg
, int waitflag
)
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
];
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 */
335 uap
->nua_waitflag
= waitflag
;
337 TAILQ_INSERT_TAIL(myqueue
->ucq_queue
, uap
, nua_svcq
);
339 uap
->nua_flags
|= NFS_UC_QUEUED
;
340 if (myqueue
->ucq_flags
& NFS_UC_QUEUE_SLEEPING
) {
344 #ifdef NFS_UC_Q_DEBUG
346 uint32_t count
= OSIncrementAtomic(&nfsrv_uc_queue_count
);
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
;
353 if (nfsrv_uc_queue_limit
&& count
> nfsrv_uc_queue_limit
) {
354 panic("nfsd up-call queue limit exceeded\n");
358 lck_mtx_unlock(myqueue
->ucq_lock
);
363 * Set the up-call routine on the socket associated with the passed in
365 * Assumes nfsd_mutex is held.
368 nfsrv_uc_addsock(struct nfsrv_sock
*slp
, int start
)
371 struct nfsrv_uc_arg
*arg
;
373 if (start
&& nfsrv_uc_thread_count
== 0) {
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
382 if (nfsrv_uc_thread_count
) {
383 MALLOC(arg
, struct nfsrv_uc_arg
*, sizeof(struct nfsrv_uc_arg
), M_TEMP
, M_WAITOK
| M_ZERO
);
390 arg
->nua_qi
= NFS_UC_HASH(slp
);
392 sock_setupcall(slp
->ns_so
, nfsrv_uc_proxy
, arg
);
396 DPRINT("setting nfsrv_rcv up-call\n");
397 sock_setupcall(slp
->ns_so
, nfsrv_rcv
, slp
);
400 /* just playin' it safe */
401 sock_setsockopt(slp
->ns_so
, SOL_SOCKET
, SO_UPCALLCLOSEWAIT
, &on
, sizeof(on
));
406 #endif /* CONFIG_NFS_SERVER */