/* SPDX-License-Identifier: GPL-2.0 */

/*
 * Copyright (c) 2025-2026, DDN/Whamcloud, Inc.
 */

/*
 * Author: Yingjin Qian <qian@ddn.com>
 */

#ifndef _OSD_INTERNAL_H
#define _OSD_INTERNAL_H

#include <linux/rwsem.h>
#include <linux/dcache.h>
#include <linux/dirent.h>
#include <linux/statfs.h>
#include <linux/file.h>
#include <lustre_compat.h>

#include <obd.h>
#include <obd_class.h>
#include <dt_object.h>

struct osd_object {
	struct dt_object	 oo_dt;

	/*
	 * Inode in the memory FS for file system object represented by this
	 * osd_object. This inode is pinned for the whole duration of the file
	 * life.
	 */
	struct inode		*oo_inode;
	struct rw_semaphore	 oo_guard;

	/* the i_flags in LMA */
	__u32			 oo_lma_flags;
	__u32			 oo_destroyed:1;

	struct lu_object_header	*oo_header;
};

struct osd_device {
	/* Super-class */
	struct dt_device	 od_dt_dev;
	/* Information about underlying memory file system */
	struct vfsmount		*od_mnt;
	/* Service name associated with the OSD device. */
	char			 od_svname[MAX_OBD_NAME];
	char			 od_mntdev[MAX_OBD_NAME];
	int			 od_index;
	atomic_t		 od_connects;
	struct lu_site		 od_site;

	/*
	 * Enable to write back the data in the memory FS into the
	 * persistent storage.
	 */
	unsigned int		 od_writeback_enabled:1;
	unsigned int		 od_is_ost:1;
};

struct osd_thandle {
	struct thandle		ot_super;
	struct list_head	ot_commit_dcb_list;
	struct list_head	ot_stop_dcb_list;
};

struct osd_it_dirent {
	struct lu_fid	oitd_fid;
	__u64           oitd_ino;
	__u64           oitd_off;
	unsigned short  oitd_namelen;
	unsigned int    oitd_type;
	char            oitd_name[];
} __attribute__((packed));

/*
 * As @osd_it_dirent (in memory dirent struct for osd) is greater
 * than lu_dirent struct. osd readdir reads less number of dirent than
 * required for mdd dir page. so buffer size need to be increased so that
 * there would be one MemFS readdir for every mdd readdir page.
 */

#define OSD_IT_BUFSIZE       (PAGE_SIZE + PAGE_SIZE/4)

struct osd_it {
	struct osd_object	*oit_obj;
	struct file		 oit_file;
	/* How many entries have been read-cached from storage */
	int			 oit_rd_dirent;
	/* Current entry is being iterated by caller */
	int			 oit_it_dirent;
	/* Current processing entry */
	struct osd_it_dirent	*oit_dirent;
	/* Buffer to hold entries, size == OSD_IT_BUFSIZE */
	void			*oit_buf;
};

extern atomic_t descriptors_cnt;
extern unsigned int wbcfs_flush_descriptors_cnt;
extern struct work_struct flush_fput;
#define osd_alloc_file_pseudo(inode, mnt, name, flags, fops)		\
({									\
	struct file *__f;						\
	int __descriptors_cnt;						\
	__f = alloc_file_pseudo(inode, mnt, name, flags, fops);		\
	__descriptors_cnt = atomic_inc_return(&descriptors_cnt);	\
	if (unlikely(__descriptors_cnt >= wbcfs_flush_descriptors_cnt)) {\
		/* drop here to skip queue_work */			\
		atomic_set(&descriptors_cnt, 0);			\
		queue_work(system_long_wq, &flush_fput);		\
	}								\
	__f;								\
})

/* Slab to allocate osd_it */
extern struct kmem_cache *osd_it_cachep;

struct osd_hash_it {
	struct list_head	*hit_cursor;
	struct osd_object	*hit_obj;
};

extern struct kmem_cache *osd_hash_it_cachep;

