cifs: Deferred close for files
authorRohith Surabattula <rohiths@microsoft.com>
Tue, 13 Apr 2021 05:26:42 +0000 (00:26 -0500)
committerSteve French <stfrench@microsoft.com>
Mon, 3 May 2021 16:20:35 +0000 (11:20 -0500)
When file is closed, SMB2 close request is not sent to server
immediately and is deferred for acregmax defined interval. When file is
reopened by same process for read or write, the file handle
is reused if an oplock is held.

When client receives a oplock/lease break, file is closed immediately
if reference count is zero, else oplock is downgraded.

Signed-off-by: Rohith Surabattula <rohiths@microsoft.com>
Reviewed-by: Shyam Prasad N <sprasad@microsoft.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/cifsfs.c
fs/cifs/cifsglob.h
fs/cifs/cifsproto.h
fs/cifs/file.c
fs/cifs/inode.c
fs/cifs/misc.c

index 44fd850f05c16dc7ec55f1aa1855464a8fd79975..8a6894577697c4187fa98f16076ae4cea3c8996d 100644 (file)
@@ -133,6 +133,7 @@ struct workqueue_struct     *cifsiod_wq;
 struct workqueue_struct        *decrypt_wq;
 struct workqueue_struct        *fileinfo_put_wq;
 struct workqueue_struct        *cifsoplockd_wq;
+struct workqueue_struct *deferredclose_wq;
 __u32 cifs_lock_secret;
 
 /*
@@ -390,6 +391,8 @@ cifs_alloc_inode(struct super_block *sb)
        /* cifs_inode->vfs_inode.i_flags = S_NOATIME | S_NOCMTIME; */
        INIT_LIST_HEAD(&cifs_inode->openFileList);
        INIT_LIST_HEAD(&cifs_inode->llist);
+       INIT_LIST_HEAD(&cifs_inode->deferred_closes);
+       spin_lock_init(&cifs_inode->deferred_lock);
        return &cifs_inode->vfs_inode;
 }
 
@@ -1637,9 +1640,16 @@ init_cifs(void)
                goto out_destroy_fileinfo_put_wq;
        }
 
+       deferredclose_wq = alloc_workqueue("deferredclose",
+                                          WQ_FREEZABLE|WQ_MEM_RECLAIM, 0);
+       if (!deferredclose_wq) {
+               rc = -ENOMEM;
+               goto out_destroy_cifsoplockd_wq;
+       }
+
        rc = cifs_fscache_register();
        if (rc)
-               goto out_destroy_cifsoplockd_wq;
+               goto out_destroy_deferredclose_wq;
 
        rc = cifs_init_inodecache();
        if (rc)
@@ -1707,6 +1717,8 @@ out_destroy_inodecache:
        cifs_destroy_inodecache();
 out_unreg_fscache:
        cifs_fscache_unregister();
+out_destroy_deferredclose_wq:
+       destroy_workqueue(deferredclose_wq);
 out_destroy_cifsoplockd_wq:
        destroy_workqueue(cifsoplockd_wq);
 out_destroy_fileinfo_put_wq:
@@ -1741,6 +1753,7 @@ exit_cifs(void)
        cifs_destroy_mids();
        cifs_destroy_inodecache();
        cifs_fscache_unregister();
+       destroy_workqueue(deferredclose_wq);
        destroy_workqueue(cifsoplockd_wq);
        destroy_workqueue(decrypt_wq);
        destroy_workqueue(fileinfo_put_wq);
index b23a0ee8c6f8264e4d739691adffd7b1e8f3742a..d88b4b523dcc4c74d5f9a49ecb8a504f2399ae3e 100644 (file)
@@ -1154,6 +1154,14 @@ struct cifs_pending_open {
        __u32 oplock;
 };
 
+struct cifs_deferred_close {
+       struct list_head dlist;
+       struct tcon_link *tlink;
+       __u16  netfid;
+       __u64  persistent_fid;
+       __u64  volatile_fid;
+};
+
 /*
  * This info hangs off the cifsFileInfo structure, pointed to by llist.
  * This is used to track byte stream locks on the file
@@ -1248,6 +1256,9 @@ struct cifsFileInfo {
        struct cifs_search_info srch_inf;
        struct work_struct oplock_break; /* work for oplock breaks */
        struct work_struct put; /* work for the final part of _put */
