summaryrefslogtreecommitdiff
path: root/fs/xfs/xfs_ioctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/xfs/xfs_ioctl.c')
-rw-r--r--fs/xfs/xfs_ioctl.c135
1 files changed, 135 insertions, 0 deletions
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index d5597d7b98c0..7ff325d088ae 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -31,6 +31,8 @@
#include "xfs_btree.h"
#include <linux/fsmap.h>
#include "xfs_fsmap.h"
+#include <linux/fsrefcounts.h>
+#include "xfs_fsrefs.h"
#include "scrub/xfs_scrub.h"
#include "xfs_sb.h"
#include "xfs_ag.h"
@@ -1626,6 +1628,136 @@ out_free:
}
STATIC int
+xfs_ioc_getfsrefcounts(
+ struct xfs_inode *ip,
+ struct fsrefs_head __user *arg)
+{
+ struct xfs_fsrefs_head xhead = {0};
+ struct fsrefs_head head;
+ struct fsrefs *recs;
+ unsigned int count;
+ __u32 last_flags = 0;
+ bool done = false;
+ int error;
+
+ if (copy_from_user(&head, arg, sizeof(struct fsrefs_head)))
+ return -EFAULT;
+ if (memchr_inv(head.fch_reserved, 0, sizeof(head.fch_reserved)) ||
+ memchr_inv(head.fch_keys[0].fcr_reserved, 0,
+ sizeof(head.fch_keys[0].fcr_reserved)) ||
+ memchr_inv(head.fch_keys[1].fcr_reserved, 0,
+ sizeof(head.fch_keys[1].fcr_reserved)))
+ return -EINVAL;
+
+ /*
+ * Use an internal memory buffer so that we don't have to copy fsrefs
+ * data to userspace while holding locks. Start by trying to allocate
+ * up to 128k for the buffer, but fall back to a single page if needed.
+ */
+ count = min_t(unsigned int, head.fch_count,
+ 131072 / sizeof(struct fsrefs));
+ recs = kvzalloc(count * sizeof(struct fsrefs), GFP_KERNEL);
+ if (!recs) {
+ count = min_t(unsigned int, head.fch_count,
+ PAGE_SIZE / sizeof(struct fsrefs));
+ recs = kvzalloc(count * sizeof(struct fsrefs), GFP_KERNEL);
+ if (!recs)
+ return -ENOMEM;
+ }
+
+ xhead.fch_iflags = head.fch_iflags;
+ xfs_fsrefs_to_internal(&xhead.fch_keys[0], &head.fch_keys[0]);
+ xfs_fsrefs_to_internal(&xhead.fch_keys[1], &head.fch_keys[1]);
+
+ trace_xfs_getfsrefs_low_key(ip->i_mount, &xhead.fch_keys[0]);
+ trace_xfs_getfsrefs_high_key(ip->i_mount, &xhead.fch_keys[1]);
+
+ head.fch_entries = 0;
+ do {
+ struct fsrefs __user *user_recs;
+ struct fsrefs *last_rec;
+
+ user_recs = &arg->fch_recs[head.fch_entries];
+ xhead.fch_entries = 0;
+ xhead.fch_count = min_t(unsigned int, count,
+ head.fch_count - head.fch_entries);
+
+ /* Run query, record how many entries we got. */
+ error = xfs_getfsrefs(ip->i_mount, &xhead, recs);
+ switch (error) {
+ case 0:
+ /*
+ * There are no more records in the result set. Copy
+ * whatever we got to userspace and break out.
+ */
+ done = true;
+ break;
+ case -ECANCELED:
+ /*
+ * The internal memory buffer is full. Copy whatever
+ * records we got to userspace and go again if we have
+ * not yet filled the userspace buffer.
+ */
+ error = 0;
+ break;
+ default:
+ goto out_free;
+ }
+ head.fch_entries += xhead.fch_entries;
+ head.fch_oflags = xhead.fch_oflags;
+
+ /*
+ * If the caller wanted a record count or there aren't any
+ * new records to return, we're done.
+ */
+ if (head.fch_count == 0 || xhead.fch_entries == 0)
+ break;
+
+ /* Copy all the records we got out to userspace. */
+ if (copy_to_user(user_recs, recs,
+ xhead.fch_entries * sizeof(struct fsrefs))) {
+ error = -EFAULT;
+ goto out_free;
+ }
+
+ /* Remember the last record flags we copied to userspace. */
+ last_rec = &recs[xhead.fch_entries - 1];
+ last_flags = last_rec->fcr_flags;
+
+ /* Set up the low key for the next iteration. */
+ xfs_fsrefs_to_internal(&xhead.fch_keys[0], last_rec);
+ trace_xfs_getfsrefs_low_key(ip->i_mount, &xhead.fch_keys[0]);
+ } while (!done && head.fch_entries < head.fch_count);
+
+ /*
+ * If there are no more records in the query result set and we're not
+ * in counting mode, mark the last record returned with the LAST flag.
+ */
+ if (done && head.fch_count > 0 && head.fch_entries > 0) {
+ struct fsrefs __user *user_rec;
+
+ last_flags |= FCR_OF_LAST;
+ user_rec = &arg->fch_recs[head.fch_entries - 1];
+
+ if (copy_to_user(&user_rec->fcr_flags, &last_flags,
+ sizeof(last_flags))) {
+ error = -EFAULT;
+ goto out_free;
+ }
+ }
+
+ /* copy back header */
+ if (copy_to_user(arg, &head, sizeof(struct fsrefs_head))) {
+ error = -EFAULT;
+ goto out_free;
+ }
+
+out_free:
+ kmem_free(recs);
+ return error;
+}
+
+STATIC int
xfs_ioc_scrub_metadata(
struct file *file,
void __user *arg)
@@ -1959,6 +2091,9 @@ xfs_file_ioctl(
case FS_IOC_GETFSMAP:
return xfs_ioc_getfsmap(ip, arg);
+ case FS_IOC_GETFSREFCOUNTS:
+ return xfs_ioc_getfsrefcounts(ip, arg);
+
case XFS_IOC_SCRUBV_METADATA:
return xfs_ioc_scrubv_metadata(filp, arg);
case XFS_IOC_SCRUB_METADATA: