view usr/src/cmd/lp/lib/filters/insfilter.c @ 4:1a15d5aaf794

synchronized with onnv_86 (6202) in onnv-gate
author Koji Uno <koji.uno@sun.com>
date Mon, 31 Aug 2009 14:38:03 +0900
parents c9caec207d52
children
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/


/*
 * Copyright (c) 1998-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"
/* EMACS_MODES: !fill, lnumb, !overwrite, !nodelete, !picture */

#include "assert.h"
#include "string.h"
#include "errno.h"
#include "stdlib.h"

#include "lp.h"
#include "filters.h"

#include "regex.h"


#define	MATCH(PT, PM) (STREQU((PT)->pattern, PATT_STAR) || \
					match((PT)->re, *((PM)->pvalue)))


typedef struct PARM {
	char			*keyword;
	unsigned short		flags;
	char			**pvalue;
}			PARM;

#define	X_MUST	0x0800	/* Pipeline MUST use this parm */
#define	X_FIRST	0x1000	/* Use parm only in 1st cmd of pipeline */
#define	X_FIXED	0x2000	/* Get value from elsewhere, not parms */
#define	X_MANY	0x4000	/* Several values allowed for parm */
#define	X_USED	0x8000	/* Used already, don't use again */

static struct S {
	TYPE			input_type;
	TYPE			output_type;
	TYPE			printer_type;
	char			*printer;
	PARM			*parms;
} S;

#if	defined(__STDC__)

static int		searchlist_t(TYPE *, TYPE *);
static int		instantiate(_FILTER **, TYPE *, TYPE *,
							int (*)(), void *);
static int		check_pipeline(_FILTER *, PARM *);
static char		*build_pipe(_FILTER *, PARM *, unsigned short *);
#else

static int		searchlist_t();
static int		instantiate();
static int		check_pipeline();
static char		*build_pipe();

#endif

/*
 * Table of recognized keywords, with info. about them.
 */

#define	NFIXED 4

static PARM		parmtable[] = {

/* These must be the first NFIXED, and in this order */
PARM_INPUT,	X_FIXED,			&S.input_type.name,
PARM_OUTPUT,	X_FIXED,			&S.output_type.name,
PARM_TERM,	X_FIXED,			&S.printer_type.name,
PARM_PRINTER,	X_FIXED,			&S.printer,

PARM_CPI,	FPARM_CPI,			0,
PARM_LPI,	FPARM_LPI,			0,
PARM_LENGTH,	FPARM_LENGTH,			0,
PARM_WIDTH,	FPARM_WIDTH,			0,
PARM_PAGES,	FPARM_PAGES | X_FIRST | X_MUST,	0,
PARM_CHARSET,	FPARM_CHARSET,			0,
PARM_FORM,	FPARM_FORM,			0,
PARM_COPIES,	FPARM_COPIES | X_FIRST,		0,
PARM_MODES,	FPARM_MODES | X_MANY | X_MUST,	0,
0,		0,				0,
};

/*
 * insfilter()
 */

FILTERTYPE
#if	defined(__STDC__)
insfilter(
	char			**pipes,
	char			*input_type,
	char			*output_type,
	char			*printer_type,
	char			*printer,
	char			**parms,
	unsigned short		*flagsp
)
#else
insfilter(pipes, input_type, output_type, printer_type, printer, parms, flagsp)
	char			**pipes,
				*input_type,
				*output_type,
				*printer_type,
				*printer,
				**parms;
	unsigned short		*flagsp;
