/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 2003-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                                                                      *
***********************************************************************/
#pragma prototyped

/*
 * sfio decode/encode discipline wrapper
 */

static const char	id[] = "codex";

#include <sfio_t.h>
#include <codex.h>
#include <namval.h>

static int
copy_close(Codex_t* code)
{
	return 0;
}

static Codexmeth_t	codex_copy =
{
	"copy",
	"No-op copy.",
	0,
	CODEX_DECODE|CODEX_ENCODE,
	0,
	0,
	0,
	copy_close,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
};

Codexstate_t	codexstate =
{
	"codex",
	codex_first,
	{ CODEX_VERSION },
	{ {0}, 0, 0, -1, 0, &codex_copy, 0},
};

static int
codex_except(Sfio_t* sp, int op, void* data, Sfdisc_t* disc)
{
	register Codex_t*	code = CODEX(disc);
	int			f;
	int			r;

	r = 0;
	switch (op)
	{
	case SF_ATEXIT:
		sfdisc(sp, SF_POPDISC);
		break;
	case SF_CLOSING:
	case SF_DPOP:
	case SF_FINAL:
		if (code->meth->syncf)
		{
			SFDCNEXT(sp, f);
			if (r = (*code->meth->syncf)(code))
				sp->_flags |= SF_ERROR;
			SFDCPREV(sp, f);
		}
		if (op != SF_CLOSING)
		{
			if (code->meth->donef)
			{
				SFDCNEXT(sp, f);
				r = (*code->meth->donef)(code);
				SFDCPREV(sp, f);
			}
			if (code->dp)
			{
				sfclose(code->dp);
				code->dp = 0;
			}
			if (code->flags & CODEX_ACTIVE)
				code->flags &= ~CODEX_ACTIVE;
			else
			{
				if (code->meth->closef)
					r = (*code->meth->closef)(code);
				else if (code->data)
					free(code->data);
				free(code);
			}
		}
		break;
	case SF_DBUFFER:
		r = 1;
		break;
	case SF_SYNC:
		if (code->meth->syncf && !(code->flags & CODEX_FLUSH))
		{
			SFDCNEXT(sp, f);
			if ((r = (*code->meth->syncf)(code)) < 0)
				sp->_flags |= SF_ERROR;
			SFDCPREV(sp, f);
		}
		break;
	case CODEX_DATA:
		code->flags |= CODEX_FLUSH;
		sfsync(sp);
		code->flags &= ~CODEX_FLUSH;
		if (data && code->meth->dataf && !(r = (*code->meth->dataf)(code, (Codexdata_t*)data)) && ((Codexdata_t*)data)->size)
			r = 1;
		break;
	case CODEX_GETPOS:
		if (!code->meth->seekf || (*((Sfoff_t*)data) = (*code->meth->seekf)(code, (Sfoff_t)0, SEEK_CUR)) < 0)
			r = -1;
		break;
	case CODEX_SETPOS:
		if (!code->meth->seekf || (*code->meth->seekf)(code, *((Sfoff_t*)data), SEEK_SET) < 0)
			r = -1;
		break;
	case CODEX_SIZE:
		if (!data)
			r = -1;
		else
			code->size = *((Sfoff_t*)data);
		break;
	}
	return r;
}

static int
trace_close(Codex_t* code)
{
	Codexmeth_t*	meth = code->meth->next;
	int		r;

	if (!meth->closef)
		return 0;
	r = (*meth->closef)(code);
	sfprintf(sfstderr, "codex: %d: %s: close()=%d\n", code->index, meth->name, r);
	free(meth);
	return r;
}

static int
trace_init(Codex_t* code)
{
	Codexmeth_t*	meth = code->meth->next;
	int		r;

	if (!meth->initf)
		return 0;
	r = (*meth->initf)(code);
	sfprintf(sfstderr, "codex: %d: %s: init()=%d\n", code->index, meth->name, r);
	return r;
}

static int
trace_done(Codex_t* code)
{
	Codexmeth_t*	meth = code->meth->next;
	int		r;

	if (!meth->donef)
		return 0;
	r = (*meth->donef)(code);
	sfprintf(sfstderr, "codex: %d: %s: done()=%d\n", code->index, meth->name, r);
	return r;
}

