Viewing: osd_object.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 <linux/fs_struct.h>
#include <dt_object.h>
#include "osd_internal.h"
#include "wbcfs.h"
/* Concurrency: no external locking is necessary. */
static int osd_index_try(const struct lu_env *env, struct dt_object *dt,
const struct dt_index_features *feat)
{
int rc;
if (likely(feat == &dt_directory_features)) {
dt->do_index_ops = &osd_wbcfs_dir_ops;
rc = 0;
} else if (unlikely(feat == &dt_acct_features)) {
/* TODO: Add quota support. */
rc = -ENOTSUPP;
} else if (unlikely(feat == &dt_otable_features)) {
/* TODO: Add scrub support. */
dt->do_index_ops = &osd_hash_index_ops;
rc = 0;
} else {
dt->do_index_ops = &osd_hash_index_ops;
rc = 0;
}
return rc;
}
static int osd_otable_it_attr_get(const struct lu_env *env,
struct dt_object *dt,
struct lu_attr *attr)
{
attr->la_valid = 0;
return 0;
}
static const struct dt_object_operations osd_obj_otable_it_ops = {
.do_attr_get = osd_otable_it_attr_get,
.do_index_try = osd_index_try,
};
static void __osd_object_init(struct osd_object *obj)
{
LASSERT(obj->oo_inode != NULL);
obj->oo_dt.do_body_ops = &osd_wbcfs_body_ops;
obj->oo_dt.do_lu.lo_header->loh_attr |=
(LOHA_EXISTS | (obj->oo_inode->i_mode & S_IFMT));
}
/*
* Concurrency: No concurrent access is possible that early in object
* life cycle.
*/
static int osd_object_init(const struct lu_env *env, struct lu_object *l,
const struct lu_object_conf *conf)
{
struct osd_object *obj = osd_obj(l);
struct osd_device *osd = osd_obj2dev(obj);
const struct lu_fid *fid = lu_object_fid(l);
struct inode *inode = NULL;
__u64 hash;
if (fid_is_otable_it(&l->lo_header->loh_fid)) {
obj->oo_dt.do_ops = &osd_obj_otable_it_ops;
l->lo_header->loh_attr |= LOHA_EXISTS;
return 0;
}
hash = lu_fid_build_ino(fid, 0);
inode = ilookup5(osd_sb(osd), hash, memfs_test_inode_by_fid,
(void *)fid);
obj->oo_dt.do_body_ops = &osd_wbcfs_body_ops;
if (inode) {
/*
* The inode may still be in the icache after its last link
* was dropped because a persistent dentry pins it. Treat such
* a stale inode as non-existent: returning it as a live object
* would later trip the nlink==0 && !oo_destroyed assertion in
* osd_object_release().
*/
if (inode->i_nlink == 0) {
iput(inode);
inode = NULL;
} else {
obj->oo_inode = inode;
__osd_object_init(obj);
/*
* TODO: check LMA EA and convert LMAI flags to lustre
* LMA flags and cache it in object.
*/
}
}
CDEBUG(D_INODE, "%s: object init for fid="DFID" inode@%pK nlink=%d\n",
osd_name(osd), PFID(fid), inode, inode ? inode->i_nlink : 0);
return 0;
}
static void osd_object_free(const struct lu_env *env, struct lu_object *l)
{
struct osd_object *obj = osd_obj(l);
struct lu_object_header *h = obj->oo_header;
dt_object_fini(&obj->oo_dt);
OBD_FREE_PTR(obj);
if (unlikely(h))
lu_object_header_free(h);
}
/*
* Called just before the object is freed. Releases all resources except for
* object itself (that is released by osd_object_free()).
*
* Concurrency: no concurrent access is possible that late in object
* life-cycle.
*/
static void osd_object_delete(const struct lu_env *env, struct lu_object *l)
{
struct osd_object *obj = osd_obj(l);
struct inode *inode = obj->oo_inode;
if (!inode)
return;
obj->oo_inode = NULL;
CDEBUG(D_INODE,
"%s: object "DFID" delete: inode@%pK nlink=%u count=%d\n",
osd_name(osd_obj2dev(obj)), PFID(lu_object_fid(l)),
inode, inode->i_nlink, atomic_read(&inode->i_count));
iput(inode);
}
/* Concurrency: ->loo_object_release() is called under site spin-lock. */
static void osd_object_release(const struct lu_env *env, struct lu_object *l)
{
struct osd_object *o = osd_obj(l);
/*
* Nobody should be releasing a non-destroyed object with nlink=0
* the API allows this, but wbcfs does not like and then report
* this inode as deleted.
*/
if (o->oo_destroyed == 0 && o->oo_inode && o->oo_inode->i_nlink == 0)
CERROR("%s: Object "DFID" wrong: %d inode@%pK nlink=%u\n",
osd_name(osd_obj2dev(o)), PFID(lu_object_fid(l)),
o->oo_destroyed, o->oo_inode,
o->oo_inode ? o->oo_inode->i_nlink : 0);
LASSERT(!(o->oo_destroyed == 0 && o->oo_inode &&
o->oo_inode->i_nlink == 0));
}
static int osd_object_print(const struct lu_env *env, void *cookie,
lu_printer_t p, const struct lu_object *l)
{
struct osd_object *o = osd_obj(l);
return (*p)(env, cookie,
LUSTRE_OSD_WBCFS_NAME"-object@%p(i:%p:%lu/%u)",
o, o->oo_inode,
o->oo_inode ? o->oo_inode->i_ino : 0UL,
o->oo_inode ? o->oo_inode->i_generation : 0);
}
static void osd_inode_getattr(const struct lu_env *env,
struct inode *inode, struct lu_attr *attr)
{
attr->la_valid |= LA_ATIME | LA_MTIME | LA_CTIME | LA_MODE |
LA_SIZE | LA_BLOCKS | LA_UID | LA_GID |
LA_PROJID | LA_FLAGS | LA_NLINK | LA_RDEV |
LA_BLKSIZE | LA_TYPE | LA_BTIME;
attr->la_atime = inode_get_atime_sec(inode);
attr->la_mtime = inode_get_mtime_sec(inode);
attr->la_ctime = inode_get_ctime_sec(inode);
attr->la_btime = memfs_get_btime(inode);
attr->la_mode = inode->i_mode;
attr->la_size = i_size_read(inode);
attr->la_blocks = inode->i_blocks;
attr->la_uid = i_uid_read(inode);
attr->la_gid = i_gid_read(inode);
attr->la_projid = i_projid_read(inode);
attr->la_flags = ll_inode_to_ext_flags(inode->i_flags);
attr->la_nlink = inode->i_nlink;
attr->la_rdev = inode->i_rdev;
attr->la_blksize = 1 << inode->i_blkbits;
attr->la_blkbits = inode->i_blkbits;
/*
* MemFS did not transfer inherit flags from raw inode
* to inode flags, and MemFS internally test raw inode
* @i_flags directly. Instead of patching ext4, we do it here.
*/
if (memfs_get_flags(inode) & LUSTRE_PROJINHERIT_FL)
attr->la_flags |= LUSTRE_PROJINHERIT_FL;
}
static int osd_attr_get(const struct lu_env *env, struct dt_object *dt,
struct lu_attr *attr)
{
struct osd_object *obj = osd_dt_obj(dt);
if (unlikely(!dt_object_exists(dt)))
return -ENOENT;
if (unlikely(obj->oo_destroyed))
return -ENOENT;
LASSERT(!dt_object_remote(dt));
down_read(&obj->oo_guard);
osd_inode_getattr(env, obj->oo_inode, attr);
if (obj->oo_lma_flags & LUSTRE_ORPHAN_FL) {
attr->la_valid |= LA_FLAGS;
attr->la_flags |= LUSTRE_ORPHAN_FL;
}
if (obj->oo_lma_flags & LUSTRE_ENCRYPT_FL) {
attr->la_valid |= LA_FLAGS;
attr->la_flags |= LUSTRE_ENCRYPT_FL;
}
up_read(&obj->oo_guard);
CDEBUG(D_INFO, "%s: getattr "DFID" inode@%pK nlink=%d\n",
osd_name(osd_obj2dev(obj)), PFID(lu_object_fid(&dt->do_lu)),
obj->oo_inode, obj->oo_inode->i_nlink);
return 0;
}
static int osd_inode_setattr(const struct lu_env *env,
struct inode *inode, const struct lu_attr *attr)
{
__u64 bits = attr->la_valid;
/* Only allow set size for regular file */
if (!S_ISREG(inode->i_mode))
bits &= ~(LA_SIZE | LA_BLOCKS);
if (bits == 0)
return 0;
if (bits & LA_ATIME)
inode_set_atime_to_ts(inode,
osd_inode_time(inode, attr->la_atime));
if (bits & LA_CTIME)
inode_set_ctime_to_ts(inode,
osd_inode_time(inode, attr->la_ctime));
if (bits & LA_MTIME)
inode_set_mtime_to_ts(inode,
osd_inode_time(inode, attr->la_mtime));
if (bits & LA_SIZE) {
spin_lock(&inode->i_lock);
i_size_write(inode, attr->la_size);
spin_unlock(&inode->i_lock);
}
/*
* OSD should not change "i_blocks" which is used by quota.
* "i_blocks" should be changed by ldiskfs only.
*/
if (bits & LA_MODE)
inode->i_mode = (inode->i_mode & S_IFMT) |
(attr->la_mode & ~S_IFMT);
if (bits & LA_UID)
i_uid_write(inode, attr->la_uid);
if (bits & LA_GID)
i_gid_write(inode, attr->la_gid);
if (bits & LA_PROJID)
i_projid_write(inode, attr->la_projid);
if (bits & LA_NLINK)
set_nlink(inode, attr->la_nlink);
if (bits & LA_RDEV)
inode->i_rdev = attr->la_rdev;
if (bits & LA_FLAGS) {
/* always keep S_NOCMTIME */
inode->i_flags = ll_ext_to_inode_flags(attr->la_flags) |
S_NOCMTIME;
#if defined(S_ENCRYPTED)
/* Always remove S_ENCRYPTED, because ldiskfs must not be
* aware of encryption status. It is just stored into LMA
* so that it can be forwared to client side.
*/
inode->i_flags &= ~S_ENCRYPTED;
#endif
/*
* MemFS did not transfer inherit flags from
* @inode->i_flags to raw inode i_flags when writing
* flags, we do it explictly here.
*/
if (attr->la_flags & LUSTRE_PROJINHERIT_FL)
MEMFS_I(inode)->mei_flags |= LUSTRE_PROJINHERIT_FL;
else
MEMFS_I(inode)->mei_flags &= ~LUSTRE_PROJINHERIT_FL;
}
return 0;
}
static int osd_attr_set(const struct lu_env *env, struct dt_object *dt,
const struct lu_attr *attr, struct thandle *handle)
{
struct osd_object *obj = osd_dt_obj(dt);
struct inode *inode;
int rc;
if (!dt_object_exists(dt))
return -ENOENT;
LASSERT(!dt_object_remote(dt));
inode = obj->oo_inode;
down_write(&obj->oo_guard);
rc = osd_inode_setattr(env, inode, attr);
up_write(&obj->oo_guard);
if (rc)
RETURN(rc);
/* TODO: extra flags for LUSTRE_LMA_FL_MASKS */
return 0;
}
static int osd_mkfile(const struct lu_env *env, struct osd_object *obj,
umode_t mode, struct dt_allocation_hint *hint,
struct thandle *th, struct lu_attr *attr)
{
struct osd_device *osd = osd_obj2dev(obj);
struct dt_object *parent = NULL;
struct inode *inode;
struct iattr iattr = {
.ia_valid = ATTR_UID | ATTR_GID |
ATTR_CTIME | ATTR_MTIME | ATTR_ATIME,
.ia_ctime.tv_sec = attr->la_ctime,
.ia_mtime.tv_sec = attr->la_mtime,
.ia_atime.tv_sec = attr->la_atime,
.ia_uid = GLOBAL_ROOT_UID,
.ia_gid = GLOBAL_ROOT_GID,
};
const struct timespec64 omit = { .tv_nsec = UTIME_OMIT };
const struct lu_fid *fid = lu_object_fid(&obj->oo_dt.do_lu);
if (attr->la_valid & LA_UID)
iattr.ia_uid = make_kuid(&init_user_ns, attr->la_uid);
if (attr->la_valid & LA_GID)
iattr.ia_gid = make_kgid(&init_user_ns, attr->la_gid);
LASSERT(obj->oo_inode == NULL);
if (hint != NULL && hint->dah_parent != NULL &&
!dt_object_remote(hint->dah_parent))
parent = hint->dah_parent;
/* if a time component is not valid set it to UTIME_OMIT */
if (!(attr->la_valid & LA_CTIME))
iattr.ia_ctime = omit;
if (!(attr->la_valid & LA_MTIME))
iattr.ia_mtime = omit;
if (!(attr->la_valid & LA_ATIME))
iattr.ia_atime = omit;
inode = memfs_create_inode(osd_sb(osd),
parent ? osd_dt_obj(parent)->oo_inode :
osd_sb(osd)->s_root->d_inode,
mode, &iattr, 0, false);
if (IS_ERR(inode))
return PTR_ERR(inode);
/* Do not update file c/mtime in MemFS. */
inode->i_flags |= S_NOCMTIME;
inode->i_ino = lu_fid_build_ino(fid, 0);
inode->i_generation = lu_fid_build_gen(fid);
MEMFS_I(inode)->mei_fid = *fid;
if (unlikely(insert_inode_locked(inode) < 0)) {
CERROR("%s: Failed to insert inode %lu "DFID": doubly allocated?\n",
osd_name(osd), inode->i_ino, PFID(fid));
iput(inode);
RETURN(-EIO);
}
CDEBUG(D_INODE,
"%s: create object "DFID": inode@%pK nlink=%d mode=%#o\n",
osd_name(osd), PFID(fid), inode, inode->i_nlink, inode->i_mode);
obj->oo_inode = inode;
RETURN(0);
}
static int osd_mkdir(const struct lu_env *env, struct osd_object *obj,
struct lu_attr *attr,
struct dt_allocation_hint *hint,
struct dt_object_format *dof,
struct thandle *th)
{
__u32 mode = (attr->la_mode & (S_IFMT | 0777 | S_ISVTX | S_ISGID));
LASSERT(S_ISDIR(attr->la_mode));
return osd_mkfile(env, obj, mode, hint, th, attr);
}
static int osd_mk_index(const struct lu_env *env, struct osd_object *obj,
struct lu_attr *attr,
struct dt_allocation_hint *hint,
struct dt_object_format *dof,
struct thandle *th)
{
__u32 mode = (attr->la_mode & (S_IFMT | S_IALLUGO | S_ISVTX));
const struct dt_index_features *feat = dof->u.dof_idx.di_feat;
struct memfs_inode_info *mei;
size_t keylen = 0;
size_t reclen = 0;
int rc;
ENTRY;
LASSERT(S_ISREG(attr->la_mode));
/* Only support index with fixed key length. */
if (feat->dif_flags & DT_IND_VARKEY)
RETURN(-EINVAL);
keylen = feat->dif_keysize_max;
if (!(feat->dif_flags & DT_IND_VARREC))
reclen = feat->dif_recsize_max;
rc = osd_mkfile(env, obj, mode, hint, th, attr);
if (rc)
GOTO(out, rc);
LASSERT(obj->oo_inode != NULL);
mei = MEMFS_I(obj->oo_inode);
mei->mei_index_type = INDEX_TYPE_HASH;
rc = hash_index_init(&mei->mei_hash_index, keylen, reclen);
if (rc) {
CERROR("%s: failed to create index for FID="DFID": rc=%d\n",
osd_name(osd_obj2dev(obj)),
PFID(lu_object_fid(&obj->oo_dt.do_lu)), rc);
/* TODO: cleanup @oo_inode... */
}
out:
RETURN(rc);
}
static int osd_mkreg(const struct lu_env *env, struct osd_object *obj,
struct lu_attr *attr,
struct dt_allocation_hint *hint,
struct dt_object_format *dof,
struct thandle *th)
{
LASSERT(S_ISREG(attr->la_mode));
return osd_mkfile(env, obj, (attr->la_mode &
(S_IFMT | S_IALLUGO | S_ISVTX)), hint, th,
attr);
}
static int osd_mksym(const struct lu_env *env, struct osd_object *obj,
struct lu_attr *attr,
struct dt_allocation_hint *hint,
struct dt_object_format *dof,
struct thandle *th)
{
LASSERT(S_ISLNK(attr->la_mode));
/* TODO: symlink support. */
RETURN(-EOPNOTSUPP);
}
static int osd_mknod(const struct lu_env *env, struct osd_object *obj,
struct lu_attr *attr,
struct dt_allocation_hint *hint,
struct dt_object_format *dof,
struct thandle *th)
{
umode_t mode = attr->la_mode & (S_IFMT | S_IALLUGO | S_ISVTX);
int result;
LASSERT(obj->oo_inode == NULL);
LASSERT(S_ISCHR(mode) || S_ISBLK(mode) ||
S_ISFIFO(mode) || S_ISSOCK(mode));
result = osd_mkfile(env, obj, mode, hint, th, attr);
if (result == 0) {
LASSERT(obj->oo_inode != NULL);
/*
* This inode should be marked dirty for i_rdev. Currently
* that is done in the osd_attr_init().
*/
init_special_inode(obj->oo_inode, obj->oo_inode->i_mode,
attr->la_rdev);
}
return result;
}
typedef int (*osd_obj_type_f)(const struct lu_env *env,
struct osd_object *obj,
struct lu_attr *attr,
struct dt_allocation_hint *hint,
struct dt_object_format *dof,
struct thandle *th);
static osd_obj_type_f osd_create_type_f(enum dt_format_type type)
{
osd_obj_type_f result;
switch (type) {
case DFT_DIR:
result = osd_mkdir;
break;
case DFT_REGULAR:
result = osd_mkreg;
break;
case DFT_SYM:
result = osd_mksym;
break;
case DFT_NODE:
result = osd_mknod;
break;
case DFT_INDEX:
result = osd_mk_index;
break;
default:
LBUG();
break;
}
return result;
}
static void osd_attr_init(const struct lu_env *env, struct osd_object *obj,
struct lu_attr *attr, struct dt_object_format *dof,
struct thandle *handle)
{
struct inode *inode = obj->oo_inode;
__u64 valid = attr->la_valid;
int result;
attr->la_valid &= ~(LA_TYPE | LA_MODE);
if (dof->dof_type != DFT_NODE)
attr->la_valid &= ~LA_RDEV;
if ((valid & LA_ATIME) &&
(attr->la_atime == inode_get_atime_sec(inode)))
attr->la_valid &= ~LA_ATIME;
if ((valid & LA_CTIME) &&
(attr->la_ctime == inode_get_ctime_sec(inode)))
attr->la_valid &= ~LA_CTIME;
if ((valid & LA_MTIME) &&
(attr->la_mtime == inode_get_mtime_sec(inode)))
attr->la_valid &= ~LA_MTIME;
/* TODO: Perform quota transfer. */
if (attr->la_valid != 0) {
result = osd_inode_setattr(env, inode, attr);
/*
* The osd_inode_setattr() should always succeed here. The
* only error that could be returned is EDQUOT when we are
* trying to change the UID or GID of the inode. However, this
* should not happen since quota enforcement is no longer
* enabled on MemFS (lquota is supported and takes care of it).
*/
LASSERTF(result == 0, "%d\n", result);
}
attr->la_valid = valid;
}
/* Helper function for osd_create(). */
static int __osd_create(const struct lu_env *env, struct osd_object *obj,
struct lu_attr *attr, struct dt_allocation_hint *hint,
struct dt_object_format *dof, struct thandle *th)
{
int result;
__u32 umask;
/* we drop umask so that permissions we pass are not affected */
umask = current->fs->umask;
current->fs->umask = 0;
result = osd_create_type_f(dof->dof_type)(env, obj, attr, hint, dof,
th);
if (likely(obj->oo_inode && result == 0)) {
LASSERT(inode_state_read_once(obj->oo_inode) & I_NEW);
/*
* Unlock the inode before attr initialization to avoid
* unnecessary dqget operations. LU-6378
*/
unlock_new_inode(obj->oo_inode);
osd_attr_init(env, obj, attr, dof, th);
__osd_object_init(obj);
}
/* restore previous umask value */
current->fs->umask = umask;
return result;
}
static void osd_ah_init(const struct lu_env *env, struct dt_allocation_hint *ah,
struct dt_object *parent, struct dt_object *child,
umode_t child_mode)
{
LASSERT(ah);
ah->dah_parent = parent;
}
/* OSD layer object creation funcation for OST objects. */
static int osd_create(const struct lu_env *env, struct dt_object *dt,
struct lu_attr *attr, struct dt_allocation_hint *hint,
struct dt_object_format *dof, struct thandle *th)
{
const struct lu_fid *fid = lu_object_fid(&dt->do_lu);
struct osd_object *obj = osd_dt_obj(dt);
int rc;
ENTRY;
if (dt_object_exists(dt))
RETURN(-EEXIST);
LASSERT(!dt_object_remote(dt));
LASSERT(dt_write_locked(env, dt));
/* Quota files cannot be created from the kernel any more */
if (unlikely(fid_is_acct(fid)))
RETURN(-EPERM);
rc = __osd_create(env, obj, attr, hint, dof, th);
/* TODO: Update LMA EA with @fid. */
LASSERT(ergo(rc == 0,
dt_object_exists(dt) && !dt_object_remote(dt)));
RETURN(rc);
}
static int osd_destroy(const struct lu_env *env, struct dt_object *dt,
struct thandle *th)
{
const struct lu_fid *fid = lu_object_fid(&dt->do_lu);
struct osd_object *obj = osd_dt_obj(dt);
struct inode *inode = obj->oo_inode;
struct osd_device *osd = osd_obj2dev(obj);
ENTRY;
LASSERT(inode);
LASSERT(!lu_object_is_dying(dt->do_lu.lo_header));
if (unlikely(fid_is_acct(fid)))
RETURN(-EPERM);
/* TODO: Agent entry remvoal... */
if (S_ISDIR(inode->i_mode)) {
if (inode->i_nlink > 2)
CERROR("%s: dir "DFID" ino %lu nlink %u at unlink.\n",
osd_name(osd), PFID(fid), inode->i_ino,
inode->i_nlink);
down_write(&obj->oo_guard);
clear_nlink(inode);
up_write(&obj->oo_guard);
}
set_bit(LU_OBJECT_HEARD_BANSHEE, &dt->do_lu.lo_header->loh_flags);
obj->oo_destroyed = 1;
CDEBUG(D_INODE,
"%s: Object "DFID" destroyed: inode@%pK nlink=%d mode=%#o\n",
osd_name(osd), PFID(lu_object_fid(&dt->do_lu)), inode,
inode->i_nlink, inode->i_mode);
RETURN(0);
}
/*
* Concurrency: @dt is write locked.
*/
static int osd_ref_add(const struct lu_env *env, struct dt_object *dt,
struct thandle *th)
{
struct osd_object *obj = osd_dt_obj(dt);
struct inode *inode = obj->oo_inode;
int rc = 0;
if (!dt_object_exists(dt) || obj->oo_destroyed)
return -ENOENT;
LASSERT(!dt_object_remote(dt));
LASSERT(dt_write_locked(env, dt));
CDEBUG(D_INODE, "%s:"DFID" increase nlink %d inode@%pK\n",
osd_name(osd_obj2dev(obj)), PFID(lu_object_fid(&dt->do_lu)),
inode->i_nlink, inode);
/*
* The DIR_NLINK feature allows directories to exceed LDISKFS_LINK_MAX
* (65000) subdirectories by storing "1" in i_nlink if the link count
* would otherwise overflow. Directory tranversal tools understand
* that (st_nlink == 1) indicates that the filesystem dose not track
* hard links count on the directory, and will not abort subdirectory
* scanning early once (st_nlink - 2) subdirs have been found.
*
* This also has to properly handle the case of inodes with nlink == 0
* in case they are being linked into the PENDING directory
*/
down_write(&obj->oo_guard);
if (unlikely(inode->i_nlink == 0))
/* inc_nlink from 0 may cause WARN_ON */
set_nlink(inode, 1);
else
inc_nlink(inode);
up_write(&obj->oo_guard);
return rc;
}
/*
* Concurrency: @dt is write locked.
*/
static int osd_ref_del(const struct lu_env *env, struct dt_object *dt,
struct thandle *th)
{
struct osd_object *obj = osd_dt_obj(dt);
struct inode *inode = obj->oo_inode;
struct osd_device *osd = osd_dev(dt->do_lu.lo_dev);
if (!dt_object_exists(dt))
return -ENOENT;
LASSERT(!dt_object_remote(dt));
LASSERT(dt_write_locked(env, dt));
if (CFS_FAIL_CHECK(OBD_FAIL_OSD_REF_DEL))
return -EIO;
down_write(&obj->oo_guard);
if (inode->i_nlink == 0) {
CDEBUG_LIMIT(fid_is_norm(lu_object_fid(&dt->do_lu)) ?
D_ERROR : D_INODE, "%s: nlink == 0 on "DFID".\n",
osd_name(osd), PFID(lu_object_fid(&dt->do_lu)));
up_write(&obj->oo_guard);
return 0;
}
CDEBUG(D_INODE, DFID" decrease nlink %d inode@%pK\n",
PFID(lu_object_fid(&dt->do_lu)), inode->i_nlink, inode);
if (!S_ISDIR(inode->i_mode) || inode->i_nlink > 2)
drop_nlink(inode);
up_write(&obj->oo_guard);
return 0;
}
/* Concurrency: @dt is write locked. */
static int osd_xattr_set(const struct lu_env *env, struct dt_object *dt,
const struct lu_buf *buf, const char *name, int fl,
struct thandle *handle)
{
struct inode *inode = osd_dt_obj(dt)->oo_inode;
int flags = 0;
int rc;
ENTRY;
LASSERT(inode);
LASSERT(buf);
if (fl & LU_XATTR_REPLACE)
flags |= XATTR_REPLACE;
if (fl & LU_XATTR_CREATE)
flags |= XATTR_CREATE;
/* FIXME: using VFS i_op->setxattr()? */
rc = memfs_xattr_set(inode, buf->lb_buf, buf->lb_len, name, flags);
RETURN(rc);
}
/* Concurrency: @dt is read locked. */
static int osd_xattr_get(const struct lu_env *env, struct dt_object *dt,
struct lu_buf *buf, const char *name)
{
struct inode *inode = osd_dt_obj(dt)->oo_inode;
int rc;
ENTRY;
LASSERT(buf);
if (!dt_object_exists(dt))
RETURN(-ENOENT);
LASSERT(!dt_object_remote(dt));
/* FIXME: using VFS i_op->getxattr()? */
rc = memfs_xattr_get(inode, buf->lb_buf, buf->lb_len, name);
RETURN(rc);
}
/* Concurrency: @dt is write locked. */
static int osd_xattr_del(const struct lu_env *env, struct dt_object *dt,
const char *name, struct thandle *handle)
{
struct inode *inode = osd_dt_obj(dt)->oo_inode;
if (!dt_object_exists(dt))
return -ENOENT;
LASSERT(!dt_object_remote(dt));
/* FIXME: using VFS i_op->removexattr() */
memfs_xattr_del(inode, name);
return 0;
}
/* TODO: Implement xattr listing. */
static int osd_xattr_list(const struct lu_env *env, struct dt_object *dt,
const struct lu_buf *buf)
{
RETURN(0);
}
/* MemFS does not support object sync, return zero to ignore the error. */
static int osd_object_sync(const struct lu_env *env, struct dt_object *dt,
__u64 start, __u64 end)
{
RETURN(0);
}
const struct dt_object_operations osd_obj_ops = {
.do_attr_get = osd_attr_get,
.do_attr_set = osd_attr_set,
.do_ah_init = osd_ah_init,
.do_create = osd_create,
.do_destroy = osd_destroy,
.do_index_try = osd_index_try,
.do_ref_add = osd_ref_add,
.do_ref_del = osd_ref_del,
.do_xattr_get = osd_xattr_get,
.do_xattr_set = osd_xattr_set,
.do_xattr_del = osd_xattr_del,
.do_xattr_list = osd_xattr_list,
.do_object_sync = osd_object_sync,
};
const struct lu_object_operations osd_lu_obj_ops = {
.loo_object_init = osd_object_init,
.loo_object_delete = osd_object_delete,
.loo_object_release = osd_object_release,
.loo_object_free = osd_object_free,
.loo_object_print = osd_object_print,
};