/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (c) 2023-2024 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <djwong@kernel.org>
 */
#ifndef __XFS_SCRUB_DIRTREE_H__
#define __XFS_SCRUB_DIRTREE_H__

/*
 * Each of these represents one parent pointer path step in a chain going
 * up towards the directory tree root.  These are stored inside an xfarray.
 */
struct xchk_dirpath_step {
	/* Directory entry name associated with this parent link. */
	xfblob_cookie		name_cookie;
	unsigned int		name_len;

	/* Handle of the parent directory. */
	struct xfs_parent_rec	pptr_rec;
};

enum xchk_dirpath_outcome {
	XCHK_DIRPATH_SCANNING = 0,	/* still being put together */
	XCHK_DIRPATH_DELETE,		/* delete this path */
	XCHK_DIRPATH_CORRUPT,		/* corruption detected in path */
	XCHK_DIRPATH_LOOP,		/* cycle detected further up */
	XCHK_DIRPATH_STALE,		/* path is stale */
	XCHK_DIRPATH_OK,		/* path reaches the root */

	XREP_DIRPATH_DELETING,		/* path is being deleted */
	XREP_DIRPATH_DELETED,		/* path has been deleted */
	XREP_DIRPATH_ADOPTING,		/* path is being adopted */
	XREP_DIRPATH_ADOPTED,		/* path has been adopted */
};

/*
 * Each of these represents one parent pointer path out of the directory being
 * scanned.  These exist in-core, and hopefully there aren't more than a
 * handful of them.
 */
struct xchk_dirpath {
	struct list_head	list;

	/* Index of the first step in this path. */
	xfarray_idx_t		first_step;

	/* Index of the second step in this path. */
	xfarray_idx_t		second_step;

	/* Inodes seen while walking this path. */
	struct xino_bitmap	seen_inodes;

	/* Number of steps in this path. */
	unsigned int		nr_steps;

	/* Which path is this? */
	unsigned int		path_nr;

	/* What did we conclude from following this path? */
	enum xchk_dirpath_outcome outcome;
};

struct xchk_dirtree_outcomes {
	/* Number of XCHK_DIRPATH_DELETE */
	unsigned int		bad;

	/* Number of XCHK_DIRPATH_CORRUPT or XCHK_DIRPATH_LOOP */
	unsigned int		suspect;

	/* Number of XCHK_DIRPATH_OK */
	unsigned int		good;

	/* Directory needs to be added to lost+found */
	bool			needs_adoption;
};

struct xchk_dirtree {
	struct xfs_scrub	*sc;

	/* Root inode that we're looking for. */
	xfs_ino_t		root_ino;

	/*
	 * This is the inode that we're scanning.  The live update hook can
	 * continue to be called after xchk_teardown drops sc->ip but before
	 * it calls buf_cleanup, so we keep a copy.
	 */
	xfs_ino_t		scan_ino;

	/*
	 * If we start deleting redundant paths to this subdirectory, this is
	 * the inode number of the surviving parent and the dotdot entry will
	 * be set to this value.  If the value is NULLFSINO, then use @root_ino
	 * as a stand-in until the orphanage can adopt the subdirectory.
	 */
	xfs_ino_t		parent_ino;

	/* Scratch buffer for scanning pptr xattrs */
	struct xfs_parent_rec	pptr_rec;
	struct xfs_da_args	pptr_args;

	/* Name buffer */
	struct xfs_name		xname;
	char			namebuf[MAXNAMELEN];

	/* Information for reparenting this directory. */
	struct xrep_adoption	adoption;

	/*
	 * Hook into directory updates so that we can receive live updates
	 * from other writer threads.
	 */
	struct xfs_dir_hook	dhook;

	/* Parent pointer update arguments. */
	struct xfs_parent_args	ppargs;

	/* lock for everything below here */
	struct mutex		lock;

	/* buffer for the live update functions to use for dirent names */
	struct xfs_name		hook_xname;
	unsigned char		hook_namebuf[MAXNAMELEN];

	/*
	 * All path steps observed during this scan.  Each of the path
	 * steps for a particular pathwalk are recorded in sequential
	 * order in the xfarray.  A pathwalk ends either with a step
	 * pointing to the root directory (success) or pointing to NULLFSINO
	 * (loop detected, empty dir detected, etc).
	 */
	struct xfarray		*path_steps;

	/* All names observed during this scan. */
	struct xfblob		*path_names;

	/* All paths being tracked by this scanner. */
	struct list_head	path_list;

	/* Number of paths in path_list. */
	unsigned int		nr_paths;

	/* Number of parents found by a pptr scan. */
	unsigned int		parents_found;

	/* Have the path data been invalidated by a concurrent update? */
	bool			stale:1;

	/* Has the scan been aborted? */
	bool			aborted:1;
};

#define xchk_dirtree_for_each_path_safe(dl, path, n) \
	list_for_each_entry_safe((path), (n), &(dl)->path_list, list)

#define xchk_dirtree_for_each_path(dl, path) \
	list_for_each_entry((path), &(dl)->path_list, list)

static inline bool
xchk_dirtree_parentless(const struct xchk_dirtree *dl)
{
	struct xfs_scrub	*sc = dl->sc;

	if (sc->ip == sc->mp->m_rootip)
		return true;
	if (VFS_I(sc->ip)->i_nlink == 0)
		return true;
	return false;
}

int xchk_dirtree_find_paths_to_root(struct xchk_dirtree *dl);
int xchk_dirpath_append(struct xchk_dirtree *dl, struct xfs_inode *ip,
		struct xchk_dirpath *path, const struct xfs_name *name,
		const struct xfs_parent_rec *pptr);
void xchk_dirtree_evaluate(struct xchk_dirtree *dl,
		struct xchk_dirtree_outcomes *oc);

#endif /* __XFS_SCRUB_DIRTREE_H__ */
