1#ifndef _DARWIN_USE_64_BIT_INODE
2# define _DARWIN_USE_64_BIT_INODE 1
3#endif
4/*
5 * Copyright (c) 1999-2010 Apple Inc. All rights reserved.
6 *
7 * @APPLE_LICENSE_HEADER_START@
8 *
9 * This file contains Original Code and/or Modifications of Original Code
10 * as defined in and that are subject to the Apple Public Source License
11 * Version 2.0 (the 'License'). You may not use this file except in
12 * compliance with the License. Please obtain a copy of the License at
13 * http://www.opensource.apple.com/apsl/ and read it before using this
14 * file.
15 *
16 * The Original Code and all software distributed under the License are
17 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
18 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
19 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
21 * Please see the License for the specific language governing rights and
22 * limitations under the License.
23 *
24 * @APPLE_LICENSE_HEADER_END@
25 */
26/*-
27 * Copyright (c) 1980, 1989, 1993
28 *	The Regents of the University of California.  All rights reserved.
29 *
30 * Redistribution and use in source and binary forms, with or without
31 * modification, are permitted provided that the following conditions
32 * are met:
33 * 1. Redistributions of source code must retain the above copyright
34 *    notice, this list of conditions and the following disclaimer.
35 * 2. Redistributions in binary form must reproduce the above copyright
36 *    notice, this list of conditions and the following disclaimer in the
37 *    documentation and/or other materials provided with the distribution.
38 * 3. All advertising materials mentioning features or use of this software
39 *    must display the following acknowledgement:
40 *	This product includes software developed by the University of
41 *	California, Berkeley and its contributors.
42 * 4. Neither the name of the University nor the names of its contributors
43 *    may be used to endorse or promote products derived from this software
44 *    without specific prior written permission.
45 *
46 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
47 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
48 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
49 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
50 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
51 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
52 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
53 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
54 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
55 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
56 * SUCH DAMAGE.
57 */
58
59
60#include <sys/param.h>
61#include <sys/stat.h>
62#include <sys/mount.h>
63#include <sys/time.h>
64#include <sys/sysctl.h>
65#include <System/sys/fsctl.h>
66
67#include <netdb.h>
68#include <arpa/inet.h>
69
70#include <err.h>
71#include <fstab.h>
72#include <stdio.h>
73#include <stdlib.h>
74#include <string.h>
75#include <ctype.h>
76#include <unistd.h>
77#include <errno.h>
78
79#include <pthread.h>
80
81struct syncarg {
82    const char *mntname;
83    int wakeup_flag;
84    pthread_cond_t *wakeup_cond;
85    pthread_mutex_t *wakeup_lock;
86};
87
88typedef enum { MNTON, MNTFROM } mntwhat;
89
90int	fake, fflag, vflag;
91char	*nfshost;
92
93int	 checkvfsname(const char *, char **);
94char	*getmntname(const char *, mntwhat, char **);
95int	 getmntfsid(const char *, fsid_t *);
96int	 sysctl_fsid(int, fsid_t *, void *, size_t *, void *, size_t);
97int	 unmount_by_fsid(const char *mntpt, int flag);
98char	**makevfslist(char *);
99int	 selected(int);
100int	 namematch(struct hostent *);
101int	 umountall(char **);
102int	 umountfs(char *, char **);
103void	 usage(void);
104
105static void*
106syncit(void *vap) {
107	int rv;
108	pthread_mutex_t *lock;
109	int full_sync = FSCTL_SYNC_WAIT;
110	struct syncarg *args = vap;
111
112	rv = fsctl(args->mntname, FSIOC_SYNC_VOLUME, &full_sync, 0);
113	if (rv == -1) {
114#ifdef DEBUG
115		warn("fsctl %s", args->mntname);
116#endif
117	}
118
119	lock = args->wakeup_lock;
120	(void)pthread_mutex_lock(lock);
121        args->wakeup_flag = 1;
122	pthread_cond_signal(args->wakeup_cond);
123	(void)pthread_mutex_unlock(lock);
124
125	return NULL;
126}
127
128int
129main(int argc, char *argv[])
130{
131	int all, ch, errs, mnts;
132	char **typelist = NULL;
133	struct statfs *mntbuf;
134
135	/*
136	 * We used to call sync(2) here, but this should be unneccessary
137	 * given that a sync occurs at a more proper level (VFS_SYNC()
138	 * in dounmount() in the non-forced unmount case).
139	 *
140	 * We add the sync() back in for the -f case below to cover the
141	 * situation where the filesystem was mounted RW and force
142	 * unmounted when it really didn't have to be.
143	 *
144	 * See 5328558 for some context.
145	 */
146
147	all = 0;
148	while ((ch = getopt(argc, argv, "AaFfh:t:v")) != EOF)
149		switch (ch) {
150		case 'A':
151			all = 2;
152			break;
153		case 'a':
154			all = 1;
155			break;
156		case 'F':
157			fake = 1;
158			break;
159		case 'f':
160			fflag = MNT_FORCE;
161			break;
162		case 'h':	/* -h implies -A. */
163			all = 2;
164			nfshost = optarg;
165			break;
166		case 't':
167			if (typelist != NULL)
168				errx(1, "only one -t option may be specified.");
169			typelist = makevfslist(optarg);
170			break;
171		case 'v':
172			vflag = 1;
173			break;
174		default:
175			usage();
176			/* NOTREACHED */
177		}
178	argc -= optind;
179	argv += optind;
180
181	if ((argc == 0 && !all) || (argc != 0 && all))
182		usage();
183
184	/* -h implies "-t nfs" if no -t flag. */
185	if ((nfshost != NULL) && (typelist == NULL))
186		typelist = makevfslist("nfs");
187
188	if (fflag & MNT_FORCE) {
189		/*
190		 * If we really mean business, we don't want to get hung up on
191		 * any remote file systems.  So, we set the "noremotehang" flag.
192		 */
193		pid_t pid;
194		pid = getpid();
195		errs = sysctlbyname("vfs.generic.noremotehang", NULL, NULL, &pid, sizeof(pid));
196		if ((errs != 0) && vflag)
197			warn("sysctl vfs.generic.noremotehang");
198	}
199
200	errs = EXIT_SUCCESS;
201	switch (all) {
202	case 2:
203		if ((mnts = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0) {
204			warn("getmntinfo");
205			errs = 1;
206			break;
207		}
208		for (errs = 0, mnts--; mnts > 0; mnts--) {
209			if (checkvfsname(mntbuf[mnts].f_fstypename, typelist))
210				continue;
211			if (umountfs(mntbuf[mnts].f_mntonname, typelist) != 0)
212				errs = 1;
213		}
214		break;
215	case 1:
216		if (setfsent() == 0)
217			err(1, "%s", _PATH_FSTAB);
218		errs = umountall(typelist);
219		break;
220	case 0:
221		for (errs = 0; *argv != NULL; ++argv)
222			if (umountfs(*argv, typelist) != 0)
223				errs = 1;
224		break;
225	}
226	exit(errs);
227}
228
229int
230umountall(char **typelist)
231{
232	struct fstab *fs;
233	int rval;
234	char *cp;
235
236	while ((fs = getfsent()) != NULL) {
237		/* Ignore the root. */
238		if (strcmp(fs->fs_file, "/") == 0)
239			continue;
240		/*
241		 * !!!
242		 * Historic practice: ignore unknown FSTAB_* fields.
243		 */
244		if (strcmp(fs->fs_type, FSTAB_RW) &&
245		    strcmp(fs->fs_type, FSTAB_RO) &&
246		    strcmp(fs->fs_type, FSTAB_RQ))
247			continue;
248#if 0
249		/* If an unknown file system type, complain. */
250		if (getvfsbyname(fs->fs_vfstype, &vfc) < 0) {
251			warnx("%s: unknown mount type `%s'", fs->fs_spec, fs->fs_vfstype);
252			continue;
253		}
254		if (checkvfsname(fs->fs_vfstype, typelist))
255			continue;
256#endif
257
258		/*
259		 * We want to unmount the file systems in the reverse order
260		 * that they were mounted.  So, we save off the file name
261		 * in some allocated memory, and then call recursively.
262		 */
263		if ((cp = malloc((size_t)strlen(fs->fs_file) + 1)) == NULL)
264			err(1, NULL);
265		(void)strcpy(cp, fs->fs_file);
266		rval = umountall(typelist);
267		rval = umountfs(cp, typelist) || rval;
268		free(cp);
269		return (rval);
270	}
271	return (0);
272}
273
274int
275umountfs(char *name, char **typelist)
276{
277	struct hostent *hp, *hp6;
278	struct stat sb;
279	int isftpfs, errnum;
280	char *type, *delimp, *hostname, *mntpt, rname[MAXPATHLEN], *tname;
281	char *pname = name; /* save the name parameter */
282
283	/*
284	 * First directly check the
285	 * current mount list for a match.  If we find it,
286	 * we skip the realpath()/stat() below.
287	 */
288	tname = name;
289	/* check if name is a non-device "mount from" name */
290	if ((mntpt = getmntname(tname, MNTON, &type)) == NULL) {
291		/* or if name is a mounted-on directory */
292		mntpt = tname;
293		tname = getmntname(mntpt, MNTFROM, &type);
294	}
295	if (mntpt && tname) {
296		if (fflag & MNT_FORCE) {
297			/*
298			 * The bulk of this block is to try to do a sync on the filesystem
299			 * being unmounted.  We want to do this in another thread, so we
300			 * can avoid blocking for a hardware or network reason.  We will
301			 * wait 10 seconds for the sync to finish; after that, we just
302			 * ignore it and go ahead with the unmounting.
303			 *
304			 * We only want to do this in the event of a forced unmount.
305			 */
306			int rv;
307			pthread_t tid;
308			pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
309			pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
310			struct syncarg args;
311			struct timespec timeout;
312
313			/* we found a match */
314			name = tname;
315
316			args.mntname = mntpt;
317			args.wakeup_flag = 0;
318			args.wakeup_cond = &cond;
319			args.wakeup_lock = &lock;
320
321			timeout.tv_sec = time(NULL) + 10;	/* Wait 10 seconds */
322			timeout.tv_nsec = 0;
323
324			rv = pthread_create(&tid, NULL, &syncit, &args);
325			if (rv == 0 && pthread_mutex_lock(&lock) == 0) {
326				while (args.wakeup_flag == 0 && rv == 0)
327					rv = pthread_cond_timedwait(&cond, &lock, &timeout);
328
329				/* If this fails, not much we can do at this point... */
330				(void)pthread_mutex_unlock(&lock);
331				if (rv != 0) {
332					errno = rv;
333					warn("pthread_cond_timeout failed; continuing with unmount");
334				}
335			}
336		}
337		goto got_mount_point;
338	}
339
340	/*
341	 * Note: in the face of path resolution errors (realpath/stat),
342	 * we just try using the name passed in as is.
343	 */
344	/* even if path resolution succeeds, but can't find mountpoint
345	 * with the resolved path, we still want to try using the name
346	 * as passed in.
347	 */
348
349	if (realpath(name, rname) == NULL) {
350		if (vflag)
351			warn("realpath(%s)", rname);
352	} else {
353		name = rname;
354	}
355
356	/* we could just try MNTON and MNTFROM on name and again (if
357	 * name is not the passed in param) MNTON and MNTFROM on
358	 * pname.
359	 *
360	 * but we stat(name) here to avoid umounting the wrong thing
361	 * if the mount table has an entry with the MNTFROM that is
362	 * the same as the MNTON in another entry.
363	*/
364
365	if (stat(name, &sb) < 0) {
366		if (vflag)
367			warn("stat(%s)", name);
368		/* maybe name is a non-device "mount from" name? */
369		if ((mntpt = getmntname(name, MNTON, &type)))
370			goto got_mount_point;
371		mntpt = name;
372		/* or name is a directory we simply can't reach? */
373		if ((name = getmntname(mntpt, MNTFROM, &type)))
374			goto got_mount_point;
375	} else if (S_ISBLK(sb.st_mode)) {
376		if ((mntpt = getmntname(name, MNTON, &type)))
377			goto got_mount_point;
378	} else if (S_ISDIR(sb.st_mode)) {
379		mntpt = name;
380		if ((name = getmntname(mntpt, MNTFROM, &type)))
381			goto got_mount_point;
382	} else {
383		warnx("%s: not a directory or special device", name);
384	}
385
386	/* haven't found mountpoint.
387	 *
388	 * if we were not using the name as passed in, then try using it.
389	 */
390	if ((NULL == name) || (strcmp(name, pname) != 0)) {
391		name = pname;
392
393		if ((mntpt = getmntname(name, MNTON, &type)))
394			goto got_mount_point;
395		mntpt = name;
396		if ((name = getmntname(mntpt, MNTFROM, &type)))
397			goto got_mount_point;
398	}
399
400	warnx("%s: not currently mounted", pname);
401	return (1);
402
403got_mount_point:
404
405	if (checkvfsname(type, typelist))
406		return (1);
407
408	if (!strncmp("ftp://", name, 6))
409		isftpfs = 1;
410	else
411		isftpfs = 0;
412
413	hp = hp6 = NULL;
414	delimp = NULL;
415	if (!strcmp(type, "nfs") && !isftpfs) {
416		/*
417		 * Parse the NFS host out of the name.
418		 *
419		 * If it starts with '[' then skip IPv6 literal characters
420		 * until we find ']'.  If we find other characters (or the
421		 * closing ']' isn't followed by a ':', then don't consider
422		 * it to be an IPv6 literal address.
423		 *
424		 * Scan the name string to find ":/" (or just ":").  The name
425		 * is the portion of the string preceding the first ":/" (or ":").
426		 */
427		char *p, *colon, *colonslash, c;
428		hostname = colon = colonslash = NULL;
429		p = name;
430		if (*p == '[') {  /* Looks like an IPv6 literal address */
431			p++;
432			while (isxdigit(*p) || (*p == ':')) {
433				if (*p == ':') {
434					if (!colon)
435						colon = p;
436					if (!colonslash && (*(p+1) == '/'))
437						colonslash = p;
438				}
439				p++;
440			}
441			if ((*p == ']') && (*(p+1) == ':')) {
442				/* Found "[IPv6]:", double check that it's acceptable and use it. */
443				struct sockaddr_in6 sin6;
444				c = *p;
445				*p = '\0';
446				if (inet_pton(AF_INET6, name+1, &sin6))
447					hostname = name + 1;
448				*p = c;
449			}
450		}
451		/* if hostname not found yet, search for ":/" and ":" */
452		while (!hostname && *p && (!colon || !colonslash)) {
453			if (*p == ':') {
454				if (!colon)
455					colon = p;
456				if (!colonslash && (*(p+1) == '/'))
457					colonslash = p;
458			}
459			p++;
460		}
461		if (!hostname && (colonslash || colon)) {
462			/* host name is the string preceding the colon(slash) */
463			hostname = name;
464			if (colonslash)
465				p = colonslash;
466			else if (colon)
467				p = colon;
468		}
469		if (hostname) {
470			c = *p;
471			*p = '\0';
472			/* we just want all the names/aliases */
473			hp = getipnodebyname(hostname, AF_INET, 0, &errnum);
474			hp6 = getipnodebyname(hostname, AF_INET6, 0, &errnum);
475			*p = c;
476		}
477	}
478
479	if (hp || hp6) {
480		int match = (namematch(hp) || namematch(hp6));
481		if (hp)
482			freehostent(hp);
483		if (hp6)
484			freehostent(hp6);
485		if (!match)
486			return (1);
487	}
488
489	if (vflag)
490		(void)printf("%s unmount from %s\n", name, mntpt);
491	if (fake)
492		return (0);
493
494	if (unmount(mntpt, fflag) < 0) {
495		/*
496		 * If we're root and it looks like the error is that the
497		 * mounted on directory is just not reachable or if we really
498		 * want this filesystem unmounted (MNT_FORCE), then try doing
499		 * the unmount by fsid.  (Note: the sysctl only works for root)
500		 */
501		if ((getuid() == 0) &&
502		    ((errno == ESTALE) || (errno == ENOENT) || (fflag & MNT_FORCE))) {
503			if (vflag)
504				warn("unmount(%s)", mntpt);
505			if (unmount_by_fsid(mntpt, fflag) < 0) {
506				warn("unmount(%s)", mntpt);
507				return (1);
508			}
509		} else if (errno == EBUSY) {
510			fprintf(stderr, "umount(%s): %s -- try 'diskutil unmount'\n", mntpt, strerror(errno));
511			return (1);
512		} else {
513			warn("unmount(%s)", mntpt);
514			return (1);
515		}
516	}
517
518	return (0);
519}
520
521static struct statfs *mntbuf;
522static int mntsize;
523
524char *
525getmntname(const char *name, mntwhat what, char **type)
526{
527	int i;
528
529	if (mntbuf == NULL &&
530	    (mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0) {
531		warn("getmntinfo");
532		return (NULL);
533	}
534	for (i = mntsize-1; i >= 0; i--) {
535		if ((what == MNTON) && !strcmp(mntbuf[i].f_mntfromname, name)) {
536			if (type)
537				*type = mntbuf[i].f_fstypename;
538			return (mntbuf[i].f_mntonname);
539		}
540		if ((what == MNTFROM) && !strcmp(mntbuf[i].f_mntonname, name)) {
541			if (type)
542				*type = mntbuf[i].f_fstypename;
543			return (mntbuf[i].f_mntfromname);
544		}
545	}
546	return (NULL);
547}
548
549int
550getmntfsid(const char *name, fsid_t *fsid)
551{
552	int i;
553
554	if (mntbuf == NULL &&
555	    (mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0) {
556		warn("getmntinfo");
557		return (-1);
558	}
559	for (i = mntsize-1; i >= 0; i--) {
560		if (!strcmp(mntbuf[i].f_mntonname, name)) {
561			*fsid = mntbuf[i].f_fsid;
562			return (0);
563		}
564	}
565	return (-1);
566}
567
568int
569namematch(struct hostent *hp)
570{
571	char *cp, **np;
572
573	if (nfshost == NULL)
574		return (1);
575
576	if (hp == NULL)
577		return (0);
578
579	if (strcasecmp(nfshost, hp->h_name) == 0)
580		return (1);
581
582	if ((cp = strchr(hp->h_name, '.')) != NULL) {
583		*cp = '\0';
584		if (strcasecmp(nfshost, hp->h_name) == 0)
585			return (1);
586	}
587	for (np = hp->h_aliases; *np; np++) {
588		if (strcasecmp(nfshost, *np) == 0)
589			return (1);
590		if ((cp = strchr(*np, '.')) != NULL) {
591			*cp = '\0';
592			if (strcasecmp(nfshost, *np) == 0)
593				return (1);
594		}
595	}
596	return (0);
597}
598
599
600int
601sysctl_fsid(
602	int op,
603	fsid_t *fsid,
604	void *oldp,
605	size_t *oldlenp,
606	void *newp,
607	size_t newlen)
608{
609	int ctlname[CTL_MAXNAME+2];
610	size_t ctllen;
611	const char *sysstr = "vfs.generic.ctlbyfsid";
612	struct vfsidctl vc;
613
614	ctllen = CTL_MAXNAME+2;
615	if (sysctlnametomib(sysstr, ctlname, &ctllen) == -1) {
616		warn("sysctlnametomib(%s)", sysstr);
617		return (-1);
618	};
619	ctlname[ctllen] = op;
620
621	bzero(&vc, sizeof(vc));
622	vc.vc_vers = VFS_CTL_VERS1;
623	vc.vc_fsid = *fsid;
624	vc.vc_ptr = newp;
625	vc.vc_len = newlen;
626	return (sysctl(ctlname, ctllen + 1, oldp, oldlenp, &vc, sizeof(vc)));
627}
628
629
630int
631unmount_by_fsid(const char *mntpt, int flag)
632{
633	fsid_t fsid;
634	if (getmntfsid(mntpt, &fsid) < 0)
635		return (-1);
636	if (vflag)
637		printf("attempting to unmount %s by fsid\n", mntpt);
638	return sysctl_fsid(VFS_CTL_UMOUNT, &fsid, NULL, 0, &flag, sizeof(flag));
639}
640
641void
642usage()
643{
644	(void)fprintf(stderr,
645	    "usage: %s\n       %s\n",
646	    "umount [-fv] [-t fstypelist] special | node",
647	    "umount -a[fv] [-h host] [-t fstypelist]");
648	exit(1);
649}
650