#include "xfs_errortag.h"
#include "xfs_btree.h"
#include "xfs_alloc.h"
+#include "xfs_parent.h"
/*
* Metadata File Management
trace_xfs_imeta_resv_init(ip, ask);
return 0;
}
+
+/* Initialize a metadata update structure. */
+static inline int
+xfs_imeta_init(
+ struct xfs_mount *mp,
+ const struct xfs_imeta_path *path,
+ struct xfs_imeta_update *upd)
+{
+ struct xfs_trans *tp;
+ int error;
+
+ memset(upd, 0, sizeof(struct xfs_imeta_update));
+ upd->mp = mp;
+ upd->path = path;
+
+ if (!xfs_has_metadir(mp))
+ return 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. For example, if the path is /quota/user, we require that
+ * /quota already exists.
+ */
+ error = xfs_trans_alloc_empty(mp, &tp);
+ if (error)
+ return error;
+ error = xfs_imeta_dir_parent(tp, upd->path, &upd->dp);
+ xfs_trans_cancel(tp);
+ if (error == -ENOENT) {
+ xfs_fs_mark_sick(mp, XFS_SICK_FS_METADIR);
+ return -EFSCORRUPTED;
+ }
+ if (error)
+ return error;
+
+ return xfs_parent_start(mp, &upd->ppargs);
+}
+
+/*
+ * Unlock and release resources after committing (or cancelling) a metadata
+ * directory tree operation. The caller retains its reference to @upd->ip
+ * and must release it explicitly.
+ */
+static inline void
+xfs_imeta_teardown(
+ struct xfs_imeta_update *upd,
+ int error)
+{
+ trace_xfs_imeta_teardown(upd, error);
+
+ if (upd->ppargs) {
+ xfs_parent_finish(upd->mp, 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) {
+ if (upd->dp_locked)
+ xfs_iunlock(upd->dp, XFS_ILOCK_EXCL);
+ upd->dp_locked = false;
+
+ xfs_imeta_irele(upd->dp);
+ upd->dp = NULL;
+ }
+}
+
+/*
+ * Begin the process of creating a metadata file by allocating transactions
+ * and taking whatever resources we're going to need.
+ */
+int
+xfs_imeta_start_create(
+ struct xfs_mount *mp,
+ const struct xfs_imeta_path *path,
+ struct xfs_imeta_update *upd)
+{
+ int error;
+
+ error = xfs_imeta_init(mp, path, upd);
+ if (error)
+ return error;
+
+ /*
+ * If we ever need the ability to create rt metadata files on a
+ * pre-metadir filesystem, we'll need to dqattach the parent here.
+ * Currently we assume that mkfs will create the files and quotacheck
+ * will account for them.
+ */
+
+ error = xfs_trans_alloc(mp, &M_RES(mp)->tr_imeta_create,
+ xfs_create_space_res(mp, MAXNAMELEN), 0, 0, &upd->tp);
+ if (error)
+ goto out_teardown;
+
+ /*
+ * Lock the parent directory if there is one. We can't ijoin it to
+ * the transaction until after the child file has been created.
+ */
+ if (upd->dp) {
+ xfs_ilock(upd->dp, XFS_ILOCK_EXCL | XFS_ILOCK_PARENT);
+ upd->dp_locked = true;
+ }
+
+ trace_xfs_imeta_start_create(upd);
+ return 0;
+out_teardown:
+ xfs_imeta_teardown(upd, error);
+ return error;
+}
+
+/*
+ * Begin the process of linking a metadata file by allocating transactions
+ * and locking whatever resources we're going to need.
+ */
+static inline int
+xfs_imeta_start_dir_update(
+ struct xfs_mount *mp,
+ const struct xfs_imeta_path *path,
+ struct xfs_inode *ip,
+ struct xfs_trans_res *tr_resv,
+ unsigned int resblks,
+ struct xfs_imeta_update *upd)
+{
+ int error;
+
+ error = xfs_imeta_init(mp, path, upd);
+ if (error)
+ return error;
+
+ upd->ip = ip;
+
+ if (upd->dp) {
+ int nospace_error = 0;
+
+ error = xfs_trans_alloc_dir(upd->dp, tr_resv, upd->ip,
+ &resblks, &upd->tp, &nospace_error);
+ if (error)
+ goto out_teardown;
+ if (!resblks) {
+ /* We don't allow reservationless updates. */
+ 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;
+ } else {
+ error = xfs_trans_alloc_inode(upd->ip, tr_resv, resblks, 0,
+ false, &upd->tp);
+ if (error)
+ goto out_teardown;
+ }
+
+ upd->ip_locked = true;
+ return 0;
+out_teardown:
+ xfs_imeta_teardown(upd, error);
+ return error;
+}
+
+/*
+ * Begin the process of linking a metadata file by allocating transactions
+ * and locking whatever resources we're going to need.
+ */
+int
+xfs_imeta_start_link(
+ struct xfs_mount *mp,
+ const struct xfs_imeta_path *path,
+ struct xfs_inode *ip,
+ struct xfs_imeta_update *upd)
+{
+ int error;
+
+ error = xfs_imeta_start_dir_update(mp, path, ip,
+ &M_RES(mp)->tr_imeta_link,
+ xfs_link_space_res(mp, MAXNAMELEN), upd);
+ if (error)
+ return error;
+
+ trace_xfs_imeta_start_link(upd);
+ return 0;
+}
+
+/*
+ * Begin the process of unlinking a metadata file by allocating transactions
+ * and locking whatever resources we're going to need.
+ */
+int
+xfs_imeta_start_unlink(
+ struct xfs_mount *mp,
+ const struct xfs_imeta_path *path,
+ struct xfs_inode *ip,
+ struct xfs_imeta_update *upd)
+{
+ int error;
+
+ error = xfs_imeta_start_dir_update(mp, path, ip,
+ &M_RES(mp)->tr_imeta_unlink,
+ xfs_remove_space_res(mp, MAXNAMELEN), upd);
+ if (error)
+ return error;
+
+ trace_xfs_imeta_start_unlink(upd);
+ return 0;
+}
+
+/* Commit a metadir update and unlock/drop all resources. */
+int
+xfs_imeta_commit_update(
+ struct xfs_imeta_update *upd)
+{
+ int error;
+
+ trace_xfs_imeta_update_commit(upd);
+
+ error = xfs_trans_commit(upd->tp);
+ upd->tp = NULL;
+
+ xfs_imeta_teardown(upd, error);
+ return error;
+}
+
+/* Cancel a metadir update and unlock/drop all resources. */
+void
+xfs_imeta_cancel_update(
+ struct xfs_imeta_update *upd,
+ int error)
+{
+ trace_xfs_imeta_update_cancel(upd);
+
+ xfs_trans_cancel(upd->tp);
+ upd->tp = NULL;
+
+ xfs_imeta_teardown(upd, error);
+}
+
+/* Create a metadata for the last component of the path. */
+STATIC int
+xfs_imeta_mkdir(
+ struct xfs_mount *mp,
+ const struct xfs_imeta_path *path)
+{
+ struct xfs_imeta_update upd;
+ struct xfs_inode *ip = NULL;
+ int error;
+
+ if (xfs_is_shutdown(mp))
+ return -EIO;
+
+ /* Allocate a transaction to create the last directory. */
+ error = xfs_imeta_start_create(mp, path, &upd);
+ if (error)
+ return error;
+
+ /* Create the subdirectory and take our reference. */
+ error = xfs_imeta_create(&upd, S_IFDIR, &ip);
+ if (error)
+ goto out_cancel;
+
+ error = xfs_imeta_commit_update(&upd);
+
+ /*
+ * We don't pass the directory we just created to the caller, so finish
+ * setting up the inode, then release the dir and the dquots.
+ */
+ goto out_irele;
+
+out_cancel:
+ xfs_imeta_cancel_update(&upd, error);
+out_irele:
+ /* Have to finish setting up the inode to ensure it's deleted. */
+ if (ip) {
+ xfs_finish_inode_setup(ip);
+ xfs_irele(ip);
+ }
+ return error;
+}
+
+/*
+ * Make sure that every metadata directory path component exists and is a
+ * directory.
+ */
+int
+xfs_imeta_ensure_dirpath(
+ struct xfs_mount *mp,
+ const struct xfs_imeta_path *path)
+{
+ struct xfs_imeta_path temp_path = {
+ .im_path = path->im_path,
+ .im_depth = 1,
+ .im_ftype = XFS_DIR3_FT_DIR,
+ };
+ unsigned int i;
+ int error = 0;
+
+ if (!xfs_has_metadir(mp))
+ return 0;
+
+ for (i = 0; i < path->im_depth - 1; i++, temp_path.im_depth++) {
+ error = xfs_imeta_mkdir(mp, &temp_path);
+ if (error && error != -EEXIST)
+ return error;
+ }
+
+ return 0;
+}