#endif
{
	_FILTER			*pipeline;

	FILTERTYPE		ret;


	S.input_type.name = input_type;
	S.input_type.info = isterminfo(input_type);
	S.output_type.name = output_type;
	S.output_type.info = isterminfo(output_type);
	S.printer_type.name = printer_type;
	S.printer_type.info = isterminfo(printer_type);
	S.printer = printer;

	/*
	 * If the filters have't been loaded yet, do so now.
	 * We'll load the standard table, but the caller can override
	 * this by first calling "loadfilters()" with the appropriate
	 * filter table name.
	 */
	if (!filters && loadfilters((char *)0) == -1)
		return (fl_none);

	/*
	 * Allocate and initialize space to hold additional
	 * information about each item in "parms".
	 * THIS SPACE MUST BE FREED BEFORE EXITING THIS ROUTINE!
	 */
	{
		register int		n;

		register PARM *		pp;
		register PARM *		ppt;

		register char **	p;



		for (n = 0, p = parms; *p; n++, p++)
			;
		n /= 2;
		n += NFIXED; /* for fixed parms (in/out/printer types) */

		if (!(S.parms = (PARM *)Malloc((n + 1) * sizeof (PARM)))) {
			errno = ENOMEM;
			return (fl_none);
		}

		for (ppt = parmtable; ppt->keyword; ppt++)
			ppt->flags &= ~X_USED;

		/*
		 * Load the parameter list with the fixed ``type''
		 * parameters. Mark them as used (if appropriate)
		 * so we don't pick them up from the callers list.
		 */
		pp = S.parms;
		for (ppt = parmtable; ppt < parmtable + NFIXED; ppt++) {
			pp->keyword = ppt->keyword;
			pp->flags = ppt->flags;
			if (ppt->flags & X_FIXED)
				pp->pvalue = ppt->pvalue;
			else
				pp->pvalue = parms + 1;
			if (!(ppt->flags & X_MANY))
				ppt->flags |= X_USED;
			pp++;
		}

		/*
		 * Copy each parameter from the caller supplied list
		 * to another list, adding information gathered from
		 * the keyword table. Note that some keywords should
		 * be given only once; additional occurrances in the
		 * caller's list will be ignored.
		 */
		for (p = parms; *p; p += 2)
			for (ppt = parmtable; ppt->keyword; ppt++)
				if (STREQU(*p, ppt->keyword) &&
						!(ppt->flags & X_USED)) {

					pp->keyword = ppt->keyword;
					pp->flags = ppt->flags;
					if (ppt->flags & X_FIXED)
						pp->pvalue = ppt->pvalue;
					else
						pp->pvalue = p + 1;

					if (!(ppt->flags & X_MANY))
						ppt->flags |= X_USED;

					pp++;
					break;

				}

		pp->keyword = 0;

	}

	/*
	 * Preview the list of filters, to rule out those that
	 * can't possibly work.
	 */
	{
		register _FILTER *	pf;

		for (pf = filters; pf->name; pf++) {

			pf->mark = FL_CLEAR;

			if (printer && !searchlist(printer, pf->printers))
				pf->mark = FL_SKIP;

			else if (printer_type &&
					!searchlist_t(&(S.printer_type),
							pf->printer_types))
				pf->mark = FL_SKIP;

		}
	}

	/*
	 * Find a pipeline that will convert the input-type to the
	 * output-type and map the parameters as well.
	 */
	if (!instantiate(&pipeline, &S.input_type, &S.output_type,
			check_pipeline, S.parms)) {
		ret = fl_none;
		goto Return;
	}

	if (!pipes) {
		ret = fl_both;
		goto Return;

	} else {
		register _FILTER *	pf;
		register _FILTER *	pfastf; /* first in fast pipe */
		register _FILTER *	pslowf; /* last in slow pipe */

		/*
		 * Found a pipeline, so now build it.
		 */

		/*
		 * Split pipeline after last slow filter.
		 * "pipeline" will point to first filter in slow
		 * pipe, "pfastf" will point to first filter in
		 * fast pipe.
		 */
		for (pf = pfastf = pipeline, pslowf = 0; pf; pf = pf->next)
			if (pf->type == fl_slow) {
				pslowf = pf;
				pfastf = pf->next;
			}

		if (pslowf) {
			assert(pslowf != pfastf);
			pslowf->next = 0;
			pipes[0] = build_pipe(pipeline, S.parms, flagsp);
			ret = fl_slow;
		} else
			pipes[0] = 0;

		if (pfastf) {
			pipes[1] = build_pipe(pfastf, S.parms, flagsp);
			ret = fl_fast;
		} else
			pipes[1] = 0;

		if (pslowf && pfastf)
			ret = fl_both;

		/*
		 * Check for the oops case.
		 */
		if (pslowf && !pipes[0] || pfastf && !pipes[1])
			ret = fl_none;

	}

Return:	Free((char *)S.parms);

	return (ret);
}

/*
 * searchlist_t() - SEARCH (TYPE *) LIST FOR ITEM
 */

static int
#if	defined(__STDC__)
typematch(
	TYPE			*type1,
	TYPE			*type2
)
#else
typematch(type1, type2)
	TYPE			*type1, *type2;
#endif
{
	if (STREQU(type1->name, NAME_ANY) || STREQU(type2->name, NAME_ANY) ||
			STREQU(type1->name, type2->name) ||
			(STREQU(type1->name, NAME_TERMINFO) && type2->info) ||
			(STREQU(type2->name, NAME_TERMINFO) && type1->info))
		return (1);
	else
		return (0);
}

static int
#if	defined(__STDC__)
searchlist_t(
	TYPE			*itemp,
	TYPE			*list
)
#else
searchlist_t(itemp, list)
	TYPE			*itemp;
	register TYPE		*list;
