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