X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=cmd%2Fzfs%2Fzfs_main.c;h=12a5cf6cf9b98944a62d8b853da350a24484a11d;hb=c568ab8d99d88b69f54159cd771cb9b20784174e;hp=e8a4421ccf872f88125614773c96250522401348;hpb=eca7b76001a7d33f78bd98884aef8325bdbf98e7;p=mirror_zfs.git diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index e8a4421cc..12a5cf6cf 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -21,16 +21,19 @@ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2011, 2015 by Delphix. All rights reserved. + * Copyright (c) 2011, 2019 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 . + * Copyright 2016 Nexenta Systems, Inc. + * Copyright (c) 2018 Datto Inc. */ #include #include #include +#include #include #include #include @@ -46,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -53,13 +57,16 @@ #include #include #include +#include #include #include +#include #include #include #include #include +#include #include #ifdef HAVE_IDMAP #include @@ -70,6 +77,7 @@ #include "zfs_util.h" #include "zfs_comutil.h" #include "libzfs_impl.h" +#include "zfs_projectutil.h" libzfs_handle_t *g_zfs; @@ -103,6 +111,12 @@ static int zfs_do_holds(int argc, char **argv); static int zfs_do_release(int argc, char **argv); static int zfs_do_diff(int argc, char **argv); static int zfs_do_bookmark(int argc, char **argv); +static int zfs_do_channel_program(int argc, char **argv); +static int zfs_do_remap(int argc, char **argv); +static int zfs_do_load_key(int argc, char **argv); +static int zfs_do_unload_key(int argc, char **argv); +static int zfs_do_change_key(int argc, char **argv); +static int zfs_do_project(int argc, char **argv); /* * Enable a reasonable set of defaults for libumem debugging on DEBUG builds. @@ -145,11 +159,18 @@ typedef enum { HELP_UNALLOW, HELP_USERSPACE, HELP_GROUPSPACE, + HELP_PROJECTSPACE, + HELP_PROJECT, HELP_HOLD, HELP_HOLDS, HELP_RELEASE, HELP_DIFF, + HELP_REMAP, HELP_BOOKMARK, + HELP_CHANNEL_PROGRAM, + HELP_LOAD_KEY, + HELP_UNLOAD_KEY, + HELP_CHANGE_KEY, } zfs_help_t; typedef struct zfs_command { @@ -177,6 +198,7 @@ static zfs_command_t command_table[] = { { "promote", zfs_do_promote, HELP_PROMOTE }, { "rename", zfs_do_rename, HELP_RENAME }, { "bookmark", zfs_do_bookmark, HELP_BOOKMARK }, + { "program", zfs_do_channel_program, HELP_CHANNEL_PROGRAM }, { NULL }, { "list", zfs_do_list, HELP_LIST }, { NULL }, @@ -184,8 +206,12 @@ static zfs_command_t command_table[] = { { "get", zfs_do_get, HELP_GET }, { "inherit", zfs_do_inherit, HELP_INHERIT }, { "upgrade", zfs_do_upgrade, HELP_UPGRADE }, + { NULL }, { "userspace", zfs_do_userspace, HELP_USERSPACE }, { "groupspace", zfs_do_userspace, HELP_GROUPSPACE }, + { "projectspace", zfs_do_userspace, HELP_PROJECTSPACE }, + { NULL }, + { "project", zfs_do_project, HELP_PROJECT }, { NULL }, { "mount", zfs_do_mount, HELP_MOUNT }, { "unmount", zfs_do_unmount, HELP_UNMOUNT }, @@ -203,6 +229,10 @@ static zfs_command_t command_table[] = { { "holds", zfs_do_holds, HELP_HOLDS }, { "release", zfs_do_release, HELP_RELEASE }, { "diff", zfs_do_diff, HELP_DIFF }, + { "remap", zfs_do_remap, HELP_REMAP }, + { "load-key", zfs_do_load_key, HELP_LOAD_KEY }, + { "unload-key", zfs_do_unload_key, HELP_UNLOAD_KEY }, + { "change-key", zfs_do_change_key, HELP_CHANGE_KEY }, }; #define NCOMMAND (sizeof (command_table) / sizeof (command_table[0])) @@ -231,7 +261,7 @@ get_usage(zfs_help_t idx) "[-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] " " ...\n")); @@ -244,14 +274,16 @@ get_usage(zfs_help_t idx) "[filesystem|volume|snapshot] ...\n")); case HELP_MOUNT: return (gettext("\tmount\n" - "\tmount [-vO] [-o opts] <-a | filesystem>\n")); + "\tmount [-lvO] [-o opts] <-a | filesystem>\n")); case HELP_PROMOTE: return (gettext("\tpromote \n")); case HELP_RECEIVE: - return (gettext("\treceive [-vnsFu] \n" - "\treceive [-vnsFu] [-o origin=] [-d | -e] " - "\n" + return (gettext("\treceive [-vnsFhu] " + "[-o =] ... [-x ] ...\n" + "\t \n" + "\treceive [-vnsFhu] [-o =] ... " + "[-x ] ... \n" + "\t [-d | -e] \n" "\treceive -A \n")); case HELP_RENAME: return (gettext("\trename [-f] " @@ -261,25 +293,25 @@ get_usage(zfs_help_t idx) case HELP_ROLLBACK: return (gettext("\trollback [-rRf] \n")); case HELP_SEND: - return (gettext("\tsend [-DnPpRvLe] [-[iI] snapshot] " + return (gettext("\tsend [-DnPpRvLecwhb] [-[i|I] snapshot] " "\n" - "\tsend [-Le] [-i snapshot|bookmark] " + "\tsend [-nvPLecw] [-i snapshot|bookmark] " "\n" "\tsend [-nvPe] -t \n")); case HELP_SET: return (gettext("\tset ... " " ...\n")); case HELP_SHARE: - return (gettext("\tshare <-a | filesystem>\n")); + return (gettext("\tshare [-l] <-a [nfs|smb] | filesystem>\n")); case HELP_SNAPSHOT: - return (gettext("\tsnapshot|snap [-r] [-o property=value] ... " + return (gettext("\tsnapshot [-r] [-o property=value] ... " "@ ...\n")); case HELP_UNMOUNT: return (gettext("\tunmount [-f] " "<-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 \n" "\tallow [-ldug] " @@ -310,17 +342,43 @@ get_usage(zfs_help_t idx) "[-s field] ...\n" "\t [-S field] ... [-t type[,...]] " "\n")); + case HELP_PROJECTSPACE: + return (gettext("\tprojectspace [-Hp] [-o field[,...]] " + "[-s field] ... \n" + "\t [-S field] ... \n")); + case HELP_PROJECT: + return (gettext("\tproject [-d|-r] \n" + "\tproject -c [-0] [-d|-r] [-p id] \n" + "\tproject -C [-k] [-r] \n" + "\tproject [-p id] [-r] [-s] \n")); case HELP_HOLD: return (gettext("\thold [-r] ...\n")); case HELP_HOLDS: - return (gettext("\tholds [-r] ...\n")); + return (gettext("\tholds [-rH] ...\n")); case HELP_RELEASE: return (gettext("\trelease [-r] ...\n")); case HELP_DIFF: return (gettext("\tdiff [-FHt] " "[snapshot|filesystem]\n")); + case HELP_REMAP: + return (gettext("\tremap \n")); case HELP_BOOKMARK: return (gettext("\tbookmark \n")); + case HELP_CHANNEL_PROGRAM: + return (gettext("\tprogram [-jn] [-t ] " + "[-m ]\n" + "\t [lua args...]\n")); + case HELP_LOAD_KEY: + return (gettext("\tload-key [-rn] [-L ] " + "<-a | filesystem|volume>\n")); + case HELP_UNLOAD_KEY: + return (gettext("\tunload-key [-r] " + "<-a | filesystem|volume>\n")); + case HELP_CHANGE_KEY: + return (gettext("\tchange-key [-l] [-o keyformat=]\n" + "\t [-o keylocation=] [-o pbkfd2iters=]\n" + "\t \n" + "\tchange-key -i [-l] \n")); } abort(); @@ -349,6 +407,18 @@ safe_malloc(size_t size) return (data); } +void * +safe_realloc(void *data, size_t size) +{ + void *newp; + if ((newp = realloc(data, size)) == NULL) { + free(data); + nomem(); + } + + return (newp); +} + static char * safe_strdup(char *str) { @@ -444,10 +514,26 @@ usage(boolean_t requested) (void) fprintf(fp, " NO NO \n"); (void) fprintf(fp, "\t%-15s ", "groupused@..."); (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "projectused@..."); + (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "userobjused@..."); + (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "groupobjused@..."); + (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "projectobjused@..."); + (void) fprintf(fp, " NO NO \n"); (void) fprintf(fp, "\t%-15s ", "userquota@..."); (void) fprintf(fp, "YES NO | none\n"); (void) fprintf(fp, "\t%-15s ", "groupquota@..."); (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "projectquota@..."); + (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "userobjquota@..."); + (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "groupobjquota@..."); + (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "projectobjquota@..."); + (void) fprintf(fp, "YES NO | none\n"); (void) fprintf(fp, "\t%-15s ", "written@"); (void) fprintf(fp, " NO NO \n"); @@ -455,9 +541,9 @@ usage(boolean_t requested) "with standard units such as K, M, G, etc.\n")); (void) fprintf(fp, gettext("\nUser-defined properties can " "be specified by using a name containing a colon (:).\n")); - (void) fprintf(fp, gettext("\nThe {user|group}{used|quota}@ " - "properties must be appended with\n" - "a user or group specifier of one of these forms:\n" + (void) fprintf(fp, gettext("\nThe {user|group|project}" + "[obj]{used|quota}@ properties must be appended with\n" + "a user|group|project specifier of one of these forms:\n" " POSIX name (eg: \"matt\")\n" " POSIX id (eg: \"126829\")\n" " SMB name@domain (eg: \"matt@sun\")\n" @@ -486,26 +572,48 @@ usage(boolean_t requested) * Take a property=value argument string and add it to the given nvlist. * Modifies the argument inplace. */ -static int +static boolean_t parseprop(nvlist_t *props, char *propname) { - char *propval, *strval; + char *propval; if ((propval = strchr(propname, '=')) == NULL) { (void) fprintf(stderr, gettext("missing " "'=' for property=value argument\n")); - return (-1); + 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 @@ -659,8 +767,10 @@ zfs_do_clone(int argc, char **argv) while ((c = getopt(argc, argv, "o:p")) != -1) { switch (c) { case 'o': - if (parseprop(props, optarg) != 0) + if (!parseprop(props, optarg)) { + nvlist_free(props); return (1); + } break; case 'p': parents = B_TRUE; @@ -692,8 +802,10 @@ zfs_do_clone(int argc, char **argv) } /* 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)) { @@ -703,10 +815,16 @@ zfs_do_clone(int argc, char **argv) * 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 */ @@ -728,8 +846,7 @@ zfs_do_clone(int argc, char **argv) return (!!ret); usage: - if (zhp) - zfs_close(zhp); + ASSERT3P(zhp, ==, NULL); nvlist_free(props); usage(B_FALSE); return (-1); @@ -801,7 +918,7 @@ zfs_do_create(int argc, char **argv) nomem(); break; case 'o': - if (parseprop(props, optarg) != 0) + if (!parseprop(props, optarg)) goto error; break; case 's': @@ -847,7 +964,7 @@ zfs_do_create(int argc, char **argv) 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) @@ -864,7 +981,7 @@ zfs_do_create(int argc, char **argv) (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) { + props, 0, NULL, zpool_handle, B_TRUE, msg)) == NULL) { zpool_close(zpool_handle); goto error; } @@ -950,6 +1067,7 @@ typedef struct destroy_cbdata { int64_t cb_snapused; char *cb_snapspec; char *cb_bookmark; + uint64_t cb_snap_count; } destroy_cbdata_t; /* @@ -1012,11 +1130,22 @@ out: return (0); } +static int +destroy_batched(destroy_cbdata_t *cb) +{ + int error = zfs_destroy_snaps_nvl(g_zfs, + cb->cb_batchedsnaps, B_FALSE); + fnvlist_free(cb->cb_batchedsnaps); + cb->cb_batchedsnaps = fnvlist_alloc(); + return (error); +} + static int destroy_callback(zfs_handle_t *zhp, void *data) { destroy_cbdata_t *cb = data; const char *name = zfs_get_name(zhp); + int error; if (cb->cb_verbose) { if (cb->cb_parsable) { @@ -1051,17 +1180,25 @@ destroy_callback(zfs_handle_t *zhp, void *data) * because we must delete a clone before its origin. */ if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT) { + cb->cb_snap_count++; fnvlist_add_boolean(cb->cb_batchedsnaps, name); + if (cb->cb_snap_count % 10 == 0 && cb->cb_defer_destroy) + error = destroy_batched(cb); } else { - int error = zfs_destroy_snaps_nvl(g_zfs, - cb->cb_batchedsnaps, B_FALSE); - fnvlist_free(cb->cb_batchedsnaps); - cb->cb_batchedsnaps = fnvlist_alloc(); - + error = destroy_batched(cb); if (error != 0 || zfs_unmount(zhp, NULL, cb->cb_force ? MS_FORCE : 0) != 0 || zfs_destroy(zhp, cb->cb_defer_destroy) != 0) { zfs_close(zhp); + /* + * When performing a recursive destroy we ignore errors + * so that the recursive destroy could continue + * destroying past problem datasets + */ + if (cb->cb_recurse) { + cb->cb_error = B_TRUE; + return (0); + } return (-1); } } @@ -1280,8 +1417,10 @@ zfs_do_destroy(int argc, char **argv) *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 || @@ -1299,7 +1438,7 @@ zfs_do_destroy(int argc, char **argv) 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); @@ -1370,7 +1509,7 @@ zfs_do_destroy(int argc, char **argv) "cannot destroy bookmark"); } - nvlist_free(cb.cb_nvl); + nvlist_free(nvl); return (err); } else { @@ -1412,7 +1551,6 @@ zfs_do_destroy(int argc, char **argv) rv = 1; goto out; } - cb.cb_batchedsnaps = fnvlist_alloc(); if (zfs_iter_dependents(zhp, B_FALSE, destroy_callback, &cb) != 0) { @@ -1430,7 +1568,7 @@ zfs_do_destroy(int argc, char **argv) err = zfs_destroy_snaps_nvl(g_zfs, cb.cb_batchedsnaps, cb.cb_defer_destroy); } - if (err != 0) + if (err != 0 || cb.cb_error == B_TRUE) rv = 1; } @@ -1595,7 +1733,7 @@ zfs_do_get(int argc, char **argv) { 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; @@ -2162,7 +2300,7 @@ zfs_do_upgrade(int argc, char **argv) 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); @@ -2192,6 +2330,8 @@ zfs_do_upgrade(int argc, char **argv) * [-S field [-S field]...] [-t type[,...]] filesystem | snapshot * zfs groupspace [-Hinp] [-o field[,...]] [-s field [-s field]...] * [-S field [-S field]...] [-t type[,...]] filesystem | snapshot + * zfs projectspace [-Hp] [-o field[,...]] [-s field [-s field]...] + * [-S field [-S field]...] filesystem | snapshot * * -H Scripted mode; elide headers and separate columns by tabs. * -i Translate SID to POSIX ID. @@ -2211,18 +2351,24 @@ enum us_field_types { 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) #define USTYPE_PSX_USR (1 << 1) #define USTYPE_SMB_GRP (1 << 2) #define USTYPE_SMB_USR (1 << 3) +#define USTYPE_PROJ (1 << 4) #define USTYPE_ALL \ - (USTYPE_PSX_GRP | USTYPE_PSX_USR | USTYPE_SMB_GRP | USTYPE_SMB_USR) + (USTYPE_PSX_GRP | USTYPE_PSX_USR | USTYPE_SMB_GRP | USTYPE_SMB_USR | \ + USTYPE_PROJ) static int us_type_bits[] = { USTYPE_PSX_GRP, @@ -2316,9 +2462,9 @@ compare_nums: rc = (rv64 < lv64) ? 1 : -1; } else { if ((nvlist_lookup_string(lnvl, propname, - &lvstr) == ENOENT) || + &lvstr) == ENOENT) || (nvlist_lookup_string(rnvl, propname, - &rvstr) == ENOENT)) { + &rvstr) == ENOENT)) { goto compare_nums; } rc = strcmp(lvstr, rvstr); @@ -2337,6 +2483,7 @@ compare_nums: if (rv64 != lv64) rc = (rv64 < lv64) ? 1 : -1; break; + default: break; } @@ -2362,6 +2509,27 @@ compare_nums: 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 boolean_t +zfs_prop_is_project(unsigned p) +{ + return (p == ZFS_PROP_PROJECTUSED || p == ZFS_PROP_PROJECTQUOTA || + p == ZFS_PROP_PROJECTOBJUSED || p == ZFS_PROP_PROJECTOBJQUOTA); +} + static inline const char * us_type2str(unsigned field_type) { @@ -2374,6 +2542,8 @@ us_type2str(unsigned field_type) return ("SMB User"); case USTYPE_SMB_GRP: return ("SMB Group"); + case USTYPE_PROJ: + return ("Project"); default: return ("Undefined"); } @@ -2451,7 +2621,7 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) 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; @@ -2459,7 +2629,7 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) if ((g = getgrgid(rid)) != NULL) name = g->gr_name; } - } else { + } else if (zfs_prop_is_user(prop)) { type = USTYPE_PSX_USR; if (!cb->cb_numname) { struct passwd *p; @@ -2467,6 +2637,8 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) if ((p = getpwuid(rid)) != NULL) name = p->pw_name; } + } else { + type = USTYPE_PROJ; } } @@ -2499,7 +2671,7 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) 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; /* @@ -2516,23 +2688,46 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) } /* 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 || + prop == ZFS_PROP_PROJECTUSED || + prop == ZFS_PROP_PROJECTQUOTA) { + 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) { + if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED || + prop == ZFS_PROP_PROJECTUSED) { 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 || + prop == ZFS_PROP_PROJECTQUOTA) { propname = "quota"; if (!nvlist_exists(props, "used")) (void) nvlist_add_uint64(props, "used", 0); + } else if (prop == ZFS_PROP_USEROBJUSED || + prop == ZFS_PROP_GROUPOBJUSED || prop == ZFS_PROP_PROJECTOBJUSED) { + propname = "objused"; + if (!nvlist_exists(props, "objquota")) + (void) nvlist_add_uint64(props, "objquota", 0); + } else if (prop == ZFS_PROP_USEROBJQUOTA || + prop == ZFS_PROP_GROUPOBJQUOTA || + prop == ZFS_PROP_PROJECTOBJQUOTA) { + 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) @@ -2562,7 +2757,7 @@ print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types, 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), @@ -2570,7 +2765,7 @@ print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types, 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); @@ -2581,18 +2776,21 @@ print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types, 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; @@ -2601,16 +2799,33 @@ print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types, 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; } @@ -2624,9 +2839,9 @@ print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types, 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++; @@ -2651,10 +2866,10 @@ print_us(boolean_t scripted, boolean_t parsable, int *fields, int types, 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++; @@ -2678,7 +2893,7 @@ zfs_do_userspace(int argc, char **argv) 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; @@ -2703,13 +2918,22 @@ zfs_do_userspace(int argc, char **argv) if (argc < 2) usage(B_FALSE); - if (strcmp(argv[0], "groupspace") == 0) + if (strcmp(argv[0], "groupspace") == 0) { /* Toggle default group types */ types = USTYPE_PSX_GRP | USTYPE_SMB_GRP; + } else if (strcmp(argv[0], "projectspace") == 0) { + types = USTYPE_PROJ; + prtnum = B_TRUE; + } while ((c = getopt(argc, argv, "nHpo:s:S:t:i")) != -1) { switch (c) { case 'n': + if (types == USTYPE_PROJ) { + (void) fprintf(stderr, + gettext("invalid option 'n'\n")); + usage(B_FALSE); + } prtnum = B_TRUE; break; case 'H': @@ -2731,9 +2955,19 @@ zfs_do_userspace(int argc, char **argv) } break; case 't': + if (types == USTYPE_PROJ) { + (void) fprintf(stderr, + gettext("invalid option 't'\n")); + usage(B_FALSE); + } tfield = optarg; break; case 'i': + if (types == USTYPE_PROJ) { + (void) fprintf(stderr, + gettext("invalid option 'i'\n")); + usage(B_FALSE); + } sid2posix = B_TRUE; break; case ':': @@ -2803,8 +3037,15 @@ zfs_do_userspace(int argc, char **argv) } while (delim != NULL); } - if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET)) == NULL) + if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_FILESYSTEM | + ZFS_TYPE_SNAPSHOT)) == NULL) + return (1); + if (zhp->zfs_head_type != ZFS_TYPE_FILESYSTEM) { + (void) fprintf(stderr, gettext("operation is only applicable " + "to filesystems and their snapshots\n")); + zfs_close(zhp); return (1); + } if ((avl_pool = uu_avl_pool_create("us_avl_pool", sizeof (us_node_t), offsetof(us_node_t, usn_avlnode), us_compare, UU_DEFAULT)) == NULL) @@ -2827,15 +3068,20 @@ zfs_do_userspace(int argc, char **argv) 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) && - !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP)))) + (zfs_prop_is_group(p) && + !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP))) || + (zfs_prop_is_project(p) && types != USTYPE_PROJ)) continue; + cb.cb_prop = p; - if ((ret = zfs_userspace(zhp, p, userspace_cb, &cb)) != 0) + if ((ret = zfs_userspace(zhp, p, userspace_cb, &cb)) != 0) { + zfs_close(zhp); return (ret); + } } + zfs_close(zhp); /* Sort the list */ if ((node = uu_avl_first(avl_tree)) == NULL) @@ -3327,7 +3573,7 @@ zfs_do_promote(int argc, char **argv) * * -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, @@ -3577,8 +3823,10 @@ zfs_do_set(int argc, char **argv) if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) nomem(); for (i = 1; i < ds_start; i++) { - if ((ret = parseprop(props, argv[i])) != 0) + if (!parseprop(props, argv[i])) { + ret = -1; goto error; + } } ret = zfs_for_each(argc - ds_start, argv + ds_start, 0, @@ -3645,8 +3893,11 @@ zfs_do_snapshot(int argc, char **argv) while ((c = getopt(argc, argv, "ro:")) != -1) { switch (c) { case 'o': - if (parseprop(props, optarg) != 0) + if (!parseprop(props, optarg)) { + nvlist_free(sd.sd_nvl); + nvlist_free(props); return (1); + } break; case 'r': sd.sd_recursive = B_TRUE; @@ -3717,8 +3968,26 @@ zfs_do_send(int argc, char **argv) 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'}, + {"raw", no_argument, NULL, 'w'}, + {"backup", no_argument, NULL, 'b'}, + {"holds", no_argument, NULL, 'h'}, + {0, 0, 0, 0} + }; + /* check options */ - while ((c = getopt(argc, argv, ":i:I:RDpvnPLet:")) != -1) { + while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwb", long_options, + NULL)) != -1) { switch (c) { case 'i': if (fromname) @@ -3737,6 +4006,12 @@ zfs_do_send(int argc, char **argv) case 'p': flags.props = B_TRUE; break; + case 'b': + flags.backup = B_TRUE; + break; + case 'h': + flags.holds = B_TRUE; + break; case 'P': flags.parsable = B_TRUE; flags.verbose = B_TRUE; @@ -3762,14 +4037,52 @@ zfs_do_send(int argc, char **argv) case 't': resume_token = optarg; break; + case 'c': + flags.compress = B_TRUE; + break; + case 'w': + flags.raw = B_TRUE; + flags.compress = B_TRUE; + flags.embed_data = B_TRUE; + flags.largeblock = 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); } } @@ -3779,7 +4092,7 @@ zfs_do_send(int argc, char **argv) if (resume_token != NULL) { if (fromname != NULL || flags.replicate || flags.props || - flags.dedup) { + flags.backup || flags.dedup) { (void) fprintf(stderr, gettext("invalid flags combined with -t\n")); usage(B_FALSE); @@ -3819,13 +4132,12 @@ zfs_do_send(int argc, char **argv) if (strchr(argv[0], '@') == NULL || (fromname && strchr(fromname, '#') != NULL)) { char frombuf[ZFS_MAX_DATASET_NAME_LEN]; - enum lzc_send_flags lzc_flags = 0; if (flags.replicate || flags.doall || flags.props || - flags.dedup || flags.dryrun || flags.verbose || - flags.progress) { - (void) fprintf(stderr, - gettext("Error: " + flags.backup || flags.dedup || flags.holds || + (strchr(argv[0], '@') == NULL && + (flags.dryrun || flags.verbose || flags.progress))) { + (void) fprintf(stderr, gettext("Error: " "Unsupported flag with filesystem or bookmark.\n")); return (1); } @@ -3834,25 +4146,20 @@ zfs_do_send(int argc, char **argv) if (zhp == NULL) return (1); - if (flags.largeblock) - lzc_flags |= LZC_SEND_FLAG_LARGE_BLOCK; - if (flags.embed_data) - lzc_flags |= LZC_SEND_FLAG_EMBED_DATA; - 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'; (void) strlcat(frombuf, fromname, sizeof (frombuf)); fromname = frombuf; } - err = zfs_send_one(zhp, fromname, STDOUT_FILENO, lzc_flags); + err = zfs_send_one(zhp, fromname, STDOUT_FILENO, flags); zfs_close(zhp); return (err != 0); } @@ -3923,30 +4230,50 @@ zfs_do_send(int argc, char **argv) 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; - nvpair_t *nvp = NULL; if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) nomem(); /* check options */ - while ((c = getopt(argc, argv, ":o:denuvFsA")) != -1) { + while ((c = getopt(argc, argv, ":o:x:dehnuvFsA")) != -1) { switch (c) { case 'o': - if (parseprop(props, optarg) != 0) - return (1); + 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': + if (flags.istail) { + (void) fprintf(stderr, gettext("invalid option " + "combination: -d and -e are mutually " + "exclusive\n")); + usage(B_FALSE); + } flags.isprefix = B_TRUE; break; case 'e': - flags.isprefix = B_TRUE; + if (flags.isprefix) { + (void) fprintf(stderr, gettext("invalid option " + "combination: -d and -e are mutually " + "exclusive\n")); + usage(B_FALSE); + } flags.istail = B_TRUE; break; + case 'h': + flags.skipholds = B_TRUE; + break; case 'n': flags.dryrun = B_TRUE; break; @@ -3980,6 +4307,10 @@ zfs_do_receive(int argc, char **argv) argc -= optind; argv += optind; + /* zfs recv -e (use "tail" name) implies -d (remove dataset "head") */ + if (flags.istail) + flags.isprefix = B_TRUE; + /* check number of arguments */ if (argc < 1) { (void) fprintf(stderr, gettext("missing snapshot argument\n")); @@ -3990,17 +4321,10 @@ zfs_do_receive(int argc, char **argv) usage(B_FALSE); } - while ((nvp = nvlist_next_nvpair(props, nvp))) { - if (strcmp(nvpair_name(nvp), "origin") != 0) { - (void) fprintf(stderr, gettext("invalid option")); - usage(B_FALSE); - } - } - if (abort_resumable) { if (flags.isprefix || flags.istail || flags.dryrun || flags.resumable || flags.nomount) { - (void) fprintf(stderr, gettext("invalid option")); + (void) fprintf(stderr, gettext("invalid option\n")); usage(B_FALSE); } @@ -4012,9 +4336,12 @@ zfs_do_receive(int argc, char **argv) ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) { zfs_handle_t *zhp = zfs_open(g_zfs, namebuf, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); - if (zhp == NULL) + 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); @@ -4027,11 +4354,14 @@ zfs_do_receive(int argc, char **argv) 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); } @@ -4040,9 +4370,11 @@ zfs_do_receive(int argc, char **argv) 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], props, &flags, STDIN_FILENO, NULL); + nvlist_free(props); return (err != 0); } @@ -4069,10 +4401,23 @@ zfs_do_receive(int argc, char **argv) #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" #define ZFS_DELEG_PERM_BOOKMARK "bookmark" +#define ZFS_DELEG_PERM_REMAP "remap" +#define ZFS_DELEG_PERM_LOAD_KEY "load-key" +#define ZFS_DELEG_PERM_CHANGE_KEY "change-key" + +#define ZFS_DELEG_PERM_PROJECTUSED "projectused" +#define ZFS_DELEG_PERM_PROJECTQUOTA "projectquota" +#define ZFS_DELEG_PERM_PROJECTOBJUSED "projectobjused" +#define ZFS_DELEG_PERM_PROJECTOBJQUOTA "projectobjquota" #define ZFS_NUM_DELEG_NOTES ZFS_DELEG_NOTE_NONE @@ -4093,12 +4438,23 @@ static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = { { ZFS_DELEG_PERM_SHARE, ZFS_DELEG_NOTE_SHARE }, { ZFS_DELEG_PERM_SNAPSHOT, ZFS_DELEG_NOTE_SNAPSHOT }, { ZFS_DELEG_PERM_BOOKMARK, ZFS_DELEG_NOTE_BOOKMARK }, + { ZFS_DELEG_PERM_REMAP, ZFS_DELEG_NOTE_REMAP }, + { ZFS_DELEG_PERM_LOAD_KEY, ZFS_DELEG_NOTE_LOAD_KEY }, + { ZFS_DELEG_PERM_CHANGE_KEY, ZFS_DELEG_NOTE_CHANGE_KEY }, { ZFS_DELEG_PERM_GROUPQUOTA, ZFS_DELEG_NOTE_GROUPQUOTA }, { ZFS_DELEG_PERM_GROUPUSED, ZFS_DELEG_NOTE_GROUPUSED }, { 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 }, + { ZFS_DELEG_PERM_PROJECTUSED, ZFS_DELEG_NOTE_PROJECTUSED }, + { ZFS_DELEG_PERM_PROJECTQUOTA, ZFS_DELEG_NOTE_PROJECTQUOTA }, + { ZFS_DELEG_PERM_PROJECTOBJUSED, ZFS_DELEG_NOTE_PROJECTOBJUSED }, + { ZFS_DELEG_PERM_PROJECTOBJQUOTA, ZFS_DELEG_NOTE_PROJECTOBJQUOTA }, { NULL, ZFS_DELEG_NOTE_NONE } }; @@ -4176,6 +4532,14 @@ deleg_perm_type(zfs_deleg_note_t note) 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: + case ZFS_DELEG_NOTE_PROJECTUSED: + case ZFS_DELEG_NOTE_PROJECTQUOTA: + case ZFS_DELEG_NOTE_PROJECTOBJUSED: + case ZFS_DELEG_NOTE_PROJECTOBJQUOTA: /* other */ return (gettext("other")); default: @@ -4183,7 +4547,7 @@ deleg_perm_type(zfs_deleg_note_t note) } } -static int inline +static int who_type2weight(zfs_deleg_who_type_t who_type) { int res; @@ -4403,7 +4767,7 @@ fs_perm_fini(fs_perm_t *fsperm) 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) { @@ -4500,8 +4864,9 @@ parse_fs_perm(fs_perm_t *fsperm, nvlist_t *nvl) 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) { @@ -4537,6 +4902,7 @@ parse_fs_perm(fs_perm_t *fsperm, nvlist_t *nvl) if (g) nice_name = g->gr_name; break; + default: break; } @@ -4553,7 +4919,7 @@ parse_fs_perm(fs_perm_t *fsperm, nvlist_t *nvl) who_perm = &node->who_perm; } } - + VERIFY3P(who_perm, !=, NULL); (void) parse_who_perm(who_perm, nvl2, perm_locality); } @@ -4658,6 +5024,12 @@ deleg_perm_comment(zfs_deleg_note_t note) case ZFS_DELEG_NOTE_SNAPSHOT: str = gettext(""); break; + case ZFS_DELEG_NOTE_LOAD_KEY: + str = gettext("Allows loading or unloading an encryption key"); + break; + case ZFS_DELEG_NOTE_CHANGE_KEY: + str = gettext("Allows changing or adding an encryption key"); + break; /* * case ZFS_DELEG_NOTE_VSCAN: * str = gettext(""); @@ -4679,6 +5051,33 @@ deleg_perm_comment(zfs_deleg_note_t note) 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; + case ZFS_DELEG_NOTE_PROJECTQUOTA: + str = gettext("Allows accessing any projectquota@... property"); + break; + case ZFS_DELEG_NOTE_PROJECTOBJQUOTA: + str = gettext("Allows accessing any \n\t\t\t\t" + "projectobjquota@... property"); + break; + case ZFS_DELEG_NOTE_PROJECTUSED: + str = gettext("Allows reading any projectused@... property"); + break; + case ZFS_DELEG_NOTE_PROJECTOBJUSED: + str = gettext("Allows accessing any \n\t\t\t\t" + "projectobjused@... property"); + break; /* other */ default: str = ""; @@ -4739,9 +5138,9 @@ allow_usage(boolean_t un, boolean_t requested, const char *msg) (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); } @@ -4861,8 +5260,8 @@ store_allow_perm(zfs_deleg_who_type_t type, boolean_t local, boolean_t descend, int i; char ld[2] = { '\0', '\0' }; char who_buf[MAXNAMELEN + 32]; - char base_type = ZFS_DELEG_WHO_UNKNOWN; - char set_type = ZFS_DELEG_WHO_UNKNOWN; + char base_type = '\0'; + char set_type = '\0'; nvlist_t *base_nvl = NULL; nvlist_t *set_nvl = NULL; nvlist_t *nvl; @@ -4911,8 +5310,10 @@ store_allow_perm(zfs_deleg_who_type_t type, boolean_t local, boolean_t descend, 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) { @@ -5108,7 +5509,6 @@ print_set_creat_perms(uu_avl_t *who_avl) gettext("Create time permissions:\n"), NULL }; - const char **title_ptr = sc_title; who_perm_node_t *who_node = NULL; int prev_weight = -1; @@ -5122,7 +5522,7 @@ print_set_creat_perms(uu_avl_t *who_avl) deleg_perm_node_t *deleg_node; if (prev_weight != weight) { - (void) printf("%s", *title_ptr++); + (void) printf("%s", sc_title[weight]); prev_weight = weight; } @@ -5146,7 +5546,7 @@ print_set_creat_perms(uu_avl_t *who_avl) } } -static void inline +static void print_uge_deleg_perms(uu_avl_t *who_avl, boolean_t local, boolean_t descend, const char *title) { @@ -5197,8 +5597,10 @@ print_uge_deleg_perms(uu_avl_t *who_avl, boolean_t local, boolean_t descend, case ZFS_DELEG_EVERYONE: who = gettext("everyone"); who_name = NULL; - default: break; + + default: + assert(who != NULL); } prt_who = B_FALSE; @@ -5532,8 +5934,6 @@ print_holds(boolean_t scripted, int nwidth, int tagwidth, nvlist_t *nvl) uint64_t val = 0; time_t time; struct tm t; - char sep = scripted ? '\t' : ' '; - int sepnum = scripted ? 1 : 2; (void) nvpair_value_uint64(nvp2, &val); time = (time_t)val; @@ -5541,8 +5941,13 @@ print_holds(boolean_t scripted, int nwidth, int tagwidth, nvlist_t *nvl) (void) strftime(tsbuf, DATETIME_BUF_LEN, gettext(STRFTIME_FMT_STR), &t); - (void) printf("%-*s%*c%-*s%*c%s\n", nwidth, zname, - sepnum, sep, tagwidth, tagname, sepnum, sep, tsbuf); + if (scripted) { + (void) printf("%s\t%s\t%s\n", zname, + tagname, tsbuf); + } else { + (void) printf("%-*s %-*s %s\n", nwidth, + zname, tagwidth, tagname, tsbuf); + } } } } @@ -5588,9 +5993,10 @@ holds_callback(zfs_handle_t *zhp, void *data) } /* - * zfs holds [-r] ... + * zfs holds [-rH] ... * - * -r Recursively hold + * -r Lists holds that are set on the named snapshots recursively. + * -H Scripted mode; elide headers and separate columns by tabs. */ static int zfs_do_holds(int argc, char **argv) @@ -5685,7 +6091,12 @@ zfs_do_holds(int argc, char **argv) #define CHECK_SPINNER 30 #define SPINNER_TIME 3 /* seconds */ -#define MOUNT_TIME 5 /* seconds */ +#define MOUNT_TIME 1 /* seconds */ + +typedef struct get_all_state { + boolean_t ga_verbose; + get_all_cb_t *ga_cbp; +} get_all_state_t; static int get_one_dataset(zfs_handle_t *zhp, void *data) @@ -5694,10 +6105,10 @@ get_one_dataset(zfs_handle_t *zhp, void *data) static int spinval = 0; static int spincheck = 0; static time_t last_spin_time = (time_t)0; - get_all_cb_t *cbp = data; + get_all_state_t *state = data; zfs_type_t type = zfs_get_type(zhp); - if (cbp->cb_verbose) { + if (state->ga_verbose) { if (--spincheck < 0) { time_t now = time(NULL); if (last_spin_time + SPINNER_TIME < now) { @@ -5723,25 +6134,23 @@ get_one_dataset(zfs_handle_t *zhp, void *data) zfs_close(zhp); return (0); } - libzfs_add_handle(cbp, zhp); - assert(cbp->cb_used <= cbp->cb_alloc); + libzfs_add_handle(state->ga_cbp, zhp); + assert(state->ga_cbp->cb_used <= state->ga_cbp->cb_alloc); return (0); } static void -get_all_datasets(zfs_handle_t ***dslist, size_t *count, boolean_t verbose) +get_all_datasets(get_all_cb_t *cbp, boolean_t verbose) { - get_all_cb_t cb = { 0 }; - cb.cb_verbose = verbose; - cb.cb_getone = get_one_dataset; + get_all_state_t state = { + .ga_verbose = verbose, + .ga_cbp = cbp + }; if (verbose) set_progress_header(gettext("Reading ZFS config")); - (void) zfs_iter_root(g_zfs, get_one_dataset, &cb); - - *dslist = cb.cb_handles; - *count = cb.cb_used; + (void) zfs_iter_root(g_zfs, get_one_dataset, &state); if (verbose) finish_progress(gettext("done.")); @@ -5752,8 +6161,19 @@ get_all_datasets(zfs_handle_t ***dslist, size_t *count, boolean_t verbose) * similar, we have a common function with an extra parameter to determine which * mode we are using. */ -#define OP_SHARE 0x1 -#define OP_MOUNT 0x2 +typedef enum { OP_SHARE, OP_MOUNT } share_mount_op_t; + +typedef struct share_mount_state { + share_mount_op_t sm_op; + boolean_t sm_verbose; + int sm_flags; + char *sm_options; + char *sm_proto; /* only valid for OP_SHARE */ + pthread_mutex_t sm_lock; /* protects the remaining fields */ + uint_t sm_total; /* number of filesystems to process */ + uint_t sm_done; /* number of filesystems processed */ + int sm_status; /* -1 if any of the share/mount operations failed */ +} share_mount_state_t; /* * Share or mount a dataset. @@ -5871,6 +6291,22 @@ share_mount_one(zfs_handle_t *zhp, int op, int flags, char *protocol, return (0); } + /* + * If this filesystem is encrypted and does not have + * a loaded key, we can not mount it. + */ + if ((flags & MS_CRYPT) == 0 && + zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF && + zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS) == + ZFS_KEYSTATUS_UNAVAILABLE) { + if (!explicit) + return (0); + + (void) fprintf(stderr, gettext("cannot %s '%s': " + "encryption key not loaded\n"), cmdname, zfs_get_name(zhp)); + return (1); + } + /* * If this filesystem is inconsistent and has a receive resume * token, we can not mount it. @@ -5883,7 +6319,7 @@ share_mount_one(zfs_handle_t *zhp, int op, int flags, char *protocol, (void) fprintf(stderr, gettext("cannot %s '%s': " "Contains partially-completed state from " - "\"zfs receive -r\", which can be resumed with " + "\"zfs receive -s\", which can be resumed with " "\"zfs send -t\"\n"), cmdname, zfs_get_name(zhp)); return (1); @@ -5903,10 +6339,10 @@ share_mount_one(zfs_handle_t *zhp, int op, int flags, char *protocol, 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); @@ -5917,7 +6353,7 @@ share_mount_one(zfs_handle_t *zhp, int op, int flags, char *protocol, } if (!zfs_is_mounted(zhp, NULL) && - zfs_mount(zhp, NULL, 0) != 0) + zfs_mount(zhp, NULL, flags) != 0) return (1); if (protocol == NULL) { @@ -5995,6 +6431,29 @@ report_mount_progress(int current, int total) update_progress(info); } +/* + * zfs_foreach_mountpoint() callback that mounts or shares one filesystem and + * updates the progress meter. + */ +static int +share_mount_one_cb(zfs_handle_t *zhp, void *arg) +{ + share_mount_state_t *sms = arg; + int ret; + + ret = share_mount_one(zhp, sms->sm_op, sms->sm_flags, sms->sm_proto, + B_FALSE, sms->sm_options); + + pthread_mutex_lock(&sms->sm_lock); + if (ret != 0) + sms->sm_status = ret; + sms->sm_done++; + if (sms->sm_verbose) + report_mount_progress(sms->sm_done, sms->sm_total); + pthread_mutex_unlock(&sms->sm_lock); + return (ret); +} + static void append_options(char *mntopts, char *newopts) { @@ -6024,7 +6483,7 @@ share_mount(int op, int argc, char **argv) int flags = 0; /* check options */ - while ((c = getopt(argc, argv, op == OP_MOUNT ? ":avo:O" : "a")) + while ((c = getopt(argc, argv, op == OP_MOUNT ? ":alvo:O" : "al")) != -1) { switch (c) { case 'a': @@ -6033,6 +6492,9 @@ share_mount(int op, int argc, char **argv) case 'v': verbose = B_TRUE; break; + case 'l': + flags |= MS_CRYPT; + break; case 'o': if (*optarg == '\0') { (void) fprintf(stderr, gettext("empty mount " @@ -6066,8 +6528,6 @@ share_mount(int op, int argc, char **argv) /* check number of arguments */ if (do_all) { - zfs_handle_t **dslist = NULL; - size_t i, count = 0; char *protocol = NULL; if (op == OP_SHARE && argc > 0) { @@ -6088,24 +6548,35 @@ share_mount(int op, int argc, char **argv) } start_progress_timer(); - get_all_datasets(&dslist, &count, verbose); + get_all_cb_t cb = { 0 }; + get_all_datasets(&cb, verbose); - if (count == 0) + if (cb.cb_used == 0) { + if (options != NULL) + free(options); return (0); + } - qsort(dslist, count, sizeof (void *), libzfs_dataset_cmp); - - for (i = 0; i < count; i++) { - if (verbose) - report_mount_progress(i, count); + share_mount_state_t share_mount_state = { 0 }; + share_mount_state.sm_op = op; + share_mount_state.sm_verbose = verbose; + share_mount_state.sm_flags = flags; + share_mount_state.sm_options = options; + share_mount_state.sm_proto = protocol; + share_mount_state.sm_total = cb.cb_used; + pthread_mutex_init(&share_mount_state.sm_lock, NULL); - if (share_mount_one(dslist[i], op, flags, protocol, - B_FALSE, options) != 0) - ret = 1; - zfs_close(dslist[i]); - } + /* + * libshare isn't mt-safe, so only do the operation in parallel + * if we're mounting. + */ + zfs_foreach_mountpoint(g_zfs, cb.cb_handles, cb.cb_used, + share_mount_one_cb, &share_mount_state, op == OP_MOUNT); + ret = share_mount_state.sm_status; - free(dslist); + for (int i = 0; i < cb.cb_used; i++) + zfs_close(cb.cb_handles[i]); + free(cb.cb_handles); } else if (argc == 0) { struct mnttab entry; @@ -6116,14 +6587,18 @@ share_mount(int op, int argc, char **argv) } /* - * 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 || @@ -6153,6 +6628,9 @@ share_mount(int op, int argc, char **argv) } } + if (options != NULL) + free(options); + return (ret); } @@ -6198,8 +6676,8 @@ unshare_unmount_compare(const void *larg, const void *rarg, void *unused) /* * 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) @@ -6212,7 +6690,7 @@ 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. @@ -6243,8 +6721,8 @@ unshare_unmount_path(int op, char *path, int flags, boolean_t is_manual) "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)); @@ -6333,7 +6811,7 @@ unshare_unmount(int op, int argc, char **argv) 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; @@ -6341,6 +6819,11 @@ unshare_unmount(int op, int argc, char **argv) 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); @@ -6355,9 +6838,9 @@ unshare_unmount(int op, int argc, char **argv) /* * 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 @@ -6372,6 +6855,19 @@ unshare_unmount(int op, int argc, char **argv) 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")); @@ -6405,6 +6901,15 @@ unshare_unmount(int op, int argc, char **argv) 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, @@ -6464,14 +6969,14 @@ unshare_unmount(int op, int argc, char **argv) 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; case OP_MOUNT: if (zfs_unmount(node->un_zhp, - node->un_mountp, flags) != 0) + node->un_zhp->zfs_name, flags) != 0) ret = 1; break; } @@ -6595,6 +7100,21 @@ zfs_do_unshare(int argc, char **argv) return (unshare_unmount(OP_SHARE, argc, argv)); } +static int +disable_command_idx(char *command) +{ + for (int i = 0; i < NCOMMAND; i++) { + if (command_table[i].name == NULL) + continue; + + if (strcmp(command, command_table[i].name) == 0) { + command_table[i].name = NULL; + return (0); + } + } + return (1); +} + static int find_command_idx(char *command, int *idx) { @@ -6646,7 +7166,7 @@ zfs_do_diff(int argc, char **argv) if (argc < 1) { (void) fprintf(stderr, - gettext("must provide at least one snapshot name\n")); + gettext("must provide at least one snapshot name\n")); usage(B_FALSE); } @@ -6666,12 +7186,13 @@ zfs_do_diff(int argc, char **argv) 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); /* @@ -6687,6 +7208,55 @@ zfs_do_diff(int argc, char **argv) return (err != 0); } + +/* + * zfs remap + * + * N.B. The remap command has been disabled and may be removed in the future. + * + * Remap the indirect blocks in the given filesystem or volume so that they no + * longer reference blocks on previously removed vdevs and we can eventually + * shrink the size of the indirect mapping objects for the previously removed + * vdevs. Note that remapping all blocks might not be possible and that + * references from snapshots will still exist and cannot be remapped. + * + * This functionality is no longer particularly useful now that the removal + * code can map large chunks. Furthermore, explaining what this command + * does and why it may be useful requires a detailed understanding of the + * internals of device removal. These are details users should not be + * bothered with. If required, the remap command can be re-enabled by + * setting the ZFS_REMAP_ENABLED environment variable. + * + * > ZFS_REMAP_ENABLED=yes zfs remap + */ +static int +zfs_do_remap(int argc, char **argv) +{ + const char *fsname; + int err = 0; + int c; + + /* check options */ + while ((c = getopt(argc, argv, "")) != -1) { + switch (c) { + case '?': + (void) fprintf(stderr, + gettext("invalid option '%c'\n"), optopt); + usage(B_FALSE); + } + } + + if (argc != 2) { + (void) fprintf(stderr, gettext("wrong number of arguments\n")); + usage(B_FALSE); + } + + fsname = argv[1]; + err = zfs_remap_indirects(g_zfs, fsname); + + return (err); +} + /* * zfs bookmark * @@ -6696,6 +7266,7 @@ 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; nvlist_t *nvl; int ret = 0; @@ -6724,9 +7295,15 @@ zfs_do_bookmark(int argc, char **argv) goto usage; } + if (strchr(argv[0], '@') == NULL) { + (void) fprintf(stderr, + gettext("invalid snapshot name '%s': " + "must contain a '@'\n"), argv[0]); + goto usage; + } 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; } @@ -6736,12 +7313,24 @@ zfs_do_bookmark(int argc, char **argv) * 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; @@ -6749,17 +7338,17 @@ zfs_do_bookmark(int argc, char **argv) nvl = fnvlist_alloc(); - fnvlist_add_string(nvl, argv[1], snapname); + fnvlist_add_string(nvl, bookname, snapname); ret = lzc_bookmark(nvl, NULL); fnvlist_free(nvl); if (ret != 0) { - const char *err_msg; + const char *err_msg = NULL; char errbuf[1024]; (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, - "cannot create bookmark '%s'"), argv[1]); + "cannot create bookmark '%s'"), bookname); switch (ret) { case EXDEV: @@ -6777,12 +7366,17 @@ zfs_do_bookmark(int argc, char **argv) case ENOSPC: err_msg = "out of space"; break; + case ENOENT: + err_msg = "dataset does not exist"; + break; default: - err_msg = "unknown error"; + (void) zfs_standard_error(g_zfs, ret, errbuf); break; } - (void) fprintf(stderr, "%s: %s\n", errbuf, - dgettext(TEXT_DOMAIN, err_msg)); + if (err_msg != NULL) { + (void) fprintf(stderr, "%s: %s\n", errbuf, + dgettext(TEXT_DOMAIN, err_msg)); + } } return (ret != 0); @@ -6792,18 +7386,643 @@ usage: return (-1); } +static int +zfs_do_channel_program(int argc, char **argv) +{ + int ret, fd, c; + char *progbuf, *filename, *poolname; + size_t progsize, progread; + nvlist_t *outnvl = NULL; + uint64_t instrlimit = ZCP_DEFAULT_INSTRLIMIT; + uint64_t memlimit = ZCP_DEFAULT_MEMLIMIT; + boolean_t sync_flag = B_TRUE, json_output = B_FALSE; + zpool_handle_t *zhp; + + /* check options */ + while ((c = getopt(argc, argv, "nt:m:j")) != -1) { + switch (c) { + case 't': + case 'm': { + uint64_t arg; + char *endp; + + errno = 0; + arg = strtoull(optarg, &endp, 0); + if (errno != 0 || *endp != '\0') { + (void) fprintf(stderr, gettext( + "invalid argument " + "'%s': expected integer\n"), optarg); + goto usage; + } + + if (c == 't') { + instrlimit = arg; + } else { + ASSERT3U(c, ==, 'm'); + memlimit = arg; + } + break; + } + case 'n': { + sync_flag = B_FALSE; + break; + } + case 'j': { + json_output = B_TRUE; + break; + } + case '?': + (void) fprintf(stderr, gettext("invalid option '%c'\n"), + optopt); + goto usage; + } + } + + argc -= optind; + argv += optind; + + if (argc < 2) { + (void) fprintf(stderr, + gettext("invalid number of arguments\n")); + goto usage; + } + + poolname = argv[0]; + filename = argv[1]; + if (strcmp(filename, "-") == 0) { + fd = 0; + filename = "standard input"; + } else if ((fd = open(filename, O_RDONLY)) < 0) { + (void) fprintf(stderr, gettext("cannot open '%s': %s\n"), + filename, strerror(errno)); + return (1); + } + + if ((zhp = zpool_open(g_zfs, poolname)) == NULL) { + (void) fprintf(stderr, gettext("cannot open pool '%s'"), + poolname); + if (fd != 0) + (void) close(fd); + return (1); + } + zpool_close(zhp); + + /* + * Read in the channel program, expanding the program buffer as + * necessary. + */ + progread = 0; + progsize = 1024; + progbuf = safe_malloc(progsize); + do { + ret = read(fd, progbuf + progread, progsize - progread); + progread += ret; + if (progread == progsize && ret > 0) { + progsize *= 2; + progbuf = safe_realloc(progbuf, progsize); + } + } while (ret > 0); + + if (fd != 0) + (void) close(fd); + if (ret < 0) { + free(progbuf); + (void) fprintf(stderr, + gettext("cannot read '%s': %s\n"), + filename, strerror(errno)); + return (1); + } + progbuf[progread] = '\0'; + + /* + * Any remaining arguments are passed as arguments to the lua script as + * a string array: + * { + * "argv" -> [ "arg 1", ... "arg n" ], + * } + */ + nvlist_t *argnvl = fnvlist_alloc(); + fnvlist_add_string_array(argnvl, ZCP_ARG_CLIARGV, argv + 2, argc - 2); + + if (sync_flag) { + ret = lzc_channel_program(poolname, progbuf, + instrlimit, memlimit, argnvl, &outnvl); + } else { + ret = lzc_channel_program_nosync(poolname, progbuf, + instrlimit, memlimit, argnvl, &outnvl); + } + + if (ret != 0) { + /* + * On error, report the error message handed back by lua if one + * exists. Otherwise, generate an appropriate error message, + * falling back on strerror() for an unexpected return code. + */ + char *errstring = NULL; + const char *msg = gettext("Channel program execution failed"); + uint64_t instructions = 0; + if (outnvl != NULL && nvlist_exists(outnvl, ZCP_RET_ERROR)) { + (void) nvlist_lookup_string(outnvl, + ZCP_RET_ERROR, &errstring); + if (errstring == NULL) + errstring = strerror(ret); + if (ret == ETIME) { + (void) nvlist_lookup_uint64(outnvl, + ZCP_ARG_INSTRLIMIT, &instructions); + } + } else { + switch (ret) { + case EINVAL: + errstring = + "Invalid instruction or memory limit."; + break; + case ENOMEM: + errstring = "Return value too large."; + break; + case ENOSPC: + errstring = "Memory limit exhausted."; + break; + case ETIME: + errstring = "Timed out."; + break; + case EPERM: + errstring = "Permission denied. Channel " + "programs must be run as root."; + break; + default: + (void) zfs_standard_error(g_zfs, ret, msg); + } + } + if (errstring != NULL) + (void) fprintf(stderr, "%s:\n%s\n", msg, errstring); + + if (ret == ETIME && instructions != 0) + (void) fprintf(stderr, + gettext("%llu Lua instructions\n"), + (u_longlong_t)instructions); + } else { + if (json_output) { + (void) nvlist_print_json(stdout, outnvl); + } else if (nvlist_empty(outnvl)) { + (void) fprintf(stdout, gettext("Channel program fully " + "executed and did not produce output.\n")); + } else { + (void) fprintf(stdout, gettext("Channel program fully " + "executed and produced output:\n")); + dump_nvlist(outnvl, 4); + } + } + + free(progbuf); + fnvlist_free(outnvl); + fnvlist_free(argnvl); + return (ret != 0); + +usage: + usage(B_FALSE); + return (-1); +} + + +typedef struct loadkey_cbdata { + boolean_t cb_loadkey; + boolean_t cb_recursive; + boolean_t cb_noop; + char *cb_keylocation; + uint64_t cb_numfailed; + uint64_t cb_numattempted; +} loadkey_cbdata_t; + +static int +load_key_callback(zfs_handle_t *zhp, void *data) +{ + int ret; + boolean_t is_encroot; + loadkey_cbdata_t *cb = data; + uint64_t keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS); + + /* + * If we are working recursively, we want to skip loading / unloading + * keys for non-encryption roots and datasets whose keys are already + * in the desired end-state. + */ + if (cb->cb_recursive) { + ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL); + if (ret != 0) + return (ret); + if (!is_encroot) + return (0); + + if ((cb->cb_loadkey && keystatus == ZFS_KEYSTATUS_AVAILABLE) || + (!cb->cb_loadkey && keystatus == ZFS_KEYSTATUS_UNAVAILABLE)) + return (0); + } + + cb->cb_numattempted++; + + if (cb->cb_loadkey) + ret = zfs_crypto_load_key(zhp, cb->cb_noop, cb->cb_keylocation); + else + ret = zfs_crypto_unload_key(zhp); + + if (ret != 0) { + cb->cb_numfailed++; + return (ret); + } + + return (0); +} + +static int +load_unload_keys(int argc, char **argv, boolean_t loadkey) +{ + int c, ret = 0, flags = 0; + boolean_t do_all = B_FALSE; + loadkey_cbdata_t cb = { 0 }; + + cb.cb_loadkey = loadkey; + + while ((c = getopt(argc, argv, "anrL:")) != -1) { + /* noop and alternate keylocations only apply to zfs load-key */ + if (loadkey) { + switch (c) { + case 'n': + cb.cb_noop = B_TRUE; + continue; + case 'L': + cb.cb_keylocation = optarg; + continue; + default: + break; + } + } + + switch (c) { + case 'a': + do_all = B_TRUE; + cb.cb_recursive = B_TRUE; + break; + case 'r': + flags |= ZFS_ITER_RECURSE; + cb.cb_recursive = B_TRUE; + break; + default: + (void) fprintf(stderr, + gettext("invalid option '%c'\n"), optopt); + usage(B_FALSE); + } + } + + argc -= optind; + argv += optind; + + if (!do_all && argc == 0) { + (void) fprintf(stderr, + gettext("Missing dataset argument or -a option\n")); + usage(B_FALSE); + } + + if (do_all && argc != 0) { + (void) fprintf(stderr, + gettext("Cannot specify dataset with -a option\n")); + usage(B_FALSE); + } + + if (cb.cb_recursive && cb.cb_keylocation != NULL && + strcmp(cb.cb_keylocation, "prompt") != 0) { + (void) fprintf(stderr, gettext("alternate keylocation may only " + "be 'prompt' with -r or -a\n")); + usage(B_FALSE); + } + + ret = zfs_for_each(argc, argv, flags, + ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, NULL, NULL, 0, + load_key_callback, &cb); + + if (cb.cb_noop || (cb.cb_recursive && cb.cb_numattempted != 0)) { + (void) printf(gettext("%llu / %llu key(s) successfully %s\n"), + (u_longlong_t)(cb.cb_numattempted - cb.cb_numfailed), + (u_longlong_t)cb.cb_numattempted, + loadkey ? (cb.cb_noop ? "verified" : "loaded") : + "unloaded"); + } + + if (cb.cb_numfailed != 0) + ret = -1; + + return (ret); +} + +static int +zfs_do_load_key(int argc, char **argv) +{ + return (load_unload_keys(argc, argv, B_TRUE)); +} + + +static int +zfs_do_unload_key(int argc, char **argv) +{ + return (load_unload_keys(argc, argv, B_FALSE)); +} + +static int +zfs_do_change_key(int argc, char **argv) +{ + int c, ret; + uint64_t keystatus; + boolean_t loadkey = B_FALSE, inheritkey = B_FALSE; + zfs_handle_t *zhp = NULL; + nvlist_t *props = fnvlist_alloc(); + + while ((c = getopt(argc, argv, "lio:")) != -1) { + switch (c) { + case 'l': + loadkey = B_TRUE; + break; + case 'i': + inheritkey = B_TRUE; + break; + case 'o': + if (!parseprop(props, optarg)) { + nvlist_free(props); + return (1); + } + break; + default: + (void) fprintf(stderr, + gettext("invalid option '%c'\n"), optopt); + usage(B_FALSE); + } + } + + if (inheritkey && !nvlist_empty(props)) { + (void) fprintf(stderr, + gettext("Properties not allowed for inheriting\n")); + usage(B_FALSE); + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + (void) fprintf(stderr, gettext("Missing dataset argument\n")); + usage(B_FALSE); + } + + if (argc > 1) { + (void) fprintf(stderr, gettext("Too many arguments\n")); + usage(B_FALSE); + } + + zhp = zfs_open(g_zfs, argv[argc - 1], + ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); + if (zhp == NULL) + usage(B_FALSE); + + if (loadkey) { + keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS); + if (keystatus != ZFS_KEYSTATUS_AVAILABLE) { + ret = zfs_crypto_load_key(zhp, B_FALSE, NULL); + if (ret != 0) { + nvlist_free(props); + zfs_close(zhp); + return (-1); + } + } + + /* refresh the properties so the new keystatus is visible */ + zfs_refresh_properties(zhp); + } + + ret = zfs_crypto_rewrap(zhp, props, inheritkey); + if (ret != 0) { + nvlist_free(props); + zfs_close(zhp); + return (-1); + } + + nvlist_free(props); + zfs_close(zhp); + return (0); +} + +/* + * 1) zfs project [-d|-r] + * List project ID and inherit flag of file(s) or directories. + * -d: List the directory itself, not its children. + * -r: List subdirectories recursively. + * + * 2) zfs project -C [-k] [-r] + * Clear project inherit flag and/or ID on the file(s) or directories. + * -k: Keep the project ID unchanged. If not specified, the project ID + * will be reset as zero. + * -r: Clear on subdirectories recursively. + * + * 3) zfs project -c [-0] [-d|-r] [-p id] + * Check project ID and inherit flag on the file(s) or directories, + * report the outliers. + * -0: Print file name followed by a NUL instead of newline. + * -d: Check the directory itself, not its children. + * -p: Specify the referenced ID for comparing with the target file(s) + * or directories' project IDs. If not specified, the target (top) + * directory's project ID will be used as the referenced one. + * -r: Check subdirectories recursively. + * + * 4) zfs project [-p id] [-r] [-s] + * Set project ID and/or inherit flag on the file(s) or directories. + * -p: Set the project ID as the given id. + * -r: Set on subdirectorie recursively. If not specify "-p" option, + * it will use top-level directory's project ID as the given id, + * then set both project ID and inherit flag on all descendants + * of the top-level directory. + * -s: Set project inherit flag. + */ +static int +zfs_do_project(int argc, char **argv) +{ + zfs_project_control_t zpc = { + .zpc_expected_projid = ZFS_INVALID_PROJID, + .zpc_op = ZFS_PROJECT_OP_DEFAULT, + .zpc_dironly = B_FALSE, + .zpc_keep_projid = B_FALSE, + .zpc_newline = B_TRUE, + .zpc_recursive = B_FALSE, + .zpc_set_flag = B_FALSE, + }; + int ret = 0, c; + + if (argc < 2) + usage(B_FALSE); + + while ((c = getopt(argc, argv, "0Ccdkp:rs")) != -1) { + switch (c) { + case '0': + zpc.zpc_newline = B_FALSE; + break; + case 'C': + if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) { + (void) fprintf(stderr, gettext("cannot " + "specify '-C' '-c' '-s' together\n")); + usage(B_FALSE); + } + + zpc.zpc_op = ZFS_PROJECT_OP_CLEAR; + break; + case 'c': + if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) { + (void) fprintf(stderr, gettext("cannot " + "specify '-C' '-c' '-s' together\n")); + usage(B_FALSE); + } + + zpc.zpc_op = ZFS_PROJECT_OP_CHECK; + break; + case 'd': + zpc.zpc_dironly = B_TRUE; + /* overwrite "-r" option */ + zpc.zpc_recursive = B_FALSE; + break; + case 'k': + zpc.zpc_keep_projid = B_TRUE; + break; + case 'p': { + char *endptr; + + errno = 0; + zpc.zpc_expected_projid = strtoull(optarg, &endptr, 0); + if (errno != 0 || *endptr != '\0') { + (void) fprintf(stderr, + gettext("project ID must be less than " + "%u\n"), UINT32_MAX); + usage(B_FALSE); + } + if (zpc.zpc_expected_projid >= UINT32_MAX) { + (void) fprintf(stderr, + gettext("invalid project ID\n")); + usage(B_FALSE); + } + break; + } + case 'r': + zpc.zpc_recursive = B_TRUE; + /* overwrite "-d" option */ + zpc.zpc_dironly = B_FALSE; + break; + case 's': + if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) { + (void) fprintf(stderr, gettext("cannot " + "specify '-C' '-c' '-s' together\n")); + usage(B_FALSE); + } + + zpc.zpc_set_flag = B_TRUE; + zpc.zpc_op = ZFS_PROJECT_OP_SET; + break; + default: + (void) fprintf(stderr, gettext("invalid option '%c'\n"), + optopt); + usage(B_FALSE); + } + } + + if (zpc.zpc_op == ZFS_PROJECT_OP_DEFAULT) { + if (zpc.zpc_expected_projid != ZFS_INVALID_PROJID) + zpc.zpc_op = ZFS_PROJECT_OP_SET; + else + zpc.zpc_op = ZFS_PROJECT_OP_LIST; + } + + switch (zpc.zpc_op) { + case ZFS_PROJECT_OP_LIST: + if (zpc.zpc_keep_projid) { + (void) fprintf(stderr, + gettext("'-k' is only valid together with '-C'\n")); + usage(B_FALSE); + } + if (!zpc.zpc_newline) { + (void) fprintf(stderr, + gettext("'-0' is only valid together with '-c'\n")); + usage(B_FALSE); + } + break; + case ZFS_PROJECT_OP_CHECK: + if (zpc.zpc_keep_projid) { + (void) fprintf(stderr, + gettext("'-k' is only valid together with '-C'\n")); + usage(B_FALSE); + } + break; + case ZFS_PROJECT_OP_CLEAR: + if (zpc.zpc_dironly) { + (void) fprintf(stderr, + gettext("'-d' is useless together with '-C'\n")); + usage(B_FALSE); + } + if (!zpc.zpc_newline) { + (void) fprintf(stderr, + gettext("'-0' is only valid together with '-c'\n")); + usage(B_FALSE); + } + if (zpc.zpc_expected_projid != ZFS_INVALID_PROJID) { + (void) fprintf(stderr, + gettext("'-p' is useless together with '-C'\n")); + usage(B_FALSE); + } + break; + case ZFS_PROJECT_OP_SET: + if (zpc.zpc_dironly) { + (void) fprintf(stderr, + gettext("'-d' is useless for set project ID and/or " + "inherit flag\n")); + usage(B_FALSE); + } + if (zpc.zpc_keep_projid) { + (void) fprintf(stderr, + gettext("'-k' is only valid together with '-C'\n")); + usage(B_FALSE); + } + if (!zpc.zpc_newline) { + (void) fprintf(stderr, + gettext("'-0' is only valid together with '-c'\n")); + usage(B_FALSE); + } + break; + default: + ASSERT(0); + break; + } + + argv += optind; + argc -= optind; + if (argc == 0) { + (void) fprintf(stderr, + gettext("missing file or directory target(s)\n")); + usage(B_FALSE); + } + + for (int i = 0; i < argc; i++) { + int err; + + err = zfs_project_handle(argv[i], &zpc); + if (err && !ret) + ret = err; + } + + return (ret); +} + int main(int argc, char **argv) { int ret = 0; int i = 0; char *cmdname; + char **newargv; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); - dprintf_setup(&argc, argv); - opterr = 0; /* @@ -6834,6 +8053,13 @@ main(int argc, char **argv) if (strcmp(cmdname, "snap") == 0) cmdname = "snapshot"; + /* + * The 'remap' command has been disabled and may be removed in the + * future. See the comment above zfs_do_remap() for details. + */ + if (!libzfs_envvar_is_set("ZFS_REMAP_ENABLED")) + disable_command_idx("remap"); + /* * Special case '-?' */ @@ -6852,17 +8078,26 @@ main(int argc, char **argv) libzfs_print_on_error(g_zfs, B_TRUE); + /* + * Many commands modify input strings for string parsing reasons. + * We create a copy to protect the original argv. + */ + newargv = malloc((argc + 1) * sizeof (newargv[0])); + for (i = 0; i < argc; i++) + newargv[i] = strdup(argv[i]); + newargv[argc] = NULL; + /* * Run the appropriate command. */ libzfs_mnttab_cache(g_zfs, B_TRUE); if (find_command_idx(cmdname, &i) == 0) { current_command = &command_table[i]; - ret = command_table[i].func(argc - 1, argv + 1); + ret = command_table[i].func(argc - 1, newargv + 1); } else if (strchr(cmdname, '=') != NULL) { verify(find_command_idx("set", &i) == 0); current_command = &command_table[i]; - ret = command_table[i].func(argc, argv); + ret = command_table[i].func(argc, newargv); } else { (void) fprintf(stderr, gettext("unrecognized " "command '%s'\n"), cmdname); @@ -6870,6 +8105,10 @@ main(int argc, char **argv) ret = 1; } + for (i = 0; i < argc; i++) + free(newargv[i]); + free(newargv); + if (ret == 0 && log_history) (void) zpool_log_history(g_zfs, history_str);