2024-04-22 09:47:44 -07:00
// 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"
2024-04-22 09:47:47 -07:00
# include "xfs_attr_item.h"
# include "xfs_health.h"
struct kmem_cache * xfs_parent_args_cache ;
2024-04-22 09:47:44 -07:00
/*
* 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 ;
}
2024-04-22 09:47:46 -07:00
/* 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 ) ) ;
}
2024-04-22 09:47:47 -07:00
/*
* 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 ;
}
2024-04-22 09:47:49 -07:00
/* 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 ;
}
2024-04-22 09:47:50 -07:00
/* 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 ;
}
2024-04-22 09:47:55 -07:00
/*
* 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 ;
}
2024-04-22 09:48:01 -07:00
/*
* 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 ) ;
}
2024-04-22 09:48:09 -07:00
/* 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 ) ;
}