|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (c) 2022-2024 Oracle. | 
|  | * All rights reserved. | 
|  | */ | 
|  | #include "libxfs_priv.h" | 
|  | #include "xfs_fs.h" | 
|  | #include "xfs_format.h" | 
|  | #include "xfs_da_format.h" | 
|  | #include "xfs_log_format.h" | 
|  | #include "xfs_shared.h" | 
|  | #include "xfs_trans_resv.h" | 
|  | #include "xfs_mount.h" | 
|  | #include "xfs_bmap_btree.h" | 
|  | #include "xfs_inode.h" | 
|  | #include "xfs_trace.h" | 
|  | #include "xfs_trans.h" | 
|  | #include "xfs_da_btree.h" | 
|  | #include "xfs_attr.h" | 
|  | #include "xfs_dir2.h" | 
|  | #include "xfs_dir2_priv.h" | 
|  | #include "xfs_attr_sf.h" | 
|  | #include "xfs_bmap.h" | 
|  | #include "xfs_defer.h" | 
|  | #include "xfs_parent.h" | 
|  | #include "xfs_trans_space.h" | 
|  | #include "defer_item.h" | 
|  | #include "xfs_health.h" | 
|  |  | 
|  | struct kmem_cache		*xfs_parent_args_cache; | 
|  |  | 
|  | /* | 
|  | * Parent pointer attribute handling. | 
|  | * | 
|  | * Because the attribute name is a filename component, it will never be longer | 
|  | * than 255 bytes and must not contain nulls or slashes.  These are roughly the | 
|  | * same constraints that apply to attribute names. | 
|  | * | 
|  | * The attribute value must always be a struct xfs_parent_rec.  This means the | 
|  | * attribute will never be in remote format because 12 bytes is nowhere near | 
|  | * xfs_attr_leaf_entsize_local_max() (~75% of block size). | 
|  | * | 
|  | * Creating a new parent attribute will always create a new attribute - there | 
|  | * should never, ever be an existing attribute in the tree for a new inode. | 
|  | * ENOSPC behavior is problematic - creating the inode without the parent | 
|  | * pointer is effectively a corruption, so we allow parent attribute creation | 
|  | * to dip into the reserve block pool to avoid unexpected ENOSPC errors from | 
|  | * occurring. | 
|  | */ | 
|  |  | 
|  | /* Return true if parent pointer attr name is valid. */ | 
|  | bool | 
|  | xfs_parent_namecheck( | 
|  | unsigned int			attr_flags, | 
|  | const void			*name, | 
|  | size_t				length) | 
|  | { | 
|  | /* | 
|  | * Parent pointers always use logged operations, so there should never | 
|  | * be incomplete xattrs. | 
|  | */ | 
|  | if (attr_flags & XFS_ATTR_INCOMPLETE) | 
|  | return false; | 
|  |  | 
|  | return xfs_dir2_namecheck(name, length); | 
|  | } | 
|  |  | 
|  | /* Return true if parent pointer attr value is valid. */ | 
|  | bool | 
|  | xfs_parent_valuecheck( | 
|  | struct xfs_mount		*mp, | 
|  | const void			*value, | 
|  | size_t				valuelen) | 
|  | { | 
|  | const struct xfs_parent_rec	*rec = value; | 
|  |  | 
|  | if (!xfs_has_parent(mp)) | 
|  | return false; | 
|  |  | 
|  | /* The xattr value must be a parent record. */ | 
|  | if (valuelen != sizeof(struct xfs_parent_rec)) | 
|  | return false; | 
|  |  | 
|  | /* The parent record must be local. */ | 
|  | if (value == NULL) | 
|  | return false; | 
|  |  | 
|  | /* The parent inumber must be valid. */ | 
|  | if (!xfs_verify_dir_ino(mp, be64_to_cpu(rec->p_ino))) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* Compute the attribute name hash for a parent pointer. */ | 
|  | xfs_dahash_t | 
|  | xfs_parent_hashval( | 
|  | struct xfs_mount		*mp, | 
|  | const uint8_t			*name, | 
|  | int				namelen, | 
|  | xfs_ino_t			parent_ino) | 
|  | { | 
|  | struct xfs_name			xname = { | 
|  | .name			= name, | 
|  | .len			= namelen, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * Use the same dirent name hash as would be used on the directory, but | 
|  | * mix in the parent inode number to avoid collisions on hardlinked | 
|  | * files with identical names but different parents. | 
|  | */ | 
|  | return xfs_dir2_hashname(mp, &xname) ^ | 
|  | upper_32_bits(parent_ino) ^ lower_32_bits(parent_ino); | 
|  | } | 
|  |  | 
|  | /* Compute the attribute name hash from the xattr components. */ | 
|  | xfs_dahash_t | 
|  | xfs_parent_hashattr( | 
|  | struct xfs_mount		*mp, | 
|  | const uint8_t			*name, | 
|  | int				namelen, | 
|  | const void			*value, | 
|  | int				valuelen) | 
|  | { | 
|  | const struct xfs_parent_rec	*rec = value; | 
|  |  | 
|  | /* Requires a local attr value in xfs_parent_rec format */ | 
|  | if (valuelen != sizeof(struct xfs_parent_rec)) { | 
|  | ASSERT(valuelen == sizeof(struct xfs_parent_rec)); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (!value) { | 
|  | ASSERT(value != NULL); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return xfs_parent_hashval(mp, name, namelen, be64_to_cpu(rec->p_ino)); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Initialize the parent pointer arguments structure.  Caller must have zeroed | 
|  | * the contents of @args.  @tp is only required for updates. | 
|  | */ | 
|  | static void | 
|  | xfs_parent_da_args_init( | 
|  | struct xfs_da_args	*args, | 
|  | struct xfs_trans	*tp, | 
|  | struct xfs_parent_rec	*rec, | 
|  | struct xfs_inode	*child, | 
|  | xfs_ino_t		owner, | 
|  | const struct xfs_name	*parent_name) | 
|  | { | 
|  | args->geo = child->i_mount->m_attr_geo; | 
|  | args->whichfork = XFS_ATTR_FORK; | 
|  | args->attr_filter = XFS_ATTR_PARENT; | 
|  | args->op_flags = XFS_DA_OP_LOGGED | XFS_DA_OP_OKNOENT; | 
|  | args->trans = tp; | 
|  | args->dp = child; | 
|  | args->owner = owner; | 
|  | args->name = parent_name->name; | 
|  | args->namelen = parent_name->len; | 
|  | args->value = rec; | 
|  | args->valuelen = sizeof(struct xfs_parent_rec); | 
|  | xfs_attr_sethash(args); | 
|  | } | 
|  |  | 
|  | /* Make sure the incore state is ready for a parent pointer query/update. */ | 
|  | static inline int | 
|  | xfs_parent_iread_extents( | 
|  | struct xfs_trans	*tp, | 
|  | struct xfs_inode	*child) | 
|  | { | 
|  | /* Parent pointers require that the attr fork must exist. */ | 
|  | if (XFS_IS_CORRUPT(child->i_mount, !xfs_inode_has_attr_fork(child))) { | 
|  | xfs_inode_mark_sick(child, XFS_SICK_INO_PARENT); | 
|  | return -EFSCORRUPTED; | 
|  | } | 
|  |  | 
|  | return xfs_iread_extents(tp, child, XFS_ATTR_FORK); | 
|  | } | 
|  |  | 
|  | /* Add a parent pointer to reflect a dirent addition. */ | 
|  | int | 
|  | xfs_parent_addname( | 
|  | struct xfs_trans	*tp, | 
|  | struct xfs_parent_args	*ppargs, | 
|  | struct xfs_inode	*dp, | 
|  | const struct xfs_name	*parent_name, | 
|  | struct xfs_inode	*child) | 
|  | { | 
|  | int			error; | 
|  |  | 
|  | error = xfs_parent_iread_extents(tp, child); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | xfs_inode_to_parent_rec(&ppargs->rec, dp); | 
|  | xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child, | 
|  | child->i_ino, parent_name); | 
|  | xfs_attr_defer_add(&ppargs->args, XFS_ATTR_DEFER_SET); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Remove a parent pointer to reflect a dirent removal. */ | 
|  | int | 
|  | xfs_parent_removename( | 
|  | struct xfs_trans	*tp, | 
|  | struct xfs_parent_args	*ppargs, | 
|  | struct xfs_inode	*dp, | 
|  | const struct xfs_name	*parent_name, | 
|  | struct xfs_inode	*child) | 
|  | { | 
|  | int			error; | 
|  |  | 
|  | error = xfs_parent_iread_extents(tp, child); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | xfs_inode_to_parent_rec(&ppargs->rec, dp); | 
|  | xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child, | 
|  | child->i_ino, parent_name); | 
|  | xfs_attr_defer_add(&ppargs->args, XFS_ATTR_DEFER_REMOVE); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Replace one parent pointer with another to reflect a rename. */ | 
|  | int | 
|  | xfs_parent_replacename( | 
|  | struct xfs_trans	*tp, | 
|  | struct xfs_parent_args	*ppargs, | 
|  | struct xfs_inode	*old_dp, | 
|  | const struct xfs_name	*old_name, | 
|  | struct xfs_inode	*new_dp, | 
|  | const struct xfs_name	*new_name, | 
|  | struct xfs_inode	*child) | 
|  | { | 
|  | int			error; | 
|  |  | 
|  | error = xfs_parent_iread_extents(tp, child); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | xfs_inode_to_parent_rec(&ppargs->rec, old_dp); | 
|  | xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child, | 
|  | child->i_ino, old_name); | 
|  |  | 
|  | xfs_inode_to_parent_rec(&ppargs->new_rec, new_dp); | 
|  | ppargs->args.new_name = new_name->name; | 
|  | ppargs->args.new_namelen = new_name->len; | 
|  | ppargs->args.new_value = &ppargs->new_rec; | 
|  | ppargs->args.new_valuelen = sizeof(struct xfs_parent_rec); | 
|  | xfs_attr_defer_add(&ppargs->args, XFS_ATTR_DEFER_REPLACE); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Extract parent pointer information from any parent pointer xattr into | 
|  | * @parent_ino/gen.  The last two parameters can be NULL pointers. | 
|  | * | 
|  | * Returns 0 if this is not a parent pointer xattr at all; or -EFSCORRUPTED for | 
|  | * garbage. | 
|  | */ | 
|  | int | 
|  | xfs_parent_from_attr( | 
|  | struct xfs_mount	*mp, | 
|  | unsigned int		attr_flags, | 
|  | const unsigned char	*name, | 
|  | unsigned int		namelen, | 
|  | const void		*value, | 
|  | unsigned int		valuelen, | 
|  | xfs_ino_t		*parent_ino, | 
|  | uint32_t		*parent_gen) | 
|  | { | 
|  | const struct xfs_parent_rec	*rec = value; | 
|  |  | 
|  | ASSERT(attr_flags & XFS_ATTR_PARENT); | 
|  |  | 
|  | if (!xfs_parent_namecheck(attr_flags, name, namelen)) | 
|  | return -EFSCORRUPTED; | 
|  | if (!xfs_parent_valuecheck(mp, value, valuelen)) | 
|  | return -EFSCORRUPTED; | 
|  |  | 
|  | if (parent_ino) | 
|  | *parent_ino = be64_to_cpu(rec->p_ino); | 
|  | if (parent_gen) | 
|  | *parent_gen = be32_to_cpu(rec->p_gen); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Look up a parent pointer record (@parent_name -> @pptr) of @ip. | 
|  | * | 
|  | * Caller must hold at least ILOCK_SHARED.  The scratchpad need not be | 
|  | * initialized. | 
|  | * | 
|  | * Returns 0 if the pointer is found, -ENOATTR if there is no match, or a | 
|  | * negative errno. | 
|  | */ | 
|  | int | 
|  | xfs_parent_lookup( | 
|  | struct xfs_trans		*tp, | 
|  | struct xfs_inode		*ip, | 
|  | const struct xfs_name		*parent_name, | 
|  | struct xfs_parent_rec		*pptr, | 
|  | struct xfs_da_args		*scratch) | 
|  | { | 
|  | memset(scratch, 0, sizeof(struct xfs_da_args)); | 
|  | xfs_parent_da_args_init(scratch, tp, pptr, ip, ip->i_ino, parent_name); | 
|  | return xfs_attr_get_ilocked(scratch); | 
|  | } | 
|  |  | 
|  | /* Sanity-check a parent pointer before we try to perform repairs. */ | 
|  | static inline bool | 
|  | xfs_parent_sanity_check( | 
|  | struct xfs_mount		*mp, | 
|  | const struct xfs_name		*parent_name, | 
|  | const struct xfs_parent_rec	*pptr) | 
|  | { | 
|  | if (!xfs_parent_namecheck(XFS_ATTR_PARENT, parent_name->name, | 
|  | parent_name->len)) | 
|  | return false; | 
|  |  | 
|  | if (!xfs_parent_valuecheck(mp, pptr, sizeof(*pptr))) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Attach the parent pointer (@parent_name -> @pptr) to @ip immediately. | 
|  | * Caller must not have a transaction or hold the ILOCK.  This is for | 
|  | * specialized repair functions only.  The scratchpad need not be initialized. | 
|  | */ | 
|  | int | 
|  | xfs_parent_set( | 
|  | struct xfs_inode	*ip, | 
|  | xfs_ino_t		owner, | 
|  | const struct xfs_name	*parent_name, | 
|  | struct xfs_parent_rec	*pptr, | 
|  | struct xfs_da_args	*scratch) | 
|  | { | 
|  | if (!xfs_parent_sanity_check(ip->i_mount, parent_name, pptr)) { | 
|  | ASSERT(0); | 
|  | return -EFSCORRUPTED; | 
|  | } | 
|  |  | 
|  | memset(scratch, 0, sizeof(struct xfs_da_args)); | 
|  | xfs_parent_da_args_init(scratch, NULL, pptr, ip, owner, parent_name); | 
|  | return xfs_attr_set(scratch, XFS_ATTRUPDATE_CREATE, false); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Remove the parent pointer (@parent_name -> @pptr) from @ip immediately. | 
|  | * Caller must not have a transaction or hold the ILOCK.  This is for | 
|  | * specialized repair functions only.  The scratchpad need not be initialized. | 
|  | */ | 
|  | int | 
|  | xfs_parent_unset( | 
|  | struct xfs_inode		*ip, | 
|  | xfs_ino_t			owner, | 
|  | const struct xfs_name		*parent_name, | 
|  | struct xfs_parent_rec		*pptr, | 
|  | struct xfs_da_args		*scratch) | 
|  | { | 
|  | if (!xfs_parent_sanity_check(ip->i_mount, parent_name, pptr)) { | 
|  | ASSERT(0); | 
|  | return -EFSCORRUPTED; | 
|  | } | 
|  |  | 
|  | memset(scratch, 0, sizeof(struct xfs_da_args)); | 
|  | xfs_parent_da_args_init(scratch, NULL, pptr, ip, owner, parent_name); | 
|  | return xfs_attr_set(scratch, XFS_ATTRUPDATE_REMOVE, false); | 
|  | } |