1b4d0d230SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 20da0b7fdSDavid Howells /* AFS dynamic root handling 366c7e1d3SDavid Howells * 466c7e1d3SDavid Howells * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved. 566c7e1d3SDavid Howells * Written by David Howells (dhowells@redhat.com) 666c7e1d3SDavid Howells */ 766c7e1d3SDavid Howells 866c7e1d3SDavid Howells #include <linux/fs.h> 966c7e1d3SDavid Howells #include <linux/namei.h> 1066c7e1d3SDavid Howells #include <linux/dns_resolver.h> 1166c7e1d3SDavid Howells #include "internal.h" 1266c7e1d3SDavid Howells 13e49c7b2fSDavid Howells static atomic_t afs_autocell_ino; 14e49c7b2fSDavid Howells 15e49c7b2fSDavid Howells /* 16e49c7b2fSDavid Howells * iget5() comparator for inode created by autocell operations 17e49c7b2fSDavid Howells * 18e49c7b2fSDavid Howells * These pseudo inodes don't match anything. 19e49c7b2fSDavid Howells */ 20e49c7b2fSDavid Howells static int afs_iget5_pseudo_test(struct inode *inode, void *opaque) 21e49c7b2fSDavid Howells { 22e49c7b2fSDavid Howells return 0; 23e49c7b2fSDavid Howells } 24e49c7b2fSDavid Howells 25e49c7b2fSDavid Howells /* 26e49c7b2fSDavid Howells * iget5() inode initialiser 27e49c7b2fSDavid Howells */ 28e49c7b2fSDavid Howells static int afs_iget5_pseudo_set(struct inode *inode, void *opaque) 29e49c7b2fSDavid Howells { 30e49c7b2fSDavid Howells struct afs_super_info *as = AFS_FS_S(inode->i_sb); 31e49c7b2fSDavid Howells struct afs_vnode *vnode = AFS_FS_I(inode); 32e49c7b2fSDavid Howells struct afs_fid *fid = opaque; 33e49c7b2fSDavid Howells 34e49c7b2fSDavid Howells vnode->volume = as->volume; 35e49c7b2fSDavid Howells vnode->fid = *fid; 36e49c7b2fSDavid Howells inode->i_ino = fid->vnode; 37e49c7b2fSDavid Howells inode->i_generation = fid->unique; 38e49c7b2fSDavid Howells return 0; 39e49c7b2fSDavid Howells } 40e49c7b2fSDavid Howells 41e49c7b2fSDavid Howells /* 42e49c7b2fSDavid Howells * Create an inode for a dynamic root directory or an autocell dynamic 43e49c7b2fSDavid Howells * automount dir. 44e49c7b2fSDavid Howells */ 45e49c7b2fSDavid Howells struct inode *afs_iget_pseudo_dir(struct super_block *sb, bool root) 46e49c7b2fSDavid Howells { 47e49c7b2fSDavid Howells struct afs_super_info *as = AFS_FS_S(sb); 48e49c7b2fSDavid Howells struct afs_vnode *vnode; 49e49c7b2fSDavid Howells struct inode *inode; 50e49c7b2fSDavid Howells struct afs_fid fid = {}; 51e49c7b2fSDavid Howells 52e49c7b2fSDavid Howells _enter(""); 53e49c7b2fSDavid Howells 54e49c7b2fSDavid Howells if (as->volume) 55e49c7b2fSDavid Howells fid.vid = as->volume->vid; 56e49c7b2fSDavid Howells if (root) { 57e49c7b2fSDavid Howells fid.vnode = 1; 58e49c7b2fSDavid Howells fid.unique = 1; 59e49c7b2fSDavid Howells } else { 60e49c7b2fSDavid Howells fid.vnode = atomic_inc_return(&afs_autocell_ino); 61e49c7b2fSDavid Howells fid.unique = 0; 62e49c7b2fSDavid Howells } 63e49c7b2fSDavid Howells 64e49c7b2fSDavid Howells inode = iget5_locked(sb, fid.vnode, 65e49c7b2fSDavid Howells afs_iget5_pseudo_test, afs_iget5_pseudo_set, &fid); 66e49c7b2fSDavid Howells if (!inode) { 67e49c7b2fSDavid Howells _leave(" = -ENOMEM"); 68e49c7b2fSDavid Howells return ERR_PTR(-ENOMEM); 69e49c7b2fSDavid Howells } 70e49c7b2fSDavid Howells 71e49c7b2fSDavid Howells _debug("GOT INODE %p { ino=%lu, vl=%llx, vn=%llx, u=%x }", 72e49c7b2fSDavid Howells inode, inode->i_ino, fid.vid, fid.vnode, fid.unique); 73e49c7b2fSDavid Howells 74e49c7b2fSDavid Howells vnode = AFS_FS_I(inode); 75e49c7b2fSDavid Howells 76e49c7b2fSDavid Howells /* there shouldn't be an existing inode */ 77e49c7b2fSDavid Howells BUG_ON(!(inode->i_state & I_NEW)); 78e49c7b2fSDavid Howells 79e49c7b2fSDavid Howells inode->i_size = 0; 80e49c7b2fSDavid Howells inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO; 81e49c7b2fSDavid Howells if (root) { 82e49c7b2fSDavid Howells inode->i_op = &afs_dynroot_inode_operations; 83e49c7b2fSDavid Howells inode->i_fop = &simple_dir_operations; 84e49c7b2fSDavid Howells } else { 85e49c7b2fSDavid Howells inode->i_op = &afs_autocell_inode_operations; 86e49c7b2fSDavid Howells } 87e49c7b2fSDavid Howells set_nlink(inode, 2); 88e49c7b2fSDavid Howells inode->i_uid = GLOBAL_ROOT_UID; 89e49c7b2fSDavid Howells inode->i_gid = GLOBAL_ROOT_GID; 90e49c7b2fSDavid Howells inode->i_ctime = inode->i_atime = inode->i_mtime = current_time(inode); 91e49c7b2fSDavid Howells inode->i_blocks = 0; 92e49c7b2fSDavid Howells inode->i_generation = 0; 93e49c7b2fSDavid Howells 94e49c7b2fSDavid Howells set_bit(AFS_VNODE_PSEUDODIR, &vnode->flags); 95e49c7b2fSDavid Howells if (!root) { 96e49c7b2fSDavid Howells set_bit(AFS_VNODE_MOUNTPOINT, &vnode->flags); 97e49c7b2fSDavid Howells inode->i_flags |= S_AUTOMOUNT; 98e49c7b2fSDavid Howells } 99e49c7b2fSDavid Howells 100e49c7b2fSDavid Howells inode->i_flags |= S_NOATIME; 101e49c7b2fSDavid Howells unlock_new_inode(inode); 102e49c7b2fSDavid Howells _leave(" = %p", inode); 103e49c7b2fSDavid Howells return inode; 104e49c7b2fSDavid Howells } 105e49c7b2fSDavid Howells 10666c7e1d3SDavid Howells /* 10766c7e1d3SDavid Howells * Probe to see if a cell may exist. This prevents positive dentries from 10866c7e1d3SDavid Howells * being created unnecessarily. 10966c7e1d3SDavid Howells */ 11066c7e1d3SDavid Howells static int afs_probe_cell_name(struct dentry *dentry) 11166c7e1d3SDavid Howells { 11266c7e1d3SDavid Howells struct afs_cell *cell; 113a58946c1SDavid Howells struct afs_net *net = afs_d2net(dentry); 11466c7e1d3SDavid Howells const char *name = dentry->d_name.name; 11566c7e1d3SDavid Howells size_t len = dentry->d_name.len; 11666c7e1d3SDavid Howells int ret; 11766c7e1d3SDavid Howells 11866c7e1d3SDavid Howells /* Names prefixed with a dot are R/W mounts. */ 11966c7e1d3SDavid Howells if (name[0] == '.') { 12066c7e1d3SDavid Howells if (len == 1) 12166c7e1d3SDavid Howells return -EINVAL; 12266c7e1d3SDavid Howells name++; 12366c7e1d3SDavid Howells len--; 12466c7e1d3SDavid Howells } 12566c7e1d3SDavid Howells 12692e3cc91SDavid Howells cell = afs_find_cell(net, name, len); 12766c7e1d3SDavid Howells if (!IS_ERR(cell)) { 128a58946c1SDavid Howells afs_put_cell(net, cell); 12966c7e1d3SDavid Howells return 0; 13066c7e1d3SDavid Howells } 13166c7e1d3SDavid Howells 132a58946c1SDavid Howells ret = dns_query(net->net, "afsdb", name, len, "srv=1", 133a58946c1SDavid Howells NULL, NULL, false); 13466c7e1d3SDavid Howells if (ret == -ENODATA) 13566c7e1d3SDavid Howells ret = -EDESTADDRREQ; 13666c7e1d3SDavid Howells return ret; 13766c7e1d3SDavid Howells } 13866c7e1d3SDavid Howells 13966c7e1d3SDavid Howells /* 14066c7e1d3SDavid Howells * Try to auto mount the mountpoint with pseudo directory, if the autocell 14166c7e1d3SDavid Howells * operation is setted. 14266c7e1d3SDavid Howells */ 14366c7e1d3SDavid Howells struct inode *afs_try_auto_mntpt(struct dentry *dentry, struct inode *dir) 14466c7e1d3SDavid Howells { 14566c7e1d3SDavid Howells struct afs_vnode *vnode = AFS_FS_I(dir); 14666c7e1d3SDavid Howells struct inode *inode; 14766c7e1d3SDavid Howells int ret = -ENOENT; 14866c7e1d3SDavid Howells 1493b6492dfSDavid Howells _enter("%p{%pd}, {%llx:%llu}", 15066c7e1d3SDavid Howells dentry, dentry, vnode->fid.vid, vnode->fid.vnode); 15166c7e1d3SDavid Howells 15266c7e1d3SDavid Howells if (!test_bit(AFS_VNODE_AUTOCELL, &vnode->flags)) 15366c7e1d3SDavid Howells goto out; 15466c7e1d3SDavid Howells 15566c7e1d3SDavid Howells ret = afs_probe_cell_name(dentry); 15666c7e1d3SDavid Howells if (ret < 0) 15766c7e1d3SDavid Howells goto out; 15866c7e1d3SDavid Howells 15966c7e1d3SDavid Howells inode = afs_iget_pseudo_dir(dir->i_sb, false); 16066c7e1d3SDavid Howells if (IS_ERR(inode)) { 16166c7e1d3SDavid Howells ret = PTR_ERR(inode); 16266c7e1d3SDavid Howells goto out; 16366c7e1d3SDavid Howells } 16466c7e1d3SDavid Howells 16566c7e1d3SDavid Howells _leave("= %p", inode); 16666c7e1d3SDavid Howells return inode; 16766c7e1d3SDavid Howells 16866c7e1d3SDavid Howells out: 16966c7e1d3SDavid Howells _leave("= %d", ret); 1701401a0fcSAl Viro return ret == -ENOENT ? NULL : ERR_PTR(ret); 17166c7e1d3SDavid Howells } 17266c7e1d3SDavid Howells 17366c7e1d3SDavid Howells /* 17466c7e1d3SDavid Howells * Look up @cell in a dynroot directory. This is a substitution for the 17566c7e1d3SDavid Howells * local cell name for the net namespace. 17666c7e1d3SDavid Howells */ 17766c7e1d3SDavid Howells static struct dentry *afs_lookup_atcell(struct dentry *dentry) 17866c7e1d3SDavid Howells { 17966c7e1d3SDavid Howells struct afs_cell *cell; 18066c7e1d3SDavid Howells struct afs_net *net = afs_d2net(dentry); 18166c7e1d3SDavid Howells struct dentry *ret; 18266c7e1d3SDavid Howells char *name; 18366c7e1d3SDavid Howells int len; 18466c7e1d3SDavid Howells 18566c7e1d3SDavid Howells if (!net->ws_cell) 18666c7e1d3SDavid Howells return ERR_PTR(-ENOENT); 18766c7e1d3SDavid Howells 18866c7e1d3SDavid Howells ret = ERR_PTR(-ENOMEM); 18966c7e1d3SDavid Howells name = kmalloc(AFS_MAXCELLNAME + 1, GFP_KERNEL); 19066c7e1d3SDavid Howells if (!name) 19166c7e1d3SDavid Howells goto out_p; 19266c7e1d3SDavid Howells 19392e3cc91SDavid Howells down_read(&net->cells_lock); 19492e3cc91SDavid Howells cell = net->ws_cell; 19566c7e1d3SDavid Howells if (cell) { 19666c7e1d3SDavid Howells len = cell->name_len; 19766c7e1d3SDavid Howells memcpy(name, cell->name, len + 1); 19866c7e1d3SDavid Howells } 19992e3cc91SDavid Howells up_read(&net->cells_lock); 20066c7e1d3SDavid Howells 20166c7e1d3SDavid Howells ret = ERR_PTR(-ENOENT); 20266c7e1d3SDavid Howells if (!cell) 20366c7e1d3SDavid Howells goto out_n; 20466c7e1d3SDavid Howells 20566c7e1d3SDavid Howells ret = lookup_one_len(name, dentry->d_parent, len); 20666c7e1d3SDavid Howells 20766c7e1d3SDavid Howells /* We don't want to d_add() the @cell dentry here as we don't want to 20866c7e1d3SDavid Howells * the cached dentry to hide changes to the local cell name. 20966c7e1d3SDavid Howells */ 21066c7e1d3SDavid Howells 21166c7e1d3SDavid Howells out_n: 21266c7e1d3SDavid Howells kfree(name); 21366c7e1d3SDavid Howells out_p: 21466c7e1d3SDavid Howells return ret; 21566c7e1d3SDavid Howells } 21666c7e1d3SDavid Howells 21766c7e1d3SDavid Howells /* 21866c7e1d3SDavid Howells * Look up an entry in a dynroot directory. 21966c7e1d3SDavid Howells */ 22066c7e1d3SDavid Howells static struct dentry *afs_dynroot_lookup(struct inode *dir, struct dentry *dentry, 22166c7e1d3SDavid Howells unsigned int flags) 22266c7e1d3SDavid Howells { 22366c7e1d3SDavid Howells _enter("%pd", dentry); 22466c7e1d3SDavid Howells 22566c7e1d3SDavid Howells ASSERTCMP(d_inode(dentry), ==, NULL); 22666c7e1d3SDavid Howells 2271da4bd9fSDavid Howells if (flags & LOOKUP_CREATE) 2281da4bd9fSDavid Howells return ERR_PTR(-EOPNOTSUPP); 2291da4bd9fSDavid Howells 23066c7e1d3SDavid Howells if (dentry->d_name.len >= AFSNAMEMAX) { 23166c7e1d3SDavid Howells _leave(" = -ENAMETOOLONG"); 23266c7e1d3SDavid Howells return ERR_PTR(-ENAMETOOLONG); 23366c7e1d3SDavid Howells } 23466c7e1d3SDavid Howells 23566c7e1d3SDavid Howells if (dentry->d_name.len == 5 && 23666c7e1d3SDavid Howells memcmp(dentry->d_name.name, "@cell", 5) == 0) 23766c7e1d3SDavid Howells return afs_lookup_atcell(dentry); 23866c7e1d3SDavid Howells 2391401a0fcSAl Viro return d_splice_alias(afs_try_auto_mntpt(dentry, dir), dentry); 24066c7e1d3SDavid Howells } 24166c7e1d3SDavid Howells 24266c7e1d3SDavid Howells const struct inode_operations afs_dynroot_inode_operations = { 24366c7e1d3SDavid Howells .lookup = afs_dynroot_lookup, 24466c7e1d3SDavid Howells }; 24566c7e1d3SDavid Howells 24666c7e1d3SDavid Howells /* 24766c7e1d3SDavid Howells * Dirs in the dynamic root don't need revalidation. 24866c7e1d3SDavid Howells */ 24966c7e1d3SDavid Howells static int afs_dynroot_d_revalidate(struct dentry *dentry, unsigned int flags) 25066c7e1d3SDavid Howells { 25166c7e1d3SDavid Howells return 1; 25266c7e1d3SDavid Howells } 25366c7e1d3SDavid Howells 25466c7e1d3SDavid Howells /* 25566c7e1d3SDavid Howells * Allow the VFS to enquire as to whether a dentry should be unhashed (mustn't 25666c7e1d3SDavid Howells * sleep) 25766c7e1d3SDavid Howells * - called from dput() when d_count is going to 0. 25866c7e1d3SDavid Howells * - return 1 to request dentry be unhashed, 0 otherwise 25966c7e1d3SDavid Howells */ 26066c7e1d3SDavid Howells static int afs_dynroot_d_delete(const struct dentry *dentry) 26166c7e1d3SDavid Howells { 26266c7e1d3SDavid Howells return d_really_is_positive(dentry); 26366c7e1d3SDavid Howells } 26466c7e1d3SDavid Howells 26566c7e1d3SDavid Howells const struct dentry_operations afs_dynroot_dentry_operations = { 26666c7e1d3SDavid Howells .d_revalidate = afs_dynroot_d_revalidate, 26766c7e1d3SDavid Howells .d_delete = afs_dynroot_d_delete, 26866c7e1d3SDavid Howells .d_release = afs_d_release, 26966c7e1d3SDavid Howells .d_automount = afs_d_automount, 27066c7e1d3SDavid Howells }; 2710da0b7fdSDavid Howells 2720da0b7fdSDavid Howells /* 2730da0b7fdSDavid Howells * Create a manually added cell mount directory. 2740da0b7fdSDavid Howells * - The caller must hold net->proc_cells_lock 2750da0b7fdSDavid Howells */ 2760da0b7fdSDavid Howells int afs_dynroot_mkdir(struct afs_net *net, struct afs_cell *cell) 2770da0b7fdSDavid Howells { 2780da0b7fdSDavid Howells struct super_block *sb = net->dynroot_sb; 2790da0b7fdSDavid Howells struct dentry *root, *subdir; 2800da0b7fdSDavid Howells int ret; 2810da0b7fdSDavid Howells 2820da0b7fdSDavid Howells if (!sb || atomic_read(&sb->s_active) == 0) 2830da0b7fdSDavid Howells return 0; 2840da0b7fdSDavid Howells 2850da0b7fdSDavid Howells /* Let the ->lookup op do the creation */ 2860da0b7fdSDavid Howells root = sb->s_root; 2870da0b7fdSDavid Howells inode_lock(root->d_inode); 2880da0b7fdSDavid Howells subdir = lookup_one_len(cell->name, root, cell->name_len); 2890da0b7fdSDavid Howells if (IS_ERR(subdir)) { 2900da0b7fdSDavid Howells ret = PTR_ERR(subdir); 2910da0b7fdSDavid Howells goto unlock; 2920da0b7fdSDavid Howells } 2930da0b7fdSDavid Howells 2940da0b7fdSDavid Howells /* Note that we're retaining an extra ref on the dentry */ 2950da0b7fdSDavid Howells subdir->d_fsdata = (void *)1UL; 2960da0b7fdSDavid Howells ret = 0; 2970da0b7fdSDavid Howells unlock: 2980da0b7fdSDavid Howells inode_unlock(root->d_inode); 2990da0b7fdSDavid Howells return ret; 3000da0b7fdSDavid Howells } 3010da0b7fdSDavid Howells 3020da0b7fdSDavid Howells /* 3030da0b7fdSDavid Howells * Remove a manually added cell mount directory. 3040da0b7fdSDavid Howells * - The caller must hold net->proc_cells_lock 3050da0b7fdSDavid Howells */ 3060da0b7fdSDavid Howells void afs_dynroot_rmdir(struct afs_net *net, struct afs_cell *cell) 3070da0b7fdSDavid Howells { 3080da0b7fdSDavid Howells struct super_block *sb = net->dynroot_sb; 3090da0b7fdSDavid Howells struct dentry *root, *subdir; 3100da0b7fdSDavid Howells 3110da0b7fdSDavid Howells if (!sb || atomic_read(&sb->s_active) == 0) 3120da0b7fdSDavid Howells return; 3130da0b7fdSDavid Howells 3140da0b7fdSDavid Howells root = sb->s_root; 3150da0b7fdSDavid Howells inode_lock(root->d_inode); 3160da0b7fdSDavid Howells 3170da0b7fdSDavid Howells /* Don't want to trigger a lookup call, which will re-add the cell */ 3180da0b7fdSDavid Howells subdir = try_lookup_one_len(cell->name, root, cell->name_len); 3190da0b7fdSDavid Howells if (IS_ERR_OR_NULL(subdir)) { 3200da0b7fdSDavid Howells _debug("lookup %ld", PTR_ERR(subdir)); 3210da0b7fdSDavid Howells goto no_dentry; 3220da0b7fdSDavid Howells } 3230da0b7fdSDavid Howells 3240da0b7fdSDavid Howells _debug("rmdir %pd %u", subdir, d_count(subdir)); 3250da0b7fdSDavid Howells 3260da0b7fdSDavid Howells if (subdir->d_fsdata) { 3270da0b7fdSDavid Howells _debug("unpin %u", d_count(subdir)); 3280da0b7fdSDavid Howells subdir->d_fsdata = NULL; 3290da0b7fdSDavid Howells dput(subdir); 3300da0b7fdSDavid Howells } 3310da0b7fdSDavid Howells dput(subdir); 3320da0b7fdSDavid Howells no_dentry: 3330da0b7fdSDavid Howells inode_unlock(root->d_inode); 3340da0b7fdSDavid Howells _leave(""); 3350da0b7fdSDavid Howells } 3360da0b7fdSDavid Howells 3370da0b7fdSDavid Howells /* 3380da0b7fdSDavid Howells * Populate a newly created dynamic root with cell names. 3390da0b7fdSDavid Howells */ 3400da0b7fdSDavid Howells int afs_dynroot_populate(struct super_block *sb) 3410da0b7fdSDavid Howells { 3420da0b7fdSDavid Howells struct afs_cell *cell; 3430da0b7fdSDavid Howells struct afs_net *net = afs_sb2net(sb); 3440da0b7fdSDavid Howells int ret; 3450da0b7fdSDavid Howells 3463b05e528SDavid Howells mutex_lock(&net->proc_cells_lock); 3470da0b7fdSDavid Howells 3480da0b7fdSDavid Howells net->dynroot_sb = sb; 3496b3944e4SDavid Howells hlist_for_each_entry(cell, &net->proc_cells, proc_link) { 3500da0b7fdSDavid Howells ret = afs_dynroot_mkdir(net, cell); 3510da0b7fdSDavid Howells if (ret < 0) 3520da0b7fdSDavid Howells goto error; 3530da0b7fdSDavid Howells } 3540da0b7fdSDavid Howells 3550da0b7fdSDavid Howells ret = 0; 3560da0b7fdSDavid Howells out: 3570da0b7fdSDavid Howells mutex_unlock(&net->proc_cells_lock); 3580da0b7fdSDavid Howells return ret; 3590da0b7fdSDavid Howells 3600da0b7fdSDavid Howells error: 3610da0b7fdSDavid Howells net->dynroot_sb = NULL; 3620da0b7fdSDavid Howells goto out; 3630da0b7fdSDavid Howells } 3640da0b7fdSDavid Howells 3650da0b7fdSDavid Howells /* 3660da0b7fdSDavid Howells * When a dynamic root that's in the process of being destroyed, depopulate it 3670da0b7fdSDavid Howells * of pinned directories. 3680da0b7fdSDavid Howells */ 3690da0b7fdSDavid Howells void afs_dynroot_depopulate(struct super_block *sb) 3700da0b7fdSDavid Howells { 3710da0b7fdSDavid Howells struct afs_net *net = afs_sb2net(sb); 3720da0b7fdSDavid Howells struct dentry *root = sb->s_root, *subdir, *tmp; 3730da0b7fdSDavid Howells 3740da0b7fdSDavid Howells /* Prevent more subdirs from being created */ 3750da0b7fdSDavid Howells mutex_lock(&net->proc_cells_lock); 3760da0b7fdSDavid Howells if (net->dynroot_sb == sb) 3770da0b7fdSDavid Howells net->dynroot_sb = NULL; 3780da0b7fdSDavid Howells mutex_unlock(&net->proc_cells_lock); 3790da0b7fdSDavid Howells 3805e0b17b0SDavid Howells if (root) { 3810da0b7fdSDavid Howells inode_lock(root->d_inode); 3820da0b7fdSDavid Howells 3830da0b7fdSDavid Howells /* Remove all the pins for dirs created for manually added cells */ 3840da0b7fdSDavid Howells list_for_each_entry_safe(subdir, tmp, &root->d_subdirs, d_child) { 3850da0b7fdSDavid Howells if (subdir->d_fsdata) { 3860da0b7fdSDavid Howells subdir->d_fsdata = NULL; 3870da0b7fdSDavid Howells dput(subdir); 3880da0b7fdSDavid Howells } 3890da0b7fdSDavid Howells } 3900da0b7fdSDavid Howells 3910da0b7fdSDavid Howells inode_unlock(root->d_inode); 3920da0b7fdSDavid Howells } 3935e0b17b0SDavid Howells } 394