18fae3551SRodney W. Grimes /*- 28fae3551SRodney W. Grimes * Copyright (c) 1980, 1989, 1993 38fae3551SRodney W. Grimes * The Regents of the University of California. All rights reserved. 48fae3551SRodney W. Grimes * 58fae3551SRodney W. Grimes * Redistribution and use in source and binary forms, with or without 68fae3551SRodney W. Grimes * modification, are permitted provided that the following conditions 78fae3551SRodney W. Grimes * are met: 88fae3551SRodney W. Grimes * 1. Redistributions of source code must retain the above copyright 98fae3551SRodney W. Grimes * notice, this list of conditions and the following disclaimer. 108fae3551SRodney W. Grimes * 2. Redistributions in binary form must reproduce the above copyright 118fae3551SRodney W. Grimes * notice, this list of conditions and the following disclaimer in the 128fae3551SRodney W. Grimes * documentation and/or other materials provided with the distribution. 138fae3551SRodney W. Grimes * 3. All advertising materials mentioning features or use of this software 148fae3551SRodney W. Grimes * must display the following acknowledgement: 158fae3551SRodney W. Grimes * This product includes software developed by the University of 168fae3551SRodney W. Grimes * California, Berkeley and its contributors. 178fae3551SRodney W. Grimes * 4. Neither the name of the University nor the names of its contributors 188fae3551SRodney W. Grimes * may be used to endorse or promote products derived from this software 198fae3551SRodney W. Grimes * without specific prior written permission. 208fae3551SRodney W. Grimes * 218fae3551SRodney W. Grimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 228fae3551SRodney W. Grimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 238fae3551SRodney W. Grimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 248fae3551SRodney W. Grimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 258fae3551SRodney W. Grimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 268fae3551SRodney W. Grimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 278fae3551SRodney W. Grimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 288fae3551SRodney W. Grimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 298fae3551SRodney W. Grimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 308fae3551SRodney W. Grimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 318fae3551SRodney W. Grimes * SUCH DAMAGE. 328fae3551SRodney W. Grimes */ 338fae3551SRodney W. Grimes 348fae3551SRodney W. Grimes #ifndef lint 35adb378ceSPhilippe Charnier static const char copyright[] = 368fae3551SRodney W. Grimes "@(#) Copyright (c) 1980, 1989, 1993\n\ 378fae3551SRodney W. Grimes The Regents of the University of California. All rights reserved.\n"; 388fae3551SRodney W. Grimes #endif /* not lint */ 398fae3551SRodney W. Grimes 408fae3551SRodney W. Grimes #ifndef lint 41adb378ceSPhilippe Charnier #if 0 42d499a0efSBruce Evans static char sccsid[] = "@(#)umount.c 8.8 (Berkeley) 5/8/95"; 43adb378ceSPhilippe Charnier #endif 44adb378ceSPhilippe Charnier static const char rcsid[] = 457f3dea24SPeter Wemm "$FreeBSD$"; 468fae3551SRodney W. Grimes #endif /* not lint */ 478fae3551SRodney W. Grimes 488fae3551SRodney W. Grimes #include <sys/param.h> 498fae3551SRodney W. Grimes #include <sys/mount.h> 508360efbdSAlfred Perlstein #include <sys/socket.h> 518fae3551SRodney W. Grimes 528fae3551SRodney W. Grimes #include <netdb.h> 538fae3551SRodney W. Grimes #include <rpc/rpc.h> 548fae3551SRodney W. Grimes #include <nfs/rpcv2.h> 558fae3551SRodney W. Grimes 568fae3551SRodney W. Grimes #include <err.h> 578fae3551SRodney W. Grimes #include <fstab.h> 588fae3551SRodney W. Grimes #include <stdio.h> 598fae3551SRodney W. Grimes #include <stdlib.h> 608fae3551SRodney W. Grimes #include <string.h> 618fae3551SRodney W. Grimes #include <unistd.h> 628fae3551SRodney W. Grimes 63a69497d7SMatthew Dillon #include "mounttab.h" 64a69497d7SMatthew Dillon 65bc70c172SBrian Feldman #define ISDOT(x) ((x)[0] == '.' && (x)[1] == '\0') 66bc70c172SBrian Feldman #define ISDOTDOT(x) ((x)[0] == '.' && (x)[1] == '.' && (x)[2] == '\0') 678fae3551SRodney W. Grimes 68bc70c172SBrian Feldman typedef enum { MNTON, MNTFROM, NOTHING } mntwhat; 69bc70c172SBrian Feldman typedef enum { MARK, UNMARK, NAME, COUNT, FREE } dowhat; 70bc70c172SBrian Feldman 71a69497d7SMatthew Dillon struct mtablist *mtabhead; 728360efbdSAlfred Perlstein struct addrinfo *nfshost_ai = NULL; 730fe9a7daSBrian Feldman int fflag, vflag; 748fae3551SRodney W. Grimes char *nfshost; 758fae3551SRodney W. Grimes 76bc70c172SBrian Feldman void checkmntlist (char *, char **, char **, char **); 77bc70c172SBrian Feldman int checkvfsname (const char *, char **); 78bc70c172SBrian Feldman char *getmntname (const char *, const char *, 79bc70c172SBrian Feldman mntwhat, char **, dowhat); 80bc70c172SBrian Feldman char *getrealname(char *, char *resolved_path); 81bc70c172SBrian Feldman char **makevfslist (const char *); 82bc70c172SBrian Feldman size_t mntinfo (struct statfs **); 838360efbdSAlfred Perlstein int namematch (struct addrinfo *); 848360efbdSAlfred Perlstein int sacmp (struct sockaddr *, struct sockaddr *); 85bc70c172SBrian Feldman int umountall (char **); 868360efbdSAlfred Perlstein int checkname (char *, char **); 878360efbdSAlfred Perlstein int umountfs (char *, char *, char *); 88bc70c172SBrian Feldman void usage (void); 89bc70c172SBrian Feldman int xdr_dir (XDR *, char *); 908fae3551SRodney W. Grimes 918fae3551SRodney W. Grimes int 92bc70c172SBrian Feldman main(int argc, char *argv[]) 938fae3551SRodney W. Grimes { 948360efbdSAlfred Perlstein int all, errs, ch, mntsize, error; 95bc70c172SBrian Feldman char **typelist = NULL, *mntonname, *mntfromname; 96bc70c172SBrian Feldman char *type, *mntfromnamerev, *mntonnamerev; 97d499a0efSBruce Evans struct statfs *mntbuf; 988360efbdSAlfred Perlstein struct addrinfo hints; 998fae3551SRodney W. Grimes 1008fae3551SRodney W. Grimes /* Start disks transferring immediately. */ 1018fae3551SRodney W. Grimes sync(); 1028fae3551SRodney W. Grimes 1030fe9a7daSBrian Feldman all = errs = 0; 104bc70c172SBrian Feldman while ((ch = getopt(argc, argv, "Aafh:t:v")) != -1) 1058fae3551SRodney W. Grimes switch (ch) { 106d499a0efSBruce Evans case 'A': 107d499a0efSBruce Evans all = 2; 108d499a0efSBruce Evans break; 1098fae3551SRodney W. Grimes case 'a': 1108fae3551SRodney W. Grimes all = 1; 1118fae3551SRodney W. Grimes break; 1128fae3551SRodney W. Grimes case 'f': 1138fae3551SRodney W. Grimes fflag = MNT_FORCE; 1148fae3551SRodney W. Grimes break; 115d499a0efSBruce Evans case 'h': /* -h implies -A. */ 116d499a0efSBruce Evans all = 2; 1178fae3551SRodney W. Grimes nfshost = optarg; 1188fae3551SRodney W. Grimes break; 1198fae3551SRodney W. Grimes case 't': 120d499a0efSBruce Evans if (typelist != NULL) 121bc70c172SBrian Feldman err(1, "only one -t option may be specified"); 122d499a0efSBruce Evans typelist = makevfslist(optarg); 1238fae3551SRodney W. Grimes break; 1248fae3551SRodney W. Grimes case 'v': 1258fae3551SRodney W. Grimes vflag = 1; 1268fae3551SRodney W. Grimes break; 1278fae3551SRodney W. Grimes default: 1288fae3551SRodney W. Grimes usage(); 1298fae3551SRodney W. Grimes /* NOTREACHED */ 1308fae3551SRodney W. Grimes } 1318fae3551SRodney W. Grimes argc -= optind; 1328fae3551SRodney W. Grimes argv += optind; 1338fae3551SRodney W. Grimes 134adb378ceSPhilippe Charnier if ((argc == 0 && !all) || (argc != 0 && all)) 1358fae3551SRodney W. Grimes usage(); 1368fae3551SRodney W. Grimes 1378fae3551SRodney W. Grimes /* -h implies "-t nfs" if no -t flag. */ 1388fae3551SRodney W. Grimes if ((nfshost != NULL) && (typelist == NULL)) 139d499a0efSBruce Evans typelist = makevfslist("nfs"); 1408fae3551SRodney W. Grimes 1418360efbdSAlfred Perlstein if (nfshost != NULL) { 1428360efbdSAlfred Perlstein memset(&hints, 0, sizeof hints); 1438360efbdSAlfred Perlstein error = getaddrinfo(nfshost, NULL, &hints, &nfshost_ai); 1448360efbdSAlfred Perlstein if (error) { 1458360efbdSAlfred Perlstein fprintf(stderr, "ndp: %s: %s\n", nfshost, 1468360efbdSAlfred Perlstein gai_strerror(error)); 1478360efbdSAlfred Perlstein } 1488360efbdSAlfred Perlstein } 1498360efbdSAlfred Perlstein 150d499a0efSBruce Evans switch (all) { 151d499a0efSBruce Evans case 2: 152bc70c172SBrian Feldman if ((mntsize = mntinfo(&mntbuf)) <= 0) 153d499a0efSBruce Evans break; 154bc70c172SBrian Feldman /* 155bc70c172SBrian Feldman * We unmount the nfs-mounts in the reverse order 156bc70c172SBrian Feldman * that they were mounted. 157bc70c172SBrian Feldman */ 158bc70c172SBrian Feldman for (errs = 0, mntsize--; mntsize > 0; mntsize--) { 159bc70c172SBrian Feldman if (checkvfsname(mntbuf[mntsize].f_fstypename, 160bc70c172SBrian Feldman typelist)) 161d499a0efSBruce Evans continue; 162bc70c172SBrian Feldman /* 163bc70c172SBrian Feldman * Check if a mountpoint is laid over by another mount. 164bc70c172SBrian Feldman * A warning will be printed to stderr if this is 165bc70c172SBrian Feldman * the case. The laid over mount remains unmounted. 166bc70c172SBrian Feldman */ 167bc70c172SBrian Feldman mntonname = mntbuf[mntsize].f_mntonname; 168bc70c172SBrian Feldman mntfromname = mntbuf[mntsize].f_mntfromname; 169bc70c172SBrian Feldman mntonnamerev = getmntname(getmntname(mntonname, 170bc70c172SBrian Feldman NULL, MNTFROM, &type, NAME), NULL, 171bc70c172SBrian Feldman MNTON, &type, NAME); 172bc70c172SBrian Feldman 173bc70c172SBrian Feldman mntfromnamerev = getmntname(mntonnamerev, 174bc70c172SBrian Feldman NULL, MNTFROM, &type, NAME); 175bc70c172SBrian Feldman 176bc70c172SBrian Feldman if (strcmp(mntonnamerev, mntonname) == 0 && 177bc70c172SBrian Feldman strcmp(mntfromnamerev, mntfromname ) != 0) 178bc70c172SBrian Feldman warnx("cannot umount %s, %s\n " 179bc70c172SBrian Feldman "is mounted there, umount it first", 180bc70c172SBrian Feldman mntonname, mntfromnamerev); 181bc70c172SBrian Feldman 1828360efbdSAlfred Perlstein if (checkname(mntbuf[mntsize].f_mntonname, 183bc70c172SBrian Feldman typelist) != 0) 184d499a0efSBruce Evans errs = 1; 185d499a0efSBruce Evans } 186bc70c172SBrian Feldman free(mntbuf); 187d499a0efSBruce Evans break; 188d499a0efSBruce Evans case 1: 1898fae3551SRodney W. Grimes if (setfsent() == 0) 1908fae3551SRodney W. Grimes err(1, "%s", _PATH_FSTAB); 191d499a0efSBruce Evans errs = umountall(typelist); 192d499a0efSBruce Evans break; 193d499a0efSBruce Evans case 0: 1948fae3551SRodney W. Grimes for (errs = 0; *argv != NULL; ++argv) 1958360efbdSAlfred Perlstein if (checkname(*argv, typelist) != 0) 196d499a0efSBruce Evans errs = 1; 197d499a0efSBruce Evans break; 198d499a0efSBruce Evans } 199bc70c172SBrian Feldman (void)getmntname(NULL, NULL, NOTHING, NULL, FREE); 2008fae3551SRodney W. Grimes exit(errs); 2018fae3551SRodney W. Grimes } 2028fae3551SRodney W. Grimes 2038fae3551SRodney W. Grimes int 204bc70c172SBrian Feldman umountall(char **typelist) 2058fae3551SRodney W. Grimes { 2060602ee7cSBrian Feldman struct vfsconf vfc; 2078fae3551SRodney W. Grimes struct fstab *fs; 208adb378ceSPhilippe Charnier int rval; 2098fae3551SRodney W. Grimes char *cp; 2100602ee7cSBrian Feldman static int firstcall = 1; 2118fae3551SRodney W. Grimes 21291a81678SBrian Feldman if ((fs = getfsent()) != NULL) 2130602ee7cSBrian Feldman firstcall = 0; 21491a81678SBrian Feldman else if (firstcall) 21591a81678SBrian Feldman errx(1, "fstab reading failure"); 21691a81678SBrian Feldman else 21791a81678SBrian Feldman return (0); 218bc70c172SBrian Feldman do { 2198fae3551SRodney W. Grimes /* Ignore the root. */ 2208fae3551SRodney W. Grimes if (strcmp(fs->fs_file, "/") == 0) 2218fae3551SRodney W. Grimes continue; 2228fae3551SRodney W. Grimes /* 2238fae3551SRodney W. Grimes * !!! 2248fae3551SRodney W. Grimes * Historic practice: ignore unknown FSTAB_* fields. 2258fae3551SRodney W. Grimes */ 2268fae3551SRodney W. Grimes if (strcmp(fs->fs_type, FSTAB_RW) && 2278fae3551SRodney W. Grimes strcmp(fs->fs_type, FSTAB_RO) && 2288fae3551SRodney W. Grimes strcmp(fs->fs_type, FSTAB_RQ)) 2298fae3551SRodney W. Grimes continue; 230b6e55a05SDag-Erling Smørgrav /* Ignore unknown file system types. */ 23181667275SDag-Erling Smørgrav if (getvfsbyname(fs->fs_vfstype, &vfc) == -1) 2328fae3551SRodney W. Grimes continue; 233d499a0efSBruce Evans if (checkvfsname(fs->fs_vfstype, typelist)) 2348fae3551SRodney W. Grimes continue; 2358fae3551SRodney W. Grimes 2368fae3551SRodney W. Grimes /* 2378fae3551SRodney W. Grimes * We want to unmount the file systems in the reverse order 2388fae3551SRodney W. Grimes * that they were mounted. So, we save off the file name 2398fae3551SRodney W. Grimes * in some allocated memory, and then call recursively. 2408fae3551SRodney W. Grimes */ 2418fae3551SRodney W. Grimes if ((cp = malloc((size_t)strlen(fs->fs_file) + 1)) == NULL) 242bc70c172SBrian Feldman err(1, "malloc failed"); 2438fae3551SRodney W. Grimes (void)strcpy(cp, fs->fs_file); 244d499a0efSBruce Evans rval = umountall(typelist); 2458360efbdSAlfred Perlstein rval = checkname(cp, typelist) || rval; 246bc70c172SBrian Feldman free(cp); 2470602ee7cSBrian Feldman return (rval); 2480602ee7cSBrian Feldman } while ((fs = getfsent()) != NULL); 2498fae3551SRodney W. Grimes return (0); 2508fae3551SRodney W. Grimes } 2518fae3551SRodney W. Grimes 2528360efbdSAlfred Perlstein /* 2538360efbdSAlfred Perlstein * Do magic checks on mountpoint and device or hand over 2548360efbdSAlfred Perlstein * it to unmount(2) if everything fails. 2558360efbdSAlfred Perlstein */ 2568fae3551SRodney W. Grimes int 2578360efbdSAlfred Perlstein checkname(char *name, char **typelist) 2588fae3551SRodney W. Grimes { 259bc70c172SBrian Feldman size_t len; 2608360efbdSAlfred Perlstein int speclen; 261bc70c172SBrian Feldman char *mntonname, *mntfromname; 2620602ee7cSBrian Feldman char *mntfromnamerev; 2630fe9a7daSBrian Feldman char *resolved, realname[MAXPATHLEN]; 2648360efbdSAlfred Perlstein char *type, *hostp, *delimp, *origname; 2658fae3551SRodney W. Grimes 2660602ee7cSBrian Feldman len = 0; 2678360efbdSAlfred Perlstein mntfromname = mntonname = delimp = hostp = NULL; 2688fae3551SRodney W. Grimes 269bc70c172SBrian Feldman /* 270bc70c172SBrian Feldman * 1. Check if the name exists in the mounttable. 271bc70c172SBrian Feldman */ 272bc70c172SBrian Feldman (void)checkmntlist(name, &mntfromname, &mntonname, &type); 273bc70c172SBrian Feldman /* 274bc70c172SBrian Feldman * 2. Remove trailing slashes if there are any. After that 275bc70c172SBrian Feldman * we look up the name in the mounttable again. 276bc70c172SBrian Feldman */ 277bc70c172SBrian Feldman if (mntfromname == NULL && mntonname == NULL) { 278bc70c172SBrian Feldman speclen = strlen(name); 279bc70c172SBrian Feldman for (speclen = strlen(name); 280bc70c172SBrian Feldman speclen > 1 && name[speclen - 1] == '/'; 281bc70c172SBrian Feldman speclen--) 282bc70c172SBrian Feldman name[speclen - 1] = '\0'; 283bc70c172SBrian Feldman (void)checkmntlist(name, &mntfromname, &mntonname, &type); 284bc70c172SBrian Feldman resolved = name; 285bc70c172SBrian Feldman /* Save off original name in origname */ 286bc70c172SBrian Feldman if ((origname = strdup(name)) == NULL) 287bc70c172SBrian Feldman err(1, "strdup"); 288bc70c172SBrian Feldman /* 289bc70c172SBrian Feldman * 3. Check if the deprecated nfs-syntax with an '@' 290bc70c172SBrian Feldman * has been used and translate it to the ':' syntax. 291bc70c172SBrian Feldman * Look up the name in the mounttable again. 292bc70c172SBrian Feldman */ 293bc70c172SBrian Feldman if (mntfromname == NULL && mntonname == NULL) { 294bc70c172SBrian Feldman if ((delimp = strrchr(name, '@')) != NULL) { 295bc70c172SBrian Feldman hostp = delimp + 1; 296bc70c172SBrian Feldman if (*hostp != '\0') { 297bc70c172SBrian Feldman /* 298bc70c172SBrian Feldman * Make both '@' and ':' 299bc70c172SBrian Feldman * notations equal 300bc70c172SBrian Feldman */ 301bc70c172SBrian Feldman char *host = strdup(hostp); 302bc70c172SBrian Feldman len = strlen(hostp); 303bc70c172SBrian Feldman if (host == NULL) 304bc70c172SBrian Feldman err(1, "strdup"); 305bc70c172SBrian Feldman memmove(name + len + 1, name, 306bc70c172SBrian Feldman (size_t)(delimp - name)); 307bc70c172SBrian Feldman name[len] = ':'; 308bc70c172SBrian Feldman memmove(name, host, len); 309bc70c172SBrian Feldman free(host); 310bc70c172SBrian Feldman } 311bc70c172SBrian Feldman for (speclen = strlen(name); 312bc70c172SBrian Feldman speclen > 1 && name[speclen - 1] == '/'; 313bc70c172SBrian Feldman speclen--) 314bc70c172SBrian Feldman name[speclen - 1] = '\0'; 315bc70c172SBrian Feldman name[len + speclen + 1] = '\0'; 316bc70c172SBrian Feldman (void)checkmntlist(name, &mntfromname, 317bc70c172SBrian Feldman &mntonname, &type); 318bc70c172SBrian Feldman resolved = name; 319bc70c172SBrian Feldman } 320bc70c172SBrian Feldman /* 321bc70c172SBrian Feldman * 4. Check if a relative mountpoint has been 322bc70c172SBrian Feldman * specified. This should happen as last check, 323bc70c172SBrian Feldman * the order is important. To prevent possible 324bc70c172SBrian Feldman * nfs-hangs, we just call realpath(3) on the 325bc70c172SBrian Feldman * basedir of mountpoint and add the dirname again. 326bc70c172SBrian Feldman * Check the name in mounttable one last time. 327bc70c172SBrian Feldman */ 328bc70c172SBrian Feldman if (mntfromname == NULL && mntonname == NULL) { 329bc70c172SBrian Feldman (void)strcpy(name, origname); 330bc70c172SBrian Feldman if ((getrealname(name, realname)) != NULL) { 331bc70c172SBrian Feldman (void)checkmntlist(realname, 332bc70c172SBrian Feldman &mntfromname, &mntonname, &type); 333bc70c172SBrian Feldman resolved = realname; 334bc70c172SBrian Feldman } 335bc70c172SBrian Feldman /* 3368360efbdSAlfred Perlstein * 5. All tests failed, just hand over the 3378360efbdSAlfred Perlstein * mountpoint to the kernel, maybe the statfs 3388360efbdSAlfred Perlstein * structure has been truncated or is not 3398360efbdSAlfred Perlstein * useful anymore because of a chroot(2). 3408360efbdSAlfred Perlstein * Please note that nfs will not be able to 3418360efbdSAlfred Perlstein * notify the nfs-server about unmounting. 3428360efbdSAlfred Perlstein * These things can change in future when the 3438360efbdSAlfred Perlstein * fstat structure get's more reliable, 3448360efbdSAlfred Perlstein * but at the moment we cannot thrust it. 345bc70c172SBrian Feldman */ 346bc70c172SBrian Feldman if (mntfromname == NULL && mntonname == NULL) { 347bc70c172SBrian Feldman (void)strcpy(name, origname); 3488360efbdSAlfred Perlstein if (umountfs(NULL, origname, 3498360efbdSAlfred Perlstein "none") == 0) {; 3508360efbdSAlfred Perlstein warnx("%s not found in " 3518360efbdSAlfred Perlstein "mount table, " 3528360efbdSAlfred Perlstein "unmounted it anyway", 353bc70c172SBrian Feldman origname); 354bc70c172SBrian Feldman free(origname); 3558360efbdSAlfred Perlstein return (0); 3568360efbdSAlfred Perlstein } else 3578360efbdSAlfred Perlstein free(origname); 3588fae3551SRodney W. Grimes return (1); 3598fae3551SRodney W. Grimes } 3608fae3551SRodney W. Grimes } 3618fae3551SRodney W. Grimes } 362bc70c172SBrian Feldman free(origname); 363bc70c172SBrian Feldman } else 364bc70c172SBrian Feldman resolved = name; 3658fae3551SRodney W. Grimes 366d499a0efSBruce Evans if (checkvfsname(type, typelist)) 367d499a0efSBruce Evans return (1); 3688fae3551SRodney W. Grimes 369bc70c172SBrian Feldman /* 370bc70c172SBrian Feldman * Check if the reverse entrys of the mounttable are really the 371bc70c172SBrian Feldman * same as the normal ones. 372bc70c172SBrian Feldman */ 373bc70c172SBrian Feldman if ((mntfromnamerev = strdup(getmntname(getmntname(mntfromname, 3740602ee7cSBrian Feldman NULL, MNTON, &type, NAME), NULL, MNTFROM, &type, NAME))) == NULL) 375bc70c172SBrian Feldman err(1, "strdup"); 376bc70c172SBrian Feldman /* 377bc70c172SBrian Feldman * Mark the uppermost mount as unmounted. 378bc70c172SBrian Feldman */ 3790602ee7cSBrian Feldman (void)getmntname(mntfromname, mntonname, NOTHING, &type, MARK); 380bc70c172SBrian Feldman /* 381bc70c172SBrian Feldman * If several equal mounts are in the mounttable, check the order 382bc70c172SBrian Feldman * and warn the user if necessary. 383bc70c172SBrian Feldman */ 384bc70c172SBrian Feldman if (strcmp(mntfromnamerev, mntfromname ) != 0 && 385bc70c172SBrian Feldman strcmp(resolved, mntonname) != 0) { 386bc70c172SBrian Feldman warnx("cannot umount %s, %s\n " 387bc70c172SBrian Feldman "is mounted there, umount it first", 388bc70c172SBrian Feldman mntonname, mntfromnamerev); 389d499a0efSBruce Evans 390bc70c172SBrian Feldman /* call getmntname again to set mntcheck[i] to 0 */ 3910602ee7cSBrian Feldman (void)getmntname(mntfromname, mntonname, 392bc70c172SBrian Feldman NOTHING, &type, UNMARK); 393bc70c172SBrian Feldman return (1); 394bc70c172SBrian Feldman } 395bc70c172SBrian Feldman free(mntfromnamerev); 3968360efbdSAlfred Perlstein umountfs(mntfromname, mntonname, type); 3978360efbdSAlfred Perlstein } 3988360efbdSAlfred Perlstein 3998360efbdSAlfred Perlstein /* 4008360efbdSAlfred Perlstein * NFS stuff and unmount(2) call 4018360efbdSAlfred Perlstein */ 4028360efbdSAlfred Perlstein int 4038360efbdSAlfred Perlstein umountfs(char *mntfromname, char *mntonname, char *type) 4048360efbdSAlfred Perlstein { 4058360efbdSAlfred Perlstein enum clnt_stat clnt_stat; 4068360efbdSAlfred Perlstein struct timeval try; 4078360efbdSAlfred Perlstein struct mtablist *mtab; 4088360efbdSAlfred Perlstein struct addrinfo *ai, hints; 4098360efbdSAlfred Perlstein int do_rpc; 4108360efbdSAlfred Perlstein CLIENT *clp; 4118360efbdSAlfred Perlstein char *nfsdirname, *orignfsdirname; 4128360efbdSAlfred Perlstein char *hostp, *delimp; 4138360efbdSAlfred Perlstein 4148360efbdSAlfred Perlstein mtab = NULL; 4158360efbdSAlfred Perlstein ai = NULL; 4168360efbdSAlfred Perlstein nfsdirname = delimp = orignfsdirname = NULL; 4178360efbdSAlfred Perlstein memset(&hints, 0, sizeof hints); 4188360efbdSAlfred Perlstein 4198360efbdSAlfred Perlstein if (!strcmp(type, "nfs")) { 4208360efbdSAlfred Perlstein if ((nfsdirname = strdup(mntfromname)) == NULL) 4218360efbdSAlfred Perlstein err(1, "strdup"); 4228360efbdSAlfred Perlstein orignfsdirname = nfsdirname; 4238360efbdSAlfred Perlstein if ((delimp = strrchr(nfsdirname, ':')) != NULL) { 4248360efbdSAlfred Perlstein *delimp = '\0'; 4258360efbdSAlfred Perlstein hostp = nfsdirname; 4268360efbdSAlfred Perlstein getaddrinfo(hostp, NULL, &hints, &ai); 4278360efbdSAlfred Perlstein if (ai == NULL) { 4288360efbdSAlfred Perlstein warnx("can't get net id for host"); 4298360efbdSAlfred Perlstein } 4308360efbdSAlfred Perlstein nfsdirname = delimp + 1; 4318360efbdSAlfred Perlstein } 4328360efbdSAlfred Perlstein } 433bc70c172SBrian Feldman /* 434bc70c172SBrian Feldman * Check if we have to start the rpc-call later. 435bc70c172SBrian Feldman * If there are still identical nfs-names mounted, 436bc70c172SBrian Feldman * we skip the rpc-call. Obviously this has to 437bc70c172SBrian Feldman * happen before unmount(2), but it should happen 438bc70c172SBrian Feldman * after the previous namecheck. 439bc70c172SBrian Feldman */ 4400fe9a7daSBrian Feldman if (strcmp(type, "nfs") == 0 && getmntname(mntfromname, NULL, NOTHING, 4410fe9a7daSBrian Feldman &type, COUNT) != NULL) 4420fe9a7daSBrian Feldman do_rpc = 1; 443bc70c172SBrian Feldman else 4440fe9a7daSBrian Feldman do_rpc = 0; 4458360efbdSAlfred Perlstein 4468360efbdSAlfred Perlstein if (!namematch(ai)) 447d499a0efSBruce Evans return (1); 448bc70c172SBrian Feldman if (unmount(mntonname, fflag) != 0 ) { 449bc70c172SBrian Feldman warn("unmount of %s failed", mntonname); 4508fae3551SRodney W. Grimes return (1); 4518fae3551SRodney W. Grimes } 452bc70c172SBrian Feldman if (vflag) 453bc70c172SBrian Feldman (void)printf("%s: unmount from %s\n", mntfromname, mntonname); 454bc70c172SBrian Feldman /* 455bc70c172SBrian Feldman * Report to mountd-server which nfsname 456bc70c172SBrian Feldman * has been unmounted. 457bc70c172SBrian Feldman */ 4588360efbdSAlfred Perlstein if (ai != NULL && !(fflag & MNT_FORCE) && do_rpc) { 4598360efbdSAlfred Perlstein clp = clnt_create(hostp, RPCPROG_MNT, RPCMNT_VER1, "udp"); 4608360efbdSAlfred Perlstein if (clp == NULL) { 4618fae3551SRodney W. Grimes clnt_pcreateerror("Cannot MNT PRC"); 4628fae3551SRodney W. Grimes return (1); 4638fae3551SRodney W. Grimes } 4648360efbdSAlfred Perlstein clp->cl_auth = authsys_create_default(); 4658fae3551SRodney W. Grimes try.tv_sec = 20; 4668fae3551SRodney W. Grimes try.tv_usec = 0; 467bc70c172SBrian Feldman clnt_stat = clnt_call(clp, RPCMNT_UMOUNT, xdr_dir, 468bc70c172SBrian Feldman nfsdirname, xdr_void, (caddr_t)0, try); 4698fae3551SRodney W. Grimes if (clnt_stat != RPC_SUCCESS) { 4708fae3551SRodney W. Grimes clnt_perror(clp, "Bad MNT RPC"); 4718fae3551SRodney W. Grimes return (1); 4728fae3551SRodney W. Grimes } 473a69497d7SMatthew Dillon /* 474a69497d7SMatthew Dillon * Remove the unmounted entry from /var/db/mounttab. 475a69497d7SMatthew Dillon */ 476a69497d7SMatthew Dillon if (read_mtab(mtab)) { 477a69497d7SMatthew Dillon mtab = mtabhead; 478a69497d7SMatthew Dillon clean_mtab(hostp, nfsdirname); 479a69497d7SMatthew Dillon if(!write_mtab()) 480a69497d7SMatthew Dillon warnx("cannot remove entry %s:%s", 481a69497d7SMatthew Dillon hostp, nfsdirname); 482a69497d7SMatthew Dillon free_mtab(); 483a69497d7SMatthew Dillon } 484bc70c172SBrian Feldman free(orignfsdirname); 4858fae3551SRodney W. Grimes auth_destroy(clp->cl_auth); 4868fae3551SRodney W. Grimes clnt_destroy(clp); 4878fae3551SRodney W. Grimes } 4888fae3551SRodney W. Grimes return (0); 4898fae3551SRodney W. Grimes } 4908fae3551SRodney W. Grimes 4918fae3551SRodney W. Grimes char * 492bc70c172SBrian Feldman getmntname(const char *fromname, const char *onname, 493bc70c172SBrian Feldman mntwhat what, char **type, dowhat mark) 4948fae3551SRodney W. Grimes { 495d499a0efSBruce Evans static struct statfs *mntbuf; 496bc70c172SBrian Feldman static size_t mntsize = 0; 497bc70c172SBrian Feldman static char *mntcheck = NULL; 498bc70c172SBrian Feldman static char *mntcount = NULL; 499bc70c172SBrian Feldman int i, count; 5008fae3551SRodney W. Grimes 501bc70c172SBrian Feldman if (mntsize <= 0) { 502bc70c172SBrian Feldman if ((mntsize = mntinfo(&mntbuf)) <= 0) 5038fae3551SRodney W. Grimes return (NULL); 5048fae3551SRodney W. Grimes } 505bc70c172SBrian Feldman if (mntcheck == NULL) { 506bc70c172SBrian Feldman if ((mntcheck = calloc(mntsize + 1, sizeof(int))) == NULL || 507bc70c172SBrian Feldman (mntcount = calloc(mntsize + 1, sizeof(int))) == NULL) 508bc70c172SBrian Feldman err(1, "calloc"); 509bc70c172SBrian Feldman } 510bc70c172SBrian Feldman /* 511bc70c172SBrian Feldman * We want to get the file systems in the reverse order 512bc70c172SBrian Feldman * that they were mounted. Mounted and unmounted filesystems 513bc70c172SBrian Feldman * are marked or unmarked in a table called 'mntcheck'. 514bc70c172SBrian Feldman * Unmount(const char *dir, int flags) does only take the 515bc70c172SBrian Feldman * mountpoint as argument, not the destination. If we don't pay 516bc70c172SBrian Feldman * attention to the order, it can happen that a overlaying 517bc70c172SBrian Feldman * filesystem get's unmounted instead of the one the user 518bc70c172SBrian Feldman * has choosen. 519bc70c172SBrian Feldman */ 520bc70c172SBrian Feldman switch (mark) { 521bc70c172SBrian Feldman case NAME: 522bc70c172SBrian Feldman /* Return only the specific name */ 523bc70c172SBrian Feldman for (i = mntsize - 1; i >= 0; i--) { 524bc70c172SBrian Feldman if (fromname != NULL && what == MNTON && 525bc70c172SBrian Feldman !strcmp(mntbuf[i].f_mntfromname, fromname) && 526bc70c172SBrian Feldman mntcheck[i] != 1) { 5278fae3551SRodney W. Grimes if (type) 528d499a0efSBruce Evans *type = mntbuf[i].f_fstypename; 5298fae3551SRodney W. Grimes return (mntbuf[i].f_mntonname); 5308fae3551SRodney W. Grimes } 531bc70c172SBrian Feldman if (fromname != NULL && what == MNTFROM && 532bc70c172SBrian Feldman !strcmp(mntbuf[i].f_mntonname, fromname) && 533bc70c172SBrian Feldman mntcheck[i] != 1) { 5348fae3551SRodney W. Grimes if (type) 535d499a0efSBruce Evans *type = mntbuf[i].f_fstypename; 5368fae3551SRodney W. Grimes return (mntbuf[i].f_mntfromname); 5378fae3551SRodney W. Grimes } 5388fae3551SRodney W. Grimes } 5398fae3551SRodney W. Grimes return (NULL); 540bc70c172SBrian Feldman case MARK: 541bc70c172SBrian Feldman /* Mark current mount with '1' and return name */ 542bc70c172SBrian Feldman for (i = mntsize - 1; i >= 0; i--) { 543bc70c172SBrian Feldman if (mntcheck[i] == 0 && 544bc70c172SBrian Feldman (strcmp(mntbuf[i].f_mntonname, onname) == 0) && 545bc70c172SBrian Feldman (strcmp(mntbuf[i].f_mntfromname, fromname) == 0)) { 546bc70c172SBrian Feldman mntcheck[i] = 1; 547bc70c172SBrian Feldman return (mntbuf[i].f_mntonname); 548bc70c172SBrian Feldman } 549bc70c172SBrian Feldman } 550bc70c172SBrian Feldman return (NULL); 551bc70c172SBrian Feldman case UNMARK: 552bc70c172SBrian Feldman /* Unmark current mount with '0' and return name */ 553bc70c172SBrian Feldman for (i = 0; i < mntsize; i++) { 554bc70c172SBrian Feldman if (mntcheck[i] == 1 && 555bc70c172SBrian Feldman (strcmp(mntbuf[i].f_mntonname, onname) == 0) && 556bc70c172SBrian Feldman (strcmp(mntbuf[i].f_mntfromname, fromname) == 0)) { 557bc70c172SBrian Feldman mntcheck[i] = 0; 558bc70c172SBrian Feldman return (mntbuf[i].f_mntonname); 559bc70c172SBrian Feldman } 560bc70c172SBrian Feldman } 561bc70c172SBrian Feldman return (NULL); 562bc70c172SBrian Feldman case COUNT: 563bc70c172SBrian Feldman /* Count the equal mntfromnames */ 564bc70c172SBrian Feldman count = 0; 565bc70c172SBrian Feldman for (i = mntsize - 1; i >= 0; i--) { 566bc70c172SBrian Feldman if (strcmp(mntbuf[i].f_mntfromname, fromname) == 0) 567bc70c172SBrian Feldman count++; 568bc70c172SBrian Feldman } 569bc70c172SBrian Feldman /* Mark the already unmounted mounts and return 5700602ee7cSBrian Feldman * mntfromname if count <= 1. Else return NULL. 571bc70c172SBrian Feldman */ 572bc70c172SBrian Feldman for (i = mntsize - 1; i >= 0; i--) { 573bc70c172SBrian Feldman if (strcmp(mntbuf[i].f_mntfromname, fromname) == 0) { 574bc70c172SBrian Feldman if (mntcount[i] == 1) 575bc70c172SBrian Feldman count--; 5760fe9a7daSBrian Feldman else { 577bc70c172SBrian Feldman mntcount[i] = 1; 5780fe9a7daSBrian Feldman break; 5790fe9a7daSBrian Feldman } 5800602ee7cSBrian Feldman } 5810602ee7cSBrian Feldman } 5820602ee7cSBrian Feldman if (count <= 1) 583bc70c172SBrian Feldman return (mntbuf[i].f_mntonname); 584bc70c172SBrian Feldman else 585bc70c172SBrian Feldman return (NULL); 586bc70c172SBrian Feldman case FREE: 587bc70c172SBrian Feldman free(mntbuf); 588bc70c172SBrian Feldman free(mntcheck); 589a69497d7SMatthew Dillon free(mntcount); 590bc70c172SBrian Feldman return (NULL); 591bc70c172SBrian Feldman default: 592bc70c172SBrian Feldman return (NULL); 593bc70c172SBrian Feldman } 5948fae3551SRodney W. Grimes } 5958fae3551SRodney W. Grimes 5968fae3551SRodney W. Grimes int 5978360efbdSAlfred Perlstein sacmp(struct sockaddr *sa1, struct sockaddr *sa2) 5988fae3551SRodney W. Grimes { 5998360efbdSAlfred Perlstein void *p1, *p2; 6008360efbdSAlfred Perlstein int len; 6018fae3551SRodney W. Grimes 6028360efbdSAlfred Perlstein if (sa1->sa_family != sa2->sa_family) 6038fae3551SRodney W. Grimes return (1); 6048fae3551SRodney W. Grimes 6058360efbdSAlfred Perlstein switch (sa1->sa_family) { 6068360efbdSAlfred Perlstein case AF_INET: 6078360efbdSAlfred Perlstein p1 = &((struct sockaddr_in *)sa1)->sin_addr; 6088360efbdSAlfred Perlstein p2 = &((struct sockaddr_in *)sa2)->sin_addr; 6098360efbdSAlfred Perlstein len = 4; 6108360efbdSAlfred Perlstein break; 6118360efbdSAlfred Perlstein case AF_INET6: 6128360efbdSAlfred Perlstein p1 = &((struct sockaddr_in6 *)sa1)->sin6_addr; 6138360efbdSAlfred Perlstein p2 = &((struct sockaddr_in6 *)sa2)->sin6_addr; 6148360efbdSAlfred Perlstein len = 16; 6158360efbdSAlfred Perlstein if (((struct sockaddr_in6 *)sa1)->sin6_scope_id != 6168360efbdSAlfred Perlstein ((struct sockaddr_in6 *)sa2)->sin6_scope_id) 6178360efbdSAlfred Perlstein return (1); 6188360efbdSAlfred Perlstein break; 6198360efbdSAlfred Perlstein default: 6208360efbdSAlfred Perlstein return (1); 6218360efbdSAlfred Perlstein } 6228360efbdSAlfred Perlstein 6238360efbdSAlfred Perlstein return memcmp(p1, p2, len); 6248360efbdSAlfred Perlstein } 6258360efbdSAlfred Perlstein 6268360efbdSAlfred Perlstein int 6278360efbdSAlfred Perlstein namematch(struct addrinfo *ai) 6288360efbdSAlfred Perlstein { 6298360efbdSAlfred Perlstein struct addrinfo *aip; 6308360efbdSAlfred Perlstein 6318360efbdSAlfred Perlstein if (nfshost == NULL || nfshost_ai == NULL) 6328fae3551SRodney W. Grimes return (1); 6338fae3551SRodney W. Grimes 6348360efbdSAlfred Perlstein while (ai != NULL) { 6358360efbdSAlfred Perlstein aip = nfshost_ai; 6368360efbdSAlfred Perlstein while (aip != NULL) { 6378360efbdSAlfred Perlstein if (sacmp(ai->ai_addr, aip->ai_addr) == 0) 6388fae3551SRodney W. Grimes return (1); 6398360efbdSAlfred Perlstein aip = aip->ai_next; 6408fae3551SRodney W. Grimes } 6418360efbdSAlfred Perlstein ai = ai->ai_next; 6428fae3551SRodney W. Grimes } 6438360efbdSAlfred Perlstein 6448fae3551SRodney W. Grimes return (0); 6458fae3551SRodney W. Grimes } 6468fae3551SRodney W. Grimes 647bc70c172SBrian Feldman void 648bc70c172SBrian Feldman checkmntlist(char *name, char **fromname, char **onname, char **type) 649bc70c172SBrian Feldman { 650bc70c172SBrian Feldman 651bc70c172SBrian Feldman *fromname = getmntname(name, NULL, MNTFROM, type, NAME); 652bc70c172SBrian Feldman if (*fromname == NULL) { 653bc70c172SBrian Feldman *onname = getmntname(name, NULL, MNTON, type, NAME); 654bc70c172SBrian Feldman if (*onname != NULL) 655bc70c172SBrian Feldman *fromname = name; 656bc70c172SBrian Feldman } else 657bc70c172SBrian Feldman *onname = name; 658bc70c172SBrian Feldman } 659bc70c172SBrian Feldman 660bc70c172SBrian Feldman size_t 661bc70c172SBrian Feldman mntinfo(struct statfs **mntbuf) 662bc70c172SBrian Feldman { 663bc70c172SBrian Feldman static struct statfs *origbuf; 664bc70c172SBrian Feldman size_t bufsize; 665bc70c172SBrian Feldman int mntsize; 666bc70c172SBrian Feldman 667a69497d7SMatthew Dillon mntsize = getfsstat(NULL, 0, MNT_NOWAIT); 668bc70c172SBrian Feldman if (mntsize <= 0) 669bc70c172SBrian Feldman return (0); 670bc70c172SBrian Feldman bufsize = (mntsize + 1) * sizeof(struct statfs); 671bc70c172SBrian Feldman if ((origbuf = malloc(bufsize)) == NULL) 672bc70c172SBrian Feldman err(1, "malloc"); 673bc70c172SBrian Feldman mntsize = getfsstat(origbuf, (long)bufsize, MNT_NOWAIT); 674bc70c172SBrian Feldman *mntbuf = origbuf; 675bc70c172SBrian Feldman return (mntsize); 676bc70c172SBrian Feldman } 677bc70c172SBrian Feldman 678bc70c172SBrian Feldman char * 679bc70c172SBrian Feldman getrealname(char *name, char *realname) 680bc70c172SBrian Feldman { 681bc70c172SBrian Feldman char *dirname; 682bc70c172SBrian Feldman int havedir; 683bc70c172SBrian Feldman size_t baselen; 684bc70c172SBrian Feldman size_t dirlen; 685bc70c172SBrian Feldman 686bc70c172SBrian Feldman dirname = '\0'; 687bc70c172SBrian Feldman havedir = 0; 688bc70c172SBrian Feldman if (*name == '/') { 689bc70c172SBrian Feldman if (ISDOT(name + 1) || ISDOTDOT(name + 1)) 690bc70c172SBrian Feldman strcpy(realname, "/"); 691bc70c172SBrian Feldman else { 692bc70c172SBrian Feldman if ((dirname = strrchr(name + 1, '/')) == NULL) 6930fe9a7daSBrian Feldman snprintf(realname, MAXPATHLEN, "%s", name); 694bc70c172SBrian Feldman else 695bc70c172SBrian Feldman havedir = 1; 696bc70c172SBrian Feldman } 697bc70c172SBrian Feldman } else { 698bc70c172SBrian Feldman if (ISDOT(name) || ISDOTDOT(name)) 699bc70c172SBrian Feldman (void)realpath(name, realname); 700bc70c172SBrian Feldman else { 701bc70c172SBrian Feldman if ((dirname = strrchr(name, '/')) == NULL) { 702bc70c172SBrian Feldman if ((realpath(name, realname)) == NULL) 703bc70c172SBrian Feldman return (NULL); 704bc70c172SBrian Feldman } else 705bc70c172SBrian Feldman havedir = 1; 706bc70c172SBrian Feldman } 707bc70c172SBrian Feldman } 708bc70c172SBrian Feldman if (havedir) { 709bc70c172SBrian Feldman *dirname++ = '\0'; 710bc70c172SBrian Feldman if (ISDOT(dirname)) { 711bc70c172SBrian Feldman *dirname = '\0'; 712bc70c172SBrian Feldman if ((realpath(name, realname)) == NULL) 713bc70c172SBrian Feldman return (NULL); 714bc70c172SBrian Feldman } else if (ISDOTDOT(dirname)) { 715bc70c172SBrian Feldman *--dirname = '/'; 716bc70c172SBrian Feldman if ((realpath(name, realname)) == NULL) 717bc70c172SBrian Feldman return (NULL); 718bc70c172SBrian Feldman } else { 719bc70c172SBrian Feldman if ((realpath(name, realname)) == NULL) 720bc70c172SBrian Feldman return (NULL); 721bc70c172SBrian Feldman baselen = strlen(realname); 722bc70c172SBrian Feldman dirlen = strlen(dirname); 723bc70c172SBrian Feldman if (baselen + dirlen + 1 > MAXPATHLEN) 724bc70c172SBrian Feldman return (NULL); 725bc70c172SBrian Feldman if (realname[1] == '\0') { 726bc70c172SBrian Feldman memmove(realname + 1, dirname, dirlen); 727bc70c172SBrian Feldman realname[dirlen + 1] = '\0'; 728bc70c172SBrian Feldman } else { 729bc70c172SBrian Feldman realname[baselen] = '/'; 730bc70c172SBrian Feldman memmove(realname + baselen + 1, 731bc70c172SBrian Feldman dirname, dirlen); 732bc70c172SBrian Feldman realname[baselen + dirlen + 1] = '\0'; 733bc70c172SBrian Feldman } 734bc70c172SBrian Feldman } 735bc70c172SBrian Feldman } 736bc70c172SBrian Feldman return (realname); 737bc70c172SBrian Feldman } 738bc70c172SBrian Feldman 7398fae3551SRodney W. Grimes /* 7408fae3551SRodney W. Grimes * xdr routines for mount rpc's 7418fae3551SRodney W. Grimes */ 7428fae3551SRodney W. Grimes int 743bc70c172SBrian Feldman xdr_dir(XDR *xdrsp, char *dirp) 7448fae3551SRodney W. Grimes { 745bc70c172SBrian Feldman 7468fae3551SRodney W. Grimes return (xdr_string(xdrsp, &dirp, RPCMNT_PATHLEN)); 7478fae3551SRodney W. Grimes } 7488fae3551SRodney W. Grimes 7498fae3551SRodney W. Grimes void 7508fae3551SRodney W. Grimes usage() 7518fae3551SRodney W. Grimes { 752bc70c172SBrian Feldman 753210a5dc8SPhilippe Charnier (void)fprintf(stderr, "%s\n%s\n", 754210a5dc8SPhilippe Charnier "usage: umount [-fv] special | node", 755210a5dc8SPhilippe Charnier " umount -a | -A [-fv] [-h host] [-t type]"); 7568fae3551SRodney W. Grimes exit(1); 7578fae3551SRodney W. Grimes } 758