fdisk: backport disk check from util-linux
[oweals/busybox.git] / util-linux / umount.c
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 GPLv2, see file LICENSE in this source tree.
9  */
10
11 //usage:#define umount_trivial_usage
12 //usage:       "[OPTIONS] FILESYSTEM|DIRECTORY"
13 //usage:#define umount_full_usage "\n\n"
14 //usage:       "Unmount file systems\n"
15 //usage:     "\nOptions:"
16 //usage:        IF_FEATURE_UMOUNT_ALL(
17 //usage:     "\n        -a      Unmount all file systems" IF_FEATURE_MTAB_SUPPORT(" in /etc/mtab")
18 //usage:        )
19 //usage:        IF_FEATURE_MTAB_SUPPORT(
20 //usage:     "\n        -n      Don't erase /etc/mtab entries"
21 //usage:        )
22 //usage:     "\n        -r      Try to remount devices as read-only if mount is busy"
23 //usage:     "\n        -l      Lazy umount (detach filesystem)"
24 //usage:     "\n        -f      Force umount (i.e., unreachable NFS server)"
25 //usage:        IF_FEATURE_MOUNT_LOOP(
26 //usage:     "\n        -d      Free loop device if it has been used"
27 //usage:        )
28 //usage:
29 //usage:#define umount_example_usage
30 //usage:       "$ umount /dev/hdc1\n"
31
32 #include <mntent.h>
33 #include <sys/mount.h>
34 #include "libbb.h"
35
36 #if defined(__dietlibc__)
37 // TODO: This does not belong here.
38 /* 16.12.2006, Sampo Kellomaki (sampo@iki.fi)
39  * dietlibc-0.30 does not have implementation of getmntent_r() */
40 static struct mntent *getmntent_r(FILE* stream, struct mntent* result,
41                 char* buffer UNUSED_PARAM, int bufsize UNUSED_PARAM)
42 {
43         struct mntent* ment = getmntent(stream);
44         return memcpy(result, ment, sizeof(*ment));
45 }
46 #endif
47
48 /* ignored: -v -d -t -i */
49 #define OPTION_STRING           "fldnra" "vdt:i"
50 #define OPT_FORCE               (1 << 0) // Same as MNT_FORCE
51 #define OPT_LAZY                (1 << 1) // Same as MNT_DETACH
52 #define OPT_FREELOOP            (1 << 2)
53 #define OPT_NO_MTAB             (1 << 3)
54 #define OPT_REMOUNT             (1 << 4)
55 #define OPT_ALL                 (ENABLE_FEATURE_UMOUNT_ALL ? (1 << 5) : 0)
56
57 int umount_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
58 int umount_main(int argc UNUSED_PARAM, char **argv)
59 {
60         int doForce;
61         struct mntent me;
62         FILE *fp;
63         char *fstype = NULL;
64         int status = EXIT_SUCCESS;
65         unsigned opt;
66         struct mtab_list {
67                 char *dir;
68                 char *device;
69                 struct mtab_list *next;
70         } *mtl, *m;
71
72         opt = getopt32(argv, OPTION_STRING, &fstype);
73         //argc -= optind;
74         argv += optind;
75
76         // MNT_FORCE and MNT_DETACH (from linux/fs.h) must match
77         // OPT_FORCE and OPT_LAZY, otherwise this trick won't work:
78         doForce = MAX((opt & OPT_FORCE), (opt & OPT_LAZY));
79
80         /* Get a list of mount points from mtab.  We read them all in now mostly
81          * for umount -a (so we don't have to worry about the list changing while
82          * we iterate over it, or about getting stuck in a loop on the same failing
83          * entry.  Notice that this also naturally reverses the list so that -a
84          * umounts the most recent entries first. */
85         m = mtl = NULL;
86
87         // If we're umounting all, then m points to the start of the list and
88         // the argument list should be empty (which will match all).
89         fp = setmntent(bb_path_mtab_file, "r");
90         if (!fp) {
91                 if (opt & OPT_ALL)
92                         bb_error_msg_and_die("can't open '%s'", bb_path_mtab_file);
93         } else {
94                 while (getmntent_r(fp, &me, bb_common_bufsiz1, sizeof(bb_common_bufsiz1))) {
95                         /* Match fstype if passed */
96                         if (!match_fstype(&me, fstype))
97                                 continue;
98                         m = xzalloc(sizeof(*m));
99                         m->next = mtl;
100                         m->device = xstrdup(me.mnt_fsname);
101                         m->dir = xstrdup(me.mnt_dir);
102                         mtl = m;
103                 }
104                 endmntent(fp);
105         }
106
107         // If we're not umounting all, we need at least one argument.
108         if (!(opt & OPT_ALL) && !fstype) {
109                 if (!argv[0])
110                         bb_show_usage();
111                 m = NULL;
112         }
113
114         // Loop through everything we're supposed to umount, and do so.
115         for (;;) {
116                 int curstat;
117                 char *zapit = *argv;
118                 char *path;
119
120                 // Do we already know what to umount this time through the loop?
121                 if (m)
122                         path = xstrdup(m->dir);
123                 // For umount -a, end of mtab means time to exit.
124                 else if (opt & OPT_ALL)
125                         break;
126                 // Use command line argument (and look it up in mtab list)
127                 else {
128                         if (!zapit)
129                                 break;
130                         argv++;
131                         path = xmalloc_realpath(zapit);
132                         if (path) {
133                                 for (m = mtl; m; m = m->next)
134                                         if (strcmp(path, m->dir) == 0 || strcmp(path, m->device) == 0)
135                                                 break;
136                         }
137                 }
138                 // If we couldn't find this sucker in /etc/mtab, punt by passing our
139                 // command line argument straight to the umount syscall.  Otherwise,
140                 // umount the directory even if we were given the block device.
141                 if (m) zapit = m->dir;
142
143                 // Let's ask the thing nicely to unmount.
144                 curstat = umount(zapit);
145
146                 // Force the unmount, if necessary.
147                 if (curstat && doForce)
148                         curstat = umount2(zapit, doForce);
149
150                 // If still can't umount, maybe remount read-only?
151                 if (curstat) {
152                         if ((opt & OPT_REMOUNT) && errno == EBUSY && m) {
153                                 // Note! Even if we succeed here, later we should not
154                                 // free loop device or erase mtab entry!
155                                 const char *msg = "%s busy - remounted read-only";
156                                 curstat = mount(m->device, zapit, NULL, MS_REMOUNT|MS_RDONLY, NULL);
157                                 if (curstat) {
158                                         msg = "can't remount %s read-only";
159                                         status = EXIT_FAILURE;
160                                 }
161                                 bb_error_msg(msg, m->device);
162                         } else {
163                                 status = EXIT_FAILURE;
164                                 bb_perror_msg("can't %sumount %s", (doForce ? "forcibly " : ""), zapit);
165                         }
166                 } else {
167                         // De-allocate the loop device.  This ioctl should be ignored on
168                         // any non-loop block devices.
169                         if (ENABLE_FEATURE_MOUNT_LOOP && (opt & OPT_FREELOOP) && m)
170                                 del_loop(m->device);
171                         if (ENABLE_FEATURE_MTAB_SUPPORT && !(opt & OPT_NO_MTAB) && m)
172                                 erase_mtab(m->dir);
173                 }
174
175                 // Find next matching mtab entry for -a or umount /dev
176                 // Note this means that "umount /dev/blah" will unmount all instances
177                 // of /dev/blah, not just the most recent.
178                 if (m) {
179                         while ((m = m->next) != NULL)
180                                 // NB: if m is non-NULL, path is non-NULL as well
181                                 if ((opt & OPT_ALL) || strcmp(path, m->device) == 0)
182                                         break;
183                 }
184                 free(path);
185         }
186
187         // Free mtab list if necessary
188         if (ENABLE_FEATURE_CLEAN_UP) {
189                 while (mtl) {
190                         m = mtl->next;
191                         free(mtl->device);
192                         free(mtl->dir);
193                         free(mtl);
194                         mtl = m;
195                 }
196         }
197
198         return status;
199 }