# include	<stdio.h>

# include	"../ingres.h"
# include	"../aux.h"
# include	"../unix.h"
# include	"../access.h"
# include	"../lock.h"

/*
**  INITUCODE -- initialize standalone process
**
**	This function initializes a standalone process, initializing
**	a lot of global variables, scanning the argument vector for
**	some special flags (-u and +-w), seperating flags and
**	parameters, and so forth.
**
**	Every standalone program should begin with the lines:
**		#	ifdef xTTR1
**			tTrace(&argc, argv, 'T');
**		#	endif
**			i = initucode(argc, argv, ...);
**			switch (i)
**				...
**
**	On a return of 2, 3, or 4, essentially none of the processing
**	is done (particularly true with return 4).  Virtually nothing
**	can be done in the calling program except print a "usage"
**	message and exit.  The exception to this is that 'Pathname'
**	is set, so that it can be used in the error printing.  For
**	example, ingres.c cats file .../files/usage on this sort of
**	error.
**
**	If it is preferable to not lock the database at this time,
**	the 'waitmode' parameter should be passed as -1.  This still
**	causes the 'Wait_action' variable to be initialized, but the
**	database is not actually locked.  It can be locked by calling:
**		db_lock(Dbpath, M_EXCL);
**	at the proper time.
**
**	For the main effects of this routine, see the "Side Effects"
**	section below.
**
**	argv[argc] is set to NULL so that it is well known on version
**	six or version seven systems.
**
**	Parameters:
**		argc -- argc from main.
**		argv -- argv from main.
**		dbflag -- TRUE -- take the first parameter as the
**				database name.
**			FALSE -- don't take the first parameter as
**				the database name.
**		paramlist -- a pointer to an array[4] of pointers
**			to character; set to the extra fields of
**			the users file entry for the real user
**			executing the code (not the user on the
**			-u flag).  If NULL, this is ignored.
**		waitmode -- M_EXCL -- set an exclusive lock on the
**				database.
**			M_SHARE -- set a shared lock on the database.
**			-1 -- don't set a lock on the database.
**				However, other stuff (Wait_action) is
**				still set up so that the lock can be
**				placed later by calling 'db_lock'.
**
**	Returns:
**		0 -- everything is ok.
**		1 -- the database does not exist.
**		2 -- you are not authorized to access this database.
**		3 -- you are not a valid INGRES user.
**		4 -- no database name was specified (only if dbflag
**			== TRUE).
**		5 -- everything is ok, but there was an indirect
**			taken.
**		6 -- there was an indirect taken, but there was no
**			database there.
**
**		If dbflag == FALSE, you can only get returns 0 and
**			3.
**
**	Side Effects:
**		A lot of variables are set, as follows:
**
**		Dbpath -- set to the pathname of the database (only
**			if dbflag == TRUE).  It is set even if the
**			database does not exist.
**		Parmvect -- set to the parameters from argv, that is,
**			anything not beginning with '+' or '-'.
**		Flagvect -- set to the flags from argv, that is,
**			everything beginning with '+' or '-'.  The
**			flags '+w', '-w', and '-u' are stripped out,
**			however.
**		Wait_action -- set to the appropriate action (A_SLP
**			or A_RTN) based on the +-w flags and whether
**			we are running in background or not.
**			This is automatically used by 'db_lock()'.
**		Usercode -- set to the persons effective user code
**			(that is, after the -u processing).  Only
**			the INGRES user or the DBA can use the -u
**			flag.
**		Pathname -- set to the pathname of the INGRES subtree.
**		Status -- an integer set to the user status field
**			of the users file for the real user.
**		Ing_uid -- set to the user id of the INGRES user.
**
**		The rubout signal (signal 2) is caught, and refered
**		to the standard rubout processor (see rub.c); thus,
**		a routine called 'rubproc' must be defined in the
**		standalone code (which will just call exit, in the
**		normal case).
**
**		The 'adminhdr' part of the 'Admin' struct is filled
**		in.  This is not done with readadmin() and is not
**		equivalent to an 'admininit()', but it does make
**		the DBA and database status available.
**
**		This routine can also exit immediately with an
**		error message.
**
**	Defined Constants:
**		MAXPVECT -- the maximum number of parameter type
**			arguments to any standalone program.
**		MAXFVECT -- the maximum number of flag type arg-
**			uments to any standalong program (not inclu-
**			ding flags in the users file, and the +-w
**			and -u flags).
**
**	Defines:
**		initucode
**		db_lock -- to lock the database.
**		initdbpath -- to initialize the database pathname.
**		Parmvect[]
**		Flagvect[]
**		Usercode
**		Wait_action
**		Dbpath
**		Status -- the user status.
**		Lock
**		Ing_uid
**		Standalone -- '1' to indicate running standalone.
**
**	Requires:
**		getpw -- to determine the Pathname entry.
**		Admin
**
**	Called By:
**		All standalone routines.
**
**	Files:
**		/etc/passwd -- to get the pathname for user "ingres".
**		.../files/users -- to get all the per-user information,
**			and to process the -u flag.
**
**	Compilation Flags:
**		xB_UNIX, xV6_UNIX -- see comments in aux.h
**
**	Trace Flags:
**		none
**
**	Diagnostics:
**		Bad flag %s
**			The -u flag was incorrect.
**		Too many parameters
**			More than MAXPVECT parameters were specified.
**		Too many flags
**			More than MAXFVECT flags were specified.
**		Invalid user name %s
**			The name on the -u flag is not known to INGRES.
**		You may not use the -u flag
**			You are not INGRES or the DBA.  If there is
**			no known database name, it means you are not
**			INGRES.
**		Database temporarily unavailable
**			Someone else is using the database in exclusive
**			mode, or you want it in exclusive mode and
**			someone else is using it.
**
**	History:
**		8/15/79 (eric) (6.2/7) -- set argv[argc] = NULL.
**		6/29/79 (eric) (6.2/6) -- added Standalone.
**		3/14/79 (eric) -- modified 'initdbpath' so that it
**			knows about databases being in data/base
**			instead of datadir.
**		1/19/79 (eric) -- added 'initdbpath'.
**		1/12/79 (eric) -- heavily modified for 6.2/0.
*/


