cifs: Fix flushing, invalidation and file size with FICLONE
[platform/kernel/linux-starfive.git] / fs / smb / client / cifsfs.c
index 22869cd..2131638 100644 (file)
@@ -1191,36 +1191,108 @@ const char *cifs_get_link(struct dentry *dentry, struct inode *inode,
 
 const struct inode_operations cifs_symlink_inode_ops = {
        .get_link = cifs_get_link,
+       .setattr = cifs_setattr,
        .permission = cifs_permission,
        .listxattr = cifs_listxattr,
 };
 
+/*
+ * Advance the EOF marker to after the source range.
+ */
+static int cifs_precopy_set_eof(struct inode *src_inode, struct cifsInodeInfo *src_cifsi,
+                               struct cifs_tcon *src_tcon,
+                               unsigned int xid, loff_t src_end)
+{
+       struct cifsFileInfo *writeable_srcfile;
+       int rc = -EINVAL;
+
+       writeable_srcfile = find_writable_file(src_cifsi, FIND_WR_FSUID_ONLY);
+       if (writeable_srcfile) {
+               if (src_tcon->ses->server->ops->set_file_size)
+                       rc = src_tcon->ses->server->ops->set_file_size(
+                               xid, src_tcon, writeable_srcfile,
+                               src_inode->i_size, true /* no need to set sparse */);
+               else
+                       rc = -ENOSYS;
+               cifsFileInfo_put(writeable_srcfile);
+               cifs_dbg(FYI, "SetFSize for copychunk rc = %d\n", rc);
+       }
+
+       if (rc < 0)
+               goto set_failed;
+
+       netfs_resize_file(&src_cifsi->netfs, src_end);
+       fscache_resize_cookie(cifs_inode_cookie(src_inode), src_end);
+       return 0;
+
+set_failed:
+       return filemap_write_and_wait(src_inode->i_mapping);
+}
+
+/*
+ * Flush out either the folio that overlaps the beginning of a range in which
+ * pos resides or the folio that overlaps the end of a range unless that folio
+ * is entirely within the range we're going to invalidate.  We extend the flush
+ * bounds to encompass the folio.
+ */
+static int cifs_flush_folio(struct inode *inode, loff_t pos, loff_t *_fstart, loff_t *_fend,
+                           bool first)
+{
+       struct folio *folio;
+       unsigned long long fpos, fend;
+       pgoff_t index = pos / PAGE_SIZE;
+       size_t size;
+       int rc = 0;
+
+       folio = filemap_get_folio(inode->i_mapping, index);
+       if (IS_ERR(folio))
+               return 0;
+
+       size = folio_size(folio);
+       fpos = folio_pos(folio);
+       fend = fpos + size - 1;
+       *_fstart = min_t(unsigned long long, *_fstart, fpos);
+       *_fend   = max_t(unsigned long long, *_fend, fend);
+       if ((first && pos == fpos) || (!first && pos == fend))
+               goto out;
+
+       rc = filemap_write_and_wait_range(inode->i_mapping, fpos, fend);
+out:
+       folio_put(folio);
+       return rc;
+}
+
 static loff_t cifs_remap_file_range(struct file *src_file, loff_t off,
                struct file *dst_file, loff_t destoff, loff_t len,
                unsigned int remap_flags)
 {
        struct inode *src_inode = file_inode(src_file);
        struct inode *target_inode = file_inode(dst_file);
+       struct cifsInodeInfo *src_cifsi = CIFS_I(src_inode);
+       struct cifsInodeInfo *target_cifsi = CIFS_I(target_inode);
        struct cifsFileInfo *smb_file_src = src_file->private_data;
-       struct cifsFileInfo *smb_file_target;
-       struct cifs_tcon *target_tcon;
+       struct cifsFileInfo *smb_file_target = dst_file->private_data;
+       struct cifs_tcon *target_tcon, *src_tcon;
+       unsigned long long destend, fstart, fend, new_size;
        unsigned int xid;
        int rc;
 
-       if (remap_flags & ~(REMAP_FILE_DEDUP | REMAP_FILE_ADVISORY))
+       if (remap_flags & REMAP_FILE_DEDUP)
+               return -EOPNOTSUPP;
+       if (remap_flags & ~REMAP_FILE_ADVISORY)
                return -EINVAL;
 
        cifs_dbg(FYI, "clone range\n");
 
        xid = get_xid();
 
-       if (!src_file->private_data || !dst_file->private_data) {
+       if (!smb_file_src || !smb_file_target) {
                rc = -EBADF;
                cifs_dbg(VFS, "missing cifsFileInfo on copy range src file\n");
                goto out;
        }
 
-       smb_file_target = dst_file->private_data;
+       src_tcon = tlink_tcon(smb_file_src->tlink);
        target_tcon = tlink_tcon(smb_file_target->tlink);
 
        /*
@@ -1233,20 +1305,63 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off,
        if (len == 0)
                len = src_inode->i_size - off;
 
-       cifs_dbg(FYI, "about to flush pages\n");
-       /* should we flush first and last page first */
-       truncate_inode_pages_range(&target_inode->i_data, destoff,
-                                  PAGE_ALIGN(destoff + len)-1);
+       cifs_dbg(FYI, "clone range\n");
+
+       /* Flush the source buffer */
+       rc = filemap_write_and_wait_range(src_inode->i_mapping, off,
+                                         off + len - 1);
+       if (rc)
+               goto unlock;
+
+       /* The server-side copy will fail if the source crosses the EOF marker.
+        * Advance the EOF marker after the flush above to the end of the range
+        * if it's short of that.
+        */
+       if (src_cifsi->netfs.remote_i_size < off + len) {
+               rc = cifs_precopy_set_eof(src_inode, src_cifsi, src_tcon, xid, off + len);
+               if (rc < 0)
+                       goto unlock;
+       }
+
+       new_size = destoff + len;
+       destend = destoff + len - 1;
+
+       /* Flush the folios at either end of the destination range to prevent
+        * accidental loss of dirty data outside of the range.
+        */
+       fstart = destoff;
+       fend = destend;
 
-       if (target_tcon->ses->server->ops->duplicate_extents)
+       rc = cifs_flush_folio(target_inode, destoff, &fstart, &fend, true);
+       if (rc)
+               goto unlock;
+       rc = cifs_flush_folio(target_inode, destend, &fstart, &fend, false);
+       if (rc)
+               goto unlock;
+
+       /* Discard all the folios that overlap the destination region. */
+       cifs_dbg(FYI, "about to discard pages %llx-%llx\n", fstart, fend);
+       truncate_inode_pages_range(&target_inode->i_data, fstart, fend);
+
+       fscache_invalidate(cifs_inode_cookie(target_inode), NULL,
+                          i_size_read(target_inode), 0);
+
+       rc = -EOPNOTSUPP;
+       if (target_tcon->ses->server->ops->duplicate_extents) {
                rc = target_tcon->ses->server->ops->duplicate_extents(xid,
                        smb_file_src, smb_file_target, off, len, destoff);
-       else
-               rc = -EOPNOTSUPP;
+               if (rc == 0 && new_size > i_size_read(target_inode)) {
+                       truncate_setsize(target_inode, new_size);
+                       netfs_resize_file(&target_cifsi->netfs, new_size);
+                       fscache_resize_cookie(cifs_inode_cookie(target_inode),
+                                             new_size);
+               }
+       }
 
        /* force revalidate of size and timestamps of target file now
           that target is updated on the server */
        CIFS_I(target_inode)->time = 0;
+unlock:
        /* although unlocking in the reverse order from locking is not
           strictly necessary here it is a little cleaner to be consistent */
        unlock_two_nondirectories(src_inode, target_inode);
@@ -1262,10 +1377,12 @@ ssize_t cifs_file_copychunk_range(unsigned int xid,
 {
        struct inode *src_inode = file_inode(src_file);
        struct inode *target_inode = file_inode(dst_file);
+       struct cifsInodeInfo *src_cifsi = CIFS_I(src_inode);
        struct cifsFileInfo *smb_file_src;
        struct cifsFileInfo *smb_file_target;
        struct cifs_tcon *src_tcon;
        struct cifs_tcon *target_tcon;
+       unsigned long long destend, fstart, fend;
        ssize_t rc;
 
        cifs_dbg(FYI, "copychunk range\n");
@@ -1305,13 +1422,41 @@ ssize_t cifs_file_copychunk_range(unsigned int xid,
        if (rc)
                goto unlock;
 
-       /* should we flush first and last page first */
-       truncate_inode_pages(&target_inode->i_data, 0);
+       /* The server-side copy will fail if the source crosses the EOF marker.
+        * Advance the EOF marker after the flush above to the end of the range
+        * if it's short of that.
+        */
+       if (src_cifsi->server_eof < off + len) {
+               rc = cifs_precopy_set_eof(src_inode, src_cifsi, src_tcon, xid, off + len);
+               if (rc < 0)
+                       goto unlock;
+       }
+
+       destend = destoff + len - 1;
+
+       /* Flush the folios at either end of the destination range to prevent
+        * accidental loss of dirty data outside of the range.
+        */
+       fstart = destoff;
+       fend = destend;
+
+       rc = cifs_flush_folio(target_inode, destoff, &fstart, &fend, true);
+       if (rc)
+               goto unlock;
+       rc = cifs_flush_folio(target_inode, destend, &fstart, &fend, false);
+       if (rc)
+               goto unlock;
+
+       /* Discard all the folios that overlap the destination region. */
+       truncate_inode_pages_range(&target_inode->i_data, fstart, fend);
 
        rc = file_modified(dst_file);
-       if (!rc)
+       if (!rc) {
                rc = target_tcon->ses->server->ops->copychunk_range(xid,
                        smb_file_src, smb_file_target, off, len, destoff);
+               if (rc > 0 && destoff + rc > i_size_read(target_inode))
+                       truncate_setsize(target_inode, destoff + rc);
+       }
 
        file_accessed(src_file);