+       struct delayed_work deferred;
+       bool oplock_break_received; /* Flag to indicate oplock break */
+       bool deferred_scheduled;
 };
 
 struct cifs_io_parms {
@@ -1392,6 +1403,7 @@ struct cifsInodeInfo {
 #define CIFS_INO_DELETE_PENDING                  (3) /* delete pending on server */
 #define CIFS_INO_INVALID_MAPPING         (4) /* pagecache is invalid */
 #define CIFS_INO_LOCK                    (5) /* lock bit for synchronization */
+#define CIFS_INO_MODIFIED_ATTR            (6) /* Indicate change in mtime/ctime */
        unsigned long flags;
        spinlock_t writers_lock;
        unsigned int writers;           /* Number of writers on this inode */
@@ -1404,6 +1416,8 @@ struct cifsInodeInfo {
        struct fscache_cookie *fscache;
 #endif
        struct inode vfs_inode;
+       struct list_head deferred_closes; /* list of deferred closes */
+       spinlock_t deferred_lock; /* protection on deferred list */
 };
 
 static inline struct cifsInodeInfo *
@@ -1871,11 +1885,14 @@ extern bool disable_legacy_dialects;  /* forbid vers=1.0 and vers=2.0 mounts */
 
 void cifs_oplock_break(struct work_struct *work);
 void cifs_queue_oplock_break(struct cifsFileInfo *cfile);
+void smb2_deferred_work_close(struct work_struct *work);
 
+extern const struct slow_work_ops cifs_oplock_break_ops;
 extern struct workqueue_struct *cifsiod_wq;
 extern struct workqueue_struct *decrypt_wq;
 extern struct workqueue_struct *fileinfo_put_wq;
 extern struct workqueue_struct *cifsoplockd_wq;
+extern struct workqueue_struct *deferredclose_wq;
 extern __u32 cifs_lock_secret;
 
 extern mempool_t *cifs_mid_poolp;
index c8faa3e82fe7044a1b8c3909d537728b6e6bdf4b..c6dacce87d3a24da13f6547d198c0b59fdd02b86 100644 (file)
@@ -267,6 +267,17 @@ 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);
+
+extern bool cifs_is_deferred_close(struct cifsFileInfo *cfile,
+                               struct cifs_deferred_close **dclose);
+
+extern void cifs_add_deferred_close(struct cifsFileInfo *cfile,
+                               struct cifs_deferred_close *dclose);
+
+extern void cifs_del_deferred_close(struct cifsFileInfo *cfile);
+
+extern void cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode);
+
 extern struct TCP_Server_Info *cifs_get_tcp_session(struct smb3_fs_context *ctx);
 extern void cifs_put_tcp_session(struct TCP_Server_Info *server,
                                 int from_reconnect);
index 3d4e6e7dac1d2341da5de5261733a9dfaca242e9..7e97aeabd61617b5cbb902a4eec937f0f167614e 100644 (file)
@@ -322,9 +322,12 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
        cfile->dentry = dget(dentry);
        cfile->f_flags = file->f_flags;
        cfile->invalidHandle = false;
+       cfile->oplock_break_received = false;
+       cfile->deferred_scheduled = false;
        cfile->tlink = cifs_get_tlink(tlink);
        INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
        INIT_WORK(&cfile->put, cifsFileInfo_put_work);
+       INIT_DELAYED_WORK(&cfile->deferred, smb2_deferred_work_close);
        mutex_init(&cfile->fh_mutex);
        spin_lock_init(&cfile->file_info_lock);
 
@@ -565,6 +568,23 @@ int cifs_open(struct inode *inode, struct file *file)
                        file->f_op = &cifs_file_direct_ops;
        }
 
