From: Mark Wooding Date: Mon, 1 Jan 2007 15:30:39 +0000 (+0000) Subject: Merge branches 'cleanup' and 'services' X-Git-Tag: 1.0.0pre8~94 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/tripe/commitdiff_plain/d167fc1b2599ab06f857e984fc203fd7f64f4c0a?hp=-c Merge branches 'cleanup' and 'services' * cleanup: server/admin: Fix tokenization of statistics output. Fix typos in messages. doc: Various small cleanups to tripe-admin.5. cleanup: Various simple whitespace changes. cleanup: Rename a few badly-chosen variables. * services: doc: Document the services messages. admin: Implement the main job commands. admin: Implement job table infrastructure. admin: Service ownership infrastructure and commands. servutil: Implement version number comparison. admin: New ?TOKENS formatting directive. admin: Rename the unknown-service error. admin: Improve handling of background jobs. admin: Option parser macros. admin: Put all command options at the start of the command-line. admin: Fix premature close in a_bgrelease. admin: Remove locking; new safe client destruction. Conflicts: server/admin.c --- d167fc1b2599ab06f857e984fc203fd7f64f4c0a diff --combined doc/tripe-admin.5.in index 0d9e7b66,4a966aa0,17c83597..822bbf50 --- a/doc/tripe-admin.5.in +++ b/doc/tripe-admin.5.in @@@@ -65,10 -65,10 -65,15 +65,15 @@@@ All commands can be run as simple comma 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 @@@@ -92,10 -92,10 -97,6 +97,6 @@@@ Finally, th .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 @@@@ -150,6 -150,6 -151,64 +151,64 @@@@ response: it will always detach and the 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 @@@@ -164,14 -164,13 -223,14 +223,13 @@@@ An Internet socket, naming an IPv4 addr 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 @@@@ -213,7 -212,7 -272,7 +271,7 @@@@ letters control collections of message .\"* 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 @@@@ -257,6 -256,6 -316,10 +315,10 @@@@ Emits a 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 @@@@ -267,7 -266,7 -330,7 +329,7 @@@@ or in a greeting message .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 @@@@ -311,6 -310,6 -374,11 +373,11 @@@@ packets which are to be encrypted and s 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 . @@@@ -379,10 -378,16 -447,10 +446,16 @@@@ Run the command in the background, usin .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 @@@@ -434,6 -439,6 -502,108 +507,108 @@@@ This is useful if firewalling decision 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 @@@@ -524,15 -529,15 -694,15 +699,15 @@@@ its version string. The server nam 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: @@@@ -614,6 -619,6 -784,13 +789,13 @@@@ An error occurred during the attempt t .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 .) @@@@ -646,6 -651,6 -823,30 +828,30 @@@@ The DNS nam .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 @@@@ -662,13 -667,13 -863,32 +868,32 @@@@ an 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. @@@@ -717,6 -722,6 -937,17 +942,17 @@@@ as a result of .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 @@@@ -1096,6 -1101,7 -1327,6 +1332,7 @@@@ An administration client issued a warni .SH "SUMMARY" .SS "Command responses" .nf + +.BI "BGDETACH " tag .BI "BGFAIL " tag " " tokens \fR... .BI "BGINFO " tag " " tokens \fR... .BI "BGOK " tag diff --combined server/admin.c index b166b853,bd5cc029,d0201693..797b4786 --- a/server/admin.c +++ b/server/admin.c @@@@ -62,8 -62,8 -62,10 +62,10 @@@@ static const trace_opt w_opts[] = /*----- 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; @@@@ -75,8 -75,8 -77,6 +77,6 @@@@ #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") @@@@ -301,6 -301,6 -301,9 +301,9 @@@@ static void a_vformat(dstr *d, const ch 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) { @@@@ -338,6 -338,7 -341,6 +341,7 @@@@ static void a_vwrite(admin *a, const ch 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); @@@@ -351,6 -352,7 -354,6 +355,7 @@@@ static void a_write(admin *a, const cha const char *fmt, ...) { va_list ap; + + va_start(ap, fmt); a_vwrite(a, status, tag, fmt, ap); va_end(ap); @@@@ -372,6 -374,7 -375,6 +377,7 @@@@ static void a_ok(admin *a) { a_write(a 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); @@@@ -380,6 -383,7 -383,6 +386,7 @@@@ 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); @@@@ -425,7 -429,7 -428,7 +432,7 @@@@ static void a_rawalert(unsigned f_and, 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; @@@@ -433,20 -437,21 -436,18 +440,19 @@@@ 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@ --- * * @@@@ -570,9 -575,7 -571,9 +576,7 @@@@ static void a_sigdie(int sig, void *v */ 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@ --- * * @@@@ -621,6 -624,6 -622,25 +625,25 @@@@ static peer *a_findpeer(admin *a, cons #define BGTAG(bg) \ (((admin_bgop *)(bg))->tag ? ((admin_bgop *)(bg))->tag : "") ++ /* --- @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 @@@@ -642,8 -645,8 -662,7 +665,7 @@@@ static void a_bgrelease(admin_bgop *bg 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@ --- * @@@@ -683,16 -686,16 -702,21 +705,21 @@@@ static void a_bgfail(admin_bgop *bg, co * @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); @@@@ -703,9 -706,9 -727,260 +730,260 @@@@ 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 ----------------------------------------*/ @@@@ -837,7 -840,7 -1112,8 +1115,8 @@@@ static void a_resolve(admin *a, admin_r * 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); ) @@@@ -865,6 -868,6 -1141,44 +1144,44 @@@@ fail 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@ --- * @@@@ -910,72 -913,72 -1224,58 +1227,58 @@@@ static void a_doadd(admin_resop *r, in 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; } @@@@ -1052,38 -1055,38 -1352,21 +1355,21 @@@@ static void a_ping(admin *a, unsigned a 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)) { @@@@ -1094,6 -1097,6 -1377,8 +1380,8 @@@@ bad_syntax: a_fail(a, "bad-syntax", "%s", cmd, "[OPTIONS] PEER", cmd, A_END); ++ fail: ++ if (pg) xfree(pg); return; } @@@@ -1102,6 -1105,6 -1387,160 +1390,160 @@@@ static void acmd_ping(admin *a, unsigne 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 --- */ @@@@ -1174,35 -1177,30 -1613,23 +1616,18 @@@@ static void acmd_trace(admin *a, unsign #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[]) { @@@@ -1221,9 -1219,10 -1648,33 +1646,34 @@@@ } } ++ 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); @@@@ -1339,17 -1338,27 -1790,17 +1789,27 @@@@ static void acmd_stats(admin *a, unsign 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); } @@@@ -1357,6 -1366,7 -1808,6 +1817,7 @@@@ 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); @@@@ -1366,6 -1376,7 -1817,6 +1827,7 @@@@ 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); @@@@ -1379,7 -1390,7 -1830,6 +1841,6 @@@@ static void acmd_quit(admin *a, unsigne { a_warn("SERVER", "quit", "admin-request", A_END); a_ok(a); -- a_unlock(a); a_quit(); } @@@@ -1392,6 -1403,7 -1842,6 +1853,7 @@@@ static void acmd_version(admin *a, unsi 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); @@@@ -1409,8 -1421,8 -1859,9 +1871,9 @@@@ typedef struct acmd 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 }, @@@@ -1419,6 -1431,6 -1870,7 +1882,7 @@@@ { "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 }, @@@@ -1429,6 -1441,6 -1881,16 +1893,16 @@@@ { "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 }, @@@@ -1443,6 -1455,7 -1905,6 +1917,7 @@@@ 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); @@@@ -1454,70 -1467,70 -1916,64 +1929,64 @@@@ /*----- 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@ --- * @@@@ -1543,28 -1556,28 -1999,21 +2012,21 @@@@ static void freequeue(oqueue *q 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@ --- * @@@@ -1608,11 -1621,11 -2057,8 +2070,8 @@@@ static void a_line(char *p, size_t len 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; } } @@@@ -1638,6 -1651,6 -2084,8 +2097,8 @@@@ void a_create(int fd_in, int fd_out, un 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); @@@@ -1678,6 -1691,6 -2126,19 +2139,19 @@@@ static void a_accept(int fd, unsigned m 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: --- @@@@ -1687,10 -1700,7 -2148,10 +2161,7 @@@@ * 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@ --- * * @@@@ -1709,6 -1719,6 -2170,10 +2180,10 @@@@ void a_init(const char *name struct sigaction sa; size_t sz; ++ /* --- Create services table --- */ ++ ++ sym_create(&a_svcs); ++ /* --- Set up the socket address --- */ sz = strlen(name) + 1; diff --combined server/tripe.c index be37617a,7e4ffc07,dd385fbd..b5bd21c4 --- a/server/tripe.c +++ b/server/tripe.c @@@@ -96,10 -96,7 -96,10 +96,7 @@@@ static void usage(FILE *fp [-k PRIV-KEYRING] [-K PUB-KEYRING] [-t KEY-TAG]\n"); } - -static void version(FILE *fp) - -{ - - pquis(fp, "$, version " VERSION "\n"); - -} + +static void version(FILE *fp) { pquis(fp, "$, version " VERSION "\n"); } static void help(FILE *fp) { @@@@ -341,6 -338,6 -341,7 +338,7 @@@@ int main(int argc, char *argv[] sel_addtimer(&sel, &it, &tv, interval, 0); for (;;) { ++ a_preselect(); if (!sel_select(&sel)) selerr = 0; else if (errno != EINTR && errno != EAGAIN) {