1/* vi: set sw=4 ts=4: */
2/*
3 * Mini umount implementation for busybox
4 *
5 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
6 * Copyright (C) 2005 by Rob Landley <rob@landley.net>
7 *
8 * Licensed under GPL version 2, see file LICENSE in this tarball for details.
9 */
10#include <mntent.h>
11#include <sys/mount.h>
12/* Make sure we have all the new mount flags we actually try to use. */
13#ifndef MS_BIND
14# define MS_BIND        (1 << 12)
15#endif
16#ifndef MS_MOVE
17# define MS_MOVE        (1 << 13)
18#endif
19#ifndef MS_RECURSIVE
20# define MS_RECURSIVE   (1 << 14)
21#endif
22#ifndef MS_SILENT
23# define MS_SILENT      (1 << 15)
24#endif
25/* The shared subtree stuff, which went in around 2.6.15. */
26#ifndef MS_UNBINDABLE
27# define MS_UNBINDABLE  (1 << 17)
28#endif
29#ifndef MS_PRIVATE
30# define MS_PRIVATE     (1 << 18)
31#endif
32#ifndef MS_SLAVE
33# define MS_SLAVE       (1 << 19)
34#endif
35#ifndef MS_SHARED
36# define MS_SHARED      (1 << 20)
37#endif
38#ifndef MS_RELATIME
39# define MS_RELATIME    (1 << 21)
40#endif
41#include "libbb.h"
42#ifndef PATH_MAX
43# define PATH_MAX (4*1024)
44#endif
45
46
47#if defined(__dietlibc__)
48/* 16.12.2006, Sampo Kellomaki (sampo@iki.fi)
49 * dietlibc-0.30 does not have implementation of getmntent_r() */
50static struct mntent *getmntent_r(FILE* stream, struct mntent* result,
51		char* buffer UNUSED_PARAM, int bufsize UNUSED_PARAM)
52{
53	struct mntent* ment = getmntent(stream);
54	return memcpy(result, ment, sizeof(*ment));
55}
56#endif
57
58/* ignored: -v -d -t -i */
59#define OPTION_STRING           "fldnra" "vdt:i"
60#define OPT_FORCE               (1 << 0)
61#define OPT_LAZY                (1 << 1)
62#define OPT_FREELOOP            (1 << 2)
63#define OPT_NO_MTAB             (1 << 3)
64#define OPT_REMOUNT             (1 << 4)
65#define OPT_ALL                 (ENABLE_FEATURE_UMOUNT_ALL ? (1 << 5) : 0)
66
67// These constants from linux/fs.h must match OPT_FORCE and OPT_LAZY,
68// otherwise "doForce" trick below won't work!
69//#define MNT_FORCE  0x00000001 /* Attempt to forcibly umount */
70//#define MNT_DETACH 0x00000002 /* Just detach from the tree */
71
72int umount_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
73int umount_main(int argc UNUSED_PARAM, char **argv)
74{
75	int doForce;
76	char *const buf = xmalloc(PATH_MAX * 2 + 128); /* to save stack */
77	struct mntent me;
78	FILE *fp;
79	char *fstype = NULL;
80	int status = EXIT_SUCCESS;
81	unsigned opt;
82	struct mtab_list {
83		char *dir;
84		char *device;
85		struct mtab_list *next;
86	} *mtl, *m;
87
88	opt = getopt32(argv, OPTION_STRING, &fstype);
89	//argc -= optind;
90	argv += optind;
91	doForce = MAX((opt & OPT_FORCE), (opt & OPT_LAZY));
92
93	/* Get a list of mount points from mtab.  We read them all in now mostly
94	 * for umount -a (so we don't have to worry about the list changing while
95	 * we iterate over it, or about getting stuck in a loop on the same failing
96	 * entry.  Notice that this also naturally reverses the list so that -a
97	 * umounts the most recent entries first. */
98	m = mtl = NULL;
99
100	// If we're umounting all, then m points to the start of the list and
101	// the argument list should be empty (which will match all).
102	fp = setmntent(bb_path_mtab_file, "r");
103	if (!fp) {
104		if (opt & OPT_ALL)
105			bb_error_msg_and_die("can't open '%s'", bb_path_mtab_file);
106	} else {
107		while (getmntent_r(fp, &me, buf, PATH_MAX * 2 + 128)) {
108			/* Match fstype if passed */
109			if (!match_fstype(&me, fstype))
110				continue;
111			m = xzalloc(sizeof(*m));
112			m->next = mtl;
113			m->device = xstrdup(me.mnt_fsname);
114			m->dir = xstrdup(me.mnt_dir);
115			mtl = m;
116		}
117		endmntent(fp);
118	}
119
120	// If we're not umounting all, we need at least one argument.
121	if (!(opt & OPT_ALL) && !fstype) {
122		if (!argv[0])
123			bb_show_usage();
124		m = NULL;
125	}
126
127	// Loop through everything we're supposed to umount, and do so.
128	for (;;) {
129		int curstat;
130		char *zapit = *argv;
131		char *path;
132
133		// Do we already know what to umount this time through the loop?
134		if (m)
135			path = xstrdup(m->dir);
136		// For umount -a, end of mtab means time to exit.
137		else if (opt & OPT_ALL)
138			break;
139		// Use command line argument (and look it up in mtab list)
140		else {
141			if (!zapit)
142				break;
143			argv++;
144			path = xmalloc_realpath(zapit);
145			if (path) {
146				for (m = mtl; m; m = m->next)
147					if (strcmp(path, m->dir) == 0 || strcmp(path, m->device) == 0)
148						break;
149			}
150		}
151		// If we couldn't find this sucker in /etc/mtab, punt by passing our
152		// command line argument straight to the umount syscall.  Otherwise,
153		// umount the directory even if we were given the block device.
154		if (m) zapit = m->dir;
155
156		// Let's ask the thing nicely to unmount.
157		curstat = umount(zapit);
158
159		// Force the unmount, if necessary.
160		if (curstat && doForce)
161			curstat = umount2(zapit, doForce);
162
163		// If still can't umount, maybe remount read-only?
164		if (curstat) {
165			if ((opt & OPT_REMOUNT) && errno == EBUSY && m) {
166				// Note! Even if we succeed here, later we should not
167				// free loop device or erase mtab entry!
168				const char *msg = "%s busy - remounted read-only";
169				curstat = mount(m->device, zapit, NULL, MS_REMOUNT|MS_RDONLY, NULL);
170				if (curstat) {
171					msg = "can't remount %s read-only";
172					status = EXIT_FAILURE;
173				}
174				bb_error_msg(msg, m->device);
175			} else {
176				status = EXIT_FAILURE;
177				bb_perror_msg("can't %sumount %s", (doForce ? "forcibly " : ""), zapit);
178			}
179		} else {
180			// De-allocate the loop device.  This ioctl should be ignored on
181			// any non-loop block devices.
182			if (ENABLE_FEATURE_MOUNT_LOOP && (opt & OPT_FREELOOP) && m)
183				del_loop(m->device);
184			if (ENABLE_FEATURE_MTAB_SUPPORT && !(opt & OPT_NO_MTAB) && m)
185				erase_mtab(m->dir);
186		}
187
188		// Find next matching mtab entry for -a or umount /dev
189		// Note this means that "umount /dev/blah" will unmount all instances
190		// of /dev/blah, not just the most recent.
191		if (m) {
192			while ((m = m->next) != NULL)
193				// NB: if m is non-NULL, path is non-NULL as well
194				if ((opt & OPT_ALL) || strcmp(path, m->device) == 0)
195					break;
196		}
197		free(path);
198	}
199
200	// Free mtab list if necessary
201	if (ENABLE_FEATURE_CLEAN_UP) {
202		while (mtl) {
203			m = mtl->next;
204			free(mtl->device);
205			free(mtl->dir);
206			free(mtl);
207			mtl = m;
208		}
209		free(buf);
210	}
211
212	return status;
213}
214