Major rewrite of mount, umount, losetup. Untangled lots of code, shrunk
[oweals/busybox.git] / util-linux / mount.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini mount implementation for busybox
4  *
5  * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>.
6  * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
7  * Copyright (C) 2005 by Rob Landley <rob@landley.net>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22  *
23  */
24
25 #include <limits.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <string.h>
30 #include <stdio.h>
31 #include <mntent.h>
32 #include <ctype.h>
33 #include <sys/mount.h>
34 #include <fcntl.h>              // for CONFIG_FEATURE_MOUNT_LOOP
35 #include <sys/ioctl.h>  // for CONFIG_FEATURE_MOUNT_LOOP
36 #include "busybox.h"
37
38 /* This is just a warning of a common mistake.  Possibly this should be a
39  * uclibc faq entry rather than in busybox... */
40 #if ENABLE_NFSMOUNT && defined(__UCLIBC__) && ! defined(__UCLIBC_HAS_RPC__)
41 #error "You need to build uClibc with UCLIBC_HAS_RPC for busybox mount with NFS support to compile."
42 #endif
43
44 // These two aren't always defined in old headers
45 #ifndef MS_BIND
46 #define MS_BIND         4096
47 #endif
48 #ifndef MS_MOVE
49 #define MS_MOVE         8192
50 #endif
51
52 /* Consume standard mount options (from -o options or --options).
53  * Set appropriate flags and collect unrecognized ones as a comma separated
54  * string to pass to kernel */ 
55
56 struct {
57         const char *name;
58         long flags;
59 } static const  mount_options[] = {
60         {"loop", 0},
61         {"defaults", 0},
62         {"noauto", 0},
63         {"ro", MS_RDONLY},
64         {"rw", ~MS_RDONLY},
65         {"nosuid", MS_NOSUID},
66         {"suid", ~MS_NOSUID},
67         {"dev", ~MS_NODEV},
68         {"nodev", MS_NODEV},
69         {"exec", ~MS_NOEXEC},
70         {"noexec", MS_NOEXEC},
71         {"sync", MS_SYNCHRONOUS},
72         {"async", ~MS_SYNCHRONOUS},
73         {"remount", MS_REMOUNT},
74         {"atime", MS_NOATIME},
75         {"noatime", MS_NOATIME},
76         {"diratime", MS_NODIRATIME},
77         {"nodiratime", MS_NODIRATIME},
78         {"bind", MS_BIND},
79         {"move", MS_MOVE}
80 };
81
82 /* Uses the mount_options list above */
83 static void parse_mount_options(char *options, int *flags, char **strflags)
84 {
85         // Loop through options
86         for(;;) {
87                 int i;
88                 char *comma = strchr(options, ',');
89
90                 if(comma) *comma = 0;
91
92                 // Find this option in mount_options
93                 for(i = 0; i < (sizeof(mount_options) / sizeof(*mount_options)); i++) {
94                         if(!strcasecmp(mount_options[i].name, options)) {
95                                 long fl = mount_options[i].flags;
96                                 if(fl < 0) *flags &= fl;
97                                 else *flags |= fl;
98                                 break;
99                         }
100                 }
101                 // Unrecognized mount option?
102                 if(i == sizeof(mount_options)) {
103                         // Add it to strflags, to pass on to kernel
104                         i = *strflags ? strlen(*strflags) : 0;
105                         *strflags = xrealloc(*strflags, i+strlen(options)+2);
106                         // Comma separated if it's not the first one
107                         if(i) (*strflags)[i] = ',';
108                         strcpy((*strflags)+i, options);
109                 }
110                 // Advance to next option, or finish
111                 if(comma) {
112                         *comma = ',';
113                         options = ++comma;
114                 } else break;
115         }
116 }
117
118 /* This does the work */
119
120 extern int mount_main(int argc, char **argv)
121 {
122         char *string_flags = 0, *fsType = 0, *blockDevice = 0, *directory = 0,
123                  *loopFile = 0, *buf = 0,
124                  *files[] = {"/etc/filesystems", "/proc/filesystems", 0};
125         int i, opt, all = FALSE, fakeIt = FALSE, allowWrite = FALSE,
126                 rc = EXIT_FAILURE, useMtab = ENABLE_FEATURE_MTAB_SUPPORT;
127         int flags=0xc0ed0000;   // Needed for linux 2.2, ignored by 2.4 and 2.6.
128         FILE *file = 0,*f = 0;
129         char path[PATH_MAX*2];
130         struct mntent m;
131         struct stat statbuf;
132
133         /* parse long options, like --bind and --move.  Note that -o option
134          * and --option are synonymous.  Yes, this means --remount,rw works. */
135
136         for(i = opt = 0; i < argc; i++) {
137                 if(argv[i][0] == '-' && argv[i][1] == '-')
138                         parse_mount_options(argv[i]+2, &flags, &string_flags);
139                 else argv[opt++] = argv[i];
140         }
141         argc = opt;
142
143         // Parse remaining options
144
145         while((opt = getopt(argc, argv, "o:t:rwafnv")) > 0) {
146                 switch (opt) {
147                 case 'o':
148                         parse_mount_options(optarg, &flags, &string_flags);
149                         break;
150                 case 't':
151                         fsType = optarg;
152                         break;
153                 case 'r':
154                         flags |= MS_RDONLY;
155                         break;
156                 case 'w':
157                         allowWrite=TRUE;
158                         break;
159                 case 'a':
160                         all = TRUE;
161                         break;
162                 case 'f':
163                         fakeIt = TRUE;
164                         break;
165                 case 'n':
166                         useMtab = FALSE;
167                         break;
168                 case 'v':
169                         break;          // ignore -v
170                 default:
171                         bb_show_usage();
172                 }
173         }
174
175         // If we have no arguments, show currently mounted filesystems
176
177         if(!all && (optind == argc)) {
178                 FILE *mountTable = setmntent(bb_path_mtab_file, "r");
179
180                 if(!mountTable) bb_perror_msg_and_die(bb_path_mtab_file);
181
182                 while (getmntent_r(mountTable,&m,path,sizeof(path))) {
183                         blockDevice = m.mnt_fsname;
184
185                         // Clean up display a little bit regarding root devie
186                         if(!strcmp(blockDevice, "rootfs")) continue;
187                         if(!strcmp(blockDevice, "/dev/root"))
188                                 blockDevice = find_block_device("/");
189
190                         if(!fsType || !strcmp(m.mnt_type, fsType))
191                                 printf("%s on %s type %s (%s)\n", blockDevice, m.mnt_dir,
192                                            m.mnt_type, m.mnt_opts);
193                         if(ENABLE_FEATURE_CLEAN_UP && blockDevice != m.mnt_fsname)
194                                 free(blockDevice);
195                 }
196                 endmntent(mountTable);
197                 return EXIT_SUCCESS;
198         }
199
200         /* The next argument is what to mount.  if there's an argument after that
201          * it's where to mount it.  If we're not mounting all, and we have both
202          * of these arguments, jump straight to the actual mount. */
203
204         statbuf.st_mode=0;
205         if(optind < argc)
206                 blockDevice = !stat(argv[optind], &statbuf) ?
207                                 bb_simplify_path(argv[optind]) :
208                                 (ENABLE_FEATURE_CLEAN_UP ? strdup(argv[optind]) : argv[optind]);
209         if(optind+1 < argc) directory = bb_simplify_path(argv[optind+1]);
210
211     // If we don't have to loop through fstab, skip ahead a bit.
212
213         if(!all && optind+1!=argc) goto singlemount;
214
215         // Loop through /etc/fstab entries to look up this entry.
216
217         if(!(file=setmntent("/etc/fstab","r")))
218                 bb_perror_msg_and_die("\nCannot read /etc/fstab");
219         for(;;) {
220
221                 // Get next fstab entry
222
223                 if(!getmntent_r(file,&m,path,sizeof(path))) {
224                         if(!all)
225                                 bb_perror_msg("Can't find %s in /etc/fstab\n", blockDevice);
226                         break;
227                 }
228                 
229                 // If we're mounting all and all doesn't mount this one, skip it.
230
231                 if(all) {
232                         if(strstr(m.mnt_opts,"noauto") || strstr(m.mnt_type,"swap"))
233                                 continue;
234                         flags=0;
235
236                 /* If we're mounting something specific and this isn't it, skip it.
237                  * Note we must match both the exact text in fstab (ala "proc") or
238                  * a full path from root */
239
240                 } else if(strcmp(blockDevice,m.mnt_fsname) &&
241                                   strcmp(argv[optind],m.mnt_fsname) &&
242                                   strcmp(blockDevice,m.mnt_dir) &&
243                                   strcmp(argv[optind],m.mnt_dir)) continue;
244
245                 /* Parse flags from /etc/fstab (unless this is a single mount
246                  * overriding fstab -- note the "all" test above zeroed the flags,
247                  * to prevent flags from previous entries affecting this one, so
248                  * the only way we could get here with nonzero flags is a single
249                  * mount). */
250
251                 if(!flags) {
252                         if(ENABLE_FEATURE_CLEAN_UP) free(string_flags);
253                         string_flags=NULL;
254                         parse_mount_options(m.mnt_opts, &flags, &string_flags);
255                 }
256
257                 /* Fill out remaining fields with info from mtab */
258
259                 if(ENABLE_FEATURE_CLEAN_UP) {
260                         free(blockDevice);
261                         blockDevice=strdup(m.mnt_fsname);
262                         free(directory);
263                         directory=strdup(m.mnt_type);
264                 } else {
265                         blockDevice=m.mnt_fsname;
266                         directory=m.mnt_dir;
267                 }
268                 fsType=m.mnt_type;
269
270                 /* Ok, we're ready to actually mount a specific source on a specific
271                  * directory now. */
272
273 singlemount:
274
275                 // If they said -w, override fstab
276
277                 if(allowWrite) flags&=~MS_RDONLY;
278
279                 // Might this be an NFS filesystem?
280
281                 if(ENABLE_NFSMOUNT && (!fsType || !strcmp(fsType,"nfs")) &&
282                         strchr(blockDevice, ':') != NULL)
283                 {
284                         if(nfsmount(blockDevice, directory, &flags, &string_flags, 1))
285                                 bb_perror_msg("nfsmount failed");
286                         else {
287                                 rc=EXIT_SUCCESS;
288                                 fsType="nfs";
289                         }
290                 } else {
291                         
292                         // Do we need to allocate a loopback device?
293
294                         if(ENABLE_FEATURE_MOUNT_LOOP && !fakeIt && S_ISREG(statbuf.st_mode))
295                         {
296                                 loopFile = blockDevice;
297                                 blockDevice = 0;
298                                 switch(set_loop(&blockDevice, loopFile, 0)) {
299                                         case 0:
300                                         case 1:
301                                                 break;
302                                         default:
303                                                 bb_error_msg_and_die(
304                                                         errno == EPERM || errno == EACCES ?
305                                                         bb_msg_perm_denied_are_you_root :
306                                                         "Couldn't setup loop device");
307                                                 break;
308                                 }
309                         }
310
311                         /* If we know the fstype (or don't need to), jump straight
312                          * to the actual mount. */
313
314                         if(fsType || (flags & (MS_REMOUNT | MS_BIND | MS_MOVE)))
315                                 goto mount_it_now;
316                 }
317                 
318                 // Loop through filesystem types until mount succeeds or we run out
319
320                 for(i = 0; files[i] && rc; i++) {
321                         f = fopen(files[i], "r");
322                         if(!f) continue;
323                         // Get next block device backed filesystem
324                         for(buf = 0; (buf = fsType = bb_get_chomped_line_from_file(f));
325                                         free(buf))
326                         {
327                                 // Skip funky entries in /proc
328                                 if(!strncmp(buf,"nodev",5) && isspace(buf[5])) continue;
329                                 
330                                 while(isspace(*fsType)) fsType++;
331                                 if(*buf=='#' || *buf=='*') continue;
332                                 if(!*fsType) continue;
333 mount_it_now:
334                                 // Okay, try to mount
335
336                                 if (!fakeIt) {
337                                         for(;;) {
338                                                 rc = mount(blockDevice, directory, fsType, flags, string_flags);
339                                                 if(!rc || (flags&MS_RDONLY) || (errno!=EACCES && errno!=EROFS))
340                                                         break;
341                                                 bb_error_msg("%s is write-protected, mounting read-only", blockDevice);
342                                                 flags|=MS_RDONLY;
343                                         }
344                                 }
345                                 if(!rc) break;
346                         }
347                         if(f) fclose(f);
348                         if(!f || !rc) break;
349                 }
350
351                 /* If the mount was sucessful, and we're maintaining an old-style
352                  * mtab file by hand, add new entry to it now. */
353                 if((!rc || fakeIt) && useMtab) {
354                         FILE *mountTable = setmntent(bb_path_mtab_file, "a+");
355
356                         if(!mountTable) bb_perror_msg(bb_path_mtab_file);
357                         else {
358                                 // Remove trailing / (if any) from directory we mounted on
359                                 int length=strlen(directory);
360                                 if(length>1 && directory[length-1] == '/')
361                                         directory[length-1]=0;
362
363                                 // Fill out structure (should be ok to re-use existing one).
364                                 m.mnt_fsname=blockDevice;
365                                 m.mnt_dir=directory;
366                                 m.mnt_type=fsType ? : "--bind";
367                                 m.mnt_opts=string_flags ? :
368                                         ((flags & MS_RDONLY) ? "ro" : "rw");
369                                 m.mnt_freq = 0;
370                                 m.mnt_passno = 0;
371
372                                 // Write and close
373                                 addmntent(mountTable, &m);
374                                 endmntent(mountTable);
375                         }
376                 } else {
377                         // Mount failed.  Clean up
378                         if(loopFile) {
379                                 del_loop(blockDevice);
380                                 if(ENABLE_FEATURE_CLEAN_UP) free(loopFile);
381                         }
382                         // Don't whine about already mounted fs when mounting all.
383                         if(rc<0 && errno == EBUSY && all) rc=0;
384                         else if (errno == EPERM)
385                                 bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
386                 }
387                 // We couldn't free this earlier becase fsType could be in buf.
388                 if(ENABLE_FEATURE_CLEAN_UP) {
389                         free(buf);
390                         free(blockDevice);
391                         free(directory);
392                 }
393                 if(!all) break;
394         }
395
396         if(file) endmntent(file);
397         if(rc) bb_perror_msg("Mounting %s on %s failed", blockDevice, directory);
398
399         return rc ? : EXIT_FAILURE;
400 }