]>
Commit | Line | Data |
---|---|---|
24c8dbbb DH |
1 | /* client.c: NFS client sharing and management code |
2 | * | |
3 | * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved. | |
4 | * Written by David Howells ([email protected]) | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU General Public License | |
8 | * as published by the Free Software Foundation; either version | |
9 | * 2 of the License, or (at your option) any later version. | |
10 | */ | |
11 | ||
12 | ||
13 | #include <linux/config.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/init.h> | |
16 | ||
17 | #include <linux/time.h> | |
18 | #include <linux/kernel.h> | |
19 | #include <linux/mm.h> | |
20 | #include <linux/string.h> | |
21 | #include <linux/stat.h> | |
22 | #include <linux/errno.h> | |
23 | #include <linux/unistd.h> | |
24 | #include <linux/sunrpc/clnt.h> | |
25 | #include <linux/sunrpc/stats.h> | |
26 | #include <linux/sunrpc/metrics.h> | |
27 | #include <linux/nfs_fs.h> | |
28 | #include <linux/nfs_mount.h> | |
29 | #include <linux/nfs4_mount.h> | |
30 | #include <linux/lockd/bind.h> | |
31 | #include <linux/smp_lock.h> | |
32 | #include <linux/seq_file.h> | |
33 | #include <linux/mount.h> | |
34 | #include <linux/nfs_idmap.h> | |
35 | #include <linux/vfs.h> | |
36 | #include <linux/inet.h> | |
37 | #include <linux/nfs_xdr.h> | |
38 | ||
39 | #include <asm/system.h> | |
40 | ||
41 | #include "nfs4_fs.h" | |
42 | #include "callback.h" | |
43 | #include "delegation.h" | |
44 | #include "iostat.h" | |
45 | #include "internal.h" | |
46 | ||
47 | #define NFSDBG_FACILITY NFSDBG_CLIENT | |
48 | ||
49 | static DEFINE_SPINLOCK(nfs_client_lock); | |
50 | static LIST_HEAD(nfs_client_list); | |
51 | static DECLARE_WAIT_QUEUE_HEAD(nfs_client_active_wq); | |
52 | ||
5006a76c DH |
53 | /* |
54 | * RPC cruft for NFS | |
55 | */ | |
56 | static struct rpc_version *nfs_version[5] = { | |
57 | [2] = &nfs_version2, | |
58 | #ifdef CONFIG_NFS_V3 | |
59 | [3] = &nfs_version3, | |
60 | #endif | |
61 | #ifdef CONFIG_NFS_V4 | |
62 | [4] = &nfs_version4, | |
63 | #endif | |
64 | }; | |
65 | ||
66 | struct rpc_program nfs_program = { | |
67 | .name = "nfs", | |
68 | .number = NFS_PROGRAM, | |
69 | .nrvers = ARRAY_SIZE(nfs_version), | |
70 | .version = nfs_version, | |
71 | .stats = &nfs_rpcstat, | |
72 | .pipe_dir_name = "/nfs", | |
73 | }; | |
74 | ||
75 | struct rpc_stat nfs_rpcstat = { | |
76 | .program = &nfs_program | |
77 | }; | |
78 | ||
79 | ||
80 | #ifdef CONFIG_NFS_V3_ACL | |
81 | static struct rpc_stat nfsacl_rpcstat = { &nfsacl_program }; | |
82 | static struct rpc_version * nfsacl_version[] = { | |
83 | [3] = &nfsacl_version3, | |
84 | }; | |
85 | ||
86 | struct rpc_program nfsacl_program = { | |
87 | .name = "nfsacl", | |
88 | .number = NFS_ACL_PROGRAM, | |
89 | .nrvers = ARRAY_SIZE(nfsacl_version), | |
90 | .version = nfsacl_version, | |
91 | .stats = &nfsacl_rpcstat, | |
92 | }; | |
93 | #endif /* CONFIG_NFS_V3_ACL */ | |
94 | ||
24c8dbbb DH |
95 | /* |
96 | * Allocate a shared client record | |
97 | * | |
98 | * Since these are allocated/deallocated very rarely, we don't | |
99 | * bother putting them in a slab cache... | |
100 | */ | |
101 | static struct nfs_client *nfs_alloc_client(const char *hostname, | |
102 | const struct sockaddr_in *addr, | |
103 | int nfsversion) | |
104 | { | |
105 | struct nfs_client *clp; | |
106 | int error; | |
107 | ||
108 | if ((clp = kzalloc(sizeof(*clp), GFP_KERNEL)) == NULL) | |
109 | goto error_0; | |
110 | ||
111 | error = rpciod_up(); | |
112 | if (error < 0) { | |
113 | dprintk("%s: couldn't start rpciod! Error = %d\n", | |
114 | __FUNCTION__, error); | |
115 | __set_bit(NFS_CS_RPCIOD, &clp->cl_res_state); | |
116 | goto error_1; | |
117 | } | |
118 | ||
119 | if (nfsversion == 4) { | |
120 | if (nfs_callback_up() < 0) | |
121 | goto error_2; | |
122 | __set_bit(NFS_CS_CALLBACK, &clp->cl_res_state); | |
123 | } | |
124 | ||
125 | atomic_set(&clp->cl_count, 1); | |
126 | clp->cl_cons_state = NFS_CS_INITING; | |
127 | ||
128 | clp->cl_nfsversion = nfsversion; | |
129 | memcpy(&clp->cl_addr, addr, sizeof(clp->cl_addr)); | |
130 | ||
131 | if (hostname) { | |
132 | clp->cl_hostname = kstrdup(hostname, GFP_KERNEL); | |
133 | if (!clp->cl_hostname) | |
134 | goto error_3; | |
135 | } | |
136 | ||
137 | INIT_LIST_HEAD(&clp->cl_superblocks); | |
138 | clp->cl_rpcclient = ERR_PTR(-EINVAL); | |
139 | ||
140 | #ifdef CONFIG_NFS_V4 | |
141 | init_rwsem(&clp->cl_sem); | |
142 | INIT_LIST_HEAD(&clp->cl_delegations); | |
143 | INIT_LIST_HEAD(&clp->cl_state_owners); | |
144 | INIT_LIST_HEAD(&clp->cl_unused); | |
145 | spin_lock_init(&clp->cl_lock); | |
146 | INIT_WORK(&clp->cl_renewd, nfs4_renew_state, clp); | |
147 | rpc_init_wait_queue(&clp->cl_rpcwaitq, "NFS client"); | |
148 | clp->cl_boot_time = CURRENT_TIME; | |
149 | clp->cl_state = 1 << NFS4CLNT_LEASE_EXPIRED; | |
150 | #endif | |
151 | ||
152 | return clp; | |
153 | ||
154 | error_3: | |
155 | nfs_callback_down(); | |
156 | __clear_bit(NFS_CS_CALLBACK, &clp->cl_res_state); | |
157 | error_2: | |
158 | rpciod_down(); | |
159 | __clear_bit(NFS_CS_RPCIOD, &clp->cl_res_state); | |
160 | error_1: | |
161 | kfree(clp); | |
162 | error_0: | |
163 | return NULL; | |
164 | } | |
165 | ||
166 | /* | |
167 | * Destroy a shared client record | |
168 | */ | |
169 | static void nfs_free_client(struct nfs_client *clp) | |
170 | { | |
171 | dprintk("--> nfs_free_client(%d)\n", clp->cl_nfsversion); | |
172 | ||
173 | #ifdef CONFIG_NFS_V4 | |
174 | if (__test_and_clear_bit(NFS_CS_IDMAP, &clp->cl_res_state)) { | |
175 | while (!list_empty(&clp->cl_unused)) { | |
176 | struct nfs4_state_owner *sp; | |
177 | ||
178 | sp = list_entry(clp->cl_unused.next, | |
179 | struct nfs4_state_owner, | |
180 | so_list); | |
181 | list_del(&sp->so_list); | |
182 | kfree(sp); | |
183 | } | |
184 | BUG_ON(!list_empty(&clp->cl_state_owners)); | |
185 | nfs_idmap_delete(clp); | |
186 | } | |
187 | #endif | |
188 | ||
189 | /* -EIO all pending I/O */ | |
190 | if (!IS_ERR(clp->cl_rpcclient)) | |
191 | rpc_shutdown_client(clp->cl_rpcclient); | |
192 | ||
193 | if (__test_and_clear_bit(NFS_CS_CALLBACK, &clp->cl_res_state)) | |
194 | nfs_callback_down(); | |
195 | ||
196 | if (__test_and_clear_bit(NFS_CS_RPCIOD, &clp->cl_res_state)) | |
197 | rpciod_down(); | |
198 | ||
199 | kfree(clp->cl_hostname); | |
200 | kfree(clp); | |
201 | ||
202 | dprintk("<-- nfs_free_client()\n"); | |
203 | } | |
204 | ||
205 | /* | |
206 | * Release a reference to a shared client record | |
207 | */ | |
208 | void nfs_put_client(struct nfs_client *clp) | |
209 | { | |
210 | dprintk("--> nfs_put_client({%d})\n", atomic_read(&clp->cl_count)); | |
211 | ||
212 | if (atomic_dec_and_lock(&clp->cl_count, &nfs_client_lock)) { | |
213 | list_del(&clp->cl_share_link); | |
214 | spin_unlock(&nfs_client_lock); | |
215 | ||
216 | BUG_ON(!list_empty(&clp->cl_superblocks)); | |
217 | ||
218 | nfs_free_client(clp); | |
219 | } | |
220 | } | |
221 | ||
222 | /* | |
223 | * Find a client by address | |
224 | * - caller must hold nfs_client_lock | |
225 | */ | |
226 | static struct nfs_client *__nfs_find_client(const struct sockaddr_in *addr, int nfsversion) | |
227 | { | |
228 | struct nfs_client *clp; | |
229 | ||
230 | list_for_each_entry(clp, &nfs_client_list, cl_share_link) { | |
231 | /* Different NFS versions cannot share the same nfs_client */ | |
232 | if (clp->cl_nfsversion != nfsversion) | |
233 | continue; | |
234 | ||
235 | if (memcmp(&clp->cl_addr.sin_addr, &addr->sin_addr, | |
236 | sizeof(clp->cl_addr.sin_addr)) != 0) | |
237 | continue; | |
238 | ||
239 | if (clp->cl_addr.sin_port == addr->sin_port) | |
240 | goto found; | |
241 | } | |
242 | ||
243 | return NULL; | |
244 | ||
245 | found: | |
246 | atomic_inc(&clp->cl_count); | |
247 | return clp; | |
248 | } | |
249 | ||
250 | /* | |
251 | * Find a client by IP address and protocol version | |
252 | * - returns NULL if no such client | |
253 | */ | |
254 | struct nfs_client *nfs_find_client(const struct sockaddr_in *addr, int nfsversion) | |
255 | { | |
256 | struct nfs_client *clp; | |
257 | ||
258 | spin_lock(&nfs_client_lock); | |
259 | clp = __nfs_find_client(addr, nfsversion); | |
260 | spin_unlock(&nfs_client_lock); | |
261 | ||
262 | BUG_ON(clp->cl_cons_state == 0); | |
263 | ||
264 | return clp; | |
265 | } | |
266 | ||
267 | /* | |
268 | * Look up a client by IP address and protocol version | |
269 | * - creates a new record if one doesn't yet exist | |
270 | */ | |
271 | struct nfs_client *nfs_get_client(const char *hostname, | |
272 | const struct sockaddr_in *addr, | |
273 | int nfsversion) | |
274 | { | |
275 | struct nfs_client *clp, *new = NULL; | |
276 | int error; | |
277 | ||
278 | dprintk("--> nfs_get_client(%s,"NIPQUAD_FMT":%d,%d)\n", | |
279 | hostname ?: "", NIPQUAD(addr->sin_addr), | |
280 | addr->sin_port, nfsversion); | |
281 | ||
282 | /* see if the client already exists */ | |
283 | do { | |
284 | spin_lock(&nfs_client_lock); | |
285 | ||
286 | clp = __nfs_find_client(addr, nfsversion); | |
287 | if (clp) | |
288 | goto found_client; | |
289 | if (new) | |
290 | goto install_client; | |
291 | ||
292 | spin_unlock(&nfs_client_lock); | |
293 | ||
294 | new = nfs_alloc_client(hostname, addr, nfsversion); | |
295 | } while (new); | |
296 | ||
297 | return ERR_PTR(-ENOMEM); | |
298 | ||
299 | /* install a new client and return with it unready */ | |
300 | install_client: | |
301 | clp = new; | |
302 | list_add(&clp->cl_share_link, &nfs_client_list); | |
303 | spin_unlock(&nfs_client_lock); | |
304 | dprintk("--> nfs_get_client() = %p [new]\n", clp); | |
305 | return clp; | |
306 | ||
307 | /* found an existing client | |
308 | * - make sure it's ready before returning | |
309 | */ | |
310 | found_client: | |
311 | spin_unlock(&nfs_client_lock); | |
312 | ||
313 | if (new) | |
314 | nfs_free_client(new); | |
315 | ||
316 | if (clp->cl_cons_state == NFS_CS_INITING) { | |
317 | DECLARE_WAITQUEUE(myself, current); | |
318 | ||
319 | add_wait_queue(&nfs_client_active_wq, &myself); | |
320 | ||
321 | for (;;) { | |
322 | set_current_state(TASK_INTERRUPTIBLE); | |
323 | if (signal_pending(current) || | |
324 | clp->cl_cons_state > NFS_CS_READY) | |
325 | break; | |
326 | schedule(); | |
327 | } | |
328 | ||
329 | remove_wait_queue(&nfs_client_active_wq, &myself); | |
330 | ||
331 | if (signal_pending(current)) { | |
332 | nfs_put_client(clp); | |
333 | return ERR_PTR(-ERESTARTSYS); | |
334 | } | |
335 | } | |
336 | ||
337 | if (clp->cl_cons_state < NFS_CS_READY) { | |
338 | error = clp->cl_cons_state; | |
339 | nfs_put_client(clp); | |
340 | return ERR_PTR(error); | |
341 | } | |
342 | ||
343 | dprintk("--> nfs_get_client() = %p [share]\n", clp); | |
344 | return clp; | |
345 | } | |
346 | ||
347 | /* | |
348 | * Mark a server as ready or failed | |
349 | */ | |
350 | void nfs_mark_client_ready(struct nfs_client *clp, int state) | |
351 | { | |
352 | clp->cl_cons_state = state; | |
353 | wake_up_all(&nfs_client_active_wq); | |
354 | } | |
5006a76c DH |
355 | |
356 | /* | |
357 | * Initialise the timeout values for a connection | |
358 | */ | |
359 | static void nfs_init_timeout_values(struct rpc_timeout *to, int proto, | |
360 | unsigned int timeo, unsigned int retrans) | |
361 | { | |
362 | to->to_initval = timeo * HZ / 10; | |
363 | to->to_retries = retrans; | |
364 | if (!to->to_retries) | |
365 | to->to_retries = 2; | |
366 | ||
367 | switch (proto) { | |
368 | case IPPROTO_TCP: | |
369 | if (!to->to_initval) | |
370 | to->to_initval = 60 * HZ; | |
371 | if (to->to_initval > NFS_MAX_TCP_TIMEOUT) | |
372 | to->to_initval = NFS_MAX_TCP_TIMEOUT; | |
373 | to->to_increment = to->to_initval; | |
374 | to->to_maxval = to->to_initval + (to->to_increment * to->to_retries); | |
375 | to->to_exponential = 0; | |
376 | break; | |
377 | case IPPROTO_UDP: | |
378 | default: | |
379 | if (!to->to_initval) | |
380 | to->to_initval = 11 * HZ / 10; | |
381 | if (to->to_initval > NFS_MAX_UDP_TIMEOUT) | |
382 | to->to_initval = NFS_MAX_UDP_TIMEOUT; | |
383 | to->to_maxval = NFS_MAX_UDP_TIMEOUT; | |
384 | to->to_exponential = 1; | |
385 | break; | |
386 | } | |
387 | } | |
388 | ||
389 | /* | |
390 | * Create an RPC client handle | |
391 | */ | |
392 | int nfs_create_rpc_client(struct nfs_client *clp, int proto, | |
393 | unsigned int timeo, | |
394 | unsigned int retrans, | |
395 | rpc_authflavor_t flavor) | |
396 | { | |
397 | struct rpc_timeout timeparms; | |
398 | struct rpc_xprt *xprt = NULL; | |
399 | struct rpc_clnt *clnt = NULL; | |
400 | ||
401 | if (!IS_ERR(clp->cl_rpcclient)) | |
402 | return 0; | |
403 | ||
404 | nfs_init_timeout_values(&timeparms, proto, timeo, retrans); | |
405 | clp->retrans_timeo = timeparms.to_initval; | |
406 | clp->retrans_count = timeparms.to_retries; | |
407 | ||
408 | /* create transport and client */ | |
409 | xprt = xprt_create_proto(proto, &clp->cl_addr, &timeparms); | |
410 | if (IS_ERR(xprt)) { | |
411 | dprintk("%s: cannot create RPC transport. Error = %ld\n", | |
412 | __FUNCTION__, PTR_ERR(xprt)); | |
413 | return PTR_ERR(xprt); | |
414 | } | |
415 | ||
416 | /* Bind to a reserved port! */ | |
417 | xprt->resvport = 1; | |
418 | /* Create the client RPC handle */ | |
419 | clnt = rpc_create_client(xprt, clp->cl_hostname, &nfs_program, | |
420 | clp->rpc_ops->version, RPC_AUTH_UNIX); | |
421 | if (IS_ERR(clnt)) { | |
422 | dprintk("%s: cannot create RPC client. Error = %ld\n", | |
423 | __FUNCTION__, PTR_ERR(clnt)); | |
424 | return PTR_ERR(clnt); | |
425 | } | |
426 | ||
427 | clnt->cl_intr = 1; | |
428 | clnt->cl_softrtry = 1; | |
429 | clp->cl_rpcclient = clnt; | |
430 | return 0; | |
431 | } |