@@ -86,6 +86,7 @@ xfs-y += xfs_aops.o \
xfs_mount.o \
xfs_mru_cache.o \
xfs_pwork.o \
+ xfs_parent_utils.o \
xfs_reflink.o \
xfs_stats.o \
xfs_super.o \
@@ -752,6 +752,75 @@ struct xfs_scrub_metadata {
XFS_SCRUB_OFLAG_NO_REPAIR_NEEDED)
#define XFS_SCRUB_FLAGS_ALL (XFS_SCRUB_FLAGS_IN | XFS_SCRUB_FLAGS_OUT)
+#define XFS_PPTR_MAXNAMELEN 256
+
+/* return parents of the handle, not the open fd */
+#define XFS_PPTR_IFLAG_HANDLE (1U << 0)
+
+/* target was the root directory */
+#define XFS_PPTR_OFLAG_ROOT (1U << 1)
+
+/* Cursor is done iterating pptrs */
+#define XFS_PPTR_OFLAG_DONE (1U << 2)
+
+ #define XFS_PPTR_FLAG_ALL (XFS_PPTR_IFLAG_HANDLE | XFS_PPTR_OFLAG_ROOT | \
+ XFS_PPTR_OFLAG_DONE)
+
+/* Get an inode parent pointer through ioctl */
+struct xfs_parent_ptr {
+ __u64 xpp_ino; /* Inode */
+ __u32 xpp_gen; /* Inode generation */
+ __u32 xpp_diroffset; /* Directory offset */
+ __u64 xpp_rsvd; /* Reserved */
+ __u8 xpp_name[]; /* File name */
+};
+
+/* Iterate through an inodes parent pointers */
+struct xfs_pptr_info {
+ /* File handle, if XFS_PPTR_IFLAG_HANDLE is set */
+ struct xfs_handle pi_handle;
+
+ /*
+ * Structure to track progress in iterating the parent pointers.
+ * Must be initialized to zeroes before the first ioctl call, and
+ * not touched by callers after that.
+ */
+ struct xfs_attrlist_cursor pi_cursor;
+
+ /* Operational flags: XFS_PPTR_*FLAG* */
+ __u32 pi_flags;
+
+ /* Must be set to zero */
+ __u32 pi_reserved;
+
+ /* size of the trailing buffer in bytes */
+ __u32 pi_ptrs_size;
+
+ /* # of entries filled in (output) */
+ __u32 pi_count;
+
+ /* Must be set to zero */
+ __u64 pi_reserved2[5];
+
+ /* Byte offset of each record within the buffer */
+ __u32 pi_offsets[];
+};
+
+static inline size_t
+xfs_pptr_info_sizeof(int nr_ptrs)
+{
+ return sizeof(struct xfs_pptr_info) +
+ (nr_ptrs * sizeof(struct xfs_parent_ptr));
+}
+
+static inline struct xfs_parent_ptr*
+xfs_ppinfo_to_pp(
+ struct xfs_pptr_info *info,
+ int idx)
+{
+ return (struct xfs_parent_ptr *)((char *)info + info->pi_offsets[idx]);
+}
+
/*
* ioctl limits
*/
@@ -797,6 +866,7 @@ struct xfs_scrub_metadata {
/* XFS_IOC_GETFSMAP ------ hoisted 59 */
#define XFS_IOC_SCRUB_METADATA _IOWR('X', 60, struct xfs_scrub_metadata)
#define XFS_IOC_AG_GEOMETRY _IOWR('X', 61, struct xfs_ag_geometry)
+#define XFS_IOC_GETPARENTS _IOWR('X', 62, struct xfs_parent_ptr)
/*
* ioctl commands that replace IRIX syssgi()'s
@@ -61,6 +61,36 @@ xfs_init_parent_name_rec(
rec->p_diroffset = cpu_to_be32(p_diroffset);
}
+/*
+ * Convert an ondisk parent_name xattr to its incore format. If @value is
+ * NULL, set @irec->p_namelen to zero and leave @irec->p_name untouched.
+ */
+void
+xfs_parent_irec_from_disk(
+ struct xfs_parent_name_irec *irec,
+ const struct xfs_parent_name_rec *rec,
+ const void *value,
+ int valuelen)
+{
+ irec->p_ino = be64_to_cpu(rec->p_ino);
+ irec->p_gen = be32_to_cpu(rec->p_gen);
+ irec->p_diroffset = be32_to_cpu(rec->p_diroffset);
+
+ if (!value) {
+ irec->p_namelen = 0;
+ return;
+ }
+
+ ASSERT(valuelen > 0);
+ ASSERT(valuelen < MAXNAMELEN);
+
+ valuelen = min(valuelen, MAXNAMELEN);
+
+ irec->p_namelen = valuelen;
+ memcpy(irec->p_name, value, valuelen);
+ memset(&irec->p_name[valuelen], 0, sizeof(irec->p_name) - valuelen);
+}
+
int
__xfs_parent_init(
struct xfs_mount *mp,
@@ -8,6 +8,25 @@
extern struct kmem_cache *xfs_parent_intent_cache;
+/*
+ * Incore version of a parent pointer, also contains dirent name so callers
+ * can pass/obtain all the parent pointer information in a single structure
+ */
+struct xfs_parent_name_irec {
+ /* Key fields for looking up a particular parent pointer. */
+ xfs_ino_t p_ino;
+ uint32_t p_gen;
+ xfs_dir2_dataptr_t p_diroffset;
+
+ /* Attributes of a parent pointer. */
+ uint8_t p_namelen;
+ unsigned char p_name[MAXNAMELEN];
+};
+
+void xfs_parent_irec_from_disk(struct xfs_parent_name_irec *irec,
+ const struct xfs_parent_name_rec *rec,
+ const void *value, int valuelen);
+
/*
* Dynamically allocd structure used to wrap the needed data to pass around
* the defer ops machinery
@@ -37,6 +37,7 @@
#include "xfs_health.h"
#include "xfs_reflink.h"
#include "xfs_ioctl.h"
+#include "xfs_parent_utils.h"
#include "xfs_xattr.h"
#include <linux/mount.h>
@@ -1676,6 +1677,137 @@ xfs_ioc_scrub_metadata(
return 0;
}
+/*
+ * IOCTL routine to get the parent pointers of an inode and return it to user
+ * space. Caller must pass a buffer space containing a struct xfs_pptr_info,
+ * followed by a region large enough to contain an array of struct
+ * xfs_parent_ptr of a size specified in pi_ptrs_size. If the inode contains
+ * more parent pointers than can fit in the buffer space, caller may re-call
+ * the function using the returned pi_cursor to resume iteration. The
+ * number of xfs_parent_ptr returned will be stored in pi_ptrs_count.
+ *
+ * Returns 0 on success or non-zero on failure
+ */
+STATIC int
+xfs_ioc_get_parent_pointer(
+ struct file *filp,
+ void __user *arg)
+{
+ struct xfs_pptr_info *ppi = NULL;
+ int error = 0;
+ struct xfs_inode *file_ip = XFS_I(file_inode(filp));
+ struct xfs_inode *call_ip = file_ip;
+ struct xfs_mount *mp = file_ip->i_mount;
+ void __user *o_pptr;
+ struct xfs_parent_ptr *i_pptr;
+ unsigned int bytes;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ /* Allocate an xfs_pptr_info to put the user data */
+ ppi = kvmalloc(sizeof(struct xfs_pptr_info), GFP_KERNEL);
+ if (!ppi)
+ return -ENOMEM;
+
+ /* Copy the data from the user */
+ error = copy_from_user(ppi, arg, sizeof(struct xfs_pptr_info));
+ if (error) {
+ error = -EFAULT;
+ goto out;
+ }
+
+ /* Check size of buffer requested by user */
+ if (ppi->pi_ptrs_size > XFS_XATTR_LIST_MAX) {
+ error = -ENOMEM;
+ goto out;
+ }
+ if (ppi->pi_ptrs_size < sizeof(struct xfs_pptr_info)) {
+ error = -EINVAL;
+ goto out;
+ }
+
+ if (ppi->pi_flags & ~XFS_PPTR_FLAG_ALL) {
+ error = -EINVAL;
+ goto out;
+ }
+ ppi->pi_flags &= ~(XFS_PPTR_OFLAG_ROOT | XFS_PPTR_OFLAG_DONE);
+
+ /*
+ * Now that we know how big the trailing buffer is, expand
+ * our kernel xfs_pptr_info to be the same size
+ */
+ ppi = kvrealloc(ppi, sizeof(struct xfs_pptr_info),
+ xfs_pptr_info_sizeof(ppi->pi_ptrs_size),
+ GFP_KERNEL | __GFP_ZERO);
+ if (!ppi)
+ return -ENOMEM;
+
+ if (ppi->pi_flags & XFS_PPTR_IFLAG_HANDLE) {
+ struct xfs_handle *hanp = &ppi->pi_handle;
+
+ if (memcmp(&hanp->ha_fsid, mp->m_fixedfsid,
+ sizeof(xfs_fsid_t))) {
+ error = -EINVAL;
+ goto out;
+ }
+
+ if (hanp->ha_fid.fid_ino != file_ip->i_ino) {
+ error = xfs_iget(mp, NULL, hanp->ha_fid.fid_ino,
+ XFS_IGET_UNTRUSTED, 0, &call_ip);
+ if (error)
+ goto out;
+ }
+
+ if (VFS_I(call_ip)->i_generation != hanp->ha_fid.fid_gen) {
+ error = -EINVAL;
+ goto out;
+ }
+ }
+
+ /* Get the parent pointers */
+ error = xfs_getparent_pointers(call_ip, ppi);
+ if (error)
+ goto out;
+
+ /*
+ * If we ran out of buffer space before copying any parent pointers at
+ * all, the caller's buffer was too short. Tell userspace that, erm,
+ * the message is too long.
+ */
+ if (ppi->pi_count == 0 && !(ppi->pi_flags & XFS_PPTR_OFLAG_DONE)) {
+ error = -EMSGSIZE;
+ goto out;
+ }
+
+ /* Copy the parent pointer head back to the user */
+ bytes = xfs_getparents_arraytop(ppi, ppi->pi_count);
+ error = copy_to_user(arg, ppi, bytes);
+ if (error) {
+ error = -EFAULT;
+ goto out;
+ }
+
+ if (ppi->pi_count == 0)
+ goto out;
+
+ /* Copy the parent pointer records back to the user. */
+ o_pptr = (__user char*)arg + ppi->pi_offsets[ppi->pi_count - 1];
+ i_pptr = xfs_ppinfo_to_pp(ppi, ppi->pi_count - 1);
+ bytes = ((char *)ppi + ppi->pi_ptrs_size) - (char *)i_pptr;
+ error = copy_to_user(o_pptr, i_pptr, bytes);
+ if (error) {
+ error = -EFAULT;
+ goto out;
+ }
+
+out:
+ if (call_ip != file_ip)
+ xfs_irele(call_ip);
+ kvfree(ppi);
+ return error;
+}
+
int
xfs_ioc_swapext(
xfs_swapext_t *sxp)
@@ -1965,7 +2097,8 @@ xfs_file_ioctl(
case XFS_IOC_FSGETXATTRA:
return xfs_ioc_fsgetxattra(ip, arg);
-
+ case XFS_IOC_GETPARENTS:
+ return xfs_ioc_get_parent_pointer(filp, arg);
case XFS_IOC_GETBMAP:
case XFS_IOC_GETBMAPA:
case XFS_IOC_GETBMAPX:
@@ -150,6 +150,10 @@ xfs_check_ondisk_structs(void)
XFS_CHECK_OFFSET(struct xfs_efi_log_format_32, efi_extents, 16);
XFS_CHECK_OFFSET(struct xfs_efi_log_format_64, efi_extents, 16);
+ /* parent pointer ioctls */
+ XFS_CHECK_STRUCT_SIZE(struct xfs_parent_ptr, 24);
+ XFS_CHECK_STRUCT_SIZE(struct xfs_pptr_info, 96);
+
/*
* The v5 superblock format extended several v4 header structures with
* additional data. While new fields are only accessible on v5
new file mode 100644
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022 Oracle, Inc.
+ * All rights reserved.
+ */
+#include "xfs.h"
+#include "xfs_fs.h"
+#include "xfs_format.h"
+#include "xfs_log_format.h"
+#include "xfs_shared.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_bmap_btree.h"
+#include "xfs_inode.h"
+#include "xfs_error.h"
+#include "xfs_trace.h"
+#include "xfs_trans.h"
+#include "xfs_da_format.h"
+#include "xfs_da_btree.h"
+#include "xfs_attr.h"
+#include "xfs_ioctl.h"
+#include "xfs_parent.h"
+#include "xfs_da_btree.h"
+#include "xfs_parent_utils.h"
+
+struct xfs_getparent_ctx {
+ struct xfs_attr_list_context context;
+ struct xfs_parent_name_irec pptr_irec;
+ struct xfs_pptr_info *ppi;
+};
+
+static inline unsigned int
+xfs_getparents_rec_sizeof(
+ const struct xfs_parent_name_irec *irec)
+{
+ return round_up(sizeof(struct xfs_parent_ptr) + irec->p_namelen + 1,
+ sizeof(uint32_t));
+}
+
+static void
+xfs_getparent_listent(
+ struct xfs_attr_list_context *context,
+ int flags,
+ unsigned char *name,
+ int namelen,
+ void *value,
+ int valuelen)
+{
+ struct xfs_getparent_ctx *gp;
+ struct xfs_pptr_info *ppi;
+ struct xfs_parent_ptr *pptr;
+ struct xfs_parent_name_irec *irec;
+ struct xfs_mount *mp = context->dp->i_mount;
+ int arraytop;
+
+ gp = container_of(context, struct xfs_getparent_ctx, context);
+ ppi = gp->ppi;
+ irec = &gp->pptr_irec;
+
+ /* Ignore non-parent xattrs */
+ if (!(flags & XFS_ATTR_PARENT))
+ return;
+
+ /*
+ * Report corruption for xattrs with any other flag set, or for a
+ * parent pointer that has a remote value. The attr list functions
+ * filtered any INCOMPLETE attrs for us.
+ */
+ if (XFS_IS_CORRUPT(mp,
+ hweight32(flags & XFS_ATTR_NSP_ONDISK_MASK) > 1) ||
+ XFS_IS_CORRUPT(mp, value == NULL)) {
+ context->seen_enough = -EFSCORRUPTED;
+ return;
+ }
+
+ xfs_parent_irec_from_disk(&gp->pptr_irec, (void *)name, value,
+ valuelen);
+
+ /*
+ * We found a parent pointer, but we've filled up the buffer. Signal
+ * to the caller that we did /not/ reach the end of the parent pointer
+ * recordset.
+ */
+ arraytop = xfs_getparents_arraytop(ppi, ppi->pi_count + 1);
+ context->firstu -= xfs_getparents_rec_sizeof(irec);
+ if (context->firstu < arraytop) {
+ context->seen_enough = 1;
+ return;
+ }
+
+ /* Format the parent pointer directly into the caller buffer. */
+ ppi->pi_offsets[ppi->pi_count] = context->firstu;
+ pptr = xfs_ppinfo_to_pp(ppi, ppi->pi_count);
+ pptr->xpp_ino = irec->p_ino;
+ pptr->xpp_gen = irec->p_gen;
+ pptr->xpp_diroffset = irec->p_diroffset;
+ pptr->xpp_rsvd = 0;
+
+ memcpy(pptr->xpp_name, irec->p_name, irec->p_namelen);
+ pptr->xpp_name[irec->p_namelen] = 0;
+ ppi->pi_count++;
+}
+
+/* Retrieve the parent pointers for a given inode. */
+int
+xfs_getparent_pointers(
+ struct xfs_inode *ip,
+ struct xfs_pptr_info *ppi)
+{
+ struct xfs_getparent_ctx *gp;
+ int error;
+
+ gp = kzalloc(sizeof(struct xfs_getparent_ctx), GFP_KERNEL);
+ if (!gp)
+ return -ENOMEM;
+ gp->ppi = ppi;
+ gp->context.dp = ip;
+ gp->context.resynch = 1;
+ gp->context.put_listent = xfs_getparent_listent;
+ gp->context.bufsize = round_down(ppi->pi_ptrs_size, sizeof(uint32_t));
+ gp->context.firstu = gp->context.bufsize;
+
+ /* Copy the cursor provided by caller */
+ memcpy(&gp->context.cursor, &ppi->pi_cursor,
+ sizeof(struct xfs_attrlist_cursor));
+ ppi->pi_count = 0;
+
+ error = xfs_attr_list(&gp->context);
+ if (error)
+ goto out_free;
+ if (gp->context.seen_enough < 0) {
+ error = gp->context.seen_enough;
+ goto out_free;
+ }
+
+ /* Is this the root directory? */
+ if (ip->i_ino == ip->i_mount->m_sb.sb_rootino)
+ ppi->pi_flags |= XFS_PPTR_OFLAG_ROOT;
+
+ /*
+ * If we did not run out of buffer space, then we reached the end of
+ * the pptr recordset, so set the DONE flag.
+ */
+ if (gp->context.seen_enough == 0)
+ ppi->pi_flags |= XFS_PPTR_OFLAG_DONE;
+
+ /* Update the caller with the current cursor position */
+ memcpy(&ppi->pi_cursor, &gp->context.cursor,
+ sizeof(struct xfs_attrlist_cursor));
+out_free:
+ kfree(gp);
+ return error;
+}
+
new file mode 100644
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022 Oracle, Inc.
+ * All rights reserved.
+ */
+#ifndef __XFS_PARENT_UTILS_H__
+#define __XFS_PARENT_UTILS_H__
+
+static inline unsigned int
+xfs_getparents_arraytop(
+ const struct xfs_pptr_info *ppi,
+ unsigned int nr)
+{
+ return sizeof(struct xfs_pptr_info) +
+ (nr * sizeof(ppi->pi_offsets[0]));
+}
+
+int xfs_getparent_pointers(struct xfs_inode *ip, struct xfs_pptr_info *ppi);
+
+#endif /* __XFS_PARENT_UTILS_H__ */