static ssize_t
trace_read(Sfio_t* f, void* buf, size_t n, Sfdisc_t* disc)
{
	Codexmeth_t*	meth = CODEX(disc)->meth->next;
	ssize_t		r;

	if (!meth->readf)
		return 0;
	r = (*meth->readf)(f, buf, n, disc);
	sfprintf(sfstderr, "codex: %d: %s: read(%I*u)=%I*d\n", CODEX(disc)->index, meth->name, sizeof(n), n, sizeof(r), r);
	return r;
}

static ssize_t
trace_write(Sfio_t* f, const void* buf, size_t n, Sfdisc_t* disc)
{
	Codexmeth_t*	meth = CODEX(disc)->meth->next;
	ssize_t		r;

	if (!meth->writef)
		return 0;
	r = (*meth->writef)(f, buf, n, disc);
	sfprintf(sfstderr, "codex: %d: %s: write(%I*u)=%I*d\n", CODEX(disc)->index, meth->name, sizeof(n), n, sizeof(r), r);
	return r;
}

static int
trace_except(Sfio_t* f, int op, void* data, Sfdisc_t* disc)
{
	Codexmeth_t*	meth = CODEX(disc)->meth->next;
	char*		event;
	int		r;
	char		tmp[8];

	r = codex_except(f, op, data, disc);
	switch (op)
	{
	case SF_ATEXIT:
		event = "ATEXIT";
		break;
	case SF_CLOSING:
		event = "CLOSING";
		break;
	case SF_DBUFFER:
		event = "DBUFFER";
		break;
	case SF_DPOLL:
		event = "DPOLL";
		break;
	case SF_DPOP:
		event = "DPOP";
		break;
	case SF_DPUSH:
		event = "DPUSH";
		break;
	case SF_FINAL:
		event = "FINAL";
		break;
	case SF_LOCKED:
		event = "LOCKED";
		break;
	case SF_NEW:
		event = "NEW";
		break;
	case SF_PURGE:
		event = "PURGE";
		break;
	case SF_READ:
		event = "READ";
		break;
	case SF_READY:
		event = "READY";
		break;
	case SF_SEEK:
		event = "SEEK";
		break;
	case SF_SYNC:
		event = "SYNC";
		break;
	case SF_WRITE:
		event = "WRITE";
		break;
	case CODEX_DATA:
		event = "CODEX_DATA";
		break;
	case CODEX_GETPOS:
		event = "CODEX_GETPOS";
		break;
	case CODEX_SETPOS:
		event = "CODEX_SETPOS";
		break;
	case CODEX_SIZE:
		event = "CODEX_SIZE";
		break;
	default:
		sfsprintf(event = tmp, sizeof(tmp), "%d", op);
		break;
	}
	sfprintf(sfstderr, "codex: %d: %s: except(%s,%p)=%d\n", CODEX(disc)->index, meth->name, event, data, r);
	return r;
}

static int
trace_sync(Codex_t* code)
{
	Codexmeth_t*	meth = code->meth->next;
	int		r;

	if (!meth->syncf)
		return 0;
	r = (*meth->syncf)(code);
	sfprintf(sfstderr, "codex: %d: %s: sync()=%d\n", code->index, meth->name, r);
	return r;
}

static Sfoff_t
trace_seek(Codex_t* code, Sfoff_t pos, int op)
{
	Codexmeth_t*	meth = code->meth->next;
	Sfoff_t		r;

	if (!meth->seekf)
		return 0;
	r = (*meth->seekf)(code, pos, op);
	sfprintf(sfstderr, "codex: %d: %s: seek(%I*d,%d)=%I*d\n", code->index, meth->name, sizeof(pos), pos, op, sizeof(r), r);
	return r;
}

static int
trace_data(Codex_t* code, Codexdata_t* data)
{
	Codexmeth_t*	meth = code->meth->next;
	int		r;
	unsigned char*	e;
	unsigned char*	u;

	if (!meth->dataf)
		return 0;
	r = (*meth->dataf)(code, data);
	sfprintf(sfstderr, "codex: %d: %s: data()=%d", code->index, meth->name, r);
	if (r >= 0)
	{
		if (data->buf)
			for (e = (u = (unsigned char*)data->buf) + data->size; u < e; u++)
				sfprintf(sfstderr, "%02x", *u);
		else
			sfprintf(sfstderr, "%0*I*x", data->size * 2, sizeof(data->num), data->num);
	}
	sfprintf(sfstderr, "\n");
	return r;
}

