1/*
2 * Copyright (c) 2006-2008 Apple Inc. All rights reserved.
3 *
4 * This file contains Original Code and/or Modifications of Original Code as
5 * defined in and that are subject to the Apple Public Source License Version
6 * 2.0 (the 'License'). You may not use this file except in compliance with the
7 * License.
8 *
9 * Please obtain a copy of the License at http://www.opensource.apple.com/apsl/
10 * and read it before using this file.
11 *
12 * The Original Code and all software distributed under the License are
13 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR
16 * A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations under the
18 * License.
19 */
20
21#include <sys/mount.h>
22#include <sys/param.h>
23#include <sys/stat.h>
24#include <sys/types.h>
25#include <sys/wait.h>
26
27#include <err.h>
28#include <errno.h>
29#include <mntopts.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <sysexits.h>
34#include <unistd.h>
35
36#include "ntfs.h"
37#include "ntfs_types.h"
38
39static struct mntopt mopts[] = {
40	MOPT_STDOPTS,
41	MOPT_FSTAB_COMPAT,
42	MOPT_ASYNC,
43	MOPT_SYNC,
44	MOPT_FORCE,
45	MOPT_UPDATE,
46	MOPT_RELOAD,
47	{ NULL, 0, 0, 0 }
48};
49
50static void usage(const char *progname) __attribute__((noreturn));
51static void usage(const char *progname)
52{
53	errx(EX_USAGE, "usage: %s [-s] [-o options] special-device "
54			"filesystem-node\n", progname);
55}
56
57/**
58 * do_exec - Execute an external command.
59 */
60static int do_exec(const char *progname, char *const args[])
61{
62	pid_t pid;
63	union wait status;
64	int eo;
65
66	pid = fork();
67	if (pid == -1) {
68		fprintf(stderr, "%s: fork failed: %s\n", progname,
69				strerror(errno));
70		return -1;
71	}
72	if (!pid) {
73		/* In child process, execute external command. */
74		(void)execv(args[0], args);
75		/* We only get here if the execv() failed. */
76		eo = errno;
77		fprintf(stderr, "%s: execv %s failed: %s\n", progname, args[0],
78				strerror(eo));
79		exit(eo);
80	}
81	/* In parent process, wait for exernal command to finish. */
82	if (wait4(pid, (int*)&status, 0, NULL) != pid) {
83		fprintf(stderr, "%s: BUG executing %s command.\n", progname,
84				args[0]);
85		return -1;
86	}
87	if (!WIFEXITED(status)) {
88		fprintf(stderr, "%s: %s command aborted by signal %d.\n",
89				progname, args[0], WTERMSIG(status));
90		return -1;
91	}
92	eo = WEXITSTATUS(status);
93	if (eo) {
94		fprintf(stderr, "%s: %s command failed: %s\n", progname,
95				args[0], strerror(eo));
96		return -1;
97	}
98	return 0;
99}
100
101static void rmslashes(char *rrpin, char *rrpout)
102{
103	char *rrpoutstart;
104
105	*rrpout = *rrpin;
106	for (rrpoutstart = rrpout; *rrpin != '\0'; *rrpout++ = *rrpin++) {
107		/* Skip all double slashes. */
108		while (*rrpin == '/' && *(rrpin + 1) == '/')
109			 rrpin++;
110	}
111	/* Remove trailing slash if necessary. */
112	if (rrpout - rrpoutstart > 1 && *(rrpout - 1) == '/')
113		*(rrpout - 1) = '\0';
114	else
115		*rrpout = '\0';
116}
117
118static void checkpath(const char *path, char *resolved)
119{
120	struct stat sb;
121
122	if (!realpath(path, resolved) || stat(resolved, &sb))
123		err(EX_USAGE, "%s", resolved);
124	if (!S_ISDIR(sb.st_mode))
125		errx(EX_USAGE, "%s: not a directory", resolved);
126}
127
128int main(int argc, char **argv)
129{
130	char *progname, *dev;
131	ntfs_mount_options_header *opts_hdr;
132	ntfs_mount_options_1_0 *opts;
133	int ch, dummy, flags = 0;
134	char dir[MAXPATHLEN];
135	const char ntfs[] = "ntfs";
136	char *const kextargs[] = { "/sbin/kextload",
137			"/System/Library/Extensions/ntfs.kext", NULL };
138	struct vfsconf vfc;
139	BOOL case_sensitive;
140
141	/* Default to mounting read-only. */
142	flags = MNT_RDONLY;
143
144	/* Save & strip off program name. */
145	progname = argv[0];
146	/* Set up default options. */
147	case_sensitive = FALSE;
148	/* Parse the options. */
149	while ((ch = getopt(argc, argv, "so:h?")) != -1) {
150		switch (ch) {
151		case 's':
152			case_sensitive = TRUE;
153			break;
154		case 'o': {
155			mntoptparse_t tmp;
156
157			tmp = getmntopts(optarg, mopts, &flags, &dummy);
158			if (!tmp)
159				err(EX_OSERR, "getmntopts() failed");
160			freemntopts(tmp);
161			break;
162		}
163		case 'h':
164		case '?':
165		default:
166			usage(progname);
167			break;
168		}
169	}
170	argc -= optind;
171	argv += optind;
172	/* Parse the device to mount and the directory to mount it on. */
173	if (argc != 2)
174		usage(progname);
175	dev = argv[0];
176	checkpath(argv[1], dir);
177	rmslashes(dev, dev);
178	/*
179	 * Set up the NTFS mount options structure for the mount(2) call.
180	 *
181	 * We currently implement version 1.0, which only has the flags option
182	 * and the only currently defined flag is NTFS_OPT_CASE_SENSITIVE.
183	 */
184	opts_hdr = valloc(((sizeof(*opts_hdr) + 7) & ~7) + sizeof(*opts));
185	if (!opts_hdr)
186		err(EX_OSERR, "valloc() failed");
187	*opts_hdr = (ntfs_mount_options_header) {
188		.fspec = dev,
189		.major_ver = 1,
190		.minor_ver = 0,
191	};
192	opts = (ntfs_mount_options_1_0*)((char*)opts_hdr +
193			((sizeof(*opts_hdr) + 7) & ~7));
194	*opts = (ntfs_mount_options_1_0) {
195		.flags = case_sensitive ? NTFS_MNT_OPT_CASE_SENSITIVE : 0,
196	};
197	/* If the kext is not loaded, load it now. */
198	if (getvfsbyname(ntfs, &vfc)) {
199		/*
200		 * Ignore errors from the load attempt and instead simply check
201		 * that NTFS is now loaded and if not bail out now.
202		 */
203		(void)do_exec(progname, kextargs);
204		if (getvfsbyname(ntfs, &vfc))
205			errx(EX_OSERR, "Failed to load NTFS file system kext.");
206	}
207	if (mount(ntfs, dir, flags, opts_hdr) < 0)
208		err(EX_OSERR, "%s on %s", dev, dir);
209	free(opts_hdr);
210	return 0;
211}
212