diff options
Diffstat (limited to 'fs/smb/client/dir.c')
| -rw-r--r-- | fs/smb/client/dir.c | 514 |
1 files changed, 407 insertions, 107 deletions
diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index d1e95632ac54..e4295a5b55b3 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -14,7 +14,6 @@ #include <linux/mount.h> #include <linux/file.h> #include "cifsfs.h" -#include "cifspdu.h" #include "cifsglob.h" #include "cifsproto.h" #include "cifs_debug.h" @@ -23,6 +22,7 @@ #include "fs_context.h" #include "cifs_ioctl.h" #include "fscache.h" +#include "cached_dir.h" static void renew_parental_timestamps(struct dentry *direntry) @@ -82,10 +82,11 @@ char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *pa const char *tree, int tree_len, bool prefix) { - int dfsplen; - int pplen = 0; - struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb); + struct cifs_sb_info *cifs_sb = CIFS_SB(direntry); + unsigned int sbflags = cifs_sb_flags(cifs_sb); char dirsep = CIFS_DIR_SEP(cifs_sb); + int pplen = 0; + int dfsplen; char *s; if (unlikely(!page)) @@ -96,7 +97,7 @@ char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *pa else dfsplen = 0; - if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_USE_PREFIX_PATH) + if (sbflags & CIFS_MOUNT_USE_PREFIX_PATH) pplen = cifs_sb->prepath ? strlen(cifs_sb->prepath) + 1 : 0; s = dentry_path_raw(direntry, page, PATH_MAX); @@ -123,7 +124,7 @@ char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *pa if (dfsplen) { s -= dfsplen; memcpy(s, tree, dfsplen); - if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) { + if (sbflags & CIFS_MOUNT_POSIX_PATHS) { int i; for (i = 0; i < dfsplen; i++) { if (s[i] == '\\') @@ -152,7 +153,7 @@ char *build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page static int check_name(struct dentry *direntry, struct cifs_tcon *tcon) { - struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb); + struct cifs_sb_info *cifs_sb = CIFS_SB(direntry); int i; if (unlikely(tcon->fsAttrInfo.MaxPathNameComponentLength && @@ -160,7 +161,7 @@ check_name(struct dentry *direntry, struct cifs_tcon *tcon) le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength))) return -ENAMETOOLONG; - if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS)) { + if (!(cifs_sb_flags(cifs_sb) & CIFS_MOUNT_POSIX_PATHS)) { for (i = 0; i < direntry->d_name.len; i++) { if (direntry->d_name.name[i] == '\\') { cifs_dbg(FYI, "Invalid file name\n"); @@ -171,45 +172,67 @@ check_name(struct dentry *direntry, struct cifs_tcon *tcon) return 0; } +static char *alloc_parent_path(struct dentry *dentry, size_t namelen) +{ + struct cifs_sb_info *cifs_sb = CIFS_SB(dentry); + void *page = alloc_dentry_path(); + const char *path; + size_t size; + char *npath; -/* Inode operations in similar order to how they appear in Linux file fs.h */ + path = build_path_from_dentry(dentry->d_parent, page); + if (IS_ERR(path)) { + npath = ERR_CAST(path); + goto out; + } + + size = strlen(path) + namelen + 2; + npath = kmalloc(size, GFP_KERNEL); + if (!npath) + npath = ERR_PTR(-ENOMEM); + else + scnprintf(npath, size, "%s%c", path, CIFS_DIR_SEP(cifs_sb)); +out: + free_dentry_path(page); + return npath; +} -static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned int xid, - struct tcon_link *tlink, unsigned int oflags, umode_t mode, __u32 *oplock, - struct cifs_fid *fid, struct cifs_open_info_data *buf) +/* Inode operations in similar order to how they appear in Linux file fs.h */ +static int __cifs_do_create(struct inode *dir, struct dentry *direntry, + const char *full_path, unsigned int xid, + struct tcon_link *tlink, unsigned int oflags, + umode_t mode, __u32 *oplock, struct cifs_fid *fid, + struct cifs_open_info_data *buf, + struct inode **inode) { int rc = -ENOENT; int create_options = CREATE_NOT_DIR; int desired_access; - struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); + struct cifs_sb_info *cifs_sb = CIFS_SB(dir); struct cifs_tcon *tcon = tlink_tcon(tlink); - const char *full_path; - void *page = alloc_dentry_path(); struct inode *newinode = NULL; + unsigned int sbflags = cifs_sb_flags(cifs_sb); int disposition; struct TCP_Server_Info *server = tcon->ses->server; struct cifs_open_parms oparms; + struct cached_fid *parent_cfid = NULL; int rdwr_for_fscache = 0; + __le32 lease_flags = 0; + *inode = NULL; *oplock = 0; if (tcon->ses->server->oplocks) *oplock = REQ_OPLOCK; - full_path = build_path_from_dentry(direntry, page); - if (IS_ERR(full_path)) { - free_dentry_path(page); - return PTR_ERR(full_path); - } - /* If we're caching, we need to be able to fill in around partial writes. */ - if (cifs_fscache_enabled(inode) && (oflags & O_ACCMODE) == O_WRONLY) + if (cifs_fscache_enabled(dir) && (oflags & O_ACCMODE) == O_WRONLY) rdwr_for_fscache = 1; #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY if (tcon->unix_ext && cap_unix(tcon->ses) && !tcon->broken_posix_open && (CIFS_UNIX_POSIX_PATH_OPS_CAP & le64_to_cpu(tcon->fsUnixInfo.Capability))) { - rc = cifs_posix_open(full_path, &newinode, inode->i_sb, mode, + rc = cifs_posix_open(full_path, &newinode, dir->i_sb, mode, oflags, oplock, &fid->netfid, xid); switch (rc) { case 0: @@ -221,8 +244,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned if (S_ISDIR(newinode->i_mode)) { CIFSSMBClose(xid, tcon, fid->netfid); iput(newinode); - rc = -EISDIR; - goto out; + return -EISDIR; } if (!S_ISREG(newinode->i_mode)) { @@ -265,7 +287,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned break; default: - goto out; + return rc; } /* * fallthrough to retry, using older open call, this is case @@ -283,27 +305,32 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned desired_access |= GENERIC_WRITE; if (rdwr_for_fscache == 1) desired_access |= GENERIC_READ; + if (oflags & O_TMPFILE) + desired_access |= DELETE; disposition = FILE_OVERWRITE_IF; - if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) + if (oflags & O_CREAT) { + if (oflags & O_EXCL) + disposition = FILE_CREATE; + else if (oflags & O_TRUNC) + disposition = FILE_OVERWRITE_IF; + else + disposition = FILE_OPEN_IF; + } else if (oflags & O_TMPFILE) { disposition = FILE_CREATE; - else if ((oflags & (O_CREAT | O_TRUNC)) == (O_CREAT | O_TRUNC)) - disposition = FILE_OVERWRITE_IF; - else if ((oflags & O_CREAT) == O_CREAT) - disposition = FILE_OPEN_IF; - else + } else { cifs_dbg(FYI, "Create flag not set in create function\n"); + } /* * BB add processing to set equivalent of mode - e.g. via CreateX with * ACLs */ - if (!server->ops->open) { - rc = -ENOSYS; - goto out; - } + if (!server->ops->open) + return -EOPNOTSUPP; + create_options |= cifs_open_create_options(oflags, create_options); /* * if we're not using unix extensions, see if we need to set * ATTR_READONLY on the create call @@ -311,7 +338,29 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned if (!tcon->unix_ext && (mode & S_IWUGO) == 0) create_options |= CREATE_OPTION_READONLY; + retry_open: + if (tcon->cfids && direntry->d_parent && server->dialect >= SMB30_PROT_ID) { + parent_cfid = NULL; + spin_lock(&tcon->cfids->cfid_list_lock); + list_for_each_entry(parent_cfid, &tcon->cfids->entries, entry) { + if (parent_cfid->dentry == direntry->d_parent) { + cifs_dbg(FYI, "found a parent cached file handle\n"); + if (is_valid_cached_dir(parent_cfid)) { + lease_flags + |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE; + memcpy(fid->parent_lease_key, + parent_cfid->fid.lease_key, + SMB2_LEASE_KEY_SIZE); + parent_cfid->dirents.is_valid = false; + parent_cfid->dirents.is_failed = true; + } + break; + } + } + spin_unlock(&tcon->cfids->cfid_list_lock); + } + oparms = (struct cifs_open_parms) { .tcon = tcon, .cifs_sb = cifs_sb, @@ -320,6 +369,7 @@ retry_open: .disposition = disposition, .path = full_path, .fid = fid, + .lease_flags = lease_flags, .mode = mode, }; rc = server->ops->open(xid, &oparms, oplock, buf); @@ -330,10 +380,10 @@ retry_open: rdwr_for_fscache = 2; goto retry_open; } - goto out; + return rc; } if (rdwr_for_fscache == 2) - cifs_invalidate_cache(inode, FSCACHE_INVAL_DIO_WRITE); + cifs_invalidate_cache(dir, FSCACHE_INVAL_DIO_WRITE); #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY /* @@ -349,10 +399,10 @@ retry_open: .device = 0, }; - if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID) { + if (sbflags & CIFS_MOUNT_SET_UID) { args.uid = current_fsuid(); - if (inode->i_mode & S_ISGID) - args.gid = inode->i_gid; + if (dir->i_mode & S_ISGID) + args.gid = dir->i_gid; else args.gid = current_fsgid(); } else { @@ -374,24 +424,24 @@ retry_open: cifs_create_get_file_info: /* server might mask mode so we have to query for it */ if (tcon->unix_ext) - rc = cifs_get_inode_info_unix(&newinode, full_path, inode->i_sb, + rc = cifs_get_inode_info_unix(&newinode, full_path, dir->i_sb, xid); else { #else { #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ /* TODO: Add support for calling POSIX query info here, but passing in fid */ - rc = cifs_get_inode_info(&newinode, full_path, buf, inode->i_sb, xid, fid); + rc = cifs_get_inode_info(&newinode, full_path, buf, dir->i_sb, xid, fid); if (newinode) { if (server->ops->set_lease_key) server->ops->set_lease_key(newinode, fid); if ((*oplock & CIFS_CREATE_ACTION) && S_ISREG(newinode->i_mode)) { - if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_DYNPERM) + if (sbflags & CIFS_MOUNT_DYNPERM) newinode->i_mode = mode; - if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID) { + if (sbflags & CIFS_MOUNT_SET_UID) { newinode->i_uid = current_fsuid(); - if (inode->i_mode & S_ISGID) - newinode->i_gid = inode->i_gid; + if (dir->i_mode & S_ISGID) + newinode->i_gid = dir->i_gid; else newinode->i_gid = current_fsgid(); } @@ -408,17 +458,12 @@ cifs_create_set_dentry: goto out_err; } - if (newinode) - if (S_ISDIR(newinode->i_mode)) { - rc = -EISDIR; - goto out_err; - } - - d_drop(direntry); - d_add(direntry, newinode); + if (newinode && S_ISDIR(newinode->i_mode)) { + rc = -EISDIR; + goto out_err; + } -out: - free_dentry_path(page); + *inode = newinode; return rc; out_err: @@ -426,26 +471,60 @@ out_err: server->ops->close(xid, tcon, fid); if (newinode) iput(newinode); - goto out; + return rc; } -int -cifs_atomic_open(struct inode *inode, struct dentry *direntry, - struct file *file, unsigned oflags, umode_t mode) +static int cifs_do_create(struct inode *dir, struct dentry *direntry, + unsigned int xid, struct tcon_link *tlink, + unsigned int oflags, umode_t mode, + __u32 *oplock, struct cifs_fid *fid, + struct cifs_open_info_data *buf, + struct inode **inode) { + void *page = alloc_dentry_path(); + const char *full_path; int rc; - unsigned int xid; - struct tcon_link *tlink; - struct cifs_tcon *tcon; + + full_path = build_path_from_dentry(direntry, page); + if (IS_ERR(full_path)) { + rc = PTR_ERR(full_path); + } else { + rc = __cifs_do_create(dir, direntry, full_path, xid, + tlink, oflags, mode, oplock, + fid, buf, inode); + } + free_dentry_path(page); + return rc; +} + + +/* + * Look up, create and open a CIFS file. + * + * The initial dentry state is in-lookup or hashed-negative. On success, dentry + * will become hashed-positive by calling d_splice_alias() if in-lookup, + * otherwise d_instantiate(). + */ +int cifs_atomic_open(struct inode *dir, struct dentry *direntry, + struct file *file, unsigned int oflags, umode_t mode) +{ + struct cifs_sb_info *cifs_sb = CIFS_SB(dir); + struct cifs_open_info_data buf = {}; struct TCP_Server_Info *server; - struct cifs_fid fid = {}; + struct cifsFileInfo *file_info; struct cifs_pending_open open; + struct cifs_fid fid = {}; + struct tcon_link *tlink; + struct cifs_tcon *tcon; + unsigned int sbflags; + struct dentry *alias; + struct inode *inode; + unsigned int xid; __u32 oplock; - struct cifsFileInfo *file_info; - struct cifs_open_info_data buf = {}; + int rc; - if (unlikely(cifs_forced_shutdown(CIFS_SB(inode->i_sb)))) - return -EIO; + if (unlikely(cifs_forced_shutdown(cifs_sb))) + return smb_EIO(smb_eio_trace_forced_shutdown); /* * Posix open is only called (at lookup time) for file create now. For @@ -459,8 +538,6 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry, * in network traffic in the other paths. */ if (!(oflags & O_CREAT)) { - struct dentry *res; - /* * Check for hashed negative dentry. We have already revalidated * the dentry and it is fine. No need to perform another lookup. @@ -468,19 +545,15 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry, if (!d_in_lookup(direntry)) return -ENOENT; - res = cifs_lookup(inode, direntry, 0); - if (IS_ERR(res)) - return PTR_ERR(res); - - return finish_no_open(file, res); + return finish_no_open(file, cifs_lookup(dir, direntry, 0)); } xid = get_xid(); cifs_dbg(FYI, "parent inode = 0x%p name is: %pd and dentry = 0x%p\n", - inode, direntry, direntry); + dir, direntry, direntry); - tlink = cifs_sb_tlink(CIFS_SB(inode->i_sb)); + tlink = cifs_sb_tlink(cifs_sb); if (IS_ERR(tlink)) { rc = PTR_ERR(tlink); goto out_free_xid; @@ -499,13 +572,21 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry, cifs_add_pending_open(&fid, tlink, &open); - rc = cifs_do_create(inode, direntry, xid, tlink, oflags, mode, - &oplock, &fid, &buf); + rc = cifs_do_create(dir, direntry, xid, tlink, oflags, mode, + &oplock, &fid, &buf, &inode); if (rc) { cifs_del_pending_open(&open); goto out; } + if (d_in_lookup(direntry)) { + alias = d_splice_alias(inode, direntry); + if (!IS_ERR_OR_NULL(alias)) + direntry = alias; + } else { + d_instantiate(direntry, inode); + } + if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) file->f_mode |= FMODE_CREATED; @@ -517,13 +598,13 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry, goto out; } - if (file->f_flags & O_DIRECT && - CIFS_SB(inode->i_sb)->mnt_cifs_flags & CIFS_MOUNT_STRICT_IO) { - if (CIFS_SB(inode->i_sb)->mnt_cifs_flags & CIFS_MOUNT_NO_BRL) + sbflags = cifs_sb_flags(cifs_sb); + if ((file->f_flags & O_DIRECT) && (sbflags & CIFS_MOUNT_STRICT_IO)) { + if (sbflags & CIFS_MOUNT_NO_BRL) file->f_op = &cifs_file_direct_nobrl_ops; else file->f_op = &cifs_file_direct_ops; - } + } file_info = cifs_new_fileinfo(&fid, file, tlink, oplock, buf.symlink_target); if (file_info == NULL) { @@ -545,9 +626,16 @@ out_free_xid: return rc; } -int cifs_create(struct mnt_idmap *idmap, struct inode *inode, +/* + * Create a CIFS file. + * + * The initial dentry state is hashed-negative. On success, dentry will become + * hashed-positive by calling d_instantiate(). + */ +int cifs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *direntry, umode_t mode, bool excl) { + struct cifs_sb_info *cifs_sb = CIFS_SB(dir); int rc; unsigned int xid = get_xid(); /* @@ -561,19 +649,20 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *inode, struct tcon_link *tlink; struct cifs_tcon *tcon; struct TCP_Server_Info *server; + struct inode *inode; struct cifs_fid fid; __u32 oplock; struct cifs_open_info_data buf = {}; cifs_dbg(FYI, "cifs_create parent inode = 0x%p name is: %pd and dentry = 0x%p\n", - inode, direntry, direntry); + dir, direntry, direntry); - if (unlikely(cifs_forced_shutdown(CIFS_SB(inode->i_sb)))) { - rc = -EIO; + if (unlikely(cifs_forced_shutdown(cifs_sb))) { + rc = smb_EIO(smb_eio_trace_forced_shutdown); goto out_free_xid; } - tlink = cifs_sb_tlink(CIFS_SB(inode->i_sb)); + tlink = cifs_sb_tlink(cifs_sb); rc = PTR_ERR(tlink); if (IS_ERR(tlink)) goto out_free_xid; @@ -584,9 +673,13 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *inode, if (server->ops->new_lease_key) server->ops->new_lease_key(&fid); - rc = cifs_do_create(inode, direntry, xid, tlink, oflags, mode, &oplock, &fid, &buf); - if (!rc && server->ops->close) - server->ops->close(xid, tcon, &fid); + rc = cifs_do_create(dir, direntry, xid, tlink, oflags, + mode, &oplock, &fid, &buf, &inode); + if (!rc) { + d_instantiate(direntry, inode); + if (server->ops->close) + server->ops->close(xid, tcon, &fid); + } cifs_free_open_info(&buf); cifs_put_tlink(tlink); @@ -611,7 +704,7 @@ int cifs_mknod(struct mnt_idmap *idmap, struct inode *inode, cifs_sb = CIFS_SB(inode->i_sb); if (unlikely(cifs_forced_shutdown(cifs_sb))) - return -EIO; + return smb_EIO(smb_eio_trace_forced_shutdown); tlink = cifs_sb_tlink(cifs_sb); if (IS_ERR(tlink)) @@ -658,6 +751,7 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, const char *full_path; void *page; int retry_count = 0; + struct dentry *de; xid = get_xid(); @@ -669,16 +763,15 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, cifs_sb = CIFS_SB(parent_dir_inode->i_sb); tlink = cifs_sb_tlink(cifs_sb); if (IS_ERR(tlink)) { - free_xid(xid); - return ERR_CAST(tlink); + de = ERR_CAST(tlink); + goto free_xid; } pTcon = tlink_tcon(tlink); rc = check_name(direntry, pTcon); if (unlikely(rc)) { - cifs_put_tlink(tlink); - free_xid(xid); - return ERR_PTR(rc); + de = ERR_PTR(rc); + goto put_tlink; } /* can not grab the rename sem here since it would @@ -687,16 +780,38 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, page = alloc_dentry_path(); full_path = build_path_from_dentry(direntry, page); if (IS_ERR(full_path)) { - cifs_put_tlink(tlink); - free_xid(xid); - free_dentry_path(page); - return ERR_CAST(full_path); + de = ERR_CAST(full_path); + goto free_dentry_path; } if (d_really_is_positive(direntry)) { cifs_dbg(FYI, "non-NULL inode in lookup\n"); } else { + struct cached_fid *cfid = NULL; + cifs_dbg(FYI, "NULL inode in lookup\n"); + + /* + * We can only rely on negative dentries having the same + * spelling as the cached dirent if case insensitivity is + * forced on mount. + * + * XXX: if servers correctly announce Case Sensitivity Search + * on GetInfo of FileFSAttributeInformation, then we can take + * correct action even if case insensitive is not forced on + * mount. + */ + if (pTcon->nocase && !open_cached_dir_by_dentry(pTcon, direntry->d_parent, &cfid)) { + /* + * dentry is negative and parent is fully cached: + * we can assume file does not exist + */ + if (cfid->dirents.is_valid) { + close_cached_dir(cfid); + goto out; + } + close_cached_dir(cfid); + } } cifs_dbg(FYI, "Full path: %s inode = 0x%p\n", full_path, d_inode(direntry)); @@ -730,24 +845,29 @@ again: } newInode = ERR_PTR(rc); } + +out: + de = d_splice_alias(newInode, direntry); +free_dentry_path: free_dentry_path(page); +put_tlink: cifs_put_tlink(tlink); +free_xid: free_xid(xid); - return d_splice_alias(newInode, direntry); + return de; } static int cifs_d_revalidate(struct inode *dir, const struct qstr *name, struct dentry *direntry, unsigned int flags) { - struct inode *inode; - int rc; - if (flags & LOOKUP_RCU) return -ECHILD; if (d_really_is_positive(direntry)) { - inode = d_inode(direntry); + int rc; + struct inode *inode = d_inode(direntry); + if ((flags & LOOKUP_REVAL) && !CIFS_CACHE_READ(CIFS_I(inode))) CIFS_I(inode)->time = 0; /* force reval */ @@ -787,6 +907,22 @@ cifs_d_revalidate(struct inode *dir, const struct qstr *name, return 1; } + } else { + struct cifs_sb_info *cifs_sb = CIFS_SB(dir->i_sb); + struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); + struct cached_fid *cfid; + + if (!open_cached_dir_by_dentry(tcon, direntry->d_parent, &cfid)) { + /* + * dentry is negative and parent is fully cached: + * we can assume file does not exist + */ + if (cfid->dirents.is_valid) { + close_cached_dir(cfid); + return 1; + } + close_cached_dir(cfid); + } } /* @@ -892,6 +1028,170 @@ static int cifs_ci_compare(const struct dentry *dentry, return 0; } +static int set_tmpfile_attr(const unsigned int xid, unsigned int oflags, + struct inode *inode, const char *full_path, + struct TCP_Server_Info *server) +{ + struct cifsInodeInfo *cinode = CIFS_I(inode); + FILE_BASIC_INFO fi; + + cinode->cifsAttrs |= ATTR_HIDDEN; + if (oflags & O_EXCL) + cinode->cifsAttrs |= ATTR_TEMPORARY; + + fi = (FILE_BASIC_INFO) { + .Attributes = cpu_to_le32(cinode->cifsAttrs), + }; + return server->ops->set_file_info(inode, full_path, &fi, xid); +} + +/* + * Create a hidden temporary CIFS file with delete-on-close bit set. + * + * The initial dentry state is unhashed-negative. On success, dentry will + * become unhashed-positive by calling d_instantiate(). + */ +int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, + struct file *file, umode_t mode) +{ + struct dentry *dentry = file->f_path.dentry; + struct cifs_sb_info *cifs_sb = CIFS_SB(dir); + size_t namesize = CIFS_TMPNAME_LEN + 1; + char *path __free(kfree) = NULL, *name; + unsigned int oflags = file->f_flags; + int retries = 0, max_retries = 16; + struct TCP_Server_Info *server; + struct cifs_pending_open open; + struct cifsFileInfo *cfile; + struct cifs_fid fid = {}; + struct tcon_link *tlink; + struct cifs_tcon *tcon; + unsigned int sbflags; + struct inode *inode; + unsigned int xid; + __u32 oplock; + int namelen; + int rc; + + if (unlikely(cifs_forced_shutdown(cifs_sb))) + return smb_EIO(smb_eio_trace_forced_shutdown); + + tlink = cifs_sb_tlink(cifs_sb); + if (IS_ERR(tlink)) + return PTR_ERR(tlink); + tcon = tlink_tcon(tlink); + server = tcon->ses->server; + + xid = get_xid(); + + if (server->vals->protocol_id < SMB20_PROT_ID) { + cifs_dbg(VFS | ONCE, "O_TMPFILE is supported only in SMB2+\n"); + rc = -EOPNOTSUPP; + goto out; + } + + if (server->ops->new_lease_key) + server->ops->new_lease_key(&fid); + cifs_add_pending_open(&fid, tlink, &open); + + path = alloc_parent_path(dentry, namesize - 1); + if (IS_ERR(path)) { + cifs_del_pending_open(&open); + rc = PTR_ERR(path); + path = NULL; + goto out; + } + + name = path + strlen(path); + do { + /* Append tmpfile name to @path */ + namelen = scnprintf(name, namesize, CIFS_TMPNAME_PREFIX "%x", + atomic_inc_return(&cifs_tmpcounter)); + rc = __cifs_do_create(dir, dentry, path, xid, tlink, oflags, + mode, &oplock, &fid, NULL, &inode); + if (!rc) { + rc = d_mark_tmpfile_name(file, &QSTR_LEN(name, namelen)); + if (rc) { + cifs_dbg(VFS | ONCE, "%s: failed to set filename in dentry: %d\n", + __func__, rc); + rc = -EISDIR; + iput(inode); + goto err_open; + } + set_nlink(inode, 0); + mark_inode_dirty(inode); + d_instantiate(dentry, inode); + break; + } + } while (unlikely(rc == -EEXIST) && ++retries < max_retries); + + if (rc) { + cifs_del_pending_open(&open); + goto out; + } + + rc = finish_open(file, dentry, generic_file_open); + if (rc) + goto err_open; + + sbflags = cifs_sb_flags(cifs_sb); + if ((file->f_flags & O_DIRECT) && (sbflags & CIFS_MOUNT_STRICT_IO)) { + if (sbflags & CIFS_MOUNT_NO_BRL) + file->f_op = &cifs_file_direct_nobrl_ops; + else + file->f_op = &cifs_file_direct_ops; + } + + cfile = cifs_new_fileinfo(&fid, file, tlink, oplock, NULL); + if (!cfile) { + rc = -ENOMEM; + goto err_open; + } + + rc = set_tmpfile_attr(xid, oflags, inode, path, server); + if (rc) + goto out; + + fscache_use_cookie(cifs_inode_cookie(file_inode(file)), + file->f_mode & FMODE_WRITE); +out: + cifs_put_tlink(tlink); + free_xid(xid); + return rc; +err_open: + cifs_del_pending_open(&open); + if (server->ops->close) + server->ops->close(xid, tcon, &fid); + goto out; +} + +char *cifs_silly_fullpath(struct dentry *dentry) +{ + unsigned char name[CIFS_SILLYNAME_LEN + 1]; + int retries = 0, max_retries = 16; + size_t namesize = sizeof(name); + struct dentry *sdentry = NULL; + char *path; + + do { + dput(sdentry); + scnprintf(name, namesize, CIFS_SILLYNAME_PREFIX "%x", + atomic_inc_return(&cifs_sillycounter)); + sdentry = lookup_noperm(&QSTR(name), dentry->d_parent); + if (IS_ERR(sdentry)) + return ERR_CAST(sdentry); + if (d_is_negative(sdentry)) { + dput(sdentry); + path = alloc_parent_path(dentry, CIFS_SILLYNAME_LEN); + if (!IS_ERR(path)) + strcat(path, name); + return path; + } + } while (++retries < max_retries); + dput(sdentry); + return ERR_PTR(-EBUSY); +} + const struct dentry_operations cifs_ci_dentry_ops = { .d_revalidate = cifs_d_revalidate, .d_hash = cifs_ci_hash, |
