| /* |
| * 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); |
| } |
| } |