Introduce zfs rewrite subcommand (#17246)

This allows to rewrite content of specified file(s) as-is without
modifications, but at a different location, compression, checksum,
dedup, copies and other parameter values.  It is faster than read
plus write, since it does not require data copying to user-space.
It is also faster for sync=always datasets, since without data
modification it does not require ZIL writing.  Also since it is
protected by normal range range locks, it can be done under any
other load.  Also it does not affect file's modification time or
other properties.

Signed-off-by:	Alexander Motin <mav@FreeBSD.org>
Sponsored by:	iXsystems, Inc.
Reviewed-by: Tony Hutter <hutter2@llnl.gov>
Reviewed-by: Rob Norris <robn@despairlabs.com>
This commit is contained in:
Alexander Motin 2025-05-12 13:22:17 -04:00 committed by GitHub
parent 9aae14a14a
commit 49fbdd4533
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 636 additions and 5 deletions

View File

@ -37,6 +37,7 @@
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <sys/debug.h> #include <sys/debug.h>
#include <dirent.h>
#include <errno.h> #include <errno.h>
#include <getopt.h> #include <getopt.h>
#include <libgen.h> #include <libgen.h>
@ -121,6 +122,7 @@ static int zfs_do_change_key(int argc, char **argv);
static int zfs_do_project(int argc, char **argv); static int zfs_do_project(int argc, char **argv);
static int zfs_do_version(int argc, char **argv); static int zfs_do_version(int argc, char **argv);
static int zfs_do_redact(int argc, char **argv); static int zfs_do_redact(int argc, char **argv);
static int zfs_do_rewrite(int argc, char **argv);
static int zfs_do_wait(int argc, char **argv); static int zfs_do_wait(int argc, char **argv);
#ifdef __FreeBSD__ #ifdef __FreeBSD__
@ -193,6 +195,7 @@ typedef enum {
HELP_CHANGE_KEY, HELP_CHANGE_KEY,
HELP_VERSION, HELP_VERSION,
HELP_REDACT, HELP_REDACT,
HELP_REWRITE,
HELP_JAIL, HELP_JAIL,
HELP_UNJAIL, HELP_UNJAIL,
HELP_WAIT, HELP_WAIT,
@ -227,7 +230,7 @@ static zfs_command_t command_table[] = {
{ "promote", zfs_do_promote, HELP_PROMOTE }, { "promote", zfs_do_promote, HELP_PROMOTE },
{ "rename", zfs_do_rename, HELP_RENAME }, { "rename", zfs_do_rename, HELP_RENAME },
{ "bookmark", zfs_do_bookmark, HELP_BOOKMARK }, { "bookmark", zfs_do_bookmark, HELP_BOOKMARK },
{ "program", zfs_do_channel_program, HELP_CHANNEL_PROGRAM }, { "diff", zfs_do_diff, HELP_DIFF },
{ NULL }, { NULL },
{ "list", zfs_do_list, HELP_LIST }, { "list", zfs_do_list, HELP_LIST },
{ NULL }, { NULL },
@ -249,27 +252,31 @@ static zfs_command_t command_table[] = {
{ NULL }, { NULL },
{ "send", zfs_do_send, HELP_SEND }, { "send", zfs_do_send, HELP_SEND },
{ "receive", zfs_do_receive, HELP_RECEIVE }, { "receive", zfs_do_receive, HELP_RECEIVE },
{ "redact", zfs_do_redact, HELP_REDACT },
{ NULL }, { NULL },
{ "allow", zfs_do_allow, HELP_ALLOW }, { "allow", zfs_do_allow, HELP_ALLOW },
{ NULL },
{ "unallow", zfs_do_unallow, HELP_UNALLOW }, { "unallow", zfs_do_unallow, HELP_UNALLOW },
{ NULL }, { NULL },
{ "hold", zfs_do_hold, HELP_HOLD }, { "hold", zfs_do_hold, HELP_HOLD },
{ "holds", zfs_do_holds, HELP_HOLDS }, { "holds", zfs_do_holds, HELP_HOLDS },
{ "release", zfs_do_release, HELP_RELEASE }, { "release", zfs_do_release, HELP_RELEASE },
{ "diff", zfs_do_diff, HELP_DIFF }, { NULL },
{ "load-key", zfs_do_load_key, HELP_LOAD_KEY }, { "load-key", zfs_do_load_key, HELP_LOAD_KEY },
{ "unload-key", zfs_do_unload_key, HELP_UNLOAD_KEY }, { "unload-key", zfs_do_unload_key, HELP_UNLOAD_KEY },
{ "change-key", zfs_do_change_key, HELP_CHANGE_KEY }, { "change-key", zfs_do_change_key, HELP_CHANGE_KEY },
{ "redact", zfs_do_redact, HELP_REDACT }, { NULL },
{ "program", zfs_do_channel_program, HELP_CHANNEL_PROGRAM },
{ "rewrite", zfs_do_rewrite, HELP_REWRITE },
{ "wait", zfs_do_wait, HELP_WAIT }, { "wait", zfs_do_wait, HELP_WAIT },
#ifdef __FreeBSD__ #ifdef __FreeBSD__
{ NULL },
{ "jail", zfs_do_jail, HELP_JAIL }, { "jail", zfs_do_jail, HELP_JAIL },
{ "unjail", zfs_do_unjail, HELP_UNJAIL }, { "unjail", zfs_do_unjail, HELP_UNJAIL },
#endif #endif
#ifdef __linux__ #ifdef __linux__
{ NULL },
{ "zone", zfs_do_zone, HELP_ZONE }, { "zone", zfs_do_zone, HELP_ZONE },
{ "unzone", zfs_do_unzone, HELP_UNZONE }, { "unzone", zfs_do_unzone, HELP_UNZONE },
#endif #endif
@ -432,6 +439,9 @@ get_usage(zfs_help_t idx)
case HELP_REDACT: case HELP_REDACT:
return (gettext("\tredact <snapshot> <bookmark> " return (gettext("\tredact <snapshot> <bookmark> "
"<redaction_snapshot> ...\n")); "<redaction_snapshot> ...\n"));
case HELP_REWRITE:
return (gettext("\trewrite [-rvx] [-o <offset>] [-l <length>] "
"<directory|file ...>\n"));
case HELP_JAIL: case HELP_JAIL:
return (gettext("\tjail <jailid|jailname> <filesystem>\n")); return (gettext("\tjail <jailid|jailname> <filesystem>\n"));
case HELP_UNJAIL: case HELP_UNJAIL:
@ -9016,6 +9026,192 @@ zfs_do_project(int argc, char **argv)
return (ret); return (ret);
} }
static int
zfs_rewrite_file(const char *path, boolean_t verbose, zfs_rewrite_args_t *args)
{
int fd, ret = 0;
fd = open(path, O_WRONLY);
if (fd < 0) {
ret = errno;
(void) fprintf(stderr, gettext("failed to open %s: %s\n"),
path, strerror(errno));
return (ret);
}
if (ioctl(fd, ZFS_IOC_REWRITE, args) < 0) {
ret = errno;
(void) fprintf(stderr, gettext("failed to rewrite %s: %s\n"),
path, strerror(errno));
} else if (verbose) {
printf("%s\n", path);
}
close(fd);
return (ret);
}
static int
zfs_rewrite_dir(const char *path, boolean_t verbose, boolean_t xdev, dev_t dev,
zfs_rewrite_args_t *args, nvlist_t *dirs)
{
struct dirent *ent;
DIR *dir;
int ret = 0, err;
dir = opendir(path);
if (dir == NULL) {
if (errno == ENOENT)
return (0);
ret = errno;
(void) fprintf(stderr, gettext("failed to opendir %s: %s\n"),
path, strerror(errno));
return (ret);
}
size_t plen = strlen(path) + 1;
while ((ent = readdir(dir)) != NULL) {
char *fullname;
struct stat st;
if (ent->d_type != DT_REG && ent->d_type != DT_DIR)
continue;
if (strcmp(ent->d_name, ".") == 0 ||
strcmp(ent->d_name, "..") == 0)
continue;
if (plen + strlen(ent->d_name) >= PATH_MAX) {
(void) fprintf(stderr, gettext("path too long %s/%s\n"),
path, ent->d_name);
ret = ENAMETOOLONG;
continue;
}
if (asprintf(&fullname, "%s/%s", path, ent->d_name) == -1) {
(void) fprintf(stderr,
gettext("failed to allocate memory\n"));
ret = ENOMEM;
continue;
}
if (xdev) {
if (lstat(fullname, &st) < 0) {
ret = errno;
(void) fprintf(stderr,
gettext("failed to stat %s: %s\n"),
fullname, strerror(errno));
free(fullname);
continue;
}
if (st.st_dev != dev) {
free(fullname);
continue;
}
}
if (ent->d_type == DT_REG) {
err = zfs_rewrite_file(fullname, verbose, args);
if (err)
ret = err;
} else { /* DT_DIR */
fnvlist_add_uint64(dirs, fullname, dev);
}
free(fullname);
}
closedir(dir);
return (ret);
}
static int
zfs_rewrite_path(const char *path, boolean_t verbose, boolean_t recurse,
boolean_t xdev, zfs_rewrite_args_t *args, nvlist_t *dirs)
{
struct stat st;
int ret = 0;
if (lstat(path, &st) < 0) {
ret = errno;
(void) fprintf(stderr, gettext("failed to stat %s: %s\n"),
path, strerror(errno));
return (ret);
}
if (S_ISREG(st.st_mode)) {
ret = zfs_rewrite_file(path, verbose, args);
} else if (S_ISDIR(st.st_mode) && recurse) {
ret = zfs_rewrite_dir(path, verbose, xdev, st.st_dev, args,
dirs);
}
return (ret);
}
static int
zfs_do_rewrite(int argc, char **argv)
{
int ret = 0, err, c;
boolean_t recurse = B_FALSE, verbose = B_FALSE, xdev = B_FALSE;
if (argc < 2)
usage(B_FALSE);
zfs_rewrite_args_t args;
memset(&args, 0, sizeof (args));
while ((c = getopt(argc, argv, "l:o:rvx")) != -1) {
switch (c) {
case 'l':
args.len = strtoll(optarg, NULL, 0);
break;
case 'o':
args.off = strtoll(optarg, NULL, 0);
break;
case 'r':
recurse = B_TRUE;
break;
case 'v':
verbose = B_TRUE;
break;
case 'x':
xdev = B_TRUE;
break;
default:
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
usage(B_FALSE);
}
}
argv += optind;
argc -= optind;
if (argc == 0) {
(void) fprintf(stderr,
gettext("missing file or directory target(s)\n"));
usage(B_FALSE);
}
nvlist_t *dirs = fnvlist_alloc();
for (int i = 0; i < argc; i++) {
err = zfs_rewrite_path(argv[i], verbose, recurse, xdev, &args,
dirs);
if (err)
ret = err;
}
nvpair_t *dir;
while ((dir = nvlist_next_nvpair(dirs, NULL)) != NULL) {
err = zfs_rewrite_dir(nvpair_name(dir), verbose, xdev,
fnvpair_value_uint64(dir), &args, dirs);
if (err)
ret = err;
fnvlist_remove_nvpair(dirs, dir);
}
fnvlist_free(dirs);
return (ret);
}
static int static int
zfs_do_wait(int argc, char **argv) zfs_do_wait(int argc, char **argv)
{ {

View File

@ -73,6 +73,7 @@ usr/share/man/man8/zfs-recv.8
usr/share/man/man8/zfs-redact.8 usr/share/man/man8/zfs-redact.8
usr/share/man/man8/zfs-release.8 usr/share/man/man8/zfs-release.8
usr/share/man/man8/zfs-rename.8 usr/share/man/man8/zfs-rename.8
usr/share/man/man8/zfs-rewrite.8
usr/share/man/man8/zfs-rollback.8 usr/share/man/man8/zfs-rollback.8
usr/share/man/man8/zfs-send.8 usr/share/man/man8/zfs-send.8
usr/share/man/man8/zfs-set.8 usr/share/man/man8/zfs-set.8

View File

@ -1620,6 +1620,15 @@ typedef enum zfs_ioc {
#endif #endif
typedef struct zfs_rewrite_args {
uint64_t off;
uint64_t len;
uint64_t flags;
uint64_t arg;
} zfs_rewrite_args_t;
#define ZFS_IOC_REWRITE _IOW(0x83, 3, zfs_rewrite_args_t)
/* /*
* ZFS-specific error codes used for returning descriptive errors * ZFS-specific error codes used for returning descriptive errors
* to the userland through zfs ioctls. * to the userland through zfs ioctls.

View File

@ -40,6 +40,7 @@ extern int zfs_clone_range(znode_t *, uint64_t *, znode_t *, uint64_t *,
uint64_t *, cred_t *); uint64_t *, cred_t *);
extern int zfs_clone_range_replay(znode_t *, uint64_t, uint64_t, uint64_t, extern int zfs_clone_range_replay(znode_t *, uint64_t, uint64_t, uint64_t,
const blkptr_t *, size_t); const blkptr_t *, size_t);
extern int zfs_rewrite(znode_t *, uint64_t, uint64_t, uint64_t, uint64_t);
extern int zfs_getsecattr(znode_t *, vsecattr_t *, int, cred_t *); extern int zfs_getsecattr(znode_t *, vsecattr_t *, int, cred_t *);
extern int zfs_setsecattr(znode_t *, vsecattr_t *, int, cred_t *); extern int zfs_setsecattr(znode_t *, vsecattr_t *, int, cred_t *);

View File

@ -50,6 +50,7 @@ dist_man_MANS = \
%D%/man8/zfs-redact.8 \ %D%/man8/zfs-redact.8 \
%D%/man8/zfs-release.8 \ %D%/man8/zfs-release.8 \
%D%/man8/zfs-rename.8 \ %D%/man8/zfs-rename.8 \
%D%/man8/zfs-rewrite.8 \
%D%/man8/zfs-rollback.8 \ %D%/man8/zfs-rollback.8 \
%D%/man8/zfs-send.8 \ %D%/man8/zfs-send.8 \
%D%/man8/zfs-set.8 \ %D%/man8/zfs-set.8 \

76
man/man8/zfs-rewrite.8 Normal file
View File

@ -0,0 +1,76 @@
.\" SPDX-License-Identifier: CDDL-1.0
.\"
.\" CDDL HEADER START
.\"
.\" The contents of this file are subject to the terms of the
.\" Common Development and Distribution License (the "License").
.\" You may not use this file except in compliance with the License.
.\"
.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
.\" or https://opensource.org/licenses/CDDL-1.0.
.\" See the License for the specific language governing permissions
.\" and limitations under the License.
.\"
.\" When distributing Covered Code, include this CDDL HEADER in each
.\" file and include the License file at usr/src/OPENSOLARIS.LICENSE.
.\" If applicable, add the following below this CDDL HEADER, with the
.\" fields enclosed by brackets "[]" replaced with your own identifying
.\" information: Portions Copyright [yyyy] [name of copyright owner]
.\"
.\" CDDL HEADER END
.\"
.\" Copyright (c) 2025 iXsystems, Inc.
.\"
.Dd May 6, 2025
.Dt ZFS-REWRITE 8
.Os
.
.Sh NAME
.Nm zfs-rewrite
.Nd rewrite specified files without modification
.Sh SYNOPSIS
.Nm zfs
.Cm rewrite
.Oo Fl rvx Ns Oc
.Op Fl l Ar length
.Op Fl o Ar offset
.Ar file Ns | Ns Ar directory Ns
.
.Sh DESCRIPTION
Rewrite blocks of specified
.Ar file
as is without modification at a new location and possibly with new
properties, such as checksum, compression, dedup, copies, etc,
as if they were atomically read and written back.
.Bl -tag -width "-r"
.It Fl l Ar length
Rewrite at most this number of bytes.
.It Fl o Ar offset
Start at this offset in bytes.
.It Fl r
Recurse into directories.
.It Fl v
Print names of all successfully rewritten files.
.It Fl x
Don't cross file system mount points when recursing.
.El
.Sh NOTES
Rewrite of cloned blocks and blocks that are part of any snapshots,
same as some property changes may increase pool space usage.
Holes that were never written or were previously zero-compressed are
not rewritten and will remain holes even if compression is disabled.
.Pp
Rewritten blocks will be seen as modified in next snapshot and as such
included into the incremental
.Nm zfs Cm send
stream.
.Pp
If a
.Fl l
or
.Fl o
value request a rewrite to regions past the end of the file, then those
regions are silently ignored, and no error is reported.
.
.Sh SEE ALSO
.Xr zfsprops 7

View File

@ -37,7 +37,7 @@
.\" Copyright 2018 Nexenta Systems, Inc. .\" Copyright 2018 Nexenta Systems, Inc.
.\" Copyright 2019 Joyent, Inc. .\" Copyright 2019 Joyent, Inc.
.\" .\"
.Dd May 12, 2022 .Dd April 18, 2025
.Dt ZFS 8 .Dt ZFS 8
.Os .Os
. .
@ -299,6 +299,12 @@ Execute ZFS administrative operations
programmatically via a Lua script-language channel program. programmatically via a Lua script-language channel program.
.El .El
. .
.Ss Data rewrite
.Bl -tag -width ""
.It Xr zfs-rewrite 8
Rewrite specified files without modification.
.El
.
.Ss Jails .Ss Jails
.Bl -tag -width "" .Bl -tag -width ""
.It Xr zfs-jail 8 .It Xr zfs-jail 8

View File

@ -305,6 +305,18 @@ zfs_ioctl(vnode_t *vp, ulong_t com, intptr_t data, int flag, cred_t *cred,
*(offset_t *)data = off; *(offset_t *)data = off;
return (0); return (0);
} }
case ZFS_IOC_REWRITE: {
zfs_rewrite_args_t *args = (zfs_rewrite_args_t *)data;
if ((flag & FWRITE) == 0)
return (SET_ERROR(EBADF));
error = vn_lock(vp, LK_SHARED);
if (error)
return (error);
error = zfs_rewrite(VTOZ(vp), args->off, args->len,
args->flags, args->arg);
VOP_UNLOCK(vp);
return (error);
}
} }
return (SET_ERROR(ENOTTY)); return (SET_ERROR(ENOTTY));
} }

View File

@ -985,6 +985,27 @@ zpl_ioctl_setdosflags(struct file *filp, void __user *arg)
return (err); return (err);
} }
static int
zpl_ioctl_rewrite(struct file *filp, void __user *arg)
{
struct inode *ip = file_inode(filp);
zfs_rewrite_args_t args;
fstrans_cookie_t cookie;
int err;
if (copy_from_user(&args, arg, sizeof (args)))
return (-EFAULT);
if (unlikely(!(filp->f_mode & FMODE_WRITE)))
return (-EBADF);
cookie = spl_fstrans_mark();
err = -zfs_rewrite(ITOZ(ip), args.off, args.len, args.flags, args.arg);
spl_fstrans_unmark(cookie);
return (err);
}
static long static long
zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{ {
@ -1003,6 +1024,8 @@ zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
return (zpl_ioctl_getdosflags(filp, (void *)arg)); return (zpl_ioctl_getdosflags(filp, (void *)arg));
case ZFS_IOC_SETDOSFLAGS: case ZFS_IOC_SETDOSFLAGS:
return (zpl_ioctl_setdosflags(filp, (void *)arg)); return (zpl_ioctl_setdosflags(filp, (void *)arg));
case ZFS_IOC_REWRITE:
return (zpl_ioctl_rewrite(filp, (void *)arg));
default: default:
return (-ENOTTY); return (-ENOTTY);
} }

View File

@ -1050,6 +1050,143 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr)
return (0); return (0);
} }
/*
* Rewrite a range of file as-is without modification.
*
* IN: zp - znode of file to be rewritten.
* off - Offset of the range to rewrite.
* len - Length of the range to rewrite.
* flags - Random rewrite parameters.
* arg - flags-specific argument.
*
* RETURN: 0 if success
* error code if failure
*/
int
zfs_rewrite(znode_t *zp, uint64_t off, uint64_t len, uint64_t flags,
uint64_t arg)
{
int error;
if (flags != 0 || arg != 0)
return (SET_ERROR(EINVAL));
zfsvfs_t *zfsvfs = ZTOZSB(zp);
if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0)
return (error);
if (zfs_is_readonly(zfsvfs)) {
zfs_exit(zfsvfs, FTAG);
return (SET_ERROR(EROFS));
}
if (off >= zp->z_size) {
zfs_exit(zfsvfs, FTAG);
return (0);
}
if (len == 0 || len > zp->z_size - off)
len = zp->z_size - off;
/* Flush any mmap()'d data to disk */
if (zn_has_cached_data(zp, off, off + len - 1))
zn_flush_cached_data(zp, B_TRUE);
zfs_locked_range_t *lr;
lr = zfs_rangelock_enter(&zp->z_rangelock, off, len, RL_WRITER);
const uint64_t uid = KUID_TO_SUID(ZTOUID(zp));
const uint64_t gid = KGID_TO_SGID(ZTOGID(zp));
const uint64_t projid = zp->z_projid;
dmu_buf_impl_t *db = (dmu_buf_impl_t *)sa_get_db(zp->z_sa_hdl);
DB_DNODE_ENTER(db);
dnode_t *dn = DB_DNODE(db);
uint64_t n, noff = off, nr = 0, nw = 0;
while (len > 0) {
/*
* Rewrite only actual data, skipping any holes. This might
* be inaccurate for dirty files, but we don't really care.
*/
if (noff == off) {
/* Find next data in the file. */
error = dnode_next_offset(dn, 0, &noff, 1, 1, 0);
if (error || noff >= off + len) {
if (error == ESRCH) /* No more data. */
error = 0;
break;
}
ASSERT3U(noff, >=, off);
len -= noff - off;
off = noff;
/* Find where the data end. */
error = dnode_next_offset(dn, DNODE_FIND_HOLE, &noff,
1, 1, 0);
if (error != 0)
noff = off + len;
}
ASSERT3U(noff, >, off);
if (zfs_id_overblockquota(zfsvfs, DMU_USERUSED_OBJECT, uid) ||
zfs_id_overblockquota(zfsvfs, DMU_GROUPUSED_OBJECT, gid) ||
(projid != ZFS_DEFAULT_PROJID &&
zfs_id_overblockquota(zfsvfs, DMU_PROJECTUSED_OBJECT,
projid))) {
error = SET_ERROR(EDQUOT);
break;
}
n = MIN(MIN(len, noff - off),
DMU_MAX_ACCESS / 2 - P2PHASE(off, zp->z_blksz));
dmu_tx_t *tx = dmu_tx_create(zfsvfs->z_os);
dmu_tx_hold_write_by_dnode(tx, dn, off, n);
error = dmu_tx_assign(tx, DMU_TX_WAIT);
if (error) {
dmu_tx_abort(tx);
break;
}
/* Mark all dbufs within range as dirty to trigger rewrite. */
dmu_buf_t **dbp;
int numbufs;
error = dmu_buf_hold_array_by_dnode(dn, off, n, TRUE, FTAG,
&numbufs, &dbp, DMU_READ_PREFETCH);
if (error) {
dmu_tx_abort(tx);
break;
}
for (int i = 0; i < numbufs; i++) {
nr += dbp[i]->db_size;
if (dmu_buf_is_dirty(dbp[i], tx))
continue;
nw += dbp[i]->db_size;
dmu_buf_will_dirty(dbp[i], tx);
}
dmu_buf_rele_array(dbp, numbufs, FTAG);
dmu_tx_commit(tx);
len -= n;
off += n;
if (issig()) {
error = SET_ERROR(EINTR);
break;
}
}
DB_DNODE_EXIT(db);
dataset_kstats_update_read_kstats(&zfsvfs->z_kstat, nr);
dataset_kstats_update_write_kstats(&zfsvfs->z_kstat, nw);
zfs_rangelock_exit(lr);
zfs_exit(zfsvfs, FTAG);
return (error);
}
int int
zfs_getsecattr(znode_t *zp, vsecattr_t *vsecp, int flag, cred_t *cr) zfs_getsecattr(znode_t *zp, vsecattr_t *vsecp, int flag, cred_t *cr)
{ {

View File

@ -306,6 +306,10 @@ tags = ['functional', 'cli_root', 'zfs_rename']
tests = ['zfs_reservation_001_pos', 'zfs_reservation_002_pos'] tests = ['zfs_reservation_001_pos', 'zfs_reservation_002_pos']
tags = ['functional', 'cli_root', 'zfs_reservation'] tags = ['functional', 'cli_root', 'zfs_reservation']
[tests/functional/cli_root/zfs_rewrite]
tests = ['zfs_rewrite']
tags = ['functional', 'cli_root', 'zfs_rewrite']
[tests/functional/cli_root/zfs_rollback] [tests/functional/cli_root/zfs_rollback]
tests = ['zfs_rollback_001_pos', 'zfs_rollback_002_pos', tests = ['zfs_rollback_001_pos', 'zfs_rollback_002_pos',
'zfs_rollback_003_neg', 'zfs_rollback_004_neg'] 'zfs_rollback_003_neg', 'zfs_rollback_004_neg']

View File

@ -194,6 +194,10 @@ tags = ['functional', 'cli_root', 'zfs_rename']
tests = ['zfs_reservation_001_pos', 'zfs_reservation_002_pos'] tests = ['zfs_reservation_001_pos', 'zfs_reservation_002_pos']
tags = ['functional', 'cli_root', 'zfs_reservation'] tags = ['functional', 'cli_root', 'zfs_reservation']
[tests/functional/cli_root/zfs_rewrite]
tests = ['zfs_rewrite']
tags = ['functional', 'cli_root', 'zfs_rewrite']
[tests/functional/cli_root/zfs_rollback] [tests/functional/cli_root/zfs_rollback]
tests = ['zfs_rollback_003_neg', 'zfs_rollback_004_neg'] tests = ['zfs_rollback_003_neg', 'zfs_rollback_004_neg']
tags = ['functional', 'cli_root', 'zfs_rollback'] tags = ['functional', 'cli_root', 'zfs_rollback']

View File

@ -862,6 +862,9 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zfs_reservation/setup.ksh \ functional/cli_root/zfs_reservation/setup.ksh \
functional/cli_root/zfs_reservation/zfs_reservation_001_pos.ksh \ functional/cli_root/zfs_reservation/zfs_reservation_001_pos.ksh \
functional/cli_root/zfs_reservation/zfs_reservation_002_pos.ksh \ functional/cli_root/zfs_reservation/zfs_reservation_002_pos.ksh \
functional/cli_root/zfs_rewrite/cleanup.ksh \
functional/cli_root/zfs_rewrite/setup.ksh \
functional/cli_root/zfs_rewrite/zfs_rewrite.ksh \
functional/cli_root/zfs_rollback/cleanup.ksh \ functional/cli_root/zfs_rollback/cleanup.ksh \
functional/cli_root/zfs_rollback/setup.ksh \ functional/cli_root/zfs_rollback/setup.ksh \
functional/cli_root/zfs_rollback/zfs_rollback_001_pos.ksh \ functional/cli_root/zfs_rollback/zfs_rollback_001_pos.ksh \

View File

@ -0,0 +1,26 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
. $STF_SUITE/include/libtest.shlib
default_cleanup

View File

@ -0,0 +1,28 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
. $STF_SUITE/include/libtest.shlib
DISK=${DISKS%% *}
default_setup $DISK

View File

@ -0,0 +1,104 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2025, iXsystems, Inc.
#
# DESCRIPTION:
# Verify zfs rewrite rewrites specified files blocks.
#
# STRATEGY:
# 1. Create two files, one of which is in a directory.
# 2. Save the checksums and block pointers.
# 3. Rewrite part of the files.
# 4. Verify checksums are the same.
# 5. Verify block pointers of the rewritten part have changed.
# 6. Rewrite all the files.
# 7. Verify checksums are the same.
# 8. Verify all block pointers have changed.
. $STF_SUITE/include/libtest.shlib
typeset tmp=$(mktemp)
typeset bps=$(mktemp)
typeset bps1=$(mktemp)
typeset bps2=$(mktemp)
function cleanup
{
rm -rf $tmp $bps $bps1 $bps2 $TESTDIR/*
}
log_assert "zfs rewrite rewrites specified files blocks"
log_onexit cleanup
log_must zfs set recordsize=128k $TESTPOOL/$TESTFS
log_must mkdir $TESTDIR/dir
log_must dd if=/dev/urandom of=$TESTDIR/file1 bs=128k count=8
log_must dd if=$TESTDIR/file1 of=$TESTDIR/dir/file2 bs=128k
log_must sync_pool $TESTPOOL
typeset orig_hash1=$(xxh128digest $TESTDIR/file1)
typeset orig_hash2=$(xxh128digest $TESTDIR/dir/file2)
log_must [ "$orig_hash1" = "$orig_hash2" ]
log_must eval "zdb -Ovv $TESTPOOL/$TESTFS file1 > $tmp"
log_must eval "awk '/ L0 / { print l++ \" \" \$3 }' < $tmp > $bps1"
log_must eval "zdb -Ovv $TESTPOOL/$TESTFS dir/file2 > $tmp"
log_must eval "awk '/ L0 / { print l++ \" \" \$3 }' < $tmp > $bps2"
log_must zfs rewrite -o 327680 -l 262144 -r -x $TESTDIR/file1 $TESTDIR/dir/file2
log_must sync_pool $TESTPOOL
typeset new_hash1=$(xxh128digest $TESTDIR/file1)
typeset new_hash2=$(xxh128digest $TESTDIR/dir/file2)
log_must [ "$orig_hash1" = "$new_hash1" ]
log_must [ "$orig_hash2" = "$new_hash2" ]
log_must eval "zdb -Ovv $TESTPOOL/$TESTFS file1 > $tmp"
log_must eval "awk '/ L0 / { print l++ \" \" \$3 }' < $tmp > $bps"
typeset same=$(echo $(sort -n $bps $bps1 | uniq -d | cut -f1 -d' '))
log_must [ "$same" = "0 1 5 6 7" ]
log_must eval "zdb -Ovv $TESTPOOL/$TESTFS dir/file2 > $tmp"
log_must eval "awk '/ L0 / { print l++ \" \" \$3 }' < $tmp > $bps"
typeset same=$(echo $(sort -n $bps $bps2 | uniq -d | cut -f1 -d' '))
log_must [ "$same" = "0 1 5 6 7" ]
log_must zfs rewrite -r $TESTDIR/file1 $TESTDIR/dir/file2
log_must sync_pool $TESTPOOL
typeset new_hash1=$(xxh128digest $TESTDIR/file1)
typeset new_hash2=$(xxh128digest $TESTDIR/dir/file2)
log_must [ "$orig_hash1" = "$new_hash1" ]
log_must [ "$orig_hash2" = "$new_hash2" ]
log_must eval "zdb -Ovv $TESTPOOL/$TESTFS file1 > $tmp"
log_must eval "awk '/ L0 / { print l++ \" \" \$3 }' < $tmp > $bps"
typeset same=$(echo $(sort -n $bps $bps1 | uniq -d | cut -f1 -d' '))
log_must [ -z "$same" ]
log_must eval "zdb -Ovv $TESTPOOL/$TESTFS dir/file2 > $tmp"
log_must eval "awk '/ L0 / { print l++ \" \" \$3 }' < $tmp > $bps"
typeset same=$(echo $(sort -n $bps $bps2 | uniq -d | cut -f1 -d' '))
log_must [ -z "$same" ]
log_pass