/*
* 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
*/
/*
*/
/*
* Routines to manage ZFS mounts. We separate all the nasty routines that have
* to deal with the OS. The following functions are the main entry points --
* they are used by mount and unmount and when changing a filesystem's
* mountpoint.
*
* zfs_is_mounted()
* zfs_mount()
* zfs_mountall()
* zfs_unmount()
* zfs_unmountall()
*
* This file also contains the functions used to manage sharing filesystems via
* NFS and iSCSI:
*
* zfs_is_shared()
* zfs_share()
* zfs_unshare()
*
* zfs_is_shared_nfs()
* zfs_is_shared_smb()
* zfs_share_proto()
* zfs_shareall();
* zfs_unshare_nfs()
* zfs_unshare_smb()
* zfs_unshareall_nfs()
* zfs_unshareall_smb()
* zfs_unshareall()
* zfs_unshareall_bypath()
*
* The following functions are available for pool consumers, and will
*
* zpool_enable_datasets()
* zpool_disable_datasets()
*/
#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <libgen.h>
#include <libintl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <zone.h>
#include <string.h>
#include <libzfs.h>
#include "libzfs_impl.h"
#include <sys/systeminfo.h>
/*
* The share protocols table must be in the same order as the zfs_share_prot_t
* enum in libzfs_impl.h
*/
typedef struct {
char *p_name;
};
};
};
};
/*
* search the sharetab cache for the given mountpoint and protocol, returning
* a zfs_share_type_t value.
*/
static zfs_share_type_t
{
void *sa_hdl;
return (SHARED_NOT_SHARED);
}
if (!found)
return (SHARED_NOT_SHARED);
switch (proto) {
case PROTO_NFS:
return (SHARED_NFS);
case PROTO_SMB:
return (SHARED_SMB);
default:
return (0);
}
}
/*
* Returns true if the specified directory is empty. If we can't open the
* directory at all, return true so that the mount can fail with a more
* informative error message.
*/
static boolean_t
{
return (B_TRUE);
continue;
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Checks to see if the mount is active. If the filesystem is mounted, we fill
* in 'where' with the current mountpoint, and return 1. Otherwise, we return
* 0.
*/
{
return (B_FALSE);
return (B_TRUE);
}
{
return (B_FALSE);
return (B_FALSE);
return (B_TRUE);
}
/*
* Returns true if the given dataset is mountable, false otherwise. Returns the
* mountpoint in 'buf'.
*/
static boolean_t
{
return (B_FALSE);
return (B_FALSE);
return (B_FALSE);
getzoneid() == GLOBAL_ZONEID)
return (B_FALSE);
if (source)
*source = sourcetype;
return (B_TRUE);
}
/*
* Mount the given filesystem.
*/
int
{
mntopts[0] = '\0';
else
/*
* If the pool is imported read-only then all mounts must be read-only
*/
/*
* See if there's a temporary mount point defined in the
* mount options.
*/
while (*opts != '\0') {
char *value;
case 0:
}
}
/*
* zfs_is_mountable() checks to see whether the dataset's
* "mountpoint", "canmount", and "zoned" properties are consistent
* with being mounted. It does not do a full evaluation of all
* possible obstacles to mounting.
*/
is_tmpmount)) {
if (tmpmount)
return (0);
}
/*
* For encrypted datasets attempt to load the key.
* zfs_key_load will return -1 and set errno to ENOTTY if
* we need to prompt and we are in the SMF start method
* for svc:/system/filesystem/local. In hindsight all datasets
* that have keysource=passphrase,prompt should also have had
* canmount=noauto. To preserve that partiuclar behaviour
* we check errno but any other key loading failure we report.
* Also the zfs_key_load used to be in the zfs_is_mountable()
* function but it got moved out because that function is also
* called during dataset destroy and we don't want to attempt
* key load while destroying a dataset.
*/
if (is_tmpmount) {
}
/*
* If a dataset being mounted with a regular (i.e., not
* temporary) mount inherits its mountpoint from its
* parent, make sure the parent isn't temp-mounted.
*/
if (!is_tmpmount &&
/* lop off last component to construct the parent name */
*cpt = '\0';
ZFS_TYPE_FILESYSTEM)) == NULL) {
"failure opening parent"));
}
/*
* Don't mount parent datasets or check for a temp-mounted
* parent dataset if we're operating in a local
* zone and the parent of the dataset is not zoned.
* In other words, if the parent dataset is across the "zone
* boundary" from the child dataset, the parent zone
* can't be mounted anyway, so no point in trying. Nor is
* there a problem if the parent is temp-mounted. A
* temporary mount in the global zone doesn't interfere
* with the mount in local zone.
*/
if (getzoneid() == GLOBAL_ZONEID ||
"parent dataset has a temporary "
"mountpoint"));
return (zfs_error_fmt(hdl,
"cannot mount '%s' on '%s'"),
}
} else {
/*
* Mount the parent dataset. We do this to
* lessen the likelihood that mounts will be
* done out of order, which can cause
* mountpoint directories to contain
* intermediate directories, which prevents
* mounts from succeeding (because zfs won't do
* a mount on a non-empty directory).
*
* We call zfs_mount() recursively to accomplish
* this, working our way up the dataset
* hierarchy. The recursion will stop when a
* dataset is reached which is already mounted
* or which has a "local" (i.e., not inherited)
* mountpoint or which has no parent (i.e.,
* the root of the hierarchy). Then the
* recursion will unwind, mounting each dataset
* as it goes down the tree. If a temporarily-
* mounted dataset is encountered above the one
* we're trying to mount, report an error and
* don't mount anything. We don't permit regular
* zfs mounts under temporary mounts.
*/
int tflag = 0;
"failure mounting parent dataset"));
return (zfs_error_fmt(hdl,
"cannot mount '%s' on '%s'"),
}
}
}
}
/* Create the directory if it doesn't already exist */
/* temp mounts require that the mount point already exist */
if (is_tmpmount) {
"mountpoint must already exist "
"for temporary mounts"));
}
"failed to create mountpoint, "
}
}
/*
* Determine if the mountpoint is empty. If so, refuse to perform the
* mount. We don't perform this check if MS_OVERLAY is specified, which
* would defeat the point. We also avoid this check if 'remount' is
* specified.
*/
if ((flags & MS_OVERLAY) == 0 &&
!dir_is_empty(mountpoint)) {
"directory is not empty"));
}
/*
* Check to see if this a shadow mount and adjust the mount options as
* necessary.
*/
return (-1);
/* perform the mount */
/*
* Generic errors are nasty, but there are just way too many
* from mount(), and they're well-understood. We pick a few
* common ones to improve upon.
*/
"mountpoint or dataset is busy"));
"Can't mount file system, FSID conflicts "
"with another mounted file system"));
"Insufficient privileges"));
int spa_version;
"file system on a version %d pool. Pool must be"
" upgraded to mount this file system."),
} else {
}
if (is_shadow)
(void) zfs_shadow_unmount(zhp);
}
/*
* Add the mounted entry into our cache. Because it may
* be a remount, remove it first.
*/
/*
* Add this newly mounted fs into the cache. The cache must be a
* complete representation of all known fs's. Note that the mntopts
* string cannot be added to this entry since it is not complete.
* mount() only returns the values of properties that were requested.
* This would miss properties added by the system such as zone=ZONENAME
* and sharezone=ZONEID. So we leave the mntopts out of the cache
* entry and add them later if and when they are needed.
*/
NULL);
return (0);
}
/*
* Unmount a single filesystem.
*/
static int
{
mountpoint));
}
return (0);
}
/*
* Unmount the given filesystem.
*/
int
{
/* check to see if we need to unmount the filesystem */
if (mountpoint != NULL) {
/*
* if it isn't NULL. This could get overwritten.
*/
return (0);
}
/*
* Unshare and unmount the filesystem
*/
return (-1);
}
(void) zfs_shareall(zhp);
return (-1);
}
/*
* Unmount the shadow filesystem if necessary.
*/
if (zfs_shadow_unmount(zhp) != 0) {
return (-1);
}
return (0);
}
/*
* mount this filesystem and any children inheriting the mountpoint property.
* To do this, just act like we're changing the mountpoint property, but don't
* unmount the filesystems first.
*/
int
{
int ret;
return (-1);
return (ret);
}
/*
* Unmount this filesystem and any children inheriting the mountpoint property.
* To do this, just act like we're changing the mountpoint property, but don't
* remount the filesystems afterwards.
*/
int
{
int ret;
return (-1);
return (ret);
}
{
if (ZFS_IS_VOLUME(zhp))
return (B_FALSE);
curr_proto++)
}
int
{
return (0);
}
int
{
return (0);
}
/*
* Check to see if the filesystem is currently shared.
*/
{
char *mountpoint;
return (SHARED_NOT_SHARED);
*where = mountpoint;
else
return (rc);
} else {
return (SHARED_NOT_SHARED);
}
}
{
PROTO_NFS) != SHARED_NOT_SHARED);
}
{
PROTO_SMB) != SHARED_NOT_SHARED);
}
/*
* Share the given filesystem according to the options in the specified
* protocol specific properties (sharenfs, sharesmb). We rely
* on "libshare" to do the dirty work for us.
*/
static int
{
int rc;
int zoned;
return (0);
return (0);
B_FALSE))
return (0);
/*
* Perform sharing upgrades
*/
/*
* If the 'zoned' property is set, then zfs_is_shareable()
* will have already bailed out if we are in the global zone.
* NFS can be a server in a local zone, but nothing else
*/
continue;
/*
* Skip this protocol, if protocol is not supported
*/
== SA_OK) {
share_prot |=
}
}
/*
* Now publish all shares on this mountpoint/dataset.
* Wait for completion.
*/
if (share_prot != SA_PROT_NONE) {
SA_OK) {
zfs_get_name(zhp));
return (-1);
}
}
return (0);
}
int
{
}
int
{
}
int
{
}
/*
* Unshare a filesystem by mountpoint.
*/
static int
{
int rc;
if (sa_prot == SA_PROT_NONE)
return (0);
name));
}
return (0);
}
/*
* Unshare the given filesystem.
*/
int
{
char *mntpt;
int ret;
/* check to see if need to unmount the filesystem */
if (mountpoint != NULL) {
/*
* if it isn't NULL. This could get overwritten.
*/
return (0);
}
return (ret);
}
int
{
}
int
{
}
/*
* Same as zfs_unmountall(), but for NFS and SMB unshares.
*/
int
{
int ret;
/* Any share prop will do -- it is just to gather the right FSs */
return (-1);
return (ret);
}
int
{
}
int
{
}
int
{
}
int
{
}
/*
* Remove the mountpoint associated with the current dataset, if necessary.
* We only remove the underlying directory if:
*
* - The mountpoint is not 'none' or 'legacy'
* - The mountpoint is non-empty
* - The mountpoint is the default or inherited
* - The 'zoned' property is set, or we're in a local zone
*
* Any other directories we leave alone.
*/
void
{
return;
if (source == ZPROP_SRC_DEFAULT ||
source == ZPROP_SRC_INHERITED) {
/*
* Try to remove the directory, silently ignoring any errors.
* The filesystem may have since been removed or moved around,
* and this error isn't really useful to the administrator in
* any way.
*/
(void) rmdir(mountpoint);
}
}
void
{
void *ptr;
newsz * sizeof (void *));
}
}
static int
{
/*
* Since we are iterating over the datasets in the pool
* in dataset hierarchy we do a prelim attempt at getting
* encryption keys loaded if they are needed.
* This is needed for cases where a dataset's mountpoint
* is lexically before that of the dataset it inherits
* the keysource property (and thus its wrapping key) from,
* If we were to return a failure here we would terminate the
* dataset iteration at this point, so worst case no datasets
* would get mounted if the top level is encrypted and its key
* wasn't available but keys for other datasets are.
*/
return (0);
}
}
return (0);
}
return (0);
}
return (-1);
}
return (0);
}
int
libzfs_dataset_cmp(const void *a, const void *b)
{
if (gota)
return (-1);
if (gotb)
return (1);
}
/*
* Mount and share all datasets within the given pool. This assumes that no
* datasets within the pool are currently mounted. Because users can create
* complicated nested hierarchies of mountpoints, we first gather all the
* datasets and mountpoints within the pool, and sort them by mountpoint. Once
* we have the list of all filesystems, we iterate over them in order and mount
*/
int
{
int *good;
/*
* Gather all non-snap datasets within the pool.
*/
goto out;
/*
* If the top level dataset is encrypted load its keys.
*/
}
goto out;
/*
* Sort the datasets by mountpoint.
*/
/*
* And mount all the datasets, keeping track of which ones succeeded or
* failed. Treat an already mounted dataset the same as success.
*/
goto out;
ret = 0;
good[i] = 1;
else if (err == 0)
good[i] = 1;
else
ret = -1;
}
/*
* Then share all the ones that need to be shared. This needs
* to be a separate pass in order to avoid excessive reloading
* of the configuration. Good should never be NULL since
* zfs_alloc is supposed to exit if memory isn't available.
*/
ret = -1;
}
out:
return (ret);
}
typedef struct share_info {
char *si_mountpoint;
} share_info_t;
static int
mountpoint_compare(const void *a, const void *b)
{
}
/* alias for 2002/240 */
/*
* Unshare and unmount all datasets within the given pool. We don't want to
* rely on traversing the DSL to discover the filesystems within the pool,
* because this may be expensive (if not all of them are mounted), and can fail
* gather all the filesystems that are currently mounted.
*/
int
{
int i;
/*
* Ignore non-ZFS entries.
*/
continue;
/*
* Ignore filesystems not within this pool.
*/
continue;
/*
* At this point we've found a filesystem within our pool. Add
* it to our growing list.
*/
if (alloc == 0) {
goto out;
alloc = 8;
} else {
void *ptr;
alloc * sizeof (share_info_t),
goto out;
alloc *= 2;
}
}
goto out;
/*
* This is allowed to fail, in case there is some I/O error. It
* is only used to determine if we need to remove the underlying
* mountpoint, so failure is not fatal.
*/
used++;
}
/*
* At this point, we have the entire list of filesystems, so sort it by
* mountpoint.
*/
/*
* Walk through and first unshare everything.
*/
for (i = 0; i < used; i++) {
"Failed to unshare %s\n"),
goto out;
}
}
/*
* Now unmount everything, removing the underlying directories as
* appropriate.
*/
for (i = 0; i < used; i++) {
flags) != 0)
goto out;
goto out;
}
}
for (i = 0; i < used; i++) {
}
ret = 0;
out:
for (i = 0; i < used; i++) {
}
return (ret);
}
int
{
int err;
B_FALSE)) != 0)
return (err);
return (0);
}
int
{
int err;
if (!zfs_is_share(zhp))
return (err);
return (0);
}