Fix mount helper
[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 #ifdef HAVE_SELINUX
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 #endif
82 #ifdef MS_I_VERSION
83         { MNTOPT_IVERSION,      MS_I_VERSION,   ZS_COMMENT      },
84 #endif
85 #ifdef MS_MANDLOCK
86         { MNTOPT_NBMAND,        MS_MANDLOCK,    ZS_COMMENT      },
87 #endif
88         /* Valid options not found in mount(8) */
89         { MNTOPT_BIND,          MS_BIND,        ZS_COMMENT      },
90 #ifdef MS_REC
91         { MNTOPT_RBIND,         MS_BIND|MS_REC, ZS_COMMENT      },
92 #endif
93         { MNTOPT_COMMENT,       MS_COMMENT,     ZS_COMMENT      },
94 #ifdef MS_NOSUB
95         { MNTOPT_NOSUB,         MS_NOSUB,       ZS_COMMENT      },
96 #endif
97 #ifdef MS_SILENT
98         { MNTOPT_QUIET,         MS_SILENT,      ZS_COMMENT      },
99 #endif
100         /* Custom zfs options */
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                         break;
126                 }
127         }
128
129         for (opt = option_map; opt->name != NULL; opt++) {
130                 if (strncmp(name, opt->name, strlen(name)) == 0) {
131                         *mntflags |= opt->mntmask;
132                         *zfsflags |= opt->zfsmask;
133
134                         /* MS_USERS implies default user options */
135                         if (opt->mntmask & (MS_USERS))
136                                 *mntflags |= (MS_NOEXEC|MS_NOSUID|MS_NODEV);
137
138                         /* MS_OWNER|MS_GROUP imply default owner options */
139                         if (opt->mntmask & (MS_OWNER | MS_GROUP))
140                                 *mntflags |= (MS_NOSUID|MS_NODEV);
141
142                         error = 0;
143                         goto out;
144                 }
145         }
146
147         if (!sloppy)
148                 error = ENOENT;
149 out:
150         /* If required further process on the value may be done here */
151         free(name);
152         return (error);
153 }
154
155 /*
156  * Translate the mount option string in to MS_* mount flags for the
157  * kernel vfs.  When sloppy is non-zero unknown options will be ignored
158  * otherwise they are considered fatal are copied in to badopt.
159  */
160 static int
161 parse_options(char *mntopts, unsigned long *mntflags,
162     unsigned long *zfsflags, int sloppy, char *badopt)
163 {
164         int error = 0, quote = 0, flag = 0;
165         char *ptr, *opt, *opts;
166
167         opts = strdup(mntopts);
168         if (opts == NULL)
169                 return (ENOMEM);
170
171         *mntflags = 0;
172         opt = NULL;
173
174         /*
175          * Scan through all mount options which must be comma delimited.
176          * We must be careful to notice regions which are double quoted
177          * and skip commas in these regions.  Each option is then checked
178          * to determine if it is a known option.
179          */
180         for (ptr = opts; ptr && !flag; ptr++) {
181                 if (opt == NULL)
182                         opt = ptr;
183
184                 if (*ptr == '"')
185                         quote = !quote;
186
187                 if (quote)
188                         continue;
189
190                 if (*ptr == '\0')
191                         flag = 1;
192
193                 if ((*ptr == ',') || (*ptr == '\0')) {
194                         *ptr = '\0';
195
196                         error = parse_option(opt, mntflags, zfsflags, sloppy);
197                         if (error) {
198                                 strcpy(badopt, opt);
199                                 goto out;
200                         }
201
202                         opt = NULL;
203                 }
204         }
205
206 out:
207         free(opts);
208         return (error);
209 }
210
211 /*
212  * If a file or directory in your current working directory is named
213  * 'dataset' then mount(8) will prepend your current working directory
214  * to dataset.  The is no way to prevent this behavior so we simply
215  * check for it and strip the prepended patch when it is added.
216  */
217 static char *
218 parse_dataset(char *dataset)
219 {
220         char cwd[PATH_MAX];
221
222         (void) getcwd(cwd, PATH_MAX);
223         if (!strncmp(cwd, dataset, strlen(cwd)))
224                 return (dataset + strlen(cwd) + 1);
225
226         return (dataset);
227 }
228
229 /*
230  * Update the mtab_* code to use the libmount library when it is commonly
231  * available otherwise fallback to legacy mode.  The mount(8) utility will
232  * manage the lock file for us to prevent racing updates to /etc/mtab.
233  */
234 static int
235 mtab_is_writeable(void)
236 {
237         struct stat st;
238         int error, fd;
239
240         error = stat(MNTTAB, &st);
241         if (error || S_ISLNK(st.st_mode))
242                 return (0);
243
244         fd = open(MNTTAB, O_RDWR | O_CREAT, 0644);
245         if (fd < 0)
246                 return (0);
247
248         close(fd);
249         return (1);
250 }
251
252 static int
253 mtab_update(char *dataset, char *mntpoint, char *type, char *mntopts)
254 {
255         struct mntent mnt;
256         FILE *fp;
257         int error;
258
259         mnt.mnt_fsname = dataset;
260         mnt.mnt_dir = mntpoint;
261         mnt.mnt_type = type;
262         mnt.mnt_opts = mntopts ? mntopts : "";
263         mnt.mnt_freq = 0;
264         mnt.mnt_passno = 0;
265
266         fp = setmntent(MNTTAB, "a+");
267         if (!fp) {
268                 (void) fprintf(stderr, gettext(
269                     "filesystem '%s' was mounted, but %s "
270                     "could not be opened due to error %d\n"),
271                     dataset, MNTTAB, errno);
272                 return (MOUNT_FILEIO);
273         }
274
275         error = addmntent(fp, &mnt);
276         if (error) {
277                 (void) fprintf(stderr, gettext(
278                     "filesystem '%s' was mounted, but %s "
279                     "could not be updated due to error %d\n"),
280                     dataset, MNTTAB, errno);
281                 return (MOUNT_FILEIO);
282         }
283
284         (void) endmntent(fp);
285
286         return (MOUNT_SUCCESS);
287 }
288
289 int
290 main(int argc, char **argv)
291 {
292         zfs_handle_t *zhp;
293         char legacy[ZFS_MAXPROPLEN];
294         char mntopts[MNT_LINE_MAX] = { '\0' };
295         char badopt[MNT_LINE_MAX] = { '\0' };
296         char *dataset, *mntpoint;
297         unsigned long mntflags = 0, zfsflags = 0;
298         int sloppy = 0, fake = 0, verbose = 0, nomtab = 0, zfsutil = 0;
299         int error, c;
300
301         (void) setlocale(LC_ALL, "");
302         (void) textdomain(TEXT_DOMAIN);
303
304         opterr = 0;
305
306         /* check options */
307         while ((c = getopt(argc, argv, "sfnvo:h?")) != -1) {
308                 switch (c) {
309                 case 's':
310                         sloppy = 1;
311                         break;
312                 case 'f':
313                         fake = 1;
314                         break;
315                 case 'n':
316                         nomtab = 1;
317                         break;
318                 case 'v':
319                         verbose++;
320                         break;
321                 case 'o':
322                         (void) strlcpy(mntopts, optarg, sizeof (mntopts));
323                         break;
324                 case 'h':
325                 case '?':
326                         (void) fprintf(stderr, gettext("Invalid option '%c'\n"),
327                             optopt);
328                         (void) fprintf(stderr, gettext("Usage: mount.zfs "
329                             "[-sfnv] [-o options] <dataset> <mountpoint>\n"));
330                         return (MOUNT_USAGE);
331                 }
332         }
333
334         argc -= optind;
335         argv += optind;
336
337         /* check that we only have two arguments */
338         if (argc != 2) {
339                 if (argc == 0)
340                         (void) fprintf(stderr, gettext("missing dataset "
341                             "argument\n"));
342                 else if (argc == 1)
343                         (void) fprintf(stderr,
344                             gettext("missing mountpoint argument\n"));
345                 else
346                         (void) fprintf(stderr, gettext("too many arguments\n"));
347                 (void) fprintf(stderr, "usage: mount <dataset> <mountpoint>\n");
348                 return (MOUNT_USAGE);
349         }
350
351         dataset = parse_dataset(argv[0]);
352         mntpoint = argv[1];
353
354         /* validate mount options and set mntflags */
355         error = parse_options(mntopts, &mntflags, &zfsflags, sloppy, badopt);
356         if (error) {
357                 switch (error) {
358                 case ENOMEM:
359                         (void) fprintf(stderr, gettext("filesystem '%s' "
360                             "cannot be mounted due to a memory allocation "
361                             "failure\n"), dataset);
362                         return (MOUNT_SYSERR);
363                 case EINVAL:
364                         (void) fprintf(stderr, gettext("filesystem '%s' "
365                             "cannot be mounted of due to the invalid option "
366                             "'%s'\n"), dataset, badopt);
367                         (void) fprintf(stderr, gettext("Use the '-s' option "
368                             "to ignore the bad mount option.\n"));
369                         return (MOUNT_USAGE);
370                 default:
371                         (void) fprintf(stderr, gettext("filesystem '%s' "
372                             "cannot be mounted due to internal error %d\n"),
373                             dataset, error);
374                         return (MOUNT_SOFTWARE);
375                 }
376         }
377
378 #ifdef HAVE_LIBSELINUX
379         /*
380          * Automatically add the default zfs context when selinux is enabled
381          * and the caller has not specified their own context.  This must be
382          * done until zfs is added to the default selinux policy configuration
383          * as a known filesystem type which supports xattrs.
384          */
385         if (is_selinux_enabled() && !(zfsflags & ZS_NOCONTEXT))
386                 (void) strlcat(mntopts, ",context=\"system_u:"
387                     "object_r:file_t:s0\"", sizeof (mntopts));
388 #endif /* HAVE_LIBSELINUX */
389
390
391         if (verbose)
392                 (void) fprintf(stdout, gettext("mount.zfs:\n"
393                     "  dataset:    \"%s\"\n  mountpoint: \"%s\"\n"
394                     "  mountflags: 0x%lx\n  zfsflags:   0x%lx\n"
395                     "  mountopts:  \"%s\"\n\n"),
396                     dataset, mntpoint, mntflags, zfsflags, mntopts);
397
398         if (mntflags & MS_REMOUNT)
399                 nomtab = 1;
400
401         if (zfsflags * ZS_ZFSUTIL)
402                 zfsutil = 1;
403
404         if ((g_zfs = libzfs_init()) == NULL)
405                 return (MOUNT_SYSERR);
406
407         /* try to open the dataset to access the mount point */
408         if ((zhp = zfs_open(g_zfs, dataset, ZFS_TYPE_FILESYSTEM)) == NULL) {
409                 (void) fprintf(stderr, gettext("filesystem '%s' cannot be "
410                     "mounted, unable to open the dataset\n"), dataset);
411                 libzfs_fini(g_zfs);
412                 return (MOUNT_USAGE);
413         }
414
415         (void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, legacy,
416             sizeof (legacy), NULL, NULL, 0, B_FALSE);
417
418         zfs_close(zhp);
419         libzfs_fini(g_zfs);
420
421         /*
422          * Legacy mount points may only be mounted using 'mount', never using
423          * 'zfs mount'.  However, since 'zfs mount' actually invokes 'mount'
424          * we differentiate the two cases using the 'zfsutil' mount option.
425          * This mount option should only be supplied by the 'zfs mount' util.
426          */
427         if (zfsutil && !strcmp(legacy, ZFS_MOUNTPOINT_LEGACY)) {
428                 (void) fprintf(stderr, gettext(
429                     "filesystem '%s' cannot be mounted using 'zfs mount'.\n"
430                     "Use 'zfs set mountpoint=%s' or 'mount -t zfs %s %s'.\n"
431                     "See zfs(8) for more information.\n"),
432                    dataset, mntpoint, dataset, mntpoint);
433                 return (MOUNT_USAGE);
434         }
435
436         if (!zfsutil && strcmp(legacy, ZFS_MOUNTPOINT_LEGACY)) {
437                 (void) fprintf(stderr, gettext(
438                     "filesystem '%s' cannot be mounted using 'mount'.\n"
439                     "Use 'zfs set mountpoint=%s' or 'zfs mount %s'.\n"
440                     "See zfs(8) for more information.\n"),
441                     dataset, "legacy", dataset);
442                 return (MOUNT_USAGE);
443         }
444
445         if (!fake) {
446                 error = mount(dataset, mntpoint, MNTTYPE_ZFS,
447                     mntflags, mntopts);
448                 if (error) {
449                         switch (errno) {
450                         case EBUSY:
451                                 (void) fprintf(stderr, gettext("filesystem "
452                                     "'%s' is already mounted\n"), dataset);
453                                 return (MOUNT_SYSERR);
454                         default:
455                                 (void) fprintf(stderr, gettext("filesystem "
456                                     "'%s' can not be mounted due to error "
457                                     "%d\n"), dataset, errno);
458                                 return (MOUNT_USAGE);
459                         }
460                 }
461         }
462
463         if (!nomtab && mtab_is_writeable()) {
464                 error = mtab_update(dataset, mntpoint, MNTTYPE_ZFS, mntopts);
465                 if (error)
466                         return (error);
467         }
468
469         return (MOUNT_SUCCESS);
470 }