+
+static int
+nfs4_deleg_conflict(u32 share, u32 dtype)
+{
+ return (((share & NFS4_SHARE_ACCESS_WRITE) &&
+ dtype == NFS4_OPEN_DELEGATE_READ) ||
+ ((share & NFS4_SHARE_ACCESS_READ) &&
+ dtype == NFS4_OPEN_DELEGATE_WRITE));
+}
+
+#define DONT_DELEGATE 8
+
+/*
+ * nfs4_check_deleg_recall()
+ *
+ * Test any delegation that is currently within an incompleted recalled
+ * state, and return NFSERR_DELAY for conflicting open share.
+ * flag is set to DONT_DELEGATE for shares that match the deleg type.
+ */
+static int
+nfs4_check_deleg_recall(struct nfs4_file *fp, struct nfsd4_open *op, int *flag)
+{
+ struct nfs4_delegation *dp;
+ int status = 0;
+
+ list_for_each_entry(dp, &fp->fi_del_perfile, dl_del_perfile) {
+ dprintk("NFSD: found delegation %p with dl_state %d\n",
+ dp, atomic_read(&dp->dl_state));
+ if (atomic_read(&dp->dl_state) == NFS4_RECALL_IN_PROGRESS) {
+ if(nfs4_deleg_conflict(op->op_share_access, dp->dl_type))
+ status = nfserr_jukebox;
+ else
+ *flag = DONT_DELEGATE;
+ }
+ }
+ return status;
+}
+
+static int
+nfs4_check_open(struct nfs4_file *fp, struct nfs4_stateowner *sop, struct nfsd4_open *open, struct nfs4_stateid **stpp)
+{
+ struct nfs4_stateid *local;
+ int status = nfserr_share_denied;
+
+ list_for_each_entry(local, &fp->fi_perfile, st_perfile) {
+ /* have we seen this open owner */
+ if (local->st_stateowner == sop) {
+ *stpp = local;
+ continue;
+ }
+ /* ignore lock owners */
+ if (local->st_stateowner->so_is_open_owner == 0)
+ continue;
+ /* check for conflicting share reservations */
+ if (!test_share(local, open))
+ goto out;
+ }
+ status = 0;
+out:
+ return status;
+}
+
+static int
+nfs4_new_open(struct svc_rqst *rqstp, struct nfs4_stateid **stpp,
+ struct svc_fh *cur_fh, int flags)
+{
+ struct nfs4_stateid *stp;
+ int status;
+
+ stp = kmalloc(sizeof(struct nfs4_stateid), GFP_KERNEL);
+ if (stp == NULL)
+ return nfserr_resource;
+
+ status = nfsd_open(rqstp, cur_fh, S_IFREG, flags, &stp->st_vfs_file);
+ if (status) {
+ if (status == nfserr_dropit)
+ status = nfserr_jukebox;
+ kfree(stp);
+ return status;
+ }
+ vfsopen++;
+ stp->st_vfs_set = 1;
+ *stpp = stp;
+ return 0;
+}
+
+static int
+nfs4_upgrade_open(struct svc_rqst *rqstp, struct svc_fh *cur_fh, struct nfs4_stateid *stp, struct nfsd4_open *open)
+{
+ struct file *filp = stp->st_vfs_file;
+ struct inode *inode = filp->f_dentry->d_inode;
+ unsigned int share_access;
+ int status;
+
+ set_access(&share_access, stp->st_access_bmap);
+ share_access = ~share_access;
+ share_access &= open->op_share_access;
+
+ /* update the struct file */
+ if (share_access & NFS4_SHARE_ACCESS_WRITE) {
+ status = get_write_access(inode);
+ if (status)
+ return nfserrno(status);
+ if (open->op_truncate) {
+ struct iattr iattr = {
+ .ia_valid = ATTR_SIZE,
+ .ia_size = 0,
+ };
+ status = nfsd_setattr(rqstp, cur_fh, &iattr, 0,
+ (time_t)0);
+ if (status) {
+ put_write_access(inode);
+ return status;
+ }
+ }
+
+ /* remember the open */
+ filp->f_mode = (filp->f_mode | FMODE_WRITE) & ~FMODE_READ;
+ set_bit(open->op_share_access, &stp->st_access_bmap);
+ set_bit(open->op_share_deny, &stp->st_deny_bmap);
+ }
+ return nfs_ok;
+}
+
+
+/* decrement seqid on successful reclaim, it will be bumped in encode_open */
+static void
+nfs4_set_claim_prev(struct nfsd4_open *open, int *status)
+{
+ if (open->op_claim_type == NFS4_OPEN_CLAIM_PREVIOUS) {
+ if (*status)
+ *status = nfserr_reclaim_bad;
+ else {
+ open->op_stateowner->so_confirmed = 1;
+ open->op_stateowner->so_seqid--;
+ }
+ }
+}
+
+/*
+ * Attempt to hand out a delegation.
+ */
+static void
+nfs4_open_delegation(struct svc_fh *fh, struct nfsd4_open *open, struct nfs4_stateid *stp, int *flag)
+{
+ struct nfs4_delegation *dp;
+ struct nfs4_stateowner *sop = stp->st_stateowner;
+ struct nfs4_callback *cb = &sop->so_client->cl_callback;
+ struct file_lock fl, *flp = &fl;
+ int status;
+
+ if (*flag == DONT_DELEGATE) {
+ *flag = NFS4_OPEN_DELEGATE_NONE;
+ return;
+ }
+
+ /* set flag */
+ *flag = NFS4_OPEN_DELEGATE_NONE;
+ if (open->op_claim_type != NFS4_OPEN_CLAIM_NULL
+ || !atomic_read(&cb->cb_set) || !sop->so_confirmed)
+ return;
+
+ if (!(open->op_share_access & NFS4_SHARE_ACCESS_WRITE))
+ *flag = NFS4_OPEN_DELEGATE_READ;
+
+ else if (!(open->op_share_access & NFS4_SHARE_ACCESS_READ))
+ *flag = NFS4_OPEN_DELEGATE_WRITE;
+
+ if (!(dp = alloc_init_deleg(sop->so_client, stp->st_file, fh, *flag)))
+ return;
+ locks_init_lock(&fl);
+ fl.fl_lmops = &nfsd_lease_mng_ops;
+ fl.fl_flags = FL_LEASE;
+ fl.fl_end = OFFSET_MAX;
+ fl.fl_owner = (fl_owner_t)dp;
+ fl.fl_file = stp->st_vfs_file;
+ fl.fl_pid = current->tgid;
+
+ if ((status = setlease(stp->st_vfs_file,
+ *flag == NFS4_OPEN_DELEGATE_READ? F_RDLCK: F_WRLCK, &flp))) {
+ dprintk("NFSD: setlease failed [%d], no delegation\n", status);
+ list_del(&dp->dl_del_perfile);
+ list_del(&dp->dl_del_perclnt);
+ kfree(dp);
+ free_delegation++;
+ *flag = NFS4_OPEN_DELEGATE_NONE;
+ return;
+ }
+
+ memcpy(&open->op_delegate_stateid, &dp->dl_stateid, sizeof(dp->dl_stateid));
+
+ dprintk("NFSD: delegation stateid=(%08x/%08x/%08x/%08x)\n\n",
+ dp->dl_stateid.si_boot,
+ dp->dl_stateid.si_stateownerid,
+ dp->dl_stateid.si_fileid,
+ dp->dl_stateid.si_generation);
+}
+