1 /*
2  * umount(8) -- mount a filesystem
3  *
4  * Copyright (C) 2011 Red Hat, Inc. All rights reserved.
5  * Written by Karel Zak <kzak@redhat.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it would be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <getopt.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 
30 #include <libmount.h>
31 
32 #include "nls.h"
33 #include "c.h"
34 #include "env.h"
35 #include "optutils.h"
36 #include "exitcodes.h"
37 #include "closestream.h"
38 #include "pathnames.h"
39 #include "canonicalize.h"
40 #include "xalloc.h"
41 
table_parser_errcb(struct libmnt_table * tb,const char * filename,int line)42 static int table_parser_errcb(struct libmnt_table *tb __attribute__((__unused__)),
43 			const char *filename, int line)
44 {
45 	if (filename)
46 		warnx(_("%s: parse error: ignore entry at line %d."),
47 							filename, line);
48 	return 0;
49 }
50 
51 
print_version(void)52 static void __attribute__((__noreturn__)) print_version(void)
53 {
54 	const char *ver = NULL;
55 	const char **features = NULL, **p;
56 
57 	mnt_get_library_version(&ver);
58 	mnt_get_library_features(&features);
59 
60 	printf(_("%s from %s (libmount %s"),
61 			program_invocation_short_name,
62 			PACKAGE_STRING,
63 			ver);
64 	p = features;
65 	while (p && *p) {
66 		fputs(p == features ? ": " : ", ", stdout);
67 		fputs(*p++, stdout);
68 	}
69 	fputs(")\n", stdout);
70 	exit(MOUNT_EX_SUCCESS);
71 }
usage(FILE * out)72 static void __attribute__((__noreturn__)) usage(FILE *out)
73 {
74 	fputs(USAGE_HEADER, out);
75 	fprintf(out, _(
76 		" %1$s [-hV]\n"
77 		" %1$s -a [options]\n"
78 		" %1$s [options] <source> | <directory>\n"),
79 		program_invocation_short_name);
80 
81 	fputs(USAGE_OPTIONS, out);
82 	fputs(_(" -a, --all               unmount all filesystems\n"), out);
83 	fputs(_(" -A, --all-targets       unmount all mountpoints for the given device in the\n"
84 	        "                           current namespace\n"), out);
85 	fputs(_(" -c, --no-canonicalize   don't canonicalize paths\n"), out);
86 	fputs(_(" -d, --detach-loop       if mounted loop device, also free this loop device\n"), out);
87 	fputs(_("     --fake              dry run; skip the umount(2) syscall\n"), out);
88 	fputs(_(" -f, --force             force unmount (in case of an unreachable NFS system)\n"), out);
89 	fputs(_(" -i, --internal-only     don't call the umount.<type> helpers\n"), out);
90 	fputs(_(" -n, --no-mtab           don't write to /etc/mtab\n"), out);
91 	fputs(_(" -l, --lazy              detach the filesystem now, clean up things later\n"), out);
92 	fputs(_(" -O, --test-opts <list>  limit the set of filesystems (use with -a)\n"), out);
93 	fputs(_(" -R, --recursive         recursively unmount a target with all its children\n"), out);
94 	fputs(_(" -r, --read-only         in case unmounting fails, try to remount read-only\n"), out);
95 	fputs(_(" -t, --types <list>      limit the set of filesystem types\n"), out);
96 	fputs(_(" -v, --verbose           say what is being done\n"), out);
97 
98 	fputs(USAGE_SEPARATOR, out);
99 	fputs(USAGE_HELP, out);
100 	fputs(USAGE_VERSION, out);
101 	fprintf(out, USAGE_MAN_TAIL("umount(8)"));
102 
103 	exit(out == stderr ? MOUNT_EX_USAGE : MOUNT_EX_SUCCESS);
104 }
105 
exit_non_root(const char * option)106 static void __attribute__((__noreturn__)) exit_non_root(const char *option)
107 {
108 	const uid_t ruid = getuid();
109 	const uid_t euid = geteuid();
110 
111 	if (ruid == 0 && euid != 0) {
112 		/* user is root, but setuid to non-root */
113 		if (option)
114 			errx(MOUNT_EX_USAGE,
115 				_("only root can use \"--%s\" option "
116 				 "(effective UID is %u)"),
117 					option, euid);
118 		errx(MOUNT_EX_USAGE, _("only root can do that "
119 				 "(effective UID is %u)"), euid);
120 	}
121 	if (option)
122 		errx(MOUNT_EX_USAGE, _("only root can use \"--%s\" option"), option);
123 	errx(MOUNT_EX_USAGE, _("only root can do that"));
124 }
125 
success_message(struct libmnt_context * cxt)126 static void success_message(struct libmnt_context *cxt)
127 {
128 	const char *tgt, *src;
129 
130 	if (mnt_context_helper_executed(cxt)
131 	    || mnt_context_get_status(cxt) != 1)
132 		return;
133 
134 	tgt = mnt_context_get_target(cxt);
135 	if (!tgt)
136 		return;
137 
138 	src = mnt_context_get_source(cxt);
139 	if (src)
140 		warnx(_("%s (%s) unmounted"), tgt, src);
141 	else
142 		warnx(_("%s unmounted"), tgt);
143 }
144 
145 /*
146  * Handles generic errors like ENOMEM, ...
147  *
148  * rc = 0 success
149  *     <0 error (usually -errno)
150  *
151  * Returns exit status (MOUNT_EX_*) and prints error message.
152  */
handle_generic_errors(int rc,const char * msg,...)153 static int handle_generic_errors(int rc, const char *msg, ...)
154 {
155 	va_list va;
156 
157 	va_start(va, msg);
158 	errno = -rc;
159 
160 	switch(errno) {
161 	case EINVAL:
162 	case EPERM:
163 		vwarn(msg, va);
164 		rc = MOUNT_EX_USAGE;
165 		break;
166 	case ENOMEM:
167 		vwarn(msg, va);
168 		rc = MOUNT_EX_SYSERR;
169 		break;
170 	default:
171 		vwarn(msg, va);
172 		rc = MOUNT_EX_FAIL;
173 		break;
174 	}
175 	va_end(va);
176 	return rc;
177 }
178 
mk_exit_code(struct libmnt_context * cxt,int rc)179 static int mk_exit_code(struct libmnt_context *cxt, int rc)
180 {
181 	int syserr;
182 	const char *tgt = mnt_context_get_target(cxt);
183 
184 	if (mnt_context_helper_executed(cxt))
185 		/*
186 		 * /sbin/umount.<type> called, return status
187 		 */
188 		return mnt_context_get_helper_status(cxt);
189 
190 	if (rc == 0 && mnt_context_get_status(cxt) == 1)
191 		/*
192 		 * Libmount success && syscall success.
193 		 */
194 		return MOUNT_EX_SUCCESS;
195 
196 
197 	if (!mnt_context_syscall_called(cxt)) {
198 		/*
199 		 * libmount errors (extra library checks)
200 		 */
201 		if (rc == -EPERM && !mnt_context_tab_applied(cxt)) {
202 			/* failed to evaluate permissions because not found
203 			 * relevant entry in mtab */
204 			warnx(_("%s: not mounted"), tgt);
205 			return MOUNT_EX_USAGE;
206 		}
207 		return handle_generic_errors(rc, _("%s: umount failed"), tgt);
208 
209 	} else if (mnt_context_get_syscall_errno(cxt) == 0) {
210 		/*
211 		 * umount(2) syscall success, but something else failed
212 		 * (probably error in mtab processing).
213 		 */
214 		if (rc < 0)
215 			return handle_generic_errors(rc,
216 				_("%s: filesystem was unmounted, but mount(8) failed"),
217 				tgt);
218 
219 		return MOUNT_EX_SOFTWARE;	/* internal error */
220 
221 	}
222 
223 	/*
224 	 * umount(2) errors
225 	 */
226 	syserr = mnt_context_get_syscall_errno(cxt);
227 
228 	switch(syserr) {
229 	case ENXIO:
230 		warnx(_("%s: invalid block device"), tgt);	/* ??? */
231 		break;
232 	case EINVAL:
233 		warnx(_("%s: not mounted"), tgt);
234 		break;
235 	case EIO:
236 		warnx(_("%s: can't write superblock"), tgt);
237 		break;
238 	case EBUSY:
239 		warnx(_("%s: target is busy\n"
240 		       "        (In some cases useful info about processes that\n"
241 		       "         use the device is found by lsof(8) or fuser(1).)"),
242 			tgt);
243 		break;
244 	case ENOENT:
245 		if (tgt && *tgt)
246 			warnx(_("%s: mountpoint not found"), tgt);
247 		else
248 			warnx(_("undefined mountpoint"));
249 		break;
250 	case EPERM:
251 		warnx(_("%s: must be superuser to unmount"), tgt);
252 		break;
253 	case EACCES:
254 		warnx(_("%s: block devices are not permitted on filesystem"), tgt);
255 		break;
256 	default:
257 		errno = syserr;
258 		warn("%s", tgt);
259 		break;
260 	}
261 	return MOUNT_EX_FAIL;
262 }
263 
umount_all(struct libmnt_context * cxt)264 static int umount_all(struct libmnt_context *cxt)
265 {
266 	struct libmnt_iter *itr;
267 	struct libmnt_fs *fs;
268 	int mntrc, ignored, rc = 0;
269 
270 	itr = mnt_new_iter(MNT_ITER_BACKWARD);
271 	if (!itr) {
272 		warn(_("failed to initialize libmount iterator"));
273 		return MOUNT_EX_SYSERR;
274 	}
275 
276 	while (mnt_context_next_umount(cxt, itr, &fs, &mntrc, &ignored) == 0) {
277 
278 		const char *tgt = mnt_fs_get_target(fs);
279 
280 		if (ignored) {
281 			if (mnt_context_is_verbose(cxt))
282 				printf(_("%-25s: ignored\n"), tgt);
283 		} else {
284 			int xrc = mk_exit_code(cxt, mntrc);
285 
286 			if (xrc == MOUNT_EX_SUCCESS
287 			    && mnt_context_is_verbose(cxt))
288 				printf("%-25s: successfully unmounted\n", tgt);
289 			rc |= xrc;
290 		}
291 	}
292 
293 	mnt_free_iter(itr);
294 	return rc;
295 }
296 
umount_one(struct libmnt_context * cxt,const char * spec)297 static int umount_one(struct libmnt_context *cxt, const char *spec)
298 {
299 	int rc;
300 
301 	if (!spec)
302 		return MOUNT_EX_SOFTWARE;
303 
304 	if (mnt_context_set_target(cxt, spec))
305 		err(MOUNT_EX_SYSERR, _("failed to set umount target"));
306 
307 	rc = mnt_context_umount(cxt);
308 	rc = mk_exit_code(cxt, rc);
309 
310 	if (rc == MOUNT_EX_SUCCESS && mnt_context_is_verbose(cxt))
311 		success_message(cxt);
312 
313 	mnt_reset_context(cxt);
314 	return rc;
315 }
316 
new_mountinfo(struct libmnt_context * cxt)317 static struct libmnt_table *new_mountinfo(struct libmnt_context *cxt)
318 {
319 	struct libmnt_table *tb = mnt_new_table();
320 	if (!tb)
321 		err(MOUNT_EX_SYSERR, _("libmount table allocation failed"));
322 
323 	mnt_table_set_parser_errcb(tb, table_parser_errcb);
324 	mnt_table_set_cache(tb, mnt_context_get_cache(cxt));
325 
326 	if (mnt_table_parse_file(tb, _PATH_PROC_MOUNTINFO)) {
327 		warn(_("failed to parse %s"), _PATH_PROC_MOUNTINFO);
328 		mnt_unref_table(tb);
329 		tb = NULL;
330 	}
331 
332 	return tb;
333 }
334 
335 /*
336  * like umount_one() but does not return error is @spec not mounted
337  */
umount_one_if_mounted(struct libmnt_context * cxt,const char * spec)338 static int umount_one_if_mounted(struct libmnt_context *cxt, const char *spec)
339 {
340 	int rc;
341 	struct libmnt_fs *fs;
342 
343 	rc = mnt_context_find_umount_fs(cxt, spec, &fs);
344 	if (rc == 1) {
345 		rc = MOUNT_EX_SUCCESS;		/* alredy unmounted */
346 		mnt_reset_context(cxt);
347 	} else if (rc < 0) {
348 		rc = mk_exit_code(cxt, rc);	/* error */
349 		mnt_reset_context(cxt);
350 	} else
351 		rc = umount_one(cxt, mnt_fs_get_target(fs));
352 
353 	return rc;
354 }
355 
umount_do_recurse(struct libmnt_context * cxt,struct libmnt_table * tb,struct libmnt_fs * fs)356 static int umount_do_recurse(struct libmnt_context *cxt,
357 		struct libmnt_table *tb, struct libmnt_fs *fs)
358 {
359 	struct libmnt_fs *child;
360 	struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_BACKWARD);
361 	int rc;
362 
363 	if (!itr)
364 		err(MOUNT_EX_SYSERR, _("libmount iterator allocation failed"));
365 
366 	/* umount all childern */
367 	for (;;) {
368 		rc = mnt_table_next_child_fs(tb, itr, fs, &child);
369 		if (rc < 0) {
370 			warnx(_("failed to get child fs of %s"),
371 					mnt_fs_get_target(fs));
372 			rc = MOUNT_EX_SOFTWARE;
373 			goto done;
374 		} else if (rc == 1)
375 			break;		/* no more children */
376 
377 		rc = umount_do_recurse(cxt, tb, child);
378 		if (rc != MOUNT_EX_SUCCESS)
379 			goto done;
380 	}
381 
382 	rc = umount_one_if_mounted(cxt, mnt_fs_get_target(fs));
383 done:
384 	mnt_free_iter(itr);
385 	return rc;
386 }
387 
umount_recursive(struct libmnt_context * cxt,const char * spec)388 static int umount_recursive(struct libmnt_context *cxt, const char *spec)
389 {
390 	struct libmnt_table *tb;
391 	struct libmnt_fs *fs;
392 	int rc;
393 
394 	tb = new_mountinfo(cxt);
395 	if (!tb)
396 		return MOUNT_EX_SOFTWARE;
397 
398 	/* it's always real mountpoint, don't assume that the target maybe a device */
399 	mnt_context_disable_swapmatch(cxt, 1);
400 
401 	fs = mnt_table_find_target(tb, spec, MNT_ITER_BACKWARD);
402 	if (fs)
403 		rc = umount_do_recurse(cxt, tb, fs);
404 	else {
405 		rc = MOUNT_EX_USAGE;
406 		warnx(access(spec, F_OK) == 0 ?
407 				_("%s: not mounted") :
408 				_("%s: not found"), spec);
409 	}
410 
411 	mnt_unref_table(tb);
412 	return rc;
413 }
414 
umount_alltargets(struct libmnt_context * cxt,const char * spec,int rec)415 static int umount_alltargets(struct libmnt_context *cxt, const char *spec, int rec)
416 {
417 	struct libmnt_fs *fs;
418 	struct libmnt_table *tb;
419 	struct libmnt_iter *itr = NULL;
420 	dev_t devno = 0;
421 	int rc;
422 
423 	/* Convert @spec to device name, Use the same logic like regular
424 	 * "umount <spec>".
425 	 */
426 	rc = mnt_context_find_umount_fs(cxt, spec, &fs);
427 	if (rc == 1) {
428 		rc = MOUNT_EX_USAGE;
429 		warnx(access(spec, F_OK) == 0 ?
430 				_("%s: not mounted") :
431 				_("%s: not found"), spec);
432 		return rc;
433 	}
434 	if (rc < 0)
435 		return mk_exit_code(cxt, rc);		/* error */
436 
437 	if (!mnt_fs_get_srcpath(fs) || !mnt_fs_get_devno(fs))
438 		errx(MOUNT_EX_USAGE, _("%s: failed to determine source "
439 				"(--all-targets is unsupported on systems with "
440 				"regular mtab file)."), spec);
441 
442 	itr = mnt_new_iter(MNT_ITER_BACKWARD);
443 	if (!itr)
444 		err(MOUNT_EX_SYSERR, _("libmount iterator allocation failed"));
445 
446 	/* get on @cxt independent mountinfo */
447 	tb = new_mountinfo(cxt);
448 	if (!tb) {
449 		rc = MOUNT_EX_SOFTWARE;
450 		goto done;
451 	}
452 
453 	/* Note that @fs is from mount context and the context will be reseted
454 	 * after each umount() call */
455 	devno = mnt_fs_get_devno(fs);
456 	fs = NULL;
457 
458 	mnt_reset_context(cxt);
459 
460 	while (mnt_table_next_fs(tb, itr, &fs) == 0) {
461 		if (mnt_fs_get_devno(fs) != devno)
462 			continue;
463 		mnt_context_disable_swapmatch(cxt, 1);
464 		if (rec)
465 			rc = umount_do_recurse(cxt, tb, fs);
466 		else
467 			rc = umount_one_if_mounted(cxt, mnt_fs_get_target(fs));
468 
469 		if (rc != MOUNT_EX_SUCCESS)
470 			break;
471 	}
472 
473 done:
474 	mnt_free_iter(itr);
475 	mnt_unref_table(tb);
476 
477 	return rc;
478 }
479 
480 /*
481  * Check path -- non-root user should not be able to resolve path which is
482  * unreadable for him.
483  */
sanitize_path(const char * path)484 static char *sanitize_path(const char *path)
485 {
486 	char *p;
487 
488 	if (!path)
489 		return NULL;
490 
491 	p = canonicalize_path_restricted(path);
492 	if (!p)
493 		err(MOUNT_EX_USAGE, "%s", path);
494 
495 	return p;
496 }
497 
main(int argc,char ** argv)498 int main(int argc, char **argv)
499 {
500 	int c, rc = 0, all = 0, recursive = 0, alltargets = 0;
501 	struct libmnt_context *cxt;
502 	char *types = NULL;
503 
504 	enum {
505 		UMOUNT_OPT_FAKE = CHAR_MAX + 1,
506 	};
507 
508 	static const struct option longopts[] = {
509 		{ "all", 0, 0, 'a' },
510 		{ "all-targets", 0, 0, 'A' },
511 		{ "detach-loop", 0, 0, 'd' },
512 		{ "fake", 0, 0, UMOUNT_OPT_FAKE },
513 		{ "force", 0, 0, 'f' },
514 		{ "help", 0, 0, 'h' },
515 		{ "internal-only", 0, 0, 'i' },
516 		{ "lazy", 0, 0, 'l' },
517 		{ "no-canonicalize", 0, 0, 'c' },
518 		{ "no-mtab", 0, 0, 'n' },
519 		{ "read-only", 0, 0, 'r' },
520 		{ "recursive", 0, 0, 'R' },
521 		{ "test-opts", 1, 0, 'O' },
522 		{ "types", 1, 0, 't' },
523 		{ "verbose", 0, 0, 'v' },
524 		{ "version", 0, 0, 'V' },
525 		{ NULL, 0, 0, 0 }
526 	};
527 
528 	static const ul_excl_t excl[] = {       /* rows and cols in in ASCII order */
529 		{ 'A','a' },			/* all-targets,all */
530 		{ 'R','a' },			/* recursive,all */
531 		{ 'O','R','t'},			/* options,recursive,types */
532 		{ 'R','r' },			/* recursive,read-only */
533 		{ 0 }
534 	};
535 	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
536 
537 	sanitize_env();
538 	setlocale(LC_ALL, "");
539 	bindtextdomain(PACKAGE, LOCALEDIR);
540 	textdomain(PACKAGE);
541 	atexit(close_stdout);
542 
543 	mnt_init_debug(0);
544 	cxt = mnt_new_context();
545 	if (!cxt)
546 		err(MOUNT_EX_SYSERR, _("libmount context allocation failed"));
547 
548 	mnt_context_set_tables_errcb(cxt, table_parser_errcb);
549 
550 	while ((c = getopt_long(argc, argv, "aAcdfhilnRrO:t:vV",
551 					longopts, NULL)) != -1) {
552 
553 
554 		/* only few options are allowed for non-root users */
555 		if (mnt_context_is_restricted(cxt) && !strchr("hdilVv", c))
556 			exit_non_root(option_to_longopt(c, longopts));
557 
558 		err_exclusive_options(c, longopts, excl, excl_st);
559 
560 		switch(c) {
561 		case 'a':
562 			all = 1;
563 			break;
564 		case 'A':
565 			alltargets = 1;
566 			break;
567 		case 'c':
568 			mnt_context_disable_canonicalize(cxt, TRUE);
569 			break;
570 		case 'd':
571 			mnt_context_enable_loopdel(cxt, TRUE);
572 			break;
573 		case UMOUNT_OPT_FAKE:
574 			mnt_context_enable_fake(cxt, TRUE);
575 			break;
576 		case 'f':
577 			mnt_context_enable_force(cxt, TRUE);
578 			break;
579 		case 'h':
580 			usage(stdout);
581 			break;
582 		case 'i':
583 			mnt_context_disable_helpers(cxt, TRUE);
584 			break;
585 		case 'l':
586 			mnt_context_enable_lazy(cxt, TRUE);
587 			break;
588 		case 'n':
589 			mnt_context_disable_mtab(cxt, TRUE);
590 			break;
591 		case 'r':
592 			mnt_context_enable_rdonly_umount(cxt, TRUE);
593 			break;
594 		case 'R':
595 			recursive = TRUE;
596 			break;
597 		case 'O':
598 			if (mnt_context_set_options_pattern(cxt, optarg))
599 				err(MOUNT_EX_SYSERR, _("failed to set options pattern"));
600 			break;
601 		case 't':
602 			types = optarg;
603 			break;
604 		case 'v':
605 			mnt_context_enable_verbose(cxt, TRUE);
606 			break;
607 		case 'V':
608 			print_version();
609 			break;
610 		default:
611 			usage(stderr);
612 			break;
613 		}
614 	}
615 
616 	argc -= optind;
617 	argv += optind;
618 
619 	if (all) {
620 		if (!types)
621 			types = "noproc,nodevfs,nodevpts,nosysfs,norpc_pipefs,nonfsd";
622 
623 		mnt_context_set_fstype_pattern(cxt, types);
624 		rc = umount_all(cxt);
625 
626 	} else if (argc < 1) {
627 		usage(stderr);
628 
629 	} else if (alltargets) {
630 		while (argc--)
631 			rc += umount_alltargets(cxt, *argv++, recursive);
632 	} else if (recursive) {
633 		while (argc--)
634 			rc += umount_recursive(cxt, *argv++);
635 	} else {
636 		while (argc--) {
637 			char *path = *argv;
638 
639 			if (mnt_context_is_restricted(cxt)
640 			    && !mnt_tag_is_valid(path))
641 				path = sanitize_path(path);
642 
643 			rc += umount_one(cxt, path);
644 
645 			if (path != *argv)
646 				free(path);
647 			argv++;
648 		}
649 	}
650 
651 	mnt_free_context(cxt);
652 	return (rc < 256) ? rc : 255;
653 }
654 
655