/*
 * main.c - common main function for lsof
 *
 * V. Abell, Purdue University Computing Center
 */


/*
 * Copyright 1994 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 1994 Purdue Research Foundation.\nAll rights reserved.\n";
static char *rcsid = "$Id: main.c,v 1.27 96/02/21 09:16:38 abe Exp $";
#endif


#include "lsof.h"


/*
 * Local definitions
 */

static int GObk[] = { 1, 1 };		/* option backspace values */
static int GOx1 = 1;			/* first opt[][] index */
static int GOx2 = 1;			/* second opt[][] index */
static char *GOv = NULL;		/* option `:' value pointer */


_PROTOTYPE(static int GetOpt,(int ct, char *opt[], char *rules, int *err));


/*
 * main() - main function for lsof
 */

main(argc, argv)
	int argc;
	char *argv[];
{
	int c, i, rv;
	char *cp;
	int err = 0;
	int fh = 0;
	struct lfile *lf;
	char options[64];
	struct sfile *sfp;
	struct lproc **slp = NULL;
	int sp = 0;
	int version = 0;

	if ((Pn = strrchr(argv[0], '/')) != NULL)
		Pn++;
	else
		Pn = argv[0];
/*
 * Common initialization.
 */
	Mypid = getpid();
	if ((Mygid = (gid_t)getgid()) != getegid())
		Setgid = 1;
	if ((Myuid = (uid_t)getuid()) && !geteuid())
		Setuidroot = 1;
	if ((Namech = (char *)malloc(MAXPATHLEN)) == NULL) {
		(void) fprintf(stderr, "%s: no space for name buffer\n", Pn);
		exit(1);
	}
/*
 * Create option mask.
 */
	(void) sprintf(options, "?ab%sc:%sd:%sF:g:hi:%sl%snNoOp:Pr:sS:tu:Uvw%s",

#if	defined(HAS_AFS) && defined(HASAOPT)
			"A:",
#else	/* !defined(HAS_AFS) || !defined(HASAOPT) */
			"",
#endif	/* defined(HAS_AFS) && defined(HASAOPT) */

#if	defined(HASNCACHE)
			"C",
#else	/* !defined(HASNCACHE) */
			"",
#endif	/* defined(HASNCACHE) */

#if	defined(HASDCACHE)
			"D:",
#else	/* !defined(HASDCACHE) */
			"",
#endif	/* defined(HASDCACHE) */

#if	defined(HASKOPT)
			"k:",
#else	/* !defined(HASKOPT) */
			"",
#endif	/* defined(HASKOPT) */

#if	defined(HASMOPT)
			"m:",
#else	/* !defined(HASMOPT) */
			"",
#endif	/* defined(HASMOPT) */

#if	defined(HASXOPT)
# if	defined(HASXOPT_ROOT)
			(Myuid == 0) ? "X" : ""
# else	/* !defined(HASXOPT_ROOT) */
			"X"
# endif	/* defined(HASXOPT_ROOT) */
#else	/* !defined(HASXOPT) */
			""
#endif	/* defined(HASXOPT) */

			);
/*
 * Loop through options.
 */
	while ((c = GetOpt(argc, argv, options, &rv)) != EOF) {
		if (rv) {
			err = 1;
			continue;
		}
		switch(c) {

		case 'a':
			Fand = 1;
			break;

#if	defined(HAS_AFS) && defined(HASAOPT)
		case 'A':
			if (GOv == NULL || *GOv == '-') {
			    (void) fprintf(stderr,
				"%s: -A not followed by path\n", Pn);
			    err = 1;
			} else
			    AFSApath = GOv;
			break;
#endif	/* defined(HAS_AFS) && defined(HASAOPT) */

		case 'b':
			Fblock = 1;
			break;
		case 'c':
			if (enter_str_lst("c", GOv, &Cmdl))
			    err = 1;
			break;

#if	defined(HASNCACHE)
		case 'C':
			Fncache = 0;
			break;
#endif	/* defined(HASNCACHE) */

		case 'd':
			if (enter_fd(GOv))
			    err = 1;
			break;

#if	defined(HASDCACHE)
		case 'D':
			if (ctrl_dcache(GOv))
			    err = 1;
			break;
#endif	/* defined(HASDCACHE) */

		case 'F':
			if (GOv == NULL || *GOv == '-'
			||  strcmp(GOv, "0") == 0) {
			    if (GOv != NULL) {
				if (*GOv == '-') {
				    GOx1 = GObk[0];
				    GOx2 = GObk[1];
				} else if (*GOv == '0')
				    Terminator = '\0';
			    }
			    for (i = 0; FieldSel[i].nm; i++) {
				FieldSel[i].n = 1;
			    }
			    Ffield = 1;
			    break;
			}
			if (strcmp(GOv, "?") == 0) {
			    fh = 1;
			    break;
			}
			for (; *GOv; GOv++) {
			    for (i = 0; FieldSel[i].nm; i++) {
				if (FieldSel[i].id == *GOv) {
				    FieldSel[i].n = 1;
				    if (i == LSOF_FIX_TERM)
					Terminator = '\0';
				    break;
				}
			    }
			    if ( ! FieldSel[i].nm) {
				(void) fprintf(stderr,
				    "%s: unknown field: %c\n", Pn, *GOv);
				err++;
			    }
			}
			Ffield = 1;
			break;
		case 'g':
			if (GOv != NULL) {
			    if (*GOv == '-') {
				GOx1 = GObk[0];
				GOx2 = GObk[1];
			    } else if (enter_id(PGRP, GOv))
				err = 1;
			}
			Fpgrp = 1;
			break;
		case 'h':
		case '?':
			Fhelp = 1;
			break;
		case 'i':
			if (GOv == NULL || *GOv == '-') {
			    Fnet = 1;
			    if (GOv != NULL) {
				GOx1 = GObk[0];
				GOx2 = GObk[1];
			    }
			    break;
			}
			if (enter_network_address(GOv))
			    err = 1;
			break;

#if	defined(HASKOPT)
		case 'k':
			if (GOv == NULL || *GOv == '-') {
			    (void) fprintf(stderr,
				"%s: -k not followed by path\n", Pn);
			    err = 1;
			} else
			    Nmlst = GOv;
			break;
#endif	/* defined(HASKOPT) */

		case 'l':
			Futol = 0;
			break;

#if	defined(HASMOPT)
		case 'm':
			if (GOv == NULL || *GOv == '-') {
			    (void) fprintf(stderr,
				"%s: -m not followed by path\n", Pn);
			    err = 1;
			} else
			    Memory = GOv;
			break;
#endif	/* defined(HASMOPT) */

		case 'n':
			Fhost = 0;
			break;
		case 'N':
			Fnfs = 1;
			break;
		case 'o':
			Foffset = 1;
			break;
		case 'O':
			Fovhd = 1;
			break;
		case 'p':
			if (enter_id(PID, GOv))
			    err = 1;
			break;
		case 'P':
			Fport = 0;
			break;
		case 'r':
			if (GOv == NULL || *GOv == '-')
			    i = -1;
			else {
			    for (cp = GOv, i = 0; *cp; cp++) {
				if (!isdigit(*cp)) {
				    i = -1;
				    break;
				}
				i = (i * 10) + ((int)*cp - '0');
			    }
			}
			if (i > 0)
			    RptTm = i;
			else if (i < 0) {
			    if (GOv != NULL) {
				GOx1 = GObk[0];
				GOx2 = GObk[1];
			    }
			    RptTm = RPTTM;
			} else {
			    (void) fprintf(stderr,
				"%s: illegal -r time: %s\n", Pn, GOv);
			    err = 1;
			}
			break;
		case 's':
			Fsize = 1;
			break;
		case 'S':
			if (GOv == NULL || *GOv == '-') {
			    if (GOv != NULL) {
				GOx1 = GObk[0];
				GOx2 = GObk[1];
			    }
			    TmLimit = TMLIMIT;
			} else
			    TmLimit = atoi(GOv);
			if (TmLimit < TMLIMMIN) {
			    (void) fprintf(stderr,
				"%s: WARNING: -S time (%d) changed to %d\n",
				Pn, TmLimit, TMLIMMIN);
			    TmLimit = TMLIMMIN;
			}
			break;
		case 't':
			Fterse = Fwarn = 1;
			break;
		case 'u':
			if (enter_uid(GOv))
			    err = 1;
			break;
		case 'U':
			Funix = 1;
			break;
		case 'v':
			version = 1;
			break;
		case 'w':
			Fwarn = 1;
			break;

#if	defined(HASXOPT)
		case 'X':
			Fxopt = Fxopt ? 0 : 1;
			break;
#endif	/* defined(HASXOPT) */

		default:
			(void) fprintf(stderr, "%s: unknown option (%c)\n",
			    Pn, c);
			err = 1;
		}
	}
	if (Fsize && Foffset) {
		(void) fprintf(stderr, "%s: -o and -s are mutually exclusive\n",
			Pn);
		err++;
	}
	if (Ffield) {
		if (Fterse) {
			(void) fprintf(stderr,
				"%s: -f and -t are mutually exclusive\n", Pn);
			err++;
		}
		FieldSel[LSOF_FIX_PID].n = 1;
	}
	if (DChelp || err || Fhelp || fh || version)
		usage(err ? 1 : 0, fh, version);

/*
 * Compute the selection flags.
 */
	if (Cmdl)
		Selflags |= SELCMD;
	if (Fdl)
		Selflags |= SELFD;
	if (Fnet)
		Selflags |= SELNET;
	if (Fnfs)
		Selflags |= SELNFS;
	if (Funix)
		Selflags |= SELUNX;
	if (Npgrp)
		Selflags |= SELPGRP;
	if (Npid)
		Selflags |= SELPID;
	if (Nuid)
		Selflags |= SELUID;
	if (Nwad)
		Selflags |= SELNA;
	if (GOx1 < argc)
		Selflags |= SELNM;
	if (Selflags == 0) {
		if (Fand) {
			(void) fprintf(stderr,
				"%s: no select options to AND via -a\n", Pn);
			usage(1, 0, 0);
		}
		Selflags = SELALL;
	} else
		Selall = 0;
/*
 * Read the mount table unless only network or Unix domain socket files
 * are being displayed, or are part of an ANDed option specification.
 */
	if ((!Fand && (Selflags & ~(SELNA|SELNET|SELUNX)))
	||  ( Fand && (Selflags &  (SELNA|SELNET|SELUNX)) == 0)) {
		if (!readmnt())
			exit(1);
	}
/*
 * Process the file arguments.
 */
	if (GOx1 < argc) {
		if (ck_file_arg(GOx1, argc, argv))
			usage(1, 0, 0);
	}
/*
 * Do dialect-specific initialization.
 */
	initialize();

#if	defined(WILLDROPGID)
/*
 * If this process isn't setuid(root), but it is setgid(not_real_gid),
 * relinquish the setgid power.  (If it hasn't already been done.)
 */
	(void) dropgid();
#endif	/* defined(WILLDROPGID) */

#if	defined(HASDCACHE)
/*
 * If there is a device cache, prepare the device table.
 */
	if (DCstate)
		readdev();
#endif	/* defined(HASDCACHE) */

/*
 * Gather and report process information every RptTm seconds.
 */
	do {

	/*
	 * Gather information about processes.
	 */
	    gather_proc_info();
	/*
	 * Select the information to be displayed and sort it by Process ID,
	 * unless sorting is inhibited.  In that case, the gather_proc_info()
	 * function will have produced output as it became available.
	 */
	    if (Nlproc > 1) {
		if (slp == NULL) {
		    slp = (struct lproc **)malloc(
			  (MALLOC_S)(sizeof(struct lproc *) * Nlproc));
		    sp = Nlproc;
		} else if (Nlproc > sp) {
		    slp = (struct lproc **)realloc(slp,
			  (MALLOC_S)(sizeof(struct lproc *) * Nlproc));
		    sp = Nlproc;
		}
		if (slp == NULL) {
		    (void) fprintf(stderr,
			"%s: WARNING -- no space for %d sort pointers\n",
			Pn, Nlproc);
		    exit(1);
		}
		for (i = 0; i < Nlproc; i++) {
		    slp[i] = &Lproc[i];
		}
		(void) qsort((QSORT_P *)slp, (size_t)Nlproc,
			     (size_t)sizeof(struct lproc *), comppid);
	    }
	    for (i = 0; i < Nlproc; i++) {
		Lp = (Nlproc > 1) ? slp[i] : &Lproc[i];
		if (Lp->pss)
		    print_proc();
		if (RptTm) {
		    for (Lf = Lp->file; Lf; Lf = lf) {
			if (Lf->dev_ch)
			    (void) free((FREE_P *)Lf->dev_ch);
			if (Lf->nm)
			    (void) free((FREE_P *)Lf->nm);
			if (Lf->nma)
			    (void) free((FREE_P *)Lf->nma);
			lf = Lf->next;
			(void) free((FREE_P *)Lf);
		    }
		}
	    }
	/*
	 * If a repeat time is set, sleep for the specified time.
	 */
	    if (RptTm) {
		if (Ffield) {
		    putchar(LSOF_FID_MARK);
		    putchar('\n');
		} else
		    puts("=======");
		(void) fflush(stdout);
		(void) sleep(RptTm);
		Hdr = Nlproc = 0;
	    }
	} while (RptTm);
/*
 * See if all requested information was displayed.  Return zero if it
 * was; one, if not.
 */
	for (sfp = Sfile; sfp; sfp = sfp->next) {
		if (sfp->f == 0)
			return(1);
	}
	return(0);
}


