/* * 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. */ /* 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); }