4 * Copyright (C) 2004-2006, 2008 Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (C) 1998-2003 Internet Software Consortium.
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
12 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
14 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
16 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
20 #if !defined(lint) && !defined(SABER)
21 static const char rcsid
[] = "Id: ctl_srvr.c,v 1.10 2008/11/14 02:36:51 marka Exp";
26 #include "port_before.h"
28 #include <sys/param.h>
30 #include <sys/socket.h>
33 #include <netinet/in.h>
34 #include <arpa/nameser.h>
35 #include <arpa/inet.h>
49 #include <isc/assertions.h>
51 #include <isc/eventlib.h>
53 #include <isc/logging.h>
54 #include <isc/memcluster.h>
58 #include "port_after.h"
61 # define SPRINTF(x) strlen(sprintf/**/x)
63 # define SPRINTF(x) ((size_t)sprintf x)
68 #define lastverb_p(verb) (verb->name == NULL || verb->func == NULL)
69 #define address_expr ctl_sa_ntop((struct sockaddr *)&sess->sa, \
70 tmp, sizeof tmp, ctx->logger)
75 available
= 0, initializing
, writing
, reading
, reading_data
,
76 processing
, idling
, quitting
, closing
80 struct sockaddr_in in
;
81 #ifndef NO_SOCKADDR_UN
82 struct sockaddr_un un
;
87 LINK(struct ctl_sess
) link
;
88 struct ctl_sctx
* ctx
;
97 struct ctl_buf outbuf
;
98 const struct ctl_verb
* verb
;
100 const void * respctx
;
102 ctl_srvrdone donefunc
;
112 const struct ctl_verb
* verbs
;
113 const struct ctl_verb
* connverb
;
117 struct timespec timeout
;
120 LIST(struct ctl_sess
) sess
;
125 static void ctl_accept(evContext
, void *, int,
128 static void ctl_close(struct ctl_sess
*);
129 static void ctl_new_state(struct ctl_sess
*,
132 static void ctl_start_read(struct ctl_sess
*);
133 static void ctl_stop_read(struct ctl_sess
*);
134 static void ctl_readable(evContext
, void *, int, int);
135 static void ctl_rdtimeout(evContext
, void *,
138 static void ctl_wrtimeout(evContext
, void *,
141 static void ctl_docommand(struct ctl_sess
*);
142 static void ctl_writedone(evContext
, void *, int, int);
143 static void ctl_morehelp(struct ctl_sctx
*,
145 const struct ctl_verb
*,
147 u_int
, const void *, void *);
148 static void ctl_signal_done(struct ctl_sctx
*,
153 static const char * state_names
[] = {
154 "available", "initializing", "writing", "reading",
155 "reading_data", "processing", "idling", "quitting", "closing"
158 static const char space
[] = " ";
160 static const struct ctl_verb fakehelpverb
= {
161 "fakehelp", ctl_morehelp
, NULL
169 * create, condition, and start a listener on the control port.
172 ctl_server(evContext lev
, const struct sockaddr
*sap
, size_t sap_len
,
173 const struct ctl_verb
*verbs
,
174 u_int unkncode
, u_int timeoutcode
,
175 u_int timeout
, int backlog
, int max_sess
,
176 ctl_logfunc logger
, void *uctx
)
178 static const char me
[] = "ctl_server";
179 static const int on
= 1;
180 const struct ctl_verb
*connverb
;
181 struct ctl_sctx
*ctx
;
186 for (connverb
= verbs
;
187 connverb
->name
!= NULL
&& connverb
->func
!= NULL
;
189 if (connverb
->name
[0] == '\0')
191 if (connverb
->func
== NULL
) {
192 (*logger
)(ctl_error
, "%s: no connection verb found", me
);
195 ctx
= memget(sizeof *ctx
);
197 (*logger
)(ctl_error
, "%s: getmem: %s", me
, strerror(errno
));
202 ctx
->unkncode
= unkncode
;
203 ctx
->timeoutcode
= timeoutcode
;
205 ctx
->timeout
= evConsTime(timeout
, 0);
206 ctx
->logger
= logger
;
207 ctx
->connverb
= connverb
;
208 ctx
->max_sess
= max_sess
;
210 INIT_LIST(ctx
->sess
);
211 ctx
->sock
= socket(sap
->sa_family
, SOCK_STREAM
, PF_UNSPEC
);
212 if (ctx
->sock
> evHighestFD(ctx
->ev
)) {
218 (*ctx
->logger
)(ctl_error
, "%s: socket: %s",
219 me
, strerror(errno
));
220 memput(ctx
, sizeof *ctx
);
224 if (ctx
->sock
> evHighestFD(lev
)) {
226 (*ctx
->logger
)(ctl_error
, "%s: file descriptor > evHighestFD");
228 memput(ctx
, sizeof *ctx
);
231 #ifdef NO_UNIX_REUSEADDR
232 if (sap
->sa_family
!= AF_UNIX
)
234 if (setsockopt(ctx
->sock
, SOL_SOCKET
, SO_REUSEADDR
,
235 (const char *)&on
, sizeof on
) != 0) {
236 (*ctx
->logger
)(ctl_warning
,
237 "%s: setsockopt(REUSEADDR): %s",
238 me
, strerror(errno
));
240 if (bind(ctx
->sock
, sap
, sap_len
) < 0) {
243 (*ctx
->logger
)(ctl_error
, "%s: bind: %s: %s",
244 me
, ctl_sa_ntop((const struct sockaddr
*)sap
,
245 tmp
, sizeof tmp
, ctx
->logger
),
246 strerror(save_errno
));
248 memput(ctx
, sizeof *ctx
);
252 if (fcntl(ctx
->sock
, F_SETFD
, 1) < 0) {
253 (*ctx
->logger
)(ctl_warning
, "%s: fcntl: %s", me
,
256 if (evListen(lev
, ctx
->sock
, backlog
, ctl_accept
, ctx
,
259 (*ctx
->logger
)(ctl_error
, "%s: evListen(fd %d): %s",
260 me
, ctx
->sock
, strerror(errno
));
262 memput(ctx
, sizeof *ctx
);
266 (*ctx
->logger
)(ctl_debug
, "%s: new ctx %p, sock %d",
274 * if the control listener is open, close it. clean out all eventlib
275 * stuff. close all active sessions.
278 ctl_endserver(struct ctl_sctx
*ctx
) {
279 static const char me
[] = "ctl_endserver";
280 struct ctl_sess
*this, *next
;
282 (*ctx
->logger
)(ctl_debug
, "%s: ctx %p, sock %d, acID %p, sess %p",
283 me
, ctx
, ctx
->sock
, ctx
->acID
.opaque
, ctx
->sess
);
284 if (ctx
->acID
.opaque
!= NULL
) {
285 (void)evCancelConn(ctx
->ev
, ctx
->acID
);
286 ctx
->acID
.opaque
= NULL
;
288 if (ctx
->sock
!= -1) {
289 (void) close(ctx
->sock
);
292 for (this = HEAD(ctx
->sess
); this != NULL
; this = next
) {
293 next
= NEXT(this, link
);
296 memput(ctx
, sizeof *ctx
);
300 * If body is non-NULL then it we add a "." line after it.
301 * Caller must have escaped lines with leading ".".
304 ctl_response(struct ctl_sess
*sess
, u_int code
, const char *text
,
305 u_int flags
, const void *respctx
, ctl_srvrdone donefunc
,
306 void *uap
, const char *body
, size_t bodylen
)
308 static const char me
[] = "ctl_response";
309 struct iovec iov
[3], *iovp
= iov
;
310 struct ctl_sctx
*ctx
= sess
->ctx
;
311 char tmp
[MAX_NTOP
], *pc
;
314 REQUIRE(sess
->state
== initializing
||
315 sess
->state
== processing
||
316 sess
->state
== reading_data
||
317 sess
->state
== writing
);
318 REQUIRE(sess
->wrtiID
.opaque
== NULL
);
319 REQUIRE(sess
->wrID
.opaque
== NULL
);
320 ctl_new_state(sess
, writing
, me
);
321 sess
->donefunc
= donefunc
;
323 if (!allocated_p(sess
->outbuf
) &&
324 ctl_bufget(&sess
->outbuf
, ctx
->logger
) < 0) {
325 (*ctx
->logger
)(ctl_error
, "%s: %s: cant get an output buffer",
329 if (sizeof "000-\r\n" + strlen(text
) > (size_t)MAX_LINELEN
) {
330 (*ctx
->logger
)(ctl_error
, "%s: %s: output buffer ovf, closing",
334 sess
->outbuf
.used
= SPRINTF((sess
->outbuf
.text
, "%03d%c%s\r\n",
335 code
, (flags
& CTL_MORE
) != 0 ? '-' : ' ',
337 for (pc
= sess
->outbuf
.text
, n
= 0;
338 n
< (int)sess
->outbuf
.used
-2; pc
++, n
++)
339 if (!isascii((unsigned char)*pc
) ||
340 !isprint((unsigned char)*pc
))
342 *iovp
++ = evConsIovec(sess
->outbuf
.text
, sess
->outbuf
.used
);
346 *iovp
++ = evConsIovec(tmp
, bodylen
);
347 DE_CONST(".\r\n", tmp
);
348 *iovp
++ = evConsIovec(tmp
, 3);
350 (*ctx
->logger
)(ctl_debug
, "%s: [%d] %s", me
,
351 sess
->outbuf
.used
, sess
->outbuf
.text
);
352 if (evWrite(ctx
->ev
, sess
->sock
, iov
, iovp
- iov
,
353 ctl_writedone
, sess
, &sess
->wrID
) < 0) {
354 (*ctx
->logger
)(ctl_error
, "%s: %s: evWrite: %s", me
,
355 address_expr
, strerror(errno
));
358 if (evSetIdleTimer(ctx
->ev
, ctl_wrtimeout
, sess
, ctx
->timeout
,
361 (*ctx
->logger
)(ctl_error
, "%s: %s: evSetIdleTimer: %s", me
,
362 address_expr
, strerror(errno
));
365 if (evTimeRW(ctx
->ev
, sess
->wrID
, sess
->wrtiID
) < 0) {
366 (*ctx
->logger
)(ctl_error
, "%s: %s: evTimeRW: %s", me
,
367 address_expr
, strerror(errno
));
369 ctl_signal_done(ctx
, sess
);
373 sess
->respctx
= respctx
;
374 sess
->respflags
= flags
;
378 ctl_sendhelp(struct ctl_sess
*sess
, u_int code
) {
379 static const char me
[] = "ctl_sendhelp";
380 struct ctl_sctx
*ctx
= sess
->ctx
;
382 sess
->helpcode
= code
;
383 sess
->verb
= &fakehelpverb
;
384 ctl_morehelp(ctx
, sess
, NULL
, me
, CTL_MORE
,
385 (const void *)ctx
->verbs
, NULL
);
389 ctl_getcsctx(struct ctl_sess
*sess
) {
390 return (sess
->csctx
);
394 ctl_setcsctx(struct ctl_sess
*sess
, void *csctx
) {
395 void *old
= sess
->csctx
;
401 /* Private functions. */
404 ctl_accept(evContext lev
, void *uap
, int fd
,
405 const void *lav
, int lalen
,
406 const void *rav
, int ralen
)
408 static const char me
[] = "ctl_accept";
409 struct ctl_sctx
*ctx
= uap
;
410 struct ctl_sess
*sess
= NULL
;
418 (*ctx
->logger
)(ctl_error
, "%s: accept: %s",
419 me
, strerror(errno
));
422 if (ctx
->cur_sess
== ctx
->max_sess
) {
423 (*ctx
->logger
)(ctl_error
, "%s: %s: too many control sessions",
424 me
, ctl_sa_ntop((const struct sockaddr
*)rav
,
430 sess
= memget(sizeof *sess
);
432 (*ctx
->logger
)(ctl_error
, "%s: memget: %s", me
,
437 if (fcntl(fd
, F_SETFD
, 1) < 0) {
438 (*ctx
->logger
)(ctl_warning
, "%s: fcntl: %s", me
,
442 INIT_LINK(sess
, link
);
443 APPEND(ctx
->sess
, sess
, link
);
446 sess
->wrID
.opaque
= NULL
;
447 sess
->rdID
.opaque
= NULL
;
448 sess
->wrtiID
.opaque
= NULL
;
449 sess
->rdtiID
.opaque
= NULL
;
450 sess
->respctx
= NULL
;
452 if (((const struct sockaddr
*)rav
)->sa_family
== AF_UNIX
)
453 ctl_sa_copy((const struct sockaddr
*)lav
,
454 (struct sockaddr
*)&sess
->sa
);
456 ctl_sa_copy((const struct sockaddr
*)rav
,
457 (struct sockaddr
*)&sess
->sa
);
458 sess
->donefunc
= NULL
;
459 buffer_init(sess
->inbuf
);
460 buffer_init(sess
->outbuf
);
461 sess
->state
= available
;
462 ctl_new_state(sess
, initializing
, me
);
463 sess
->verb
= ctx
->connverb
;
464 (*ctx
->logger
)(ctl_debug
, "%s: %s: accepting (fd %d)",
465 me
, address_expr
, sess
->sock
);
466 (*ctx
->connverb
->func
)(ctx
, sess
, ctx
->connverb
, "", 0,
467 (const struct sockaddr
*)rav
, ctx
->uctx
);
471 ctl_new_state(struct ctl_sess
*sess
, enum state new_state
, const char *reason
)
473 static const char me
[] = "ctl_new_state";
474 struct ctl_sctx
*ctx
= sess
->ctx
;
477 (*ctx
->logger
)(ctl_debug
, "%s: %s: %s -> %s (%s)",
479 state_names
[sess
->state
],
480 state_names
[new_state
], reason
);
481 sess
->state
= new_state
;
485 ctl_close(struct ctl_sess
*sess
) {
486 static const char me
[] = "ctl_close";
487 struct ctl_sctx
*ctx
= sess
->ctx
;
490 REQUIRE(sess
->state
== initializing
||
491 sess
->state
== writing
||
492 sess
->state
== reading
||
493 sess
->state
== processing
||
494 sess
->state
== reading_data
||
495 sess
->state
== idling
);
496 REQUIRE(sess
->sock
!= -1);
497 if (sess
->state
== reading
|| sess
->state
== reading_data
)
499 else if (sess
->state
== writing
) {
500 if (sess
->wrID
.opaque
!= NULL
) {
501 (void) evCancelRW(ctx
->ev
, sess
->wrID
);
502 sess
->wrID
.opaque
= NULL
;
504 if (sess
->wrtiID
.opaque
!= NULL
) {
505 (void) evClearIdleTimer(ctx
->ev
, sess
->wrtiID
);
506 sess
->wrtiID
.opaque
= NULL
;
509 ctl_new_state(sess
, closing
, me
);
510 (void) close(sess
->sock
);
511 if (allocated_p(sess
->inbuf
))
512 ctl_bufput(&sess
->inbuf
);
513 if (allocated_p(sess
->outbuf
))
514 ctl_bufput(&sess
->outbuf
);
515 (*ctx
->logger
)(ctl_debug
, "%s: %s: closed (fd %d)",
516 me
, address_expr
, sess
->sock
);
517 UNLINK(ctx
->sess
, sess
, link
);
518 memput(sess
, sizeof *sess
);
523 ctl_start_read(struct ctl_sess
*sess
) {
524 static const char me
[] = "ctl_start_read";
525 struct ctl_sctx
*ctx
= sess
->ctx
;
528 REQUIRE(sess
->state
== initializing
||
529 sess
->state
== writing
||
530 sess
->state
== processing
||
531 sess
->state
== idling
);
532 REQUIRE(sess
->rdtiID
.opaque
== NULL
);
533 REQUIRE(sess
->rdID
.opaque
== NULL
);
534 sess
->inbuf
.used
= 0;
535 if (evSetIdleTimer(ctx
->ev
, ctl_rdtimeout
, sess
, ctx
->timeout
,
538 (*ctx
->logger
)(ctl_error
, "%s: %s: evSetIdleTimer: %s", me
,
539 address_expr
, strerror(errno
));
543 if (evSelectFD(ctx
->ev
, sess
->sock
, EV_READ
,
544 ctl_readable
, sess
, &sess
->rdID
) < 0) {
545 (*ctx
->logger
)(ctl_error
, "%s: %s: evSelectFD: %s", me
,
546 address_expr
, strerror(errno
));
549 ctl_new_state(sess
, reading
, me
);
553 ctl_stop_read(struct ctl_sess
*sess
) {
554 static const char me
[] = "ctl_stop_read";
555 struct ctl_sctx
*ctx
= sess
->ctx
;
557 REQUIRE(sess
->state
== reading
|| sess
->state
== reading_data
);
558 REQUIRE(sess
->rdID
.opaque
!= NULL
);
559 (void) evDeselectFD(ctx
->ev
, sess
->rdID
);
560 sess
->rdID
.opaque
= NULL
;
561 if (sess
->rdtiID
.opaque
!= NULL
) {
562 (void) evClearIdleTimer(ctx
->ev
, sess
->rdtiID
);
563 sess
->rdtiID
.opaque
= NULL
;
565 ctl_new_state(sess
, idling
, me
);
569 ctl_readable(evContext lev
, void *uap
, int fd
, int evmask
) {
570 static const char me
[] = "ctl_readable";
571 struct ctl_sess
*sess
= uap
;
572 struct ctl_sctx
*ctx
;
573 char *eos
, tmp
[MAX_NTOP
];
576 REQUIRE(sess
!= NULL
);
578 REQUIRE(evmask
== EV_READ
);
579 REQUIRE(sess
->state
== reading
|| sess
->state
== reading_data
);
582 evTouchIdleTimer(lev
, sess
->rdtiID
);
583 if (!allocated_p(sess
->inbuf
) &&
584 ctl_bufget(&sess
->inbuf
, ctx
->logger
) < 0) {
585 (*ctx
->logger
)(ctl_error
, "%s: %s: cant get an input buffer",
590 n
= read(sess
->sock
, sess
->inbuf
.text
+ sess
->inbuf
.used
,
591 MAX_LINELEN
- sess
->inbuf
.used
);
593 (*ctx
->logger
)(ctl_debug
, "%s: %s: read: %s",
595 (n
== 0) ? "Unexpected EOF" : strerror(errno
));
599 sess
->inbuf
.used
+= n
;
600 eos
= memchr(sess
->inbuf
.text
, '\n', sess
->inbuf
.used
);
601 if (eos
!= NULL
&& eos
!= sess
->inbuf
.text
&& eos
[-1] == '\r') {
603 if ((sess
->respflags
& CTL_DATA
) != 0) {
604 INSIST(sess
->verb
!= NULL
);
605 (*sess
->verb
->func
)(sess
->ctx
, sess
, sess
->verb
,
607 CTL_DATA
, sess
->respctx
,
613 sess
->inbuf
.used
-= ((eos
- sess
->inbuf
.text
) + 1);
614 if (sess
->inbuf
.used
== 0U)
615 ctl_bufput(&sess
->inbuf
);
617 memmove(sess
->inbuf
.text
, eos
+ 1, sess
->inbuf
.used
);
620 if (sess
->inbuf
.used
== (size_t)MAX_LINELEN
) {
621 (*ctx
->logger
)(ctl_error
, "%s: %s: line too long, closing",
628 ctl_wrtimeout(evContext lev
, void *uap
,
632 static const char me
[] = "ctl_wrtimeout";
633 struct ctl_sess
*sess
= uap
;
634 struct ctl_sctx
*ctx
= sess
->ctx
;
641 REQUIRE(sess
->state
== writing
);
642 sess
->wrtiID
.opaque
= NULL
;
643 (*ctx
->logger
)(ctl_warning
, "%s: %s: write timeout, closing",
645 if (sess
->wrID
.opaque
!= NULL
) {
646 (void) evCancelRW(ctx
->ev
, sess
->wrID
);
647 sess
->wrID
.opaque
= NULL
;
649 ctl_signal_done(ctx
, sess
);
650 ctl_new_state(sess
, processing
, me
);
655 ctl_rdtimeout(evContext lev
, void *uap
,
659 static const char me
[] = "ctl_rdtimeout";
660 struct ctl_sess
*sess
= uap
;
661 struct ctl_sctx
*ctx
= sess
->ctx
;
668 REQUIRE(sess
->state
== reading
);
669 sess
->rdtiID
.opaque
= NULL
;
670 (*ctx
->logger
)(ctl_warning
, "%s: %s: timeout, closing",
672 if (sess
->state
== reading
|| sess
->state
== reading_data
)
674 ctl_signal_done(ctx
, sess
);
675 ctl_new_state(sess
, processing
, me
);
676 ctl_response(sess
, ctx
->timeoutcode
, "Timeout.", CTL_EXIT
, NULL
,
677 NULL
, NULL
, NULL
, 0);
681 ctl_docommand(struct ctl_sess
*sess
) {
682 static const char me
[] = "ctl_docommand";
683 char *name
, *rest
, tmp
[MAX_NTOP
];
684 struct ctl_sctx
*ctx
= sess
->ctx
;
685 const struct ctl_verb
*verb
;
687 REQUIRE(allocated_p(sess
->inbuf
));
688 (*ctx
->logger
)(ctl_debug
, "%s: %s: \"%s\" [%u]",
690 sess
->inbuf
.text
, (u_int
)sess
->inbuf
.used
);
691 ctl_new_state(sess
, processing
, me
);
692 name
= sess
->inbuf
.text
+ strspn(sess
->inbuf
.text
, space
);
693 rest
= name
+ strcspn(name
, space
);
696 rest
+= strspn(rest
, space
);
698 for (verb
= ctx
->verbs
;
699 verb
!= NULL
&& verb
->name
!= NULL
&& verb
->func
!= NULL
;
701 if (verb
->name
[0] != '\0' && strcasecmp(name
, verb
->name
) == 0)
703 if (verb
!= NULL
&& verb
->name
!= NULL
&& verb
->func
!= NULL
) {
705 (*verb
->func
)(ctx
, sess
, verb
, rest
, 0, NULL
, ctx
->uctx
);
709 if (sizeof "Unrecognized command \"\" (args \"\")" +
710 strlen(name
) + strlen(rest
) > sizeof buf
)
711 strcpy(buf
, "Unrecognized command (buf ovf)");
714 "Unrecognized command \"%s\" (args \"%s\")",
716 ctl_response(sess
, ctx
->unkncode
, buf
, 0, NULL
, NULL
, NULL
,
722 ctl_writedone(evContext lev
, void *uap
, int fd
, int bytes
) {
723 static const char me
[] = "ctl_writedone";
724 struct ctl_sess
*sess
= uap
;
725 struct ctl_sctx
*ctx
= sess
->ctx
;
727 int save_errno
= errno
;
732 REQUIRE(sess
->state
== writing
);
733 REQUIRE(fd
== sess
->sock
);
734 REQUIRE(sess
->wrtiID
.opaque
!= NULL
);
735 sess
->wrID
.opaque
= NULL
;
736 (void) evClearIdleTimer(ctx
->ev
, sess
->wrtiID
);
737 sess
->wrtiID
.opaque
= NULL
;
739 (*ctx
->logger
)(ctl_error
, "%s: %s: %s",
740 me
, address_expr
, strerror(save_errno
));
745 INSIST(allocated_p(sess
->outbuf
));
746 ctl_bufput(&sess
->outbuf
);
747 if ((sess
->respflags
& CTL_EXIT
) != 0) {
748 ctl_signal_done(ctx
, sess
);
751 } else if ((sess
->respflags
& CTL_MORE
) != 0) {
752 INSIST(sess
->verb
!= NULL
);
753 (*sess
->verb
->func
)(sess
->ctx
, sess
, sess
->verb
, "",
754 CTL_MORE
, sess
->respctx
, sess
->ctx
->uctx
);
756 ctl_signal_done(ctx
, sess
);
757 ctl_start_read(sess
);
762 ctl_morehelp(struct ctl_sctx
*ctx
, struct ctl_sess
*sess
,
763 const struct ctl_verb
*verb
, const char *text
,
764 u_int respflags
, const void *respctx
, void *uctx
)
766 const struct ctl_verb
*this = respctx
, *next
= this + 1;
773 REQUIRE(!lastverb_p(this));
774 REQUIRE((respflags
& CTL_MORE
) != 0);
775 if (lastverb_p(next
))
776 respflags
&= ~CTL_MORE
;
777 ctl_response(sess
, sess
->helpcode
, this->help
, respflags
, next
,
778 NULL
, NULL
, NULL
, 0);
782 ctl_signal_done(struct ctl_sctx
*ctx
, struct ctl_sess
*sess
) {
783 if (sess
->donefunc
!= NULL
) {
784 (*sess
->donefunc
)(ctx
, sess
, sess
->uap
);
785 sess
->donefunc
= NULL
;