#endif
{
	if (!list || !list->name)
		return (0);

	/*
	 * This is a linear search--we believe that the lists
	 * will be short.
	 */
	while (list->name) {
		if (typematch(itemp, list))
			return (1);
		list++;
	}
	return (0);
}

/*
 * instantiate() - CREATE FILTER-PIPELINE KNOWING INPUT/OUTPUT TYPES
 */

/*
 *	The "instantiate()" routine is the meat of the "insfilter()"
 *	algorithm. It is given an input-type and output-type and finds a
 *	filter-pipline that will convert the input-type into the
 *	output-type. Since the filter-pipeline must meet other criteria,
 *	a function "verify" is also given, along with the set of criteria;
 *	these are used by "instantiate()" to verify a filter-pipeline.
 *
 *	The filter-pipeline is built up and returned in "pipeline".
 *	Conceptually this is just a list of filters, with the pipeline to
 *	be constructed by simply concatenating the filter simple-commmands
 *	(after filling in option templates) in the order found in the
 *	list. What is used in the routine, though, is a pair of linked
 *	lists, one list forming the ``right-half'' of the pipeline, the
 *	other forming the ``left-half''. The pipeline is then the two
 *	lists taken together.
 *
 *	The "instantiate()" routine looks for a single filter that matches
 *	the input-type and output-type and satisfies the criteria. If one
 *	is found, it is added to the end of the ``left-half'' list (it
 *	could be added to the beginning of the ``right-half'' list with no
 *	problem). The two lists are linked together to form one linked
 *	list, which is passed, along with the set of criteria, to the
 *	"verify" routine to check the filter-pipeline. If it passes the
 *	check, the work is done.
 *
 *	If a single filter is not found, "instantiate()" examines all
 *	pairs of filters where one in the pair can accept the input-type
 *	and the other can produce the output-type. For each of these, it
 *	calls itself again to find a filter that can join the pair
 *	together--one that accepts as input the output-type of the first
 *	in the pair, and produces as output the input-type of the second
 *	in the pair.  This joining filter may be a single filter or may
 *	be a filter-pipeline. "instantiate()" checks for the trivial case
 *	where the input-type is the output-type; with trivial cases it
 *	links the two lists without adding a filter and checks it with
 *	"verify".
 */

/*
 * instantiate()
 */

/*
 * A PIPELIST is what is passed to each recursive call to "instantiate()".
 * It contains a pointer to the end of the ``left-list'', a pointer to the
 * head of the ``right-list'', and a pointer to the head of the left-list.
 * The latter is passed to "verify". The end of the right-list (and thus
 * the end of the entire list when left and right are joined) is the
 * filter with a null ``next'' pointer.
 */
typedef struct PIPELIST {
	_FILTER *		lhead;
	_FILTER *		ltail;
	_FILTER *		rhead;
}			PIPELIST;

#if	defined(__STDC__)
static int		_instantiate(PIPELIST *, TYPE *, TYPE *,
					int (*)(_FILTER *, void *), void *);
#else
static int		_instantiate();
#endif

static int		peg;

static int
#if	defined(__STDC__)
instantiate(
	_FILTER			**pline,
	TYPE			*input,
	TYPE			*output,
	int			(*verify)(_FILTER *, void *),
	void			*criteria
)
#else
instantiate(pline, input, output, verify, criteria)
	_FILTER			**pline;
	TYPE			*input,
				*output;
	int			(*verify)();
	char			*criteria;
#endif
{
	PIPELIST		p;
	int			ret;

	peg = 0;
	p.lhead = p.ltail = p.rhead = 0;
	ret = _instantiate(&p, input, output, verify, criteria);
	*pline = p.lhead;
	return (ret);
}

#define	ENTER()		int our_tag; our_tag = ++peg;

#define	LEAVE(Y)	if (!Y) { \
				register _FILTER *f; \
				for (f = filters; f->name; f++) \
					CLEAR(f); \
				return (0); \
			} else \
				return (1)

#define	MARK(F, M)	(((F)->mark |= M), (F)->level = our_tag)

#define	CLEAR(F)	if ((F)->level == our_tag) \
				(F)->level = 0, (F)->mark = FL_CLEAR

#define	CHECK(F, M)	(((F)->mark & M) && (F)->level == our_tag)

#define	USED(F)		((F)->mark)

static int
#if	defined(__STDC__)
_instantiate(
	PIPELIST		*pp,
	TYPE			*inputp,
	TYPE			*outputp,
	int			(*verify)(_FILTER *, void *),
	void			*criteria
)
#else
_instantiate(pp, inputp, outputp, verify, criteria)
	PIPELIST		*pp;
	TYPE			*inputp,
				*outputp;
	int			(*verify)();
	char			*criteria;
