/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (C) 2017-2018 HUAWEI, Inc.
 *             https://www.huawei.com/
 * Copyright (C) 2021, Alibaba Cloud
 */

#ifndef __EROFS_INTERNAL_H
#define __EROFS_INTERNAL_H

#include <sys/types.h>
#include <sys/param.h> // MUST FIRST
#include <sys/condvar.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/mutex.h>
#include <sys/queue.h>
#include <sys/vnode.h>

#include "erofs_fs.h"

MALLOC_DECLARE(M_EROFS);

struct cdev;
struct g_consumer;
struct erofs_device_info;

/* EROFS_SUPER_MAGIC_V1 to represent the whole file system */
#define EROFS_SUPER_MAGIC EROFS_SUPER_MAGIC_V1

typedef uint64_t erofs_nid_t;
typedef uint64_t erofs_off_t;
typedef uint64_t erofs_blk_t;

#define EROFS_MOUNT_POSIX_ACL  0x00000020

#define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option)
#define set_opt(opt, option)   ((opt)->mount_opt |= EROFS_MOUNT_##option)
#define test_opt(opt, option)  ((opt)->mount_opt & EROFS_MOUNT_##option)

struct erofs_sb_info_opts {
	unsigned int mount_opt;
};

struct erofs_device_info {
	struct vnode *devvp;
	struct cdev *dev;
	struct g_consumer *cp;
	uint64_t mediasize;
	uint32_t sectorsize;

	erofs_blk_t blocks;
	erofs_blk_t uniaddr;
};

struct erofs_xattr_prefix_item {
	uint8_t base_index;
	uint8_t infix_len;
	char *infix;
};

struct erofs_map_blocks {
	erofs_off_t m_pa, m_la;
	uint64_t m_plen, m_llen;

	unsigned short m_deviceid;
	uint8_t m_algorithmformat;
	unsigned int m_flags;
};

enum erofs_zextent_cache_state {
	EROFS_ZCACHE_EMPTY,
	EROFS_ZCACHE_INFLIGHT,
	EROFS_ZCACHE_READY,
	EROFS_ZCACHE_FAILED,
};

struct erofs_zextent_cache_metrics {
	uint64_t hits;
	uint64_t misses;
	uint64_t bypasses;
	uint64_t evictions;
	uint64_t reclaims;
	size_t resident_bytes;
};

struct erofs_zextent_cache {
	struct erofs_map_blocks map;
	void *data;
	erofs_nid_t nid;
	size_t decoded_size;
	size_t charged_bytes;
	size_t budget_bytes;
	uint64_t minimum_decode_work;
	struct erofs_zextent_cache_metrics metrics[Z_EROFS_COMPRESSION_MAX];
	unsigned int waiters;
	int error;
	enum erofs_zextent_cache_state state;
	struct cv cv;
	bool closing;
};

enum erofs_xattr_cache_state {
	EROFS_XATTR_CACHE_EMPTY,
	EROFS_XATTR_CACHE_INFLIGHT,
	EROFS_XATTR_CACHE_READY,
	EROFS_XATTR_CACHE_FAILED,
};

struct erofs_xattr_cache {
	struct mtx lock;
	struct cv cv;
	void *data;
	size_t size;
	size_t charged_bytes;
	unsigned int waiters;
	int error;
	enum erofs_xattr_cache_state state;
	bool closing;
	bool initialized;
};

#define EROFS_STREAM_CTX_WRAPPER_SIZE 256

struct erofs_stream_ctx;
typedef int erofs_stream_ctx_init_t(struct erofs_stream_ctx *ctx);
typedef void erofs_stream_ctx_fini_t(struct erofs_stream_ctx *ctx);

struct erofs_stream_ctx {
	STAILQ_ENTRY(erofs_stream_ctx) link;
	struct erofs_sb_info *sbi;
	erofs_stream_ctx_fini_t *fini;
	size_t charged_bytes;
	uint8_t algorithm;
	bool cached;
	bool allocation_failed;
};

STAILQ_HEAD(erofs_stream_ctx_head, erofs_stream_ctx);

struct erofs_stream_pool_codec {
	struct erofs_stream_ctx_head idle;
	unsigned int contexts;
	unsigned int cached;
	unsigned int borrowed;
	unsigned int idle_count;
};

