}
 }
 
+int cifs_file_flush(const unsigned int xid, struct inode *inode,
+                   struct cifsFileInfo *cfile)
+{
+       struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+       struct cifs_tcon *tcon;
+       int rc;
+
+       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NOSSYNC)
+               return 0;
+
+       if (cfile && (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE)) {
+               tcon = tlink_tcon(cfile->tlink);
+               return tcon->ses->server->ops->flush(xid, tcon,
+                                                    &cfile->fid);
+       }
+       rc = cifs_get_writable_file(CIFS_I(inode), FIND_WR_ANY, &cfile);
+       if (!rc) {
+               tcon = tlink_tcon(cfile->tlink);
+               rc = tcon->ses->server->ops->flush(xid, tcon, &cfile->fid);
+               cifsFileInfo_put(cfile);
+       } else if (rc == -EBADF) {
+               rc = 0;
+       }
+       return rc;
+}
+
+static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry)
+{
+       struct cifsInodeInfo *cinode = CIFS_I(d_inode(dentry));
+       struct inode *inode = d_inode(dentry);
+       struct cifsFileInfo *cfile = NULL;
+       struct TCP_Server_Info *server;
+       struct cifs_tcon *tcon;
+       int rc;
+
+       rc = filemap_write_and_wait(inode->i_mapping);
+       if (is_interrupt_error(rc))
+               return -ERESTARTSYS;
+       mapping_set_error(inode->i_mapping, rc);
+
+       cfile = find_writable_file(cinode, FIND_WR_FSUID_ONLY);
+       rc = cifs_file_flush(xid, inode, cfile);
+       if (!rc) {
+               if (cfile) {
+                       tcon = tlink_tcon(cfile->tlink);
+                       server = tcon->ses->server;
+                       rc = server->ops->set_file_size(xid, tcon,
+                                                       cfile, 0, false);
+               }
+               if (!rc) {
+                       netfs_resize_file(&cinode->netfs, 0, true);
+                       cifs_setsize(inode, 0);
+                       inode->i_blocks = 0;
+               }
+       }
+       if (cfile)
+               cifsFileInfo_put(cfile);
+       return rc;
+}
+
 int cifs_open(struct inode *inode, struct file *file)
 
 {
                        file->f_op = &cifs_file_direct_ops;
        }
 
+       if (file->f_flags & O_TRUNC) {
+               rc = cifs_do_truncate(xid, file_dentry(file));
+               if (rc)
+                       goto out;
+       }
+
        /* Get the cached handle as SMB2 close is deferred */
        if (OPEN_FMODE(file->f_flags) & FMODE_WRITE) {
                rc = cifs_get_writable_path(tcon, full_path,
 int cifs_strict_fsync(struct file *file, loff_t start, loff_t end,
                      int datasync)
 {
-       unsigned int xid;
-       int rc = 0;
-       struct cifs_tcon *tcon;
-       struct TCP_Server_Info *server;
        struct cifsFileInfo *smbfile = file->private_data;
        struct inode *inode = file_inode(file);
-       struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+       unsigned int xid;
+       int rc = 0;
 
        rc = file_write_and_wait_range(file, start, end);
        if (rc) {
                }
        }
 
-       tcon = tlink_tcon(smbfile->tlink);
-       if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NOSSYNC)) {
-               server = tcon->ses->server;
-               if (server->ops->flush == NULL) {
-                       rc = -ENOSYS;
-                       goto strict_fsync_exit;
-               }
-
-               if ((OPEN_FMODE(smbfile->f_flags) & FMODE_WRITE) == 0) {
-                       smbfile = find_writable_file(CIFS_I(inode), FIND_WR_ANY);
-                       if (smbfile) {
-                               rc = server->ops->flush(xid, tcon, &smbfile->fid);
-                               cifsFileInfo_put(smbfile);
-                       } else
-                               cifs_dbg(FYI, "ignore fsync for file not open for write\n");
-               } else
-                       rc = server->ops->flush(xid, tcon, &smbfile->fid);
-       }
-
-strict_fsync_exit:
+       rc = cifs_file_flush(xid, inode, smbfile);
        free_xid(xid);
        return rc;
 }
 
 
 void cifs_setsize(struct inode *inode, loff_t offset)
 {
-       struct cifsInodeInfo *cifs_i = CIFS_I(inode);
-
        spin_lock(&inode->i_lock);
        i_size_write(inode, offset);
        spin_unlock(&inode->i_lock);
-
-       /* Cached inode must be refreshed on truncate */
-       cifs_i->time = 0;
+       inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
        truncate_pagecache(inode, offset);
 }
 
