// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2023-2024 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <djwong@kernel.org>
 */
#include "libxfs.h"
#include "libxfs/xfile.h"
#include "libxfs/xfblob.h"
#include "libfrog/platform.h"
#include "libfrog/workqueue.h"
#include "repair/globals.h"
#include "repair/err_protos.h"
#include "repair/slab.h"
#include "libxfs/listxattr.h"
#include "repair/threads.h"
#include "repair/incore.h"
#include "repair/pptr.h"
#include "repair/strblobs.h"

#undef PPTR_DEBUG

#ifdef PPTR_DEBUG
# define dbg_printf(f, a...)  do {printf(f, ## a); fflush(stdout); } while (0)
#else
# define dbg_printf(f, a...)
#endif

/*
 * Parent Pointer Validation
 * =========================
 *
 * Phase 6 validates the connectivity of the directory tree after validating
 * that all the space metadata are correct, and confirming all the inodes that
 * we intend to keep.  The first part of phase 6 walks the directories of the
 * filesystem to ensure that every file that isn't the root directory has a
 * parent.  Unconnected files are attached to the orphanage.  Filesystems with
 * the directory parent pointer feature enabled must also ensure that for every
 * directory entry that points to a child file, that child has a matching
 * parent pointer.
 *
 * There are many ways that we could check the parent pointers, but the means
 * that we have chosen is to build a per-AG master index of all parent pointers
 * of all inodes stored in that AG, and use that as the basis for comparison.
 * This consumes a lot of memory, but performing both a forward scan to check
 * dirent -> parent pointer and a backwards scan of parent pointer -> dirent
 * takes longer than the simple method presented here.  Userspace adds the
 * additional twist that inodes are not cached (and there are no ILOCKs), which
 * makes that approach even less attractive.
 *
 * During the directory walk at the start of phase 6, we transform each child
 * directory entry found into its parent pointer equivalent.  In other words,
 * the forward information:
 *
 *     (dir_ino, name, child_ino)
 *
 * becomes this backwards information:
 *
 *     (child_agino*, dir_ino*, dir_gen, name_cookie*)
 *
 * Key fields are starred.
 *
 * This tuple is recorded in the per-AG master parent pointer index.  Note
 * that names are stored separately in an xfblob data structure so that the
 * rest of the information can be sorted and processed as fixed-size records;
 * the incore parent pointer record contains a pointer to the strblob data.
 * Because string blobs are deduplicated, there's a 1:1 mapping of name cookies
 * to strings, which means that we can use the name cookie as a comparison key
 * instead of loading the full dentry name every time we want to perform a
 * comparison.
 *
 * Once we've finished with the forward scan, we get to work on the backwards
 * scan.  Each AG is processed independently.  First, we sort the per-AG master
 * records in order of child_agino, dir_ino, and name_cookie.  Each inode in
 * the AG is then processed in numerical order.
 *
 * The first thing that happens to the file is that we read all the extended
 * attributes to look for parent pointers.  Attributes that claim to be parent
 * pointers but are obviously garbage are thrown away.  The rest of the ondisk
 * parent pointers for that file are stored in memory like this:
 *
 *     (dir_ino*, dir_gen, name_cookie*)
 *
 * After loading the ondisk parent pointer name, we search the strblobs
 * structure to see if it has already recorded the name.  If so, this value is
 * used as the name cookie.  If the name has not yet been recorded, we flag the
 * incore record for later deletion.
 *
 * When we've concluded the xattr scan, the per-file records are sorted in
 * order of dir_ino and name_cookie.
 *
 * There are three possibilities here:
 *
 * A. The first record in the per-AG master index is an exact match for the
 * first record in the per-file index.  Everything is consistent, and we can
 * proceed with the lockstep scan detailed below.
 *
 * B. The per-AG master index cursor points to a higher inode number than the
 * first inode we are scanning.  Delete the ondisk parent pointers
 * corresponding to the per-file records until condition (B) is no longer true.
 *
 * C. The per-AG master index cursor instead points to a lower inode number
 * than the one we are scanning.  This means that there exists a directory
 * entry pointing at an inode that is free.  We supposedly already settled
 * which inodes are free and which aren't, which means in-memory information is
 * inconsistent.  Abort.
 *
 * Otherwise, we are ready to check the file parent pointers against the
 * master.  If the ondisk directory metadata are all consistent, this recordset
 * should correspond exactly to the subset of the master records with a
 * child_agino matching the file that we're scanning.  We should be able to
 * walk both sets in lockstep, and find one of the following outcomes:
 *
 * 1) The master index cursor is ahead of the ondisk index cursor.  This means
 * that the inode has parent pointers that were not found during the dirent
 * scan.  These should be deleted.
 *
 * 2) The ondisk index gets ahead of the master index.  This means that the
 * dirent scan found parent pointers that are not attached to the inode.
 * These should be added.
 *
 * 3) The parent_gen or (dirent) name are not consistent.  Update the parent
 * pointer to the values that we found during the dirent scan.
 *
 * 4) Everything matches.  Move on to the next parent pointer.
 *
 * The current implementation does not try to rebuild directories from parent
 * pointer information, as this requires a lengthy scan of the filesystem for
 * each broken directory.
 */

struct ag_pptr {
	/* parent directory handle */
	xfs_ino_t		parent_ino;
	uint32_t		parent_gen;

	/* dirent name length */
	unsigned short		namelen;

	/* AG_PPTR_* flags */
	unsigned short		flags;

	/* cookie for the actual dirent name */
	xfblob_cookie		name_cookie;

	/* agino of the child file */
	xfs_agino_t		child_agino;

	/* hash of the dirent name */
	xfs_dahash_t		namehash;
};

/* This might be a duplicate due to dotdot reprocessing */
#define AG_PPTR_POSSIBLE_DUP	(1U << 0)

struct file_pptr {
	/* parent directory handle */
	xfs_ino_t		parent_ino;
	uint32_t		parent_gen;

	/* Is the name stored in the global nameblobs structure? */
	unsigned int		name_in_nameblobs;

