279 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Contains mounting routines used for handling traversal via SMB junctions.4 *5 * Copyright (c) 2007 Igor Mammedov6 * Copyright (C) International Business Machines Corp., 20087 * Author(s): Igor Mammedov (niallain@gmail.com)8 * Steve French (sfrench@us.ibm.com)9 * Copyright (c) 2023 Paulo Alcantara <palcantara@suse.de>10 */11 12#include <linux/dcache.h>13#include <linux/mount.h>14#include <linux/namei.h>15#include <linux/slab.h>16#include <linux/vfs.h>17#include <linux/fs.h>18#include <linux/inet.h>19#include "cifsglob.h"20#include "cifsproto.h"21#include "cifsfs.h"22#include "cifs_debug.h"23#include "fs_context.h"24 25static LIST_HEAD(cifs_automount_list);26 27static void cifs_expire_automounts(struct work_struct *work);28static DECLARE_DELAYED_WORK(cifs_automount_task,29 cifs_expire_automounts);30static int cifs_mountpoint_expiry_timeout = 500 * HZ;31 32static void cifs_expire_automounts(struct work_struct *work)33{34 struct list_head *list = &cifs_automount_list;35 36 mark_mounts_for_expiry(list);37 if (!list_empty(list))38 schedule_delayed_work(&cifs_automount_task,39 cifs_mountpoint_expiry_timeout);40}41 42void cifs_release_automount_timer(void)43{44 if (WARN_ON(!list_empty(&cifs_automount_list)))45 return;46 cancel_delayed_work_sync(&cifs_automount_task);47}48 49/**50 * cifs_build_devname - build a devicename from a UNC and optional prepath51 * @nodename: pointer to UNC string52 * @prepath: pointer to prefixpath (or NULL if there isn't one)53 *54 * Build a new cifs devicename after chasing a DFS referral. Allocate a buffer55 * big enough to hold the final thing. Copy the UNC from the nodename, and56 * concatenate the prepath onto the end of it if there is one.57 *58 * Returns pointer to the built string, or a ERR_PTR. Caller is responsible59 * for freeing the returned string.60 */61char *62cifs_build_devname(char *nodename, const char *prepath)63{64 size_t pplen;65 size_t unclen;66 char *dev;67 char *pos;68 69 /* skip over any preceding delimiters */70 nodename += strspn(nodename, "\\");71 if (!*nodename)72 return ERR_PTR(-EINVAL);73 74 /* get length of UNC and set pos to last char */75 unclen = strlen(nodename);76 pos = nodename + unclen - 1;77 78 /* trim off any trailing delimiters */79 while (*pos == '\\') {80 --pos;81 --unclen;82 }83 84 /* allocate a buffer:85 * +2 for preceding "//"86 * +1 for delimiter between UNC and prepath87 * +1 for trailing NULL88 */89 pplen = prepath ? strlen(prepath) : 0;90 dev = kmalloc(2 + unclen + 1 + pplen + 1, GFP_KERNEL);91 if (!dev)92 return ERR_PTR(-ENOMEM);93 94 pos = dev;95 /* add the initial "//" */96 *pos = '/';97 ++pos;98 *pos = '/';99 ++pos;100 101 /* copy in the UNC portion from referral */102 memcpy(pos, nodename, unclen);103 pos += unclen;104 105 /* copy the prefixpath remainder (if there is one) */106 if (pplen) {107 *pos = '/';108 ++pos;109 memcpy(pos, prepath, pplen);110 pos += pplen;111 }112 113 /* NULL terminator */114 *pos = '\0';115 116 convert_delimiter(dev, '/');117 return dev;118}119 120static bool is_dfs_mount(struct dentry *dentry)121{122 struct cifs_sb_info *cifs_sb = CIFS_SB(dentry->d_sb);123 struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);124 bool ret;125 126 spin_lock(&tcon->tc_lock);127 ret = !!tcon->origin_fullpath;128 spin_unlock(&tcon->tc_lock);129 return ret;130}131 132/* Return full path out of a dentry set for automount */133static char *automount_fullpath(struct dentry *dentry, void *page)134{135 struct cifs_sb_info *cifs_sb = CIFS_SB(dentry->d_sb);136 struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);137 size_t len;138 char *s;139 140 spin_lock(&tcon->tc_lock);141 if (!tcon->origin_fullpath) {142 spin_unlock(&tcon->tc_lock);143 return build_path_from_dentry_optional_prefix(dentry,144 page,145 true);146 }147 spin_unlock(&tcon->tc_lock);148 149 s = dentry_path_raw(dentry, page, PATH_MAX);150 if (IS_ERR(s))151 return s;152 /* for root, we want "" */153 if (!s[1])154 s++;155 156 spin_lock(&tcon->tc_lock);157 len = strlen(tcon->origin_fullpath);158 if (s < (char *)page + len) {159 spin_unlock(&tcon->tc_lock);160 return ERR_PTR(-ENAMETOOLONG);161 }162 163 s -= len;164 memcpy(s, tcon->origin_fullpath, len);165 spin_unlock(&tcon->tc_lock);166 convert_delimiter(s, '/');167 168 return s;169}170 171static void fs_context_set_ids(struct smb3_fs_context *ctx)172{173 kuid_t uid = current_fsuid();174 kgid_t gid = current_fsgid();175 176 if (ctx->multiuser) {177 if (!ctx->uid_specified)178 ctx->linux_uid = uid;179 if (!ctx->gid_specified)180 ctx->linux_gid = gid;181 }182 if (!ctx->cruid_specified)183 ctx->cred_uid = uid;184}185 186/*187 * Create a vfsmount that we can automount188 */189static struct vfsmount *cifs_do_automount(struct path *path)190{191 int rc;192 struct dentry *mntpt = path->dentry;193 struct fs_context *fc;194 void *page = NULL;195 struct smb3_fs_context *ctx, *cur_ctx;196 struct smb3_fs_context tmp;197 char *full_path;198 struct vfsmount *mnt;199 200 if (IS_ROOT(mntpt))201 return ERR_PTR(-ESTALE);202 203 cur_ctx = CIFS_SB(mntpt->d_sb)->ctx;204 205 fc = fs_context_for_submount(path->mnt->mnt_sb->s_type, mntpt);206 if (IS_ERR(fc))207 return ERR_CAST(fc);208 209 ctx = smb3_fc2context(fc);210 211 page = alloc_dentry_path();212 full_path = automount_fullpath(mntpt, page);213 if (IS_ERR(full_path)) {214 mnt = ERR_CAST(full_path);215 goto out;216 }217 218 tmp = *cur_ctx;219 tmp.source = NULL;220 tmp.leaf_fullpath = NULL;221 tmp.UNC = tmp.prepath = NULL;222 tmp.dfs_root_ses = NULL;223 fs_context_set_ids(&tmp);224 225 rc = smb3_fs_context_dup(ctx, &tmp);226 if (rc) {227 mnt = ERR_PTR(rc);228 goto out;229 }230 231 rc = smb3_parse_devname(full_path, ctx);232 if (rc) {233 mnt = ERR_PTR(rc);234 goto out;235 }236 237 ctx->source = smb3_fs_context_fullpath(ctx, '/');238 if (IS_ERR(ctx->source)) {239 mnt = ERR_CAST(ctx->source);240 ctx->source = NULL;241 goto out;242 }243 ctx->dfs_automount = ctx->dfs_conn = is_dfs_mount(mntpt);244 cifs_dbg(FYI, "%s: ctx: source=%s UNC=%s prepath=%s dfs_automount=%d\n",245 __func__, ctx->source, ctx->UNC, ctx->prepath, ctx->dfs_automount);246 247 mnt = fc_mount(fc);248out:249 put_fs_context(fc);250 free_dentry_path(page);251 return mnt;252}253 254/*255 * Attempt to automount the referral256 */257struct vfsmount *cifs_d_automount(struct path *path)258{259 struct vfsmount *newmnt;260 261 cifs_dbg(FYI, "%s: %pd\n", __func__, path->dentry);262 263 newmnt = cifs_do_automount(path);264 if (IS_ERR(newmnt)) {265 cifs_dbg(FYI, "leaving %s [automount failed]\n" , __func__);266 return newmnt;267 }268 269 mntget(newmnt); /* prevent immediate expiration */270 mnt_set_expiry(newmnt, &cifs_automount_list);271 schedule_delayed_work(&cifs_automount_task,272 cifs_mountpoint_expiry_timeout);273 cifs_dbg(FYI, "leaving %s [ok]\n" , __func__);274 return newmnt;275}276 277const struct inode_operations cifs_namespace_inode_operations = {278};279