blob: 256c85560192b3fd7ce02d944bb43ccd45f7f8ef [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022-2023 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <djwong@kernel.org>
*/
#include "libxfs.h"
#include "command.h"
#include "output.h"
#include "init.h"
static xfs_filblks_t
count_rtblocks(
struct xfs_inode *ip)
{
struct xfs_iext_cursor icur;
struct xfs_bmbt_irec got;
xfs_filblks_t count = 0;
struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_DATA_FORK);
int error;
error = -libxfs_iread_extents(NULL, ip, XFS_DATA_FORK);
if (error) {
dbprintf(
_("could not read AG %u agino %u extents, err=%d\n"),
XFS_INO_TO_AGNO(ip->i_mount, ip->i_ino),
XFS_INO_TO_AGINO(ip->i_mount, ip->i_ino),
error);
return 0;
}
for_each_xfs_iext(ifp, &icur, &got)
if (!isnullstartblock(got.br_startblock))
count += got.br_blockcount;
return count;
}
static xfs_agino_t
get_next_unlinked(
xfs_agnumber_t agno,
xfs_agino_t agino,
bool verbose)
{
struct xfs_buf *ino_bp;
struct xfs_dinode *dip;
struct xfs_inode *ip;
xfs_ino_t ino;
xfs_agino_t ret;
int error;
ino = XFS_AGINO_TO_INO(mp, agno, agino);
error = -libxfs_iget(mp, NULL, ino, 0, &ip);
if (error)
goto bad;
if (verbose) {
xfs_filblks_t blocks, rtblks = 0;
if (XFS_IS_REALTIME_INODE(ip))
rtblks = count_rtblocks(ip);
blocks = ip->i_nblocks - rtblks;
dbprintf(_(" blocks %llu rtblocks %llu\n"),
blocks, rtblks);
} else {
dbprintf("\n");
}
error = -libxfs_imap_to_bp(mp, NULL, &ip->i_imap, &ino_bp);
if (error)
goto bad;
dip = xfs_buf_offset(ino_bp, ip->i_imap.im_boffset);
ret = be32_to_cpu(dip->di_next_unlinked);
libxfs_buf_relse(ino_bp);
return ret;
bad:
dbprintf(_("AG %u agino %u: %s\n"), agno, agino, strerror(error));
return NULLAGINO;
}
static void
dump_unlinked_bucket(
xfs_agnumber_t agno,
struct xfs_buf *agi_bp,
unsigned int bucket,
bool quiet,
bool verbose)
{
struct xfs_agi *agi = agi_bp->b_addr;
xfs_agino_t agino;
unsigned int i = 0;
agino = be32_to_cpu(agi->agi_unlinked[bucket]);
if (agino != NULLAGINO)
dbprintf(_("AG %u bucket %u agino %u"), agno, bucket, agino);
else if (!quiet && agino == NULLAGINO)
dbprintf(_("AG %u bucket %u agino NULL\n"), agno, bucket);
while (agino != NULLAGINO) {
agino = get_next_unlinked(agno, agino, verbose);
if (agino != NULLAGINO)
dbprintf(_(" [%u] agino %u"), i++, agino);
else if (!quiet && agino == NULLAGINO)
dbprintf(_(" [%u] agino NULL\n"), i++);
}
}
static void
dump_unlinked(
struct xfs_perag *pag,
unsigned int bucket,
bool quiet,
bool verbose)
{
struct xfs_buf *agi_bp;
xfs_agnumber_t agno = pag->pag_agno;
int error;
error = -libxfs_ialloc_read_agi(pag, NULL, 0, &agi_bp);
if (error) {
dbprintf(_("AGI %u: %s\n"), agno, strerror(errno));
return;
}
if (bucket != -1U) {
dump_unlinked_bucket(agno, agi_bp, bucket, quiet, verbose);
goto relse;
}
for (bucket = 0; bucket < XFS_AGI_UNLINKED_BUCKETS; bucket++) {
dump_unlinked_bucket(agno, agi_bp, bucket, quiet, verbose);
}
relse:
libxfs_buf_relse(agi_bp);
}
static int
dump_iunlinked_f(
int argc,
char **argv)
{
struct xfs_perag *pag;
xfs_agnumber_t agno = NULLAGNUMBER;
unsigned int bucket = -1U;
bool quiet = false;
bool verbose = false;
int c;
while ((c = getopt(argc, argv, "a:b:qv")) != EOF) {
switch (c) {
case 'a':
agno = atoi(optarg);
if (agno >= mp->m_sb.sb_agcount) {
dbprintf(_("Unknown AG %u, agcount is %u.\n"),
agno, mp->m_sb.sb_agcount);
return 0;
}
break;
case 'b':
bucket = atoi(optarg);
if (bucket >= XFS_AGI_UNLINKED_BUCKETS) {
dbprintf(_("Unknown bucket %u, max is 63.\n"),
bucket);
return 0;
}
break;
case 'q':
quiet = true;
break;
case 'v':
verbose = true;
break;
default:
dbprintf(_("Bad option for dump_iunlinked command.\n"));
return 0;
}
}
if (agno != NULLAGNUMBER) {
struct xfs_perag *pag = libxfs_perag_get(mp, agno);
dump_unlinked(pag, bucket, quiet, verbose);
libxfs_perag_put(pag);
return 0;
}
for_each_perag(mp, agno, pag)
dump_unlinked(pag, bucket, quiet, verbose);
return 0;
}
static const cmdinfo_t dump_iunlinked_cmd =
{ "dump_iunlinked", NULL, dump_iunlinked_f, 0, -1, 0,
N_("[-a agno] [-b bucket] [-q] [-v]"),
N_("dump chain of unlinked inode buckets"), NULL };
/*
* Look up the inode cluster buffer and log the on-disk unlinked inode change
* we need to make.
*/
static int
iunlink_log_dinode(
struct xfs_trans *tp,
struct xfs_inode *ip,
struct xfs_perag *pag,
xfs_agino_t next_agino)
{
struct xfs_mount *mp = tp->t_mountp;
struct xfs_dinode *dip;
struct xfs_buf *ibp;
int offset;
int error;
error = -libxfs_imap_to_bp(mp, tp, &ip->i_imap, &ibp);
if (error)
return error;
dip = xfs_buf_offset(ibp, ip->i_imap.im_boffset);
dip->di_next_unlinked = cpu_to_be32(next_agino);
offset = ip->i_imap.im_boffset +
offsetof(struct xfs_dinode, di_next_unlinked);
libxfs_dinode_calc_crc(mp, dip);
libxfs_trans_log_buf(tp, ibp, offset, offset + sizeof(xfs_agino_t) - 1);
return 0;
}
static int
iunlink_insert_inode(
struct xfs_trans *tp,
struct xfs_perag *pag,
struct xfs_buf *agibp,
struct xfs_inode *ip)
{
struct xfs_mount *mp = tp->t_mountp;
struct xfs_agi *agi = agibp->b_addr;
xfs_agino_t next_agino;
xfs_agino_t agino = XFS_INO_TO_AGINO(mp, ip->i_ino);
short bucket_index = agino % XFS_AGI_UNLINKED_BUCKETS;
int offset;
int error;
/*
* Get the index into the agi hash table for the list this inode will
* go on. Make sure the pointer isn't garbage and that this inode
* isn't already on the list.
*/
next_agino = be32_to_cpu(agi->agi_unlinked[bucket_index]);
if (next_agino == agino || !xfs_verify_agino_or_null(pag, next_agino))
return EFSCORRUPTED;
if (next_agino != NULLAGINO) {
/*
* There is already another inode in the bucket, so point this
* inode to the current head of the list.
*/
error = iunlink_log_dinode(tp, ip, pag, next_agino);
if (error)
return error;
}
/* Update the bucket. */
agi->agi_unlinked[bucket_index] = cpu_to_be32(agino);
offset = offsetof(struct xfs_agi, agi_unlinked) +
(sizeof(xfs_agino_t) * bucket_index);
libxfs_trans_log_buf(tp, agibp, offset,
offset + sizeof(xfs_agino_t) - 1);
return 0;
}
/*
* This is called when the inode's link count has gone to 0 or we are creating
* a tmpfile via O_TMPFILE. The inode @ip must have nlink == 0.
*
* We place the on-disk inode on a list in the AGI. It will be pulled from this
* list when the inode is freed.
*/
static int
iunlink(
struct xfs_trans *tp,
struct xfs_inode *ip)
{
struct xfs_mount *mp = tp->t_mountp;
struct xfs_perag *pag;
struct xfs_buf *agibp;
int error;
ASSERT(VFS_I(ip)->i_nlink == 0);
ASSERT(VFS_I(ip)->i_mode != 0);
pag = libxfs_perag_get(mp, XFS_INO_TO_AGNO(mp, ip->i_ino));
/* Get the agi buffer first. It ensures lock ordering on the list. */
error = -libxfs_read_agi(pag, tp, 0, &agibp);
if (error)
goto out;
error = iunlink_insert_inode(tp, pag, agibp, ip);
out:
libxfs_perag_put(pag);
return error;
}
static int
create_unlinked(
struct xfs_mount *mp)
{
struct cred cr = { };
struct fsxattr fsx = { };
struct xfs_inode *ip;
struct xfs_trans *tp;
unsigned int resblks;
int error;
resblks = XFS_IALLOC_SPACE_RES(mp);
error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_create_tmpfile, resblks,
0, 0, &tp);
if (error) {
dbprintf(_("alloc trans: %s\n"), strerror(error));
return error;
}
error = -libxfs_dir_ialloc(&tp, NULL, S_IFREG | 0600, 0, 0, &cr, &fsx,
&ip);
if (error) {
dbprintf(_("create inode: %s\n"), strerror(error));
goto out_cancel;
}
error = iunlink(tp, ip);
if (error) {
dbprintf(_("unlink inode: %s\n"), strerror(error));
goto out_rele;
}
error = -libxfs_trans_commit(tp);
if (error)
dbprintf(_("commit inode: %s\n"), strerror(error));
dbprintf(_("Created unlinked inode %llu in agno %u\n"),
(unsigned long long)ip->i_ino,
XFS_INO_TO_AGNO(mp, ip->i_ino));
libxfs_irele(ip);
return error;
out_rele:
libxfs_irele(ip);
out_cancel:
libxfs_trans_cancel(tp);
return error;
}
static int
iunlink_f(
int argc,
char **argv)
{
int nr = 1;
int c;
int error;
while ((c = getopt(argc, argv, "n:")) != EOF) {
switch (c) {
case 'n':
nr = atoi(optarg);
if (nr <= 0) {
dbprintf(_("%s: need positive number\n"));
return 0;
}
break;
default:
dbprintf(_("Bad option for iunlink command.\n"));
return 0;
}
}
for (c = 0; c < nr; c++) {
error = create_unlinked(mp);
if (error)
return 1;
}
return 0;
}
static const cmdinfo_t iunlink_cmd =
{ "iunlink", NULL, iunlink_f, 0, -1, 0,
N_("[-n nr]"),
N_("allocate inodes and put them on the unlinked list"), NULL };
void
iunlink_init(void)
{
add_command(&dump_iunlinked_cmd);
if (expert_mode)
add_command(&iunlink_cmd);
}