Skip to content

Commit

Permalink
Expose the number of hole blocks in a file
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
grwilson authored and ahrens committed Apr 5, 2018
1 parent 533ea04 commit 9b56285
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 0 deletions.
2 changes: 2 additions & 0 deletions include/libzfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,8 @@ extern int zfs_device_get_devid(struct udev_device *, char *, size_t);
extern int zfs_device_get_physical(struct udev_device *, char *, size_t);
#endif

extern int zfs_get_hole_count(const char *, uint64_t *, uint64_t *);

#ifdef __cplusplus
}
#endif
Expand Down
9 changes: 9 additions & 0 deletions include/sys/dmu.h
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,15 @@ int dmu_sync(struct zio *zio, uint64_t txg, dmu_sync_cb_t *done, zgd_t *zgd);
int dmu_offset_next(objset_t *os, uint64_t object, boolean_t hole,
uint64_t *off);

/*
* Check if a DMU object has any dirty blocks. If so, sync out
* all pending transaction groups. Otherwise, this function
* does not alter DMU state. This could be improved to only sync
* out the necessary transaction groups for this particular
* object.
*/
int dmu_object_wait_synced(objset_t *os, uint64_t object);

/*
* Initial setup and final teardown.
*/
Expand Down
5 changes: 5 additions & 0 deletions include/sys/fs/zfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,11 @@ typedef enum zfs_ioc {
*/
#define BLKZNAME _IOR(0x12, 125, char[ZFS_MAX_DATASET_NAME_LEN])

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

/*
* Internal SPA load state. Used by FMA diagnosis engine.
*/
Expand Down
47 changes: 47 additions & 0 deletions lib/libzfs/libzfs_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -2143,3 +2143,50 @@ zprop_iter(zprop_func func, void *cb, boolean_t show_all, boolean_t ordered,
{
return (zprop_iter_common(func, cb, show_all, ordered, type));
}

/*
* 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. It
* also optionally fetches the block size when bs is non-NULL. With hole count
* and block size the full space consumed by the holes of a file can be
* 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
* not NULL). On error, a non-zero errno is returned and the values in count
* and bs are undefined.
*/
int
zfs_get_hole_count(const char *path, uint64_t *count, uint64_t *bs)
{
int fd, err;
struct stat64 ss;
uint64_t fill;

fd = open(path, O_RDONLY);
if (fd == -1)
return (errno);

if (ioctl(fd, ZFS_IOC_COUNT_FILLED, &fill) == -1) {
err = errno;
(void) close(fd);
return (err);
}

if (fstat64(fd, &ss) == -1) {
err = errno;
(void) close(fd);
return (err);
}

*count = (ss.st_size + ss.st_blksize - 1) / ss.st_blksize - fill;
VERIFY3S(*count, >=, 0);
if (bs != NULL) {
*bs = ss.st_blksize;
}

if (close(fd) == -1) {
return (errno);
}
return (0);
}
31 changes: 31 additions & 0 deletions module/zfs/dmu.c
Original file line number Diff line number Diff line change
Expand Up @@ -2337,6 +2337,37 @@ __dmu_object_info_from_dnode(dnode_t *dn, dmu_object_info_t *doi)
doi->doi_fill_count += BP_GET_FILL(&dnp->dn_blkptr[i]);
}

/*
* Give the ZFS object, if it contains any dirty nodes
* this function flushes all dirty blocks to disk. This
* ensures the DMU object info is updated. A more efficient
* future version might just find the TXG with the maximum
* ID and wait for that to be synced.
*/
int
dmu_object_wait_synced(objset_t *os, uint64_t object)
{
dnode_t *dn;
int error, i;

error = dnode_hold(os, object, FTAG, &dn);
if (error) {
return (error);
}

for (i = 0; i < TXG_SIZE; i++) {
if (list_link_active(&dn->dn_dirty_link[i])) {
break;
}
}
dnode_rele(dn, FTAG);
if (i != TXG_SIZE) {
txg_wait_synced(dmu_objset_pool(os), 0);
}

return (0);
}

void
dmu_object_info_from_dnode(dnode_t *dn, dmu_object_info_t *doi)
{
Expand Down
34 changes: 34 additions & 0 deletions module/zfs/zpl_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
struct inode *ip = file_inode(filep);
znode_t *zp = ITOZ(ip);
zfsvfs_t *zfsvfs = ITOZSB(ip);
uint64_t ndata;
int error;
dmu_object_info_t doi;

ZFS_ENTER(zfsvfs);
ZFS_VERIFY_ZP(zp);

error = -dmu_object_wait_synced(zfsvfs->z_os, zp->z_id);
if (error != 0) {
ZFS_EXIT(zfsvfs);
return (error);
}

error = -dmu_object_info(zfsvfs->z_os, zp->z_id, &doi);
if (error != 0) {
ZFS_EXIT(zfsvfs);
return (error);
}

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

}

static long
zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
Expand All @@ -900,6 +932,8 @@ zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
return (zpl_ioctl_getxattr(filp, (void *)arg));
case ZFS_IOC_FSSETXATTR:
return (zpl_ioctl_setxattr(filp, (void *)arg));
case ZFS_IOC_COUNT_FILLED:
return (zpl_ioctl_count_filled(filp, (void *)arg));
default:
return (-ENOTTY);
}
Expand Down

0 comments on commit 9b56285

Please sign in to comment.