mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-26 20:22:14 +03:00
Fix mount helper
Several issues related to strange mount/umount behavior were reported and this commit should address most of them. The original idea was to put in place a zfs mount helper (mount.zfs). This helper is used to enforce 'legacy' mount behavior, and perform any extra mount argument processing (selinux, zfsutil, etc). This helper wasn't ready for the 0.6.0-rc1 release but with this change it's functional but needs to extensively tested. This change addresses the following open issues. Closes #101 Closes #107 Closes #113 Closes #115 Closes #119
This commit is contained in:
+52
-432
@@ -3827,371 +3827,6 @@ zfs_do_python(int argc, char **argv)
|
||||
return (-1);
|
||||
}
|
||||
|
||||
typedef struct option_map {
|
||||
const char *name;
|
||||
int mask;
|
||||
} option_map_t;
|
||||
|
||||
static const option_map_t option_map[] = {
|
||||
/* Canonicalized filesystem independent options from mount(8) */
|
||||
{ MNTOPT_NOAUTO, MS_COMMENT },
|
||||
{ MNTOPT_DEFAULTS, MS_COMMENT },
|
||||
{ MNTOPT_NODEVICES, MS_NODEV },
|
||||
{ MNTOPT_DIRSYNC, MS_DIRSYNC },
|
||||
{ MNTOPT_NOEXEC, MS_NOEXEC },
|
||||
{ MNTOPT_GROUP, MS_GROUP },
|
||||
{ MNTOPT_NETDEV, MS_COMMENT },
|
||||
{ MNTOPT_NOFAIL, MS_COMMENT },
|
||||
{ MNTOPT_NOSUID, MS_NOSUID },
|
||||
{ MNTOPT_OWNER, MS_OWNER },
|
||||
{ MNTOPT_REMOUNT, MS_REMOUNT },
|
||||
{ MNTOPT_RO, MS_RDONLY },
|
||||
{ MNTOPT_SYNC, MS_SYNCHRONOUS },
|
||||
{ MNTOPT_USER, MS_USERS },
|
||||
{ MNTOPT_USERS, MS_USERS },
|
||||
#ifdef MS_NOATIME
|
||||
{ MNTOPT_NOATIME, MS_NOATIME },
|
||||
#endif
|
||||
#ifdef MS_NODIRATIME
|
||||
{ MNTOPT_NODIRATIME, MS_NODIRATIME },
|
||||
#endif
|
||||
#ifdef MS_RELATIME
|
||||
{ MNTOPT_RELATIME, MS_RELATIME },
|
||||
#endif
|
||||
#ifdef MS_STRICTATIME
|
||||
{ MNTOPT_DFRATIME, MS_STRICTATIME },
|
||||
#endif
|
||||
#ifdef HAVE_SELINUX
|
||||
{ MNTOPT_CONTEXT, MS_COMMENT },
|
||||
{ MNTOPT_FSCONTEXT, MS_COMMENT },
|
||||
{ MNTOPT_DEFCONTEXT, MS_COMMENT },
|
||||
{ MNTOPT_ROOTCONTEXT, MS_COMMENT },
|
||||
#endif
|
||||
#ifdef MS_I_VERSION
|
||||
{ MNTOPT_IVERSION, MS_I_VERSION },
|
||||
#endif
|
||||
#ifdef MS_MANDLOCK
|
||||
{ MNTOPT_NBMAND, MS_MANDLOCK },
|
||||
#endif
|
||||
/* Valid options not found in mount(8) */
|
||||
{ MNTOPT_BIND, MS_BIND },
|
||||
#ifdef MS_REC
|
||||
{ MNTOPT_RBIND, MS_BIND|MS_REC },
|
||||
#endif
|
||||
{ MNTOPT_COMMENT, MS_COMMENT },
|
||||
{ MNTOPT_BOOTWAIT, MS_COMMENT },
|
||||
{ MNTOPT_NOBOOTWAIT, MS_COMMENT },
|
||||
{ MNTOPT_OPTIONAL, MS_COMMENT },
|
||||
{ MNTOPT_SHOWTHROUGH, MS_COMMENT },
|
||||
#ifdef MS_NOSUB
|
||||
{ MNTOPT_NOSUB, MS_NOSUB },
|
||||
#endif
|
||||
#ifdef MS_SILENT
|
||||
{ MNTOPT_QUIET, MS_SILENT },
|
||||
#endif
|
||||
/* Custom zfs options */
|
||||
{ MNTOPT_NOXATTR, MS_COMMENT },
|
||||
{ NULL, 0 } };
|
||||
|
||||
/*
|
||||
* Break the mount option in to a name/value pair. The name is
|
||||
* validated against the option map and mount flags set accordingly.
|
||||
*/
|
||||
static int
|
||||
parse_option(char *mntopt, unsigned long *mntflags, int sloppy)
|
||||
{
|
||||
const option_map_t *opt;
|
||||
char *ptr, *name, *value = NULL;
|
||||
int rc = 0;
|
||||
|
||||
name = strdup(mntopt);
|
||||
if (name == NULL)
|
||||
return (ENOMEM);
|
||||
|
||||
for (ptr = name; ptr && *ptr; ptr++) {
|
||||
if (*ptr == '=') {
|
||||
*ptr = '\0';
|
||||
value = ptr+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (opt = option_map; opt->name != NULL; opt++) {
|
||||
if (strncmp(name, opt->name, strlen(name)) == 0) {
|
||||
*mntflags |= opt->mask;
|
||||
|
||||
/* MS_USERS implies default user options */
|
||||
if (opt->mask & (MS_USERS))
|
||||
*mntflags |= (MS_NOEXEC|MS_NOSUID|MS_NODEV);
|
||||
|
||||
/* MS_OWNER|MS_GROUP imply default owner options */
|
||||
if (opt->mask & (MS_OWNER | MS_GROUP))
|
||||
*mntflags |= (MS_NOSUID|MS_NODEV);
|
||||
|
||||
rc = 0;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sloppy)
|
||||
rc = ENOENT;
|
||||
out:
|
||||
/* If required further process on the value may be done here */
|
||||
free(name);
|
||||
return (rc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Translate the mount option string in to MS_* mount flags for the
|
||||
* kernel vfs. When sloppy is non-zero unknown options will be ignored
|
||||
* otherwise they are considered fatal are copied in to badopt.
|
||||
*/
|
||||
static int
|
||||
parse_options(char *mntopts, unsigned long *mntflags, int sloppy, char *badopt)
|
||||
{
|
||||
int rc = 0, quote = 0;
|
||||
char *ptr, *opt, *opts;
|
||||
|
||||
opts = strdup(mntopts);
|
||||
if (opts == NULL)
|
||||
return (ENOMEM);
|
||||
|
||||
*mntflags = 0;
|
||||
opt = NULL;
|
||||
|
||||
/*
|
||||
* Scan through all mount options which must be comma delimited.
|
||||
* We must be careful to notice regions which are double quoted
|
||||
* and skip commas in these regions. Each option is then checked
|
||||
* to determine if it is a known option.
|
||||
*/
|
||||
for (ptr = opts; ptr && *ptr; ptr++) {
|
||||
if (opt == NULL)
|
||||
opt = ptr;
|
||||
|
||||
if (*ptr == '"')
|
||||
quote = !quote;
|
||||
|
||||
if (quote)
|
||||
continue;
|
||||
|
||||
if ((*ptr == ',') || (*ptr == '\0')) {
|
||||
*ptr = '\0';
|
||||
rc = parse_option(opt, mntflags, sloppy);
|
||||
if (rc) {
|
||||
strcpy(badopt, opt);
|
||||
goto out;
|
||||
}
|
||||
|
||||
opt = NULL;
|
||||
}
|
||||
}
|
||||
out:
|
||||
free(opts);
|
||||
return (rc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when invoked as /sbin/mount.zfs, mount helper for mount(8).
|
||||
*/
|
||||
static int
|
||||
manual_mount(int argc, char **argv)
|
||||
{
|
||||
zfs_handle_t *zhp;
|
||||
char legacy[ZFS_MAXPROPLEN];
|
||||
char mntopts[MNT_LINE_MAX] = { '\0' };
|
||||
char badopt[MNT_LINE_MAX] = { '\0' };
|
||||
char *dataset, *mntpoint;
|
||||
unsigned long mntflags;
|
||||
int sloppy = 0, fake = 0, verbose = 0;
|
||||
int rc, c;
|
||||
|
||||
/* check options */
|
||||
while ((c = getopt(argc, argv, "sfnvo:h?")) != -1) {
|
||||
switch (c) {
|
||||
case 's':
|
||||
sloppy = 1;
|
||||
break;
|
||||
case 'f':
|
||||
fake = 1;
|
||||
break;
|
||||
case 'n':
|
||||
/* Ignored, handled by mount(8) */
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
case 'o':
|
||||
(void) strlcpy(mntopts, optarg, sizeof (mntopts));
|
||||
break;
|
||||
case 'h':
|
||||
case '?':
|
||||
(void) fprintf(stderr, gettext("Invalid option '%c'\n"),
|
||||
optopt);
|
||||
(void) fprintf(stderr, gettext("Usage: mount.zfs "
|
||||
"[-sfnv] [-o options] <dataset> <mountpoint>\n"));
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* check that we only have two arguments */
|
||||
if (argc != 2) {
|
||||
if (argc == 0)
|
||||
(void) fprintf(stderr, gettext("missing dataset "
|
||||
"argument\n"));
|
||||
else if (argc == 1)
|
||||
(void) fprintf(stderr,
|
||||
gettext("missing mountpoint argument\n"));
|
||||
else
|
||||
(void) fprintf(stderr, gettext("too many arguments\n"));
|
||||
(void) fprintf(stderr, "usage: mount <dataset> <mountpoint>\n");
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
|
||||
dataset = argv[0];
|
||||
mntpoint = argv[1];
|
||||
|
||||
/* try to open the dataset to access the mount point */
|
||||
if ((zhp = zfs_open(g_zfs, dataset, ZFS_TYPE_FILESYSTEM)) == NULL) {
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' cannot be "
|
||||
"mounted, unable to open the dataset\n"), dataset);
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
|
||||
(void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, legacy,
|
||||
sizeof (legacy), NULL, NULL, 0, B_FALSE);
|
||||
|
||||
zfs_close(zhp);
|
||||
|
||||
/* check for legacy mountpoint or util mount option */
|
||||
if ((!strcmp(legacy, ZFS_MOUNTPOINT_LEGACY) == 0) &&
|
||||
(strstr(mntopts, MNTOPT_ZFSUTIL) == NULL)) {
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' cannot be "
|
||||
"mounted using 'mount -a -t zfs'\n"), dataset);
|
||||
(void) fprintf(stderr, gettext("Use 'zfs set mountpoint=%s' "
|
||||
"instead.\n"), mntpoint);
|
||||
(void) fprintf(stderr, gettext("If you must use 'mount -a -t "
|
||||
"zfs' or /etc/fstab, use 'zfs set mountpoint=legacy'.\n"));
|
||||
(void) fprintf(stderr, gettext("See zfs(8) for more "
|
||||
"information.\n"));
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
|
||||
/* validate mount options and set mntflags */
|
||||
rc = parse_options(mntopts, &mntflags, sloppy, badopt);
|
||||
if (rc) {
|
||||
switch (rc) {
|
||||
case ENOMEM:
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' "
|
||||
"cannot be mounted due to a memory allocation "
|
||||
"failure\n"), dataset);
|
||||
return (MOUNT_SYSERR);
|
||||
case EINVAL:
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' "
|
||||
"cannot be mounted of due to the invalid option "
|
||||
"'%s'\n"), dataset, badopt);
|
||||
(void) fprintf(stderr, gettext("Use the '-s' option "
|
||||
"to ignore the bad mount option.\n"));
|
||||
return (MOUNT_USAGE);
|
||||
default:
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' "
|
||||
"cannot be mounted due to internal error %d\n"),
|
||||
dataset, rc);
|
||||
return (MOUNT_SOFTWARE);
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose > 2)
|
||||
printf("mount.zfs: dataset: \"%s\", mountpoint: \"%s\" "
|
||||
"mountflags: 0x%lx, mountopts: \"%s\"\n", dataset,
|
||||
mntpoint, mntflags, mntopts);
|
||||
|
||||
/* load the zfs posix layer module (zpl) */
|
||||
if (libzfs_load_module("zpl")) {
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' cannot be "
|
||||
"mounted without the zpl kernel module\n"), dataset);
|
||||
(void) fprintf(stderr, gettext("Use 'dmesg' to determine why "
|
||||
"the module could not be loaded.\n"));
|
||||
return (MOUNT_SYSERR);
|
||||
}
|
||||
|
||||
if (!fake) {
|
||||
rc = mount(dataset, mntpoint, MNTTYPE_ZFS, mntflags, mntopts);
|
||||
if (rc) {
|
||||
(void) fprintf(stderr, gettext("filesystem '%s' can"
|
||||
"not be mounted due to error %d\n"), dataset, rc);
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
return (MOUNT_SUCCESS);
|
||||
}
|
||||
|
||||
#ifdef HAVE_UNMOUNT_HELPER
|
||||
/*
|
||||
* Called when invoked as /sbin/umount.zfs, mount helper for mount(8).
|
||||
* Unlike a manual mount, we allow unmounts of non-legacy filesystems,
|
||||
* as this is the dominant administrative interface.
|
||||
*/
|
||||
static int
|
||||
manual_unmount(int argc, char **argv)
|
||||
{
|
||||
int verbose = 0, flags = 0;
|
||||
int c;
|
||||
|
||||
/* check options */
|
||||
while ((c = getopt(argc, argv, "nlfvrh?")) != -1) {
|
||||
switch (c) {
|
||||
case 'n':
|
||||
/* Ignored, handled by mount(8) */
|
||||
break;
|
||||
case 'l':
|
||||
flags = MS_DETACH;
|
||||
break;
|
||||
case 'f':
|
||||
flags = MS_FORCE;
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
case 'r':
|
||||
/* Remount read-only on umount failure, unsupported */
|
||||
(void) fprintf(stderr, gettext("Unsupported option "
|
||||
"'%c'\n"), optopt);
|
||||
return (MOUNT_USAGE);
|
||||
case 'h':
|
||||
case '?':
|
||||
(void) fprintf(stderr, gettext("Invalid option '%c'\n"),
|
||||
optopt);
|
||||
(void) fprintf(stderr, gettext("Usage: umount.zfs "
|
||||
"[-nlfvr] <mountpoint>\n"));
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* check that we only have one argument */
|
||||
if (argc != 1) {
|
||||
if (argc == 0)
|
||||
(void) fprintf(stderr, gettext("missing mountpoint "
|
||||
"argument\n"));
|
||||
else
|
||||
(void) fprintf(stderr, gettext("too many arguments\n"));
|
||||
|
||||
(void) fprintf(stderr, gettext("Usage: umount.zfs [-nlfvr] "
|
||||
"<mountpoint>\n"));
|
||||
return (MOUNT_USAGE);
|
||||
}
|
||||
|
||||
return (unshare_unmount_path(OP_MOUNT, argv[0], flags, B_TRUE));
|
||||
}
|
||||
#endif /* HAVE_UNMOUNT_HELPER */
|
||||
|
||||
static int
|
||||
find_command_idx(char *command, int *idx)
|
||||
{
|
||||
@@ -4289,7 +3924,6 @@ main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
int i = 0;
|
||||
char *progname;
|
||||
char *cmdname;
|
||||
|
||||
(void) setlocale(LC_ALL, "");
|
||||
@@ -4304,78 +3938,64 @@ main(int argc, char **argv)
|
||||
}
|
||||
|
||||
/*
|
||||
* This command also doubles as the /etc/fs mount and unmount program.
|
||||
* Determine if we should take this behavior based on argv[0].
|
||||
* Make sure the user has specified some command.
|
||||
*/
|
||||
progname = basename(argv[0]);
|
||||
if (strcmp(progname, "mount.zfs") == 0) {
|
||||
ret = manual_mount(argc, argv);
|
||||
#ifdef HAVE_UNMOUNT_HELPER
|
||||
} else if (strcmp(progname, "umount.zfs") == 0) {
|
||||
ret = manual_unmount(argc, argv);
|
||||
#endif /* HAVE_UNMOUNT_HELPER */
|
||||
} else {
|
||||
/*
|
||||
* Make sure the user has specified some command.
|
||||
*/
|
||||
if (argc < 2) {
|
||||
(void) fprintf(stderr, gettext("missing command\n"));
|
||||
usage(B_FALSE);
|
||||
}
|
||||
|
||||
cmdname = argv[1];
|
||||
|
||||
/*
|
||||
* The 'umount' command is an alias for 'unmount'
|
||||
*/
|
||||
if (strcmp(cmdname, "umount") == 0)
|
||||
cmdname = "unmount";
|
||||
|
||||
/*
|
||||
* The 'recv' command is an alias for 'receive'
|
||||
*/
|
||||
if (strcmp(cmdname, "recv") == 0)
|
||||
cmdname = "receive";
|
||||
|
||||
/*
|
||||
* Special case '-?'
|
||||
*/
|
||||
if ((strcmp(cmdname, "-?") == 0) ||
|
||||
(strcmp(cmdname, "--help") == 0))
|
||||
usage(B_TRUE);
|
||||
|
||||
if ((g_zfs = libzfs_init()) == NULL)
|
||||
return (1);
|
||||
|
||||
zpool_set_history_str("zfs", argc, argv, history_str);
|
||||
verify(zpool_stage_history(g_zfs, history_str) == 0);
|
||||
|
||||
libzfs_print_on_error(g_zfs, B_TRUE);
|
||||
|
||||
/*
|
||||
* 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);
|
||||
} else if (strchr(cmdname, '=') != NULL) {
|
||||
verify(find_command_idx("set", &i) == 0);
|
||||
current_command = &command_table[i];
|
||||
ret = command_table[i].func(argc, argv);
|
||||
} else {
|
||||
(void) fprintf(stderr, gettext("unrecognized "
|
||||
"command '%s'\n"), cmdname);
|
||||
usage(B_FALSE);
|
||||
ret = 1;
|
||||
}
|
||||
libzfs_mnttab_cache(g_zfs, B_FALSE);
|
||||
if (argc < 2) {
|
||||
(void) fprintf(stderr, gettext("missing command\n"));
|
||||
usage(B_FALSE);
|
||||
}
|
||||
|
||||
(void) fclose(mnttab_file);
|
||||
cmdname = argv[1];
|
||||
|
||||
/*
|
||||
* The 'umount' command is an alias for 'unmount'
|
||||
*/
|
||||
if (strcmp(cmdname, "umount") == 0)
|
||||
cmdname = "unmount";
|
||||
|
||||
/*
|
||||
* The 'recv' command is an alias for 'receive'
|
||||
*/
|
||||
if (strcmp(cmdname, "recv") == 0)
|
||||
cmdname = "receive";
|
||||
|
||||
/*
|
||||
* Special case '-?'
|
||||
*/
|
||||
if ((strcmp(cmdname, "-?") == 0) ||
|
||||
(strcmp(cmdname, "--help") == 0))
|
||||
usage(B_TRUE);
|
||||
|
||||
if ((g_zfs = libzfs_init()) == NULL)
|
||||
return (1);
|
||||
|
||||
zpool_set_history_str("zfs", argc, argv, history_str);
|
||||
verify(zpool_stage_history(g_zfs, history_str) == 0);
|
||||
|
||||
libzfs_print_on_error(g_zfs, B_TRUE);
|
||||
|
||||
/*
|
||||
* 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);
|
||||
} else if (strchr(cmdname, '=') != NULL) {
|
||||
verify(find_command_idx("set", &i) == 0);
|
||||
current_command = &command_table[i];
|
||||
ret = command_table[i].func(argc, argv);
|
||||
} else {
|
||||
(void) fprintf(stderr, gettext("unrecognized "
|
||||
"command '%s'\n"), cmdname);
|
||||
usage(B_FALSE);
|
||||
ret = 1;
|
||||
}
|
||||
libzfs_mnttab_cache(g_zfs, B_FALSE);
|
||||
libzfs_fini(g_zfs);
|
||||
|
||||
(void) fclose(mnttab_file);
|
||||
|
||||
/*
|
||||
* The 'ZFS_ABORT' environment variable causes us to dump core on exit
|
||||
* for the purposes of running ::findleaks.
|
||||
|
||||
Reference in New Issue
Block a user