Skip to content

Commit c596772

Browse files
daimngochucklever
authored andcommitted
NFSD: handle GETATTR conflict with write delegation
If the GETATTR request on a file that has write delegation in effect and the request attributes include the change info and size attribute then the request is handled as below: Server sends CB_GETATTR to client to get the latest change info and file size. If these values are the same as the server's cached values then the GETATTR proceeds as normal. If either the change info or file size is different from the server's cached values, or the file was already marked as modified, then: . update time_modify and time_metadata into file's metadata with current time . encode GETATTR as normal except the file size is encoded with the value returned from CB_GETATTR . mark the file as modified If the CB_GETATTR fails for any reasons, the delegation is recalled and NFS4ERR_DELAY is returned for the GETATTR. Signed-off-by: Dai Ngo <[email protected]> Reviewed-by: Jeff Layton <[email protected]> Signed-off-by: Chuck Lever <[email protected]>
1 parent 6487a13 commit c596772

File tree

4 files changed

+123
-13
lines changed

4 files changed

+123
-13
lines changed

fs/nfsd/nfs4state.c

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ static void free_session(struct nfsd4_session *);
127127

128128
static const struct nfsd4_callback_ops nfsd4_cb_recall_ops;
129129
static const struct nfsd4_callback_ops nfsd4_cb_notify_lock_ops;
130+
static const struct nfsd4_callback_ops nfsd4_cb_getattr_ops;
130131

131132
static struct workqueue_struct *laundry_wq;
132133

@@ -1189,6 +1190,10 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp,
11891190
dp->dl_recalled = false;
11901191
nfsd4_init_cb(&dp->dl_recall, dp->dl_stid.sc_client,
11911192
&nfsd4_cb_recall_ops, NFSPROC4_CLNT_CB_RECALL);
1193+
nfsd4_init_cb(&dp->dl_cb_fattr.ncf_getattr, dp->dl_stid.sc_client,
1194+
&nfsd4_cb_getattr_ops, NFSPROC4_CLNT_CB_GETATTR);
1195+
dp->dl_cb_fattr.ncf_file_modified = false;
1196+
dp->dl_cb_fattr.ncf_cb_bmap[0] = FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE;
11921197
get_nfs4_file(fp);
11931198
dp->dl_stid.sc_file = fp;
11941199
return dp;
@@ -3044,11 +3049,59 @@ nfsd4_cb_recall_any_release(struct nfsd4_callback *cb)
30443049
spin_unlock(&nn->client_lock);
30453050
}
30463051

3052+
static int
3053+
nfsd4_cb_getattr_done(struct nfsd4_callback *cb, struct rpc_task *task)
3054+
{
3055+
struct nfs4_cb_fattr *ncf =
3056+
container_of(cb, struct nfs4_cb_fattr, ncf_getattr);
3057+
3058+
ncf->ncf_cb_status = task->tk_status;
3059+
switch (task->tk_status) {
3060+
case -NFS4ERR_DELAY:
3061+
rpc_delay(task, 2 * HZ);
3062+
return 0;
3063+
default:
3064+
return 1;
3065+
}
3066+
}
3067+
3068+
static void
3069+
nfsd4_cb_getattr_release(struct nfsd4_callback *cb)
3070+
{
3071+
struct nfs4_cb_fattr *ncf =
3072+
container_of(cb, struct nfs4_cb_fattr, ncf_getattr);
3073+
struct nfs4_delegation *dp =
3074+
container_of(ncf, struct nfs4_delegation, dl_cb_fattr);
3075+
3076+
nfs4_put_stid(&dp->dl_stid);
3077+
clear_bit(CB_GETATTR_BUSY, &ncf->ncf_cb_flags);
3078+
wake_up_bit(&ncf->ncf_cb_flags, CB_GETATTR_BUSY);
3079+
}
3080+
30473081
static const struct nfsd4_callback_ops nfsd4_cb_recall_any_ops = {
30483082
.done = nfsd4_cb_recall_any_done,
30493083
.release = nfsd4_cb_recall_any_release,
30503084
};
30513085

