/*
 * 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";
#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);
	    Error();
	}
/*
 * 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);
		    Error();
		}
	    }
	/*
	 * 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);
			Error();
		    }
		}
		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);
		    Error();
		}
		if (!(mpn->dn = (char *)malloc(sz + 1))) {
		    (void) fprintf(stderr,
			"%s: no space for mount supplement path: %d \"%s\"\n",
			Pn, ln, buf);
		    Error();
		}
		(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)) {
		zeromem((char *)s, 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;
	int mqueue;
	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 Mqueue directory
	 */
	    mqueue = strcmp(fp[2], "mqueue");

	/*
	 * 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 (!nfs && !HasNFS)
		HasNFS = 1;
	    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);
		    Error();
	        }
	    }
	    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 if (!mqueue) {
		mp->ty = N_MQUEUE;
		MqueueDev = mp->dev;
	    } 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);
		    Error();
		}
		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);
}
