File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / bird / conf / cf-lex.l
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Mar 17 19:50:23 2021 UTC (4 years, 1 month ago) by misho
Branches: bird, MAIN
CVS tags: v1_6_8p3, HEAD
bird 1.6.8

    1: /*
    2:  *	BIRD -- Configuration Lexer
    3:  *
    4:  *	(c) 1998--2000 Martin Mares <mj@ucw.cz>
    5:  *
    6:  *	Can be freely distributed and used under the terms of the GNU GPL.
    7:  */
    8: 
    9: /**
   10:  * DOC: Lexical analyzer
   11:  *
   12:  * The lexical analyzer used for configuration files and CLI commands
   13:  * is generated using the |flex| tool accompanied by a couple of
   14:  * functions maintaining the hash tables containing information about
   15:  * symbols and keywords.
   16:  *
   17:  * Each symbol is represented by a &symbol structure containing name
   18:  * of the symbol, its lexical scope, symbol class (%SYM_PROTO for a
   19:  * name of a protocol, %SYM_CONSTANT for a constant etc.) and class
   20:  * dependent data.  When an unknown symbol is encountered, it's
   21:  * automatically added to the symbol table with class %SYM_VOID.
   22:  *
   23:  * The keyword tables are generated from the grammar templates
   24:  * using the |gen_keywords.m4| script.
   25:  */
   26: 
   27: %{
   28: #undef REJECT     /* Avoid name clashes */
   29: 
   30: #include <errno.h>
   31: #include <stdlib.h>
   32: #include <stdarg.h>
   33: #include <stdint.h>
   34: #include <unistd.h>
   35: #include <libgen.h>
   36: #include <glob.h>
   37: #include <fcntl.h>
   38: #include <sys/stat.h>
   39: #include <sys/types.h>
   40: #include <sys/stat.h>
   41: 
   42: #define PARSER 1
   43: 
   44: #include "nest/bird.h"
   45: #include "nest/route.h"
   46: #include "nest/protocol.h"
   47: #include "filter/filter.h"
   48: #include "conf/conf.h"
   49: #include "conf/cf-parse.tab.h"
   50: #include "lib/string.h"
   51: #include "lib/hash.h"
   52: 
   53: struct keyword {
   54:   byte *name;
   55:   int value;
   56:   struct keyword *next;
   57: };
   58: 
   59: #include "conf/keywords.h"
   60: 
   61: /* Could be defined by Bison in cf-parse.tab.h, inteferes with SYM hash */
   62: #ifdef SYM
   63: #undef SYM
   64: #endif
   65: 
   66: 
   67: static uint cf_hash(byte *c);
   68: 
   69: #define KW_KEY(n)		n->name
   70: #define KW_NEXT(n)		n->next
   71: #define KW_EQ(a,b)		!strcmp(a,b)
   72: #define KW_FN(k)		cf_hash(k)
   73: #define KW_ORDER		8 /* Fixed */
   74: 
   75: #define SYM_KEY(n)		n->name, n->scope->active
   76: #define SYM_NEXT(n)		n->next
   77: #define SYM_EQ(a,s1,b,s2)	!strcmp(a,b) && s1 == s2
   78: #define SYM_FN(k,s)		cf_hash(k)
   79: #define SYM_ORDER		6 /* Initial */
   80: 
   81: #define SYM_REHASH		sym_rehash
   82: #define SYM_PARAMS		/8, *1, 2, 2, 6, 20
   83: 
   84: 
   85: HASH_DEFINE_REHASH_FN(SYM, struct symbol)
   86: 
   87: HASH(struct keyword) kw_hash;
   88: 
   89: 
   90: static struct sym_scope *conf_this_scope;
   91: 
   92: linpool *cfg_mem;
   93: 
   94: int (*cf_read_hook)(byte *buf, unsigned int max, int fd);
   95: struct include_file_stack *ifs;
   96: static struct include_file_stack *ifs_head;
   97: 
   98: #define MAX_INCLUDE_DEPTH 8
   99: 
  100: #define YY_INPUT(buf,result,max) result = cf_read_hook(buf, max, ifs->fd);
  101: #define YY_NO_UNPUT
  102: #define YY_FATAL_ERROR(msg) cf_error(msg)
  103: #define YY_USER_ACTION ifs->chno += yyleng; ifs->toklen = yyleng;
  104: 
  105: static void cf_include(char *arg, int alen);
  106: static int check_eof(void);
  107: 
  108: %}
  109: 
  110: %option noyywrap
  111: %option noinput
  112: %option nounput
  113: %option noreject
  114: 
  115: %x COMMENT CCOMM CLI
  116: 
  117: ALPHA [a-zA-Z_]
  118: DIGIT [0-9]
  119: XIGIT [0-9a-fA-F]
  120: ALNUM [a-zA-Z_0-9]
  121: WHITE [ \t]
  122: include   ^{WHITE}*include{WHITE}*\".*\"{WHITE}*;
  123: 
  124: %%
  125: {include} {
  126:   char *start, *end;
  127: 
  128:   if (!ifs->depth)
  129:     cf_error("Include not allowed in CLI");
  130: 
  131:   start = strchr(yytext, '"');
  132:   start++;
  133: 
  134:   end = strchr(start, '"');
  135:   *end = 0;
  136: 
  137:   if (start == end)
  138:     cf_error("Include with empty argument");
  139: 
  140:   cf_include(start, end-start);
  141: }
  142: 
  143: {DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+ {
  144:   ip4_addr a;
  145:   if (!ip4_pton(yytext, &a))
  146:     cf_error("Invalid IPv4 address %s", yytext);
  147: 
  148: #ifdef IPV6
  149:   cf_lval.i32 = ip4_to_u32(a);
  150:   return RTRID;
  151: #else
  152:   cf_lval.a = ipa_from_ip4(a);
  153:   return IPA;
  154: #endif
  155: }
  156: 
  157: ({XIGIT}*::|({XIGIT}*:){3,})({XIGIT}*|{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+) {
  158: #ifdef IPV6
  159:   if (ipa_pton(yytext, &cf_lval.a))
  160:     return IPA;
  161:   cf_error("Invalid IPv6 address %s", yytext);
  162: #else
  163:   cf_error("This is an IPv4 router, therefore IPv6 addresses are not supported");
  164: #endif
  165: }
  166: 
  167: 0x{XIGIT}+ {
  168:   char *e;
  169:   unsigned long int l;
  170:   errno = 0;
  171:   l = strtoul(yytext+2, &e, 16);
  172:   if (e && *e || errno == ERANGE || (unsigned long int)(unsigned int) l != l)
  173:     cf_error("Number out of range");
  174:   cf_lval.i = l;
  175:   return NUM;
  176: }
  177: 
  178: {DIGIT}+ {
  179:   char *e;
  180:   unsigned long int l;
  181:   errno = 0;
  182:   l = strtoul(yytext, &e, 10);
  183:   if (e && *e || errno == ERANGE || (unsigned long int)(unsigned int) l != l)
  184:     cf_error("Number out of range");
  185:   cf_lval.i = l;
  186:   return NUM;
  187: }
  188: 
  189: else: {
  190:   /* Hack to distinguish if..else from else: in case */
  191:   return ELSECOL;
  192: }
  193: 
  194: ({ALPHA}{ALNUM}*|[']({ALNUM}|[-]|[\.]|[:])*[']) {
  195:   if(*yytext == '\'') {
  196:     yytext[yyleng-1] = 0;
  197:     yytext++;
  198:   }
  199: 
  200:   struct keyword *k = HASH_FIND(kw_hash, KW, yytext);
  201:   if (k)
  202:   {
  203:     if (k->value > 0)
  204:       return k->value;
  205:     else
  206:     {
  207:       cf_lval.i = -k->value;
  208:       return ENUM;
  209:     }
  210:   }
  211: 
  212:   cf_lval.s = cf_get_symbol(yytext);
  213:   return SYM;
  214: }
  215: 
  216: <CLI>(.|\n) {
  217:   BEGIN(INITIAL);
  218:   return CLI_MARKER;
  219: }
  220: 
  221: \.\. {
  222:   return DDOT;
  223: }
  224: 
  225: [={}:;,.()+*/%<>~\[\]?!\|-] {
  226:   return yytext[0];
  227: }
  228: 
  229: ["][^"\n]*["] {
  230:   yytext[yyleng-1] = 0;
  231:   cf_lval.t = cfg_strdup(yytext+1);
  232:   return TEXT;
  233: }
  234: 
  235: ["][^"\n]*\n	cf_error("Unterminated string");
  236: 
  237: <INITIAL,COMMENT><<EOF>>	{ if (check_eof()) return END; }
  238: 
  239: {WHITE}+
  240: 
  241: \n	ifs->lino++; ifs->chno = 0;
  242: 
  243: #	BEGIN(COMMENT);
  244: 
  245: \/\*	BEGIN(CCOMM);
  246: 
  247: .	cf_error("Unknown character");
  248: 
  249: <COMMENT>\n {
  250:   ifs->lino++;
  251:   ifs->chno = 0;
  252:   BEGIN(INITIAL);
  253: }
  254: 
  255: <COMMENT>.
  256: 
  257: <CCOMM>\*\/	BEGIN(INITIAL);
  258: <CCOMM>\n	ifs->lino++; ifs->chno = 0;
  259: <CCOMM>\/\*	cf_error("Comment nesting not supported");
  260: <CCOMM><<EOF>>	cf_error("Unterminated comment");
  261: <CCOMM>.
  262: 
  263: \!\= return NEQ;
  264: \!\~ return NMA;
  265: \<\= return LEQ;
  266: \>\= return GEQ;
  267: \&\& return AND;
  268: \|\| return OR;
  269: 
  270: \[\= return PO;
  271: \=\] return PC;
  272: 
  273: %%
  274: 
  275: static uint
  276: cf_hash(byte *c)
  277: {
  278:   uint h = 13 << 24;
  279: 
  280:   while (*c)
  281:     h = h + (h >> 2) + (h >> 5) + ((uint) *c++ << 24);
  282:   return h;
  283: }
  284: 
  285: 
  286: /*
  287:  * IFS stack - it contains structures needed for recursive processing
  288:  * of include in config files. On the top of the stack is a structure
  289:  * for currently processed file. Other structures are either for
  290:  * active files interrupted because of include directive (these have
  291:  * fd and flex buffer) or for inactive files scheduled to be processed
  292:  * later (when parent requested including of several files by wildcard
  293:  * match - these do not have fd and flex buffer yet).
  294:  *
  295:  * FIXME: Most of these ifs and include functions are really sysdep/unix.
  296:  */
  297: 
  298: static struct include_file_stack *
  299: push_ifs(struct include_file_stack *old)
  300: {
  301:   struct include_file_stack *ret;
  302:   ret = cfg_allocz(sizeof(struct include_file_stack));
  303:   ret->lino = 1;
  304:   ret->prev = old;
  305:   return ret;
  306: }
  307: 
  308: static struct include_file_stack *
  309: pop_ifs(struct include_file_stack *old)
  310: {
  311:  yy_delete_buffer(old->buffer);
  312:  close(old->fd);
  313:  return old->prev;
  314: }
  315: 
  316: static void
  317: enter_ifs(struct include_file_stack *new)
  318: {
  319:   if (!new->buffer)
  320:     {
  321:       new->fd = open(new->file_name, O_RDONLY);
  322:       if (new->fd < 0)
  323:         {
  324:           ifs = ifs->up;
  325: 	  cf_error("Unable to open included file %s: %m", new->file_name);
  326:         }
  327: 
  328:       new->buffer = yy_create_buffer(NULL, YY_BUF_SIZE);
  329:     }
  330: 
  331:   yy_switch_to_buffer(new->buffer);
  332: }
  333: 
  334: /**
  335:  * cf_lex_unwind - unwind lexer state during error
  336:  *
  337:  * cf_lex_unwind() frees the internal state on IFS stack when the lexical
  338:  * analyzer is terminated by cf_error().
  339:  */
  340: void
  341: cf_lex_unwind(void)
  342: {
  343:   struct include_file_stack *n;
  344: 
  345:   for (n = ifs; n != ifs_head; n = n->prev)
  346:     {
  347:       /* Memory is freed automatically */
  348:       if (n->buffer)
  349: 	yy_delete_buffer(n->buffer);
  350:       if (n->fd)
  351:         close(n->fd);
  352:     }
  353: 
  354:   ifs = ifs_head;
  355: }
  356: 
  357: static void
  358: cf_include(char *arg, int alen)
  359: {
  360:   struct include_file_stack *base_ifs = ifs;
  361:   int new_depth, rv, i;
  362:   char *patt;
  363:   glob_t g = {};
  364: 
  365:   new_depth = ifs->depth + 1;
  366:   if (new_depth > MAX_INCLUDE_DEPTH)
  367:     cf_error("Max include depth reached");
  368: 
  369:   /* expand arg to properly handle relative filenames */
  370:   if (*arg != '/')
  371:     {
  372:       int dlen = strlen(ifs->file_name);
  373:       char *dir = alloca(dlen + 1);
  374:       patt = alloca(dlen + alen + 2);
  375:       memcpy(dir, ifs->file_name, dlen + 1);
  376:       sprintf(patt, "%s/%s", dirname(dir), arg);
  377:     }
  378:   else
  379:     patt = arg;
  380: 
  381:   /* Skip globbing if there are no wildcards, mainly to get proper
  382:      response when the included config file is missing */
  383:   if (!strpbrk(arg, "?*["))
  384:     {
  385:       ifs = push_ifs(ifs);
  386:       ifs->file_name = cfg_strdup(patt);
  387:       ifs->depth = new_depth;
  388:       ifs->up = base_ifs;
  389:       enter_ifs(ifs);
  390:       return;
  391:     }
  392: 
  393:   /* Expand the pattern */
  394:   rv = glob(patt, GLOB_ERR | GLOB_NOESCAPE, NULL, &g);
  395:   if (rv == GLOB_ABORTED)
  396:     cf_error("Unable to match pattern %s: %m", patt);
  397:   if ((rv != 0) || (g.gl_pathc <= 0))
  398:     return;
  399: 
  400:   /*
  401:    * Now we put all found files to ifs stack in reverse order, they
  402:    * will be activated and processed in order as ifs stack is popped
  403:    * by pop_ifs() and enter_ifs() in check_eof().
  404:    */
  405:   for(i = g.gl_pathc - 1; i >= 0; i--)
  406:     {
  407:       char *fname = g.gl_pathv[i];
  408:       struct stat fs;
  409: 
  410:       if (stat(fname, &fs) < 0)
  411: 	{
  412: 	  globfree(&g);
  413: 	  cf_error("Unable to stat included file %s: %m", fname);
  414: 	}
  415: 
  416:       if (fs.st_mode & S_IFDIR)
  417:         continue;
  418: 
  419:       /* Prepare new stack item */
  420:       ifs = push_ifs(ifs);
  421:       ifs->file_name = cfg_strdup(fname);
  422:       ifs->depth = new_depth;
  423:       ifs->up = base_ifs;
  424:     }
  425: 
  426:   globfree(&g);
  427:   enter_ifs(ifs);
  428: }
  429: 
  430: static int
  431: check_eof(void)
  432: {
  433:   if (ifs == ifs_head)
  434:     {
  435:       /* EOF in main config file */
  436:       ifs->lino = 1; /* Why this? */
  437:       return 1;
  438:     }
  439: 
  440:   ifs = pop_ifs(ifs);
  441:   enter_ifs(ifs);
  442:   return 0;
  443: }
  444: 
  445: static struct symbol *
  446: cf_new_symbol(byte *c)
  447: {
  448:   struct symbol *s;
  449: 
  450:   uint l = strlen(c);
  451:   if (l > SYM_MAX_LEN)
  452:     cf_error("Symbol too long");
  453: 
  454:   s = cfg_alloc(sizeof(struct symbol) + l);
  455:   s->scope = conf_this_scope;
  456:   s->class = SYM_VOID;
  457:   s->def = NULL;
  458:   s->aux = 0;
  459:   strcpy(s->name, c);
  460: 
  461:   if (!new_config->sym_hash.data)
  462:     HASH_INIT(new_config->sym_hash, new_config->pool, SYM_ORDER);
  463: 
  464:   HASH_INSERT2(new_config->sym_hash, SYM, new_config->pool, s);
  465: 
  466:   return s;
  467: }
  468: 
  469: /**
  470:  * cf_find_symbol - find a symbol by name
  471:  * @cfg: specificed config
  472:  * @c: symbol name
  473:  *
  474:  * This functions searches the symbol table in the config @cfg for a symbol of
  475:  * given name. First it examines the current scope, then the second recent one
  476:  * and so on until it either finds the symbol and returns a pointer to its
  477:  * &symbol structure or reaches the end of the scope chain and returns %NULL to
  478:  * signify no match.
  479:  */
  480: struct symbol *
  481: cf_find_symbol(struct config *cfg, byte *c)
  482: {
  483:   struct symbol *s;
  484: 
  485:   if (cfg->sym_hash.data &&
  486:       (s = HASH_FIND(cfg->sym_hash, SYM, c, 1)))
  487:     return s;
  488: 
  489:   if (cfg->fallback &&
  490:       cfg->fallback->sym_hash.data &&
  491:       (s = HASH_FIND(cfg->fallback->sym_hash, SYM, c, 1)))
  492:     return s;
  493: 
  494:   return NULL;
  495: }
  496: 
  497: /**
  498:  * cf_get_symbol - get a symbol by name
  499:  * @c: symbol name
  500:  *
  501:  * This functions searches the symbol table of the currently parsed config
  502:  * (@new_config) for a symbol of given name. It returns either the already
  503:  * existing symbol or a newly allocated undefined (%SYM_VOID) symbol if no
  504:  * existing symbol is found.
  505:  */
  506: struct symbol *
  507: cf_get_symbol(byte *c)
  508: {
  509:   return cf_find_symbol(new_config, c) ?: cf_new_symbol(c);
  510: }
  511: 
  512: struct symbol *
  513: cf_default_name(char *template, int *counter)
  514: {
  515:   char buf[SYM_MAX_LEN];
  516:   struct symbol *s;
  517:   char *perc = strchr(template, '%');
  518: 
  519:   for(;;)
  520:     {
  521:       bsprintf(buf, template, ++(*counter));
  522:       s = cf_get_symbol(buf);
  523:       if (s->class == SYM_VOID)
  524: 	return s;
  525:       if (!perc)
  526: 	break;
  527:     }
  528:   cf_error("Unable to generate default name");
  529: }
  530: 
  531: /**
  532:  * cf_define_symbol - define meaning of a symbol
  533:  * @sym: symbol to be defined
  534:  * @type: symbol class to assign
  535:  * @def: class dependent data
  536:  *
  537:  * Defines new meaning of a symbol. If the symbol is an undefined
  538:  * one (%SYM_VOID), it's just re-defined to the new type. If it's defined
  539:  * in different scope, a new symbol in current scope is created and the
  540:  * meaning is assigned to it. If it's already defined in the current scope,
  541:  * an error is reported via cf_error().
  542:  *
  543:  * Result: Pointer to the newly defined symbol. If we are in the top-level
  544:  * scope, it's the same @sym as passed to the function.
  545:  */
  546: struct symbol *
  547: cf_define_symbol(struct symbol *sym, int type, void *def)
  548: {
  549:   if (sym->class)
  550:     {
  551:       if (sym->scope == conf_this_scope)
  552: 	cf_error("Symbol already defined");
  553:       sym = cf_new_symbol(sym->name);
  554:     }
  555:   sym->class = type;
  556:   sym->def = def;
  557:   return sym;
  558: }
  559: 
  560: static void
  561: cf_lex_init_kh(void)
  562: {
  563:   HASH_INIT(kw_hash, &root_pool, KW_ORDER);
  564: 
  565:   struct keyword *k;
  566:   for (k=keyword_list; k->name; k++)
  567:     HASH_INSERT(kw_hash, KW, k);
  568: }
  569: 
  570: /**
  571:  * cf_lex_init - initialize the lexer
  572:  * @is_cli: true if we're going to parse CLI command, false for configuration
  573:  * @c: configuration structure
  574:  *
  575:  * cf_lex_init() initializes the lexical analyzer and prepares it for
  576:  * parsing of a new input.
  577:  */
  578: void
  579: cf_lex_init(int is_cli, struct config *c)
  580: {
  581:   if (!kw_hash.data)
  582:     cf_lex_init_kh();
  583: 
  584:   ifs_head = ifs = push_ifs(NULL);
  585:   if (!is_cli)
  586:     {
  587:       ifs->file_name = c->file_name;
  588:       ifs->fd = c->file_fd;
  589:       ifs->depth = 1;
  590:     }
  591: 
  592:   yyrestart(NULL);
  593:   ifs->buffer = YY_CURRENT_BUFFER;
  594: 
  595:   if (is_cli)
  596:     BEGIN(CLI);
  597:   else
  598:     BEGIN(INITIAL);
  599: 
  600:   conf_this_scope = cfg_allocz(sizeof(struct sym_scope));
  601:   conf_this_scope->active = 1;
  602: }
  603: 
  604: /**
  605:  * cf_push_scope - enter new scope
  606:  * @sym: symbol representing scope name
  607:  *
  608:  * If we want to enter a new scope to process declarations inside
  609:  * a nested block, we can just call cf_push_scope() to push a new
  610:  * scope onto the scope stack which will cause all new symbols to be
  611:  * defined in this scope and all existing symbols to be sought for
  612:  * in all scopes stored on the stack.
  613:  */
  614: void
  615: cf_push_scope(struct symbol *sym)
  616: {
  617:   struct sym_scope *s = cfg_alloc(sizeof(struct sym_scope));
  618: 
  619:   s->next = conf_this_scope;
  620:   conf_this_scope = s;
  621:   s->active = 1;
  622:   s->name = sym;
  623: }
  624: 
  625: /**
  626:  * cf_pop_scope - leave a scope
  627:  *
  628:  * cf_pop_scope() pops the topmost scope from the scope stack,
  629:  * leaving all its symbols in the symbol table, but making them
  630:  * invisible to the rest of the config.
  631:  */
  632: void
  633: cf_pop_scope(void)
  634: {
  635:   conf_this_scope->active = 0;
  636:   conf_this_scope = conf_this_scope->next;
  637:   ASSERT(conf_this_scope);
  638: }
  639: 
  640: /**
  641:  * cf_symbol_class_name - get name of a symbol class
  642:  * @sym: symbol
  643:  *
  644:  * This function returns a string representing the class
  645:  * of the given symbol.
  646:  */
  647: char *
  648: cf_symbol_class_name(struct symbol *sym)
  649: {
  650:   if (cf_symbol_is_constant(sym))
  651:     return "constant";
  652: 
  653:   switch (sym->class)
  654:     {
  655:     case SYM_VOID:
  656:       return "undefined";
  657:     case SYM_PROTO:
  658:       return "protocol";
  659:     case SYM_TEMPLATE:
  660:       return "protocol template";
  661:     case SYM_FUNCTION:
  662:       return "function";
  663:     case SYM_FILTER:
  664:       return "filter";
  665:     case SYM_TABLE:
  666:       return "routing table";
  667:     case SYM_ROA:
  668:       return "ROA table";
  669:     default:
  670:       return "unknown type";
  671:     }
  672: }
  673: 
  674: 
  675: /**
  676:  * DOC: Parser
  677:  *
  678:  * Both the configuration and CLI commands are analyzed using a syntax
  679:  * driven parser generated by the |bison| tool from a grammar which
  680:  * is constructed from information gathered from grammar snippets by
  681:  * the |gen_parser.m4| script.
  682:  *
  683:  * Grammar snippets are files (usually with extension |.Y|) contributed
  684:  * by various BIRD modules in order to provide information about syntax of their
  685:  * configuration and their CLI commands. Each snipped consists of several
  686:  * sections, each of them starting with a special keyword: |CF_HDR| for
  687:  * a list of |#include| directives needed by the C code, |CF_DEFINES|
  688:  * for a list of C declarations, |CF_DECLS| for |bison| declarations
  689:  * including keyword definitions specified as |CF_KEYWORDS|, |CF_GRAMMAR|
  690:  * for the grammar rules, |CF_CODE| for auxiliary C code and finally
  691:  * |CF_END| at the end of the snippet.
  692:  *
  693:  * To create references between the snippets, it's possible to define
  694:  * multi-part rules by utilizing the |CF_ADDTO| macro which adds a new
  695:  * alternative to a multi-part rule.
  696:  *
  697:  * CLI commands are defined using a |CF_CLI| macro. Its parameters are:
  698:  * the list of keywords determining the command, the list of parameters,
  699:  * help text for the parameters and help text for the command.
  700:  *
  701:  * Values of |enum| filter types can be defined using |CF_ENUM| with
  702:  * the following parameters: name of filter type, prefix common for all
  703:  * literals of this type and names of all the possible values.
  704:  */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>