// SPDX-License-Identifier: GPL-2.0-only /* * fs/sharefs/lookup.c * * Copyright (c) 1998-2022 Erez Zadok * Copyright (c) 2009 Shrikar Archak * Copyright (c) 2003-2022 Stony Brook University * Copyright (c) 2003-2022 The Research Foundation of SUNY * Copyright (c) 2023 Huawei Device Co., Ltd. */ #include "sharefs.h" #include "authentication.h" /* The dentry cache is just so we have properly sized dentries */ static struct kmem_cache *sharefs_dentry_cachep; int sharefs_init_dentry_cache(void) { sharefs_dentry_cachep = kmem_cache_create("sharefs_dentry", sizeof(struct sharefs_dentry_info), 0, SLAB_RECLAIM_ACCOUNT, NULL); return sharefs_dentry_cachep ? 0 : -ENOMEM; } void sharefs_destroy_dentry_cache(void) { if (sharefs_dentry_cachep) kmem_cache_destroy(sharefs_dentry_cachep); } void free_dentry_private_data(struct dentry *dentry) { if (!dentry || !dentry->d_fsdata) return; kmem_cache_free(sharefs_dentry_cachep, dentry->d_fsdata); dentry->d_fsdata = NULL; } /* allocate new dentry private data */ int new_dentry_private_data(struct dentry *dentry) { struct sharefs_dentry_info *info = SHAREFS_D(dentry); /* use zalloc to init dentry_info.lower_path */ info = kmem_cache_zalloc(sharefs_dentry_cachep, GFP_ATOMIC); if (!info) return -ENOMEM; spin_lock_init(&info->lock); dentry->d_fsdata = info; return 0; } static int sharefs_inode_test(struct inode *inode, void *candidate_lower_inode) { struct inode *current_lower_inode = sharefs_lower_inode(inode); if (current_lower_inode == (struct inode *)candidate_lower_inode) return 1; /* found a match */ else return 0; /* no match */ } static int sharefs_inode_set(struct inode *inode, void *lower_inode) { /* we do actual inode initialization in sharefs_iget */ return 0; } struct inode *sharefs_iget(struct super_block *sb, struct inode *lower_inode) { struct inode *inode; /* the new inode to return */ if (!igrab(lower_inode)) return ERR_PTR(-ESTALE); inode = iget5_locked(sb, /* our superblock */ /* * hashval: we use inode number, but we can * also use "(unsigned long)lower_inode" * instead. */ lower_inode->i_ino, /* hashval */ sharefs_inode_test, /* inode comparison function */ sharefs_inode_set, /* inode init function */ lower_inode); /* data passed to test+set fxns */ if (!inode) { iput(lower_inode); return ERR_PTR(-ENOMEM); } if (lower_inode->i_nlink == 0) { iput(lower_inode); iput(inode); return ERR_PTR(-ENOENT); } /* if found a cached inode, then just return it (after iput) */ if (!(inode->i_state & I_NEW)) { iput(lower_inode); return inode; } /* initialize new inode */ inode->i_ino = lower_inode->i_ino; sharefs_set_lower_inode(inode, lower_inode); atomic64_inc(&inode->i_version); /* use different set of inode ops for symlinks & directories */ if (S_ISDIR(lower_inode->i_mode)) inode->i_op = &sharefs_dir_iops; else if (S_ISLNK(lower_inode->i_mode)) inode->i_op = &sharefs_symlink_iops; else inode->i_op = &sharefs_main_iops; /* use different set of file ops for directories */ if (S_ISDIR(lower_inode->i_mode)) inode->i_fop = &sharefs_dir_fops; else inode->i_fop = &sharefs_main_fops; inode->i_atime.tv_sec = 0; inode->i_atime.tv_nsec = 0; inode->i_mtime.tv_sec = 0; inode->i_mtime.tv_nsec = 0; inode->i_ctime.tv_sec = 0; inode->i_ctime.tv_nsec = 0; /* properly initialize special inodes */ if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) || S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode)) init_special_inode(inode, lower_inode->i_mode, lower_inode->i_rdev); /* all well, copy inode attributes */ fsstack_copy_attr_all(inode, lower_inode); fsstack_copy_inode_size(inode, lower_inode); unlock_new_inode(inode); return inode; } /* * Helper interpose routine, called directly by ->lookup to handle * spliced dentries. */ static struct dentry *__sharefs_interpose(struct dentry *dentry, struct super_block *sb, struct path *lower_path) { struct inode *inode; struct inode *lower_inode = d_inode(lower_path->dentry); struct dentry *ret_dentry; /* * We allocate our new inode below by calling sharefs_iget, * which will initialize some of the new inode's fields */ /* inherit lower inode number for sharefs's inode */ inode = sharefs_iget(sb, lower_inode); if (IS_ERR(inode)) { ret_dentry = ERR_PTR(PTR_ERR(inode)); goto out; } ret_dentry = d_splice_alias(inode, dentry); out: return ret_dentry; } /* * Connect a sharefs inode dentry/inode with several lower ones. This is * the classic stackable file system "vnode interposition" action. * * @dentry: sharefs's dentry which interposes on lower one * @sb: sharefs's super_block * @lower_path: the lower path (caller does path_get/put) */ int sharefs_interpose(struct dentry *dentry, struct super_block *sb, struct path *lower_path) { struct dentry *ret_dentry; ret_dentry = __sharefs_interpose(dentry, sb, lower_path); return PTR_ERR(ret_dentry); } /* * Main driver function for sharefs's lookup. * * Returns: NULL (ok), ERR_PTR if an error occurred. * Fills in lower_parent_path with on success. */ static struct dentry *__sharefs_lookup(struct dentry *dentry, unsigned int flags, struct path *lower_parent_path) { int err = 0; struct vfsmount *lower_dir_mnt; struct dentry *lower_dir_dentry = NULL; struct dentry *lower_dentry; const char *name; struct path lower_path; struct qstr this; struct dentry *ret_dentry = NULL; /* must initialize dentry operations */ d_set_d_op(dentry, &sharefs_dops); if (IS_ROOT(dentry)) goto out; name = dentry->d_name.name; /* now start the actual lookup procedure */ lower_dir_dentry = lower_parent_path->dentry; lower_dir_mnt = lower_parent_path->mnt; /* Use vfs_path_lookup to check if the dentry exists or not */ err = vfs_path_lookup(lower_dir_dentry, lower_dir_mnt, name, 0, &lower_path); /* no error: handle positive dentries */ if (!err) { sharefs_set_lower_path(dentry, &lower_path); ret_dentry = __sharefs_interpose(dentry, dentry->d_sb, &lower_path); if (IS_ERR(ret_dentry)) { err = PTR_ERR(ret_dentry); /* path_put underlying path on error */ sharefs_put_reset_lower_path(dentry); } goto out; } /* * We don't consider ENOENT an error, and we want to return a * negative dentry. */ if (err && err != -ENOENT) goto out; /* instantiate a new negative dentry */ this.name = name; this.len = strlen(name); this.hash = full_name_hash(lower_dir_dentry, this.name, this.len); lower_dentry = d_lookup(lower_dir_dentry, &this); if (lower_dentry) goto setup_lower; lower_dentry = d_alloc(lower_dir_dentry, &this); if (!lower_dentry) { err = -ENOMEM; goto out; } /* * Calling ->lookup instead of d_add will give the lower fs a chance * to allocate the d_fsdata field but will still instantiate and hash the * lower_dentry. Without this, sharefs could not stack on top of itself. */ d_inode(lower_dir_dentry)->i_op->lookup(d_inode(lower_dir_dentry), lower_dentry, flags); setup_lower: lower_path.dentry = lower_dentry; lower_path.mnt = mntget(lower_dir_mnt); sharefs_set_lower_path(dentry, &lower_path); /* * If the intent is to create a file, then don't return an error, so * the VFS will continue the process of making this negative dentry * into a positive one. */ if (err == -ENOENT || (flags & (LOOKUP_CREATE|LOOKUP_RENAME_TARGET))) err = 0; out: if (err) return ERR_PTR(err); return ret_dentry; } struct dentry *sharefs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { int err; struct dentry *ret, *parent; struct path lower_parent_path; parent = dget_parent(dentry); sharefs_get_lower_path(parent, &lower_parent_path); /* allocate dentry private data. We free it in ->d_release */ err = new_dentry_private_data(dentry); if (err) { ret = ERR_PTR(err); goto out; } ret = __sharefs_lookup(dentry, flags, &lower_parent_path); if (IS_ERR(ret)) { sharefs_err("sharefs_lookup error!"); goto out; } if (ret) dentry = ret; if (d_inode(dentry)) fsstack_copy_attr_times(d_inode(dentry), sharefs_lower_inode(d_inode(dentry))); /* update parent directory's atime */ fsstack_copy_attr_atime(d_inode(parent), sharefs_lower_inode(d_inode(parent))); fixup_perm_from_level(d_inode(parent), dentry); out: sharefs_put_lower_path(parent, &lower_parent_path); dput(parent); return ret; }