/*-
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (c) 1994 The Regents of the University of California.
 * Copyright (c) 1994 Jan-Simon Pendry.
 * Copyright (c) 2005, 2006 Masanori Ozawa <ozawa@ongs.co.jp>, ONGS Inc.
 * Copyright (c) 2006 Daichi Goto <daichi@freebsd.org>
 * All rights reserved.
 *
 * This code is derived from software donated to Berkeley by
 * Jan-Simon Pendry.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef _KERNEL

/* copy method of attr from lower to upper */
typedef enum _unionfs_copymode {
	UNIONFS_TRADITIONAL = 0,
	UNIONFS_TRANSPARENT,
	UNIONFS_MASQUERADE
} unionfs_copymode;

/* whiteout policy of upper layer */
typedef enum _unionfs_whitemode {
       UNIONFS_WHITE_ALWAYS = 0,
       UNIONFS_WHITE_WHENNEEDED
} unionfs_whitemode;

struct unionfs_mount {
	struct mount   *um_lowermp;     /* MNT_REFed lower mount object */
	struct mount   *um_uppermp;     /* MNT_REFed upper mount object */
	struct vnode   *um_lowervp;	/* VREFed once */
	struct vnode   *um_uppervp;	/* VREFed once */
	struct vnode   *um_rootvp;	/* ROOT vnode */
	struct mount_upper_node	um_lower_link;	/* node in lower FS list of uppers */
	struct mount_upper_node	um_upper_link;	/* node in upper FS list of uppers */
	unionfs_copymode um_copymode;
	unionfs_whitemode um_whitemode;
	uid_t		um_uid;
	gid_t		um_gid;
	u_short		um_udir;
	u_short		um_ufile;
};

/* unionfs status list */
struct unionfs_node_status {
	LIST_ENTRY(unionfs_node_status) uns_list;	/* Status list */
	pid_t		uns_pid;		/* current process id */
	int		uns_node_flag;		/* uns flag */
	int		uns_lower_opencnt;	/* open count of lower */
	int		uns_upper_opencnt;	/* open count of upper */
	int		uns_lower_openmode;	/* open mode of lower */
	int		uns_readdir_status;	/* read status of readdir */
};

/* union node status flags */
#define	UNS_OPENL_4_READDIR	0x01	/* open lower layer for readdir */

/* A cache of vnode references */
struct unionfs_node {
	struct vnode   *un_lowervp;		/* lower side vnode */
	struct vnode   *un_uppervp;		/* upper side vnode */
	struct vnode   *un_dvp;			/* parent unionfs vnode */
	struct vnode   *un_vnode;		/* Back pointer */
	LIST_HEAD(, unionfs_node_status) un_unshead;
						/* unionfs status head */
	LIST_HEAD(unionfs_node_hashhead, unionfs_node) *un_hashtbl;
						/* dir vnode hash table */
	union {
		LIST_ENTRY(unionfs_node) un_hash; /* hash list entry */
		STAILQ_ENTRY(unionfs_node) un_rele; /* deferred release list */
	};

	char           *un_path;		/* path */
	int		un_pathlen;		/* strlen of path */

	/*
	 * unionfs node flags
	 * Changing these flags requires the vnode to be locked exclusive.
	 */
	#define UNIONFS_OPENEXTL		0x01	/* openextattr (lower) */
	#define UNIONFS_OPENEXTU		0x02	/* openextattr (upper) */
	#define UNIONFS_COPY_IN_PROGRESS	0x04	/* copy/dir shadow in progres */
	#define UNIONFS_LOOKUP_IN_PROGRESS	0x08
	unsigned int	un_flag;		/* unionfs node flag */
};

extern struct vop_vector unionfs_vnodeops;

static inline struct unionfs_node *
unionfs_check_vnode(struct vnode *vp, const char *file __unused,
    int line __unused)
{
	/*
	 * unionfs_lock() needs the NULL check here, as it explicitly
	 * handles the case in which the vnode has been vgonel()'ed.
	 */
	KASSERT(vp->v_op == &unionfs_vnodeops || vp->v_data == NULL,
	    ("%s:%d: non-unionfs vnode %p", file, line, vp));
	return ((struct unionfs_node *)vp->v_data);
}

#define	MOUNTTOUNIONFSMOUNT(mp) ((struct unionfs_mount *)((mp)->mnt_data))
#define	VTOUNIONFS(vp) unionfs_check_vnode(vp, __FILE__, __LINE__)
#define	UNIONFSTOV(xp) ((xp)->un_vnode)

int	unionfs_init(struct vfsconf *);
int	unionfs_uninit(struct vfsconf *);
int	unionfs_nodeget(struct mount *, struct vnode *, struct vnode *,
	    struct vnode *, struct vnode **, struct componentname *);
void	unionfs_noderem(struct vnode *);
struct unionfs_node_status *	unionfs_find_node_status(struct unionfs_node *,
	    struct thread *td);
void	unionfs_get_node_status(struct unionfs_node *, struct thread *,
	    struct unionfs_node_status **);
void	unionfs_tryrem_node_status(struct unionfs_node *,
	    struct unionfs_node_status *);
int	unionfs_check_rmdir(struct vnode *, struct ucred *, struct thread *td);
int	unionfs_copyfile(struct vnode *, int, struct ucred *,
	    struct thread *);
int	unionfs_copylink(struct vnode *, struct ucred *, struct thread *);
void	unionfs_create_uppervattr_core(struct unionfs_mount *, struct vattr *,
	    struct vattr *, struct thread *);
int	unionfs_create_uppervattr(struct unionfs_mount *, struct vnode *,
	    struct vattr *, struct ucred *, struct thread *);
int	unionfs_mkshadowdir(struct vnode *, struct vnode *,
	    struct componentname *, struct thread *);
int	unionfs_mkwhiteout(struct vnode *, struct vnode *,
	    struct componentname *, struct thread *, char *, int);
int	unionfs_relookup(struct vnode *, struct vnode **,
	    struct componentname *, struct componentname *, struct thread *,
	    char *, int, u_long);
void	unionfs_forward_vop_start_pair(struct vnode *, int *,
	    struct vnode *, int *);
bool	unionfs_forward_vop_finish_pair(struct vnode *, struct vnode *, int,
	    struct vnode *, struct vnode *, int);
int	unionfs_set_in_progress_flag(struct vnode *, unsigned int);
void	unionfs_clear_in_progress_flag(struct vnode *, unsigned int);

static inline void
unionfs_forward_vop_start(struct vnode *basevp, int *lkflags)
{
	unionfs_forward_vop_start_pair(basevp, lkflags, NULL, NULL);
}

static inline bool
unionfs_forward_vop_finish(struct vnode *unionvp, struct vnode *basevp,
    int lkflags)
{
	return (unionfs_forward_vop_finish_pair(unionvp, basevp, lkflags,
	    NULL, NULL, 0));
}

#define	UNIONFSVPTOLOWERVP(vp) (VTOUNIONFS(vp)->un_lowervp)
#define	UNIONFSVPTOUPPERVP(vp) (VTOUNIONFS(vp)->un_uppervp)

#ifdef MALLOC_DECLARE
MALLOC_DECLARE(M_UNIONFSNODE);
MALLOC_DECLARE(M_UNIONFSPATH);
#endif

#ifdef UNIONFS_DEBUG
#define UNIONFSDEBUG(format, args...) printf(format ,## args)
#else
#define UNIONFSDEBUG(format, args...)
#endif				/* UNIONFS_DEBUG */

#endif				/* _KERNEL */
