diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index b6ec142028e8f6fdb9ad4c47089081cdd2850c7a..a39e5b7fc844628ba810f9217eff9cf2ed45498e 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -715,6 +715,7 @@ struct cifs_ses { __u16 session_flags; #endif /* CONFIG_CIFS_SMB2 */ }; + /* no more than one of the following three session flags may be set */ #define CIFS_SES_NT4 1 #define CIFS_SES_OS2 2 @@ -821,6 +822,7 @@ struct cifs_tcon { u64 resource_id; /* server resource id */ struct fscache_cookie *fscache; /* cookie for share */ #endif + struct list_head pending_opens; /* list of incomplete opens */ /* BB add field for back pointer to sb struct(s)? */ }; @@ -863,6 +865,15 @@ cifs_get_tlink(struct tcon_link *tlink) /* This function is always expected to succeed */ extern struct cifs_tcon *cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb); +#define CIFS_OPLOCK_NO_CHANGE 0xfe + +struct cifs_pending_open { + struct list_head olist; + struct tcon_link *tlink; + __u8 lease_key[16]; + __u32 oplock; +}; + /* * This info hangs off the cifsFileInfo structure, pointed to by llist. * This is used to track byte stream locks on the file @@ -903,6 +914,7 @@ struct cifs_fid { __u64 volatile_fid; /* volatile file id for smb2 */ __u8 lease_key[SMB2_LEASE_KEY_SIZE]; /* lease key for smb2 */ #endif + struct cifs_pending_open *pending_open; }; struct cifs_fid_locks { diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index c758ee7b0307221b401e68d3e32e2eb84b7b25c3..09ea6321c55af4496392b2c9d73ddbde949477ad 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -184,6 +184,13 @@ extern bool cifs_find_lock_conflict(struct cifsFileInfo *cfile, __u64 offset, __u64 length, __u8 type, struct cifsLockInfo **conf_lock, bool rw_check); +extern void cifs_add_pending_open(struct cifs_fid *fid, + struct tcon_link *tlink, + struct cifs_pending_open *open); +extern void cifs_add_pending_open_locked(struct cifs_fid *fid, + struct tcon_link *tlink, + struct cifs_pending_open *open); +extern void cifs_del_pending_open(struct cifs_pending_open *open); #if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) extern void cifs_dfs_release_automount_timer(void); diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 443e39633107067d3fbd223a8814966409722e64..59c595e8a1b036dd12140958e7bc605cbc0986dd 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2645,6 +2645,7 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb_vol *volume_info) tcon->retry = volume_info->retry; tcon->nocase = volume_info->nocase; tcon->local_lease = volume_info->local_lease; + INIT_LIST_HEAD(&tcon->pending_opens); spin_lock(&cifs_tcp_ses_lock); list_add(&tcon->tcon_list, &ses->tcon_list); diff --git a/fs/cifs/dir.c b/fs/cifs/dir.c index 4f2147c5adb60dce9df10928a58c5b72cec4e0b3..7c0a8128364546111322b4a89caef9009a276980 100644 --- a/fs/cifs/dir.c +++ b/fs/cifs/dir.c @@ -382,6 +382,7 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry, struct cifs_tcon *tcon; struct TCP_Server_Info *server; struct cifs_fid fid; + struct cifs_pending_open open; __u32 oplock; struct cifsFileInfo *file_info; @@ -423,16 +424,21 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry, if (server->ops->new_lease_key) server->ops->new_lease_key(&fid); + cifs_add_pending_open(&fid, tlink, &open); + rc = cifs_do_create(inode, direntry, xid, tlink, oflags, mode, &oplock, &fid, opened); - if (rc) + if (rc) { + cifs_del_pending_open(&open); goto out; + } rc = finish_open(file, direntry, generic_file_open, opened); if (rc) { if (server->ops->close) server->ops->close(xid, tcon, &fid); + cifs_del_pending_open(&open); goto out; } @@ -440,6 +446,7 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry, if (file_info == NULL) { if (server->ops->close) server->ops->close(xid, tcon, &fid); + cifs_del_pending_open(&open); rc = -ENOMEM; } diff --git a/fs/cifs/file.c b/fs/cifs/file.c index e93e3d2c69e68dc361040aedf26d71eba1fb2b56..88e9c74e2cac64d15c48188e498c8abe7d0657ee 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -247,6 +247,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, struct cifsInodeInfo *cinode = CIFS_I(inode); struct cifsFileInfo *cfile; struct cifs_fid_locks *fdlocks; + struct cifs_tcon *tcon = tlink_tcon(tlink); cfile = kzalloc(sizeof(struct cifsFileInfo), GFP_KERNEL); if (cfile == NULL) @@ -274,10 +275,15 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, cfile->tlink = cifs_get_tlink(tlink); INIT_WORK(&cfile->oplock_break, cifs_oplock_break); mutex_init(&cfile->fh_mutex); - tlink_tcon(tlink)->ses->server->ops->set_fid(cfile, fid, oplock); spin_lock(&cifs_file_list_lock); - list_add(&cfile->tlist, &(tlink_tcon(tlink)->openFileList)); + if (fid->pending_open->oplock != CIFS_OPLOCK_NO_CHANGE) + oplock = fid->pending_open->oplock; + list_del(&fid->pending_open->olist); + + tlink_tcon(tlink)->ses->server->ops->set_fid(cfile, fid, oplock); + + list_add(&cfile->tlist, &tcon->openFileList); /* if readable file instance put first in list*/ if (file->f_mode & FMODE_READ) list_add(&cfile->flist, &cinode->openFileList); @@ -307,9 +313,12 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file) { struct inode *inode = cifs_file->dentry->d_inode; struct cifs_tcon *tcon = tlink_tcon(cifs_file->tlink); + struct TCP_Server_Info *server = tcon->ses->server; struct cifsInodeInfo *cifsi = CIFS_I(inode); struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct cifsLockInfo *li, *tmp; + struct cifs_fid fid; + struct cifs_pending_open open; spin_lock(&cifs_file_list_lock); if (--cifs_file->count > 0) { @@ -317,6 +326,12 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file) return; } + if (server->ops->get_lease_key) + server->ops->get_lease_key(inode, &fid); + + /* store open in pending opens to make sure we don't miss lease break */ + cifs_add_pending_open_locked(&fid, cifs_file->tlink, &open); + /* remove it from the lists */ list_del(&cifs_file->flist); list_del(&cifs_file->tlist); @@ -348,6 +363,8 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file) free_xid(xid); } + cifs_del_pending_open(&open); + /* * Delete any outstanding lock records. We'll lose them when the file * is closed anyway. @@ -368,6 +385,7 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file) } int cifs_open(struct inode *inode, struct file *file) + { int rc = -EACCES; unsigned int xid; @@ -380,6 +398,7 @@ int cifs_open(struct inode *inode, struct file *file) char *full_path = NULL; bool posix_open_ok = false; struct cifs_fid fid; + struct cifs_pending_open open; xid = get_xid(); @@ -401,7 +420,7 @@ int cifs_open(struct inode *inode, struct file *file) cFYI(1, "inode = 0x%p file flags are 0x%x for %s", inode, file->f_flags, full_path); - if (tcon->ses->server->oplocks) + if (server->oplocks) oplock = REQ_OPLOCK; else oplock = 0; @@ -434,20 +453,28 @@ int cifs_open(struct inode *inode, struct file *file) */ } + if (server->ops->get_lease_key) + server->ops->get_lease_key(inode, &fid); + + cifs_add_pending_open(&fid, tlink, &open); + if (!posix_open_ok) { if (server->ops->get_lease_key) server->ops->get_lease_key(inode, &fid); rc = cifs_nt_open(full_path, inode, cifs_sb, tcon, file->f_flags, &oplock, &fid, xid); - if (rc) + if (rc) { + cifs_del_pending_open(&open); goto out; + } } cfile = cifs_new_fileinfo(&fid, file, tlink, oplock); if (cfile == NULL) { if (server->ops->close) server->ops->close(xid, tcon, &fid); + cifs_del_pending_open(&open); rc = -ENOMEM; goto out; } diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index a921b0712eff43ce05ca81c710aa5dd05dc8397a..3a00c0d0cead9d6836f4e449f679a6909f14a89e 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -579,3 +579,33 @@ backup_cred(struct cifs_sb_info *cifs_sb) return false; } + +void +cifs_del_pending_open(struct cifs_pending_open *open) +{ + spin_lock(&cifs_file_list_lock); + list_del(&open->olist); + spin_unlock(&cifs_file_list_lock); +} + +void +cifs_add_pending_open_locked(struct cifs_fid *fid, struct tcon_link *tlink, + struct cifs_pending_open *open) +{ +#ifdef CONFIG_CIFS_SMB2 + memcpy(open->lease_key, fid->lease_key, SMB2_LEASE_KEY_SIZE); +#endif + open->oplock = CIFS_OPLOCK_NO_CHANGE; + open->tlink = tlink; + fid->pending_open = open; + list_add_tail(&open->olist, &tlink_tcon(tlink)->pending_opens); +} + +void +cifs_add_pending_open(struct cifs_fid *fid, struct tcon_link *tlink, + struct cifs_pending_open *open) +{ + spin_lock(&cifs_file_list_lock); + cifs_add_pending_open_locked(fid, tlink, open); + spin_unlock(&cifs_file_list_lock); +} diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c index 3a7f8bd5127d3db113ceca3b4f4e38d35e340083..cd31715f03f4e497195e83a3ebd4bcae321e8b60 100644 --- a/fs/cifs/smb2misc.c +++ b/fs/cifs/smb2misc.c @@ -389,6 +389,27 @@ __u8 smb2_map_lease_to_oplock(__le32 lease_state) return 0; } +struct smb2_lease_break_work { + struct work_struct lease_break; + struct tcon_link *tlink; + __u8 lease_key[16]; + __le32 lease_state; +}; + +static void +cifs_ses_oplock_break(struct work_struct *work) +{ + struct smb2_lease_break_work *lw = container_of(work, + struct smb2_lease_break_work, lease_break); + int rc; + + rc = SMB2_lease_break(0, tlink_tcon(lw->tlink), lw->lease_key, + lw->lease_state); + cFYI(1, "Lease release rc %d", rc); + cifs_put_tlink(lw->tlink); + kfree(lw); +} + static bool smb2_is_valid_lease_break(char *buffer, struct TCP_Server_Info *server) { @@ -398,6 +419,19 @@ smb2_is_valid_lease_break(char *buffer, struct TCP_Server_Info *server) struct cifs_tcon *tcon; struct cifsInodeInfo *cinode; struct cifsFileInfo *cfile; + struct cifs_pending_open *open; + struct smb2_lease_break_work *lw; + bool found; + int ack_req = rsp->Flags & SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED; + + lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL); + if (!lw) { + cERROR(1, "Memory allocation failed during lease break check"); + return false; + } + + INIT_WORK(&lw->lease_break, cifs_ses_oplock_break); + lw->lease_state = rsp->NewLeaseState; cFYI(1, "Checking for lease break"); @@ -405,28 +439,29 @@ smb2_is_valid_lease_break(char *buffer, struct TCP_Server_Info *server) spin_lock(&cifs_tcp_ses_lock); list_for_each(tmp, &server->smb_ses_list) { ses = list_entry(tmp, struct cifs_ses, smb_ses_list); + + spin_lock(&cifs_file_list_lock); list_for_each(tmp1, &ses->tcon_list) { tcon = list_entry(tmp1, struct cifs_tcon, tcon_list); cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks); - spin_lock(&cifs_file_list_lock); list_for_each(tmp2, &tcon->openFileList) { cfile = list_entry(tmp2, struct cifsFileInfo, - tlist); + tlist); cinode = CIFS_I(cfile->dentry->d_inode); if (memcmp(cinode->lease_key, rsp->LeaseKey, SMB2_LEASE_KEY_SIZE)) continue; + cFYI(1, "found in the open list"); cFYI(1, "lease key match, lease break 0x%d", le32_to_cpu(rsp->NewLeaseState)); smb2_set_oplock_level(cinode, smb2_map_lease_to_oplock(rsp->NewLeaseState)); - if (rsp->Flags & - SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED) + if (ack_req) cfile->oplock_break_cancelled = false; else cfile->oplock_break_cancelled = true; @@ -437,10 +472,39 @@ smb2_is_valid_lease_break(char *buffer, struct TCP_Server_Info *server) spin_unlock(&cifs_tcp_ses_lock); return true; } - spin_unlock(&cifs_file_list_lock); + + found = false; + list_for_each_entry(open, &tcon->pending_opens, olist) { + if (memcmp(open->lease_key, rsp->LeaseKey, + SMB2_LEASE_KEY_SIZE)) + continue; + + if (!found && ack_req) { + found = true; + memcpy(lw->lease_key, open->lease_key, + SMB2_LEASE_KEY_SIZE); + lw->tlink = cifs_get_tlink(open->tlink); + queue_work(cifsiod_wq, + &lw->lease_break); + } + + cFYI(1, "found in the pending open list"); + cFYI(1, "lease key match, lease break 0x%d", + le32_to_cpu(rsp->NewLeaseState)); + + open->oplock = + smb2_map_lease_to_oplock(rsp->NewLeaseState); + } + if (found) { + spin_unlock(&cifs_file_list_lock); + spin_unlock(&cifs_tcp_ses_lock); + return true; + } } + spin_unlock(&cifs_file_list_lock); } spin_unlock(&cifs_tcp_ses_lock); + kfree(lw); cFYI(1, "Can not process lease break - no lease matched"); return false; }