struct erofs_stream_pool {
	struct erofs_stream_pool_codec codec[Z_EROFS_COMPRESSION_MAX];
	size_t resident_bytes;
	struct cv cv;
	bool closing;
};

struct erofs_sb_info {
	struct mount *mnt;
	struct erofs_device_info dif0;

	uint32_t block_size;
	uint32_t sb_size;
	uint8_t blkszbits;
	uint32_t meta_blkaddr;
	uint32_t xattr_blkaddr;
	uint32_t xattr_prefix_start;
	uint8_t xattr_prefix_count;
	uint8_t xattr_filter_reserved;
	erofs_nid_t packed_nid;
	erofs_nid_t metabox_nid;
	struct erofs_inode *metabox_en;
	struct erofs_inode *packed_inode;
	struct erofs_xattr_prefix_item *xattr_prefixes;
	erofs_blk_t blocks;
	uint64_t inos;
	erofs_nid_t root_nid;
	int64_t epoch;
	uint32_t fixed_nsec;
	uint32_t generation_seed;
	uint32_t feature_compat;
	uint32_t feature_incompat;
	char volume_name[17];

	struct erofs_sb_info_opts opt;
	uint16_t available_compr_algs;
	uint32_t lzma_dict_size;
	uint8_t deflate_windowbits;
	uint8_t zstd_windowlog;

	/* Device table */
	uint16_t extra_devices;
	uint16_t device_id_mask;
	bool flatdev;
	erofs_blk_t total_blocks;
	erofs_blk_t flatdev_blocks;
	struct erofs_device_info *devs;
	struct mtx z_extent_cache_lock;
	struct erofs_zextent_cache z_extent_cache;
	LIST_ENTRY(erofs_sb_info) z_extent_cache_link;
	bool z_extent_cache_initialized;
	struct erofs_stream_pool stream_pool;
	LIST_ENTRY(erofs_sb_info) stream_pool_link;
	bool stream_pool_initialized;
	volatile u_long xattr_cache_resident;
};

#define MTOE(mp) ((struct erofs_sb_info *)(mp)->mnt_data)