3086+
static const struct nfsd4_callback_ops nfsd4_cb_getattr_ops = {
3087+
.done = nfsd4_cb_getattr_done,
3088+
.release = nfsd4_cb_getattr_release,
3089+
};
3090+
3091+
static void nfs4_cb_getattr(struct nfs4_cb_fattr *ncf)
3092+
{
3093+
struct nfs4_delegation *dp =
3094+
container_of(ncf, struct nfs4_delegation, dl_cb_fattr);
3095+
3096+
if (test_and_set_bit(CB_GETATTR_BUSY, &ncf->ncf_cb_flags))
3097+
return;
3098+
/* set to proper status when nfsd4_cb_getattr_done runs */
3099+
ncf->ncf_cb_status = NFS4ERR_IO;
3100+
3101+
refcount_inc(&dp->dl_stid.sc_count);
3102+
nfsd4_run_cb(&ncf->ncf_getattr);
3103+
}
3104+
30523105
static struct nfs4_client *create_client(struct xdr_netobj name,
30533106
struct svc_rqst *rqstp, nfs4_verifier *verf)
30543107
{
@@ -5854,6 +5907,8 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
58545907
struct svc_fh *parent = NULL;
58555908
int cb_up;
58565909
int status = 0;
5910+
struct kstat stat;
5911+
struct path path;
58575912

58585913
cb_up = nfsd4_cb_channel_good(oo->oo_owner.so_client);
58595914
open->op_recall = false;
@@ -5891,6 +5946,18 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
58915946
if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE) {
58925947
open->op_delegate_type = NFS4_OPEN_DELEGATE_WRITE;
58935948
trace_nfsd_deleg_write(&dp->dl_stid.sc_stateid);
5949+
path.mnt = currentfh->fh_export->ex_path.mnt;
5950+
path.dentry = currentfh->fh_dentry;
5951+
if (vfs_getattr(&path, &stat,
5952+
(STATX_SIZE | STATX_CTIME | STATX_CHANGE_COOKIE),
5953+
AT_STATX_SYNC_AS_STAT)) {
5954+
nfs4_put_stid(&dp->dl_stid);
5955+
destroy_delegation(dp);
5956+
goto out_no_deleg;
5957+
}
5958+
dp->dl_cb_fattr.ncf_cur_fsize = stat.size;
5959+
dp->dl_cb_fattr.ncf_initial_cinfo =
5960+
nfsd4_change_attribute(&stat, d_inode(currentfh->fh_dentry));
58945961
} else {
58955962
open->op_delegate_type = NFS4_OPEN_DELEGATE_READ;
58965963
trace_nfsd_deleg_read(&dp->dl_stid.sc_stateid);
@@ -8710,6 +8777,8 @@ nfsd4_get_writestateid(struct nfsd4_compound_state *cstate,
87108777
* nfsd4_deleg_getattr_conflict - Recall if GETATTR causes conflict
87118778
* @rqstp: RPC transaction context
87128779
* @inode: file to be checked for a conflict
8780+
* @modified: return true if file was modified
8781+
* @size: new size of file if modified is true
87138782
*
87148783
* This function is called when there is a conflict between a write
87158784
* delegation and a change/size GETATTR from another client. The server
@@ -8718,22 +8787,22 @@ nfsd4_get_writestateid(struct nfsd4_compound_state *cstate,
87188787
* delegation before replying to the GETATTR. See RFC 8881 section
87198788
* 18.7.4.
87208789
*
8721-
* The current implementation does not support CB_GETATTR yet. However
8722-
* this can avoid recalling the delegation could be added in follow up
8723-
* work.
8724-
*
87258790
* Returns 0 if there is no conflict; otherwise an nfs_stat
87268791
* code is returned.
87278792
*/
87288793
__be32
8729-
nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode)
8794+
nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode,
8795+
bool *modified, u64 *size)
87308796
{
87318797
__be32 status;
87328798
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
87338799
struct file_lock_context *ctx;
87348800
struct file_lock *fl;
87358801
struct nfs4_delegation *dp;
8802+
struct iattr attrs;
8803+
struct nfs4_cb_fattr *ncf;
87368804

8805+
*modified = false;
87378806
ctx = locks_inode_context(inode);
87388807
if (!ctx)
87398808
return 0;
@@ -8758,12 +8827,38 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode)
87588827
return 0;
87598828
}
87608829
break_lease:
8761-
spin_unlock(&ctx->flc_lock);
87628830
nfsd_stats_wdeleg_getattr_inc(nn);
8763-
status = nfserrno(nfsd_open_break_lease(inode, NFSD_MAY_READ));
8764-
if (status != nfserr_jukebox ||
8765-
!nfsd_wait_for_delegreturn(rqstp, inode))
8766-
return status;
8831+
dp = fl->fl_owner;
8832+
ncf = &dp->dl_cb_fattr;
8833+
nfs4_cb_getattr(&dp->dl_cb_fattr);
8834+
spin_unlock(&ctx->flc_lock);
8835+
wait_on_bit_timeout(&ncf->ncf_cb_flags, CB_GETATTR_BUSY,
8836+
TASK_INTERRUPTIBLE, NFSD_CB_GETATTR_TIMEOUT);
8837+
if (ncf->ncf_cb_status) {
8838+
/* Recall delegation only if client didn't respond */
8839+
status = nfserrno(nfsd_open_break_lease(inode, NFSD_MAY_READ));
8840+
if (status != nfserr_jukebox ||
8841+
!nfsd_wait_for_delegreturn(rqstp, inode))
8842+
return status;
8843+
}
8844+
if (!ncf->ncf_file_modified &&
8845+
(ncf->ncf_initial_cinfo != ncf->ncf_cb_change ||
8846+
ncf->ncf_cur_fsize != ncf->ncf_cb_fsize))
8847+
ncf->ncf_file_modified = true;
8848+
if (ncf->ncf_file_modified) {
8849+
/*
8850+
* Per section 10.4.3 of RFC 8881, the server would
8851+
* not update the file's metadata with the client's
8852+
* modified size
8853+
*/
8854+
attrs.ia_mtime = attrs.ia_ctime = current_time(inode);
8855+
attrs.ia_valid = ATTR_MTIME | ATTR_CTIME;
8856+
setattr_copy(&nop_mnt_idmap, inode, &attrs);
8857+
mark_inode_dirty(inode);
8858+
ncf->ncf_cur_fsize = ncf->ncf_cb_fsize;
8859+
*size = ncf->ncf_cur_fsize;
8860+
*modified = true;
8861+
}
87678862
return 0;
87688863
}
87698864
break;

