blob: 09bd8e3a1a05416a90c815c33bb8909e47d69e54 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2022 Oracle, Inc.
* All rights reserved.
*/
#include "libxfs_priv.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_log_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_inode.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_trace.h"
#include "xfs.h"
#include "xfs_fs.h"
#include "xfs_da_format.h"
#include "xfs_bmap_btree.h"
#include "xfs_trans.h"
#include "xfs_da_btree.h"
#include "xfs_attr.h"
#include "xfs_da_btree.h"
#include "xfs_attr_sf.h"
#include "xfs_bmap.h"
#include "xfs_parent.h"
#include "xfs_da_format.h"
#include "xfs_format.h"
#include "xfs_trans_space.h"
struct kmem_cache *xfs_parent_intent_cache;
/*
* Parent pointer attribute handling.
*
* Because the attribute value is a filename component, it will never be longer
* than 255 bytes. This means the attribute will always be a local format
* attribute as it is xfs_attr_leaf_entsize_local_max() for v5 filesystems will
* always be larger than this (max is 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 EA name is valid. */
bool
xfs_parent_namecheck(
struct xfs_mount *mp,
const struct xfs_parent_name_rec *rec,
size_t reclen,
unsigned int attr_flags)
{
xfs_ino_t p_ino;
if (reclen <= xfs_parent_name_rec_sizeof(0) ||
reclen > xfs_parent_name_rec_sizeof(XFS_PARENT_NAME_MAX_HASH_SIZE))
return false;
/* Only one namespace bit allowed. */
if (hweight32(attr_flags & XFS_ATTR_NSP_ONDISK_MASK) > 1)
return false;
p_ino = be64_to_cpu(rec->p_ino);
if (!xfs_verify_ino(mp, p_ino))
return false;
return true;
}
/* Return true if parent pointer EA value is valid. */
bool
xfs_parent_valuecheck(
struct xfs_mount *mp,
size_t namelen,
const void *value,
size_t valuelen)
{
if (namelen > XFS_PARENT_NAME_MAX_SIZE)
return false;
if (namelen < XFS_PARENT_NAME_MAX_SIZE && valuelen != 0)
return false;
if (namelen == XFS_PARENT_NAME_MAX_SIZE &&
valuelen >= MAXNAMELEN - XFS_PARENT_NAME_SHA512_OFFSET)
return false;
if (value == NULL)
return false;
return true;
}
/* Initializes a xfs_parent_name_rec to be stored as an attribute name */
static inline int
xfs_init_parent_name_rec(
struct xfs_parent_name_rec *rec,
const struct xfs_inode *dp,
const struct xfs_name *name,
struct xfs_inode *ip)
{
rec->p_ino = cpu_to_be64(dp->i_ino);
rec->p_gen = cpu_to_be32(VFS_IC(dp)->i_generation);
return xfs_parent_namehash(ip, name, rec->p_namehash,
XFS_PARENT_NAME_MAX_HASH_SIZE);
}
/* Compute the number of name bytes that can be encoded in the namehash. */
static inline unsigned int
xfs_parent_valuelen_adj(
int hashlen)
{
ASSERT(hashlen > 0);
if (hashlen == XFS_PARENT_NAME_MAX_HASH_SIZE)
return XFS_PARENT_NAME_SHA512_OFFSET;
return hashlen;
}
/*
* Convert an ondisk parent_name xattr to its incore format. If @value is
* NULL, set @irec->p_namelen to zero and leave @irec->p_name untouched.
*/
void
xfs_parent_irec_from_disk(
struct xfs_parent_name_irec *irec,
const struct xfs_parent_name_rec *rec,
int reclen,
const void *value,
int valuelen)
{
irec->p_ino = be64_to_cpu(rec->p_ino);
irec->p_gen = be32_to_cpu(rec->p_gen);
irec->hashlen = xfs_parent_name_hashlen(reclen);
memcpy(irec->p_namehash, rec->p_namehash, irec->hashlen);
memset(irec->p_namehash + irec->hashlen, 0,
sizeof(irec->p_namehash) - irec->hashlen);
if (!value) {
irec->p_namelen = 0;
return;
}
ASSERT(valuelen < MAXNAMELEN);
if (irec->hashlen == XFS_PARENT_NAME_MAX_HASH_SIZE) {
ASSERT(valuelen > 0);
ASSERT(valuelen <= MAXNAMELEN - XFS_PARENT_NAME_SHA512_OFFSET);
valuelen = min_t(int, valuelen,
MAXNAMELEN - XFS_PARENT_NAME_SHA512_OFFSET);
memcpy(irec->p_name, irec->p_namehash,
XFS_PARENT_NAME_SHA512_OFFSET);
memcpy(&irec->p_name[XFS_PARENT_NAME_SHA512_OFFSET],
value, valuelen);
irec->p_namelen = XFS_PARENT_NAME_SHA512_OFFSET + valuelen;
} else {
ASSERT(valuelen == 0);
memcpy(irec->p_name, irec->p_namehash, irec->hashlen);
irec->p_namelen = irec->hashlen;
}
memset(&irec->p_name[irec->p_namelen], 0,
sizeof(irec->p_name) - irec->p_namelen);
}
/*
* Convert an incore parent_name record to its ondisk format. If @value or
* @valuelen are NULL, they will not be written to.
*/
void
xfs_parent_irec_to_disk(
struct xfs_parent_name_rec *rec,
int *reclen,
void *value,
int *valuelen,
const struct xfs_parent_name_irec *irec)
{
rec->p_ino = cpu_to_be64(irec->p_ino);
rec->p_gen = cpu_to_be32(irec->p_gen);
*reclen = xfs_parent_name_rec_sizeof(irec->hashlen);
memcpy(rec->p_namehash, irec->p_namehash, irec->hashlen);
if (valuelen) {
ASSERT(*valuelen > 0);
ASSERT(*valuelen >= irec->p_namelen);
ASSERT(*valuelen < MAXNAMELEN);
if (irec->hashlen == XFS_PARENT_NAME_MAX_HASH_SIZE)
*valuelen = irec->p_namelen - XFS_PARENT_NAME_SHA512_OFFSET;
else
*valuelen = 0;
}
if (value && irec->hashlen == XFS_PARENT_NAME_MAX_HASH_SIZE)
memcpy(value, irec->p_name + XFS_PARENT_NAME_SHA512_OFFSET,
irec->p_namelen - XFS_PARENT_NAME_SHA512_OFFSET);
}
/*
* Allocate memory to control a logged parent pointer update as part of a
* dirent operation.
*/
int
__xfs_parent_init(
struct xfs_mount *mp,
bool grab_log,
struct xfs_parent_defer **parentp)
{
struct xfs_parent_defer *parent;
int error;
if (grab_log) {
error = xfs_attr_grab_log_assist(mp);
if (error)
return error;
}
parent = kmem_cache_zalloc(xfs_parent_intent_cache, GFP_KERNEL);
if (!parent) {
if (grab_log)
xfs_attr_rele_log_assist(mp);
return -ENOMEM;
}
/* init parent da_args */
parent->have_log = grab_log;
parent->args.geo = mp->m_attr_geo;
parent->args.whichfork = XFS_ATTR_FORK;
parent->args.attr_filter = XFS_ATTR_PARENT;
parent->args.op_flags = XFS_DA_OP_OKNOENT | XFS_DA_OP_LOGGED;
parent->args.name = (const uint8_t *)&parent->rec;
parent->args.namelen = 0;
*parentp = parent;
return 0;
}
/* Add a parent pointer to reflect a dirent addition. */
int
xfs_parent_add(
struct xfs_trans *tp,
struct xfs_parent_defer *parent,
struct xfs_inode *dp,
const struct xfs_name *parent_name,
struct xfs_inode *child)
{
struct xfs_da_args *args = &parent->args;
int hashlen;
unsigned int name_adj;
hashlen = xfs_init_parent_name_rec(&parent->rec, dp, parent_name,
child);
if (hashlen < 0)
return hashlen;
args->namelen = xfs_parent_name_rec_sizeof(hashlen);
args->hashval = xfs_da_hashname(args->name, args->namelen);
name_adj = xfs_parent_valuelen_adj(hashlen);
args->trans = tp;
args->dp = child;
if (parent_name) {
parent->args.value = (void *)parent_name->name + name_adj;
parent->args.valuelen = parent_name->len - name_adj;
}
return xfs_attr_defer_add(args);
}
/* Remove a parent pointer to reflect a dirent removal. */
int
xfs_parent_remove(
struct xfs_trans *tp,
struct xfs_parent_defer *parent,
struct xfs_inode *dp,
const struct xfs_name *name,
struct xfs_inode *child)
{
struct xfs_da_args *args = &parent->args;
int hashlen;
hashlen = xfs_init_parent_name_rec(&parent->rec, dp, name, child);
if (hashlen < 0)
return hashlen;
args->namelen = xfs_parent_name_rec_sizeof(hashlen);
args->trans = tp;
args->dp = child;
args->hashval = xfs_da_hashname(args->name, args->namelen);
return xfs_attr_defer_remove(args);
}
/* Replace one parent pointer with another to reflect a rename. */
int
xfs_parent_replace(
struct xfs_trans *tp,
struct xfs_parent_defer *new_parent,
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)
{
struct xfs_da_args *args = &new_parent->args;
int old_hashlen, new_hashlen;
int new_name_adj;
old_hashlen = xfs_init_parent_name_rec(&new_parent->old_rec, old_dp,
old_name, child);
if (old_hashlen < 0)
return old_hashlen;
new_hashlen = xfs_init_parent_name_rec(&new_parent->rec, new_dp,
new_name, child);
if (new_hashlen < 0)
return new_hashlen;
new_name_adj = xfs_parent_valuelen_adj(new_hashlen);
new_parent->args.name = (const uint8_t *)&new_parent->old_rec;
new_parent->args.namelen = xfs_parent_name_rec_sizeof(old_hashlen);
new_parent->args.new_name = (const uint8_t *)&new_parent->rec;
new_parent->args.new_namelen = xfs_parent_name_rec_sizeof(new_hashlen);
args->trans = tp;
args->dp = child;
new_parent->args.value = (void *)new_name->name + new_name_adj;
new_parent->args.valuelen = new_name->len - new_name_adj;
args->hashval = xfs_da_hashname(args->name, args->namelen);
return xfs_attr_defer_replace(args);
}
void
__xfs_parent_cancel(
xfs_mount_t *mp,
struct xfs_parent_defer *parent)
{
if (parent->have_log)
xlog_drop_incompat_feat(mp->m_log);
kmem_cache_free(xfs_parent_intent_cache, parent);
}
unsigned int
xfs_pptr_calc_space_res(
struct xfs_mount *mp,
unsigned int namelen)
{
/*
* Pptrs are always the first attr in an attr tree, and never larger
* than a block
*/
return XFS_DAENTER_SPACE_RES(mp, XFS_ATTR_FORK) +
XFS_NEXTENTADD_SPACE_RES(mp, namelen, XFS_ATTR_FORK);
}
/*
* Look up the @name associated with the parent pointer (@pptr) of @ip. Caller
* must hold at least ILOCK_SHARED. Returns the length of the dirent name, or
* a negative errno. The scratchpad need not be initialized.
*/
int
xfs_parent_lookup(
struct xfs_trans *tp,
struct xfs_inode *ip,
const struct xfs_parent_name_irec *pptr,
unsigned char *name,
unsigned int namelen,
struct xfs_parent_scratch *scr)
{
int reclen;
int name_adj;
int error;
xfs_parent_irec_to_disk(&scr->rec, &reclen, NULL, NULL, pptr);
name_adj = xfs_parent_valuelen_adj(pptr->hashlen);
memset(&scr->args, 0, sizeof(struct xfs_da_args));
scr->args.attr_filter = XFS_ATTR_PARENT;
scr->args.dp = ip;
scr->args.geo = ip->i_mount->m_attr_geo;
scr->args.name = (const unsigned char *)&scr->rec;
scr->args.namelen = reclen;
scr->args.op_flags = XFS_DA_OP_OKNOENT;
scr->args.trans = tp;
scr->args.valuelen = namelen - name_adj;
scr->args.value = name + name_adj;
scr->args.whichfork = XFS_ATTR_FORK;
scr->args.hashval = xfs_da_hashname(scr->args.name, scr->args.namelen);
error = xfs_attr_get_ilocked(&scr->args);
if (error)
return error;
memcpy(name, pptr->p_namehash, name_adj);
return scr->args.valuelen + name_adj;
}
/*
* Attach the parent pointer (@pptr -> @name) to @ip immediately. Caller must
* not have a transaction or hold the ILOCK. The update will not use logged
* xattrs. This is for specialized repair functions only. The scratchpad need
* not be initialized.
*/
int
xfs_parent_set(
struct xfs_inode *ip,
const struct xfs_parent_name_irec *pptr,
struct xfs_parent_scratch *scr)
{
int reclen;
int name_adj;
xfs_parent_irec_to_disk(&scr->rec, &reclen, NULL, NULL, pptr);
name_adj = xfs_parent_valuelen_adj(pptr->hashlen);
memset(&scr->args, 0, sizeof(struct xfs_da_args));
scr->args.attr_filter = XFS_ATTR_PARENT;
scr->args.dp = ip;
scr->args.geo = ip->i_mount->m_attr_geo;
scr->args.name = (const unsigned char *)&scr->rec;
scr->args.namelen = reclen;
scr->args.valuelen = pptr->p_namelen - name_adj;
scr->args.value = (void *)pptr->p_name + name_adj;
scr->args.whichfork = XFS_ATTR_FORK;
return xfs_attr_set(&scr->args);
}
/*
* Remove the parent pointer (@rec -> @name) from @ip immediately. Caller must
* not have a transaction or hold the ILOCK. The update will not use logged
* xattrs. This is for specialized repair functions only. The scratchpad need
* not be initialized.
*/
int
xfs_parent_unset(
struct xfs_inode *ip,
const struct xfs_parent_name_irec *pptr,
struct xfs_parent_scratch *scr)
{
int reclen;
xfs_parent_irec_to_disk(&scr->rec, &reclen, NULL, NULL, pptr);
memset(&scr->args, 0, sizeof(struct xfs_da_args));
scr->args.attr_filter = XFS_ATTR_PARENT;
scr->args.dp = ip;
scr->args.geo = ip->i_mount->m_attr_geo;
scr->args.name = (const unsigned char *)&scr->rec;
scr->args.namelen = reclen;
scr->args.whichfork = XFS_ATTR_FORK;
return xfs_attr_set(&scr->args);
}
/*
* Compute the parent pointer namehash for the given child file and dirent
* name. Returns the length of the hash in bytes, or a negative errno.
*/
int
xfs_parent_namehash(
struct xfs_inode *ip,
const struct xfs_name *name,
void *namehash,
unsigned int namehash_len)
{
SHA512_DESC_ON_STACK(ip->i_mount, shash);
__be32 gen = cpu_to_be32(VFS_I(ip)->i_generation);
int error;
ASSERT(SHA512_DIGEST_SIZE ==
crypto_shash_digestsize(ip->i_mount->m_sha512));
if (namehash_len != XFS_PARENT_NAME_MAX_HASH_SIZE) {
ASSERT(0);
return -EINVAL;
}
if (name->len < XFS_PARENT_NAME_MAX_HASH_SIZE) {
/*
* If the dirent name is shorter than the size of the namehash
* field, write it directly into the namehash field.
*/
memcpy(namehash, name->name, name->len);
memset(namehash + name->len, 0, namehash_len - name->len);
return name->len;
}
error = sha512_init(&shash);
if (error)
goto out;
error = sha512_process(&shash, (const u8 *)&gen, sizeof(gen));
if (error)
goto out;
error = sha512_process(&shash, name->name, name->len);
if (error)
goto out;
/*
* The sha512 hash of the child gen and dirent name is placed at the
* end of the namehash, and as many bytes as will fit are copied from
* the dirent name to the start of the namehash.
*/
error = sha512_done(&shash, namehash + XFS_PARENT_NAME_SHA512_OFFSET);
if (error)
goto out;
memcpy(namehash, name->name, XFS_PARENT_NAME_SHA512_OFFSET);
error = XFS_PARENT_NAME_MAX_HASH_SIZE;
out:
sha512_erase(&shash);
return error;
}
/* Recalculate the name hash of this parent pointer. */
int
xfs_parent_irec_hash(
struct xfs_inode *ip,
struct xfs_parent_name_irec *pptr)
{
struct xfs_name xname = {
.name = pptr->p_name,
.len = pptr->p_namelen,
};
int hashlen;
hashlen = xfs_parent_namehash(ip, &xname, &pptr->p_namehash,
sizeof(pptr->p_namehash));
if (hashlen < 0)
return hashlen;
pptr->hashlen = hashlen;
return 0;
}