# define	MAXFVECT	15
# define	MAXPVECT	20

char	*Usercode;	/* the usercode of the effective user */
int	Status;		/* the user status of the real user */
int	Rubignored;	/* set if rubouts ignored */
			/* (also in initproc for system processes) */
int	Wait_action;	/* the action on the db_lock */
char	*Dbpath;	/* the pathname of the database */
char	*Flagvect[MAXFVECT+1];	/* the flags from argv */
char	*Parmvect[MAXPVECT+1];	/* the parameters from argv */
int	Ing_uid;	/* the user id of the INGRES user */
int	Standalone;	/* this is stand alone code */

initucode(argc, argv, dbflag, paramlist, waitmode)
int	argc;
char	**argv;
int	dbflag;
char	*paramlist[4];
int	waitmode;
{
	register char	*p;
	char		*q;
	char		c;
	FILE		*iop;
	static char	sbuf[MAXLINE * 2];
	register char	*sbufp;
	char		buf[MAXLINE+1];
	register int	i;
	int		npermit;
	int		rtval;
	char		*field[UF_NFIELDS];
	int		actualuid;
	auto int	uid;
	auto int	gid;
	int		waitflag;
	char		*userflag;
	struct sgttyb	gttydummy;
	int		fvi, pvi;
	char		**avp;
	char		usr_ovrd[3];
	static int	reenter;
	extern		rubcatch();

	Standalone = TRUE;
	sbufp = sbuf;
	argv[argc] = NULL;

	/*
	**  Set up interrupts.
	*/

	reenter = 0;
	setexit();
	if (reenter++)
		exit(-1);
	if (signal(2, 1) == 0)
		signal(2, rubcatch);

	/*
	**  Get pathname of INGRES subtree from /etc/passwd file
	**  entry for USERINGRES (presumably "ingres") and save it
	**  in 'Pathname'.
	**
	**  This algorithm suggested by Jim Popa.
	*/

	if ((iop = fopen("/etc/passwd", "r")) == NULL)
		syserr("initucode: passwd");

	do
	{
		if (fgetline(buf, MAXLINE, iop) == NULL)
			syserr("initucode: no INGRES");
		
		/* decode passwd entry */
		i = 0;
		for (p = buf; *p != 0; p++)
		{
			if (*p == ':')
			{
				*p = 0;
				i++;
				field[i] = p + 1;
			}
		}

		/* check for enough fields for valid entry */
		if (i < 3)
			syserr("initucode: passwd fmt %s", buf);
	} while (!sequal(buf, USERINGRES));

	/* we now have the INGRES passwd file entry in 'buf' */
	fclose(iop);

	/* copy pathname entry into 'Pathname' variable */
	Pathname = sbufp;
	sbufp += smove(field[i - 1], sbufp) + 1;

	/* create the INGRES user id */
	if (atoi(p = field[2], &Ing_uid) != 0)
		syserr("initucode: bad Ing_uid %s", p);
#	ifdef xV6_UNIX
	Ing_uid &= 0377;
#	endif
#	ifdef xB_UNIX
	if (atoi(p = field[3], &gid) != 0)
		syserr("initucode: bad Ing_gid %s", p);
	Ing_uid = (Ing_uid & 0377) | ((gid & 0377) << 8);
#	endif

	/*
	**  Scan the argument vector.  The following flags are pulled
	**  out of the vector (and argc and argv are adjusted so it
	**  looks like they never existed):
	**	+w, -w -- (don't) wait for the database to be free.
	**	-uxxx -- run as user xxx.  If first character is a
	**	colon, the format must be '-u:xx' where 'xx' is the
	**	internal user code.
	*/

	avp = argv;
	fvi = 0;
	pvi = 0;
	waitflag = 0;
	userflag = NULL;
	usr_ovrd[0] = 0;

	for (i = argc; --i > 0; )
	{
		p = *++avp;
		if (p[0] == '+')
		{
			if (p[1] == 'w')
				waitflag = 1;
			else
				goto boring;
		}
		else if (p[0] == '-')
		{
			switch (p[1])
			{
			  case 'w':
				waitflag = -1;
				break;
			
			  case 'u':
				if (p[2] == ':')
				{
					if (p[3] == 0 || p[4] == 0 || p[5] != 0)
					{
						printf("Bad flag %s\n", p);
						exit(-1);
					}
					smove(&p[3], usr_ovrd);
				}
				else
					userflag = &p[2];
				break;

			  default:
				/* not an interesting flag */
			boring:
				if (fvi >= MAXFVECT)
				{
					printf("Too many flags\n");
					exit(-1);
				}
				Flagvect[fvi++] = p;
				break;
			}
		}
		else
		{
			/* not a flag: save in Parmvect */
			if (pvi >= MAXPVECT)
			{
				printf("Too many parmameters\n");
				exit(-1);
			}
			Parmvect[pvi++] = p;
		}
	}

	if (pvi <= 0 && dbflag)
	{
		return (4);	/* no database name specified */
	}

	/*
	**  Scan the "users" file.
	*/

	if ((iop = fopen(ztack(Pathname, "/files/users"), "r")) == NULL)
		syserr("initucode: open error");
	
	/* get uid (out of loop) for test */
#	ifdef xV6_UNIX
	actualuid = getuid() & 0377;
#	endif
#	ifndef xV6_UNIX
	actualuid = getuid();
#	endif
	
	/* scan users file, one line at a time */
	rtval = 3;
	while ((Usercode == NULL || userflag != NULL) && fgetline(buf, MAXLINE, iop) != NULL)
	{
		/* decode users file entry */
		i = 0;
		field[0] = buf;
		for (p = buf; *p != 0; p++)
		{
			if (*p == ':')
			{
				*p = 0;
				i++;
				field[i] = p + 1;
			}
		}

		/* check for correct number of fields */
		if (i != UF_NFIELDS - 1)
			syserr("initucode: users fmt %s", buf);

		/*
		**  Check to see if this entry is the override user.
		**  If so, save his user code in usr_ovrd.
		*/

		if (userflag != NULL && sequal(userflag, field[UF_NAME]))
		{
			smove(field[UF_UCODE], usr_ovrd);
			userflag = NULL;
		}

		/* don't bother with this shit if not needed */
		if (Usercode != NULL)
			continue;
		
		/*
		**  Build the user id of this entry into 'uid'
		**  and see if it is this user.
		*/

		if (atoi(p = field[UF_UID], &uid) != 0)
			syserr("initucode: users: bad UID %s", p);

#		ifdef xB_UNIX
		if (atoi(p = field[UF_GID], &gid) != 0)
			syserr("initucode: users: bad GID %s", p);
		uid = (uid & 0377) | ((gid & 0377) << 8);
#		endif

#		ifdef xV6_UNIX
		if ((uid & 0377) != actualuid)
			continue;
#		endif
#		ifndef xV6_UNIX
		if (uid != actualuid)
			continue;
#		endif

		/*
		**  We now have the real user entry.
		**	Fetch the usercode, the status bits, and other
		**	fields from the users file, and save them in
		**	a safe place (sbuf).
		*/

		Usercode = sbufp;
		sbufp += smove(field[UF_UCODE], sbufp) + 1;
		Status = oatoi(field[UF_STAT]);
		if (paramlist != NULL)
		{
			for (i = 0; i < 4; i++)
			{
				paramlist[i] = sbufp;
				sbufp += smove(field[UF_FLAGS + i], sbufp) + 1;
			}
		}

		/* validate access permission */
		rtval = 0;
		if (!dbflag || (Status & U_SUPER) != 0)
			continue;
		p = field[UF_DBLIST];
		if (*p == 0)
			continue;

		/* select permission/no-permission */
		npermit = 0;
		if (*p == '-')
		{
			p++;
			npermit++;
		}

		/* scan for database listed */
		if (!npermit)
			rtval = 2;
		for (c = *p; c != 0; p = q + 1)
		{
			for (q = p; *q != ',' && *q != 0; q++)
				continue;
			c = *q;
			*q = 0;
			if (sequal(Parmvect[0], p))
			{
				rtval = npermit ? 2 : 0;
				break;
			}
		}
	}
	fclose(iop);

	if (rtval != 0)
		return (rtval);

	/*
	**  Check for existance of the database.  This is done by
	**	first building the pathname of the database into
	**	'Dbpath', and then reading the admin file (just
	**	the adhdr part).
	*/

	if (dbflag)
	{
		Dbpath = sbufp;
		switch (i = initdbpath(Parmvect[0], Dbpath, TRUE))
		{
		  case 0:
			rtval = 0;
			break;

		  case 1:
			rtval = 5;
			break;

		  case 2:
			rtval = 1;
			break;

		  case 3:
			rtval = 6;
			break;

		  default:
			syserr("initucode: initdbpath %d", i);
		}
		sbufp += length(Dbpath) + 1;

		if (rtval == 0 || rtval == 5)
		{
			i = open(ztack(Dbpath, "/admin"), 0);
			if (i < 0)
				rtval += 1;
			else
			{
				read(i, &Admin.adhdr, sizeof Admin.adhdr);
				close(i);
				if ((Admin.adhdr.adflags & A_NEWFMT) != 0)
					syserr("I don't know about new databases");
			}
		}
	}

	/*
	**  Check to see if the name on the -u flag is valid, and
	**	that this user is allowed to use it.
	*/

	if (userflag != NULL)
	{
		printf("Invalid user name %s\n", userflag);
		exit(-1);
	}
	if (usr_ovrd[0] != '\0')
	{
		if ((Status & U_SUPER) == 0)
		{
			if (!dbflag || !bequal(Admin.adhdr.adowner, Usercode, 2))
			{
				printf("You may not use the -u flag\n");
				exit(-1);
			}
		}
		bmove(usr_ovrd, Usercode, 2);
	}

	/*
	**  Process the +-w flag.
	**	First, determine the locking mode.  If +w, always
	**	wait; if -w, never wait; if unspecified, wait if in
	**	background, but print error and exit if running
	**	interactive.
	*/

	if (waitflag > 0 || (waitflag == 0 && gtty(0, &gttydummy) < 0))
		Wait_action = A_SLP;
	else
		Wait_action = A_RTN;
	if (dbflag && waitmode >= 0)
		db_lock(waitmode);
	
	/*
	**  Return authorization value.
	*/

	return (rtval);
}
/*
**  DB_LOCK -- lock database
**
**	Locks the database.  Everyone should do this before using any
**	database.
**
**	Parameters:
**		database -- the pathname of the database.
**		mode -- M_EXCL -- get an exclusive lock.
**			M_SHARE -- get a shared lock.
**
**	Returns:
**		none
**
**	Side Effects:
**		Alockdes is opened.
**
**	Requires:
**		setdbl
**		stat -- to find the inumber of the database.
**		Lock -- must have the .dbnode part filled in (done
**			by initdbpath).
**		Admin -- must have the adflags part filled in.
*/

