zfs_main.c revision a8b6ddaf31808c845e00161dda0a3d1fe31ae281
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <libgen.h>
#include <libintl.h>
#include <libuutil.h>
#include <libnvpair.h>
#include <locale.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <zone.h>
#include <grp.h>
#include <pwd.h>
#include <time.h>
#include <libzfs.h>
#include <libuutil.h>
#include "zfs_iter.h"
#include "zfs_util.h"
#include "zfs_comutil.h"
static FILE *mnttab_file;
static char history_str[HIS_MAX_RECORD_LEN];
/*
* Enable a reasonable set of defaults for libumem debugging on DEBUG builds.
*/
#ifdef DEBUG
const char *
_umem_debug_init(void)
{
return ("default,verbose"); /* $UMEM_DEBUG setting */
}
const char *
_umem_logging_init(void)
{
return ("fail,contents"); /* $UMEM_LOGGING setting */
}
#endif
typedef enum {
} zfs_help_t;
typedef struct zfs_command {
const char *name;
/*
* Master command table. Each ZFS command has a name, associated function, and
* usage message. The usage messages need to be internationalized, so we have
* to have a function to return the usage message based on a command index.
*
* These commands are organized according to how they are displayed in the usage
* message. An empty command (one with a NULL name) indicates an empty line in
* the generic usage message.
*/
static zfs_command_t command_table[] = {
{ NULL },
{ NULL },
{ NULL },
{ NULL },
{ NULL },
{ NULL },
{ NULL },
{ NULL },
};
static const char *
{
switch (idx) {
case HELP_CLONE:
return (gettext("\tclone [-p] [-o property=value] ... "
"<snapshot> <filesystem|volume>\n"));
case HELP_CREATE:
return (gettext("\tcreate [-p] [-o property=value] ... "
"<filesystem>\n"
"\tcreate [-ps] [-b blocksize] [-o property=value] ... "
"-V <size> <volume>\n"));
case HELP_DESTROY:
return (gettext("\tdestroy [-rRf] <filesystem|volume>\n"
"\tdestroy [-rRd] <snapshot>\n"));
case HELP_GET:
return (gettext("\tget [-rHp] [-d max] "
"[-o \"all\" | field[,...]] [-s source[,...]]\n"
"\t <\"all\" | property[,...]> "
"[filesystem|volume|snapshot] ...\n"));
case HELP_INHERIT:
return (gettext("\tinherit [-rS] <property> "
"<filesystem|volume|snapshot> ...\n"));
case HELP_UPGRADE:
return (gettext("\tupgrade [-v]\n"
"\tupgrade [-r] [-V version] <-a | filesystem ...>\n"));
case HELP_LIST:
return (gettext("\tlist [-rH][-d max] "
"[-o property[,...]] [-t type[,...]] [-s property] ...\n"
"\t [-S property] ... "
"[filesystem|volume|snapshot] ...\n"));
case HELP_MOUNT:
return (gettext("\tmount\n"
"\tmount [-vO] [-o opts] <-a | filesystem>\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"));
case HELP_RENAME:
return (gettext("\trename <filesystem|volume|snapshot> "
"<filesystem|volume|snapshot>\n"
"\trename -p <filesystem|volume> <filesystem|volume>\n"
"\trename -r <snapshot> <snapshot>"));
case HELP_ROLLBACK:
return (gettext("\trollback [-rRf] <snapshot>\n"));
case HELP_SEND:
return (gettext("\tsend [-RDp] [-[iI] snapshot] <snapshot>\n"));
case HELP_SET:
return (gettext("\tset <property=value> "
"<filesystem|volume|snapshot> ...\n"));
case HELP_SHARE:
return (gettext("\tshare <-a | filesystem>\n"));
case HELP_SNAPSHOT:
return (gettext("\tsnapshot [-r] [-o property=value] ... "
"<filesystem@snapname|volume@snapname>\n"));
case HELP_UNMOUNT:
return (gettext("\tunmount [-f] "
"<-a | filesystem|mountpoint>\n"));
case HELP_UNSHARE:
return (gettext("\tunshare "
"<-a | filesystem|mountpoint>\n"));
case HELP_ALLOW:
return (gettext("\tallow <filesystem|volume>\n"
"\tallow [-ldug] "
"<\"everyone\"|user|group>[,...] <perm|@setname>[,...]\n"
"\t <filesystem|volume>\n"
"\tallow [-ld] -e <perm|@setname>[,...] "
"<filesystem|volume>\n"
"\tallow -c <perm|@setname>[,...] <filesystem|volume>\n"
"\tallow -s @setname <perm|@setname>[,...] "
"<filesystem|volume>\n"));
case HELP_UNALLOW:
return (gettext("\tunallow [-rldug] "
"<\"everyone\"|user|group>[,...]\n"
"\t [<perm|@setname>[,...]] <filesystem|volume>\n"
"\tunallow [-rld] -e [<perm|@setname>[,...]] "
"<filesystem|volume>\n"
"\tunallow [-r] -c [<perm|@setname>[,...]] "
"<filesystem|volume>\n"
"\tunallow [-r] -s @setname [<perm|@setname>[,...]] "
"<filesystem|volume>\n"));
case HELP_USERSPACE:
return (gettext("\tuserspace [-hniHp] [-o field[,...]] "
"[-sS field] ... [-t type[,...]]\n"
"\t <filesystem|snapshot>\n"));
case HELP_GROUPSPACE:
return (gettext("\tgroupspace [-hniHpU] [-o field[,...]] "
"[-sS field] ... [-t type[,...]]\n"
"\t <filesystem|snapshot>\n"));
case HELP_HOLD:
return (gettext("\thold [-r] <tag> <snapshot> ...\n"));
case HELP_HOLDS:
return (gettext("\tholds [-r] <snapshot> ...\n"));
case HELP_RELEASE:
return (gettext("\trelease [-r] <tag> <snapshot> ...\n"));
}
abort();
/* NOTREACHED */
}
void
nomem(void)
{
exit(1);
}
/*
* Utility function to guarantee malloc() success.
*/
void *
{
void *data;
nomem();
return (data);
}
static char *
safe_strdup(char *str)
{
nomem();
return (dupstr);
}
/*
* Callback routine that will print out information for each of
* the properties.
*/
static int
{
if (zfs_prop_readonly(prop))
else
if (zfs_prop_inheritable(prop))
else
else
return (ZPROP_CONT);
}
/*
* Display usage message. If we're inside a command, display only the usage for
* that command. Otherwise, iterate over the entire command table and display
* a complete usage message.
*/
static void
{
int i;
if (current_command == NULL) {
gettext("where 'command' is one of the following:\n\n"));
for (i = 0; i < NCOMMAND; i++) {
else
}
"pool/[dataset/]*dataset[@name]\n"));
} else {
}
if (current_command != NULL &&
if (show_properties) {
gettext("\nThe following properties are supported:\n"));
"PROPERTY", "EDIT", "INHERIT", "VALUES");
/* Iterate over all properties */
"with standard units such as K, M, G, etc.\n"));
"be specified by using a name containing a colon (:).\n"));
"properties must be appended with\n"
"a user or group specifier of one of these forms:\n"
" POSIX name (eg: \"matt\")\n"
" POSIX id (eg: \"126829\")\n"
" SMB name@domain (eg: \"matt@sun\")\n"
" SMB SID (eg: \"S-1-234-567-89\")\n"));
} else {
gettext("\nFor the property list, run: %s\n"),
"zfs set|get");
gettext("\nFor the delegated permission list, run: %s\n"),
"zfs allow|unallow");
}
/*
* See comments at end of main().
*/
(void) printf("dumping core by request\n");
abort();
}
}
static int
{
"'=' for -o option\n"));
return (-1);
}
*propval = '\0';
propval++;
"specified multiple times\n"), propname);
return (-1);
}
nomem();
return (0);
}
static int
{
char *tmp;
int depth;
if (*tmp) {
}
if (depth < 0) {
gettext("Depth can not be negative.\n"));
}
return (depth);
}
static char *pt_reverse = "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
static void
start_progress_timer(void)
{
}
static void
set_progress_header(char *header)
{
if (pt_shown) {
}
}
static void
update_progress(char *update)
{
} else if (pt_shown) {
}
}
static void
finish_progress(char *done)
{
if (pt_shown) {
}
}
/*
* zfs clone [-p] [-o prop=value] ... <snap> <fs | vol>
*
* Given an existing dataset, create a writable copy whose initial contents
* are the same as the source. The newly created dataset maintains a
* dependency on the original; the original cannot be destroyed so long as
* the clone exists.
*
* The '-p' flag creates all the non-existing ancestors of the target first.
*/
static int
{
int ret;
int c;
nomem();
/* check options */
switch (c) {
case 'o':
return (1);
break;
case 'p':
break;
case '?':
optopt);
goto usage;
}
}
/* check number of arguments */
if (argc < 1) {
"argument\n"));
goto usage;
}
if (argc < 2) {
"argument\n"));
goto usage;
}
if (argc > 2) {
goto usage;
}
/* open the source dataset */
return (1);
ZFS_TYPE_VOLUME)) {
/*
* Now create the ancestors of the target dataset. If the
* target already exists and '-p' option was used we should not
* complain.
*/
return (0);
return (1);
}
/* pass to libzfs */
/* create the mountpoint if necessary */
if (ret == 0) {
}
}
return (!!ret);
if (zhp)
return (-1);
}
/*
* zfs create [-p] [-o prop=value] ... fs
* zfs create [-ps] [-b blocksize] [-o prop=value] ... -V vol size
*
* Create a new dataset. This command can be used to create filesystems
* and volumes. Snapshot creation is handled by 'zfs snapshot'.
* For volumes, the user must specify a size to be used.
*
* The '-s' flag applies only to volumes, and indicates that we should not try
* to set the reservation for this volume. By default we set a reservation
* equal to the size for any volume. For pools with SPA_VERSION >=
* SPA_VERSION_REFRESERVATION, we set a refreservation instead.
*
* The '-p' flag creates all the non-existing ancestors of the target first.
*/
static int
{
int c;
int ret = 1;
int canmount;
nomem();
/* check options */
switch (c) {
case 'V':
"size '%s': %s\n"), optarg,
goto error;
}
if (nvlist_add_uint64(props,
nomem();
break;
case 'p':
break;
case 'b':
"block size '%s': %s\n"), optarg,
goto error;
}
if (nvlist_add_uint64(props,
intval) != 0)
nomem();
break;
case 'o':
goto error;
break;
case 's':
break;
case ':':
"argument\n"));
goto badusage;
break;
case '?':
optopt);
goto badusage;
}
}
"used when creating a volume\n"));
goto badusage;
}
/* check number of arguments */
if (argc == 0) {
goto badusage;
}
if (argc > 1) {
goto badusage;
}
char *p;
char *strval;
*p = '\0';
if (p != NULL)
*p = '/';
if (zpool_handle == NULL)
goto error;
else
&strval) != 0) {
if (nvlist_add_uint64(props,
nomem();
}
}
}
/*
* Now create the ancestors of target dataset. If the target
* already exists and '-p' option was used we should not
* complain.
*/
ret = 0;
goto error;
}
goto error;
}
/* pass to libzfs */
goto error;
goto error;
/*
* if the user doesn't want the dataset automatically mounted,
*/
/*
* verbose error message to let the user know that their filesystem was
* in fact created, even if we failed to mount or share it.
*/
ret = 0;
if (canmount == ZFS_CANMOUNT_ON) {
"successfully created, but not mounted\n"));
ret = 1;
"successfully created, but not shared\n"));
ret = 1;
}
}
if (zhp)
return (ret);
return (2);
}
/*
* zfs destroy [-rRf] <fs, vol>
* zfs destroy [-rRd] <snap>
*
* -r Recursively destroy all children
* -R Recursively destroy all dependents, including clones
* -f Force unmounting of any dependents
* -d If we can't destroy now, mark for deferred destruction
*
* Destroys the given dataset. By default, it will unmount any filesystems,
* and refuse to destroy a dataset that has any dependents. A dependent can
* either be a child, or a clone of a child.
*/
typedef struct destroy_cbdata {
int cb_force;
int cb_recurse;
int cb_error;
int cb_needforce;
int cb_doclones;
char *cb_snapname;
/*
* Check for any dependents based on the '-r' or '-R' flags.
*/
static int
{
/*
* This is a direct descendant, not a clone somewhere else in
* the hierarchy.
*/
if (cbp->cb_recurse)
goto out;
"%s has children\n"),
"the following datasets:\n"));
}
} else {
/*
* This is a clone. We only want to report this if the '-r'
* wasn't specified, or the target is a snapshot.
*/
if (!cbp->cb_recurse &&
goto out;
"%s has dependent clones\n"),
"the following datasets:\n"));
}
}
out:
return (0);
}
static int
{
/*
* Ignore pools (which we've already flagged as an error before getting
* here).
*/
return (0);
}
/*
* Bail out on the first error.
*/
return (-1);
}
return (0);
}
static int
{
char thissnap[MAXPATHLEN];
int rv;
if (szhp) {
/*
* Destroy any clones of this snapshot
*/
cbp) != 0) {
if (closezhp)
return (-1);
}
}
if (closezhp)
return (rv);
}
static int
{
destroy_cbdata_t cb = { 0 };
int c;
char *cp;
/* check options */
switch (c) {
case 'd':
break;
case 'f':
break;
case 'r':
break;
case 'R':
break;
case '?':
default:
optopt);
}
}
/* check number of arguments */
if (argc == 0) {
}
if (argc > 1) {
}
/*
* If we are doing recursive destroy of a snapshot, then the
* named snapshot may not exist. Go straight to libzfs.
*/
int ret;
*cp = '\0';
return (1);
*cp = '@';
cp++;
if (cb.cb_doclones) {
/*
* Temporarily ignore the defer_destroy setting since
* it's not supported for clones.
*/
return (1);
}
}
if (ret) {
gettext("no snapshots destroyed\n"));
}
return (ret != 0);
}
/* Open the given dataset */
return (1);
/*
* Perform an explicit check for pools before going any further.
*/
"operation does not apply to pools\n"),
zfs_get_name(zhp));
"%s' to destroy all datasets in the pool\n"),
zfs_get_name(zhp));
return (1);
}
/*
*/
&cb) != 0) {
return (1);
}
return (1);
}
/*
* Do the real thing. The callback will close the handle regardless of
* whether it succeeds or not.
*/
return (1);
return (0);
}
static boolean_t
{
int i;
for (i = 0; i < ZFS_GET_NCOLS &&
if (col == GET_COL_RECVD)
return (B_TRUE);
return (B_FALSE);
}
/*
* zfs get [-rHp] [-o all | field[,field]...] [-s source[,source]...]
* < all | property[,property]... > < fs | snap | vol > ...
*
* -r recurse over any child datasets
* -H scripted mode. Headers are stripped, and fields are separated
* by tabs instead of spaces.
* -o Set of fields to display. One of "name,property,value,
* received,source". Default is "name,property,value,source".
* "all" is an alias for all five.
* -s Set of sources to allow. One of
* "local,default,inherited,received,temporary,none". Default is
* all six.
* -p Display values in parsable (literal) format.
*
* Prints properties for the given datasets. The user can control which
* columns to display as well as which property types to allow.
*/
/*
* Invoked to display the properties for a single dataset.
*/
static int
{
char buf[ZFS_MAXPROPLEN];
char rbuf[ZFS_MAXPROPLEN];
char source[ZFS_MAXNAMELEN];
char *strval;
char *sourceval;
/*
* Skip the special fake placeholder. This will also skip over
* the name property when 'all' is specified.
*/
continue;
sizeof (source),
cbp->cb_literal) != 0) {
continue;
ZFS_TYPE_DATASET)) {
gettext("No such property '%s'\n"),
continue;
}
}
cbp->cb_literal) == 0))
}
} else {
continue;
strval = "-";
} else {
ZPROP_VALUE, &strval) == 0);
ZPROP_SOURCE, &sourceval) == 0);
zfs_get_name(zhp)) == 0) {
ZPROP_SOURCE_VAL_RECVD) == 0) {
} else {
}
}
cbp->cb_literal) == 0))
}
}
return (0);
}
static int
{
zprop_get_cbdata_t cb = { 0 };
int i, c, flags = 0;
int ret;
int limit = 0;
zprop_list_t fake_name = { 0 };
/*
* Set up default columns and sources.
*/
/* check options */
switch (c) {
case 'p':
break;
case 'd':
break;
case 'r':
break;
case 'H':
break;
case ':':
"'%c' option\n"), optopt);
break;
case 'o':
/*
* Process the set of columns to display. We zero out
* the structure to give us a blank slate.
*/
i = 0;
while (*optarg != '\0') {
static char *col_subopts[] =
{ "name", "property", "value", "received",
if (i == ZFS_GET_NCOLS) {
"many fields given to -o "
"option\n"));
}
&value)) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
if (i > 0) {
gettext("\"all\" conflicts "
"with specific fields "
"given to -o option\n"));
}
i = ZFS_GET_NCOLS;
break;
default:
gettext("invalid column name "
"'%s'\n"), value);
}
}
break;
case 's':
cb.cb_sources = 0;
while (*optarg != '\0') {
static char *source_subopts[] = {
"local", "default", "inherited",
"received", "temporary", "none",
NULL };
&value)) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
default:
gettext("invalid source "
"'%s'\n"), value);
}
}
break;
case '?':
optopt);
}
}
if (argc < 1) {
"argument\n"));
}
!= 0)
argc--;
argv++;
/*
* As part of zfs_expand_proplist(), we keep track of the maximum column
* width for each property. For the 'NAME' (and 'SOURCE') columns, we
* need to know the maximum name length. However, the user likely did
* not specify 'name' as one of the properties to fetch, so we need to
* make sure we always include at least this property for
* print_get_headers() to work properly.
*/
}
/* run for each object */
else
return (ret);
}
/*
* inherit [-rS] <property> <fs|vol> ...
*
* -r Recurse over all children
* -S Revert to received value, if any
*
* For each dataset specified on the command line, inherit the given property
* from its parent. Inheriting a property at the pool level will cause it to
* use the default value. The '-r' flag will recurse over all children, and is
* useful for setting a property on a hierarchy-wide basis, regardless of any
* local modifications for each dataset.
*/
typedef struct inherit_cbdata {
const char *cb_propname;
static int
{
/*
* If we're doing it recursively, then ignore properties that
* are not valid for this type of dataset.
*/
if (prop != ZPROP_INVAL &&
return (0);
}
static int
{
}
static int
{
int c;
inherit_cbdata_t cb = { 0 };
char *propname;
int ret;
int flags = 0;
/* check options */
switch (c) {
case 'r':
break;
case 'S':
break;
case '?':
default:
optopt);
}
}
/* check number of arguments */
if (argc < 1) {
}
if (argc < 2) {
}
argc--;
argv++;
if (zfs_prop_readonly(prop)) {
"%s property is read-only\n"),
propname);
return (1);
}
"be inherited\n"), propname);
if (prop == ZFS_PROP_QUOTA ||
prop == ZFS_PROP_RESERVATION ||
prop == ZFS_PROP_REFQUOTA ||
"%s=none' to clear\n"), propname);
return (1);
}
prop == ZFS_PROP_VERSION)) {
"be reverted to a received value\n"), propname);
return (1);
}
} else if (!zfs_prop_user(propname)) {
propname);
}
if (flags & ZFS_ITER_RECURSE) {
} else {
}
return (ret);
}
typedef struct upgrade_cbdata {
char cb_lastfs[ZFS_MAXNAMELEN];
static int
{
return (B_FALSE);
}
static int
{
char *str;
"formatted using a newer software version and\n"
"cannot be accessed on the current system.\n\n");
} else {
"out of date, and can be upgraded. After being\n"
"upgraded, these filesystems (and any 'zfs send' "
"streams generated from\n"
"subsequent snapshots) will no longer be "
"accessible by older software versions.\n\n");
}
if (!cb->cb_foundone) {
}
}
return (0);
}
static int
{
int needed_spa_version;
int spa_version;
return (-1);
if (needed_spa_version < 0)
return (-1);
if (spa_version < needed_spa_version) {
/* can't upgrade */
"upgraded; the pool version needs to first "
"be upgraded\nto version %d\n\n"),
cb->cb_numfailed++;
return (0);
}
/* upgrade */
char verstr[16];
/*
* If they did "zfs upgrade -a", then we could
* be doing ioctls to different pools. We need
* to log this history once to each pool.
*/
}
cb->cb_numupgraded++;
else
cb->cb_numfailed++;
/* can't downgrade */
"it is already at version %u\n"),
cb->cb_numfailed++;
} else {
cb->cb_numsamegraded++;
}
return (0);
}
/*
* zfs upgrade
* zfs upgrade -v
* zfs upgrade [-r] [-V <version>] <-a | filesystem>
*/
static int
{
int ret;
upgrade_cbdata_t cb = { 0 };
char c;
int flags = ZFS_ITER_ARGS_CAN_BE_PATHS;
/* check options */
switch (c) {
case 'r':
break;
case 'v':
break;
case 'V':
}
break;
case 'a':
break;
case '?':
default:
optopt);
}
}
if (showversions) {
/* Show info on available versions. */
"supported:\n\n"));
(void) printf("--- -----------------------------------------"
"---------------\n");
"unique identifier (FUID)\n"));
"properties\n"));
"version, including supported releases,\n"));
(void) printf("see the ZFS Administration Guide.\n\n");
ret = 0;
/* Upgrade filesystems */
if (cb.cb_version == 0)
if (cb.cb_numsamegraded) {
"this version\n"),
}
if (cb.cb_numfailed != 0)
ret = 1;
} else {
/* List old-version filesytems */
"ZFS filesystem version %llu.\n\n"), ZPL_VERSION);
"formatted with the current version.\n"));
}
}
return (ret);
}
/*
* zfs userspace
*/
static int
{
zfs_userquota_prop_t p = *typep;
char namebuf[32];
char sizebuf[32];
if (p == ZFS_PROP_GROUPUSED || p == ZFS_PROP_GROUPQUOTA) {
if (g)
} else {
if (p)
}
}
if (p == ZFS_PROP_GROUPUSED || p == ZFS_PROP_GROUPQUOTA)
ug = "group";
else
ug = "user";
if (p == ZFS_PROP_USERUSED || p == ZFS_PROP_GROUPUSED)
propname = "used";
else
propname = "quota";
}
return (0);
}
static int
{
int error;
/*
* Try the python version. If the execv fails, we'll continue
* and do a simplistic implementation.
*/
(void) printf("internal error: %s not found\n"
"falling back on built-in implementation, "
"some features will not work\n", pypath);
return (1);
(void) printf("PROP TYPE NAME VALUE\n");
for (p = 0; p < ZFS_NUM_USERQUOTA_PROPS; p++) {
if (error)
break;
}
return (error);
}
/*
* list [-r][-d max] [-H] [-o property[,property]...] [-t type[,type]...]
* [-s property [-s property]...] [-S property [-S property]...]
* <dataset> ...
*
* -r Recurse over all children
* -d Limit recursion by depth.
* -H Scripted mode; elide headers and separate columns by tabs
* -o Control which fields to display.
* -t Control which object types to display.
* -s Specify sort columns, descending order.
* -S Specify sort columns, ascending order.
*
* When given no arguments, lists all filesystems in the system.
* Otherwise, list the specified datasets, optionally recursing down them if
* '-r' is specified.
*/
typedef struct list_cbdata {
/*
* Given a list of columns to display, output appropriate headers for each one.
*/
static void
{
char headerbuf[ZFS_MAXPROPLEN];
const char *header;
int i;
if (!first) {
(void) printf(" ");
} else {
}
} else {
headerbuf[i] = '\0';
}
else if (right_justify)
else
}
(void) printf("\n");
}
/*
* Given a dataset and a list of fields, print out all the properties according
* to the described layout.
*/
static void
{
char property[ZFS_MAXPROPLEN];
char *propstr;
int width;
if (!first) {
if (scripted)
(void) printf("\t");
else
(void) printf(" ");
} else {
}
propstr = "-";
else
propstr = "-";
else
} else {
propstr = "-";
else
ZPROP_VALUE, &propstr) == 0);
}
/*
* If this is being called in scripted mode, or if this is the
* last column and it is left-justified, don't include a width
* format specifier.
*/
else if (right_justify)
else
}
(void) printf("\n");
}
/*
* Generic callback function to list a dataset or snapshot.
*/
static int
{
if (!cbp->cb_scripted)
}
return (0);
}
static int
{
int c;
static char default_fields[] =
"name,used,available,referenced,mountpoint";
int types = ZFS_TYPE_DATASET;
list_cbdata_t cb = { 0 };
char *value;
int limit = 0;
int ret;
/* check options */
switch (c) {
case 'o':
break;
case 'd':
break;
case 'r':
break;
case 'H':
break;
case 's':
B_FALSE) != 0) {
}
break;
case 'S':
B_TRUE) != 0) {
}
break;
case 't':
types = 0;
while (*optarg != '\0') {
static char *type_subopts[] = { "filesystem",
&value)) {
case 0:
break;
case 1:
types |= ZFS_TYPE_VOLUME;
break;
case 2:
break;
case 3:
break;
default:
gettext("invalid type '%s'\n"),
value);
}
}
break;
case ':':
"'%c' option\n"), optopt);
break;
case '?':
optopt);
}
}
/*
* If "-o space" and no types were specified, don't display snapshots.
*/
types &= ~ZFS_TYPE_SNAPSHOT;
/*
* If the user specifies '-o all', the zprop_get_list() doesn't
* normally include the name of the dataset. For 'zfs list', we always
* want this property to be first.
*/
!= 0)
return (ret);
}
/*
* zfs rename <fs | snap | vol> <fs | snap | vol>
* zfs rename -p <fs | vol> <fs | vol>
* zfs rename -r <snap> <snap>
*
* Renames the given dataset to another of the same type.
*
* The '-p' flag creates all the non-existing ancestors of the target first.
*/
/* ARGSUSED */
static int
{
int c;
int ret;
/* check options */
switch (c) {
case 'p':
break;
case 'r':
break;
case '?':
default:
optopt);
}
}
/* check number of arguments */
if (argc < 1) {
"argument\n"));
}
if (argc < 2) {
"argument\n"));
}
if (argc > 2) {
}
"exclusive\n"));
}
"rename must be a snapshot\n"));
}
return (1);
/* If we were asked and the name looks good, try to create ancestors. */
return (1);
}
return (ret);
}
/*
* zfs promote <fs>
*
* Promotes the given clone fs to be the parent
*/
/* ARGSUSED */
static int
{
int ret;
/* check options */
}
/* check number of arguments */
if (argc < 2) {
" argument\n"));
}
if (argc > 2) {
}
return (1);
return (ret);
}
/*
* zfs rollback [-rRf] <snapshot>
*
* -r Delete any intervening snapshots before doing rollback
* -R Delete any snapshots and their clones
* -f ignored for backwards compatability
*
* Given a filesystem, rollback to a specific snapshot, discarding any changes
* since then and making it the active dataset. If more recent snapshots exist,
* the command will complain unless the '-r' flag is given.
*/
typedef struct rollback_cbdata {
int cb_doclones;
char *cb_target;
int cb_error;
/*
* Report any snapshots more recent than the one specified. Used when '-r' is
* not specified. We reuse this same callback for the snapshot dependents - if
* 'cb_dependent' is set, then this is a dependent and we should report it
* without checking the transaction group.
*/
static int
{
if (cbp->cb_doclones) {
return (0);
}
if (!cbp->cb_dependent) {
"rollback to '%s': more recent snapshots "
"exist\n"),
"force deletion of the following "
"snapshots:\n"));
}
if (cbp->cb_recurse) {
rollback_check, cbp) != 0) {
return (-1);
}
} else {
zfs_get_name(zhp));
}
}
} else {
"'%s': clones of previous snapshots exist\n"),
"force deletion of the following clones and "
"dependents:\n"));
}
}
return (0);
}
static int
{
int ret;
int c;
rollback_cbdata_t cb = { 0 };
char parentname[ZFS_MAXNAMELEN];
char *delim;
/* check options */
switch (c) {
case 'r':
break;
case 'R':
break;
case 'f':
break;
case '?':
optopt);
}
}
/* check number of arguments */
if (argc < 1) {
}
if (argc > 1) {
}
/* open the snapshot */
return (1);
/* open the parent dataset */
*delim = '\0';
return (1);
}
/*
* of '-r' and '-R'.
*/
goto out;
goto out;
/*
* Rollback parent to the given snapshot.
*/
out:
if (ret == 0)
return (0);
else
return (1);
}
/*
* zfs set property=value { fs | snap | vol } ...
*
* Sets the given property for all datasets specified on the command line.
*/
typedef struct set_cbdata {
char *cb_propname;
char *cb_value;
} set_cbdata_t;
static int
{
switch (libzfs_errno(g_zfs)) {
case EZFS_MOUNTFAILED:
"but unable to remount filesystem\n"));
break;
case EZFS_SHARENFSFAILED:
"but unable to reshare filesystem\n"));
break;
}
return (1);
}
return (0);
}
static int
{
int ret;
/* check for options */
}
/* check number of arguments */
if (argc < 2) {
"argument\n"));
}
if (argc < 3) {
}
/* validate property=value argument */
"property=value argument\n"));
}
gettext("missing property in property=value argument\n"));
}
return (ret);
}
/*
* zfs snapshot [-r] [-o prop=value] ... <fs@snap>
*
* Creates a snapshot with the given name. While functionally equivalent to
* 'zfs create', it is a separate command to differentiate intent.
*/
static int
{
int ret;
char c;
nomem();
/* check options */
switch (c) {
case 'o':
return (1);
break;
case 'r':
break;
case '?':
optopt);
goto usage;
}
}
/* check number of arguments */
if (argc < 1) {
goto usage;
}
if (argc > 1) {
goto usage;
}
return (ret != 0);
return (-1);
}
/*
* zfs send [-vDp] -R [-i|-I <@snap>] <fs@snap>
* zfs send [-vDp] [-i|-I <@snap>] <fs@snap>
*
* Send a backup stream to stdout.
*/
static int
{
char *cp;
sendflags_t flags = { 0 };
int c, err;
/* check options */
switch (c) {
case 'i':
if (fromname)
break;
case 'I':
if (fromname)
break;
case 'R':
break;
case 'p':
break;
case 'v':
break;
case 'D':
break;
case ':':
"'%c' option\n"), optopt);
break;
case '?':
optopt);
}
}
/* check number of arguments */
if (argc < 1) {
}
if (argc > 1) {
}
if (isatty(STDOUT_FILENO)) {
gettext("Error: Stream can not be written to a terminal.\n"
"You must redirect standard output.\n"));
return (1);
}
gettext("argument must be a snapshot\n"));
}
*cp = '\0';
return (1);
/*
* If they specified the full path to the snapshot, chop off
* everything except the short name of the snapshot, but special
* case if they specify the origin.
*/
char origin[ZFS_MAXNAMELEN];
} else {
*cp = '\0';
gettext("incremental source must be "
"in same filesystem\n"));
}
gettext("invalid incremental source\n"));
}
}
}
if (extraverbose) {
/*
* dump_nvlist prints to stdout, but that's been
* redirected to a file. Make it print to stderr
* instead.
*/
dump_nvlist(dbgnv, 0);
}
return (err != 0);
}
/*
* zfs receive [-vnFu] [-d | -e] <fs@snap>
*
* Restore a backup stream from stdin.
*/
static int
{
int c, err;
recvflags_t flags = { 0 };
/* check options */
switch (c) {
case 'd':
break;
case 'e':
break;
case 'n':
break;
case 'u':
break;
case 'v':
break;
case 'F':
break;
case ':':
"'%c' option\n"), optopt);
break;
case '?':
optopt);
}
}
/* check number of arguments */
if (argc < 1) {
}
if (argc > 1) {
}
if (isatty(STDIN_FILENO)) {
gettext("Error: Backup stream can not be read "
"from a terminal.\n"
"You must redirect standard input.\n"));
return (1);
}
return (err != 0);
}
static int
{
int errors = 0;
int i;
const char *tag;
int c;
/* check options */
switch (c) {
case 'r':
break;
case 't':
break;
case '?':
optopt);
}
}
/* check number of arguments */
if (argc < 2)
--argc;
++argv;
/* tags starting with '.' are reserved for libzfs */
}
for (i = 0; i < argc; ++i) {
char parent[ZFS_MAXNAMELEN];
const char *delim;
++errors;
continue;
}
++errors;
continue;
}
if (holding) {
++errors;
} else {
++errors;
}
}
return (errors != 0);
}
/*
* zfs hold [-r] [-t] <tag> <snap> ...
*
* -r Recursively hold
* -t Temporary hold (hidden option)
*
* Apply a user-hold with the given tag to the list of snapshots.
*/
static int
{
}
/*
* zfs release [-r] <tag> <snap> ...
*
* -r Recursively release
*
* Release a user-hold with the given tag from the list of snapshots.
*/
static int
{
}
typedef struct get_all_cbdata {
#define CHECK_SPINNER 30
static int
{
static int spinval = 0;
static int spincheck = 0;
if (cbp->cb_verbose) {
if (--spincheck < 0) {
}
}
}
/*
* Interate over any nested datasets.
*/
if (type == ZFS_TYPE_FILESYSTEM &&
return (1);
}
/*
* Skip any datasets whose type does not match.
*/
return (0);
}
else
if (cbp->cb_handles) {
}
}
return (0);
}
static void
{
get_all_cbdata_t cb = { 0 };
if (verbose)
if (verbose)
}
static int
dataset_cmp(const void *a, const void *b)
{
char mounta[MAXPATHLEN];
char mountb[MAXPATHLEN];
if (gota)
return (-1);
if (gotb)
return (1);
}
/*
* Generic callback for sharing or mounting filesystems. Because the code is so
* 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
/*
* Share or mount a dataset.
*/
static int
{
char mountpoint[ZFS_MAXPROPLEN];
char shareopts[ZFS_MAXPROPLEN];
char smbshareopts[ZFS_MAXPROPLEN];
if (type == ZFS_TYPE_FILESYSTEM) {
/*
* are in the global zone and the filesystem is exported to a
* local zone, or if we are in a local zone and the
* filesystem is not exported, then it is an error.
*/
if (!explicit)
return (0);
"dataset is exported to a local zone\n"), cmdname,
zfs_get_name(zhp));
return (1);
if (!explicit)
return (0);
"permission denied\n"), cmdname,
zfs_get_name(zhp));
return (1);
}
/*
* Ignore any filesystems which don't apply to us. This
* includes those with a legacy mountpoint, or those with
* legacy share options.
*/
if (!explicit)
return (0);
"share this filesystem, or set "
"sharenfs property on\n"));
return (1);
}
/*
* We cannot share or mount legacy filesystems. If the
* shareopts is non-legacy but the mountpoint is legacy, we
* treat it as a legacy share.
*/
if (!explicit)
return (0);
return (1);
}
if (!explicit)
return (0);
return (1);
}
/*
* canmount explicit outcome
* on no pass through
* on yes pass through
* off no return 0
* off yes display error, return 1
* noauto no return 0
* noauto yes pass through
*/
if (canmount == ZFS_CANMOUNT_OFF) {
if (!explicit)
return (0);
"'canmount' property is set to 'off'\n"), cmdname,
zfs_get_name(zhp));
return (1);
return (0);
}
/*
* shareopts are appropriate for auto management. If the
* filesystem is already mounted or shared, return (failing
* for explicit requests); otherwise mount or share the
* filesystem.
*/
switch (op) {
case OP_SHARE:
if (shared_nfs && shared_smb ||
if (!explicit)
return (0);
"'%s': filesystem already shared\n"),
zfs_get_name(zhp));
return (1);
}
return (1);
if (zfs_shareall(zhp) != 0)
return (1);
if (zfs_share_nfs(zhp))
return (1);
if (zfs_share_smb(zhp))
return (1);
} else {
"'%s': invalid share type '%s' "
"specified\n"),
return (1);
}
break;
case OP_MOUNT:
else
if (!explicit)
return (0);
"'%s': filesystem already mounted\n"),
zfs_get_name(zhp));
return (1);
}
return (1);
break;
}
} else
return (0);
}
/*
*/
static void
{
static time_t last_progress_time = 0;
char info[32];
/* report 1..n instead of 0..n-1 */
++current;
/* display header if we're here for the first time */
if (current == 1) {
/* too soon to report again */
return;
}
else
}
static void
{
/* original length plus new string to append plus 1 for the comma */
"'%c' option is too long (more than %d chars)\n"),
"-o", MNT_LINE_MAX);
}
if (*mntopts)
}
static int
{
int do_all = 0;
int c, ret = 0;
/* check options */
!= -1) {
switch (c) {
case 'a':
do_all = 1;
break;
case 'v':
break;
case 'o':
if (*optarg == '\0') {
"options (-o) specified\n"));
}
/* option validation is done later */
break;
case 'O':
flags |= MS_OVERLAY;
break;
case ':':
"'%c' option\n"), optopt);
break;
case '?':
optopt);
}
}
/* check number of arguments */
if (do_all) {
} else if (argc > 0) {
} else {
"must be 'nfs' or 'smb'\n"));
}
argc--;
argv++;
} else {
}
if (argc != 0) {
}
if (count == 0)
return (0);
for (i = 0; i < count; i++) {
if (verbose)
ret = 1;
}
} else if (argc == 0) {
"argument (specify -a for all)\n"));
}
/*
* display any active ZFS mounts. We hide any snapshots, since
* they are controlled automatically.
*/
continue;
}
} else {
types |= ZFS_TYPE_VOLUME;
if (argc > 1) {
gettext("too many arguments\n"));
}
ret = 1;
} else {
options);
}
}
return (ret);
}
/*
* zfs mount -a [nfs]
* zfs mount filesystem
*
* Mount all filesystems, or mount the given filesystem.
*/
static int
{
}
/*
* zfs share -a [nfs | smb]
* zfs share filesystem
*
* Share all filesystems, or share the given filesystem.
*/
static int
{
}
typedef struct unshare_unmount_node {
char *un_mountp;
/* ARGSUSED */
static int
{
const unshare_unmount_node_t *l = larg;
const unshare_unmount_node_t *r = rarg;
}
/*
* Convenience routine used by zfs_do_umount() and manual_unmount(). Given an
* and unmount it appropriately.
*/
static int
{
int ret;
/*
* 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.
*/
return (1);
}
/*
* Search for the given (major,minor) pair in the mount table.
*/
break;
}
if (ret != 0) {
return (1);
}
path);
return (ret != 0);
}
return (1);
}
ZFS_TYPE_FILESYSTEM)) == NULL)
return (1);
ret = 1;
goto out;
goto out;
}
char nfs_mnt_prop[ZFS_MAXPROPLEN];
char smbshare_prop[ZFS_MAXPROPLEN];
"'%s': legacy share\n"), path);
"unshare(1M) to unshare this filesystem\n"));
} else if (!zfs_is_shared(zhp)) {
"not currently shared\n"), path);
} else {
}
} else {
char mtpt_prop[ZFS_MAXPROPLEN];
if (is_manual) {
"'%s': legacy mountpoint\n"),
zfs_get_name(zhp));
"to unmount this filesystem\n"));
} else {
}
}
out:
return (ret != 0);
}
/*
* Generic callback for unsharing or unmounting a filesystem.
*/
static int
{
int do_all = 0;
int flags = 0;
int ret = 0;
int types, c;
char nfs_mnt_prop[ZFS_MAXPROPLEN];
char sharesmb[ZFS_MAXPROPLEN];
/* check options */
switch (c) {
case 'a':
do_all = 1;
break;
case 'f':
break;
case '?':
optopt);
}
}
if (do_all) {
/*
* We could make use of zfs_for_each() to walk all datasets in
* the system, but this would be very inefficient, especially
* 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
* have to unmount the deepest filesystems first. To accomplish
* this, we place all the mountpoints in an AVL tree sorted by
* the special type (dataset name), and walk the result in
* reverse to make sure to get any snapshots first.
*/
if (argc != 0) {
}
sizeof (unshare_unmount_node_t),
nomem();
/* ignore non-ZFS entries */
continue;
/* ignore snapshots */
continue;
ZFS_TYPE_FILESYSTEM)) == NULL) {
ret = 1;
continue;
}
switch (op) {
case OP_SHARE:
sizeof (nfs_mnt_prop),
break;
sizeof (nfs_mnt_prop),
continue;
break;
case OP_MOUNT:
/* Ignore legacy mounts */
sizeof (nfs_mnt_prop),
continue;
/* Ignore canmount=noauto mounts */
continue;
default:
break;
}
} else {
}
}
/*
* Walk the AVL tree in reverse, unmounting each filesystem and
* removing it from the AVL tree in the process.
*/
nomem();
switch (op) {
case OP_SHARE:
ret = 1;
break;
case OP_MOUNT:
ret = 1;
break;
}
}
} else {
if (argc != 1) {
if (argc == 0)
gettext("missing filesystem argument\n"));
else
gettext("too many arguments\n"));
}
/*
* We have an argument, but it may be a full path or a ZFS
* filesystem. Pass full paths off to unmount_path() (shared by
* manual_unmount), otherwise open the filesystem and pass to
* zfs_unmount().
*/
if (argv[0][0] == '/')
types |= ZFS_TYPE_VOLUME;
return (1);
switch (op) {
case OP_SHARE:
sizeof (nfs_mnt_prop),
0, B_FALSE) == 0);
"unshare '%s': legacy share\n"),
zfs_get_name(zhp));
"unshare(1M) to unshare this "
"filesystem\n"));
ret = 1;
} else if (!zfs_is_shared(zhp)) {
"unshare '%s': not currently "
ret = 1;
} else if (zfs_unshareall(zhp) != 0) {
ret = 1;
}
break;
case OP_MOUNT:
"unmount '%s': legacy "
"umount(1M) to unmount this "
"filesystem\n"));
ret = 1;
"unmount '%s': not currently "
"mounted\n"),
zfs_get_name(zhp));
ret = 1;
ret = 1;
}
break;
}
}
}
return (ret);
}
/*
* zfs unmount -a
* zfs unmount filesystem
*
* Unmount all filesystems, or a specific ZFS filesystem.
*/
static int
{
}
/*
* zfs unshare -a
* zfs unshare filesystem
*
* Unshare all filesystems, or a specific ZFS filesystem.
*/
static int
{
}
/* ARGSUSED */
static int
{
return (-1);
}
/*
* 'legacy'. Otherwise, complain that use should be using 'zfs mount'.
*/
static int
{
char mountpoint[ZFS_MAXPROPLEN];
int ret;
int c;
int flags = 0;
/* check options */
switch (c) {
case 'o':
break;
case 'O':
flags |= MS_OVERLAY;
break;
case 'm':
flags |= MS_NOMNTTAB;
break;
case ':':
"'%c' option\n"), optopt);
break;
case '?':
optopt);
"<path>\n"));
return (2);
}
}
/* check that we only have two arguments */
if (argc != 2) {
if (argc == 0)
"argument\n"));
else if (argc == 1)
gettext("missing mountpoint argument\n"));
else
return (2);
}
/* try to open the dataset */
return (1);
/* check for legacy mountpoint and complain appropriately */
ret = 0;
ret = 1;
}
} else {
"mounted using 'mount -F zfs'\n"), dataset);
"instead.\n"), path);
"information.\n"));
ret = 1;
}
return (ret);
}
/*
* unmounts of non-legacy filesystems, as this is the dominant administrative
* interface.
*/
static int
{
int flags = 0;
int c;
/* check options */
switch (c) {
case 'f':
break;
case '?':
optopt);
"<path>\n"));
return (2);
}
}
/* check arguments */
if (argc != 1) {
if (argc == 0)
"argument\n"));
else
return (2);
}
}
static int
{
int i;
for (i = 0; i < NCOMMAND; i++) {
continue;
*idx = i;
return (0);
}
}
return (1);
}
int
{
int ret;
int i;
char *progname;
char *cmdname;
(void) textdomain(TEXT_DOMAIN);
opterr = 0;
"initialize ZFS library\n"));
return (1);
}
"open %s\n"), MNTTAB);
return (1);
}
/*
* Determine if we should take this behavior based on argv[0].
*/
} else {
/*
* Make sure the user has specified some command.
*/
if (argc < 2) {
}
/*
* The 'umount' command is an alias for 'unmount'
*/
cmdname = "unmount";
/*
* The 'recv' command is an alias for 'receive'
*/
cmdname = "receive";
/*
* Special case '-?'
*/
/*
* Run the appropriate command.
*/
if (find_command_idx(cmdname, &i) == 0) {
current_command = &command_table[i];
current_command = &command_table[i];
} else {
"command '%s'\n"), cmdname);
}
}
(void) fclose(mnttab_file);
/*
* The 'ZFS_ABORT' environment variable causes us to dump core on exit
* for the purposes of running ::findleaks.
*/
(void) printf("dumping core by request\n");
abort();
}
return (ret);
}