linux/fs/afs/dynroot.c
Linus Torvalds 9483c37e2d vfs-6.15-rc1.afs
-----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCZ90sDQAKCRCRxhvAZXjc
 ooUnAQCaXv5U0GaEwkCcW78vw/dk7jyFG5LlrGUMvZV8MBSvuAEAsaPvU2uM6ZNf
 743B8zOopOeX3Nwy8UKRcHk1nO1m5AY=
 =NoZe
 -----END PGP SIGNATURE-----

Merge tag 'vfs-6.15-rc1.afs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull vfs afs updates from Christian Brauner:
 "This contains the work for afs for this cycle:

   - Fix an occasional hang that's only really encountered when
     rmmod'ing the kafs module

   - Remove the "-o autocell" mount option. This is obsolete with the
     dynamic root and removing it makes the next patch slightly easier

   - Change how the dynamic root mount is constructed. Currently, the
     root directory is (de)populated when it is (un)mounted if there are
     cells already configured and, further, pairs of automount points
     have to be created/removed each time a cell is added/deleted

     This is changed so that readdir on the root dir lists all the known
     cell automount pairs plus the @cell symlinks and the inodes and
     dentries are constructed by lookup on demand. This simplifies the
     cell management code

   - A few improvements to the afs_volume and afs_server tracepoints

   - Pass trace info into the afs_lookup_cell() function to allow the
     trace log to indicate the purpose of the lookup

   - Remove the 'net' parameter from afs_unuse_cell() as it's
     superfluous

   - In rxrpc, allow a kernel app (such as kafs) to store a word of
     information on rxrpc_peer records

   - Use the information stored on the rxrpc_peer record to point to the
     afs_server record. This allows the server address lookup to be done
     away with

   - Simplify the afs_server ref/activity accounting to make each one
     self-contained and not garbage collected from the cell management
     work item

   - Simplify the afs_cell ref/activity accounting to make each one of
     these also self-contained and not driven by a central management
     work item

     The current code was intended to make it such that a single timer
     for the namespace and one work item per cell could do all the work
     required to maintain these records. This, however, made for some
     sequencing problems when cleaning up these records. Further, the
     attempt to pass refs along with timers and work items made getting
     it right rather tricky when the timer or work item already had a
     ref attached and now a ref had to be got rid of"

* tag 'vfs-6.15-rc1.afs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  afs: Simplify cell record handling
  afs: Fix afs_server ref accounting
  afs: Use the per-peer app data provided by rxrpc
  rxrpc: Allow the app to store private data on peer structs
  afs: Drop the net parameter from afs_unuse_cell()
  afs: Make afs_lookup_cell() take a trace note
  afs: Improve server refcount/active count tracing
  afs: Improve afs_volume tracing to display a debug ID
  afs: Change dynroot to create contents on demand
  afs: Remove the "autocell" mount option
2025-03-24 13:15:16 -07:00

406 lines
9.8 KiB
C

// 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=%lu, vl=%llx, vn=%llx, u=%x }",
inode, inode->i_ino, fid.vid, fid.vnode, fid.unique);
vnode = AFS_FS_I(inode);
if (inode->i_state & 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, false,
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->i_state & 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 the RCU read 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) {
rcu_read_lock();
ret = afs_dynroot_readdir_cells(net, ctx);
rcu_read_unlock();
}
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->i_state & 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;
}