#define EROFS_FEATURE_FUNCS(name, compat, feature)                          \
	static inline bool erofs_sb_has_##name(const struct erofs_sb_info *sbi) \
	{                                                                   \
		return (                                                    \
		    (sbi->feature_##compat & EROFS_FEATURE_##feature) != 0); \
	}

EROFS_FEATURE_FUNCS(lz4_0padding, incompat, INCOMPAT_LZ4_0PADDING)
EROFS_FEATURE_FUNCS(compr_cfgs, incompat, INCOMPAT_COMPR_CFGS)
EROFS_FEATURE_FUNCS(big_pcluster, incompat, INCOMPAT_BIG_PCLUSTER)
EROFS_FEATURE_FUNCS(chunked_file, incompat, INCOMPAT_CHUNKED_FILE)
EROFS_FEATURE_FUNCS(device_table, incompat, INCOMPAT_DEVICE_TABLE)
EROFS_FEATURE_FUNCS(compr_head2, incompat, INCOMPAT_COMPR_HEAD2)
EROFS_FEATURE_FUNCS(ztailpacking, incompat, INCOMPAT_ZTAILPACKING)
EROFS_FEATURE_FUNCS(fragments, incompat, INCOMPAT_FRAGMENTS)
EROFS_FEATURE_FUNCS(dedupe, incompat, INCOMPAT_DEDUPE)
EROFS_FEATURE_FUNCS(xattr_prefixes, incompat, INCOMPAT_XATTR_PREFIXES)
EROFS_FEATURE_FUNCS(48bit, incompat, INCOMPAT_48BIT)
EROFS_FEATURE_FUNCS(metabox, incompat, INCOMPAT_METABOX)
EROFS_FEATURE_FUNCS(sb_chksum, compat, COMPAT_SB_CHKSUM)
EROFS_FEATURE_FUNCS(xattr_filter, compat, COMPAT_XATTR_FILTER)
EROFS_FEATURE_FUNCS(shared_ea_in_metabox, compat, COMPAT_SHARED_EA_IN_METABOX)
EROFS_FEATURE_FUNCS(plain_xattr_pfx, compat, COMPAT_PLAIN_XATTR_PFX)
EROFS_FEATURE_FUNCS(ishare_xattrs, compat, COMPAT_ISHARE_XATTRS)

static inline bool
erofs_sb_has_xattr_filter_v1(const struct erofs_sb_info *sbi)
{
	return (erofs_sb_has_xattr_filter(sbi) &&
	    sbi->xattr_filter_reserved == 0);
}

struct erofs_inode {
	erofs_nid_t nid;
	uint64_t size;
	uint64_t data_blocks;
	/* Absolute device offset, or metabox-file offset when bit 63 is set. */
	erofs_off_t inode_off;
	erofs_blk_t startblk;
	uint32_t generation;
	uint32_t nlink;
	uid_t uid;
	gid_t gid;
	mode_t mode;
	__enum_uint8(vtype) vtype;
	dev_t rdev;
	time_t mtime;
	uint32_t mtime_nsec;
	uint8_t datalayout;
	uint8_t inode_isize;
	uint32_t xattr_isize;
	bool compact_inode;
	bool dot_omitted;
	/* Compression fields */
	uint16_t z_advise;
	uint8_t z_algorithmtype[2];
	uint8_t z_lclusterbits;
	uint16_t z_idata_size;
	erofs_off_t z_fragmentoff;
	uint64_t z_tailextent_headlcn;
	uint64_t z_extents;
	bool z_initialized;
	/* Chunk-based fields */
	uint16_t chunkformat;
	uint8_t chunkbits;
	/* Fragment fields */
	bool fragment;
	struct erofs_xattr_cache xattr_cache;
};

struct erofs_fid {
	uint16_t len;
	uint16_t pad;
	uint32_t nid_hi;
	uint32_t nid_lo;
	uint32_t gen;
};

_Static_assert(sizeof(struct erofs_fid) == 16,
    "EROFS file handle ABI must be 16 bytes");
_Static_assert(sizeof(struct erofs_fid) <= sizeof(struct fid),
    "struct erofs_fid must fit within struct fid");

#define VTOE(vp) ((struct erofs_inode *)(vp)->v_data)

static inline unsigned int
erofs_inode_version(unsigned int ifmt)
{
	return ((ifmt >> EROFS_I_VERSION_BIT) & EROFS_I_VERSION_MASK);
}

static inline unsigned int
erofs_inode_datalayout(unsigned int ifmt)
{
	return ((ifmt >> EROFS_I_DATALAYOUT_BIT) & EROFS_I_DATALAYOUT_MASK);
}

static inline bool
erofs_nid_in_metabox(erofs_nid_t nid)
{
	return ((nid & EROFS_DIRENT_NID_METABOX) != 0);
}

/* Allocated on disk at m_pa. */
#define EROFS_MAP_MAPPED	 0x0001
/* Located in metadata. */
#define EROFS_MAP_META		 0x0002
#define EROFS_MAP_PARTIAL_MAPPED 0x0004
#define EROFS_MAP_PARTIAL_REF	 0x0008
#define EROFS_MAP_FRAGMENT	 0x0010
#define EROFS_MAP_FULL(f) \
	(!((f) & (EROFS_MAP_PARTIAL_MAPPED | EROFS_MAP_PARTIAL_REF)))

#define EROFS_GET_BLOCKS_FIEMAP	  0x0001
#define EROFS_GET_BLOCKS_READMORE 0x0002
#define EROFS_GET_BLOCKS_FINDTAIL 0x0004

enum {
	Z_EROFS_COMPRESSION_SHIFTED = Z_EROFS_COMPRESSION_MAX,
	Z_EROFS_COMPRESSION_INTERLACED,
	Z_EROFS_COMPRESSION_RUNTIME_MAX
};

_Static_assert(Z_EROFS_COMPRESSION_RUNTIME_MAX <= UINT8_MAX,
    "algorithm format must fit in uint8_t");

struct erofs_map_dev {
	struct erofs_device_info *m_dif;

	erofs_off_t m_pa;
	unsigned int m_deviceid;
	uint64_t m_plen;
};

struct erofs_buf {
	void *data;
	void (*release)(void *);
};

#define EROFS_BUF_INITIALIZER { .data = NULL, .release = NULL }

/* Buffer and device I/O. */
int erofs_bread(struct erofs_sb_info *sbi, erofs_off_t off, size_t len, void **bufp);
int erofs_read_physical(struct erofs_sb_info *sbi, unsigned int device_id,
    erofs_off_t off, size_t len, void **bufp);
void erofs_brelse(void *buf);
void erofs_put_metabuf(struct erofs_buf *buf);
int erofs_read_metadata(struct erofs_sb_info *sbi, erofs_nid_t nid,
    erofs_off_t off, size_t len, struct erofs_buf *buf);
/* Logical mapping and file data. */
int erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    struct erofs_map_blocks *map);
int erofs_read_data(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    erofs_off_t loff, size_t len, void **bufp);
int erofs_read_data_readahead(struct erofs_sb_info *sbi,
    struct erofs_inode *vi, erofs_off_t loff, size_t len, bool sequential,
    void **bufp);
int erofs_read_file(struct vnode *vp, struct uio *uio, int ioflag);
int erofs_validate_symlink_target(struct erofs_sb_info *sbi,
    struct erofs_inode *vi);
int erofs_readlink_target(struct vnode *vp, struct uio *uio);

/* Inode and vnode lifecycle. */
bool erofs_nid_is_valid(struct erofs_sb_info *sbi, erofs_nid_t nid);
bool erofs_dirent_type_matches(uint8_t file_type,
    __enum_uint8(vtype) vtype);
int erofs_read_inode(struct erofs_sb_info *sbi, erofs_nid_t nid,
    struct erofs_inode *vi);
int erofs_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp);
void erofs_xattr_cache_init(struct erofs_inode *vi);
void erofs_xattr_cache_fini(struct erofs_sb_info *sbi,
    struct erofs_inode *vi);

