diff options
Diffstat (limited to 'fs/smb/client/inode.c')
-rw-r--r-- | fs/smb/client/inode.c | 260 |
1 files changed, 171 insertions, 89 deletions
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index fe453a4b3dc8..239dd84a336f 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -1931,7 +1931,7 @@ cifs_drop_nlink(struct inode *inode) * but will return the EACCES to the caller. Note that the VFS does not call * unlink on negative dentries currently. */ -int cifs_unlink(struct inode *dir, struct dentry *dentry) +static int __cifs_unlink(struct inode *dir, struct dentry *dentry, bool sillyrename) { int rc = 0; unsigned int xid; @@ -1984,7 +1984,7 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry) } netfs_wait_for_outstanding_io(inode); - cifs_close_deferred_file_under_dentry(tcon, full_path); + cifs_close_deferred_file_under_dentry(tcon, dentry); #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY if (cap_unix(tcon->ses) && (CIFS_UNIX_POSIX_PATH_OPS_CAP & le64_to_cpu(tcon->fsUnixInfo.Capability))) { @@ -2003,7 +2003,24 @@ retry_std_delete: goto psx_del_no_retry; } - rc = server->ops->unlink(xid, tcon, full_path, cifs_sb, dentry); + /* For SMB2+, if the file is open, we always perform a silly rename. + * + * We check for d_count() right after calling + * cifs_close_deferred_file_under_dentry() to make sure that the + * dentry's refcount gets dropped in case the file had any deferred + * close. + */ + if (!sillyrename && server->vals->protocol_id > SMB10_PROT_ID) { + spin_lock(&dentry->d_lock); + if (d_count(dentry) > 1) + sillyrename = true; + spin_unlock(&dentry->d_lock); + } + + if (sillyrename) + rc = -EBUSY; + else + rc = server->ops->unlink(xid, tcon, full_path, cifs_sb, dentry); psx_del_no_retry: if (!rc) { @@ -2071,6 +2088,11 @@ unlink_out: return rc; } +int cifs_unlink(struct inode *dir, struct dentry *dentry) +{ + return __cifs_unlink(dir, dentry, false); +} + static int cifs_mkdir_qinfo(struct inode *parent, struct dentry *dentry, umode_t mode, const char *full_path, struct cifs_sb_info *cifs_sb, @@ -2358,14 +2380,16 @@ int cifs_rmdir(struct inode *inode, struct dentry *direntry) rc = server->ops->rmdir(xid, tcon, full_path, cifs_sb); cifs_put_tlink(tlink); + cifsInode = CIFS_I(d_inode(direntry)); + if (!rc) { + set_bit(CIFS_INO_DELETE_PENDING, &cifsInode->flags); spin_lock(&d_inode(direntry)->i_lock); i_size_write(d_inode(direntry), 0); clear_nlink(d_inode(direntry)); spin_unlock(&d_inode(direntry)->i_lock); } - cifsInode = CIFS_I(d_inode(direntry)); /* force revalidate to go get info when needed */ cifsInode->time = 0; @@ -2458,8 +2482,11 @@ cifs_do_rename(const unsigned int xid, struct dentry *from_dentry, } #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ do_rename_exit: - if (rc == 0) + if (rc == 0) { d_move(from_dentry, to_dentry); + /* Force a new lookup */ + d_drop(from_dentry); + } cifs_put_tlink(tlink); return rc; } @@ -2470,6 +2497,7 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir, struct dentry *target_dentry, unsigned int flags) { const char *from_name, *to_name; + struct TCP_Server_Info *server; void *page1, *page2; struct cifs_sb_info *cifs_sb; struct tcon_link *tlink; @@ -2505,6 +2533,7 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir, if (IS_ERR(tlink)) return PTR_ERR(tlink); tcon = tlink_tcon(tlink); + server = tcon->ses->server; page1 = alloc_dentry_path(); page2 = alloc_dentry_path(); @@ -2522,10 +2551,10 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir, goto cifs_rename_exit; } - cifs_close_deferred_file_under_dentry(tcon, from_name); + cifs_close_deferred_file_under_dentry(tcon, source_dentry); if (d_inode(target_dentry) != NULL) { netfs_wait_for_outstanding_io(d_inode(target_dentry)); - cifs_close_deferred_file_under_dentry(tcon, to_name); + cifs_close_deferred_file_under_dentry(tcon, target_dentry); } rc = cifs_do_rename(xid, source_dentry, from_name, target_dentry, @@ -2591,19 +2620,53 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir, unlink_target: #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ - - /* Try unlinking the target dentry if it's not negative */ - if (d_really_is_positive(target_dentry) && (rc == -EACCES || rc == -EEXIST)) { - if (d_is_dir(target_dentry)) - tmprc = cifs_rmdir(target_dir, target_dentry); - else - tmprc = cifs_unlink(target_dir, target_dentry); - if (tmprc) - goto cifs_rename_exit; - rc = cifs_do_rename(xid, source_dentry, from_name, - target_dentry, to_name); - if (!rc) - rehash = false; + if (d_really_is_positive(target_dentry)) { + if (!rc) { + struct inode *inode = d_inode(target_dentry); + /* + * Samba and ksmbd servers allow renaming a target + * directory that is open, so make sure to update + * ->i_nlink and then mark it as delete pending. + */ + if (S_ISDIR(inode->i_mode)) { + drop_cached_dir_by_name(xid, tcon, to_name, cifs_sb); + spin_lock(&inode->i_lock); + i_size_write(inode, 0); + clear_nlink(inode); + spin_unlock(&inode->i_lock); + set_bit(CIFS_INO_DELETE_PENDING, &CIFS_I(inode)->flags); + CIFS_I(inode)->time = 0; /* force reval */ + inode_set_ctime_current(inode); + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + } + } else if (rc == -EACCES || rc == -EEXIST) { + /* + * Rename failed, possibly due to a busy target. + * Retry it by unliking the target first. + */ + if (d_is_dir(target_dentry)) { + tmprc = cifs_rmdir(target_dir, target_dentry); + } else { + tmprc = __cifs_unlink(target_dir, target_dentry, + server->vals->protocol_id > SMB10_PROT_ID); + } + if (tmprc) { + /* + * Some servers will return STATUS_ACCESS_DENIED + * or STATUS_DIRECTORY_NOT_EMPTY when failing to + * rename a non-empty directory. Make sure to + * propagate the appropriate error back to + * userspace. + */ + if (tmprc == -EEXIST || tmprc == -ENOTEMPTY) + rc = tmprc; + goto cifs_rename_exit; + } + rc = cifs_do_rename(xid, source_dentry, from_name, + target_dentry, to_name); + if (!rc) + rehash = false; + } } /* force revalidate to go get info when needed */ @@ -2629,6 +2692,8 @@ cifs_dentry_needs_reval(struct dentry *dentry) struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); struct cached_fid *cfid = NULL; + if (test_bit(CIFS_INO_DELETE_PENDING, &cifs_i->flags)) + return false; if (cifs_i->time == 0) return true; @@ -2639,7 +2704,7 @@ cifs_dentry_needs_reval(struct dentry *dentry) return true; if (!open_cached_dir_by_dentry(tcon, dentry->d_parent, &cfid)) { - if (cfid->time && cifs_i->time > cfid->time) { + if (cifs_i->time > cfid->time) { close_cached_dir(cfid); return false; } @@ -2779,7 +2844,7 @@ int cifs_revalidate_dentry_attr(struct dentry *dentry) } cifs_dbg(FYI, "Update attributes: %s inode 0x%p count %d dentry: 0x%p d_time %ld jiffies %ld\n", - full_path, inode, inode->i_count.counter, + full_path, inode, icount_read(inode), dentry, cifs_get_time(dentry), jiffies); again: @@ -2942,28 +3007,25 @@ int cifs_fiemap(struct inode *inode, struct fiemap_extent_info *fei, u64 start, 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); + netfs_wait_for_outstanding_io(inode); } -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 @@ -2974,19 +3036,25 @@ cifs_set_file_size(struct inode *inode, struct iattr *attrs, * 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; @@ -3004,20 +3072,15 @@ cifs_set_file_size(struct inode *inode, struct iattr *attrs, * 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. @@ -3025,15 +3088,7 @@ set_size_out: * 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; @@ -3053,7 +3108,7 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) 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); @@ -3067,6 +3122,9 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) 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); @@ -3094,9 +3152,16 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) 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; + /* + * Avoid setting timestamps on the server for ftruncate(2) to + * prevent it from disabling automatic timestamp updates as per + * MS-FSA 2.1.4.17. + */ + attrs->ia_valid &= ~(ATTR_CTIME | ATTR_MTIME); } /* skip mode change if it's just for clearing setuid/setgid */ @@ -3141,14 +3206,24 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) 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); @@ -3156,8 +3231,8 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) } 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); } @@ -3199,8 +3274,7 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) 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; @@ -3220,6 +3294,9 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) 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); @@ -3246,25 +3323,23 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) 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; + /* + * Avoid setting timestamps on the server for ftruncate(2) to + * prevent it from disabling automatic timestamp updates as per + * MS-FSA 2.1.4.17. + */ + attrs->ia_valid &= ~(ATTR_CTIME | ATTR_MTIME); } if (attrs->ia_valid & ATTR_UID) @@ -3394,6 +3469,13 @@ cifs_setattr(struct mnt_idmap *idmap, struct dentry *direntry, 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 |