fs/nfsd/nfs4xdr.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3507,6 +3507,8 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
35073507
unsigned long mask[2];
35083508
} u;
35093509
unsigned long bit;
3510+
bool file_modified = false;
3511+
u64 size = 0;
35103512

35113513
WARN_ON_ONCE(bmval[1] & NFSD_WRITEONLY_ATTRS_WORD1);
35123514
WARN_ON_ONCE(!nfsd_attrs_supported(minorversion, bmval));
@@ -3533,7 +3535,8 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
35333535
}
35343536
args.size = 0;
35353537
if (u.attrmask[0] & (FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE)) {
3536-
status = nfsd4_deleg_getattr_conflict(rqstp, d_inode(dentry));
3538+
status = nfsd4_deleg_getattr_conflict(rqstp, d_inode(dentry),
3539+
&file_modified, &size);
35373540
if (status)
35383541
goto out;
35393542
}
@@ -3543,7 +3546,10 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
35433546
AT_STATX_SYNC_AS_STAT);
35443547
if (err)
35453548
goto out_nfserr;
3546-
args.size = args.stat.size;
3549+
if (file_modified)
3550+
args.size = size;
3551+
else
3552+
args.size = args.stat.size;
35473553

35483554
if (!(args.stat.result_mask & STATX_BTIME))
35493555
/* underlying FS does not offer btime so we can't share it */

fs/nfsd/nfsd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ void nfsd_lockd_shutdown(void);
367367
#define NFSD_CLIENT_MAX_TRIM_PER_RUN 128
368368
#define NFS4_CLIENTS_PER_GB 1024
369369
#define NFSD_DELEGRETURN_TIMEOUT (HZ / 34) /* 30ms */
370+
#define NFSD_CB_GETATTR_TIMEOUT NFSD_DELEGRETURN_TIMEOUT
370371

371372
/*
372373
* The following attributes are currently not supported by the NFSv4 server:

fs/nfsd/state.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,16 @@ struct nfs4_cb_fattr {
142142
/* from CB_GETATTR reply */
143143
u64 ncf_cb_change;
144144
u64 ncf_cb_fsize;
145+
146+
unsigned long ncf_cb_flags;
147+
bool ncf_file_modified;
148+
u64 ncf_initial_cinfo;
149+
u64 ncf_cur_fsize;
145150
};
146151

152+
/* bits for ncf_cb_flags */
153+
#define CB_GETATTR_BUSY 0
154+
147155
/*
148156
* Represents a delegation stateid. The nfs4_client holds references to these
149157
* and they are put when it is being destroyed or when the delegation is
@@ -773,5 +781,5 @@ static inline bool try_to_expire_client(struct nfs4_client *clp)
773781
}
774782

775783
extern __be32 nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp,
776-
struct inode *inode);
784+
struct inode *inode, bool *file_modified, u64 *size);
777785
#endif /* NFSD4_STATE_H */

0 commit comments

Comments
 (0)