mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2025-05-23 23:14:59 +03:00
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:
parent
9aae14a14a
commit
49fbdd4533
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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 *);
|
||||||
|
@ -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
76
man/man8/zfs-rewrite.8
Normal 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
|
@ -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
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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']
|
||||||
|
@ -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']
|
||||||
|
@ -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 \
|
||||||
|
26
tests/zfs-tests/tests/functional/cli_root/zfs_rewrite/cleanup.ksh
Executable file
26
tests/zfs-tests/tests/functional/cli_root/zfs_rewrite/cleanup.ksh
Executable 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
|
28
tests/zfs-tests/tests/functional/cli_root/zfs_rewrite/setup.ksh
Executable file
28
tests/zfs-tests/tests/functional/cli_root/zfs_rewrite/setup.ksh
Executable 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
|
104
tests/zfs-tests/tests/functional/cli_root/zfs_rewrite/zfs_rewrite.ksh
Executable file
104
tests/zfs-tests/tests/functional/cli_root/zfs_rewrite/zfs_rewrite.ksh
Executable 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
|
Loading…
Reference in New Issue
Block a user