/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2011, 2014 by Delphix. All rights reserved.
+ * Copyright (c) 2011, 2015 by Delphix. All rights reserved.
+ * Copyright 2012 Milan Jurik. All rights reserved.
* Copyright (c) 2012, Joyent, Inc. All rights reserved.
* Copyright (c) 2013 Steven Hartland. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>.
+ * Copyright 2016 Nexenta Systems, Inc.
*/
#include <assert.h>
#include <ctype.h>
#include <errno.h>
+#include <getopt.h>
#include <libgen.h>
#include <libintl.h>
#include <libuutil.h>
"[-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"));
case HELP_PROMOTE:
return (gettext("\tpromote <clone-filesystem>\n"));
case HELP_RECEIVE:
- return (gettext("\treceive [-vnFu] <filesystem|volume|"
- "snapshot>\n"
- "\treceive [-vnFu] [-d | -e] <filesystem>\n"));
+ return (gettext("\treceive [-vnsFu] "
+ "[-o <property>=<value>] ... [-x <property>] ...\n"
+ "\t <filesystem|volume|snapshot>\n"
+ "\treceive [-vnsFu] [-o <property>=<value>] ... "
+ "[-x <property>] ... \n"
+ "\t [-d | -e] <filesystem>\n"
+ "\treceive -A <filesystem|volume>\n"));
case HELP_RENAME:
return (gettext("\trename [-f] <filesystem|volume|snapshot> "
"<filesystem|volume|snapshot>\n"
case HELP_ROLLBACK:
return (gettext("\trollback [-rRf] <snapshot>\n"));
case HELP_SEND:
- return (gettext("\tsend [-DnPpRvLe] [-[iI] snapshot] "
+ return (gettext("\tsend [-DnPpRvLec] [-[i|I] snapshot] "
"<snapshot>\n"
- "\tsend [-Le] [-i snapshot|bookmark] "
- "<filesystem|volume|snapshot>\n"));
+ "\tsend [-Lec] [-i snapshot|bookmark] "
+ "<filesystem|volume|snapshot>\n"
+ "\tsend [-nvPe] -t <receive_resume_token>\n"));
case HELP_SET:
- return (gettext("\tset <property=value> "
+ return (gettext("\tset <property=value> ... "
"<filesystem|volume|snapshot> ...\n"));
case HELP_SHARE:
- return (gettext("\tshare <-a | filesystem>\n"));
+ return (gettext("\tshare <-a [nfs|smb] | filesystem>\n"));
case HELP_SNAPSHOT:
return (gettext("\tsnapshot|snap [-r] [-o property=value] ... "
"<filesystem|volume>@<snap> ...\n"));
"<-a | filesystem|mountpoint>\n"));
case HELP_UNSHARE:
return (gettext("\tunshare "
- "<-a | filesystem|mountpoint>\n"));
+ "<-a [nfs|smb] | filesystem|mountpoint>\n"));
case HELP_ALLOW:
return (gettext("\tallow <filesystem|volume>\n"
"\tallow [-ldug] "
exit(requested ? 0 : 2);
}
-static int
-parseprop(nvlist_t *props)
+/*
+ * Take a property=value argument string and add it to the given nvlist.
+ * Modifies the argument inplace.
+ */
+static boolean_t
+parseprop(nvlist_t *props, char *propname)
{
- char *propname = optarg;
- char *propval, *strval;
+ char *propval;
if ((propval = strchr(propname, '=')) == NULL) {
(void) fprintf(stderr, gettext("missing "
- "'=' for -o option\n"));
- return (-1);
+ "'=' for property=value argument\n"));
+ return (B_FALSE);
}
*propval = '\0';
propval++;
- if (nvlist_lookup_string(props, propname, &strval) == 0) {
+ if (nvlist_exists(props, propname)) {
(void) fprintf(stderr, gettext("property '%s' "
"specified multiple times\n"), propname);
- return (-1);
+ return (B_FALSE);
}
if (nvlist_add_string(props, propname, propval) != 0)
nomem();
- return (0);
+ return (B_TRUE);
+}
+
+/*
+ * Take a property name argument and add it to the given nvlist.
+ * Modifies the argument inplace.
+ */
+static boolean_t
+parsepropname(nvlist_t *props, char *propname)
+{
+ if (strchr(propname, '=') != NULL) {
+ (void) fprintf(stderr, gettext("invalid character "
+ "'=' in property argument\n"));
+ return (B_FALSE);
+ }
+ if (nvlist_exists(props, propname)) {
+ (void) fprintf(stderr, gettext("property '%s' "
+ "specified multiple times\n"), propname);
+ return (B_FALSE);
+ }
+ if (nvlist_add_boolean(props, propname) != 0)
+ nomem();
+ return (B_TRUE);
}
static int
*/
if (zfs_prop_valid_for_type(ZFS_PROP_CANMOUNT, type, B_FALSE) &&
zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_ON) {
- if (zfs_mount(zhp, NULL, 0) != 0) {
+ if (geteuid() != 0) {
+ (void) fprintf(stderr, gettext("filesystem "
+ "successfully created, but it may only be "
+ "mounted by root\n"));
+ ret = 1;
+ } else if (zfs_mount(zhp, NULL, 0) != 0) {
(void) fprintf(stderr, gettext("filesystem "
"successfully created, but not mounted\n"));
ret = 1;
while ((c = getopt(argc, argv, "o:p")) != -1) {
switch (c) {
case 'o':
- if (parseprop(props))
+ if (!parseprop(props, optarg)) {
+ nvlist_free(props);
return (1);
+ }
break;
case 'p':
parents = B_TRUE;
}
/* open the source dataset */
- if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_SNAPSHOT)) == NULL)
+ if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_SNAPSHOT)) == NULL) {
+ nvlist_free(props);
return (1);
+ }
if (parents && zfs_name_valid(argv[1], ZFS_TYPE_FILESYSTEM |
ZFS_TYPE_VOLUME)) {
* complain.
*/
if (zfs_dataset_exists(g_zfs, argv[1], ZFS_TYPE_FILESYSTEM |
- ZFS_TYPE_VOLUME))
+ ZFS_TYPE_VOLUME)) {
+ zfs_close(zhp);
+ nvlist_free(props);
return (0);
- if (zfs_create_ancestors(g_zfs, argv[1]) != 0)
+ }
+ if (zfs_create_ancestors(g_zfs, argv[1]) != 0) {
+ zfs_close(zhp);
+ nvlist_free(props);
return (1);
+ }
}
/* pass to libzfs */
nomem();
break;
case 'o':
- if (parseprop(props))
+ if (!parseprop(props, optarg))
goto error;
break;
case 's':
(void) fprintf(stderr, gettext("missing size "
"argument\n"));
goto badusage;
- break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
if (type == ZFS_TYPE_VOLUME && !noreserve) {
zpool_handle_t *zpool_handle;
+ nvlist_t *real_props = NULL;
uint64_t spa_version;
char *p;
zfs_prop_t resv_prop;
char *strval;
+ char msg[1024];
- if ((p = strchr(argv[0], '/')))
+ if ((p = strchr(argv[0], '/')) != NULL)
*p = '\0';
zpool_handle = zpool_open(g_zfs, argv[0]);
if (p != NULL)
goto error;
spa_version = zpool_get_prop_int(zpool_handle,
ZPOOL_PROP_VERSION, NULL);
- zpool_close(zpool_handle);
if (spa_version >= SPA_VERSION_REFRESERVATION)
resv_prop = ZFS_PROP_REFRESERVATION;
else
resv_prop = ZFS_PROP_RESERVATION;
- volsize = zvol_volsize_to_reservation(volsize, props);
+
+ (void) snprintf(msg, sizeof (msg),
+ gettext("cannot create '%s'"), argv[0]);
+ if (props && (real_props = zfs_valid_proplist(g_zfs, type,
+ props, 0, NULL, zpool_handle, msg)) == NULL) {
+ zpool_close(zpool_handle);
+ goto error;
+ }
+ zpool_close(zpool_handle);
+
+ volsize = zvol_volsize_to_reservation(volsize, real_props);
+ nvlist_free(real_props);
if (nvlist_lookup_string(props, zfs_prop_to_name(resv_prop),
&strval) != 0) {
*at = '\0';
zhp = zfs_open(g_zfs, argv[0],
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
- if (zhp == NULL)
+ if (zhp == NULL) {
+ nvlist_free(cb.cb_nvl);
return (1);
+ }
cb.cb_snapspec = at + 1;
if (gather_snapshots(zfs_handle_dup(zhp), &cb) != 0 ||
if (cb.cb_verbose) {
char buf[16];
- zfs_nicenum(cb.cb_snapused, buf, sizeof (buf));
+ zfs_nicebytes(cb.cb_snapused, buf, sizeof (buf));
if (cb.cb_parsable) {
(void) printf("reclaim\t%llu\n",
(u_longlong_t)cb.cb_snapused);
"cannot destroy bookmark");
}
- nvlist_free(cb.cb_nvl);
+ nvlist_free(nvl);
return (err);
} else {
char buf[ZFS_MAXPROPLEN];
char rbuf[ZFS_MAXPROPLEN];
zprop_source_t sourcetype;
- char source[ZFS_MAXNAMELEN];
+ char source[ZFS_MAX_DATASET_NAME_LEN];
zprop_get_cbdata_t *cbp = data;
nvlist_t *user_props = zfs_get_user_props(zhp);
zprop_list_t *pl = cbp->cb_proplist;
{
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;
uint64_t cb_version;
boolean_t cb_newer;
boolean_t cb_foundone;
- char cb_lastfs[ZFS_MAXNAMELEN];
+ char cb_lastfs[ZFS_MAX_DATASET_NAME_LEN];
} upgrade_cbdata_t;
static int
if (cb.cb_numfailed != 0)
ret = 1;
} else {
- /* List old-version filesytems */
+ /* List old-version filesystems */
boolean_t found;
(void) printf(gettext("This system is currently running "
"ZFS filesystem version %llu.\n\n"), ZPL_VERSION);
USFIELD_TYPE,
USFIELD_NAME,
USFIELD_USED,
- USFIELD_QUOTA
+ USFIELD_QUOTA,
+ USFIELD_OBJUSED,
+ USFIELD_OBJQUOTA
};
-static char *us_field_hdr[] = { "TYPE", "NAME", "USED", "QUOTA" };
-static char *us_field_names[] = { "type", "name", "used", "quota" };
+static char *us_field_hdr[] = { "TYPE", "NAME", "USED", "QUOTA",
+ "OBJUSED", "OBJQUOTA" };
+static char *us_field_names[] = { "type", "name", "used", "quota",
+ "objused", "objquota" };
#define USFIELD_LAST (sizeof (us_field_names) / sizeof (char *))
#define USTYPE_PSX_GRP (1 << 0)
case ZFS_PROP_NAME:
propname = "name";
if (numname) {
+compare_nums:
(void) nvlist_lookup_uint64(lnvl, propname,
&lv64);
(void) nvlist_lookup_uint64(rnvl, propname,
if (rv64 != lv64)
rc = (rv64 < lv64) ? 1 : -1;
} else {
- (void) nvlist_lookup_string(lnvl, propname,
- &lvstr);
- (void) nvlist_lookup_string(rnvl, propname,
- &rvstr);
+ if ((nvlist_lookup_string(lnvl, propname,
+ &lvstr) == ENOENT) ||
+ (nvlist_lookup_string(rnvl, propname,
+ &rvstr) == ENOENT)) {
+ goto compare_nums;
+ }
rc = strcmp(lvstr, rvstr);
}
break;
if (rv64 != lv64)
rc = (rv64 < lv64) ? 1 : -1;
break;
+
default:
break;
}
return (0);
}
+static boolean_t
+zfs_prop_is_user(unsigned p)
+{
+ return (p == ZFS_PROP_USERUSED || p == ZFS_PROP_USERQUOTA ||
+ p == ZFS_PROP_USEROBJUSED || p == ZFS_PROP_USEROBJQUOTA);
+}
+
+static boolean_t
+zfs_prop_is_group(unsigned p)
+{
+ return (p == ZFS_PROP_GROUPUSED || p == ZFS_PROP_GROUPQUOTA ||
+ p == ZFS_PROP_GROUPOBJUSED || p == ZFS_PROP_GROUPOBJQUOTA);
+}
+
static inline const char *
us_type2str(unsigned field_type)
{
if (domain != NULL && domain[0] != '\0') {
#ifdef HAVE_IDMAP
/* SMB */
- char sid[ZFS_MAXNAMELEN + 32];
+ char sid[MAXNAMELEN + 32];
uid_t id;
uint64_t classes;
int err;
if (cb->cb_sid2posix || domain == NULL || domain[0] == '\0') {
/* POSIX or -i */
- if (prop == ZFS_PROP_GROUPUSED || prop == ZFS_PROP_GROUPQUOTA) {
+ if (zfs_prop_is_group(prop)) {
type = USTYPE_PSX_GRP;
if (!cb->cb_numname) {
struct group *g;
namelen = strlen(name);
}
nameidx = us_field_index("name");
- if (namelen > cb->cb_width[nameidx])
+ if (nameidx >= 0 && namelen > cb->cb_width[nameidx])
cb->cb_width[nameidx] = namelen;
/*
}
/* Calculate/update width of USED/QUOTA fields */
- if (cb->cb_nicenum)
- zfs_nicenum(space, sizebuf, sizeof (sizebuf));
- else
+ if (cb->cb_nicenum) {
+ if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED ||
+ prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA) {
+ zfs_nicebytes(space, sizebuf, sizeof (sizebuf));
+ } else {
+ zfs_nicenum(space, sizebuf, sizeof (sizebuf));
+ }
+ } else {
(void) snprintf(sizebuf, sizeof (sizebuf), "%llu",
(u_longlong_t)space);
+ }
sizelen = strlen(sizebuf);
if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED) {
propname = "used";
if (!nvlist_exists(props, "quota"))
(void) nvlist_add_uint64(props, "quota", 0);
- } else {
+ } else if (prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA) {
propname = "quota";
if (!nvlist_exists(props, "used"))
(void) nvlist_add_uint64(props, "used", 0);
+ } else if (prop == ZFS_PROP_USEROBJUSED ||
+ prop == ZFS_PROP_GROUPOBJUSED) {
+ propname = "objused";
+ if (!nvlist_exists(props, "objquota"))
+ (void) nvlist_add_uint64(props, "objquota", 0);
+ } else if (prop == ZFS_PROP_USEROBJQUOTA ||
+ prop == ZFS_PROP_GROUPOBJQUOTA) {
+ propname = "objquota";
+ if (!nvlist_exists(props, "objused"))
+ (void) nvlist_add_uint64(props, "objused", 0);
+ } else {
+ return (-1);
}
sizeidx = us_field_index(propname);
- if (sizelen > cb->cb_width[sizeidx])
+ if (sizeidx >= 0 && sizelen > cb->cb_width[sizeidx])
cb->cb_width[sizeidx] = sizelen;
if (nvlist_add_uint64(props, propname, space) != 0)
size_t *width, us_node_t *node)
{
nvlist_t *nvl = node->usn_nvl;
- char valstr[ZFS_MAXNAMELEN];
+ char valstr[MAXNAMELEN];
boolean_t first = B_TRUE;
int cfield = 0;
int field;
data_type_t type;
uint32_t val32;
uint64_t val64;
- char *strval = NULL;
+ char *strval = "-";
while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
if (strcmp(nvpair_name(nvp),
break;
}
- type = nvpair_type(nvp);
+ type = nvp == NULL ? DATA_TYPE_UNKNOWN : nvpair_type(nvp);
switch (type) {
case DATA_TYPE_UINT32:
(void) nvpair_value_uint32(nvp, &val32);
case DATA_TYPE_STRING:
(void) nvpair_value_string(nvp, &strval);
break;
+ case DATA_TYPE_UNKNOWN:
+ break;
default:
(void) fprintf(stderr, "invalid data type\n");
}
switch (field) {
case USFIELD_TYPE:
- strval = (char *)us_type2str(val32);
+ if (type == DATA_TYPE_UINT32)
+ strval = (char *)us_type2str(val32);
break;
case USFIELD_NAME:
if (type == DATA_TYPE_UINT64) {
(void) sprintf(valstr, "%llu",
- (u_longlong_t) val64);
+ (u_longlong_t)val64);
strval = valstr;
}
break;
if (type == DATA_TYPE_UINT64) {
if (parsable) {
(void) sprintf(valstr, "%llu",
- (u_longlong_t) val64);
+ (u_longlong_t)val64);
+ strval = valstr;
+ } else if (field == USFIELD_QUOTA &&
+ val64 == 0) {
+ strval = "none";
} else {
- zfs_nicenum(val64, valstr,
+ zfs_nicebytes(val64, valstr,
sizeof (valstr));
+ strval = valstr;
}
- if (field == USFIELD_QUOTA &&
- strcmp(valstr, "0") == 0)
+ }
+ break;
+ case USFIELD_OBJUSED:
+ case USFIELD_OBJQUOTA:
+ if (type == DATA_TYPE_UINT64) {
+ if (parsable) {
+ (void) sprintf(valstr, "%llu",
+ (u_longlong_t)val64);
+ strval = valstr;
+ } else if (field == USFIELD_OBJQUOTA &&
+ val64 == 0) {
strval = "none";
- else
+ } else {
+ zfs_nicenum(val64, valstr,
+ sizeof (valstr));
strval = valstr;
+ }
}
break;
}
if (scripted)
(void) printf("%s", strval);
else if (field == USFIELD_TYPE || field == USFIELD_NAME)
- (void) printf("%-*s", (int) width[field], strval);
+ (void) printf("%-*s", (int)width[field], strval);
else
- (void) printf("%*s", (int) width[field], strval);
+ (void) printf("%*s", (int)width[field], strval);
first = B_FALSE;
cfield++;
col = gettext(us_field_hdr[field]);
if (field == USFIELD_TYPE || field == USFIELD_NAME) {
(void) printf(first ? "%-*s" : " %-*s",
- (int) width[field], col);
+ (int)width[field], col);
} else {
(void) printf(first ? "%*s" : " %*s",
- (int) width[field], col);
+ (int)width[field], col);
}
first = B_FALSE;
cfield++;
uu_avl_t *avl_tree;
uu_avl_walk_t *walk;
char *delim;
- char deffields[] = "type,name,used,quota";
+ char deffields[] = "type,name,used,quota,objused,objquota";
char *ofield = NULL;
char *tfield = NULL;
int cfield = 0;
cb.cb_width[i] = strlen(gettext(us_field_hdr[i]));
for (p = 0; p < ZFS_NUM_USERQUOTA_PROPS; p++) {
- if (((p == ZFS_PROP_USERUSED || p == ZFS_PROP_USERQUOTA) &&
+ if ((zfs_prop_is_user(p) &&
!(types & (USTYPE_PSX_USR | USTYPE_SMB_USR))) ||
- ((p == ZFS_PROP_GROUPUSED || p == ZFS_PROP_GROUPQUOTA) &&
+ (zfs_prop_is_group(p) &&
!(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP))))
continue;
+
cb.cb_prop = p;
if ((ret = zfs_userspace(zhp, p, userspace_cb, &cb)) != 0)
return (ret);
*
* -r Delete any intervening snapshots before doing rollback
* -R Delete any snapshots and their clones
- * -f ignored for backwards compatability
+ * -f ignored for backwards compatibility
*
* Given a filesystem, rollback to a specific snapshot, discarding any changes
* since then and making it the active dataset. If more recent snapshots exist,
boolean_t force = B_FALSE;
rollback_cbdata_t cb = { 0 };
zfs_handle_t *zhp, *snap;
- char parentname[ZFS_MAXNAMELEN];
+ char parentname[ZFS_MAX_DATASET_NAME_LEN];
char *delim;
/* check options */
}
/*
- * zfs set property=value { fs | snap | vol } ...
+ * zfs set property=value ... { fs | snap | vol } ...
*
- * Sets the given property for all datasets specified on the command line.
+ * Sets the given properties for all datasets specified on the command line.
*/
-typedef struct set_cbdata {
- char *cb_propname;
- char *cb_value;
-} set_cbdata_t;
static int
set_callback(zfs_handle_t *zhp, void *data)
{
- set_cbdata_t *cbp = data;
+ nvlist_t *props = data;
- if (zfs_prop_set(zhp, cbp->cb_propname, cbp->cb_value) != 0) {
+ if (zfs_prop_set_list(zhp, props) != 0) {
switch (libzfs_errno(g_zfs)) {
case EZFS_MOUNTFAILED:
(void) fprintf(stderr, gettext("property may be set "
static int
zfs_do_set(int argc, char **argv)
{
- set_cbdata_t cb;
+ nvlist_t *props = NULL;
+ int ds_start = -1; /* argv idx of first dataset arg */
int ret = 0;
+ int i;
/* check for options */
if (argc > 1 && argv[1][0] == '-') {
/* check number of arguments */
if (argc < 2) {
- (void) fprintf(stderr, gettext("missing property=value "
- "argument\n"));
+ (void) fprintf(stderr, gettext("missing arguments\n"));
usage(B_FALSE);
}
if (argc < 3) {
- (void) fprintf(stderr, gettext("missing dataset name\n"));
+ if (strchr(argv[1], '=') == NULL) {
+ (void) fprintf(stderr, gettext("missing property=value "
+ "argument(s)\n"));
+ } else {
+ (void) fprintf(stderr, gettext("missing dataset "
+ "name(s)\n"));
+ }
usage(B_FALSE);
}
- /* validate property=value argument */
- cb.cb_propname = argv[1];
- if (((cb.cb_value = strchr(cb.cb_propname, '=')) == NULL) ||
- (cb.cb_value[1] == '\0')) {
- (void) fprintf(stderr, gettext("missing value in "
- "property=value argument\n"));
+ /* validate argument order: prop=val args followed by dataset args */
+ for (i = 1; i < argc; i++) {
+ if (strchr(argv[i], '=') != NULL) {
+ if (ds_start > 0) {
+ /* out-of-order prop=val argument */
+ (void) fprintf(stderr, gettext("invalid "
+ "argument order\n"));
+ usage(B_FALSE);
+ }
+ } else if (ds_start < 0) {
+ ds_start = i;
+ }
+ }
+ if (ds_start < 0) {
+ (void) fprintf(stderr, gettext("missing dataset name(s)\n"));
usage(B_FALSE);
}
- *cb.cb_value = '\0';
- cb.cb_value++;
-
- if (*cb.cb_propname == '\0') {
- (void) fprintf(stderr,
- gettext("missing property in property=value argument\n"));
- usage(B_FALSE);
+ /* Populate a list of property settings */
+ if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0)
+ nomem();
+ for (i = 1; i < ds_start; i++) {
+ if (!parseprop(props, argv[i])) {
+ ret = -1;
+ goto error;
+ }
}
- ret = zfs_for_each(argc - 2, argv + 2, 0,
- ZFS_TYPE_DATASET, NULL, NULL, 0, set_callback, &cb);
+ ret = zfs_for_each(argc - ds_start, argv + ds_start, 0,
+ ZFS_TYPE_DATASET, NULL, NULL, 0, set_callback, props);
+error:
+ nvlist_free(props);
return (ret);
}
while ((c = getopt(argc, argv, "ro:")) != -1) {
switch (c) {
case 'o':
- if (parseprop(props))
+ if (!parseprop(props, optarg)) {
+ nvlist_free(sd.sd_nvl);
+ nvlist_free(props);
return (1);
+ }
break;
case 'r':
sd.sd_recursive = B_TRUE;
{
char *fromname = NULL;
char *toname = NULL;
+ char *resume_token = NULL;
char *cp;
zfs_handle_t *zhp;
sendflags_t flags = { 0 };
nvlist_t *dbgnv = NULL;
boolean_t extraverbose = B_FALSE;
+ struct option long_options[] = {
+ {"replicate", no_argument, NULL, 'R'},
+ {"props", no_argument, NULL, 'p'},
+ {"parsable", no_argument, NULL, 'P'},
+ {"dedup", no_argument, NULL, 'D'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"dryrun", no_argument, NULL, 'n'},
+ {"large-block", no_argument, NULL, 'L'},
+ {"embed", no_argument, NULL, 'e'},
+ {"resume", required_argument, NULL, 't'},
+ {"compressed", no_argument, NULL, 'c'},
+ {0, 0, 0, 0}
+ };
+
/* check options */
- while ((c = getopt(argc, argv, ":i:I:RDpvnPLe")) != -1) {
+ while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLet:c", long_options,
+ NULL)) != -1) {
switch (c) {
case 'i':
if (fromname)
case 'e':
flags.embed_data = B_TRUE;
break;
+ case 't':
+ resume_token = optarg;
+ break;
+ case 'c':
+ flags.compress = B_TRUE;
+ break;
case ':':
- (void) fprintf(stderr, gettext("missing argument for "
- "'%c' option\n"), optopt);
+ /*
+ * If a parameter was not passed, optopt contains the
+ * value that would normally lead us into the
+ * appropriate case statement. If it's > 256, then this
+ * must be a longopt and we should look at argv to get
+ * the string. Otherwise it's just the character, so we
+ * should use it directly.
+ */
+ if (optopt <= UINT8_MAX) {
+ (void) fprintf(stderr,
+ gettext("missing argument for '%c' "
+ "option\n"), optopt);
+ } else {
+ (void) fprintf(stderr,
+ gettext("missing argument for '%s' "
+ "option\n"), argv[optind - 1]);
+ }
usage(B_FALSE);
break;
case '?':
- (void) fprintf(stderr, gettext("invalid option '%c'\n"),
- optopt);
+ /*FALLTHROUGH*/
+ default:
+ /*
+ * If an invalid flag was passed, optopt contains the
+ * character if it was a short flag, or 0 if it was a
+ * longopt.
+ */
+ if (optopt != 0) {
+ (void) fprintf(stderr,
+ gettext("invalid option '%c'\n"), optopt);
+ } else {
+ (void) fprintf(stderr,
+ gettext("invalid option '%s'\n"),
+ argv[optind - 1]);
+
+ }
usage(B_FALSE);
}
}
argc -= optind;
argv += optind;
- /* check number of arguments */
- if (argc < 1) {
- (void) fprintf(stderr, gettext("missing snapshot argument\n"));
- usage(B_FALSE);
- }
- if (argc > 1) {
- (void) fprintf(stderr, gettext("too many arguments\n"));
- usage(B_FALSE);
+ if (resume_token != NULL) {
+ if (fromname != NULL || flags.replicate || flags.props ||
+ flags.dedup) {
+ (void) fprintf(stderr,
+ gettext("invalid flags combined with -t\n"));
+ usage(B_FALSE);
+ }
+ if (argc != 0) {
+ (void) fprintf(stderr, gettext("no additional "
+ "arguments are permitted with -t\n"));
+ usage(B_FALSE);
+ }
+ } else {
+ if (argc < 1) {
+ (void) fprintf(stderr,
+ gettext("missing snapshot argument\n"));
+ usage(B_FALSE);
+ }
+ if (argc > 1) {
+ (void) fprintf(stderr, gettext("too many arguments\n"));
+ usage(B_FALSE);
+ }
}
if (!flags.dryrun && isatty(STDOUT_FILENO)) {
return (1);
}
+ if (resume_token != NULL) {
+ return (zfs_send_resume(g_zfs, &flags, STDOUT_FILENO,
+ resume_token));
+ }
+
/*
* Special case sending a filesystem, or from a bookmark.
*/
if (strchr(argv[0], '@') == NULL ||
(fromname && strchr(fromname, '#') != NULL)) {
- char frombuf[ZFS_MAXNAMELEN];
+ char frombuf[ZFS_MAX_DATASET_NAME_LEN];
enum lzc_send_flags lzc_flags = 0;
if (flags.replicate || flags.doall || flags.props ||
lzc_flags |= LZC_SEND_FLAG_LARGE_BLOCK;
if (flags.embed_data)
lzc_flags |= LZC_SEND_FLAG_EMBED_DATA;
+ if (flags.compress)
+ lzc_flags |= LZC_SEND_FLAG_COMPRESS;
if (fromname != NULL &&
(fromname[0] == '#' || fromname[0] == '@')) {
* Incremental source name begins with # or @.
* Default to same fs as target.
*/
- (void) strncpy(frombuf, argv[0], sizeof (frombuf));
+ (void) strlcpy(frombuf, argv[0], sizeof (frombuf));
cp = strchr(frombuf, '@');
if (cp != NULL)
*cp = '\0';
* case if they specify the origin.
*/
if (fromname && (cp = strchr(fromname, '@')) != NULL) {
- char origin[ZFS_MAXNAMELEN];
+ char origin[ZFS_MAX_DATASET_NAME_LEN];
zprop_source_t src;
(void) zfs_prop_get(zhp, ZFS_PROP_ORIGIN,
}
/*
- * zfs receive [-vnFu] [-d | -e] <fs@snap>
- *
* Restore a backup stream from stdin.
*/
static int
zfs_do_receive(int argc, char **argv)
{
- int c, err;
+ int c, err = 0;
recvflags_t flags = { 0 };
+ boolean_t abort_resumable = B_FALSE;
+ nvlist_t *props;
+
+ if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0)
+ nomem();
/* check options */
- while ((c = getopt(argc, argv, ":denuvF")) != -1) {
+ while ((c = getopt(argc, argv, ":o:x:denuvFsA")) != -1) {
switch (c) {
+ case 'o':
+ if (!parseprop(props, optarg)) {
+ nvlist_free(props);
+ usage(B_FALSE);
+ }
+ break;
+ case 'x':
+ if (!parsepropname(props, optarg)) {
+ nvlist_free(props);
+ usage(B_FALSE);
+ }
+ break;
case 'd':
flags.isprefix = B_TRUE;
break;
case 'v':
flags.verbose = B_TRUE;
break;
+ case 's':
+ flags.resumable = B_TRUE;
+ break;
case 'F':
flags.force = B_TRUE;
break;
+ case 'A':
+ abort_resumable = B_TRUE;
+ break;
case ':':
(void) fprintf(stderr, gettext("missing argument for "
"'%c' option\n"), optopt);
usage(B_FALSE);
}
+ if (abort_resumable) {
+ if (flags.isprefix || flags.istail || flags.dryrun ||
+ flags.resumable || flags.nomount) {
+ (void) fprintf(stderr, gettext("invalid option\n"));
+ usage(B_FALSE);
+ }
+
+ char namebuf[ZFS_MAX_DATASET_NAME_LEN];
+ (void) snprintf(namebuf, sizeof (namebuf),
+ "%s/%%recv", argv[0]);
+
+ if (zfs_dataset_exists(g_zfs, namebuf,
+ ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) {
+ zfs_handle_t *zhp = zfs_open(g_zfs,
+ namebuf, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+ if (zhp == NULL) {
+ nvlist_free(props);
+ return (1);
+ }
+ err = zfs_destroy(zhp, B_FALSE);
+ zfs_close(zhp);
+ } else {
+ zfs_handle_t *zhp = zfs_open(g_zfs,
+ argv[0], ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+ if (zhp == NULL)
+ usage(B_FALSE);
+ if (!zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT) ||
+ zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN,
+ NULL, 0, NULL, NULL, 0, B_TRUE) == -1) {
+ (void) fprintf(stderr,
+ gettext("'%s' does not have any "
+ "resumable receive state to abort\n"),
+ argv[0]);
+ nvlist_free(props);
+ zfs_close(zhp);
+ return (1);
+ }
+ err = zfs_destroy(zhp, B_FALSE);
+ zfs_close(zhp);
+ }
+ nvlist_free(props);
+ return (err != 0);
+ }
+
if (isatty(STDIN_FILENO)) {
(void) fprintf(stderr,
gettext("Error: Backup stream can not be read "
"from a terminal.\n"
"You must redirect standard input.\n"));
+ nvlist_free(props);
return (1);
}
-
- err = zfs_receive(g_zfs, argv[0], &flags, STDIN_FILENO, NULL);
+ err = zfs_receive(g_zfs, argv[0], props, &flags, STDIN_FILENO, NULL);
+ nvlist_free(props);
return (err != 0);
}
#define ZFS_DELEG_PERM_GROUPQUOTA "groupquota"
#define ZFS_DELEG_PERM_USERUSED "userused"
#define ZFS_DELEG_PERM_GROUPUSED "groupused"
+#define ZFS_DELEG_PERM_USEROBJQUOTA "userobjquota"
+#define ZFS_DELEG_PERM_GROUPOBJQUOTA "groupobjquota"
+#define ZFS_DELEG_PERM_USEROBJUSED "userobjused"
+#define ZFS_DELEG_PERM_GROUPOBJUSED "groupobjused"
+
#define ZFS_DELEG_PERM_HOLD "hold"
#define ZFS_DELEG_PERM_RELEASE "release"
#define ZFS_DELEG_PERM_DIFF "diff"
{ ZFS_DELEG_PERM_USERPROP, ZFS_DELEG_NOTE_USERPROP },
{ ZFS_DELEG_PERM_USERQUOTA, ZFS_DELEG_NOTE_USERQUOTA },
{ ZFS_DELEG_PERM_USERUSED, ZFS_DELEG_NOTE_USERUSED },
+ { ZFS_DELEG_PERM_USEROBJQUOTA, ZFS_DELEG_NOTE_USEROBJQUOTA },
+ { ZFS_DELEG_PERM_USEROBJUSED, ZFS_DELEG_NOTE_USEROBJUSED },
+ { ZFS_DELEG_PERM_GROUPOBJQUOTA, ZFS_DELEG_NOTE_GROUPOBJQUOTA },
+ { ZFS_DELEG_PERM_GROUPOBJUSED, ZFS_DELEG_NOTE_GROUPOBJUSED },
{ NULL, ZFS_DELEG_NOTE_NONE }
};
case ZFS_DELEG_NOTE_USERPROP:
case ZFS_DELEG_NOTE_USERQUOTA:
case ZFS_DELEG_NOTE_USERUSED:
+ case ZFS_DELEG_NOTE_USEROBJQUOTA:
+ case ZFS_DELEG_NOTE_USEROBJUSED:
+ case ZFS_DELEG_NOTE_GROUPOBJQUOTA:
+ case ZFS_DELEG_NOTE_GROUPOBJUSED:
/* other */
return (gettext("other"));
default:
}
}
-static int inline
+static int
who_type2weight(zfs_deleg_who_type_t who_type)
{
int res;
uu_avl_destroy(fsperm->fsp_uge_avl);
}
-static void inline
+static void
set_deleg_perm_node(uu_avl_t *avl, deleg_perm_node_t *node,
zfs_deleg_who_type_t who_type, const char *name, char locality)
{
avl_pool = fspset->fsps_who_perm_avl_pool;
avl = fsperm->fsp_uge_avl;
break;
+
default:
- break;
+ assert(!"unhandled zfs_deleg_who_type_t");
}
if (is_set) {
if (g)
nice_name = g->gr_name;
break;
+
default:
break;
}
who_perm = &node->who_perm;
}
}
-
+ VERIFY3P(who_perm, !=, NULL);
(void) parse_who_perm(who_perm, nvl2, perm_locality);
}
case ZFS_DELEG_NOTE_USERUSED:
str = gettext("Allows reading any userused@... property");
break;
+ case ZFS_DELEG_NOTE_USEROBJQUOTA:
+ str = gettext("Allows accessing any userobjquota@... property");
+ break;
+ case ZFS_DELEG_NOTE_GROUPOBJQUOTA:
+ str = gettext("Allows accessing any \n\t\t\t\t"
+ "groupobjquota@... property");
+ break;
+ case ZFS_DELEG_NOTE_GROUPOBJUSED:
+ str = gettext("Allows reading any groupobjused@... property");
+ break;
+ case ZFS_DELEG_NOTE_USEROBJUSED:
+ str = gettext("Allows reading any userobjused@... property");
+ break;
/* other */
default:
str = "";
(void) fprintf(fp, gettext("Usage: %s\n"), get_usage(un ? HELP_UNALLOW :
HELP_ALLOW));
(void) fprintf(fp, gettext("Options:\n"));
- for (i = 0; i < (un ? unallow_size : allow_size); i++) {
- const char *opt = opt_desc[i++];
- const char *optdsc = opt_desc[i];
+ for (i = 0; i < (un ? unallow_size : allow_size); i += 2) {
+ const char *opt = opt_desc[i];
+ const char *optdsc = opt_desc[i + 1];
(void) fprintf(fp, gettext(" %-10s %s\n"), opt, optdsc);
}
{
int i;
char ld[2] = { '\0', '\0' };
- char who_buf[ZFS_MAXNAMELEN+32];
- char base_type = ZFS_DELEG_WHO_UNKNOWN;
- char set_type = ZFS_DELEG_WHO_UNKNOWN;
+ char who_buf[MAXNAMELEN + 32];
+ char base_type = '\0';
+ char set_type = '\0';
nvlist_t *base_nvl = NULL;
nvlist_t *set_nvl = NULL;
nvlist_t *nvl;
ld[0] = ZFS_DELEG_LOCAL;
if (descend)
ld[1] = ZFS_DELEG_DESCENDENT;
- default:
break;
+
+ default:
+ assert(set_type != '\0' && base_type != '\0');
}
if (perms != NULL) {
}
}
-static void inline
+static void
print_uge_deleg_perms(uu_avl_t *who_avl, boolean_t local, boolean_t descend,
const char *title)
{
case ZFS_DELEG_EVERYONE:
who = gettext("everyone");
who_name = NULL;
- default:
break;
+
+ default:
+ assert(who != NULL);
}
prt_who = B_FALSE;
print_fs_perms(fs_perm_set_t *fspset)
{
fs_perm_node_t *node = NULL;
- char buf[ZFS_MAXNAMELEN+32];
+ char buf[MAXNAMELEN + 32];
const char *dsname = buf;
for (node = uu_list_first(fspset->fsps_list); node != NULL;
uu_avl_t *uge_avl = node->fspn_fsperm.fsp_uge_avl;
int left = 0;
- (void) snprintf(buf, ZFS_MAXNAMELEN+32,
+ (void) snprintf(buf, sizeof (buf),
gettext("---- Permissions on %s "),
node->fspn_fsperm.fsp_name);
(void) printf("%s", dsname);
cleanup0:
nvlist_free(perm_nvl);
- if (update_perm_nvl != NULL)
- nvlist_free(update_perm_nvl);
+ nvlist_free(update_perm_nvl);
cleanup1:
fs_perm_set_fini(&fs_perm_set);
cleanup2:
for (i = 0; i < argc; ++i) {
zfs_handle_t *zhp;
- char parent[ZFS_MAXNAMELEN];
+ char parent[ZFS_MAX_DATASET_NAME_LEN];
const char *delim;
char *path = argv[i];
nvlist_t *nvl = NULL;
nvpair_t *nvp = NULL;
const char *zname = zfs_get_name(zhp);
- size_t znamelen = strnlen(zname, ZFS_MAXNAMELEN);
+ size_t znamelen = strlen(zname);
if (cbp->cb_recursive) {
const char *snapname;
while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
const char *tag = nvpair_name(nvp);
- size_t taglen = strnlen(tag, MAXNAMELEN);
+ size_t taglen = strlen(tag);
if (taglen > cbp->cb_max_taglen)
cbp->cb_max_taglen = taglen;
}
return (0);
}
+ /*
+ * If this filesystem is inconsistent and has a receive resume
+ * token, we can not mount it.
+ */
+ if (zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT) &&
+ zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN,
+ NULL, 0, NULL, NULL, 0, B_TRUE) == 0) {
+ if (!explicit)
+ return (0);
+
+ (void) fprintf(stderr, gettext("cannot %s '%s': "
+ "Contains partially-completed state from "
+ "\"zfs receive -r\", which can be resumed with "
+ "\"zfs send -t\"\n"),
+ cmdname, zfs_get_name(zhp));
+ return (1);
+ }
+
/*
* At this point, we have verified that the mountpoint and/or
* shareopts are appropriate for auto management. If the
shared_smb = zfs_is_shared_smb(zhp, NULL);
if ((shared_nfs && shared_smb) ||
- ((shared_nfs && strcmp(shareopts, "on") == 0) &&
- (strcmp(smbshareopts, "off") == 0)) ||
- ((shared_smb && strcmp(smbshareopts, "on") == 0) &&
- (strcmp(shareopts, "off") == 0))) {
+ (shared_nfs && strcmp(shareopts, "on") == 0 &&
+ strcmp(smbshareopts, "off") == 0) ||
+ (shared_smb && strcmp(smbshareopts, "on") == 0 &&
+ strcmp(shareopts, "off") == 0)) {
if (!explicit)
return (0);
start_progress_timer();
get_all_datasets(&dslist, &count, verbose);
- if (count == 0)
+ if (count == 0) {
+ if (options != NULL)
+ free(options);
return (0);
+ }
qsort(dslist, count, sizeof (void *), libzfs_dataset_cmp);
}
/*
- * When mount is given no arguments, go through /etc/mtab and
- * display any active ZFS mounts. We hide any snapshots, since
- * they are controlled automatically.
+ * When mount is given no arguments, go through
+ * /proc/self/mounts and display any active ZFS mounts.
+ * We hide any snapshots, since they are controlled
+ * automatically.
*/
/* Reopen MNTTAB to prevent reading stale data from open file */
- if (freopen(MNTTAB, "r", mnttab_file) == NULL)
+ if (freopen(MNTTAB, "r", mnttab_file) == NULL) {
+ if (options != NULL)
+ free(options);
return (ENOENT);
+ }
while (getmntent(mnttab_file, &entry) == 0) {
if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0 ||
}
}
+ if (options != NULL)
+ free(options);
+
return (ret);
}
/*
* Convenience routine used by zfs_do_umount() and manual_unmount(). Given an
- * absolute path, find the entry /etc/mtab, verify that its a ZFS filesystem,
- * and unmount it appropriately.
+ * absolute path, find the entry /proc/self/mounts, verify that its a
+ * ZFS filesystems, and unmount it appropriately.
*/
static int
unshare_unmount_path(int op, char *path, int flags, boolean_t is_manual)
ino_t path_inode;
/*
- * Search for the path in /etc/mtab. Rather than looking for the
+ * Search for the path in /proc/self/mounts. Rather than looking for the
* specific path, which can be fooled by non-standard paths (i.e. ".."
* or "//"), we stat() the path and search for the corresponding
* (major,minor) device pair.
"currently mounted\n"), cmdname, path);
return (1);
}
- (void) fprintf(stderr, gettext("warning: %s not in mtab\n"),
- path);
+ (void) fprintf(stderr, gettext("warning: %s not in"
+ "/proc/self/mounts\n"), path);
if ((ret = umount2(path, flags)) != 0)
(void) fprintf(stderr, gettext("%s: %s\n"), path,
strerror(errno));
char sharesmb[ZFS_MAXPROPLEN];
/* check options */
- while ((c = getopt(argc, argv, op == OP_SHARE ? "a" : "af")) != -1) {
+ while ((c = getopt(argc, argv, op == OP_SHARE ? ":a" : "af")) != -1) {
switch (c) {
case 'a':
do_all = 1;
case 'f':
flags = MS_FORCE;
break;
+ case ':':
+ (void) fprintf(stderr, gettext("missing argument for "
+ "'%c' option\n"), optopt);
+ usage(B_FALSE);
+ break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
/*
* We could make use of zfs_for_each() to walk all datasets in
* the system, but this would be very inefficient, especially
- * since we would have to linearly search /etc/mtab for each
- * one. Instead, do one pass through /etc/mtab looking for
- * zfs entries and call zfs_unmount() for each one.
+ * since we would have to linearly search /proc/self/mounts for
+ * each one. Instead, do one pass through /proc/self/mounts
+ * looking for zfs entries and call zfs_unmount() for each one.
*
* Things get a little tricky if the administrator has created
* mountpoints beneath other ZFS filesystems. In this case, we
unshare_unmount_node_t *node;
uu_avl_index_t idx;
uu_avl_walk_t *walk;
+ char *protocol = NULL;
+
+ if (op == OP_SHARE && argc > 0) {
+ if (strcmp(argv[0], "nfs") != 0 &&
+ strcmp(argv[0], "smb") != 0) {
+ (void) fprintf(stderr, gettext("share type "
+ "must be 'nfs' or 'smb'\n"));
+ usage(B_FALSE);
+ }
+ protocol = argv[0];
+ argc--;
+ argv++;
+ }
if (argc != 0) {
(void) fprintf(stderr, gettext("too many arguments\n"));
continue;
}
+ /*
+ * Ignore datasets that are excluded/restricted by
+ * parent pool name.
+ */
+ if (zpool_skip_pool(zfs_get_pool_name(zhp))) {
+ zfs_close(zhp);
+ continue;
+ }
+
switch (op) {
case OP_SHARE:
verify(zfs_prop_get(zhp, ZFS_PROP_SHARENFS,
switch (op) {
case OP_SHARE:
- if (zfs_unshareall_bypath(node->un_zhp,
- node->un_mountp) != 0)
+ if (zfs_unshareall_bytype(node->un_zhp,
+ node->un_mountp, protocol) != 0)
ret = 1;
break;
if (copy == NULL)
usage(B_FALSE);
- if ((atp = strchr(copy, '@')))
+ if ((atp = strchr(copy, '@')) != NULL)
*atp = '\0';
- if ((zhp = zfs_open(g_zfs, copy, ZFS_TYPE_FILESYSTEM)) == NULL)
+ if ((zhp = zfs_open(g_zfs, copy, ZFS_TYPE_FILESYSTEM)) == NULL) {
+ free(copy);
return (1);
-
+ }
free(copy);
/*
static int
zfs_do_bookmark(int argc, char **argv)
{
- char snapname[ZFS_MAXNAMELEN];
+ char snapname[ZFS_MAX_DATASET_NAME_LEN];
+ char bookname[ZFS_MAX_DATASET_NAME_LEN];
zfs_handle_t *zhp;
nvlist_t *nvl;
int ret = 0;
if (strchr(argv[1], '#') == NULL) {
(void) fprintf(stderr,
- gettext("invalid bookmark name '%s' -- "
+ gettext("invalid bookmark name '%s': "
"must contain a '#'\n"), argv[1]);
goto usage;
}
* Snapshot name begins with @.
* Default to same fs as bookmark.
*/
- (void) strncpy(snapname, argv[1], sizeof (snapname));
+ (void) strlcpy(snapname, argv[1], sizeof (snapname));
*strchr(snapname, '#') = '\0';
(void) strlcat(snapname, argv[0], sizeof (snapname));
} else {
- (void) strncpy(snapname, argv[0], sizeof (snapname));
+ (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));
+ }
+
zhp = zfs_open(g_zfs, snapname, ZFS_TYPE_SNAPSHOT);
if (zhp == NULL)
goto usage;
nvl = fnvlist_alloc();
- fnvlist_add_string(nvl, argv[1], snapname);
+ fnvlist_add_string(nvl, bookname, snapname);
ret = lzc_bookmark(nvl, NULL);
fnvlist_free(nvl);
(void) snprintf(errbuf, sizeof (errbuf),
dgettext(TEXT_DOMAIN,
- "cannot create bookmark '%s'"), argv[1]);
+ "cannot create bookmark '%s'"), bookname);
switch (ret) {
case EXDEV:
case ENOSPC:
err_msg = "out of space";
break;
+ case ENOENT:
+ err_msg = "dataset does not exist";
+ break;
default:
err_msg = "unknown error";
break;