mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-22 02:27:36 +03:00
Implement bookmark copying
This feature allows copying existing bookmarks using
zfs bookmark fs#target fs#newbookmark
There are some niche use cases for such functionality,
e.g. when using bookmarks as markers for replication progress.
Copying redaction bookmarks produces a normal bookmark that
cannot be used for redacted send (we are not duplicating
the redaction object).
ZCP support for bookmarking (both creation and copying) will be
implemented in a separate patch based on this work.
Overview:
- Terminology:
- source = existing snapshot or bookmark
- new/bmark = new bookmark
- Implement bookmark copying in `dsl_bookmark.c`
- create new bookmark node
- copy source's `zbn_phys` to new's `zbn_phys`
- zero-out redaction object id in copy
- Extend existing bookmark ioctl nvlist schema to accept
bookmarks as sources
- => `dsl_bookmark_create_nvl_validate` is authoritative
- use `dsl_dataset_is_before` check for both snapshot
and bookmark sources
- Adjust CLI
- refactor shortname expansion logic in `zfs_do_bookmark`
- Update man pages
- warn about redaction bookmark handling
- Add test cases
- CLI
- pyyzfs libzfs_core bindings
Reviewed-by: Matt Ahrens <matt@delphix.com>
Reviewed-by: Paul Dagnelie <pcd@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Christian Schwarz <me@cschwarz.com>
Closes #9571
This commit is contained in:
committed by
Brian Behlendorf
parent
7b49bbc816
commit
a73f361fdb
+65
-36
@@ -30,6 +30,7 @@
|
||||
* Copyright (c) 2019 Datto Inc.
|
||||
* Copyright (c) 2019, loli10K <ezomori.nozomu@gmail.com>
|
||||
* Copyright 2019 Joyent, Inc.
|
||||
* Copyright (c) 2019, 2020 by Christian Schwarz. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
@@ -383,7 +384,8 @@ get_usage(zfs_help_t idx)
|
||||
return (gettext("\tdiff [-FHt] <snapshot> "
|
||||
"[snapshot|filesystem]\n"));
|
||||
case HELP_BOOKMARK:
|
||||
return (gettext("\tbookmark <snapshot> <bookmark>\n"));
|
||||
return (gettext("\tbookmark <snapshot|bookmark> "
|
||||
"<newbookmark>\n"));
|
||||
case HELP_CHANNEL_PROGRAM:
|
||||
return (gettext("\tprogram [-jn] [-t <instruction limit>] "
|
||||
"[-m <memory limit (b)>]\n"
|
||||
@@ -7535,16 +7537,17 @@ out:
|
||||
}
|
||||
|
||||
/*
|
||||
* zfs bookmark <fs@snap> <fs#bmark>
|
||||
* zfs bookmark <fs@source>|<fs#source> <fs#bookmark>
|
||||
*
|
||||
* Creates a bookmark with the given name from the given snapshot.
|
||||
* Creates a bookmark with the given name from the source snapshot
|
||||
* or creates a copy of an existing source bookmark.
|
||||
*/
|
||||
static int
|
||||
zfs_do_bookmark(int argc, char **argv)
|
||||
{
|
||||
char snapname[ZFS_MAX_DATASET_NAME_LEN];
|
||||
char bookname[ZFS_MAX_DATASET_NAME_LEN];
|
||||
zfs_handle_t *zhp;
|
||||
char *source, *bookname;
|
||||
char expbuf[ZFS_MAX_DATASET_NAME_LEN];
|
||||
int source_type;
|
||||
nvlist_t *nvl;
|
||||
int ret = 0;
|
||||
int c;
|
||||
@@ -7564,7 +7567,7 @@ zfs_do_bookmark(int argc, char **argv)
|
||||
|
||||
/* check number of arguments */
|
||||
if (argc < 1) {
|
||||
(void) fprintf(stderr, gettext("missing snapshot argument\n"));
|
||||
(void) fprintf(stderr, gettext("missing source argument\n"));
|
||||
goto usage;
|
||||
}
|
||||
if (argc < 2) {
|
||||
@@ -7572,50 +7575,72 @@ zfs_do_bookmark(int argc, char **argv)
|
||||
goto usage;
|
||||
}
|
||||
|
||||
if (strchr(argv[0], '@') == NULL) {
|
||||
source = argv[0];
|
||||
bookname = argv[1];
|
||||
|
||||
if (strchr(source, '@') == NULL && strchr(source, '#') == NULL) {
|
||||
(void) fprintf(stderr,
|
||||
gettext("invalid snapshot name '%s': "
|
||||
"must contain a '@'\n"), argv[0]);
|
||||
gettext("invalid source name '%s': "
|
||||
"must contain a '@' or '#'\n"), source);
|
||||
goto usage;
|
||||
}
|
||||
if (strchr(argv[1], '#') == NULL) {
|
||||
if (strchr(bookname, '#') == NULL) {
|
||||
(void) fprintf(stderr,
|
||||
gettext("invalid bookmark name '%s': "
|
||||
"must contain a '#'\n"), argv[1]);
|
||||
"must contain a '#'\n"), bookname);
|
||||
goto usage;
|
||||
}
|
||||
|
||||
if (argv[0][0] == '@') {
|
||||
/*
|
||||
* Snapshot name begins with @.
|
||||
* Default to same fs as bookmark.
|
||||
*/
|
||||
(void) strlcpy(snapname, argv[1], sizeof (snapname));
|
||||
*strchr(snapname, '#') = '\0';
|
||||
(void) strlcat(snapname, argv[0], sizeof (snapname));
|
||||
} else {
|
||||
(void) strlcpy(snapname, argv[0], sizeof (snapname));
|
||||
}
|
||||
if (argv[1][0] == '#') {
|
||||
/*
|
||||
* Bookmark name begins with #.
|
||||
* Default to same fs as snapshot.
|
||||
*/
|
||||
(void) strlcpy(bookname, argv[0], sizeof (bookname));
|
||||
*strchr(bookname, '@') = '\0';
|
||||
(void) strlcat(bookname, argv[1], sizeof (bookname));
|
||||
} else {
|
||||
(void) strlcpy(bookname, argv[1], sizeof (bookname));
|
||||
/*
|
||||
* expand source or bookname to full path:
|
||||
* one of them may be specified as short name
|
||||
*/
|
||||
{
|
||||
char **expand;
|
||||
char *source_short, *bookname_short;
|
||||
source_short = strpbrk(source, "@#");
|
||||
bookname_short = strpbrk(bookname, "#");
|
||||
if (source_short == source &&
|
||||
bookname_short == bookname) {
|
||||
(void) fprintf(stderr, gettext(
|
||||
"either source or bookmark must be specified as "
|
||||
"full dataset paths"));
|
||||
goto usage;
|
||||
} else if (source_short != source &&
|
||||
bookname_short != bookname) {
|
||||
expand = NULL;
|
||||
} else if (source_short != source) {
|
||||
strlcpy(expbuf, source, sizeof (expbuf));
|
||||
expand = &bookname;
|
||||
} else if (bookname_short != bookname) {
|
||||
strlcpy(expbuf, bookname, sizeof (expbuf));
|
||||
expand = &source;
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
if (expand != NULL) {
|
||||
*strpbrk(expbuf, "@#") = '\0'; /* dataset name in buf */
|
||||
(void) strlcat(expbuf, *expand, sizeof (expbuf));
|
||||
*expand = expbuf;
|
||||
}
|
||||
}
|
||||
|
||||
zhp = zfs_open(g_zfs, snapname, ZFS_TYPE_SNAPSHOT);
|
||||
/* determine source type */
|
||||
switch (*strpbrk(source, "@#")) {
|
||||
case '@': source_type = ZFS_TYPE_SNAPSHOT; break;
|
||||
case '#': source_type = ZFS_TYPE_BOOKMARK; break;
|
||||
default: abort();
|
||||
}
|
||||
|
||||
/* test the source exists */
|
||||
zfs_handle_t *zhp;
|
||||
zhp = zfs_open(g_zfs, source, source_type);
|
||||
if (zhp == NULL)
|
||||
goto usage;
|
||||
zfs_close(zhp);
|
||||
|
||||
|
||||
nvl = fnvlist_alloc();
|
||||
fnvlist_add_string(nvl, bookname, snapname);
|
||||
fnvlist_add_string(nvl, bookname, source);
|
||||
ret = lzc_bookmark(nvl, NULL);
|
||||
fnvlist_free(nvl);
|
||||
|
||||
@@ -7631,6 +7656,10 @@ zfs_do_bookmark(int argc, char **argv)
|
||||
case EXDEV:
|
||||
err_msg = "bookmark is in a different pool";
|
||||
break;
|
||||
case ZFS_ERR_BOOKMARK_SOURCE_NOT_ANCESTOR:
|
||||
err_msg = "source is not an ancestor of the "
|
||||
"new bookmark's dataset";
|
||||
break;
|
||||
case EEXIST:
|
||||
err_msg = "bookmark exists";
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user