mirror of
				https://git.proxmox.com/git/mirror_iproute2
				synced 2025-11-04 02:56:43 +00:00 
			
		
		
		
	This big patch was compiled by vimgrepping for memset calls and changing to C99 initializer if applicable. One notable exception is the initialization of union bpf_attr in tc/tc_bpf.c: changing it would break for older gcc versions (at least <=3.4.6). Calls to memset for struct rtattr pointer fields for parse_rtattr*() were just dropped since they are not needed. The changes here allowed the compiler to discover some unused variables, so get rid of them, too. Signed-off-by: Phil Sutter <phil@nwl.cc> Acked-by: David Ahern <dsa@cumulusnetworks.com>
		
			
				
	
	
		
			571 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			571 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * m_ematch.c		Extended Matches
 | 
						|
 *
 | 
						|
 *		This program is free software; you can distribute it and/or
 | 
						|
 *		modify it under the terms of the GNU General Public License
 | 
						|
 *		as published by the Free Software Foundation; either version
 | 
						|
 *		2 of the License, or (at your option) any later version.
 | 
						|
 *
 | 
						|
 * Authors:	Thomas Graf <tgraf@suug.ch>
 | 
						|
 */
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <syslog.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <sys/socket.h>
 | 
						|
#include <netinet/in.h>
 | 
						|
#include <arpa/inet.h>
 | 
						|
#include <string.h>
 | 
						|
#include <dlfcn.h>
 | 
						|
#include <stdarg.h>
 | 
						|
#include <errno.h>
 | 
						|
 | 
						|
#include "utils.h"
 | 
						|
#include "tc_util.h"
 | 
						|
#include "m_ematch.h"
 | 
						|
 | 
						|
#define EMATCH_MAP "/etc/iproute2/ematch_map"
 | 
						|
 | 
						|
static struct ematch_util *ematch_list;
 | 
						|
 | 
						|
/* export to bison parser */
 | 
						|
int ematch_argc;
 | 
						|
char **ematch_argv;
 | 
						|
char *ematch_err;
 | 
						|
struct ematch *ematch_root;
 | 
						|
 | 
						|
static int begin_argc;
 | 
						|
static char **begin_argv;
 | 
						|
 | 
						|
static inline void map_warning(int num, char *kind)
 | 
						|
{
 | 
						|
	fprintf(stderr,
 | 
						|
	    "Error: Unable to find ematch \"%s\" in %s\n" \
 | 
						|
	    "Please assign a unique ID to the ematch kind the suggested " \
 | 
						|
	    "entry is:\n" \
 | 
						|
	    "\t%d\t%s\n",
 | 
						|
	    kind, EMATCH_MAP, num, kind);
 | 
						|
}
 | 
						|
 | 
						|
