| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2019 Oracle. All Rights Reserved. |
| * Author: Darrick J. Wong <darrick.wong@oracle.com> |
| */ |
| #include <string.h> |
| #include <strings.h> |
| #include "xfs.h" |
| #include "fsgeom.h" |
| #include "bulkstat.h" |
| |
| /* |
| * Wrapper functions for BULKSTAT and INUMBERS |
| * =========================================== |
| * |
| * The functions in this file are thin wrappers around the most recent version |
| * of the BULKSTAT and INUMBERS ioctls. BULKSTAT is used to query XFS-specific |
| * stat information about a group of inodes. INUMBERS is used to query |
| * allocation information about batches of XFS inodes. |
| * |
| * At the moment, the public xfrog_* functions provide all functionality of the |
| * V5 interface. If the V5 interface is not available on the running kernel, |
| * the functions will emulate them as best they can with previous versions of |
| * the interface (currently V1). If emulation is not possible, EINVAL will be |
| * returned. |
| * |
| * The XFROG_FLAG_BULKSTAT_FORCE_V[15] flags can be used to force use of a |
| * particular version of the kernel interface for testing. |
| */ |
| |
| /* |
| * Grab the fs geometry information that is needed to needed to emulate v5 with |
| * v1 interfaces. |
| */ |
| static inline int |
| xfrog_bulkstat_prep_v1_emulation( |
| struct xfs_fd *xfd) |
| { |
| if (xfd->fsgeom.blocksize > 0) |
| return 0; |
| |
| return xfd_prepare_geometry(xfd); |
| } |
| |
| /* Bulkstat a single inode using v5 ioctl. */ |
| static int |
| xfrog_bulkstat_single5( |
| struct xfs_fd *xfd, |
| uint64_t ino, |
| unsigned int flags, |
| struct xfs_bulkstat *bulkstat) |
| { |
| struct xfs_bulkstat_req *req; |
| int ret; |
| |
| if (flags & ~(XFS_BULK_IREQ_SPECIAL)) |
| return -EINVAL; |
| |
| if (xfd->fsgeom.flags & XFS_FSOP_GEOM_FLAGS_NREXT64) |
| flags |= XFS_BULK_IREQ_NREXT64; |
| |
| ret = xfrog_bulkstat_alloc_req(1, ino, &req); |
| if (ret) |
| return ret; |
| |
| req->hdr.flags = flags; |
| ret = ioctl(xfd->fd, XFS_IOC_BULKSTAT, req); |
| if (ret) { |
| ret = -errno; |
| goto free; |
| } |
| |
| if (req->hdr.ocount == 0) { |
| ret = -ENOENT; |
| goto free; |
| } |
| |
| memcpy(bulkstat, req->bulkstat, sizeof(struct xfs_bulkstat)); |
| |
| if (!(xfd->fsgeom.flags & XFS_FSOP_GEOM_FLAGS_NREXT64)) { |
| bulkstat->bs_extents64 = bulkstat->bs_extents; |
| bulkstat->bs_extents = 0; |
| } |
| |
| free: |
| free(req); |
| return ret; |
| } |
| |
| /* Bulkstat a single inode using v1 ioctl. */ |
| static int |
| xfrog_bulkstat_single1( |
| struct xfs_fd *xfd, |
| uint64_t ino, |
| unsigned int flags, |
| struct xfs_bulkstat *bulkstat) |
| { |
| struct xfs_bstat bstat; |
| struct xfs_fsop_bulkreq bulkreq = { 0 }; |
| int error; |
| |
| if (flags) |
| return -EINVAL; |
| |
| error = xfrog_bulkstat_prep_v1_emulation(xfd); |
| if (error) |
| return error; |
| |
| bulkreq.lastip = (__u64 *)&ino; |
| bulkreq.icount = 1; |
| bulkreq.ubuffer = &bstat; |
| error = ioctl(xfd->fd, XFS_IOC_FSBULKSTAT_SINGLE, &bulkreq); |
| if (error) |
| return -errno; |
| |
| xfrog_bulkstat_v1_to_v5(xfd, bulkstat, &bstat); |
| return 0; |
| } |
| |
| /* Bulkstat a single inode. Returns zero or a negative error code. */ |
| int |
| xfrog_bulkstat_single( |
| struct xfs_fd *xfd, |
| uint64_t ino, |
| unsigned int flags, |
| struct xfs_bulkstat *bulkstat) |
| { |
| int error; |
| |
| if (xfd->flags & XFROG_FLAG_BULKSTAT_FORCE_V1) |
| goto try_v1; |
| |
| error = xfrog_bulkstat_single5(xfd, ino, flags, bulkstat); |
| if (error == 0 || (xfd->flags & XFROG_FLAG_BULKSTAT_FORCE_V5)) |
| return error; |
| |
| /* If the v5 ioctl wasn't found, we punt to v1. */ |
| switch (error) { |
| case -EOPNOTSUPP: |
| case -ENOTTY: |
| assert(!(xfd->fsgeom.flags & XFS_FSOP_GEOM_FLAGS_NREXT64)); |
| xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V1; |
| break; |
| } |
| |
| try_v1: |
| return xfrog_bulkstat_single1(xfd, ino, flags, bulkstat); |
| } |
| |
| /* |
| * Set up the necessary control structures to emulate a V5 bulk request ioctl |
| * by calling a V1 bulk request ioctl. This enables callers to run on older |
| * kernels. |
| * |
| * Returns 0 if the emulation should proceed; ECANCELED if there are no |
| * records; or a negative error code. |
| */ |
| static int |
| xfrog_bulk_req_v1_setup( |
| struct xfs_fd *xfd, |
| struct xfs_bulk_ireq *hdr, |
| struct xfs_fsop_bulkreq *bulkreq, |
| size_t rec_size) |
| { |
| void *buf; |
| |
| if (hdr->flags & XFS_BULK_IREQ_AGNO) { |
| uint32_t agno = cvt_ino_to_agno(xfd, hdr->ino); |
| |
| if (hdr->ino == 0) |
| hdr->ino = cvt_agino_to_ino(xfd, hdr->agno, 0); |
| else if (agno < hdr->agno) |
| return -EINVAL; |
| else if (agno > hdr->agno) |
| goto no_results; |
| } |
| |
| if (cvt_ino_to_agno(xfd, hdr->ino) > xfd->fsgeom.agcount) |
| goto no_results; |
| |
| buf = malloc(hdr->icount * rec_size); |
| if (!buf) |
| return -errno; |
| |
| if (hdr->ino) |
| hdr->ino--; |
| bulkreq->lastip = (__u64 *)&hdr->ino, |
| bulkreq->icount = hdr->icount, |
| bulkreq->ocount = (__s32 *)&hdr->ocount, |
| bulkreq->ubuffer = buf; |
| return 0; |
| |
| no_results: |
| hdr->ocount = 0; |
| return -ECANCELED; |
| } |
| |
| /* |
| * Clean up after using a V1 bulk request to emulate a V5 bulk request call. |
| * |
| * If the ioctl was successful, we need to convert the returned V1-format bulk |
| * request data into the V5-format bulk request data and copy it into the |
| * caller's buffer. We also need to free all resources allocated during the |
| * setup setup. |
| */ |
| static int |
| xfrog_bulk_req_v1_cleanup( |
| struct xfs_fd *xfd, |
| struct xfs_bulk_ireq *hdr, |
| struct xfs_fsop_bulkreq *bulkreq, |
| size_t v1_rec_size, |
| uint64_t (*v1_ino)(void *v1_rec), |
| void *v5_records, |
| size_t v5_rec_size, |
| void (*cvt)(struct xfs_fd *xfd, void *v5, void *v1), |
| unsigned int startino_adj, |
| int error) |
| { |
| void *v1_rec = bulkreq->ubuffer; |
| void *v5_rec = v5_records; |
| unsigned int i; |
| |
| if (error == -ECANCELED) { |
| error = 0; |
| goto free; |
| } |
| if (error) |
| goto free; |
| |
| /* |
| * Convert each record from v1 to v5 format, keeping the startino |
| * value up to date and (if desired) stopping at the end of the |
| * AG. |
| */ |
| for (i = 0; |
| i < hdr->ocount; |
| i++, v1_rec += v1_rec_size, v5_rec += v5_rec_size) { |
| uint64_t ino = v1_ino(v1_rec); |
| |
| /* Stop if we hit a different AG. */ |
| if ((hdr->flags & XFS_BULK_IREQ_AGNO) && |
| cvt_ino_to_agno(xfd, ino) != hdr->agno) { |
| hdr->ocount = i; |
| break; |
| } |
| cvt(xfd, v5_rec, v1_rec); |
| hdr->ino = ino + startino_adj; |
| } |
| |
| free: |
| free(bulkreq->ubuffer); |
| return error; |
| } |
| |
| static uint64_t xfrog_bstat_ino(void *v1_rec) |
| { |
| return ((struct xfs_bstat *)v1_rec)->bs_ino; |
| } |
| |
| static void xfrog_bstat_cvt(struct xfs_fd *xfd, void *v5, void *v1) |
| { |
| xfrog_bulkstat_v1_to_v5(xfd, v5, v1); |
| } |
| |
| /* Bulkstat a bunch of inodes using the v5 interface. */ |
| static int |
| xfrog_bulkstat5( |
| struct xfs_fd *xfd, |
| struct xfs_bulkstat_req *req) |
| { |
| int ret; |
| int i; |
| |
| if (xfd->fsgeom.flags & XFS_FSOP_GEOM_FLAGS_NREXT64) |
| req->hdr.flags |= XFS_BULK_IREQ_NREXT64; |
| |
| ret = ioctl(xfd->fd, XFS_IOC_BULKSTAT, req); |
| if (ret) |
| return -errno; |
| |
| if (!(xfd->fsgeom.flags & XFS_FSOP_GEOM_FLAGS_NREXT64)) { |
| for (i = 0; i < req->hdr.ocount; i++) { |
| req->bulkstat[i].bs_extents64 = |
| req->bulkstat[i].bs_extents; |
| req->bulkstat[i].bs_extents = 0; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Bulkstat a bunch of inodes using the v1 interface. */ |
| static int |
| xfrog_bulkstat1( |
| struct xfs_fd *xfd, |
| struct xfs_bulkstat_req *req) |
| { |
| struct xfs_fsop_bulkreq bulkreq = { 0 }; |
| int error; |
| |
| error = xfrog_bulkstat_prep_v1_emulation(xfd); |
| if (error) |
| return error; |
| |
| error = xfrog_bulk_req_v1_setup(xfd, &req->hdr, &bulkreq, |
| sizeof(struct xfs_bstat)); |
| if (error == -ECANCELED) |
| goto out_teardown; |
| if (error) |
| return error; |
| |
| error = ioctl(xfd->fd, XFS_IOC_FSBULKSTAT, &bulkreq); |
| if (error) |
| error = -errno; |
| |
| out_teardown: |
| return xfrog_bulk_req_v1_cleanup(xfd, &req->hdr, &bulkreq, |
| sizeof(struct xfs_bstat), xfrog_bstat_ino, |
| &req->bulkstat, sizeof(struct xfs_bulkstat), |
| xfrog_bstat_cvt, 1, error); |
| } |
| |
| /* Bulkstat a bunch of inodes. Returns zero or a positive error code. */ |
| int |
| xfrog_bulkstat( |
| struct xfs_fd *xfd, |
| struct xfs_bulkstat_req *req) |
| { |
| int error; |
| |
| if (xfd->flags & XFROG_FLAG_BULKSTAT_FORCE_V1) |
| goto try_v1; |
| |
| error = xfrog_bulkstat5(xfd, req); |
| if (error == 0 || (xfd->flags & XFROG_FLAG_BULKSTAT_FORCE_V5)) |
| return error; |
| |
| /* If the v5 ioctl wasn't found, we punt to v1. */ |
| switch (error) { |
| case -EOPNOTSUPP: |
| case -ENOTTY: |
| assert(!(xfd->fsgeom.flags & XFS_FSOP_GEOM_FLAGS_NREXT64)); |
| xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V1; |
| break; |
| } |
| |
| try_v1: |
| return xfrog_bulkstat1(xfd, req); |
| } |
| |
| static bool |
| time_too_big( |
| uint64_t time) |
| { |
| time_t TIME_MAX; |
| |
| memset(&TIME_MAX, 0xFF, sizeof(TIME_MAX)); |
| return time > TIME_MAX; |
| } |
| |
| /* Convert bulkstat data from v5 format to v1 format. */ |
| int |
| xfrog_bulkstat_v5_to_v1( |
| struct xfs_fd *xfd, |
| struct xfs_bstat *bs1, |
| const struct xfs_bulkstat *bs5) |
| { |
| if (bs5->bs_aextents > UINT16_MAX || |
| bs5->bs_extents64 > INT32_MAX || |
| cvt_off_fsb_to_b(xfd, bs5->bs_extsize_blks) > UINT32_MAX || |
| cvt_off_fsb_to_b(xfd, bs5->bs_cowextsize_blks) > UINT32_MAX || |
| time_too_big(bs5->bs_atime) || |
| time_too_big(bs5->bs_ctime) || |
| time_too_big(bs5->bs_mtime)) |
| return -ERANGE; |
| |
| bs1->bs_ino = bs5->bs_ino; |
| bs1->bs_mode = bs5->bs_mode; |
| bs1->bs_nlink = bs5->bs_nlink; |
| bs1->bs_uid = bs5->bs_uid; |
| bs1->bs_gid = bs5->bs_gid; |
| bs1->bs_rdev = bs5->bs_rdev; |
| bs1->bs_blksize = bs5->bs_blksize; |
| bs1->bs_size = bs5->bs_size; |
| bs1->bs_atime.tv_sec = bs5->bs_atime; |
| bs1->bs_mtime.tv_sec = bs5->bs_mtime; |
| bs1->bs_ctime.tv_sec = bs5->bs_ctime; |
| bs1->bs_atime.tv_nsec = bs5->bs_atime_nsec; |
| bs1->bs_mtime.tv_nsec = bs5->bs_mtime_nsec; |
| bs1->bs_ctime.tv_nsec = bs5->bs_ctime_nsec; |
| bs1->bs_blocks = bs5->bs_blocks; |
| bs1->bs_xflags = bs5->bs_xflags; |
| bs1->bs_extsize = cvt_off_fsb_to_b(xfd, bs5->bs_extsize_blks); |
| bs1->bs_extents = bs5->bs_extents64; |
| bs1->bs_gen = bs5->bs_gen; |
| bs1->bs_projid_lo = bs5->bs_projectid & 0xFFFF; |
| bs1->bs_forkoff = bs5->bs_forkoff; |
| bs1->bs_projid_hi = bs5->bs_projectid >> 16; |
| bs1->bs_sick = bs5->bs_sick; |
| bs1->bs_checked = bs5->bs_checked; |
| bs1->bs_cowextsize = cvt_off_fsb_to_b(xfd, bs5->bs_cowextsize_blks); |
| bs1->bs_dmevmask = 0; |
| bs1->bs_dmstate = 0; |
| bs1->bs_aextents = bs5->bs_aextents; |
| return 0; |
| } |
| |
| /* Convert bulkstat data from v1 format to v5 format. */ |
| void |
| xfrog_bulkstat_v1_to_v5( |
| struct xfs_fd *xfd, |
| struct xfs_bulkstat *bs5, |
| const struct xfs_bstat *bs1) |
| { |
| memset(bs5, 0, sizeof(*bs5)); |
| bs5->bs_version = XFS_BULKSTAT_VERSION_V1; |
| |
| bs5->bs_ino = bs1->bs_ino; |
| bs5->bs_mode = bs1->bs_mode; |
| bs5->bs_nlink = bs1->bs_nlink; |
| bs5->bs_uid = bs1->bs_uid; |
| bs5->bs_gid = bs1->bs_gid; |
| bs5->bs_rdev = bs1->bs_rdev; |
| bs5->bs_blksize = bs1->bs_blksize; |
| bs5->bs_size = bs1->bs_size; |
| bs5->bs_atime = bs1->bs_atime.tv_sec; |
| bs5->bs_mtime = bs1->bs_mtime.tv_sec; |
| bs5->bs_ctime = bs1->bs_ctime.tv_sec; |
| bs5->bs_atime_nsec = bs1->bs_atime.tv_nsec; |
| bs5->bs_mtime_nsec = bs1->bs_mtime.tv_nsec; |
| bs5->bs_ctime_nsec = bs1->bs_ctime.tv_nsec; |
| bs5->bs_blocks = bs1->bs_blocks; |
| bs5->bs_xflags = bs1->bs_xflags; |
| bs5->bs_extsize_blks = cvt_b_to_off_fsbt(xfd, bs1->bs_extsize); |
| bs5->bs_gen = bs1->bs_gen; |
| bs5->bs_projectid = bstat_get_projid(bs1); |
| bs5->bs_forkoff = bs1->bs_forkoff; |
| bs5->bs_sick = bs1->bs_sick; |
| bs5->bs_checked = bs1->bs_checked; |
| bs5->bs_cowextsize_blks = cvt_b_to_off_fsbt(xfd, bs1->bs_cowextsize); |
| bs5->bs_aextents = bs1->bs_aextents; |
| bs5->bs_extents64 = bs1->bs_extents; |
| } |
| |
| /* Allocate a bulkstat request. Returns zero or a negative error code. */ |
| int |
| xfrog_bulkstat_alloc_req( |
| uint32_t nr, |
| uint64_t startino, |
| struct xfs_bulkstat_req **preq) |
| { |
| struct xfs_bulkstat_req *breq; |
| |
| breq = calloc(1, XFS_BULKSTAT_REQ_SIZE(nr)); |
| if (!breq) |
| return -ENOMEM; |
| |
| breq->hdr.icount = nr; |
| breq->hdr.ino = startino; |
| |
| *preq = breq; |
| return 0; |
| } |
| |
| /* Set a bulkstat cursor to iterate only a particular AG. */ |
| void |
| xfrog_bulkstat_set_ag( |
| struct xfs_bulkstat_req *req, |
| uint32_t agno) |
| { |
| req->hdr.agno = agno; |
| req->hdr.flags |= XFS_BULK_IREQ_AGNO; |
| } |
| |
| /* Convert a inumbers data from v5 format to v1 format. */ |
| void |
| xfrog_inumbers_v5_to_v1( |
| struct xfs_inogrp *ig1, |
| const struct xfs_inumbers *ig5) |
| { |
| ig1->xi_startino = ig5->xi_startino; |
| ig1->xi_alloccount = ig5->xi_alloccount; |
| ig1->xi_allocmask = ig5->xi_allocmask; |
| } |
| |
| /* Convert a inumbers data from v1 format to v5 format. */ |
| void |
| xfrog_inumbers_v1_to_v5( |
| struct xfs_inumbers *ig5, |
| const struct xfs_inogrp *ig1) |
| { |
| memset(ig5, 0, sizeof(*ig5)); |
| ig5->xi_version = XFS_INUMBERS_VERSION_V1; |
| |
| ig5->xi_startino = ig1->xi_startino; |
| ig5->xi_alloccount = ig1->xi_alloccount; |
| ig5->xi_allocmask = ig1->xi_allocmask; |
| } |
| |
| static uint64_t xfrog_inum_ino(void *v1_rec) |
| { |
| return ((struct xfs_inogrp *)v1_rec)->xi_startino; |
| } |
| |
| static void xfrog_inum_cvt(struct xfs_fd *xfd, void *v5, void *v1) |
| { |
| xfrog_inumbers_v1_to_v5(v5, v1); |
| } |
| |
| /* Query inode allocation bitmask information using v5 ioctl. */ |
| static int |
| xfrog_inumbers5( |
| struct xfs_fd *xfd, |
| struct xfs_inumbers_req *req) |
| { |
| int ret; |
| |
| ret = ioctl(xfd->fd, XFS_IOC_INUMBERS, req); |
| if (ret) |
| return -errno; |
| return 0; |
| } |
| |
| /* Query inode allocation bitmask information using v1 ioctl. */ |
| static int |
| xfrog_inumbers1( |
| struct xfs_fd *xfd, |
| struct xfs_inumbers_req *req) |
| { |
| struct xfs_fsop_bulkreq bulkreq = { 0 }; |
| int error; |
| |
| error = xfrog_bulkstat_prep_v1_emulation(xfd); |
| if (error) |
| return error; |
| |
| error = xfrog_bulk_req_v1_setup(xfd, &req->hdr, &bulkreq, |
| sizeof(struct xfs_inogrp)); |
| if (error == -ECANCELED) |
| goto out_teardown; |
| if (error) |
| return error; |
| |
| error = ioctl(xfd->fd, XFS_IOC_FSINUMBERS, &bulkreq); |
| if (error) |
| error = -errno; |
| |
| out_teardown: |
| return xfrog_bulk_req_v1_cleanup(xfd, &req->hdr, &bulkreq, |
| sizeof(struct xfs_inogrp), xfrog_inum_ino, |
| &req->inumbers, sizeof(struct xfs_inumbers), |
| xfrog_inum_cvt, 64, error); |
| } |
| |
| /* |
| * Query inode allocation bitmask information. Returns zero or a negative |
| * error code. |
| */ |
| int |
| xfrog_inumbers( |
| struct xfs_fd *xfd, |
| struct xfs_inumbers_req *req) |
| { |
| int error; |
| |
| if (xfd->flags & XFROG_FLAG_BULKSTAT_FORCE_V1) |
| goto try_v1; |
| |
| error = xfrog_inumbers5(xfd, req); |
| if (error == 0 || (xfd->flags & XFROG_FLAG_BULKSTAT_FORCE_V5)) |
| return error; |
| |
| /* If the v5 ioctl wasn't found, we punt to v1. */ |
| switch (error) { |
| case -EOPNOTSUPP: |
| case -ENOTTY: |
| xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V1; |
| break; |
| } |
| |
| try_v1: |
| return xfrog_inumbers1(xfd, req); |
| } |
| |
| /* Allocate a inumbers request. Returns zero or a negative error code. */ |
| int |
| xfrog_inumbers_alloc_req( |
| uint32_t nr, |
| uint64_t startino, |
| struct xfs_inumbers_req **preq) |
| { |
| struct xfs_inumbers_req *ireq; |
| |
| ireq = calloc(1, XFS_INUMBERS_REQ_SIZE(nr)); |
| if (!ireq) |
| return -errno; |
| |
| ireq->hdr.icount = nr; |
| ireq->hdr.ino = startino; |
| |
| *preq = ireq; |
| return 0; |
| } |
| |
| /* Set an inumbers cursor to iterate only a particular AG. */ |
| void |
| xfrog_inumbers_set_ag( |
| struct xfs_inumbers_req *req, |
| uint32_t agno) |
| { |
| req->hdr.agno = agno; |
| req->hdr.flags |= XFS_BULK_IREQ_AGNO; |
| } |