	/* hash of the dirent name */
	xfs_dahash_t		namehash;

	/* parent pointer name length */
	unsigned int		namelen;

	/* cookie for the file dirent name */
	xfblob_cookie		name_cookie;
};

struct ag_pptrs {
	/* Lock to protect pptr_recs during the dirent scan. */
	pthread_mutex_t		lock;

	/* Parent pointer records for files in this AG. */
	struct xfs_slab		*pptr_recs;
};

struct file_scan {
	struct ag_pptrs		*ag_pptrs;

	/* cursor for comparing ag_pptrs.pptr_recs against file_pptrs_recs */
	struct xfs_slab_cursor	*ag_pptr_recs_cur;

	/* xfs_parent_rec records for a file that we're checking */
	struct xfs_slab		*file_pptr_recs;

	/* cursor for comparing file_pptr_recs against pptrs_recs */
	struct xfs_slab_cursor	*file_pptr_recs_cur;

	/* names associated with file_pptr_recs */
	struct xfblob		*file_pptr_names;

	/* Number of parent pointers recorded for this file. */
	unsigned int		nr_file_pptrs;

	/* Does this file have garbage xattrs with ATTR_PARENT set? */
	bool			have_garbage;

	/* xattrs that we have to remove from this file */
	struct xfs_slab		*garbage_xattr_recs;

	/* attr names associated with garbage_xattr_recs */
	struct xfblob		*garbage_xattr_names;
};

struct garbage_xattr {
	/* xfs_da_args.attr_filter for the attribute being removed */
	unsigned int		attr_filter;

	/* attribute name length */
	unsigned int		attrnamelen;

	/* attribute value length */
	unsigned int		attrvaluelen;

	/* cookie for the attribute name */
	xfblob_cookie		attrname_cookie;

	/* cookie for the attribute value */
	xfblob_cookie		attrvalue_cookie;
};

/* Global names storage file. */
static struct strblobs	*nameblobs;
static pthread_mutex_t	names_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct ag_pptrs	*fs_pptrs;

static int
cmp_ag_pptr(
	const void		*a,
	const void		*b)
{
	const struct ag_pptr	*pa = a;
	const struct ag_pptr	*pb = b;

	if (pa->child_agino < pb->child_agino)
		return -1;
	if (pa->child_agino > pb->child_agino)
		return 1;

	if (pa->parent_ino < pb->parent_ino)
		return -1;
	if (pa->parent_ino > pb->parent_ino)
		return 1;

	if (pa->namehash < pb->namehash)
		return -1;
	if (pa->namehash > pb->namehash)
		return 1;

	if (pa->name_cookie < pb->name_cookie)
		return -1;
	if (pa->name_cookie > pb->name_cookie)
		return 1;

	return 0;
}

static int
cmp_file_pptr(
	const void		*a,
	const void		*b)
{
	const struct file_pptr	*pa = a;
	const struct file_pptr	*pb = b;

	if (pa->parent_ino < pb->parent_ino)
		return -1;
	if (pa->parent_ino > pb->parent_ino)
		return 1;

	/*
	 * Push the parent pointer names that we didn't find in the dirent scan
	 * towards the end of the list so that we delete them as excess.
	 */
	if (!pa->name_in_nameblobs && pb->name_in_nameblobs)
		return 1;
	if (pa->name_in_nameblobs && !pb->name_in_nameblobs)
		return -1;

	if (pa->namehash < pb->namehash)
		return -1;
	if (pa->namehash > pb->namehash)
		return 1;

	if (pa->name_cookie < pb->name_cookie)
		return -1;
	if (pa->name_cookie > pb->name_cookie)
		return 1;

	return 0;
}

void
parent_ptr_free(
	struct xfs_mount	*mp)
{
	xfs_agnumber_t		agno;

	if (!xfs_has_parent(mp))
		return;

	for (agno = 0; agno < mp->m_sb.sb_agcount; agno++) {
		free_slab(&fs_pptrs[agno].pptr_recs);
		pthread_mutex_destroy(&fs_pptrs[agno].lock);
	}
	free(fs_pptrs);
	fs_pptrs = NULL;

	strblobs_destroy(&nameblobs);
}

void
parent_ptr_init(
	struct xfs_mount	*mp)
{
	char			*descr;
	uint64_t		iused;
	xfs_agnumber_t		agno;
	int			error;

	if (!xfs_has_parent(mp))
		return;

	/* One hash bucket per inode, up to about 8M of memory on 64-bit. */
	iused = min(mp->m_sb.sb_icount - mp->m_sb.sb_ifree, 1048573);
	descr = kasprintf(GFP_KERNEL, "xfs_repair (%s): parent pointer names",
			mp->m_fsname);
	error = strblobs_init(descr, iused, &nameblobs);
	kfree(descr);
	if (error)
		do_error(_("init parent pointer names failed: %s\n"),
				strerror(error));

	fs_pptrs = calloc(mp->m_sb.sb_agcount, sizeof(struct ag_pptrs));
	if (!fs_pptrs)
		do_error(
 _("init parent pointer per-AG record array failed: %s\n"),
				strerror(errno));

	for (agno = 0; agno < mp->m_sb.sb_agcount; agno++) {
		error = pthread_mutex_init(&fs_pptrs[agno].lock, NULL);
		if (error)
			do_error(
 _("init agno %u parent pointer lock failed: %s\n"),
					agno, strerror(error));

		error = -init_slab(&fs_pptrs[agno].pptr_recs,
				sizeof(struct ag_pptr));
		if (error)
			do_error(
 _("init agno %u parent pointer recs failed: %s\n"),
					agno, strerror(error));
	}
}