/* Directory operations. */
int erofs_readdir_block(struct vnode *vp, struct uio *uio, int *eofflag,
    int *ncookies, uint64_t **cookies);
int erofs_dirent_namelen(const char *blk, uint32_t nameoff, uint32_t endoff,
    bool trailing, size_t *namelenp);
int erofs_validate_dirblock(const char *blk, uint32_t blksz, uint32_t maxsize,
    uint32_t *ndirentsp);
int erofs_lookup(struct vop_cachedlookup_args *ap);

/* Compressed mapping and data. */
int z_erofs_fill_inode(struct erofs_sb_info *sbi, struct erofs_inode *vi);
int z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    struct erofs_map_blocks *map);
void z_erofs_extent_cache_init(struct erofs_sb_info *sbi);
void z_erofs_extent_cache_fini(struct erofs_sb_info *sbi);
void z_erofs_stream_pool_init(struct erofs_sb_info *sbi);
void z_erofs_stream_pool_fini(struct erofs_sb_info *sbi);
int z_erofs_stream_ctx_get(struct erofs_sb_info *sbi, uint8_t algorithm,
    size_t context_size, erofs_stream_ctx_init_t *init,
    erofs_stream_ctx_fini_t *fini, struct erofs_stream_ctx **ctxp);
void z_erofs_stream_ctx_put(struct erofs_stream_ctx *ctx, bool reusable);
int z_erofs_stream_ctx_charge(struct erofs_stream_ctx *ctx, size_t bytes);
void z_erofs_stream_ctx_uncharge(struct erofs_stream_ctx *ctx, size_t bytes);
void *z_erofs_stream_ctx_alloc(struct erofs_stream_ctx *ctx, size_t bytes);
void z_erofs_stream_ctx_free(struct erofs_stream_ctx *ctx, void *address);
int z_erofs_read_data(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    erofs_off_t loff, size_t len, void **bufp);
int z_erofs_read_uio(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    struct uio *uio);
int z_erofs_decompress(struct erofs_sb_info *sbi,
    const struct erofs_map_blocks *map, const void *src, size_t srclen,
    void *dst, size_t dstlen, bool partial);

/* Compression configuration. */
int z_erofs_parse_cfgs(struct erofs_sb_info *sbi,
    const struct erofs_super_block *dsb);

/* VOP vectors. */
extern struct vop_vector erofs_vnodeops;
extern struct vop_vector erofs_fifoops;

#endif /* __EROFS_INTERNAL_H */
