Skip to content

Commit a846f69

Browse files
committed
Introduce zfs rewrite subcommand
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 <[email protected]> Sponsored by: iXsystems, Inc.
1 parent 1d8f625 commit a846f69

File tree

9 files changed

+437
-5
lines changed

9 files changed

+437
-5
lines changed

cmd/zfs/zfs_main.c

+194-4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include <assert.h>
3838
#include <ctype.h>
3939
#include <sys/debug.h>
40+
#include <dirent.h>
4041
#include <errno.h>
4142
#include <getopt.h>
4243
#include <libgen.h>
@@ -121,6 +122,7 @@ static int zfs_do_change_key(int argc, char **argv);
121122
static int zfs_do_project(int argc, char **argv);
122123
static int zfs_do_version(int argc, char **argv);
123124
static int zfs_do_redact(int argc, char **argv);
125+
static int zfs_do_rewrite(int argc, char **argv);
124126
static int zfs_do_wait(int argc, char **argv);
125127

126128
#ifdef __FreeBSD__
@@ -193,6 +195,7 @@ typedef enum {
193195
HELP_CHANGE_KEY,
194196
HELP_VERSION,
195197
HELP_REDACT,
198+
HELP_REWRITE,
196199
HELP_JAIL,
197200
HELP_UNJAIL,
198201
HELP_WAIT,
@@ -227,7 +230,7 @@ static zfs_command_t command_table[] = {
227230
{ "promote", zfs_do_promote, HELP_PROMOTE },
228231
{ "rename", zfs_do_rename, HELP_RENAME },
229232
{ "bookmark", zfs_do_bookmark, HELP_BOOKMARK },
230-
{ "program", zfs_do_channel_program, HELP_CHANNEL_PROGRAM },
233+
{ "diff", zfs_do_diff, HELP_DIFF },
231234
{ NULL },
232235
{ "list", zfs_do_list, HELP_LIST },
233236
{ NULL },
@@ -249,27 +252,31 @@ static zfs_command_t command_table[] = {
249252
{ NULL },
250253
{ "send", zfs_do_send, HELP_SEND },
251254
{ "receive", zfs_do_receive, HELP_RECEIVE },
255+
{ "redact", zfs_do_redact, HELP_REDACT },
252256
{ NULL },
253257
{ "allow", zfs_do_allow, HELP_ALLOW },
254-
{ NULL },
255258
{ "unallow", zfs_do_unallow, HELP_UNALLOW },
256259
{ NULL },
257260
{ "hold", zfs_do_hold, HELP_HOLD },
258261
{ "holds", zfs_do_holds, HELP_HOLDS },
259262
{ "release", zfs_do_release, HELP_RELEASE },
260-
{ "diff", zfs_do_diff, HELP_DIFF },
263+
{ NULL },
261264
{ "load-key", zfs_do_load_key, HELP_LOAD_KEY },
262265
{ "unload-key", zfs_do_unload_key, HELP_UNLOAD_KEY },
263266
{ "change-key", zfs_do_change_key, HELP_CHANGE_KEY },
264-
{ "redact", zfs_do_redact, HELP_REDACT },
267+
{ NULL },
268+
{ "program", zfs_do_channel_program, HELP_CHANNEL_PROGRAM },
269+
{ "rewrite", zfs_do_rewrite, HELP_REWRITE },
265270
{ "wait", zfs_do_wait, HELP_WAIT },
266271

267272
#ifdef __FreeBSD__
273+
{ NULL },
268274
{ "jail", zfs_do_jail, HELP_JAIL },
269275
{ "unjail", zfs_do_unjail, HELP_UNJAIL },
270276
#endif
271277

272278
#ifdef __linux__
279+
{ NULL },
273280
{ "zone", zfs_do_zone, HELP_ZONE },
274281
{ "unzone", zfs_do_unzone, HELP_UNZONE },
275282
#endif
@@ -432,6 +439,9 @@ get_usage(zfs_help_t idx)
432439
case HELP_REDACT:
433440
return (gettext("\tredact <snapshot> <bookmark> "
434441
"<redaction_snapshot> ...\n"));
442+
case HELP_REWRITE:
443+
return (gettext("\trewrite [-rx] [-o <offset>] [-l <length>] "
444+
"<directory|file ...>\n"));
435445
case HELP_JAIL:
436446
return (gettext("\tjail <jailid|jailname> <filesystem>\n"));
437447
case HELP_UNJAIL:
@@ -9016,6 +9026,186 @@ zfs_do_project(int argc, char **argv)
90169026
return (ret);
90179027
}
90189028

9029+
static int
9030+
zfs_rewrite_file(const char *path, zfs_rewrite_args_t *args)
9031+
{
9032+
int fd, ret = 0;
9033+
9034+
fd = open(path, O_WRONLY);
9035+
if (fd < 0) {
9036+
ret = errno;
9037+
(void) fprintf(stderr, gettext("failed to open %s: %s\n"),
9038+
path, strerror(errno));
9039+
return (ret);
9040+
}
9041+
9042+
if (ioctl(fd, ZFS_IOC_REWRITE, args) < 0) {
9043+
ret = errno;
9044+
(void) fprintf(stderr, gettext("failed to rewrite %s: %s\n"),
9045+
path, strerror(errno));
9046+
}
9047+
9048+
close(fd);
9049+
return (ret);
9050+
}
9051+
9052+
static int
9053+
zfs_rewrite_dir(const char *path, boolean_t xdev, dev_t dev,
9054+
zfs_rewrite_args_t *args, nvlist_t *dirs)
9055+
{
9056+
struct dirent *ent;
9057+
DIR *dir;
9058+
int ret = 0, err;
9059+
9060+
dir = opendir(path);
9061+
if (dir == NULL) {
9062+
if (errno == ENOENT)
9063+
return (0);
9064+
ret = errno;
9065+
(void) fprintf(stderr, gettext("failed to opendir %s: %s\n"),
9066+
path, strerror(errno));
9067+
return (ret);
9068+
}
9069+
9070+
size_t plen = strlen(path) + 1;
9071+
while ((ent = readdir(dir)) != NULL) {
9072+
char *fullname;
9073+
struct stat st;
9074+
9075+
if (ent->d_type != DT_REG && ent->d_type != DT_DIR)
9076+
continue;
9077+
9078+
if (strcmp(ent->d_name, ".") == 0 ||
9079+
strcmp(ent->d_name, "..") == 0)
9080+
continue;
9081+
9082+
if (plen + strlen(ent->d_name) >= PATH_MAX) {
9083+
(void) fprintf(stderr, gettext("path too long %s/%s\n"),
9084+
path, ent->d_name);
9085+
ret = ENAMETOOLONG;
9086+
continue;
9087+
}
9088+
9089+
if (asprintf(&fullname, "%s/%s", path, ent->d_name) == -1) {
9090+
(void) fprintf(stderr,
9091+
gettext("failed to allocate memory\n"));
9092+
ret = ENOMEM;
9093+
continue;
9094+
}
9095+
9096+
if (xdev) {
9097+
if (stat(fullname, &st) < 0) {
9098+
ret = errno;
9099+
(void) fprintf(stderr,
9100+
gettext("failed to stat %s: %s\n"),
9101+
fullname, strerror(errno));
9102+
free(fullname);
9103+
continue;
9104+
}
9105+
if (st.st_dev != dev) {
9106+
free(fullname);
9107+
continue;
9108+
}
9109+
}
9110+
9111+
if (ent->d_type == DT_REG) {
9112+
err = zfs_rewrite_file(fullname, args);
9113+
if (err)
9114+
ret = err;
9115+
} else { /* DT_DIR */
9116+
fnvlist_add_uint64(dirs, fullname, dev);
9117+
}
9118+
9119+
free(fullname);
9120+
}
9121+
9122+
closedir(dir);
9123+
return (ret);
9124+
}
9125+
9126+
static int
9127+
zfs_rewrite_path(const char *path, boolean_t recurse, boolean_t xdev,
9128+
zfs_rewrite_args_t *args, nvlist_t *dirs)
9129+
{
9130+
struct stat st;
9131+
int ret = 0;
9132+
9133+
if (stat(path, &st) < 0) {
9134+
ret = errno;
9135+
(void) fprintf(stderr, gettext("failed to stat %s: %s\n"),
9136+
path, strerror(errno));
9137+
return (ret);
9138+
}
9139+
9140+
if (S_ISREG(st.st_mode))
9141+
ret = zfs_rewrite_file(path, args);
9142+
else if (S_ISDIR(st.st_mode) && recurse)
9143+
ret = zfs_rewrite_dir(path, xdev, st.st_dev, args, dirs);
9144+
return (ret);
9145+
}
9146+
9147+
static int
9148+
zfs_do_rewrite(int argc, char **argv)
9149+
{
9150+
int ret = 0, err, c;
9151+
boolean_t recurse = B_FALSE, xdev = B_FALSE;
9152+
9153+
if (argc < 2)
9154+
usage(B_FALSE);
9155+
9156+
zfs_rewrite_args_t args;
9157+
args.off = 0;
9158+
args.len = 0;
9159+
args.flags = 0;
9160+
9161+
while ((c = getopt(argc, argv, "l:o:rx")) != -1) {
9162+
switch (c) {
9163+
case 'l':
9164+
args.len = strtoll(optarg, NULL, 0);
9165+
break;
9166+
case 'o':
9167+
args.off = strtoll(optarg, NULL, 0);
9168+
break;
9169+
case 'r':
9170+
recurse = B_TRUE;
9171+
break;
9172+
case 'x':
9173+
xdev = B_TRUE;
9174+
break;
9175+
default:
9176+
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
9177+
optopt);
9178+
usage(B_FALSE);
9179+
}
9180+
}
9181+
9182+
argv += optind;
9183+
argc -= optind;
9184+
if (argc == 0) {
9185+
(void) fprintf(stderr,
9186+
gettext("missing file or directory target(s)\n"));
9187+
usage(B_FALSE);
9188+
}
9189+
9190+
nvlist_t *dirs = fnvlist_alloc();
9191+
for (int i = 0; i < argc; i++) {
9192+
err = zfs_rewrite_path(argv[i], recurse, xdev, &args, dirs);
9193+
if (err)
9194+
ret = err;
9195+
}
9196+
nvpair_t *dir;
9197+
while ((dir = nvlist_next_nvpair(dirs, NULL)) != NULL) {
9198+
err = zfs_rewrite_dir(nvpair_name(dir), xdev,
9199+
fnvpair_value_uint64(dir), &args, dirs);
9200+
if (err)
9201+
ret = err;
9202+
fnvlist_remove_nvpair(dirs, dir);
9203+
}
9204+
fnvlist_free(dirs);
9205+
9206+
return (ret);
9207+
}
9208+
90199209
static int
90209210
zfs_do_wait(int argc, char **argv)
90219211
{

include/sys/fs/zfs.h

+8
Original file line numberDiff line numberDiff line change
@@ -1620,6 +1620,14 @@ typedef enum zfs_ioc {
16201620

16211621
#endif
16221622

1623+
typedef struct zfs_rewrite_args {
1624+
uint64_t off;
1625+
uint64_t len;
1626+
uint64_t flags;
1627+
} zfs_rewrite_args_t;
1628+
1629+
#define ZFS_IOC_REWRITE _IOW(0x83, 3, zfs_rewrite_args_t)
1630+
16231631
/*
16241632
* ZFS-specific error codes used for returning descriptive errors
16251633
* to the userland through zfs ioctls.

include/sys/zfs_vnops.h

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ extern int zfs_clone_range(znode_t *, uint64_t *, znode_t *, uint64_t *,
4040
uint64_t *, cred_t *);
4141
extern int zfs_clone_range_replay(znode_t *, uint64_t, uint64_t, uint64_t,
4242
const blkptr_t *, size_t);
43+
extern int zfs_rewrite(znode_t *, uint64_t, uint64_t, uint64_t);
4344

4445
extern int zfs_getsecattr(znode_t *, vsecattr_t *, int, cred_t *);
4546
extern int zfs_setsecattr(znode_t *, vsecattr_t *, int, cred_t *);

man/Makefile.am

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ dist_man_MANS = \
5050
%D%/man8/zfs-redact.8 \
5151
%D%/man8/zfs-release.8 \
5252
%D%/man8/zfs-rename.8 \
53+
%D%/man8/zfs-rewrite.8 \
5354
%D%/man8/zfs-rollback.8 \
5455
%D%/man8/zfs-send.8 \
5556
%D%/man8/zfs-set.8 \

man/man8/zfs-rewrite.8

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
.\" SPDX-License-Identifier: CDDL-1.0
2+
.\"
3+
.\" CDDL HEADER START
4+
.\"
5+
.\" The contents of this file are subject to the terms of the
6+
.\" Common Development and Distribution License (the "License").
7+
.\" You may not use this file except in compliance with the License.
8+
.\"
9+
.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10+
.\" or https://opensource.org/licenses/CDDL-1.0.
11+
.\" See the License for the specific language governing permissions
12+
.\" and limitations under the License.
13+
.\"
14+
.\" When distributing Covered Code, include this CDDL HEADER in each
15+
.\" file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16+
.\" If applicable, add the following below this CDDL HEADER, with the
17+
.\" fields enclosed by brackets "[]" replaced with your own identifying
18+
.\" information: Portions Copyright [yyyy] [name of copyright owner]
19+
.\"
20+
.\" CDDL HEADER END
21+
.\"
22+
.\" Copyright (c) 2025 iXsystems, Inc.
23+
.\"
24+
.Dd April 18, 2025
25+
.Dt ZFS-REWRITE 8
26+
.Os
27+
.
28+
.Sh NAME
29+
.Nm zfs-rewrite
30+
.Nd rewrite specified files/directories without modification
31+
.Sh SYNOPSIS
32+
.Nm zfs
33+
.Cm rewrite
34+
.Oo Fl rx Ns Oc
35+
.Op Fl l Ar length
36+
.Op Fl o Ar offset
37+
.Ar file Ns | Ns Ar directory Ns
38+
.
39+
.Sh DESCRIPTION
40+
Rewrite blocks of specified
41+
.Ar file
42+
as is without modification at a new location and possibly with new
43+
properties, such as checksum, compression, deduplication, copies, etc,
44+
as if they were atomically read and written back.
45+
.Bl -tag -width "-r"
46+
.It Fl l Ar length
47+
Rewrite at most this number of bytes.
48+
.It Fl o Ar offset
49+
Start at this offset in bytes.
50+
.It Fl r
51+
Recurse into directories.
52+
.It Fl x
53+
Don't cross file system mount points when recursing.
54+
.El
55+
.
56+
.Sh SEE ALSO
57+
.Xr zfsprops 7 ,

man/man8/zfs.8

+7-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
.\" Copyright 2018 Nexenta Systems, Inc.
3838
.\" Copyright 2019 Joyent, Inc.
3939
.\"
40-
.Dd May 12, 2022
40+
.Dd April 18, 2025
4141
.Dt ZFS 8
4242
.Os
4343
.
@@ -299,6 +299,12 @@ Execute ZFS administrative operations
299299
programmatically via a Lua script-language channel program.
300300
.El
301301
.
302+
.Ss Data rewrite
303+
.Bl -tag -width ""
304+
.It Xr zfs-rewrite 8
305+
Rewrite specified files/directories without modification.
306+
.El
307+
.
302308
.Ss Jails
303309
.Bl -tag -width ""
304310
.It Xr zfs-jail 8

module/os/freebsd/zfs/zfs_vnops_os.c

+12
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,18 @@ zfs_ioctl(vnode_t *vp, ulong_t com, intptr_t data, int flag, cred_t *cred,
305305
*(offset_t *)data = off;
306306
return (0);
307307
}
308+
case ZFS_IOC_REWRITE: {
309+
zfs_rewrite_args_t *args = (zfs_rewrite_args_t *)data;
310+
if ((flag & FWRITE) == 0)
311+
return (SET_ERROR(EBADF));
312+
error = vn_lock(vp, LK_SHARED);
313+
if (error)
314+
return (error);
315+
error = zfs_rewrite(VTOZ(vp), args->off, args->len,
316+
args->flags);
317+
VOP_UNLOCK(vp);
318+
return (error);
319+
}
308320
}
309321
return (SET_ERROR(ENOTTY));
310322
}

0 commit comments

Comments
 (0)