| // 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; |
| } |