Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose the number of hole blocks in a file #7392

Closed
wants to merge 8 commits into from

Conversation

ahrens
Copy link
Member

@ahrens ahrens commented Apr 5, 2018

Description

We would like to expose the number of hole (sparse) blocks in a file.
This can be useful to for example if you want to fill in the holes with
some data; knowing the number of holes in advances allows you to report
progress on hole filling. We could use SEEK_HOLE to do that but it would
be O(n) where n is the number of holes present in the file.

We would like to get feedback on this approach - is this an OK way to do it, and if so what additional work is needed to get it integrated.

Work by @grwilson

Motivation and Context

How Has This Been Tested?

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance enhancement (non-breaking change which improves efficiency)
  • Code cleanup (non-breaking change which makes code smaller or more readable)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation (a change to man pages or other documentation)

Checklist:

  • My code follows the ZFS on Linux code style requirements.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • All commit messages are properly formatted and contain Signed-off-by.
  • Change has been approved by a ZFS on Linux member.

@ahrens ahrens requested a review from behlendorf April 5, 2018 19:20
Copy link
Contributor

@behlendorf behlendorf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I'd still like to get fiemap implemented at some point since it's a much richer interface. But it sounds like this is enough for your use case and there's no issue with filesystem specific ioctls.

Along with this change we should add a basic test case to verify it's working. I thought OpenZFS already had one we could adapt for this but I wasn't able to find it.

@@ -888,6 +888,38 @@ zpl_ioctl_setxattr(struct file *filp, void __user *arg)
return (err);
}