static int lookup_map(__u16 num, char *dst, int len, const char *file)
 | 
						|
{
 | 
						|
	int err = -EINVAL;
 | 
						|
	char buf[512];
 | 
						|
	FILE *fd = fopen(file, "r");
 | 
						|
 | 
						|
	if (fd == NULL)
 | 
						|
		return -errno;
 | 
						|
 | 
						|
	while (fgets(buf, sizeof(buf), fd)) {
 | 
						|
		char namebuf[512], *p = buf;
 | 
						|
		int id;
 | 
						|
 | 
						|
		while (*p == ' ' || *p == '\t')
 | 
						|
			p++;
 | 
						|
		if (*p == '#' || *p == '\n' || *p == 0)
 | 
						|
			continue;
 | 
						|
 | 
						|
		if (sscanf(p, "%d %s", &id, namebuf) != 2) {
 | 
						|
			fprintf(stderr, "ematch map %s corrupted at %s\n",
 | 
						|
			    file, p);
 | 
						|
			goto out;
 | 
						|
		}
 | 
						|
 | 
						|
		if (id == num) {
 | 
						|
			if (dst)
 | 
						|
				strncpy(dst, namebuf, len - 1);
 | 
						|
			err = 0;
 | 
						|
			goto out;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	err = -ENOENT;
 | 
						|
out:
 | 
						|
	fclose(fd);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
static int lookup_map_id(char *kind, int *dst, const char *file)
 | 
						|
{
 | 
						|
	int err = -EINVAL;
 | 
						|
	char buf[512];
 | 
						|
	FILE *fd = fopen(file, "r");
 | 
						|
 | 
						|
	if (fd == NULL)
 | 
						|
		return -errno;
 | 
						|
 | 
						|
	while (fgets(buf, sizeof(buf), fd)) {
 | 
						|
		char namebuf[512], *p = buf;
 | 
						|
		int id;
 | 
						|
 | 
						|
		while (*p == ' ' || *p == '\t')
 | 
						|
			p++;
 | 
						|
		if (*p == '#' || *p == '\n' || *p == 0)
 | 
						|
			continue;
 | 
						|
 | 
						|
		if (sscanf(p, "%d %s", &id, namebuf) != 2) {
 | 
						|
			fprintf(stderr, "ematch map %s corrupted at %s\n",
 | 
						|
			    file, p);
 | 
						|
			goto out;
 | 
						|
		}
 | 
						|
 | 
						|
		if (!strcasecmp(namebuf, kind)) {
 | 
						|
			if (dst)
 | 
						|
				*dst = id;
 | 
						|
			err = 0;
 | 
						|
			goto out;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	err = -ENOENT;
 | 
						|
	*dst = 0;
 | 
						|
out:
 | 
						|
	fclose(fd);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
static struct ematch_util *get_ematch_kind(char *kind)
 | 
						|
{
 | 
						|
	static void *body;
 | 
						|
	void *dlh;
 | 
						|
	char buf[256];
 | 
						|
	struct ematch_util *e;
 | 
						|
 | 
						|
	for (e = ematch_list; e; e = e->next) {
 | 
						|
		if (strcmp(e->kind, kind) == 0)
 | 
						|
			return e;
 | 
						|
	}
 | 
						|
 | 
						|
	snprintf(buf, sizeof(buf), "em_%s.so", kind);
 | 
						|
	dlh = dlopen(buf, RTLD_LAZY);
 | 
						|
	if (dlh == NULL) {
 | 
						|
		dlh = body;
 | 
						|
		if (dlh == NULL) {
 | 
						|
			dlh = body = dlopen(NULL, RTLD_LAZY);
 | 
						|
			if (dlh == NULL)
 | 
						|
				return NULL;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	snprintf(buf, sizeof(buf), "%s_ematch_util", kind);
 | 
						|
	e = dlsym(dlh, buf);
 | 
						|
	if (e == NULL)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	e->next = ematch_list;
 | 
						|
	ematch_list = e;
 | 
						|
 | 
						|
	return e;
 | 
						|
}
 | 
						|
 | 
						|
static struct ematch_util *get_ematch_kind_num(__u16 kind)
 | 
						|
{
 | 
						|
	char name[32];
 | 
						|
 | 
						|
	if (lookup_map(kind, name, sizeof(name), EMATCH_MAP) < 0)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	return get_ematch_kind(name);
 | 
						|
}
 | 
						|
 | 
						|
static int parse_tree(struct nlmsghdr *n, struct ematch *tree)
 | 
						|
{
 | 
						|
	int index = 1;
 | 
						|
	struct ematch *t;
 | 
						|
 | 
						|
	for (t = tree; t; t = t->next) {
 | 
						|
		struct rtattr *tail = NLMSG_TAIL(n);
 | 
						|
		struct tcf_ematch_hdr hdr = { .flags = t->relation };
 | 
						|
 | 
						|
		if (t->inverted)
 | 
						|
			hdr.flags |= TCF_EM_INVERT;
 | 
						|
 | 
						|
		addattr_l(n, MAX_MSG, index++, NULL, 0);
 | 
						|
 | 
						|
		if (t->child) {
 | 
						|
			__u32 r = t->child_ref;
 | 
						|
 | 
						|
			addraw_l(n, MAX_MSG, &hdr, sizeof(hdr));
 | 
						|
			addraw_l(n, MAX_MSG, &r, sizeof(r));
 | 
						|
		} else {
 | 
						|
			int num = 0, err;
 | 
						|
			char buf[64];
 | 
						|
			struct ematch_util *e;
 | 
						|
 | 
						|
			if (t->args == NULL)
 | 
						|
				return -1;
 | 
						|
 | 
						|
			strncpy(buf, (char *) t->args->data, sizeof(buf)-1);
 | 
						|
			e = get_ematch_kind(buf);
 | 
						|
			if (e == NULL) {
 | 
						|
				fprintf(stderr, "Unknown ematch \"%s\"\n",
 | 
						|
				    buf);
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
 | 
						|
			err = lookup_map_id(buf, &num, EMATCH_MAP);
 | 
						|
			if (err < 0) {
 | 
						|
				if (err == -ENOENT)
 | 
						|
					map_warning(e->kind_num, buf);
 | 
						|
				return err;
 | 
						|
			}
 | 
						|
 | 
						|
			hdr.kind = num;
 | 
						|
			if (e->parse_eopt(n, &hdr, t->args->next) < 0)
 | 
						|
				return -1;
 | 
						|
		}
 | 
						|
 | 
						|
		tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int flatten_tree(struct ematch *head, struct ematch *tree)
 | 
						|
{
 | 
						|
	int i, count = 0;
 | 
						|
	struct ematch *t;
 | 
						|
 | 
						|
	for (;;) {
 | 
						|
		count++;
 | 
						|
 | 
						|
		if (tree->child) {
 | 
						|
			for (t = head; t->next; t = t->next);
 | 
						|
			t->next = tree->child;
 | 
						|
			count += flatten_tree(head, tree->child);
 | 
						|
		}
 | 
						|
 | 
						|
		if (tree->relation == 0)
 | 
						|
			break;
 | 
						|
 | 
						|
		tree = tree->next;
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0, t = head; t; t = t->next, i++)
 | 
						|
		t->index = i;
 | 
						|
 | 
						|
	for (t = head; t; t = t->next)
 | 
						|
		if (t->child)
 | 
						|
			t->child_ref = t->child->index;
 | 
						|
 | 
						|
	return count;
 | 
						|
}
 | 
						|
 | 
						|
int em_parse_error(int err, struct bstr *args, struct bstr *carg,
 | 
						|
		   struct ematch_util *e, char *fmt, ...)
 | 
						|
{
 | 
						|
	va_list a;
 | 
						|
 | 
						|
	va_start(a, fmt);
 | 
						|
	vfprintf(stderr, fmt, a);
 | 
						|
	va_end(a);
 | 
						|
 | 
						|
	if (ematch_err)
 | 
						|
		fprintf(stderr, ": %s\n... ", ematch_err);
 | 
						|
	else
 | 
						|
		fprintf(stderr, "\n... ");
 | 
						|
 | 
						|
	while (ematch_argc < begin_argc) {
 | 
						|
		if (ematch_argc == (begin_argc - 1))
 | 
						|
			fprintf(stderr, ">>%s<< ", *begin_argv);
 | 
						|
		else
 | 
						|
			fprintf(stderr, "%s ", *begin_argv);
 | 
						|
		begin_argv++;
 | 
						|
		begin_argc--;
 | 
						|
	}
 | 
						|
 | 
						|
	fprintf(stderr, "...\n");
 | 
						|
 | 
						|
	if (args) {
 | 
						|
		fprintf(stderr, "... %s(", e->kind);
 | 
						|
		while (args) {
 | 
						|
			fprintf(stderr, "%s", args == carg ? ">>" : "");
 | 
						|
			bstr_print(stderr, args, 1);
 | 
						|
			fprintf(stderr, "%s%s", args == carg ? "<<" : "",
 | 
						|
			    args->next ? " " : "");
 | 
						|
			args = args->next;
 | 
						|
		}
 | 
						|
		fprintf(stderr, ")...\n");
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	if (e == NULL) {
 | 
						|
		fprintf(stderr,
 | 
						|
		    "Usage: EXPR\n" \
 | 
						|
		    "where: EXPR  := TERM [ { and | or } EXPR ]\n" \
 | 
						|
		    "       TERM  := [ not ] { MATCH | '(' EXPR ')' }\n" \
 | 
						|
		    "       MATCH := module '(' ARGS ')'\n" \
 | 
						|
		    "       ARGS := ARG1 ARG2 ...\n" \
 | 
						|
		    "\n" \
 | 
						|
		    "Example: a(x y) and not (b(x) or c(x y z))\n");
 | 
						|
	} else
 | 
						|
		e->print_usage(stderr);
 | 
						|
 | 
						|
	return -err;
 | 
						|
}
 | 
						|
 | 
						|
static inline void free_ematch_err(void)
 | 
						|
{
 | 
						|
	if (ematch_err) {
 | 
						|
		free(ematch_err);
 | 
						|
		ematch_err = NULL;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
extern int ematch_parse(void);
 | 
						|
 | 
						|
int parse_ematch(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
 | 
						|
{
 | 
						|
	begin_argc = ematch_argc = *argc_p;
 | 
						|
	begin_argv = ematch_argv = *argv_p;
 | 
						|
 | 
						|
	if (ematch_parse()) {
 | 
						|
		int err = em_parse_error(EINVAL, NULL, NULL, NULL,
 | 
						|
		    "Parse error");
 | 
						|
		free_ematch_err();
 | 
						|
		return err;
 | 
						|
	}
 | 
						|
 | 
						|
	free_ematch_err();
 | 
						|
 | 
						|
	/* undo look ahead by parser */
 | 
						|
	ematch_argc++;
 | 
						|
	ematch_argv--;
 | 
						|
 | 
						|
	if (ematch_root) {
 | 
						|
		struct rtattr *tail, *tail_list;
 | 
						|
 | 
						|
		struct tcf_ematch_tree_hdr hdr = {
 | 
						|
			.nmatches = flatten_tree(ematch_root, ematch_root),
 | 
						|
			.progid = TCF_EM_PROG_TC
 | 
						|
		};
 | 
						|
 | 
						|
		tail = NLMSG_TAIL(n);
 | 
						|
		addattr_l(n, MAX_MSG, tca_id, NULL, 0);
 | 
						|
		addattr_l(n, MAX_MSG, TCA_EMATCH_TREE_HDR, &hdr, sizeof(hdr));
 | 
						|
 | 
						|
		tail_list = NLMSG_TAIL(n);
 | 
						|
		addattr_l(n, MAX_MSG, TCA_EMATCH_TREE_LIST, NULL, 0);
 | 
						|
 | 
						|
		if (parse_tree(n, ematch_root) < 0)
 | 
						|
			return -1;
 | 
						|
 | 
						|
		tail_list->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail_list;
 | 
						|
		tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
 | 
						|
	}
 | 
						|
 | 
						|
	*argc_p = ematch_argc;
 | 
						|
	*argv_p = ematch_argv;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int print_ematch_seq(FILE *fd, struct rtattr **tb, int start,
 | 
						|
			    int prefix)
 | 
						|
{
 | 
						|
	int n, i = start;
 | 
						|
	struct tcf_ematch_hdr *hdr;
 | 
						|
	int dlen;
 | 
						|
	void *data;
 | 
						|
 | 
						|
	for (;;) {
 | 
						|
		if (tb[i] == NULL)
 | 
						|
			return -1;
 | 
						|
 | 
						|
		dlen = RTA_PAYLOAD(tb[i]) - sizeof(*hdr);
 | 
						|
		data = (void *) RTA_DATA(tb[i]) + sizeof(*hdr);
 | 
						|
 | 
						|
		if (dlen < 0)
 | 
						|
			return -1;
 | 
						|
 | 
						|
		hdr = RTA_DATA(tb[i]);
 | 
						|
 | 
						|
		if (hdr->flags & TCF_EM_INVERT)
 | 
						|
			fprintf(fd, "NOT ");
 | 
						|
 | 
						|
		if (hdr->kind == 0) {
 | 
						|
			__u32 ref;
 | 
						|
 | 
						|
			if (dlen < sizeof(__u32))
 | 
						|
				return -1;
 | 
						|
 | 
						|
			ref = *(__u32 *) data;
 | 
						|
			fprintf(fd, "(\n");
 | 
						|
			for (n = 0; n <= prefix; n++)
 | 
						|
				fprintf(fd, "  ");
 | 
						|
			if (print_ematch_seq(fd, tb, ref + 1, prefix + 1) < 0)
 | 
						|
				return -1;
 | 
						|
			for (n = 0; n < prefix; n++)
 | 
						|
				fprintf(fd, "  ");
 | 
						|
			fprintf(fd, ") ");
 | 
						|
 | 
						|
		} else {
 | 
						|
			struct ematch_util *e;
 | 
						|
 | 
						|
			e = get_ematch_kind_num(hdr->kind);
 | 
						|
			if (e == NULL)
 | 
						|
				fprintf(fd, "[unknown ematch %d]\n",
 | 
						|
				    hdr->kind);
 | 
						|
			else {
 | 
						|
				fprintf(fd, "%s(", e->kind);
 | 
						|
				if (e->print_eopt(fd, hdr, data, dlen) < 0)
 | 
						|
					return -1;
 | 
						|
				fprintf(fd, ")\n");
 | 
						|
			}
 | 
						|
			if (hdr->flags & TCF_EM_REL_MASK)
 | 
						|
				for (n = 0; n < prefix; n++)
 | 
						|
					fprintf(fd, "  ");
 | 
						|
		}
 | 
						|
 | 
						|
		switch (hdr->flags & TCF_EM_REL_MASK) {
 | 
						|
			case TCF_EM_REL_AND:
 | 
						|
				fprintf(fd, "AND ");
 | 
						|
				break;
 | 
						|
 | 
						|
			case TCF_EM_REL_OR:
 | 
						|
				fprintf(fd, "OR ");
 | 
						|
				break;
 | 
						|
 | 
						|
			default:
 | 
						|
				return 0;
 | 
						|
		}
 | 
						|
 | 
						|
		i++;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int print_ematch_list(FILE *fd, struct tcf_ematch_tree_hdr *hdr,
 | 
						|
			     struct rtattr *rta)
 | 
						|
{
 | 
						|
	int err = -1;
 | 
						|
	struct rtattr **tb;
 | 
						|
 | 
						|
	tb = malloc((hdr->nmatches + 1) * sizeof(struct rtattr *));
 | 
						|
	if (tb == NULL)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	if (hdr->nmatches > 0) {
 | 
						|
		if (parse_rtattr_nested(tb, hdr->nmatches, rta) < 0)
 | 
						|
			goto errout;
 | 
						|
 | 
						|
		fprintf(fd, "\n  ");
 | 
						|
		if (print_ematch_seq(fd, tb, 1, 1) < 0)
 | 
						|
			goto errout;
 | 
						|
	}
 | 
						|
 | 
						|
	err = 0;
 | 
						|
errout:
 | 
						|
	free(tb);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
int print_ematch(FILE *fd, const struct rtattr *rta)
 | 
						|
{
 | 
						|
	struct rtattr *tb[TCA_EMATCH_TREE_MAX+1];
 | 
						|
	struct tcf_ematch_tree_hdr *hdr;
 | 
						|
 | 
						|
	if (parse_rtattr_nested(tb, TCA_EMATCH_TREE_MAX, rta) < 0)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	if (tb[TCA_EMATCH_TREE_HDR] == NULL) {
 | 
						|
		fprintf(stderr, "Missing ematch tree header\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (tb[TCA_EMATCH_TREE_LIST] == NULL) {
 | 
						|
		fprintf(stderr, "Missing ematch tree list\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (RTA_PAYLOAD(tb[TCA_EMATCH_TREE_HDR]) < sizeof(*hdr)) {
 | 
						|
		fprintf(stderr, "Ematch tree header size mismatch\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	hdr = RTA_DATA(tb[TCA_EMATCH_TREE_HDR]);
 | 
						|
 | 
						|
	return print_ematch_list(fd, hdr, tb[TCA_EMATCH_TREE_LIST]);
 | 
						|
}
 | 
						|
 | 
						|
struct bstr *bstr_alloc(const char *text)
 | 
						|
{
 | 
						|
	struct bstr *b = calloc(1, sizeof(*b));
 | 
						|
 | 
						|
	if (b == NULL)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	b->data = strdup(text);
 | 
						|
	if (b->data == NULL) {
 | 
						|
		free(b);
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	b->len = strlen(text);
 | 
						|
 | 
						|
	return b;
 | 
						|
}
 | 
						|
 | 
						|
unsigned long bstrtoul(const struct bstr *b)
 | 
						|
{
 | 
						|
	char *inv = NULL;
 | 
						|
	unsigned long l;
 | 
						|
	char buf[b->len+1];
 | 
						|
 | 
						|
	memcpy(buf, b->data, b->len);
 | 
						|
	buf[b->len] = '\0';
 | 
						|
 | 
						|
	l = strtoul(buf, &inv, 0);
 | 
						|
	if (l == ULONG_MAX || inv == buf)
 | 
						|
		return ULONG_MAX;
 | 
						|
 | 
						|
	return l;
 | 
						|
}
 | 
						|
 | 
						|
void bstr_print(FILE *fd, const struct bstr *b, int ascii)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	char *s = b->data;
 | 
						|
 | 
						|
	if (ascii)
 | 
						|
		for (i = 0; i < b->len; i++)
 | 
						|
		    fprintf(fd, "%c", isprint(s[i]) ? s[i] : '.');
 | 
						|
	else {
 | 
						|
		for (i = 0; i < b->len; i++)
 | 
						|
		    fprintf(fd, "%02x", s[i]);
 | 
						|
		fprintf(fd, "\"");
 | 
						|
		for (i = 0; i < b->len; i++)
 | 
						|
		    fprintf(fd, "%c", isprint(s[i]) ? s[i] : '.');
 | 
						|
		fprintf(fd, "\"");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void print_ematch_tree(const struct ematch *tree)
 | 
						|
{
 | 
						|
	const struct ematch *t;
 | 
						|
 | 
						|
	for (t = tree; t; t = t->next) {
 | 
						|
		if (t->inverted)
 | 
						|
			printf("NOT ");
 | 
						|
 | 
						|
		if (t->child) {
 | 
						|
			printf("(");
 | 
						|
			print_ematch_tree(t->child);
 | 
						|
			printf(")");
 | 
						|
		} else {
 | 
						|
			struct bstr *b;
 | 
						|
 | 
						|
			for (b = t->args; b; b = b->next)
 | 
						|
				printf("%s%s", b->data, b->next ? " " : "");
 | 
						|
		}
 | 
						|
 | 
						|
		if (t->relation == TCF_EM_REL_AND)
 | 
						|
			printf(" AND ");
 | 
						|
		else if (t->relation == TCF_EM_REL_OR)
 | 
						|
			printf(" OR ");
 | 
						|
	}
 | 
						|
}
 |