diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c index 841a305d7948..2dc9851a1d37 100644 --- a/fs/nfsd/nfs4recover.c +++ b/fs/nfsd/nfs4recover.c @@ -39,6 +39,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -46,6 +49,27 @@ #define NFSDDBG_FACILITY NFSDDBG_PROC +/* Globals */ +char recovery_dirname[PATH_MAX] = "/var/lib/nfs/v4recovery"; +static struct nameidata rec_dir; +static int rec_dir_init = 0; + +static void +nfs4_save_user(uid_t *saveuid, gid_t *savegid) +{ + *saveuid = current->fsuid; + *savegid = current->fsgid; + current->fsuid = 0; + current->fsgid = 0; +} + +static void +nfs4_reset_user(uid_t saveuid, gid_t savegid) +{ + current->fsuid = saveuid; + current->fsgid = savegid; +} + static void md5_to_hex(char *out, char *md5) { @@ -95,3 +119,145 @@ nfs4_make_rec_clidname(char *dname, struct xdr_netobj *clname) crypto_free_tfm(tfm); return status; } + +typedef int (recdir_func)(struct dentry *, struct dentry *); + +struct dentry_list { + struct dentry *dentry; + struct list_head list; +}; + +struct dentry_list_arg { + struct list_head dentries; + struct dentry *parent; +}; + +static int +nfsd4_build_dentrylist(void *arg, const char *name, int namlen, + loff_t offset, ino_t ino, unsigned int d_type) +{ + struct dentry_list_arg *dla = arg; + struct list_head *dentries = &dla->dentries; + struct dentry *parent = dla->parent; + struct dentry *dentry; + struct dentry_list *child; + + if (name && isdotent(name, namlen)) + return nfs_ok; + dentry = lookup_one_len(name, parent, namlen); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + child = kmalloc(sizeof(*child), GFP_KERNEL); + if (child == NULL) + return -ENOMEM; + child->dentry = dentry; + list_add(&child->list, dentries); + return 0; +} + +static int +nfsd4_list_rec_dir(struct dentry *dir, recdir_func *f) +{ + struct file *filp; + struct dentry_list_arg dla = { + .parent = dir, + }; + struct list_head *dentries = &dla.dentries; + struct dentry_list *child; + uid_t uid; + gid_t gid; + int status; + + if (!rec_dir_init) + return 0; + + nfs4_save_user(&uid, &gid); + + filp = dentry_open(dget(dir), mntget(rec_dir.mnt), + O_RDWR); + status = PTR_ERR(filp); + if (IS_ERR(filp)) + goto out; + INIT_LIST_HEAD(dentries); + status = vfs_readdir(filp, nfsd4_build_dentrylist, &dla); + fput(filp); + while (!list_empty(dentries)) { + child = list_entry(dentries->next, struct dentry_list, list); + status = f(dir, child->dentry); + if (status) + goto out; + list_del(&child->list); + dput(child->dentry); + kfree(child); + } +out: + while (!list_empty(dentries)) { + child = list_entry(dentries->next, struct dentry_list, list); + list_del(&child->list); + dput(child->dentry); + kfree(child); + } + nfs4_reset_user(uid, gid); + return status; +} + +static int +load_recdir(struct dentry *parent, struct dentry *child) +{ + if (child->d_name.len != HEXDIR_LEN - 1) { + printk("nfsd4: illegal name %s in recovery directory\n", + child->d_name.name); + /* Keep trying; maybe the others are OK: */ + return nfs_ok; + } + nfs4_client_to_reclaim(child->d_name.name); + return nfs_ok; +} + +int +nfsd4_recdir_load(void) { + int status; + + status = nfsd4_list_rec_dir(rec_dir.dentry, load_recdir); + if (status) + printk("nfsd4: failed loading clients from recovery" + " directory %s\n", rec_dir.dentry->d_name.name); + return status; +} + +/* + * Hold reference to the recovery directory. + */ + +void +nfsd4_init_recdir(char *rec_dirname) +{ + uid_t uid = 0; + gid_t gid = 0; + int status; + + printk("NFSD: Using %s as the NFSv4 state recovery directory\n", + rec_dirname); + + BUG_ON(rec_dir_init); + + nfs4_save_user(&uid, &gid); + + status = path_lookup(rec_dirname, LOOKUP_FOLLOW, &rec_dir); + if (status == -ENOENT) + printk("NFSD: recovery directory %s doesn't exist\n", + rec_dirname); + + if (!status) + rec_dir_init = 1; + nfs4_reset_user(uid, gid); +} + +void +nfsd4_shutdown_recdir(void) +{ + if (!rec_dir_init) + return; + rec_dir_init = 0; + path_release(&rec_dir); +} diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 0b47a97e953d..6b9d23c39afe 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -71,6 +71,7 @@ static stateid_t onestateid; /* bits all 1 */ static struct nfs4_stateid * find_stateid(stateid_t *stid, int flags); static struct nfs4_delegation * find_delegation_stateid(struct inode *ino, stateid_t *stid); static void release_stateid_lockowners(struct nfs4_stateid *open_stp); +extern char recovery_dirname[]; /* Locking: * @@ -3091,8 +3092,8 @@ alloc_reclaim(void) /* * failure => all reset bets are off, nfserr_no_grace... */ -static int -nfs4_client_to_reclaim(char *name) +int +nfs4_client_to_reclaim(const char *name) { unsigned int strhashval; struct nfs4_client_reclaim *crp = NULL; @@ -3202,6 +3203,17 @@ nfs4_state_init(void) reclaim_str_hashtbl_size = 0; } +static void +nfsd4_load_reboot_recovery_data(void) +{ + int status; + + nfsd4_init_recdir(recovery_dirname); + status = nfsd4_recdir_load(); + if (status) + printk("NFSD: Failure reading reboot recovery data\n"); +} + /* initialization to perform when the nfsd service is started: */ static void @@ -3228,6 +3240,7 @@ nfs4_state_start(void) status = nfsd4_init_slabs(); if (status) return status; + nfsd4_load_reboot_recovery_data(); __nfs4_state_start(); nfs4_init = 1; return 0; @@ -3286,6 +3299,7 @@ __nfs4_state_shutdown(void) cancel_delayed_work(&laundromat_work); flush_workqueue(laundry_wq); destroy_workqueue(laundry_wq); + nfsd4_shutdown_recdir(); nfs4_init = 0; } diff --git a/include/linux/nfsd/state.h b/include/linux/nfsd/state.h index 83d29ec03a58..19481ab122df 100644 --- a/include/linux/nfsd/state.h +++ b/include/linux/nfsd/state.h @@ -278,6 +278,10 @@ extern void nfsd4_probe_callback(struct nfs4_client *clp); extern void nfsd4_cb_recall(struct nfs4_delegation *dp); extern void nfs4_put_delegation(struct nfs4_delegation *dp); extern int nfs4_make_rec_clidname(char *clidname, struct xdr_netobj *clname); +extern void nfsd4_init_recdir(char *recdir_name); +extern int nfsd4_recdir_load(void); +extern void nfsd4_shutdown_recdir(void); +extern int nfs4_client_to_reclaim(const char *name); static inline void nfs4_put_stateowner(struct nfs4_stateowner *so)