and
.BR PING )
block the client until they finish, but the rest of the server continues
-- running.
-- .SS "Asynchronous messages"
-- There are three types of asynchronous messages which
-- aren't associated with any particular command.
++ running. See
++ .B "Background commands"
++ to find out how to issue long-running commands without blocking.
++ .SS "Asynchronous broadcasts"
++ There are three types of asynchronous broadcast messages which aren't
++ associated with any particular command. Clients can select which
++ broadcast messages they're interested in using the
++ .B WATCH
++ command.
.PP
The
.B WARN
.B NOTE
message is a machine-readable notification about some routine but
interesting event such as creation or destruction of peers.
-- .PP
-- The presence of asynchronous messages can be controlled using the
-- .B WATCH
-- command.
.SS "Background commands"
Some commands (e.g.,
.B ADD
lines followed by
.B BGOK
response.
++ .SS "Client-provided services"
++ .\"* 25 Service-related messages
++ An administration client can provide services to other clients.
++ Services are given names and versions. A client can attempt to
++ .I claim
++ a particular service by issuing the
++ .B SVCCLAIM
++ command. This may fail, for example, if some other client already
++ provides the same or later version of the service.
++ .PP
++ Other clients can issue
++ .I "service commands"
++ using the
++ .B "SVCSUBMIT"
++ command; the service provider is expected to handle these commands and
++ reply to them.
++ .PP
++ There are three important asynchronous messages which will be sent to
++ service providers.
++ .SP
++ .BI "SVCCANCEL " jobid
++ The named job has been cancelled, either because the issuing client has
++ disconnected or explicitly cancelled the job using the
++ .B BGCANCEL
++ command.
++ .SP
++ .BI "SVCCLAIM " service " " version
++ Another client has claimed a later version of the named
++ .I service. The recipient is no longer the provider of this service.
++ .SP
++ .BI "SVCJOB " jobid " " service " " command " " args \fR...
++ Announces the arrival of a new job. The
++ .I jobid
++ is a simple token consisting of alphanumeric characters which
++ .B tripe
++ uses to identify this job.
++ .PP
++ The service provider can reply to the job using the commands
++ .BR SVCINFO ,
++ .B SVCOK
++ and
++ .BR SVCFAIL .
++ The first of these sends an
++ .B INFO
++ response and leaves the job active; the other two send an
++ .B OK
++ or
++ .B FAIL
++ response respectively, and mark the job as being complete.
++ .PP
++ (Since
++ .B SVCSUBMIT
++ is a potentially long-running command, it can be run in the background.
++ This detail is hidden from service providers:
++ .B tripe
++ will issue the corresponding
++ .BR BG ...
++ responses when appropriate.)
.SS "Network addresses"
A network address is a sequence of words. The first is a token
identifying the network address family. The length of an address and
address is always in numeric dotted-quad form, and the port is given as
a plain number. On input, DNS hostnames and symbolic port names are
permitted. Name resolution does not block the main server, but will
- -block the requesting client. This hopefully makes life simpler for
- -stupid clients. Complex clients which don't wish to be held up can open
- -extra connections or do the resolution themselves.)
+ +block the requesting client, unless the command is run in the background.
.PP
If, on input, no recognised address family token is found, the following
words are assumed to represent an
.B INET
- -address.
+ +address. Addresses output by the server always have an address family
+ +token.
.SS "Key-value output"
Some commands (e.g.,
.B STATS
.\"* 10 Commands
The commands provided are:
.SP
-- .BI "ADD " peer " \fR[" options "\fR] " address "\fR..."
++ .BI "ADD \fR[" options "\fR] " peer " " address "\fR..."
Adds a new peer. The peer is given the name
.IR peer ;
the peer's public key is assumed to be in the file
line reporting the IP address and port number stored for
.IR peer .
.SP
++ .BI "BGCANCEL " tag
++ Cancels the background job with the named
++ .IR tag .
++ .SP
.BI "CHECKCHAL " challenge
Verifies a challenge as being one earlier issued by
.B GETCHAL
.B "DAEMON"
Causes the server to disassociate itself from its terminal and become a
background task. This only works once. A warning is issued.
- -.TP
+ +.SP
.BI "EPING \fR[" options "\fR] " peer
Sends an encrypted ping to the peer, and expects an encrypted response.
This checks that the peer is running (and not being impersonated), and
Used by configuration scripts so that they can set up routing tables
appropriately after adding new peers.
.SP
++ .B "JOBS"
++ Emits an
++ .B INFO
++ line giving the tag for each outstanding background job.
++ .SP
.BI "KILL " peer
Causes the server to forget all about
.IR peer .
.BI "\-timeout " time
Wait for
.I time
- -seconds before giving up on a response. The default is 5 seconds. (The
- -time format is the same as for the
- -.B "ADD \-keepalive"
- -option.)
+ +seconds before giving up on a response. The default is 5 seconds. The
+ +.I time
+ +is expressed as a nonnegative integer followed optionally by
+ +.BR d ,
+ +.BR h ,
+ +.BR m ,
+ +or
+ +.BR s
+ +for days, hours, minutes, or seconds respectively; if no suffix is
+ +given, seconds are assumed.
.\"-opts
.RE
.SP
names: a setup script for a particular peer can change the name, and
then update the server's records so that they're accurate.
.SP
++ .BI "SVCCLAIM " service " " version
++ Attempts to claim the named
++ .IR service ,
++ offering the given
++ .IR version .
++ The claim is successful if the service is currently unclaimed, or if
++ a version earlier than
++ .I version
++ is provided; otherwise the command fails with the error
++ .BR "service-exists" .
++ .SP
++ .BI "SVCENSURE " service " \fR[" version \fR]
++ Ensure that
++ .I service
++ is provided, and (if specified) to at least the given
++ .IR version .
++ An error is reported if these conditions are not met; otherwise the
++ command succeeds silently.
++ .SP
++ .BI "SVCFAIL " jobid " " tokens \fR...
++ Send a
++ .B FAIL
++ (or
++ .BR BGFAIL )
++ response to the service job with the given
++ .IR jobid ,
++ passing the
++ .I tokens
++ as the reason for failure. The job is closed.
++ .SP
++ .BI "SVCINFO " jobid " " tokens \fR...
++ Send an
++ .B INFO
++ (or
++ .BR BGINFO )
++ response to the service job with the given
++ .IR jobid ,
++ passing the
++ .I tokens
++ as the info message. The job remains open.
++ .SP
++ .B "SVCLIST"
++ Output a line of the form
++ .RS
++ .IP
++ .B INFO
++ .I service
++ .I version
++ .PP
++ for each service currently provided.
++ .RE
++ .SP
++ .BI "SVCOK " jobid
++ Send an
++ .B OK
++ (or
++ .BR BGINFO )
++ response to the service job with the given
++ .IR jobid .
++ The job is closed.
++ .SP
++ .BI "SVCQUERY " service
++ Emits a number of
++ .B info
++ lines in key-value format, describing the named
++ .IR service.
++ The following keys are used.
++ .RS
++ .TP
++ .B name
++ The service's name.
++ .TP
++ .B version
++ The service's version string.
++ .RE
++ .SP
++ .BI "SVCRELEASE " service
++ Announce that the client no longer wishes to provide the named
++ .IR service .
++ .SP
++ .BI "SVCSUBMIT \fR[" options "\fR] " service " " command " " arguments \fR...
++ Submit a job to the provider of the given
++ .IR service ,
++ passing it the named
++ .I command
++ and the given
++ .IR arguments .
++ The following options are accepted.
++ .RS
++ .\"+opts
++ .TP
++ .BI "\-background " tag
++ Run the command in the background, using the given
++ .IR tag .
++ .TP
++ .BI "\-version " version
++ Ensure that at least the given
++ .I version
++ of the service is available before submitting the job.
++ .RE
++ .\"-opts
++ .SP
.BI "STATS " peer
Emits a number of
.B INFO
is reserved to the Straylight/Edgeware implementation.
.SP
.BR "WATCH " [\fIoptions\fP]
-- Enables or disables asynchronous messages
++ Enables or disables asynchronous broadcasts
.IR "for the current connection only" .
See
.B "Trace lists"
above. The default watch state for the connection the server opens
automatically on stdin/stdout is to show warnings and trace messages;
-- other connections show no asynchronous messages. (This is done in order
-- to guarantee that a program reading the server's stdout does not miss
-- any warnings.)
++ other connections show no asynchronous broadcast messages. (This is
++ done in order to guarantee that a program reading the server's stdout
++ does not miss any warnings.)
.RS
.PP
Message types provided are:
.BR ADD .)
The given port number is out of range.
.SP
++ .BI "not-service-provider " service
++ (For
++ .BR SVCRELEASE .)
++ The invoking client is not the current provider of the named
++ .IR service ,
++ and is therefore not allowed to release it.
++ .SP
.BI "peer-create-fail " peer
(For
.BR ADD .)
.I hostname
took too long to resolve.
.SP
++ .BI "service-exists " service " " version
++ (For
++ .BR SVCCLAIM .)
++ Another client is already providing the stated
++ .I version
++ of the
++ .IR service .
++ .SP
++ .BI "service-too-old " service " " version
++ (For
++ .B SVCENSURE
++ and
++ .BR SVCSUBMIT .)
++ Only the given
++ .I version
++ of the requested
++ .I service
++ is available, which does not meet the stated requirements.
++ .SP
++ .BI "tag-exists " tag
++ (For long-running commands.) The named
++ .I tag
++ is already the tag of an outstanding job.
++ .SP
.BI "unknown-command " token
The command
.B token
There is no peer called
.IR name .
.SP
-- .BI "unknown-service " service
++ .BI "unknown-port " port
(For
.BR ADD .)
-- The service name
-- .I service
++ The port name
++ .I port
couldn't be found in
.BR /etc/services .
++ .TP
++ .BI "unknown-service " service
++ (For
++ .BR SVCENSURE ,
++ .BR SVCQUERY ,
++ .BR SVCRELEASE ,
++ and
++ .BR SVCSUBMIT .)
++ The token
++ .I service
++ is not recognized as the name of a client-provided service.
++ .TP
++ .BI "unknown-tag " tag
++ (For
++ .BR BGCANCEL .)
++ The given
++ .I tag
++ is not the tag for any outstanding background job. It may have just
++ finished.
.SH "NOTIFICATIONS"
.\"* 30 Notification broadcasts (NOTE codes)
The following notifications are sent to clients who request them.
.B SETIFNAME
command.
.SP
++ .BI "SVCCLAIM " service " " version
++ The named
++ .I service
++ is now available, at the stated
++ .IR version .
++ .SP
++ .BI "SVCRELEASE " service
++ The named
++ .I service
++ is no longer available.
++ .SP
.BI "USER " tokens\fR...
An administration client issued a notification using the
.B NOTIFY
.SH "SUMMARY"
.SS "Command responses"
.nf
+ +.BI "BGDETACH " tag
.BI "BGFAIL " tag " " tokens \fR...
.BI "BGINFO " tag " " tokens \fR...
.BI "BGOK " tag
/*----- Static variables --------------------------------------------------*/
static admin *admins;
++ static admin *a_dead;
static sel_file sock;
static const char *sockname;
++ static sym_table a_svcs;
static unsigned flags = 0;
static admin *a_stdin = 0;
static sig s_term, s_int, s_hup;
#define T_PING SEC(5)
static void a_destroy(admin */*a*/);
-- static void a_lock(admin */*a*/);
-- static void a_unlock(admin */*a*/);
#define BOOL(x) ((x) ? "t" : "nil")
base64_encode(&b64, p, n, d);
base64_encode(&b64, 0, 0, d);
while (d->len && d->buf[d->len - 1] == '=') d->len--;
++ } else if (strcmp(fmt, "?TOKENS") == 0) {
++ const char *const *av = va_arg(ap, const char *const *);
++ while (*av) quotify(d, *av++);
} else if (strcmp(fmt, "?PEER") == 0)
quotify(d, p_name(va_arg(ap, peer *)));
else if (strcmp(fmt, "?ERRNO") == 0) {
const char *fmt, va_list ap)
{
dstr d = DSTR_INIT;
+ +
if (tag) dstr_puts(&d, "BG");
dstr_puts(&d, status);
if (tag) quotify(&d, tag);
const char *fmt, ...)
{
va_list ap;
+ +
va_start(ap, fmt);
a_vwrite(a, status, tag, fmt, ap);
va_end(ap);
static void a_info(admin *a, const char *fmt, ...)
{
va_list ap;
+ +
va_start(ap, fmt);
a_vwrite(a, "INFO", 0, fmt, ap);
va_end(ap);
static void a_fail(admin *a, const char *fmt, ...)
{
va_list ap;
+ +
va_start(ap, fmt);
a_vwrite(a, "FAIL", 0, fmt, ap);
va_end(ap);
dstr_destroy(&d);
}
- -static void a_valert(unsigned f_and, unsigned f_eq, const char *tag,
+ +static void a_valert(unsigned f_and, unsigned f_eq, const char *status,
const char *fmt, va_list ap)
{
dstr d = DSTR_INIT;
if (!(flags & F_INIT))
return;
a_vformat(&d, fmt, ap);
- - a_rawalert(f_and, f_eq, tag, fmt ? d.buf : 0, fmt ? d.len : 0);
+ + a_rawalert(f_and, f_eq, status, fmt ? d.buf : 0, fmt ? d.len : 0);
dstr_destroy(&d);
}
-- #if 0 /*unused*/
- -static void a_alert(unsigned f_and, unsigned f_eq, const char *tag,
+ +static void a_alert(unsigned f_and, unsigned f_eq, const char *status,
const char *fmt, ...)
{
va_list ap;
+ +
va_start(ap, fmt);
- - a_valert(f_and, f_eq, tag, fmt, ap);
+ + a_valert(f_and, f_eq, status, fmt, ap);
va_end(ap);
}
-- #endif
/* --- @a_warn@ --- *
*
*/
static void a_sighup(int sig, void *v)
- -{
- - a_warn("SERVER", "ignore", "signal", "SIGHUP", A_END);
- -}
+ + { a_warn("SERVER", "ignore", "signal", "SIGHUP", A_END); }
/* --- @a_parsetime@ --- *
*
#define BGTAG(bg) \
(((admin_bgop *)(bg))->tag ? ((admin_bgop *)(bg))->tag : "<foreground>")
++ /* --- @a_bgfind@ --- *
++ *
++ * Arguments: @admin *a@ = a client block
++ * @const char *tag@ = the requested tag
++ *
++ * Returns: The requested background job, or null.
++ */
++
++ static admin_bgop *a_bgfind(admin *a, const char *tag)
++ {
++ admin_bgop *bg;
++
++ for (bg = a->bg; bg; bg = bg->next) {
++ if (bg->tag && strcmp(tag, bg->tag) == 0)
++ return (bg);
++ }
++ return (0);
++ }
++
/* --- @a_bgrelease@ --- *
*
* Arguments: @admin_bgop *bg@ = backgrounded operation
if (bg->prev) bg->prev->next = bg->next;
else a->bg = bg->next;
xfree(bg);
-- if (a->f & AF_CLOSE) a_destroy(a);
-- a_unlock(a);
++ if (!a->bg && (a->f & AF_CLOSE)) a_destroy(a);
}
/* --- @a_bgok@, @a_bginfo@, @a_bgfail@ --- *
* @const char *tag@ = background tag, or null for foreground
* @void (*cancel)(admin_bgop *)@ = cancel function
*
-- * Returns: ---
++ * Returns: Zero for success, nonzero on failure.
*
* Use: Links a background job into the list.
*/
-- static void a_bgadd(admin *a, admin_bgop *bg, const char *tag,
-- void (*cancel)(admin_bgop *))
++ static int a_bgadd(admin *a, admin_bgop *bg, const char *tag,
++ void (*cancel)(admin_bgop *))
{
-- if (tag)
++ if (tag) {
++ if (a_bgfind(a, tag)) {
++ a_fail(a, "tag-exists", "%s", tag, A_END);
++ return (-1);
++ }
bg->tag = xstrdup(tag);
++ }
else {
bg->tag = 0;
selbuf_disable(&a->b);
bg->prev = 0;
if (a->bg) a->bg->prev = bg;
a->bg = bg;
-- a_lock(a);
T( trace(T_ADMIN, "admin: add bgop %s", BGTAG(bg)); )
if (tag) a_write(a, "DETACH", tag, A_END);
++ return (0);
++ }
++
++ /*----- Job table manipulation --------------------------------------------*/
++
++ #define JOB_SHIFT 16
++ #define JOB_INDEXMASK ((1ul << JOB_SHIFT) - 1)
++ #define JOB_SEQMASK ((1ul << (32 - JOB_SHIFT)) - 1)
++
++ #define JOB_END 0xfffffffful
++
++ static unsigned long a_joboffset;
++
++ /* --- @a_jobidencode@ --- *
++ *
++ * Arguments: @admin_svcop *svc@ = pointer to a service operation
++ *
++ * Returns: A jobid for this job, in an internal static buffer.
++ *
++ * Use: Constructs a jobid. In order to dissuade people from
++ * predicting jobids, we obfuscate them.
++ *
++ * A `raw' jobid consists of two 16-bit fields. The low 16 bits
++ * are an index into a big array. The high 16 bits are a
++ * sequence number recording how many times that slot has been
++ * reused.
++ *
++ * This `raw' jobid is then obfuscated by adding a randomly-
++ * generated offset, and multiplying (mod %$2^{32}$%) by a fixed
++ * odd constant.
++ */
++
++ static const char *a_jobidencode(admin_svcop *svc)
++ {
++ admin_jobtable *j = &svc->prov->j;
++ static char buf[10];
++ unsigned long pre;
++ unsigned seq;
++
++ assert(svc->index <= JOB_INDEXMASK);
++ seq = j->v[svc->index].seq;
++ assert(seq <= JOB_SEQMASK);
++ pre = (unsigned long)svc->index | ((unsigned long)seq << JOB_SHIFT);
++ sprintf(buf, "J%08lx", ((pre + a_joboffset) * 0x0f87a7a3ul) & 0xffffffff);
++ return (buf);
++ }
++
++ /* --- @a_jobiddecode@ --- *
++ *
++ * Arguments: @admin_jobtable *j@ = pointer to a job table
++ * @const char *jid@ = pointer to a jobid string
++ *
++ * Returns: A pointer to the job's @svcop@ structure.
++ */
++
++ static admin_svcop *a_jobiddecode(admin_jobtable *j, const char *jid)
++ {
++ unsigned i;
++ unsigned long pre;
++
++ if (jid[0] != 'J')
++ return (0);
++ for (i = 1; i < 9; i++) {
++ if (!isxdigit((unsigned char)jid[i]))
++ return (0);
++ }
++ if (jid[9] != 0)
++ return (0);
++ pre = strtoul(jid + 1, 0, 16);
++ pre = ((pre * 0xbd11c40bul) - a_joboffset) & 0xffffffff;
++ i = pre & JOB_INDEXMASK;
++ if (i >= j->n || j->v[i].seq != (pre >> JOB_SHIFT))
++ return (0);
++ return (j->v[i].u.op);
++ }
++
++ /* --- @a_jobcreate@ --- *
++ *
++ * Arguments: @admin *a@ = pointer to administration client
++ *
++ * Returns: A pointer to a freshly-allocated @svcop@, or null.
++ *
++ * Use: Allocates a fresh @svcop@ and links it into a job table.
++ */
++
++ static admin_svcop *a_jobcreate(admin *a)
++ {
++ admin_svcop *svc;
++ unsigned i;
++ unsigned sz;
++ admin_jobtable *j = &a->j;
++
++ if (j->free != JOB_END) {
++ i = j->free;
++ j->free = j->v[i].u.next;
++ } else {
++ if (j->n == j->sz) {
++ if (j->sz > JOB_INDEXMASK)
++ return (0);
++ sz = j->sz;
++ if (!sz) {
++ j->sz = 16;
++ j->v = xmalloc(j->sz * sizeof(*j->v));
++ } else {
++ j->sz = sz << 1;
++ j->v = xrealloc(j->v, j->sz * sizeof(*j->v), sz * sizeof(*j->v));
++ }
++ }
++ i = j->n++;
++ j->v[i].seq = 0;
++ }
++ svc = xmalloc(sizeof(*svc));
++ svc->index = i;
++ svc->prov = a;
++ svc->next = j->active;
++ svc->prev = 0;
++ if (j->active) j->active->prev = svc;
++ j->active = svc;
++ j->v[i].u.op = svc;
++ IF_TRACING(T_ADMIN, {
++ trace(T_ADMIN, "admin: created job %s (%u)", a_jobidencode(svc), i);
++ })
++ return (svc);
++ }
++
++ /* --- @a_jobdestroy@ --- *
++ *
++ * Arguments: @admin_svcop *svc@ = pointer to job block
++ *
++ * Returns: ---
++ *
++ * Use: Frees up a completed (or cancelled) job.
++ */
++
++ static void a_jobdestroy(admin_svcop *svc)
++ {
++ admin *a = svc->prov;
++ admin_jobtable *j = &a->j;
++ unsigned i = svc->index;
++
++ IF_TRACING(T_ADMIN, {
++ trace(T_ADMIN, "admin: destroying job %s (%u)", a_jobidencode(svc), i);
++ })
++ assert(j->v[i].u.op = svc);
++ j->v[i].u.next = j->free;
++ j->v[i].seq++;
++ j->free = i;
++ if (svc->next) svc->next->prev = svc->prev;
++ if (svc->prev) svc->prev->next = svc->next;
++ else j->active = svc->next;
++ }
++
++ /* --- @a_jobtableinit@ --- *
++ *
++ * Arguments: @admin_jobtable *j@ = pointer to job table
++ *
++ * Returns: ---
++ *
++ * Use: Initializes a job table.
++ */
++
++ static void a_jobtableinit(admin_jobtable *j)
++ {
++ if (!a_joboffset)
++ a_joboffset = GR_RANGE(&rand_global, 0xffffffff) + 1;
++ j->n = j->sz = 0;
++ j->active = 0;
++ j->free = JOB_END;
++ j->v = 0;
++ }
++
++ /* --- @a_jobtablefinal@ --- *
++ *
++ * Arguments: @admin_jobtable *j@ = pointer to job table
++ *
++ * Returns: ---
++ *
++ * Use: Closes down a job table.
++ */
++
++ static void a_jobtablefinal(admin_jobtable *j)
++ {
++ admin_svcop *svc, *ssvc;
++
++ for (svc = j->active; svc; svc = ssvc) {
++ ssvc = svc->next;
++ a_bgfail(&svc->bg, "provider-failed", A_END);
++ a_bgrelease(&svc->bg);
++ }
++ if (j->v) xfree(j->v);
++ }
++
++ /*----- Services infrastructure -------------------------------------------*/
++
++ /* --- @a_svcfind@ --- *
++ *
++ * Arguments: @admin *a@ = the requesting client
++ * @const char *name@ = service name wanted
++ *
++ * Returns: The service requested, or null.
++ *
++ * Use: Finds a service; reports an error if the service couldn't be
++ * found.
++ */
++
++ static admin_service *a_svcfind(admin *a, const char *name)
++ {
++ admin_service *svc;
++
++ if ((svc = sym_find(&a_svcs, name, -1, 0, 0)) == 0) {
++ a_fail(a, "unknown-service", "%s", name, A_END);
++ return (0);
++ }
++ return (svc);
++ }
++
++ /* --- @a_svcunlink@ --- *
++ *
++ * Arguments: @admin_service *svc@ = pointer to service structure
++ *
++ * Returns: ---
++ *
++ * Use: Unlinks the service from its provider, without issuing a
++ * message or freeing the structure. The version string is
++ * freed, however.
++ */
++
++ static void a_svcunlink(admin_service *svc)
++ {
++ if (svc->next)
++ svc->next->prev = svc->prev;
++ if (svc->prev)
++ svc->prev->next = svc->next;
++ else
++ svc->prov->svcs = svc->next;
++ xfree(svc->version);
++ }
++
++ /* --- @a_svcrelease@ --- *
++ *
++ * Arguments: @admin_service *svc@ = pointer to service structure
++ *
++ * Returns: ---
++ *
++ * Use: Releases a service and frees its structure.
++ */
++
++ static void a_svcrelease(admin_service *svc)
++ {
++ a_notify("SVCRELEASE", "%s", SYM_NAME(svc), A_END);
++ a_svcunlink(svc);
++ sym_remove(&a_svcs, svc);
}
/*----- Name resolution operations ----------------------------------------*/
* answer straight away.
*/
-- a_bgadd(a, &r->bg, tag, a_rescancel);
++ if (a_bgadd(a, &r->bg, tag, a_rescancel))
++ goto fail;
T( trace(T_ADMIN, "admin: %u, resop %s, hostname `%s'",
a->seq, BGTAG(r), r->addr); )
xfree(r);
}
++ /*----- Option parsing ----------------------------------------------------*/
++
++ #define OPTIONS(argc, argv, guts) do { \
++ char **o_av = argv; \
++ for (;; o_av++) { \
++ if (!*o_av) \
++ break; \
++ if (mystrieq(*o_av, "--")) { \
++ o_av++; \
++ break; \
++ } \
++ guts \
++ if (**o_av == '-') \
++ goto bad_syntax; \
++ break; \
++ } \
++ argc -= o_av - argv; \
++ argv = o_av; \
++ } while (0)
++
++ #define OPT(name, guts) if (mystrieq(*o_av, name)) { guts continue; }
++
++ #define OPTARG(name, arg, guts) OPT(name, { \
++ const char *arg; \
++ arg = *++o_av; \
++ if (!arg) goto bad_syntax; \
++ guts \
++ })
++
++ #define OPTTIME(name, arg, guts) OPTARG(name, o_arg, { \
++ long arg; \
++ if ((arg = a_parsetime(o_arg)) < 0) { \
++ a_fail(a, "bad-time-spec", "%s", o_arg, A_END); \
++ goto fail; \
++ } \
++ guts \
++ })
++
/*----- Adding peers ------------------------------------------------------*/
/* --- @a_doadd@ --- *
static void acmd_add(admin *a, unsigned ac, char *av[])
{
-- unsigned i, j;
const char *tag = 0;
-- admin_addop *add = 0;
++ admin_addop *add;
/* --- Set stuff up --- */
add = xmalloc(sizeof(*add));
-- add->peer.name = xstrdup(av[0]);
++ add->peer.name = 0;
add->peer.t_ka = 0;
add->peer.tops = tun_default;
-- /* --- Make sure someone's not got there already --- */
--
-- if (p_find(av[0])) {
-- a_fail(a, "peer-exists", "%s", av[0], A_END);
-- goto fail;
-- }
--
/* --- Parse options --- */
-- i = 1;
-- for (;;) {
-- if (!av[i])
-- goto bad_syntax;
-- if (mystrieq(av[i], "-background")) {
-- if (!av[++i]) goto bad_syntax;
-- tag = av[i];
-- } else if (mystrieq(av[i], "-tunnel")) {
-- if (!av[++i]) goto bad_syntax;
-- for (j = 0;; j++) {
-- if (!tunnels[j]) {
-- a_fail(a, "unknown-tunnel", "%s", av[i], A_END);
++ OPTIONS(ac, av, {
++ OPTARG("-background", arg, { tag = arg; })
++ OPTARG("-tunnel", arg, {
++ unsigned i;
++ for (i = 0;; i++) {
++ if (!tunnels[i]) {
++ a_fail(a, "unknown-tunnel", "%s", arg, A_END);
goto fail;
}
-- if (mystrieq(av[i], tunnels[j]->name)) {
-- add->peer.tops = tunnels[j];
++ if (mystrieq(arg, tunnels[i]->name)) {
++ add->peer.tops = tunnels[i];
break;
}
}
-- } else if (mystrieq(av[i], "-keepalive")) {
-- long t;
-- if (!av[++i]) goto bad_syntax;
-- if ((t = a_parsetime(av[i])) < 0) {
-- a_fail(a, "bad-time-spec", "%s", av[i], A_END);
-- goto fail;
-- }
-- add->peer.t_ka = t;
-- } else if (mystrieq(av[i], "--")) {
-- i++;
-- break;
-- } else
-- break;
-- i++;
++ })
++ OPTTIME("-keepalive", t, { add->peer.t_ka = t; })
++ });
++
++ /* --- Make sure someone's not got there already --- */
++
++ if (!*av)
++ goto bad_syntax;
++ if (p_find(*av)) {
++ a_fail(a, "peer-exists", "%s", *av, A_END);
++ goto fail;
}
++ add->peer.name = xstrdup(*av++);
++ ac--;
/* --- Crank up the resolver --- */
-- a_resolve(a, &add->r, tag, a_doadd, ac - i, av + i);
++ a_resolve(a, &add->r, tag, a_doadd, ac, av);
return;
/* --- Clearing up --- */
bad_syntax:
-- a_fail(a, "bad-syntax", "add", "PEER [OPTIONS] ADDR ...", A_END);
++ a_fail(a, "bad-syntax", "add", "[OPTIONS] PEER ADDR ...", A_END);
fail:
-- xfree(add->peer.name);
++ if (add->peer.name) xfree(add->peer.name);
xfree(add);
return;
}
const char *cmd, unsigned msg)
{
long t = T_PING;
-- int i;
peer *p;
admin_pingop *pg = 0;
const char *tag = 0;
-- i = 0;
-- for (;;) {
-- if (!av[i])
-- goto bad_syntax;
-- if (mystrieq(av[i], "-background")) {
-- if (!av[++i]) goto bad_syntax;
-- tag = av[i];
-- } else if (mystrieq(av[i], "-timeout")) {
-- if (!av[++i]) goto bad_syntax;
-- if ((t = a_parsetime(av[i])) < 0) {
-- a_fail(a, "bad-time-spec", "%s", av[i], A_END);
-- return;
-- }
-- } else if (mystrieq(av[i], "--")) {
-- i++;
-- break;
-- } else
-- break;
-- i++;
-- }
--
-- if (!av[i]) goto bad_syntax;
-- if ((p = a_findpeer(a, av[i])) == 0)
++ OPTIONS(ac, av, {
++ OPTARG("-background", arg, { tag = arg; })
++ OPTTIME("-timeout", arg, { t = arg; })
++ });
++ if (!*av || av[1]) goto bad_syntax;
++ if ((p = a_findpeer(a, *av)) == 0)
return;
pg = xmalloc(sizeof(*pg));
gettimeofday(&pg->pingtime, 0);
-- a_bgadd(a, &pg->bg, tag, a_pingcancel);
++ if (a_bgadd(a, &pg->bg, tag, a_pingcancel))
++ goto fail;
T( trace(T_ADMIN, "admin: ping op %s: %s to %s",
BGTAG(pg), cmd, p_name(p)); )
if (p_pingsend(p, &pg->ping, msg, t, a_pong, pg)) {
bad_syntax:
a_fail(a, "bad-syntax", "%s", cmd, "[OPTIONS] PEER", cmd, A_END);
++ fail:
++ if (pg) xfree(pg);
return;
}
static void acmd_eping(admin *a, unsigned ac, char *av[])
{ a_ping(a, ac, av, "eping", MISC_EPING); }
++ /*----- Service commands --------------------------------------------------*/
++
++ static void acmd_svcclaim(admin *a, unsigned ac, char *av[])
++ {
++ admin_service *svc;
++ unsigned f;
++
++ svc = sym_find(&a_svcs, av[0], -1, sizeof(*svc), &f);
++ if (f) {
++ if (versioncmp(av[1], svc->version) <= 0) {
++ a_fail(a,
++ "service-exists",
++ "%s", SYM_NAME(svc),
++ "%s", svc->version,
++ A_END);
++ return;
++ }
++ a_write(svc->prov, "SVCCLAIM", 0, "%s", av[0], "%s", av[1], A_END);
++ a_svcunlink(svc);
++ }
++ svc->prov = a;
++ svc->version = xstrdup(av[1]);
++ svc->next = a->svcs;
++ svc->prev = 0;
++ if (a->svcs) a->svcs->prev = svc;
++ a->svcs = svc;
++ a_notify("SVCCLAIM", "%s", SYM_NAME(svc), "%s", svc->version, A_END);
++ a_ok(a);
++ }
++
++ static void acmd_svcrelease(admin *a, unsigned ac, char *av[])
++ {
++ admin_service *svc;
++
++ if ((svc = a_svcfind(a, av[0])) == 0)
++ return;
++ if (svc->prov != a) {
++ a_fail(a, "not-service-provider", "%s", SYM_NAME(svc), A_END);
++ return;
++ }
++ a_svcrelease(svc);
++ a_ok(a);
++ }
++
++ static void acmd_svcensure(admin *a, unsigned ac, char *av[])
++ {
++ admin_service *svc;
++
++ if ((svc = a_svcfind(a, av[0])) == 0)
++ return;
++ if (av[1] && versioncmp(svc->version, av[1]) < 0) {
++ a_fail(a, "service-too-old",
++ "%s", SYM_NAME(svc),
++ "%s", svc->version,
++ A_END);
++ return;
++ }
++ a_ok(a);
++ }
++
++ static void acmd_svcquery(admin *a, unsigned ac, char *av[])
++ {
++ admin_service *svc;
++
++ if ((svc = a_svcfind(a, av[0])) != 0) {
++ a_info(a, "name=%s", SYM_NAME(svc), "version=%s", svc->version, A_END);
++ a_ok(a);
++ }
++ }
++
++ static void acmd_svclist(admin *a, unsigned ac, char *av[])
++ {
++ admin_service *svc;
++ sym_iter i;
++
++ for (sym_mkiter(&i, &a_svcs); (svc = sym_next(&i)) != 0; )
++ a_info(a, "%s", SYM_NAME(svc), "%s", svc->version, A_END);
++ a_ok(a);
++ }
++
++ static void a_canceljob(admin_bgop *bg)
++ {
++ admin_svcop *svc = (admin_svcop *)bg;
++
++ a_write(svc->prov, "SVCCANCEL", 0, "%s", a_jobidencode(svc), A_END);
++ a_jobdestroy(svc);
++ }
++
++ static void acmd_svcsubmit(admin *a, unsigned ac, char *av[])
++ {
++ const char *tag = 0;
++ admin_service *svc;
++ admin_svcop *svcop;
++ const char *ver = 0;
++ dstr d = DSTR_INIT;
++
++ OPTIONS(ac, av, {
++ OPTARG("-background", arg, { tag = arg; })
++ OPTARG("-version", arg, { ver = arg; })
++ });
++ if (!*av) goto bad_syntax;
++ if ((svc = a_svcfind(a, *av)) == 0) goto fail;
++ if (ver && versioncmp(svc->version, ver) < 0) {
++ a_fail(a, "service-too-old",
++ "%s", SYM_NAME(svc),
++ "%s", svc->version,
++ A_END);
++ goto fail;
++ }
++ if ((svcop = a_jobcreate(svc->prov)) == 0) {
++ a_fail(a, "provider-overloaded", A_END);
++ goto fail;
++ }
++ if (a_bgadd(a, &svcop->bg, tag, a_canceljob)) {
++ a_jobdestroy(svcop);
++ goto fail;
++ }
++ a_write(svc->prov, "SVCJOB", 0,
++ "%s", a_jobidencode(svcop),
++ "?TOKENS", av,
++ A_END);
++ goto done;
++
++ bad_syntax:
++ a_fail(a, "bad-syntax", "svcsubmit", "[OPTIONS] SERVICE ARGS...", A_END);
++ fail:
++ done:
++ dstr_destroy(&d);
++ return;
++ }
++
++ static void a_replycmd(admin *a, const char *status, int donep, char *av[])
++ {
++ admin_svcop *svc;
++
++ if ((svc = a_jobiddecode(&a->j, av[0])) == 0) {
++ a_fail(a, "unknown-jobid", av[0], A_END);
++ return;
++ }
++ a_write(svc->bg.a, status, svc->bg.tag, "?TOKENS", av + 1, A_END);
++ if (donep) {
++ a_jobdestroy(svc);
++ a_bgrelease(&svc->bg);
++ }
++ a_ok(a);
++ }
++
++ static void acmd_svcok(admin *a, unsigned ac, char *av[])
++ { a_replycmd(a, "OK", 1, av); }
++ static void acmd_svcinfo(admin *a, unsigned ac, char *av[])
++ { a_replycmd(a, "INFO", 0, av); }
++ static void acmd_svcfail(admin *a, unsigned ac, char *av[])
++ { a_replycmd(a, "FAIL", 1, av); }
++
/*----- Administration commands -------------------------------------------*/
/* --- Miscellaneous commands --- */
#endif
static void acmd_watch(admin *a, unsigned ac, char *av[])
- -{
- - traceish(a, ac, av, "watch", w_opts, &a->f);
- -}
+ + { traceish(a, ac, av, "watch", w_opts, &a->f); }
static void alertcmd(admin *a, unsigned f_and, unsigned f_eq,
-- const char *tag, unsigned ac, char *av[])
-- {
-- dstr d = DSTR_INIT;
-- unsigned i;
--
-- dstr_puts(&d, "USER");
-- for (i = 0; i < ac; i++)
-- quotify(&d, av[i]);
-- dstr_putz(&d);
-- a_rawalert(f_and, f_eq, tag, d.buf, d.len);
-- dstr_destroy(&d);
-- a_ok(a);
-- }
--
++ const char *status, char *av[])
++ { a_alert(f_and, f_eq, status, "USER", "?TOKENS", av, A_END); a_ok(a); }
static void acmd_notify(admin *a, unsigned ac, char *av[])
-- { alertcmd(a, AF_NOTE, AF_NOTE, "NOTE", ac, av); }
++ { alertcmd(a, AF_NOTE, AF_NOTE, "NOTE", av); }
static void acmd_warn(admin *a, unsigned ac, char *av[])
-- { alertcmd(a, AF_WARN, AF_WARN, "WARN", ac, av); }
++ { alertcmd(a, AF_WARN, AF_WARN, "WARN", av); }
static void acmd_port(admin *a, unsigned ac, char *av[])
- -{
- - a_info(a, "%u", p_port(), A_END);
- - a_ok(a);
- -}
+ + { a_info(a, "%u", p_port(), A_END); a_ok(a); }
static void acmd_daemon(admin *a, unsigned ac, char *av[])
{
}
}
++ static void acmd_jobs(admin *a, unsigned ac, char *av[])
++ {
++ admin_bgop *bg;
++
++ for (bg = a->bg; bg; bg = bg->next) {
++ assert(bg->tag);
++ a_info(a, "%s", bg->tag, A_END);
++ }
++ a_ok(a);
++ }
++
++ static void acmd_bgcancel(admin *a, unsigned ac, char *av[])
++ {
++ admin_bgop *bg;
++
++ if ((bg = a_bgfind(a, av[0])) == 0)
++ a_fail(a, "unknown-tag", "%s", av[0], A_END);
++ else {
++ bg->cancel(bg);
++ a_bgrelease(bg);
++ a_ok(a);
++ }
++ }
++
static void acmd_list(admin *a, unsigned ac, char *av[])
{
peer *p;
+ +
for (p = p_first(); p; p = p_next(p))
a_info(a, "%s", p_name(p), A_END);
a_ok(a);
a_info(a, "start-time=%s", timestr(st->t_start), A_END);
a_info(a, "last-packet-time=%s", timestr(st->t_last), A_END);
a_info(a, "last-keyexch-time=%s", timestr(st->t_kx), A_END);
- - a_info(a, "packets-in=%lu bytes-in=%lu", st->n_in, st->sz_in, A_END);
- - a_info(a, "packets-out=%lu bytes-out=%lu",
- - st->n_out, st->sz_out, A_END);
- - a_info(a, "keyexch-packets-in=%lu keyexch-bytes-in=%lu",
- - st->n_kxin, st->sz_kxin, A_END);
- - a_info(a, "keyexch-packets-out=%lu keyexch-bytes-out=%lu",
- - st->n_kxout, st->sz_kxout, A_END);
- - a_info(a, "ip-packets-in=%lu ip-bytes-in=%lu",
- - st->n_ipin, st->sz_ipin, A_END);
- - a_info(a, "ip-packets-out=%lu ip-bytes-out=%lu",
- - st->n_ipout, st->sz_ipout, A_END);
+ + a_info(a, "packets-in=%lu", st->n_in, "bytes-in=%lu", st->sz_in, A_END);
+ + a_info(a,
+ + "packets-out=%lu", st->n_out,
+ + "bytes-out=%lu", st->sz_out,
+ + A_END);
+ + a_info(a,
+ + "keyexch-packets-in=%lu", st->n_kxin,
+ + "keyexch-bytes-in=%lu", st->sz_kxin,
+ + A_END);
+ + a_info(a,
+ + "keyexch-packets-out=%lu", st->n_kxout,
+ + "keyexch-bytes-out=%lu", st->sz_kxout,
+ + A_END);
+ + a_info(a,
+ + "ip-packets-in=%lu", st->n_ipin,
+ + "ip-bytes-in=%lu", st->sz_ipin,
+ + A_END);
+ + a_info(a,
+ + "ip-packets-out=%lu", st->n_ipout,
+ + "ip-bytes-out=%lu", st->sz_ipout,
+ + A_END);
a_info(a, "rejected-packets=%lu", st->n_reject, A_END);
a_ok(a);
}
static void acmd_kill(admin *a, unsigned ac, char *av[])
{
peer *p;
+ +
if ((p = a_findpeer(a, av[0])) != 0) {
p_destroy(p);
a_ok(a);
static void acmd_forcekx(admin *a, unsigned ac, char *av[])
{
peer *p;
+ +
if ((p = a_findpeer(a, av[0])) != 0) {
kx_start(&p->kx, 1);
a_ok(a);
{
a_warn("SERVER", "quit", "admin-request", A_END);
a_ok(a);
-- a_unlock(a);
a_quit();
}
static void acmd_tunnels(admin *a, unsigned ac, char *av[])
{
int i;
+ +
for (i = 0; tunnels[i]; i++)
a_info(a, "%s", tunnels[i]->name, A_END);
a_ok(a);
static void acmd_help(admin */*a*/, unsigned /*ac*/, char */*av*/[]);
static const acmd acmdtab[] = {
-- { "add", "PEER [OPTIONS] ADDR ...", 2, 0xffff, acmd_add },
++ { "add", "[OPTIONS] PEER ADDR ...", 2, 0xffff, acmd_add },
{ "addr", "PEER", 1, 1, acmd_addr },
++ { "bgcancel", "TAG", 1, 1, acmd_bgcancel },
{ "checkchal", "CHAL", 1, 1, acmd_checkchal },
{ "daemon", 0, 0, 0, acmd_daemon },
{ "eping", "[OPTIONS] PEER", 1, 0xffff, acmd_eping },
{ "greet", "PEER CHAL", 2, 2, acmd_greet },
{ "help", 0, 0, 0, acmd_help },
{ "ifname", "PEER", 1, 1, acmd_ifname },
++ { "jobs", 0, 0, 0, acmd_jobs },
{ "kill", "PEER", 1, 1, acmd_kill },
{ "list", 0, 0, 0, acmd_list },
{ "notify", "MESSAGE ...", 1, 0xffff, acmd_notify },
{ "reload", 0, 0, 0, acmd_reload },
{ "servinfo", 0, 0, 0, acmd_servinfo },
{ "setifname", "PEER NEW-NAME", 2, 2, acmd_setifname },
++ { "svcclaim", "SERVICE VERSION", 2, 2, acmd_svcclaim },
++ { "svcensure", "SERVICE [VERSION]", 1, 2, acmd_svcensure },
++ { "svcfail", "JOBID TOKENS...", 1, 0xffff, acmd_svcfail },
++ { "svcinfo", "JOBID TOKENS...", 1, 0xffff, acmd_svcinfo },
++ { "svclist", 0, 0, 0, acmd_svclist },
++ { "svcok", "JOBID", 1, 1, acmd_svcok },
++ { "svcquery", "SERVICE", 1, 1, acmd_svcquery },
++ { "svcrelease", "SERVICE", 1, 1, acmd_svcrelease },
++ { "svcsubmit", "[OPTIONS] SERVICE TOKENS...",
++ 2, 0xffff, acmd_svcsubmit },
{ "stats", "PEER", 1, 1, acmd_stats },
#ifndef NTRACE
{ "trace", "[OPTIONS]", 0, 1, acmd_trace },
static void acmd_help(admin *a, unsigned ac, char *av[])
{
const acmd *c;
+ +
for (c = acmdtab; c->name; c++) {
if (c->help)
a_info(a, "%s", c->name, "*%s", c->help, A_END);
/*----- Connection handling -----------------------------------------------*/
-- /* --- @a_lock@ --- *
- *
- * Arguments: @admin *a@ = pointer to an admin block
- *
- * Returns: ---
- *
- * Use: Locks an admin block so that it won't be destroyed
- * immediately.
- */
-
- static void a_lock(admin *a) { a->ref++; }
-
- /* --- @a_dodestroy@ --- *
++ /* --- @a_destroypending@ --- *
*
- * Arguments: @admin *a@ = pointer to an admin block
- *
- * Returns: ---
- *
- * Use: Locks an admin block so that it won't be destroyed
- * immediately.
- */
-
- static void a_lock(admin *a) { a->ref++; }
-
- /* --- @a_dodestroy@ --- *
- *
-- * Arguments: @admin *a@ = pointer to an admin block
++ * Arguments: ---
*
* Returns: ---
*
-- * Use: Actually does the legwork of destroying an admin block.
++ * Use: Destroys pending admin connections at a safe time.
*/
-- static void a_dodestroy(admin *a)
++ static void a_destroypending(void)
{
++ admin *a, *aa;
admin_bgop *bg, *bbg;
++ admin_service *svc, *ssvc;
-- T( trace(T_ADMIN, "admin: completing destruction of connection %u",
-- a->seq); )
++ /* --- Destroy connections marked as pending --- */
-- selbuf_destroy(&a->b);
-- for (bg = a->bg; bg; bg = bbg) {
-- bbg = bg->next;
-- bg->cancel(bg);
-- if (bg->tag) xfree(bg->tag);
-- xfree(bg);
++ for (a = a_dead; a; a = aa) {
++ aa = a->next;
++ assert(a->f & AF_DEAD);
++
++ /* --- Report what we're doing --- */
++
++ T( trace(T_ADMIN, "admin: completing destruction of connection %u",
++ a->seq); )
++
++ /* --- Abort any background jobs in progress --- */
++
++ for (bg = a->bg; bg; bg = bbg) {
++ bbg = bg->next;
++ bg->cancel(bg);
++ if (bg->tag) xfree(bg->tag);
++ xfree(bg);
++ }
++
++ /* --- Release services I hold, and abort pending jobs --- */
++
++ for (svc = a->svcs; svc; svc = ssvc) {
++ ssvc = svc->next;
++ a_svcrelease(svc);
++ }
++ a_jobtablefinal(&a->j);
++
++ /* --- Close file descriptors and selectory --- */
++
++ selbuf_destroy(&a->b);
++ if (a->b.reader.fd != a->w.fd) close(a->b.reader.fd);
++ close(a->w.fd);
++ if (a_stdin == a) a_stdin = 0;
++
++ /* --- Done --- */
++
++ DESTROY(a);
}
-- if (a->b.reader.fd != a->w.fd) close(a->b.reader.fd);
-- close(a->w.fd);
--
-- if (a_stdin == a)
-- a_stdin = 0;
-- if (a->next)
-- a->next->prev = a->prev;
-- if (a->prev)
-- a->prev->next = a->next;
-- else
-- admins = a->next;
-- DESTROY(a);
-- }
-- /* --- @a_unlock@ --- *
-- *
-- * Arguments: @admin *a@ = pointer to an admin block
-- *
-- * Returns: ---
-- *
-- * Use: Unlocks an admin block, allowing its destruction. This is
-- * also the second half of @a_destroy@.
-- */
++ /* --- All pending destruction completed --- */
-- static void a_unlock(admin *a)
-- {
-- assert(a->ref);
-- if (!--a->ref && (a->f & AF_DEAD))
-- a_dodestroy(a);
++ a_dead = 0;
}
/* --- @a_destroy@ --- *
static void a_destroy(admin *a)
{
-- /* --- Don't multiply destroy admin blocks --- */
--
if (a->f & AF_DEAD)
return;
-- /* --- Make sure nobody expects it to work --- */
--
-- a->f |= AF_DEAD;
-- T( trace(T_ADMIN, "admin: destroying connection %u", a->seq); )
--
-- /* --- Free the output buffers --- */
++ if (a->next) a->next->prev = a->prev;
++ if (a->prev) a->prev->next = a->next;
++ else admins = a->next;
-- if (a->out.hd)
-- sel_rmfile(&a->w);
++ if (a->out.hd) sel_rmfile(&a->w);
freequeue(&a->out);
-- /* --- If the block is locked, that's all we can manage --- */
++ a->f |= AF_DEAD;
++ a->next = a_dead;
++ a_dead = a;
-- if (!a->ref)
-- a_dodestroy(a);
-- T( else
-- trace(T_ADMIN, "admin: deferring destruction..."); )
++ T( trace(T_ADMIN, "admin: killing connection %u", a->seq); )
}
/* --- @a_line@ --- *
a_fail(a, "bad-syntax", "%s", c->name, "", A_END);
else
a_fail(a, "bad-syntax", "%s", c->name, "%s", c->help, A_END);
-- } else {
-- a_lock(a);
++ } else
c->func(a, ac, av + 1);
-- a_unlock(a);
-- }
return;
}
}
T( trace(T_ADMIN, "admin: accepted connection %u", a->seq); )
a->bg = 0;
a->ref = 0;
++ a->svcs = 0;
++ a_jobtableinit(&a->j);
a->f = f;
if (fd_in == STDIN_FILENO) a_stdin = a;
fdflags(fd_in, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
a_create(nfd, nfd, 0);
}
++ /* --- @a_preselect@ --- *
++ *
++ * Arguments: ---
++ *
++ * Returns: ---
++ *
++ * Use: Informs the admin module that we're about to select again,
++ * and that it should do cleanup things it has delayed until a
++ * `safe' time.
++ */
++
++ void a_preselect(void) { if (a_dead) a_destroypending(); }
++
/* --- @a_daemon@ --- *
*
* Arguments: ---
* Use: Informs the admin module that it's a daemon.
*/
- -void a_daemon(void)
- -{
- - flags |= F_DAEMON;
- -}
+ +void a_daemon(void) { flags |= F_DAEMON; }
/* --- @a_init@ --- *
*
struct sigaction sa;
size_t sz;
++ /* --- Create services table --- */
++
++ sym_create(&a_svcs);
++
/* --- Set up the socket address --- */
sz = strlen(name) + 1;