// SPDX-License-Identifier: LGPL-2.1 /* * * Directory search handling * * Copyright (C) International Business Machines Corp., 2004, 2008 * Copyright (C) Red Hat, Inc., 2011 * Author(s): Steve French (sfrench@us.ibm.com) *
*/ #include <linux/fs.h> #include <linux/namei.h> #include <linux/pagemap.h> #include <linux/slab.h> #include <linux/stat.h> #include"cifspdu.h" #include"cifsglob.h" #include"cifsproto.h" #include"cifs_unicode.h" #include"cifs_debug.h" #include"cifs_fs_sb.h" #include"cifsfs.h" #include"smb2proto.h" #include"fs_context.h" #include"cached_dir.h" #include"reparse.h"
/* * To be safe - for UCS to UTF-8 with strings loaded with the rare long * characters alloc more to account for such multibyte target UTF-8 * characters.
*/ #define UNICODE_NAME_MAX ((4 * NAME_MAX) + 2)
if (file) {
cf = file->private_data; if (cf == NULL) {
cifs_dbg(FYI, "empty cifs private file data\n"); return;
} if (cf->invalidHandle)
cifs_dbg(FYI, "Invalid handle\n"); if (cf->srch_inf.endOfSearch)
cifs_dbg(FYI, "end of search\n"); if (cf->srch_inf.emptyDir)
cifs_dbg(FYI, "empty dir\n");
}
} #else staticinlinevoid dump_cifs_file_struct(struct file *file, char *label)
{
} #endif/* DEBUG2 */
/* * Attempt to preload the dcache with the results from the FIND_FIRST/NEXT * * Find the dentry that matches "name". If there isn't one, create one. If it's * a negative dentry or the uniqueid or filetype(mode) changed, * then drop it and recreate it.
*/ staticvoid
cifs_prime_dcache(struct dentry *parent, struct qstr *name, struct cifs_fattr *fattr)
{ struct dentry *dentry, *alias; struct inode *inode; struct super_block *sb = parent->d_sb; struct cifs_sb_info *cifs_sb = CIFS_SB(sb); bool posix = cifs_sb_master_tcon(cifs_sb)->posix_extensions; bool reparse_need_reval = false;
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); int rc;
cifs_dbg(FYI, "%s: for %s\n", __func__, name->name);
dentry = try_lookup_noperm(name, parent); if (!dentry) { /* * If we know that the inode will need to be revalidated * immediately, then don't create a new dentry for it. * We'll end up doing an on the wire call either way and * this spares us an invalidation.
*/
retry: if (posix) { switch (fattr->cf_mode & S_IFMT) { case S_IFLNK: case S_IFBLK: case S_IFCHR:
reparse_need_reval = true; break; default: break;
}
} elseif (fattr->cf_cifsattrs & ATTR_REPARSE) {
reparse_need_reval = true;
}
if (reparse_need_reval ||
(fattr->cf_flags & CIFS_FATTR_NEED_REVAL)) return;
dentry = d_alloc_parallel(parent, name, &wq);
} if (IS_ERR(dentry)) return; if (!d_in_lookup(dentry)) {
inode = d_inode(dentry); if (inode) { if (d_mountpoint(dentry)) {
dput(dentry); return;
} /* * If we're generating inode numbers, then we don't * want to clobber the existing one with the one that * the readdir code created.
*/ if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM))
fattr->cf_uniqueid = CIFS_I(inode)->uniqueid;
/* * Update inode in place if both i_ino and i_mode didn't * change.
*/ if (CIFS_I(inode)->uniqueid == fattr->cf_uniqueid) { /* * Query dir responses don't provide enough * information about reparse points other than * their reparse tags. Save an invalidation by * not clobbering some existing attributes when * reparse tag and ctime haven't changed.
*/
rc = 0; if (fattr->cf_cifsattrs & ATTR_REPARSE) { if (likely(reparse_inode_match(inode, fattr))) {
fattr->cf_mode = inode->i_mode;
fattr->cf_rdev = inode->i_rdev;
fattr->cf_uid = inode->i_uid;
fattr->cf_gid = inode->i_gid;
fattr->cf_eof = CIFS_I(inode)->netfs.remote_i_size;
fattr->cf_symlink_target = NULL;
} else {
CIFS_I(inode)->time = 0;
rc = -ESTALE;
}
} if (!rc && !cifs_fattr_to_inode(inode, fattr, true)) {
dput(dentry); return;
}
}
}
d_invalidate(dentry);
dput(dentry); goto retry;
} else {
inode = cifs_iget(sb, fattr); if (!inode)
inode = ERR_PTR(-ENOMEM);
alias = d_splice_alias(inode, dentry);
d_lookup_done(dentry); if (alias && !IS_ERR(alias))
dput(alias);
}
dput(dentry);
}
/* * The IO_REPARSE_TAG_LX_ tags originally were used by WSL but they * are preferred by the Linux client in some cases since, unlike * the NFS reparse tag (or EAs), they don't require an extra query * to determine which type of special file they represent. * TODO: go through all documented reparse tags to see if we can * reasonably map some of them to directories vs. files vs. symlinks
*/ if ((fattr->cf_cifsattrs & ATTR_REPARSE) &&
cifs_reparse_point_to_fattr(cifs_sb, fattr, &data)) goto out_reparse;
if (fattr->cf_cifsattrs & ATTR_READONLY)
fattr->cf_mode &= ~S_IWUGO;
/* * We of course don't get ACL info in FIND_FIRST/NEXT results, so * mark it for revalidation so that "ls -l" will look right. It might * be super-slow, but if we don't do this then the ownership of files * may look wrong since the inodes may not have timed out by the time * "ls" does a stat() call on them.
*/ if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_CIFS_ACL) ||
(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MODE_FROM_SID))
fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL &&
fattr->cf_cifsattrs & ATTR_SYSTEM) { if (fattr->cf_eof == 0) {
fattr->cf_mode &= ~S_IFMT;
fattr->cf_mode |= S_IFIFO;
fattr->cf_dtype = DT_FIFO;
} else { /* * trying to get the type and mode via SFU can be slow, * so just call those regular files for now, and mark * for reval
*/
fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
}
}
}
/* Fill a cifs_fattr struct with info from SMB_FIND_FILE_POSIX_INFO. */ staticvoid
cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info, struct cifs_sb_info *cifs_sb)
{ struct smb2_posix_info_parsed parsed;
if (fattr->cf_cifsattrs & ATTR_REPARSE)
fattr->cf_cifstag = le32_to_cpu(info->ReparseTag);
/* The Mode field in the response can now include the file type as well */
fattr->cf_mode = wire_mode_to_posix(le32_to_cpu(info->Mode),
fattr->cf_cifsattrs & ATTR_DIRECTORY);
fattr->cf_dtype = S_DT(fattr->cf_mode);
switch (fattr->cf_mode & S_IFMT) { case S_IFLNK: case S_IFBLK: case S_IFCHR:
fattr->cf_flags |= CIFS_FATTR_NEED_REVAL; break; default: break;
}
ffirst_retry: /* test for Unix extensions */ /* but now check for them on the share/mount not on the SMB session */ /* if (cap_unix(tcon->ses) { */ if (tcon->unix_ext)
cifsFile->srch_inf.info_level = SMB_FIND_FILE_UNIX; elseif (tcon->posix_extensions)
cifsFile->srch_inf.info_level = SMB_FIND_FILE_POSIX_INFO; elseif ((tcon->ses->capabilities &
tcon->ses->server->vals->cap_nt_find) == 0) {
cifsFile->srch_inf.info_level = SMB_FIND_FILE_INFO_STANDARD;
} elseif (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM) {
cifsFile->srch_inf.info_level = SMB_FIND_FILE_ID_FULL_DIR_INFO;
} else/* not srvinos - BB fixme add check for backlevel? */ {
cifsFile->srch_inf.info_level = SMB_FIND_FILE_FULL_DIRECTORY_INFO;
}
search_flags = CIFS_SEARCH_CLOSE_AT_END | CIFS_SEARCH_RETURN_RESUME; if (backup_cred(cifs_sb))
search_flags |= CIFS_SEARCH_BACKUP_SEARCH;
do {
rc = _initiate_cifs_search(xid, file, full_path); /* * If we don't have enough credits to start reading the * directory just try again after short wait.
*/ if (rc != -EDEADLK) break;
usleep_range(512, 2048);
} while (retry_count++ < 5);
return rc;
}
/* return length of unicode string in bytes */ staticint cifs_unicode_bytelen(constchar *str)
{ int len; const __le16 *ustr = (const __le16 *)str;
for (len = 0; len <= PATH_MAX; len++) { if (ustr[len] == 0) return len << 1;
}
cifs_dbg(FYI, "Unicode string longer than PATH_MAX found\n"); return len << 1;
}
/* payload should have already been checked at this point */ if (posix_info_parse(info, NULL, &parsed) < 0) {
cifs_dbg(VFS, "Invalid POSIX info payload\n"); return;
}
switch (level) { case SMB_FIND_FILE_POSIX_INFO:
cifs_fill_dirent_posix(de, info); break; case SMB_FIND_FILE_UNIX:
cifs_fill_dirent_unix(de, info, is_unicode); break; case SMB_FIND_FILE_DIRECTORY_INFO:
cifs_fill_dirent_dir(de, info); break; case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
cifs_fill_dirent_full(de, info); break; case SMB_FIND_FILE_ID_FULL_DIR_INFO:
cifs_fill_dirent_search(de, info); break; case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
cifs_fill_dirent_both(de, info); break; case SMB_FIND_FILE_INFO_STANDARD:
cifs_fill_dirent_std(de, info); break; default:
cifs_dbg(FYI, "Unknown findfirst level %d\n", level); return -EINVAL;
}
return 0;
}
#define UNICODE_DOT cpu_to_le16(0x2e)
/* return 0 if no match and 1 for . (current directory) and 2 for .. (parent) */ staticint cifs_entry_is_dot(struct cifs_dirent *de, bool is_unicode)
{ int rc = 0;
if (!de->name) return 0;
if (is_unicode) {
__le16 *ufilename = (__le16 *)de->name; if (de->namelen == 2) { /* check for . */ if (ufilename[0] == UNICODE_DOT)
rc = 1;
} elseif (de->namelen == 4) { /* check for .. */ if (ufilename[0] == UNICODE_DOT &&
ufilename[1] == UNICODE_DOT)
rc = 2;
}
} else/* ASCII */ { if (de->namelen == 1) { if (de->name[0] == '.')
rc = 1;
} elseif (de->namelen == 2) { if (de->name[0] == '.' && de->name[1] == '.')
rc = 2;
}
}
return rc;
}
/* Check if directory that we are searching has changed so we can decide
whether we can use the cached search results from the previous search */ staticint is_dir_changed(struct file *file)
{ struct inode *inode = file_inode(file); struct cifsInodeInfo *cifs_inode_info = CIFS_I(inode);
if (cifs_inode_info->time == 0) return 1; /* directory was changed, e.g. unlink or new file */ else return 0;
/* * Find the corresponding entry in the search. Note that the SMB server returns * search entries for . and .. which complicates logic here if we choose to * parse for them and we do not assume that they are located in the findfirst * return buffer. We start counting in the buffer with entry 2 and increment for * every entry (do not increment for . or .. entry).
*/ staticint
find_cifs_entry(constunsignedint xid, struct cifs_tcon *tcon, loff_t pos, struct file *file, constchar *full_path, char **current_entry, int *num_to_ret)
{
__u16 search_flags; int rc = 0; int pos_in_buf = 0;
loff_t first_entry_in_buffer;
loff_t index_to_find = pos; struct cifsFileInfo *cfile = file->private_data; struct cifs_sb_info *cifs_sb = CIFS_FILE_SB(file); struct TCP_Server_Info *server = tcon->ses->server; /* check if index in the buffer */
if (!server->ops->query_dir_first || !server->ops->query_dir_next) return -ENOSYS;
/* * If first entry in buf is zero then is first buffer * in search response data which means it is likely . and .. * will be in this buffer, although some servers do not return * . and .. for the root of a drive and for those we need * to start two entries earlier.
*/
dump_cifs_file_struct(file, "In fce "); if (((index_to_find < cfile->srch_inf.index_of_last_entry) &&
is_dir_changed(file)) || (index_to_find < first_entry_in_buffer)) { /* close and restart search */
cifs_dbg(FYI, "search backing up - close and restart search\n");
spin_lock(&cfile->file_info_lock); if (server->ops->dir_needs_close(cfile)) {
cfile->invalidHandle = true;
spin_unlock(&cfile->file_info_lock); if (server->ops->close_dir)
server->ops->close_dir(xid, tcon, &cfile->fid);
} else
spin_unlock(&cfile->file_info_lock); if (cfile->srch_inf.ntwrk_buf_start) {
cifs_dbg(FYI, "freeing SMB ff cache buf on search rewind\n"); if (cfile->srch_inf.smallBuf)
cifs_small_buf_release(cfile->srch_inf.
ntwrk_buf_start); else
cifs_buf_release(cfile->srch_inf.
ntwrk_buf_start); /* Reset all pointers to the network buffer to prevent stale references */
cfile->srch_inf.ntwrk_buf_start = NULL;
cfile->srch_inf.srch_entries_start = NULL;
cfile->srch_inf.last_entry = NULL;
}
rc = initiate_cifs_search(xid, file, full_path); if (rc) {
cifs_dbg(FYI, "error %d reinitiating a search on rewind\n",
rc); return rc;
} /* FindFirst/Next set last_entry to NULL on malformed reply */ if (cfile->srch_inf.last_entry)
cifs_save_resume_key(cfile->srch_inf.last_entry, cfile);
}
search_flags = CIFS_SEARCH_CLOSE_AT_END | CIFS_SEARCH_RETURN_RESUME; if (backup_cred(cifs_sb))
search_flags |= CIFS_SEARCH_BACKUP_SEARCH;
while ((index_to_find >= cfile->srch_inf.index_of_last_entry) &&
(rc == 0) && !cfile->srch_inf.endOfSearch) {
cifs_dbg(FYI, "calling findnext2\n");
rc = server->ops->query_dir_next(xid, tcon, &cfile->fid,
search_flags,
&cfile->srch_inf); if (rc) return -ENOENT; /* FindFirst/Next set last_entry to NULL on malformed reply */ if (cfile->srch_inf.last_entry)
cifs_save_resume_key(cfile->srch_inf.last_entry, cfile);
} if (index_to_find < cfile->srch_inf.index_of_last_entry) { /* we found the buffer that contains the entry */ /* scan and find it */ int i; char *cur_ent; char *end_of_smb;
if (cfile->srch_inf.ntwrk_buf_start == NULL) {
cifs_dbg(VFS, "ntwrk_buf_start is NULL during readdir\n"); return -EIO;
}
for (i = 0; (i < (pos_in_buf)) && (cur_ent != NULL); i++) { /* go entry by entry figuring out which is first */
cur_ent = nxt_dir_entry(cur_ent, end_of_smb,
cfile->srch_inf.info_level);
} if ((cur_ent == NULL) && (i < pos_in_buf)) { /* BB fixme - check if we should flag this error */
cifs_dbg(VFS, "reached end of buf searching for pos in buf %d index to find %lld rc %d\n",
pos_in_buf, index_to_find, rc);
}
rc = 0;
*current_entry = cur_ent;
} else {
cifs_dbg(FYI, "index not in buffer - could not findnext into it\n"); return 0;
}
list_for_each_entry(dirent, &cde->entries, entry) { /* * Skip all early entries prior to the current lseek() * position.
*/ if (ctx->pos > dirent->pos) continue; /* * We recorded the current ->pos value for the dirent * when we stored it in the cache. * However, this sequence of ->pos values may have holes * in it, for example dot-dirs returned from the server * are suppressed. * Handle this by forcing ctx->pos to be the same as the * ->pos of the current dirent we emit from the cache. * This means that when we emit these entries from the cache * we now emit them with the same ->pos value as in the * initial scan.
*/
ctx->pos = dirent->pos;
rc = dir_emit(ctx, dirent->name, dirent->namelen,
dirent->fattr.cf_uniqueid,
dirent->fattr.cf_dtype); if (!rc) return rc;
ctx->pos++;
} returntrue;
}
staticvoid update_cached_dirents_count(struct cached_dirents *cde, struct file *file)
{ if (cde->file != file) return; if (cde->is_valid || cde->is_failed) return;
cde->pos++;
}
staticvoid finished_cached_dirents_count(struct cached_dirents *cde, struct dir_context *ctx, struct file *file)
{ if (cde->file != file) return; if (cde->is_valid || cde->is_failed) return; if (ctx->pos != cde->pos) return;
if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) &&
couldbe_mf_symlink(&fattr)) /* * trying to get the type and mode can be slow, * so just call those regular files for now, and mark * for reval
*/
fattr.cf_flags |= CIFS_FATTR_NEED_REVAL;
mutex_lock(&cfid->dirents.de_mutex); /* * If this was reading from the start of the directory * we need to initialize scanning and storing the * directory content.
*/ if (ctx->pos == 0 && cfid->dirents.file == NULL) {
cfid->dirents.file = file;
cfid->dirents.pos = 2;
} /* * If we already have the entire directory cached then * we can just serve the cache.
*/ if (cfid->dirents.is_valid) { if (!dir_emit_dots(file, ctx)) {
mutex_unlock(&cfid->dirents.de_mutex); goto rddir2_exit;
}
emit_cached_dirents(&cfid->dirents, ctx);
mutex_unlock(&cfid->dirents.de_mutex); goto rddir2_exit;
}
mutex_unlock(&cfid->dirents.de_mutex);
/* Drop the cache while calling initiate_cifs_search and * find_cifs_entry in case there will be reconnects during * query_directory.
*/
close_cached_dir(cfid);
cfid = NULL;
cache_not_found: /* * Ensure FindFirst doesn't fail before doing filldir() for '.' and * '..'. Otherwise we won't be able to notify VFS in case of failure.
*/ if (file->private_data == NULL) {
rc = initiate_cifs_search(xid, file, full_path);
cifs_dbg(FYI, "initiate cifs search rc %d\n", rc); if (rc) goto rddir2_exit;
}
if (!dir_emit_dots(file, ctx)) goto rddir2_exit;
/* 1) If search is active, is in current search buffer? if it before then restart search
if after then keep searching till find it */
cifsFile = file->private_data; if (cifsFile->srch_inf.endOfSearch) { if (cifsFile->srch_inf.emptyDir) {
cifs_dbg(FYI, "End of search, empty dir\n");
rc = 0; goto rddir2_exit;
}
} /* else { cifsFile->invalidHandle = true; tcon->ses->server->close(xid, tcon, &cifsFile->fid);
} */
for (i = 0; i < num_to_fill; i++) { if (current_entry == NULL) { /* evaluate whether this case is an error */
cifs_dbg(VFS, "past SMB end, num to fill %d i %d\n",
num_to_fill, i); break;
} /* * if buggy server returns . and .. late do we want to * check for that here?
*/
*tmp_buf = 0;
rc = cifs_filldir(current_entry, file, ctx,
tmp_buf, max_len, cfid); if (rc) { if (rc > 0)
rc = 0; break;
}
ctx->pos++; if (cfid) {
mutex_lock(&cfid->dirents.de_mutex);
update_cached_dirents_count(&cfid->dirents, file);
mutex_unlock(&cfid->dirents.de_mutex);
}
if (ctx->pos ==
cifsFile->srch_inf.index_of_last_entry) {
cifs_dbg(FYI, "last entry in buf at pos %lld %s\n",
ctx->pos, tmp_buf);
cifs_save_resume_key(current_entry, cifsFile); break;
}
current_entry =
nxt_dir_entry(current_entry, end_of_smb,
cifsFile->srch_inf.info_level);
}
kfree(tmp_buf);
rddir2_exit: if (cfid)
close_cached_dir(cfid);
free_dentry_path(page);
free_xid(xid); return rc;
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.33 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.