CIFS: Fix fast lease break after open problem
authorPavel Shilovsky <pshilovsky@etersoft.ru>
Wed, 19 Sep 2012 13:22:45 +0000 (06:22 -0700)
committerSteve French <smfrench@gmail.com>
Tue, 25 Sep 2012 02:46:33 +0000 (21:46 -0500)
Now we walk though cifsFileInfo's list for every incoming lease
break and look for an equivalent there. That approach misses lease
breaks that come just after an open response - we don't have time
to populate new cifsFileInfo structure to the list. Fix this by
adding new list of pending opens and look for a lease there if we
didn't find it in the list of cifsFileInfo structures.

Signed-off-by: Pavel Shilovsky <pshilovsky@etersoft.ru>
Signed-off-by: Steve French <sfrench@us.ibm.com>
fs/cifs/cifsglob.h
fs/cifs/cifsproto.h
fs/cifs/connect.c
fs/cifs/dir.c
fs/cifs/file.c
fs/cifs/misc.c
fs/cifs/smb2misc.c

index b6ec142028e8f6fdb9ad4c47089081cdd2850c7a..a39e5b7fc844628ba810f9217eff9cf2ed45498e 100644 (file)
@@ -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 {
index c758ee7b0307221b401e68d3e32e2eb84b7b25c3..09ea6321c55af4496392b2c9d73ddbde949477ad 100644 (file)
@@ -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);
index 443e39633107067d3fbd223a8814966409722e64..59c595e8a1b036dd12140958e7bc605cbc0986dd 100644 (file)
@@ -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);
index 4f2147c5adb60dce9df10928a58c5b72cec4e0b3..7c0a8128364546111322b4a89caef9009a276980 100644 (file)
@@ -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;
        }
 
index e93e3d2c69e68dc361040aedf26d71eba1fb2b56..88e9c74e2cac64d15c48188e498c8abe7d0657ee 100644 (file)
@@ -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;
        }
index a921b0712eff43ce05ca81c710aa5dd05dc8397a..3a00c0d0cead9d6836f4e449f679a6909f14a89e 100644 (file)
@@ -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);
+}
index 3a7f8bd5127d3db113ceca3b4f4e38d35e340083..cd31715f03f4e497195e83a3ebd4bcae321e8b60 100644 (file)
@@ -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;
 }