| // SPDX-License-Identifier: LGPL-2.1 | |
| /* | |
| * | |
| * vfs operations that deal with dentries | |
| * | |
| * Copyright (C) International Business Machines Corp., 2002,2009 | |
| * Author(s): Steve French (sfrench@us.ibm.com) | |
| * | |
| */ | |
| #include <linux/fs.h> | |
| #include <linux/stat.h> | |
| #include <linux/slab.h> | |
| #include <linux/namei.h> | |
| #include <linux/mount.h> | |
| #include <linux/file.h> | |
| #include "cifsfs.h" | |
| #include "cifsglob.h" | |
| #include "cifsproto.h" | |
| #include "cifs_debug.h" | |
| #include "cifs_fs_sb.h" | |
| #include "cifs_unicode.h" | |
| #include "fs_context.h" | |
| #include "cifs_ioctl.h" | |
| #include "fscache.h" | |
| #include "cached_dir.h" | |
| static void | |
| renew_parental_timestamps(struct dentry *direntry) | |
| { | |
| /* BB check if there is a way to get the kernel to do this or if we | |
| really need this */ | |
| do { | |
| cifs_set_time(direntry, jiffies); | |
| direntry = direntry->d_parent; | |
| } while (!IS_ROOT(direntry)); | |
| } | |
| char * | |
| cifs_build_path_to_root(struct smb3_fs_context *ctx, struct cifs_sb_info *cifs_sb, | |
| struct cifs_tcon *tcon, int add_treename) | |
| { | |
| int pplen = ctx->prepath ? strlen(ctx->prepath) + 1 : 0; | |
| int dfsplen; | |
| char *full_path = NULL; | |
| /* if no prefix path, simply set path to the root of share to "" */ | |
| if (pplen == 0) { | |
| full_path = kzalloc(1, GFP_KERNEL); | |
| return full_path; | |
| } | |
| if (add_treename) | |
| dfsplen = strnlen(tcon->tree_name, MAX_TREE_SIZE + 1); | |
| else | |
| dfsplen = 0; | |
| full_path = kmalloc(dfsplen + pplen + 1, GFP_KERNEL); | |
| if (full_path == NULL) | |
| return full_path; | |
| if (dfsplen) | |
| memcpy(full_path, tcon->tree_name, dfsplen); | |
| full_path[dfsplen] = CIFS_DIR_SEP(cifs_sb); | |
| memcpy(full_path + dfsplen + 1, ctx->prepath, pplen); | |
| convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb)); | |
| return full_path; | |
| } | |
| /* Note: caller must free return buffer */ | |
| const char * | |
| build_path_from_dentry(struct dentry *direntry, void *page) | |
| { | |
| struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb); | |
| struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); | |
| bool prefix = tcon->Flags & SMB_SHARE_IS_IN_DFS; | |
| return build_path_from_dentry_optional_prefix(direntry, page, | |
| prefix); | |
| } | |
| char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page, | |
| const char *tree, int tree_len, | |
| bool prefix) | |
| { | |
| 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)) | |
| return ERR_PTR(-ENOMEM); | |
| if (prefix) | |
| dfsplen = strnlen(tree, tree_len + 1); | |
| else | |
| dfsplen = 0; | |
| 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); | |
| if (IS_ERR(s)) | |
| return s; | |
| if (!s[1]) // for root we want "", not "/" | |
| s++; | |
| if (s < (char *)page + pplen + dfsplen) | |
| return ERR_PTR(-ENAMETOOLONG); | |
| if (pplen) { | |
| cifs_dbg(FYI, "using cifs_sb prepath <%s>\n", cifs_sb->prepath); | |
| s -= pplen; | |
| memcpy(s + 1, cifs_sb->prepath, pplen - 1); | |
| *s = '/'; | |
| } | |
| if (dirsep != '/') { | |
| /* BB test paths to Windows with '/' in the midst of prepath */ | |
| strreplace(s, '/', dirsep); | |
| } | |
| if (dfsplen) { | |
| s -= dfsplen; | |
| memcpy(s, tree, dfsplen); | |
| if (sbflags & CIFS_MOUNT_POSIX_PATHS) { | |
| int i; | |
| for (i = 0; i < dfsplen; i++) { | |
| if (s[i] == '\\') | |
| s[i] = '/'; | |
| } | |
| } | |
| } | |
| return s; | |
| } | |
| char *build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page, | |
| bool prefix) | |
| { | |
| struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb); | |
| struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); | |
| return __build_path_from_dentry_optional_prefix(direntry, page, tcon->tree_name, | |
| MAX_TREE_SIZE, prefix); | |
| } | |
| /* | |
| * Don't allow path components longer than the server max. | |
| * Don't allow the separator character in a path component. | |
| * The VFS will not allow "/", but "\" is allowed by posix. | |
| */ | |
| static int | |
| check_name(struct dentry *direntry, struct cifs_tcon *tcon) | |
| { | |
| struct cifs_sb_info *cifs_sb = CIFS_SB(direntry); | |
| int i; | |
| if (unlikely(tcon->fsAttrInfo.MaxPathNameComponentLength && | |
| direntry->d_name.len > | |
| le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength))) | |
| return -ENAMETOOLONG; | |
| 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"); | |
| return -EINVAL; | |
| } | |
| } | |
| } | |
| 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; | |
| 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; | |
| } | |
| /* 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, bool *opened) | |
| { | |
| int rc = -ENOENT; | |
| int create_options = CREATE_NOT_DIR; | |
| int desired_access; | |
| struct cifs_sb_info *cifs_sb = CIFS_SB(dir); | |
| struct cifs_tcon *tcon = tlink_tcon(tlink); | |
| 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; | |
| *opened = false; | |
| *oplock = 0; | |
| if (tcon->ses->server->oplocks) | |
| *oplock = REQ_OPLOCK; | |
| /* If we're caching, we need to be able to fill in around partial writes. */ | |
| 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, dir->i_sb, mode, | |
| oflags, oplock, &fid->netfid, xid); | |
| switch (rc) { | |
| case 0: | |
| *opened = true; | |
| if (newinode == NULL) { | |
| /* query inode info */ | |
| goto cifs_create_get_file_info; | |
| } | |
| if ((oflags & __O_REGULAR) && !S_ISREG(newinode->i_mode)) { | |
| CIFSSMBClose(xid, tcon, fid->netfid); | |
| iput(newinode); | |
| return -EFTYPE; | |
| } | |
| if (S_ISDIR(newinode->i_mode)) { | |
| CIFSSMBClose(xid, tcon, fid->netfid); | |
| iput(newinode); | |
| return -EISDIR; | |
| } | |
| if (!S_ISREG(newinode->i_mode)) { | |
| /* | |
| * The server may allow us to open things like | |
| * FIFOs, but the client isn't set up to deal | |
| * with that. Keep the handle until the caller | |
| * can finish the lookup. | |
| */ | |
| goto cifs_create_get_file_info; | |
| } | |
| /* success, no need to query */ | |
| goto cifs_create_set_dentry; | |
| case -ENOENT: | |
| goto cifs_create_get_file_info; | |
| case -EIO: | |
| case -EINVAL: | |
| /* | |
| * EIO could indicate that (posix open) operation is not | |
| * supported, despite what server claimed in capability | |
| * negotiation. | |
| * | |
| * POSIX open in samba versions 3.3.1 and earlier could | |
| * incorrectly fail with invalid parameter. | |
| */ | |
| tcon->broken_posix_open = true; | |
| break; | |
| case -EREMOTE: | |
| case -EOPNOTSUPP: | |
| /* | |
| * EREMOTE indicates DFS junction, which is not handled | |
| * in posix open. If either that or op not supported | |
| * returned, follow the normal lookup. | |
| */ | |
| break; | |
| default: | |
| return rc; | |
| } | |
| /* | |
| * fallthrough to retry, using older open call, this is case | |
| * where server does not support this SMB level, and falsely | |
| * claims capability (also get here for DFS case which should be | |
| * rare for path not covered on files) | |
| */ | |
| } | |
| #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ | |
| desired_access = 0; | |
| if (OPEN_FMODE(oflags) & FMODE_READ) | |
| desired_access |= GENERIC_READ; /* is this too little? */ | |
| if (OPEN_FMODE(oflags) & FMODE_WRITE) | |
| 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) { | |
| 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 { | |
| 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) | |
| 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 | |
| */ | |
| 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, | |
| .desired_access = desired_access, | |
| .create_options = cifs_create_options(cifs_sb, create_options), | |
| .disposition = disposition, | |
| .path = full_path, | |
| .fid = fid, | |
| .lease_flags = lease_flags, | |
| .mode = mode, | |
| }; | |
| rc = server->ops->open(xid, &oparms, oplock, buf); | |
| if (rc) { | |
| cifs_dbg(FYI, "cifs_create returned 0x%x\n", rc); | |
| if (rc == -EACCES && rdwr_for_fscache == 1) { | |
| desired_access &= ~GENERIC_READ; | |
| rdwr_for_fscache = 2; | |
| goto retry_open; | |
| } | |
| return rc; | |
| } | |
| *opened = true; | |
| if (rdwr_for_fscache == 2) | |
| cifs_invalidate_cache(dir, FSCACHE_INVAL_DIO_WRITE); | |
| #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY | |
| /* | |
| * If Open reported that we actually created a file then we now have to | |
| * set the mode if possible. | |
| */ | |
| if ((tcon->unix_ext) && (*oplock & CIFS_CREATE_ACTION)) { | |
| struct cifs_unix_set_info_args args = { | |
| .mode = mode, | |
| .ctime = NO_CHANGE_64, | |
| .atime = NO_CHANGE_64, | |
| .mtime = NO_CHANGE_64, | |
| .device = 0, | |
| }; | |
| if (sbflags & CIFS_MOUNT_SET_UID) { | |
| args.uid = current_fsuid(); | |
| if (dir->i_mode & S_ISGID) | |
| args.gid = dir->i_gid; | |
| else | |
| args.gid = current_fsgid(); | |
| } else { | |
| args.uid = INVALID_UID; /* no change */ | |
| args.gid = INVALID_GID; /* no change */ | |
| } | |
| CIFSSMBUnixSetFileInfo(xid, tcon, &args, fid->netfid, | |
| current->tgid); | |
| } else { | |
| /* | |
| * BB implement mode setting via Windows security | |
| * descriptors e.g. | |
| */ | |
| /* CIFSSMBWinSetPerms(xid,tcon,path,mode,-1,-1,nls);*/ | |
| /* Could set r/o dos attribute if mode & 0222 == 0 */ | |
| } | |
| 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, 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, 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 (sbflags & CIFS_MOUNT_DYNPERM) | |
| newinode->i_mode = mode; | |
| if (sbflags & CIFS_MOUNT_SET_UID) { | |
| newinode->i_uid = current_fsuid(); | |
| if (dir->i_mode & S_ISGID) | |
| newinode->i_gid = dir->i_gid; | |
| else | |
| newinode->i_gid = current_fsgid(); | |
| } | |
| } | |
| } | |
| } | |
| #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY | |
| cifs_create_set_dentry: | |
| #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ | |
| if (rc != 0) { | |
| cifs_dbg(FYI, "Create worked, get_inode_info failed rc = %d\n", | |
| rc); | |
| goto out_err; | |
| } | |
| if (newinode) { | |
| if ((oflags & __O_REGULAR) && !S_ISREG(newinode->i_mode)) { | |
| rc = -EFTYPE; | |
| goto out_err; | |
| } | |
| if (S_ISDIR(newinode->i_mode)) { | |
| rc = -EISDIR; | |
| goto out_err; | |
| } | |
| } | |
| *inode = newinode; | |
| return rc; | |
| out_err: | |
| if (*opened && server->ops->close) | |
| server->ops->close(xid, tcon, fid); | |
| if (newinode) | |
| iput(newinode); | |
| return rc; | |
| } | |
| 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, bool *opened) | |
| { | |
| void *page = alloc_dentry_path(); | |
| const char *full_path; | |
| int rc; | |
| full_path = build_path_from_dentry(direntry, page); | |
| if (IS_ERR(full_path)) { | |
| rc = PTR_ERR(full_path); | |
| *opened = false; | |
| } else { | |
| rc = __cifs_do_create(dir, direntry, full_path, xid, | |
| tlink, oflags, mode, oplock, | |
| fid, buf, inode, opened); | |
| } | |
| 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 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; | |
| bool is_regular; | |
| bool opened; | |
| int rc; | |
| 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 | |
| * opens (rather than creates), because we do not know if it is a file | |
| * or directory yet, and current Samba no longer allows us to do posix | |
| * open on dirs, we could end up wasting an open call on what turns out | |
| * to be a dir. For file opens, we wait to call posix open till | |
| * cifs_open. It could be added to atomic_open in the future but the | |
| * performance tradeoff of the extra network request when EISDIR or | |
| * EACCES is returned would have to be weighed against the 50% reduction | |
| * in network traffic in the other paths. | |
| */ | |
| if (!(oflags & O_CREAT)) { | |
| /* | |
| * Check for hashed negative dentry. We have already revalidated | |
| * the dentry and it is fine. No need to perform another lookup. | |
| */ | |
| if (!d_in_lookup(direntry)) | |
| return -ENOENT; | |
| 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", | |
| dir, direntry, direntry); | |
| tlink = cifs_sb_tlink(cifs_sb); | |
| if (IS_ERR(tlink)) { | |
| rc = PTR_ERR(tlink); | |
| goto out_free_xid; | |
| } | |
| tcon = tlink_tcon(tlink); | |
| rc = check_name(direntry, tcon); | |
| if (rc) | |
| goto out; | |
| server = tcon->ses->server; | |
| if (server->ops->new_lease_key) | |
| server->ops->new_lease_key(&fid); | |
| cifs_add_pending_open(&fid, tlink, &open); | |
| rc = cifs_do_create(dir, direntry, xid, tlink, oflags, mode, | |
| &oplock, &fid, &buf, &inode, &opened); | |
| if (rc) { | |
| cifs_del_pending_open(&open); | |
| goto out; | |
| } | |
| is_regular = S_ISREG(inode->i_mode); | |
| if (!is_regular || !opened) { | |
| if (opened && server->ops->close) | |
| server->ops->close(xid, tcon, &fid); | |
| cifs_del_pending_open(&open); | |
| if (S_ISLNK(inode->i_mode) && | |
| (oflags & (O_NOFOLLOW | __O_REGULAR)) == | |
| (O_NOFOLLOW | __O_REGULAR) && !(oflags & O_EXCL)) { | |
| iput(inode); | |
| rc = -ELOOP; | |
| 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 (is_regular && opened && | |
| (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) | |
| file->f_mode |= FMODE_CREATED; | |
| if (!is_regular || !opened) { | |
| rc = finish_no_open(file, NULL); | |
| goto out; | |
| } | |
| rc = finish_open(file, direntry, generic_file_open); | |
| if (rc) { | |
| if (server->ops->close) | |
| server->ops->close(xid, tcon, &fid); | |
| cifs_del_pending_open(&open); | |
| goto out; | |
| } | |
| 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) { | |
| if (server->ops->close) | |
| server->ops->close(xid, tcon, &fid); | |
| cifs_del_pending_open(&open); | |
| rc = -ENOMEM; | |
| goto out; | |
| } | |
| fscache_use_cookie(cifs_inode_cookie(file_inode(file)), | |
| file->f_mode & FMODE_WRITE); | |
| out: | |
| cifs_put_tlink(tlink); | |
| out_free_xid: | |
| free_xid(xid); | |
| cifs_free_open_info(&buf); | |
| return rc; | |
| } | |
| /* | |
| * 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) | |
| { | |
| struct cifs_sb_info *cifs_sb = CIFS_SB(dir); | |
| int rc; | |
| unsigned int xid = get_xid(); | |
| /* | |
| * BB below access is probably too much for mknod to request | |
| * but we have to do query and setpathinfo so requesting | |
| * less could fail (unless we want to request getatr and setatr | |
| * permissions (only). At least for POSIX we do not have to | |
| * request so much. | |
| */ | |
| unsigned oflags = O_EXCL | O_CREAT | O_RDWR; | |
| struct tcon_link *tlink; | |
| struct cifs_tcon *tcon; | |
| struct TCP_Server_Info *server; | |
| struct inode *inode; | |
| struct cifs_fid fid; | |
| __u32 oplock; | |
| bool opened; | |
| struct cifs_open_info_data buf = {}; | |
| cifs_dbg(FYI, "cifs_create parent inode = 0x%p name is: %pd and dentry = 0x%p\n", | |
| dir, direntry, direntry); | |
| 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); | |
| rc = PTR_ERR(tlink); | |
| if (IS_ERR(tlink)) | |
| goto out_free_xid; | |
| tcon = tlink_tcon(tlink); | |
| server = tcon->ses->server; | |
| if (server->ops->new_lease_key) | |
| server->ops->new_lease_key(&fid); | |
| rc = cifs_do_create(dir, direntry, xid, tlink, oflags, | |
| mode, &oplock, &fid, &buf, &inode, &opened); | |
| if (!rc) { | |
| d_instantiate(direntry, inode); | |
| if (opened && server->ops->close) | |
| server->ops->close(xid, tcon, &fid); | |
| } | |
| cifs_free_open_info(&buf); | |
| cifs_put_tlink(tlink); | |
| out_free_xid: | |
| free_xid(xid); | |
| return rc; | |
| } | |
| int cifs_mknod(struct mnt_idmap *idmap, struct inode *inode, | |
| struct dentry *direntry, umode_t mode, dev_t device_number) | |
| { | |
| int rc = -EPERM; | |
| unsigned int xid; | |
| struct cifs_sb_info *cifs_sb; | |
| struct tcon_link *tlink; | |
| struct cifs_tcon *tcon; | |
| const char *full_path; | |
| void *page; | |
| if (!old_valid_dev(device_number)) | |
| return -EINVAL; | |
| cifs_sb = CIFS_SB(inode->i_sb); | |
| 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); | |
| page = alloc_dentry_path(); | |
| tcon = tlink_tcon(tlink); | |
| xid = get_xid(); | |
| full_path = build_path_from_dentry(direntry, page); | |
| if (IS_ERR(full_path)) { | |
| rc = PTR_ERR(full_path); | |
| goto mknod_out; | |
| } | |
| trace_smb3_mknod_enter(xid, tcon->tid, tcon->ses->Suid, full_path); | |
| rc = tcon->ses->server->ops->make_node(xid, inode, direntry, tcon, | |
| full_path, mode, | |
| device_number); | |
| mknod_out: | |
| if (rc) | |
| trace_smb3_mknod_err(xid, tcon->tid, tcon->ses->Suid, rc); | |
| else | |
| trace_smb3_mknod_done(xid, tcon->tid, tcon->ses->Suid); | |
| free_dentry_path(page); | |
| free_xid(xid); | |
| cifs_put_tlink(tlink); | |
| return rc; | |
| } | |
| struct dentry * | |
| cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, | |
| unsigned int flags) | |
| { | |
| unsigned int xid; | |
| int rc = 0; /* to get around spurious gcc warning, set to zero here */ | |
| struct cifs_sb_info *cifs_sb; | |
| struct tcon_link *tlink; | |
| struct cifs_tcon *pTcon; | |
| struct inode *newInode = NULL; | |
| const char *full_path; | |
| void *page; | |
| int retry_count = 0; | |
| struct dentry *de; | |
| xid = get_xid(); | |
| cifs_dbg(FYI, "parent inode = 0x%p name is: %pd and dentry = 0x%p\n", | |
| parent_dir_inode, direntry, direntry); | |
| /* check whether path exists */ | |
| cifs_sb = CIFS_SB(parent_dir_inode->i_sb); | |
| tlink = cifs_sb_tlink(cifs_sb); | |
| if (IS_ERR(tlink)) { | |
| de = ERR_CAST(tlink); | |
| goto free_xid; | |
| } | |
| pTcon = tlink_tcon(tlink); | |
| rc = check_name(direntry, pTcon); | |
| if (unlikely(rc)) { | |
| de = ERR_PTR(rc); | |
| goto put_tlink; | |
| } | |
| /* can not grab the rename sem here since it would | |
| deadlock in the cases (beginning of sys_rename itself) | |
| in which we already have the sb rename sem */ | |
| page = alloc_dentry_path(); | |
| full_path = build_path_from_dentry(direntry, page); | |
| if (IS_ERR(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)); | |
| again: | |
| if (pTcon->posix_extensions) { | |
| rc = smb311_posix_get_inode_info(&newInode, full_path, NULL, | |
| parent_dir_inode->i_sb, xid); | |
| } else if (pTcon->unix_ext) { | |
| rc = cifs_get_inode_info_unix(&newInode, full_path, | |
| parent_dir_inode->i_sb, xid); | |
| } else { | |
| rc = cifs_get_inode_info(&newInode, full_path, NULL, | |
| parent_dir_inode->i_sb, xid, NULL); | |
| } | |
| if (rc == 0) { | |
| /* since paths are not looked up by component - the parent | |
| directories are presumed to be good here */ | |
| renew_parental_timestamps(direntry); | |
| } else if (rc == -EAGAIN && retry_count++ < 10) { | |
| goto again; | |
| } else if (rc == -ENOENT) { | |
| cifs_set_time(direntry, jiffies); | |
| newInode = NULL; | |
| } else { | |
| if (rc != -EACCES) { | |
| cifs_dbg(FYI, "Unexpected lookup error %d\n", rc); | |
| /* We special case check for Access Denied - since that | |
| is a common return code */ | |
| } | |
| 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 de; | |
| } | |
| static int | |
| cifs_d_revalidate(struct inode *dir, const struct qstr *name, | |
| struct dentry *direntry, unsigned int flags) | |
| { | |
| if (flags & LOOKUP_RCU) | |
| return -ECHILD; | |
| if (d_really_is_positive(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 */ | |
| rc = cifs_revalidate_dentry(direntry); | |
| if (rc) { | |
| cifs_dbg(FYI, "cifs_revalidate_dentry failed with rc=%d", rc); | |
| switch (rc) { | |
| case -ENOENT: | |
| case -ESTALE: | |
| /* | |
| * Those errors mean the dentry is invalid | |
| * (file was deleted or recreated) | |
| */ | |
| return 0; | |
| default: | |
| /* | |
| * Otherwise some unexpected error happened | |
| * report it as-is to VFS layer | |
| */ | |
| return rc; | |
| } | |
| } | |
| else { | |
| /* | |
| * If the inode wasn't known to be a dfs entry when | |
| * the dentry was instantiated, such as when created | |
| * via ->readdir(), it needs to be set now since the | |
| * attributes will have been updated by | |
| * cifs_revalidate_dentry(). | |
| */ | |
| if (IS_AUTOMOUNT(inode) && | |
| !(direntry->d_flags & DCACHE_NEED_AUTOMOUNT)) { | |
| spin_lock(&direntry->d_lock); | |
| direntry->d_flags |= DCACHE_NEED_AUTOMOUNT; | |
| spin_unlock(&direntry->d_lock); | |
| } | |
| 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); | |
| } | |
| } | |
| /* | |
| * This may be nfsd (or something), anyway, we can't see the | |
| * intent of this. So, since this can be for creation, drop it. | |
| */ | |
| if (!flags) | |
| return 0; | |
| /* | |
| * Drop the negative dentry, in order to make sure to use the | |
| * case sensitive name which is specified by user if this is | |
| * for creation. | |
| */ | |
| if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET)) | |
| return 0; | |
| if (time_after(jiffies, cifs_get_time(direntry) + HZ) || !lookupCacheEnabled) | |
| return 0; | |
| return 1; | |
| } | |
| /* static int cifs_d_delete(struct dentry *direntry) | |
| { | |
| int rc = 0; | |
| cifs_dbg(FYI, "In cifs d_delete, name = %pd\n", direntry); | |
| return rc; | |
| } */ | |
| const struct dentry_operations cifs_dentry_ops = { | |
| .d_revalidate = cifs_d_revalidate, | |
| .d_automount = cifs_d_automount, | |
| /* d_delete: cifs_d_delete, */ /* not needed except for debugging */ | |
| }; | |
| static int cifs_ci_hash(const struct dentry *dentry, struct qstr *q) | |
| { | |
| struct nls_table *codepage = CIFS_SB(dentry->d_sb)->local_nls; | |
| unsigned long hash; | |
| wchar_t c; | |
| int i, charlen; | |
| hash = init_name_hash(dentry); | |
| for (i = 0; i < q->len; i += charlen) { | |
| charlen = codepage->char2uni(&q->name[i], q->len - i, &c); | |
| /* error out if we can't convert the character */ | |
| if (unlikely(charlen < 0)) | |
| return charlen; | |
| hash = partial_name_hash(cifs_toupper(c), hash); | |
| } | |
| q->hash = end_name_hash(hash); | |
| return 0; | |
| } | |
| static int cifs_ci_compare(const struct dentry *dentry, | |
| unsigned int len, const char *str, const struct qstr *name) | |
| { | |
| struct nls_table *codepage = CIFS_SB(dentry->d_sb)->local_nls; | |
| wchar_t c1, c2; | |
| int i, l1, l2; | |
| /* | |
| * We make the assumption here that uppercase characters in the local | |
| * codepage are always the same length as their lowercase counterparts. | |
| * | |
| * If that's ever not the case, then this will fail to match it. | |
| */ | |
| if (name->len != len) | |
| return 1; | |
| for (i = 0; i < len; i += l1) { | |
| /* Convert characters in both strings to UTF-16. */ | |
| l1 = codepage->char2uni(&str[i], len - i, &c1); | |
| l2 = codepage->char2uni(&name->name[i], name->len - i, &c2); | |
| /* | |
| * If we can't convert either character, just declare it to | |
| * be 1 byte long and compare the original byte. | |
| */ | |
| if (unlikely(l1 < 0 && l2 < 0)) { | |
| if (str[i] != name->name[i]) | |
| return 1; | |
| l1 = 1; | |
| continue; | |
| } | |
| /* | |
| * Here, we again ass|u|me that upper/lowercase versions of | |
| * a character are the same length in the local NLS. | |
| */ | |
| if (l1 != l2) | |
| return 1; | |
| /* Now compare uppercase versions of these characters */ | |
| if (cifs_toupper(c1) != cifs_toupper(c2)) | |
| return 1; | |
| } | |
| 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; | |
| bool opened; | |
| 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, &opened); | |
| 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) { | |
| if (rc == -ENOENT) | |
| rc = -EOPNOTSUPP; | |
| 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, | |
| .d_compare = cifs_ci_compare, | |
| .d_automount = cifs_d_automount, | |
| }; |