// SPDX-License-Identifier: GPL-2.0
/*
* Functions to handle the cached directory entries
*
* Copyright (c) 2022, Ronnie Sahlberg <lsahlber@redhat.com>
*/
#include <linux/namei.h>
#include "cifsglob.h"
#include "cifsproto.h"
#include "cifs_debug.h"
#include "smb2proto.h"
#include "cached_dir.h"
static struct cached_fid *init_cached_dir(const char *path);
static void free_cached_dir(struct cached_fid *cfid);
static void smb2_close_cached_fid(struct kref *ref);
static void cfids_laundromat_worker(struct work_struct *work);
static void close_cached_dir_locked(struct cached_fid *cfid);
struct cached_dir_dentry {
struct list_head entry;
struct dentry *dentry;
};
static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
const char *path,
bool lookup_only,
__u32 max_cached_dirs)
{
struct cached_fid *cfid;
list_for_each_entry(cfid, &cfids->entries, entry) {
if (!strcmp(cfid->path, path)) {
/*
* If it doesn't have a lease it is either not yet
* fully cached or it may be in the process of
* being deleted due to a lease break.
*/
if (!is_valid_cached_dir(cfid))
return NULL;
kref_get(&cfid->refcount);
return cfid;
}
}
if (lookup_only) {
return NULL;
}
if (cfids->num_entries >= max_cached_dirs) {
return NULL;
}
cfid = init_cached_dir(path);
if (cfid == NULL) {
return NULL;
}
cfid->cfids = cfids;
cfids->num_entries++;
list_add(&cfid->entry, &cfids->entries);
cfid->on_list = true;
kref_get(&cfid->refcount);
/*
* Set @cfid->has_lease to true during construction so that the lease
* reference can be put in cached_dir_lease_break() due to a potential
* lease break right after the request is sent or while @cfid is still
* being cached, or if a reconnection is triggered during construction.
* Concurrent processes won't be to use it yet due to @cfid->time being
* zero.
*/
cfid->has_lease = true;
return cfid;
}
static struct dentry *
path_to_dentry(struct cifs_sb_info *cifs_sb, const char *path)
{
struct dentry *dentry;
const char *s, *p;
char sep;
sep = CIFS_DIR_SEP(cifs_sb);
dentry = dget(cifs_sb->root);
s = path;
do {
struct inode *dir = d_inode(dentry);
struct dentry *child;
if (!S_ISDIR(dir->i_mode)) {
dput(dentry);
dentry = ERR_PTR(-ENOTDIR);
break;
}
/* skip separators */
while (*s == sep)
s++;
if (!*s)
break;
p = s++;
/* next separator */
while (*s && *s != sep)
s++;
child = lookup_noperm_positive_unlocked(&QSTR_LEN(p, s - p),
dentry);
dput(dentry);
dentry = child;
} while (!IS_ERR(dentry));
return dentry;
}
static const char *path_no_prefix(struct cifs_sb_info *cifs_sb,
const char *path)
{
size_t len = 0;
if (!*path)
return path;
if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_USE_PREFIX_PATH) &&
cifs_sb->prepath) {
len = strlen(cifs_sb->prepath) + 1;
if (unlikely(len > strlen(path)))
return ERR_PTR(-EINVAL);
}
return path + len;
}
/*
* Open the and cache a directory handle.
* If error then *cfid is not initialized.
*/
int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
const char *path,
struct cifs_sb_info *cifs_sb,
bool lookup_only, struct cached_fid **ret_cfid)
{
struct cifs_ses *ses;
struct TCP_Server_Info *server;
struct cifs_open_parms oparms;
struct smb2_create_rsp *o_rsp = NULL;
struct smb2_query_info_rsp *qi_rsp = NULL;
int resp_buftype[2];
struct smb_rqst rqst[2];
struct kvec rsp_iov[2];
struct kvec open_iov[SMB2_CREATE_IOV_SIZE];
struct kvec qi_iov[1];
int rc, flags = 0;
__le16 *utf16_path = NULL;
u8 oplock = SMB2_OPLOCK_LEVEL_II;
struct cifs_fid *pfid;
struct dentry *dentry = NULL;
struct cached_fid *cfid;
struct cached_fids *cfids;
const char *npath;
int retries = 0, cur_sleep = 1;
__le32 lease_flags = 0;
if (cifs_sb->root == NULL)
return -ENOENT;
if (tcon == NULL)
return