diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 2ce48deceb2c0c5d1d74ba7ddf25c70a8c21f02d..8f89a263f0022d0ad8fcb9bc3373de54060bf880 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -377,7 +377,7 @@ nfsd4_read(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_read } /* check stateid */ if ((status = nfs4_preprocess_stateid_op(current_fh, &read->rd_stateid, - CHECK_FH, &stp))) { + CHECK_FH | RDWR_STATE, &stp))) { dprintk("NFSD: nfsd4_read: couldn't process stateid!\n"); goto out; } @@ -467,7 +467,7 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_se nfs4_lock_state(); if ((status = nfs4_preprocess_stateid_op(current_fh, &setattr->sa_stateid, - CHECK_FH, &stp))) { + CHECK_FH | RDWR_STATE, &stp))) { dprintk("NFSD: nfsd4_setattr: couldn't process stateid!\n"); goto out; } @@ -507,7 +507,7 @@ nfsd4_write(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_writ goto zero_stateid; } if ((status = nfs4_preprocess_stateid_op(current_fh, stateid, - CHECK_FH, &stp))) { + CHECK_FH | RDWR_STATE, &stp))) { dprintk("NFSD: nfsd4_write: couldn't process stateid!\n"); goto out; } @@ -681,6 +681,9 @@ nfsd4_proc_compound(struct svc_rqst *rqstp, case OP_LINK: op->status = nfsd4_link(rqstp, ¤t_fh, &save_fh, &op->u.link); break; + case OP_LOCK: + op->status = nfsd4_lock(rqstp, ¤t_fh, &op->u.lock); + break; case OP_LOOKUP: op->status = nfsd4_lookup(rqstp, ¤t_fh, &op->u.lookup); break; diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index bbdb08bc30b9521f02b127fe0fdd3413089462f5..80da15300d0dfd84889e77c91271c058da221850 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -38,7 +38,6 @@ #include <linux/major.h> #include <linux/slab.h> - #include <linux/sunrpc/svc.h> #include <linux/nfsd/nfsd.h> #include <linux/nfsd/cache.h> @@ -70,6 +69,10 @@ u32 alloc_sowner = 0; u32 free_sowner = 0; u32 vfsopen = 0; u32 vfsclose = 0; +u32 alloc_lsowner= 0; + +/* forward declarations */ +struct nfs4_stateid * find_stateid(stateid_t *stid, int flags); /* Locking: * @@ -106,7 +109,7 @@ opaque_hashval(const void *ptr, int nbytes) /* forward declarations */ static void release_stateowner(struct nfs4_stateowner *sop); -static void release_stateid(struct nfs4_stateid *stp); +static void release_stateid(struct nfs4_stateid *stp, int flags); static void release_file(struct nfs4_file *fp); @@ -757,7 +760,7 @@ free_stateowner(struct nfs4_stateowner *sop) { } static struct nfs4_stateowner * -alloc_init_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct nfsd4_open *open) { +alloc_init_open_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct nfsd4_open *open) { struct nfs4_stateowner *sop; struct nfs4_replay *rp; unsigned int idhashval; @@ -773,6 +776,7 @@ alloc_init_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct n list_add(&sop->so_strhash, &ownerstr_hashtbl[strhashval]); list_add(&sop->so_perclient, &clp->cl_perclient); add_perclient++; + sop->so_is_open_owner = 1; sop->so_id = current_ownerid++; sop->so_client = clp; sop->so_seqid = open->op_seqid; @@ -797,7 +801,10 @@ release_stateowner(struct nfs4_stateowner *sop) while (!list_empty(&sop->so_perfilestate)) { stp = list_entry(sop->so_perfilestate.next, struct nfs4_stateid, st_perfilestate); - release_stateid(stp); + if(sop->so_is_open_owner) + release_stateid(stp, OPEN_STATE); + else + release_stateid(stp, LOCK_STATE); } free_stateowner(sop); } @@ -824,13 +831,13 @@ init_stateid(struct nfs4_stateid *stp, struct nfs4_file *fp, struct nfs4_stateow } static void -release_stateid(struct nfs4_stateid *stp) { +release_stateid(struct nfs4_stateid *stp, int flags) { list_del_init(&stp->st_hash); list_del_perfile++; list_del_init(&stp->st_perfile); list_del_init(&stp->st_perfilestate); - if(stp->st_vfs_set) { + if((stp->st_vfs_set) && (flags & OPEN_STATE)) { nfsd_close(&stp->st_vfs_file); vfsclose++; dput(stp->st_vfs_file.f_dentry); @@ -851,13 +858,14 @@ release_file(struct nfs4_file *fp) } void -release_open_state(struct nfs4_stateid *stp, struct nfsd4_close *cl) +release_state_owner(struct nfs4_stateid *stp, struct nfs4_stateowner **sopp, + int flag) { struct nfs4_stateowner *sop = stp->st_stateowner; struct nfs4_file *fp = stp->st_file; - dprintk("NFSD: release_open_state\n"); - release_stateid(stp); + dprintk("NFSD: release_state_owner\n"); + release_stateid(stp, flag); /* * release unused nfs4_stateowners. * XXX will need to be placed on an open_stateid_lru list to be @@ -866,7 +874,7 @@ release_open_state(struct nfs4_stateid *stp, struct nfsd4_close *cl) */ if (sop->so_confirmed && list_empty(&sop->so_perfilestate)) { release_stateowner(sop); - cl->cl_stateowner = NULL; + *sopp = NULL; } /* unused nfs4_file's are releseed. XXX slab cache? */ if (list_empty(&fp->fi_perfile)) { @@ -875,10 +883,10 @@ release_open_state(struct nfs4_stateid *stp, struct nfsd4_close *cl) } static int -cmp_owner_str(struct nfs4_stateowner *sop, struct nfsd4_open *open) { - return ((sop->so_owner.len == open->op_owner.len) && - !memcmp(sop->so_owner.data, open->op_owner.data, sop->so_owner.len) && - (sop->so_client->cl_clientid.cl_id == open->op_clientid.cl_id)); +cmp_owner_str(struct nfs4_stateowner *sop, struct xdr_netobj *owner, clientid_t *clid) { + return ((sop->so_owner.len == owner->len) && + !memcmp(sop->so_owner.data, owner->data, owner->len) && + (sop->so_client->cl_clientid.cl_id == clid->cl_id)); } /* search ownerstr_hashtbl[] for owner */ @@ -889,7 +897,7 @@ find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open, struct nf list_for_each_safe(pos, next, &ownerstr_hashtbl[hashval]) { local = list_entry(pos, struct nfs4_stateowner, so_strhash); - if(!cmp_owner_str(local, open)) + if(!cmp_owner_str(local, &open->op_owner, &open->op_clientid)) continue; *op = local; return(1); @@ -1071,7 +1079,7 @@ nfsd4_process_open1(struct nfsd4_open *open) goto out; instantiate_new_owner: status = nfserr_resource; - if (!(sop = alloc_init_stateowner(strhashval, clp, open))) + if (!(sop = alloc_init_open_stateowner(strhashval, clp, open))) goto out; open->op_stateowner = sop; status = nfs_ok; @@ -1205,7 +1213,7 @@ nfsd4_renew(clientid_t *clid) int status; nfs4_lock_state(); - printk("process_renew(%08x/%08x): starting\n", + dprintk("process_renew(%08x/%08x): starting\n", clid->cl_boot, clid->cl_id); status = nfserr_stale_clientid; if (STALE_CLIENTID(clid)) @@ -1231,7 +1239,7 @@ nfsd4_renew(clientid_t *clid) * Presumably this is because the client took too long to * RENEW, so return NFS4ERR_EXPIRED. */ - printk("nfsd4_renew: clientid not found!\n"); + dprintk("nfsd4_renew: clientid not found!\n"); status = nfserr_expired; out: nfs4_unlock_state(); @@ -1277,25 +1285,6 @@ laundromat_main(void *not_used) schedule_delayed_work(&laundromat_work, t*HZ); } -/* search stateid_hashtbl[] for stateid */ -struct nfs4_stateid * -find_stateid(stateid_t *stid) -{ - struct list_head *pos, *next; - struct nfs4_stateid *local = NULL; - u32 st_id = stid->si_stateownerid; - u32 f_id = stid->si_fileid; - unsigned int hashval = stateid_hashval(st_id, f_id); - - list_for_each_safe(pos, next, &stateid_hashtbl[hashval]) { - local = list_entry(pos, struct nfs4_stateid, st_hash); - if((local->st_stateid.si_stateownerid == st_id) && - (local->st_stateid.si_fileid == f_id)) - return local; - } - return NULL; -} - /* search ownerid_hashtbl[] for stateid owner (stateid->si_stateownerid) */ struct nfs4_stateowner * find_openstateowner_id(u32 st_id) { @@ -1338,7 +1327,7 @@ nfs4_preprocess_stateid_op(struct svc_fh *current_fh, stateid_t *stateid, int fl struct nfs4_stateid *stp; int status; - dprintk("NFSD: preprocess_stateid_op:stateid = (%08x/%08x/%08x/%08x)\n", + dprintk("NFSD: preprocess_stateid_op: stateid = (%08x/%08x/%08x/%08x)\n", stateid->si_boot, stateid->si_stateownerid, stateid->si_fileid, stateid->si_generation); @@ -1351,27 +1340,27 @@ nfs4_preprocess_stateid_op(struct svc_fh *current_fh, stateid_t *stateid, int fl /* BAD STATEID */ status = nfserr_bad_stateid; - if (!(stp = find_stateid(stateid))) { - dprintk("NFSD: process stateid: no open stateid!\n"); + if (!(stp = find_stateid(stateid, flags))) { + dprintk("NFSD: preprocess_stateid_op: no open stateid!\n"); goto out; } if ((flags & CHECK_FH) && nfs4_check_fh(current_fh, stp)) { - dprintk("NFSD: preprocess_seqid_op: fh-stateid mismatch!\n"); + dprintk("NFSD: preprocess_stateid_op: fh-stateid mismatch!\n"); goto out; } if (!stp->st_stateowner->so_confirmed) { - dprintk("process_stateid: lockowner not confirmed yet!\n"); + dprintk("preprocess_stateid_op: lockowner not confirmed yet!\n"); goto out; } if (stateid->si_generation > stp->st_stateid.si_generation) { - dprintk("process_stateid: future stateid?!\n"); + dprintk("preprocess_stateid_op: future stateid?!\n"); goto out; } /* OLD STATEID */ status = nfserr_old_stateid; if (stateid->si_generation < stp->st_stateid.si_generation) { - dprintk("process_stateid: old stateid!\n"); + dprintk("preprocess_stateid_op: old stateid!\n"); goto out; } *stpp = stp; @@ -1418,7 +1407,7 @@ nfs4_preprocess_seqid_op(struct svc_fh *current_fh, u32 seqid, stateid_t *statei * this might be a retransmitted CLOSE which has arrived after * the openfile has been released. */ - if (!(stp = find_stateid(stateid))) + if (!(stp = find_stateid(stateid, flags))) goto no_nfs4_stateid; status = nfserr_bad_stateid; @@ -1507,7 +1496,7 @@ nfsd4_open_confirm(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfs if ((status = nfs4_preprocess_seqid_op(current_fh, oc->oc_seqid, &oc->oc_req_stateid, - CHECK_FH | CONFIRM, + CHECK_FH | CONFIRM | OPEN_STATE, &oc->oc_stateowner, &stp))) goto out; @@ -1539,7 +1528,8 @@ nfsd4_open_downgrade(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct n nfs4_lock_state(); if ((status = nfs4_preprocess_seqid_op(current_fh, od->od_seqid, &od->od_stateid, - CHECK_FH, &od->od_stateowner, &stp))) + CHECK_FH | OPEN_STATE, + &od->od_stateowner, &stp))) goto out; status = nfserr_inval; @@ -1578,7 +1568,7 @@ nfsd4_close(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_clos nfs4_lock_state(); if ((status = nfs4_preprocess_seqid_op(current_fh, close->cl_seqid, &close->cl_stateid, - CHECK_FH, + CHECK_FH | OPEN_STATE, &close->cl_stateowner, &stp))) goto out; /* @@ -1588,13 +1578,374 @@ nfsd4_close(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_clos update_stateid(&stp->st_stateid); memcpy(&close->cl_stateid, &stp->st_stateid, sizeof(stateid_t)); - /* release_open_state() calls nfsd_close() if needed */ - release_open_state(stp,close); + /* release_state_owner() calls nfsd_close() if needed */ + release_state_owner(stp, &close->cl_stateowner, OPEN_STATE); out: nfs4_unlock_state(); return status; } +/* + * Lock owner state (byte-range locks) + */ +#define LOFF_OVERFLOW(start, len) ((u64)(len) > ~(u64)(start)) +#define LOCK_HASH_BITS 8 +#define LOCK_HASH_SIZE (1 << LOCK_HASH_BITS) +#define LOCK_HASH_MASK (LOCK_HASH_SIZE - 1) + +#define lockownerid_hashval(id) \ + ((id) & LOCK_HASH_MASK) +#define lock_ownerstr_hashval(x, clientid, ownername) \ + ((file_hashval(x) + (clientid) + opaque_hashval((ownername.data), (ownername.len))) & LOCK_HASH_MASK) + +static struct list_head lock_ownerid_hashtbl[LOCK_HASH_SIZE]; +static struct list_head lock_ownerstr_hashtbl[LOCK_HASH_SIZE]; +static struct list_head lockstateid_hashtbl[STATEID_HASH_SIZE]; + +struct nfs4_stateid * +find_stateid(stateid_t *stid, int flags) +{ + struct list_head *pos, *next; + struct nfs4_stateid *local = NULL; + u32 st_id = stid->si_stateownerid; + u32 f_id = stid->si_fileid; + unsigned int hashval; + + dprintk("NFSD: find_stateid flags 0x%x\n",flags); + if ((flags & LOCK_STATE) || (flags & RDWR_STATE)) { + hashval = stateid_hashval(st_id, f_id); + list_for_each_safe(pos, next, &lockstateid_hashtbl[hashval]) { + local = list_entry(pos, struct nfs4_stateid, st_hash); + if((local->st_stateid.si_stateownerid == st_id) && + (local->st_stateid.si_fileid == f_id)) + return local; + } + } + if ((flags & OPEN_STATE) || (flags & RDWR_STATE)) { + hashval = stateid_hashval(st_id, f_id); + list_for_each_safe(pos, next, &stateid_hashtbl[hashval]) { + local = list_entry(pos, struct nfs4_stateid, st_hash); + if((local->st_stateid.si_stateownerid == st_id) && + (local->st_stateid.si_fileid == f_id)) + return local; + } + } else + printk("NFSD: find_stateid: ERROR: no state flag\n"); + return NULL; +} + + +/* + * TODO: Linux file offsets are _signed_ 64-bit quantities, which means that + * we can't properly handle lock requests that go beyond the (2^63 - 1)-th + * byte, because of sign extension problems. Since NFSv4 calls for 64-bit + * locking, this prevents us from being completely protocol-compliant. The + * real solution to this problem is to start using unsigned file offsets in + * the VFS, but this is a very deep change! + */ +static inline void +nfs4_transform_lock_offset(struct file_lock *lock) +{ + if (lock->fl_start < 0) + lock->fl_start = OFFSET_MAX; + if (lock->fl_end < 0) + lock->fl_end = OFFSET_MAX; +} + +int +nfs4_verify_lock_stateowner(struct nfs4_stateowner *sop, unsigned int hashval) +{ + struct list_head *pos, *next; + struct nfs4_stateowner *local = NULL; + int status = 0; + + if (hashval >= LOCK_HASH_SIZE) + goto out; + list_for_each_safe(pos, next, &lock_ownerid_hashtbl[hashval]) { + local = list_entry(pos, struct nfs4_stateowner, so_idhash); + if (local == sop) { + status = 1; + goto out; + } + } +out: + return status; +} + + +static inline void +nfs4_set_lock_denied(struct file_lock *fl, struct nfsd4_lock_denied *deny) +{ + struct nfs4_stateowner *sop = (struct nfs4_stateowner *) fl->fl_owner; + + deny->ld_sop = NULL; + if (nfs4_verify_lock_stateowner(sop, fl->fl_pid)) + deny->ld_sop = sop; + deny->ld_start = fl->fl_start; + deny->ld_length = ~(u64)0; + if (fl->fl_end != ~(u64)0) + deny->ld_length = fl->fl_end - fl->fl_start + 1; + deny->ld_type = NFS4_READ_LT; + if (fl->fl_type != F_RDLCK) + deny->ld_type = NFS4_WRITE_LT; +} + + +static int +find_lockstateowner_str(unsigned int hashval, struct xdr_netobj *owner, clientid_t *clid, struct nfs4_stateowner **op) { + struct list_head *pos, *next; + struct nfs4_stateowner *local = NULL; + + list_for_each_safe(pos, next, &lock_ownerstr_hashtbl[hashval]) { + local = list_entry(pos, struct nfs4_stateowner, so_strhash); + if(!cmp_owner_str(local, owner, clid)) + continue; + *op = local; + return(1); + } + *op = NULL; + return 0; +} + +/* + * Alloc a lock owner structure. + * Called in nfsd4_lock - therefore, OPEN and OPEN_CONFIRM (if needed) has + * occured. + * + * strhashval = lock_ownerstr_hashval + * so_seqid = lock->lk_new_lock_seqid - 1: it gets bumped in encode + */ + +static struct nfs4_stateowner * +alloc_init_lock_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct nfsd4_lock *lock) { + struct nfs4_stateowner *sop; + struct nfs4_replay *rp; + unsigned int idhashval; + + if (!(sop = alloc_stateowner(&lock->lk_new_owner))) + return (struct nfs4_stateowner *)NULL; + idhashval = lockownerid_hashval(current_ownerid); + INIT_LIST_HEAD(&sop->so_idhash); + INIT_LIST_HEAD(&sop->so_strhash); + INIT_LIST_HEAD(&sop->so_perclient); + INIT_LIST_HEAD(&sop->so_perfilestate); + list_add(&sop->so_idhash, &lock_ownerid_hashtbl[idhashval]); + list_add(&sop->so_strhash, &lock_ownerstr_hashtbl[strhashval]); + list_add(&sop->so_perclient, &clp->cl_perclient); + add_perclient++; + sop->so_is_open_owner = 0; + sop->so_id = current_ownerid++; + sop->so_client = clp; + sop->so_seqid = lock->lk_new_lock_seqid - 1; + sop->so_confirmed = 1; + rp = &sop->so_replay; + rp->rp_status = NFSERR_SERVERFAULT; + rp->rp_buflen = 0; + rp->rp_buf = rp->rp_ibuf; + alloc_lsowner++; + return sop; +} + +struct nfs4_stateid * +alloc_init_lock_stateid(struct nfs4_stateowner *sop, struct nfs4_file *fp, struct nfs4_stateid *open_stp) +{ + struct nfs4_stateid *stp; + unsigned int hashval = stateid_hashval(sop->so_id, fp->fi_id); + + if ((stp = kmalloc(sizeof(struct nfs4_stateid), + GFP_KERNEL)) == NULL) + goto out; + + INIT_LIST_HEAD(&stp->st_hash); + INIT_LIST_HEAD(&stp->st_perfile); + INIT_LIST_HEAD(&stp->st_perfilestate); + list_add(&stp->st_hash, &lockstateid_hashtbl[hashval]); + list_add(&stp->st_perfile, &fp->fi_perfile); + list_add_perfile++; + list_add(&stp->st_perfilestate, &sop->so_perfilestate); + stp->st_stateowner = sop; + stp->st_file = fp; + stp->st_stateid.si_boot = boot_time; + stp->st_stateid.si_stateownerid = sop->so_id; + stp->st_stateid.si_fileid = fp->fi_id; + stp->st_stateid.si_generation = 0; + stp->st_vfs_file = open_stp->st_vfs_file; + stp->st_vfs_set = open_stp->st_vfs_set; + stp->st_share_access = -1; + stp->st_share_deny = -1; + +out: + return stp; +} + +/* + * LOCK operation + */ +int +nfsd4_lock(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_lock *lock) +{ + struct nfs4_stateowner *lock_sop = NULL, *open_sop = NULL; + struct nfs4_stateid *lock_stp; + struct file *filp; + struct file_lock file_lock; + struct file_lock *conflock; + int status = 0; + unsigned int strhashval; + + dprintk("NFSD: nfsd4_lock: start=%Ld length=%Ld\n", + lock->lk_offset, lock->lk_length); + + lock->lk_stateowner = NULL; + nfs4_lock_state(); + + if (lock->lk_is_new) { + /* + * Client indicates that this is a new lockowner. + * Use open owner and open stateid to create lock owner and lock + * stateid. + */ + struct nfs4_stateid *open_stp = NULL; + struct nfs4_file *fp; + + status = nfserr_stale_clientid; + if (STALE_CLIENTID(&lock->lk_new_clientid)) { + printk("NFSD: nfsd4_lock: clientid is stale!\n"); + goto out; + } + /* validate and update open stateid and open seqid */ + status = nfs4_preprocess_seqid_op(current_fh, + lock->lk_new_open_seqid, + &lock->lk_new_open_stateid, + CHECK_FH | OPEN_STATE, + &open_sop, &open_stp); + if (status) + goto out; + /* create lockowner and lock stateid */ + fp = open_stp->st_file; + strhashval = lock_ownerstr_hashval(fp->fi_inode, + open_sop->so_client->cl_clientid.cl_id, + lock->v.new.owner); + + /* + * If we already have this lock owner, the client is in + * error (or our bookeeping is wrong!) + * for asking for a 'new lock'. + */ + status = nfserr_bad_stateid; + if (find_lockstateowner_str(strhashval, &lock->v.new.owner, + &lock->v.new.clientid, &lock_sop)) + goto out; + status = nfserr_resource; + if (!(lock->lk_stateowner = alloc_init_lock_stateowner(strhashval, + open_sop->so_client, lock))) + goto out; + if ((lock_stp = alloc_init_lock_stateid(lock->lk_stateowner, + fp, open_stp)) == NULL) + goto out; + /* bump the open seqid used to create the lock */ + open_sop->so_seqid++; + } else { + /* lock (lock owner + lock stateid) already exists */ + status = nfs4_preprocess_seqid_op(current_fh, + lock->lk_old_lock_seqid, + &lock->lk_old_lock_stateid, + CHECK_FH | LOCK_STATE, + &lock->lk_stateowner, &lock_stp); + if (status) + goto out; + } + /* lock->lk_stateowner and lock_stp have been created or found */ + filp = &lock_stp->st_vfs_file; + + if ((status = fh_verify(rqstp, current_fh, S_IFREG, MAY_LOCK))) { + printk("NFSD: nfsd4_lock: permission denied!\n"); + goto out; + } + + switch (lock->lk_type) { + case NFS4_READ_LT: + case NFS4_READW_LT: + file_lock.fl_type = F_RDLCK; + break; + case NFS4_WRITE_LT: + case NFS4_WRITEW_LT: + file_lock.fl_type = F_WRLCK; + break; + default: + status = nfserr_inval; + goto out; + } + file_lock.fl_owner = (fl_owner_t) lock->lk_stateowner; + file_lock.fl_pid = lockownerid_hashval(lock->lk_stateowner->so_id); + file_lock.fl_file = filp; + file_lock.fl_flags = FL_POSIX; + file_lock.fl_notify = NULL; + file_lock.fl_insert = NULL; + file_lock.fl_remove = NULL; + + file_lock.fl_start = lock->lk_offset; + if ((lock->lk_length == ~(u64)0) || + LOFF_OVERFLOW(lock->lk_offset, lock->lk_length)) + file_lock.fl_end = ~(u64)0; + else + file_lock.fl_end = lock->lk_offset + lock->lk_length - 1; + nfs4_transform_lock_offset(&file_lock); + + /* + * Try to lock the file in the VFS. + * Note: locks.c uses the BKL to protect the inode's lock list. + */ + + status = posix_lock_file(filp, &file_lock); + dprintk("NFSD: nfsd4_lock: posix_test_lock passed. posix_lock_file status %d\n",status); + switch (-status) { + case 0: /* success! */ + update_stateid(&lock_stp->st_stateid); + memcpy(&lock->lk_resp_stateid, &lock_stp->st_stateid, + sizeof(stateid_t)); + goto out; + case (EAGAIN): + goto conflicting_lock; + case (EDEADLK): + status = nfserr_deadlock; + default: + dprintk("NFSD: nfsd4_lock: posix_lock_file() failed! status %d\n",status); + goto out_destroy_new_stateid; + } + +conflicting_lock: + dprintk("NFSD: nfsd4_lock: conflicting lock found!\n"); + status = nfserr_denied; + /* XXX There is a race here. Future patch needed to provide + * an atomic posix_lock_and_test_file + */ + if (!(conflock = posix_test_lock(filp, &file_lock))) { + status = nfserr_serverfault; + goto out; + } + nfs4_set_lock_denied(conflock, &lock->lk_denied); + +out_destroy_new_stateid: + if (lock->lk_is_new) { + dprintk("NFSD: nfsd4_lock: destroy new stateid!\n"); + /* + * An error encountered after instantiation of the new + * stateid has forced us to destroy it. + */ + if (!seqid_mutating_err(status)) + open_sop->so_seqid--; + + release_state_owner(lock_stp, &lock->lk_stateowner, LOCK_STATE); + } +out: + nfs4_unlock_state(); + return status; +} + +/* + * Start and stop routines + */ + void nfs4_state_init(void) { @@ -1617,6 +1968,11 @@ nfs4_state_init(void) } for (i = 0; i < STATEID_HASH_SIZE; i++) { INIT_LIST_HEAD(&stateid_hashtbl[i]); + INIT_LIST_HEAD(&lockstateid_hashtbl[i]); + } + for (i = 0; i < LOCK_HASH_SIZE; i++) { + INIT_LIST_HEAD(&lock_ownerid_hashtbl[i]); + INIT_LIST_HEAD(&lock_ownerstr_hashtbl[i]); } memset(&zerostateid, 0, sizeof(stateid_t)); memset(&onestateid, ~0, sizeof(stateid_t)); @@ -1656,8 +2012,8 @@ __nfs4_state_shutdown(void) add_perclient, del_perclient); dprintk("NFSD: alloc_file %d free_file %d\n", alloc_file, free_file); - dprintk("NFSD: alloc_sowner %d free_sowner %d\n", - alloc_sowner, free_sowner); + dprintk("NFSD: alloc_sowner %d alloc_lsowner %d free_sowner %d\n", + alloc_sowner, alloc_lsowner, free_sowner); dprintk("NFSD: vfsopen %d vfsclose %d\n", vfsopen, vfsclose); } diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index bdf83155c625f8929ea60b912a48088f293f2f50..1158815985330e503b4970964b92d2872edf5eb8 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -567,6 +567,44 @@ nfsd4_decode_link(struct nfsd4_compoundargs *argp, struct nfsd4_link *link) DECODE_TAIL; } +static int +nfsd4_decode_lock(struct nfsd4_compoundargs *argp, struct nfsd4_lock *lock) +{ + DECODE_HEAD; + + /* + * type, reclaim(boolean), offset, length, new_lock_owner(boolean) + */ + READ_BUF(28); + READ32(lock->lk_type); + if ((lock->lk_type < NFS4_READ_LT) || (lock->lk_type > NFS4_WRITEW_LT)) + goto xdr_error; + READ32(lock->lk_reclaim); + READ64(lock->lk_offset); + READ64(lock->lk_length); + READ32(lock->lk_is_new); + + if (lock->lk_is_new) { + READ_BUF(36); + READ32(lock->lk_new_open_seqid); + READ32(lock->lk_new_open_stateid.si_generation); + + COPYMEM(&lock->lk_new_open_stateid.si_opaque, sizeof(stateid_opaque_t)); + READ32(lock->lk_new_lock_seqid); + COPYMEM(&lock->lk_new_clientid, sizeof(clientid_t)); + READ32(lock->lk_new_owner.len); + READ_BUF(lock->lk_new_owner.len); + READMEM(lock->lk_new_owner.data, lock->lk_new_owner.len); + } else { + READ_BUF(20); + READ32(lock->lk_old_lock_stateid.si_generation); + COPYMEM(&lock->lk_old_lock_stateid.si_opaque, sizeof(stateid_opaque_t)); + READ32(lock->lk_old_lock_seqid); + } + + DECODE_TAIL; +} + static int nfsd4_decode_lookup(struct nfsd4_compoundargs *argp, struct nfsd4_lookup *lookup) { @@ -989,6 +1027,9 @@ nfsd4_decode_compound(struct nfsd4_compoundargs *argp) case OP_LINK: op->status = nfsd4_decode_link(argp, &op->u.link); break; + case OP_LOCK: + op->status = nfsd4_decode_lock(argp, &op->u.lock); + break; case OP_LOOKUP: op->status = nfsd4_decode_lookup(argp, &op->u.lookup); break; @@ -1709,6 +1750,42 @@ nfsd4_encode_getfh(struct nfsd4_compoundres *resp, int nfserr, struct svc_fh *fh } } +/* +* Including all fields other than the name, a LOCK4denied structure requires +* 8(clientid) + 4(namelen) + 8(offset) + 8(length) + 4(type) = 32 bytes. +*/ +static void +nfsd4_encode_lock_denied(struct nfsd4_compoundres *resp, struct nfsd4_lock_denied *ld) +{ + ENCODE_HEAD; + + RESERVE_SPACE(32 + XDR_LEN(ld->ld_sop->so_owner.len)); + WRITE64(ld->ld_start); + WRITE64(ld->ld_length); + WRITE32(ld->ld_type); + WRITEMEM(&ld->ld_sop->so_client->cl_clientid, 8); + WRITE32(ld->ld_sop->so_owner.len); + WRITEMEM(ld->ld_sop->so_owner.data, ld->ld_sop->so_owner.len); + ADJUST_ARGS(); +} + +static void +nfsd4_encode_lock(struct nfsd4_compoundres *resp, int nfserr, struct nfsd4_lock *lock) +{ + + ENCODE_SEQID_OP_HEAD; + + if (!nfserr) { + RESERVE_SPACE(4 + sizeof(stateid_t)); + WRITE32(lock->lk_resp_stateid.si_generation); + WRITEMEM(&lock->lk_resp_stateid.si_opaque, sizeof(stateid_opaque_t)); + ADJUST_ARGS(); + } else if (nfserr == nfserr_denied) + nfsd4_encode_lock_denied(resp, &lock->lk_denied); + + ENCODE_SEQID_OP_TAIL(lock->lk_stateowner); +} + static void nfsd4_encode_link(struct nfsd4_compoundres *resp, int nfserr, struct nfsd4_link *link) { @@ -2119,6 +2196,9 @@ nfsd4_encode_operation(struct nfsd4_compoundres *resp, struct nfsd4_op *op) case OP_LINK: nfsd4_encode_link(resp, op->status, &op->u.link); break; + case OP_LOCK: + nfsd4_encode_lock(resp, op->status, &op->u.lock); + break; case OP_LOOKUP: break; case OP_LOOKUPP: diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h index 9e1bf6cc55e923d7c3239c984786d8fb570ca5ee..a65be432b9f54aa88a4f5f877dd5daae1c10493b 100644 --- a/include/linux/nfs4.h +++ b/include/linux/nfs4.h @@ -134,6 +134,15 @@ enum open_delegation_type4 { NFS4_OPEN_DELEGATE_WRITE = 2 }; +enum lock_type4 { + NFS4_UNLOCK_LT = 0, + NFS4_READ_LT = 1, + NFS4_WRITE_LT = 2, + NFS4_READW_LT = 3, + NFS4_WRITEW_LT = 4 +}; + + /* Mandatory Attributes */ #define FATTR4_WORD0_SUPPORTED_ATTRS (1) #define FATTR4_WORD0_TYPE (1 << 1) diff --git a/include/linux/nfsd/nfsd.h b/include/linux/nfsd/nfsd.h index 3e3be1abc991b424a31d9d019eae19b11920d7df..8c4fb5cfff21a3ab921cffa411790dd852966f33 100644 --- a/include/linux/nfsd/nfsd.h +++ b/include/linux/nfsd/nfsd.h @@ -172,6 +172,8 @@ void nfsd_lockd_shutdown(void); #define nfserr_serverfault __constant_htonl(NFSERR_SERVERFAULT) #define nfserr_badtype __constant_htonl(NFSERR_BADTYPE) #define nfserr_jukebox __constant_htonl(NFSERR_JUKEBOX) +#define nfserr_denied __constant_htonl(NFSERR_DENIED) +#define nfserr_deadlock __constant_htonl(NFSERR_DEADLOCK) #define nfserr_expired __constant_htonl(NFSERR_EXPIRED) #define nfserr_bad_cookie __constant_htonl(NFSERR_BAD_COOKIE) #define nfserr_same __constant_htonl(NFSERR_SAME) diff --git a/include/linux/nfsd/state.h b/include/linux/nfsd/state.h index c0975c670e42172677b3fc843f8a574cf16291fb..b48b3620158547d77d5934f80db63db58865780d 100644 --- a/include/linux/nfsd/state.h +++ b/include/linux/nfsd/state.h @@ -117,16 +117,24 @@ struct nfs4_replay { }; /* -* nfs4_stateowner can either be an open_owner, or (eventually) a lock_owner +* nfs4_stateowner can either be an open_owner, or a lock_owner * -* o so_perfilestate list is used to ensure no dangling nfs4_stateid -* reverences when we release a stateowner. +* so_idhash: stateid_hashtbl[] for open owner, lockstateid_hashtbl[] +* for lock_owner +* so_strhash: ownerstr_hashtbl[] for open_owner, lock_ownerstr_hashtbl[] +* for lock_owner +* so_perclient: nfs4_client->cl_perclient entry - used when nfs4_client +* struct is reaped. +* so_perfilestate: heads the list of nfs4_stateid (either open or lock) +* and is used to ensure no dangling nfs4_stateid references when we +* release a stateowner. */ struct nfs4_stateowner { struct list_head so_idhash; /* hash by so_id */ struct list_head so_strhash; /* hash by op_name */ struct list_head so_perclient; /* nfs4_client->cl_perclient */ struct list_head so_perfilestate; /* list: nfs4_stateid */ + int so_is_open_owner; /* 1=openowner,0=lockowner */ u32 so_id; struct nfs4_client * so_client; u32 so_seqid; @@ -152,12 +160,18 @@ struct nfs4_file { * nfs4_stateid can either be an open stateid or (eventually) a lock stateid * * (open)nfs4_stateid: one per (open)nfs4_stateowner, nfs4_file +* +* st_hash: stateid_hashtbl[] entry or lockstateid_hashtbl entry +* st_perfile: file_hashtbl[] entry. +* st_perfile_state: nfs4_stateowner->so_perfilestate +* st_share_access: used only for open stateid +* st_share_deny: used only for open stateid */ struct nfs4_stateid { - struct list_head st_hash; + struct list_head st_hash; struct list_head st_perfile; - struct list_head st_perfilestate; + struct list_head st_perfilestate; struct nfs4_stateowner * st_stateowner; struct nfs4_file * st_file; stateid_t st_stateid; @@ -170,6 +184,9 @@ struct nfs4_stateid { /* flags for preprocess_seqid_op() */ #define CHECK_FH 0x00000001 #define CONFIRM 0x00000002 +#define OPEN_STATE 0x00000004 +#define LOCK_STATE 0x00000008 +#define RDWR_STATE 0x00000010 #define seqid_mutating_err(err) \ (((err) != nfserr_stale_clientid) && \ diff --git a/include/linux/nfsd/xdr4.h b/include/linux/nfsd/xdr4.h index 254d5d2c5647c3c7dc32050a16615e78e56c87e7..bbb037e08f18cc505d91dc73697026e4f2ad9f27 100644 --- a/include/linux/nfsd/xdr4.h +++ b/include/linux/nfsd/xdr4.h @@ -40,6 +40,7 @@ #define _LINUX_NFSD_XDR4_H #define NFSD4_MAX_TAGLEN 128 +#define XDR_LEN(n) (((n) + 3) & ~3) typedef u32 delegation_zero_t; typedef u32 delegation_boot_t; @@ -111,6 +112,56 @@ struct nfsd4_link { struct nfsd4_change_info li_cinfo; /* response */ }; +struct nfsd4_lock_denied { + struct nfs4_stateowner *ld_sop; + u64 ld_start; + u64 ld_length; + u32 ld_type; +}; + +struct nfsd4_lock { + /* request */ + u32 lk_type; + u32 lk_reclaim; /* boolean */ + u64 lk_offset; + u64 lk_length; + u32 lk_is_new; + union { + struct { + u32 open_seqid; + stateid_t open_stateid; + u32 lock_seqid; + clientid_t clientid; + struct xdr_netobj owner; + } new; + struct { + stateid_t lock_stateid; + u32 lock_seqid; + } old; + } v; + + /* response */ + union { + struct { + stateid_t stateid; + } ok; + struct nfsd4_lock_denied denied; + } u; + + struct nfs4_stateowner *lk_stateowner; +}; +#define lk_new_open_seqid v.new.open_seqid +#define lk_new_open_stateid v.new.open_stateid +#define lk_new_lock_seqid v.new.lock_seqid +#define lk_new_clientid v.new.clientid +#define lk_new_owner v.new.owner +#define lk_old_lock_stateid v.old.lock_stateid +#define lk_old_lock_seqid v.old.lock_seqid + +#define lk_rflags u.ok.rflags +#define lk_resp_stateid u.ok.stateid +#define lk_denied u.denied + struct nfsd4_lookup { u32 lo_len; /* request */ char * lo_name; /* request */ @@ -266,6 +317,7 @@ struct nfsd4_op { struct nfsd4_getattr getattr; struct svc_fh * getfh; struct nfsd4_link link; + struct nfsd4_lock lock; struct nfsd4_lookup lookup; struct nfsd4_verify nverify; struct nfsd4_open open; @@ -357,6 +409,8 @@ extern int nfsd4_close(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_close *close); extern int nfsd4_open_downgrade(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_open_downgrade *od); +extern int nfsd4_lock(struct svc_rqst *rqstp, struct svc_fh *current_fh, + struct nfsd4_lock *lock); #endif /*