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