umount.c revision 117712
1/*- 2 * Copyright (c) 1980, 1989, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#ifndef lint 35static const char copyright[] = 36"@(#) Copyright (c) 1980, 1989, 1993\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38#endif /* not lint */ 39 40#ifndef lint 41#if 0 42static char sccsid[] = "@(#)umount.c 8.8 (Berkeley) 5/8/95"; 43#endif 44static const char rcsid[] = 45 "$FreeBSD: head/sbin/umount/umount.c 117712 2003-07-18 01:10:16Z iedowse $"; 46#endif /* not lint */ 47 48#include <sys/param.h> 49#include <sys/mount.h> 50#include <sys/socket.h> 51 52#include <netdb.h> 53#include <rpc/rpc.h> 54#include <nfs/rpcv2.h> 55 56#include <err.h> 57#include <errno.h> 58#include <fstab.h> 59#include <stdio.h> 60#include <stdlib.h> 61#include <string.h> 62#include <unistd.h> 63 64#include "mounttab.h" 65 66#define ISDOT(x) ((x)[0] == '.' && (x)[1] == '\0') 67#define ISDOTDOT(x) ((x)[0] == '.' && (x)[1] == '.' && (x)[2] == '\0') 68 69typedef enum { MNTON, MNTFROM, NOTHING } mntwhat; 70typedef enum { MARK, UNMARK, NAME, COUNT, FREE } dowhat; 71 72struct addrinfo *nfshost_ai = NULL; 73int fflag, vflag; 74char *nfshost; 75 76struct statfs *checkmntlist (char *, char **); 77int checkvfsname (const char *, char **); 78struct statfs *getmntentry (const char *, const char *, mntwhat, char **, 79 dowhat); 80char *getrealname(char *, char *resolved_path); 81char **makevfslist (const char *); 82size_t mntinfo (struct statfs **); 83int namematch (struct addrinfo *); 84int sacmp (struct sockaddr *, struct sockaddr *); 85int umountall (char **); 86int checkname (char *, char **); 87int umountfs (char *, char *, fsid_t *, char *); 88void usage (void); 89int xdr_dir (XDR *, char *); 90 91int 92main(int argc, char *argv[]) 93{ 94 int all, errs, ch, mntsize, error; 95 char **typelist = NULL; 96 struct statfs *mntbuf, *sfs; 97 struct addrinfo hints; 98 99 /* Start disks transferring immediately. */ 100 sync(); 101 102 all = errs = 0; 103 while ((ch = getopt(argc, argv, "AaF:fh:t:v")) != -1) 104 switch (ch) { 105 case 'A': 106 all = 2; 107 break; 108 case 'a': 109 all = 1; 110 break; 111 case 'F': 112 setfstab(optarg); 113 break; 114 case 'f': 115 fflag = MNT_FORCE; 116 break; 117 case 'h': /* -h implies -A. */ 118 all = 2; 119 nfshost = optarg; 120 break; 121 case 't': 122 if (typelist != NULL) 123 err(1, "only one -t option may be specified"); 124 typelist = makevfslist(optarg); 125 break; 126 case 'v': 127 vflag = 1; 128 break; 129 default: 130 usage(); 131 /* NOTREACHED */ 132 } 133 argc -= optind; 134 argv += optind; 135 136 if ((argc == 0 && !all) || (argc != 0 && all)) 137 usage(); 138 139 /* -h implies "-t nfs" if no -t flag. */ 140 if ((nfshost != NULL) && (typelist == NULL)) 141 typelist = makevfslist("nfs"); 142 143 if (nfshost != NULL) { 144 memset(&hints, 0, sizeof hints); 145 error = getaddrinfo(nfshost, NULL, &hints, &nfshost_ai); 146 if (error) 147 errx(1, "%s: %s", nfshost, gai_strerror(error)); 148 } 149 150 switch (all) { 151 case 2: 152 if ((mntsize = mntinfo(&mntbuf)) <= 0) 153 break; 154 /* 155 * We unmount the nfs-mounts in the reverse order 156 * that they were mounted. 157 */ 158 for (errs = 0, mntsize--; mntsize > 0; mntsize--) { 159 sfs = &mntbuf[mntsize]; 160 if (checkvfsname(sfs->f_fstypename, typelist)) 161 continue; 162 if (umountfs(sfs->f_mntfromname, sfs->f_mntonname, 163 &sfs->f_fsid, sfs->f_fstypename) != 0) 164 errs = 1; 165 } 166 free(mntbuf); 167 break; 168 case 1: 169 if (setfsent() == 0) 170 err(1, "%s", getfstab()); 171 errs = umountall(typelist); 172 break; 173 case 0: 174 for (errs = 0; *argv != NULL; ++argv) 175 if (checkname(*argv, typelist) != 0) 176 errs = 1; 177 break; 178 } 179 (void)getmntentry(NULL, NULL, NOTHING, NULL, FREE); 180 exit(errs); 181} 182 183int 184umountall(char **typelist) 185{ 186 struct xvfsconf vfc; 187 struct fstab *fs; 188 int rval; 189 char *cp; 190 static int firstcall = 1; 191 192 if ((fs = getfsent()) != NULL) 193 firstcall = 0; 194 else if (firstcall) 195 errx(1, "fstab reading failure"); 196 else 197 return (0); 198 do { 199 /* Ignore the root. */ 200 if (strcmp(fs->fs_file, "/") == 0) 201 continue; 202 /* 203 * !!! 204 * Historic practice: ignore unknown FSTAB_* fields. 205 */ 206 if (strcmp(fs->fs_type, FSTAB_RW) && 207 strcmp(fs->fs_type, FSTAB_RO) && 208 strcmp(fs->fs_type, FSTAB_RQ)) 209 continue; 210 /* Ignore unknown file system types. */ 211 if (getvfsbyname(fs->fs_vfstype, &vfc) == -1) 212 continue; 213 if (checkvfsname(fs->fs_vfstype, typelist)) 214 continue; 215 216 /* 217 * We want to unmount the file systems in the reverse order 218 * that they were mounted. So, we save off the file name 219 * in some allocated memory, and then call recursively. 220 */ 221 if ((cp = malloc((size_t)strlen(fs->fs_file) + 1)) == NULL) 222 err(1, "malloc failed"); 223 (void)strcpy(cp, fs->fs_file); 224 rval = umountall(typelist); 225 rval = checkname(cp, typelist) || rval; 226 free(cp); 227 return (rval); 228 } while ((fs = getfsent()) != NULL); 229 return (0); 230} 231 232/* 233 * Do magic checks on mountpoint and device or hand over 234 * it to unmount(2) if everything fails. 235 */ 236int 237checkname(char *name, char **typelist) 238{ 239 size_t len; 240 int speclen; 241 char *resolved, realname[MAXPATHLEN]; 242 char *type, *hostp, *delimp, *origname; 243 struct statfs *sfs; 244 245 len = 0; 246 delimp = hostp = NULL; 247 sfs = NULL; 248 249 /* 250 * 1. Check if the name exists in the mounttable. 251 */ 252 sfs = checkmntlist(name, &type); 253 /* 254 * 2. Remove trailing slashes if there are any. After that 255 * we look up the name in the mounttable again. 256 */ 257 if (sfs == NULL) { 258 speclen = strlen(name); 259 for (speclen = strlen(name); 260 speclen > 1 && name[speclen - 1] == '/'; 261 speclen--) 262 name[speclen - 1] = '\0'; 263 sfs = checkmntlist(name, &type); 264 resolved = name; 265 /* Save off original name in origname */ 266 if ((origname = strdup(name)) == NULL) 267 err(1, "strdup"); 268 /* 269 * 3. Check if the deprecated nfs-syntax with an '@' 270 * has been used and translate it to the ':' syntax. 271 * Look up the name in the mounttable again. 272 */ 273 if (sfs == NULL) { 274 if ((delimp = strrchr(name, '@')) != NULL) { 275 hostp = delimp + 1; 276 if (*hostp != '\0') { 277 /* 278 * Make both '@' and ':' 279 * notations equal 280 */ 281 char *host = strdup(hostp); 282 len = strlen(hostp); 283 if (host == NULL) 284 err(1, "strdup"); 285 memmove(name + len + 1, name, 286 (size_t)(delimp - name)); 287 name[len] = ':'; 288 memmove(name, host, len); 289 free(host); 290 } 291 for (speclen = strlen(name); 292 speclen > 1 && name[speclen - 1] == '/'; 293 speclen--) 294 name[speclen - 1] = '\0'; 295 name[len + speclen + 1] = '\0'; 296 sfs = checkmntlist(name, &type); 297 resolved = name; 298 } 299 /* 300 * 4. Check if a relative mountpoint has been 301 * specified. This should happen as last check, 302 * the order is important. To prevent possible 303 * nfs-hangs, we just call realpath(3) on the 304 * basedir of mountpoint and add the dirname again. 305 * Check the name in mounttable one last time. 306 */ 307 if (sfs == NULL) { 308 (void)strcpy(name, origname); 309 if ((getrealname(name, realname)) != NULL) { 310 sfs = checkmntlist(realname, &type); 311 resolved = realname; 312 } 313 /* 314 * 5. All tests failed, just hand over the 315 * mountpoint to the kernel, maybe the statfs 316 * structure has been truncated or is not 317 * useful anymore because of a chroot(2). 318 * Please note that nfs will not be able to 319 * notify the nfs-server about unmounting. 320 * These things can change in future when the 321 * fstat structure get's more reliable, 322 * but at the moment we cannot thrust it. 323 */ 324 if (sfs == NULL) { 325 (void)strcpy(name, origname); 326 if (umountfs(NULL, origname, NULL, 327 "none") == 0) {; 328 warnx("%s not found in " 329 "mount table, " 330 "unmounted it anyway", 331 origname); 332 free(origname); 333 return (0); 334 } else 335 free(origname); 336 return (1); 337 } 338 } 339 } 340 free(origname); 341 } else 342 resolved = name; 343 344 if (checkvfsname(type, typelist)) 345 return (1); 346 347 /* 348 * Mark the uppermost mount as unmounted. 349 */ 350 (void)getmntentry(sfs->f_mntfromname, sfs->f_mntonname, NOTHING, &type, 351 MARK); 352 return (umountfs(sfs->f_mntfromname, sfs->f_mntonname, &sfs->f_fsid, 353 type)); 354} 355 356/* 357 * NFS stuff and unmount(2) call 358 */ 359int 360umountfs(char *mntfromname, char *mntonname, fsid_t *fsid, char *type) 361{ 362 char fsidbuf[64]; 363 enum clnt_stat clnt_stat; 364 struct timeval try; 365 struct addrinfo *ai, hints; 366 int do_rpc; 367 CLIENT *clp; 368 char *nfsdirname, *orignfsdirname; 369 char *hostp, *delimp; 370 371 ai = NULL; 372 do_rpc = 0; 373 hostp = NULL; 374 nfsdirname = delimp = orignfsdirname = NULL; 375 memset(&hints, 0, sizeof hints); 376 377 if (strcmp(type, "nfs") == 0) { 378 if ((nfsdirname = strdup(mntfromname)) == NULL) 379 err(1, "strdup"); 380 orignfsdirname = nfsdirname; 381 if ((delimp = strrchr(nfsdirname, ':')) != NULL) { 382 *delimp = '\0'; 383 hostp = nfsdirname; 384 getaddrinfo(hostp, NULL, &hints, &ai); 385 if (ai == NULL) { 386 warnx("can't get net id for host"); 387 } 388 nfsdirname = delimp + 1; 389 } 390 391 /* 392 * Check if we have to start the rpc-call later. 393 * If there are still identical nfs-names mounted, 394 * we skip the rpc-call. Obviously this has to 395 * happen before unmount(2), but it should happen 396 * after the previous namecheck. 397 * A non-NULL return means that this is the last 398 * mount from mntfromname that is still mounted. 399 */ 400 if (getmntentry(mntfromname, NULL, NOTHING, &type, COUNT) 401 != NULL) 402 do_rpc = 1; 403 } 404 405 if (!namematch(ai)) 406 return (1); 407 /* First try to unmount using the specified file system ID. */ 408 if (fsid != NULL) { 409 snprintf(fsidbuf, sizeof(fsidbuf), "FSID:%d:%d", fsid->val[0], 410 fsid->val[1]); 411 if (unmount(fsidbuf, fflag | MNT_BYFSID) != 0) { 412 warn("unmount of %s failed", mntonname); 413 if (errno != ENOENT) 414 return (1); 415 /* Compatability for old kernels. */ 416 warnx("retrying using path instead of file system ID"); 417 fsid = NULL; 418 } 419 } 420 if (fsid == NULL && unmount(mntonname, fflag) != 0) { 421 warn("unmount of %s failed", mntonname); 422 return (1); 423 } 424 if (vflag) 425 (void)printf("%s: unmount from %s\n", mntfromname, mntonname); 426 /* 427 * Report to mountd-server which nfsname 428 * has been unmounted. 429 */ 430 if (ai != NULL && !(fflag & MNT_FORCE) && do_rpc) { 431 clp = clnt_create(hostp, RPCPROG_MNT, RPCMNT_VER1, "udp"); 432 if (clp == NULL) { 433 warnx("%s: %s", hostp, 434 clnt_spcreateerror("RPCPROG_MNT")); 435 return (1); 436 } 437 clp->cl_auth = authsys_create_default(); 438 try.tv_sec = 20; 439 try.tv_usec = 0; 440 clnt_stat = clnt_call(clp, RPCMNT_UMOUNT, (xdrproc_t)xdr_dir, 441 nfsdirname, (xdrproc_t)xdr_void, (caddr_t)0, try); 442 if (clnt_stat != RPC_SUCCESS) { 443 warnx("%s: %s", hostp, 444 clnt_sperror(clp, "RPCMNT_UMOUNT")); 445 return (1); 446 } 447 /* 448 * Remove the unmounted entry from /var/db/mounttab. 449 */ 450 if (read_mtab()) { 451 clean_mtab(hostp, nfsdirname, vflag); 452 if(!write_mtab(vflag)) 453 warnx("cannot remove mounttab entry %s:%s", 454 hostp, nfsdirname); 455 free_mtab(); 456 } 457 free(orignfsdirname); 458 auth_destroy(clp->cl_auth); 459 clnt_destroy(clp); 460 } 461 return (0); 462} 463 464struct statfs * 465getmntentry(const char *fromname, const char *onname, mntwhat what, 466 char **type, dowhat mark) 467{ 468 static struct statfs *mntbuf; 469 static size_t mntsize = 0; 470 static char *mntcheck = NULL; 471 static char *mntcount = NULL; 472 int i, count; 473 474 if (mntsize <= 0) { 475 if ((mntsize = mntinfo(&mntbuf)) <= 0) 476 return (NULL); 477 } 478 if (mntcheck == NULL) { 479 if ((mntcheck = calloc(mntsize + 1, sizeof(int))) == NULL || 480 (mntcount = calloc(mntsize + 1, sizeof(int))) == NULL) 481 err(1, "calloc"); 482 } 483 /* 484 * We want to get the file systems in the reverse order 485 * that they were mounted. Mounted and unmounted file systems 486 * are marked or unmarked in a table called 'mntcheck'. 487 * Unmount(const char *dir, int flags) does only take the 488 * mountpoint as argument, not the destination. If we don't pay 489 * attention to the order, it can happen that an overlaying 490 * file system gets unmounted instead of the one the user 491 * has choosen. 492 */ 493 switch (mark) { 494 case NAME: 495 /* Return only the specific name */ 496 for (i = mntsize - 1; i >= 0; i--) { 497 if (fromname != NULL && !strcmp((what == MNTFROM) ? 498 mntbuf[i].f_mntfromname : mntbuf[i].f_mntonname, 499 fromname) && mntcheck[i] != 1) { 500 if (type) 501 *type = mntbuf[i].f_fstypename; 502 return (&mntbuf[i]); 503 } 504 } 505 506 return (NULL); 507 case MARK: 508 /* Mark current mount with '1' and return name */ 509 for (i = mntsize - 1; i >= 0; i--) { 510 if (mntcheck[i] == 0 && 511 (strcmp(mntbuf[i].f_mntonname, onname) == 0) && 512 (strcmp(mntbuf[i].f_mntfromname, fromname) == 0)) { 513 mntcheck[i] = 1; 514 return (&mntbuf[i]); 515 } 516 } 517 return (NULL); 518 case UNMARK: 519 /* Unmark current mount with '0' and return name */ 520 for (i = 0; i < mntsize; i++) { 521 if (mntcheck[i] == 1 && 522 (strcmp(mntbuf[i].f_mntonname, onname) == 0) && 523 (strcmp(mntbuf[i].f_mntfromname, fromname) == 0)) { 524 mntcheck[i] = 0; 525 return (&mntbuf[i]); 526 } 527 } 528 return (NULL); 529 case COUNT: 530 /* Count the equal mntfromnames */ 531 count = 0; 532 for (i = mntsize - 1; i >= 0; i--) { 533 if (strcmp(mntbuf[i].f_mntfromname, fromname) == 0) 534 count++; 535 } 536 /* Mark the already unmounted mounts and return 537 * mntfromname if count <= 1. Else return NULL. 538 */ 539 for (i = mntsize - 1; i >= 0; i--) { 540 if (strcmp(mntbuf[i].f_mntfromname, fromname) == 0) { 541 if (mntcount[i] == 1) 542 count--; 543 else { 544 mntcount[i] = 1; 545 break; 546 } 547 } 548 } 549 if (count <= 1) 550 return (&mntbuf[i]); 551 else 552 return (NULL); 553 case FREE: 554 free(mntbuf); 555 free(mntcheck); 556 free(mntcount); 557 return (NULL); 558 default: 559 return (NULL); 560 } 561} 562 563int 564sacmp(struct sockaddr *sa1, struct sockaddr *sa2) 565{ 566 void *p1, *p2; 567 int len; 568 569 if (sa1->sa_family != sa2->sa_family) 570 return (1); 571 572 switch (sa1->sa_family) { 573 case AF_INET: 574 p1 = &((struct sockaddr_in *)sa1)->sin_addr; 575 p2 = &((struct sockaddr_in *)sa2)->sin_addr; 576 len = 4; 577 break; 578 case AF_INET6: 579 p1 = &((struct sockaddr_in6 *)sa1)->sin6_addr; 580 p2 = &((struct sockaddr_in6 *)sa2)->sin6_addr; 581 len = 16; 582 if (((struct sockaddr_in6 *)sa1)->sin6_scope_id != 583 ((struct sockaddr_in6 *)sa2)->sin6_scope_id) 584 return (1); 585 break; 586 default: 587 return (1); 588 } 589 590 return memcmp(p1, p2, len); 591} 592 593int 594namematch(struct addrinfo *ai) 595{ 596 struct addrinfo *aip; 597 598 if (nfshost == NULL || nfshost_ai == NULL) 599 return (1); 600 601 while (ai != NULL) { 602 aip = nfshost_ai; 603 while (aip != NULL) { 604 if (sacmp(ai->ai_addr, aip->ai_addr) == 0) 605 return (1); 606 aip = aip->ai_next; 607 } 608 ai = ai->ai_next; 609 } 610 611 return (0); 612} 613 614struct statfs * 615checkmntlist(char *name, char **type) 616{ 617 struct statfs *sfs; 618 619 sfs = getmntentry(name, NULL, MNTON, type, NAME); 620 if (sfs == NULL) 621 sfs = getmntentry(name, NULL, MNTFROM, type, NAME); 622 return (sfs); 623} 624 625size_t 626mntinfo(struct statfs **mntbuf) 627{ 628 static struct statfs *origbuf; 629 size_t bufsize; 630 int mntsize; 631 632 mntsize = getfsstat(NULL, 0, MNT_NOWAIT); 633 if (mntsize <= 0) 634 return (0); 635 bufsize = (mntsize + 1) * sizeof(struct statfs); 636 if ((origbuf = malloc(bufsize)) == NULL) 637 err(1, "malloc"); 638 mntsize = getfsstat(origbuf, (long)bufsize, MNT_NOWAIT); 639 *mntbuf = origbuf; 640 return (mntsize); 641} 642 643char * 644getrealname(char *name, char *realname) 645{ 646 char *dirname; 647 int havedir; 648 size_t baselen; 649 size_t dirlen; 650 651 dirname = '\0'; 652 havedir = 0; 653 if (*name == '/') { 654 if (ISDOT(name + 1) || ISDOTDOT(name + 1)) 655 strcpy(realname, "/"); 656 else { 657 if ((dirname = strrchr(name + 1, '/')) == NULL) 658 snprintf(realname, MAXPATHLEN, "%s", name); 659 else 660 havedir = 1; 661 } 662 } else { 663 if (ISDOT(name) || ISDOTDOT(name)) 664 (void)realpath(name, realname); 665 else { 666 if ((dirname = strrchr(name, '/')) == NULL) { 667 if ((realpath(name, realname)) == NULL) 668 return (NULL); 669 } else 670 havedir = 1; 671 } 672 } 673 if (havedir) { 674 *dirname++ = '\0'; 675 if (ISDOT(dirname)) { 676 *dirname = '\0'; 677 if ((realpath(name, realname)) == NULL) 678 return (NULL); 679 } else if (ISDOTDOT(dirname)) { 680 *--dirname = '/'; 681 if ((realpath(name, realname)) == NULL) 682 return (NULL); 683 } else { 684 if ((realpath(name, realname)) == NULL) 685 return (NULL); 686 baselen = strlen(realname); 687 dirlen = strlen(dirname); 688 if (baselen + dirlen + 1 > MAXPATHLEN) 689 return (NULL); 690 if (realname[1] == '\0') { 691 memmove(realname + 1, dirname, dirlen); 692 realname[dirlen + 1] = '\0'; 693 } else { 694 realname[baselen] = '/'; 695 memmove(realname + baselen + 1, 696 dirname, dirlen); 697 realname[baselen + dirlen + 1] = '\0'; 698 } 699 } 700 } 701 return (realname); 702} 703 704/* 705 * xdr routines for mount rpc's 706 */ 707int 708xdr_dir(XDR *xdrsp, char *dirp) 709{ 710 711 return (xdr_string(xdrsp, &dirp, RPCMNT_PATHLEN)); 712} 713 714void 715usage() 716{ 717 718 (void)fprintf(stderr, "%s\n%s\n", 719 "usage: umount [-fv] special | node", 720 " umount -a | -A [ -F fstab] [-fv] [-h host] [-t type]"); 721 exit(1); 722} 723