27a9014b3953894afd6e632892641d8ba09ac38b
[zfs.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/stat.h>
32 #include <libzfs.h>
33 #include <locale.h>
34 #ifdef HAVE_LIBSELINUX
35 #include <selinux/selinux.h>
36 #endif /* HAVE_LIBSELINUX */
37
38 libzfs_handle_t *g_zfs;
39
40 typedef struct option_map {
41         const char *name;
42         unsigned long mntmask;
43         unsigned long zfsmask;
44 } option_map_t;
45
46 static const option_map_t option_map[] = {
47         /* Canonicalized filesystem independent options from mount(8) */
48         { MNTOPT_NOAUTO,        MS_COMMENT,     ZS_COMMENT      },
49         { MNTOPT_DEFAULTS,      MS_COMMENT,     ZS_COMMENT      },
50         { MNTOPT_NODEVICES,     MS_NODEV,       ZS_COMMENT      },
51         { MNTOPT_DIRSYNC,       MS_DIRSYNC,     ZS_COMMENT      },
52         { MNTOPT_NOEXEC,        MS_NOEXEC,      ZS_COMMENT      },
53         { MNTOPT_GROUP,         MS_GROUP,       ZS_COMMENT      },
54         { MNTOPT_NETDEV,        MS_COMMENT,     ZS_COMMENT      },
55         { MNTOPT_NOFAIL,        MS_COMMENT,     ZS_COMMENT      },
56         { MNTOPT_NOSUID,        MS_NOSUID,      ZS_COMMENT      },
57         { MNTOPT_OWNER,         MS_OWNER,       ZS_COMMENT      },
58         { MNTOPT_REMOUNT,       MS_REMOUNT,     ZS_COMMENT      },
59         { MNTOPT_RO,            MS_RDONLY,      ZS_COMMENT      },
60         { MNTOPT_RW,            MS_COMMENT,     ZS_COMMENT      },
61         { MNTOPT_SYNC,          MS_SYNCHRONOUS, ZS_COMMENT      },
62         { MNTOPT_USER,          MS_USERS,       ZS_COMMENT      },
63         { MNTOPT_USERS,         MS_USERS,       ZS_COMMENT      },
64 #ifdef MS_NOATIME
65         { MNTOPT_NOATIME,       MS_NOATIME,     ZS_COMMENT      },
66 #endif
67 #ifdef MS_NODIRATIME
68         { MNTOPT_NODIRATIME,    MS_NODIRATIME,  ZS_COMMENT      },
69 #endif
70 #ifdef MS_RELATIME
71         { MNTOPT_RELATIME,      MS_RELATIME,    ZS_COMMENT      },
72 #endif
73 #ifdef MS_STRICTATIME
74         { MNTOPT_DFRATIME,      MS_STRICTATIME, ZS_COMMENT      },
75 #endif
76         { MNTOPT_CONTEXT,       MS_COMMENT,     ZS_NOCONTEXT    },
77         { MNTOPT_NOCONTEXT,     MS_COMMENT,     ZS_NOCONTEXT    },
78         { MNTOPT_FSCONTEXT,     MS_COMMENT,     ZS_NOCONTEXT    },
79         { MNTOPT_DEFCONTEXT,    MS_COMMENT,     ZS_NOCONTEXT    },
80         { MNTOPT_ROOTCONTEXT,   MS_COMMENT,     ZS_NOCONTEXT    },
81 #ifdef MS_I_VERSION
82         { MNTOPT_IVERSION,      MS_I_VERSION,   ZS_COMMENT      },
83 #endif
84 #ifdef MS_MANDLOCK
85         { MNTOPT_NBMAND,        MS_MANDLOCK,    ZS_COMMENT      },
86 #endif
87         /* Valid options not found in mount(8) */
88         { MNTOPT_BIND,          MS_BIND,        ZS_COMMENT      },
89 #ifdef MS_REC
90         { MNTOPT_RBIND,         MS_BIND|MS_REC, ZS_COMMENT      },
91 #endif
92         { MNTOPT_COMMENT,       MS_COMMENT,     ZS_COMMENT      },
93 #ifdef MS_NOSUB
94         { MNTOPT_NOSUB,         MS_NOSUB,       ZS_COMMENT      },
95 #endif
96 #ifdef MS_SILENT
97         { MNTOPT_QUIET,         MS_SILENT,      ZS_COMMENT      },
98 #endif
99         /* Custom zfs options */
100         { MNTOPT_XATTR,         MS_COMMENT,     ZS_COMMENT      },
101         { MNTOPT_NOXATTR,       MS_COMMENT,     ZS_COMMENT      },
102         { MNTOPT_ZFSUTIL,       MS_COMMENT,     ZS_ZFSUTIL      },
103         { NULL,                 0,              0               } };
104
105 /*
106  * Break the mount option in to a name/value pair.  The name is
107  * validated against the option map and mount flags set accordingly.
108  */
109 static int
110 parse_option(char *mntopt, unsigned long *mntflags,
111     unsigned long *zfsflags, int sloppy)
112 {
113         const option_map_t *opt;
114         char *ptr, *name, *value = NULL;
115         int error = 0;
116
117         name = strdup(mntopt);
118         if (name == NULL)
119                 return (ENOMEM);
120
121         for (ptr = name; ptr && *ptr; ptr++) {
122                 if (*ptr == '=') {
123                         *ptr = '\0';
124                         value = ptr+1;
125                         VERIFY3P(value, !=, NULL);
126                         break;
127                 }
128         }
129
130         for (opt = option_map; opt->name != NULL; opt++) {
131                 if (strncmp(name, opt->name, strlen(name)) == 0) {
132                         *mntflags |= opt->mntmask;
133                         *zfsflags |= opt->zfsmask;
134                         error = 0;
135                         goto out;
136                 }
137         }
138
139         if (!sloppy)
140                 error = ENOENT;
141 out:
142         /* If required further process on the value may be done here */
143         free(name);
144         return (error);
145 }
146
147 /*
148  * Translate the mount option string in to MS_* mount flags for the
149  * kernel vfs.  When sloppy is non-zero unknown options will be ignored
150  * otherwise they are considered fatal are copied in to badopt.
151  */
152 static int
153 parse_options(char *mntopts, unsigned long *mntflags, unsigned long *zfsflags,
154     int sloppy, char *badopt, char *mtabopt)
155 {
156         int error = 0, quote = 0, flag = 0, count = 0;
157         char *ptr, *opt, *opts;
158
159         opts = strdup(mntopts);
160         if (opts == NULL)
161                 return (ENOMEM);
162
163         *mntflags = 0;
164         opt = NULL;
165
166         /*
167          * Scan through all mount options which must be comma delimited.
168          * We must be careful to notice regions which are double quoted
169          * and skip commas in these regions.  Each option is then checked
170          * to determine if it is a known option.
171          */
172         for (ptr = opts; ptr && !flag; ptr++) {
173                 if (opt == NULL)
174                         opt = ptr;
175
176                 if (*ptr == '"')
177                         quote = !quote;
178
179                 if (quote)
180                         continue;
181
182                 if (*ptr == '\0')
183                         flag = 1;
184
185                 if ((*ptr == ',') || (*ptr == '\0')) {
186                         *ptr = '\0';
187
188                         error = parse_option(opt, mntflags, zfsflags, sloppy);
189                         if (error) {
190                                 strcpy(badopt, opt);
191                                 goto out;
192
193                         }
194
195                         if (!(*mntflags & MS_REMOUNT) &&
196                             !(*zfsflags & ZS_ZFSUTIL)) {
197                                 if (count > 0)
198                                         strlcat(mtabopt, ",", MNT_LINE_MAX);
199
200                                 strlcat(mtabopt, opt, MNT_LINE_MAX);
201                                 count++;
202                         }
203
204                         opt = NULL;
205                 }
206         }
207
208 out:
209         free(opts);
210         return (error);
211 }
212
213 /*
214  * Return the pool/dataset to mount given the name passed to mount.  This
215  * is expected to be of the form pool/dataset, however may also refer to
216  * a block device if that device contains a valid zfs label.
217  */
218 static char *
219 parse_dataset(char *dataset)
220 {
221         char cwd[PATH_MAX];
222         struct stat64 statbuf;
223         int error;
224         int len;
225
226         /*
227          * We expect a pool/dataset to be provided, however if we're
228          * given a device which is a member of a zpool we attempt to
229          * extract the pool name stored in the label.  Given the pool
230          * name we can mount the root dataset.
231          */
232         error = stat64(dataset, &statbuf);
233         if (error == 0) {
234                 nvlist_t *config;
235                 char *name;
236                 int fd;
237
238                 fd = open(dataset, O_RDONLY);
239                 if (fd < 0)
240                         goto out;
241
242                 error = zpool_read_label(fd, &config);
243                 (void) close(fd);
244                 if (error)
245                         goto out;
246
247                 error = nvlist_lookup_string(config,
248                     ZPOOL_CONFIG_POOL_NAME, &name);
249                 if (error == 0)
250                         dataset = strdup(name);
251
252                 nvlist_free(config);
253                 return (dataset);
254         }
255 out:
256         /*
257          * If a file or directory in your current working directory is
258          * named 'dataset' then mount(8) will prepend your current working
259          * directory to the dataset.  There is no way to prevent this
260          * behavior so we simply check for it and strip the prepended
261          * patch when it is added.
262          */
263         if (getcwd(cwd, PATH_MAX) == NULL)
264                 return (dataset);
265
266         len = strlen(cwd);
267
268         /* Do not add one when cwd already ends in a trailing '/' */
269         if (!strncmp(cwd, dataset, len))
270                 return (dataset + len + (cwd[len-1] != '/'));
271
272         return (dataset);
273 }
274
275 /*
276  * Update the mtab_* code to use the libmount library when it is commonly
277  * available otherwise fallback to legacy mode.  The mount(8) utility will
278  * manage the lock file for us to prevent racing updates to /etc/mtab.
279  */
280 static int
281 mtab_is_writeable(void)
282 {
283         struct stat st;
284         int error, fd;
285
286         error = lstat(MNTTAB, &st);
287         if (error || S_ISLNK(st.st_mode))
288                 return (0);
289
290         fd = open(MNTTAB, O_RDWR | O_CREAT, 0644);
291         if (fd < 0)
292                 return (0);
293
294         close(fd);
295         return (1);
296 }
297
298 static int
299 mtab_update(char *dataset, char *mntpoint, char *type, char *mntopts)
300 {
301         struct mntent mnt;
302         FILE *fp;
303         int error;
304
305         mnt.mnt_fsname = dataset;
306         mnt.mnt_dir = mntpoint;
307         mnt.mnt_type = type;
308         mnt.mnt_opts = mntopts ? mntopts : "";
309         mnt.mnt_freq = 0;
310         mnt.mnt_passno = 0;
311
312         fp = setmntent(MNTTAB, "a+");
313         if (!fp) {
314                 (void) fprintf(stderr, gettext(
315                     "filesystem '%s' was mounted, but %s "
316                     "could not be opened due to error %d\n"),
317                     dataset, MNTTAB, errno);
318                 return (MOUNT_FILEIO);
319         }
320
321         error = addmntent(fp, &mnt);
322         if (error) {
323                 (void) fprintf(stderr, gettext(
324                     "filesystem '%s' was mounted, but %s "
325                     "could not be updated due to error %d\n"),
326                     dataset, MNTTAB, errno);
327                 return (MOUNT_FILEIO);
328         }
329
330         (void) endmntent(fp);
331
332         return (MOUNT_SUCCESS);
333 }
334
335 int
336 main(int argc, char **argv)
337 {
338         zfs_handle_t *zhp;
339         char legacy[ZFS_MAXPROPLEN];
340         char mntopts[MNT_LINE_MAX] = { '\0' };
341         char badopt[MNT_LINE_MAX] = { '\0' };
342         char mtabopt[MNT_LINE_MAX] = { '\0' };
343         char mntpoint[PATH_MAX];
344         char *dataset;
345         unsigned long mntflags = 0, zfsflags = 0, remount = 0;
346         int sloppy = 0, fake = 0, verbose = 0, nomtab = 0, zfsutil = 0;
347         int error, c;
348
349         (void) setlocale(LC_ALL, "");
350         (void) textdomain(TEXT_DOMAIN);
351
352         opterr = 0;
353
354         /* check options */
355         while ((c = getopt(argc, argv, "sfnvo:h?")) != -1) {
356                 switch (c) {
357                 case 's':
358                         sloppy = 1;
359                         break;
360                 case 'f':
361                         fake = 1;
362                         break;
363                 case 'n':
364                         nomtab = 1;
365                         break;
366                 case 'v':
367                         verbose++;
368                         break;
369                 case 'o':
370                         (void) strlcpy(mntopts, optarg, sizeof (mntopts));
371                         break;
372                 case 'h':
373                 case '?':
374                         (void) fprintf(stderr, gettext("Invalid option '%c'\n"),
375                             optopt);
376                         (void) fprintf(stderr, gettext("Usage: mount.zfs "
377                             "[-sfnv] [-o options] <dataset> <mountpoint>\n"));
378                         return (MOUNT_USAGE);
379                 }
380         }
381
382         argc -= optind;
383         argv += optind;
384
385         /* check that we only have two arguments */
386         if (argc != 2) {
387                 if (argc == 0)
388                         (void) fprintf(stderr, gettext("missing dataset "
389                             "argument\n"));
390                 else if (argc == 1)
391                         (void) fprintf(stderr,
392                             gettext("missing mountpoint argument\n"));
393                 else
394                         (void) fprintf(stderr, gettext("too many arguments\n"));
395                 (void) fprintf(stderr, "usage: mount <dataset> <mountpoint>\n");
396                 return (MOUNT_USAGE);
397         }
398
399         dataset = parse_dataset(argv[0]);
400
401         /* canonicalize the mount point */
402         if (realpath(argv[1], mntpoint) == NULL) {
403                 (void) fprintf(stderr, gettext("filesystem '%s' cannot be "
404                     "mounted at '%s' due to canonicalization error %d.\n"),
405                     dataset, argv[1], errno);
406                 return (MOUNT_SYSERR);
407         }
408
409         /* validate mount options and set mntflags */
410         error = parse_options(mntopts, &mntflags, &zfsflags, sloppy,
411             badopt, mtabopt);
412         if (error) {
413                 switch (error) {
414                 case ENOMEM:
415                         (void) fprintf(stderr, gettext("filesystem '%s' "
416                             "cannot be mounted due to a memory allocation "
417                             "failure.\n"), dataset);
418                         return (MOUNT_SYSERR);
419                 case ENOENT:
420                         (void) fprintf(stderr, gettext("filesystem '%s' "
421                             "cannot be mounted of due invalid option "
422                             "'%s'.\n"), dataset, badopt);
423                         (void) fprintf(stderr, gettext("Use the '-s' option "
424                             "to ignore the bad mount option.\n"));
425                         return (MOUNT_USAGE);
426                 default:
427                         (void) fprintf(stderr, gettext("filesystem '%s' "
428                             "cannot be mounted due to internal error %d.\n"),
429                             dataset, error);
430                         return (MOUNT_SOFTWARE);
431                 }
432         }
433
434 #ifdef HAVE_LIBSELINUX
435         /*
436          * Automatically add the default zfs context when selinux is enabled
437          * and the caller has not specified their own context.  This must be
438          * done until zfs is added to the default selinux policy configuration
439          * as a known filesystem type which supports xattrs.
440          */
441         if (is_selinux_enabled() && !(zfsflags & ZS_NOCONTEXT)) {
442                 (void) strlcat(mntopts, ",context=\"system_u:"
443                     "object_r:file_t:s0\"", sizeof (mntopts));
444                 (void) strlcat(mtabopt, ",context=\"system_u:"
445                     "object_r:file_t:s0\"", sizeof (mtabopt));
446         }
447 #endif /* HAVE_LIBSELINUX */
448
449
450         if (verbose)
451                 (void) fprintf(stdout, gettext("mount.zfs:\n"
452                     "  dataset:    \"%s\"\n  mountpoint: \"%s\"\n"
453                     "  mountflags: 0x%lx\n  zfsflags:   0x%lx\n"
454                     "  mountopts:  \"%s\"\n  mtabopts:   \"%s\"\n"),
455                     dataset, mntpoint, mntflags, zfsflags, mntopts, mtabopt);
456
457         if (mntflags & MS_REMOUNT) {
458                 nomtab = 1;
459                 remount = 1;
460         }
461
462         if (zfsflags & ZS_ZFSUTIL)
463                 zfsutil = 1;
464
465         if ((g_zfs = libzfs_init()) == NULL)
466                 return (MOUNT_SYSERR);
467
468         /* try to open the dataset to access the mount point */
469         if ((zhp = zfs_open(g_zfs, dataset,
470             ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL) {
471                 (void) fprintf(stderr, gettext("filesystem '%s' cannot be "
472                     "mounted, unable to open the dataset\n"), dataset);
473                 libzfs_fini(g_zfs);
474                 return (MOUNT_USAGE);
475         }
476
477         /* treat all snapshots as legacy mount points */
478         if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT)
479                 (void) strlcpy(legacy, ZFS_MOUNTPOINT_LEGACY, ZFS_MAXPROPLEN);
480         else
481                 (void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, legacy,
482                     sizeof (legacy), NULL, NULL, 0, B_FALSE);
483
484         zfs_close(zhp);
485         libzfs_fini(g_zfs);
486
487         /*
488          * Legacy mount points may only be mounted using 'mount', never using
489          * 'zfs mount'.  However, since 'zfs mount' actually invokes 'mount'
490          * we differentiate the two cases using the 'zfsutil' mount option.
491          * This mount option should only be supplied by the 'zfs mount' util.
492          *
493          * The only exception to the above rule is '-o remount' which is
494          * always allowed for non-legacy datasets.  This is done because when
495          * using zfs as your root file system both rc.sysinit/umountroot and
496          * systemd depend on 'mount -o remount <mountpoint>' to work.
497          */
498         if (zfsutil && !strcmp(legacy, ZFS_MOUNTPOINT_LEGACY)) {
499                 (void) fprintf(stderr, gettext(
500                     "filesystem '%s' cannot be mounted using 'zfs mount'.\n"
501                     "Use 'zfs set mountpoint=%s' or 'mount -t zfs %s %s'.\n"
502                     "See zfs(8) for more information.\n"),
503                    dataset, mntpoint, dataset, mntpoint);
504                 return (MOUNT_USAGE);
505         }
506
507         if (!zfsutil && !(remount || fake) &&
508             strcmp(legacy, ZFS_MOUNTPOINT_LEGACY)) {
509                 (void) fprintf(stderr, gettext(
510                     "filesystem '%s' cannot be mounted using 'mount'.\n"
511                     "Use 'zfs set mountpoint=%s' or 'zfs mount %s'.\n"
512                     "See zfs(8) for more information.\n"),
513                     dataset, "legacy", dataset);
514                 return (MOUNT_USAGE);
515         }
516
517         if (!fake) {
518                 error = mount(dataset, mntpoint, MNTTYPE_ZFS,
519                     mntflags, mntopts);
520                 if (error) {
521                         switch (errno) {
522                         case ENOENT:
523                                 (void) fprintf(stderr, gettext("mount point "
524                                     "'%s' does not exist\n"), mntpoint);
525                                 return (MOUNT_SYSERR);
526                         case EBUSY:
527                                 (void) fprintf(stderr, gettext("filesystem "
528                                     "'%s' is already mounted\n"), dataset);
529                                 return (MOUNT_SYSERR);
530                         default:
531                                 (void) fprintf(stderr, gettext("filesystem "
532                                     "'%s' can not be mounted due to error "
533                                     "%d\n"), dataset, errno);
534                                 return (MOUNT_USAGE);
535                         }
536                 }
537         }
538
539         if (!nomtab && mtab_is_writeable()) {
540                 error = mtab_update(dataset, mntpoint, MNTTYPE_ZFS, mtabopt);
541                 if (error)
542                         return (error);
543         }
544
545         return (MOUNT_SUCCESS);
546 }