static Codexmeth_t	codex_trace =
{
	"trace",
	"Debug trace wrapper.",
	0,
	CODEX_DECODE|CODEX_ENCODE,
	0,
	0,
	0,
	trace_close,
	trace_init,
	trace_done,
	trace_read,
	trace_write,
	trace_sync,
	trace_seek,
	trace_data,
};

#define OPT_TRACE	1
#define OPT_VERBOSE	2

static const Namval_t		options[] =
{
	"trace",	OPT_TRACE,
	"verbose",	OPT_VERBOSE,
	0,		0
};

/*
 * called by stropt() to set options
 */

static int
setopt(void* a, const void* p, register int n, register const char* v)
{
	NoP(a);
	if (p)
		switch (((Namval_t*)p)->value)
		{
		case OPT_TRACE:
			codexstate.trace = n ? strdup(v) : (char*)0;
			break;
		case OPT_VERBOSE:
			codexstate.verbose = n ? strdup(v) : (char*)0;
			break;
		}
	return 0;
}

static void
save(Codexcache_t* cache, Codex_t* code, const char* name, int namelen, Codexnum_t flags)
{
	if (cache->code && cache->code != CODEXERROR)
	{
		if (cache->code->meth->closef)
			(*cache->code->meth->closef)(cache->code);
		else if (cache->code->data)
			free(cache->code->data);
		if (cache->code->dp)
			sfclose(cache->code->dp);
		free(cache->code);
	}
	cache->code = code;
	cache->flags = flags;
	strncopy(cache->name, name, namelen < sizeof(cache->name) ? (namelen + 1) : sizeof(cache->name));
	cache->cached = ++codexstate.cached;
}

/*
 * single sfio method discipline push of meth!=0 onto sp
 */

