diff options
Diffstat (limited to 'fs/nfs/callback.c')
-rw-r--r-- | fs/nfs/callback.c | 321 |
1 files changed, 211 insertions, 110 deletions
diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index 4c8459e5bdee..2245bef50f37 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c | |||
@@ -12,6 +12,7 @@ | |||
12 | #include <linux/sunrpc/svc.h> | 12 | #include <linux/sunrpc/svc.h> |
13 | #include <linux/sunrpc/svcsock.h> | 13 | #include <linux/sunrpc/svcsock.h> |
14 | #include <linux/nfs_fs.h> | 14 | #include <linux/nfs_fs.h> |
15 | #include <linux/errno.h> | ||
15 | #include <linux/mutex.h> | 16 | #include <linux/mutex.h> |
16 | #include <linux/freezer.h> | 17 | #include <linux/freezer.h> |
17 | #include <linux/kthread.h> | 18 | #include <linux/kthread.h> |
@@ -23,6 +24,7 @@ | |||
23 | #include "nfs4_fs.h" | 24 | #include "nfs4_fs.h" |
24 | #include "callback.h" | 25 | #include "callback.h" |
25 | #include "internal.h" | 26 | #include "internal.h" |
27 | #include "netns.h" | ||
26 | 28 | ||
27 | #define NFSDBG_FACILITY NFSDBG_CALLBACK | 29 | #define NFSDBG_FACILITY NFSDBG_CALLBACK |
28 | 30 | ||
@@ -37,7 +39,32 @@ static struct nfs_callback_data nfs_callback_info[NFS4_MAX_MINOR_VERSION + 1]; | |||
37 | static DEFINE_MUTEX(nfs_callback_mutex); | 39 | static DEFINE_MUTEX(nfs_callback_mutex); |
38 | static struct svc_program nfs4_callback_program; | 40 | static struct svc_program nfs4_callback_program; |
39 | 41 | ||
40 | unsigned short nfs_callback_tcpport6; | 42 | static int nfs4_callback_up_net(struct svc_serv *serv, struct net *net) |
43 | { | ||
44 | int ret; | ||
45 | struct nfs_net *nn = net_generic(net, nfs_net_id); | ||
46 | |||
47 | ret = svc_create_xprt(serv, "tcp", net, PF_INET, | ||
48 | nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); | ||
49 | if (ret <= 0) | ||
50 | goto out_err; | ||
51 | nn->nfs_callback_tcpport = ret; | ||
52 | dprintk("NFS: Callback listener port = %u (af %u, net %p)\n", | ||
53 | nn->nfs_callback_tcpport, PF_INET, net); | ||
54 | |||
55 | ret = svc_create_xprt(serv, "tcp", net, PF_INET6, | ||
56 | nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); | ||
57 | if (ret > 0) { | ||
58 | nn->nfs_callback_tcpport6 = ret; | ||
59 | dprintk("NFS: Callback listener port = %u (af %u, net %p)\n", | ||
60 | nn->nfs_callback_tcpport6, PF_INET6, net); | ||
61 | } else if (ret != -EAFNOSUPPORT) | ||
62 | goto out_err; | ||
63 | return 0; | ||
64 | |||
65 | out_err: | ||
66 | return (ret) ? ret : -ENOMEM; | ||
67 | } | ||
41 | 68 | ||
42 | /* | 69 | /* |
43 | * This is the NFSv4 callback kernel thread. | 70 | * This is the NFSv4 callback kernel thread. |
@@ -78,38 +105,23 @@ nfs4_callback_svc(void *vrqstp) | |||
78 | * Prepare to bring up the NFSv4 callback service | 105 | * Prepare to bring up the NFSv4 callback service |
79 | */ | 106 | */ |
80 | static struct svc_rqst * | 107 | static struct svc_rqst * |
81 | nfs4_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt) | 108 | nfs4_callback_up(struct svc_serv *serv) |
82 | { | 109 | { |
83 | int ret; | ||
84 | |||
85 | ret = svc_create_xprt(serv, "tcp", &init_net, PF_INET, | ||
86 | nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); | ||
87 | if (ret <= 0) | ||
88 | goto out_err; | ||
89 | nfs_callback_tcpport = ret; | ||
90 | dprintk("NFS: Callback listener port = %u (af %u)\n", | ||
91 | nfs_callback_tcpport, PF_INET); | ||
92 | |||
93 | ret = svc_create_xprt(serv, "tcp", &init_net, PF_INET6, | ||
94 | nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); | ||
95 | if (ret > 0) { | ||
96 | nfs_callback_tcpport6 = ret; | ||
97 | dprintk("NFS: Callback listener port = %u (af %u)\n", | ||
98 | nfs_callback_tcpport6, PF_INET6); | ||
99 | } else if (ret == -EAFNOSUPPORT) | ||
100 | ret = 0; | ||
101 | else | ||
102 | goto out_err; | ||
103 | |||
104 | return svc_prepare_thread(serv, &serv->sv_pools[0], NUMA_NO_NODE); | 110 | return svc_prepare_thread(serv, &serv->sv_pools[0], NUMA_NO_NODE); |
105 | |||
106 | out_err: | ||
107 | if (ret == 0) | ||
108 | ret = -ENOMEM; | ||
109 | return ERR_PTR(ret); | ||
110 | } | 111 | } |
111 | 112 | ||
112 | #if defined(CONFIG_NFS_V4_1) | 113 | #if defined(CONFIG_NFS_V4_1) |
114 | static int nfs41_callback_up_net(struct svc_serv *serv, struct net *net) | ||
115 | { | ||
116 | /* | ||
117 | * Create an svc_sock for the back channel service that shares the | ||
118 | * fore channel connection. | ||
119 | * Returns the input port (0) and sets the svc_serv bc_xprt on success | ||
120 | */ | ||
121 | return svc_create_xprt(serv, "tcp-bc", net, PF_INET, 0, | ||
122 | SVC_SOCK_ANONYMOUS); | ||
123 | } | ||
124 | |||
113 | /* | 125 | /* |
114 | * The callback service for NFSv4.1 callbacks | 126 | * The callback service for NFSv4.1 callbacks |
115 | */ | 127 | */ |
@@ -149,28 +161,9 @@ nfs41_callback_svc(void *vrqstp) | |||
149 | * Bring up the NFSv4.1 callback service | 161 | * Bring up the NFSv4.1 callback service |
150 | */ | 162 | */ |
151 | static struct svc_rqst * | 163 | static struct svc_rqst * |
152 | nfs41_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt) | 164 | nfs41_callback_up(struct svc_serv *serv) |
153 | { | 165 | { |
154 | struct svc_rqst *rqstp; | 166 | struct svc_rqst *rqstp; |
155 | int ret; | ||
156 | |||
157 | /* | ||
158 | * Create an svc_sock for the back channel service that shares the | ||
159 | * fore channel connection. | ||
160 | * Returns the input port (0) and sets the svc_serv bc_xprt on success | ||
161 | */ | ||
162 | ret = svc_create_xprt(serv, "tcp-bc", &init_net, PF_INET, 0, | ||
163 | SVC_SOCK_ANONYMOUS); | ||
164 | if (ret < 0) { | ||
165 | rqstp = ERR_PTR(ret); | ||
166 | goto out; | ||
167 | } | ||
168 | |||
169 | /* | ||
170 | * Save the svc_serv in the transport so that it can | ||
171 | * be referenced when the session backchannel is initialized | ||
172 | */ | ||
173 | xprt->bc_serv = serv; | ||
174 | 167 | ||
175 | INIT_LIST_HEAD(&serv->sv_cb_list); | 168 | INIT_LIST_HEAD(&serv->sv_cb_list); |
176 | spin_lock_init(&serv->sv_cb_lock); | 169 | spin_lock_init(&serv->sv_cb_lock); |
@@ -180,90 +173,74 @@ nfs41_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt) | |||
180 | svc_xprt_put(serv->sv_bc_xprt); | 173 | svc_xprt_put(serv->sv_bc_xprt); |
181 | serv->sv_bc_xprt = NULL; | 174 | serv->sv_bc_xprt = NULL; |
182 | } | 175 | } |
183 | out: | ||
184 | dprintk("--> %s return %ld\n", __func__, | 176 | dprintk("--> %s return %ld\n", __func__, |
185 | IS_ERR(rqstp) ? PTR_ERR(rqstp) : 0); | 177 | IS_ERR(rqstp) ? PTR_ERR(rqstp) : 0); |
186 | return rqstp; | 178 | return rqstp; |
187 | } | 179 | } |
188 | 180 | ||
189 | static inline int nfs_minorversion_callback_svc_setup(u32 minorversion, | 181 | static void nfs_minorversion_callback_svc_setup(struct svc_serv *serv, |
190 | struct svc_serv *serv, struct rpc_xprt *xprt, | ||
191 | struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp)) | 182 | struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp)) |
192 | { | 183 | { |
193 | if (minorversion) { | 184 | *rqstpp = nfs41_callback_up(serv); |
194 | *rqstpp = nfs41_callback_up(serv, xprt); | 185 | *callback_svc = nfs41_callback_svc; |
195 | *callback_svc = nfs41_callback_svc; | ||
196 | } | ||
197 | return minorversion; | ||
198 | } | 186 | } |
199 | 187 | ||
200 | static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, | 188 | static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, |
201 | struct nfs_callback_data *cb_info) | 189 | struct svc_serv *serv) |
202 | { | 190 | { |
203 | if (minorversion) | 191 | if (minorversion) |
204 | xprt->bc_serv = cb_info->serv; | 192 | /* |
193 | * Save the svc_serv in the transport so that it can | ||
194 | * be referenced when the session backchannel is initialized | ||
195 | */ | ||
196 | xprt->bc_serv = serv; | ||
205 | } | 197 | } |
206 | #else | 198 | #else |
207 | static inline int nfs_minorversion_callback_svc_setup(u32 minorversion, | 199 | static int nfs41_callback_up_net(struct svc_serv *serv, struct net *net) |
208 | struct svc_serv *serv, struct rpc_xprt *xprt, | ||
209 | struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp)) | ||
210 | { | 200 | { |
211 | return 0; | 201 | return 0; |
212 | } | 202 | } |
213 | 203 | ||
204 | static void nfs_minorversion_callback_svc_setup(struct svc_serv *serv, | ||
205 | struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp)) | ||
206 | { | ||
207 | *rqstpp = ERR_PTR(-ENOTSUPP); | ||
208 | *callback_svc = ERR_PTR(-ENOTSUPP); | ||
209 | } | ||
210 | |||
214 | static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, | 211 | static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, |
215 | struct nfs_callback_data *cb_info) | 212 | struct svc_serv *serv) |
216 | { | 213 | { |
217 | } | 214 | } |
218 | #endif /* CONFIG_NFS_V4_1 */ | 215 | #endif /* CONFIG_NFS_V4_1 */ |
219 | 216 | ||
220 | /* | 217 | static int nfs_callback_start_svc(int minorversion, struct rpc_xprt *xprt, |
221 | * Bring up the callback thread if it is not already up. | 218 | struct svc_serv *serv) |
222 | */ | ||
223 | int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) | ||
224 | { | 219 | { |
225 | struct svc_serv *serv = NULL; | ||
226 | struct svc_rqst *rqstp; | 220 | struct svc_rqst *rqstp; |
227 | int (*callback_svc)(void *vrqstp); | 221 | int (*callback_svc)(void *vrqstp); |
228 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; | 222 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; |
229 | char svc_name[12]; | 223 | char svc_name[12]; |
230 | int ret = 0; | 224 | int ret; |
231 | int minorversion_setup; | ||
232 | struct net *net = &init_net; | ||
233 | 225 | ||
234 | mutex_lock(&nfs_callback_mutex); | 226 | nfs_callback_bc_serv(minorversion, xprt, serv); |
235 | if (cb_info->users++ || cb_info->task != NULL) { | ||
236 | nfs_callback_bc_serv(minorversion, xprt, cb_info); | ||
237 | goto out; | ||
238 | } | ||
239 | serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL); | ||
240 | if (!serv) { | ||
241 | ret = -ENOMEM; | ||
242 | goto out_err; | ||
243 | } | ||
244 | /* As there is only one thread we need to over-ride the | ||
245 | * default maximum of 80 connections | ||
246 | */ | ||
247 | serv->sv_maxconn = 1024; | ||
248 | 227 | ||
249 | ret = svc_bind(serv, net); | 228 | if (cb_info->task) |
250 | if (ret < 0) { | 229 | return 0; |
251 | printk(KERN_WARNING "NFS: bind callback service failed\n"); | ||
252 | goto out_err; | ||
253 | } | ||
254 | 230 | ||
255 | minorversion_setup = nfs_minorversion_callback_svc_setup(minorversion, | 231 | switch (minorversion) { |
256 | serv, xprt, &rqstp, &callback_svc); | 232 | case 0: |
257 | if (!minorversion_setup) { | ||
258 | /* v4.0 callback setup */ | 233 | /* v4.0 callback setup */ |
259 | rqstp = nfs4_callback_up(serv, xprt); | 234 | rqstp = nfs4_callback_up(serv); |
260 | callback_svc = nfs4_callback_svc; | 235 | callback_svc = nfs4_callback_svc; |
236 | break; | ||
237 | default: | ||
238 | nfs_minorversion_callback_svc_setup(serv, | ||
239 | &rqstp, &callback_svc); | ||
261 | } | 240 | } |
262 | 241 | ||
263 | if (IS_ERR(rqstp)) { | 242 | if (IS_ERR(rqstp)) |
264 | ret = PTR_ERR(rqstp); | 243 | return PTR_ERR(rqstp); |
265 | goto out_err; | ||
266 | } | ||
267 | 244 | ||
268 | svc_sock_update_bufs(serv); | 245 | svc_sock_update_bufs(serv); |
269 | 246 | ||
@@ -276,41 +253,165 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) | |||
276 | svc_exit_thread(cb_info->rqst); | 253 | svc_exit_thread(cb_info->rqst); |
277 | cb_info->rqst = NULL; | 254 | cb_info->rqst = NULL; |
278 | cb_info->task = NULL; | 255 | cb_info->task = NULL; |
279 | goto out_err; | 256 | return PTR_ERR(cb_info->task); |
257 | } | ||
258 | dprintk("nfs_callback_up: service started\n"); | ||
259 | return 0; | ||
260 | } | ||
261 | |||
262 | static void nfs_callback_down_net(u32 minorversion, struct svc_serv *serv, struct net *net) | ||
263 | { | ||
264 | struct nfs_net *nn = net_generic(net, nfs_net_id); | ||
265 | |||
266 | if (--nn->cb_users[minorversion]) | ||
267 | return; | ||
268 | |||
269 | dprintk("NFS: destroy per-net callback data; net=%p\n", net); | ||
270 | svc_shutdown_net(serv, net); | ||
271 | } | ||
272 | |||
273 | static int nfs_callback_up_net(int minorversion, struct svc_serv *serv, struct net *net) | ||
274 | { | ||
275 | struct nfs_net *nn = net_generic(net, nfs_net_id); | ||
276 | int ret; | ||
277 | |||
278 | if (nn->cb_users[minorversion]++) | ||
279 | return 0; | ||
280 | |||
281 | dprintk("NFS: create per-net callback data; net=%p\n", net); | ||
282 | |||
283 | ret = svc_bind(serv, net); | ||
284 | if (ret < 0) { | ||
285 | printk(KERN_WARNING "NFS: bind callback service failed\n"); | ||
286 | goto err_bind; | ||
287 | } | ||
288 | |||
289 | switch (minorversion) { | ||
290 | case 0: | ||
291 | ret = nfs4_callback_up_net(serv, net); | ||
292 | break; | ||
293 | case 1: | ||
294 | ret = nfs41_callback_up_net(serv, net); | ||
295 | break; | ||
296 | default: | ||
297 | printk(KERN_ERR "NFS: unknown callback version: %d\n", | ||
298 | minorversion); | ||
299 | ret = -EINVAL; | ||
300 | break; | ||
280 | } | 301 | } |
281 | out: | 302 | |
303 | if (ret < 0) { | ||
304 | printk(KERN_ERR "NFS: callback service start failed\n"); | ||
305 | goto err_socks; | ||
306 | } | ||
307 | return 0; | ||
308 | |||
309 | err_socks: | ||
310 | svc_rpcb_cleanup(serv, net); | ||
311 | err_bind: | ||
312 | dprintk("NFS: Couldn't create callback socket: err = %d; " | ||
313 | "net = %p\n", ret, net); | ||
314 | return ret; | ||
315 | } | ||
316 | |||
317 | static struct svc_serv *nfs_callback_create_svc(int minorversion) | ||
318 | { | ||
319 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; | ||
320 | struct svc_serv *serv; | ||
321 | |||
322 | /* | ||
323 | * Check whether we're already up and running. | ||
324 | */ | ||
325 | if (cb_info->task) { | ||
326 | /* | ||
327 | * Note: increase service usage, because later in case of error | ||
328 | * svc_destroy() will be called. | ||
329 | */ | ||
330 | svc_get(cb_info->serv); | ||
331 | return cb_info->serv; | ||
332 | } | ||
333 | |||
334 | /* | ||
335 | * Sanity check: if there's no task, | ||
336 | * we should be the first user ... | ||
337 | */ | ||
338 | if (cb_info->users) | ||
339 | printk(KERN_WARNING "nfs_callback_create_svc: no kthread, %d users??\n", | ||
340 | cb_info->users); | ||
341 | |||
342 | serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL); | ||
343 | if (!serv) { | ||
344 | printk(KERN_ERR "nfs_callback_create_svc: create service failed\n"); | ||
345 | return ERR_PTR(-ENOMEM); | ||
346 | } | ||
347 | /* As there is only one thread we need to over-ride the | ||
348 | * default maximum of 80 connections | ||
349 | */ | ||
350 | serv->sv_maxconn = 1024; | ||
351 | dprintk("nfs_callback_create_svc: service created\n"); | ||
352 | return serv; | ||
353 | } | ||
354 | |||
355 | /* | ||
356 | * Bring up the callback thread if it is not already up. | ||
357 | */ | ||
358 | int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) | ||
359 | { | ||
360 | struct svc_serv *serv; | ||
361 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; | ||
362 | int ret; | ||
363 | struct net *net = xprt->xprt_net; | ||
364 | |||
365 | mutex_lock(&nfs_callback_mutex); | ||
366 | |||
367 | serv = nfs_callback_create_svc(minorversion); | ||
368 | if (IS_ERR(serv)) { | ||
369 | ret = PTR_ERR(serv); | ||
370 | goto err_create; | ||
371 | } | ||
372 | |||
373 | ret = nfs_callback_up_net(minorversion, serv, net); | ||
374 | if (ret < 0) | ||
375 | goto err_net; | ||
376 | |||
377 | ret = nfs_callback_start_svc(minorversion, xprt, serv); | ||
378 | if (ret < 0) | ||
379 | goto err_start; | ||
380 | |||
381 | cb_info->users++; | ||
282 | /* | 382 | /* |
283 | * svc_create creates the svc_serv with sv_nrthreads == 1, and then | 383 | * svc_create creates the svc_serv with sv_nrthreads == 1, and then |
284 | * svc_prepare_thread increments that. So we need to call svc_destroy | 384 | * svc_prepare_thread increments that. So we need to call svc_destroy |
285 | * on both success and failure so that the refcount is 1 when the | 385 | * on both success and failure so that the refcount is 1 when the |
286 | * thread exits. | 386 | * thread exits. |
287 | */ | 387 | */ |
288 | if (serv) | 388 | err_net: |
289 | svc_destroy(serv); | 389 | svc_destroy(serv); |
390 | err_create: | ||
290 | mutex_unlock(&nfs_callback_mutex); | 391 | mutex_unlock(&nfs_callback_mutex); |
291 | return ret; | 392 | return ret; |
292 | out_err: | 393 | |
293 | dprintk("NFS: Couldn't create callback socket or server thread; " | 394 | err_start: |
294 | "err = %d\n", ret); | 395 | nfs_callback_down_net(minorversion, serv, net); |
295 | cb_info->users--; | 396 | dprintk("NFS: Couldn't create server thread; err = %d\n", ret); |
296 | if (serv) | 397 | goto err_net; |
297 | svc_shutdown_net(serv, net); | ||
298 | goto out; | ||
299 | } | 398 | } |
300 | 399 | ||
301 | /* | 400 | /* |
302 | * Kill the callback thread if it's no longer being used. | 401 | * Kill the callback thread if it's no longer being used. |
303 | */ | 402 | */ |
304 | void nfs_callback_down(int minorversion) | 403 | void nfs_callback_down(int minorversion, struct net *net) |
305 | { | 404 | { |
306 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; | 405 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; |
307 | 406 | ||
308 | mutex_lock(&nfs_callback_mutex); | 407 | mutex_lock(&nfs_callback_mutex); |
408 | nfs_callback_down_net(minorversion, cb_info->serv, net); | ||
309 | cb_info->users--; | 409 | cb_info->users--; |
310 | if (cb_info->users == 0 && cb_info->task != NULL) { | 410 | if (cb_info->users == 0 && cb_info->task != NULL) { |
311 | kthread_stop(cb_info->task); | 411 | kthread_stop(cb_info->task); |
312 | svc_shutdown_net(cb_info->serv, &init_net); | 412 | dprintk("nfs_callback_down: service stopped\n"); |
313 | svc_exit_thread(cb_info->rqst); | 413 | svc_exit_thread(cb_info->rqst); |
414 | dprintk("nfs_callback_down: service destroyed\n"); | ||
314 | cb_info->serv = NULL; | 415 | cb_info->serv = NULL; |
315 | cb_info->rqst = NULL; | 416 | cb_info->rqst = NULL; |
316 | cb_info->task = NULL; | 417 | cb_info->task = NULL; |