diff options
Diffstat (limited to 'fs/nfsd/nfs4recover.c')
-rw-r--r-- | fs/nfsd/nfs4recover.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c new file mode 100644 index 000000000000..095f1740f3ae --- /dev/null +++ b/fs/nfsd/nfs4recover.c | |||
@@ -0,0 +1,431 @@ | |||
1 | /* | ||
2 | * linux/fs/nfsd/nfs4recover.c | ||
3 | * | ||
4 | * Copyright (c) 2004 The Regents of the University of Michigan. | ||
5 | * All rights reserved. | ||
6 | * | ||
7 | * Andy Adamson <andros@citi.umich.edu> | ||
8 | * | ||
9 | * Redistribution and use in source and binary forms, with or without | ||
10 | * modification, are permitted provided that the following conditions | ||
11 | * are met: | ||
12 | * | ||
13 | * 1. Redistributions of source code must retain the above copyright | ||
14 | * notice, this list of conditions and the following disclaimer. | ||
15 | * 2. Redistributions in binary form must reproduce the above copyright | ||
16 | * notice, this list of conditions and the following disclaimer in the | ||
17 | * documentation and/or other materials provided with the distribution. | ||
18 | * 3. Neither the name of the University nor the names of its | ||
19 | * contributors may be used to endorse or promote products derived | ||
20 | * from this software without specific prior written permission. | ||
21 | * | ||
22 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED | ||
23 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||
24 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
25 | * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | ||
26 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
27 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
28 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | ||
29 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||
30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||
31 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
32 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
33 | * | ||
34 | */ | ||
35 | |||
36 | |||
37 | #include <linux/sunrpc/svc.h> | ||
38 | #include <linux/nfsd/nfsd.h> | ||
39 | #include <linux/nfs4.h> | ||
40 | #include <linux/nfsd/state.h> | ||
41 | #include <linux/nfsd/xdr4.h> | ||
42 | #include <linux/param.h> | ||
43 | #include <linux/file.h> | ||
44 | #include <linux/namei.h> | ||
45 | #include <asm/uaccess.h> | ||
46 | #include <asm/scatterlist.h> | ||
47 | #include <linux/crypto.h> | ||
48 | |||
49 | |||
50 | #define NFSDDBG_FACILITY NFSDDBG_PROC | ||
51 | |||
52 | /* Globals */ | ||
53 | static struct nameidata rec_dir; | ||
54 | static int rec_dir_init = 0; | ||
55 | |||
56 | static void | ||
57 | nfs4_save_user(uid_t *saveuid, gid_t *savegid) | ||
58 | { | ||
59 | *saveuid = current->fsuid; | ||
60 | *savegid = current->fsgid; | ||
61 | current->fsuid = 0; | ||
62 | current->fsgid = 0; | ||
63 | } | ||
64 | |||
65 | static void | ||
66 | nfs4_reset_user(uid_t saveuid, gid_t savegid) | ||
67 | { | ||
68 | current->fsuid = saveuid; | ||
69 | current->fsgid = savegid; | ||
70 | } | ||
71 | |||
72 | static void | ||
73 | md5_to_hex(char *out, char *md5) | ||
74 | { | ||
75 | int i; | ||
76 | |||
77 | for (i=0; i<16; i++) { | ||
78 | unsigned char c = md5[i]; | ||
79 | |||
80 | *out++ = '0' + ((c&0xf0)>>4) + (c>=0xa0)*('a'-'9'-1); | ||
81 | *out++ = '0' + (c&0x0f) + ((c&0x0f)>=0x0a)*('a'-'9'-1); | ||
82 | } | ||
83 | *out = '\0'; | ||
84 | } | ||
85 | |||
86 | int | ||
87 | nfs4_make_rec_clidname(char *dname, struct xdr_netobj *clname) | ||
88 | { | ||
89 | struct xdr_netobj cksum; | ||
90 | struct crypto_tfm *tfm; | ||
91 | struct scatterlist sg[1]; | ||
92 | int status = nfserr_resource; | ||
93 | |||
94 | dprintk("NFSD: nfs4_make_rec_clidname for %.*s\n", | ||
95 | clname->len, clname->data); | ||
96 | tfm = crypto_alloc_tfm("md5", 0); | ||
97 | if (tfm == NULL) | ||
98 | goto out; | ||
99 | cksum.len = crypto_tfm_alg_digestsize(tfm); | ||
100 | cksum.data = kmalloc(cksum.len, GFP_KERNEL); | ||
101 | if (cksum.data == NULL) | ||
102 | goto out; | ||
103 | crypto_digest_init(tfm); | ||
104 | |||
105 | sg[0].page = virt_to_page(clname->data); | ||
106 | sg[0].offset = offset_in_page(clname->data); | ||
107 | sg[0].length = clname->len; | ||
108 | |||
109 | crypto_digest_update(tfm, sg, 1); | ||
110 | crypto_digest_final(tfm, cksum.data); | ||
111 | |||
112 | md5_to_hex(dname, cksum.data); | ||
113 | |||
114 | kfree(cksum.data); | ||
115 | status = nfs_ok; | ||
116 | out: | ||
117 | if (tfm) | ||
118 | crypto_free_tfm(tfm); | ||
119 | return status; | ||
120 | } | ||
121 | |||
122 | static int | ||
123 | nfsd4_rec_fsync(struct dentry *dentry) | ||
124 | { | ||
125 | struct file *filp; | ||
126 | int status = nfs_ok; | ||
127 | |||
128 | dprintk("NFSD: nfs4_fsync_rec_dir\n"); | ||
129 | filp = dentry_open(dget(dentry), mntget(rec_dir.mnt), O_RDWR); | ||
130 | if (IS_ERR(filp)) { | ||
131 | status = PTR_ERR(filp); | ||
132 | goto out; | ||
133 | } | ||
134 | if (filp->f_op && filp->f_op->fsync) | ||
135 | status = filp->f_op->fsync(filp, filp->f_dentry, 0); | ||
136 | fput(filp); | ||
137 | out: | ||
138 | if (status) | ||
139 | printk("nfsd4: unable to sync recovery directory\n"); | ||
140 | return status; | ||
141 | } | ||
142 | |||
143 | int | ||
144 | nfsd4_create_clid_dir(struct nfs4_client *clp) | ||
145 | { | ||
146 | char *dname = clp->cl_recdir; | ||
147 | struct dentry *dentry; | ||
148 | uid_t uid; | ||
149 | gid_t gid; | ||
150 | int status; | ||
151 | |||
152 | dprintk("NFSD: nfsd4_create_clid_dir for \"%s\"\n", dname); | ||
153 | |||
154 | if (!rec_dir_init || clp->cl_firststate) | ||
155 | return 0; | ||
156 | |||
157 | nfs4_save_user(&uid, &gid); | ||
158 | |||
159 | /* lock the parent */ | ||
160 | down(&rec_dir.dentry->d_inode->i_sem); | ||
161 | |||
162 | dentry = lookup_one_len(dname, rec_dir.dentry, HEXDIR_LEN-1); | ||
163 | if (IS_ERR(dentry)) { | ||
164 | status = PTR_ERR(dentry); | ||
165 | goto out_unlock; | ||
166 | } | ||
167 | status = -EEXIST; | ||
168 | if (dentry->d_inode) { | ||
169 | dprintk("NFSD: nfsd4_create_clid_dir: DIRECTORY EXISTS\n"); | ||
170 | goto out_put; | ||
171 | } | ||
172 | status = vfs_mkdir(rec_dir.dentry->d_inode, dentry, S_IRWXU); | ||
173 | out_put: | ||
174 | dput(dentry); | ||
175 | out_unlock: | ||
176 | up(&rec_dir.dentry->d_inode->i_sem); | ||
177 | if (status == 0) { | ||
178 | clp->cl_firststate = 1; | ||
179 | status = nfsd4_rec_fsync(rec_dir.dentry); | ||
180 | } | ||
181 | nfs4_reset_user(uid, gid); | ||
182 | dprintk("NFSD: nfsd4_create_clid_dir returns %d\n", status); | ||
183 | return status; | ||
184 | } | ||
185 | |||
186 | typedef int (recdir_func)(struct dentry *, struct dentry *); | ||
187 | |||
188 | struct dentry_list { | ||
189 | struct dentry *dentry; | ||
190 | struct list_head list; | ||
191 | }; | ||
192 | |||
193 | struct dentry_list_arg { | ||
194 | struct list_head dentries; | ||
195 | struct dentry *parent; | ||
196 | }; | ||
197 | |||
198 | static int | ||
199 | nfsd4_build_dentrylist(void *arg, const char *name, int namlen, | ||
200 | loff_t offset, ino_t ino, unsigned int d_type) | ||
201 | { | ||
202 | struct dentry_list_arg *dla = arg; | ||
203 | struct list_head *dentries = &dla->dentries; | ||
204 | struct dentry *parent = dla->parent; | ||
205 | struct dentry *dentry; | ||
206 | struct dentry_list *child; | ||
207 | |||
208 | if (name && isdotent(name, namlen)) | ||
209 | return nfs_ok; | ||
210 | dentry = lookup_one_len(name, parent, namlen); | ||
211 | if (IS_ERR(dentry)) | ||
212 | return PTR_ERR(dentry); | ||
213 | child = kmalloc(sizeof(*child), GFP_KERNEL); | ||
214 | if (child == NULL) | ||
215 | return -ENOMEM; | ||
216 | child->dentry = dentry; | ||
217 | list_add(&child->list, dentries); | ||
218 | return 0; | ||
219 | } | ||
220 | |||
221 | static int | ||
222 | nfsd4_list_rec_dir(struct dentry *dir, recdir_func *f) | ||
223 | { | ||
224 | struct file *filp; | ||
225 | struct dentry_list_arg dla = { | ||
226 | .parent = dir, | ||
227 | }; | ||
228 | struct list_head *dentries = &dla.dentries; | ||
229 | struct dentry_list *child; | ||
230 | uid_t uid; | ||
231 | gid_t gid; | ||
232 | int status; | ||
233 | |||
234 | if (!rec_dir_init) | ||
235 | return 0; | ||
236 | |||
237 | nfs4_save_user(&uid, &gid); | ||
238 | |||
239 | filp = dentry_open(dget(dir), mntget(rec_dir.mnt), | ||
240 | O_RDWR); | ||
241 | status = PTR_ERR(filp); | ||
242 | if (IS_ERR(filp)) | ||
243 | goto out; | ||
244 | INIT_LIST_HEAD(dentries); | ||
245 | status = vfs_readdir(filp, nfsd4_build_dentrylist, &dla); | ||
246 | fput(filp); | ||
247 | while (!list_empty(dentries)) { | ||
248 | child = list_entry(dentries->next, struct dentry_list, list); | ||
249 | status = f(dir, child->dentry); | ||
250 | if (status) | ||
251 | goto out; | ||
252 | list_del(&child->list); | ||
253 | dput(child->dentry); | ||
254 | kfree(child); | ||
255 | } | ||
256 | out: | ||
257 | while (!list_empty(dentries)) { | ||
258 | child = list_entry(dentries->next, struct dentry_list, list); | ||
259 | list_del(&child->list); | ||
260 | dput(child->dentry); | ||
261 | kfree(child); | ||
262 | } | ||
263 | nfs4_reset_user(uid, gid); | ||
264 | return status; | ||
265 | } | ||
266 | |||
267 | static int | ||
268 | nfsd4_remove_clid_file(struct dentry *dir, struct dentry *dentry) | ||
269 | { | ||
270 | int status; | ||
271 | |||
272 | if (!S_ISREG(dir->d_inode->i_mode)) { | ||
273 | printk("nfsd4: non-file found in client recovery directory\n"); | ||
274 | return -EINVAL; | ||
275 | } | ||
276 | down(&dir->d_inode->i_sem); | ||
277 | status = vfs_unlink(dir->d_inode, dentry); | ||
278 | up(&dir->d_inode->i_sem); | ||
279 | return status; | ||
280 | } | ||
281 | |||
282 | static int | ||
283 | nfsd4_clear_clid_dir(struct dentry *dir, struct dentry *dentry) | ||
284 | { | ||
285 | int status; | ||
286 | |||
287 | /* For now this directory should already be empty, but we empty it of | ||
288 | * any regular files anyway, just in case the directory was created by | ||
289 | * a kernel from the future.... */ | ||
290 | nfsd4_list_rec_dir(dentry, nfsd4_remove_clid_file); | ||
291 | down(&dir->d_inode->i_sem); | ||
292 | status = vfs_rmdir(dir->d_inode, dentry); | ||
293 | up(&dir->d_inode->i_sem); | ||
294 | return status; | ||
295 | } | ||
296 | |||
297 | static int | ||
298 | nfsd4_unlink_clid_dir(char *name, int namlen) | ||
299 | { | ||
300 | struct dentry *dentry; | ||
301 | int status; | ||
302 | |||
303 | dprintk("NFSD: nfsd4_unlink_clid_dir. name %.*s\n", namlen, name); | ||
304 | |||
305 | dentry = lookup_one_len(name, rec_dir.dentry, namlen); | ||
306 | if (IS_ERR(dentry)) { | ||
307 | status = PTR_ERR(dentry); | ||
308 | return status; | ||
309 | } | ||
310 | status = -ENOENT; | ||
311 | if (!dentry->d_inode) | ||
312 | goto out; | ||
313 | |||
314 | status = nfsd4_clear_clid_dir(rec_dir.dentry, dentry); | ||
315 | out: | ||
316 | dput(dentry); | ||
317 | return status; | ||
318 | } | ||
319 | |||
320 | void | ||
321 | nfsd4_remove_clid_dir(struct nfs4_client *clp) | ||
322 | { | ||
323 | uid_t uid; | ||
324 | gid_t gid; | ||
325 | int status; | ||
326 | |||
327 | if (!rec_dir_init || !clp->cl_firststate) | ||
328 | return; | ||
329 | |||
330 | nfs4_save_user(&uid, &gid); | ||
331 | status = nfsd4_unlink_clid_dir(clp->cl_recdir, HEXDIR_LEN-1); | ||
332 | nfs4_reset_user(uid, gid); | ||
333 | if (status == 0) | ||
334 | status = nfsd4_rec_fsync(rec_dir.dentry); | ||
335 | if (status) | ||
336 | printk("NFSD: Failed to remove expired client state directory" | ||
337 | " %.*s\n", HEXDIR_LEN, clp->cl_recdir); | ||
338 | return; | ||
339 | } | ||
340 | |||
341 | static int | ||
342 | purge_old(struct dentry *parent, struct dentry *child) | ||
343 | { | ||
344 | int status; | ||
345 | |||
346 | if (nfs4_has_reclaimed_state(child->d_name.name)) | ||
347 | return nfs_ok; | ||
348 | |||
349 | status = nfsd4_clear_clid_dir(parent, child); | ||
350 | if (status) | ||
351 | printk("failed to remove client recovery directory %s\n", | ||
352 | child->d_name.name); | ||
353 | /* Keep trying, success or failure: */ | ||
354 | return nfs_ok; | ||
355 | } | ||
356 | |||
357 | void | ||
358 | nfsd4_recdir_purge_old(void) { | ||
359 | int status; | ||
360 | |||
361 | if (!rec_dir_init) | ||
362 | return; | ||
363 | status = nfsd4_list_rec_dir(rec_dir.dentry, purge_old); | ||
364 | if (status == 0) | ||
365 | status = nfsd4_rec_fsync(rec_dir.dentry); | ||
366 | if (status) | ||
367 | printk("nfsd4: failed to purge old clients from recovery" | ||
368 | " directory %s\n", rec_dir.dentry->d_name.name); | ||
369 | return; | ||
370 | } | ||
371 | |||
372 | static int | ||
373 | load_recdir(struct dentry *parent, struct dentry *child) | ||
374 | { | ||
375 | if (child->d_name.len != HEXDIR_LEN - 1) { | ||
376 | printk("nfsd4: illegal name %s in recovery directory\n", | ||
377 | child->d_name.name); | ||
378 | /* Keep trying; maybe the others are OK: */ | ||
379 | return nfs_ok; | ||
380 | } | ||
381 | nfs4_client_to_reclaim(child->d_name.name); | ||
382 | return nfs_ok; | ||
383 | } | ||
384 | |||
385 | int | ||
386 | nfsd4_recdir_load(void) { | ||
387 | int status; | ||
388 | |||
389 | status = nfsd4_list_rec_dir(rec_dir.dentry, load_recdir); | ||
390 | if (status) | ||
391 | printk("nfsd4: failed loading clients from recovery" | ||
392 | " directory %s\n", rec_dir.dentry->d_name.name); | ||
393 | return status; | ||
394 | } | ||
395 | |||
396 | /* | ||
397 | * Hold reference to the recovery directory. | ||
398 | */ | ||
399 | |||
400 | void | ||
401 | nfsd4_init_recdir(char *rec_dirname) | ||
402 | { | ||
403 | uid_t uid = 0; | ||
404 | gid_t gid = 0; | ||
405 | int status; | ||
406 | |||
407 | printk("NFSD: Using %s as the NFSv4 state recovery directory\n", | ||
408 | rec_dirname); | ||
409 | |||
410 | BUG_ON(rec_dir_init); | ||
411 | |||
412 | nfs4_save_user(&uid, &gid); | ||
413 | |||
414 | status = path_lookup(rec_dirname, LOOKUP_FOLLOW, &rec_dir); | ||
415 | if (status == -ENOENT) | ||
416 | printk("NFSD: recovery directory %s doesn't exist\n", | ||
417 | rec_dirname); | ||
418 | |||
419 | if (!status) | ||
420 | rec_dir_init = 1; | ||
421 | nfs4_reset_user(uid, gid); | ||
422 | } | ||
423 | |||
424 | void | ||
425 | nfsd4_shutdown_recdir(void) | ||
426 | { | ||
427 | if (!rec_dir_init) | ||
428 | return; | ||
429 | rec_dir_init = 0; | ||
430 | path_release(&rec_dir); | ||
431 | } | ||