/*
 * GetOpt() -- Local get option
 *
 * Liberally adapted from the public domain AT&T getopt() source,
 * distributed at the 1985 UNIFORM conference in Dallas
 *
 * The modifications allow `?' to be an option character and allow
 * the caller to decide that an option that may be followed by a
 * value doesn't have one -- e.g., has a default instead.
 */

static int
GetOpt(ct, opt, rules, err)
	int ct;				/* option count */
	char *opt[];			/* options */
	char *rules;			/* option rules */
	int *err;			/* error return */
{
	static int sp = 1;
	register int c;
	register char *cp;

	if (GOx2 == 1) {

	/*
	 * Move to a new entry of the option array.
	 */
		if (GOx1 >= ct
		||  opt[GOx1][0] != '-' || opt[GOx1][1] == '\0')
			return(EOF);
		else if (strcmp(opt[GOx1], "--") == 0) {
			GOx1++;
			return(EOF);
		}
	}
/*
 * Flag `:' option character as an error.
 *
 * Check for a rule on this option character.
 */
	*err = 0;
	if ((c = opt[GOx1][GOx2]) == ':') {
		(void) fprintf(stderr,
			"%s: colon is an illegal option character.\n", Pn);
		*err = 1;
	} else if ((cp = strchr(rules, c)) == NULL) {
		(void) fprintf(stderr, "%s: illegal option character: %c\n",
			Pn, c);
		*err = 2;
	}
	if (*err) {

	/*
	 * An error was detected.
	 *
	 * Advance to the next option character.
	 *
	 * Return the character causing the error.
	 */
		if (opt[GOx1][++GOx2] == '\0') {
			GOx1++;
			GOx2 = 1;
		}
		return(c);
	}
	if (*(cp + 1) == ':') {

	/*
	 * The option may have a following value.  The caller decides
	 * if it does.
	 *
	 * Save the position of the possible value in case the caller
	 * decides it does not belong to the option and wants it
	 * reconsidered as an option character.  The caller does that
	 * with:
	 *		GOx1 = GObk[0]; GOx2 = GObk[1];
	 *
	 * Don't indicate that an option of ``--'' is a possible value.
	 *
	 * Finally, on the assumption that the caller will decide that
	 * the possible value belongs to the option, position to the
	 * option following the possible value, so that the next call
	 * to GetOpt() will find it.
	 */
		if(opt[GOx1][GOx2 + 1] != '\0') {
			GObk[0] = GOx1;
			GObk[1] = ++GOx2;
			GOv = &opt[GOx1++][GOx2];
		} else if (++GOx1 >= ct)
			GOv = NULL;
		else {
			GObk[0] = GOx1;
			GObk[1] = 1;
			GOv = opt[GOx1];
			if (strcmp(GOv, "--") == 0)
				GOv = NULL;
			else
				GOx1++;
		}
		GOx2 = 1;
	} else {

	/*
	 * The option character stands alone with no following value.
	 *
	 * Advance to the next option character.
	 */
		if (opt[GOx1][++GOx2] == '\0') {
			GOx2 = 1;
			GOx1++;
		}
		GOv = NULL;
	}
/*
 * Return the option character.
 */
	return(c);
}
