xref: /titanic_44/usr/src/lib/libresolv2/common/isc/ctl_clnt.c (revision 9525b14bcdeb5b5f6f95ab27c2f48f18bd2ec829)
1 /*
2  * Copyright (C) 2004, 2005, 2007, 2008  Internet Systems Consortium, Inc. ("ISC")
3  * Copyright (C) 1998-2003  Internet Software Consortium.
4  *
5  * Permission to use, copy, modify, and/or distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15  * PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #if !defined(lint) && !defined(SABER)
19 static const char rcsid[] = "$Id: ctl_clnt.c,v 1.11 2008/11/14 02:36:51 marka Exp $";
20 #endif /* not lint */
21 
22 /* Extern. */
23 
24 #include "port_before.h"
25 
26 #include <sys/param.h>
27 #include <sys/file.h>
28 #include <sys/socket.h>
29 
30 #include <netinet/in.h>
31 #include <arpa/nameser.h>
32 #include <arpa/inet.h>
33 
34 #include <ctype.h>
35 #include <errno.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <time.h>
40 #include <unistd.h>
41 #ifdef HAVE_MEMORY_H
42 #include <memory.h>
43 #endif
44 
45 #include <isc/assertions.h>
46 #include <isc/ctl.h>
47 #include <isc/eventlib.h>
48 #include <isc/list.h>
49 #include <isc/memcluster.h>
50 
51 #include "ctl_p.h"
52 
53 #include "port_after.h"
54 
55 /* Constants. */
56 
57 
58 /* Macros. */
59 
60 #define donefunc_p(ctx) ((ctx).donefunc != NULL)
61 #define arpacode_p(line) (isdigit((unsigned char)(line[0])) && \
62 			  isdigit((unsigned char)(line[1])) && \
63 			  isdigit((unsigned char)(line[2])))
64 #define arpacont_p(line) (line[3] == '-')
65 #define arpadone_p(line) (line[3] == ' ' || line[3] == '\t' || \
66 			  line[3] == '\r' || line[3] == '\0')
67 
68 /* Types. */
69 
70 enum state {
71 	initializing = 0, connecting, connected, destroyed
72 };
73 
74 struct ctl_tran {
75 	LINK(struct ctl_tran)	link;
76 	LINK(struct ctl_tran)	wlink;
77 	struct ctl_cctx *	ctx;
78 	struct ctl_buf		outbuf;
79 	ctl_clntdone		donefunc;
80 	void *			uap;
81 };
82 
83 struct ctl_cctx {
84 	enum state		state;
85 	evContext		ev;
86 	int			sock;
87 	ctl_logfunc		logger;
88 	ctl_clntdone		donefunc;
89 	void *			uap;
90 	evConnID		coID;
91 	evTimerID		tiID;
92 	evFileID		rdID;
93 	evStreamID		wrID;
94 	struct ctl_buf		inbuf;
95 	struct timespec		timeout;
96 	LIST(struct ctl_tran)	tran;
97 	LIST(struct ctl_tran)	wtran;
98 };
99 
100 /* Forward. */
101 
102 static struct ctl_tran *new_tran(struct ctl_cctx *, ctl_clntdone, void *, int);
103 static void		start_write(struct ctl_cctx *);
104 static void		destroy(struct ctl_cctx *, int);
105 static void		error(struct ctl_cctx *);
106 static void		new_state(struct ctl_cctx *, enum state);
107 static void		conn_done(evContext, void *, int,
108 				  const void *, int,
109 				  const void *, int);
110 static void		write_done(evContext, void *, int, int);
111 static void		start_read(struct ctl_cctx *);
112 static void		stop_read(struct ctl_cctx *);
113 static void		readable(evContext, void *, int, int);
114 static void		start_timer(struct ctl_cctx *);
115 static void		stop_timer(struct ctl_cctx *);
116 static void		touch_timer(struct ctl_cctx *);
117 static void		timer(evContext, void *,
118 			      struct timespec, struct timespec);
119 
120 #ifndef HAVE_MEMCHR
121 static void *
memchr(const void * b,int c,size_t len)122 memchr(const void *b, int c, size_t len) {
123 	const unsigned char *p = b;
124 	size_t i;
125 
126 	for (i = 0; i < len; i++, p++)
127 		if (*p == (unsigned char)c)
128 			return ((void *)p);
129 	return (NULL);
130 }
131 #endif
132 
133 /* Private data. */
134 
135 static const char * const state_names[] = {
136 	"initializing", "connecting", "connected", "destroyed"
137 };
138 
139 /* Public. */
140 
141 /*%
142  * void
143  * ctl_client()
144  *	create, condition, and connect to a listener on the control port.
145  */
146 struct ctl_cctx *
ctl_client(evContext lev,const struct sockaddr * cap,size_t cap_len,const struct sockaddr * sap,size_t sap_len,ctl_clntdone donefunc,void * uap,u_int timeout,ctl_logfunc logger)147 ctl_client(evContext lev, const struct sockaddr *cap, size_t cap_len,
148 	   const struct sockaddr *sap, size_t sap_len,
149 	   ctl_clntdone donefunc, void *uap,
150 	   u_int timeout, ctl_logfunc logger)
151 {
152 	static const char me[] = "ctl_client";
153 	static const int on = 1;
154 	struct ctl_cctx *ctx;
155 	struct sockaddr *captmp;
156 
157 	if (logger == NULL)
158 		logger = ctl_logger;
159 	ctx = memget(sizeof *ctx);
160 	if (ctx == NULL) {
161 		(*logger)(ctl_error, "%s: getmem: %s", me, strerror(errno));
162 		goto fatal;
163 	}
164 	ctx->state = initializing;
165 	ctx->ev = lev;
166 	ctx->logger = logger;
167 	ctx->timeout = evConsTime(timeout, 0);
168 	ctx->donefunc = donefunc;
169 	ctx->uap = uap;
170 	ctx->coID.opaque = NULL;
171 	ctx->tiID.opaque = NULL;
172 	ctx->rdID.opaque = NULL;
173 	ctx->wrID.opaque = NULL;
174 	buffer_init(ctx->inbuf);
175 	INIT_LIST(ctx->tran);
176 	INIT_LIST(ctx->wtran);
177 	ctx->sock = socket(sap->sa_family, SOCK_STREAM, PF_UNSPEC);
178 	if (ctx->sock > evHighestFD(ctx->ev)) {
179 		ctx->sock = -1;
180 		errno = ENOTSOCK;
181 	}
182 	if (ctx->sock < 0) {
183 		(*ctx->logger)(ctl_error, "%s: socket: %s",
184 			       me, strerror(errno));
185 		goto fatal;
186 	}
187 	if (cap != NULL) {
188 		if (setsockopt(ctx->sock, SOL_SOCKET, SO_REUSEADDR,
189 			       (const char *)&on, sizeof on) != 0) {
190 			(*ctx->logger)(ctl_warning,
191 				       "%s: setsockopt(REUSEADDR): %s",
192 				       me, strerror(errno));
193 		}
194 		DE_CONST(cap, captmp);
195 		if (bind(ctx->sock, captmp, cap_len) < 0) {
196 			(*ctx->logger)(ctl_error, "%s: bind: %s", me,
197 				       strerror(errno));
198 			goto fatal;
199 		}
200 	}
201 	if (evConnect(lev, ctx->sock, (const struct sockaddr *)sap, sap_len,
202 		      conn_done, ctx, &ctx->coID) < 0) {
203 		(*ctx->logger)(ctl_error, "%s: evConnect(fd %d): %s",
204 			       me, ctx->sock, strerror(errno));
205  fatal:
206 		if (ctx != NULL) {
207 			if (ctx->sock >= 0)
208 				close(ctx->sock);
209 			memput(ctx, sizeof *ctx);
210 		}
211 		return (NULL);
212 	}
213 	new_state(ctx, connecting);
214 	return (ctx);
215 }
216 
217 /*%
218  * void
219  * ctl_endclient(ctx)
220  *	close a client and release all of its resources.
221  */
222 void
ctl_endclient(struct ctl_cctx * ctx)223 ctl_endclient(struct ctl_cctx *ctx) {
224 	if (ctx->state != destroyed)
225 		destroy(ctx, 0);
226 	memput(ctx, sizeof *ctx);
227 }
228 
229 /*%
230  * int
231  * ctl_command(ctx, cmd, len, donefunc, uap)
232  *	Queue a transaction, which will begin with sending cmd
233  *	and complete by calling donefunc with the answer.
234  */
235 int
ctl_command(struct ctl_cctx * ctx,const char * cmd,size_t len,ctl_clntdone donefunc,void * uap)236 ctl_command(struct ctl_cctx *ctx, const char *cmd, size_t len,
237 	    ctl_clntdone donefunc, void *uap)
238 {
239 	struct ctl_tran *tran;
240 	char *pc;
241 	unsigned int n;
242 
243 	switch (ctx->state) {
244 	case destroyed:
245 		errno = ENOTCONN;
246 		return (-1);
247 	case connecting:
248 	case connected:
249 		break;
250 	default:
251 		abort();
252 	}
253 	if (len >= (size_t)MAX_LINELEN) {
254 		errno = EMSGSIZE;
255 		return (-1);
256 	}
257 	tran = new_tran(ctx, donefunc, uap, 1);
258 	if (tran == NULL)
259 		return (-1);
260 	if (ctl_bufget(&tran->outbuf, ctx->logger) < 0)
261 		return (-1);
262 	memcpy(tran->outbuf.text, cmd, len);
263 	tran->outbuf.used = len;
264 	for (pc = tran->outbuf.text, n = 0; n < tran->outbuf.used; pc++, n++)
265 		if (!isascii((unsigned char)*pc) ||
266 		    !isprint((unsigned char)*pc))
267 			*pc = '\040';
268 	start_write(ctx);
269 	return (0);
270 }
271 
272 /* Private. */
273 
274 static struct ctl_tran *
new_tran(struct ctl_cctx * ctx,ctl_clntdone donefunc,void * uap,int w)275 new_tran(struct ctl_cctx *ctx, ctl_clntdone donefunc, void *uap, int w) {
276 	struct ctl_tran *new = memget(sizeof *new);
277 
278 	if (new == NULL)
279 		return (NULL);
280 	new->ctx = ctx;
281 	buffer_init(new->outbuf);
282 	new->donefunc = donefunc;
283 	new->uap = uap;
284 	INIT_LINK(new, link);
285 	INIT_LINK(new, wlink);
286 	APPEND(ctx->tran, new, link);
287 	if (w)
288 		APPEND(ctx->wtran, new, wlink);
289 	return (new);
290 }
291 
292 static void
start_write(struct ctl_cctx * ctx)293 start_write(struct ctl_cctx *ctx) {
294 	static const char me[] = "isc/ctl_clnt::start_write";
295 	struct ctl_tran *tran;
296 	struct iovec iov[2], *iovp = iov;
297 	char * tmp;
298 
299 	REQUIRE(ctx->state == connecting || ctx->state == connected);
300 	/* If there is a write in progress, don't try to write more yet. */
301 	if (ctx->wrID.opaque != NULL)
302 		return;
303 	/* If there are no trans, make sure timer is off, and we're done. */
304 	if (EMPTY(ctx->wtran)) {
305 		if (ctx->tiID.opaque != NULL)
306 			stop_timer(ctx);
307 		return;
308 	}
309 	/* Pull it off the head of the write queue. */
310 	tran = HEAD(ctx->wtran);
311 	UNLINK(ctx->wtran, tran, wlink);
312 	/* Since there are some trans, make sure timer is successfully "on". */
313 	if (ctx->tiID.opaque != NULL)
314 		touch_timer(ctx);
315 	else
316 		start_timer(ctx);
317 	if (ctx->state == destroyed)
318 		return;
319 	/* Marshall a newline-terminated message and clock it out. */
320 	*iovp++ = evConsIovec(tran->outbuf.text, tran->outbuf.used);
321 	DE_CONST("\r\n", tmp);
322 	*iovp++ = evConsIovec(tmp, 2);
323 	if (evWrite(ctx->ev, ctx->sock, iov, iovp - iov,
324 		    write_done, tran, &ctx->wrID) < 0) {
325 		(*ctx->logger)(ctl_error, "%s: evWrite: %s", me,
326 			       strerror(errno));
327 		error(ctx);
328 		return;
329 	}
330 	if (evTimeRW(ctx->ev, ctx->wrID, ctx->tiID) < 0) {
331 		(*ctx->logger)(ctl_error, "%s: evTimeRW: %s", me,
332 			       strerror(errno));
333 		error(ctx);
334 		return;
335 	}
336 }
337 
338 static void
destroy(struct ctl_cctx * ctx,int notify)339 destroy(struct ctl_cctx *ctx, int notify) {
340 	struct ctl_tran *this, *next;
341 
342 	if (ctx->sock != -1) {
343 		(void) close(ctx->sock);
344 		ctx->sock = -1;
345 	}
346 	switch (ctx->state) {
347 	case connecting:
348 		REQUIRE(ctx->wrID.opaque == NULL);
349 		REQUIRE(EMPTY(ctx->tran));
350 		/*
351 		 * This test is nec'y since destroy() can be called from
352 		 * start_read() while the state is still "connecting".
353 		 */
354 		if (ctx->coID.opaque != NULL) {
355 			(void)evCancelConn(ctx->ev, ctx->coID);
356 			ctx->coID.opaque = NULL;
357 		}
358 		break;
359 	case connected:
360 		REQUIRE(ctx->coID.opaque == NULL);
361 		if (ctx->wrID.opaque != NULL) {
362 			(void)evCancelRW(ctx->ev, ctx->wrID);
363 			ctx->wrID.opaque = NULL;
364 		}
365 		if (ctx->rdID.opaque != NULL)
366 			stop_read(ctx);
367 		break;
368 	case destroyed:
369 		break;
370 	default:
371 		abort();
372 	}
373 	if (allocated_p(ctx->inbuf))
374 		ctl_bufput(&ctx->inbuf);
375 	for (this = HEAD(ctx->tran); this != NULL; this = next) {
376 		next = NEXT(this, link);
377 		if (allocated_p(this->outbuf))
378 			ctl_bufput(&this->outbuf);
379 		if (notify && this->donefunc != NULL)
380 			(*this->donefunc)(ctx, this->uap, NULL, 0);
381 		memput(this, sizeof *this);
382 	}
383 	if (ctx->tiID.opaque != NULL)
384 		stop_timer(ctx);
385 	new_state(ctx, destroyed);
386 }
387 
388 static void
error(struct ctl_cctx * ctx)389 error(struct ctl_cctx *ctx) {
390 	REQUIRE(ctx->state != destroyed);
391 	destroy(ctx, 1);
392 }
393 
394 static void
new_state(struct ctl_cctx * ctx,enum state new_state)395 new_state(struct ctl_cctx *ctx, enum state new_state) {
396 	static const char me[] = "isc/ctl_clnt::new_state";
397 
398 	(*ctx->logger)(ctl_debug, "%s: %s -> %s", me,
399 		       state_names[ctx->state], state_names[new_state]);
400 	ctx->state = new_state;
401 }
402 
403 static void
conn_done(evContext ev,void * uap,int fd,const void * la,int lalen,const void * ra,int ralen)404 conn_done(evContext ev, void *uap, int fd,
405 	  const void *la, int lalen,
406 	  const void *ra, int ralen)
407 {
408 	static const char me[] = "isc/ctl_clnt::conn_done";
409 	struct ctl_cctx *ctx = uap;
410 	struct ctl_tran *tran;
411 
412 	UNUSED(ev);
413 	UNUSED(la);
414 	UNUSED(lalen);
415 	UNUSED(ra);
416 	UNUSED(ralen);
417 
418 	ctx->coID.opaque = NULL;
419 	if (fd < 0) {
420 		(*ctx->logger)(ctl_error, "%s: evConnect: %s", me,
421 			       strerror(errno));
422 		error(ctx);
423 		return;
424 	}
425 	new_state(ctx, connected);
426 	tran = new_tran(ctx, ctx->donefunc, ctx->uap, 0);
427 	if (tran == NULL) {
428 		(*ctx->logger)(ctl_error, "%s: new_tran failed: %s", me,
429 			       strerror(errno));
430 		error(ctx);
431 		return;
432 	}
433 	start_read(ctx);
434 	if (ctx->state == destroyed) {
435 		(*ctx->logger)(ctl_error, "%s: start_read failed: %s",
436 			       me, strerror(errno));
437 		error(ctx);
438 		return;
439 	}
440 }
441 
442 static void
write_done(evContext lev,void * uap,int fd,int bytes)443 write_done(evContext lev, void *uap, int fd, int bytes) {
444 	struct ctl_tran *tran = (struct ctl_tran *)uap;
445 	struct ctl_cctx *ctx = tran->ctx;
446 
447 	UNUSED(lev);
448 	UNUSED(fd);
449 
450 	ctx->wrID.opaque = NULL;
451 	if (ctx->tiID.opaque != NULL)
452 		touch_timer(ctx);
453 	ctl_bufput(&tran->outbuf);
454 	start_write(ctx);
455 	if (bytes < 0)
456 		destroy(ctx, 1);
457 	else
458 		start_read(ctx);
459 }
460 
461 static void
start_read(struct ctl_cctx * ctx)462 start_read(struct ctl_cctx *ctx) {
463 	static const char me[] = "isc/ctl_clnt::start_read";
464 
465 	REQUIRE(ctx->state == connecting || ctx->state == connected);
466 	REQUIRE(ctx->rdID.opaque == NULL);
467 	if (evSelectFD(ctx->ev, ctx->sock, EV_READ, readable, ctx,
468 		       &ctx->rdID) < 0)
469 	{
470 		(*ctx->logger)(ctl_error, "%s: evSelect(fd %d): %s", me,
471 			       ctx->sock, strerror(errno));
472 		error(ctx);
473 		return;
474 	}
475 }
476 
477 static void
stop_read(struct ctl_cctx * ctx)478 stop_read(struct ctl_cctx *ctx) {
479 	REQUIRE(ctx->coID.opaque == NULL);
480 	REQUIRE(ctx->rdID.opaque != NULL);
481 	(void)evDeselectFD(ctx->ev, ctx->rdID);
482 	ctx->rdID.opaque = NULL;
483 }
484 
485 static void
readable(evContext ev,void * uap,int fd,int evmask)486 readable(evContext ev, void *uap, int fd, int evmask) {
487 	static const char me[] = "isc/ctl_clnt::readable";
488 	struct ctl_cctx *ctx = uap;
489 	struct ctl_tran *tran;
490 	ssize_t n;
491 	char *eos;
492 
493 	UNUSED(ev);
494 
495 	REQUIRE(ctx != NULL);
496 	REQUIRE(fd >= 0);
497 	REQUIRE(evmask == EV_READ);
498 	REQUIRE(ctx->state == connected);
499 	REQUIRE(!EMPTY(ctx->tran));
500 	tran = HEAD(ctx->tran);
501 	if (!allocated_p(ctx->inbuf) &&
502 	    ctl_bufget(&ctx->inbuf, ctx->logger) < 0) {
503 		(*ctx->logger)(ctl_error, "%s: can't get an input buffer", me);
504 		error(ctx);
505 		return;
506 	}
507 	n = read(ctx->sock, ctx->inbuf.text + ctx->inbuf.used,
508 		 MAX_LINELEN - ctx->inbuf.used);
509 	if (n <= 0) {
510 		(*ctx->logger)(ctl_warning, "%s: read: %s", me,
511 			       (n == 0) ? "Unexpected EOF" : strerror(errno));
512 		error(ctx);
513 		return;
514 	}
515 	if (ctx->tiID.opaque != NULL)
516 		touch_timer(ctx);
517 	ctx->inbuf.used += n;
518 	(*ctx->logger)(ctl_debug, "%s: read %d, used %d", me,
519 		       n, ctx->inbuf.used);
520  again:
521 	eos = memchr(ctx->inbuf.text, '\n', ctx->inbuf.used);
522 	if (eos != NULL && eos != ctx->inbuf.text && eos[-1] == '\r') {
523 		int done = 0;
524 
525 		eos[-1] = '\0';
526 		if (!arpacode_p(ctx->inbuf.text)) {
527 			/* XXX Doesn't FTP do this sometimes? Is it legal? */
528 			(*ctx->logger)(ctl_error, "%s: no arpa code (%s)", me,
529 				       ctx->inbuf.text);
530 			error(ctx);
531 			return;
532 		}
533 		if (arpadone_p(ctx->inbuf.text))
534 			done = 1;
535 		else if (arpacont_p(ctx->inbuf.text))
536 			done = 0;
537 		else {
538 			/* XXX Doesn't FTP do this sometimes? Is it legal? */
539 			(*ctx->logger)(ctl_error, "%s: no arpa flag (%s)", me,
540 				       ctx->inbuf.text);
541 			error(ctx);
542 			return;
543 		}
544 		(*tran->donefunc)(ctx, tran->uap, ctx->inbuf.text,
545 				  (done ? 0 : CTL_MORE));
546 		ctx->inbuf.used -= ((eos - ctx->inbuf.text) + 1);
547 		if (ctx->inbuf.used == 0U)
548 			ctl_bufput(&ctx->inbuf);
549 		else
550 			memmove(ctx->inbuf.text, eos + 1, ctx->inbuf.used);
551 		if (done) {
552 			UNLINK(ctx->tran, tran, link);
553 			memput(tran, sizeof *tran);
554 			stop_read(ctx);
555 			start_write(ctx);
556 			return;
557 		}
558 		if (allocated_p(ctx->inbuf))
559 			goto again;
560 		return;
561 	}
562 	if (ctx->inbuf.used == (size_t)MAX_LINELEN) {
563 		(*ctx->logger)(ctl_error, "%s: line too long (%-10s...)", me,
564 			       ctx->inbuf.text);
565 		error(ctx);
566 	}
567 }
568 
569 /* Timer related stuff. */
570 
571 static void
start_timer(struct ctl_cctx * ctx)572 start_timer(struct ctl_cctx *ctx) {
573 	static const char me[] = "isc/ctl_clnt::start_timer";
574 
575 	REQUIRE(ctx->tiID.opaque == NULL);
576 	if (evSetIdleTimer(ctx->ev, timer, ctx, ctx->timeout, &ctx->tiID) < 0){
577 		(*ctx->logger)(ctl_error, "%s: evSetIdleTimer: %s", me,
578 			       strerror(errno));
579 		error(ctx);
580 		return;
581 	}
582 }
583 
584 static void
stop_timer(struct ctl_cctx * ctx)585 stop_timer(struct ctl_cctx *ctx) {
586 	static const char me[] = "isc/ctl_clnt::stop_timer";
587 
588 	REQUIRE(ctx->tiID.opaque != NULL);
589 	if (evClearIdleTimer(ctx->ev, ctx->tiID) < 0) {
590 		(*ctx->logger)(ctl_error, "%s: evClearIdleTimer: %s", me,
591 			       strerror(errno));
592 		error(ctx);
593 		return;
594 	}
595 	ctx->tiID.opaque = NULL;
596 }
597 
598 static void
touch_timer(struct ctl_cctx * ctx)599 touch_timer(struct ctl_cctx *ctx) {
600 	REQUIRE(ctx->tiID.opaque != NULL);
601 
602 	evTouchIdleTimer(ctx->ev, ctx->tiID);
603 }
604 
605 static void
timer(evContext ev,void * uap,struct timespec due,struct timespec itv)606 timer(evContext ev, void *uap, struct timespec due, struct timespec itv) {
607 	static const char me[] = "isc/ctl_clnt::timer";
608 	struct ctl_cctx *ctx = uap;
609 
610 	UNUSED(ev);
611 	UNUSED(due);
612 	UNUSED(itv);
613 
614 	ctx->tiID.opaque = NULL;
615 	(*ctx->logger)(ctl_error, "%s: timeout after %u seconds while %s", me,
616 		       ctx->timeout.tv_sec, state_names[ctx->state]);
617 	error(ctx);
618 }
619 
620 /*! \file */
621