Merge branch 'merge' of git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc
[linux-2.6.git] / fs / nfsd / nfs4recover.c
1 /*
2 *  Copyright (c) 2004 The Regents of the University of Michigan.
3 *  All rights reserved.
4 *
5 *  Andy Adamson <andros@citi.umich.edu>
6 *
7 *  Redistribution and use in source and binary forms, with or without
8 *  modification, are permitted provided that the following conditions
9 *  are met:
10 *
11 *  1. Redistributions of source code must retain the above copyright
12 *     notice, this list of conditions and the following disclaimer.
13 *  2. Redistributions in binary form must reproduce the above copyright
14 *     notice, this list of conditions and the following disclaimer in the
15 *     documentation and/or other materials provided with the distribution.
16 *  3. Neither the name of the University nor the names of its
17 *     contributors may be used to endorse or promote products derived
18 *     from this software without specific prior written permission.
19 *
20 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
21 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
22 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 *  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27 *  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33
34 #include <linux/file.h>
35 #include <linux/slab.h>
36 #include <linux/namei.h>
37 #include <linux/crypto.h>
38 #include <linux/sched.h>
39
40 #include "nfsd.h"
41 #include "state.h"
42 #include "vfs.h"
43
44 #define NFSDDBG_FACILITY                NFSDDBG_PROC
45
46 /* Globals */
47 static struct file *rec_file;
48 static char user_recovery_dirname[PATH_MAX] = "/var/lib/nfs/v4recovery";
49
50 static int
51 nfs4_save_creds(const struct cred **original_creds)
52 {
53         struct cred *new;
54
55         new = prepare_creds();
56         if (!new)
57                 return -ENOMEM;
58
59         new->fsuid = 0;
60         new->fsgid = 0;
61         *original_creds = override_creds(new);
62         put_cred(new);
63         return 0;
64 }
65
66 static void
67 nfs4_reset_creds(const struct cred *original)
68 {
69         revert_creds(original);
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 __be32
87 nfs4_make_rec_clidname(char *dname, struct xdr_netobj *clname)
88 {
89         struct xdr_netobj cksum;
90         struct hash_desc desc;
91         struct scatterlist sg;
92         __be32 status = nfserr_jukebox;
93
94         dprintk("NFSD: nfs4_make_rec_clidname for %.*s\n",
95                         clname->len, clname->data);
96         desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP;
97         desc.tfm = crypto_alloc_hash("md5", 0, CRYPTO_ALG_ASYNC);
98         if (IS_ERR(desc.tfm))
99                 goto out_no_tfm;
100         cksum.len = crypto_hash_digestsize(desc.tfm);
101         cksum.data = kmalloc(cksum.len, GFP_KERNEL);
102         if (cksum.data == NULL)
103                 goto out;
104
105         sg_init_one(&sg, clname->data, clname->len);
106
107         if (crypto_hash_digest(&desc, &sg, sg.length, cksum.data))
108                 goto out;
109
110         md5_to_hex(dname, cksum.data);
111
112         status = nfs_ok;
113 out:
114         kfree(cksum.data);
115         crypto_free_hash(desc.tfm);
116 out_no_tfm:
117         return status;
118 }
119
120 int
121 nfsd4_create_clid_dir(struct nfs4_client *clp)
122 {
123         const struct cred *original_cred;
124         char *dname = clp->cl_recdir;
125         struct dentry *dir, *dentry;
126         int status;
127
128         dprintk("NFSD: nfsd4_create_clid_dir for \"%s\"\n", dname);
129
130         if (!rec_file || clp->cl_firststate)
131                 return 0;
132
133         clp->cl_firststate = 1;
134         status = nfs4_save_creds(&original_cred);
135         if (status < 0)
136                 return status;
137
138         dir = rec_file->f_path.dentry;
139         /* lock the parent */
140         mutex_lock(&dir->d_inode->i_mutex);
141
142         dentry = lookup_one_len(dname, dir, HEXDIR_LEN-1);
143         if (IS_ERR(dentry)) {
144                 status = PTR_ERR(dentry);
145                 goto out_unlock;
146         }
147         status = -EEXIST;
148         if (dentry->d_inode)
149                 goto out_put;
150         status = mnt_want_write_file(rec_file);
151         if (status)
152                 goto out_put;
153         status = vfs_mkdir(dir->d_inode, dentry, S_IRWXU);
154         mnt_drop_write_file(rec_file);
155 out_put:
156         dput(dentry);
157 out_unlock:
158         mutex_unlock(&dir->d_inode->i_mutex);
159         if (status == 0)
160                 vfs_fsync(rec_file, 0);
161         else
162                 printk(KERN_ERR "NFSD: failed to write recovery record"
163                                 " (err %d); please check that %s exists"
164                                 " and is writeable", status,
165                                 user_recovery_dirname);
166         nfs4_reset_creds(original_cred);
167         return status;
168 }
169
170 typedef int (recdir_func)(struct dentry *, struct dentry *);
171
172 struct name_list {
173         char name[HEXDIR_LEN];
174         struct list_head list;
175 };
176
177 static int
178 nfsd4_build_namelist(void *arg, const char *name, int namlen,
179                 loff_t offset, u64 ino, unsigned int d_type)
180 {
181         struct list_head *names = arg;
182         struct name_list *entry;
183
184         if (namlen != HEXDIR_LEN - 1)
185                 return 0;
186         entry = kmalloc(sizeof(struct name_list), GFP_KERNEL);
187         if (entry == NULL)
188                 return -ENOMEM;
189         memcpy(entry->name, name, HEXDIR_LEN - 1);
190         entry->name[HEXDIR_LEN - 1] = '\0';
191         list_add(&entry->list, names);
192         return 0;
193 }
194
195 static int
196 nfsd4_list_rec_dir(recdir_func *f)
197 {
198         const struct cred *original_cred;
199         struct dentry *dir = rec_file->f_path.dentry;
200         LIST_HEAD(names);
201         int status;
202
203         status = nfs4_save_creds(&original_cred);
204         if (status < 0)
205                 return status;
206
207         status = vfs_llseek(rec_file, 0, SEEK_SET);
208         if (status < 0) {
209                 nfs4_reset_creds(original_cred);
210                 return status;
211         }
212
213         status = vfs_readdir(rec_file, nfsd4_build_namelist, &names);
214         mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
215         while (!list_empty(&names)) {
216                 struct name_list *entry;
217                 entry = list_entry(names.next, struct name_list, list);
218                 if (!status) {
219                         struct dentry *dentry;
220                         dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
221                         if (IS_ERR(dentry)) {
222                                 status = PTR_ERR(dentry);
223                                 break;
224                         }
225                         status = f(dir, dentry);
226                         dput(dentry);
227                 }
228                 list_del(&entry->list);
229                 kfree(entry);
230         }
231         mutex_unlock(&dir->d_inode->i_mutex);
232         nfs4_reset_creds(original_cred);
233         return status;
234 }
235
236 static int
237 nfsd4_unlink_clid_dir(char *name, int namlen)
238 {
239         struct dentry *dir, *dentry;
240         int status;
241
242         dprintk("NFSD: nfsd4_unlink_clid_dir. name %.*s\n", namlen, name);
243
244         dir = rec_file->f_path.dentry;
245         mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
246         dentry = lookup_one_len(name, dir, namlen);
247         if (IS_ERR(dentry)) {
248                 status = PTR_ERR(dentry);
249                 goto out_unlock;
250         }
251         status = -ENOENT;
252         if (!dentry->d_inode)
253                 goto out;
254         status = vfs_rmdir(dir->d_inode, dentry);
255 out:
256         dput(dentry);
257 out_unlock:
258         mutex_unlock(&dir->d_inode->i_mutex);
259         return status;
260 }
261
262 void
263 nfsd4_remove_clid_dir(struct nfs4_client *clp)
264 {
265         const struct cred *original_cred;
266         int status;
267
268         if (!rec_file || !clp->cl_firststate)
269                 return;
270
271         status = mnt_want_write_file(rec_file);
272         if (status)
273                 goto out;
274         clp->cl_firststate = 0;
275
276         status = nfs4_save_creds(&original_cred);
277         if (status < 0)
278                 goto out;
279
280         status = nfsd4_unlink_clid_dir(clp->cl_recdir, HEXDIR_LEN-1);
281         nfs4_reset_creds(original_cred);
282         if (status == 0)
283                 vfs_fsync(rec_file, 0);
284         mnt_drop_write_file(rec_file);
285 out:
286         if (status)
287                 printk("NFSD: Failed to remove expired client state directory"
288                                 " %.*s\n", HEXDIR_LEN, clp->cl_recdir);
289         return;
290 }
291
292 static int
293 purge_old(struct dentry *parent, struct dentry *child)
294 {
295         int status;
296
297         if (nfs4_has_reclaimed_state(child->d_name.name, false))
298                 return 0;
299
300         status = vfs_rmdir(parent->d_inode, child);
301         if (status)
302                 printk("failed to remove client recovery directory %s\n",
303                                 child->d_name.name);
304         /* Keep trying, success or failure: */
305         return 0;
306 }
307
308 void
309 nfsd4_recdir_purge_old(void) {
310         int status;
311
312         if (!rec_file)
313                 return;
314         status = mnt_want_write_file(rec_file);
315         if (status)
316                 goto out;
317         status = nfsd4_list_rec_dir(purge_old);
318         if (status == 0)
319                 vfs_fsync(rec_file, 0);
320         mnt_drop_write_file(rec_file);
321 out:
322         if (status)
323                 printk("nfsd4: failed to purge old clients from recovery"
324                         " directory %s\n", rec_file->f_path.dentry->d_name.name);
325 }
326
327 static int
328 load_recdir(struct dentry *parent, struct dentry *child)
329 {
330         if (child->d_name.len != HEXDIR_LEN - 1) {
331                 printk("nfsd4: illegal name %s in recovery directory\n",
332                                 child->d_name.name);
333                 /* Keep trying; maybe the others are OK: */
334                 return 0;
335         }
336         nfs4_client_to_reclaim(child->d_name.name);
337         return 0;
338 }
339
340 int
341 nfsd4_recdir_load(void) {
342         int status;
343
344         if (!rec_file)
345                 return 0;
346
347         status = nfsd4_list_rec_dir(load_recdir);
348         if (status)
349                 printk("nfsd4: failed loading clients from recovery"
350                         " directory %s\n", rec_file->f_path.dentry->d_name.name);
351         return status;
352 }
353
354 /*
355  * Hold reference to the recovery directory.
356  */
357
358 void
359 nfsd4_init_recdir()
360 {
361         const struct cred *original_cred;
362         int status;
363
364         printk("NFSD: Using %s as the NFSv4 state recovery directory\n",
365                         user_recovery_dirname);
366
367         BUG_ON(rec_file);
368
369         status = nfs4_save_creds(&original_cred);
370         if (status < 0) {
371                 printk("NFSD: Unable to change credentials to find recovery"
372                        " directory: error %d\n",
373                        status);
374                 return;
375         }
376
377         rec_file = filp_open(user_recovery_dirname, O_RDONLY | O_DIRECTORY, 0);
378         if (IS_ERR(rec_file)) {
379                 printk("NFSD: unable to find recovery directory %s\n",
380                                 user_recovery_dirname);
381                 rec_file = NULL;
382         }
383
384         nfs4_reset_creds(original_cred);
385 }
386
387 void
388 nfsd4_shutdown_recdir(void)
389 {
390         if (!rec_file)
391                 return;
392         fput(rec_file);
393         rec_file = NULL;
394 }
395
396 /*
397  * Change the NFSv4 recovery directory to recdir.
398  */
399 int
400 nfs4_reset_recoverydir(char *recdir)
401 {
402         int status;
403         struct path path;
404
405         status = kern_path(recdir, LOOKUP_FOLLOW, &path);
406         if (status)
407                 return status;
408         status = -ENOTDIR;
409         if (S_ISDIR(path.dentry->d_inode->i_mode)) {
410                 strcpy(user_recovery_dirname, recdir);
411                 status = 0;
412         }
413         path_put(&path);
414         return status;
415 }
416
417 char *
418 nfs4_recoverydir(void)
419 {
420         return user_recovery_dirname;
421 }