Path: blob/main/sys/contrib/openzfs/cmd/mount_zfs.c
48259 views
// SPDX-License-Identifier: CDDL-1.01/*2* CDDL HEADER START3*4* The contents of this file are subject to the terms of the5* Common Development and Distribution License (the "License").6* You may not use this file except in compliance with the License.7*8* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE9* or https://opensource.org/licenses/CDDL-1.0.10* See the License for the specific language governing permissions11* and limitations under the License.12*13* When distributing Covered Code, include this CDDL HEADER in each14* file and include the License file at usr/src/OPENSOLARIS.LICENSE.15* If applicable, add the following below this CDDL HEADER, with the16* fields enclosed by brackets "[]" replaced with your own identifying17* information: Portions Copyright [yyyy] [name of copyright owner]18*19* CDDL HEADER END20*/2122/*23* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.24* Copyright (c) 2011 Lawrence Livermore National Security, LLC.25*/2627#include <libintl.h>28#include <unistd.h>29#include <sys/file.h>30#include <sys/mount.h>31#include <sys/mntent.h>32#include <sys/stat.h>33#include <libzfs.h>34#include <libzutil.h>35#include <locale.h>36#include <getopt.h>37#include <fcntl.h>38#include <errno.h>3940#define ZS_COMMENT 0x00000000 /* comment */41#define ZS_ZFSUTIL 0x00000001 /* caller is zfs(8) */4243libzfs_handle_t *g_zfs;4445/*46* Opportunistically convert a target string into a pool name. If the47* string does not represent a block device with a valid zfs label48* then it is passed through without modification.49*/50static void51parse_dataset(const char *target, char **dataset)52{53/*54* Prior to util-linux 2.36.2, if a file or directory in the55* current working directory was named 'dataset' then mount(8)56* would prepend the current working directory to the dataset.57* Check for it and strip the prepended path when it is added.58*/59char cwd[PATH_MAX];60if (getcwd(cwd, PATH_MAX) == NULL) {61perror("getcwd");62return;63}64int len = strlen(cwd);65if (strncmp(cwd, target, len) == 0)66target += len;6768/* Assume pool/dataset is more likely */69strlcpy(*dataset, target, PATH_MAX);7071int fd = open(target, O_RDONLY | O_CLOEXEC);72if (fd < 0)73return;7475nvlist_t *cfg = NULL;76if (zpool_read_label(fd, &cfg, NULL) == 0) {77const char *nm = NULL;78if (!nvlist_lookup_string(cfg, ZPOOL_CONFIG_POOL_NAME, &nm))79strlcpy(*dataset, nm, PATH_MAX);80nvlist_free(cfg);81}8283if (close(fd))84perror("close");85}8687/*88* Update the mtab_* code to use the libmount library when it is commonly89* available otherwise fallback to legacy mode. The mount(8) utility will90* manage the lock file for us to prevent racing updates to /etc/mtab.91*/92static int93mtab_is_writeable(void)94{95struct stat st;96int error, fd;9798error = lstat("/etc/mtab", &st);99if (error || S_ISLNK(st.st_mode))100return (0);101102fd = open("/etc/mtab", O_RDWR | O_CREAT, 0644);103if (fd < 0)104return (0);105106close(fd);107return (1);108}109110static int111mtab_update(const char *dataset, const char *mntpoint, const char *type,112const char *mntopts)113{114struct mntent mnt;115FILE *fp;116int error;117118mnt.mnt_fsname = (char *)dataset;119mnt.mnt_dir = (char *)mntpoint;120mnt.mnt_type = (char *)type;121mnt.mnt_opts = (char *)(mntopts ?: "");122mnt.mnt_freq = 0;123mnt.mnt_passno = 0;124125fp = setmntent("/etc/mtab", "a+e");126if (!fp) {127(void) fprintf(stderr, gettext(128"filesystem '%s' was mounted, but /etc/mtab "129"could not be opened due to error: %s\n"),130dataset, strerror(errno));131return (MOUNT_FILEIO);132}133134error = addmntent(fp, &mnt);135if (error) {136(void) fprintf(stderr, gettext(137"filesystem '%s' was mounted, but /etc/mtab "138"could not be updated due to error: %s\n"),139dataset, strerror(errno));140return (MOUNT_FILEIO);141}142143(void) endmntent(fp);144145return (MOUNT_SUCCESS);146}147148int149main(int argc, char **argv)150{151zfs_handle_t *zhp;152char prop[ZFS_MAXPROPLEN];153uint64_t zfs_version = 0;154char mntopts[MNT_LINE_MAX] = { '\0' };155char badopt[MNT_LINE_MAX] = { '\0' };156char mtabopt[MNT_LINE_MAX] = { '\0' };157char mntpoint[PATH_MAX];158char dataset[PATH_MAX], *pdataset = dataset;159unsigned long mntflags = 0, zfsflags = 0, remount = 0;160int sloppy = 0, fake = 0, verbose = 0, nomtab = 0, zfsutil = 0;161int error, c;162163(void) setlocale(LC_ALL, "");164(void) setlocale(LC_NUMERIC, "C");165(void) textdomain(TEXT_DOMAIN);166167opterr = 0;168169/* check options */170while ((c = getopt_long(argc, argv, "sfnvo:h?", 0, 0)) != -1) {171switch (c) {172case 's':173sloppy = 1;174break;175case 'f':176fake = 1;177break;178case 'n':179nomtab = 1;180break;181case 'v':182verbose++;183break;184case 'o':185(void) strlcpy(mntopts, optarg, sizeof (mntopts));186break;187case 'h':188case '?':189if (optopt)190(void) fprintf(stderr,191gettext("Invalid option '%c'\n"), optopt);192(void) fprintf(stderr, gettext("Usage: mount.zfs "193"[-sfnvh] [-o options] <dataset> <mountpoint>\n"));194return (MOUNT_USAGE);195}196}197198argc -= optind;199argv += optind;200201/* check that we only have two arguments */202if (argc != 2) {203if (argc == 0)204(void) fprintf(stderr, gettext("missing dataset "205"argument\n"));206else if (argc == 1)207(void) fprintf(stderr,208gettext("missing mountpoint argument\n"));209else210(void) fprintf(stderr, gettext("too many arguments\n"));211(void) fprintf(stderr, "usage: mount <dataset> <mountpoint>\n");212return (MOUNT_USAGE);213}214215parse_dataset(argv[0], &pdataset);216217/* canonicalize the mount point */218if (realpath(argv[1], mntpoint) == NULL) {219(void) fprintf(stderr, gettext("filesystem '%s' cannot be "220"mounted at '%s' due to canonicalization error: %s\n"),221dataset, argv[1], strerror(errno));222return (MOUNT_SYSERR);223}224225/* validate mount options and set mntflags */226error = zfs_parse_mount_options(mntopts, &mntflags, &zfsflags, sloppy,227badopt, mtabopt);228if (error) {229switch (error) {230case ENOMEM:231(void) fprintf(stderr, gettext("filesystem '%s' "232"cannot be mounted due to a memory allocation "233"failure.\n"), dataset);234return (MOUNT_SYSERR);235case ENOENT:236(void) fprintf(stderr, gettext("filesystem '%s' "237"cannot be mounted due to invalid option "238"'%s'.\n"), dataset, badopt);239(void) fprintf(stderr, gettext("Use the '-s' option "240"to ignore the bad mount option.\n"));241return (MOUNT_USAGE);242default:243(void) fprintf(stderr, gettext("filesystem '%s' "244"cannot be mounted due to internal error %d.\n"),245dataset, error);246return (MOUNT_SOFTWARE);247}248}249250if (mntflags & MS_REMOUNT) {251nomtab = 1;252remount = 1;253}254255if (zfsflags & ZS_ZFSUTIL)256zfsutil = 1;257258if ((g_zfs = libzfs_init()) == NULL) {259(void) fprintf(stderr, "%s\n", libzfs_error_init(errno));260return (MOUNT_SYSERR);261}262263/* try to open the dataset to access the mount point */264if ((zhp = zfs_open(g_zfs, dataset,265ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL) {266(void) fprintf(stderr, gettext("filesystem '%s' cannot be "267"mounted, unable to open the dataset\n"), dataset);268libzfs_fini(g_zfs);269return (MOUNT_USAGE);270}271272if (sloppy || libzfs_envvar_is_set("ZFS_MOUNT_HELPER")) {273zfs_adjust_mount_options(zhp, mntpoint, mntopts, mtabopt);274}275276/* treat all snapshots as legacy mount points */277if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT)278(void) strlcpy(prop, ZFS_MOUNTPOINT_LEGACY, ZFS_MAXPROPLEN);279else280(void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, prop,281sizeof (prop), NULL, NULL, 0, B_FALSE);282283/*284* Fetch the max supported zfs version in case we get ENOTSUP285* back from the mount command, since we need the zfs handle286* to do so.287*/288zfs_version = zfs_prop_get_int(zhp, ZFS_PROP_VERSION);289if (zfs_version == 0) {290fprintf(stderr, gettext("unable to fetch "291"ZFS version for filesystem '%s'\n"), dataset);292zfs_close(zhp);293libzfs_fini(g_zfs);294return (MOUNT_SYSERR);295}296297/*298* Legacy mount points may only be mounted using 'mount', never using299* 'zfs mount'. However, since 'zfs mount' actually invokes 'mount'300* we differentiate the two cases using the 'zfsutil' mount option.301* This mount option should only be supplied by the 'zfs mount' util.302*303* The only exception to the above rule is '-o remount' which is304* always allowed for non-legacy datasets. This is done because when305* using zfs as your root file system both rc.sysinit/umountroot and306* systemd depend on 'mount -o remount <mountpoint>' to work.307*/308if (zfsutil && (strcmp(prop, ZFS_MOUNTPOINT_LEGACY) == 0)) {309(void) fprintf(stderr, gettext(310"filesystem '%s' cannot be mounted using 'zfs mount'.\n"311"Use 'zfs set mountpoint=%s' or 'mount -t zfs %s %s'.\n"312"See zfs(8) for more information.\n"),313dataset, mntpoint, dataset, mntpoint);314zfs_close(zhp);315libzfs_fini(g_zfs);316return (MOUNT_USAGE);317}318319if (!zfsutil && !(remount || fake) &&320strcmp(prop, ZFS_MOUNTPOINT_LEGACY)) {321(void) fprintf(stderr, gettext(322"filesystem '%s' cannot be mounted using 'mount'.\n"323"Use 'zfs set mountpoint=%s' or 'zfs mount %s'.\n"324"See zfs(8) for more information.\n"),325dataset, "legacy", dataset);326zfs_close(zhp);327libzfs_fini(g_zfs);328return (MOUNT_USAGE);329}330331if (verbose)332(void) fprintf(stdout, gettext("mount.zfs:\n"333" dataset: \"%s\"\n mountpoint: \"%s\"\n"334" mountflags: 0x%lx\n zfsflags: 0x%lx\n"335" mountopts: \"%s\"\n mtabopts: \"%s\"\n"),336dataset, mntpoint, mntflags, zfsflags, mntopts, mtabopt);337338if (!fake) {339if (!remount && !sloppy &&340!libzfs_envvar_is_set("ZFS_MOUNT_HELPER")) {341error = zfs_mount_at(zhp, mntopts, mntflags, mntpoint);342if (error) {343(void) fprintf(stderr, "zfs_mount_at() failed: "344"%s", libzfs_error_description(g_zfs));345zfs_close(zhp);346libzfs_fini(g_zfs);347return (MOUNT_SYSERR);348}349} else {350error = mount(dataset, mntpoint, MNTTYPE_ZFS,351mntflags, mntopts);352}353}354355zfs_close(zhp);356libzfs_fini(g_zfs);357358if (error) {359switch (errno) {360case ENOENT:361(void) fprintf(stderr, gettext("mount point "362"'%s' does not exist\n"), mntpoint);363return (MOUNT_SYSERR);364case EBUSY:365(void) fprintf(stderr, gettext("filesystem "366"'%s' is already mounted\n"), dataset);367return (MOUNT_BUSY);368case ENOTSUP:369if (zfs_version > ZPL_VERSION) {370(void) fprintf(stderr,371gettext("filesystem '%s' (v%d) is not "372"supported by this implementation of "373"ZFS (max v%d).\n"), dataset,374(int)zfs_version, (int)ZPL_VERSION);375} else {376(void) fprintf(stderr,377gettext("filesystem '%s' mount "378"failed for unknown reason.\n"), dataset);379}380return (MOUNT_SYSERR);381#ifdef MS_MANDLOCK382case EPERM:383if (mntflags & MS_MANDLOCK) {384(void) fprintf(stderr, gettext("filesystem "385"'%s' has the 'nbmand=on' property set, "386"this mount\noption may be disabled in "387"your kernel. Use 'zfs set nbmand=off'\n"388"to disable this option and try to "389"mount the filesystem again.\n"), dataset);390return (MOUNT_SYSERR);391}392#endif393zfs_fallthrough;394default:395(void) fprintf(stderr, gettext("filesystem "396"'%s' can not be mounted: %s\n"), dataset,397strerror(errno));398return (MOUNT_USAGE);399}400}401402if (!nomtab && mtab_is_writeable()) {403error = mtab_update(dataset, mntpoint, MNTTYPE_ZFS, mtabopt);404if (error)405return (error);406}407408return (MOUNT_SUCCESS);409}410411412