1 /* $NetBSD: auto_clnt.c,v 1.1.1.1 2009/06/23 10:08:59 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* auto_clnt 3 6 /* SUMMARY 7 /* client endpoint maintenance 8 /* SYNOPSIS 9 /* #include <auto_clnt.h> 10 /* 11 /* AUTO_CLNT *auto_clnt_create(service, timeout, max_idle, max_ttl) 12 /* const char *service; 13 /* int timeout; 14 /* int max_idle; 15 /* int max_ttl; 16 /* 17 /* VSTREAM *auto_clnt_access(auto_clnt) 18 /* AUTO_CLNT *auto_clnt; 19 /* 20 /* void auto_clnt_recover(auto_clnt) 21 /* AUTO_CLNT *auto_clnt; 22 /* 23 /* const char *auto_clnt_name(auto_clnt) 24 /* AUTO_CLNT *auto_clnt; 25 /* 26 /* void auto_clnt_free(auto_clnt) 27 /* AUTO_CLNT *auto_clnt; 28 /* DESCRIPTION 29 /* This module maintains IPC client endpoints that automatically 30 /* disconnect after a being idle for a configurable amount of time, 31 /* that disconnect after a configurable time to live, 32 /* and that transparently handle most server-initiated disconnects. 33 /* 34 /* This module tries each operation only a limited number of 35 /* times and then reports an error. This is unlike the 36 /* clnt_stream(3) module which will retry forever, so that 37 /* the application never experiences an error. 38 /* 39 /* auto_clnt_create() instantiates a client endpoint. 40 /* 41 /* auto_clnt_access() returns an open stream to the service specified 42 /* to auto_clnt_create(). The stream instance may change between calls. 43 /* The result is a null pointer in case of failure. 44 /* 45 /* auto_clnt_recover() recovers from a server-initiated disconnect 46 /* that happened in the middle of an I/O operation. 47 /* 48 /* auto_clnt_name() returns the name of the specified client endpoint. 49 /* 50 /* auto_clnt_free() destroys of the specified client endpoint. 51 /* 52 /* Arguments: 53 /* .IP service 54 /* The service argument specifies "transport:servername" where 55 /* transport is currently limited to one of the following: 56 /* .RS 57 /* .IP inet 58 /* servername has the form "inet:host:port". 59 /* .IP local 60 /* servername has the form "local:private/servicename" or 61 /* "local:public/servicename". This is the preferred way to 62 /* specify Postfix daemons that are configured as "unix" in 63 /* master.cf. 64 /* .IP unix 65 /* servername has the form "unix:private/servicename" or 66 /* "unix:public/servicename". This does not work on Solaris, 67 /* where Postfix uses STREAMS instead of UNIX-domain sockets. 68 /* .RE 69 /* .IP timeout 70 /* The time limit for sending, receiving, or for connecting 71 /* to a server. Specify a value <=0 to disable the time limit. 72 /* .IP max_idle 73 /* Idle time after which the client disconnects. Specify 0 to 74 /* disable the limit. 75 /* .IP max_ttl 76 /* Upper bound on the time that a connection is allowed to persist. 77 /* Specify 0 to disable the limit. 78 /* .IP open_action 79 /* Application call-back routine that opens a stream or returns a 80 /* null pointer upon failure. In case of success, the call-back routine 81 /* is expected to set the stream pathname to the server endpoint name. 82 /* .IP context 83 /* Application context that is passed to the open_action routine. 84 /* DIAGNOSTICS 85 /* Warnings: communication failure. Fatal error: out of memory. 86 /* LICENSE 87 /* .ad 88 /* .fi 89 /* The Secure Mailer license must be distributed with this software. 90 /* AUTHOR(S) 91 /* Wietse Venema 92 /* IBM T.J. Watson Research 93 /* P.O. Box 704 94 /* Yorktown Heights, NY 10598, USA 95 /*--*/ 96 97 /* System library. */ 98 99 #include <sys_defs.h> 100 #include <string.h> 101 102 /* Utility library. */ 103 104 #include <msg.h> 105 #include <mymalloc.h> 106 #include <vstream.h> 107 #include <events.h> 108 #include <iostuff.h> 109 #include <connect.h> 110 #include <split_at.h> 111 #include <auto_clnt.h> 112 113 /* Application-specific. */ 114 115 /* 116 * AUTO_CLNT is an opaque structure. None of the access methods can easily 117 * be implemented as a macro, and access is not performance critical anyway. 118 */ 119 struct AUTO_CLNT { 120 VSTREAM *vstream; /* buffered I/O */ 121 char *endpoint; /* host:port or pathname */ 122 int timeout; /* I/O time limit */ 123 int max_idle; /* time before client disconnect */ 124 int max_ttl; /* time before client disconnect */ 125 int (*connect) (const char *, int, int); /* unix, local, inet */ 126 }; 127 128 static void auto_clnt_close(AUTO_CLNT *); 129 130 /* auto_clnt_event - server-initiated disconnect or client-side max_idle */ 131 132 static void auto_clnt_event(int unused_event, char *context) 133 { 134 AUTO_CLNT *auto_clnt = (AUTO_CLNT *) context; 135 136 /* 137 * Sanity check. This routine causes the stream to be closed, so it 138 * cannot be called when the stream is already closed. 139 */ 140 if (auto_clnt->vstream == 0) 141 msg_panic("auto_clnt_event: stream is closed"); 142 143 auto_clnt_close(auto_clnt); 144 } 145 146 /* auto_clnt_ttl_event - client-side expiration */ 147 148 static void auto_clnt_ttl_event(int event, char *context) 149 { 150 151 /* 152 * XXX This function is needed only because event_request_timer() cannot 153 * distinguish between requests that specify the same call-back routine 154 * and call-back context. The fix is obvious: specify a request ID along 155 * with the call-back routine, but there is too much code that would have 156 * to be changed. 157 * 158 * XXX Should we be concerned that an overly agressive optimizer will 159 * eliminate this function and replace calls to auto_clnt_ttl_event() by 160 * direct calls to auto_clnt_event()? It should not, because there exists 161 * code that takes the address of both functions. 162 */ 163 auto_clnt_event(event, context); 164 } 165 166 /* auto_clnt_open - connect to service */ 167 168 static void auto_clnt_open(AUTO_CLNT *auto_clnt) 169 { 170 const char *myname = "auto_clnt_open"; 171 int fd; 172 173 /* 174 * Sanity check. 175 */ 176 if (auto_clnt->vstream) 177 msg_panic("auto_clnt_open: stream is open"); 178 179 /* 180 * Schedule a read event so that we can clean up when the remote side 181 * disconnects, and schedule a timer event so that we can cleanup an idle 182 * connection. Note that both events are handled by the same routine. 183 * 184 * Finally, schedule an event to force disconnection even when the 185 * connection is not idle. This is to prevent one client from clinging on 186 * to a server forever. 187 */ 188 fd = auto_clnt->connect(auto_clnt->endpoint, BLOCKING, auto_clnt->timeout); 189 if (fd < 0) { 190 msg_warn("connect to %s: %m", auto_clnt->endpoint); 191 } else { 192 if (msg_verbose) 193 msg_info("%s: connected to %s", myname, auto_clnt->endpoint); 194 auto_clnt->vstream = vstream_fdopen(fd, O_RDWR); 195 vstream_control(auto_clnt->vstream, 196 VSTREAM_CTL_PATH, auto_clnt->endpoint, 197 VSTREAM_CTL_TIMEOUT, auto_clnt->timeout, 198 VSTREAM_CTL_END); 199 } 200 201 if (auto_clnt->vstream != 0) { 202 close_on_exec(vstream_fileno(auto_clnt->vstream), CLOSE_ON_EXEC); 203 event_enable_read(vstream_fileno(auto_clnt->vstream), auto_clnt_event, 204 (char *) auto_clnt); 205 if (auto_clnt->max_idle > 0) 206 event_request_timer(auto_clnt_event, (char *) auto_clnt, 207 auto_clnt->max_idle); 208 if (auto_clnt->max_ttl > 0) 209 event_request_timer(auto_clnt_ttl_event, (char *) auto_clnt, 210 auto_clnt->max_ttl); 211 } 212 } 213 214 /* auto_clnt_close - disconnect from service */ 215 216 static void auto_clnt_close(AUTO_CLNT *auto_clnt) 217 { 218 const char *myname = "auto_clnt_close"; 219 220 /* 221 * Sanity check. 222 */ 223 if (auto_clnt->vstream == 0) 224 msg_panic("%s: stream is closed", myname); 225 226 /* 227 * Be sure to disable read and timer events. 228 */ 229 if (msg_verbose) 230 msg_info("%s: disconnect %s stream", 231 myname, VSTREAM_PATH(auto_clnt->vstream)); 232 event_disable_readwrite(vstream_fileno(auto_clnt->vstream)); 233 event_cancel_timer(auto_clnt_event, (char *) auto_clnt); 234 event_cancel_timer(auto_clnt_ttl_event, (char *) auto_clnt); 235 (void) vstream_fclose(auto_clnt->vstream); 236 auto_clnt->vstream = 0; 237 } 238 239 /* auto_clnt_recover - recover from server-initiated disconnect */ 240 241 void auto_clnt_recover(AUTO_CLNT *auto_clnt) 242 { 243 244 /* 245 * Clean up. Don't re-connect until the caller needs it. 246 */ 247 if (auto_clnt->vstream) 248 auto_clnt_close(auto_clnt); 249 } 250 251 /* auto_clnt_access - access a client stream */ 252 253 VSTREAM *auto_clnt_access(AUTO_CLNT *auto_clnt) 254 { 255 256 /* 257 * Open a stream or restart the idle timer. 258 * 259 * Important! Do not restart the TTL timer! 260 */ 261 if (auto_clnt->vstream == 0) { 262 auto_clnt_open(auto_clnt); 263 } else { 264 if (auto_clnt->max_idle > 0) 265 event_request_timer(auto_clnt_event, (char *) auto_clnt, 266 auto_clnt->max_idle); 267 } 268 return (auto_clnt->vstream); 269 } 270 271 /* auto_clnt_create - create client stream object */ 272 273 AUTO_CLNT *auto_clnt_create(const char *service, int timeout, 274 int max_idle, int max_ttl) 275 { 276 const char *myname = "auto_clnt_create"; 277 char *transport = mystrdup(service); 278 char *endpoint; 279 AUTO_CLNT *auto_clnt; 280 281 /* 282 * Don't open the stream until the caller needs it. 283 */ 284 if ((endpoint = split_at(transport, ':')) == 0 285 || *endpoint == 0 || *transport == 0) 286 msg_fatal("need service transport:endpoint instead of \"%s\"", service); 287 if (msg_verbose) 288 msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint); 289 auto_clnt = (AUTO_CLNT *) mymalloc(sizeof(*auto_clnt)); 290 auto_clnt->vstream = 0; 291 auto_clnt->endpoint = mystrdup(endpoint); 292 auto_clnt->timeout = timeout; 293 auto_clnt->max_idle = max_idle; 294 auto_clnt->max_ttl = max_ttl; 295 if (strcmp(transport, "inet") == 0) { 296 auto_clnt->connect = inet_connect; 297 } else if (strcmp(transport, "local") == 0) { 298 auto_clnt->connect = LOCAL_CONNECT; 299 } else if (strcmp(transport, "unix") == 0) { 300 auto_clnt->connect = unix_connect; 301 } else { 302 msg_fatal("invalid transport name: %s in service: %s", 303 transport, service); 304 } 305 myfree(transport); 306 return (auto_clnt); 307 } 308 309 /* auto_clnt_name - return client stream name */ 310 311 const char *auto_clnt_name(AUTO_CLNT *auto_clnt) 312 { 313 return (auto_clnt->endpoint); 314 } 315 316 /* auto_clnt_free - destroy client stream instance */ 317 318 void auto_clnt_free(AUTO_CLNT *auto_clnt) 319 { 320 if (auto_clnt->vstream) 321 auto_clnt_close(auto_clnt); 322 myfree(auto_clnt->endpoint); 323 myfree((char *) auto_clnt); 324 } 325