static int
push(Sfio_t* sp, const char* name, Codexnum_t flags, Codexdisc_t* disc, Codexmeth_t* meth)
{
	register char**		a;
	register char*		s;
	register char*		b;
	register int		c;
	char*			v;
	int			f;
	int			namelen;
	int			serial;
	Codexnum_t		deen;
	Sfoff_t			size;
	Codex_t*		code;
	Codexcache_t*		cache;
	Codexcache_t*		cp;
	Codexmeth_t*		trace;
	char*			arg[CODEX_ARGS];
	char			can[CODEX_NAME+CODEX_ARGS];

	/*
	 * check for matching inactive method in the cache
	 * this avoids openf/closef thrashing
	 */

	code = 0;
	cache = 0;
	deen = flags & (CODEX_DECODE|CODEX_ENCODE);
	s = (char*)name;
	namelen = (b = strchr(s, '+')) ? (b - s) : strlen(s);
	for (cp = codexstate.cache; cp < &codexstate.cache[elementsof(codexstate.cache)]; cp++)
		if (!cp->code)
			(cache = cp)->cached = 0;
		else if (!(cp->code->flags & CODEX_ACTIVE))
		{
			if (strneq(s, cp->name, namelen) && (cp->flags & deen) && (!cp->name[namelen] || cp->name[namelen] == '-' || cp->name[namelen] == '+'))
			{
				cp->cached = ++codexstate.cached;
				if ((code = (cache = cp)->code) == CODEXERROR)
				{
					if (disc->errorf)
						(*disc->errorf)(NiL, disc, 2, "%s: invalid method", s);
					goto bad;
				}
				break;
			}
			else if (!cache || cp->cached < cache->cached)
				cache = cp;
		}
	sfset(sp, SF_SHARE|SF_PUBLIC, 0);
	size = -1;
	if (!code)
	{
		if (!(code = newof(0, Codex_t, 1, 0)))
		{
			if (disc->errorf)
				(*disc->errorf)(NiL, disc, 2, "out of space");
			goto bad;
		}
		code->flags = deen;
		code->index = ++codexstate.index;
		code->sp = sp;
		code->disc = disc;
		code->meth = meth;
		a = arg;
		*a++ = (char*)s;
		*a++ = b = can;
		do
		{
			c = *s++;
			if (c == '-' || c == '+' || c == 0)
			{
				*b++ = 0;
				if (streq(*(a - 1), "RETAIN"))
				{
					flags |= CODEX_RETAIN;
					b = *(a - 1);
				}
				else if (strneq(*(a - 1), "SIZE=", 5))
				{
					size = strtoll(*(a - 1) + 5, NiL, 0);
					b = *(a - 1);
				}
				else if (strneq(*(a - 1), "SOURCE=", 7))
				{
					if (!(code->dp = sfopen(NiL, *(a - 1) + 7, "r")))
					{
						if (disc->errorf)
							(*disc->errorf)(NiL, disc, ERROR_SYSTEM|2, "%s: cannot read delta source", *(a - 1) + 7);
						goto bad;
					}
					b = *(a - 1);
				}
				else if (streq(*(a - 1), "TRACE"))
				{
					flags |= CODEX_TRACE;
					b = *(a - 1);
				}
				else if (streq(*(a - 1), "VERBOSE"))
				{
					flags |= CODEX_VERBOSE;
					b = *(a - 1);
				}
				else
				{
					if (a >= &arg[elementsof(arg)-1] || b >= &can[sizeof(can)-1])
						break;
					*a++ = b;
				}
			}
			else
				*b++ = c;
		} while (c && b < &can[sizeof(can)-1]);
		*b = 0;
		if (!**(a - 1))
			a--;
		*a = 0;
		s = arg[1];
		if ((*meth->openf)(code, arg, deen))
		{
			free(code);
			code = 0;
		}
		if (((flags & (CODEX_TRACE|CODEX_VERBOSE)) || codexstate.trace && strmatch(name, codexstate.trace) && (flags |= CODEX_TRACE) || codexstate.verbose && strmatch(name, codexstate.verbose) && (flags |= CODEX_VERBOSE)) && (trace = memdup(&codex_trace, sizeof(codex_trace))))
			sfprintf(sfstderr, "codex: %d: %s: open(\"%s\",%s,%s)\n", code ? code->index : 0, meth->name, arg[0], (sp->_flags & SF_READ) ? "READ" : "WRITE", (deen & CODEX_DECODE) ? "DECODE" : "ENCODE");
		else
			trace = 0;
		if (!code)
		{
			if (cache)
				save(cache, CODEXERROR, name, namelen, deen);
			goto bad;
		}
		if (!(meth = code->meth))
			meth = code->meth = &codex_copy;
		code->flags = deen | (code->meth->flags & ~(CODEX_DECODE|CODEX_ENCODE));
		if (trace)
		{
			trace->next = meth;
			code->meth = meth = trace;
			code->sfdisc.readf = trace_read;
			code->sfdisc.writef = trace_write;
			code->sfdisc.exceptf = (flags & CODEX_VERBOSE) ? trace_except : codex_except;
		}
		else
		{
			code->sfdisc.readf = meth->readf;
			code->sfdisc.writef = meth->writef;
			code->sfdisc.exceptf = codex_except;
		}
		if (cache)
			save(cache, code, name, namelen, deen);
	}
	else if (b)
		while (*b++)
		{
			v = b;
			for (v = b; *b && *b != '-' && *b != '+'; b++);
			if (strneq(v, "SIZE=", 5))
				size = strtoll(v + 5, NiL, 0);
			else if (strneq(v, "SOURCE=", 7))
			{
				v += 7;
				if ((b - v) >= sizeof(can))
				{
					if (disc->errorf)
						(*disc->errorf)(NiL, disc, ERROR_SYSTEM|2, "%-.*s: cannot delta source pathname too long", b - v, v);
					goto bad;
				}
				else
				{
					strncopy(can, v, b - v);
					if (!(code->dp = sfopen(NiL, can, "r")))
					{
						if (disc->errorf)
							(*disc->errorf)(NiL, disc, ERROR_SYSTEM|2, "%s: cannot read delta source", can);
						goto bad;
					}
				}
			}
		}
	if (cache)
		code->flags |= CODEX_ACTIVE;
	if (code->meth == &codex_copy)
	{
		c = 0;
		goto done;
	}
	code->sp = sp;
	code->size = size;
	code->disc = disc;
	code->flags |= CODEX_FLUSH;
	if (sfdisc(sp, &code->sfdisc) != &code->sfdisc)
	{
		if (disc->errorf)
			(*disc->errorf)(NiL, disc, 2, "%s: sfio discipline push error", name);
		goto bad;
	}
	code->serial = serial = codexstate.serial;
	code->flags &= ~CODEX_FLUSH;
	if (!(sp->_flags & SF_READ))
		sfset(sp, SF_IOCHECK, 1);
	if (code->meth->initf)
	{
		SFDCNEXT(sp, f);
		c = (*code->meth->initf)(code);
		SFDCPREV(sp, f);
		if (c)
			goto bad;
	}
	code->flags |= flags & (CODEX_RETAIN|CODEX_TRACE|CODEX_VERBOSE);
	return serial;
 bad:
	c = -1;
	codexpop(sp, NiL, serial);
 done:
	if (code)
	{
		if (cache)
			code->flags &= ~CODEX_ACTIVE;
		else
		{
			if (meth->closef)
				(*meth->closef)(code);
			else if (code->data)
				free(code->data);
			if (code->dp)
				sfclose(code->dp);
			free(code);
		}
	}
	return c;
}

