*
* CDDL HEADER END
*/
+
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ * Copyright (c) 2012 by Delphix. All rights reserved.
*/
/*
#include <wait.h>
#include <libzfs.h>
+#include <libzfs_core.h>
#include "libzfs_impl.h"
#include "zfs_prop.h"
+#include "zfeature_common.h"
int
libzfs_errno(libzfs_handle_t *hdl)
return (hdl->libzfs_error);
}
+const char *
+libzfs_error_init(int error)
+{
+ switch (error) {
+ case ENXIO:
+ return (dgettext(TEXT_DOMAIN, "The ZFS modules are not "
+ "loaded.\nTry running '/sbin/modprobe zfs' as root "
+ "to load them.\n"));
+ case ENOENT:
+ return (dgettext(TEXT_DOMAIN, "The /dev/zfs device is "
+ "missing and must be created.\nTry running 'udevadm "
+ "trigger' as root to create it.\n"));
+ case ENOEXEC:
+ return (dgettext(TEXT_DOMAIN, "The ZFS modules cannot be "
+ "auto-loaded.\nTry running '/sbin/modprobe zfs' as "
+ "root to manually load them.\n"));
+ case EACCES:
+ return (dgettext(TEXT_DOMAIN, "Permission denied the "
+ "ZFS utilities must be run as root.\n"));
+ default:
+ return (dgettext(TEXT_DOMAIN, "Failed to initialize the "
+ "libzfs library.\n"));
+ }
+}
+
const char *
libzfs_error_action(libzfs_handle_t *hdl)
{
case EZFS_RESILVERING:
return (dgettext(TEXT_DOMAIN, "currently resilvering"));
case EZFS_BADVERSION:
- return (dgettext(TEXT_DOMAIN, "unsupported version"));
+ return (dgettext(TEXT_DOMAIN, "unsupported version or "
+ "feature"));
case EZFS_POOLUNAVAIL:
return (dgettext(TEXT_DOMAIN, "pool is unavailable"));
case EZFS_DEVOVERFLOW:
switch (error) {
case ENXIO:
case ENODEV:
+ case EPIPE:
zfs_verror(hdl, EZFS_IO, fmt, ap);
break;
int index = 0;
char u;
- while (n >= 1024) {
+ while (n >= 1024 && index < 6) {
n /= 1024;
index++;
}
static int
libzfs_module_loaded(const char *module)
{
- FILE *f;
- int result = 0;
- char name[256];
-
- f = fopen("/proc/modules", "r");
- if (f == NULL)
- return -1;
+ const char path_prefix[] = "/sys/module/";
+ char path[256];
- while (fgets(name, sizeof(name), f)) {
- char *c = strchr(name, ' ');
- if (!c)
- continue;
- *c = 0;
- if (strcmp(module, name) == 0) {
- result = 1;
- break;
- }
- }
- fclose(f);
+ memcpy(path, path_prefix, sizeof (path_prefix) - 1);
+ strcpy(path + sizeof (path_prefix) - 1, module);
- return result;
+ return (access(path, F_OK) == 0);
}
int
-libzfs_run_process(const char *path, char *argv[])
+libzfs_run_process(const char *path, char *argv[], int flags)
{
pid_t pid;
- int rc;
+ int error, devnull_fd;
pid = vfork();
if (pid == 0) {
- close(1);
- close(2);
+ devnull_fd = open("/dev/null", O_WRONLY);
+
+ if (devnull_fd < 0)
+ _exit(-1);
+
+ if (!(flags & STDOUT_VERBOSE))
+ (void) dup2(devnull_fd, STDOUT_FILENO);
+
+ if (!(flags & STDERR_VERBOSE))
+ (void) dup2(devnull_fd, STDERR_FILENO);
+
+ close(devnull_fd);
+
(void) execvp(path, argv);
_exit(-1);
} else if (pid > 0) {
int status;
- while ((rc = waitpid(pid, &status, 0)) == -1 &&
+ while ((error = waitpid(pid, &status, 0)) == -1 &&
errno == EINTR);
- if (rc < 0 || !WIFEXITED(status))
- return -1;
+ if (error < 0 || !WIFEXITED(status))
+ return (-1);
- return WEXITSTATUS(status);
+ return (WEXITSTATUS(status));
}
- return -1;
+ return (-1);
}
-int
+/*
+ * Verify the required ZFS_DEV device is available and optionally attempt
+ * to load the ZFS modules. Under normal circumstances the modules
+ * should already have been loaded by some external mechanism.
+ *
+ * Environment variables:
+ * - ZFS_MODULE_LOADING="YES|yes|ON|on" - Attempt to load modules.
+ * - ZFS_MODULE_TIMEOUT="<seconds>" - Seconds to wait for ZFS_DEV
+ */
+static int
libzfs_load_module(const char *module)
{
char *argv[4] = {"/sbin/modprobe", "-q", (char *)module, (char *)0};
+ char *load_str, *timeout_str;
+ long timeout = 10; /* seconds */
+ long busy_timeout = 10; /* milliseconds */
+ int load = 0, fd;
+ hrtime_t start;
+
+ /* Optionally request module loading */
+ if (!libzfs_module_loaded(module)) {
+ load_str = getenv("ZFS_MODULE_LOADING");
+ if (load_str) {
+ if (!strncasecmp(load_str, "YES", strlen("YES")) ||
+ !strncasecmp(load_str, "ON", strlen("ON")))
+ load = 1;
+ else
+ load = 0;
+ }
- if (libzfs_module_loaded(module))
- return 0;
+ if (load && libzfs_run_process("/sbin/modprobe", argv, 0))
+ return (ENOEXEC);
+ }
+
+ /* Module loading is synchronous it must be available */
+ if (!libzfs_module_loaded(module))
+ return (ENXIO);
+
+ /*
+ * Device creation by udev is asynchronous and waiting may be
+ * required. Busy wait for 10ms and then fall back to polling every
+ * 10ms for the allowed timeout (default 10s, max 10m). This is
+ * done to optimize for the common case where the device is
+ * immediately available and to avoid penalizing the possible
+ * case where udev is slow or unable to create the device.
+ */
+ timeout_str = getenv("ZFS_MODULE_TIMEOUT");
+ if (timeout_str) {
+ timeout = strtol(timeout_str, NULL, 0);
+ timeout = MAX(MIN(timeout, (10 * 60)), 0); /* 0 <= N <= 600 */
+ }
- return libzfs_run_process("/sbin/modprobe", argv);
+ start = gethrtime();
+ do {
+ fd = open(ZFS_DEV, O_RDWR);
+ if (fd >= 0) {
+ (void) close(fd);
+ return (0);
+ } else if (errno != ENOENT) {
+ return (errno);
+ } else if (NSEC2MSEC(gethrtime() - start) < busy_timeout) {
+ sched_yield();
+ } else {
+ usleep(10 * MILLISEC);
+ }
+ } while (NSEC2MSEC(gethrtime() - start) < (timeout * MILLISEC));
+
+ return (ENOENT);
}
libzfs_handle_t *
libzfs_init(void)
{
libzfs_handle_t *hdl;
+ int error;
- if (libzfs_load_module("zfs") != 0) {
- (void) fprintf(stderr, gettext("Failed to load ZFS module "
- "stack.\nLoad the module manually by running "
- "'insmod <location>/zfs.ko' as root.\n"));
+ error = libzfs_load_module(ZFS_DRIVER);
+ if (error) {
+ errno = error;
return (NULL);
}
}
if ((hdl->libzfs_fd = open(ZFS_DEV, O_RDWR)) < 0) {
- (void) fprintf(stderr, gettext("Unable to open %s: %s.\n"),
- ZFS_DEV, strerror(errno));
- if (errno == ENOENT)
- (void) fprintf(stderr,
- gettext("Verify the ZFS module stack is "
- "loaded by running '/sbin/modprobe zfs'.\n"));
-
free(hdl);
return (NULL);
}
hdl->libzfs_sharetab = fopen("/etc/dfs/sharetab", "r");
+ if (libzfs_core_init() != 0) {
+ (void) close(hdl->libzfs_fd);
+ (void) fclose(hdl->libzfs_mnttab);
+ (void) fclose(hdl->libzfs_sharetab);
+ free(hdl);
+ return (NULL);
+ }
+
zfs_prop_init();
zpool_prop_init();
+ zpool_feature_init();
libzfs_mnttab_init(hdl);
return (hdl);
#endif
if (hdl->libzfs_sharetab)
(void) fclose(hdl->libzfs_sharetab);
-#ifdef HAVE_ZPL
zfs_uninit_libshare(hdl);
-#endif
- if (hdl->libzfs_log_str)
- (void) free(hdl->libzfs_log_str);
zpool_free_handles(hdl);
libzfs_fru_clear(hdl, B_TRUE);
namespace_clear(hdl);
libzfs_mnttab_fini(hdl);
+ libzfs_core_fini();
free(hdl);
}
return (NULL);
}
- rewind(hdl->libzfs_mnttab);
+ /* Reopen MNTTAB to prevent reading stale data from open file */
+ if (freopen(MNTTAB, "r", hdl->libzfs_mnttab) == NULL)
+ return (NULL);
+
while ((ret = getextmntent(hdl->libzfs_mnttab, &entry, 0)) == 0) {
if (makedevice(entry.mnt_major, entry.mnt_minor) ==
statbuf.st_dev) {
}
/*
- * Given a shorthand device name, check if a file by that name exists in a list
- * of directories under /dev. If one is found, store its full path in the
- * buffer pointed to by the path argument and return 0, else return -1. The
- * path buffer must be allocated by the caller.
+ * Append partition suffix to an otherwise fully qualified device path.
+ * This is used to generate the name the full path as its stored in
+ * ZPOOL_CONFIG_PATH for whole disk devices. On success the new length
+ * of 'path' will be returned on error a negative value is returned.
+ */
+int
+zfs_append_partition(char *path, size_t max_len)
+{
+ int len = strlen(path);
+
+ if (strncmp(path, UDISK_ROOT, strlen(UDISK_ROOT)) == 0) {
+ if (len + 6 >= max_len)
+ return (-1);
+
+ (void) strcat(path, "-part1");
+ len += 6;
+ } else {
+ if (len + 2 >= max_len)
+ return (-1);
+
+ if (isdigit(path[len-1])) {
+ (void) strcat(path, "p1");
+ len += 2;
+ } else {
+ (void) strcat(path, "1");
+ len += 1;
+ }
+ }
+
+ return (len);
+}
+
+/*
+ * Given a shorthand device name check if a file by that name exists in any
+ * of the 'zpool_default_import_path' or ZPOOL_IMPORT_PATH directories. If
+ * one is found, store its fully qualified path in the 'path' buffer passed
+ * by the caller and return 0, otherwise return an error.
*/
int
-zfs_resolve_shortname(const char *name, char *path, size_t pathlen)
+zfs_resolve_shortname(const char *name, char *path, size_t len)
{
- int i, err;
- char dirs[5][9] = {"by-id", "by-label", "by-path", "by-uuid", "zpool"};
-
- (void) snprintf(path, pathlen, "%s/%s", DISK_ROOT, name);
- err = access(path, F_OK);
- for (i = 0; i < 5 && err < 0; i++) {
- (void) snprintf(path, pathlen, "%s/%s/%s",
- UDISK_ROOT, dirs[i], name);
- err = access(path, F_OK);
+ int i, error = -1;
+ char *dir, *env, *envdup;
+
+ env = getenv("ZPOOL_IMPORT_PATH");
+ errno = ENOENT;
+
+ if (env) {
+ envdup = strdup(env);
+ dir = strtok(envdup, ":");
+ while (dir && error) {
+ (void) snprintf(path, len, "%s/%s", dir, name);
+ error = access(path, F_OK);
+ dir = strtok(NULL, ":");
+ }
+ free(envdup);
+ } else {
+ for (i = 0; i < DEFAULT_IMPORT_PATH_SIZE && error < 0; i++) {
+ (void) snprintf(path, len, "%s/%s",
+ zpool_default_import_path[i], name);
+ error = access(path, F_OK);
+ }
}
- return err;
+
+ return (error ? ENOENT : 0);
}
/*
- * Append partition suffix to a device path. This should be used to generate
- * the name of a whole disk as it is stored in the vdev label. The
- * user-visible names of whole disks do not contain the partition information.
- * Modifies buf which must be allocated by the caller.
+ * Given a shorthand device name look for a match against 'cmp_name'. This
+ * is done by checking all prefix expansions using either the default
+ * 'zpool_default_import_paths' or the ZPOOL_IMPORT_PATH environment
+ * variable. Proper partition suffixes will be appended if this is a
+ * whole disk. When a match is found 0 is returned otherwise ENOENT.
*/
-void
-zfs_append_partition(const char *path, char *buf, size_t buflen)
+static int
+zfs_strcmp_shortname(char *name, char *cmp_name, int wholedisk)
{
- if (strncmp(path, UDISK_ROOT, strlen(UDISK_ROOT)) == 0)
- (void) snprintf(buf, buflen, "%s%s%s", path, "-part",
- FIRST_SLICE);
- else
- (void) snprintf(buf, buflen, "%s%s%s", path,
- isdigit(path[strlen(path)-1]) ? "p" : "",
- FIRST_SLICE);
+ int path_len, cmp_len, i = 0, error = ENOENT;
+ char *dir, *env, *envdup = NULL;
+ char path_name[MAXPATHLEN];
+
+ cmp_len = strlen(cmp_name);
+ env = getenv("ZPOOL_IMPORT_PATH");
+
+ if (env) {
+ envdup = strdup(env);
+ dir = strtok(envdup, ":");
+ } else {
+ dir = zpool_default_import_path[i];
+ }
+
+ while (dir) {
+ /* Trim trailing directory slashes from ZPOOL_IMPORT_PATH */
+ while (dir[strlen(dir)-1] == '/')
+ dir[strlen(dir)-1] = '\0';
+
+ path_len = snprintf(path_name, MAXPATHLEN, "%s/%s", dir, name);
+ if (wholedisk)
+ path_len = zfs_append_partition(path_name, MAXPATHLEN);
+
+ if ((path_len == cmp_len) && strcmp(path_name, cmp_name) == 0) {
+ error = 0;
+ break;
+ }
+
+ if (env) {
+ dir = strtok(NULL, ":");
+ } else if (++i < DEFAULT_IMPORT_PATH_SIZE) {
+ dir = zpool_default_import_path[i];
+ } else {
+ dir = NULL;
+ }
+ }
+
+ if (env)
+ free(envdup);
+
+ return (error);
+}
+
+/*
+ * Given either a shorthand or fully qualified path name look for a match
+ * against 'cmp'. The passed name will be expanded as needed for comparison
+ * purposes and redundant slashes stripped to ensure an accurate match.
+ */
+int
+zfs_strcmp_pathname(char *name, char *cmp, int wholedisk)
+{
+ int path_len, cmp_len;
+ char path_name[MAXPATHLEN];
+ char cmp_name[MAXPATHLEN];
+ char *dir;
+
+ /* Strip redundant slashes if one exists due to ZPOOL_IMPORT_PATH */
+ memset(cmp_name, 0, MAXPATHLEN);
+ dir = strtok(cmp, "/");
+ while (dir) {
+ strcat(cmp_name, "/");
+ strcat(cmp_name, dir);
+ dir = strtok(NULL, "/");
+ }
+
+ if (name[0] != '/')
+ return (zfs_strcmp_shortname(name, cmp_name, wholedisk));
+
+ (void) strlcpy(path_name, name, MAXPATHLEN);
+ path_len = strlen(path_name);
+ cmp_len = strlen(cmp_name);
+
+ if (wholedisk) {
+ path_len = zfs_append_partition(path_name, MAXPATHLEN);
+ if (path_len == -1)
+ return (ENOMEM);
+ }
+
+ if ((path_len != cmp_len) || strcmp(path_name, cmp_name))
+ return (ENOENT);
+
+ return (0);
}
/*
int
zfs_ioctl(libzfs_handle_t *hdl, int request, zfs_cmd_t *zc)
{
- int error;
-
- zc->zc_history = (uint64_t)(uintptr_t)hdl->libzfs_log_str;
- error = ioctl(hdl->libzfs_fd, request, zc);
- if (hdl->libzfs_log_str) {
- free(hdl->libzfs_log_str);
- hdl->libzfs_log_str = NULL;
- }
- zc->zc_history = 0;
-
- return (error);
+ return (ioctl(hdl->libzfs_fd, request, zc));
}
/*
break;
}
if (i == strlen(ends)) {
- zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
- "invalid numeric suffix '%s'"), buf);
+ if (hdl)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid numeric suffix '%s'"), buf);
return (-1);
}
/*
- * We want to allow trailing 'b' characters for 'GB' or 'Mb'. But don't
- * allow 'BB' - that's just weird.
+ * Allow 'G' = 'GB' = 'GiB', case-insensitively.
+ * However, 'BB' and 'BiB' are disallowed.
*/
- if (buf[1] == '\0' || (toupper(buf[1]) == 'B' && buf[2] == '\0' &&
- toupper(buf[0]) != 'B'))
- return (10*i);
-
- zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
- "invalid numeric suffix '%s'"), buf);
+ if (buf[1] == '\0' ||
+ (toupper(buf[0]) != 'B' &&
+ ((toupper(buf[1]) == 'B' && buf[2] == '\0') ||
+ (toupper(buf[1]) == 'I' && toupper(buf[2]) == 'B' &&
+ buf[3] == '\0'))))
+ return (10 * i);
+
+ if (hdl)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid numeric suffix '%s'"), buf);
return (-1);
}
"use 'none' to disable quota/refquota"));
goto error;
}
+
+ /*
+ * Special handling for "*_limit=none". In this case it's not
+ * 0 but UINT64_MAX.
+ */
+ if ((type & ZFS_TYPE_DATASET) && isnone &&
+ (prop == ZFS_PROP_FILESYSTEM_LIMIT ||
+ prop == ZFS_PROP_SNAPSHOT_LIMIT)) {
+ *ivalp = UINT64_MAX;
+ }
break;
case PROP_TYPE_INDEX:
prop = zprop_name_to_prop(propname, type);
- if (prop != ZPROP_INVAL && !zprop_valid_for_type(prop, type))
+ if (prop != ZPROP_INVAL && !zprop_valid_for_type(prop, type, B_FALSE))
prop = ZPROP_INVAL;
/*
* this is a pool property or if this isn't a user-defined
* dataset property,
*/
- if (prop == ZPROP_INVAL && (type == ZFS_TYPE_POOL ||
- (!zfs_prop_user(propname) && !zfs_prop_userquota(propname)))) {
+ if (prop == ZPROP_INVAL && ((type == ZFS_TYPE_POOL &&
+ !zpool_prop_feature(propname) &&
+ !zpool_prop_unsupported(propname)) ||
+ (type == ZFS_TYPE_DATASET && !zfs_prop_user(propname) &&
+ !zfs_prop_userquota(propname) && !zfs_prop_written(propname)))) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"invalid property '%s'"), propname);
return (zfs_error(hdl, EZFS_BADPROP,
entry->pl_prop = prop;
if (prop == ZPROP_INVAL) {
- if ((entry->pl_user_prop = zfs_strdup(hdl, propname)) == NULL) {
+ if ((entry->pl_user_prop = zfs_strdup(hdl, propname)) ==
+ NULL) {
free(entry);
return (-1);
}