+       spin_lock(&CIFS_I(inode)->deferred_lock);
+       /* Get the cached handle as SMB2 close is deferred */
+       rc = cifs_get_readable_path(tcon, full_path, &cfile);
+       if (rc == 0) {
+               if (file->f_flags == cfile->f_flags) {
+                       file->private_data = cfile;
+                       cifs_del_deferred_close(cfile);
+                       spin_unlock(&CIFS_I(inode)->deferred_lock);
+                       goto out;
+               } else {
+                       spin_unlock(&CIFS_I(inode)->deferred_lock);
+                       _cifsFileInfo_put(cfile, true, false);
+               }
+       } else {
+               spin_unlock(&CIFS_I(inode)->deferred_lock);
+       }
+
        if (server->oplocks)
                oplock = REQ_OPLOCK;
        else
@@ -846,11 +866,52 @@ reopen_error_exit:
        return rc;
 }
 
+void smb2_deferred_work_close(struct work_struct *work)
+{
+       struct cifsFileInfo *cfile = container_of(work,
+                       struct cifsFileInfo, deferred.work);
+
+       spin_lock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock);
+       cifs_del_deferred_close(cfile);
+       cfile->deferred_scheduled = false;
+       spin_unlock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock);
+       _cifsFileInfo_put(cfile, true, false);
+}
+
 int cifs_close(struct inode *inode, struct file *file)
 {
+       struct cifsFileInfo *cfile;
+       struct cifsInodeInfo *cinode = CIFS_I(inode);
+       struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+       struct cifs_deferred_close *dclose;
+
        if (file->private_data != NULL) {
-               _cifsFileInfo_put(file->private_data, true, false);
+               cfile = file->private_data;
                file->private_data = NULL;
+               dclose = kmalloc(sizeof(struct cifs_deferred_close), GFP_KERNEL);
+               if ((cinode->oplock == CIFS_CACHE_RHW_FLG) &&
+                   dclose) {
+                       if (test_bit(CIFS_INO_MODIFIED_ATTR, &cinode->flags))
+                               inode->i_ctime = inode->i_mtime = current_time(inode);
+                       spin_lock(&cinode->deferred_lock);
+                       cifs_add_deferred_close(cfile, dclose);
+                       if (cfile->deferred_scheduled) {
+                               mod_delayed_work(deferredclose_wq,
+                                               &cfile->deferred, cifs_sb->ctx->acregmax);
+                       } else {
+                               /* Deferred close for files */
+                               queue_delayed_work(deferredclose_wq,
+                                               &cfile->deferred, cifs_sb->ctx->acregmax);
+                               cfile->deferred_scheduled = true;
+                               spin_unlock(&cinode->deferred_lock);
+                               return 0;
+                       }
+                       spin_unlock(&cinode->deferred_lock);
+                       _cifsFileInfo_put(cfile, true, false);
+               } else {
+                       _cifsFileInfo_put(cfile, true, false);
+                       kfree(dclose);
+               }
        }
 
        /* return code from the ->release op is always ignored */
@@ -1947,7 +2008,8 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode,
                if (fsuid_only && !uid_eq(open_file->uid, current_fsuid()))
                        continue;
                if (OPEN_FMODE(open_file->f_flags) & FMODE_READ) {
-                       if (!open_file->invalidHandle) {
+                       if ((!open_file->invalidHandle) &&
+                               (!open_file->oplock_break_received)) {
                                /* found a good file */
                                /* lock it so it will not be closed on us */
                                cifsFileInfo_get(open_file);
@@ -2476,6 +2538,8 @@ retry:
        if (cfile)
                cifsFileInfo_put(cfile);
        free_xid(xid);
+       /* Indication to update ctime and mtime as close is deferred */
+       set_bit(CIFS_INO_MODIFIED_ATTR, &CIFS_I(inode)->flags);
        return rc;
 }
 
@@ -2584,6 +2648,8 @@ static int cifs_write_end(struct file *file, struct address_space *mapping,
 
        unlock_page(page);
        put_page(page);
+       /* Indication to update ctime and mtime as close is deferred */
+       set_bit(CIFS_INO_MODIFIED_ATTR, &CIFS_I(inode)->flags);
 
        return rc;
 }
@@ -4744,6 +4810,8 @@ void cifs_oplock_break(struct work_struct *work)
        struct TCP_Server_Info *server = tcon->ses->server;
        int rc = 0;
        bool purge_cache = false;
+       bool is_deferred = false;
+       struct cifs_deferred_close *dclose;
 
        wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
                        TASK_UNINTERRUPTIBLE);
@@ -4791,6 +4859,18 @@ oplock_break_ack:
                cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
        }
        _cifsFileInfo_put(cfile, false /* do not wait for ourself */, false);
+       /*
+        * When oplock break is received and there are no active
+        * file handles but cached, then set the flag oplock_break_received.
+        * So, new open will not use cached handle.
+        */
+       spin_lock(&CIFS_I(inode)->deferred_lock);
+       is_deferred = cifs_is_deferred_close(cfile, &dclose);
+       if (is_deferred) {
+               cfile->oplock_break_received = true;
+               mod_delayed_work(deferredclose_wq, &cfile->deferred, 0);
+       }
+       spin_unlock(&CIFS_I(inode)->deferred_lock);
        cifs_done_oplock_break(cinode);
 }
 