#endif
{
	register _FILTER	*prev_lhead;
	register _FILTER	*prev_ltail;


	/*
	 * Must be first ``statement'' after declarations.
	 */
	ENTER();

	/*
	 * We're done when we've added filters on the left and right
	 * that let us connect the left and right directly; i.e. when
	 * the output of the left is the same type as the input of the
	 * right. HOWEVER, there must be at least one filter involved,
	 * to allow the filter feature to be used for handling modes,
	 * pages, copies, etc. not just FILTERING data.
	 */
	if (typematch(inputp, outputp) && pp->lhead) {

		/*
		 * Getting here means that we must have a left and right
		 * pipeline. Why? For "pp->lhead" to be non-zero it
		 * must have been set below. The first place below
		 * doesn't set the right pipeline, but it also doesn't
		 * get us here (at least not directly). The only
		 * place we can get to here again is the second place
		 * "pp->phead" is set, and THAT sets the right pipeline.
		 */
		pp->ltail->next = pp->rhead;
		if ((*verify)(pp->lhead, criteria))
			LEAVE(1);
		else
			LEAVE(0);

	}

	/*
	 * Each time we search the list of filters, we examine
	 * them in the order given and stop searching when a filter
	 * that meets the needs is found. If the list is ordered with
	 * fast filters before slow filters, then fast filters will
	 * be chosen over otherwise-equal filters.
	 */

	/*
	 * See if there's a single filter that will work.
	 * Just in case we can't find one, mark those that
	 * will work as left- or right-filters, to save time
	 * later.
	 *
	 * Also, record exactly *which* input/output
	 * type would be needed if the filter was used.
	 * This record will be complete (both input and output
	 * recorded) IF the single filter works. Otherwise,
	 * only the input, for the left possible filters,
	 * and the output, for the right possible filters,
	 * will be recorded. Thus, we'll have to record the
	 * missing types later.
	 */
	{
		register _FILTER *		pf;


		for (pf = filters; pf->name; pf++) {

			if (USED(pf))
				continue;

			if (searchlist_t(inputp, pf->input_types)) {
				MARK(pf, FL_LEFT);
				pf->inputp = inputp;
			}
			if (searchlist_t(outputp, pf->output_types)) {
				MARK(pf, FL_RIGHT);
				pf->outputp = outputp;
			}

			if (CHECK(pf, FL_LEFT) && CHECK(pf, FL_RIGHT)) {
				prev_lhead = pp->lhead;
				prev_ltail = pp->ltail;

				if (!pp->lhead)
					pp->lhead = pf;
				else
					pp->ltail->next = pf;
				(pp->ltail = pf)->next = pp->rhead;

				if ((*verify)(pp->lhead, criteria))
					LEAVE(1);

				if ((pp->ltail = prev_ltail))
					pp->ltail->next = 0;
				pp->lhead = prev_lhead;

			}

		}
	}

	/*
	 * Try all DISJOINT pairs of left- and right-filters; recursively
	 * call this function to find a filter that will connect
	 * them (it might be a ``null'' filter).
	 */
	{
		register _FILTER *	pfl;
		register _FILTER *	pfr;

		register TYPE *		llist;
		register TYPE *		rlist;


		for (pfl = filters; pfl->name; pfl++) {

			if (!CHECK(pfl, FL_LEFT))
				continue;

			for (pfr = filters; pfr->name; pfr++) {

				if (pfr == pfl || !CHECK(pfr, FL_RIGHT))
					continue;

				prev_lhead = pp->lhead;
				prev_ltail = pp->ltail;

				if (!pp->lhead)
					pp->lhead = pfl;
				else
					pp->ltail->next = pfl;
				(pp->ltail = pfl)->next = 0;

				pfr->next = pp->rhead;
				pp->rhead = pfr;

				/*
				 * Try all the possible output types of
				 * the left filter with all the possible
				 * input types of the right filter. If
				 * we find a combo. that works, record
				 * the output and input types for the
				 * respective filters.
				 */
				for (llist = pfl->output_types; llist->name;
								llist++)
					for (rlist = pfr->input_types;
							rlist->name; rlist++)
						if (_instantiate(pp, llist,
								rlist, verify,
								criteria)) {
							pfl->outputp = llist;
							pfr->inputp = rlist;
							LEAVE(1);
						}
				pp->rhead = pfr->next;
				if ((pp->ltail = prev_ltail))
					pp->ltail->next = 0;
				pp->lhead = prev_lhead;

			}

		}
	}

	LEAVE(0);
}

