From 92520bbeef256957c150df01f946ad8890140a78 Mon Sep 17 00:00:00 2001
From: Aurelien Aptel <aaptel@suse.com>
Date: Thu, 5 Nov 2015 15:24:20 +0100
Subject: [PATCH] cifs: make shares unaccessible at root level mountable
Based on Shirish Pargaonkar's disconnected root patch [1].
The initial patch was modified to create a disconnected root when any
intermediary path is unaccessible (EACCESS) but *also* when "not
found" (ENOENT).
The other change was to modify build_path_from_dentry() in dir.c so that
if it has to build a path for a dentry that uses a disconnected root, it
prefixes that path with the disconnected root actual path.
Previously, when called with a dentry that had a disconnected root,
build_path_path() would stop when it reached that disconnected root,
which is not the same as the root of the share, resulting in a wrong
output path.
1: https://bugzilla.samba.org/show_bug.cgi?id=8950
Signed-off-by: Aurelien Aptel <aaptel@suse.com>
---
fs/cifs/cifs_fs_sb.h | 10 +++
fs/cifs/cifsfs.c | 84 +++++++++++++++++++-
fs/cifs/cifsproto.h | 1 +
fs/cifs/connect.c | 3 +
fs/cifs/dir.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++-----
fs/cifs/inode.c | 36 ++++++++-
6 files changed, 325 insertions(+), 26 deletions(-)
@@ -67,5 +67,15 @@ struct cifs_sb_info {
struct backing_dev_info bdi;
struct delayed_work prune_tlinks;
struct rcu_head rcu;
+ struct list_head rtdislist; /* list of disconnected root dentries */
+ spinlock_t rtdislock; /* lock for disconnected root dentry list */
+};
+
+struct cifs_rdelem {
+ int rdcount;
+ struct list_head rdlist;
+ char * rdname;
+ struct dentry * rdentry;
+ struct inode * rdinode;
};
#endif /* _CIFS_FS_SB_H */
@@ -584,6 +584,80 @@ static const struct super_operations cifs_super_ops = {
#endif
};
+void
+cifs_free_rdelem(struct cifs_rdelem *rdelem)
+{
+ kfree(rdelem->rdname);
+ kfree(rdelem);
+}
+
+struct cifs_rdelem *
+cifs_alloc_rdelem(char *full_path, struct dentry *rdentry,
+ struct inode *rdinode)
+{
+ struct cifs_rdelem *rdelem;
+ rdelem = kmalloc(sizeof(struct cifs_rdelem), GFP_KERNEL);
+ if (!rdelem) {
+ cifs_dbg(FYI, "%s Can't allocate root dentry\n", __func__);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ rdelem->rdname = kstrdup(full_path, GFP_KERNEL);
+
+ if (!rdelem->rdname) {
+ cifs_dbg(FYI, "%s Can't allocate root dentry name\n", __func__);
+ kfree(rdelem);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ rdelem->rdinode = rdinode;
+ rdelem->rdentry = rdentry;
+
+ return rdelem;
+}
+
+static struct dentry *
+create_root_dis_dentry(struct super_block *sb, struct inode *rinode,
+ char *fpath)
+{
+ int rc;
+ unsigned int xid;
+ struct dentry *dentry = NULL;
+ struct cifs_rdelem *rdelem = NULL;
+ struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
+ struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+
+ xid = get_xid();
+ if (tcon->unix_ext)
+ rc = cifs_get_inode_info_unix(&rinode, fpath, sb, xid);
+ else
+ rc = cifs_get_inode_info(&rinode, fpath, NULL, sb, xid, NULL);
+ free_xid(xid);
+
+ if ((rc == 0) && (rinode != NULL)) {
+ dentry = d_obtain_alias(rinode);
+ if (IS_ERR(dentry)) {
+ iput(rinode);
+ goto rdelem_ret;
+ }
+
+ if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)) {
+
+ rdelem =
+ cifs_alloc_rdelem(fpath, dentry, rinode);
+ if (IS_ERR(rdelem))
+ goto rdelem_ret;
+ spin_lock(&cifs_sb->rtdislock);
+ list_add(&rdelem->rdlist, &cifs_sb->rtdislist);
+ spin_unlock(&cifs_sb->rtdislock);
+ }
+ return dentry;
+ }
+
+rdelem_ret:
+ return ERR_PTR(-EACCES);
+}
+
/*
* Get root dentry from superblock according to prefix path mount option.
* Return dentry with refcount + 1 on success and NULL otherwise.
@@ -596,9 +670,10 @@ cifs_get_root(struct smb_vol *vol, struct super_block *sb)
char *full_path = NULL;
char *s, *p;
char sep;
+ struct inode *rinode = NULL;
+ struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
- full_path = cifs_build_path_to_root(vol, cifs_sb,
- cifs_sb_master_tcon(cifs_sb));
+ full_path = cifs_build_path_to_root(vol, cifs_sb, tcon);
if (full_path == NULL)
return ERR_PTR(-ENOMEM);
@@ -639,6 +714,11 @@ cifs_get_root(struct smb_vol *vol, struct super_block *sb)
dput(dentry);
dentry = child;
} while (!IS_ERR(dentry));
+
+ if (IS_ERR(dentry) && (PTR_ERR(dentry) == -EACCES || PTR_ERR(dentry) == -ENOENT) && *s) {
+ dentry = create_root_dis_dentry(sb, rinode, full_path);
+ }
+
kfree(full_path);
return dentry;
}
@@ -205,6 +205,7 @@ extern void cifs_add_pending_open_locked(struct cifs_fid *fid,
struct tcon_link *tlink,
struct cifs_pending_open *open);
extern void cifs_del_pending_open(struct cifs_pending_open *open);
+extern void cifs_free_rdelem(struct cifs_rdelem *);
#if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL)
extern void cifs_dfs_release_automount_timer(void);
@@ -3190,6 +3190,9 @@ void cifs_setup_cifs_sb(struct smb_vol *pvolume_info,
spin_lock_init(&cifs_sb->tlink_tree_lock);
cifs_sb->tlink_tree = RB_ROOT;
+ spin_lock_init(&cifs_sb->rtdislock);
+ INIT_LIST_HEAD(&cifs_sb->rtdislist);
+
/*
* Temporarily set r/wsize for matching superblock. If we end up using
* new sb then client will later negotiate it downward if needed.
@@ -77,6 +77,9 @@ cifs_build_path_to_root(struct smb_vol *vol, struct cifs_sb_info *cifs_sb,
return full_path;
}
+static struct cifs_rdelem * find_rdelem_by_dentry_no_del(const struct dentry *rdentry,
+ struct cifs_sb_info * cifs_sb);
+
/* Note: caller must free return buffer */
char *
build_path_from_dentry(struct dentry *direntry)
@@ -89,6 +92,25 @@ build_path_from_dentry(struct dentry *direntry)
struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb);
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
unsigned seq;
+ struct cifs_rdelem *dis_root = NULL;
+ size_t dis_root_len = 0;
+
+ /*
+ * First look if the dentry has a disconnected root in its
+ * parent hierarchy. If that's the case we must append that
+ * root path as a prefix.
+ */
+
+ rcu_read_lock();
+ for (temp = direntry; temp && !IS_ROOT(temp); temp = temp->d_parent);
+ if (temp && (dis_root = find_rdelem_by_dentry_no_del(temp, cifs_sb)))
+ dis_root_len = strlen(dis_root->rdname);
+ rcu_read_unlock();
+
+ /*
+ * Now let's compute the length of the path so that we can
+ * allocate the right size
+ */
dirsep = CIFS_DIR_SEP(cifs_sb);
if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
@@ -108,8 +130,19 @@ cifs_bp_rename_retry:
return NULL;
}
}
+ if (dis_root && temp == dis_root->rdentry) {
+ /*
+ * Account for the disconnected root prefix
+ * Note: leading dir separator already in the length
+ */
+ namelen += dis_root_len;
+ }
rcu_read_unlock();
+ /*
+ * Next step is to actually fill the full_path string
+ */
+
full_path = kmalloc(namelen+1, GFP_KERNEL);
if (full_path == NULL)
return full_path;
@@ -136,7 +169,12 @@ cifs_bp_rename_retry:
return NULL;
}
}
+ if (dis_root && temp == dis_root->rdentry) {
+ namelen -= dis_root_len;
+ strncpy(full_path + namelen, dis_root->rdname, dis_root_len);
+ }
rcu_read_unlock();
+
if (namelen != dfsplen || read_seqretry(&rename_lock, seq)) {
cifs_dbg(FYI, "did not end path lookup where expected. namelen=%ddfsplen=%d\n",
namelen, dfsplen);
@@ -704,6 +742,73 @@ mknod_out:
return rc;
}
+static struct cifs_rdelem *
+find_rdelem_by_inode(struct inode *rdinode, struct cifs_sb_info * cifs_sb)
+{
+ struct cifs_rdelem *rdelem;
+ spin_lock(&cifs_sb->rtdislock);
+ list_for_each_entry(rdelem, &cifs_sb->rtdislist, rdlist) {
+ if (rdelem->rdinode == rdinode) {
+ list_del(&rdelem->rdlist);
+ spin_unlock(&cifs_sb->rtdislock);
+ return rdelem;
+ }
+ }
+ spin_unlock(&cifs_sb->rtdislock);
+ return NULL;
+}
+
+static struct cifs_rdelem *
+find_rdelem_by_dentry(const struct dentry *rdentry,
+ struct cifs_sb_info * cifs_sb)
+{
+ struct cifs_rdelem *rdelem;
+ spin_lock(&cifs_sb->rtdislock);
+ list_for_each_entry(rdelem, &cifs_sb->rtdislist, rdlist) {
+ if (rdelem->rdentry == rdentry) {
+ list_del(&rdelem->rdlist);
+ spin_unlock(&cifs_sb->rtdislock);
+ return rdelem;
+ }
+ }
+ spin_unlock(&cifs_sb->rtdislock);
+ return NULL;
+}
+
+static struct cifs_rdelem *
+find_rdelem_by_dentry_no_del(const struct dentry *rdentry,
+ struct cifs_sb_info * cifs_sb)
+{
+ struct cifs_rdelem *rdelem;
+ struct cifs_rdelem *found = NULL;
+ spin_lock(&cifs_sb->rtdislock);
+ list_for_each_entry(rdelem, &cifs_sb->rtdislist, rdlist) {
+ if (rdelem->rdentry == rdentry) {
+ found = rdelem;
+ break;
+ }
+ }
+ spin_unlock(&cifs_sb->rtdislock);
+ return found;
+}
+
+
+static void
+find_rdelem_by_path(char *full_path, struct inode **newInode,
+ struct cifs_sb_info * cifs_sb)
+{
+ struct cifs_rdelem *rdelem;
+ spin_lock(&cifs_sb->rtdislock);
+ list_for_each_entry(rdelem, &cifs_sb->rtdislist, rdlist) {
+ if (!strcmp(rdelem->rdname, full_path)) {
+ *newInode = ilookup(rdelem->rdinode->i_sb,
+ rdelem->rdinode->i_ino);
+ break;
+ }
+ }
+ spin_unlock(&cifs_sb->rtdislock);
+}
+
struct dentry *
cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry,
unsigned int flags)
@@ -715,6 +820,9 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry,
struct cifs_tcon *pTcon;
struct inode *newInode = NULL;
char *full_path = NULL;
+ struct dentry *ret = NULL;
+ struct cifs_rdelem *rdelem;
+ struct qstr dname;
xid = get_xid();
@@ -751,20 +859,66 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry,
}
cifs_dbg(FYI, "Full path: %s inode = 0x%p\n",
full_path, d_inode(direntry));
+ if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)) {
+ /*
+ * Looking for an existing disconnected root dentry if any,
+ * before sending out a lookup on the wire.
+ */
+ find_rdelem_by_path(full_path, &newInode, cifs_sb);
+ }
- 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 (!newInode) {
+ 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);
}
+ /* else, found an anonymous root dentry with an inode */
+
+
if ((rc == 0) && (newInode != NULL)) {
- d_add(direntry, newInode);
+
/* since paths are not looked up by component - the parent
directories are presumed to be good here */
- renew_parental_timestamps(direntry);
+ if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)) {
+ dname.name = direntry->d_name.name;
+ dname.len = strlen(direntry->d_name.name) + 1;
+ /*
+ * Perhaps another lookup beat us to this.
+ */
+ spin_lock(&cifs_sb->rtdislock);
+ ret = d_lookup(direntry->d_parent, &dname);
+ if (ret && !IS_ERR(ret)) {
+ dput(ret);
+ spin_unlock(&cifs_sb->rtdislock);
+ goto lookup_out;
+ } else
+ ret = d_splice_alias(newInode, direntry);
+ spin_unlock(&cifs_sb->rtdislock);
+ } else
+ ret = d_splice_alias(newInode, direntry);
+ if (!ret)
+ renew_parental_timestamps(direntry);
+
+ else {
+ if (!IS_ERR(ret)) {
+ if (!(cifs_sb->mnt_cifs_flags &
+ CIFS_MOUNT_SERVER_INUM)) {
+ rdelem =
+ find_rdelem_by_inode(newInode, cifs_sb);
+ if (rdelem)
+ cifs_free_rdelem(rdelem);
+ }
+ renew_parental_timestamps(ret);
+ dput(ret);
+ goto lookup_out;
+ } else
+ rc = PTR_ERR(ret);
+ }
+
} else if (rc == -ENOENT) {
rc = 0;
@@ -777,12 +931,41 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry,
/* We special case check for Access Denied - since that
is a common return code */
}
-
+ ret = ERR_PTR(rc);
lookup_out:
kfree(full_path);
cifs_put_tlink(tlink);
free_xid(xid);
- return ERR_PTR(rc);
+ return ret;
+}
+
+static void
+cifs_d_common_releasedelete(const struct dentry *dentry)
+{
+ struct cifs_rdelem *rdelem;
+ struct cifs_sb_info *cifs_sb;
+
+ cifs_sb = CIFS_SB(dentry->d_sb);
+
+ /* disconnected root dentries that did not get spliced */
+ if (IS_ROOT(dentry) && dentry->d_flags & DCACHE_DISCONNECTED) {
+ rdelem = find_rdelem_by_dentry(dentry, cifs_sb);
+ if (rdelem)
+ cifs_free_rdelem(rdelem);
+ }
+}
+
+static int
+cifs_d_delete(const struct dentry *dentry)
+{
+ cifs_d_common_releasedelete(dentry);
+ return 0;
+}
+
+static void
+cifs_d_release(struct dentry *dentry)
+{
+ cifs_d_common_releasedelete(dentry);
}
static int
@@ -834,19 +1017,11 @@ cifs_d_revalidate(struct dentry *direntry, unsigned int flags)
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_dfs_d_automount,
-/* d_delete: cifs_d_delete, */ /* not needed except for debugging */
+ .d_delete = cifs_d_delete,
+ .d_release = cifs_d_release,
};
static int cifs_ci_hash(const struct dentry *dentry, struct qstr *q)
@@ -921,4 +1096,6 @@ const struct dentry_operations cifs_ci_dentry_ops = {
.d_hash = cifs_ci_hash,
.d_compare = cifs_ci_compare,
.d_automount = cifs_dfs_d_automount,
+ .d_delete = cifs_d_delete,
+ .d_release = cifs_d_release,
};
@@ -923,8 +923,10 @@ inode_has_hashed_dentries(struct inode *inode)
spin_lock(&inode->i_lock);
hlist_for_each_entry(dentry, &inode->i_dentry, d_u.d_alias) {
if (!d_unhashed(dentry) || IS_ROOT(dentry)) {
- spin_unlock(&inode->i_lock);
- return true;
+ if (!(dentry->d_flags & DCACHE_DISCONNECTED)) {
+ spin_unlock(&inode->i_lock);
+ return true;
+ }
}
}
spin_unlock(&inode->i_lock);
@@ -974,6 +976,23 @@ retry_iget5_locked:
return inode;
}
+void
+fill_phfattr(struct cifs_fattr *cf, struct super_block *sb)
+{
+
+ struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
+
+ memset(cf, 0, sizeof(*cf));
+ cf->cf_uniqueid = ROOT_I;
+ cf->cf_nlink = 1;
+ cf->cf_atime = CURRENT_TIME;
+ cf->cf_ctime = CURRENT_TIME;
+ cf->cf_mtime = CURRENT_TIME;
+ cf->cf_mode = S_IFDIR | S_IXUGO | S_IRWXU;
+ cf->cf_uid = cifs_sb->mnt_uid;
+ cf->cf_gid = cifs_sb->mnt_gid;
+}
+
/* gets root inode */
struct inode *cifs_root_iget(struct super_block *sb)
{
@@ -982,6 +1001,7 @@ struct inode *cifs_root_iget(struct super_block *sb)
struct inode *inode = NULL;
long rc;
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+ struct cifs_fattr phfattr;
xid = get_xid();
if (tcon->unix_ext) {
@@ -997,8 +1017,16 @@ struct inode *cifs_root_iget(struct super_block *sb)
iget_no_retry:
if (!inode) {
- inode = ERR_PTR(rc);
- goto out;
+ if (rc == -EACCES) {
+ fill_phfattr(&phfattr, sb);
+ inode = cifs_iget(sb, &phfattr);
+ if (inode)
+ rc = 0;
+ }
+ if (rc) {
+ inode = ERR_PTR(rc);
+ goto out;
+ }
}
#ifdef CONFIG_CIFS_FSCACHE
--
2.1.4