/* Remember that @dp has a dirent (@fname, @ino). */
void
add_parent_ptr(
	xfs_ino_t		ino,
	const unsigned char	*fname,
	struct xfs_inode	*dp,
	bool			possible_dup)
{
	struct xfs_mount	*mp = dp->i_mount;
	struct xfs_name		dname = {
		.name		= fname,
		.len		= strlen((char *)fname),
	};
	struct ag_pptr		ag_pptr = {
		.child_agino	= XFS_INO_TO_AGINO(mp, ino),
		.parent_ino	= dp->i_ino,
		.parent_gen	= VFS_I(dp)->i_generation,
		.namelen	= dname.len,
	};
	struct ag_pptrs		*ag_pptrs;
	xfs_agnumber_t		agno = XFS_INO_TO_AGNO(mp, ino);
	int			error;

	if (!xfs_has_parent(mp))
		return;

	if (possible_dup)
		ag_pptr.flags |= AG_PPTR_POSSIBLE_DUP;

	ag_pptr.namehash = libxfs_dir2_hashname(mp, &dname);

	pthread_mutex_lock(&names_mutex);
	error = strblobs_store(nameblobs, &ag_pptr.name_cookie, fname,
			ag_pptr.namelen, ag_pptr.namehash);
	pthread_mutex_unlock(&names_mutex);
	if (error)
		do_error(_("storing name '%s' failed: %s\n"),
				fname, strerror(error));

	ag_pptrs = &fs_pptrs[agno];
	pthread_mutex_lock(&ag_pptrs->lock);
	error = -slab_add(ag_pptrs->pptr_recs, &ag_pptr);
	pthread_mutex_unlock(&ag_pptrs->lock);
	if (error)
		do_error(_("storing name '%s' key failed: %s\n"),
				fname, strerror(error));

	dbg_printf(
 _("%s: dp %llu gen 0x%x fname '%s' namehash 0x%x ino %llu namecookie 0x%llx\n"),
			__func__,
			(unsigned long long)dp->i_ino,
			VFS_I(dp)->i_generation,
			fname,
			ag_pptr.namehash,
			(unsigned long long)ino,
			(unsigned long long)ag_pptr.name_cookie);
}

/* Remove garbage extended attributes that have ATTR_PARENT set. */
static void
remove_garbage_xattrs(
	struct xfs_inode	*ip,
	struct file_scan	*fscan)
{
	struct xfs_slab_cursor	*cur;
	struct garbage_xattr	*ga;
	void			*buf = NULL;
	size_t			bufsize = 0;
	int			error;

	error = -init_slab_cursor(fscan->garbage_xattr_recs, NULL, &cur);
	if (error)
		do_error(_("init garbage xattr cursor failed: %s\n"),
				strerror(error));

	while ((ga = pop_slab_cursor(cur)) != NULL) {
		struct xfs_da_args	args = {
			.dp		= ip,
			.attr_filter	= ga->attr_filter,
			.namelen	= ga->attrnamelen,
			.valuelen	= ga->attrvaluelen,
			.owner		= ip->i_ino,
			.geo		= ip->i_mount->m_attr_geo,
			.whichfork	= XFS_ATTR_FORK,
			.op_flags	= XFS_DA_OP_OKNOENT | XFS_DA_OP_LOGGED,
		};
		size_t		desired = ga->attrnamelen + ga->attrvaluelen;

		if (desired > bufsize) {
			free(buf);
			buf = malloc(desired);
			if (!buf)
				do_error(
 _("allocating %zu bytes to remove ino %llu garbage xattr failed: %s\n"),
						desired,
						(unsigned long long)ip->i_ino,
						strerror(errno));
			bufsize = desired;
		}

		args.name = buf;
		args.value = buf + ga->attrnamelen;

		error = -xfblob_load(fscan->garbage_xattr_names,
				ga->attrname_cookie, buf, ga->attrnamelen);
		if (error)
			do_error(
 _("loading garbage xattr name failed: %s\n"),
					strerror(error));

		error = -xfblob_load(fscan->garbage_xattr_names,
				ga->attrvalue_cookie, args.value,
				ga->attrvaluelen);
		if (error)
			do_error(
 _("loading garbage xattr value failed: %s\n"),
					strerror(error));

		libxfs_attr_sethash(&args);
		error = -libxfs_attr_set(&args, XFS_ATTRUPDATE_REMOVE, true);
		if (error)
			do_error(
 _("removing ino %llu garbage xattr failed: %s\n"),
					(unsigned long long)ip->i_ino,
					strerror(error));
	}

	free(buf);
	free_slab_cursor(&cur);
	free_slab(&fscan->garbage_xattr_recs);
	xfblob_destroy(fscan->garbage_xattr_names);
	fscan->garbage_xattr_names = NULL;
}