-static int
-cifs_set_file_size(struct inode *inode, struct iattr *attrs,
-                  unsigned int xid, const char *full_path, struct dentry *dentry)
+int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
+                      const char *full_path, struct cifsFileInfo *open_file,
+                      loff_t size)
 {
-       int rc;
-       struct cifsFileInfo *open_file;
-       struct cifsInodeInfo *cifsInode = CIFS_I(inode);
+       struct inode *inode = d_inode(dentry);
        struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+       struct cifsInodeInfo *cifsInode = CIFS_I(inode);
        struct tcon_link *tlink = NULL;
        struct cifs_tcon *tcon = NULL;
        struct TCP_Server_Info *server;
+       int rc = -EINVAL;
 
        /*
         * To avoid spurious oplock breaks from server, in the case of
         * writebehind data than the SMB timeout for the SetPathInfo
         * request would allow
         */
-       open_file = find_writable_file(cifsInode, FIND_WR_FSUID_ONLY);
-       if (open_file) {
+       if (open_file && (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE)) {
                tcon = tlink_tcon(open_file->tlink);
                server = tcon->ses->server;
-               if (server->ops->set_file_size)
-                       rc = server->ops->set_file_size(xid, tcon, open_file,
-                                                       attrs->ia_size, false);
-               else
-                       rc = -ENOSYS;
-               cifsFileInfo_put(open_file);
-               cifs_dbg(FYI, "SetFSize for attrs rc = %d\n", rc);
-       } else
-               rc = -EINVAL;
+               rc = server->ops->set_file_size(xid, tcon,
+                                               open_file,
+                                               size, false);
+               cifs_dbg(FYI, "%s: set_file_size: rc = %d\n", __func__, rc);
+       } else {
+               open_file = find_writable_file(cifsInode, FIND_WR_FSUID_ONLY);
+               if (open_file) {
+                       tcon = tlink_tcon(open_file->tlink);
+                       server = tcon->ses->server;
+                       rc = server->ops->set_file_size(xid, tcon,
+                                                       open_file,
+                                                       size, false);
+                       cifs_dbg(FYI, "%s: set_file_size: rc = %d\n", __func__, rc);
+                       cifsFileInfo_put(open_file);
+               }
+       }
 
        if (!rc)
                goto set_size_out;
         * valid, writeable file handle for it was found or because there was
         * an error setting it by handle.
         */
-       if (server->ops->set_path_size)
-               rc = server->ops->set_path_size(xid, tcon, full_path,
-                                               attrs->ia_size, cifs_sb, false, dentry);
-       else
-               rc = -ENOSYS;
-       cifs_dbg(FYI, "SetEOF by path (setattrs) rc = %d\n", rc);
-
-       if (tlink)
-               cifs_put_tlink(tlink);
+       rc = server->ops->set_path_size(xid, tcon, full_path, size,
+                                       cifs_sb, false, dentry);
+       cifs_dbg(FYI, "%s: SetEOF by path (setattrs) rc = %d\n", __func__, rc);
+       cifs_put_tlink(tlink);
 
 set_size_out:
        if (rc == 0) {
-               netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true);
-               cifs_setsize(inode, attrs->ia_size);
+               netfs_resize_file(&cifsInode->netfs, size, true);
+               cifs_setsize(inode, size);
                /*
                 * i_blocks is not related to (i_size / i_blksize), but instead
                 * 512 byte (2**9) size is required for calculating num blocks.
                 * this is best estimate we have for blocks allocated for a file
                 * Number of blocks must be rounded up so size 1 is not 0 blocks
                 */
-               inode->i_blocks = (512 - 1 + attrs->ia_size) >> 9;
-
-               /*
-                * The man page of truncate says if the size changed,
-                * then the st_ctime and st_mtime fields for the file
-                * are updated.
-                */
-               attrs->ia_ctime = attrs->ia_mtime = current_time(inode);
-               attrs->ia_valid |= ATTR_CTIME | ATTR_MTIME;
+               inode->i_blocks = (512 - 1 + size) >> 9;
        }
 
        return rc;
        struct tcon_link *tlink;
        struct cifs_tcon *pTcon;
        struct cifs_unix_set_info_args *args = NULL;
-       struct cifsFileInfo *open_file;
+       struct cifsFileInfo *open_file = NULL;
 
        cifs_dbg(FYI, "setattr_unix on file %pd attrs->ia_valid=0x%x\n",
                 direntry, attrs->ia_valid);
        if (rc < 0)
                goto out;
 
