/*- * Copyright (c) 1980, 1989, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)umount.c 8.8 (Berkeley) 5/8/95 * $FreeBSD: src/sbin/umount/umount.c,v 1.28 2001/10/13 02:04:54 iedowse Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mounttab.h" #define ISDOT(x) ((x)[0] == '.' && (x)[1] == '\0') #define ISDOTDOT(x) ((x)[0] == '.' && (x)[1] == '.' && (x)[2] == '\0') typedef enum { MNTON, MNTFROM, NOTHING } mntwhat; typedef enum { MARK, UNMARK, NAME, COUNT, FREE } dowhat; struct addrinfo *nfshost_ai = NULL; int all, dflag, fflag, vflag; char *nfshost; void checkmntlist (char *, char **, char **, char **); int checkvfsname (const char *, char **); char *getmntname (const char *, const char *, mntwhat, char **, dowhat); char *getrealname(char *, char *resolved_path); char **makevfslist (const char *); int mntinfo (struct statfs **); int namematch (struct addrinfo *); int sacmp (struct sockaddr *, struct sockaddr *); int umountall (char **); int checkname (char *, char **); int umountfs (char *, char *, char *); void usage (void) __dead2; int vn_detach (const char *); int xdr_dir (XDR *, char *); int main(int argc, char *argv[]) { int errs, ch, mntsize, error; char **typelist = NULL, *mntonname, *mntfromname; char *type, *mntfromnamerev, *mntonnamerev; struct statfs *mntbuf; struct addrinfo hints; all = errs = 0; while ((ch = getopt(argc, argv, "AadF:fh:t:v")) != -1) { switch (ch) { case 'A': all = 2; break; case 'a': all = 1; break; case 'd': dflag = 1; break; case 'F': setfstab(optarg); break; case 'f': fflag = MNT_FORCE; break; case 'h': /* -h implies -A. */ all = 2; nfshost = optarg; break; case 't': if (typelist != NULL) err(1, "only one -t option may be specified"); typelist = makevfslist(optarg); break; case 'v': vflag = 1; break; default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; if ((argc == 0 && !all) || (argc != 0 && all)) usage(); /* -h implies "-t nfs" if no -t flag. */ if ((nfshost != NULL) && (typelist == NULL)) typelist = makevfslist("nfs"); if (nfshost != NULL) { memset(&hints, 0, sizeof hints); error = getaddrinfo(nfshost, NULL, &hints, &nfshost_ai); if (error) errx(1, "%s: %s", nfshost, gai_strerror(error)); } switch (all) { case 2: if ((mntsize = mntinfo(&mntbuf)) <= 0) break; /* * We unmount the nfs-mounts in the reverse order * that they were mounted. */ for (errs = 0, mntsize--; mntsize > 0; mntsize--) { if (checkvfsname(mntbuf[mntsize].f_fstypename, typelist)) continue; /* * Check if a mountpoint is laid over by another mount. * A warning will be printed to stderr if this is * the case. The laid over mount remains unmounted. */ mntonname = mntbuf[mntsize].f_mntonname; mntfromname = mntbuf[mntsize].f_mntfromname; mntonnamerev = getmntname(getmntname(mntonname, NULL, MNTFROM, &type, NAME), NULL, MNTON, &type, NAME); mntfromnamerev = getmntname(mntonnamerev, NULL, MNTFROM, &type, NAME); if (strcmp(mntonnamerev, mntonname) == 0 && strcmp(mntfromnamerev, mntfromname ) != 0) warnx("cannot umount %s, %s\n " "is mounted there, umount it first", mntonname, mntfromnamerev); if (checkname(mntbuf[mntsize].f_mntonname, typelist) != 0) errs = 1; } free(mntbuf); break; case 1: if (setfsent() == 0) err(1, "%s", getfstab()); errs = umountall(typelist); break; case 0: for (errs = 0; *argv != NULL; ++argv) if (checkname(*argv, typelist) != 0) errs = 1; break; } getmntname(NULL, NULL, NOTHING, NULL, FREE); exit(errs); } int umountall(char **typelist) { struct vfsconf vfc; struct fstab *fs; int rval; char *cp; static int firstcall = 1; if ((fs = getfsent()) != NULL) firstcall = 0; else if (firstcall) errx(1, "fstab reading failure"); else return (0); do { /* Ignore the root. */ if (strcmp(fs->fs_file, "/") == 0) continue; /* * !!! * Historic practice: ignore unknown FSTAB_* fields. */ if (strcmp(fs->fs_type, FSTAB_RW) && strcmp(fs->fs_type, FSTAB_RO) && strcmp(fs->fs_type, FSTAB_RQ)) continue; /* Ignore unknown file system types. */ if (getvfsbyname(fs->fs_vfstype, &vfc) == -1) continue; if (checkvfsname(fs->fs_vfstype, typelist)) continue; /* * We want to unmount the file systems in the reverse order * that they were mounted. So, we save off the file name * in some allocated memory, and then call recursively. */ if ((cp = malloc((size_t)strlen(fs->fs_file) + 1)) == NULL) err(1, "malloc failed"); strcpy(cp, fs->fs_file); rval = umountall(typelist); rval = checkname(cp, typelist) || rval; free(cp); return (rval); } while ((fs = getfsent()) != NULL); return (0); } /* * Do magic checks on mountpoint and device or hand over * it to unmount(2) if everything fails. */ int checkname(char *name, char **typelist) { size_t len; int speclen; char *mntonname, *mntfromname; char *mntfromnamerev; char *resolved, realname[MAXPATHLEN]; char *type, *hostp, *delimp, *origname; char none[] = "none"; len = 0; mntfromname = mntonname = delimp = hostp = NULL; /* * 1. Check if the name exists in the mounttable. */ checkmntlist(name, &mntfromname, &mntonname, &type); if (mntfromname == NULL && mntonname == NULL) { checkmntlist(getdevpath(name, 0), &mntfromname, &mntonname, &type); } /* * 2. Remove trailing slashes if there are any. After that * we look up the name in the mounttable again. */ if (mntfromname == NULL && mntonname == NULL) { speclen = strlen(name); for (speclen = strlen(name); speclen > 1 && name[speclen - 1] == '/'; speclen--) name[speclen - 1] = '\0'; checkmntlist(name, &mntfromname, &mntonname, &type); resolved = name; /* Save off original name in origname */ if ((origname = strdup(name)) == NULL) err(1, "strdup"); /* * 3. Check if the deprecated nfs-syntax with an '@' * has been used and translate it to the ':' syntax. * Look up the name in the mounttable again. */ if (mntfromname == NULL && mntonname == NULL) { if ((delimp = strrchr(name, '@')) != NULL) { hostp = delimp + 1; if (*hostp != '\0') { /* * Make both '@' and ':' * notations equal */ char *host = strdup(hostp); len = strlen(hostp); if (host == NULL) err(1, "strdup"); memmove(name + len + 1, name, (size_t)(delimp - name)); name[len] = ':'; memmove(name, host, len); free(host); } for (speclen = strlen(name); speclen > 1 && name[speclen - 1] == '/'; speclen--) name[speclen - 1] = '\0'; name[len + speclen + 1] = '\0'; checkmntlist(name, &mntfromname, &mntonname, &type); resolved = name; } /* * 4. Check if a relative mountpoint has been * specified. This should happen as last check, * the order is important. To prevent possible * nfs-hangs, we just call realpath(3) on the * basedir of mountpoint and add the dirname again. * Check the name in mounttable one last time. */ if (mntfromname == NULL && mntonname == NULL) { strcpy(name, origname); if ((getrealname(name, realname)) != NULL) { checkmntlist(realname, &mntfromname, &mntonname, &type); resolved = realname; } /* * 5. All tests failed, just hand over the * mountpoint to the kernel, maybe the statfs * structure has been truncated or is not * useful anymore because of a chroot(2). * Please note that nfs will not be able to * notify the nfs-server about unmounting. * These things can change in future when the * fstat structure get's more reliable, * but at the moment we cannot thrust it. */ if (mntfromname == NULL && mntonname == NULL) { strcpy(name, origname); if (umountfs(NULL, origname, none) == 0) { warnx("%s not found in " "mount table, " "unmounted it anyway", origname); free(origname); return (0); } else { free(origname); return (1); } } } } free(origname); } else resolved = name; if (checkvfsname(type, typelist)) return (1); /* * Check if the reverse entrys of the mounttable are really the * same as the normal ones. */ if ((mntfromnamerev = strdup(getmntname(getmntname(mntfromname, NULL, MNTON, &type, NAME), NULL, MNTFROM, &type, NAME))) == NULL) err(1, "strdup"); /* * Mark the uppermost mount as unmounted. */ getmntname(mntfromname, mntonname, NOTHING, &type, MARK); /* * If several equal mounts are in the mounttable, check the order * and warn the user if necessary. */ if (strcmp(mntfromnamerev, mntfromname) != 0 && strcmp(resolved, mntonname) != 0) { warnx("cannot umount %s, %s\n " "is mounted there, umount it first", mntonname, mntfromnamerev); /* call getmntname again to set mntcheck[i] to 0 */ getmntname(mntfromname, mntonname, NOTHING, &type, UNMARK); return (1); } free(mntfromnamerev); return (umountfs(mntfromname, mntonname, type)); } /* * NFS stuff and unmount(2) call */ int umountfs(char *mntfromname, char *mntonname, char *type) { enum clnt_stat clnt_stat; struct timeval try; struct addrinfo *ai, hints; int do_rpc; CLIENT *clp; char *nfsdirname, *orignfsdirname; char *hostp, *delimp; ai = NULL; do_rpc = 0; hostp = NULL; nfsdirname = delimp = orignfsdirname = NULL; memset(&hints, 0, sizeof hints); if (strcmp(type, "nfs") == 0) { if ((nfsdirname = strdup(mntfromname)) == NULL) err(1, "strdup"); orignfsdirname = nfsdirname; if ((delimp = strrchr(nfsdirname, ':')) != NULL) { *delimp = '\0'; hostp = nfsdirname; getaddrinfo(hostp, NULL, &hints, &ai); if (ai == NULL) { warnx("can't get net id for host"); } nfsdirname = delimp + 1; } /* * Check if we have to start the rpc-call later. * If there are still identical nfs-names mounted, * we skip the rpc-call. Obviously this has to * happen before unmount(2), but it should happen * after the previous namecheck. * A non-NULL return means that this is the last * mount from mntfromname that is still mounted. */ if (getmntname(mntfromname, NULL, NOTHING, &type, COUNT) != NULL) do_rpc = 1; } if (!namematch(ai)) return (1); if (unmount(mntonname, fflag) != 0 ) { warn("unmount of %s failed", mntonname); return (1); } if (vflag) printf("%s: unmount from %s\n", mntfromname, mntonname); if (dflag) { if (vn_detach(mntfromname) != 0 && !all) { warnx("detach of %s failed", mntfromname); return (1); } } /* * Report to mountd-server which nfsname * has been unmounted. */ if (ai != NULL && !(fflag & MNT_FORCE) && do_rpc) { clp = clnt_create(hostp, RPCPROG_MNT, RPCMNT_VER1, "udp"); if (clp == NULL) { warnx("%s: %s", hostp, clnt_spcreateerror("RPCPROG_MNT")); return (1); } clp->cl_auth = authsys_create_default(); try.tv_sec = 20; try.tv_usec = 0; clnt_stat = clnt_call(clp, RPCMNT_UMOUNT, (xdrproc_t)xdr_dir, nfsdirname, (xdrproc_t)xdr_void, (caddr_t)0, try); if (clnt_stat != RPC_SUCCESS) { warnx("%s: %s", hostp, clnt_sperror(clp, "RPCMNT_UMOUNT")); return (1); } /* * Remove the unmounted entry from /var/db/mounttab. */ if (read_mtab()) { clean_mtab(hostp, nfsdirname, vflag); if(!write_mtab(vflag)) warnx("cannot remove mounttab entry %s:%s", hostp, nfsdirname); free_mtab(); } free(orignfsdirname); auth_destroy(clp->cl_auth); clnt_destroy(clp); } return (0); } int vn_detach(const char *fromname) { struct vn_ioctl vnio; char *p, device[MAXPATHLEN]; int fd; if (strncmp(fromname, "/dev/vn", sizeof("/dev/vn") - 1) != 0) { if (!all) warnx("invalid vn device: %s", fromname); return (-1); } /* Strip the slice/partition part (e.g., /dev/vn4s1a -> /dev/vn4) */ snprintf(device, sizeof(device), "%s", fromname); p = device + sizeof("/dev/vn"); while (*p >= '0' && *p <= '9') p++; *p = '\0'; fd = open(device, O_RDONLY); if (fd < 0) { warn("open: %s", device); return (-1); } memset(&vnio, 0, sizeof(vnio)); if (ioctl(fd, VNIOCDETACH, &vnio) < 0) { warn("VNIOCDETACH: %s", device); close(fd); return (-1); } close(fd); if (vflag) printf("%s: detached\n", device); return (0); } char * getmntname(const char *fromname, const char *onname, mntwhat what, char **type, dowhat mark) { static struct statfs *mntbuf; static int mntsize = 0; static char *mntcheck = NULL; static char *mntcount = NULL; int i, count; if (mntsize <= 0) { if ((mntsize = mntinfo(&mntbuf)) <= 0) return (NULL); } if (mntcheck == NULL) { if ((mntcheck = calloc(mntsize + 1, sizeof(int))) == NULL || (mntcount = calloc(mntsize + 1, sizeof(int))) == NULL) err(1, "calloc"); } /* * We want to get the file systems in the reverse order * that they were mounted. Mounted and unmounted filesystems * are marked or unmarked in a table called 'mntcheck'. * Unmount(const char *dir, int flags) does only take the * mountpoint as argument, not the destination. If we don't pay * attention to the order, it can happen that a overlaying * filesystem get's unmounted instead of the one the user * has choosen. */ switch (mark) { case NAME: /* Return only the specific name */ for (i = mntsize - 1; i >= 0; i--) { if (fromname != NULL && what == MNTON && !strcmp(mntbuf[i].f_mntfromname, fromname) && mntcheck[i] != 1) { if (type) *type = mntbuf[i].f_fstypename; return (mntbuf[i].f_mntonname); } if (fromname != NULL && what == MNTFROM && !strcmp(mntbuf[i].f_mntonname, fromname) && mntcheck[i] != 1) { if (type) *type = mntbuf[i].f_fstypename; return (mntbuf[i].f_mntfromname); } } return (NULL); case MARK: /* Mark current mount with '1' and return name */ for (i = mntsize - 1; i >= 0; i--) { if (mntcheck[i] == 0 && (strcmp(mntbuf[i].f_mntonname, onname) == 0) && (strcmp(mntbuf[i].f_mntfromname, fromname) == 0)) { mntcheck[i] = 1; return (mntbuf[i].f_mntonname); } } return (NULL); case UNMARK: /* Unmark current mount with '0' and return name */ for (i = 0; i < mntsize; i++) { if (mntcheck[i] == 1 && (strcmp(mntbuf[i].f_mntonname, onname) == 0) && (strcmp(mntbuf[i].f_mntfromname, fromname) == 0)) { mntcheck[i] = 0; return (mntbuf[i].f_mntonname); } } return (NULL); case COUNT: /* Count the equal mntfromnames */ count = 0; for (i = mntsize - 1; i >= 0; i--) { if (strcmp(mntbuf[i].f_mntfromname, fromname) == 0) count++; } /* Mark the already unmounted mounts and return * mntfromname if count <= 1. Else return NULL. */ for (i = mntsize - 1; i >= 0; i--) { if (strcmp(mntbuf[i].f_mntfromname, fromname) == 0) { if (mntcount[i] == 1) count--; else { mntcount[i] = 1; break; } } } if (count <= 1) return (mntbuf[i].f_mntonname); else return (NULL); case FREE: free(mntbuf); free(mntcheck); free(mntcount); return (NULL); default: return (NULL); } } int sacmp(struct sockaddr *sa1, struct sockaddr *sa2) { void *p1, *p2; int len; if (sa1->sa_family != sa2->sa_family) return (1); switch (sa1->sa_family) { case AF_INET: p1 = &((struct sockaddr_in *)sa1)->sin_addr; p2 = &((struct sockaddr_in *)sa2)->sin_addr; len = 4; break; case AF_INET6: p1 = &((struct sockaddr_in6 *)sa1)->sin6_addr; p2 = &((struct sockaddr_in6 *)sa2)->sin6_addr; len = 16; if (((struct sockaddr_in6 *)sa1)->sin6_scope_id != ((struct sockaddr_in6 *)sa2)->sin6_scope_id) return (1); break; default: return (1); } return memcmp(p1, p2, len); } int namematch(struct addrinfo *ai) { struct addrinfo *aip; if (nfshost == NULL || nfshost_ai == NULL) return (1); while (ai != NULL) { aip = nfshost_ai; while (aip != NULL) { if (sacmp(ai->ai_addr, aip->ai_addr) == 0) return (1); aip = aip->ai_next; } ai = ai->ai_next; } return (0); } void checkmntlist(char *name, char **fromname, char **onname, char **type) { *fromname = getmntname(name, NULL, MNTFROM, type, NAME); if (*fromname == NULL) { *onname = getmntname(name, NULL, MNTON, type, NAME); if (*onname != NULL) *fromname = name; } else *onname = name; } int mntinfo(struct statfs **mntbuf) { static struct statfs *origbuf; size_t bufsize; int mntsize; mntsize = getfsstat(NULL, 0, MNT_NOWAIT); if (mntsize <= 0) return (0); bufsize = (mntsize + 1) * sizeof(struct statfs); if ((origbuf = malloc(bufsize)) == NULL) err(1, "malloc"); mntsize = getfsstat(origbuf, (long)bufsize, MNT_NOWAIT); *mntbuf = origbuf; return (mntsize); } char * getrealname(char *name, char *realname) { char *dirname; int havedir; size_t baselen; size_t dirlen; dirname = NULL; havedir = 0; if (*name == '/') { if (ISDOT(name + 1) || ISDOTDOT(name + 1)) strcpy(realname, "/"); else { if ((dirname = strrchr(name + 1, '/')) == NULL) snprintf(realname, MAXPATHLEN, "%s", name); else havedir = 1; } } else { if (ISDOT(name) || ISDOTDOT(name)) realpath(name, realname); else { if ((dirname = strrchr(name, '/')) == NULL) { if ((realpath(name, realname)) == NULL) return (NULL); } else havedir = 1; } } if (havedir) { *dirname++ = '\0'; if (ISDOT(dirname)) { *dirname = '\0'; if ((realpath(name, realname)) == NULL) return (NULL); } else if (ISDOTDOT(dirname)) { *--dirname = '/'; if ((realpath(name, realname)) == NULL) return (NULL); } else { if ((realpath(name, realname)) == NULL) return (NULL); baselen = strlen(realname); dirlen = strlen(dirname); if (baselen + dirlen + 1 > MAXPATHLEN) return (NULL); if (realname[1] == '\0') { memmove(realname + 1, dirname, dirlen); realname[dirlen + 1] = '\0'; } else { realname[baselen] = '/'; memmove(realname + baselen + 1, dirname, dirlen); realname[baselen + dirlen + 1] = '\0'; } } } return (realname); } /* * xdr routines for mount rpc's */ int xdr_dir(XDR *xdrsp, char *dirp) { return (xdr_string(xdrsp, &dirp, RPCMNT_PATHLEN)); } void usage(void) { fprintf(stderr, "%s\n%s\n", "usage: umount [-dfv] special | node", " umount -a | -A [-F fstab] [-dfv] [-h host] [-t type]"); exit(1); }