/* Schedule this ATTR_PARENT extended attribute for deletion. */
static void
record_garbage_xattr(
	struct xfs_inode	*ip,
	struct file_scan	*fscan,
	unsigned int		attr_filter,
	const unsigned char	*name,
	unsigned int		namelen,
	const void		*value,
	unsigned int		valuelen)
{
	struct garbage_xattr	garbage_xattr = {
		.attr_filter	= attr_filter,
		.attrnamelen	= namelen,
		.attrvaluelen	= valuelen,
	};
	struct xfs_mount	*mp = ip->i_mount;
	char			*descr;
	int			error;

	if (no_modify) {
		if (!fscan->have_garbage)
			do_warn(
 _("would delete garbage parent pointer extended attributes in ino %llu\n"),
					(unsigned long long)ip->i_ino);
		fscan->have_garbage = true;
		return;
	}

	if (fscan->have_garbage)
		goto stuffit;
	fscan->have_garbage = true;

	do_warn(
 _("deleting garbage parent pointer extended attributes in ino %llu\n"),
			(unsigned long long)ip->i_ino);

	error = -init_slab(&fscan->garbage_xattr_recs,
			sizeof(struct garbage_xattr));
	if (error)
		do_error(_("init garbage xattr recs failed: %s\n"),
				strerror(error));

	descr = kasprintf(GFP_KERNEL, "xfs_repair (%s): garbage xattr names",
			mp->m_fsname);
	error = -xfblob_create(descr, &fscan->garbage_xattr_names);
	kfree(descr);
	if (error)
		do_error("init garbage xattr names failed: %s\n",
				strerror(error));

stuffit:
	error = -xfblob_store(fscan->garbage_xattr_names,
			&garbage_xattr.attrname_cookie, name, namelen);
	if (error)
		do_error(_("storing ino %llu garbage xattr failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));

	error = -xfblob_store(fscan->garbage_xattr_names,
			&garbage_xattr.attrvalue_cookie, value, valuelen);
	if (error)
		do_error(_("storing ino %llu garbage xattr failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));

	error = -slab_add(fscan->garbage_xattr_recs, &garbage_xattr);
	if (error)
		do_error(_("storing ino %llu garbage xattr rec failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));
}

/*
 * Store this file parent pointer's name in the file scan namelist unless it's
 * already in the global list.
 */
static int
store_file_pptr_name(
	struct file_scan		*fscan,
	struct file_pptr		*file_pptr,
	const struct xfs_name		*xname)
{
	int				error;

	error = strblobs_lookup(nameblobs, &file_pptr->name_cookie,
			xname->name, xname->len, file_pptr->namehash);
	if (!error) {
		file_pptr->name_in_nameblobs = true;
		return 0;
	}
	if (error != ENOENT)
		return error;

	file_pptr->name_in_nameblobs = false;
	return -xfblob_store(fscan->file_pptr_names, &file_pptr->name_cookie,
			xname->name, xname->len);
}

/* Decide if this is a directory parent pointer and stash it if so. */
static int
examine_xattr(
	struct xfs_trans	*tp,
	struct xfs_inode	*ip,
	unsigned int		attr_flags,
	const unsigned char	*name,
	unsigned int		namelen,
	const void		*value,
	unsigned int		valuelen,
	void			*priv)
{
	struct file_pptr	file_pptr = {
		.namelen	= namelen,
	};
	struct xfs_name		xname = {
		.name		= name,
		.len		= namelen,
	};
	struct xfs_mount	*mp = ip->i_mount;
	struct file_scan	*fscan = priv;
	int			error;

	if (!(attr_flags & XFS_ATTR_PARENT))
		return 0;

	error = -libxfs_parent_from_attr(mp, attr_flags, name, namelen, value,
			valuelen, &file_pptr.parent_ino, &file_pptr.parent_gen);
	if (error)
		goto corrupt;

	file_pptr.namehash = libxfs_dir2_hashname(mp, &xname);

	error = store_file_pptr_name(fscan, &file_pptr, &xname);
	if (error)
		do_error(
 _("storing ino %llu parent pointer '%.*s' failed: %s\n"),
				(unsigned long long)ip->i_ino,
				namelen,
				(const char *)name,
				strerror(error));

	error = -slab_add(fscan->file_pptr_recs, &file_pptr);
	if (error)
		do_error(_("storing ino %llu parent pointer rec failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));

	dbg_printf(
 _("%s: dp %llu gen 0x%x fname '%.*s' namelen %u namehash 0x%x ino %llu namecookie 0x%llx global? %d\n"),
			__func__,
			(unsigned long long)file_pptr.parent_ino,
			file_pptr.parent_gen,
			namelen,
			(const char *)name,
			namelen,
			file_pptr.namehash,
			(unsigned long long)ip->i_ino,
			(unsigned long long)file_pptr.name_cookie,
			file_pptr.name_in_nameblobs);

	fscan->nr_file_pptrs++;
	return 0;
corrupt:
	record_garbage_xattr(ip, fscan, attr_flags, name, namelen, value,
			valuelen);
	return 0;
}

/* Load a file parent pointer name from wherever we stored it. */
static int
load_file_pptr_name(
	struct file_scan	*fscan,
	const struct file_pptr	*file_pptr,
	unsigned char		*name)
{
	if (file_pptr->name_in_nameblobs)
		return strblobs_load(nameblobs, file_pptr->name_cookie,
				name, file_pptr->namelen);

	return -xfblob_load(fscan->file_pptr_names, file_pptr->name_cookie,
			name, file_pptr->namelen);
}

/* Add an on disk parent pointer to a file. */
static int
add_file_pptr(
	struct xfs_inode		*ip,
	const struct ag_pptr		*ag_pptr,
	const unsigned char		*name)
{
	struct xfs_name			xname = {
		.name			= name,
		.len			= ag_pptr->namelen,
	};
	struct xfs_parent_rec		pptr_rec = { };
	struct xfs_da_args		scratch;

	xfs_parent_rec_init(&pptr_rec, ag_pptr->parent_ino,
			ag_pptr->parent_gen);
	return -libxfs_parent_set(ip, ip->i_ino, &xname, &pptr_rec, &scratch);
}

/* Remove an on disk parent pointer from a file. */
static int
remove_file_pptr(
	struct xfs_inode		*ip,
	const struct file_pptr		*file_pptr,
	const unsigned char		*name)
{
	struct xfs_name			xname = {
		.name			= name,
		.len			= file_pptr->namelen,
	};
	struct xfs_parent_rec		pptr_rec = { };
	struct xfs_da_args		scratch;

	xfs_parent_rec_init(&pptr_rec, file_pptr->parent_ino,
			file_pptr->parent_gen);
	return -libxfs_parent_unset(ip, ip->i_ino, &xname, &pptr_rec, &scratch);
}

/* Remove all pptrs from @ip. */
static void
clear_all_pptrs(
	struct xfs_inode	*ip,
	struct file_scan	*fscan)
{
	struct xfs_slab_cursor	*cur;
	struct file_pptr	*file_pptr;
	int			error;

	if (no_modify) {
		do_warn(_("would delete unlinked ino %llu parent pointers\n"),
				(unsigned long long)ip->i_ino);
		return;
	}

	do_warn(_("deleting unlinked ino %llu parent pointers\n"),
			(unsigned long long)ip->i_ino);

	error = -init_slab_cursor(fscan->file_pptr_recs, NULL, &cur);
	if (error)
		do_error(_("init ino %llu pptr cursor failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));

	while ((file_pptr = pop_slab_cursor(cur)) != NULL) {
		unsigned char	name[MAXNAMELEN];

		error = load_file_pptr_name(fscan, file_pptr, name);
		if (error)
			do_error(
  _("loading incorrect name for ino %llu parent pointer (ino %llu gen 0x%x namecookie 0x%llx) failed: %s\n"),
					(unsigned long long)ip->i_ino,
					(unsigned long long)file_pptr->parent_ino,
					file_pptr->parent_gen,
					(unsigned long long)file_pptr->name_cookie,
					strerror(error));

		error = remove_file_pptr(ip, file_pptr, name);
		if (error)
			do_error(
 _("wiping ino %llu pptr (ino %llu gen 0x%x) failed: %s\n"),
				(unsigned long long)ip->i_ino,
				(unsigned long long)file_pptr->parent_ino,
				file_pptr->parent_gen,
				strerror(error));
	}

	free_slab_cursor(&cur);
}

/* Add @ag_pptr to @ip. */
static void
add_missing_parent_ptr(
	struct xfs_inode	*ip,
	struct file_scan	*fscan,
	const struct ag_pptr	*ag_pptr)
{
	unsigned char		name[MAXNAMELEN];
	int			error;

	error = strblobs_load(nameblobs, ag_pptr->name_cookie, name,
			ag_pptr->namelen);
	if (error)
		do_error(
 _("loading missing name for ino %llu parent pointer (ino %llu gen 0x%x namecookie 0x%llx) failed: %s\n"),
				(unsigned long long)ip->i_ino,
				(unsigned long long)ag_pptr->parent_ino,
				ag_pptr->parent_gen,
				(unsigned long long)ag_pptr->name_cookie,
				strerror(error));

	if (no_modify) {
		do_warn(
 _("would add missing ino %llu parent pointer (ino %llu gen 0x%x name '%.*s')\n"),
				(unsigned long long)ip->i_ino,
				(unsigned long long)ag_pptr->parent_ino,
				ag_pptr->parent_gen,
				ag_pptr->namelen,
				name);
		return;
	} else {
		do_warn(
 _("adding missing ino %llu parent pointer (ino %llu gen 0x%x name '%.*s')\n"),
				(unsigned long long)ip->i_ino,
				(unsigned long long)ag_pptr->parent_ino,
				ag_pptr->parent_gen,
				ag_pptr->namelen,
				name);
	}

	error = add_file_pptr(ip, ag_pptr, name);
	if (error)
		do_error(
 _("adding ino %llu pptr (ino %llu gen 0x%x name '%.*s') failed: %s\n"),
			(unsigned long long)ip->i_ino,
			(unsigned long long)ag_pptr->parent_ino,
			ag_pptr->parent_gen,
			ag_pptr->namelen,
			name,
			strerror(error));
}

/* Remove @file_pptr from @ip. */
static void
remove_incorrect_parent_ptr(
	struct xfs_inode	*ip,
	struct file_scan	*fscan,
	const struct file_pptr	*file_pptr)
{
	unsigned char		name[MAXNAMELEN] = { };
	int			error;

	error = load_file_pptr_name(fscan, file_pptr, name);
	if (error)
		do_error(
 _("loading incorrect name for ino %llu parent pointer (ino %llu gen 0x%x namecookie 0x%llx) failed: %s\n"),
				(unsigned long long)ip->i_ino,
				(unsigned long long)file_pptr->parent_ino,
				file_pptr->parent_gen,
				(unsigned long long)file_pptr->name_cookie,
				strerror(error));

	if (no_modify) {
		do_warn(
 _("would remove bad ino %llu parent pointer (ino %llu gen 0x%x name '%.*s')\n"),
				(unsigned long long)ip->i_ino,
				(unsigned long long)file_pptr->parent_ino,
				file_pptr->parent_gen,
				file_pptr->namelen,
				name);
		return;
	}

	do_warn(
 _("removing bad ino %llu parent pointer (ino %llu gen 0x%x name '%.*s')\n"),
			(unsigned long long)ip->i_ino,
			(unsigned long long)file_pptr->parent_ino,
			file_pptr->parent_gen,
			file_pptr->namelen,
			name);

	error = remove_file_pptr(ip, file_pptr, name);
	if (error)
		do_error(
 _("removing ino %llu pptr (ino %llu gen 0x%x name '%.*s') failed: %s\n"),
			(unsigned long long)ip->i_ino,
			(unsigned long long)file_pptr->parent_ino,
			file_pptr->parent_gen,
			file_pptr->namelen,
			name,
			strerror(error));
}

/*
 * We found parent pointers that point to the same inode and directory offset.
 * Make sure they have the same generation number and dirent name.
 */
static void
compare_parent_ptrs(
	struct xfs_inode	*ip,
	struct file_scan	*fscan,
	const struct ag_pptr	*ag_pptr,
	const struct file_pptr	*file_pptr)
{
	unsigned char		name1[MAXNAMELEN] = { };
	unsigned char		name2[MAXNAMELEN] = { };
	int			error;

	error = strblobs_load(nameblobs, ag_pptr->name_cookie, name1,
			ag_pptr->namelen);
	if (error)
		do_error(
 _("loading master-list name for ino %llu parent pointer (ino %llu gen 0x%x namecookie 0x%llx namelen %u) failed: %s\n"),
				(unsigned long long)ip->i_ino,
				(unsigned long long)ag_pptr->parent_ino,
				ag_pptr->parent_gen,
				(unsigned long long)ag_pptr->name_cookie,
				ag_pptr->namelen,
				strerror(error));

	error = load_file_pptr_name(fscan, file_pptr, name2);
	if (error)
		do_error(
 _("loading file-list name for ino %llu parent pointer (ino %llu gen 0x%x namecookie 0x%llx namelen %u) failed: %s\n"),
				(unsigned long long)ip->i_ino,
				(unsigned long long)file_pptr->parent_ino,
				file_pptr->parent_gen,
				(unsigned long long)file_pptr->name_cookie,
				ag_pptr->namelen,
				strerror(error));

	if (ag_pptr->parent_gen != file_pptr->parent_gen)
		goto reset;
	if (ag_pptr->namelen != file_pptr->namelen)
		goto reset;
	if (ag_pptr->namehash != file_pptr->namehash)
		goto reset;
	if (memcmp(name1, name2, ag_pptr->namelen))
		goto reset;

	return;

reset:
	if (no_modify) {
		do_warn(
 _("would update ino %llu parent pointer (ino %llu gen 0x%x name '%.*s') -> (ino %llu gen 0x%x name '%.*s')\n"),
				(unsigned long long)ip->i_ino,
				(unsigned long long)file_pptr->parent_ino,
				file_pptr->parent_gen,
				file_pptr->namelen,
				name2,
				(unsigned long long)ag_pptr->parent_ino,
				ag_pptr->parent_gen,
				ag_pptr->namelen,
				name1);
		return;
	}

	do_warn(
 _("updating ino %llu parent pointer (ino %llu gen 0x%x name '%.*s') -> (ino %llu gen 0x%x name '%.*s')\n"),
			(unsigned long long)ip->i_ino,
			(unsigned long long)file_pptr->parent_ino,
			file_pptr->parent_gen,
			file_pptr->namelen,
			name2,
			(unsigned long long)ag_pptr->parent_ino,
			ag_pptr->parent_gen,
			ag_pptr->namelen,
			name1);

	/* Remove the parent pointer that we don't want. */
	error = remove_file_pptr(ip, file_pptr, name2);
	if (error)
		do_error(
_("erasing ino %llu pptr (ino %llu gen 0x%x name '%.*s') failed: %s\n"),
			(unsigned long long)ip->i_ino,
			(unsigned long long)file_pptr->parent_ino,
			file_pptr->parent_gen,
			file_pptr->namelen,
			name2,
			strerror(error));

	/*
	 * Add the parent pointer that we do want.  It's possible that this
	 * parent pointer already exists but we haven't gotten that far in the
	 * scan, so we'll keep going on EEXIST.
	 */
	error = add_file_pptr(ip, ag_pptr, name1);
	if (error && error != EEXIST)
		do_error(
 _("updating ino %llu pptr (ino %llu gen 0x%x name '%.*s') failed: %s\n"),
			(unsigned long long)ip->i_ino,
			(unsigned long long)ag_pptr->parent_ino,
			ag_pptr->parent_gen,
			ag_pptr->namelen,
			name1,
			strerror(error));
}

static int
cmp_file_to_ag_pptr(
	const struct file_pptr	*fp,
	const struct ag_pptr	*ap)
{
	/*
	 * We finished iterating all the pptrs attached to the file before we
	 * ran out of pptrs that we found in the directory scan.  Return 1 so
	 * the caller adds the pptr from the dir scan.
	 */
	if (!fp)
		return 1;

	if (fp->parent_ino > ap->parent_ino)
		return 1;
	if (fp->parent_ino < ap->parent_ino)
		return -1;

	if (fp->namehash < ap->namehash)
		return -1;
	if (fp->namehash > ap->namehash)
		return 1;

	/*
	 * If this parent pointer wasn't found in the dirent scan, we know it
	 * should be removed.
	 */
	if (!fp->name_in_nameblobs)
		return -1;

	if (fp->name_cookie < ap->name_cookie)
		return -1;
	if (fp->name_cookie > ap->name_cookie)
		return 1;

	return 0;
}

/*
 * If this parent pointer that we got via directory scan thinks it might be a
 * duplicate, compare it to the previous pptr found by the directory scan.  If
 * they are the same, then we got duplicate entries on account of dotdot
 * reprocessing and we can ignore this one.
 */
static inline bool
crosscheck_want_skip_dup(
	const struct ag_pptr	*ag_pptr,
	const struct ag_pptr	*prev_ag_pptr)
{
	if (!(ag_pptr->flags & AG_PPTR_POSSIBLE_DUP) || !prev_ag_pptr)
		return false;

	if (ag_pptr->parent_ino  == prev_ag_pptr->parent_ino &&
	    ag_pptr->parent_gen  == prev_ag_pptr->parent_gen &&
	    ag_pptr->namelen     == prev_ag_pptr->namelen &&
	    ag_pptr->name_cookie == prev_ag_pptr->name_cookie &&
	    ag_pptr->child_agino == prev_ag_pptr->child_agino)
		return true;

	return false;
}

/*
 * Make sure that the parent pointers we observed match the ones ondisk.
 *
 * Earlier, we generated a master list of parent pointers for files in this AG
 * based on what we saw during the directory walk at the start of phase 6.
 * Now that we've read in all of this file's parent pointers, make sure the
 * lists match.
 */
static void
crosscheck_file_parent_ptrs(
	struct xfs_inode	*ip,
	struct file_scan	*fscan)
{
	struct ag_pptr		*ag_pptr, *prev_ag_pptr = NULL;
	struct file_pptr	*file_pptr;
	struct xfs_mount	*mp = ip->i_mount;
	xfs_agnumber_t		agno = XFS_INO_TO_AGNO(mp, ip->i_ino);
	xfs_agino_t		agino = XFS_INO_TO_AGINO(mp, ip->i_ino);
	int			error;

	ag_pptr = peek_slab_cursor(fscan->ag_pptr_recs_cur);

	if (!ag_pptr || ag_pptr->child_agino > agino) {
		/*
		 * The cursor for the master pptr list has gone beyond this
		 * file that we're scanning.  Evidently it has no parents at
		 * all, so we better not have found any pptrs attached to the
		 * file.
		 */
		if (fscan->nr_file_pptrs > 0)
			clear_all_pptrs(ip, fscan);

		return;
	}

	if (ag_pptr->child_agino < agino) {
		/*
		 * The cursor for the master pptr list is behind the file that
		 * we're scanning.  This suggests that the incore inode tree
		 * doesn't know about a file that is mentioned by a dirent.
		 * At this point the inode liveness is supposed to be settled,
		 * which means our incore information is inconsistent.
		 */
		do_error(
 _("found dirent referring to ino %llu even though inobt scan moved on to ino %llu?!\n"),
				(unsigned long long)XFS_AGINO_TO_INO(mp, agno,
					ag_pptr->child_agino),
				(unsigned long long)ip->i_ino);
		/* does not return */
	}

	/*
	 * The master pptr list cursor is pointing to the inode that we want
	 * to check.  Sort the pptr records that we recorded from the ondisk
	 * pptrs for this file, then set up for the comparison.
	 */
	qsort_slab(fscan->file_pptr_recs, cmp_file_pptr);

	error = -init_slab_cursor(fscan->file_pptr_recs, cmp_file_pptr,
			&fscan->file_pptr_recs_cur);
	if (error)
		do_error(_("init ino %llu parent pointer cursor failed: %s\n"),
				(unsigned long long)ip->i_ino, strerror(error));

	do {
		int	cmp_result;

		if (crosscheck_want_skip_dup(ag_pptr, prev_ag_pptr)) {
			/*
			 * This master parent pointer thinks it's a duplicate
			 * and it matches the previous master parent pointer.
			 * We don't want to add duplicate parent pointers, so
			 * advance the master pptr cursor and loop again.
			 */
			dbg_printf(
 _("%s: dp %llu dp_gen 0x%x namelen %u ino %llu namecookie 0x%llx (skip_dup)\n"),
					__func__,
					(unsigned long long)ag_pptr->parent_ino,
					ag_pptr->parent_gen,
					ag_pptr->namelen,
					(unsigned long long)ip->i_ino,
					(unsigned long long)ag_pptr->name_cookie);
			prev_ag_pptr = ag_pptr;
			advance_slab_cursor(fscan->ag_pptr_recs_cur);
			ag_pptr = peek_slab_cursor(fscan->ag_pptr_recs_cur);
			continue;
		}
		prev_ag_pptr = ag_pptr;

		file_pptr = peek_slab_cursor(fscan->file_pptr_recs_cur);

		dbg_printf(
 _("%s: dp %llu dp_gen 0x%x namelen %u ino %llu namecookie 0x%llx (master)\n"),
				__func__,
				(unsigned long long)ag_pptr->parent_ino,
				ag_pptr->parent_gen,
				ag_pptr->namelen,
				(unsigned long long)ip->i_ino,
				(unsigned long long)ag_pptr->name_cookie);

		if (file_pptr) {
			dbg_printf(
 _("%s: dp %llu dp_gen 0x%x namelen %u ino %llu namecookie 0x%llx (file)\n"),
					__func__,
					(unsigned long long)file_pptr->parent_ino,
					file_pptr->parent_gen,
					file_pptr->namelen,
					(unsigned long long)ip->i_ino,
					(unsigned long long)file_pptr->name_cookie);
		} else {
			dbg_printf(
 _("%s: ran out of parent pointers for ino %llu (file)\n"),
					__func__,
					(unsigned long long)ip->i_ino);
		}

		cmp_result = cmp_file_to_ag_pptr(file_pptr, ag_pptr);
		if (cmp_result > 0) {
			/*
			 * The master pptr list knows about pptrs that are not
			 * in the ondisk metadata.  Add the missing pptr and
			 * advance only the master pptr cursor.
			 */
			add_missing_parent_ptr(ip, fscan, ag_pptr);
			advance_slab_cursor(fscan->ag_pptr_recs_cur);
		} else if (cmp_result < 0) {
			/*
			 * The ondisk pptrs mention a link that is not in the
			 * master list.  Delete the extra pptr and advance only
			 * the file pptr cursor.
			 */
			remove_incorrect_parent_ptr(ip, fscan, file_pptr);
			advance_slab_cursor(fscan->file_pptr_recs_cur);
		} else {
			/*
			 * Exact match, make sure the parent_gen and dirent
			 * name parts of the parent pointer match.  Move both
			 * cursors forward.
			 */
			compare_parent_ptrs(ip, fscan, ag_pptr, file_pptr);
			advance_slab_cursor(fscan->ag_pptr_recs_cur);
			advance_slab_cursor(fscan->file_pptr_recs_cur);
		}

		ag_pptr = peek_slab_cursor(fscan->ag_pptr_recs_cur);
	} while (ag_pptr && ag_pptr->child_agino == agino);

	while ((file_pptr = pop_slab_cursor(fscan->file_pptr_recs_cur))) {
		dbg_printf(
 _("%s: dp %llu dp_gen 0x%x namelen %u ino %llu namecookie 0x%llx (excess)\n"),
				__func__,
				(unsigned long long)file_pptr->parent_ino,
				file_pptr->parent_gen,
				file_pptr->namelen,
				(unsigned long long)ip->i_ino,
				(unsigned long long)file_pptr->name_cookie);

		/*
		 * The master pptr list does not have any more pptrs for this
		 * file, but we still have unprocessed ondisk pptrs.  Delete
		 * all these ondisk pptrs.
		 */
		remove_incorrect_parent_ptr(ip, fscan, file_pptr);
	}
}

/* Ensure this file's parent pointers match what we found in the dirent scan. */
static void
check_file_parent_ptrs(
	struct xfs_inode	*ip,
	struct file_scan	*fscan)
{
	struct xfs_trans	*tp = NULL;
	int			error;

	error = -init_slab(&fscan->file_pptr_recs, sizeof(struct file_pptr));
	if (error)
		do_error(_("init file parent pointer recs failed: %s\n"),
				strerror(error));

	fscan->have_garbage = false;
	fscan->nr_file_pptrs = 0;

	libxfs_trans_alloc_empty(ip->i_mount, &tp);
	error = xattr_walk(tp, ip, examine_xattr, fscan);
	if (tp)
		libxfs_trans_cancel(tp);
	if (error && !no_modify)
		do_error(_("ino %llu parent pointer scan failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));
	if (error) {
		do_warn(_("ino %llu parent pointer scan failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));
		goto out_free;
	}

	if (!no_modify && fscan->have_garbage)
		remove_garbage_xattrs(ip, fscan);

	crosscheck_file_parent_ptrs(ip, fscan);

out_free:
	free_slab(&fscan->file_pptr_recs);
	xfblob_truncate(fscan->file_pptr_names);
}

/* Check all the parent pointers of files in this AG. */
static void
check_ag_parent_ptrs(
	struct workqueue	*wq,
	uint32_t		agno,
	void			*arg)
{
	struct xfs_mount	*mp = wq->wq_ctx;
	struct file_scan	fscan = {
		.ag_pptrs	= &fs_pptrs[agno],
	};
	struct ag_pptrs		*ag_pptrs = &fs_pptrs[agno];
	struct ino_tree_node	*irec;
	char			*descr;
	int			error;

	qsort_slab(ag_pptrs->pptr_recs, cmp_ag_pptr);

	error = -init_slab_cursor(ag_pptrs->pptr_recs, cmp_ag_pptr,
			&fscan.ag_pptr_recs_cur);
	if (error)
		do_error(
 _("init agno %u parent pointer slab cursor failed: %s\n"),
				agno, strerror(error));

	descr = kasprintf(GFP_KERNEL,
			"xfs_repair (%s): file parent pointer names",
			mp->m_fsname);
	error = -xfblob_create(descr, &fscan.file_pptr_names);
	kfree(descr);
	if (error)
		do_error(
 _("init agno %u file parent pointer names failed: %s\n"),
				agno, strerror(error));

	for (irec = findfirst_inode_rec(agno);
	     irec != NULL;
	     irec = next_ino_rec(irec)) {
		unsigned int	ino_offset;

		for (ino_offset = 0;
		     ino_offset < XFS_INODES_PER_CHUNK;
		     ino_offset++) {
			struct xfs_inode *ip;
			xfs_ino_t	ino;

			if (is_inode_free(irec, ino_offset))
				continue;

			ino = XFS_AGINO_TO_INO(mp, agno,
					irec->ino_startnum + ino_offset);
			error = -libxfs_iget(mp, NULL, ino, 0, &ip);
			if (error && !no_modify)
				do_error(
 _("loading ino %llu for parent pointer check failed: %s\n"),
						(unsigned long long)ino,
						strerror(error));
			if (error) {
				do_warn(
 _("loading ino %llu for parent pointer check failed: %s\n"),
						(unsigned long long)ino,
						strerror(error));
				continue;
			}

			check_file_parent_ptrs(ip, &fscan);
			libxfs_irele(ip);
		}
	}

	xfblob_destroy(fscan.file_pptr_names);
	free_slab_cursor(&fscan.ag_pptr_recs_cur);
}

/* Check all the parent pointers of all files in this filesystem. */
void
check_parent_ptrs(
	struct xfs_mount	*mp)
{
	struct workqueue	wq;
	xfs_agnumber_t		agno;

	if (!xfs_has_parent(mp))
		return;

	create_work_queue(&wq, mp, ag_stride);

	for (agno = 0; agno < mp->m_sb.sb_agcount; agno++)
		queue_work(&wq, check_ag_parent_ptrs, agno, NULL);

	destroy_work_queue(&wq);
}

static int
erase_pptrs(
	struct xfs_trans	*tp,
	struct xfs_inode	*ip,
	unsigned int		attr_flags,
	const unsigned char	*name,
	unsigned int		namelen,
	const void		*value,
	unsigned int		valuelen,
	void			*priv)
{
	struct garbage_xattr	garbage_xattr = {
		.attr_filter	= attr_flags,
		.attrnamelen	= namelen,
		.attrvaluelen	= valuelen,
	};
	struct file_scan	*fscan = priv;
	int			error;

	if (!(attr_flags & XFS_ATTR_PARENT))
		return 0;

	error = -xfblob_store(fscan->garbage_xattr_names,
			&garbage_xattr.attrname_cookie, name, namelen);
	if (error)
		do_error(_("storing ino %llu garbage pptr failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));

	error = -xfblob_store(fscan->garbage_xattr_names,
			&garbage_xattr.attrvalue_cookie, value, valuelen);
	if (error)
		do_error(_("storing ino %llu garbage pptr failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));

	error = -slab_add(fscan->garbage_xattr_recs, &garbage_xattr);
	if (error)
		do_error(_("storing ino %llu garbage pptr rec failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));

	return 0;
}

/* Delete all of this file's parent pointers if we can. */
void
try_erase_parent_ptrs(
	struct xfs_inode	*ip)
{
	struct file_scan	fscan = {
		.have_garbage	= true,
	};
	struct xfs_mount	*mp = ip->i_mount;
	struct xfs_trans	*tp = NULL;
	char			*descr;
	int			error;

	if (!xfs_has_parent(ip->i_mount))
		return;

	if (no_modify) {
		do_warn(
 _("would delete garbage parent pointers in metadata ino %llu\n"),
				(unsigned long long)ip->i_ino);
		return;
	}

	error = -init_slab(&fscan.garbage_xattr_recs,
			sizeof(struct garbage_xattr));
	if (error)
		do_error(_("init garbage pptr recs failed: %s\n"),
				strerror(error));

	descr = kasprintf(GFP_KERNEL, "xfs_repair (%s): garbage pptr names",
			mp->m_fsname);
	error = -xfblob_create(descr, &fscan.garbage_xattr_names);
	kfree(descr);
	if (error)
		do_error("init garbage pptr names failed: %s\n",
				strerror(error));

	libxfs_trans_alloc_empty(ip->i_mount, &tp);
	error = xattr_walk(tp, ip, erase_pptrs, &fscan);
	if (tp)
		libxfs_trans_cancel(tp);
	if (error)
		do_warn(_("ino %llu garbage pptr collection failed: %s\n"),
				(unsigned long long)ip->i_ino,
				strerror(error));

	remove_garbage_xattrs(ip, &fscan);
}
