/* * dmnt.c -- Linux mount support functions for /proc-based lsof */ /* * Copyright 1997 Purdue Research Foundation, West Lafayette, Indiana * 47907. All rights reserved. * * Written by Victor A. Abell * * This software is not subject to any license of the American Telephone * and Telegraph Company or the Regents of the University of California. * * Permission is granted to anyone to use this software for any purpose on * any computer system, and to alter it and redistribute it freely, subject * to the following restrictions: * * 1. Neither the authors nor Purdue University are responsible for any * consequences of the use of this software. * * 2. The origin of this software must not be misrepresented, either by * explicit claim or by omission. Credit to the authors and Purdue * University must appear in documentation and sources. * * 3. Altered versions must be plainly marked as such, and must not be * misrepresented as being the original software. * * 4. This notice may not be removed or altered. */ #ifndef lint static char copyright[] = "@(#) Copyright 1997 Purdue Research Foundation.\nAll rights reserved.\n"; static char *rcsid = "$Id: dmnt.c,v 1.19 2012/04/10 16:39:50 abe Exp $"; #endif #include "lsof.h" /* * Local definitions */ #if defined(HASMNTSUP) #define HASHMNT 128 /* mount supplement hash bucket count * !!!MUST BE A POWER OF 2!!! */ #endif /* defined(HASMNTSUP) */ /* * Local function prototypes */ _PROTOTYPE(static char *cvtoe,(char *os)); #if defined(HASMNTSUP) _PROTOTYPE(static int getmntdev,(char *dn, size_t dnl, struct stat *s, int *ss)); _PROTOTYPE(static int hash_mnt,(char *dn)); #endif /* defined(HASMNTSUP) */ /* * Local structure definitions. */ #if defined(HASMNTSUP) typedef struct mntsup { char *dn; /* mounted directory name */ size_t dnl; /* strlen(dn) */ dev_t dev; /* device number */ int ln; /* line on which defined */ struct mntsup *next; /* next entry */ } mntsup_t; #endif /* defined(HASMNTSUP) */ /* * Local static definitions */ static struct mounts *Lmi = (struct mounts *)NULL; /* local mount info */ static int Lmist = 0; /* Lmi status */ static mntsup_t **MSHash = (mntsup_t **)NULL; /* mount supplement * hash buckets */ /* * cvtoe() -- convert octal-escaped characters in string */ static char * cvtoe(os) char *os; /* original string */ { int c, cl, cx, ol, ox, tx; char *cs; int tc; /* * Allocate space for a copy of the string in which octal-escaped characters * can be replaced by the octal value -- e.g., \040 with ' '. Leave room for * a '\0' terminator. */ if (!(ol = (int)strlen(os))) return((char *)NULL); if (!(cs = (char *)malloc(ol + 1))) { (void) fprintf(stderr, "%s: can't allocate %d bytes for octal-escaping.\n", Pn, ol + 1); Exit(1); } /* * Copy the string, replacing octal-escaped characters as they are found. */ for (cx = ox = 0, cl = ol; ox < ol; ox++) { if (((c = (int)os[ox]) == (int)'\\') && ((ox + 3) < ol)) { /* * The beginning of an octal-escaped character has been found. * * Convert the octal value to a character value. */ for (tc = 0, tx = 1; os[ox + tx] && (tx < 4); tx++) { if (((int)os[ox + tx] < (int)'0') || ((int)os[ox + tx] > (int)'7')) { /* * The escape isn't followed by octets, so ignore the * escape and just copy it. */ break; } tc <<= 3; tc += (int)(os[ox + tx] - '0'); } if (tx == 4) { /* * If three octets (plus the escape) were assembled, use their * character-forming result. * * Otherwise copy the escape and what follows it until another * escape is found. */ ox += 3; c = (tc & 0xff); } } if (cx >= cl) { /* * Expand the copy string, as required. Leave room for a '\0' * terminator. */ cl += 64; /* (Make an arbitrary increase.) */ if (!(cs = (char *)realloc(cs, cl + 1))) { (void) fprintf(stderr, "%s: can't realloc %d bytes for octal-escaping.\n", Pn, cl + 1); Exit(1); } } /* * Copy the character. */ cs[cx++] = (char)c; } /* * Terminate the copy and return its pointer. */ cs[cx] = '\0'; return(cs); } #if defined(HASMNTSUP) /* * getmntdev() - get mount device from mount supplement */ static int getmntdev(dn, dnl, s, ss) char *dn; /* mounted directory name */ size_t dnl; /* strlen(dn) */ struct stat *s; /* stat(2) buffer receptor */ int *ss; /* stat(2) status result -- i.e., SB_* * values */ { static int err = 0; int h; mntsup_t *mp, *mpn; static char *vbuf = (char *)NULL; static size_t vsz = (size_t)0; if (err) return(0); if (!MSHash) { /* * No mount supplement hash buckets have been allocated, so read the * mount supplement file and create hash buckets for its entries. */ char buf[(MAXPATHLEN*2) + 1], *dp, path[(MAXPATHLEN*2) + 1]; dev_t dev; FILE *fs; int ln = 0; size_t sz; if ((MntSup != 2) || !MntSupP) return(0); if (!is_readable(MntSupP, 1)) { /* * The mount supplement file isn't readable. */ err = 1; return(0); } if (!(fs = open_proc_stream(MntSupP, "r", &vbuf, &vsz, 0))) { /* * The mount supplement file can't be opened for reading. */ if (!Fwarn) (void) fprintf(stderr, "%s: can't open(%s): %s\n", Pn, MntSupP, strerror(errno)); err = 1; return(0); } buf[sizeof(buf) - 1] = '\0'; /* * Read the mount supplement file. */ while (fgets(buf, sizeof(buf) - 1, fs)) { ln++; if ((dp = strchr(buf, '\n'))) *dp = '\0'; if (buf[0] != '/') { /* * The mount supplement line doesn't begin with the absolute * path character '/'. */ if (!Fwarn) (void) fprintf(stderr, "%s: %s line %d: no path: \"%s\"\n", Pn, MntSupP, ln, buf); err = 1; continue; } if (!(dp = strchr(buf, ' ')) || strncmp(dp + 1, "0x", 2)) { /* * The path on the mount supplement line isn't followed by * " 0x". */ if (!Fwarn) (void) fprintf(stderr, "%s: %s line %d: no device: \"%s\"\n", Pn, MntSupP, ln, buf); err = 1; continue; } sz = (size_t)(dp - buf); (void) strncpy(path, buf, sz); path[sz] = '\0'; /* * Assemble the hexadecimal device number of the mount supplement * line. */ for (dev = 0, dp += 3; *dp; dp++) { if (!isxdigit((int)*dp)) break; if (isdigit((int)*dp)) dev = (dev << 4) + (int)*dp - (int)'0'; else dev = (dev << 4) + (int)tolower(*dp) - (int)'a' + 10; } if (*dp) { /* * The device number couldn't be assembled. */ if (!Fwarn) (void) fprintf(stderr, "%s: %s line %d: illegal device: \"%s\"\n", Pn, MntSupP, ln, buf); err = 1; continue; } /* * Search the mount supplement hash buckets. (Allocate them as * required.) */ if (!MSHash) { if (!(MSHash = (mntsup_t **)calloc(HASHMNT, sizeof(mntsup_t *))) ) { (void) fprintf(stderr, "%s: no space for mount supplement hash buckets\n", Pn); Exit(1); } } h = hash_mnt(path); for (mp = MSHash[h]; mp; mp = mp->next) { if ((mp->dnl == dnl) && !strcmp(mp->dn, path)) break; } if (mp) { /* * A path match was located. If the device number is the * same, skip this mount supplement line. Otherwise, issue * a warning. */ if (mp->dev != dev) { (void) fprintf(stderr, "%s: %s line %d path duplicate of %d: \"%s\"\n", Pn, MntSupP, ln, mp->ln, buf); err = 1; } continue; } /* * Allocate and fill a new mount supplement hash entry. */ if (!(mpn = (mntsup_t *)malloc(sizeof(mntsup_t)))) { (void) fprintf(stderr, "%s: no space for mount supplement entry: %d \"%s\"\n", Pn, ln, buf); Exit(1); } if (!(mpn->dn = (char *)malloc(sz + 1))) { (void) fprintf(stderr, "%s: no space for mount supplement path: %d \"%s\"\n", Pn, ln, buf); Exit(1); } (void) strcpy(mpn->dn, path); mpn->dnl = sz; mpn->dev = dev; mpn->ln = ln; mpn->next = MSHash[h]; MSHash[h] = mpn; } if (ferror(fs)) { if (!Fwarn) (void) fprintf(stderr, "%s: error reading %s\n", Pn, MntSupP); err = 1; } (void) fclose(fs); if (err) { if (MSHash) { for (h = 0; h < HASHMNT; h++) { for (mp = MSHash[h]; mp; mp = mpn) { mpn = mp->next; if (mp->dn) (void) free((MALLOC_P *)mp->dn); (void) free((MALLOC_P *)mp); } } (void) free((MALLOC_P *)MSHash); MSHash = (mntsup_t **)NULL; } return(0); } } /* * If no errors have been detected reading the mount supplement file, search * its hash buckets for the supplied directory path. */ if (err) return(0); h = hash_mnt(dn); for (mp = MSHash[h]; mp; mp = mp->next) { if ((dnl == mp->dnl) && !strcmp(dn, mp->dn)) { memset((void *)s, 0, sizeof(struct stat)); s->st_dev = mp->dev; *ss |= SB_DEV; return(1); } } return(0); } /* * hash_mnt() - hash mount point */ static int hash_mnt(dn) char *dn; /* mount point directory name */ { register int i, h; size_t l; if (!(l = strlen(dn))) return(0); if (l == 1) return((int)*dn & (HASHMNT - 1)); for (i = h = 0; i < (int)(l - 1); i++) { h ^= ((int)dn[i] * (int)dn[i+1]) << ((i*3)%13); } return(h & (HASHMNT - 1)); } #endif /* defined(HASMNTSUP) */ /* * readmnt() - read mount table */ struct mounts * readmnt() { char buf[MAXPATHLEN], *cp, **fp; char *dn = (char *)NULL; size_t dnl; int ds, ne; char *fp0 = (char *)NULL; char *fp1 = (char *)NULL; int fr, ignrdl, ignstat; char *ln; struct mounts *mp; FILE *ms; int nfs; struct stat sb; static char *vbuf = (char *)NULL; static size_t vsz = (size_t)0; if (Lmi || Lmist) return(Lmi); /* * Open access to /proc/mounts, assigning a page size buffer to its stream. */ (void) snpf(buf, sizeof(buf), "%s/mounts", PROCFS); ms = open_proc_stream(buf, "r", &vbuf, &vsz, 1); /* * Read mount table entries. */ while (fgets(buf, sizeof(buf), ms)) { if (get_fields(buf, (char *)NULL, &fp, (int *)NULL, 0) < 3 || !fp[0] || !fp[1] || !fp[2]) continue; /* * Convert octal-escaped characters in the device name and mounted-on * path name. */ if (fp0) { (void) free((FREE_P *)fp0); fp0 = (char *)NULL; } if (fp1) { (void) free((FREE_P *)fp1); fp1 = (char *)NULL; } if (!(fp0 = cvtoe(fp[0])) || !(fp1 = cvtoe(fp[1]))) continue; /* * Locate any colon (':') in the device name. * * If the colon is followed by * "(pid*" -- it's probably an * automounter entry. * * Ignore autofs, pipefs, and sockfs entries. */ cp = strchr(fp0, ':'); if (cp && !strncasecmp(++cp, "(pid", 4)) continue; if (!strcasecmp(fp[2], "autofs") || !strcasecmp(fp[2], "pipefs") || !strcasecmp(fp[2], "sockfs")) continue; /* * Interpolate a possible symbolic mounted directory link. */ if (dn) (void) free((FREE_P *)dn); dn = fp1; fp1 = (char *)NULL; #if defined(HASEOPT) if (Efsysl) { /* * If there is an -e file system list, check it to decide if a stat() * and Readlink() on this one should be performed. */ efsys_list_t *ep; for (ignrdl = ignstat = 0, ep = Efsysl; ep; ep = ep->next) { if (!strcmp(dn, ep->path)) { ignrdl = ep->rdlnk; ignstat = 1; break; } } } else #endif /* defined(HASEOPT */ ignrdl = ignstat = 0; /* * Avoid Readlink() when requested. */ if (!ignrdl) { if (!(ln = Readlink(dn))) { if (!Fwarn) { (void) fprintf(stderr, " Output information may be incomplete.\n"); } continue; } if (ln != dn) { (void) free((FREE_P *)dn); dn = ln; } } if (*dn != '/') continue; dnl = strlen(dn); /* * Test for duplicate and NFS directories. */ for (mp = Lmi; mp; mp = mp->next) { if ((dnl == mp->dirl) && !strcmp(dn, mp->dir)) break; } if ((nfs = strcasecmp(fp[2], "nfs"))) { if ((nfs = strcasecmp(fp[2], "nfs3"))) nfs = strcasecmp(fp[2], "nfs4"); } if (mp) { /* * If this duplicate directory is not root, ignore it. If the * already remembered entry is NFS-mounted, ignore this one. If * this one is NFS-mounted, ignore the already remembered entry. */ if (strcmp(dn, "/")) continue; if (mp->ty == N_NFS) continue; if (nfs) continue; } /* * Stat() the directory. */ if (ignstat) fr = 1; else { if ((fr = statsafely(dn, &sb))) { if (!Fwarn) { (void) fprintf(stderr, "%s: WARNING: can't stat() ", Pn); safestrprt(fp[2], stderr, 0); (void) fprintf(stderr, " file system "); safestrprt(dn, stderr, 1); (void) fprintf(stderr, " Output information may be incomplete.\n"); } } else ds = SB_ALL; } #if defined(HASMNTSUP) if (fr) { /* * If the stat() failed or wasn't called, check the mount * supplement table, if possible. */ if ((MntSup == 2) && MntSupP) { ds = 0; if (getmntdev(dn, dnl, &sb, &ds) || !(ds & SB_DEV)) { (void) fprintf(stderr, "%s: assuming dev=%#lx for %s from %s\n", Pn, (long)sb.st_dev, dn, MntSupP); } } else { if (!ignstat) continue; ds = 0; /* No stat() was allowed. */ } } #else /* !defined(HASMNTSUP) */ if (fr) { if (!ignstat) continue; ds = 0; /* No stat() was allowed. */ } #endif /* defined(HASMNTSUP) */ /* * Fill a local mount structure or reuse a previous entry when * indicated. */ if (mp) { ne = 0; if (mp->dir) { (void) free((FREE_P *)mp->dir); mp->dir = (char *)NULL; } if (mp->fsname) { (void) free((FREE_P *)mp->fsname); mp->fsname = (char *)NULL; } } else { ne = 1; if (!(mp = (struct mounts *)malloc(sizeof(struct mounts)))) { (void) fprintf(stderr, "%s: can't allocate mounts struct for: ", Pn); safestrprt(dn, stderr, 1); Exit(1); } } mp->dir = dn; dn = (char *)NULL; mp->dirl = dnl; if (ne) mp->next = Lmi; mp->dev = ((mp->ds = ds) & SB_DEV) ? sb.st_dev : 0; mp->rdev = (ds & SB_RDEV) ? sb.st_rdev : 0; mp->inode = (INODETYPE)((ds & SB_INO) ? sb.st_ino : 0); mp->mode = (ds & SB_MODE) ? sb.st_mode : 0; if (!nfs) { mp->ty = N_NFS; if (HasNFS < 2) HasNFS = 2; } else mp->ty = N_REGLR; #if defined(HASMNTSUP) /* * If support for the mount supplement file is defined and if the * +m option was supplied, print mount supplement information. */ if (MntSup == 1) { if (mp->dev) (void) printf("%s %#lx\n", mp->dir, (long)mp->dev); else (void) printf("%s 0x0\n", mp->dir); } #endif /* defined(HASMNTSUP) */ /* * Save mounted-on device or directory name. */ dn = fp0; fp0 = (char *)NULL; mp->fsname = dn; /* * Interpolate a possible file system (mounted-on) device name or * directory name link. * * Avoid Readlink() when requested. */ if (ignrdl || (*dn != '/')) { if (!(ln = mkstrcpy(dn, (MALLOC_S *)NULL))) { (void) fprintf(stderr, "%s: can't allocate space for: ", Pn); safestrprt(dn, stderr, 1); Exit(1); } ignstat = 1; } else ln = Readlink(dn); dn = (char *)NULL; /* * Stat() the file system (mounted-on) name and add file system * information to the local mount table entry. */ if (ignstat || !ln || statsafely(ln, &sb)) sb.st_mode = 0; mp->fsnmres = ln; mp->fs_mode = sb.st_mode; if (ne) Lmi = mp; } /* * Clean up and return the local mount info table address. */ (void) fclose(ms); if (dn) (void) free((FREE_P *)dn); if (fp0) (void) free((FREE_P *)fp0); if (fp1) (void) free((FREE_P *)fp1); Lmist = 1; return(Lmi); }