+       if (attrs->ia_valid & ATTR_FILE)
+               open_file = attrs->ia_file->private_data;
+
        full_path = build_path_from_dentry(direntry, page);
        if (IS_ERR(full_path)) {
                rc = PTR_ERR(full_path);
        rc = 0;
 
        if (attrs->ia_valid & ATTR_SIZE) {
-               rc = cifs_set_file_size(inode, attrs, xid, full_path, direntry);
+               rc = cifs_file_set_size(xid, direntry, full_path,
+                                       open_file, attrs->ia_size);
                if (rc != 0)
                        goto out;
+               /*
+                * The man page of truncate says if the size changed,
+                * then the st_ctime and st_mtime fields for the file
+                * are updated.
+                */
+               attrs->ia_ctime = attrs->ia_mtime = current_time(inode);
+               attrs->ia_valid |= ATTR_CTIME | ATTR_MTIME;
        }
 
        /* skip mode change if it's just for clearing setuid/setgid */
                args->ctime = NO_CHANGE_64;
 
        args->device = 0;
-       open_file = find_writable_file(cifsInode, FIND_WR_FSUID_ONLY);
-       if (open_file) {
-               u16 nfid = open_file->fid.netfid;
-               u32 npid = open_file->pid;
+       rc = -EINVAL;
+       if (open_file && (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE)) {
                pTcon = tlink_tcon(open_file->tlink);
-               rc = CIFSSMBUnixSetFileInfo(xid, pTcon, args, nfid, npid);
-               cifsFileInfo_put(open_file);
+               rc = CIFSSMBUnixSetFileInfo(xid, pTcon, args,
+                                           open_file->fid.netfid,
+                                           open_file->pid);
        } else {
+               open_file = find_writable_file(cifsInode, FIND_WR_FSUID_ONLY);
+               if (open_file) {
+                       pTcon = tlink_tcon(open_file->tlink);
+                       rc = CIFSSMBUnixSetFileInfo(xid, pTcon, args,
+                                                   open_file->fid.netfid,
+                                                   open_file->pid);
+                       cifsFileInfo_put(open_file);
+               }
+       }
+
+       if (rc) {
                tlink = cifs_sb_tlink(cifs_sb);
                if (IS_ERR(tlink)) {
                        rc = PTR_ERR(tlink);
                }
                pTcon = tlink_tcon(tlink);
                rc = CIFSSMBUnixSetPathInfo(xid, pTcon, full_path, args,
-                                   cifs_sb->local_nls,
-                                   cifs_remap(cifs_sb));
+                                           cifs_sb->local_nls,
+                                           cifs_remap(cifs_sb));
                cifs_put_tlink(tlink);
        }
 
        struct inode *inode = d_inode(direntry);
        struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
        struct cifsInodeInfo *cifsInode = CIFS_I(inode);
-       struct cifsFileInfo *wfile;
-       struct cifs_tcon *tcon;
+       struct cifsFileInfo *cfile = NULL;
        const char *full_path;
        void *page = alloc_dentry_path();
        int rc = -EACCES;
        if (rc < 0)
                goto cifs_setattr_exit;
 
+       if (attrs->ia_valid & ATTR_FILE)
+               cfile = attrs->ia_file->private_data;
+
        full_path = build_path_from_dentry(direntry, page);
        if (IS_ERR(full_path)) {
                rc = PTR_ERR(full_path);
 
        rc = 0;
 
-       if ((attrs->ia_valid & ATTR_MTIME) &&
-           !(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NOSSYNC)) {
-               rc = cifs_get_writable_file(cifsInode, FIND_WR_ANY, &wfile);
-               if (!rc) {
-                       tcon = tlink_tcon(wfile->tlink);
-                       rc = tcon->ses->server->ops->flush(xid, tcon, &wfile->fid);
-                       cifsFileInfo_put(wfile);
-                       if (rc)
-                               goto cifs_setattr_exit;
-               } else if (rc != -EBADF)
+       if (attrs->ia_valid & ATTR_MTIME) {
+               rc = cifs_file_flush(xid, inode, cfile);
+               if (rc)
                        goto cifs_setattr_exit;
-               else
-                       rc = 0;
        }
 
        if (attrs->ia_valid & ATTR_SIZE) {
-               rc = cifs_set_file_size(inode, attrs, xid, full_path, direntry);
+               rc = cifs_file_set_size(xid, direntry, full_path,
+                                       cfile, attrs->ia_size);
                if (rc != 0)
                        goto cifs_setattr_exit;
+               /*
+                * The man page of truncate says if the size changed,
+                * then the st_ctime and st_mtime fields for the file
+                * are updated.
+                */
+               attrs->ia_ctime = attrs->ia_mtime = current_time(inode);
+               attrs->ia_valid |= ATTR_CTIME | ATTR_MTIME;
        }
 
        if (attrs->ia_valid & ATTR_UID)
 
        if (unlikely(cifs_forced_shutdown(cifs_sb)))
                return -EIO;
+       /*
+        * Avoid setting [cm]time with O_TRUNC to prevent the server from
+        * disabling automatic timestamp updates as specified in
+        * MS-FSA 2.1.4.17.
+        */
+       if (attrs->ia_valid & ATTR_OPEN)
+               return 0;
 
        do {
 #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY