]>
Commit | Line | Data |
---|---|---|
b9d70dc8 PM |
1 | /* |
2 | * BIRD - filters | |
3 | * | |
1c20608e | 4 | * Copyright 1998--2000 Pavel Machek |
b9d70dc8 PM |
5 | * |
6 | * Can be freely distributed and used under the terms of the GNU GPL. | |
c9f8c1a8 | 7 | * |
1877dab2 | 8 | FIXME: priority of ! should be lower |
b9d70dc8 PM |
9 | */ |
10 | ||
11 | CF_HDR | |
12 | ||
2edb31b0 MM |
13 | CF_DEFINES |
14 | ||
b8cc390e | 15 | #define P(a,b) ((a << 8) | b) |
2d496d20 | 16 | |
b8cc390e OZ |
17 | static inline u32 pair(u32 a, u32 b) { return (a << 16) | b; } |
18 | static inline u32 pair_a(u32 p) { return p >> 16; } | |
19 | static inline u32 pair_b(u32 p) { return p & 0xFFFF; } | |
20 | ||
21 | ||
22 | /* | |
23 | * Sets and their items are during parsing handled as lists, linked | |
24 | * through left ptr. The first item in a list also contains a pointer | |
25 | * to the last item in a list (right ptr). For convenience, even items | |
26 | * are handled as one-item lists. Lists are merged by f_merge_items(). | |
27 | */ | |
28 | ||
29 | static inline struct f_tree * | |
30 | f_new_item(struct f_val from, struct f_val to) | |
92a72a4c | 31 | { |
b8cc390e OZ |
32 | struct f_tree *t = f_new_tree(); |
33 | t->right = t; | |
34 | t->from = from; | |
35 | t->to = to; | |
36 | return t; | |
37 | } | |
92a72a4c | 38 | |
b8cc390e OZ |
39 | static inline struct f_tree * |
40 | f_merge_items(struct f_tree *a, struct f_tree *b) | |
41 | { | |
42 | if (!a) return b; | |
43 | a->right->left = b; | |
44 | a->right = b->right; | |
45 | b->right = NULL; | |
46 | return a; | |
47 | } | |
92a72a4c | 48 | |
b8cc390e OZ |
49 | static inline struct f_tree * |
50 | f_new_pair_item(int fa, int ta, int fb, int tb) | |
51 | { | |
52 | struct f_tree *t = f_new_tree(); | |
53 | t->right = t; | |
54 | t->from.type = t->to.type = T_PAIR; | |
55 | t->from.val.i = pair(fa, fb); | |
56 | t->to.val.i = pair(ta, tb); | |
57 | return t; | |
92a72a4c OZ |
58 | } |
59 | ||
b8cc390e OZ |
60 | static inline struct f_tree * |
61 | f_new_pair_set(int fa, int ta, int fb, int tb) | |
4fc36f39 | 62 | { |
b8cc390e OZ |
63 | struct f_tree *lst = NULL; |
64 | int i; | |
c454872f | 65 | |
b8cc390e OZ |
66 | if ((fa == ta) || ((fb == 0) && (tb == 0xFFFF))) |
67 | return f_new_pair_item(fa, ta, fb, tb); | |
68 | ||
69 | if ((ta < fa) || (tb < fb)) | |
70 | cf_error( "From value cannot be higher that To value in pair sets"); | |
c454872f | 71 | |
b8cc390e OZ |
72 | for (i = fa; i <= ta; i++) |
73 | lst = f_merge_items(lst, f_new_pair_item(i, i, fb, tb)); | |
74 | ||
75 | return lst; | |
4fc36f39 OF |
76 | } |
77 | ||
42a0c054 OZ |
78 | #define EC_ALL 0xFFFFFFFF |
79 | ||
80 | static struct f_tree * | |
81 | f_new_ec_item(u32 kind, u32 ipv4_used, u32 key, u32 vf, u32 vt) | |
82 | { | |
83 | u64 fm, to; | |
84 | ||
85 | if (ipv4_used || (key >= 0x10000)) { | |
86 | check_u16(vf); | |
87 | if (vt == EC_ALL) | |
88 | vt = 0xFFFF; | |
89 | else | |
90 | check_u16(vt); | |
91 | } | |
92 | ||
93 | if (kind == EC_GENERIC) { | |
94 | fm = ec_generic(key, vf); | |
95 | to = ec_generic(key, vt); | |
96 | } | |
97 | else if (ipv4_used) { | |
98 | fm = ec_ip4(kind, key, vf); | |
99 | to = ec_ip4(kind, key, vt); | |
100 | } | |
101 | else if (key < 0x10000) { | |
102 | fm = ec_as2(kind, key, vf); | |
103 | to = ec_as2(kind, key, vt); | |
104 | } | |
105 | else { | |
106 | fm = ec_as4(kind, key, vf); | |
107 | to = ec_as4(kind, key, vt); | |
108 | } | |
109 | ||
110 | struct f_tree *t = f_new_tree(); | |
111 | t->right = t; | |
112 | t->from.type = t->to.type = T_EC; | |
113 | t->from.val.ec = fm; | |
114 | t->to.val.ec = to; | |
115 | return t; | |
116 | } | |
117 | ||
118 | static inline struct f_inst * | |
119 | f_generate_empty(struct f_inst *dyn) | |
120 | { | |
121 | struct f_inst *e = f_new_inst(); | |
122 | e->code = 'E'; | |
123 | ||
124 | switch (dyn->aux & EAF_TYPE_MASK) { | |
125 | case EAF_TYPE_AS_PATH: | |
126 | e->aux = T_PATH; | |
127 | break; | |
128 | case EAF_TYPE_INT_SET: | |
129 | e->aux = T_CLIST; | |
130 | break; | |
131 | case EAF_TYPE_EC_SET: | |
132 | e->aux = T_ECLIST; | |
133 | break; | |
134 | default: | |
135 | cf_error("Can't empty that attribute"); | |
136 | } | |
137 | ||
138 | dyn->code = P('e','S'); | |
139 | dyn->a1.p = e; | |
140 | return dyn; | |
141 | } | |
142 | ||
143 | ||
144 | static inline struct f_inst * | |
145 | f_generate_dpair(struct f_inst *t1, struct f_inst *t2) | |
146 | { | |
147 | struct f_inst *rv; | |
148 | ||
149 | if ((t1->code == 'c') && (t2->code == 'c')) { | |
150 | if ((t1->aux != T_INT) || (t2->aux != T_INT)) | |
151 | cf_error( "Can't operate with value of non-integer type in pair constructor"); | |
152 | ||
153 | check_u16(t1->a2.i); | |
154 | check_u16(t2->a2.i); | |
155 | ||
156 | rv = f_new_inst(); | |
157 | rv->code = 'c'; | |
158 | rv->aux = T_PAIR; | |
159 | rv->a2.i = pair(t1->a2.i, t2->a2.i); | |
160 | } | |
161 | else { | |
162 | rv = f_new_inst(); | |
163 | rv->code = P('m', 'p'); | |
164 | rv->a1.p = t1; | |
165 | rv->a2.p = t2; | |
166 | } | |
167 | ||
168 | return rv; | |
169 | } | |
170 | ||
171 | static inline struct f_inst * | |
172 | f_generate_ec(u16 kind, struct f_inst *tk, struct f_inst *tv) | |
173 | { | |
174 | struct f_inst *rv; | |
175 | int c1 = 0, c2 = 0, ipv4_used = 0; | |
176 | u32 key = 0, val2 = 0; | |
177 | ||
178 | if (tk->code == 'c') { | |
179 | c1 = 1; | |
180 | ||
181 | if (tk->aux == T_INT) { | |
182 | ipv4_used = 0; key = tk->a2.i; | |
183 | } | |
184 | else if (tk->aux == T_QUAD) { | |
185 | ipv4_used = 1; key = tk->a2.i; | |
186 | } | |
187 | else | |
188 | cf_error("Can't operate with key of non-integer/IPv4 type in EC constructor"); | |
189 | } | |
190 | ||
191 | #ifndef IPV6 | |
192 | /* IP->Quad implicit conversion */ | |
193 | else if (tk->code == 'C') { | |
194 | c1 = 1; | |
195 | struct f_val *val = tk->a1.p; | |
1103b32e OZ |
196 | |
197 | if (val->type == T_INT) { | |
198 | ipv4_used = 0; key = val->val.i; | |
199 | } | |
200 | else if (val->type == T_QUAD) { | |
201 | ipv4_used = 1; key = val->val.i; | |
202 | } | |
203 | else if (val->type == T_IP) { | |
42a0c054 OZ |
204 | ipv4_used = 1; key = ipa_to_u32(val->val.px.ip); |
205 | } | |
206 | else | |
207 | cf_error("Can't operate with key of non-integer/IPv4 type in EC constructor"); | |
208 | } | |
209 | #endif | |
210 | ||
211 | if (tv->code == 'c') { | |
212 | if (tv->aux != T_INT) | |
213 | cf_error("Can't operate with value of non-integer type in EC constructor"); | |
214 | c2 = 1; | |
215 | val2 = tv->a2.i; | |
216 | } | |
217 | ||
218 | if (c1 && c2) { | |
219 | u64 ec; | |
220 | ||
221 | if (kind == EC_GENERIC) { | |
222 | ec = ec_generic(key, val2); | |
223 | } | |
224 | else if (ipv4_used) { | |
225 | check_u16(val2); | |
226 | ec = ec_ip4(kind, key, val2); | |
227 | } | |
228 | else if (key < 0x10000) { | |
229 | ec = ec_as2(kind, key, val2); | |
230 | } | |
231 | else { | |
232 | check_u16(val2); | |
233 | ec = ec_as4(kind, key, val2); | |
234 | } | |
235 | ||
236 | NEW_F_VAL; | |
237 | rv = f_new_inst(); | |
238 | rv->code = 'C'; | |
239 | rv->a1.p = val; | |
240 | val->type = T_EC; | |
241 | val->val.ec = ec; | |
242 | } | |
243 | else { | |
244 | rv = f_new_inst(); | |
245 | rv->code = P('m','c'); | |
246 | rv->aux = kind; | |
247 | rv->a1.p = tk; | |
248 | rv->a2.p = tv; | |
249 | } | |
250 | ||
251 | return rv; | |
78e33c29 | 252 | } |
42a0c054 OZ |
253 | |
254 | ||
255 | ||
b9d70dc8 PM |
256 | CF_DECLS |
257 | ||
e4a73dbf | 258 | CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN, |
ba921648 | 259 | ACCEPT, REJECT, ERROR, QUITBIRD, |
42a0c054 OZ |
260 | INT, BOOL, IP, PREFIX, PAIR, QUAD, EC, |
261 | SET, STRING, BGPMASK, BGPPATH, CLIST, ECLIST, | |
7db7b7db | 262 | IF, THEN, ELSE, CASE, |
42a0c054 | 263 | TRUE, FALSE, RT, RO, UNKNOWN, GENERIC, |
a5fc5958 OZ |
264 | FROM, GW, NET, MASK, PROTO, SOURCE, SCOPE, CAST, DEST, IFNAME, IFINDEX, |
265 | PREFERENCE, | |
36bbfc70 | 266 | LEN, |
f4536657 | 267 | DEFINED, |
7f77e250 | 268 | ADD, DELETE, CONTAINS, RESET, |
7ea5b00f | 269 | PREPEND, FIRST, LAST, MATCH, |
af582c48 | 270 | ROA_CHECK, |
afc54517 | 271 | EMPTY, |
1c20608e | 272 | FILTER, WHERE, EVAL) |
b9d70dc8 | 273 | |
f4536657 | 274 | %nonassoc THEN |
4ed8718a | 275 | %nonassoc ELSE |
f4536657 | 276 | |
42a0c054 | 277 | %type <x> term block cmds cmds_int cmd function_body constant constructor print_one print_list var_list var_listn dynamic_attr static_attr function_call symbol bgp_path_expr |
430da60f | 278 | %type <f> filter filter_body where_filter |
42a0c054 OZ |
279 | %type <i> type break_command pair_expr ec_kind |
280 | %type <i32> pair_atom ec_expr | |
281 | %type <e> pair_item ec_item set_item switch_item set_items switch_items switch_body | |
b1a597e0 | 282 | %type <trie> fprefix_set |
b8cc390e | 283 | %type <v> set_atom switch_atom fprefix fprefix_s fipa |
6dc7a0cb | 284 | %type <s> decls declsn one_decl function_params |
f9491630 | 285 | %type <h> bgp_path bgp_path_tail1 bgp_path_tail2 |
b9d70dc8 PM |
286 | |
287 | CF_GRAMMAR | |
288 | ||
e0f2e42f MM |
289 | CF_ADDTO(conf, filter_def) |
290 | filter_def: | |
b2b7bbfc OZ |
291 | FILTER SYM { $2 = cf_define_symbol($2, SYM_FILTER, NULL); cf_push_scope( $2 ); } |
292 | filter_body { | |
293 | $2->def = $4; | |
ae3e1af2 | 294 | $4->name = $2->name; |
d4d75628 | 295 | DBG( "We have new filter defined (%s)\n", $2->name ); |
ae3e1af2 | 296 | cf_pop_scope(); |
b9d70dc8 PM |
297 | } |
298 | ; | |
299 | ||
1c20608e MM |
300 | CF_ADDTO(conf, filter_eval) |
301 | filter_eval: | |
302 | EVAL term { f_eval_int($2); } | |
303 | ; | |
304 | ||
ba921648 PM |
305 | type: |
306 | INT { $$ = T_INT; } | |
307 | | BOOL { $$ = T_BOOL; } | |
308 | | IP { $$ = T_IP; } | |
309 | | PREFIX { $$ = T_PREFIX; } | |
310 | | PAIR { $$ = T_PAIR; } | |
126683fe | 311 | | QUAD { $$ = T_QUAD; } |
42a0c054 | 312 | | EC { $$ = T_EC; } |
ba921648 | 313 | | STRING { $$ = T_STRING; } |
10a53608 PM |
314 | | BGPMASK { $$ = T_PATH_MASK; } |
315 | | BGPPATH { $$ = T_PATH; } | |
316 | | CLIST { $$ = T_CLIST; } | |
42a0c054 | 317 | | ECLIST { $$ = T_ECLIST; } |
ba921648 PM |
318 | | type SET { |
319 | switch ($1) { | |
b1a597e0 | 320 | case T_INT: |
b1a597e0 | 321 | case T_PAIR: |
126683fe | 322 | case T_QUAD: |
42a0c054 | 323 | case T_EC: |
126683fe | 324 | case T_IP: |
b1a597e0 OZ |
325 | $$ = T_SET; |
326 | break; | |
327 | ||
328 | case T_PREFIX: | |
329 | $$ = T_PREFIX_SET; | |
330 | break; | |
331 | ||
ba921648 | 332 | default: |
a5a947d4 | 333 | cf_error( "You can't create sets of this type." ); |
ba921648 | 334 | } |
b1a597e0 | 335 | } |
ba921648 PM |
336 | ; |
337 | ||
6dc7a0cb PM |
338 | one_decl: |
339 | type SYM { | |
4ee39ff2 OZ |
340 | struct f_val * val = cfg_alloc(sizeof(struct f_val)); |
341 | val->type = T_VOID; | |
083c43e2 | 342 | $2 = cf_define_symbol($2, SYM_VARIABLE | $1, val); |
d4d75628 | 343 | DBG( "New variable %s type %x\n", $2->name, $1 ); |
083c43e2 | 344 | $2->aux2 = NULL; |
6542ece9 | 345 | $$=$2; |
ba921648 PM |
346 | } |
347 | ; | |
348 | ||
6dc7a0cb PM |
349 | /* Decls with ';' at the end */ |
350 | decls: /* EMPTY */ { $$ = NULL; } | |
351 | | one_decl ';' decls { | |
352 | $$ = $1; | |
083c43e2 | 353 | $$->aux2 = $3; |
6dc7a0cb PM |
354 | } |
355 | ; | |
356 | ||
3c989eb4 | 357 | /* Declarations that have no ';' at the end. */ |
6dc7a0cb | 358 | declsn: one_decl { $$ = $1; } |
736fd730 | 359 | | one_decl ';' declsn { |
4515bdba | 360 | $$ = $1; |
083c43e2 | 361 | $$->aux2 = $3; |
6dc7a0cb PM |
362 | } |
363 | ; | |
364 | ||
e0f2e42f | 365 | filter_body: |
ba921648 | 366 | function_body { |
e0f2e42f MM |
367 | struct filter *f = cfg_alloc(sizeof(struct filter)); |
368 | f->name = NULL; | |
ba921648 | 369 | f->root = $1; |
e0f2e42f MM |
370 | $$ = f; |
371 | } | |
372 | ; | |
373 | ||
374 | filter: | |
375 | SYM { | |
a5a947d4 | 376 | if ($1->class != SYM_FILTER) cf_error("No such filter."); |
e0f2e42f MM |
377 | $$ = $1->def; |
378 | } | |
379 | | filter_body | |
380 | ; | |
381 | ||
430da60f MM |
382 | where_filter: |
383 | WHERE term { | |
384 | /* Construct 'IF term THEN ACCEPT; REJECT;' */ | |
385 | struct filter *f = cfg_alloc(sizeof(struct filter)); | |
386 | struct f_inst *i, *acc, *rej; | |
387 | acc = f_new_inst(); /* ACCEPT */ | |
2d496d20 | 388 | acc->code = P('p',','); |
430da60f MM |
389 | acc->a1.p = NULL; |
390 | acc->a2.i = F_ACCEPT; | |
391 | rej = f_new_inst(); /* REJECT */ | |
2d496d20 | 392 | rej->code = P('p',','); |
430da60f MM |
393 | rej->a1.p = NULL; |
394 | rej->a2.i = F_REJECT; | |
395 | i = f_new_inst(); /* IF */ | |
396 | i->code = '?'; | |
397 | i->a1.p = $2; | |
398 | i->a2.p = acc; | |
399 | i->next = rej; | |
400 | f->name = NULL; | |
401 | f->root = i; | |
402 | $$ = f; | |
403 | } | |
404 | ; | |
405 | ||
ba921648 | 406 | function_params: |
d4d75628 | 407 | '(' declsn ')' { DBG( "Have function parameters\n" ); $$=$2; } |
6dc7a0cb | 408 | | '(' ')' { $$=NULL; } |
ba921648 | 409 | ; |
b9d70dc8 | 410 | |
ba921648 PM |
411 | function_body: |
412 | decls '{' cmds '}' { | |
aa461248 OZ |
413 | if ($1) { |
414 | /* Prepend instruction to clear local variables */ | |
415 | $$ = f_new_inst(); | |
416 | $$->code = P('c','v'); | |
417 | $$->a1.p = $1; | |
418 | $$->next = $3; | |
419 | } else | |
420 | $$ = $3; | |
84c7e194 | 421 | } |
ba921648 PM |
422 | ; |
423 | ||
424 | CF_ADDTO(conf, function_def) | |
425 | function_def: | |
bf3eb98e MM |
426 | FUNCTION SYM { DBG( "Beginning of function %s\n", $2->name ); |
427 | $2 = cf_define_symbol($2, SYM_FUNCTION, NULL); | |
428 | cf_push_scope($2); | |
429 | } function_params function_body { | |
430 | $2->def = $5; | |
083c43e2 | 431 | $2->aux2 = $4; |
d4d75628 | 432 | DBG("Hmm, we've got one function here - %s\n", $2->name); |
ae3e1af2 | 433 | cf_pop_scope(); |
ba921648 PM |
434 | } |
435 | ; | |
436 | ||
437 | /* Programs */ | |
438 | ||
5f47c4c1 OZ |
439 | /* Hack: $$ of cmds_int is the last node. |
440 | $$->next of cmds_int is temporary used for the first node */ | |
441 | ||
ba921648 | 442 | cmds: /* EMPTY */ { $$ = NULL; } |
5f47c4c1 OZ |
443 | | cmds_int { $$ = $1->next; $1->next = NULL; } |
444 | ; | |
445 | ||
446 | cmds_int: cmd { $$ = $1; $1->next = $1; } | |
447 | | cmds_int cmd { $$ = $2; $2->next = $1->next ; $1->next = $2; } | |
84c7e194 PM |
448 | ; |
449 | ||
2575593e | 450 | block: |
ba921648 | 451 | cmd { |
2575593e PM |
452 | $$=$1; |
453 | } | |
454 | | '{' cmds '}' { | |
455 | $$=$2; | |
456 | } | |
457 | ; | |
458 | ||
d3dd620b PM |
459 | /* |
460 | * Complex types, their bison value is struct f_val | |
461 | */ | |
e3f2d5fc | 462 | fipa: |
60de3356 | 463 | IPA %prec PREFIX_DUMMY { $$.type = T_IP; $$.val.px.ip = $1; } |
d3dd620b PM |
464 | ; |
465 | ||
b8cc390e OZ |
466 | |
467 | ||
468 | /* | |
469 | * Set constants. They are also used in switch cases. We use separate | |
470 | * nonterminals for switch (set_atom/switch_atom, set_item/switch_item ...) | |
471 | * to elude a collision between symbol (in expr) in set_atom and symbol | |
472 | * as a function call in switch case cmds. | |
473 | */ | |
474 | ||
38506f71 | 475 | set_atom: |
b8cc390e | 476 | expr { $$.type = T_INT; $$.val.i = $1; } |
126683fe | 477 | | RTRID { $$.type = T_QUAD; $$.val.i = $1; } |
92a72a4c | 478 | | fipa { $$ = $1; } |
b8cc390e OZ |
479 | | ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); } |
480 | ; | |
38506f71 | 481 | |
b8cc390e OZ |
482 | switch_atom: |
483 | NUM { $$.type = T_INT; $$.val.i = $1; } | |
484 | | '(' term ')' { $$.type = T_INT; $$.val.i = f_eval_int($2); } | |
485 | | RTRID { $$.type = T_QUAD; $$.val.i = $1; } | |
486 | | fipa { $$ = $1; } | |
487 | | ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); } | |
488 | ; | |
489 | ||
490 | pair_expr: | |
491 | term { $$ = f_eval_int($1); check_u16($$); } | |
492 | ||
493 | pair_atom: | |
494 | pair_expr { $$ = pair($1, $1); } | |
495 | | pair_expr DDOT pair_expr { $$ = pair($1, $3); } | |
496 | | '*' { $$ = 0xFFFF; } | |
497 | ; | |
498 | ||
499 | pair_item: | |
500 | '(' pair_atom ',' pair_atom ')' { | |
501 | $$ = f_new_pair_set(pair_a($2), pair_b($2), pair_a($4), pair_b($4)); | |
995e5894 | 502 | } |
b8cc390e OZ |
503 | | '(' pair_atom ',' pair_atom ')' DDOT '(' pair_expr ',' pair_expr ')' { |
504 | /* Hack: $2 and $4 should be pair_expr, but that would cause shift/reduce conflict */ | |
505 | if ((pair_a($2) != pair_b($2)) || (pair_a($4) != pair_b($4))) | |
506 | cf_error("syntax error"); | |
117e3c4b | 507 | $$ = f_new_pair_item(pair_b($2), $8, pair_b($4), $10); |
995e5894 | 508 | } |
38506f71 PM |
509 | ; |
510 | ||
42a0c054 OZ |
511 | ec_expr: |
512 | term { $$ = f_eval_int($1); } | |
513 | ||
514 | ec_kind: | |
515 | RT { $$ = EC_RT; } | |
516 | | RO { $$ = EC_RO; } | |
517 | | UNKNOWN NUM { $$ = $2; } | |
518 | | GENERIC { $$ = EC_GENERIC; } | |
519 | ; | |
520 | ||
521 | ec_item: | |
522 | '(' ec_kind ',' ec_expr ',' ec_expr ')' { $$ = f_new_ec_item($2, 0, $4, $6, $6); } | |
523 | | '(' ec_kind ',' ec_expr ',' ec_expr DDOT ec_expr ')' { $$ = f_new_ec_item($2, 0, $4, $6, $8); } | |
524 | | '(' ec_kind ',' ec_expr ',' '*' ')' { $$ = f_new_ec_item($2, 0, $4, 0, EC_ALL); } | |
525 | ; | |
526 | ||
b8cc390e OZ |
527 | set_item: |
528 | pair_item | |
42a0c054 | 529 | | ec_item |
b8cc390e OZ |
530 | | set_atom { $$ = f_new_item($1, $1); } |
531 | | set_atom DDOT set_atom { $$ = f_new_item($1, $3); } | |
532 | ; | |
533 | ||
534 | switch_item: | |
535 | pair_item | |
42a0c054 | 536 | | ec_item |
b8cc390e OZ |
537 | | switch_atom { $$ = f_new_item($1, $1); } |
538 | | switch_atom DDOT switch_atom { $$ = f_new_item($1, $3); } | |
539 | ; | |
540 | ||
38506f71 | 541 | set_items: |
b8cc390e OZ |
542 | set_item |
543 | | set_items ',' set_item { $$ = f_merge_items($1, $3); } | |
544 | ; | |
545 | ||
546 | switch_items: | |
547 | switch_item | |
548 | | switch_items ',' switch_item { $$ = f_merge_items($1, $3); } | |
38506f71 PM |
549 | ; |
550 | ||
b1a597e0 OZ |
551 | fprefix_s: |
552 | IPA '/' NUM %prec '/' { | |
553 | if (($3 < 0) || ($3 > MAX_PREFIX_LENGTH) || !ip_is_prefix($1, $3)) cf_error("Invalid network prefix: %I/%d.", $1, $3); | |
554 | $$.type = T_PREFIX; $$.val.px.ip = $1; $$.val.px.len = $3; | |
555 | } | |
556 | ; | |
557 | ||
558 | fprefix: | |
559 | fprefix_s { $$ = $1; } | |
560 | | fprefix_s '+' { $$ = $1; $$.val.px.len |= LEN_PLUS; } | |
561 | | fprefix_s '-' { $$ = $1; $$.val.px.len |= LEN_MINUS; } | |
562 | | fprefix_s '{' NUM ',' NUM '}' { | |
563 | if (! ((0 <= $3) && ($3 <= $5) && ($5 <= MAX_PREFIX_LENGTH))) cf_error("Invalid prefix pattern range: {%d, %d}.", $3, $5); | |
564 | $$ = $1; $$.val.px.len |= LEN_RANGE | ($3 << 16) | ($5 << 8); | |
565 | } | |
566 | ; | |
567 | ||
568 | fprefix_set: | |
7f0d245a OZ |
569 | fprefix { $$ = f_new_trie(cfg_mem); trie_add_fprefix($$, &($1.val.px)); } |
570 | | fprefix_set ',' fprefix { $$ = $1; trie_add_fprefix($$, &($3.val.px)); } | |
b1a597e0 OZ |
571 | ; |
572 | ||
41be4444 | 573 | switch_body: /* EMPTY */ { $$ = NULL; } |
b8cc390e OZ |
574 | | switch_body switch_items ':' cmds { |
575 | /* Fill data fields */ | |
576 | struct f_tree *t; | |
577 | for (t = $2; t; t = t->left) | |
578 | t->data = $4; | |
579 | $$ = f_merge_items($1, $2); | |
41be4444 | 580 | } |
b8cc390e OZ |
581 | | switch_body ELSECOL cmds { |
582 | struct f_tree *t = f_new_tree(); | |
583 | t->from.type = t->to.type = T_VOID; | |
584 | t->right = t; | |
585 | t->data = $3; | |
586 | $$ = f_merge_items($1, t); | |
587 | } | |
41be4444 | 588 | ; |
d3dd620b | 589 | |
e4a73dbf PM |
590 | /* CONST '(' expr ')' { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_INT; $$->a2.i = $3; } */ |
591 | ||
92a72a4c OZ |
592 | bgp_path_expr: |
593 | symbol { $$ = $1; } | |
594 | | '(' term ')' { $$ = $2; } | |
595 | ; | |
596 | ||
f9491630 | 597 | bgp_path: |
cf186034 | 598 | PO bgp_path_tail1 PC { $$ = $2; } |
f9491630 | 599 | | '/' bgp_path_tail2 '/' { $$ = $2; } |
f9491630 OZ |
600 | ; |
601 | ||
602 | bgp_path_tail1: | |
c8a6b9a3 OZ |
603 | NUM bgp_path_tail1 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_ASN; $$->val = $1; } |
604 | | '*' bgp_path_tail1 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_ASTERISK; $$->val = 0; } | |
605 | | '?' bgp_path_tail1 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_QUESTION; $$->val = 0; } | |
92a72a4c | 606 | | bgp_path_expr bgp_path_tail1 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_ASN_EXPR; $$->val = (uintptr_t) $1; } |
f9491630 OZ |
607 | | { $$ = NULL; } |
608 | ; | |
77de6882 | 609 | |
f9491630 | 610 | bgp_path_tail2: |
c8a6b9a3 OZ |
611 | NUM bgp_path_tail2 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_ASN; $$->val = $1; } |
612 | | '?' bgp_path_tail2 { $$ = cfg_alloc(sizeof(struct f_path_mask)); $$->next = $2; $$->kind = PM_ASTERISK; $$->val = 0; } | |
f9491630 | 613 | | { $$ = NULL; } |
77de6882 PM |
614 | ; |
615 | ||
23b1539b | 616 | constant: |
e4a73dbf | 617 | NUM { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_INT; $$->a2.i = $1; } |
c7b43f33 PM |
618 | | TRUE { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_BOOL; $$->a2.i = 1; } |
619 | | FALSE { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_BOOL; $$->a2.i = 0; } | |
620 | | TEXT { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_STRING; $$->a2.p = $1; } | |
e3f2d5fc | 621 | | fipa { NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; $$->a1.p = val; *val = $1; } |
758458be | 622 | | fprefix_s {NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; $$->a1.p = val; *val = $1; } |
126683fe | 623 | | RTRID { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_QUAD; $$->a2.i = $1; } |
d4d75628 | 624 | | '[' set_items ']' { DBG( "We've got a set here..." ); $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_SET; $$->a2.p = build_tree($2); DBG( "ook\n" ); } |
b1a597e0 | 625 | | '[' fprefix_set ']' { $$ = f_new_inst(); $$->code = 'c'; $$->aux = T_PREFIX_SET; $$->a2.p = $2; } |
c7b43f33 | 626 | | ENUM { $$ = f_new_inst(); $$->code = 'c'; $$->aux = $1 >> 16; $$->a2.i = $1 & 0xffff; } |
f9491630 | 627 | | bgp_path { NEW_F_VAL; $$ = f_new_inst(); $$->code = 'C'; val->type = T_PATH_MASK; val->val.path_mask = $1; $$->a1.p = val; } |
23b1539b PM |
628 | ; |
629 | ||
42a0c054 | 630 | constructor: |
78e33c29 OZ |
631 | '(' term ',' term ')' { $$ = f_generate_dpair($2, $4); } |
632 | | '(' ec_kind ',' term ',' term ')' { $$ = f_generate_ec($2, $4, $6); } | |
42a0c054 OZ |
633 | ; |
634 | ||
92a72a4c | 635 | |
db1326aa MM |
636 | /* |
637 | * Maybe there are no dynamic attributes defined by protocols. | |
638 | * For such cases, we force the dynamic_attr list to contain | |
b8cc390e | 639 | * at least an invalid token, so it is syntantically correct. |
db1326aa MM |
640 | */ |
641 | CF_ADDTO(dynamic_attr, INVALID_TOKEN { $$ = NULL; }) | |
f4536657 | 642 | |
2e18b87d | 643 | rtadot: /* EMPTY, we are not permitted RTA. prefix */ |
6c14255d | 644 | ; |
f4536657 | 645 | |
2d496d20 PM |
646 | function_call: |
647 | SYM '(' var_list ')' { | |
648 | struct symbol *sym; | |
649 | struct f_inst *inst = $3; | |
650 | if ($1->class != SYM_FUNCTION) | |
a5a947d4 | 651 | cf_error("You can't call something which is not a function. Really."); |
d4d75628 | 652 | DBG("You are calling function %s\n", $1->name); |
2d496d20 PM |
653 | $$ = f_new_inst(); |
654 | $$->code = P('c','a'); | |
655 | $$->a1.p = inst; | |
083c43e2 OZ |
656 | $$->a2.p = $1->def; |
657 | sym = $1->aux2; | |
2d496d20 PM |
658 | while (sym || inst) { |
659 | if (!sym || !inst) | |
a5a947d4 | 660 | cf_error("Wrong number of arguments for function %s.", $1->name); |
d4d75628 | 661 | DBG( "You should pass parameter called %s\n", sym->name); |
2d496d20 | 662 | inst->a1.p = sym; |
083c43e2 | 663 | sym = sym->aux2; |
2d496d20 PM |
664 | inst = inst->next; |
665 | } | |
666 | } | |
667 | ; | |
668 | ||
92a72a4c OZ |
669 | symbol: |
670 | SYM { | |
671 | $$ = f_new_inst(); | |
1103b32e OZ |
672 | |
673 | switch ($1->class & 0xff00) { | |
674 | case SYM_CONSTANT: $$->code = 'C'; break; | |
675 | case SYM_VARIABLE: $$->code = 'V'; break; | |
676 | default: cf_error("%s: variable expected.", $1->name); | |
92a72a4c | 677 | } |
1103b32e OZ |
678 | |
679 | $$->a1.p = $1->def; | |
680 | $$->a2.p = $1->name; | |
92a72a4c OZ |
681 | } |
682 | ||
2bdb5e00 | 683 | static_attr: |
a5fc5958 OZ |
684 | FROM { $$ = f_new_inst(); $$->aux = T_IP; $$->a2.i = SA_FROM; $$->a1.i = 1; } |
685 | | GW { $$ = f_new_inst(); $$->aux = T_IP; $$->a2.i = SA_GW; $$->a1.i = 1; } | |
686 | | NET { $$ = f_new_inst(); $$->aux = T_PREFIX; $$->a2.i = SA_NET; } | |
687 | | PROTO { $$ = f_new_inst(); $$->aux = T_STRING; $$->a2.i = SA_PROTO; } | |
688 | | SOURCE { $$ = f_new_inst(); $$->aux = T_ENUM_RTS; $$->a2.i = SA_SOURCE; } | |
689 | | SCOPE { $$ = f_new_inst(); $$->aux = T_ENUM_SCOPE; $$->a2.i = SA_SCOPE; $$->a1.i = 1; } | |
690 | | CAST { $$ = f_new_inst(); $$->aux = T_ENUM_RTC; $$->a2.i = SA_CAST; } | |
691 | | DEST { $$ = f_new_inst(); $$->aux = T_ENUM_RTD; $$->a2.i = SA_DEST; $$->a1.i = 1; } | |
692 | | IFNAME { $$ = f_new_inst(); $$->aux = T_STRING; $$->a2.i = SA_IFNAME; } | |
693 | | IFINDEX { $$ = f_new_inst(); $$->aux = T_INT; $$->a2.i = SA_IFINDEX; } | |
2bdb5e00 PM |
694 | ; |
695 | ||
84c7e194 | 696 | term: |
f4536657 | 697 | '(' term ')' { $$ = $2; } |
d4d75628 | 698 | | term '+' term { $$ = f_new_inst(); $$->code = '+'; $$->a1.p = $1; $$->a2.p = $3; } |
c5a06f65 PM |
699 | | term '-' term { $$ = f_new_inst(); $$->code = '-'; $$->a1.p = $1; $$->a2.p = $3; } |
700 | | term '*' term { $$ = f_new_inst(); $$->code = '*'; $$->a1.p = $1; $$->a2.p = $3; } | |
701 | | term '/' term { $$ = f_new_inst(); $$->code = '/'; $$->a1.p = $1; $$->a2.p = $3; } | |
5f4aee76 PM |
702 | | term AND term { $$ = f_new_inst(); $$->code = '&'; $$->a1.p = $1; $$->a2.p = $3; } |
703 | | term OR term { $$ = f_new_inst(); $$->code = '|'; $$->a1.p = $1; $$->a2.p = $3; } | |
2d496d20 | 704 | | term '=' term { $$ = f_new_inst(); $$->code = P('=','='); $$->a1.p = $1; $$->a2.p = $3; } |
d4d75628 PM |
705 | | term NEQ term { $$ = f_new_inst(); $$->code = P('!','='); $$->a1.p = $1; $$->a2.p = $3; } |
706 | | term '<' term { $$ = f_new_inst(); $$->code = '<'; $$->a1.p = $1; $$->a2.p = $3; } | |
707 | | term LEQ term { $$ = f_new_inst(); $$->code = P('<','='); $$->a1.p = $1; $$->a2.p = $3; } | |
708 | | term '>' term { $$ = f_new_inst(); $$->code = '<'; $$->a1.p = $3; $$->a2.p = $1; } | |
709 | | term GEQ term { $$ = f_new_inst(); $$->code = P('<','='); $$->a1.p = $3; $$->a2.p = $1; } | |
710 | | term '~' term { $$ = f_new_inst(); $$->code = '~'; $$->a1.p = $1; $$->a2.p = $3; } | |
995e5894 | 711 | | '!' term { $$ = f_new_inst(); $$->code = '!'; $$->a1.p = $2; } |
2d496d20 | 712 | | DEFINED '(' term ')' { $$ = f_new_inst(); $$->code = P('d','e'); $$->a1.p = $3; } |
23b1539b | 713 | |
92a72a4c | 714 | | symbol { $$ = $1; } |
1183b6b2 | 715 | | constant { $$ = $1; } |
42a0c054 | 716 | | constructor { $$ = $1; } |
4515bdba | 717 | |
0dc4431c | 718 | | PREFERENCE { $$ = f_new_inst(); $$->code = 'P'; } |
2bdb5e00 PM |
719 | |
720 | | rtadot static_attr { $$ = $2; $$->code = 'a'; } | |
fe613ecd | 721 | |
db1326aa | 722 | | rtadot dynamic_attr { $$ = $2; $$->code = P('e','a'); } |
36bbfc70 | 723 | |
2d496d20 | 724 | | term '.' IP { $$ = f_new_inst(); $$->code = P('c','p'); $$->a1.p = $1; $$->aux = T_IP; } |
684c6f5a | 725 | | term '.' LEN { $$ = f_new_inst(); $$->code = 'L'; $$->a1.p = $1; } |
2d496d20 | 726 | | term '.' MASK '(' term ')' { $$ = f_new_inst(); $$->code = P('i','M'); $$->a1.p = $1; $$->a2.p = $5; } |
7ea5b00f OZ |
727 | | term '.' FIRST { $$ = f_new_inst(); $$->code = P('a','f'); $$->a1.p = $1; } |
728 | | term '.' LAST { $$ = f_new_inst(); $$->code = P('a','l'); $$->a1.p = $1; } | |
7f77e250 PM |
729 | |
730 | /* Communities */ | |
10a53608 PM |
731 | /* This causes one shift/reduce conflict |
732 | | rtadot dynamic_attr '.' ADD '(' term ')' { } | |
733 | | rtadot dynamic_attr '.' DELETE '(' term ')' { } | |
734 | | rtadot dynamic_attr '.' CONTAINS '(' term ')' { } | |
a2d15746 | 735 | | rtadot dynamic_attr '.' RESET{ } |
10a53608 | 736 | */ |
7f77e250 | 737 | |
e399b6f6 | 738 | | '+' EMPTY '+' { $$ = f_new_inst(); $$->code = 'E'; $$->aux = T_PATH; } |
9c400ec9 | 739 | | '-' EMPTY '-' { $$ = f_new_inst(); $$->code = 'E'; $$->aux = T_CLIST; } |
42a0c054 | 740 | | '-' '-' EMPTY '-' '-' { $$ = f_new_inst(); $$->code = 'E'; $$->aux = T_ECLIST; } |
e399b6f6 | 741 | | PREPEND '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('A','p'); $$->a1.p = $3; $$->a2.p = $5; } |
4444ed2b PM |
742 | | ADD '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('C','a'); $$->a1.p = $3; $$->a2.p = $5; $$->aux = 'a'; } |
743 | | DELETE '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('C','a'); $$->a1.p = $3; $$->a2.p = $5; $$->aux = 'd'; } | |
e08d2ff0 | 744 | | FILTER '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('C','a'); $$->a1.p = $3; $$->a2.p = $5; $$->aux = 'f'; } |
afc54517 | 745 | |
af582c48 OZ |
746 | | ROA_CHECK '(' SYM ')' { $$ = f_generate_roa_check($3, NULL, NULL); } |
747 | | ROA_CHECK '(' SYM ',' term ',' term ')' { $$ = f_generate_roa_check($3, $5, $7); } | |
748 | ||
a2d15746 | 749 | /* | term '.' LEN { $$->code = P('P','l'); } */ |
7f77e250 | 750 | |
995e5894 PM |
751 | /* function_call is inlined here */ |
752 | | SYM '(' var_list ')' { | |
753 | struct symbol *sym; | |
754 | struct f_inst *inst = $3; | |
755 | if ($1->class != SYM_FUNCTION) | |
a5a947d4 | 756 | cf_error("You can't call something which is not a function. Really."); |
995e5894 PM |
757 | DBG("You are calling function %s\n", $1->name); |
758 | $$ = f_new_inst(); | |
759 | $$->code = P('c','a'); | |
760 | $$->a1.p = inst; | |
083c43e2 OZ |
761 | $$->a2.p = $1->def; |
762 | sym = $1->aux2; | |
995e5894 PM |
763 | while (sym || inst) { |
764 | if (!sym || !inst) | |
a5a947d4 | 765 | cf_error("Wrong number of arguments for function %s.", $1->name); |
995e5894 PM |
766 | DBG( "You should pass parameter called %s\n", sym->name); |
767 | inst->a1.p = sym; | |
083c43e2 | 768 | sym = sym->aux2; |
995e5894 PM |
769 | inst = inst->next; |
770 | } | |
771 | } | |
ba921648 PM |
772 | ; |
773 | ||
774 | break_command: | |
03e3d184 MM |
775 | QUITBIRD { $$ = F_QUITBIRD; } |
776 | | ACCEPT { $$ = F_ACCEPT; } | |
777 | | REJECT { $$ = F_REJECT; } | |
778 | | ERROR { $$ = F_ERROR; } | |
779 | | PRINT { $$ = F_NOP; } | |
780 | | PRINTN { $$ = F_NONL; } | |
ba921648 PM |
781 | ; |
782 | ||
23b1539b | 783 | print_one: |
2db3b288 | 784 | term { $$ = f_new_inst(); $$->code = 'p'; $$->a1.p = $1; $$->a2.p = NULL; } |
23b1539b PM |
785 | ; |
786 | ||
787 | print_list: /* EMPTY */ { $$ = NULL; } | |
995e5894 PM |
788 | | print_one { $$ = $1; } |
789 | | print_one ',' print_list { | |
23b1539b | 790 | if ($1) { |
995e5894 | 791 | $1->next = $3; |
23b1539b | 792 | $$ = $1; |
995e5894 | 793 | } else $$ = $3; |
23b1539b PM |
794 | } |
795 | ; | |
796 | ||
6dc7a0cb | 797 | var_listn: term { |
d3dd620b PM |
798 | $$ = f_new_inst(); |
799 | $$->code = 's'; | |
800 | $$->a1.p = NULL; | |
801 | $$->a2.p = $1; | |
802 | $$->next = NULL; | |
803 | } | |
6dc7a0cb | 804 | | term ',' var_listn { |
6542ece9 PM |
805 | $$ = f_new_inst(); |
806 | $$->code = 's'; | |
807 | $$->a1.p = NULL; | |
808 | $$->a2.p = $1; | |
809 | $$->next = $3; | |
810 | } | |
811 | ; | |
812 | ||
6dc7a0cb PM |
813 | var_list: /* EMPTY */ { $$ = NULL; } |
814 | | var_listn { $$ = $1; } | |
815 | ; | |
816 | ||
23b1539b | 817 | cmd: |
49955645 MM |
818 | IF term THEN block { |
819 | $$ = f_new_inst(); | |
820 | $$->code = '?'; | |
821 | $$->a1.p = $2; | |
822 | $$->a2.p = $4; | |
23b1539b | 823 | } |
49955645 MM |
824 | | IF term THEN block ELSE block { |
825 | struct f_inst *i = f_new_inst(); | |
826 | i->code = '?'; | |
827 | i->a1.p = $2; | |
828 | i->a2.p = $4; | |
23b1539b PM |
829 | $$ = f_new_inst(); |
830 | $$->code = '?'; | |
49955645 MM |
831 | $$->a1.p = i; |
832 | $$->a2.p = $6; | |
23b1539b | 833 | } |
ba921648 | 834 | | SYM '=' term ';' { |
84c7e194 | 835 | $$ = f_new_inst(); |
d4d75628 | 836 | DBG( "Ook, we'll set value\n" ); |
ba921648 | 837 | if (($1->class & ~T_MASK) != SYM_VARIABLE) |
a5a947d4 | 838 | cf_error( "You may set only variables." ); |
23b1539b | 839 | $$->code = 's'; |
2db3b288 PM |
840 | $$->a1.p = $1; |
841 | $$->a2.p = $3; | |
b9d70dc8 | 842 | } |
2d496d20 PM |
843 | | RETURN term ';' { |
844 | $$ = f_new_inst(); | |
d4d75628 | 845 | DBG( "Ook, we'll return the value\n" ); |
2d496d20 PM |
846 | $$->code = 'r'; |
847 | $$->a1.p = $2; | |
848 | } | |
db1326aa | 849 | | rtadot dynamic_attr '=' term ';' { |
6c14255d | 850 | $$ = $2; |
0dc4431c | 851 | $$->code = P('e','S'); |
6c14255d | 852 | $$->a1.p = $4; |
f31156ca | 853 | } |
0dc4431c PM |
854 | | rtadot static_attr '=' term ';' { |
855 | $$ = $2; | |
856 | if (!$$->a1.i) | |
857 | cf_error( "This static attribute is read-only."); | |
858 | $$->code = P('a','S'); | |
859 | $$->a1.p = $4; | |
860 | } | |
861 | | PREFERENCE '=' term ';' { | |
862 | $$ = f_new_inst(); | |
863 | $$->code = P('P','S'); | |
864 | $$->a1.p = $3; | |
865 | } | |
db1326aa | 866 | | UNSET '(' rtadot dynamic_attr ')' ';' { |
6c14255d | 867 | $$ = $4; |
9f4929e7 | 868 | $$->aux = EAF_TYPE_UNDEF | EAF_TEMP; |
2d496d20 | 869 | $$->code = P('e','S'); |
48f9e019 | 870 | $$->a1.p = NULL; |
c7b43f33 | 871 | } |
2d496d20 PM |
872 | | break_command print_list ';' { $$ = f_new_inst(); $$->code = P('p',','); $$->a1.p = $2; $$->a2.i = $1; } |
873 | | function_call ';' { $$ = $1; } | |
7db7b7db PM |
874 | | CASE term '{' switch_body '}' { |
875 | $$ = f_new_inst(); | |
2d496d20 | 876 | $$->code = P('S','W'); |
7db7b7db | 877 | $$->a1.p = $2; |
41be4444 | 878 | $$->a2.p = build_tree( $4 ); |
7db7b7db | 879 | } |
7d6eebae PM |
880 | |
881 | ||
42a0c054 | 882 | | rtadot dynamic_attr '.' EMPTY ';' { $$ = f_generate_empty($2); } |
7d6eebae | 883 | | rtadot dynamic_attr '.' PREPEND '(' term ')' ';' { $$ = f_generate_complex( P('A','p'), 'x', $2, $6 ); } |
42a0c054 OZ |
884 | | rtadot dynamic_attr '.' ADD '(' term ')' ';' { $$ = f_generate_complex( P('C','a'), 'a', $2, $6 ); } |
885 | | rtadot dynamic_attr '.' DELETE '(' term ')' ';' { $$ = f_generate_complex( P('C','a'), 'd', $2, $6 ); } | |
886 | | rtadot dynamic_attr '.' FILTER '(' term ')' ';' { $$ = f_generate_complex( P('C','a'), 'f', $2, $6 ); } | |
b9d70dc8 PM |
887 | ; |
888 | ||
889 | CF_END |