diff options
Diffstat (limited to 'fs/lockd/svcsubs.c')
-rw-r--r-- | fs/lockd/svcsubs.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c new file mode 100644 index 0000000..de75363 --- /dev/null +++ b/fs/lockd/svcsubs.c @@ -0,0 +1,309 @@ +/* + * linux/fs/lockd/svcsubs.c + * + * Various support routines for the NLM server. + * + * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/time.h> +#include <linux/in.h> +#include <linux/sunrpc/svc.h> +#include <linux/sunrpc/clnt.h> +#include <linux/nfsd/nfsfh.h> +#include <linux/nfsd/export.h> +#include <linux/lockd/lockd.h> +#include <linux/lockd/share.h> +#include <linux/lockd/sm_inter.h> + +#define NLMDBG_FACILITY NLMDBG_SVCSUBS + + +/* + * Global file hash table + */ +#define FILE_HASH_BITS 5 +#define FILE_NRHASH (1<<FILE_HASH_BITS) +static struct nlm_file * nlm_files[FILE_NRHASH]; +static DECLARE_MUTEX(nlm_file_sema); + +static inline unsigned int file_hash(struct nfs_fh *f) +{ + unsigned int tmp=0; + int i; + for (i=0; i<NFS2_FHSIZE;i++) + tmp += f->data[i]; + return tmp & (FILE_NRHASH - 1); +} + +/* + * Lookup file info. If it doesn't exist, create a file info struct + * and open a (VFS) file for the given inode. + * + * FIXME: + * Note that we open the file O_RDONLY even when creating write locks. + * This is not quite right, but for now, we assume the client performs + * the proper R/W checking. + */ +u32 +nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result, + struct nfs_fh *f) +{ + struct nlm_file *file; + unsigned int hash; + u32 nfserr; + u32 *fhp = (u32*)f->data; + + dprintk("lockd: nlm_file_lookup(%08x %08x %08x %08x %08x %08x)\n", + fhp[0], fhp[1], fhp[2], fhp[3], fhp[4], fhp[5]); + + + hash = file_hash(f); + + /* Lock file table */ + down(&nlm_file_sema); + + for (file = nlm_files[hash]; file; file = file->f_next) + if (!nfs_compare_fh(&file->f_handle, f)) + goto found; + + dprintk("lockd: creating file for (%08x %08x %08x %08x %08x %08x)\n", + fhp[0], fhp[1], fhp[2], fhp[3], fhp[4], fhp[5]); + + nfserr = nlm_lck_denied_nolocks; + file = (struct nlm_file *) kmalloc(sizeof(*file), GFP_KERNEL); + if (!file) + goto out_unlock; + + memset(file, 0, sizeof(*file)); + memcpy(&file->f_handle, f, sizeof(struct nfs_fh)); + file->f_hash = hash; + init_MUTEX(&file->f_sema); + + /* Open the file. Note that this must not sleep for too long, else + * we would lock up lockd:-) So no NFS re-exports, folks. + * + * We have to make sure we have the right credential to open + * the file. + */ + if ((nfserr = nlmsvc_ops->fopen(rqstp, f, &file->f_file)) != 0) { + dprintk("lockd: open failed (nfserr %d)\n", ntohl(nfserr)); + goto out_free; + } + + file->f_next = nlm_files[hash]; + nlm_files[hash] = file; + +found: + dprintk("lockd: found file %p (count %d)\n", file, file->f_count); + *result = file; + file->f_count++; + nfserr = 0; + +out_unlock: + up(&nlm_file_sema); + return nfserr; + +out_free: + kfree(file); +#ifdef CONFIG_LOCKD_V4 + if (nfserr == 1) + nfserr = nlm4_stale_fh; + else +#endif + nfserr = nlm_lck_denied; + goto out_unlock; +} + +/* + * Delete a file after having released all locks, blocks and shares + */ +static inline void +nlm_delete_file(struct nlm_file *file) +{ + struct inode *inode = file->f_file->f_dentry->d_inode; + struct nlm_file **fp, *f; + + dprintk("lockd: closing file %s/%ld\n", + inode->i_sb->s_id, inode->i_ino); + fp = nlm_files + file->f_hash; + while ((f = *fp) != NULL) { + if (f == file) { + *fp = file->f_next; + nlmsvc_ops->fclose(file->f_file); + kfree(file); + return; + } + fp = &f->f_next; + } + + printk(KERN_WARNING "lockd: attempt to release unknown file!\n"); +} + +/* + * Loop over all locks on the given file and perform the specified + * action. + */ +static int +nlm_traverse_locks(struct nlm_host *host, struct nlm_file *file, int action) +{ + struct inode *inode = nlmsvc_file_inode(file); + struct file_lock *fl; + struct nlm_host *lockhost; + +again: + file->f_locks = 0; + for (fl = inode->i_flock; fl; fl = fl->fl_next) { + if (!(fl->fl_flags & FL_LOCKD)) + continue; + + /* update current lock count */ + file->f_locks++; + lockhost = (struct nlm_host *) fl->fl_owner; + if (action == NLM_ACT_MARK) + lockhost->h_inuse = 1; + else if (action == NLM_ACT_CHECK) + return 1; + else if (action == NLM_ACT_UNLOCK) { + struct file_lock lock = *fl; + + if (host && lockhost != host) + continue; + + lock.fl_type = F_UNLCK; + lock.fl_start = 0; + lock.fl_end = OFFSET_MAX; + if (posix_lock_file(file->f_file, &lock) < 0) { + printk("lockd: unlock failure in %s:%d\n", + __FILE__, __LINE__); + return 1; + } + goto again; + } + } + + return 0; +} + +/* + * Operate on a single file + */ +static inline int +nlm_inspect_file(struct nlm_host *host, struct nlm_file *file, int action) +{ + if (action == NLM_ACT_CHECK) { + /* Fast path for mark and sweep garbage collection */ + if (file->f_count || file->f_blocks || file->f_shares) + return 1; + } else { + if (nlmsvc_traverse_blocks(host, file, action) + || nlmsvc_traverse_shares(host, file, action)) + return 1; + } + return nlm_traverse_locks(host, file, action); +} + +/* + * Loop over all files in the file table. + */ +static int +nlm_traverse_files(struct nlm_host *host, int action) +{ + struct nlm_file *file, **fp; + int i; + + down(&nlm_file_sema); + for (i = 0; i < FILE_NRHASH; i++) { + fp = nlm_files + i; + while ((file = *fp) != NULL) { + /* Traverse locks, blocks and shares of this file + * and update file->f_locks count */ + if (nlm_inspect_file(host, file, action)) { + up(&nlm_file_sema); + return 1; + } + + /* No more references to this file. Let go of it. */ + if (!file->f_blocks && !file->f_locks + && !file->f_shares && !file->f_count) { + *fp = file->f_next; + nlmsvc_ops->fclose(file->f_file); + kfree(file); + } else { + fp = &file->f_next; + } + } + } + up(&nlm_file_sema); + return 0; +} + +/* + * Release file. If there are no more remote locks on this file, + * close it and free the handle. + * + * Note that we can't do proper reference counting without major + * contortions because the code in fs/locks.c creates, deletes and + * splits locks without notification. Our only way is to walk the + * entire lock list each time we remove a lock. + */ +void +nlm_release_file(struct nlm_file *file) +{ + dprintk("lockd: nlm_release_file(%p, ct = %d)\n", + file, file->f_count); + + /* Lock file table */ + down(&nlm_file_sema); + + /* If there are no more locks etc, delete the file */ + if(--file->f_count == 0) { + if(!nlm_inspect_file(NULL, file, NLM_ACT_CHECK)) + nlm_delete_file(file); + } + + up(&nlm_file_sema); +} + +/* + * Mark all hosts that still hold resources + */ +void +nlmsvc_mark_resources(void) +{ + dprintk("lockd: nlmsvc_mark_resources\n"); + + nlm_traverse_files(NULL, NLM_ACT_MARK); +} + +/* + * Release all resources held by the given client + */ +void +nlmsvc_free_host_resources(struct nlm_host *host) +{ + dprintk("lockd: nlmsvc_free_host_resources\n"); + + if (nlm_traverse_files(host, NLM_ACT_UNLOCK)) + printk(KERN_WARNING + "lockd: couldn't remove all locks held by %s", + host->h_name); +} + +/* + * delete all hosts structs for clients + */ +void +nlmsvc_invalidate_all(void) +{ + struct nlm_host *host; + while ((host = nlm_find_client()) != NULL) { + nlmsvc_free_host_resources(host); + host->h_expires = 0; + host->h_killed = 1; + nlm_release_host(host); + } +} |