struct lockreq	Lock;	/* the database lock structure */

db_lock(mode)
int	mode;
{
	if ((Admin.adhdr.adflags & A_DBCONCUR) == 0)
		return;
	if (Alockdes < 0)
		Alockdes = open("/dev/ingreslock", 1);
	if (setdbl(Wait_action, mode) < 0)
	{
		printf("Database temporarily unavailable\n");
		exit(1);
	}
}
/*
**  INITDBPATH -- initialize the pathname of the database
**
**	The pathname of a specified database is created.  Indirection
**	via a file is supported, so that if the pathname is a file,
**	the first line of the file is read and used as the pathname
**	of the real database.
**
**	Parameters:
**		database -- the name of the database.  If NULL,
**			the pathname of datadir is returned.
**		dbbuf -- a buffer into which the pathname should
**			be dumped.
**		follow -- if set, follow the indirect chain of
**			database pathnames.
**
**	Returns:
**		0 -- database exists in datadir
**		1 -- database exists, but I followed a pointer.
**		2 -- database doesn't exist in datadir.
**		3 -- databae doesn't exist, but I followed a pointer.
**
**	Side Effects:
**		none.
**
**	Requires:
**		Pathname -- must have the pathname of the root of
**			the INGRES subtree.
**		Ing_uid -- must have the user id of the INGRES user.
*/

