blob: 29e385277b43d93165c700a67b3225f979d79b81 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <djwong@kernel.org>
*/
#include "libxfs_priv.h"
#include "xfs_fs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_log_format.h"
#include "xfs_trans_resv.h"
#include "xfs_bit.h"
#include "xfs_sb.h"
#include "xfs_mount.h"
#include "xfs_defer.h"
#include "xfs_trans.h"
#include "xfs_imeta.h"
#include "xfs_trace.h"
#include "xfs_inode.h"
#include "xfs_ialloc.h"
#include "xfs_bmap_btree.h"
#include "xfs_da_format.h"
#include "xfs_da_btree.h"
#include "xfs_trans_space.h"
#include "xfs_dir2.h"
#include "xfs_ag.h"
/*
* Metadata Inode Number Management
* ================================
*
* These functions provide an abstraction layer for looking up, creating, and
* deleting metadata inodes. These pointers live in the in-core superblock,
* so the functions moderate access to those fields and take care of logging.
*
* For the five existing metadata inodes (real time bitmap & summary; and the
* user, group, and quotas) we'll continue to maintain the in-core superblock
* inodes for reads and only require xfs_imeta_create and xfs_imeta_unlink to
* persist changes. New metadata inode types must only use the xfs_imeta_*
* functions.
*
* Callers wishing to create or unlink a metadata inode must pass in a
* xfs_imeta_end structure. After committing or cancelling the transaction,
* this structure must be passed to xfs_imeta_end_update to free resources that
* cannot be freed during the transaction.
*
* When the metadata directory tree (metadir) feature is enabled, we can create
* a complex directory tree in which to store metadata inodes. Inodes within
* the metadata directory tree should have the "metadata" inode flag set to
* prevent them from being exposed to the outside world.
*
* Callers are expected to take the ILOCK of metadata inodes to synchronize
* access to the file. This includes directory operations. It is not
* necessary to take the IOLOCK or MMAPLOCK since metadata inodes should never
* be exposed to user space.
*/
/* Static metadata inode paths */
static const char *rtbitmap_path[] = {"realtime", "0.bitmap"};
static const char *rtsummary_path[] = {"realtime", "0.summary"};
static const char *usrquota_path[] = {"quota", "user"};
static const char *grpquota_path[] = {"quota", "group"};
static const char *prjquota_path[] = {"quota", "project"};
XFS_IMETA_DEFINE_PATH(XFS_IMETA_RTBITMAP, rtbitmap_path);
XFS_IMETA_DEFINE_PATH(XFS_IMETA_RTSUMMARY, rtsummary_path);
XFS_IMETA_DEFINE_PATH(XFS_IMETA_USRQUOTA, usrquota_path);
XFS_IMETA_DEFINE_PATH(XFS_IMETA_GRPQUOTA, grpquota_path);
XFS_IMETA_DEFINE_PATH(XFS_IMETA_PRJQUOTA, prjquota_path);
const struct xfs_imeta_path XFS_IMETA_METADIR = {
.im_depth = 0,
.im_ftype = XFS_DIR3_FT_DIR,
};
/* Are these two paths equal? */
STATIC bool
xfs_imeta_path_compare(
const struct xfs_imeta_path *a,
const struct xfs_imeta_path *b)
{
unsigned int i;
if (a == b)
return true;
if (a->im_depth != b->im_depth)
return false;
for (i = 0; i < a->im_depth; i++)
if (a->im_path[i] != b->im_path[i] &&
strcmp(a->im_path[i], b->im_path[i]))
return false;
return true;
}
/* Is this path ok? */
static inline bool
xfs_imeta_path_check(
const struct xfs_imeta_path *path)
{
return path->im_depth <= XFS_IMETA_MAX_DEPTH;
}
/* Functions for storing and retrieving superblock inode values. */
/* Mapping of metadata inode paths to in-core superblock values. */
static const struct xfs_imeta_sbmap {
const struct xfs_imeta_path *path;
unsigned int offset;
} xfs_imeta_sbmaps[] = {
{
.path = &XFS_IMETA_RTBITMAP,
.offset = offsetof(struct xfs_sb, sb_rbmino),
},
{
.path = &XFS_IMETA_RTSUMMARY,
.offset = offsetof(struct xfs_sb, sb_rsumino),
},
{
.path = &XFS_IMETA_USRQUOTA,
.offset = offsetof(struct xfs_sb, sb_uquotino),
},
{
.path = &XFS_IMETA_GRPQUOTA,
.offset = offsetof(struct xfs_sb, sb_gquotino),
},
{
.path = &XFS_IMETA_PRJQUOTA,
.offset = offsetof(struct xfs_sb, sb_pquotino),
},
{
.path = &XFS_IMETA_METADIR,
.offset = offsetof(struct xfs_sb, sb_metadirino),
},
{ NULL, 0 },
};
/* Return a pointer to the in-core superblock inode value. */
static inline xfs_ino_t *
xfs_imeta_sbmap_to_inop(
struct xfs_mount *mp,
const struct xfs_imeta_sbmap *map)
{
return (xfs_ino_t *)(((char *)&mp->m_sb) + map->offset);
}
/* Compute location of metadata inode pointer in the in-core superblock */
static inline xfs_ino_t *
xfs_imeta_path_to_sb_inop(
struct xfs_mount *mp,
const struct xfs_imeta_path *path)
{
const struct xfs_imeta_sbmap *p;
for (p = xfs_imeta_sbmaps; p->path; p++)
if (xfs_imeta_path_compare(p->path, path))
return xfs_imeta_sbmap_to_inop(mp, p);
return NULL;
}
/* Look up a superblock metadata inode by its path. */
STATIC int
xfs_imeta_sb_lookup(
struct xfs_mount *mp,
const struct xfs_imeta_path *path,
xfs_ino_t *inop)
{
xfs_ino_t *sb_inop;
sb_inop = xfs_imeta_path_to_sb_inop(mp, path);
if (!sb_inop)
return -EINVAL;
trace_xfs_imeta_sb_lookup(mp, sb_inop);
*inop = *sb_inop;
return 0;
}
/*
* Create a new metadata inode and set a superblock pointer to this new inode.
* The superblock field must not already be pointing to an inode.
*/
STATIC int
xfs_imeta_sb_create(
struct xfs_trans **tpp,
const struct xfs_imeta_path *path,
umode_t mode,
unsigned int flags,
struct xfs_inode **ipp)
{
struct xfs_icreate_args args = {
.nlink = S_ISDIR(mode) ? 2 : 1,
};
struct xfs_mount *mp = (*tpp)->t_mountp;
xfs_ino_t *sb_inop;
xfs_ino_t ino;
int error;
xfs_icreate_args_rootfile(&args, mode);
/* Reject if the sb already points to some inode. */
sb_inop = xfs_imeta_path_to_sb_inop(mp, path);
if (!sb_inop)
return -EINVAL;
if (*sb_inop != NULLFSINO)
return -EEXIST;
/* Create a new inode and set the sb pointer. */
error = xfs_dialloc(tpp, NULL, mode, &ino);
if (error)
return error;
error = xfs_icreate(*tpp, ino, &args, ipp);
if (error)
return error;
/* Attach dquots to this file. Caller should have allocated them! */
if (!(flags & XFS_IMETA_CREATE_NOQUOTA)) {
error = xfs_qm_dqattach_locked(*ipp, false);
if (error)
return error;
xfs_trans_mod_dquot_byino(*tpp, *ipp, XFS_TRANS_DQ_ICOUNT, 1);
}
/* Update superblock pointer. */
*sb_inop = ino;
trace_xfs_imeta_sb_create(mp, sb_inop);
xfs_log_sb(*tpp);
return 0;
}
/*
* Clear the given inode pointer from the superblock and drop the link count
* of the metadata inode.
*/
STATIC int
xfs_imeta_sb_unlink(
struct xfs_trans **tpp,
const struct xfs_imeta_path *path,
struct xfs_inode *ip)
{
struct xfs_mount *mp = (*tpp)->t_mountp;
xfs_ino_t *sb_inop;
sb_inop = xfs_imeta_path_to_sb_inop(mp, path);
if (!sb_inop)
return -EINVAL;
/* Reject if the sb doesn't point to the inode that was passed in. */
if (*sb_inop != ip->i_ino)
return -ENOENT;
*sb_inop = NULLFSINO;
trace_xfs_imeta_sb_unlink(mp, sb_inop);
xfs_log_sb(*tpp);
return xfs_droplink(*tpp, ip);
}
/* Set the given inode pointer in the superblock. */
STATIC int
xfs_imeta_sb_link(
struct xfs_trans *tp,
const struct xfs_imeta_path *path,
struct xfs_inode *ip)
{
struct xfs_mount *mp = tp->t_mountp;
xfs_ino_t *sb_inop;
sb_inop = xfs_imeta_path_to_sb_inop(mp, path);
if (!sb_inop)
return -EINVAL;
if (*sb_inop == NULLFSINO)
return -EEXIST;
xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
inc_nlink(VFS_I(ip));
*sb_inop = ip->i_ino;
trace_xfs_imeta_sb_link(mp, sb_inop);
xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
xfs_log_sb(tp);
return 0;
}
/* Functions for storing and retrieving metadata directory inode values. */
static inline void
set_xname(
struct xfs_name *xname,
const struct xfs_imeta_path *path,
unsigned int path_idx,
unsigned char ftype)
{
xname->name = (const unsigned char *)path->im_path[path_idx];
xname->len = strlen(path->im_path[path_idx]);
xname->type = ftype;
}
/*
* Given a parent directory @dp, a metadata inode @path and component
* @path_idx, and the expected file type @ftype of the path component, fill out
* the @xname and look up the inode number in the directory, returning it in
* @ino.
*/
static inline int
xfs_imeta_dir_lookup_component(
struct xfs_inode *dp,
struct xfs_name *xname,
xfs_ino_t *ino)
{
int type_wanted = xname->type;
int error;
trace_xfs_imeta_dir_lookup_component(dp, xname, NULLFSINO);
if (!S_ISDIR(VFS_I(dp)->i_mode))
return -EFSCORRUPTED;
error = xfs_dir_lookup(NULL, dp, xname, ino, NULL);
if (error)
return error;
if (!xfs_verify_ino(dp->i_mount, *ino))
return -EFSCORRUPTED;
if (type_wanted != XFS_DIR3_FT_UNKNOWN && xname->type != type_wanted)
return -EFSCORRUPTED;
trace_xfs_imeta_dir_lookup_found(dp, xname, *ino);
return 0;
}
/*
* Traverse a metadata directory tree path, returning the inode corresponding
* to the parent of the last path component. If any of the path components do
* not exist, return -ENOENT.
*/
STATIC int
xfs_imeta_dir_parent(
struct xfs_mount *mp,
const struct xfs_imeta_path *path,
struct xfs_inode **dpp)
{
struct xfs_name xname;
struct xfs_inode *dp;
xfs_ino_t ino;
unsigned int i;
int error;
if (mp->m_metadirip == NULL)
return -ENOENT;
/* Grab the metadir root. */
error = xfs_imeta_iget(mp, mp->m_metadirip->i_ino, XFS_DIR3_FT_DIR,
&dp);
if (error)
return error;
/* Caller wanted the root, we're done! */
if (path->im_depth == 0) {
*dpp = dp;
return 0;
}
for (i = 0; i < path->im_depth - 1; i++) {
/* Look up the name in the current directory. */
set_xname(&xname, path, i, XFS_DIR3_FT_DIR);
error = xfs_imeta_dir_lookup_component(dp, &xname, &ino);
if (error)
goto out_rele;
/* Drop the existing dp and pick up the new one. */
xfs_imeta_irele(dp);
error = xfs_imeta_iget(mp, ino, XFS_DIR3_FT_DIR, &dp);
if (error)
return error;
}
*dpp = dp;
return 0;
out_rele:
xfs_imeta_irele(dp);
return error;
}
/*
* Look up a metadata inode from the metadata directory. If the last path
* component doesn't exist, return NULLFSINO. If any other part of the path
* does not exist, return -ENOENT so we can distinguish the two.
*/
STATIC int
xfs_imeta_dir_lookup_int(
struct xfs_mount *mp,
const struct xfs_imeta_path *path,
xfs_ino_t *inop)
{
struct xfs_name xname;
struct xfs_inode *dp = NULL;
xfs_ino_t ino;
int error;
/* metadir ino is recorded in superblock */
if (xfs_imeta_path_compare(path, &XFS_IMETA_METADIR))
return xfs_imeta_sb_lookup(mp, path, inop);
ASSERT(path->im_depth > 0);
/* Find the parent of the last path component. */
error = xfs_imeta_dir_parent(mp, path, &dp);
if (error)
return error;
/* Look up the name in the current directory. */
set_xname(&xname, path, path->im_depth - 1, path->im_ftype);
error = xfs_imeta_dir_lookup_component(dp, &xname, &ino);
switch (error) {
case 0:
*inop = ino;
break;
case -ENOENT:
*inop = NULLFSINO;
error = 0;
break;
}
xfs_imeta_irele(dp);
return error;
}
/*
* Look up a metadata inode from the metadata directory tree. If any of the
* middle path components do not exist, we consider this corruption because
* only the last component is allowed to not exist.
*/
STATIC int
xfs_imeta_dir_lookup(
struct xfs_mount *mp,
const struct xfs_imeta_path *path,
xfs_ino_t *inop)
{
int error;
error = xfs_imeta_dir_lookup_int(mp, path, inop);
if (error == -ENOENT)
return -EFSCORRUPTED;
return error;
}
/*
* Load all the metadata inode pointers that are cached in the in-core
* superblock but live somewhere in the metadata directory tree.
*/
STATIC int
xfs_imeta_dir_mount(
struct xfs_mount *mp)
{
const struct xfs_imeta_sbmap *p;
xfs_ino_t *sb_inop;
int err2;
int error = 0;
for (p = xfs_imeta_sbmaps; p->path && p->path->im_depth > 0; p++) {
if (p->path == &XFS_IMETA_METADIR)
continue;
sb_inop = xfs_imeta_sbmap_to_inop(mp, p);
err2 = xfs_imeta_dir_lookup_int(mp, p->path, sb_inop);
if (err2 == -ENOENT) {
*sb_inop = NULLFSINO;
continue;
}
if (!error && err2)
error = err2;
}
return error;
}
/* Set up an inode to be recognized as a metadata inode. */
void
xfs_imeta_set_metaflag(
struct xfs_trans *tp,
struct xfs_inode *ip)
{
VFS_I(ip)->i_mode &= ~0777;
VFS_I(ip)->i_uid = GLOBAL_ROOT_UID;
VFS_I(ip)->i_gid = GLOBAL_ROOT_GID;
ip->i_projid = 0;
ip->i_diflags |= (XFS_DIFLAG_IMMUTABLE | XFS_DIFLAG_SYNC |
XFS_DIFLAG_NOATIME | XFS_DIFLAG_NODUMP |
XFS_DIFLAG_NODEFRAG);
if (S_ISDIR(VFS_I(ip)->i_mode))
ip->i_diflags |= XFS_DIFLAG_NOSYMLINKS;
ip->i_diflags2 &= ~XFS_DIFLAG2_DAX;
ip->i_diflags2 |= XFS_DIFLAG2_METADATA;
xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
}
/*
* Create a new metadata inode accessible via the given metadata directory path.
* Callers must ensure that the directory entry does not already exist; a new
* one will be created.
*/
STATIC int
xfs_imeta_dir_create(
struct xfs_trans **tpp,
const struct xfs_imeta_path *path,
umode_t mode,
unsigned int flags,
struct xfs_inode **ipp,
struct xfs_imeta_end *cleanup)
{
struct xfs_icreate_args args = {
.nlink = S_ISDIR(mode) ? 2 : 1,
};
struct xfs_name xname;
struct xfs_mount *mp = (*tpp)->t_mountp;
struct xfs_inode *dp = NULL;
xfs_ino_t *sb_inop;
xfs_ino_t ino;
unsigned int resblks;
int error;
xfs_icreate_args_rootfile(&args, mode);
/* metadir ino is recorded in superblock; only mkfs gets to do this */
if (xfs_imeta_path_compare(path, &XFS_IMETA_METADIR)) {
error = xfs_imeta_sb_create(tpp, path, mode, flags, ipp);
if (error)
return error;
/* Set the metadata iflag, initialize directory. */
xfs_imeta_set_metaflag(*tpp, *ipp);
return xfs_dir_init(*tpp, *ipp, *ipp);
}
ASSERT(path->im_depth > 0);
/*
* Find the parent of the last path component. If the parent path does
* not exist, we consider this corruption because paths are supposed
* to exist.
*/
error = xfs_imeta_dir_parent(mp, path, &dp);
if (error == -ENOENT)
return -EFSCORRUPTED;
if (error)
return error;
/* Check that the name does not already exist in the directory. */
set_xname(&xname, path, path->im_depth - 1, XFS_DIR3_FT_UNKNOWN);
error = xfs_imeta_dir_lookup_component(dp, &xname, &ino);
switch (error) {
case -ENOENT:
break;
case 0:
error = -EEXIST;
fallthrough;
default:
goto out_rele;
}
xfs_ilock(dp, XFS_ILOCK_EXCL | XFS_ILOCK_PARENT);
args.pip = dp;
/*
* A newly created regular or special file just has one directory
* entry pointing to them, but a directory also the "." entry
* pointing to itself.
*/
error = xfs_dialloc(tpp, dp, mode, &ino);
if (error)
goto out_ilock;
error = xfs_icreate(*tpp, ino, &args, ipp);
if (error)
goto out_ilock;
xfs_imeta_set_metaflag(*tpp, *ipp);
/*
* Once we join the parent directory to the transaction we can't
* release it until after the transaction commits or cancels, so we
* must defer releasing it to end_update. This is different from
* regular file creation, where the vfs holds the parent dir reference
* and will free it. The caller is always responsible for releasing
* ipp, even if we failed.
*/
xfs_trans_ijoin(*tpp, dp, XFS_ILOCK_EXCL);
cleanup->dp = dp;
/* Create the entry. */
if (S_ISDIR(args.mode))
resblks = XFS_MKDIR_SPACE_RES(mp, xname.len);
else
resblks = XFS_CREATE_SPACE_RES(mp, xname.len);
xname.type = xfs_mode_to_ftype(args.mode);
trace_xfs_imeta_dir_try_create(dp, &xname, NULLFSINO);
error = xfs_dir_create_new_child(*tpp, resblks, dp, &xname, *ipp);
if (error)
return error;
trace_xfs_imeta_dir_created(*ipp, &xname, ino);
/* Attach dquots to this file. Caller should have allocated them! */
if (!(flags & XFS_IMETA_CREATE_NOQUOTA)) {
error = xfs_qm_dqattach_locked(*ipp, false);
if (error)
return error;
xfs_trans_mod_dquot_byino(*tpp, *ipp, XFS_TRANS_DQ_ICOUNT, 1);
}
/* Update the in-core superblock value if there is one. */
sb_inop = xfs_imeta_path_to_sb_inop(mp, path);
if (sb_inop)
*sb_inop = ino;
return 0;
out_ilock:
xfs_iunlock(dp, XFS_ILOCK_EXCL);
out_rele:
xfs_imeta_irele(dp);
return error;
}
/*
* Remove the given entry from the metadata directory and drop the link count
* of the metadata inode.
*/
STATIC int
xfs_imeta_dir_unlink(
struct xfs_trans **tpp,
const struct xfs_imeta_path *path,
struct xfs_inode *ip,
struct xfs_imeta_end *cleanup)
{
struct xfs_name xname;
struct xfs_mount *mp = (*tpp)->t_mountp;
struct xfs_inode *dp = NULL;
xfs_ino_t *sb_inop;
xfs_ino_t ino;
unsigned int resblks;
int error;
/* Metadata directory root cannot be unlinked. */
if (xfs_imeta_path_compare(path, &XFS_IMETA_METADIR)) {
ASSERT(0);
return -EFSCORRUPTED;
}
ASSERT(path->im_depth > 0);
/*
* Find the parent of the last path component. If the parent path does
* not exist, we consider this corruption because paths are supposed
* to exist.
*/
error = xfs_imeta_dir_parent(mp, path, &dp);
if (error == -ENOENT)
return -EFSCORRUPTED;
if (error)
return error;
/* Look up the name in the current directory. */
set_xname(&xname, path, path->im_depth - 1,
xfs_mode_to_ftype(VFS_I(ip)->i_mode));
error = xfs_imeta_dir_lookup_component(dp, &xname, &ino);
switch (error) {
case 0:
if (ino != ip->i_ino)
error = -ENOENT;
break;
case -ENOENT:
error = -EFSCORRUPTED;
break;
}
if (error)
goto out_rele;
xfs_lock_two_inodes(dp, XFS_ILOCK_EXCL, ip, XFS_ILOCK_EXCL);
/*
* Once we join the parent directory to the transaction we can't
* release it until after the transaction commits or cancels, so we
* must defer releasing it to end_update. This is different from
* regular file removal, where the vfs holds the parent dir reference
* and will free it. The unlink caller is always responsible for
* releasing ip, so we don't need to take care of that.
*/
xfs_trans_ijoin(*tpp, dp, XFS_ILOCK_EXCL);
xfs_trans_ijoin(*tpp, ip, XFS_ILOCK_EXCL);
cleanup->dp = dp;
resblks = XFS_REMOVE_SPACE_RES(mp);
error = xfs_dir_remove_child(*tpp, resblks, dp, &xname, ip);
if (error)
return error;
trace_xfs_imeta_dir_unlinked(dp, &xname, ip->i_ino);
/* Update the in-core superblock value if there is one. */
sb_inop = xfs_imeta_path_to_sb_inop(mp, path);
if (sb_inop)
*sb_inop = NULLFSINO;
return 0;
out_rele:
xfs_imeta_irele(dp);
return error;
}
/* Set the given path in the metadata directory to point to an inode. */
STATIC int
xfs_imeta_dir_link(
struct xfs_trans *tp,
const struct xfs_imeta_path *path,
struct xfs_inode *ip,
struct xfs_imeta_end *cleanup)
{
struct xfs_name xname;
struct xfs_mount *mp = tp->t_mountp;
struct xfs_inode *dp = NULL;
xfs_ino_t *sb_inop;
xfs_ino_t ino;
unsigned int resblks;
int error;
/* Metadata directory root cannot be linked. */
if (xfs_imeta_path_compare(path, &XFS_IMETA_METADIR)) {
ASSERT(0);
return -EFSCORRUPTED;
}
ASSERT(path->im_depth > 0);
/*
* Find the parent of the last path component. If the parent path does
* not exist, we consider this corruption because paths are supposed
* to exist.
*/
error = xfs_imeta_dir_parent(mp, path, &dp);
if (error == -ENOENT)
return -EFSCORRUPTED;
if (error)
return error;
/* Look up the name in the current directory. */
set_xname(&xname, path, path->im_depth - 1,
xfs_mode_to_ftype(VFS_I(ip)->i_mode));
error = xfs_imeta_dir_lookup_component(dp, &xname, &ino);
switch (error) {
case -ENOENT:
break;
case 0:
error = -EEXIST;
fallthrough;
default:
goto out_rele;
}
xfs_lock_two_inodes(ip, XFS_ILOCK_EXCL, dp, XFS_ILOCK_EXCL);
/*
* Once we join the parent directory to the transaction we can't
* release it until after the transaction commits or cancels, so we
* must defer releasing it to end_update. This is different from
* regular file removal, where the vfs holds the parent dir reference
* and will free it. The link caller is always responsible for
* releasing ip, so we don't need to take care of that.
*/
xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, dp, XFS_ILOCK_EXCL);
cleanup->dp = dp;
resblks = XFS_LINK_SPACE_RES(mp, target_name->len);
error = xfs_dir_link_existing_child(tp, resblks, dp, &xname, ip);
if (error)
return error;
trace_xfs_imeta_dir_link(dp, &xname, ip->i_ino);
/* Update the in-core superblock value if there is one. */
sb_inop = xfs_imeta_path_to_sb_inop(mp, path);
if (sb_inop)
*sb_inop = ip->i_ino;
return 0;
out_rele:
xfs_imeta_irele(dp);
return error;
}
/* General functions for managing metadata inode pointers */
/*
* Is this metadata inode pointer ok? We allow the fields to be set to
* NULLFSINO if the metadata structure isn't present, and we don't allow
* obviously incorrect inode pointers.
*/
static inline bool
xfs_imeta_verify(
struct xfs_mount *mp,
xfs_ino_t ino)
{
if (ino == NULLFSINO)
return true;
return xfs_verify_ino(mp, ino);
}
/* Look up a metadata inode by its path. */
int
xfs_imeta_lookup(
struct xfs_mount *mp,
const struct xfs_imeta_path *path,
xfs_ino_t *inop)
{
xfs_ino_t ino;
int error;
ASSERT(xfs_imeta_path_check(path));
if (xfs_has_metadir(mp))
error = xfs_imeta_dir_lookup(mp, path, &ino);
else
error = xfs_imeta_sb_lookup(mp, path, &ino);
if (error)
return error;
if (!xfs_imeta_verify(mp, ino))
return -EFSCORRUPTED;
*inop = ino;
return 0;
}
/*
* Create a metadata inode with the given @mode, and insert it into the
* metadata directory tree at the given @path. The path (up to the final
* component) must already exist. The new metadata inode @ipp will be ijoined
* and logged to @tpp, with the ILOCK held until the next transaction commit.
* The caller must provide a @cleanup structure.
*
* Callers must ensure that the root dquots are allocated, if applicable.
*
* NOTE: This function may pass a child inode @ipp back to the caller even if
* it returns a negative error code. If an inode is passed back, the caller
* must finish setting up the incore inode before releasing it.
*/
int
xfs_imeta_create(
struct xfs_trans **tpp,
const struct xfs_imeta_path *path,
umode_t mode,
unsigned int flags,
struct xfs_inode **ipp,
struct xfs_imeta_end *cleanup)
{
struct xfs_mount *mp = (*tpp)->t_mountp;
ASSERT(xfs_imeta_path_check(path));
*ipp = NULL;
cleanup->dp = NULL;
if (xfs_has_metadir(mp))
return xfs_imeta_dir_create(tpp, path, mode, flags, ipp,
cleanup);
return xfs_imeta_sb_create(tpp, path, mode, flags, ipp);
}
/* Free a file from the metadata directory tree. */
STATIC int
xfs_imeta_ifree(
struct xfs_trans *tp,
struct xfs_inode *ip)
{
struct xfs_mount *mp = ip->i_mount;
struct xfs_perag *pag;
struct xfs_icluster xic = { 0 };
int error;
ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL));
ASSERT(VFS_I(ip)->i_nlink == 0);
ASSERT(ip->i_df.if_nextents == 0);
ASSERT(ip->i_disk_size == 0 || !S_ISREG(VFS_I(ip)->i_mode));
ASSERT(ip->i_nblocks == 0);
pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, ip->i_ino));
error = xfs_dir_ifree(tp, pag, ip, &xic);
if (error)
goto out;
/* Metadata files do not support ownership changes or DMAPI. */
if (xic.deleted)
error = xfs_ifree_cluster(tp, pag, ip, &xic);
out:
xfs_perag_put(pag);
return error;
}
/*
* Unlink a metadata inode @ip from the metadata directory given by @path. The
* metadata inode must not be ILOCKed. Upon return, the inode will be ijoined
* and logged to @tpp, and returned with reduced link count, ready to be
* released. The caller must provide a @cleanup structure.
*/
int
xfs_imeta_unlink(
struct xfs_trans **tpp,
const struct xfs_imeta_path *path,
struct xfs_inode *ip,
struct xfs_imeta_end *cleanup)
{
struct xfs_mount *mp = (*tpp)->t_mountp;
int error;
cleanup->dp = NULL;
ASSERT(xfs_imeta_path_check(path));
ASSERT(xfs_imeta_verify((*tpp)->t_mountp, ip->i_ino));
if (xfs_has_metadir(mp))
error = xfs_imeta_dir_unlink(tpp, path, ip, cleanup);
else
error = xfs_imeta_sb_unlink(tpp, path, ip);
if (error)
return error;
/*
* Metadata files require explicit resource cleanup. In other words,
* the inactivation system will not touch these files, so we must free
* the ondisk inode by ourselves if warranted.
*/
if (VFS_I(ip)->i_nlink > 0)
return 0;
return xfs_imeta_ifree(*tpp, ip);
}
/*
* Link the metadata directory given by @path point to the given inode number.
* The path must not already exist. The caller must not hold the ILOCK, and
* the function will return with the inode joined to the transaction.
*/
int
xfs_imeta_link(
struct xfs_trans *tp,
const struct xfs_imeta_path *path,
struct xfs_inode *ip,
struct xfs_imeta_end *cleanup)
{
struct xfs_mount *mp = tp->t_mountp;
ASSERT(xfs_imeta_path_check(path));
cleanup->dp = NULL;
if (xfs_has_metadir(mp))
return xfs_imeta_dir_link(tp, path, ip, cleanup);
return xfs_imeta_sb_link(tp, path, ip);
}
/*
* Clean up after committing (or cancelling) a metadata inode creation or
* removal.
*/
void
xfs_imeta_end_update(
struct xfs_mount *mp,
struct xfs_imeta_end *cleanup,
int error)
{
trace_xfs_imeta_end_update(mp, error, __return_address);
if (cleanup->dp)
xfs_imeta_irele(cleanup->dp);
cleanup->dp = NULL;
}
/* Does this inode number refer to a static metadata inode? */
bool
xfs_is_static_meta_ino(
struct xfs_mount *mp,
xfs_ino_t ino)
{
const struct xfs_imeta_sbmap *p;
if (ino == NULLFSINO)
return false;
for (p = xfs_imeta_sbmaps; p->path; p++)
if (ino == *xfs_imeta_sbmap_to_inop(mp, p))
return true;
return false;
}
/* Ensure that the in-core superblock has all the values that it should. */
int
xfs_imeta_mount(
struct xfs_mount *mp)
{
if (xfs_has_metadir(mp))
return xfs_imeta_dir_mount(mp);
return 0;
}
/* Calculate the log block reservation to create a metadata inode. */
unsigned int
xfs_imeta_create_space_res(
struct xfs_mount *mp)
{
if (xfs_has_metadir(mp))
return max(XFS_MKDIR_SPACE_RES(mp, NAME_MAX),
XFS_CREATE_SPACE_RES(mp, NAME_MAX));
return XFS_IALLOC_SPACE_RES(mp);
}
/* Calculate the log block reservation to unlink a metadata inode. */
unsigned int
xfs_imeta_unlink_space_res(
struct xfs_mount *mp)
{
return XFS_REMOVE_SPACE_RES(mp);
}
/* Clear the metadata iflag if we're unlinking this inode. */
void
xfs_imeta_droplink(
struct xfs_inode *ip)
{
if (VFS_I(ip)->i_nlink == 0 &&
xfs_has_metadir(ip->i_mount) &&
xfs_is_metadata_inode(ip))
ip->i_diflags2 &= ~XFS_DIFLAG2_METADATA;
}