/*
 * recursively identify and push sfcode() disciplines
 * to decode the SF_READ stream ip
 *
 * return:
 *	-1	error
 *	 0	no discipline pushed
 *	>0	discipline serial number
 */

static int
decodex(Sfio_t* ip, Codexnum_t flags, Codexdisc_t* disc)
{
	register Codexmeth_t*	meth;
	void*			hdr;
	size_t			siz;
	int			serial;
	int			i;
	char			buf[CODEX_NAME];

	sfset(ip, SF_SHARE|SF_PUBLIC, 0);
	serial = 0;
	for (;;)
	{
		siz = CODEX_IDENT;
		if (!(hdr = sfreserve(ip, siz, 1)) && (!(siz = sfvalue(ip)) || !(hdr = sfreserve(ip, siz, 1))))
			break;
		meth = codexid(hdr, siz, buf, sizeof(buf));
		sfread(ip, hdr, 0);
		if (!meth)
			break;
		if ((i = codex(ip, NiL, buf, flags, disc, meth)) < 0)
		{
			if (serial)
				codexpop(ip, NiL, serial);
			return -1;
		}
		if (i)
			serial = i;
	}
	return serial;
}

typedef struct Part_s
{
	char*		name;
	Codexmeth_t*	meth;
	Codexnum_t	flags;
} Part_t;

/*
 * push the codex method composition name onto ip/op
 */

