// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2022-2024 Oracle. * All rights reserved. */ #include "xfs.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_error.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_log.h" #include "xfs_xattr.h" #include "xfs_parent.h" #include "xfs_trans_space.h" #include "xfs_attr_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); }