/*
 * check_pipeline() - CHECK THAT PIPELINE HANDLES MODES, PAGE-LIST
 */

static int
#if	defined(__STDC__)
check_pipeline(
	_FILTER			*pipeline,
	PARM			*parms
)
#else
check_pipeline(pipeline, parms)
	_FILTER			*pipeline;
	PARM			*parms;
#endif
{
	register PARM		*pm;

	register _FILTER	*pf;

	register TEMPLATE	*pt;

	register int		fail;


	for (fail = 0, pm = parms; !fail && pm->keyword; pm++) {

		if (!(pm->flags & X_MUST))
			continue;

		for (pf = pipeline; pf; pf = pf->next) {

			if (!(pt = pf->templates))
				continue;

			for (; pt->keyword; pt++)
				if (STREQU(pt->keyword, pm->keyword) &&
						pt->result && MATCH(pt, pm))
					goto Okay;

		}
		fail = 1;
		continue;

Okay:;

	}

	return (fail? 0 : 1);
}

/*
 * build_filter() - CONSTRUCT PIPELINE FROM LINKED LIST OF FILTERS
 */

#if	defined(__STDC__)
static size_t		build_simple_cmd(char **, _FILTER *, PARM *,
							unsigned short *);
#else
static size_t		build_simple_cmd();
#endif

static char *
#if	defined(__STDC__)
build_pipe(
	_FILTER			*pipeline,
	PARM			*parms,
	unsigned short		*fp
)
#else
build_pipe(pipeline, parms, fp)
	_FILTER			*pipeline;
	PARM			*parms;
	unsigned short		*fp;
#endif
{
	register _FILTER	*pf;

	register size_t		nchars;
	register size_t		n;

	char			*p;	/* NOT register */
	char			*ret;


	/*
	 * This is a two-pass routine. In the first pass we add
	 * up how much space is needed for the pipeline, in the second
	 * pass we allocate the space and construct the pipeline.
	 */

	for (nchars = 0, pf = pipeline; pf; pf = pf->next)
		if ((n = build_simple_cmd((char **)0, pf, parms, fp)) > 0)
			nchars += n + 1;   /* +1 for '|' or ending null */

	if (!(ret = p = Malloc(nchars))) {
		errno = ENOMEM;
		return (0);
	}

	for (pf = pipeline; pf; pf = pf->next, *p++ = (pf? '|' : 0))
		(void) build_simple_cmd(&p, pf, parms, fp);

	return (ret);
}

/*
 * build_simple_cmd()
 */

static size_t
#if	defined(__STDC__)
build_simple_cmd(
	char			**pp,
	_FILTER			*pf,
	PARM			*parms,
	unsigned short		*flagsp
)
#else
build_simple_cmd(pp, pf, parms, flagsp)
	char			**pp;
	_FILTER			*pf;
	PARM			*parms;
	unsigned short		*flagsp;
#endif
{
	register size_t		ncount;

	register TEMPLATE	*pt;

	register PARM		*pm;


	if (pf->command) {
		ncount = strlen(pf->command);
		if (pp) {
			strcpy (*pp, pf->command);
			*pp += ncount;
		}
	} else
		ncount = 0;

	if (!pf->templates)
		return (ncount);

	for (pm = parms; pm->keyword; pm++) {

		if ((pm->flags & X_USED) || !*(pm->pvalue))
			continue;

		for (pt = pf->templates; pt->keyword; pt++) {

			if (!STREQU(pt->keyword, pm->keyword) || !pt->result)
				continue;

			/*
			 * INPUT and OUTPUT are those for *this* filter,
			 * not for the entire pipeline.
			 */
			if (STREQU(pt->keyword, PARM_INPUT))
				pm->pvalue = &(pf->inputp->name);
			else if (STREQU(pt->keyword, PARM_OUTPUT))
				pm->pvalue = &(pf->outputp->name);

			if (MATCH(pt, pm)) {
				if (pp)
					*(*pp)++ = ' ';
				ncount++;

				ncount += replace(pp, pt->result,
						*(pm->pvalue), pt->nbra);

				/*
				 * Difficulty here due to the two pass
				 * nature of this code. The first pass
				 * just counts the number of bytes; if
				 * we mark the once-only parms as being
				 * used, then we don't pick them up the
				 * second time through. We could get
				 * difficult and mark them temporarily,
				 * but that's hard. So on the first pass
				 * we don't mark the flags. The only
				 * problem is an estimate too high.
				 */
				if (pp && pm->flags & X_FIRST)
					pm->flags |= X_USED;

				*flagsp |= pm->flags;

			}
		}
	}

	return (ncount);
}