diff options
Diffstat (limited to '')
-rw-r--r-- | src/util/auto_clnt.c | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/src/util/auto_clnt.c b/src/util/auto_clnt.c new file mode 100644 index 0000000..2703054 --- /dev/null +++ b/src/util/auto_clnt.c @@ -0,0 +1,322 @@ +/*++ +/* NAME +/* auto_clnt 3 +/* SUMMARY +/* client endpoint maintenance +/* SYNOPSIS +/* #include <auto_clnt.h> +/* +/* AUTO_CLNT *auto_clnt_create(service, timeout, max_idle, max_ttl) +/* const char *service; +/* int timeout; +/* int max_idle; +/* int max_ttl; +/* +/* VSTREAM *auto_clnt_access(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* void auto_clnt_recover(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* const char *auto_clnt_name(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* void auto_clnt_free(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* DESCRIPTION +/* This module maintains IPC client endpoints that automatically +/* disconnect after a being idle for a configurable amount of time, +/* that disconnect after a configurable time to live, +/* and that transparently handle most server-initiated disconnects. +/* +/* This module tries each operation only a limited number of +/* times and then reports an error. This is unlike the +/* clnt_stream(3) module which will retry forever, so that +/* the application never experiences an error. +/* +/* auto_clnt_create() instantiates a client endpoint. +/* +/* auto_clnt_access() returns an open stream to the service specified +/* to auto_clnt_create(). The stream instance may change between calls. +/* The result is a null pointer in case of failure. +/* +/* auto_clnt_recover() recovers from a server-initiated disconnect +/* that happened in the middle of an I/O operation. +/* +/* auto_clnt_name() returns the name of the specified client endpoint. +/* +/* auto_clnt_free() destroys of the specified client endpoint. +/* +/* Arguments: +/* .IP service +/* The service argument specifies "transport:servername" where +/* transport is currently limited to one of the following: +/* .RS +/* .IP inet +/* servername has the form "inet:host:port". +/* .IP local +/* servername has the form "local:private/servicename" or +/* "local:public/servicename". This is the preferred way to +/* specify Postfix daemons that are configured as "unix" in +/* master.cf. +/* .IP unix +/* servername has the form "unix:private/servicename" or +/* "unix:public/servicename". This does not work on Solaris, +/* where Postfix uses STREAMS instead of UNIX-domain sockets. +/* .RE +/* .IP timeout +/* The time limit for sending, receiving, or for connecting +/* to a server. Specify a value <=0 to disable the time limit. +/* .IP max_idle +/* Idle time after which the client disconnects. Specify 0 to +/* disable the limit. +/* .IP max_ttl +/* Upper bound on the time that a connection is allowed to persist. +/* Specify 0 to disable the limit. +/* .IP open_action +/* Application call-back routine that opens a stream or returns a +/* null pointer upon failure. In case of success, the call-back routine +/* is expected to set the stream pathname to the server endpoint name. +/* .IP context +/* Application context that is passed to the open_action routine. +/* DIAGNOSTICS +/* Warnings: communication failure. Fatal error: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <events.h> +#include <iostuff.h> +#include <connect.h> +#include <split_at.h> +#include <auto_clnt.h> + +/* Application-specific. */ + + /* + * AUTO_CLNT is an opaque structure. None of the access methods can easily + * be implemented as a macro, and access is not performance critical anyway. + */ +struct AUTO_CLNT { + VSTREAM *vstream; /* buffered I/O */ + char *endpoint; /* host:port or pathname */ + int timeout; /* I/O time limit */ + int max_idle; /* time before client disconnect */ + int max_ttl; /* time before client disconnect */ + int (*connect) (const char *, int, int); /* unix, local, inet */ +}; + +static void auto_clnt_close(AUTO_CLNT *); + +/* auto_clnt_event - server-initiated disconnect or client-side max_idle */ + +static void auto_clnt_event(int unused_event, void *context) +{ + AUTO_CLNT *auto_clnt = (AUTO_CLNT *) context; + + /* + * Sanity check. This routine causes the stream to be closed, so it + * cannot be called when the stream is already closed. + */ + if (auto_clnt->vstream == 0) + msg_panic("auto_clnt_event: stream is closed"); + + auto_clnt_close(auto_clnt); +} + +/* auto_clnt_ttl_event - client-side expiration */ + +static void auto_clnt_ttl_event(int event, void *context) +{ + + /* + * XXX This function is needed only because event_request_timer() cannot + * distinguish between requests that specify the same call-back routine + * and call-back context. The fix is obvious: specify a request ID along + * with the call-back routine, but there is too much code that would have + * to be changed. + * + * XXX Should we be concerned that an overly aggressive optimizer will + * eliminate this function and replace calls to auto_clnt_ttl_event() by + * direct calls to auto_clnt_event()? It should not, because there exists + * code that takes the address of both functions. + */ + auto_clnt_event(event, context); +} + +/* auto_clnt_open - connect to service */ + +static void auto_clnt_open(AUTO_CLNT *auto_clnt) +{ + const char *myname = "auto_clnt_open"; + int fd; + + /* + * Sanity check. + */ + if (auto_clnt->vstream) + msg_panic("auto_clnt_open: stream is open"); + + /* + * Schedule a read event so that we can clean up when the remote side + * disconnects, and schedule a timer event so that we can cleanup an idle + * connection. Note that both events are handled by the same routine. + * + * Finally, schedule an event to force disconnection even when the + * connection is not idle. This is to prevent one client from clinging on + * to a server forever. + */ + fd = auto_clnt->connect(auto_clnt->endpoint, BLOCKING, auto_clnt->timeout); + if (fd < 0) { + msg_warn("connect to %s: %m", auto_clnt->endpoint); + } else { + if (msg_verbose) + msg_info("%s: connected to %s", myname, auto_clnt->endpoint); + auto_clnt->vstream = vstream_fdopen(fd, O_RDWR); + vstream_control(auto_clnt->vstream, + CA_VSTREAM_CTL_PATH(auto_clnt->endpoint), + CA_VSTREAM_CTL_TIMEOUT(auto_clnt->timeout), + CA_VSTREAM_CTL_END); + } + + if (auto_clnt->vstream != 0) { + close_on_exec(vstream_fileno(auto_clnt->vstream), CLOSE_ON_EXEC); + event_enable_read(vstream_fileno(auto_clnt->vstream), auto_clnt_event, + (void *) auto_clnt); + if (auto_clnt->max_idle > 0) + event_request_timer(auto_clnt_event, (void *) auto_clnt, + auto_clnt->max_idle); + if (auto_clnt->max_ttl > 0) + event_request_timer(auto_clnt_ttl_event, (void *) auto_clnt, + auto_clnt->max_ttl); + } +} + +/* auto_clnt_close - disconnect from service */ + +static void auto_clnt_close(AUTO_CLNT *auto_clnt) +{ + const char *myname = "auto_clnt_close"; + + /* + * Sanity check. + */ + if (auto_clnt->vstream == 0) + msg_panic("%s: stream is closed", myname); + + /* + * Be sure to disable read and timer events. + */ + if (msg_verbose) + msg_info("%s: disconnect %s stream", + myname, VSTREAM_PATH(auto_clnt->vstream)); + event_disable_readwrite(vstream_fileno(auto_clnt->vstream)); + event_cancel_timer(auto_clnt_event, (void *) auto_clnt); + event_cancel_timer(auto_clnt_ttl_event, (void *) auto_clnt); + (void) vstream_fclose(auto_clnt->vstream); + auto_clnt->vstream = 0; +} + +/* auto_clnt_recover - recover from server-initiated disconnect */ + +void auto_clnt_recover(AUTO_CLNT *auto_clnt) +{ + + /* + * Clean up. Don't re-connect until the caller needs it. + */ + if (auto_clnt->vstream) + auto_clnt_close(auto_clnt); +} + +/* auto_clnt_access - access a client stream */ + +VSTREAM *auto_clnt_access(AUTO_CLNT *auto_clnt) +{ + + /* + * Open a stream or restart the idle timer. + * + * Important! Do not restart the TTL timer! + */ + if (auto_clnt->vstream == 0) { + auto_clnt_open(auto_clnt); + } else { + if (auto_clnt->max_idle > 0) + event_request_timer(auto_clnt_event, (void *) auto_clnt, + auto_clnt->max_idle); + } + return (auto_clnt->vstream); +} + +/* auto_clnt_create - create client stream object */ + +AUTO_CLNT *auto_clnt_create(const char *service, int timeout, + int max_idle, int max_ttl) +{ + const char *myname = "auto_clnt_create"; + char *transport = mystrdup(service); + char *endpoint; + AUTO_CLNT *auto_clnt; + + /* + * Don't open the stream until the caller needs it. + */ + if ((endpoint = split_at(transport, ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("need service transport:endpoint instead of \"%s\"", service); + if (msg_verbose) + msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint); + auto_clnt = (AUTO_CLNT *) mymalloc(sizeof(*auto_clnt)); + auto_clnt->vstream = 0; + auto_clnt->endpoint = mystrdup(endpoint); + auto_clnt->timeout = timeout; + auto_clnt->max_idle = max_idle; + auto_clnt->max_ttl = max_ttl; + if (strcmp(transport, "inet") == 0) { + auto_clnt->connect = inet_connect; + } else if (strcmp(transport, "local") == 0) { + auto_clnt->connect = LOCAL_CONNECT; + } else if (strcmp(transport, "unix") == 0) { + auto_clnt->connect = unix_connect; + } else { + msg_fatal("invalid transport name: %s in service: %s", + transport, service); + } + myfree(transport); + return (auto_clnt); +} + +/* auto_clnt_name - return client stream name */ + +const char *auto_clnt_name(AUTO_CLNT *auto_clnt) +{ + return (auto_clnt->endpoint); +} + +/* auto_clnt_free - destroy client stream instance */ + +void auto_clnt_free(AUTO_CLNT *auto_clnt) +{ + if (auto_clnt->vstream) + auto_clnt_close(auto_clnt); + myfree(auto_clnt->endpoint); + myfree((void *) auto_clnt); +} |