initdbpath(database, dbpath, follow)
char	*database;
char	*dbpath;
int	follow;
{
	struct stat	ibuf;
	register char	*d;
	register FILE	*f;
	register int	phase;
	int		retval;
	int		uid;

	d = dbpath;

	if (database == NULL)
	{
		concat(Pathname, "/data/base/", d);
		return (0);
	}

	/* get the basic pathname */
	concat(ztack(Pathname, "/datadir/"), database, d);

	/*
	** Iterate looking for database.
	**	"Phase" is what we are trying:
	**	   -1 -- looking in datadir
	**	    0 -- looking in data/base
	**	    1 -- following indirect.
	*/

	retval = 2;
	for (phase = -1;;)
	{
		/* find out what sort of filesystem node this is */
		if (stat(d, &ibuf) < 0)
		{
			if (phase < 0)
			{
				concat(ztack(Pathname, "/data/base/"), database, d);
				phase = 0;
				continue;
			}
			else
				return (retval);
		}
		
		/* set up the lock structure for future use */
		bmove(&ibuf, Lock.dbnode, 4);

		retval -= 2;
		if ((ibuf.st_mode & 060000) == 040000)
			return (retval);
		
		/* if second time through, the database must be a directory */
		if (phase > 0)
			syserr("initdbpath: not direc");
		
		/* if we shouldn't follow the chain, say it exists */
		if (!follow)
			return (3);
		
		/* it's a file -- see if we can use it */
		uid = ibuf.st_uid;
#		ifdef xB_UNIX
		uid = (uid & 0377) | ((ibuf.st_gid & 0377) << 8);
#		endif
#		ifdef xV6_UNIX
		uid &= 0377;
#		endif
		if (uid != Ing_uid || (ibuf.st_mode & 0777) != 0600)
			return (3);
		
		f = fopen(d, "r");
		if (f == NULL)
			syserr("initdbpath: fopen");
	
		/* read the pathname of the database */
		if (fgetline(d, MAXLINE, f) == NULL || d[0] != '/')
			syserr("initdbpath: bad indirect");
		fclose(f);

		/* prepare for next iteration */
		retval = 3;
		phase = 1;
	}
}
