// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2018-2024 Oracle. All Rights Reserved. * Author: Darrick J. Wong */ #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; }