int
codex(Sfio_t* ip, Sfio_t* op, const char* name, Codexnum_t flags, Codexdisc_t* disc, Codexmeth_t* meth)
{
	register char*		s;
	register Part_t*	p;
	register int		c;
	char*			m;
	Part_t*			b;
	Part_t*			e;
	Codexnum_t		f;
	Codexnum_t		decode;
	Codexnum_t		encode;
	int			invert;
	Part_t			part[256];

	if (!codexstate.initialized)
	{
		codexstate.initialized = 1;
		stropt(getenv("CODEX_OPTIONS"), options, sizeof(*options), setopt, NiL);
	}
	flags &= CODEX_INVERT|CODEX_RETAIN|CODEX_SERIAL|CODEX_TRACE|CODEX_VERBOSE;
	if (!(flags & CODEX_SERIAL))
	{
		flags |= CODEX_SERIAL;
		if (++codexstate.serial < 0)
			codexstate.serial = 1;
	}
	if (!disc)
		disc = &codexstate.disc;
	if (!(s = (char*)name) || !s[0] || s[0] == '-' && !s[1])
	{
		if (!meth)
			return ip ? decodex(ip, flags, disc) : -1;
		s = (char*)(name = meth->name);
	}

	/*
	 * split the method name into component parts
	 * and verify that each part is a known method
	 * CODEX_INVERT is dispatched here
	 */

	if (!(s = strdup(s)))
	{
		if (disc->errorf)
			(*disc->errorf)(NiL, disc, 2, "out of space");
		return -1;
	}
	m = s;
	if (flags & CODEX_INVERT)
	{
		flags &= ~CODEX_INVERT;
		invert = -1;
		decode = CODEX_ENCODE;
		encode = CODEX_DECODE;
		b = &part[elementsof(part)-1];
		e = &part[0];
	}
	else
	{
		invert = 1;
		decode = CODEX_DECODE;
		encode = CODEX_ENCODE;
		b = &part[0];
		e = &part[elementsof(part)-1];
	}
	p = b;
	if (*s == '<')
	{
		s++;
		p->flags = decode;
	}
	else if (*s == '>')
	{
		s++;
		p->flags = encode;
	}
	else
		p->flags = 0;
	p->name = s;
	do
	{
		if (!(c = *s++))
			;
		else if (c == '<')
			f = decode;
		else if (c == '>')
			f = encode;
		else if (c == '|' || c == '^')
			f = 0;
		else
			continue;
		*(s - 1) = 0;
		if (codexcmp(p->name, "copy"))
		{
			if (!(p->meth = codexmeth(p->name)))
			{
				if (disc->errorf)
					(*disc->errorf)(NiL, disc, 2, "%s: unknown coder", p->name);
				free(m);
				return -1;
			}
			if (!c)
				break;
			if (p == e)
			{
				if (disc->errorf)
					(*disc->errorf)(NiL, disc, 2, "%s: too many method components -- %d max", name, elementsof(part));
				free(m);
				return -1;
			}
			p += invert;
		}
		p->flags = f;
		p->name = s;
	} while (c);
	if (!*b->name)
	{
		free(m);
		return 0;
	}
	if (invert < 0)
	{
		e = b;
		b = p;
	}
	else
		e = p;

	/*
	 * assign CODEX_DECODE or CODEX_ENCODE to each part and
	 * verfy that the method supports the assignment
	 * vcodex methods give us some leeway here
	 * otherwise CODEX_DECODE must be on the left and
	 * CODEX_ENCODE must be on the right
	 * CODEX_INVERT has already been handled in the previous loop
	 */

	f = 0;
	if (ip)
		f |= CODEX_DECODE;
	if (op)
		f |= CODEX_ENCODE;
	p = b;
	for (;;)
	{
		if (p->flags == CODEX_ENCODE || p->flags != CODEX_DECODE && !(f & CODEX_DECODE))
		{
			p->flags = CODEX_ENCODE;
			f &= ~CODEX_DECODE;
		}
		else
			p->flags = CODEX_DECODE;
		if (!(p->flags & p->meth->flags) || !p->meth->vcmeth && !(p->flags & f))
		{
			if (disc->errorf)
			{
				if (e != &part[0])
					(*disc->errorf)(NiL, disc, 2, "%s: %s: cannot %s", name, p->name, p->flags == CODEX_DECODE ? ERROR_translate(NiL, NiL, id, "decode") : ERROR_translate(NiL, NiL, id, "encode"));
				else
					(*disc->errorf)(NiL, disc, 2, "%s: cannot %s", p->name, p->flags == CODEX_DECODE ? ERROR_translate(NiL, NiL, id, "decode") : ERROR_translate(NiL, NiL, id, "encode"));
			}
			free(m);
			return -1;
		}
		if (p == e)
			break;
		p++;
	}
#if 1
	p = b;
	for (;;)
	{
		error(-1, "AHA codex [%d] %u %s \"%s\"", p - b, p->flags, p->meth->name, p->name);
		if (p == e)
			break;
		p++;
	}
#endif

	/*
	 * input decode method sfio disciplines pushed from left to right
	 */

	p = b;
	for (;;)
	{
		if (p->flags == CODEX_ENCODE)
			break;
		if (push(ip, p->name, CODEX_DECODE|flags, disc, p->meth) < 0)
		{
			free(m);
			codexpop(ip, op, codexstate.serial);
			return -1;
		}
		if (p == e)
			break;
		p++;
	}

	/*
	 * output encode method sfio disciplines pushed from right to left
	 */

	p = e;
	for (;;)
	{
		if (p->flags == CODEX_DECODE)
			break;
		if (push(op, p->name, CODEX_ENCODE|flags, disc, p->meth) < 0)
		{
			free(m);
			codexpop(ip, op, codexstate.serial);
			return -1;
		}
		if (p == b)
			break;
		p--;
	}
	free(m);
	return codexstate.serial;
}

/*
 * pop contiguous ip and op codex disciplines matching serial number
 * serial==0 pops all contiguous codex disciplines
 */

int
codexpop(register Sfio_t* ip, register Sfio_t* op, int serial)
{
	int	pop;

	pop = 0;
	if (serial >= 0)
	{
		if (ip)
			while (ip->disc && (ip->disc->exceptf == codex_except || ip->disc->exceptf == trace_except) && (!serial || CODEX(ip->disc)->serial == serial))
				if (sfdisc(ip, SF_POPDISC))
					pop++;
		if (op)
			while (op->disc && (op->disc->exceptf == codex_except || op->disc->exceptf == trace_except) && (!serial || CODEX(op->disc)->serial == serial))
				if (sfdisc(op, SF_POPDISC))
					pop++;
	}
	return pop;
}
