| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (c) 2018-2024 Oracle. All Rights Reserved. |
| * Author: Darrick J. Wong <djwong@kernel.org> |
| */ |
| #include "xfs.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" |
| |
| /* |
| * Metadata Directory Tree |
| * ======================= |
| * |
| * These functions provide an abstraction layer for looking up, creating, and |
| * deleting metadata inodes that live within a special metadata directory tree. |
| * |
| * This code does not manage the five existing metadata inodes: real time |
| * bitmap & summary; and the user, group, and quotas. All other metadata |
| * inodes must use only the xfs_meta{dir,file}_* functions. |
| * |
| * Callers wishing to create or hardlink a metadata inode must create an |
| * xfs_metadir_update structure, call the appropriate xfs_metadir* function, |
| * and then call xfs_metadir_commit or xfs_metadir_cancel to commit or cancel |
| * the update. Files in the metadata directory tree currently cannot be |
| * unlinked. |
| * |
| * When the metadir feature is enabled, all metadata inodes must have the |
| * "metadata" inode flag set to prevent them from being exposed to the outside |
| * world. |
| * |
| * Callers must take the ILOCK of any inode in the metadata directory tree to |
| * synchronize access to that inode. It is never necessary to take the IOLOCK |
| * or the MMAPLOCK since metadata inodes must not be exposed to user space. |
| */ |
| |
| 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; |
| } |
| |
| /* |
| * Given a parent directory @dp and a metadata inode path component @xname, |
| * Look up the inode number in the directory, returning it in @ino. |
| * @xname.type must match the directory entry's ftype. |
| * |
| * Caller must hold ILOCK_EXCL. |
| */ |
| 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; |
| } |
| |
| /* |
| * Look up and read a metadata inode from the metadata directory. If the path |
| * component doesn't exist, return -ENOENT. |
| */ |
| 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); |
| } |
| |
| /* |
| * 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_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; |
| } |
| |
| /* |
| * Begin the process of creating a metadata file by allocating transactions |
| * and taking whatever resources we're going to need. |
| */ |
| 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; |
| |
| /* |
| * 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_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. |
| */ |
| 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; |
| } |
| |
| /* |
| * Create a metadata inode with the given @mode, and insert it into the |
| * metadata directory tree at the given @upd->path. The path up to the final |
| * component must already exist. The final path component must not exist. |
| * |
| * The new metadata inode will be attached to the update structure @upd->ip, |
| * with the ILOCK held until the caller releases it. |
| * |
| * NOTE: This function may return a new inode to the caller even if it returns |
| * a negative error code. If an inode is passed back, the caller must finish |
| * setting up the inode before releasing it. |
| */ |
| 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); |
| |
| /* Check that the name does not already exist in the directory. */ |
| 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; |
| } |
| |
| /* |
| * 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(&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; |
| |
| /* |
| * Join the directory inode to the transaction. We do not do it |
| * earlier because xfs_dialloc rolls the transaction. |
| */ |
| xfs_trans_ijoin(upd->tp, upd->dp, 0); |
| |
| /* 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_metadir_try_create(upd); |
| |
| error = xfs_dir_create_child(upd->tp, resblks, &du); |
| if (error) |
| return error; |
| |
| /* Metadir files are not accounted to quota. */ |
| |
| trace_xfs_metadir_create(upd); |
| |
| return 0; |
| } |
| |
| #ifndef __KERNEL__ |
| /* |
| * Begin the process of linking a metadata file by allocating transactions |
| * and locking whatever resources we're going to need. |
| */ |
| 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) { |
| /* 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; |
| upd->ip_locked = true; |
| |
| trace_xfs_metadir_start_link(upd); |
| return 0; |
| out_teardown: |
| xfs_metadir_teardown(upd, error); |
| return error; |
| } |
| |
| /* |
| * Link the metadata directory given by @path to the inode @upd->ip. |
| * The path (up to the final component) must already exist, but the final |
| * component must not already exist. |
| */ |
| 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); |
| |
| /* Look up the name in the current directory. */ |
| 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 /* ! __KERNEL__ */ |
| |
| /* Commit a metadir update and unlock/drop all resources. */ |
| 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; |
| } |
| |
| /* Cancel a metadir update and unlock/drop all resources. */ |
| 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); |
| } |
| |
| /* Create a metadata for the last component of the path. */ |
| 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; |
| |
| /* Allocate a transaction to create the last directory. */ |
| error = xfs_metadir_start_create(&upd); |
| if (error) |
| return error; |
| |
| /* Create the subdirectory and take our reference. */ |
| 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: |
| /* Have to finish setting up the inode to ensure it's deleted. */ |
| if (upd.ip) { |
| xfs_finish_inode_setup(upd.ip); |
| xfs_irele(upd.ip); |
| } |
| return error; |
| } |