"[-o \"all\" | field[,...]]\n"
"\t [-t type[,...]] [-s source[,...]]\n"
"\t <\"all\" | property[,...]> "
- "[filesystem|volume|snapshot] ...\n"));
+ "[filesystem|volume|snapshot|bookmark] ...\n"));
case HELP_INHERIT:
return (gettext("\tinherit [-rS] <property> "
"<filesystem|volume|snapshot> ...\n"));
{
zprop_get_cbdata_t cb = { 0 };
int i, c, flags = ZFS_ITER_ARGS_CAN_BE_PATHS;
- int types = ZFS_TYPE_DATASET;
+ int types = ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK;
char *value, *fields;
int ret = 0;
int limit = 0;
NAME_ERR_EMPTY_COMPONENT, /* name contains an empty component */
NAME_ERR_TRAILING_SLASH, /* name ends with a slash */
NAME_ERR_INVALCHAR, /* invalid character found */
- NAME_ERR_MULTIPLE_AT, /* multiple '@' characters found */
+ NAME_ERR_MULTIPLE_DELIMITERS, /* multiple '@'/'#' delimiters found */
NAME_ERR_NOLETTER, /* pool doesn't begin with a letter */
NAME_ERR_RESERVED, /* entire name is reserved */
NAME_ERR_DISKLIKE, /* reserved disk name (c[0-9].*) */
#define ZFS_PERMSET_MAXLEN 64
int pool_namecheck(const char *, namecheck_err_t *, char *);
+int entity_namecheck(const char *, namecheck_err_t *, char *);
int dataset_namecheck(const char *, namecheck_err_t *, char *);
int mountpoint_namecheck(const char *, namecheck_err_t *);
int zfs_component_namecheck(const char *, namecheck_err_t *, char *);
char what;
(void) zfs_prop_get_table();
- if (dataset_namecheck(path, &why, &what) != 0) {
+ if (entity_namecheck(path, &why, &what) != 0) {
if (hdl != NULL) {
switch (why) {
case NAME_ERR_TOOLONG:
"'%c' in name"), what);
break;
- case NAME_ERR_MULTIPLE_AT:
+ case NAME_ERR_MULTIPLE_DELIMITERS:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
- "multiple '@' delimiters in name"));
+ "multiple '@' and/or '#' delimiters in "
+ "name"));
break;
case NAME_ERR_NOLETTER:
if (!(type & ZFS_TYPE_SNAPSHOT) && strchr(path, '@') != NULL) {
if (hdl != NULL)
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
- "snapshot delimiter '@' in filesystem name"));
+ "snapshot delimiter '@' is not expected here"));
return (0);
}
return (0);
}
+ if (!(type & ZFS_TYPE_BOOKMARK) && strchr(path, '#') != NULL) {
+ if (hdl != NULL)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "bookmark delimiter '#' is not expected here"));
+ return (0);
+ }
+
+ if (type == ZFS_TYPE_BOOKMARK && strchr(path, '#') == NULL) {
+ if (hdl != NULL)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "missing '#' delimiter in bookmark name"));
+ return (0);
+ }
+
if (modifying && strchr(path, '%') != NULL) {
if (hdl != NULL)
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
return (zhp);
}
+struct zfs_open_bookmarks_cb_data {
+ const char *path;
+ zfs_handle_t *zhp;
+};
+
+static int
+zfs_open_bookmarks_cb(zfs_handle_t *zhp, void *data)
+{
+ struct zfs_open_bookmarks_cb_data *dp = data;
+
+ /*
+ * Is it the one we are looking for?
+ */
+ if (strcmp(dp->path, zfs_get_name(zhp)) == 0) {
+ /*
+ * We found it. Save it and let the caller know we are done.
+ */
+ dp->zhp = zhp;
+ return (EEXIST);
+ }
+
+ /*
+ * Not found. Close the handle and ask for another one.
+ */
+ zfs_close(zhp);
+ return (0);
+}
+
/*
- * Opens the given snapshot, filesystem, or volume. The 'types'
+ * Opens the given snapshot, bookmark, filesystem, or volume. The 'types'
* argument is a mask of acceptable types. The function will print an
* appropriate error message and return NULL if it can't be opened.
*/
{
zfs_handle_t *zhp;
char errbuf[1024];
+ char *bookp;
(void) snprintf(errbuf, sizeof (errbuf),
dgettext(TEXT_DOMAIN, "cannot open '%s'"), path);
/*
* Validate the name before we even try to open it.
*/
- if (!zfs_validate_name(hdl, path, ZFS_TYPE_DATASET, B_FALSE)) {
- zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
- "invalid dataset name"));
+ if (!zfs_validate_name(hdl, path, types, B_FALSE)) {
(void) zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
return (NULL);
}
/*
- * Try to get stats for the dataset, which will tell us if it exists.
+ * Bookmarks needs to be handled separately.
*/
- errno = 0;
- if ((zhp = make_dataset_handle(hdl, path)) == NULL) {
- (void) zfs_standard_error(hdl, errno, errbuf);
- return (NULL);
+ bookp = strchr(path, '#');
+ if (bookp == NULL) {
+ /*
+ * Try to get stats for the dataset, which will tell us if it
+ * exists.
+ */
+ errno = 0;
+ if ((zhp = make_dataset_handle(hdl, path)) == NULL) {
+ (void) zfs_standard_error(hdl, errno, errbuf);
+ return (NULL);
+ }
+ } else {
+ char dsname[ZFS_MAX_DATASET_NAME_LEN];
+ zfs_handle_t *pzhp;
+ struct zfs_open_bookmarks_cb_data cb_data = {path, NULL};
+
+ /*
+ * We need to cut out '#' and everything after '#'
+ * to get the parent dataset name only.
+ */
+ assert(bookp - path < sizeof (dsname));
+ (void) strncpy(dsname, path, bookp - path);
+ dsname[bookp - path] = '\0';
+
+ /*
+ * Create handle for the parent dataset.
+ */
+ errno = 0;
+ if ((pzhp = make_dataset_handle(hdl, dsname)) == NULL) {
+ (void) zfs_standard_error(hdl, errno, errbuf);
+ return (NULL);
+ }
+
+ /*
+ * Iterate bookmarks to find the right one.
+ */
+ errno = 0;
+ if ((zfs_iter_bookmarks(pzhp, zfs_open_bookmarks_cb,
+ &cb_data) == 0) && (cb_data.zhp == NULL)) {
+ (void) zfs_error(hdl, EZFS_NOENT, errbuf);
+ zfs_close(pzhp);
+ return (NULL);
+ }
+ if (cb_data.zhp == NULL) {
+ (void) zfs_standard_error(hdl, errno, errbuf);
+ zfs_close(pzhp);
+ return (NULL);
+ }
+ zhp = cb_data.zhp;
+
+ /*
+ * Cleanup.
+ */
+ zfs_close(pzhp);
}
if (!(types & zhp->zfs_type)) {
"trailing slash in name"));
break;
- case NAME_ERR_MULTIPLE_AT:
+ case NAME_ERR_MULTIPLE_DELIMITERS:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
- "multiple '@' delimiters in name"));
+ "multiple '@' and/or '#' delimiters in "
+ "name"));
break;
case NAME_ERR_NO_AT:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
* Given a name, determine whether or not it's a valid path
* (starts with '/' or "./"). If so, walk the mnttab trying
* to match the device number. If not, treat the path as an
- * fs/vol/snap name.
+ * fs/vol/snap/bkmark name.
*/
zfs_handle_t *
zfs_path_to_zhandle(libzfs_handle_t *hdl, char *path, zfs_type_t argtype)
.ne 2
.na
\fB\fBzfs set\fR \fIproperty\fR=\fIvalue\fR[ \fIproperty\fR=\fIvalue\fR]...
-\fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR ...\fR
+\fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR|\fIbookmark\fR ...\fR
.ad
.sp .6
.RS 4n
.sp
.ne 2
-\fB\fBzfs get\fR [\fB-r\fR|\fB-d\fR \fIdepth\fR] [\fB-Hp\fR] [\fB-o\fR \fIfield\fR[,...] [\fB-t\fR \fItype\fR[,...]] [\fB-s\fR \fIsource\fR[,...] "\fIall\fR" | \fIproperty\fR[,...] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR ...\fR
+\fB\fBzfs get\fR [\fB-r\fR|\fB-d\fR \fIdepth\fR] [\fB-Hp\fR] [\fB-o\fR \fIfield\fR[,...] [\fB-t\fR \fItype\fR[,...]] [\fB-s\fR \fIsource\fR[,...] "\fIall\fR" | \fIproperty\fR[,...] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR ...\fR|\fIbookmark\fR ...\fR
.ad
.sp .6
.RS 4n
}
/*
- * Dataset names must be of the following form:
+ * Entity names must be of the following form:
*
- * [component][/]*[component][@component]
+ * [component/]*[component][(@|#)component]?
*
* Where each component is made up of alphanumeric characters plus the following
* characters:
* names for temporary clones (for online recv).
*/
int
-dataset_namecheck(const char *path, namecheck_err_t *why, char *what)
+entity_namecheck(const char *path, namecheck_err_t *why, char *what)
{
- const char *loc, *end;
- int found_snapshot;
+ const char *start, *end, *loc;
+ int found_delim;
/*
* Make sure the name is not too long.
return (-1);
}
- loc = path;
- found_snapshot = 0;
+ start = path;
+ found_delim = 0;
for (;;) {
/* Find the end of this component */
- end = loc;
- while (*end != '/' && *end != '@' && *end != '\0')
+ end = start;
+ while (*end != '/' && *end != '@' && *end != '#' &&
+ *end != '\0')
end++;
if (*end == '\0' && end[-1] == '/') {
return (-1);
}
- /* Zero-length components are not allowed */
- if (loc == end) {
- if (why) {
- /*
- * Make sure this is really a zero-length
- * component and not a '@@'.
- */
- if (*end == '@' && found_snapshot) {
- *why = NAME_ERR_MULTIPLE_AT;
- } else {
- *why = NAME_ERR_EMPTY_COMPONENT;
- }
- }
-
- return (-1);
- }
-
/* Validate the contents of this component */
- while (loc != end) {
+ for (loc = start; loc != end; loc++) {
if (!valid_char(*loc) && *loc != '%') {
if (why) {
*why = NAME_ERR_INVALCHAR;
}
return (-1);
}
- loc++;
}
- /* If we've reached the end of the string, we're OK */
- if (*end == '\0')
- return (0);
-
- if (*end == '@') {
- /*
- * If we've found an @ symbol, indicate that we're in
- * the snapshot component, and report a second '@'
- * character as an error.
- */
- if (found_snapshot) {
+ /* Snapshot or bookmark delimiter found */
+ if (*end == '@' || *end == '#') {
+ /* Multiple delimiters are not allowed */
+ if (found_delim != 0) {
if (why)
- *why = NAME_ERR_MULTIPLE_AT;
+ *why = NAME_ERR_MULTIPLE_DELIMITERS;
return (-1);
}
- found_snapshot = 1;
+ found_delim = 1;
}
+ /* Zero-length components are not allowed */
+ if (start == end) {
+ if (why)
+ *why = NAME_ERR_EMPTY_COMPONENT;
+ return (-1);
+ }
+
+ /* If we've reached the end of the string, we're OK */
+ if (*end == '\0')
+ return (0);
+
/*
- * If there is a '/' in a snapshot name
+ * If there is a '/' in a snapshot or bookmark name
* then report an error
*/
- if (*end == '/' && found_snapshot) {
+ if (*end == '/' && found_delim != 0) {
if (why)
*why = NAME_ERR_TRAILING_SLASH;
return (-1);
}
/* Update to the next component */
- loc = end + 1;
+ start = end + 1;
}
}
+/*
+ * Dataset is any entity, except bookmark
+ */
+int
+dataset_namecheck(const char *path, namecheck_err_t *why, char *what)
+{
+ int ret = entity_namecheck(path, why, what);
+
+ if (ret == 0 && strchr(path, '#') != NULL) {
+ if (why != NULL) {
+ *why = NAME_ERR_INVALCHAR;
+ *what = '#';
+ }
+ return (-1);
+ }
+
+ return (ret);
+}
/*
* mountpoint names must be of the following form:
export TESTFILE0=testfile0.$$
export TESTFILE1=testfile1.$$
export TESTFILE2=testfile2.$$
+export TESTBKMARK=testbkmark$$
export LONGPNAME="poolname50charslong_012345678901234567890123456789"
export LONGFSNAME="fsysname50charslong_012345678901234567890123456789"
log_must $ZFS clone $snap $clone
}
+#
+# Create a bookmark of the given snapshot. Defaultly create a bookmark on
+# filesystem.
+#
+# $1 Existing filesystem or volume name. Default, $TESTFS
+# $2 Existing snapshot name. Default, $TESTSNAP
+# $3 bookmark name. Default, $TESTBKMARK
+#
+function create_bookmark
+{
+ typeset fs_vol=${1:-$TESTFS}
+ typeset snap=${2:-$TESTSNAP}
+ typeset bkmark=${3:-$TESTBKMARK}
+
+ [[ -z $fs_vol ]] && log_fail "Filesystem or volume's name is undefined."
+ [[ -z $snap ]] && log_fail "Snapshot's name is undefined."
+ [[ -z $bkmark ]] && log_fail "Bookmark's name is undefined."
+
+ if bkmarkexists $fs_vol#$bkmark; then
+ log_fail "$fs_vol#$bkmark already exists."
+ fi
+ datasetexists $fs_vol || \
+ log_fail "$fs_vol must exist."
+ snapexists $fs_vol@$snap || \
+ log_fail "$fs_vol@$snap must exist."
+
+ log_must $ZFS bookmark $fs_vol@$snap $fs_vol#$bkmark
+}
+
function default_mirror_setup
{
default_mirror_setup_noexit $1 $2 $3
log_must $RM -rf $mtpt
}
+#
+# Common function used to cleanup bookmark of file system or volume. Default
+# to delete the file system's bookmark.
+#
+# $1 bookmark name
+#
+function destroy_bookmark
+{
+ typeset bkmark=${1:-$TESTPOOL/$TESTFS#$TESTBKMARK}
+
+ if ! bkmarkexists $bkmark; then
+ log_fail "'$bkmarkp' does not existed."
+ fi
+
+ log_must $ZFS destroy $bkmark
+}
+
# Return 0 if a snapshot exists; $? otherwise
#
# $1 - snapshot name
return $?
}
+#
+# Return 0 if a bookmark exists; $? otherwise
+#
+# $1 - bookmark name
+#
+function bkmarkexists
+{
+ $ZFS list -H -t bookmark "$1" > /dev/null 2>&1
+ return $?
+}
+
#
# Set a property to a certain value on a dataset.
# Sets a property of the dataset to the value as passed in.
# correct property value.
#
# STRATEGY:
-# 1. Create pool, filesystem, volume and snapshot.
+# 1. Create pool, filesystem, volume, snapshot, and bookmark.
# 2. Setting valid parameter, 'zfs get' should succeed.
# 3. Compare the output property name with the original input property.
#
typeset dataset=($TESTPOOL/$TESTCTR $TESTPOOL/$TESTFS $TESTPOOL/$TESTVOL \
$TESTPOOL/$TESTFS@$TESTSNAP $TESTPOOL/$TESTVOL@$TESTSNAP)
+typeset bookmark_props=(creation)
+typeset bookmark=($TESTPOOL/$TESTFS#$TESTBKMARK $TESTPOOL/$TESTVOL#$TESTBKMARK)
+
#
# According to dataset and option, checking if 'zfs get' return correct
# property information.
create_snapshot $TESTPOOL/$TESTFS $TESTSNAP
create_snapshot $TESTPOOL/$TESTVOL $TESTSNAP
+# Create filesystem and volume's bookmark
+create_bookmark $TESTPOOL/$TESTFS $TESTSNAP $TESTBKMARK
+create_bookmark $TESTPOOL/$TESTVOL $TESTSNAP $TESTBKMARK
+
typeset -i i=0
while ((i < ${#dataset[@]})); do
for opt in "${options[@]}"; do
((i += 1))
done
+i=0
+while ((i < ${#bookmark[@]})); do
+ for opt in "${options[@]}"; do
+ for prop in ${bookmark_props[@]}; do
+ eval "$ZFS get $opt $prop ${bookmark[i]} > \
+ $TESTDIR/$TESTFILE0"
+ ret=$?
+ if [[ $ret != 0 ]]; then
+ log_fail "$ZFS get returned: $ret"
+ fi
+ check_return_value ${bookmark[i]} "$prop" "$opt"
+ done
+ done
+ ((i += 1))
+done
+
log_pass "Setting the valid options to dataset, it should succeed and return " \
"valid value. 'zfs get' pass."
inval_opts_str=$(gen_option_str "${inval_opts[*]}" "-" "" $opt_numb)
inval_props_str=$(gen_option_str "${inval_props[*]}" "" "," $prop_numb)
+typeset val_bookmark_props=(creation)
+typeset bookmark=($TESTPOOL/$TESTFS#$TESTBKMARK $TESTPOOL/$TESTVOL#$TESTBKMARK)
+
#
# Test different options and properties combination.
#
done
}
+#
+# Test different options and properties combination for bookmarks.
+#
+# $1 options
+# $2 properties
+#
+function test_options_bookmarks
+{
+ typeset opts=$1
+ typeset props=$2
+
+ for dst in ${bookmark[@]}; do
+ for opt in $opts; do
+ for prop in $props; do
+ $ZFS get $opt -- $prop $dst > /dev/null 2>&1
+ ret=$?
+ if [[ $ret == 0 ]]; then
+ log_fail "$ZFS get $opt -- $prop " \
+ "$dst unexpectedly succeeded."
+ fi
+ done
+ done
+ done
+}
+
log_assert "Setting the invalid option and properties, 'zfs get' should be \
failed."
log_onexit cleanup
create_snapshot $TESTPOOL/$TESTFS $TESTSNAP
create_snapshot $TESTPOOL/$TESTVOL $TESTSNAP
+# Create filesystem and volume's bookmark
+create_bookmark $TESTPOOL/$TESTFS $TESTSNAP $TESTBKMARK
+create_bookmark $TESTPOOL/$TESTVOL $TESTSNAP $TESTBKMARK
+
log_note "Valid options + invalid properties, 'zfs get' should fail."
test_options "$val_opts_str" "$inval_props_str"
+test_options_bookmark "$val_opts_str" "$inval_props_str"
log_note "Invalid options + valid properties, 'zfs get' should fail."
test_options "$inval_opts_str" "$val_props_str"
+test_options_bookmark "$inval_opts_str" "$val_bookmark_props"
log_note "Invalid options + invalid properties, 'zfs get' should fail."
test_options "$inval_opts_str" "$inval_props_str"
+test_options_bookmarks "$inval_opts_str" "$inval_props_str"
log_pass "Setting the invalid options to dataset, 'zfs get' pass."
# Verify "-d <n>" can work with other options
#
# STRATEGY:
-# 1. Create pool, filesystem, dataset, volume and snapshot.
+# 1. Create pool, filesystem, dataset, volume, snapshot, and bookmark.
# 2. Getting an -d option, other options and properties random combination.
# 3. Using the combination as the parameters of 'zfs get' to check the
# command line return value.
set -A dataset $TESTPOOL/$TESTCTR $TESTPOOL/$TESTFS $TESTPOOL/$TESTVOL \
$TESTPOOL/$TESTFS@$TESTSNAP $TESTPOOL/$TESTVOL@$TESTSNAP
+set -A bookmark_props creation
+set -A bookmark $TESTPOOL/$TESTFS#$TESTBKMARK $TESTPOOL/$TESTVOL#$TESTBKMARK
+
log_assert "Verify '-d <n>' can work with other options"
log_onexit cleanup
create_snapshot $TESTPOOL/$TESTFS $TESTSNAP
create_snapshot $TESTPOOL/$TESTVOL $TESTSNAP
+# Create filesystem and volume's bookmark
+create_bookmark $TESTPOOL/$TESTFS $TESTSNAP $TESTBKMARK
+create_bookmark $TESTPOOL/$TESTVOL $TESTSNAP $TESTBKMARK
+
typeset -i opt_numb=16
typeset -i prop_numb=16
typeset -i i=0
done
done
+for dst in ${bookmark[@]}; do
+ (( i=0 ))
+ while (( i < opt_numb )); do
+ (( item = $RANDOM % ${#options[@]} ))
+ (( depth_item = $RANDOM % ${#depth_options[@]} ))
+ log_must eval "$ZFS get -${depth_options[depth_item]} ${options[item]} $bookmark_props $dst > /dev/null 2>&1"
+ (( i += 1 ))
+ done
+done
+
log_pass "Verify '-d <n>' can work with other options"
}
#
-# Cleanup the volume snapshot and filesystem snapshot were created for
-# this test case.
+# Cleanup the volume snapshot, filesystem snapshot, volume bookmark, and
+# filesystem bookmark that were created for this test case.
#
function cleanup
{
datasetexists $TESTPOOL/$TESTFS@$TESTSNAP && \
destroy_snapshot $TESTPOOL/$TESTFS@$TESTSNAP
+ bkmarkexists $TESTPOOL/$TESTVOL#$TESTBKMARK && \
+ destroy_bookmark $TESTPOOL/$TESTVOL#$TESTBKMARK
+ bkmarkexists $TESTPOOL/$TESTFS#$TESTBKMARK && \
+ destroy_bookmark $TESTPOOL/$TESTFS#$TESTBKMARK
+
[[ -e $TESTFILE0 ]] && log_must $RM $TESTFILE0
}