#include "xfs_platform.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_metafile.h"
#include "xfs_metadir.h"
#include "xfs_trace.h"
#include "xfs_inode.h"
#include "xfs_quota.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_ag.h"
#include "xfs_dir2.h"
#include "xfs_dir2_priv.h"
#include "xfs_parent.h"
#include "xfs_health.h"
#include "xfs_errortag.h"
#include "xfs_error.h"
#include "xfs_btree.h"
#include "xfs_alloc.h"
static inline void
xfs_metadir_set_xname(
struct xfs_name *xname,
const char *path,
unsigned char ftype)
{
xname->name = (const unsigned char *)path;
xname->len = strlen(path);
xname->type = ftype;
}
static inline int
xfs_metadir_lookup(
struct xfs_trans *tp,
struct xfs_inode *dp,
struct xfs_name *xname,
xfs_ino_t *ino)
{
struct xfs_mount *mp = dp->i_mount;
struct xfs_da_args args = {
.trans = tp,
.dp = dp,
.geo = mp->m_dir_geo,
.name = xname->name,
.namelen = xname->len,
.hashval = xfs_dir2_hashname(mp, xname),
.whichfork = XFS_DATA_FORK,
.op_flags = XFS_DA_OP_OKNOENT,
.owner = dp->i_ino,
};
int error;
if (!S_ISDIR(VFS_I(dp)->i_mode)) {
xfs_fs_mark_sick(mp, XFS_SICK_FS_METADIR);
return -EFSCORRUPTED;
}
if (xfs_is_shutdown(mp))
return -EIO;
error = xfs_dir_lookup_args(&args);
if (error)
return error;
if (!xfs_verify_ino(mp, args.inumber)) {
xfs_fs_mark_sick(mp, XFS_SICK_FS_METADIR);
return -EFSCORRUPTED;
}
if (xname->type != XFS_DIR3_FT_UNKNOWN && xname->type != args.filetype) {
xfs_fs_mark_sick(mp, XFS_SICK_FS_METADIR);
return -EFSCORRUPTED;
}
trace_xfs_metadir_lookup(dp, xname, args.inumber);
*ino = args.inumber;
return 0;
}
int
xfs_metadir_load(
struct xfs_trans *tp,
struct xfs_inode *dp,
const char *path,
enum xfs_metafile_type metafile_type,
struct xfs_inode **ipp)
{
struct xfs_name xname;
xfs_ino_t ino;
int error;
xfs_metadir_set_xname(&xname, path, XFS_DIR3_FT_UNKNOWN);
xfs_ilock(dp, XFS_ILOCK_EXCL);
error = xfs_metadir_lookup(tp, dp, &xname, &ino);
xfs_iunlock(dp, XFS_ILOCK_EXCL);
if (error)
return error;
return xfs_trans_metafile_iget(tp, ino, metafile_type, ipp);
}
static inline void
xfs_metadir_teardown(
struct xfs_metadir_update *upd,
int error)
{
trace_xfs_metadir_teardown(upd, error);
if (upd->ppargs) {
xfs_parent_finish(upd->dp->i_mount, upd->ppargs);
upd->ppargs = NULL;
}
if (upd->ip) {
if (upd->ip_locked)
xfs_iunlock(upd->ip, XFS_ILOCK_EXCL);
upd->ip_locked = false;
}
if (upd->dp_locked)
xfs_iunlock(upd->dp, XFS_ILOCK_EXCL);
upd->dp_locked = false;
}
int
xfs_metadir_start_create(
struct xfs_metadir_update *upd)
{
struct xfs_mount *mp = upd->dp->i_mount;
int error;
ASSERT(upd->dp != NULL);
ASSERT(upd->ip == NULL);
ASSERT(xfs_has_metadir(mp));
ASSERT(upd->metafile_type != XFS_METAFILE_UNKNOWN);
error = xfs_parent_start(mp, &upd->ppargs);
if (error)
return error;
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_create,
xfs_create_space_res(mp, MAXNAMELEN), 0, 0, &upd->tp);
if (error)
goto out_teardown;
xfs_ilock(upd->dp, XFS_ILOCK_EXCL | XFS_ILOCK_PARENT);
upd->dp_locked = true;
trace_xfs_metadir_start_create(upd);
return 0;
out_teardown:
xfs_metadir_teardown(upd, error);
return error;
}
int
xfs_metadir_create(
struct xfs_metadir_update *upd,
umode_t mode)
{
struct xfs_icreate_args args = {
.pip = upd->dp,
.mode = mode,
};
struct xfs_name xname;
struct xfs_dir_update du = {
.dp = upd->dp,
.name = &xname,
.ppargs = upd->ppargs,
};
struct xfs_mount *mp = upd->dp->i_mount;
xfs_ino_t ino;
unsigned int resblks;
int error;
xfs_assert_ilocked(upd->dp, XFS_ILOCK_EXCL);
xfs_metadir_set_xname(&xname, upd->path, XFS_DIR3_FT_UNKNOWN);
error = xfs_metadir_lookup(upd->tp, upd->dp, &xname, &ino);
switch (error) {
case -ENOENT:
break;
case 0:
error = -EEXIST;
fallthrough;
default:
return error;
}
error = xfs_dialloc(&upd->tp, &args, &ino);
if (error)
return error;
error = xfs_icreate(upd->tp, ino, &args, &upd->ip);
if (error)
return error;
du.ip = upd->ip;
xfs_metafile_set_iflag(upd->tp, upd->ip, upd->metafile_type);
upd->ip_locked = true;
xfs_trans_ijoin(upd->tp, upd->dp, 0);
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_metadir_try_create(upd);
error = xfs_dir_create_child(upd->tp, resblks, &du);
if (error)
return error;
trace_xfs_metadir_create(upd);
return 0;
}
#ifndef __KERNEL__
int
xfs_metadir_start_link(
struct xfs_metadir_update *upd)
{
struct xfs_mount *mp = upd->dp->i_mount;
unsigned int resblks;
int nospace_error = 0;
int error;
ASSERT(upd->dp != NULL);
ASSERT(upd->ip != NULL);
ASSERT(xfs_has_metadir(mp));
error = xfs_parent_start(mp, &upd->ppargs);
if (error)
return error;
resblks = xfs_link_space_res(mp, MAXNAMELEN);
error = xfs_trans_alloc_dir(upd->dp, &M_RES(mp)->tr_link, upd->ip,
&resblks, &upd->tp, &nospace_error);
if (error)
goto out_teardown;
if (!resblks) {
xfs_trans_cancel(upd->tp);
upd->tp = NULL;
xfs_iunlock(upd->dp, XFS_ILOCK_EXCL);
xfs_iunlock(upd->ip, XFS_ILOCK_EXCL);
error = nospace_error;
goto out_teardown;
}
upd->dp_locked = true;
upd->ip_locked = true;
trace_xfs_metadir_start_link(upd);
return 0;
out_teardown:
xfs_metadir_teardown(upd, error);
return error;
}
int
xfs_metadir_link(
struct xfs_metadir_update *upd)
{
struct xfs_name xname;
struct xfs_dir_update du = {
.dp = upd->dp,
.name = &xname,
.ip = upd->ip,
.ppargs = upd->ppargs,
};
struct xfs_mount *mp = upd->dp->i_mount;
xfs_ino_t ino;
unsigned int resblks;
int error;
xfs_assert_ilocked(upd->dp, XFS_ILOCK_EXCL);
xfs_assert_ilocked(upd->ip, XFS_ILOCK_EXCL);
xfs_metadir_set_xname(&xname, upd->path,
xfs_mode_to_ftype(VFS_I(upd->ip)->i_mode));
error = xfs_metadir_lookup(upd->tp, upd->dp, &xname, &ino);
switch (error) {
case -ENOENT:
break;
case 0:
error = -EEXIST;
fallthrough;
default:
return error;
}
resblks = xfs_link_space_res(mp, xname.len);
error = xfs_dir_add_child(upd->tp, resblks, &du);
if (error)
return error;
trace_xfs_metadir_link(upd);
return 0;
}
#endif
int
xfs_metadir_commit(
struct xfs_metadir_update *upd)
{
int error;
trace_xfs_metadir_commit(upd);
error = xfs_trans_commit(upd->tp);
upd->tp = NULL;
xfs_metadir_teardown(upd, error);
return error;
}
void
xfs_metadir_cancel(
struct xfs_metadir_update *upd,
int error)
{
trace_xfs_metadir_cancel(upd);
xfs_trans_cancel(upd->tp);
upd->tp = NULL;
xfs_metadir_teardown(upd, error);
}
int
xfs_metadir_mkdir(
struct xfs_inode *dp,
const char *path,
struct xfs_inode **ipp)
{
struct xfs_metadir_update upd = {
.dp = dp,
.path = path,
.metafile_type = XFS_METAFILE_DIR,
};
int error;
if (xfs_is_shutdown(dp->i_mount))
return -EIO;
error = xfs_metadir_start_create(&upd);
if (error)
return error;
error = xfs_metadir_create(&upd, S_IFDIR);
if (error)
goto out_cancel;
error = xfs_metadir_commit(&upd);
if (error)
goto out_irele;
xfs_finish_inode_setup(upd.ip);
*ipp = upd.ip;
return 0;
out_cancel:
xfs_metadir_cancel(&upd, error);
out_irele:
if (upd.ip) {
xfs_finish_inode_setup(upd.ip);
xfs_irele(upd.ip);
}
return error;
}