# include	<ingres.h>
# include	<aux.h>
# include	<catalog.h>
# include	<symbol.h>
# include	<tree.h>
# include	"qrymod.h"
# include	<sccs.h>
# include	<errors.h>

SCCSID(@(#)protect.c	8.4	4/15/85)

/*
**  PROTECT -- protection algorithm
**
**	This module performs the INGRES protection algorithm, as
**	presented in Stonebraker & Rubinstein, "The INGRES Protection
**	System", with a few modifications.
**
**	The basic algorithm is as follows:
**
**	The algorithm is applied once to each variable used in the
**	query.  Each variable has an initial check performed to
**	determine applicability -- if the current user owns the
**	relation, or if the relation is specially marked as being
**	"all access to everyone", then the algorithm is skipped,
**	thereby having effectively no restriction.
**
**	The set of all such variables is computed in 'protect',
**	and then 'dopro' is called to process each of those.  This
**	is so the protection algorithm does not get applied recursively
**	to constraints which define more than one variable.  Notice
**	that this could in itself be a protection violation, if it
**	were acceptable to reference a relation you do not own in a
**	PERMIT statement.
**
**	The effective query mode for this variable is then computed.
**	This is the same as the query mode of the whole query if
**	the variable in question is the result variable, otherwise
**	it is "retrieve" mode.
**
**	The next step is to scan the query tree and create sets of
**	domains referenced.  Four sets are created:
**		uset -- the set of domains updated (actually,
**			referenced in the target list -- on a
**			retrieve, this will be the set of domains
**			retrieved to the user).
**		rset -- the set of domains retrieved in some
**			context other than the left hand side of
**			an equal sign.
**		aset -- the set of domains aggregated.  This only
**			includes domains aggregated with a simple
**			aggregate (not an aggregate function) with
**			no qualification, since it may be possible
**			to come up with too much information other-
**			wise.
**		qset -- the set of domains retrieved for use in
**			a qualification, but never stored.  This
**			includes domains in a qualification of an
**			aggregate or aggregate function.
**	For more details of domains in each of these sets, look at
**	the routine 'makedset'.
**
**	If we had a retrieve operation in the first place, we will
**	then merge 'uset' into 'rset' and clear 'uset', so that
**	now 'uset' only contains domains which are actually updated,
**	and 'rset' contains all domains which are retrieved.
**
**	Now that we know what is referenced, we can scan the protection
**	catalog.  We scan the entire catalog once for each variable
**	mentioned in the query (except as already taken care of as
**	described above).
**
**	We must create a set of all operations on this variable which
**	are not yet resolved, that is, for which no PERMIT statements
**	which qualify have been issued.  We store this set in the
**	variable "noperm".  As PERMIT statements are found, bits will
**	be cleared.  If the variable is not zero by the end of the
**	scan of the protection catalog, then we reject the query,
**	saying that we don't have permission -- giving us default
**	to deny.
**
**	For each tuple in the protection catalog for this relation,
**	we call "proappl" to see if it applies to this query.  This
**	routine checks the user, terminal, time of day, and so forth
**	(in fact, everything which is query-independent) and tells
**	whether this tuple might apply.
**
**	If the tuple passes this initial check, we then do the query-
**	dependent checking.  This amounts to calling "prochk" once
**	for each operation (and domain set) in the query.  What we
**	get back is a set of operations which this tuple applies to.
**	If zero, the tuple doesn't apply at all; otherwise, it
**	applies to at least one operation.  If it applies to some-
**	thing, we call it "interesting".
**
**	For "interesting" tuples, we now get the corresponding
**	qualification (if one exists), and disjoin it to a set of
**	protection constraints held in "pqual".  Also, we mark
**	any operations we found as having been done, by clearing
**	bits in "noperm".
**
**	When we have completed scanning the protection catalog,
**	we check "noperm".  If it is non-zero, then we have some
**	operation for which a PERMIT statement has not been issued,
**	and we issue an error message.  If this variable is ok,
**	then we go on and try the next variable.
**
**	When all variables have been accounted for, we check to
**	see if we have any qualifications collected from the
**	protection algorithm.  If so, we conjoin them to the
**	query tree.
**
**	Finally, we return the root of the modified tree.  This
**	tree is guaranteed to have no authorization violations,
**	and may be run as a regular query.
**
**	Parameters:
**		root -- the root of the tree.
**
**	Returns:
**		The root of the modified and authorized tree.
**
**	Side Effects:
**		A possible non-local return on access violation.
**
**	Trace Flags:
**		50 - 59
*/

int Proopmap[MAXPROQM + 1] =
{
	PRO_RETR,		/* 0 -- mdRETTERM */
	PRO_RETR,		/* 1 -- mdRETR */
	PRO_APP,		/* 2 -- mdAPP */
	PRO_REPL,		/* 3 -- mdREPL */
	PRO_DEL,		/* 4 -- mdDEL */
};

extern QTREE	Prodes;
extern char	Terminal[];
extern QTREE	*gettree();


QTREE *
protect(root)
QTREE	*root;
{
	register QTREE	*r;
	register int	i;
	register int	vn;
	register DESC	*d;
	int		qmode;
	int		varset;

	r = root;

#	ifdef xQTR1
	tTfp(50, -1, "\n->PROTECT\n\n");
#	endif

	varset = 0;

	/*
	**  Scan the range table and create a set of all variables
	**  which are 'interesting', that is, on which the protectin
	**  algorithm should be performed.
	*/

	for (vn = 0; vn < MAXVAR + 1; vn++)
	{
		if (!Qt.qt_rangev[vn].rngvmark)
			continue;
		d = Qt.qt_rangev[vn].rngvdesc;
		if (d == NULL)
			syserr("null desc vn=%d", vn);

		/* if owner, accept any query */
		if (bequal(d->reldum.relowner, Usercode, UCODE_SZ))
			continue;

		/* check for "no restriction" bit asserted (= clear) */
		if (!bitset(S_PROTALL, d->reldum.relstat))
			continue;
		if (!bitset(S_PROTRET, d->reldum.relstat) &&
		    (Qt.qt_qmode == mdRETR || Qt.qt_qmode == mdRET_UNI))
			continue;

		varset |= 1 << vn;
	}

	/*
	**  For each variable specified in varset (that is, for each
	**  variable in the initial query), do the real algorithm.
	*/

	for (vn = 0; vn < MAXVAR + 1; vn++)
	{
		if ((varset & (1 << vn)) == 0)
			continue;
		d = Qt.qt_rangev[vn].rngvdesc;

#		ifdef xQTR1
		if (tTf(50, 1))
			printf("\nvn=%d: %.12s\n", vn, d->reldum.relid);
#		endif

		/*
		**  Determine the query mode for this variable.  This
		**  is not the query mode of the original query,
		**  unless the variable is the result variable.
		*/

		qmode = Qt.qt_qmode;
		if (vn != Qt.qt_resvar || qmode == mdRET_UNI)
			qmode = mdRETTERM;

#		ifdef xQTR3
		if (qmode == 1 || qmode > 4 || qmode < 0)
			syserr("protect: bad qmode %d", qmode);
#		endif

		/* do the interesting part of the algorithm */
		dopro(vn, r, qmode, NULL);
	}

	/* return the (authorized) tree */
#	ifdef xQTR1
	if (tTf(50, 15))
		treepr(r, "PROTECT->");
#	endif
	return (r);
}
/*
**  DOPRO -- actually do the protection algorithm
**
**	This is the guts of it, broken off because it must be called
**	recursively on aggregates.  The algorithm is as discussed
**	in the module header.
**
**	Parameters:
**		varno -- the variable number of interest.
**		root -- the root of the tree to modify.
**		qmode -- the effective query mode for this relation.
**		byset -- if non-NULL, a set of domains passed back
**			which gets bound out of the aggregate func,
**			in other words, the by list.
**
**	Returns:
**		none
**
**	Side Effects:
**		The tree pointed at by 'root' gets modified.
**		Quite possibly 'Qt.qt_rangev' and 'Qt.qt_remap' get clobbered.
**
**	Called By:
**		protect
**		makedset -- on aggregates and aggregate functions.
**
**	Trace Flags:
**		51
*/

dopro(varno, root, qmode, byset)
int	varno;
QTREE	*root;
int	qmode;
int	byset[8];
{
	int		qset[8];
	int		uset[8];
	int		aset[8];
	int		rset[8];
	int		zeros[8];
	QTREE		*p;
	QTREE		*pqual;
	register int	i;
	register int	vn;
	register QTREE	*t;
	int		noperm;
	int		noqual;
	struct protect	prokey, protup;
	struct tup_id	lotid, hitid;
	struct qvect
	{
		QTREE	*q_qual;
		int	q_mode;
	};
	struct qvect	quals[4];
	int		j;
	extern QTREE	*norml();
	extern QTREE	*tree();
	extern QTREE	*trimqlend();


	t = root;
	vn = varno;

	/* create domain usage sets */
	for (i = 0; i < 8; i++)
	{
		zeros[i] = uset[i] = rset[i] = qset[i] = aset[i] = 0;
		if (byset != NULL)
			byset[i] = 0;
	}

	/*
	**  Create domain usage set for target list side.  There are
	**  two general cases: this is the root of the tree, or this
	**  is the head of an aggregate.
	*/

	switch (t->sym.type)
	{
	  case AGHEAD:
		/*
		**  An aggregate head falls into two classes: simple
		**  aggregate and aggregate function.  In an aggregate
		**  function, care must be taken to bind the variables
		**  in the by-list outside of the aggregate.  We use
		**  'rset' as a temporary here.
		*/

		if (t->left->sym.type == BYHEAD)
		{
			/* make by-list set */
			makedset(vn, t->left->left, NULL, rset, aset, qset);

			/* merge by-list set into qualification set */
			for (i = 0; i < 8; i++)
			{
				if (byset != NULL)
					byset[i] |= rset[i];
				qset[i] |= rset[i];
				rset[i] = 0;
			}

			/* make aggregate list set */
			makedset(vn, t->left->right->right, NULL, rset, aset, qset);
		}
		else
		{
			/* simple aggregate */
#			ifdef xQTR3
			if (t->left->sym.type != AOP)
				syserr("dopro: AGHEAD->left %d", t->left->sym.type);
#			endif
			
			/* check for qualification */
			if (t->right->sym.type == QLEND)
			{
				/* simple, unqualified aggregate */
				makedset(vn, t->left->right, NULL, aset, aset, qset);
			}
			else
			{
#				ifdef xQTR3
				if (t->right->sym.type != AND)
					syserr("dopro: AND=%d", t->right->sym.type);
#				endif
				makedset(vn, t->left->right, NULL, rset, aset, qset);
			}
		}
		break;
	
	  case ROOT:
		makedset(vn, t->left, uset, rset, aset, qset);
		break;
	}

	/* scan qualification */
	makedset(vn, t->right, NULL, qset, aset, qset);

	/* if retrieval, drop the 'update' set */
	/* if delete or append, force an apparent update */
	switch (qmode)
	{
	  case mdRETTERM:
		for (i = 0; i < 8; i++)
			uset[i] = 0;
		break;

	  case mdDEL:
	  case mdAPP:
		for (i = 0; i < 8; i++)
			uset[i] = -1;
		break;
	}

#	ifdef xQTR1
	if (tTf(51, 2))
	{
		printf("qmode %d\n", qmode);
		pr_set(uset, "uset");
		pr_set(rset, "rset");
		pr_set(aset, "aset");
		pr_set(qset, "qset");
	}
#	endif

	/* create a bit map of all referenced operations */
	noperm = 0;
	if (!bequal(uset, zeros, sizeof zeros))
		noperm |= Proopmap[qmode];
	if (!bequal(rset, zeros, sizeof zeros))
		noperm |= PRO_RETR;
	if (!bequal(aset, zeros, sizeof zeros))
		noperm |= PRO_AGGR;
	if (!bequal(qset, zeros, sizeof zeros))
		noperm |= PRO_TEST;

	/* if no operation, something is wrong */
	/* not nessasarily, consider a var that only occurs in an aggregate */
/*	if (noperm == 0)
		syserr("protect: no oper");	*/
	
	/* initialize qualification portion */
	for (i = 0; i < 4; )
		quals[i++].q_qual = NULL;
	noqual = FALSE;

	/* check the protection catalog */
	opencatalog("protect", OR_READ);
	setkey(&Prodes, &prokey, Qt.qt_rangev[vn].rngvdesc->reldum.relid, PRORELID);
	setkey(&Prodes, &prokey, Qt.qt_rangev[vn].rngvdesc->reldum.relowner, PRORELOWN);
	find(&Prodes, EXACTKEY, &lotid, &hitid, &prokey);

	while ((i = get(&Prodes, &lotid, &hitid, &protup, TRUE)) == 0)
	{
		if (kcompare(&Prodes, &prokey, &protup) != 0)
			continue;

#		ifdef xQTR2
		if (tTf(51, 4))
		{
			printf("PROTECT: ");
			printup(&Prodes, &protup);
		}
#		endif

		/* check if this is the correct user, terminal, etc */
		if (!proappl(&protup))
			continue;
		
		/* alright, let's check the operation */
		i = 0;
		if (qmode != mdRETTERM)
			i = quals[0].q_mode = prochk(Proopmap[qmode], uset, &protup);
		i |= quals[1].q_mode = prochk(PRO_RETR, rset, &protup);
		i |= quals[2].q_mode = prochk(PRO_AGGR, aset, &protup);
		i |= quals[3].q_mode = prochk(PRO_TEST, qset, &protup);

#		ifdef xQTR2
		if (tTf(51, 5))
			printf("Satisfies operations %o\n", i);
#		endif

		/* see if this tuple is "interesting" */
		if (i == 0)
			continue;
		
		/* it is!  get the qualification (if any) */
		if (protup.protree >= 0)
		{
			p = gettree(Qt.qt_rangev[vn].rngvdesc->reldum.relid,
				    Qt.qt_rangev[vn].rngvdesc->reldum.relowner,
				    mdPROT, protup.protree, FALSE);
#			ifdef xQTR2
			if (tTf(51, 6))
				treepr(p, "Protection Clause");
#			endif
			p = trimqlend(p->right);
#			ifdef xQTR3
			/* check for a non-null qualification */
			if (p == NULL)
				syserr("protect: null tree");
#			endif

			/* translate to the interesting variable */
			j = protup.proresvar;
			if (Qt.qt_remap[j] >= 0)
				j = Qt.qt_remap[j];
			mergevar(j, varno, p);

			/* disjoin the protection qual to real qual */
			for (j = 0; j < 4; j++)
			{
				if (quals[j].q_mode == 0)
					continue;
				if (quals[j].q_qual == NULL)
					quals[j].q_qual = p;
				else
					quals[j].q_qual = tree(quals[j].q_qual, p, OR, 0);
			}
		}
		else
			noqual = TRUE;

		/* mark this operation as having been handled */
		noperm &= ~i;
	}

	/* test 'get' return code */
	if (i < 0)
		syserr("protect: get");

#	ifdef xQTR1
	if (tTf(51, 12))
		printf("No perm on %o\n", noperm);
#	endif

	/* see if no tuples applied for some operation */
	if (noperm != 0)
		qmerror(PVIOL, Qt.qt_qmode, vn, 0);
	
	/* see if we want to modify the query at all */
	if (!noqual)
	{
		/* conjoin the qualification */
		pqual = NULL;
		for (i = 0; i < 4; i++)
			if (quals[i].q_qual != NULL)
				if (pqual == NULL)
					pqual = quals[i].q_qual;
				else
					pqual = tree(pqual, quals[i].q_qual, AND, 0);
		pqual = tree(pqual, tree(NULL, NULL, QLEND, 0), AND, 0);
		appqual(pqual, t);

		/* normalize the tree */
		t->right = norml(trimqlend(t->right));
	}
}
/*
**  MAKEDSET -- make domain reference sets
**
**	This routine creates some sets which reflect the usage of
**	domains for a particular variable.
**
**	The interesting nodes are 'case' labels in the large
**	switch statement which comprises most of the code.  To
**	describe briefly:
**
**	VAR nodes are easy: if they are for the current variable,
**		set the bit corresponding to the domain in the
**		'retrieval' set.  They can have no descendents,
**		so just return.
**	RESDOM nodes are also easy: they can be handled the same,
**		but the bit is set in the 'update' set instead.
**	AGHEAD nodes signal the beginning of an aggregate or
**		aggregate function.  In this case, we scan the
**		qualification first (noting that RESDOM and VAR
**		nodes are processed as 'qualification' sets
**		instead of 'retrieval' or 'update' sets).  Then,
**		if the aggregate has a WHERE clause or a BY list,
**		we treat it as a retrieve; otherwise, we call our-
**		selves recursively treating VAR nodes as 'aggregate'
**		types rather than 'retrieve' types.
**	BYHEAD nodes signal the beginning of a BY list.  The left
**		subtree (the actual BY-list) is processed with
**		RESDOM nodes ignored (since they are pseudo-domains
**		anyhow) and VAR nodes mapped into the 'qualification'
**		set.  Then we check the right subtree (which better
**		begin with an AOP node!) and continue processing.
**	AOP nodes must have a null left subtree, so we just drop
**		to the right subtree and iterate.  Notice that we
**		do NOT map VAR nodes into the 'aggregate' set for
**		this node, since this has already been done by the
**		AGHEAD node; also, this aggregate might be counted
**		as a retrieve operation instead of an aggregate
**		operation (as far as the protection system is con-
**		cerned) -- this has been handled by the AGHEAD
**		node.
**	All other nodes are processed recursively along both edges.
**
**	Parameters:
**		vn -- the variable number that we are currently
**			interested in.
**		tree -- the root of the tree to scan.  Notice that this
**			will in general be only one half of the tree --
**			makedset will be called once for the target
**			list and once for the qualification, with
**			different sets for the following parameters.
**		uset -- adjusted to be the set of all domains
**			updated.
**		rset -- adjusted to be the set of all domains
**			retrieved implicitly, that is, on the right-
**			hand-side of an assignment operator.
**		aset -- adjusted to be the set of all domains
**			aggregated.  Notice that this set is not
**			adjusted explicitly, but rather is passed
**			to recursive incarnations of this routine
**			as 'rset'.
**		qset -- adjusted to be the set of domains retrieved
**			implicitly in a qualification.  Like 'aset',
**			this is passed as 'rset' to recursive
**			incarnations.
**
**	Returns:
**		none
**
**	Side Effects:
**		none
**
**	Called By:
**		protect() -- in two places.
**
**	Trace Flags:
**		53
**
*/

makedset(vn, tree, uset, rset, aset, qset)
int	vn;
QTREE	*tree;
int	uset[8];
int	rset[8];
int	aset[8];
int	qset[8];
{
	register QTREE	*t;
	register int	i;
	int		byset[8];

	t = tree;

#	ifdef xQTR1
	if (tTf(53, 0))
	{
		printf("->makedset\n");
		pr_set(uset, "uset");
		pr_set(rset, "rset");
		pr_set(aset, "aset");
		pr_set(qset, "qset");
	}
#	endif

	while (t != NULL)
	{
		switch (t->sym.type)
		{
		  case VAR:
			if (t->sym.value.sym_var.varno == vn)
				lsetbit(t->sym.value.sym_var.attno, rset);
			break;

		  case AGHEAD:
			/* do protection on qualification */
			dopro(vn, t, -1, byset);

			/* merge by-list set into qualification set */
			for (i = 0; i < 8; i++)
				qset[i] |= byset[i];

			break;

		  case BYHEAD:
		  case AOP:
			syserr("makedset: node %d", t->sym.type);

		  case RESDOM:
			if (t->sym.value.sym_resdom.resno == 0)
			{
				/* tid -- ignore right subtree (and this node) */
				t = t->left;
				continue;
			}
			if (uset != NULL)
				lsetbit(t->sym.value.sym_resdom.resno, uset);
			/* explicit fall-through to "default" case */

		  default:
			/* handle left subtree (recursively) */
			makedset(vn, t->left, uset, rset, aset, qset);

			/* handle right subtree (iteratively) */
			t = t->right;
			continue;
		}
		break;
	}

#	ifdef xQTR1
	if (tTf(53, 15))
	{
		printf("makedset->\n");
		pr_set(uset, "uset");
		pr_set(rset, "rset");
		pr_set(aset, "aset");
		pr_set(qset, "qset");
	}
#	endif

	return;
}
/*
**  PROAPPL -- check for protection tuple applicable
**
**	A given protection catalog tuple is checked in a query-
**	independent way for applicability.
**
**	This routine checks such environmental constraints as the
**	user, the terminal, and the time of day.  The code is
**	fairly straightforward, just take a look.
**
**	One note: the user and terminal codes contained in the
**	protection catalog are blank to mean 'any value' of the
**	corresponding field.
**
**	Parameters:
**		protup -- the protection tuple to compare against.
**
**	Returns:
**		TRUE -- this tuple applies to the current environment.
**		FALSE -- this tuple does not apply.
**
**	Side Effects:
**		none (unless you include trashing the static vector
**			returned by localtime).
**
**	Called By:
**		protect()
**
**	Trace Flags:
**		54
*/

proappl(protup)
struct protect	*protup;
{
	register struct protect	*p;
	int			tvect[2];
	register int		*tt;
	extern int		*localtime();
	register int		mtime;

	p = protup;

	/* check for correct user [insert clique code here] */
	if (!bequal("  ", p->prouser, 2))
	{
		if (!bequal(p->prouser, Usercode, UCODE_SZ))
		{
# ifdef xQTR2
			if (tTf(54, 0))
				printf("  ~user\n");
# endif
			return (FALSE);
		}
	}

	/* check for correct terminal */
	if (p->proterm[0] != ' ')
	{
		if (!sequal(p->proterm, Terminal))
		{
# ifdef xQTR2
			if (tTf(54, 0))
				printf("  ~term\n");
# endif
			return (FALSE);
		}
	}

	/* check for correct time of day & week */
	time(tvect);
	tt = localtime(tvect);
	mtime = tt[2] * 60 + tt[1];

	if (p->protodbgn > mtime || p->protodend < mtime)
	{
# ifdef xQTR2
		if (tTf(54, 0))
			printf("  ~tod\n");
# endif
		return (FALSE);
	}
	if (p->prodowbgn > tt[6] || p->prodowend < tt[6])
	{
# ifdef xQTR2
		if (tTf(54, 0))
			printf("  ~dow\n");
# endif
		return (FALSE);
	}

	/* hasn't failed yet -- I guess it's ok */
	return (TRUE);
}
/*
**  PROCHK -- query-dependent protection tuple check
**
**	This routine does the query-dependent part of checking
**	the validity of a protection tuple.  Unlike proappl,
**	which looked at aspects of the environment but not the
**	query being run, this routine assumes that the environ-
**	ment is ok, and checks that if it applies to this tuple.
**
**	Two things are checked.  The first is if this tuple applies
**	to the operation in question (passed as 'inbit').  The
**	second is if the set of domains in the tuple contains the
**	set of domains in the query.  If either of these fail,
**	the return is zero.  Otherwise the return is the operation
**	bit.  In otherwise, the return is the operation to which
**	this tuple applies (if any).
**
**	As a special check, the domain set is checked for all
**	zero.  If so, no domains have been referenced for this
**	operation at all, and we return zero.  In other words, this
**	tuple might apply to this operation, but since we don't
**	use the operation anyhow we will ignore it.  It is important
**	to handle things in this way so that the qualification for
**	this tuple doesn't get appended if the variable is not
**	used in a particular context.
**
**	Parameters:
**		inbit -- the bit describing the operation to be
**			checked.  Note that only one bit should
**			be set in this word, although this is
**			not checked.
**		domset -- the set of domains actually referenced
**			in this query for the operation described
**			by 'inbit'.
**		protup -- the tuple in question.
**
**	Returns:
**		The operation (if any) to which this tuple applies.
**
**	Side Effects:
**		none
**
**	Called By:
**		protect() -- in four places.
**
**	Trace Flags:
**		55
*/

prochk(inbit, domset, protup)
int		inbit;
int		domset[8];
struct protect	*protup;
{
	register struct protect	*p;
	register int		*d;
	register int		i;

	p = protup;
	d = domset;

#	ifdef xQTR1
	if (tTf(55, 0))
	{
		printf("->prochk, inbit=%o, proopset=%o\n", inbit, p->proopset);
		pr_set(d, "domset");
		pr_set(p->prodomset, "prodomset");
	}
#	endif

	/* check for null domain set, if so return zero */
	for (i = 0; i < 8; i++)
		if (d[i] != 0)
			break;
	if (i >= 8)
	{
#		ifdef xQTR2
		tTfp(55, 15, "prochk-> null set\n");
#		endif
		return (0);
	}

	/* see if this tuple applies to this operation */
	if ((inbit & p->proopset) == 0)
	{
#		ifdef xQTR2
		tTfp(55, 15, "prochk-> no op\n");
#		endif
		return (0);
	}

	/* check if domains are a subset */
	for (i = 0; i < 8; i++)
	{
		if ((d[i] & ~p->prodomset[i]) != 0)
		{
			/* failure */
#			ifdef xQTR2
			tTfp(55, 15, "prochk-> not subset\n");
#			endif
			return (0);
		}
	}

	/* this is hereby an "interesting" tuple */
#	ifdef xQTR2
	if (tTf(55, 15))
		printf("prochk-> %d\n", inbit);
#	endif
	return (inbit);
}

# ifdef xQTR1

/*
**  PR_SET -- print set for debugging
**
**	This routine prints a 128-bit set for debugging.
**
**	Parameters:
**		xset -- the set to convert.
**		labl -- a label to print before the set.
**
**	Returns:
**		a pointer to the converted string.
**
**	Side Effects:
**		none
*/

pr_set(xset, labl)
short	xset[8];
char	*labl;
{
	register short	*x;
	register int	i;
	register long	*y;

	printf("\t%s: ", labl);
	x = xset;
	y = (long *) x;
	if (x == NULL)
	{
		printf("<NULL>\n");
		return;
	}
	for (i = 7; i >= 0; i--)
		printf("%x/", x[i]);
	printf(" <> ");
	for (i = 0; i < 4; i++)
		printf("/%ld", y[i]);
	printf("\n");
}

# endif
