2018-06-06 09:42:14 +07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
2005-11-02 10:58:39 +07:00
|
|
|
* Copyright (c) 2000-2003,2005 Silicon Graphics, Inc.
|
|
|
|
* All Rights Reserved.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
|
|
|
#include "xfs.h"
|
2005-11-02 10:38:42 +07:00
|
|
|
#include "xfs_fs.h"
|
2019-06-29 09:25:35 +07:00
|
|
|
#include "xfs_shared.h"
|
2013-10-23 06:51:50 +07:00
|
|
|
#include "xfs_format.h"
|
2013-10-23 06:50:10 +07:00
|
|
|
#include "xfs_log_format.h"
|
|
|
|
#include "xfs_trans_resv.h"
|
2005-04-17 05:20:36 +07:00
|
|
|
#include "xfs_mount.h"
|
|
|
|
#include "xfs_inode.h"
|
2013-10-23 06:50:10 +07:00
|
|
|
#include "xfs_trans.h"
|
2013-08-12 17:49:37 +07:00
|
|
|
#include "xfs_dir2.h"
|
2011-07-13 18:43:48 +07:00
|
|
|
#include "xfs_dir2_priv.h"
|
2009-12-15 06:14:59 +07:00
|
|
|
#include "xfs_trace.h"
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Prototypes for internal functions.
|
|
|
|
*/
|
|
|
|
static void xfs_dir2_sf_addname_easy(xfs_da_args_t *args,
|
|
|
|
xfs_dir2_sf_entry_t *sfep,
|
|
|
|
xfs_dir2_data_aoff_t offset,
|
|
|
|
int new_isize);
|
|
|
|
static void xfs_dir2_sf_addname_hard(xfs_da_args_t *args, int objchange,
|
|
|
|
int new_isize);
|
|
|
|
static int xfs_dir2_sf_addname_pick(xfs_da_args_t *args, int objchange,
|
|
|
|
xfs_dir2_sf_entry_t **sfepp,
|
|
|
|
xfs_dir2_data_aoff_t *offsetp);
|
|
|
|
#ifdef DEBUG
|
|
|
|
static void xfs_dir2_sf_check(xfs_da_args_t *args);
|
|
|
|
#else
|
|
|
|
#define xfs_dir2_sf_check(args)
|
|
|
|
#endif /* DEBUG */
|
2014-07-30 06:12:05 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static void xfs_dir2_sf_toino4(xfs_da_args_t *args);
|
|
|
|
static void xfs_dir2_sf_toino8(xfs_da_args_t *args);
|
|
|
|
|
2019-12-17 02:14:09 +07:00
|
|
|
int
|
2019-11-09 06:02:38 +07:00
|
|
|
xfs_dir2_sf_entsize(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
struct xfs_dir2_sf_hdr *hdr,
|
|
|
|
int len)
|
|
|
|
{
|
|
|
|
int count = len;
|
|
|
|
|
|
|
|
count += sizeof(struct xfs_dir2_sf_entry); /* namelen + offset */
|
|
|
|
count += hdr->i8count ? XFS_INO64_SIZE : XFS_INO32_SIZE; /* ino # */
|
|
|
|
|
|
|
|
if (xfs_sb_version_hasftype(&mp->m_sb))
|
|
|
|
count += sizeof(uint8_t);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct xfs_dir2_sf_entry *
|
|
|
|
xfs_dir2_sf_nextentry(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
struct xfs_dir2_sf_hdr *hdr,
|
|
|
|
struct xfs_dir2_sf_entry *sfep)
|
|
|
|
{
|
|
|
|
return (void *)sfep + xfs_dir2_sf_entsize(mp, hdr, sfep->namelen);
|
|
|
|
}
|
|
|
|
|
2019-11-09 06:02:59 +07:00
|
|
|
/*
|
|
|
|
* In short-form directory entries the inode numbers are stored at variable
|
|
|
|
* offset behind the entry name. If the entry stores a filetype value, then it
|
|
|
|
* sits between the name and the inode number. The actual inode numbers can
|
|
|
|
* come in two formats as well, either 4 bytes or 8 bytes wide.
|
|
|
|
*/
|
|
|
|
xfs_ino_t
|
|
|
|
xfs_dir2_sf_get_ino(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
struct xfs_dir2_sf_hdr *hdr,
|
|
|
|
struct xfs_dir2_sf_entry *sfep)
|
|
|
|
{
|
|
|
|
uint8_t *from = sfep->name + sfep->namelen;
|
|
|
|
|
|
|
|
if (xfs_sb_version_hasftype(&mp->m_sb))
|
|
|
|
from++;
|
|
|
|
|
|
|
|
if (!hdr->i8count)
|
|
|
|
return get_unaligned_be32(from);
|
|
|
|
return get_unaligned_be64(from) & XFS_MAXINUMBER;
|
|
|
|
}
|
|
|
|
|
2019-12-17 02:14:09 +07:00
|
|
|
void
|
2019-11-09 06:02:59 +07:00
|
|
|
xfs_dir2_sf_put_ino(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
struct xfs_dir2_sf_hdr *hdr,
|
|
|
|
struct xfs_dir2_sf_entry *sfep,
|
|
|
|
xfs_ino_t ino)
|
|
|
|
{
|
|
|
|
uint8_t *to = sfep->name + sfep->namelen;
|
|
|
|
|
|
|
|
ASSERT(ino <= XFS_MAXINUMBER);
|
|
|
|
|
|
|
|
if (xfs_sb_version_hasftype(&mp->m_sb))
|
|
|
|
to++;
|
|
|
|
|
|
|
|
if (hdr->i8count)
|
|
|
|
put_unaligned_be64(ino, to);
|
|
|
|
else
|
|
|
|
put_unaligned_be32(ino, to);
|
|
|
|
}
|
|
|
|
|
|
|
|
xfs_ino_t
|
|
|
|
xfs_dir2_sf_get_parent_ino(
|
|
|
|
struct xfs_dir2_sf_hdr *hdr)
|
|
|
|
{
|
|
|
|
if (!hdr->i8count)
|
|
|
|
return get_unaligned_be32(hdr->parent);
|
|
|
|
return get_unaligned_be64(hdr->parent) & XFS_MAXINUMBER;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xfs_dir2_sf_put_parent_ino(
|
|
|
|
struct xfs_dir2_sf_hdr *hdr,
|
|
|
|
xfs_ino_t ino)
|
|
|
|
{
|
|
|
|
ASSERT(ino <= XFS_MAXINUMBER);
|
|
|
|
|
|
|
|
if (hdr->i8count)
|
|
|
|
put_unaligned_be64(ino, hdr->parent);
|
|
|
|
else
|
|
|
|
put_unaligned_be32(ino, hdr->parent);
|
|
|
|
}
|
|
|
|
|
2019-11-09 06:03:30 +07:00
|
|
|
/*
|
|
|
|
* The file type field is stored at the end of the name for filetype enabled
|
|
|
|
* shortform directories, or not at all otherwise.
|
|
|
|
*/
|
|
|
|
uint8_t
|
|
|
|
xfs_dir2_sf_get_ftype(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
struct xfs_dir2_sf_entry *sfep)
|
|
|
|
{
|
|
|
|
if (xfs_sb_version_hasftype(&mp->m_sb)) {
|
|
|
|
uint8_t ftype = sfep->name[sfep->namelen];
|
|
|
|
|
|
|
|
if (ftype < XFS_DIR3_FT_MAX)
|
|
|
|
return ftype;
|
|
|
|
}
|
|
|
|
|
|
|
|
return XFS_DIR3_FT_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
2019-12-17 02:14:09 +07:00
|
|
|
void
|
2019-11-09 06:03:30 +07:00
|
|
|
xfs_dir2_sf_put_ftype(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
struct xfs_dir2_sf_entry *sfep,
|
|
|
|
uint8_t ftype)
|
|
|
|
{
|
|
|
|
ASSERT(ftype < XFS_DIR3_FT_MAX);
|
|
|
|
|
|
|
|
if (xfs_sb_version_hasftype(&mp->m_sb))
|
|
|
|
sfep->name[sfep->namelen] = ftype;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Given a block directory (dp/block), calculate its size as a shortform (sf)
|
|
|
|
* directory and a header for the sf directory, if it will fit it the
|
|
|
|
* space currently present in the inode. If it won't fit, the output
|
|
|
|
* size is too big (but not accurate).
|
|
|
|
*/
|
|
|
|
int /* size for sf form */
|
|
|
|
xfs_dir2_block_sfsize(
|
|
|
|
xfs_inode_t *dp, /* incore inode pointer */
|
2011-07-08 19:35:27 +07:00
|
|
|
xfs_dir2_data_hdr_t *hdr, /* block directory data */
|
2005-04-17 05:20:36 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfhp) /* output: header for sf form */
|
|
|
|
{
|
|
|
|
xfs_dir2_dataptr_t addr; /* data entry address */
|
|
|
|
xfs_dir2_leaf_entry_t *blp; /* leaf area of the block */
|
|
|
|
xfs_dir2_block_tail_t *btp; /* tail area of the block */
|
|
|
|
int count; /* shortform entry count */
|
|
|
|
xfs_dir2_data_entry_t *dep; /* data entry in the block */
|
|
|
|
int i; /* block entry index */
|
|
|
|
int i8count; /* count of big-inode entries */
|
|
|
|
int isdot; /* entry is "." */
|
|
|
|
int isdotdot; /* entry is ".." */
|
|
|
|
xfs_mount_t *mp; /* mount structure pointer */
|
|
|
|
int namelen; /* total name bytes */
|
2005-11-02 11:06:18 +07:00
|
|
|
xfs_ino_t parent = 0; /* parent inode number */
|
2005-04-17 05:20:36 +07:00
|
|
|
int size=0; /* total computed size */
|
2013-08-12 17:50:09 +07:00
|
|
|
int has_ftype;
|
2014-06-06 12:15:59 +07:00
|
|
|
struct xfs_da_geometry *geo;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
mp = dp->i_mount;
|
2014-06-06 12:15:59 +07:00
|
|
|
geo = mp->m_dir_geo;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-08-12 17:50:09 +07:00
|
|
|
/*
|
|
|
|
* if there is a filetype field, add the extra byte to the namelen
|
|
|
|
* for each entry that we see.
|
|
|
|
*/
|
|
|
|
has_ftype = xfs_sb_version_hasftype(&mp->m_sb) ? 1 : 0;
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
count = i8count = namelen = 0;
|
2014-06-06 12:15:59 +07:00
|
|
|
btp = xfs_dir2_block_tail_p(geo, hdr);
|
2007-06-28 13:43:50 +07:00
|
|
|
blp = xfs_dir2_block_leaf_p(btp);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Iterate over the block's data entries by using the leaf pointers.
|
|
|
|
*/
|
2006-03-17 13:27:56 +07:00
|
|
|
for (i = 0; i < be32_to_cpu(btp->count); i++) {
|
2006-03-17 13:28:18 +07:00
|
|
|
if ((addr = be32_to_cpu(blp[i].address)) == XFS_DIR2_NULL_DATAPTR)
|
2005-04-17 05:20:36 +07:00
|
|
|
continue;
|
|
|
|
/*
|
|
|
|
* Calculate the pointer to the entry at hand.
|
|
|
|
*/
|
2014-06-06 12:08:18 +07:00
|
|
|
dep = (xfs_dir2_data_entry_t *)((char *)hdr +
|
2014-06-06 12:15:59 +07:00
|
|
|
xfs_dir2_dataptr_to_off(geo, addr));
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Detect . and .., so we can special-case them.
|
|
|
|
* . is not included in sf directories.
|
|
|
|
* .. is included by just the parent inode number.
|
|
|
|
*/
|
|
|
|
isdot = dep->namelen == 1 && dep->name[0] == '.';
|
|
|
|
isdotdot =
|
|
|
|
dep->namelen == 2 &&
|
|
|
|
dep->name[0] == '.' && dep->name[1] == '.';
|
2014-07-30 06:12:05 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!isdot)
|
2006-06-09 11:48:37 +07:00
|
|
|
i8count += be64_to_cpu(dep->inumber) > XFS_DIR2_MAX_SHORT_INUM;
|
2014-07-30 06:12:05 +07:00
|
|
|
|
2013-08-12 17:50:09 +07:00
|
|
|
/* take into account the file type field */
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!isdot && !isdotdot) {
|
|
|
|
count++;
|
2013-08-12 17:50:09 +07:00
|
|
|
namelen += dep->namelen + has_ftype;
|
2005-04-17 05:20:36 +07:00
|
|
|
} else if (isdotdot)
|
2006-06-09 11:48:37 +07:00
|
|
|
parent = be64_to_cpu(dep->inumber);
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Calculate the new size, see if we should give up yet.
|
|
|
|
*/
|
2016-07-20 08:47:21 +07:00
|
|
|
size = xfs_dir2_sf_hdr_size(i8count) + /* header */
|
|
|
|
count * 3 * sizeof(u8) + /* namelen + offset */
|
|
|
|
namelen + /* name */
|
|
|
|
(i8count ? /* inumber */
|
2016-07-20 08:48:31 +07:00
|
|
|
count * XFS_INO64_SIZE :
|
|
|
|
count * XFS_INO32_SIZE);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (size > XFS_IFORK_DSIZE(dp))
|
|
|
|
return size; /* size value is a failure */
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Create the output header, if it worked.
|
|
|
|
*/
|
|
|
|
sfhp->count = count;
|
|
|
|
sfhp->i8count = i8count;
|
2019-11-09 06:02:31 +07:00
|
|
|
xfs_dir2_sf_put_parent_ino(sfhp, parent);
|
2005-04-17 05:20:36 +07:00
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert a block format directory to shortform.
|
|
|
|
* Caller has already checked that it will fit, and built us a header.
|
|
|
|
*/
|
|
|
|
int /* error */
|
|
|
|
xfs_dir2_block_to_sf(
|
2019-11-09 06:05:34 +07:00
|
|
|
struct xfs_da_args *args, /* operation arguments */
|
2012-06-22 15:50:14 +07:00
|
|
|
struct xfs_buf *bp,
|
2005-04-17 05:20:36 +07:00
|
|
|
int size, /* shortform directory size */
|
2019-11-09 06:05:34 +07:00
|
|
|
struct xfs_dir2_sf_hdr *sfhp) /* shortform directory hdr */
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2019-11-09 06:05:34 +07:00
|
|
|
struct xfs_inode *dp = args->dp;
|
|
|
|
struct xfs_mount *mp = dp->i_mount;
|
2005-04-17 05:20:36 +07:00
|
|
|
int error; /* error return value */
|
|
|
|
int logflags; /* inode logging flags */
|
2019-11-09 06:05:34 +07:00
|
|
|
struct xfs_dir2_sf_entry *sfep; /* shortform entry */
|
|
|
|
struct xfs_dir2_sf_hdr *sfp; /* shortform directory header */
|
2019-11-09 06:05:38 +07:00
|
|
|
unsigned int offset = args->geo->data_entry_offset;
|
2019-11-09 06:05:34 +07:00
|
|
|
unsigned int end;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-12-15 06:14:59 +07:00
|
|
|
trace_xfs_dir2_block_to_sf(args);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
2019-11-09 06:05:34 +07:00
|
|
|
* Allocate a temporary destination buffer the size of the inode to
|
|
|
|
* format the data into. Once we have formatted the data, we can free
|
|
|
|
* the block and copy the formatted data into the inode literal area.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2019-11-09 06:05:34 +07:00
|
|
|
sfp = kmem_alloc(mp->m_sb.sb_inodesize, 0);
|
2007-06-28 13:43:50 +07:00
|
|
|
memcpy(sfp, sfhp, xfs_dir2_sf_hdr_size(sfhp->i8count));
|
2013-12-03 19:50:57 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
2019-11-09 06:05:34 +07:00
|
|
|
* Loop over the active and unused entries. Stop when we reach the
|
|
|
|
* leaf/tail portion of the block.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2019-11-09 06:05:36 +07:00
|
|
|
end = xfs_dir3_data_end_offset(args->geo, bp->b_addr);
|
2007-06-28 13:43:50 +07:00
|
|
|
sfep = xfs_dir2_sf_firstentry(sfp);
|
2019-11-09 06:05:34 +07:00
|
|
|
while (offset < end) {
|
|
|
|
struct xfs_dir2_data_unused *dup = bp->b_addr + offset;
|
|
|
|
struct xfs_dir2_data_entry *dep = bp->b_addr + offset;
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* If it's unused, just skip over it.
|
|
|
|
*/
|
2006-03-17 13:27:37 +07:00
|
|
|
if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
|
2019-11-09 06:05:34 +07:00
|
|
|
offset += be16_to_cpu(dup->length);
|
2005-04-17 05:20:36 +07:00
|
|
|
continue;
|
|
|
|
}
|
2019-11-09 06:05:34 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Skip .
|
|
|
|
*/
|
|
|
|
if (dep->namelen == 1 && dep->name[0] == '.')
|
2006-06-09 11:48:37 +07:00
|
|
|
ASSERT(be64_to_cpu(dep->inumber) == dp->i_ino);
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Skip .., but make sure the inode number is right.
|
|
|
|
*/
|
|
|
|
else if (dep->namelen == 2 &&
|
|
|
|
dep->name[0] == '.' && dep->name[1] == '.')
|
2006-06-09 11:48:37 +07:00
|
|
|
ASSERT(be64_to_cpu(dep->inumber) ==
|
2019-11-09 06:02:31 +07:00
|
|
|
xfs_dir2_sf_get_parent_ino(sfp));
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Normal entry, copy it into shortform.
|
|
|
|
*/
|
|
|
|
else {
|
|
|
|
sfep->namelen = dep->namelen;
|
2019-11-09 06:05:34 +07:00
|
|
|
xfs_dir2_sf_put_offset(sfep, offset);
|
2005-04-17 05:20:36 +07:00
|
|
|
memcpy(sfep->name, dep->name, dep->namelen);
|
2019-11-09 06:02:59 +07:00
|
|
|
xfs_dir2_sf_put_ino(mp, sfp, sfep,
|
2013-10-29 18:11:47 +07:00
|
|
|
be64_to_cpu(dep->inumber));
|
2019-11-09 06:03:30 +07:00
|
|
|
xfs_dir2_sf_put_ftype(mp, sfep,
|
2019-11-09 06:05:48 +07:00
|
|
|
xfs_dir2_data_get_ftype(mp, dep));
|
2011-07-08 19:35:03 +07:00
|
|
|
|
2019-11-09 06:02:38 +07:00
|
|
|
sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2019-11-09 06:05:37 +07:00
|
|
|
offset += xfs_dir2_data_entsize(mp, dep->namelen);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
ASSERT((char *)sfep - (char *)sfp == size);
|
2013-12-03 19:50:57 +07:00
|
|
|
|
|
|
|
/* now we are done with the block, we can shrink the inode */
|
|
|
|
logflags = XFS_ILOG_CORE;
|
2014-06-06 12:11:18 +07:00
|
|
|
error = xfs_dir2_shrink_inode(args, args->geo->datablk, bp);
|
2013-12-03 19:50:57 +07:00
|
|
|
if (error) {
|
2014-06-25 11:58:08 +07:00
|
|
|
ASSERT(error != -ENOSPC);
|
2013-12-03 19:50:57 +07:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The buffer is now unconditionally gone, whether
|
|
|
|
* xfs_dir2_shrink_inode worked or not.
|
|
|
|
*
|
|
|
|
* Convert the inode to local format and copy the data in.
|
|
|
|
*/
|
|
|
|
ASSERT(dp->i_df.if_bytes == 0);
|
2019-11-09 06:05:34 +07:00
|
|
|
xfs_init_local_fork(dp, XFS_DATA_FORK, sfp, size);
|
2016-04-06 04:41:43 +07:00
|
|
|
dp->i_d.di_format = XFS_DINODE_FMT_LOCAL;
|
|
|
|
dp->i_d.di_size = size;
|
2013-12-03 19:50:57 +07:00
|
|
|
|
|
|
|
logflags |= XFS_ILOG_DDATA;
|
2005-04-17 05:20:36 +07:00
|
|
|
xfs_dir2_sf_check(args);
|
|
|
|
out:
|
|
|
|
xfs_trans_log_inode(args->trans, dp, logflags);
|
2019-11-09 06:05:34 +07:00
|
|
|
kmem_free(sfp);
|
2005-04-17 05:20:36 +07:00
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add a name to a shortform directory.
|
|
|
|
* There are two algorithms, "easy" and "hard" which we decide on
|
|
|
|
* before changing anything.
|
|
|
|
* Convert to block form if necessary, if the new entry won't fit.
|
|
|
|
*/
|
|
|
|
int /* error */
|
|
|
|
xfs_dir2_sf_addname(
|
|
|
|
xfs_da_args_t *args) /* operation arguments */
|
|
|
|
{
|
|
|
|
xfs_inode_t *dp; /* incore directory inode */
|
|
|
|
int error; /* error return value */
|
|
|
|
int incr_isize; /* total change in size */
|
|
|
|
int new_isize; /* di_size after adding name */
|
|
|
|
int objchange; /* changing to 8-byte inodes */
|
2005-11-02 11:06:18 +07:00
|
|
|
xfs_dir2_data_aoff_t offset = 0; /* offset for new entry */
|
2005-04-17 05:20:36 +07:00
|
|
|
int pick; /* which algorithm to use */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* shortform structure */
|
2005-11-02 11:06:18 +07:00
|
|
|
xfs_dir2_sf_entry_t *sfep = NULL; /* shortform entry */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-12-15 06:14:59 +07:00
|
|
|
trace_xfs_dir2_sf_addname(args);
|
|
|
|
|
2014-06-25 11:58:08 +07:00
|
|
|
ASSERT(xfs_dir2_sf_lookup(args) == -ENOENT);
|
2005-04-17 05:20:36 +07:00
|
|
|
dp = args->dp;
|
|
|
|
ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
|
2019-11-02 23:38:08 +07:00
|
|
|
ASSERT(dp->i_d.di_size >= offsetof(struct xfs_dir2_sf_hdr, parent));
|
2005-04-17 05:20:36 +07:00
|
|
|
ASSERT(dp->i_df.if_bytes == dp->i_d.di_size);
|
|
|
|
ASSERT(dp->i_df.if_u1.if_data != NULL);
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
|
|
|
ASSERT(dp->i_d.di_size >= xfs_dir2_sf_hdr_size(sfp->i8count));
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Compute entry (and change in) size.
|
|
|
|
*/
|
2019-11-09 06:02:38 +07:00
|
|
|
incr_isize = xfs_dir2_sf_entsize(dp->i_mount, sfp, args->namelen);
|
2005-04-17 05:20:36 +07:00
|
|
|
objchange = 0;
|
2014-07-30 06:12:05 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Do we have to change to 8 byte inodes?
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
if (args->inumber > XFS_DIR2_MAX_SHORT_INUM && sfp->i8count == 0) {
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
2014-04-14 16:07:23 +07:00
|
|
|
* Yes, adjust the inode size. old count + (parent + new)
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2016-07-20 08:48:31 +07:00
|
|
|
incr_isize += (sfp->count + 2) * XFS_INO64_DIFF;
|
2005-04-17 05:20:36 +07:00
|
|
|
objchange = 1;
|
|
|
|
}
|
2014-07-30 06:12:05 +07:00
|
|
|
|
2014-04-14 16:07:23 +07:00
|
|
|
new_isize = (int)dp->i_d.di_size + incr_isize;
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Won't fit as shortform any more (due to size),
|
|
|
|
* or the pick routine says it won't (due to offset values).
|
|
|
|
*/
|
|
|
|
if (new_isize > XFS_IFORK_DSIZE(dp) ||
|
|
|
|
(pick =
|
|
|
|
xfs_dir2_sf_addname_pick(args, objchange, &sfep, &offset)) == 0) {
|
|
|
|
/*
|
|
|
|
* Just checking or no space reservation, it doesn't fit.
|
|
|
|
*/
|
2008-05-21 13:42:05 +07:00
|
|
|
if ((args->op_flags & XFS_DA_OP_JUSTCHECK) || args->total == 0)
|
2014-06-25 11:58:08 +07:00
|
|
|
return -ENOSPC;
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Convert to block form then add the name.
|
|
|
|
*/
|
|
|
|
error = xfs_dir2_sf_to_block(args);
|
|
|
|
if (error)
|
|
|
|
return error;
|
|
|
|
return xfs_dir2_block_addname(args);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Just checking, it fits.
|
|
|
|
*/
|
2008-05-21 13:42:05 +07:00
|
|
|
if (args->op_flags & XFS_DA_OP_JUSTCHECK)
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
/*
|
|
|
|
* Do it the easy way - just add it at the end.
|
|
|
|
*/
|
|
|
|
if (pick == 1)
|
|
|
|
xfs_dir2_sf_addname_easy(args, sfep, offset, new_isize);
|
|
|
|
/*
|
|
|
|
* Do it the hard way - look for a place to insert the new entry.
|
|
|
|
* Convert to 8 byte inode numbers first if necessary.
|
|
|
|
*/
|
|
|
|
else {
|
|
|
|
ASSERT(pick == 2);
|
|
|
|
if (objchange)
|
|
|
|
xfs_dir2_sf_toino8(args);
|
|
|
|
xfs_dir2_sf_addname_hard(args, objchange, new_isize);
|
|
|
|
}
|
|
|
|
xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_DDATA);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add the new entry the "easy" way.
|
|
|
|
* This is copying the old directory and adding the new entry at the end.
|
|
|
|
* Since it's sorted by "offset" we need room after the last offset
|
|
|
|
* that's already there, and then room to convert to a block directory.
|
|
|
|
* This is already checked by the pick routine.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xfs_dir2_sf_addname_easy(
|
|
|
|
xfs_da_args_t *args, /* operation arguments */
|
|
|
|
xfs_dir2_sf_entry_t *sfep, /* pointer to new entry */
|
|
|
|
xfs_dir2_data_aoff_t offset, /* offset to use for new ent */
|
|
|
|
int new_isize) /* new directory size */
|
|
|
|
{
|
2019-11-09 06:02:38 +07:00
|
|
|
struct xfs_inode *dp = args->dp;
|
|
|
|
struct xfs_mount *mp = dp->i_mount;
|
2005-04-17 05:20:36 +07:00
|
|
|
int byteoff; /* byte offset in sf dir */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* shortform structure */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
byteoff = (int)((char *)sfep - (char *)sfp);
|
|
|
|
/*
|
|
|
|
* Grow the in-inode space.
|
|
|
|
*/
|
2019-11-09 06:02:38 +07:00
|
|
|
xfs_idata_realloc(dp, xfs_dir2_sf_entsize(mp, sfp, args->namelen),
|
2013-08-12 17:50:09 +07:00
|
|
|
XFS_DATA_FORK);
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Need to set up again due to realloc of the inode data.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
sfep = (xfs_dir2_sf_entry_t *)((char *)sfp + byteoff);
|
|
|
|
/*
|
|
|
|
* Fill in the new entry.
|
|
|
|
*/
|
|
|
|
sfep->namelen = args->namelen;
|
2007-06-28 13:43:50 +07:00
|
|
|
xfs_dir2_sf_put_offset(sfep, offset);
|
2005-04-17 05:20:36 +07:00
|
|
|
memcpy(sfep->name, args->name, sfep->namelen);
|
2019-11-09 06:02:59 +07:00
|
|
|
xfs_dir2_sf_put_ino(mp, sfp, sfep, args->inumber);
|
2019-11-09 06:03:30 +07:00
|
|
|
xfs_dir2_sf_put_ftype(mp, sfep, args->filetype);
|
2013-08-12 17:50:10 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Update the header and inode.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->count++;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (args->inumber > XFS_DIR2_MAX_SHORT_INUM)
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->i8count++;
|
2005-04-17 05:20:36 +07:00
|
|
|
dp->i_d.di_size = new_isize;
|
|
|
|
xfs_dir2_sf_check(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add the new entry the "hard" way.
|
|
|
|
* The caller has already converted to 8 byte inode numbers if necessary,
|
|
|
|
* in which case we need to leave the i8count at 1.
|
|
|
|
* Find a hole that the new entry will fit into, and copy
|
|
|
|
* the first part of the entries, the new entry, and the last part of
|
|
|
|
* the entries.
|
|
|
|
*/
|
|
|
|
/* ARGSUSED */
|
|
|
|
static void
|
|
|
|
xfs_dir2_sf_addname_hard(
|
|
|
|
xfs_da_args_t *args, /* operation arguments */
|
|
|
|
int objchange, /* changing inode number size */
|
|
|
|
int new_isize) /* new directory size */
|
|
|
|
{
|
2019-11-09 06:02:38 +07:00
|
|
|
struct xfs_inode *dp = args->dp;
|
|
|
|
struct xfs_mount *mp = dp->i_mount;
|
2005-04-17 05:20:36 +07:00
|
|
|
int add_datasize; /* data size need for new ent */
|
|
|
|
char *buf; /* buffer for old */
|
|
|
|
int eof; /* reached end of old dir */
|
|
|
|
int nbytes; /* temp for byte copies */
|
|
|
|
xfs_dir2_data_aoff_t new_offset; /* next offset value */
|
|
|
|
xfs_dir2_data_aoff_t offset; /* current offset value */
|
|
|
|
int old_isize; /* previous di_size */
|
|
|
|
xfs_dir2_sf_entry_t *oldsfep; /* entry in original dir */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *oldsfp; /* original shortform dir */
|
2005-04-17 05:20:36 +07:00
|
|
|
xfs_dir2_sf_entry_t *sfep; /* entry in new dir */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* new shortform dir */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Copy the old directory to the stack buffer.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
old_isize = (int)dp->i_d.di_size;
|
2019-08-27 02:06:22 +07:00
|
|
|
buf = kmem_alloc(old_isize, 0);
|
2011-07-08 19:35:13 +07:00
|
|
|
oldsfp = (xfs_dir2_sf_hdr_t *)buf;
|
2005-04-17 05:20:36 +07:00
|
|
|
memcpy(oldsfp, sfp, old_isize);
|
|
|
|
/*
|
|
|
|
* Loop over the old directory finding the place we're going
|
|
|
|
* to insert the new entry.
|
|
|
|
* If it's going to end up at the end then oldsfep will point there.
|
|
|
|
*/
|
2019-11-09 06:05:38 +07:00
|
|
|
for (offset = args->geo->data_first_offset,
|
2007-06-28 13:43:50 +07:00
|
|
|
oldsfep = xfs_dir2_sf_firstentry(oldsfp),
|
2019-11-09 06:05:37 +07:00
|
|
|
add_datasize = xfs_dir2_data_entsize(mp, args->namelen),
|
2005-04-17 05:20:36 +07:00
|
|
|
eof = (char *)oldsfep == &buf[old_isize];
|
|
|
|
!eof;
|
2019-11-09 06:05:37 +07:00
|
|
|
offset = new_offset + xfs_dir2_data_entsize(mp, oldsfep->namelen),
|
2019-11-09 06:02:38 +07:00
|
|
|
oldsfep = xfs_dir2_sf_nextentry(mp, oldsfp, oldsfep),
|
2005-04-17 05:20:36 +07:00
|
|
|
eof = (char *)oldsfep == &buf[old_isize]) {
|
2007-06-28 13:43:50 +07:00
|
|
|
new_offset = xfs_dir2_sf_get_offset(oldsfep);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (offset + add_datasize <= new_offset)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Get rid of the old directory, then allocate space for
|
|
|
|
* the new one. We do this so xfs_idata_realloc won't copy
|
|
|
|
* the data.
|
|
|
|
*/
|
|
|
|
xfs_idata_realloc(dp, -old_isize, XFS_DATA_FORK);
|
|
|
|
xfs_idata_realloc(dp, new_isize, XFS_DATA_FORK);
|
|
|
|
/*
|
|
|
|
* Reset the pointer since the buffer was reallocated.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Copy the first part of the directory, including the header.
|
|
|
|
*/
|
|
|
|
nbytes = (int)((char *)oldsfep - (char *)oldsfp);
|
|
|
|
memcpy(sfp, oldsfp, nbytes);
|
|
|
|
sfep = (xfs_dir2_sf_entry_t *)((char *)sfp + nbytes);
|
|
|
|
/*
|
|
|
|
* Fill in the new entry, and update the header counts.
|
|
|
|
*/
|
|
|
|
sfep->namelen = args->namelen;
|
2007-06-28 13:43:50 +07:00
|
|
|
xfs_dir2_sf_put_offset(sfep, offset);
|
2005-04-17 05:20:36 +07:00
|
|
|
memcpy(sfep->name, args->name, sfep->namelen);
|
2019-11-09 06:02:59 +07:00
|
|
|
xfs_dir2_sf_put_ino(mp, sfp, sfep, args->inumber);
|
2019-11-09 06:03:30 +07:00
|
|
|
xfs_dir2_sf_put_ftype(mp, sfep, args->filetype);
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->count++;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (args->inumber > XFS_DIR2_MAX_SHORT_INUM && !objchange)
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->i8count++;
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* If there's more left to copy, do that.
|
|
|
|
*/
|
|
|
|
if (!eof) {
|
2019-11-09 06:02:38 +07:00
|
|
|
sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);
|
2005-04-17 05:20:36 +07:00
|
|
|
memcpy(sfep, oldsfep, old_isize - nbytes);
|
|
|
|
}
|
2008-05-19 13:31:57 +07:00
|
|
|
kmem_free(buf);
|
2005-04-17 05:20:36 +07:00
|
|
|
dp->i_d.di_size = new_isize;
|
|
|
|
xfs_dir2_sf_check(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Decide if the new entry will fit at all.
|
|
|
|
* If it will fit, pick between adding the new entry to the end (easy)
|
|
|
|
* or somewhere else (hard).
|
|
|
|
* Return 0 (won't fit), 1 (easy), 2 (hard).
|
|
|
|
*/
|
|
|
|
/*ARGSUSED*/
|
|
|
|
static int /* pick result */
|
|
|
|
xfs_dir2_sf_addname_pick(
|
|
|
|
xfs_da_args_t *args, /* operation arguments */
|
|
|
|
int objchange, /* inode # size changes */
|
|
|
|
xfs_dir2_sf_entry_t **sfepp, /* out(1): new entry ptr */
|
|
|
|
xfs_dir2_data_aoff_t *offsetp) /* out(1): new offset */
|
|
|
|
{
|
2019-11-09 06:02:38 +07:00
|
|
|
struct xfs_inode *dp = args->dp;
|
|
|
|
struct xfs_mount *mp = dp->i_mount;
|
2005-04-17 05:20:36 +07:00
|
|
|
int holefit; /* found hole it will fit in */
|
|
|
|
int i; /* entry number */
|
|
|
|
xfs_dir2_data_aoff_t offset; /* data block offset */
|
|
|
|
xfs_dir2_sf_entry_t *sfep; /* shortform entry */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* shortform structure */
|
2005-04-17 05:20:36 +07:00
|
|
|
int size; /* entry's data size */
|
|
|
|
int used; /* data bytes used */
|
|
|
|
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
2019-11-09 06:05:37 +07:00
|
|
|
size = xfs_dir2_data_entsize(mp, args->namelen);
|
2019-11-09 06:05:38 +07:00
|
|
|
offset = args->geo->data_first_offset;
|
2007-06-28 13:43:50 +07:00
|
|
|
sfep = xfs_dir2_sf_firstentry(sfp);
|
2005-04-17 05:20:36 +07:00
|
|
|
holefit = 0;
|
|
|
|
/*
|
|
|
|
* Loop over sf entries.
|
|
|
|
* Keep track of data offset and whether we've seen a place
|
|
|
|
* to insert the new entry.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
for (i = 0; i < sfp->count; i++) {
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!holefit)
|
2007-06-28 13:43:50 +07:00
|
|
|
holefit = offset + size <= xfs_dir2_sf_get_offset(sfep);
|
|
|
|
offset = xfs_dir2_sf_get_offset(sfep) +
|
2019-11-09 06:05:37 +07:00
|
|
|
xfs_dir2_data_entsize(mp, sfep->namelen);
|
2019-11-09 06:02:38 +07:00
|
|
|
sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Calculate data bytes used excluding the new entry, if this
|
|
|
|
* was a data block (block form directory).
|
|
|
|
*/
|
|
|
|
used = offset +
|
2011-07-08 19:35:13 +07:00
|
|
|
(sfp->count + 3) * (uint)sizeof(xfs_dir2_leaf_entry_t) +
|
2005-04-17 05:20:36 +07:00
|
|
|
(uint)sizeof(xfs_dir2_block_tail_t);
|
|
|
|
/*
|
|
|
|
* If it won't fit in a block form then we can't insert it,
|
|
|
|
* we'll go back, convert to block, then try the insert and convert
|
|
|
|
* to leaf.
|
|
|
|
*/
|
2014-06-06 12:15:59 +07:00
|
|
|
if (used + (holefit ? 0 : size) > args->geo->blksize)
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
/*
|
|
|
|
* If changing the inode number size, do it the hard way.
|
|
|
|
*/
|
2014-07-30 06:12:05 +07:00
|
|
|
if (objchange)
|
2005-04-17 05:20:36 +07:00
|
|
|
return 2;
|
|
|
|
/*
|
|
|
|
* If it won't fit at the end then do it the hard way (use the hole).
|
|
|
|
*/
|
2014-06-06 12:15:59 +07:00
|
|
|
if (used + size > args->geo->blksize)
|
2005-04-17 05:20:36 +07:00
|
|
|
return 2;
|
|
|
|
/*
|
|
|
|
* Do it the easy way.
|
|
|
|
*/
|
|
|
|
*sfepp = sfep;
|
|
|
|
*offsetp = offset;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
/*
|
|
|
|
* Check consistency of shortform directory, assert if bad.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xfs_dir2_sf_check(
|
|
|
|
xfs_da_args_t *args) /* operation arguments */
|
|
|
|
{
|
2019-11-09 06:03:30 +07:00
|
|
|
struct xfs_inode *dp = args->dp;
|
|
|
|
struct xfs_mount *mp = dp->i_mount;
|
2005-04-17 05:20:36 +07:00
|
|
|
int i; /* entry number */
|
|
|
|
int i8count; /* number of big inode#s */
|
|
|
|
xfs_ino_t ino; /* entry inode number */
|
|
|
|
int offset; /* data offset */
|
|
|
|
xfs_dir2_sf_entry_t *sfep; /* shortform dir entry */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* shortform structure */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
2019-11-09 06:05:38 +07:00
|
|
|
offset = args->geo->data_first_offset;
|
2019-11-09 06:02:31 +07:00
|
|
|
ino = xfs_dir2_sf_get_parent_ino(sfp);
|
2005-04-17 05:20:36 +07:00
|
|
|
i8count = ino > XFS_DIR2_MAX_SHORT_INUM;
|
|
|
|
|
2007-06-28 13:43:50 +07:00
|
|
|
for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp);
|
2011-07-08 19:35:13 +07:00
|
|
|
i < sfp->count;
|
2019-11-09 06:03:30 +07:00
|
|
|
i++, sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep)) {
|
2007-06-28 13:43:50 +07:00
|
|
|
ASSERT(xfs_dir2_sf_get_offset(sfep) >= offset);
|
2019-11-09 06:03:30 +07:00
|
|
|
ino = xfs_dir2_sf_get_ino(mp, sfp, sfep);
|
2005-04-17 05:20:36 +07:00
|
|
|
i8count += ino > XFS_DIR2_MAX_SHORT_INUM;
|
|
|
|
offset =
|
2007-06-28 13:43:50 +07:00
|
|
|
xfs_dir2_sf_get_offset(sfep) +
|
2019-11-09 06:05:37 +07:00
|
|
|
xfs_dir2_data_entsize(mp, sfep->namelen);
|
2019-11-09 06:03:30 +07:00
|
|
|
ASSERT(xfs_dir2_sf_get_ftype(mp, sfep) < XFS_DIR3_FT_MAX);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2011-07-08 19:35:13 +07:00
|
|
|
ASSERT(i8count == sfp->i8count);
|
2005-04-17 05:20:36 +07:00
|
|
|
ASSERT((char *)sfep - (char *)sfp == dp->i_d.di_size);
|
|
|
|
ASSERT(offset +
|
2011-07-08 19:35:13 +07:00
|
|
|
(sfp->count + 2) * (uint)sizeof(xfs_dir2_leaf_entry_t) +
|
2014-06-06 12:15:59 +07:00
|
|
|
(uint)sizeof(xfs_dir2_block_tail_t) <= args->geo->blksize);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
#endif /* DEBUG */
|
|
|
|
|
2017-03-15 14:24:25 +07:00
|
|
|
/* Verify the consistency of an inline directory. */
|
2018-01-09 01:51:06 +07:00
|
|
|
xfs_failaddr_t
|
2017-03-15 14:24:25 +07:00
|
|
|
xfs_dir2_sf_verify(
|
2017-04-04 02:22:20 +07:00
|
|
|
struct xfs_inode *ip)
|
2017-03-15 14:24:25 +07:00
|
|
|
{
|
2017-04-04 02:22:20 +07:00
|
|
|
struct xfs_mount *mp = ip->i_mount;
|
|
|
|
struct xfs_dir2_sf_hdr *sfp;
|
2017-03-15 14:24:25 +07:00
|
|
|
struct xfs_dir2_sf_entry *sfep;
|
|
|
|
struct xfs_dir2_sf_entry *next_sfep;
|
|
|
|
char *endp;
|
2017-04-04 02:22:20 +07:00
|
|
|
struct xfs_ifork *ifp;
|
2017-03-15 14:24:25 +07:00
|
|
|
xfs_ino_t ino;
|
|
|
|
int i;
|
|
|
|
int i8count;
|
|
|
|
int offset;
|
xfs: fix inode fork extent count overflow
[commit message is verbose for discussion purposes - will trim it
down later. Some questions about implementation details at the end.]
Zorro Lang recently ran a new test to stress single inode extent
counts now that they are no longer limited by memory allocation.
The test was simply:
# xfs_io -f -c "falloc 0 40t" /mnt/scratch/big-file
# ~/src/xfstests-dev/punch-alternating /mnt/scratch/big-file
This test uncovered a problem where the hole punching operation
appeared to finish with no error, but apparently only created 268M
extents instead of the 10 billion it was supposed to.
Further, trying to punch out extents that should have been present
resulted in success, but no change in the extent count. It looked
like a silent failure.
While running the test and observing the behaviour in real time,
I observed the extent coutn growing at ~2M extents/minute, and saw
this after about an hour:
# xfs_io -f -c "stat" /mnt/scratch/big-file |grep next ; \
> sleep 60 ; \
> xfs_io -f -c "stat" /mnt/scratch/big-file |grep next
fsxattr.nextents = 127657993
fsxattr.nextents = 129683339
#
And a few minutes later this:
# xfs_io -f -c "stat" /mnt/scratch/big-file |grep next
fsxattr.nextents = 4177861124
#
Ah, what? Where did that 4 billion extra extents suddenly come from?
Stop the workload, unmount, mount:
# xfs_io -f -c "stat" /mnt/scratch/big-file |grep next
fsxattr.nextents = 166044375
#
And it's back at the expected number. i.e. the extent count is
correct on disk, but it's screwed up in memory. I loaded up the
extent list, and immediately:
# xfs_io -f -c "stat" /mnt/scratch/big-file |grep next
fsxattr.nextents = 4192576215
#
It's bad again. So, where does that number come from?
xfs_fill_fsxattr():
if (ip->i_df.if_flags & XFS_IFEXTENTS)
fa->fsx_nextents = xfs_iext_count(&ip->i_df);
else
fa->fsx_nextents = ip->i_d.di_nextents;
And that's the behaviour I just saw in a nutshell. The on disk count
is correct, but once the tree is loaded into memory, it goes whacky.
Clearly there's something wrong with xfs_iext_count():
inline xfs_extnum_t xfs_iext_count(struct xfs_ifork *ifp)
{
return ifp->if_bytes / sizeof(struct xfs_iext_rec);
}
Simple enough, but 134M extents is 2**27, and that's right about
where things went wrong. A struct xfs_iext_rec is 16 bytes in size,
which means 2**27 * 2**4 = 2**31 and we're right on target for an
integer overflow. And, sure enough:
struct xfs_ifork {
int if_bytes; /* bytes in if_u1 */
....
Once we get 2**27 extents in a file, we overflow if_bytes and the
in-core extent count goes wrong. And when we reach 2**28 extents,
if_bytes wraps back to zero and things really start to go wrong
there. This is where the silent failure comes from - only the first
2**28 extents can be looked up directly due to the overflow, all the
extents above this index wrap back to somewhere in the first 2**28
extents. Hence with a regular pattern, trying to punch a hole in the
range that didn't have holes mapped to a hole in the first 2**28
extents and so "succeeded" without changing anything. Hence "silent
failure"...
Fix this by converting if_bytes to a int64_t and converting all the
index variables and size calculations to use int64_t types to avoid
overflows in future. Signed integers are still used to enable easy
detection of extent count underflows. This enables scalability of
extent counts to the limits of the on-disk format - MAXEXTNUM
(2**31) extents.
Current testing is at over 500M extents and still going:
fsxattr.nextents = 517310478
Reported-by: Zorro Lang <zlang@redhat.com>
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
2019-10-18 03:40:33 +07:00
|
|
|
int64_t size;
|
2017-04-04 02:22:20 +07:00
|
|
|
int error;
|
2017-06-17 01:00:05 +07:00
|
|
|
uint8_t filetype;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
2017-04-04 02:22:20 +07:00
|
|
|
ASSERT(ip->i_d.di_format == XFS_DINODE_FMT_LOCAL);
|
2017-03-15 14:24:25 +07:00
|
|
|
|
2017-04-04 02:22:20 +07:00
|
|
|
ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK);
|
|
|
|
sfp = (struct xfs_dir2_sf_hdr *)ifp->if_u1.if_data;
|
|
|
|
size = ifp->if_bytes;
|
|
|
|
|
2017-03-15 14:24:25 +07:00
|
|
|
/*
|
|
|
|
* Give up if the directory is way too short.
|
|
|
|
*/
|
2017-04-04 02:22:20 +07:00
|
|
|
if (size <= offsetof(struct xfs_dir2_sf_hdr, parent) ||
|
|
|
|
size < xfs_dir2_sf_hdr_size(sfp->i8count))
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
|
|
|
endp = (char *)sfp + size;
|
|
|
|
|
|
|
|
/* Check .. entry */
|
2019-11-09 06:02:31 +07:00
|
|
|
ino = xfs_dir2_sf_get_parent_ino(sfp);
|
2017-03-15 14:24:25 +07:00
|
|
|
i8count = ino > XFS_DIR2_MAX_SHORT_INUM;
|
2017-04-04 02:22:20 +07:00
|
|
|
error = xfs_dir_ino_validate(mp, ino);
|
|
|
|
if (error)
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2019-11-09 06:05:38 +07:00
|
|
|
offset = mp->m_dir_geo->data_first_offset;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
|
|
|
/* Check all reported entries */
|
|
|
|
sfep = xfs_dir2_sf_firstentry(sfp);
|
|
|
|
for (i = 0; i < sfp->count; i++) {
|
|
|
|
/*
|
|
|
|
* struct xfs_dir2_sf_entry has a variable length.
|
|
|
|
* Check the fixed-offset parts of the structure are
|
|
|
|
* within the data buffer.
|
|
|
|
*/
|
2017-04-04 02:22:20 +07:00
|
|
|
if (((char *)sfep + sizeof(*sfep)) >= endp)
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
|
|
|
/* Don't allow names with known bad length. */
|
2017-04-04 02:22:20 +07:00
|
|
|
if (sfep->namelen == 0)
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check that the variable-length part of the structure is
|
|
|
|
* within the data buffer. The next entry starts after the
|
|
|
|
* name component, so nextentry is an acceptable test.
|
|
|
|
*/
|
2019-11-09 06:02:38 +07:00
|
|
|
next_sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);
|
2017-04-04 02:22:20 +07:00
|
|
|
if (endp < (char *)next_sfep)
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
|
|
|
/* Check that the offsets always increase. */
|
2017-04-04 02:22:20 +07:00
|
|
|
if (xfs_dir2_sf_get_offset(sfep) < offset)
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
|
|
|
/* Check the inode number. */
|
2019-11-09 06:02:59 +07:00
|
|
|
ino = xfs_dir2_sf_get_ino(mp, sfp, sfep);
|
2017-03-15 14:24:25 +07:00
|
|
|
i8count += ino > XFS_DIR2_MAX_SHORT_INUM;
|
2017-04-04 02:22:20 +07:00
|
|
|
error = xfs_dir_ino_validate(mp, ino);
|
|
|
|
if (error)
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
|
|
|
/* Check the file type. */
|
2019-11-09 06:03:30 +07:00
|
|
|
filetype = xfs_dir2_sf_get_ftype(mp, sfep);
|
2017-04-04 02:22:20 +07:00
|
|
|
if (filetype >= XFS_DIR3_FT_MAX)
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
|
|
|
offset = xfs_dir2_sf_get_offset(sfep) +
|
2019-11-09 06:05:37 +07:00
|
|
|
xfs_dir2_data_entsize(mp, sfep->namelen);
|
2017-03-15 14:24:25 +07:00
|
|
|
|
|
|
|
sfep = next_sfep;
|
|
|
|
}
|
2017-04-04 02:22:20 +07:00
|
|
|
if (i8count != sfp->i8count)
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2017-04-04 02:22:20 +07:00
|
|
|
if ((void *)sfep != (void *)endp)
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
|
|
|
/* Make sure this whole thing ought to be in local format. */
|
2017-04-04 02:22:20 +07:00
|
|
|
if (offset + (sfp->count + 2) * (uint)sizeof(xfs_dir2_leaf_entry_t) +
|
|
|
|
(uint)sizeof(xfs_dir2_block_tail_t) > mp->m_dir_geo->blksize)
|
2018-01-09 01:51:06 +07:00
|
|
|
return __this_address;
|
2017-03-15 14:24:25 +07:00
|
|
|
|
2018-01-09 01:51:06 +07:00
|
|
|
return NULL;
|
2017-03-15 14:24:25 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Create a new (shortform) directory.
|
|
|
|
*/
|
|
|
|
int /* error, always 0 */
|
|
|
|
xfs_dir2_sf_create(
|
|
|
|
xfs_da_args_t *args, /* operation arguments */
|
|
|
|
xfs_ino_t pino) /* parent inode number */
|
|
|
|
{
|
|
|
|
xfs_inode_t *dp; /* incore directory inode */
|
|
|
|
int i8count; /* parent inode is an 8-byte number */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* shortform structure */
|
2005-04-17 05:20:36 +07:00
|
|
|
int size; /* directory size */
|
|
|
|
|
2009-12-15 06:14:59 +07:00
|
|
|
trace_xfs_dir2_sf_create(args);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
dp = args->dp;
|
|
|
|
|
|
|
|
ASSERT(dp != NULL);
|
|
|
|
ASSERT(dp->i_d.di_size == 0);
|
|
|
|
/*
|
|
|
|
* If it's currently a zero-length extent file,
|
|
|
|
* convert it to local format.
|
|
|
|
*/
|
|
|
|
if (dp->i_d.di_format == XFS_DINODE_FMT_EXTENTS) {
|
|
|
|
dp->i_df.if_flags &= ~XFS_IFEXTENTS; /* just in case */
|
|
|
|
dp->i_d.di_format = XFS_DINODE_FMT_LOCAL;
|
|
|
|
xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE);
|
|
|
|
dp->i_df.if_flags |= XFS_IFINLINE;
|
|
|
|
}
|
|
|
|
ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
|
|
|
|
ASSERT(dp->i_df.if_bytes == 0);
|
|
|
|
i8count = pino > XFS_DIR2_MAX_SHORT_INUM;
|
2007-06-28 13:43:50 +07:00
|
|
|
size = xfs_dir2_sf_hdr_size(i8count);
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Make a buffer for the data.
|
|
|
|
*/
|
|
|
|
xfs_idata_realloc(dp, size, XFS_DATA_FORK);
|
|
|
|
/*
|
|
|
|
* Fill in the header,
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
|
|
|
sfp->i8count = i8count;
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Now can put in the inode number, since i8count is set.
|
|
|
|
*/
|
2019-11-09 06:02:31 +07:00
|
|
|
xfs_dir2_sf_put_parent_ino(sfp, pino);
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->count = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
dp->i_d.di_size = size;
|
|
|
|
xfs_dir2_sf_check(args);
|
|
|
|
xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_DDATA);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Lookup an entry in a shortform directory.
|
|
|
|
* Returns EEXIST if found, ENOENT if not found.
|
|
|
|
*/
|
|
|
|
int /* error */
|
|
|
|
xfs_dir2_sf_lookup(
|
|
|
|
xfs_da_args_t *args) /* operation arguments */
|
|
|
|
{
|
2019-11-09 06:02:38 +07:00
|
|
|
struct xfs_inode *dp = args->dp;
|
|
|
|
struct xfs_mount *mp = dp->i_mount;
|
2005-04-17 05:20:36 +07:00
|
|
|
int i; /* entry index */
|
2008-05-21 13:58:22 +07:00
|
|
|
int error;
|
2005-04-17 05:20:36 +07:00
|
|
|
xfs_dir2_sf_entry_t *sfep; /* shortform directory entry */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* shortform structure */
|
2008-05-21 13:41:01 +07:00
|
|
|
enum xfs_dacmp cmp; /* comparison result */
|
2008-05-21 13:58:22 +07:00
|
|
|
xfs_dir2_sf_entry_t *ci_sfep; /* case-insens. entry */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-12-15 06:14:59 +07:00
|
|
|
trace_xfs_dir2_sf_lookup(args);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
xfs_dir2_sf_check(args);
|
|
|
|
|
|
|
|
ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
|
2019-11-02 23:38:08 +07:00
|
|
|
ASSERT(dp->i_d.di_size >= offsetof(struct xfs_dir2_sf_hdr, parent));
|
2005-04-17 05:20:36 +07:00
|
|
|
ASSERT(dp->i_df.if_bytes == dp->i_d.di_size);
|
|
|
|
ASSERT(dp->i_df.if_u1.if_data != NULL);
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
|
|
|
ASSERT(dp->i_d.di_size >= xfs_dir2_sf_hdr_size(sfp->i8count));
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Special case for .
|
|
|
|
*/
|
|
|
|
if (args->namelen == 1 && args->name[0] == '.') {
|
|
|
|
args->inumber = dp->i_ino;
|
2008-05-21 13:41:01 +07:00
|
|
|
args->cmpresult = XFS_CMP_EXACT;
|
2013-08-12 17:50:10 +07:00
|
|
|
args->filetype = XFS_DIR3_FT_DIR;
|
2014-06-25 11:58:08 +07:00
|
|
|
return -EEXIST;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Special case for ..
|
|
|
|
*/
|
|
|
|
if (args->namelen == 2 &&
|
|
|
|
args->name[0] == '.' && args->name[1] == '.') {
|
2019-11-09 06:02:31 +07:00
|
|
|
args->inumber = xfs_dir2_sf_get_parent_ino(sfp);
|
2008-05-21 13:41:01 +07:00
|
|
|
args->cmpresult = XFS_CMP_EXACT;
|
2013-08-12 17:50:10 +07:00
|
|
|
args->filetype = XFS_DIR3_FT_DIR;
|
2014-06-25 11:58:08 +07:00
|
|
|
return -EEXIST;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Loop over all the entries trying to match ours.
|
|
|
|
*/
|
2008-05-21 13:58:22 +07:00
|
|
|
ci_sfep = NULL;
|
2011-07-08 19:35:13 +07:00
|
|
|
for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp); i < sfp->count;
|
2019-11-09 06:02:38 +07:00
|
|
|
i++, sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep)) {
|
2008-05-21 13:41:01 +07:00
|
|
|
/*
|
|
|
|
* Compare name and if it's an exact match, return the inode
|
|
|
|
* number. If it's the first case-insensitive match, store the
|
|
|
|
* inode number and continue looking for an exact match.
|
|
|
|
*/
|
2019-11-12 03:59:26 +07:00
|
|
|
cmp = xfs_dir2_compname(args, sfep->name, sfep->namelen);
|
2008-05-21 13:41:01 +07:00
|
|
|
if (cmp != XFS_CMP_DIFFERENT && cmp != args->cmpresult) {
|
|
|
|
args->cmpresult = cmp;
|
2019-11-09 06:02:59 +07:00
|
|
|
args->inumber = xfs_dir2_sf_get_ino(mp, sfp, sfep);
|
2019-11-09 06:03:30 +07:00
|
|
|
args->filetype = xfs_dir2_sf_get_ftype(mp, sfep);
|
2008-05-21 13:41:01 +07:00
|
|
|
if (cmp == XFS_CMP_EXACT)
|
2014-06-25 11:58:08 +07:00
|
|
|
return -EEXIST;
|
2008-05-21 13:58:22 +07:00
|
|
|
ci_sfep = sfep;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
2008-05-21 13:42:05 +07:00
|
|
|
ASSERT(args->op_flags & XFS_DA_OP_OKNOENT);
|
2008-05-21 13:41:01 +07:00
|
|
|
/*
|
|
|
|
* Here, we can only be doing a lookup (not a rename or replace).
|
2014-06-25 11:58:08 +07:00
|
|
|
* If a case-insensitive match was not found, return -ENOENT.
|
2008-05-21 13:41:01 +07:00
|
|
|
*/
|
2008-05-21 13:58:22 +07:00
|
|
|
if (!ci_sfep)
|
2014-06-25 11:58:08 +07:00
|
|
|
return -ENOENT;
|
2008-05-21 13:58:22 +07:00
|
|
|
/* otherwise process the CI match as required by the caller */
|
|
|
|
error = xfs_dir_cilookup_result(args, ci_sfep->name, ci_sfep->namelen);
|
2014-06-22 12:04:54 +07:00
|
|
|
return error;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Remove an entry from a shortform directory.
|
|
|
|
*/
|
|
|
|
int /* error */
|
|
|
|
xfs_dir2_sf_removename(
|
|
|
|
xfs_da_args_t *args)
|
|
|
|
{
|
2019-11-09 06:02:38 +07:00
|
|
|
struct xfs_inode *dp = args->dp;
|
|
|
|
struct xfs_mount *mp = dp->i_mount;
|
2005-04-17 05:20:36 +07:00
|
|
|
int byteoff; /* offset of removed entry */
|
|
|
|
int entsize; /* this entry's size */
|
|
|
|
int i; /* shortform entry index */
|
|
|
|
int newsize; /* new inode size */
|
|
|
|
int oldsize; /* old inode size */
|
|
|
|
xfs_dir2_sf_entry_t *sfep; /* shortform directory entry */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* shortform structure */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-12-15 06:14:59 +07:00
|
|
|
trace_xfs_dir2_sf_removename(args);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
|
|
|
|
oldsize = (int)dp->i_d.di_size;
|
2019-11-02 23:38:08 +07:00
|
|
|
ASSERT(oldsize >= offsetof(struct xfs_dir2_sf_hdr, parent));
|
2005-04-17 05:20:36 +07:00
|
|
|
ASSERT(dp->i_df.if_bytes == oldsize);
|
|
|
|
ASSERT(dp->i_df.if_u1.if_data != NULL);
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
|
|
|
ASSERT(oldsize >= xfs_dir2_sf_hdr_size(sfp->i8count));
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Loop over the old directory entries.
|
|
|
|
* Find the one we're deleting.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp); i < sfp->count;
|
2019-11-09 06:02:38 +07:00
|
|
|
i++, sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep)) {
|
2008-05-21 13:41:01 +07:00
|
|
|
if (xfs_da_compname(args, sfep->name, sfep->namelen) ==
|
|
|
|
XFS_CMP_EXACT) {
|
2019-11-09 06:02:59 +07:00
|
|
|
ASSERT(xfs_dir2_sf_get_ino(mp, sfp, sfep) ==
|
2011-07-08 19:35:03 +07:00
|
|
|
args->inumber);
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Didn't find it.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
if (i == sfp->count)
|
2014-06-25 11:58:08 +07:00
|
|
|
return -ENOENT;
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Calculate sizes.
|
|
|
|
*/
|
|
|
|
byteoff = (int)((char *)sfep - (char *)sfp);
|
2019-11-09 06:02:38 +07:00
|
|
|
entsize = xfs_dir2_sf_entsize(mp, sfp, args->namelen);
|
2005-04-17 05:20:36 +07:00
|
|
|
newsize = oldsize - entsize;
|
|
|
|
/*
|
|
|
|
* Copy the part if any after the removed entry, sliding it down.
|
|
|
|
*/
|
|
|
|
if (byteoff + entsize < oldsize)
|
|
|
|
memmove((char *)sfp + byteoff, (char *)sfp + byteoff + entsize,
|
|
|
|
oldsize - (byteoff + entsize));
|
|
|
|
/*
|
|
|
|
* Fix up the header and file size.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->count--;
|
2005-04-17 05:20:36 +07:00
|
|
|
dp->i_d.di_size = newsize;
|
|
|
|
/*
|
|
|
|
* Reallocate, making it smaller.
|
|
|
|
*/
|
|
|
|
xfs_idata_realloc(dp, newsize - oldsize, XFS_DATA_FORK);
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Are we changing inode number size?
|
|
|
|
*/
|
|
|
|
if (args->inumber > XFS_DIR2_MAX_SHORT_INUM) {
|
2011-07-08 19:35:13 +07:00
|
|
|
if (sfp->i8count == 1)
|
2005-04-17 05:20:36 +07:00
|
|
|
xfs_dir2_sf_toino4(args);
|
|
|
|
else
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->i8count--;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
xfs_dir2_sf_check(args);
|
|
|
|
xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_DDATA);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-11-12 23:34:23 +07:00
|
|
|
/*
|
|
|
|
* Check whether the sf dir replace operation need more blocks.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
xfs_dir2_sf_replace_needblock(
|
|
|
|
struct xfs_inode *dp,
|
|
|
|
xfs_ino_t inum)
|
|
|
|
{
|
|
|
|
int newsize;
|
|
|
|
struct xfs_dir2_sf_hdr *sfp;
|
|
|
|
|
|
|
|
if (dp->i_d.di_format != XFS_DINODE_FMT_LOCAL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
|
|
|
|
newsize = dp->i_df.if_bytes + (sfp->count + 1) * XFS_INO64_DIFF;
|
|
|
|
|
|
|
|
return inum > XFS_DIR2_MAX_SHORT_INUM &&
|
|
|
|
sfp->i8count == 0 && newsize > XFS_IFORK_DSIZE(dp);
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Replace the inode number of an entry in a shortform directory.
|
|
|
|
*/
|
|
|
|
int /* error */
|
|
|
|
xfs_dir2_sf_replace(
|
|
|
|
xfs_da_args_t *args) /* operation arguments */
|
|
|
|
{
|
2019-11-09 06:02:38 +07:00
|
|
|
struct xfs_inode *dp = args->dp;
|
|
|
|
struct xfs_mount *mp = dp->i_mount;
|
2005-04-17 05:20:36 +07:00
|
|
|
int i; /* entry index */
|
|
|
|
xfs_ino_t ino=0; /* entry old inode number */
|
|
|
|
int i8elevated; /* sf_toino8 set i8count=1 */
|
|
|
|
xfs_dir2_sf_entry_t *sfep; /* shortform directory entry */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* shortform structure */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-12-15 06:14:59 +07:00
|
|
|
trace_xfs_dir2_sf_replace(args);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
|
2019-11-02 23:38:08 +07:00
|
|
|
ASSERT(dp->i_d.di_size >= offsetof(struct xfs_dir2_sf_hdr, parent));
|
2005-04-17 05:20:36 +07:00
|
|
|
ASSERT(dp->i_df.if_bytes == dp->i_d.di_size);
|
|
|
|
ASSERT(dp->i_df.if_u1.if_data != NULL);
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
|
|
|
ASSERT(dp->i_d.di_size >= xfs_dir2_sf_hdr_size(sfp->i8count));
|
2014-07-30 06:12:05 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* New inode number is large, and need to convert to 8-byte inodes.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
if (args->inumber > XFS_DIR2_MAX_SHORT_INUM && sfp->i8count == 0) {
|
2005-04-17 05:20:36 +07:00
|
|
|
int error; /* error return value */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Won't fit as shortform, convert to block then do replace.
|
|
|
|
*/
|
2019-11-12 23:34:23 +07:00
|
|
|
if (xfs_dir2_sf_replace_needblock(dp, args->inumber)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
error = xfs_dir2_sf_to_block(args);
|
2019-11-12 23:34:23 +07:00
|
|
|
if (error)
|
2005-04-17 05:20:36 +07:00
|
|
|
return error;
|
|
|
|
return xfs_dir2_block_replace(args);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Still fits, convert to 8-byte now.
|
|
|
|
*/
|
|
|
|
xfs_dir2_sf_toino8(args);
|
|
|
|
i8elevated = 1;
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
} else
|
|
|
|
i8elevated = 0;
|
2014-07-30 06:12:05 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
ASSERT(args->namelen != 1 || args->name[0] != '.');
|
|
|
|
/*
|
|
|
|
* Replace ..'s entry.
|
|
|
|
*/
|
|
|
|
if (args->namelen == 2 &&
|
|
|
|
args->name[0] == '.' && args->name[1] == '.') {
|
2019-11-09 06:02:31 +07:00
|
|
|
ino = xfs_dir2_sf_get_parent_ino(sfp);
|
2005-04-17 05:20:36 +07:00
|
|
|
ASSERT(args->inumber != ino);
|
2019-11-09 06:02:31 +07:00
|
|
|
xfs_dir2_sf_put_parent_ino(sfp, args->inumber);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Normal entry, look for the name.
|
|
|
|
*/
|
|
|
|
else {
|
2013-08-12 17:50:09 +07:00
|
|
|
for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp); i < sfp->count;
|
2019-11-09 06:02:38 +07:00
|
|
|
i++, sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep)) {
|
2008-05-21 13:41:01 +07:00
|
|
|
if (xfs_da_compname(args, sfep->name, sfep->namelen) ==
|
|
|
|
XFS_CMP_EXACT) {
|
2019-11-09 06:02:59 +07:00
|
|
|
ino = xfs_dir2_sf_get_ino(mp, sfp, sfep);
|
2005-04-17 05:20:36 +07:00
|
|
|
ASSERT(args->inumber != ino);
|
2019-11-09 06:02:59 +07:00
|
|
|
xfs_dir2_sf_put_ino(mp, sfp, sfep,
|
|
|
|
args->inumber);
|
2019-11-09 06:03:30 +07:00
|
|
|
xfs_dir2_sf_put_ftype(mp, sfep, args->filetype);
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Didn't find it.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
if (i == sfp->count) {
|
2008-05-21 13:42:05 +07:00
|
|
|
ASSERT(args->op_flags & XFS_DA_OP_OKNOENT);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (i8elevated)
|
|
|
|
xfs_dir2_sf_toino4(args);
|
2014-06-25 11:58:08 +07:00
|
|
|
return -ENOENT;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* See if the old number was large, the new number is small.
|
|
|
|
*/
|
|
|
|
if (ino > XFS_DIR2_MAX_SHORT_INUM &&
|
|
|
|
args->inumber <= XFS_DIR2_MAX_SHORT_INUM) {
|
|
|
|
/*
|
|
|
|
* And the old count was one, so need to convert to small.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
if (sfp->i8count == 1)
|
2005-04-17 05:20:36 +07:00
|
|
|
xfs_dir2_sf_toino4(args);
|
|
|
|
else
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->i8count--;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* See if the old number was small, the new number is large.
|
|
|
|
*/
|
|
|
|
if (ino <= XFS_DIR2_MAX_SHORT_INUM &&
|
|
|
|
args->inumber > XFS_DIR2_MAX_SHORT_INUM) {
|
|
|
|
/*
|
|
|
|
* add to the i8count unless we just converted to 8-byte
|
|
|
|
* inodes (which does an implied i8count = 1)
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
ASSERT(sfp->i8count != 0);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!i8elevated)
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->i8count++;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
xfs_dir2_sf_check(args);
|
|
|
|
xfs_trans_log_inode(args->trans, dp, XFS_ILOG_DDATA);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert from 8-byte inode numbers to 4-byte inode numbers.
|
|
|
|
* The last 8-byte inode number is gone, but the count is still 1.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xfs_dir2_sf_toino4(
|
|
|
|
xfs_da_args_t *args) /* operation arguments */
|
|
|
|
{
|
2019-11-09 06:02:38 +07:00
|
|
|
struct xfs_inode *dp = args->dp;
|
|
|
|
struct xfs_mount *mp = dp->i_mount;
|
2005-04-17 05:20:36 +07:00
|
|
|
char *buf; /* old dir's buffer */
|
|
|
|
int i; /* entry index */
|
|
|
|
int newsize; /* new inode size */
|
|
|
|
xfs_dir2_sf_entry_t *oldsfep; /* old sf entry */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *oldsfp; /* old sf directory */
|
2005-04-17 05:20:36 +07:00
|
|
|
int oldsize; /* old inode size */
|
|
|
|
xfs_dir2_sf_entry_t *sfep; /* new sf entry */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* new sf directory */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-12-15 06:14:59 +07:00
|
|
|
trace_xfs_dir2_sf_toino4(args);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Copy the old directory to the buffer.
|
|
|
|
* Then nuke it from the inode, and add the new buffer to the inode.
|
|
|
|
* Don't want xfs_idata_realloc copying the data here.
|
|
|
|
*/
|
|
|
|
oldsize = dp->i_df.if_bytes;
|
2019-08-27 02:06:22 +07:00
|
|
|
buf = kmem_alloc(oldsize, 0);
|
2011-07-08 19:35:13 +07:00
|
|
|
oldsfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
|
|
|
ASSERT(oldsfp->i8count == 1);
|
2005-04-17 05:20:36 +07:00
|
|
|
memcpy(buf, oldsfp, oldsize);
|
|
|
|
/*
|
|
|
|
* Compute the new inode size.
|
|
|
|
*/
|
2016-07-20 08:48:31 +07:00
|
|
|
newsize = oldsize - (oldsfp->count + 1) * XFS_INO64_DIFF;
|
2005-04-17 05:20:36 +07:00
|
|
|
xfs_idata_realloc(dp, -oldsize, XFS_DATA_FORK);
|
|
|
|
xfs_idata_realloc(dp, newsize, XFS_DATA_FORK);
|
|
|
|
/*
|
|
|
|
* Reset our pointers, the data has moved.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
oldsfp = (xfs_dir2_sf_hdr_t *)buf;
|
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Fill in the new header.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->count = oldsfp->count;
|
|
|
|
sfp->i8count = 0;
|
2019-11-09 06:02:31 +07:00
|
|
|
xfs_dir2_sf_put_parent_ino(sfp, xfs_dir2_sf_get_parent_ino(oldsfp));
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Copy the entries field by field.
|
|
|
|
*/
|
2007-06-28 13:43:50 +07:00
|
|
|
for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp),
|
|
|
|
oldsfep = xfs_dir2_sf_firstentry(oldsfp);
|
2011-07-08 19:35:13 +07:00
|
|
|
i < sfp->count;
|
2019-11-09 06:02:38 +07:00
|
|
|
i++, sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep),
|
|
|
|
oldsfep = xfs_dir2_sf_nextentry(mp, oldsfp, oldsfep)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
sfep->namelen = oldsfep->namelen;
|
2016-07-20 08:47:21 +07:00
|
|
|
memcpy(sfep->offset, oldsfep->offset, sizeof(sfep->offset));
|
2005-04-17 05:20:36 +07:00
|
|
|
memcpy(sfep->name, oldsfep->name, sfep->namelen);
|
2019-11-09 06:02:59 +07:00
|
|
|
xfs_dir2_sf_put_ino(mp, sfp, sfep,
|
|
|
|
xfs_dir2_sf_get_ino(mp, oldsfp, oldsfep));
|
2019-11-09 06:03:30 +07:00
|
|
|
xfs_dir2_sf_put_ftype(mp, sfep,
|
|
|
|
xfs_dir2_sf_get_ftype(mp, oldsfep));
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Clean up the inode.
|
|
|
|
*/
|
2008-05-19 13:31:57 +07:00
|
|
|
kmem_free(buf);
|
2005-04-17 05:20:36 +07:00
|
|
|
dp->i_d.di_size = newsize;
|
|
|
|
xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_DDATA);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2014-04-14 16:07:23 +07:00
|
|
|
* Convert existing entries from 4-byte inode numbers to 8-byte inode numbers.
|
|
|
|
* The new entry w/ an 8-byte inode number is not there yet; we leave with
|
|
|
|
* i8count set to 1, but no corresponding 8-byte entry.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xfs_dir2_sf_toino8(
|
|
|
|
xfs_da_args_t *args) /* operation arguments */
|
|
|
|
{
|
2019-11-09 06:02:38 +07:00
|
|
|
struct xfs_inode *dp = args->dp;
|
|
|
|
struct xfs_mount *mp = dp->i_mount;
|
2005-04-17 05:20:36 +07:00
|
|
|
char *buf; /* old dir's buffer */
|
|
|
|
int i; /* entry index */
|
|
|
|
int newsize; /* new inode size */
|
|
|
|
xfs_dir2_sf_entry_t *oldsfep; /* old sf entry */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *oldsfp; /* old sf directory */
|
2005-04-17 05:20:36 +07:00
|
|
|
int oldsize; /* old inode size */
|
|
|
|
xfs_dir2_sf_entry_t *sfep; /* new sf entry */
|
2011-07-08 19:35:13 +07:00
|
|
|
xfs_dir2_sf_hdr_t *sfp; /* new sf directory */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-12-15 06:14:59 +07:00
|
|
|
trace_xfs_dir2_sf_toino8(args);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Copy the old directory to the buffer.
|
|
|
|
* Then nuke it from the inode, and add the new buffer to the inode.
|
|
|
|
* Don't want xfs_idata_realloc copying the data here.
|
|
|
|
*/
|
|
|
|
oldsize = dp->i_df.if_bytes;
|
2019-08-27 02:06:22 +07:00
|
|
|
buf = kmem_alloc(oldsize, 0);
|
2011-07-08 19:35:13 +07:00
|
|
|
oldsfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
|
|
|
ASSERT(oldsfp->i8count == 0);
|
2005-04-17 05:20:36 +07:00
|
|
|
memcpy(buf, oldsfp, oldsize);
|
|
|
|
/*
|
2014-04-14 16:07:23 +07:00
|
|
|
* Compute the new inode size (nb: entry count + 1 for parent)
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2016-07-20 08:48:31 +07:00
|
|
|
newsize = oldsize + (oldsfp->count + 1) * XFS_INO64_DIFF;
|
2005-04-17 05:20:36 +07:00
|
|
|
xfs_idata_realloc(dp, -oldsize, XFS_DATA_FORK);
|
|
|
|
xfs_idata_realloc(dp, newsize, XFS_DATA_FORK);
|
|
|
|
/*
|
|
|
|
* Reset our pointers, the data has moved.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
oldsfp = (xfs_dir2_sf_hdr_t *)buf;
|
|
|
|
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Fill in the new header.
|
|
|
|
*/
|
2011-07-08 19:35:13 +07:00
|
|
|
sfp->count = oldsfp->count;
|
|
|
|
sfp->i8count = 1;
|
2019-11-09 06:02:31 +07:00
|
|
|
xfs_dir2_sf_put_parent_ino(sfp, xfs_dir2_sf_get_parent_ino(oldsfp));
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Copy the entries field by field.
|
|
|
|
*/
|
2007-06-28 13:43:50 +07:00
|
|
|
for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp),
|
|
|
|
oldsfep = xfs_dir2_sf_firstentry(oldsfp);
|
2011-07-08 19:35:13 +07:00
|
|
|
i < sfp->count;
|
2019-11-09 06:02:38 +07:00
|
|
|
i++, sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep),
|
|
|
|
oldsfep = xfs_dir2_sf_nextentry(mp, oldsfp, oldsfep)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
sfep->namelen = oldsfep->namelen;
|
2016-07-20 08:47:21 +07:00
|
|
|
memcpy(sfep->offset, oldsfep->offset, sizeof(sfep->offset));
|
2005-04-17 05:20:36 +07:00
|
|
|
memcpy(sfep->name, oldsfep->name, sfep->namelen);
|
2019-11-09 06:02:59 +07:00
|
|
|
xfs_dir2_sf_put_ino(mp, sfp, sfep,
|
|
|
|
xfs_dir2_sf_get_ino(mp, oldsfp, oldsfep));
|
2019-11-09 06:03:30 +07:00
|
|
|
xfs_dir2_sf_put_ftype(mp, sfep,
|
|
|
|
xfs_dir2_sf_get_ftype(mp, oldsfep));
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Clean up the inode.
|
|
|
|
*/
|
2008-05-19 13:31:57 +07:00
|
|
|
kmem_free(buf);
|
2005-04-17 05:20:36 +07:00
|
|
|
dp->i_d.di_size = newsize;
|
|
|
|
xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_DDATA);
|
|
|
|
}
|