blob: 1d28dcd3cbdbd9c0ea67ae2a479e448539018c70 [file] [log] [blame]
/*
* 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.18 2011/09/07 19:07:45 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, 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; /* directory name */
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, s, ss)
char *dn; /* mount point directory name */
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 (!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->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 biuckets for the supplied directory path.
*/
if (err)
return(0);
h = hash_mnt(dn);
for (mp = MSHash[h]; mp; mp = mp->next) {
if (!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;
int ds;
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;
/*
* Ignore an entry with a colon in the device name, followed by
* "(pid*" -- it's probably an automounter entry.
*
* Ignore autofs, pipefs, and sockfs entries.
*/
if ((cp = strchr(fp0, ':')) && !strncasecmp(++cp, "(pid", 4))
continue;
if (!strcasecmp(fp[2], "autofs") || !strcasecmp(fp[2], "pipefs")
|| !strcasecmp(fp[2], "sockfs"))
continue;
/*
* Interpolate a possible symbolic 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;
/*
* Detect an NFS mount point.
*/
if (!(nfs = strcasecmp(fp[2], "nfs")) && !HasNFS)
HasNFS = 1;
/*
* Skip duplicate directories.
*/
for (mp = Lmi; mp; mp = mp->next) {
if (!strcmp(dn, mp->dir))
break;
}
if (mp)
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, &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) */
/*
* Allocate and fill a local mount structure.
*/
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->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 directory name.
*/
dn = fp0;
fp0 = (char *)NULL;
mp->fsname = dn;
/*
* Interpolate a possible file system (mounted-on) device 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;
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);
}