index a0846f78843661bb407c02630e74f42e333c0d60..b4326ffcefced4b2e019e30696fd20082d6fc892 100644 (file)
@@ -1645,6 +1645,7 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)
                goto unlink_out;
        }
 
+       cifs_close_deferred_file(CIFS_I(inode));
        if (cap_unix(tcon->ses) && (CIFS_UNIX_POSIX_PATH_OPS_CAP &
                                le64_to_cpu(tcon->fsUnixInfo.Capability))) {
                rc = CIFSPOSIXDelFile(xid, tcon, full_path,
index c15a90e422be79493e5f848b40756afa129e507e..e63fbd4a6bfe762610448677800cff83112ea3cd 100644 (file)
@@ -672,6 +672,68 @@ cifs_add_pending_open(struct cifs_fid *fid, struct tcon_link *tlink,
        spin_unlock(&tlink_tcon(open->tlink)->open_file_lock);
 }
 
+bool
+cifs_is_deferred_close(struct cifsFileInfo *cfile, struct cifs_deferred_close **pdclose)
+{
+       struct cifs_deferred_close *dclose;
+
+       list_for_each_entry(dclose, &CIFS_I(d_inode(cfile->dentry))->deferred_closes, dlist) {
+               if ((dclose->netfid == cfile->fid.netfid) &&
+                       (dclose->persistent_fid == cfile->fid.persistent_fid) &&
+                       (dclose->volatile_fid == cfile->fid.volatile_fid)) {
+                       *pdclose = dclose;
+                       return true;
+               }
+       }
+       return false;
+}
+
+void
+cifs_add_deferred_close(struct cifsFileInfo *cfile, struct cifs_deferred_close *dclose)
+{
+       bool is_deferred = false;
+       struct cifs_deferred_close *pdclose;
+
+       is_deferred = cifs_is_deferred_close(cfile, &pdclose);
+       if (is_deferred) {
+               kfree(dclose);
+               return;
+       }
+
+       dclose->tlink = cfile->tlink;
+       dclose->netfid = cfile->fid.netfid;
+       dclose->persistent_fid = cfile->fid.persistent_fid;
+       dclose->volatile_fid = cfile->fid.volatile_fid;
+       list_add_tail(&dclose->dlist, &CIFS_I(d_inode(cfile->dentry))->deferred_closes);
+}
+
+void
+cifs_del_deferred_close(struct cifsFileInfo *cfile)
+{
+       bool is_deferred = false;
+       struct cifs_deferred_close *dclose;
+
+       is_deferred = cifs_is_deferred_close(cfile, &dclose);
+       if (!is_deferred)
+               return;
+       list_del(&dclose->dlist);
+       kfree(dclose);
+}
+
+void
+cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
+{
+       struct cifsFileInfo *cfile = NULL;
+       struct cifs_deferred_close *dclose;
+
+       list_for_each_entry(cfile, &cifs_inode->openFileList, flist) {
+               spin_lock(&cifs_inode->deferred_lock);
+               if (cifs_is_deferred_close(cfile, &dclose))
+                       mod_delayed_work(deferredclose_wq, &cfile->deferred, 0);
+               spin_unlock(&cifs_inode->deferred_lock);
+       }
+}
+
 /* parses DFS refferal V3 structure
  * caller is responsible for freeing target_nodes
  * returns: