Viewing: osd_dirent.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2025-2026, DDN/Whamcloud, Inc.
*/
/*
* Author: Yingjin Qian <qian@ddn.com>
*/
#define DEBUG_SUBSYSTEM S_OSD
#include <lustre_crypto.h>
#include "osd_internal.h"
#include "wbcfs.h"
/* Lookup the directory entry (dentry) specified by @key. */
static int osd_index_dir_lookup(const struct lu_env *env, struct dt_object *dt,
struct dt_rec *rec, const struct dt_key *key)
{
struct osd_object *pobj = osd_dt_obj(dt);
struct inode *dir = pobj->oo_inode;
struct lu_fid *fid = (struct lu_fid *)rec;
char *name = (char *)key;
struct dentry *parent;
struct dentry *dchild;
struct qstr qstr;
int rc = 0;
ENTRY;
LASSERT(S_ISDIR(dir->i_mode));
parent = d_find_any_alias(dir);
if (IS_ERR(parent))
RETURN(PTR_ERR(parent));
/* FIXME: more checking for ".." lookup. */
if (strcmp(name, "..") == 0) {
*fid = MEMFS_I(d_inode(parent->d_parent))->mei_fid;
GOTO(out, rc = 1);
}
qstr.name = name;
qstr.len = strlen(name);
qstr.hash = full_name_hash(parent, qstr.name, qstr.len);
dchild = d_lookup(parent, &qstr);
if (dchild) {
*fid = MEMFS_I(d_inode(dchild))->mei_fid;
dput(dchild);
rc = 1;
}
out:
CDEBUG(D_CACHE, "%s: lookup '%s' from parent %pd@%pK "DFID": rc=%d\n",
osd_name(osd_obj2dev(pobj)), name, parent, parent,
PFID(fid), rc);
dput(parent);
RETURN(rc);
}
/**
* osd_index_dir_insert() - Index add function.
* @key: it is key i.e. file entry to be inserted
* @record: it is value of given key i.e. fid
*
* It will add the directory entry.This entry is needed to
* maintain name->fid mapping.
*
* Return:
* * %0 - on success
* * %-ve - on error
*/
static int osd_index_dir_insert(const struct lu_env *env, struct dt_object *dt,
const struct dt_rec *record,
const struct dt_key *key,
struct thandle *th)
{
struct osd_object *pobj = osd_dt_obj(dt);
struct osd_device *osd = osd_dev(dt->do_lu.lo_dev);
struct dt_insert_rec *rec = (struct dt_insert_rec *)record;
const struct lu_fid *fid = rec->rec_fid;
const char *name = (const char *)key;
struct inode *dir = pobj->oo_inode;
struct dentry *parent;
struct dentry *dentry;
struct dentry *dchild = NULL;
struct inode *inode;
struct qstr dname;
bool nedir_rename = false;
int rc = 0;
ENTRY;
if (!dt_object_exists(dt))
RETURN(-ENOENT);
LASSERT(!dt_object_remote(dt));
LASSERTF(fid_is_sane(fid), "fid "DFID" is insane!\n", PFID(fid));
/* Skip "." and ".." in MemFS. */
if (name[0] == '.' && (name[1] == '\0' ||
(name[1] == '.' && name[2] == '\0')))
RETURN(0);
/* FIXME: handle remote object in DNE environment. */
/* TODO: Store inode in @osd_thread_info? */
inode = ilookup5(osd_sb(osd), lu_fid_build_ino(fid, 0),
memfs_test_inode_by_fid, (void *)fid);
if (!inode) {
rc = -EINVAL;
CERROR("%s: lookup "DFID" from icache failed: rc=%d\n",
osd_name(osd_obj2dev(pobj)), PFID(fid), rc);
RETURN(rc);
}
parent = d_find_any_alias(dir);
if (parent == NULL) {
rc = -ENOENT;
CERROR("%s: Cannot find dentry for inode@%pK "DFID": rc=%d\n",
osd_name(osd_obj2dev(pobj)), dir,
PFID(lu_object_fid(&pobj->oo_dt.do_lu)), rc);
GOTO(out_iput, rc);
}
dname.name = name;
dname.len = strlen(name);
dname.hash = full_name_hash(parent, dname.name, dname.len);
dentry = d_alloc(parent, &dname);
if (!dentry)
GOTO(out_dput, rc = -ENOMEM);
switch (inode->i_mode & S_IFMT) {
case S_IFDIR:
/*
* TODO: Store these info into OSD thread info @osd_thread_info,
* thus we can do undo (recovery) operations upon failure.
*/
dchild = d_find_any_alias(inode);
/* mv (rename) a non-empty directory. */
if (dchild && !simple_empty(dchild))
nedir_rename = true;
fallthrough;
case S_IFREG:
dir->i_size += BOGO_DIRENT_SIZE;
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
break;
case S_IFLNK:
/* FIXME: symlink support. */
CERROR("%s: symlink does not support\n",
osd_name(osd_obj2dev(pobj)));
break;
default:
LBUG();
}
inode_inc_iversion(dir);
if (nedir_rename) {
d_move(dchild, dentry);
} else {
/*
* Pin the dentry in dcache; the matching unpin happens in
* osd_index_dir_delete() via d_make_discardable().
*/
d_make_persistent(dentry, inode);
ihold(inode);
}
dput(dentry);
dput(dchild);
CDEBUG(D_CACHE,
"%s: Insert dirent "DFID"/%pd@%pK inode@%pK nlink=%d\n",
osd_name(osd_obj2dev(pobj)), PFID(fid), dentry, dentry,
inode, inode->i_nlink);
out_dput:
dput(parent);
out_iput:
iput(inode);
RETURN(rc);
}
/*
* Index delete funtion.
* It will remove the directory entry added by index insert.
* This entry is needed to maintain name->fid mapping.
*/
static int osd_index_dir_delete(const struct lu_env *env, struct dt_object *dt,
const struct dt_key *key, struct thandle *th)
{
struct osd_object *pobj = osd_dt_obj(dt);
struct inode *dir = pobj->oo_inode;
char *name = (char *)key;
struct dentry *parent;
struct dentry *dentry;
struct inode *inode;
struct qstr qstr;
bool nedir_rename = false;
int rc = 0;
ENTRY;
/* Skip "." and ".." in MemFS. */
if (name[0] == '.' && (name[1] == '\0' ||
(name[1] == '.' && name[2] == '\0')))
RETURN(0);
parent = d_find_any_alias(dir);
if (parent == NULL && strcmp(name, "..") == 0) {
CDEBUG(D_CACHE, "%s: delete name %s from an empty dir@%pK\n",
osd_name(osd_obj2dev(pobj)), name, dir);
RETURN(0);
}
if (parent == NULL) {
CDEBUG(D_CACHE, "%s: delete name %s from an empty dir@%pK\n",
osd_name(osd_obj2dev(pobj)), name, dir);
RETURN(-ENOENT);
}
LASSERTF(parent != NULL, "dir@%pK name %s\n", dir, name);
qstr.name = name;
qstr.len = strlen(name);
qstr.hash = full_name_hash(parent, qstr.name, qstr.len);
dentry = d_lookup(parent, &qstr);
if (dentry == NULL) {
CDEBUG(D_CACHE, "%s: cannot find %s from parent@%pK %pd\n",
osd_name(osd_obj2dev(pobj)), name, dir, parent);
GOTO(out_dput_parent, rc = -ENOENT);
}
LASSERT(dentry != NULL);
inode = d_inode(dentry);
switch (inode->i_mode & S_IFMT) {
case S_IFDIR:
/*
* FIXME: rename() operation, @dentry may be not empty:
* (sanity/214).
* TODO: Put @dir_rename and @dentry into OSD thread info.
*/
if (!simple_empty(dentry))
nedir_rename = true;
/*
* MDD layer drops @nlink later via @dt_ref_del().
* drop_nlink(inode);
* drop_nlink(dir);
*/
fallthrough;
case S_IFREG:
case S_IFLNK:
dir->i_size -= BOGO_DIRENT_SIZE;
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
inode_set_mtime_to_ts(dir, inode_set_ctime_to_ts(dir,
inode_set_ctime_current(inode)));
inode_inc_iversion(dir);
/* MDD layer drops @nlink later via @dt_ref_del(). */
/* drop_nlink(inode); */
/*
* Undo the pin from osd_index_dir_insert(): clear persistence
* and drop the held reference so the dentry can be evicted.
*/
if (!nedir_rename)
d_make_discardable(dentry);
break;
default:
LBUG();
}
CDEBUG(D_CACHE,
"%s: Delete %s from dir@%pK %pd inode@%pK nlink=%d %d: rc=%d.\n",
osd_name(osd_obj2dev(pobj)), name, dir, parent, inode,
inode->i_nlink, dentry->d_lockref.count, rc);
dput(dentry);
out_dput_parent:
dput(parent);
RETURN(rc);
}
static struct osd_it *
__osd_dir_it_init(const struct lu_env *env, struct osd_device *dev,
struct inode *inode, u32 attr)
{
struct osd_it *oit;
struct file *file;
int rc;
ENTRY;
OBD_SLAB_ALLOC_PTR_GFP(oit, osd_it_cachep, GFP_NOFS);
if (oit == NULL)
RETURN(ERR_PTR(-ENOMEM));
/* TODO: store buffer as thread context data @osd_thread_info. */
OBD_ALLOC(oit->oit_buf, OSD_IT_BUFSIZE);
if (!oit->oit_buf)
GOTO(out_free, rc = -ENOMEM);
oit->oit_obj = NULL;
file = &oit->oit_file;
/* Only FMODE_64BITHASH or FMODE_32BITHASH should be set, NOT both. */
if (attr & LUDA_64BITHASH)
file->f_mode |= FMODE_64BITHASH;
else
file->f_mode |= FMODE_32BITHASH;
file->__f_path.dentry = d_find_any_alias(inode);
file->f_flags = O_NOATIME | FMODE_NONOTIFY;
file->f_mapping = inode->i_mapping;
file->f_op = inode->i_fop;
file->f_inode = inode;
if (file->f_op->open) {
rc = file->f_op->open(inode, file);
if (rc) {
dput(file->f_path.dentry);
GOTO(out_free, rc);
}
}
RETURN(oit);
out_free:
OBD_SLAB_FREE_PTR(oit, osd_it_cachep);
return ERR_PTR(rc);
}
/**
* osd_dir_it_init() - Creates or initializes iterator context.
*
* Returns: struct osd_it, iterator structure on success
*/
static struct dt_it *osd_dir_it_init(const struct lu_env *env,
struct dt_object *dt, __u32 attr)
{
struct osd_object *obj = osd_dt_obj(dt);
struct osd_device *dev = osd_obj2dev(obj);
struct lu_object *lo = &dt->do_lu;
struct osd_it *oit;
ENTRY;
if (!dt_object_exists(dt) || obj->oo_destroyed)
RETURN(ERR_PTR(-ENOENT));
oit = __osd_dir_it_init(env, dev, obj->oo_inode, attr);
if (IS_ERR(oit))
RETURN(ERR_CAST(oit));
oit->oit_obj = obj;
lu_object_get(lo);
RETURN((struct dt_it *)oit);
}
/**
* osd_dir_it_fini() - Destroy or finishes iterator context.
* @di: iterator structure to be destroyed
*/
static void osd_dir_it_fini(const struct lu_env *env, struct dt_it *di)
{
struct osd_it *oit = (struct osd_it *)di;
struct osd_object *obj = oit->oit_obj;
struct inode *inode = obj->oo_inode;
ENTRY;
dput(oit->oit_file.f_path.dentry);
oit->oit_file.f_op->release(inode, &oit->oit_file);
OBD_FREE(oit->oit_buf, OSD_IT_BUFSIZE);
OBD_SLAB_FREE_PTR(oit, osd_it_cachep);
osd_object_put(env, obj);
EXIT;
}
/*
* It position the iterator at given key, so that next lookup continues from
* that key Or it is similar to dio_it->load() but based on a key,
* rather than file position.
*
* As a special convention, osd_it_ea_get(env, di, "") has to rewind iterator
* to the beginning.
*
* TODO: Presently return 1 considering it is only used by mdd_dir_is_empty().
*/
static int osd_dir_it_get(const struct lu_env *env,
struct dt_it *di, const struct dt_key *key)
{
struct osd_it *it = (struct osd_it *)di;
struct file *file = &it->oit_file;
loff_t offset;
ENTRY;
LASSERT(((const char *)key)[0] == '\0');
offset = file->f_op->llseek(file, 0, 0);
if (offset)
CWARN("Failed to llseek(): offset %lld != 0\n", offset);
it->oit_rd_dirent = 0;
it->oit_it_dirent = 0;
it->oit_dirent = NULL;
RETURN(1);
}
/* Does nothing */
static void osd_dir_it_put(const struct lu_env *env, struct dt_it *di)
{
}
/**
* osd_memfs_filldir() - It is called internally by ->iterate*()
* @buf: in which information to be filled in.
* @name: name of the file in given dir
*
* It fills the iterator's in-memory data structure with required
* information i.e. name, namelen, rec_size etc.
*
* Returns:
* * %0 - on success
* * %1 - on buffer full
*/
static FILLDIR_TYPE do_osd_memfs_filldir(struct dir_context *ctx,
const char *name, int namelen,
loff_t offset, u64 ino,
unsigned int d_type)
{
struct memfs_dir_context *mctx = (struct memfs_dir_context *)ctx;
struct osd_it *oit = (struct osd_it *)mctx->cbdata;
struct osd_object *obj = oit->oit_obj;
struct osd_it_dirent *ent = oit->oit_dirent;
struct lu_fid *fid = &ent->oitd_fid;
char *buf = oit->oit_buf;
ENTRY;
/* This should never happen */
if (unlikely(namelen == 0 || namelen > NAME_MAX)) {
CERROR("MemFS return invalid namelen %d\n", namelen);
RETURN(-EIO);
}
/* Check for enough space. Note oitd_name is not NUL terminated. */
if (&ent->oitd_name[namelen] > buf + OSD_IT_BUFSIZE)
RETURN(1);
/* "." is just the object itself. */
if (namelen == 1 && name[0] == '.') {
if (obj != NULL)
*fid = obj->oo_dt.do_lu.lo_header->loh_fid;
} else if (namelen == 2 && name[0] == '.' && name[1] == '.') {
if (obj != NULL) {
struct inode *inode = obj->oo_inode;
struct dentry *dentry;
struct dentry *parent;
LASSERT(S_ISDIR(inode->i_mode));
dentry = d_find_any_alias(inode);
parent = dentry->d_parent;
*fid = MEMFS_I(d_inode(parent))->mei_fid;
dput(dentry);
}
} else if (mctx->dentry) {
*fid = MEMFS_I(d_inode(mctx->dentry))->mei_fid;
} else {
fid_zero(fid);
}
/* NOT export local root. */
if (obj != NULL &&
unlikely(osd_sb(osd_obj2dev(obj))->s_root->d_inode->i_ino == ino)) {
ino = obj->oo_inode->i_ino;
*fid = obj->oo_dt.do_lu.lo_header->loh_fid;
}
if (obj == NULL || !(obj->oo_lma_flags & LUSTRE_ENCRYPT_FL)) {
ent->oitd_namelen = namelen;
memcpy(ent->oitd_name, name, namelen);
} else {
int encoded_namelen = critical_chars(name, namelen);
/* Check again for enough space. */
if (&ent->oitd_name[encoded_namelen] > buf + OSD_IT_BUFSIZE)
RETURN(1);
ent->oitd_namelen = encoded_namelen;
if (encoded_namelen == namelen)
memcpy(ent->oitd_name, name, namelen);
else
critical_encode(name, namelen, ent->oitd_name);
}
ent->oitd_ino = ino;
ent->oitd_off = offset;
ent->oitd_type = d_type;
oit->oit_rd_dirent++;
oit->oit_dirent = (void *)ent +
round_up(sizeof(*ent) + ent->oitd_namelen, 8);
CDEBUG(D_DENTRY, "Filldir: fid="DFID" name=%s off=%llu rd_dirent=%u\n",
PFID(fid), name, offset, oit->oit_rd_dirent);
RETURN(0);
}
WRAP_FILLDIR_FN(do_, osd_memfs_filldir)
/**
* osd_memfs_it_fill() - Calls ->iterate*() to load a directory entry at
* a time and stored it in iterator's in-memory data structure.
* @di: iterator's in memory structure
*
* Returns:
* * %0 - on success
* * %-ve - on error
* * %1 - reach the end of entry
*/
static int osd_memfs_it_fill(const struct lu_env *env, const struct dt_it *di)
{
struct osd_it *it = (struct osd_it *)di;
struct file *filp = &it->oit_file;
struct inode *dir = file_inode(filp);
struct memfs_dir_context mctx = {
.super.actor = osd_memfs_filldir,
.dentry = NULL,
.cbdata = it
};
int rc = 0;
ENTRY;
it->oit_dirent = it->oit_buf;
it->oit_rd_dirent = 0;
inode_lock_shared(dir);
if (!IS_DEADDIR(dir)) {
if (filp->f_op->iterate_shared) {
mctx.super.pos = filp->f_pos;
rc = filp->f_op->iterate_shared(filp, &mctx.super);
filp->f_pos = mctx.super.pos;
} else {
rc = -ENOTDIR;
}
}
inode_unlock_shared(dir);
if (rc)
RETURN(rc);
if (it->oit_rd_dirent == 0) {
/*
* If it does not get any dirent, it means it has been reached
* to the end of the dir
*/
it->oit_file.f_pos = MEMFS_DIR_EOF;
rc = 1;
} else {
it->oit_dirent = it->oit_buf;
it->oit_it_dirent = 1;
}
RETURN(rc);
}
/**
* osd_dir_it_next() - It calls osd_memfs_it_fill() which will use
* ->iterate*() to load a directory entry at a time and stored it in
* iterator's in-memory data structure.
* @di: iterator's in memory structure
*
* Returns:
* * %ve - iterator reached to end
* * %0 - iterator not reached to end
* * %-ve - on error
*/
static int osd_dir_it_next(const struct lu_env *env, struct dt_it *di)
{
struct osd_it *it = (struct osd_it *)di;
int rc;
ENTRY;
if (it->oit_it_dirent < it->oit_rd_dirent) {
it->oit_dirent =
(void *)it->oit_dirent +
round_up(sizeof(struct osd_it_dirent) +
it->oit_dirent->oitd_namelen, 8);
it->oit_it_dirent++;
rc = 0;
} else {
if (it->oit_file.f_pos == MEMFS_DIR_EOF)
rc = 1;
else
rc = osd_memfs_it_fill(env, di);
}
RETURN(rc);
}
/**
* osd_dir_it_key() - Returns the key at current position from
* iterator's in memory structure.
* @di: iterator's in memory structure
*
* Returns: key i.e. struct dt_key on success
*/
static struct dt_key *osd_dir_it_key(const struct lu_env *env,
const struct dt_it *di)
{
struct osd_it *it = (struct osd_it *)di;
return (struct dt_key *)it->oit_dirent->oitd_name;
}
/**
* osd_dir_it_key_size() - Returns key's size at current position
* from iterator's in memory structure.
* @di: iterator's in memory structure
*
* Returns: key_size i.e. struct dt_key on success
*/
static int osd_dir_it_key_size(const struct lu_env *env, const struct dt_it *di)
{
struct osd_it *it = (struct osd_it *)di;
return it->oit_dirent->oitd_namelen;
}
static inline void
osd_it_append_attrs(struct lu_dirent *ent, int len, __u16 type)
{
/* check if file type is required */
if (ent->lde_attrs & LUDA_TYPE) {
struct luda_type *lt;
int align = sizeof(*lt) - 1;
len = (len + align) & ~align;
lt = (struct luda_type *)(ent->lde_name + len);
lt->lt_type = cpu_to_le16(DTTOIF(type));
}
ent->lde_attrs = cpu_to_le32(ent->lde_attrs);
}
/*
* build lu direct from backend fs dirent.
*/
static inline void
osd_it_pack_dirent(struct lu_dirent *ent, struct lu_fid *fid, __u64 offset,
char *name, __u16 namelen, __u16 type, __u32 attr)
{
ent->lde_attrs = attr | LUDA_FID;
fid_cpu_to_le(&ent->lde_fid, fid);
ent->lde_hash = cpu_to_le64(offset);
ent->lde_reclen = cpu_to_le16(lu_dirent_calc_size(namelen, attr));
strncpy(ent->lde_name, name, namelen);
ent->lde_name[namelen] = '\0';
ent->lde_namelen = cpu_to_le16(namelen);
/* append lustre attributes */
osd_it_append_attrs(ent, namelen, type);
}
/**
* osd_dir_it_rec() - Returns the value at current position from
* iterator's in memory structure.
* @di: struct osd_it, iterator's in memory structure
* @dtrec: lustre dirent
* @attr: attr requested for dirent.
*
* Returns:
* %0 - no error and \param lde has correct lustre dirent.
* %-ve - on error
*/
static inline int osd_dir_it_rec(const struct lu_env *env,
const struct dt_it *di,
struct dt_rec *dtrec, __u32 attr)
{
struct osd_it *it = (struct osd_it *)di;
struct lu_fid *fid = &it->oit_dirent->oitd_fid;
struct lu_dirent *lde = (struct lu_dirent *)dtrec;
ENTRY;
/* TODO: lfsck checking support.*/
attr &= ~LU_DIRENT_ATTRS_MASK;
/* Pack the entry anyway, at least the offset is right. */
osd_it_pack_dirent(lde, fid, it->oit_dirent->oitd_off,
it->oit_dirent->oitd_name,
it->oit_dirent->oitd_namelen,
it->oit_dirent->oitd_type, attr);
RETURN(0);
}
/**
* osd_dir_it_rec_size() - Returns the record size at current position.
* @env: execution environment
* @di: iterator's in memory structure
* @attr: attribute of the entry, only requires LUDA_TYPE to
* calculate the lu_dirent size.
*
* This function will return record(lu_dirent) size in bytes.
*
* Returns: record size(in bytes & in memory) of the current lu_dirent
* entry.
*/
static int osd_dir_it_rec_size(const struct lu_env *env, const struct dt_it *di,
__u32 attr)
{
struct osd_it *it = (struct osd_it *)di;
return lu_dirent_calc_size(it->oit_dirent->oitd_namelen, attr);
}
/**
* osd_dir_it_store() - Returns a cookie for current position of the iterator
* head, so that user can use this cookie to load/start the iterator next
* time.
* @di: iterator's in memory structure
*
* Returns: cookie for current position, on success
*/
static __u64 osd_dir_it_store(const struct lu_env *env, const struct dt_it *di)
{
struct osd_it *it = (struct osd_it *)di;
return it->oit_dirent->oitd_off;
}
/**
* osd_dir_it_load() - It calls osd_memfs_it_fill() which will use
* ->iterate*() to load a directory entry at a time and stored it
* in iterator's in-memory data structure.
* @di: struct osd_it, iterator's in memory structure
*
* Returns:
* * %ve - on success
* * %-ve - on error
*/
static int osd_dir_it_load(const struct lu_env *env,
const struct dt_it *di, __u64 hash)
{
struct osd_it *it = (struct osd_it *)di;
struct file *file = &it->oit_file;
loff_t offset;
int rc;
ENTRY;
offset = file->f_op->llseek(file, hash, 0);
if (offset != hash)
CWARN("Failed to llseek(): offset %lld != hash %llu\n",
offset, hash);
rc = osd_memfs_it_fill(env, di);
if (rc > 0)
rc = -ENODATA;
if (rc == 0)
rc = 1;
RETURN(rc);
}
const struct dt_index_operations osd_wbcfs_dir_ops = {
.dio_lookup = osd_index_dir_lookup,
.dio_insert = osd_index_dir_insert,
.dio_delete = osd_index_dir_delete,
.dio_it = {
.init = osd_dir_it_init,
.fini = osd_dir_it_fini,
.get = osd_dir_it_get,
.put = osd_dir_it_put,
.next = osd_dir_it_next,
.key = osd_dir_it_key,
.key_size = osd_dir_it_key_size,
.rec = osd_dir_it_rec,
.rec_size = osd_dir_it_rec_size,
.store = osd_dir_it_store,
.load = osd_dir_it_load
}
};