extern const struct dt_body_operations osd_wbcfs_body_ops;
extern const struct dt_object_operations osd_obj_ops;
extern const struct lu_object_operations osd_lu_obj_ops;
extern const struct lu_device_operations osd_wbcfs_lu_ops;
extern const struct dt_index_operations osd_wbcfs_dir_ops;
extern const struct dt_index_operations osd_hash_index_ops;

static inline int lu_device_is_osd(const struct lu_device *d)
{
	return ergo(d != NULL && d->ld_ops != NULL, d->ld_ops == &osd_wbcfs_lu_ops);
}

static inline struct osd_device *osd_dt_dev(const struct dt_device *d)
{
	LASSERT(lu_device_is_osd(&d->dd_lu_dev));
	return container_of(d, struct osd_device, od_dt_dev);
}

static inline struct osd_device *osd_dev(const struct lu_device *d)
{
	LASSERT(lu_device_is_osd(d));
	return osd_dt_dev(container_of(d, struct dt_device, dd_lu_dev));
}

static inline struct osd_device *osd_obj2dev(const struct osd_object *o)
{
	return osd_dev(o->oo_dt.do_lu.lo_dev);
}

static inline struct super_block *osd_sb(const struct osd_device *dev)
{
	if (!dev->od_mnt)
		return NULL;

	return dev->od_mnt->mnt_sb;
}

static inline char *osd_name(struct osd_device *osd)
{
	return osd->od_svname;
}

static inline struct lu_device *osd2lu_dev(struct osd_device *osd)
{
	return &osd->od_dt_dev.dd_lu_dev;
}

static inline struct osd_object *osd_obj(const struct lu_object *o)
{
	LASSERT(lu_device_is_osd(o->lo_dev));
	return container_of(o, struct osd_object, oo_dt.do_lu);
}

/*
 * Put the osd object once done with it.
 *
 * \param obj osd object that needs to be put
 */
static inline void osd_object_put(const struct lu_env *env,
				  struct osd_object *obj)
{
	dt_object_put(env, &obj->oo_dt);
}

static inline struct osd_object *osd_dt_obj(const struct dt_object *d)
{
	return osd_obj(&d->do_lu);
}

static inline struct timespec64 osd_inode_time(struct inode *inode,
					       s64 seconds)
{
	struct timespec64 ts = { .tv_sec = seconds };

	return ts;
}

#ifdef HAVE_FILLDIR_USE_CTX_RETURN_BOOL
#define WRAP_FILLDIR_FN(prefix, fill_fn) \
static bool fill_fn(struct dir_context *buf, const char *name, int namelen, \
		    loff_t offset, __u64 ino, unsigned int d_type)	    \
{									    \
	return !prefix##fill_fn(buf, name, namelen, offset, ino, d_type);   \
}
#else
#define WRAP_FILLDIR_FN(prefix, fill_fn) \
static int fill_fn(struct dir_context *buf, const char *name, int namelen,  \
		   loff_t offset, __u64 ino, unsigned int d_type)	    \
{									    \
	return prefix##fill_fn(buf, name, namelen, offset, ino, d_type);    \
}
#endif

/*
 * Build inode number from passed @fid.
 *
 * For 32-bit systems or syscalls limit the inode number to a 32-bit value
 * to avoid EOVERFLOW errors.  This will inevitably result in inode number
 * collisions, but fid_flatten32() tries hard to avoid this if possible.
 */
static inline __u64 lu_fid_build_ino(const struct lu_fid *fid, int api32)
{
	if (BITS_PER_LONG == 32 || api32)
		RETURN(fid_flatten32(fid));

	RETURN(fid_flatten64(fid));
}

/*
 * Build inode generation from passed @fid.  If our FID overflows the 32-bit
 * inode number then return a non-zero generation to distinguish them.
 */
static inline __u32 lu_fid_build_gen(const struct lu_fid *fid)
{
	if (fid_is_igif(fid))
		RETURN(lu_igif_gen(fid));

	RETURN(fid_flatten64(fid) >> 32);
}

/*
 * Lustre procfs
 */

int osd_wbcfs_procfs_init(struct osd_device *osd, const char *name);
void osd_wbcfs_procfs_fini(struct osd_device *osd);

#endif /* _OSD_INTERNAL_H */
