]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - cmd/mount_zfs/mount_zfs.c
Add temporary mount options
[FreeBSD/FreeBSD.git] / cmd / mount_zfs / mount_zfs.c
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * 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.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21
22 /*
23  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright (c) 2011 Lawrence Livermore National Security, LLC.
25  */
26
27 #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 <locale.h>
35
36 #define ZS_COMMENT      0x00000000      /* comment */
37 #define ZS_ZFSUTIL      0x00000001      /* caller is zfs(8) */
38
39 libzfs_handle_t *g_zfs;
40
41 typedef struct option_map {
42         const char *name;
43         unsigned long mntmask;
44         unsigned long zfsmask;
45 } option_map_t;
46
47 static const option_map_t option_map[] = {
48         /* Canonicalized filesystem independent options from mount(8) */
49         { MNTOPT_NOAUTO,        MS_COMMENT,     ZS_COMMENT      },
50         { MNTOPT_DEFAULTS,      MS_COMMENT,     ZS_COMMENT      },
51         { MNTOPT_NODEVICES,     MS_NODEV,       ZS_COMMENT      },
52         { MNTOPT_DIRSYNC,       MS_DIRSYNC,     ZS_COMMENT      },
53         { MNTOPT_NOEXEC,        MS_NOEXEC,      ZS_COMMENT      },
54         { MNTOPT_GROUP,         MS_GROUP,       ZS_COMMENT      },
55         { MNTOPT_NETDEV,        MS_COMMENT,     ZS_COMMENT      },
56         { MNTOPT_NOFAIL,        MS_COMMENT,     ZS_COMMENT      },
57         { MNTOPT_NOSUID,        MS_NOSUID,      ZS_COMMENT      },
58         { MNTOPT_OWNER,         MS_OWNER,       ZS_COMMENT      },
59         { MNTOPT_REMOUNT,       MS_REMOUNT,     ZS_COMMENT      },
60         { MNTOPT_RO,            MS_RDONLY,      ZS_COMMENT      },
61         { MNTOPT_RW,            MS_COMMENT,     ZS_COMMENT      },
62         { MNTOPT_SYNC,          MS_SYNCHRONOUS, ZS_COMMENT      },
63         { MNTOPT_USER,          MS_USERS,       ZS_COMMENT      },
64         { MNTOPT_USERS,         MS_USERS,       ZS_COMMENT      },
65         /* acl flags passed with util-linux-2.24 mount command */
66         { MNTOPT_ACL,           MS_POSIXACL,    ZS_COMMENT      },
67         { MNTOPT_NOACL,         MS_COMMENT,     ZS_COMMENT      },
68         { MNTOPT_POSIXACL,      MS_POSIXACL,    ZS_COMMENT      },
69 #ifdef MS_NOATIME
70         { MNTOPT_NOATIME,       MS_NOATIME,     ZS_COMMENT      },
71 #endif
72 #ifdef MS_NODIRATIME
73         { MNTOPT_NODIRATIME,    MS_NODIRATIME,  ZS_COMMENT      },
74 #endif
75 #ifdef MS_RELATIME
76         { MNTOPT_RELATIME,      MS_RELATIME,    ZS_COMMENT      },
77 #endif
78 #ifdef MS_STRICTATIME
79         { MNTOPT_DFRATIME,      MS_STRICTATIME, ZS_COMMENT      },
80 #endif
81         { MNTOPT_CONTEXT,       MS_COMMENT,     ZS_COMMENT      },
82         { MNTOPT_FSCONTEXT,     MS_COMMENT,     ZS_COMMENT      },
83         { MNTOPT_DEFCONTEXT,    MS_COMMENT,     ZS_COMMENT      },
84         { MNTOPT_ROOTCONTEXT,   MS_COMMENT,     ZS_COMMENT      },
85 #ifdef MS_I_VERSION
86         { MNTOPT_IVERSION,      MS_I_VERSION,   ZS_COMMENT      },
87 #endif
88 #ifdef MS_MANDLOCK
89         { MNTOPT_NBMAND,        MS_MANDLOCK,    ZS_COMMENT      },
90 #endif
91         /* Valid options not found in mount(8) */
92         { MNTOPT_BIND,          MS_BIND,        ZS_COMMENT      },
93 #ifdef MS_REC
94         { MNTOPT_RBIND,         MS_BIND|MS_REC, ZS_COMMENT      },
95 #endif
96         { MNTOPT_COMMENT,       MS_COMMENT,     ZS_COMMENT      },
97 #ifdef MS_NOSUB
98         { MNTOPT_NOSUB,         MS_NOSUB,       ZS_COMMENT      },
99 #endif
100 #ifdef MS_SILENT
101         { MNTOPT_QUIET,         MS_SILENT,      ZS_COMMENT      },
102 #endif
103         /* Custom zfs options */
104         { MNTOPT_XATTR,         MS_COMMENT,     ZS_COMMENT      },
105         { MNTOPT_NOXATTR,       MS_COMMENT,     ZS_COMMENT      },
106         { MNTOPT_ZFSUTIL,       MS_COMMENT,     ZS_ZFSUTIL      },
107         { NULL,                 0,              0               } };
108
109 /*
110  * Break the mount option in to a name/value pair.  The name is
111  * validated against the option map and mount flags set accordingly.
112  */
113 static int
114 parse_option(char *mntopt, unsigned long *mntflags,
115     unsigned long *zfsflags, int sloppy)
116 {
117         const option_map_t *opt;
118         char *ptr, *name, *value = NULL;
119         int error = 0;
120
121         name = strdup(mntopt);
122         if (name == NULL)
123                 return (ENOMEM);
124
125         for (ptr = name; ptr && *ptr; ptr++) {
126                 if (*ptr == '=') {
127                         *ptr = '\0';
128                         value = ptr+1;
129                         VERIFY3P(value, !=, NULL);
130                         break;
131                 }
132         }
133
134         for (opt = option_map; opt->name != NULL; opt++) {
135                 if (strncmp(name, opt->name, strlen(name)) == 0) {
136                         *mntflags |= opt->mntmask;
137                         *zfsflags |= opt->zfsmask;
138                         error = 0;
139                         goto out;
140                 }
141         }
142
143         if (!sloppy)
144                 error = ENOENT;
145 out:
146         /* If required further process on the value may be done here */
147         free(name);
148         return (error);
149 }
150
151 /*
152  * Translate the mount option string in to MS_* mount flags for the
153  * kernel vfs.  When sloppy is non-zero unknown options will be ignored
154  * otherwise they are considered fatal are copied in to badopt.
155  */
156 static int
157 parse_options(char *mntopts, unsigned long *mntflags, unsigned long *zfsflags,
158     int sloppy, char *badopt, char *mtabopt)
159 {
160         int error = 0, quote = 0, flag = 0, count = 0;
161         char *ptr, *opt, *opts;
162
163         opts = strdup(mntopts);
164         if (opts == NULL)
165                 return (ENOMEM);
166
167         *mntflags = 0;
168         opt = NULL;
169
170         /*
171          * Scan through all mount options which must be comma delimited.
172          * We must be careful to notice regions which are double quoted
173          * and skip commas in these regions.  Each option is then checked
174          * to determine if it is a known option.
175          */
176         for (ptr = opts; ptr && !flag; ptr++) {
177                 if (opt == NULL)
178                         opt = ptr;
179
180                 if (*ptr == '"')
181                         quote = !quote;
182
183                 if (quote)
184                         continue;
185
186                 if (*ptr == '\0')
187                         flag = 1;
188
189                 if ((*ptr == ',') || (*ptr == '\0')) {
190                         *ptr = '\0';
191
192                         error = parse_option(opt, mntflags, zfsflags, sloppy);
193                         if (error) {
194                                 strcpy(badopt, opt);
195                                 goto out;
196
197                         }
198
199                         if (!(*mntflags & MS_REMOUNT) &&
200                             !(*zfsflags & ZS_ZFSUTIL)) {
201                                 if (count > 0)
202                                         strlcat(mtabopt, ",", MNT_LINE_MAX);
203
204                                 strlcat(mtabopt, opt, MNT_LINE_MAX);
205                                 count++;
206                         }
207
208                         opt = NULL;
209                 }
210         }
211
212 out:
213         free(opts);
214         return (error);
215 }
216
217 /*
218  * Return the pool/dataset to mount given the name passed to mount.  This
219  * is expected to be of the form pool/dataset, however may also refer to
220  * a block device if that device contains a valid zfs label.
221  */
222 static char *
223 parse_dataset(char *dataset)
224 {
225         char cwd[PATH_MAX];
226         struct stat64 statbuf;
227         int error;
228         int len;
229
230         /*
231          * We expect a pool/dataset to be provided, however if we're
232          * given a device which is a member of a zpool we attempt to
233          * extract the pool name stored in the label.  Given the pool
234          * name we can mount the root dataset.
235          */
236         error = stat64(dataset, &statbuf);
237         if (error == 0) {
238                 nvlist_t *config;
239                 char *name;
240                 int fd;
241
242                 fd = open(dataset, O_RDONLY);
243                 if (fd < 0)
244                         goto out;
245
246                 error = zpool_read_label(fd, &config, NULL);
247                 (void) close(fd);
248                 if (error)
249                         goto out;
250
251                 error = nvlist_lookup_string(config,
252                     ZPOOL_CONFIG_POOL_NAME, &name);
253                 if (error) {
254                         nvlist_free(config);
255                 } else {
256                         dataset = strdup(name);
257                         nvlist_free(config);
258                         return (dataset);
259                 }
260         }
261 out:
262         /*
263          * If a file or directory in your current working directory is
264          * named 'dataset' then mount(8) will prepend your current working
265          * directory to the dataset.  There is no way to prevent this
266          * behavior so we simply check for it and strip the prepended
267          * patch when it is added.
268          */
269         if (getcwd(cwd, PATH_MAX) == NULL)
270                 return (dataset);
271
272         len = strlen(cwd);
273
274         /* Do not add one when cwd already ends in a trailing '/' */
275         if (strncmp(cwd, dataset, len) == 0)
276                 return (dataset + len + (cwd[len-1] != '/'));
277
278         return (dataset);
279 }
280
281 /*
282  * Update the mtab_* code to use the libmount library when it is commonly
283  * available otherwise fallback to legacy mode.  The mount(8) utility will
284  * manage the lock file for us to prevent racing updates to /etc/mtab.
285  */
286 static int
287 mtab_is_writeable(void)
288 {
289         struct stat st;
290         int error, fd;
291
292         error = lstat(MNTTAB, &st);
293         if (error || S_ISLNK(st.st_mode))
294                 return (0);
295
296         fd = open(MNTTAB, O_RDWR | O_CREAT, 0644);
297         if (fd < 0)
298                 return (0);
299
300         close(fd);
301         return (1);
302 }
303
304 static int
305 mtab_update(char *dataset, char *mntpoint, char *type, char *mntopts)
306 {
307         struct mntent mnt;
308         FILE *fp;
309         int error;
310
311         mnt.mnt_fsname = dataset;
312         mnt.mnt_dir = mntpoint;
313         mnt.mnt_type = type;
314         mnt.mnt_opts = mntopts ? mntopts : "";
315         mnt.mnt_freq = 0;
316         mnt.mnt_passno = 0;
317
318         fp = setmntent(MNTTAB, "a+");
319         if (!fp) {
320                 (void) fprintf(stderr, gettext(
321                     "filesystem '%s' was mounted, but %s "
322                     "could not be opened due to error %d\n"),
323                     dataset, MNTTAB, errno);
324                 return (MOUNT_FILEIO);
325         }
326
327         error = addmntent(fp, &mnt);
328         if (error) {
329                 (void) fprintf(stderr, gettext(
330                     "filesystem '%s' was mounted, but %s "
331                     "could not be updated due to error %d\n"),
332                     dataset, MNTTAB, errno);
333                 return (MOUNT_FILEIO);
334         }
335
336         (void) endmntent(fp);
337
338         return (MOUNT_SUCCESS);
339 }
340
341 static void
342 append_mntopt(const char *name, const char *val, char *mntopts,
343     char *mtabopt, boolean_t quote)
344 {
345         char tmp[MNT_LINE_MAX];
346
347         snprintf(tmp, MNT_LINE_MAX, quote ? ",%s=\"%s\"" : ",%s=%s", name, val);
348
349         if (mntopts)
350                 strlcat(mntopts, tmp, MNT_LINE_MAX);
351
352         if (mtabopt)
353                 strlcat(mtabopt, tmp, MNT_LINE_MAX);
354 }
355
356 static void
357 zfs_selinux_setcontext(zfs_handle_t *zhp, zfs_prop_t zpt, const char *name,
358     char *mntopts, char *mtabopt)
359 {
360         char context[ZFS_MAXPROPLEN];
361
362         if (zfs_prop_get(zhp, zpt, context, sizeof (context),
363             NULL, NULL, 0, B_FALSE) == 0) {
364                 if (strcmp(context, "none") != 0)
365                     append_mntopt(name, context, mntopts, mtabopt, B_TRUE);
366         }
367 }
368
369 int
370 main(int argc, char **argv)
371 {
372         zfs_handle_t *zhp;
373         char prop[ZFS_MAXPROPLEN];
374         uint64_t zfs_version = 0;
375         char mntopts[MNT_LINE_MAX] = { '\0' };
376         char badopt[MNT_LINE_MAX] = { '\0' };
377         char mtabopt[MNT_LINE_MAX] = { '\0' };
378         char mntpoint[PATH_MAX];
379         char *dataset;
380         unsigned long mntflags = 0, zfsflags = 0, remount = 0;
381         int sloppy = 0, fake = 0, verbose = 0, nomtab = 0, zfsutil = 0;
382         int error, c;
383
384         (void) setlocale(LC_ALL, "");
385         (void) textdomain(TEXT_DOMAIN);
386
387         opterr = 0;
388
389         /* check options */
390         while ((c = getopt(argc, argv, "sfnvo:h?")) != -1) {
391                 switch (c) {
392                 case 's':
393                         sloppy = 1;
394                         break;
395                 case 'f':
396                         fake = 1;
397                         break;
398                 case 'n':
399                         nomtab = 1;
400                         break;
401                 case 'v':
402                         verbose++;
403                         break;
404                 case 'o':
405                         (void) strlcpy(mntopts, optarg, sizeof (mntopts));
406                         break;
407                 case 'h':
408                 case '?':
409                         (void) fprintf(stderr, gettext("Invalid option '%c'\n"),
410                             optopt);
411                         (void) fprintf(stderr, gettext("Usage: mount.zfs "
412                             "[-sfnv] [-o options] <dataset> <mountpoint>\n"));
413                         return (MOUNT_USAGE);
414                 }
415         }
416
417         argc -= optind;
418         argv += optind;
419
420         /* check that we only have two arguments */
421         if (argc != 2) {
422                 if (argc == 0)
423                         (void) fprintf(stderr, gettext("missing dataset "
424                             "argument\n"));
425                 else if (argc == 1)
426                         (void) fprintf(stderr,
427                             gettext("missing mountpoint argument\n"));
428                 else
429                         (void) fprintf(stderr, gettext("too many arguments\n"));
430                 (void) fprintf(stderr, "usage: mount <dataset> <mountpoint>\n");
431                 return (MOUNT_USAGE);
432         }
433
434         dataset = parse_dataset(argv[0]);
435
436         /* canonicalize the mount point */
437         if (realpath(argv[1], mntpoint) == NULL) {
438                 (void) fprintf(stderr, gettext("filesystem '%s' cannot be "
439                     "mounted at '%s' due to canonicalization error %d.\n"),
440                     dataset, argv[1], errno);
441                 return (MOUNT_SYSERR);
442         }
443
444         /* validate mount options and set mntflags */
445         error = parse_options(mntopts, &mntflags, &zfsflags, sloppy,
446             badopt, mtabopt);
447         if (error) {
448                 switch (error) {
449                 case ENOMEM:
450                         (void) fprintf(stderr, gettext("filesystem '%s' "
451                             "cannot be mounted due to a memory allocation "
452                             "failure.\n"), dataset);
453                         return (MOUNT_SYSERR);
454                 case ENOENT:
455                         (void) fprintf(stderr, gettext("filesystem '%s' "
456                             "cannot be mounted due to invalid option "
457                             "'%s'.\n"), dataset, badopt);
458                         (void) fprintf(stderr, gettext("Use the '-s' option "
459                             "to ignore the bad mount option.\n"));
460                         return (MOUNT_USAGE);
461                 default:
462                         (void) fprintf(stderr, gettext("filesystem '%s' "
463                             "cannot be mounted due to internal error %d.\n"),
464                             dataset, error);
465                         return (MOUNT_SOFTWARE);
466                 }
467         }
468
469         if (verbose)
470                 (void) fprintf(stdout, gettext("mount.zfs:\n"
471                     "  dataset:    \"%s\"\n  mountpoint: \"%s\"\n"
472                     "  mountflags: 0x%lx\n  zfsflags:   0x%lx\n"
473                     "  mountopts:  \"%s\"\n  mtabopts:   \"%s\"\n"),
474                     dataset, mntpoint, mntflags, zfsflags, mntopts, mtabopt);
475
476         if (mntflags & MS_REMOUNT) {
477                 nomtab = 1;
478                 remount = 1;
479         }
480
481         if (zfsflags & ZS_ZFSUTIL)
482                 zfsutil = 1;
483
484         if ((g_zfs = libzfs_init()) == NULL) {
485                 (void) fprintf(stderr, "%s", libzfs_error_init(errno));
486                 return (MOUNT_SYSERR);
487         }
488
489         /* try to open the dataset to access the mount point */
490         if ((zhp = zfs_open(g_zfs, dataset,
491             ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL) {
492                 (void) fprintf(stderr, gettext("filesystem '%s' cannot be "
493                     "mounted, unable to open the dataset\n"), dataset);
494                 libzfs_fini(g_zfs);
495                 return (MOUNT_USAGE);
496         }
497
498         /*
499          * Checks to see if the ZFS_PROP_SELINUX_CONTEXT exists
500          * if it does, create a tmp variable in case it's needed
501          * checks to see if the selinux context is set to the default
502          * if it is, allow the setting of the other context properties
503          * this is needed because the 'context' property overrides others
504          * if it is not the default, set the 'context' property
505          */
506         if (zfs_prop_get(zhp, ZFS_PROP_SELINUX_CONTEXT, prop, sizeof (prop),
507             NULL, NULL, 0, B_FALSE) == 0) {
508                 if (strcmp(prop, "none") == 0) {
509                         zfs_selinux_setcontext(zhp, ZFS_PROP_SELINUX_FSCONTEXT,
510                             MNTOPT_FSCONTEXT, mntopts, mtabopt);
511                         zfs_selinux_setcontext(zhp, ZFS_PROP_SELINUX_DEFCONTEXT,
512                             MNTOPT_DEFCONTEXT, mntopts, mtabopt);
513                         zfs_selinux_setcontext(zhp,
514                             ZFS_PROP_SELINUX_ROOTCONTEXT, MNTOPT_ROOTCONTEXT,
515                             mntopts, mtabopt);
516                 } else {
517                         append_mntopt(MNTOPT_CONTEXT, prop,
518                             mntopts, mtabopt, B_TRUE);
519                 }
520         }
521
522         /* A hint used to determine an auto-mounted snapshot mount point */
523         append_mntopt(MNTOPT_MNTPOINT, mntpoint, mntopts, NULL, B_FALSE);
524
525         /* treat all snapshots as legacy mount points */
526         if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT)
527                 (void) strlcpy(prop, ZFS_MOUNTPOINT_LEGACY, ZFS_MAXPROPLEN);
528         else
529                 (void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, prop,
530                     sizeof (prop), NULL, NULL, 0, B_FALSE);
531
532         /*
533          * Fetch the max supported zfs version in case we get ENOTSUP
534          * back from the mount command, since we need the zfs handle
535          * to do so.
536          */
537         zfs_version = zfs_prop_get_int(zhp, ZFS_PROP_VERSION);
538         if (zfs_version == 0) {
539                 fprintf(stderr, gettext("unable to fetch "
540                     "ZFS version for filesystem '%s'\n"), dataset);
541                 return (MOUNT_SYSERR);
542         }
543
544         zfs_close(zhp);
545         libzfs_fini(g_zfs);
546
547         /*
548          * Legacy mount points may only be mounted using 'mount', never using
549          * 'zfs mount'.  However, since 'zfs mount' actually invokes 'mount'
550          * we differentiate the two cases using the 'zfsutil' mount option.
551          * This mount option should only be supplied by the 'zfs mount' util.
552          *
553          * The only exception to the above rule is '-o remount' which is
554          * always allowed for non-legacy datasets.  This is done because when
555          * using zfs as your root file system both rc.sysinit/umountroot and
556          * systemd depend on 'mount -o remount <mountpoint>' to work.
557          */
558         if (zfsutil && (strcmp(prop, ZFS_MOUNTPOINT_LEGACY) == 0)) {
559                 (void) fprintf(stderr, gettext(
560                     "filesystem '%s' cannot be mounted using 'zfs mount'.\n"
561                     "Use 'zfs set mountpoint=%s' or 'mount -t zfs %s %s'.\n"
562                     "See zfs(8) for more information.\n"),
563                     dataset, mntpoint, dataset, mntpoint);
564                 return (MOUNT_USAGE);
565         }
566
567         if (!zfsutil && !(remount || fake) &&
568             strcmp(prop, ZFS_MOUNTPOINT_LEGACY)) {
569                 (void) fprintf(stderr, gettext(
570                     "filesystem '%s' cannot be mounted using 'mount'.\n"
571                     "Use 'zfs set mountpoint=%s' or 'zfs mount %s'.\n"
572                     "See zfs(8) for more information.\n"),
573                     dataset, "legacy", dataset);
574                 return (MOUNT_USAGE);
575         }
576
577         if (!fake) {
578                 error = mount(dataset, mntpoint, MNTTYPE_ZFS,
579                     mntflags, mntopts);
580         }
581
582         if (error) {
583                 switch (errno) {
584                 case ENOENT:
585                         (void) fprintf(stderr, gettext("mount point "
586                             "'%s' does not exist\n"), mntpoint);
587                         return (MOUNT_SYSERR);
588                 case EBUSY:
589                         (void) fprintf(stderr, gettext("filesystem "
590                             "'%s' is already mounted\n"), dataset);
591                         return (MOUNT_BUSY);
592                 case ENOTSUP:
593                         if (zfs_version > ZPL_VERSION) {
594                                 (void) fprintf(stderr,
595                                     gettext("filesystem '%s' (v%d) is not "
596                                     "supported by this implementation of "
597                                     "ZFS (max v%d).\n"), dataset,
598                                     (int) zfs_version, (int) ZPL_VERSION);
599                         } else {
600                                 (void) fprintf(stderr,
601                                     gettext("filesystem '%s' mount "
602                                     "failed for unknown reason.\n"), dataset);
603                         }
604                         return (MOUNT_SYSERR);
605                 default:
606                         (void) fprintf(stderr, gettext("filesystem "
607                             "'%s' can not be mounted due to error "
608                             "%d\n"), dataset, errno);
609                         return (MOUNT_USAGE);
610                 }
611         }
612
613         if (!nomtab && mtab_is_writeable()) {
614                 error = mtab_update(dataset, mntpoint, MNTTYPE_ZFS, mtabopt);
615                 if (error)
616                         return (error);
617         }
618
619         return (MOUNT_SUCCESS);
620 }