406 lines · 9.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* AFS dynamic root handling * * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) */ #include <linux/fs.h>#include <linux/namei.h>#include <linux/dns_resolver.h>#include "internal.h" #define AFS_MIN_DYNROOT_CELL_INO 4 /* Allow for ., .., @cell, .@cell */#define AFS_MAX_DYNROOT_CELL_INO ((unsigned int)INT_MAX) static struct dentry *afs_lookup_atcell(struct inode *dir, struct dentry *dentry, ino_t ino); /* * iget5() comparator for inode created by autocell operations */static int afs_iget5_pseudo_test(struct inode *inode, void *opaque){	struct afs_fid *fid = opaque; 	return inode->i_ino == fid->vnode;} /* * iget5() inode initialiser */static int afs_iget5_pseudo_set(struct inode *inode, void *opaque){	struct afs_super_info *as = AFS_FS_S(inode->i_sb);	struct afs_vnode *vnode = AFS_FS_I(inode);	struct afs_fid *fid = opaque; 	vnode->volume		= as->volume;	vnode->fid		= *fid;	inode->i_ino		= fid->vnode;	inode->i_generation	= fid->unique;	return 0;} /* * Create an inode for an autocell dynamic automount dir. */static struct inode *afs_iget_pseudo_dir(struct super_block *sb, ino_t ino){	struct afs_vnode *vnode;	struct inode *inode;	struct afs_fid fid = { .vnode = ino, .unique = 1, }; 	_enter(""); 	inode = iget5_locked(sb, fid.vnode,			     afs_iget5_pseudo_test, afs_iget5_pseudo_set, &fid);	if (!inode) {		_leave(" = -ENOMEM");		return ERR_PTR(-ENOMEM);	} 	_debug("GOT INODE %p { ino=%llu, vl=%llx, vn=%llx, u=%x }",	       inode, inode->i_ino, fid.vid, fid.vnode, fid.unique); 	vnode = AFS_FS_I(inode); 	if (inode_state_read_once(inode) & I_NEW) {		netfs_inode_init(&vnode->netfs, NULL, false);		simple_inode_init_ts(inode);		set_nlink(inode, 2);		inode->i_size		= 0;		inode->i_mode		= S_IFDIR | 0555;		inode->i_op		= &afs_autocell_inode_operations;		inode->i_uid		= GLOBAL_ROOT_UID;		inode->i_gid		= GLOBAL_ROOT_GID;		inode->i_blocks		= 0;		inode->i_generation	= 0;		inode->i_flags		|= S_AUTOMOUNT | S_NOATIME; 		set_bit(AFS_VNODE_PSEUDODIR, &vnode->flags);		set_bit(AFS_VNODE_MOUNTPOINT, &vnode->flags); 		unlock_new_inode(inode);	}	_leave(" = %p", inode);	return inode;} /* * Try to automount the mountpoint with pseudo directory, if the autocell * option is set. */static struct dentry *afs_dynroot_lookup_cell(struct inode *dir, struct dentry *dentry,					      unsigned int flags){	struct afs_cell *cell = NULL;	struct afs_net *net = afs_d2net(dentry);	struct inode *inode = NULL;	const char *name = dentry->d_name.name;	size_t len = dentry->d_name.len;	bool dotted = false;	int ret = -ENOENT; 	/* Names prefixed with a dot are R/W mounts. */	if (name[0] == '.') {		name++;		len--;		dotted = true;	} 	cell = afs_lookup_cell(net, name, len, NULL,			       AFS_LOOKUP_CELL_DYNROOT,			       afs_cell_trace_use_lookup_dynroot);	if (IS_ERR(cell)) {		ret = PTR_ERR(cell);		goto out_no_cell;	} 	inode = afs_iget_pseudo_dir(dir->i_sb, cell->dynroot_ino * 2 + dotted);	if (IS_ERR(inode)) {		ret = PTR_ERR(inode);		goto out;	} 	dentry->d_fsdata = cell;	return d_splice_alias(inode, dentry); out:	afs_unuse_cell(cell, afs_cell_trace_unuse_lookup_dynroot);out_no_cell:	if (!inode)		return d_splice_alias(inode, dentry);	return ret == -ENOENT ? NULL : ERR_PTR(ret);} /* * Look up an entry in a dynroot directory. */static struct dentry *afs_dynroot_lookup(struct inode *dir, struct dentry *dentry,					 unsigned int flags){	_enter("%pd", dentry); 	if (flags & LOOKUP_CREATE)		return ERR_PTR(-EOPNOTSUPP); 	if (dentry->d_name.len >= AFSNAMEMAX) {		_leave(" = -ENAMETOOLONG");		return ERR_PTR(-ENAMETOOLONG);	} 	if (dentry->d_name.len == 5 &&	    memcmp(dentry->d_name.name, "@cell", 5) == 0)		return afs_lookup_atcell(dir, dentry, 2); 	if (dentry->d_name.len == 6 &&	    memcmp(dentry->d_name.name, ".@cell", 6) == 0)		return afs_lookup_atcell(dir, dentry, 3); 	return afs_dynroot_lookup_cell(dir, dentry, flags);} const struct inode_operations afs_dynroot_inode_operations = {	.lookup		= afs_dynroot_lookup,}; static void afs_dynroot_d_release(struct dentry *dentry){	struct afs_cell *cell = dentry->d_fsdata; 	afs_unuse_cell(cell, afs_cell_trace_unuse_dynroot_mntpt);} /* * Keep @cell symlink dentries around, but only keep cell autodirs when they're * being used. */static int afs_dynroot_delete_dentry(const struct dentry *dentry){	const struct qstr *name = &dentry->d_name; 	if (name->len == 5 && memcmp(name->name, "@cell", 5) == 0)		return 0;	if (name->len == 6 && memcmp(name->name, ".@cell", 6) == 0)		return 0;	return 1;} const struct dentry_operations afs_dynroot_dentry_operations = {	.d_delete	= afs_dynroot_delete_dentry,	.d_release	= afs_dynroot_d_release,	.d_automount	= afs_d_automount,}; static void afs_atcell_delayed_put_cell(void *arg){	struct afs_cell *cell = arg; 	afs_put_cell(cell, afs_cell_trace_put_atcell);} /* * Read @cell or .@cell symlinks. */static const char *afs_atcell_get_link(struct dentry *dentry, struct inode *inode,				       struct delayed_call *done){	struct afs_vnode *vnode = AFS_FS_I(inode);	struct afs_cell *cell;	struct afs_net *net = afs_i2net(inode);	const char *name;	bool dotted = vnode->fid.vnode == 3; 	if (!rcu_access_pointer(net->ws_cell))		return ERR_PTR(-ENOENT); 	if (!dentry) {		/* We're in RCU-pathwalk. */		cell = rcu_dereference(net->ws_cell);		if (dotted)			name = cell->name - 1;		else			name = cell->name;		/* Shouldn't need to set a delayed call. */		return name;	} 	down_read(&net->cells_lock); 	cell = rcu_dereference_protected(net->ws_cell, lockdep_is_held(&net->cells_lock));	if (dotted)		name = cell->name - 1;	else		name = cell->name;	afs_get_cell(cell, afs_cell_trace_get_atcell);	set_delayed_call(done, afs_atcell_delayed_put_cell, cell); 	up_read(&net->cells_lock);	return name;} static const struct inode_operations afs_atcell_inode_operations = {	.get_link	= afs_atcell_get_link,}; /* * Create an inode for the @cell or .@cell symlinks. */static struct dentry *afs_lookup_atcell(struct inode *dir, struct dentry *dentry, ino_t ino){	struct afs_vnode *vnode;	struct inode *inode;	struct afs_fid fid = { .vnode = ino, .unique = 1, }; 	inode = iget5_locked(dir->i_sb, fid.vnode,			     afs_iget5_pseudo_test, afs_iget5_pseudo_set, &fid);	if (!inode)		return ERR_PTR(-ENOMEM); 	vnode = AFS_FS_I(inode); 	if (inode_state_read_once(inode) & I_NEW) {		netfs_inode_init(&vnode->netfs, NULL, false);		simple_inode_init_ts(inode);		set_nlink(inode, 1);		inode->i_size		= 0;		inode->i_mode		= S_IFLNK | 0555;		inode->i_op		= &afs_atcell_inode_operations;		inode->i_uid		= GLOBAL_ROOT_UID;		inode->i_gid		= GLOBAL_ROOT_GID;		inode->i_blocks		= 0;		inode->i_generation	= 0;		inode->i_flags		|= S_NOATIME; 		unlock_new_inode(inode);	}	return d_splice_alias(inode, dentry);} /* * Transcribe the cell database into readdir content under net->cells_lock. * Each cell produces two entries, one prefixed with a dot and one not. */static int afs_dynroot_readdir_cells(struct afs_net *net, struct dir_context *ctx){	const struct afs_cell *cell;	loff_t newpos; 	_enter("%llu", ctx->pos); 	for (;;) {		unsigned int ix = ctx->pos >> 1; 		cell = idr_get_next(&net->cells_dyn_ino, &ix);		if (!cell)			return 0;		if (READ_ONCE(cell->state) == AFS_CELL_REMOVING ||		    READ_ONCE(cell->state) == AFS_CELL_DEAD) {			ctx->pos += 2;			ctx->pos &= ~1;			continue;		} 		newpos = ix << 1;		if (newpos > ctx->pos)			ctx->pos = newpos; 		_debug("pos %llu -> cell %u", ctx->pos, cell->dynroot_ino); 		if ((ctx->pos & 1) == 0) {			if (!dir_emit(ctx, cell->name, cell->name_len,				      cell->dynroot_ino, DT_DIR))				return 0;			ctx->pos++;		}		if ((ctx->pos & 1) == 1) {			if (!dir_emit(ctx, cell->name - 1, cell->name_len + 1,				      cell->dynroot_ino + 1, DT_DIR))				return 0;			ctx->pos++;		}	}	return 0;} /* * Read the AFS dynamic root directory.  This produces a list of cellnames, * dotted and undotted, along with @cell and .@cell links if configured. */static int afs_dynroot_readdir(struct file *file, struct dir_context *ctx){	struct afs_net *net = afs_d2net(file->f_path.dentry);	int ret = 0; 	if (!dir_emit_dots(file, ctx))		return 0; 	if (ctx->pos == 2) {		if (rcu_access_pointer(net->ws_cell) &&		    !dir_emit(ctx, "@cell", 5, 2, DT_LNK))			return 0;		ctx->pos = 3;	}	if (ctx->pos == 3) {		if (rcu_access_pointer(net->ws_cell) &&		    !dir_emit(ctx, ".@cell", 6, 3, DT_LNK))			return 0;		ctx->pos = 4;	} 	if ((unsigned long long)ctx->pos <= AFS_MAX_DYNROOT_CELL_INO) {		down_read(&net->cells_lock);		ret = afs_dynroot_readdir_cells(net, ctx);		up_read(&net->cells_lock);	}	return ret;} static const struct file_operations afs_dynroot_file_operations = {	.llseek		= generic_file_llseek,	.read		= generic_read_dir,	.iterate_shared	= afs_dynroot_readdir,	.fsync		= noop_fsync,}; /* * Create an inode for a dynamic root directory. */struct inode *afs_dynroot_iget_root(struct super_block *sb){	struct afs_super_info *as = AFS_FS_S(sb);	struct afs_vnode *vnode;	struct inode *inode;	struct afs_fid fid = { .vid = 0, .vnode = 1, .unique = 1,}; 	if (as->volume)		fid.vid = as->volume->vid; 	inode = iget5_locked(sb, fid.vnode,			     afs_iget5_pseudo_test, afs_iget5_pseudo_set, &fid);	if (!inode)		return ERR_PTR(-ENOMEM); 	vnode = AFS_FS_I(inode); 	/* there shouldn't be an existing inode */	if (inode_state_read_once(inode) & I_NEW) {		netfs_inode_init(&vnode->netfs, NULL, false);		simple_inode_init_ts(inode);		set_nlink(inode, 2);		inode->i_size		= 0;		inode->i_mode		= S_IFDIR | 0555;		inode->i_op		= &afs_dynroot_inode_operations;		inode->i_fop		= &afs_dynroot_file_operations;		inode->i_uid		= GLOBAL_ROOT_UID;		inode->i_gid		= GLOBAL_ROOT_GID;		inode->i_blocks		= 0;		inode->i_generation	= 0;		inode->i_flags		|= S_NOATIME; 		set_bit(AFS_VNODE_PSEUDODIR, &vnode->flags);		unlock_new_inode(inode);	}	_leave(" = %p", inode);	return inode;}