c7a6b245c7ad8486f2580091656d4d852b1c3fe6
[safe/jmp/linux-2.6] / fs / nfsd / nfs4recover.c
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 #include <linux/err.h>
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 <linux/scatterlist.h>
47 #include <linux/crypto.h>
48 #include <linux/sched.h>
49 #include <linux/mount.h>
50 #include "vfs.h"
51
52 #define NFSDDBG_FACILITY                NFSDDBG_PROC
53
54 /* Globals */
55 static struct path rec_dir;
56 static int rec_dir_init = 0;
57
58 static int
59 nfs4_save_creds(const struct cred **original_creds)
60 {
61         struct cred *new;
62
63         new = prepare_creds();
64         if (!new)
65                 return -ENOMEM;
66
67         new->fsuid = 0;
68         new->fsgid = 0;
69         *original_creds = override_creds(new);
70         put_cred(new);
71         return 0;
72 }
73
74 static void
75 nfs4_reset_creds(const struct cred *original)
76 {
77         revert_creds(original);
78 }
79
80 static void
81 md5_to_hex(char *out, char *md5)
82 {
83         int i;
84
85         for (i=0; i<16; i++) {
86                 unsigned char c = md5[i];
87
88                 *out++ = '0' + ((c&0xf0)>>4) + (c>=0xa0)*('a'-'9'-1);
89                 *out++ = '0' + (c&0x0f) + ((c&0x0f)>=0x0a)*('a'-'9'-1);
90         }
91         *out = '\0';
92 }
93
94 __be32
95 nfs4_make_rec_clidname(char *dname, struct xdr_netobj *clname)
96 {
97         struct xdr_netobj cksum;
98         struct hash_desc desc;
99         struct scatterlist sg;
100         __be32 status = nfserr_resource;
101
102         dprintk("NFSD: nfs4_make_rec_clidname for %.*s\n",
103                         clname->len, clname->data);
104         desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP;
105         desc.tfm = crypto_alloc_hash("md5", 0, CRYPTO_ALG_ASYNC);
106         if (IS_ERR(desc.tfm))
107                 goto out_no_tfm;
108         cksum.len = crypto_hash_digestsize(desc.tfm);
109         cksum.data = kmalloc(cksum.len, GFP_KERNEL);
110         if (cksum.data == NULL)
111                 goto out;
112
113         sg_init_one(&sg, clname->data, clname->len);
114
115         if (crypto_hash_digest(&desc, &sg, sg.length, cksum.data))
116                 goto out;
117
118         md5_to_hex(dname, cksum.data);
119
120         status = nfs_ok;
121 out:
122         kfree(cksum.data);
123         crypto_free_hash(desc.tfm);
124 out_no_tfm:
125         return status;
126 }
127
128 static void
129 nfsd4_sync_rec_dir(void)
130 {
131         mutex_lock(&rec_dir.dentry->d_inode->i_mutex);
132         nfsd_sync_dir(rec_dir.dentry);
133         mutex_unlock(&rec_dir.dentry->d_inode->i_mutex);
134 }
135
136 int
137 nfsd4_create_clid_dir(struct nfs4_client *clp)
138 {
139         const struct cred *original_cred;
140         char *dname = clp->cl_recdir;
141         struct dentry *dentry;
142         int status;
143
144         dprintk("NFSD: nfsd4_create_clid_dir for \"%s\"\n", dname);
145
146         if (!rec_dir_init || clp->cl_firststate)
147                 return 0;
148
149         status = nfs4_save_creds(&original_cred);
150         if (status < 0)
151                 return status;
152
153         /* lock the parent */
154         mutex_lock(&rec_dir.dentry->d_inode->i_mutex);
155
156         dentry = lookup_one_len(dname, rec_dir.dentry, HEXDIR_LEN-1);
157         if (IS_ERR(dentry)) {
158                 status = PTR_ERR(dentry);
159                 goto out_unlock;
160         }
161         status = -EEXIST;
162         if (dentry->d_inode) {
163                 dprintk("NFSD: nfsd4_create_clid_dir: DIRECTORY EXISTS\n");
164                 goto out_put;
165         }
166         status = mnt_want_write(rec_dir.mnt);
167         if (status)
168                 goto out_put;
169         status = vfs_mkdir(rec_dir.dentry->d_inode, dentry, S_IRWXU);
170         mnt_drop_write(rec_dir.mnt);
171 out_put:
172         dput(dentry);
173 out_unlock:
174         mutex_unlock(&rec_dir.dentry->d_inode->i_mutex);
175         if (status == 0) {
176                 clp->cl_firststate = 1;
177                 nfsd4_sync_rec_dir();
178         }
179         nfs4_reset_creds(original_cred);
180         dprintk("NFSD: nfsd4_create_clid_dir returns %d\n", status);
181         return status;
182 }
183
184 typedef int (recdir_func)(struct dentry *, struct dentry *);
185
186 struct name_list {
187         char name[HEXDIR_LEN];
188         struct list_head list;
189 };
190
191 static int
192 nfsd4_build_namelist(void *arg, const char *name, int namlen,
193                 loff_t offset, u64 ino, unsigned int d_type)
194 {
195         struct list_head *names = arg;
196         struct name_list *entry;
197
198         if (namlen != HEXDIR_LEN - 1)
199                 return 0;
200         entry = kmalloc(sizeof(struct name_list), GFP_KERNEL);
201         if (entry == NULL)
202                 return -ENOMEM;
203         memcpy(entry->name, name, HEXDIR_LEN - 1);
204         entry->name[HEXDIR_LEN - 1] = '\0';
205         list_add(&entry->list, names);
206         return 0;
207 }
208
209 static int
210 nfsd4_list_rec_dir(struct dentry *dir, recdir_func *f)
211 {
212         const struct cred *original_cred;
213         struct file *filp;
214         LIST_HEAD(names);
215         struct name_list *entry;
216         struct dentry *dentry;
217         int status;
218
219         if (!rec_dir_init)
220                 return 0;
221
222         status = nfs4_save_creds(&original_cred);
223         if (status < 0)
224                 return status;
225
226         filp = dentry_open(dget(dir), mntget(rec_dir.mnt), O_RDONLY,
227                            current_cred());
228         status = PTR_ERR(filp);
229         if (IS_ERR(filp))
230                 goto out;
231         status = vfs_readdir(filp, nfsd4_build_namelist, &names);
232         fput(filp);
233         mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
234         while (!list_empty(&names)) {
235                 entry = list_entry(names.next, struct name_list, list);
236
237                 dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
238                 if (IS_ERR(dentry)) {
239                         status = PTR_ERR(dentry);
240                         break;
241                 }
242                 status = f(dir, dentry);
243                 dput(dentry);
244                 if (status)
245                         break;
246                 list_del(&entry->list);
247                 kfree(entry);
248         }
249         mutex_unlock(&dir->d_inode->i_mutex);
250 out:
251         while (!list_empty(&names)) {
252                 entry = list_entry(names.next, struct name_list, list);
253                 list_del(&entry->list);
254                 kfree(entry);
255         }
256         nfs4_reset_creds(original_cred);
257         return status;
258 }
259
260 static int
261 nfsd4_unlink_clid_dir(char *name, int namlen)
262 {
263         struct dentry *dentry;
264         int status;
265
266         dprintk("NFSD: nfsd4_unlink_clid_dir. name %.*s\n", namlen, name);
267
268         mutex_lock_nested(&rec_dir.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
269         dentry = lookup_one_len(name, rec_dir.dentry, namlen);
270         if (IS_ERR(dentry)) {
271                 status = PTR_ERR(dentry);
272                 goto out_unlock;
273         }
274         status = -ENOENT;
275         if (!dentry->d_inode)
276                 goto out;
277         status = vfs_rmdir(rec_dir.dentry->d_inode, dentry);
278 out:
279         dput(dentry);
280 out_unlock:
281         mutex_unlock(&rec_dir.dentry->d_inode->i_mutex);
282         return status;
283 }
284
285 void
286 nfsd4_remove_clid_dir(struct nfs4_client *clp)
287 {
288         const struct cred *original_cred;
289         int status;
290
291         if (!rec_dir_init || !clp->cl_firststate)
292                 return;
293
294         status = mnt_want_write(rec_dir.mnt);
295         if (status)
296                 goto out;
297         clp->cl_firststate = 0;
298
299         status = nfs4_save_creds(&original_cred);
300         if (status < 0)
301                 goto out;
302
303         status = nfsd4_unlink_clid_dir(clp->cl_recdir, HEXDIR_LEN-1);
304         nfs4_reset_creds(original_cred);
305         if (status == 0)
306                 nfsd4_sync_rec_dir();
307         mnt_drop_write(rec_dir.mnt);
308 out:
309         if (status)
310                 printk("NFSD: Failed to remove expired client state directory"
311                                 " %.*s\n", HEXDIR_LEN, clp->cl_recdir);
312         return;
313 }
314
315 static int
316 purge_old(struct dentry *parent, struct dentry *child)
317 {
318         int status;
319
320         /* note: we currently use this path only for minorversion 0 */
321         if (nfs4_has_reclaimed_state(child->d_name.name, false))
322                 return 0;
323
324         status = vfs_rmdir(parent->d_inode, child);
325         if (status)
326                 printk("failed to remove client recovery directory %s\n",
327                                 child->d_name.name);
328         /* Keep trying, success or failure: */
329         return 0;
330 }
331
332 void
333 nfsd4_recdir_purge_old(void) {
334         int status;
335
336         if (!rec_dir_init)
337                 return;
338         status = mnt_want_write(rec_dir.mnt);
339         if (status)
340                 goto out;
341         status = nfsd4_list_rec_dir(rec_dir.dentry, purge_old);
342         if (status == 0)
343                 nfsd4_sync_rec_dir();
344         mnt_drop_write(rec_dir.mnt);
345 out:
346         if (status)
347                 printk("nfsd4: failed to purge old clients from recovery"
348                         " directory %s\n", rec_dir.dentry->d_name.name);
349 }
350
351 static int
352 load_recdir(struct dentry *parent, struct dentry *child)
353 {
354         if (child->d_name.len != HEXDIR_LEN - 1) {
355                 printk("nfsd4: illegal name %s in recovery directory\n",
356                                 child->d_name.name);
357                 /* Keep trying; maybe the others are OK: */
358                 return 0;
359         }
360         nfs4_client_to_reclaim(child->d_name.name);
361         return 0;
362 }
363
364 int
365 nfsd4_recdir_load(void) {
366         int status;
367
368         status = nfsd4_list_rec_dir(rec_dir.dentry, load_recdir);
369         if (status)
370                 printk("nfsd4: failed loading clients from recovery"
371                         " directory %s\n", rec_dir.dentry->d_name.name);
372         return status;
373 }
374
375 /*
376  * Hold reference to the recovery directory.
377  */
378
379 void
380 nfsd4_init_recdir(char *rec_dirname)
381 {
382         const struct cred *original_cred;
383         int status;
384
385         printk("NFSD: Using %s as the NFSv4 state recovery directory\n",
386                         rec_dirname);
387
388         BUG_ON(rec_dir_init);
389
390         status = nfs4_save_creds(&original_cred);
391         if (status < 0) {
392                 printk("NFSD: Unable to change credentials to find recovery"
393                        " directory: error %d\n",
394                        status);
395                 return;
396         }
397
398         status = kern_path(rec_dirname, LOOKUP_FOLLOW | LOOKUP_DIRECTORY,
399                         &rec_dir);
400         if (status)
401                 printk("NFSD: unable to find recovery directory %s\n",
402                                 rec_dirname);
403
404         if (!status)
405                 rec_dir_init = 1;
406         nfs4_reset_creds(original_cred);
407 }
408
409 void
410 nfsd4_shutdown_recdir(void)
411 {
412         if (!rec_dir_init)
413                 return;
414         rec_dir_init = 0;
415         path_put(&rec_dir);
416 }