Skip to content

Commit eec53cf

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 131df3b commit eec53cf

File tree

6 files changed

+340
-4
lines changed

6 files changed

+340
-4
lines changed

cmd/zfs/zfs_main.c

+167-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 [-r] [-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,159 @@ 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, zfs_rewrite_args_t *args, nvlist_t *dirs)
9054+
{
9055+
struct dirent *ent;
9056+
DIR *dir;
9057+
int ret = 0, err;
9058+
9059+
dir = opendir(path);
9060+
if (dir == NULL) {
9061+
if (errno == ENOENT)
9062+
return (0);
9063+
(void) fprintf(stderr, gettext("failed to opendir %s: %s\n"),
9064+
path, strerror(errno));
9065+
return (errno);
9066+
}
9067+
9068+
size_t plen = strlen(path) + 1;
9069+
while ((ent = readdir(dir)) != NULL) {
9070+
char *fullname;
9071+
9072+
if (strcmp(ent->d_name, ".") == 0 ||
9073+
strcmp(ent->d_name, "..") == 0)
9074+
continue;
9075+
9076+
if (plen + strlen(ent->d_name) >= PATH_MAX) {
9077+
(void) fprintf(stderr, gettext("path too long %s/%s\n"),
9078+
path, ent->d_name);
9079+
ret = ENAMETOOLONG;
9080+
continue;
9081+
}
9082+
9083+
if (asprintf(&fullname, "%s/%s", path, ent->d_name) == -1) {
9084+
(void) fprintf(stderr,
9085+
gettext("failed to allocate memory\n"));
9086+
ret = ENOMEM;
9087+
continue;
9088+
}
9089+
9090+
if (ent->d_type == DT_REG) {
9091+
err = zfs_rewrite_file(fullname, args);
9092+
if (err)
9093+
ret = err;
9094+
} else if (ent->d_type == DT_DIR)
9095+
fnvlist_add_boolean(dirs, fullname);
9096+
9097+
free(fullname);
9098+
}
9099+
9100+
closedir(dir);
9101+
return (ret);
9102+
}
9103+
9104+
static int
9105+
zfs_rewrite_path(const char *path, boolean_t recurse, zfs_rewrite_args_t *args,
9106+
nvlist_t *dirs)
9107+
{
9108+
struct stat st;
9109+
9110+
int ret = stat(path, &st);
9111+
if (ret) {
9112+
(void) fprintf(stderr, gettext("failed to stat %s: %s\n"),
9113+
path, strerror(errno));
9114+
return (ret);
9115+
}
9116+
9117+
if (S_ISREG(st.st_mode))
9118+
ret = zfs_rewrite_file(path, args);
9119+
else if (S_ISDIR(st.st_mode) && recurse)
9120+
ret = zfs_rewrite_dir(path, args, dirs);
9121+
return (ret);
9122+
}
9123+
9124+
static int
9125+
zfs_do_rewrite(int argc, char **argv)
9126+
{
9127+
int ret = 0, err, c;
9128+
boolean_t recurse = B_FALSE;
9129+
9130+
if (argc < 2)
9131+
usage(B_FALSE);
9132+
9133+
zfs_rewrite_args_t args;
9134+
args.off = 0;
9135+
args.len = 0;
9136+
args.flags = 0;
9137+
9138+
while ((c = getopt(argc, argv, "ro:l:")) != -1) {
9139+
switch (c) {
9140+
case 'r':
9141+
recurse = B_TRUE;
9142+
break;
9143+
case 'o':
9144+
args.off = strtoll(optarg, NULL, 0);
9145+
break;
9146+
case 'l':
9147+
args.len = strtoll(optarg, NULL, 0);
9148+
break;
9149+
default:
9150+
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
9151+
optopt);
9152+
usage(B_FALSE);
9153+
}
9154+
}
9155+
9156+
argv += optind;
9157+
argc -= optind;
9158+
if (argc == 0) {
9159+
(void) fprintf(stderr,
9160+
gettext("missing file or directory target(s)\n"));
9161+
usage(B_FALSE);
9162+
}
9163+
9164+
nvlist_t *dirs = fnvlist_alloc();
9165+
for (int i = 0; i < argc; i++) {
9166+
err = zfs_rewrite_path(argv[i], recurse, &args, dirs);
9167+
if (err)
9168+
ret = err;
9169+
}
9170+
nvpair_t *dir;
9171+
while ((dir = nvlist_next_nvpair(dirs, NULL)) != NULL) {
9172+
err = zfs_rewrite_dir(nvpair_name(dir), &args, dirs);
9173+
if (err)
9174+
ret = err;
9175+
fnvlist_remove_nvpair(dirs, dir);
9176+
}
9177+
fnvlist_free(dirs);
9178+
9179+
return (ret);
9180+
}
9181+
90199182
static int
90209183
zfs_do_wait(int argc, char **argv)
90219184
{

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 *);

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
}

module/os/linux/zfs/zpl_file.c

+23
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,27 @@ zpl_ioctl_setdosflags(struct file *filp, void __user *arg)
986986
return (err);
987987
}
988988

989+
static int
990+
zpl_ioctl_rewrite(struct file *filp, void __user *arg)
991+
{
992+
struct inode *ip = file_inode(filp);
993+
zfs_rewrite_args_t args;
994+
fstrans_cookie_t cookie;
995+
int err;
996+
997+
if (copy_from_user(&args, arg, sizeof (args)))
998+
return (-EFAULT);
999+
1000+
if (unlikely(!(filp->f_mode & FMODE_WRITE)))
1001+
return (-EBADF);
1002+
1003+
cookie = spl_fstrans_mark();
1004+
err = -zfs_rewrite(ITOZ(ip), args.off, args.len, args.flags);
1005+
spl_fstrans_unmark(cookie);
1006+
1007+
return (err);
1008+
}
1009+
9891010
static long
9901011
zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
9911012
{
@@ -1010,6 +1031,8 @@ zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
10101031
return (zpl_ioctl_ficlonerange(filp, (void *)arg));
10111032
case ZFS_IOC_COMPAT_FIDEDUPERANGE:
10121033
return (zpl_ioctl_fideduperange(filp, (void *)arg));
1034+
case ZFS_IOC_REWRITE:
1035+
return (zpl_ioctl_rewrite(filp, (void *)arg));
10131036
default:
10141037
return (-ENOTTY);
10151038
}

0 commit comments

Comments
 (0)