static long
zpl_ioctl_count_filled(struct file *filep, void __user *arg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the most part we've tried to keep the Linux specific changes confined to the zpl_* functions, and left the core ZFS functionality in zfs_vnops.c, zfs_vfsops.c, etc. So let's go ahead and split this function up the same way. Lines 901-914 can be moved in to a zfs_count_filled() function in zfs_vnops.c and then zpl_ioctl_count_filled is what's left. This way most of the linux data structures, functions, negative return codes don't seep down very far in to ZFS proper.

static long
zpl_ioctl_count_filled(struct file *filep, void __user *arg)
{
	struct inode *ip = file_inode(filep);
	zfsvfs_t *zfsvfs = ITOZSB(ip);
	dmu_object_info_t doi;
	int error;

        error = -zfs_count_filled(zfsvfs, ITOZ(ip), &doi);
        if (error == 0) {
        	uint64_t ndata = doi.doi_fill_count;
        	error = copy_to_user(arg, &ndata, sizeof (ndata));
        }

	return (error);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I adjusted the interface you suggested slightly:

int zfs_count_filled(struct inode *ip, uint64_t *fill_count)

return (err);
}

*count = (ss.st_size + ss.st_blksize - 1) / ss.st_blksize - fill;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be wise to guard against a dividing by zero here even if st_blksize should always be non-zero.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

/*
* zpl ioctl to get number of filled blocks
*/
#define ZFS_IOC_COUNT_FILLED _IOR('f', 100, uint64_t)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than using the global 'f' magic here, each filesystem is allowed to use their own unique magic for custom ioctl()'s to prevent collisions with the generic ones. This will be our first for ZFS so we'll need to pick a magic value, how about 'Z' which looks to be unused. We should also then be able to start at 1.

#define ZFS_IOCTL_MAGIC 'Z'

#define ZFS_IOC_COUNT_FILLED	_IOR(ZFS_IOCTL_MAGIC, 1, uint64_t)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to linux/Documentation/ioctl/ioctl-number.txt the Z space is in (limited) use:

'Z'     14-15   drivers/message/fusion/mptctl.h

I'd suggest to start at 32 or 64 to avoid potential conflicts.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to 'Z' but kept it at number 100.

@codecov
Copy link

codecov bot commented Apr 6, 2018

Codecov Report

Merging #7392 into master will increase coverage by 0.24%.
The diff coverage is 67.93%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #7392      +/-   ##
==========================================
+ Coverage   77.26%    77.5%   +0.24%     
==========================================
  Files         336      338       +2     
  Lines      107520   107757     +237     
==========================================
+ Hits        83072    83516     +444     
+ Misses      24448    24241     -207
Flag Coverage Δ
#kernel 78.03% <83.33%> (+0.03%) ⬆️
#user 66.51% <63.25%> (+0.37%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d1043e2...9411cda. Read the comment docs.

@behlendorf
Copy link
Contributor

behlendorf commented Apr 12, 2018

These are the hole tests I wasn't able to find previously. They were never ported to ZoL since this was a custom ioctl() which wasn't implemented. Let's make sure to pull over mkholes.c, getholes.c and the test itself as part of this PR (or at least some useful version of them).

@adilger
Copy link
Contributor

adilger commented Apr 18, 2018

For Matt's reference, the ZFS FIEMAP feature is described in issue #264, and more generally in fiemap.txt. While there are some issues that are ZFS specific that are not implemented in the current filefrag utility (multiple devices, compression, ditto blocks), the Lustre-specific version of this tool already handles multiple devices, and the others are of interest to other Linux filesystems (XFS for real-time volumes, and BtrFS for multi-device and compression).

IMHO, it would be preferable to have a single interface and user tool for this kind of information, rather than having multiple different tools. Note that even if ZFS doesn't implement the full FIEMAP interface, it could provide a similar interface by calling FS_IOC_FIEMAP with fm_extent_count = 0 (using the rest of your patch largely unchanged):

fm_extent_count specifies the number of elements in the fm_extents[] array
that can be used to return extents.  If fm_extent_count is zero, then the
fm_extents[] array is ignored (no extents will be returned), and the
fm_mapped_extents count will hold the number of extents needed in
fm_extents[] to hold the file's current mapping.

and return -EOPNOTSUPP if fm_extent_count != 0 until such a time that a full implementation is available.

/*
* zpl ioctl to get number of filled blocks
*/
#define ZFS_IOC_COUNT_FILLED _IOR('f', 100, uint64_t)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to linux/Documentation/ioctl/ioctl-number.txt the Z space is in (limited) use:

'Z'     14-15   drivers/message/fusion/mptctl.h

I'd suggest to start at 32 or 64 to avoid potential conflicts.

* calculated.
*
* On success, zero is returned, the count argument is set to the
* number of holes, and the bs argument is set to the block size (if it is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, it isn't clear how the blocksize is useful here, though I guess it doesn't add significantly to the overhead? This is already returned by stat() for the file, and it isn't like blocksize * number_of_holes is a useful value? Hmm, maybe the confusion is that "the number of holes" is actually "the number of unallocated blocks" and not "the number of contiguous unallocated regions" (which is what I'd consider the traditional definition of "hole" is).

I guess in that case blocksize is useful, but the comment (and description of this feature in general) should be improved. Rather than describe this as "number of holes", I'd write "number of unallocated (sparse) blocks inside a file".

In that case FS_IOC_FIEMAP with fm_extent_count = 0 is unfortunately not a suitable replacement for this functionality.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's covered in the first part of the comment, but I'll reiterate it here too

/*
 * zfs_get_hole_count retrieves the number of holes (blocks which are
 * zero-filled) in the specified file using the ZFS_IOC_COUNT_FILLED ioctl.

module/zfs/dmu.c Outdated
* ID and wait for that to be synced.
*/
int
dmu_object_wait_synced(objset_t *os, uint64_t object)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually a function that would be useful for Lustre also, to be able to sync a specific dnode instead of the whole filesystem, but it can be a no-op if the dnode does not have dirty pages.

error = copy_to_user(arg, &ndata, sizeof (ndata));
ZFS_EXIT(zfsvfs);
return (error);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty line should be above return()?

module/zfs/dmu.c Outdated
int error, i;

error = dnode_hold(os, object, FTAG, &dn);
if (error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Far be it from me to criticize your ZFS coding style, but it doesn't seem common even in the ZFS code to have single-line "if" blocks with {} (and also on line 2364). Please ignore if incorrect.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I'll go ahead and change these.

@ahrens
Copy link
Member Author

ahrens commented Apr 27, 2018

I've made the changes requested, except for adding the tests, which I'll get to next week.

@ahrens
Copy link
Member Author

ahrens commented May 3, 2018

Added the hole tests and rebased.

@behlendorf
Copy link
Contributor

Looks like you need to add the new holes directory to SUBDIRS in tests/zfs-tests/tests/functional/Makefile.am.

@ahrens
Copy link
Member Author

ahrens commented May 3, 2018

@behlendorf thanks - I had been testing by running just the new tests.

@ahrens
Copy link
Member Author

ahrens commented May 7, 2018

It looks like CentOS 6 doesn't have SEEK_DATA/HOLE. I can use #ifdef to make getholes.c fail in this case. Would we also need to disable the new test case on that platform?

@behlendorf
Copy link
Contributor

Would we also need to disable the new test case on that platform?

We do. What we've done historically is something like this in setup.sh to skip the entire test group when the needed functionality isn't available on the platform.

if ! getholes /etc/hosts; then
        log_unsupported "The kernel does not support SEEK_DATA / SEEK_HOLE"
fi

@ahrens
Copy link
Member Author

ahrens commented May 8, 2018

@behlendorf Turns out that doesn't work, becuase the builders don't run ZFS on root. getholes depends on both SEEK_DATA/HOLE and the new hole count ioctl (which doesn't work on /etc/hosts since it isn't on ZFS). So that is causing the hole test on most platforms to be skipped.

But the test is failing in a bizarre way on CentOS 6:

Test: /usr/share/zfs/zfs-tests/tests/functional/holes/setup (run as root) [00:00] [FAIL]
19:36:26.52 sudo: unable to execute /usr/share/zfs/zfs-tests/tests/functional/holes/setup.ksh: No such file or directory
Test: /usr/share/zfs/zfs-tests/tests/functional/holes/holes_sanity (run as root) [00:00] [SKIP]
Test: /usr/share/zfs/zfs-tests/tests/functional/holes/cleanup (run as root) [00:00] [FAIL]
19:36:26.53 sudo: unable to execute /usr/share/zfs/zfs-tests/tests/functional/holes/cleanup.ksh: No such file or directory

grwilson and others added 7 commits May 10, 2018 14:12
We would like to expose the number of hole (sparse) blocks in a file.
This can be useful to for example if you want to fill in the holes with
some data; knowing the number of holes in advances allows you to report
progress on hole filling. We could use SEEK_HOLE to do that but it would
be O(n) where n is the number of holes present in the file.
@ahrens
Copy link
Member Author

ahrens commented May 16, 2018

test failures:

CentOS 6: bizarre, test scripts are missing

Test: /usr/share/zfs/zfs-tests/tests/functional/holes/setup (run as root) [00:00] [FAIL]
00:28:58.06 sudo: unable to execute /usr/share/zfs/zfs-tests/tests/functional/holes/setup.ksh: No such file or directory
Test: /usr/share/zfs/zfs-tests/tests/functional/holes/holes_sanity (run as root) [00:00] [SKIP]
Test: /usr/share/zfs/zfs-tests/tests/functional/holes/cleanup (run as root) [00:00] [FAIL]
00:28:58.07 sudo: unable to execute /usr/share/zfs/zfs-tests/tests/functional/holes/cleanup.ksh: No such file or directory

Ubuntu 18.04: doesn't seem related to my changes (and they pass on my ubuntu 18.04 machine)

Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_export/zpool_export_001_pos (run as root) [00:00] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_export/zpool_export_002_pos (run as root) [00:00] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_export/zpool_export_003_neg (run as root) [00:00] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_001_pos (run as root) [00:07] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_002_pos (run as root) [00:01] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_003_pos (run as root) [00:01] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_009_neg (run as root) [00:07] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_010_pos (run as root) [00:03] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_011_neg (run as root) [00:03] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_012_pos (run as root) [00:07] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_014_pos (run as root) [00:03] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_015_pos (run as root) [00:00] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_rename_001_pos (run as root) [00:02] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_all_001_pos (run as root) [00:00] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_encrypted (run as root) [00:00] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/cli_root/zpool_scrub/zpool_scrub_004_pos (run as root) [00:01] [FAIL]

CentOS 7: doesn't seem related to my changes; correctly skips the holes tests

Test: /usr/share/zfs/zfs-tests/tests/functional/rsend/send-c_volume (run as root) [00:02] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/tmpfile/tmpfile_001_pos (run as root) [00:00] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/tmpfile/tmpfile_002_pos (run as root) [00:00] [FAIL]
Test: /usr/share/zfs/zfs-tests/tests/functional/tmpfile/tmpfile_003_pos (run as root) [00:00] [FAIL]

@behlendorf behlendorf added the Status: Inactive Not being actively updated label Sep 25, 2018
@ahrens ahrens added the Status: Design Review Needed Architecture or design is under discussion label Sep 27, 2018
@ahrens
Copy link
Member Author

ahrens commented Jun 4, 2021

@behlendorf Given that the fiemap changes haven't landed in the intervening 3 years, would you be open to adding this functionality as-is? Or is there a smaller change than the fiemap stuff that would make this acceptable? If not, I'll close this PR.

@ahrens
Copy link
Member Author

ahrens commented Jun 10, 2021

@behlendorf replied to me out-of-band and is concerned about adding a new custom ioctl that we'd have to support forever. We'll close this PR and continue maintaining the diffs in our branch indefinitely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Design Review Needed Architecture or design is under discussion Status: Inactive Not being actively updated
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants