7c7fba32801d04ec5cfe463e4afb4393d0bae976
[pazpar2-moved-to-github.git] / src / connection.c
1 /* $Id: connection.c,v 1.5 2007-06-26 13:03:46 adam Exp $
2    Copyright (c) 2006-2007, Index Data.
3
4 This file is part of Pazpar2.
5
6 Pazpar2 is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2, or (at your option) any later
9 version.
10
11 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Pazpar2; see the file LICENSE.  If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20  */
21
22 /** \file connection.c
23     \brief Z39.50 connection (low-level client)
24 */
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <sys/time.h>
30 #include <unistd.h>
31 #include <sys/socket.h>
32 #include <netdb.h>
33 #include <signal.h>
34 #include <ctype.h>
35 #include <assert.h>
36
37 #if HAVE_CONFIG_H
38 #include "cconfig.h"
39 #endif
40
41 #include <yaz/log.h>
42 #include <yaz/comstack.h>
43 #include <yaz/tcpip.h>
44 #include "connection.h"
45 #include "eventl.h"
46 #include "pazpar2.h"
47 #include "host.h"
48 #include "client.h"
49 #include "settings.h"
50 #include "parameters.h"
51
52
53 /** \brief Represents a physical, reusable  connection to a remote Z39.50 host
54  */
55 struct connection {
56     IOCHAN iochan;
57     COMSTACK link;
58     struct host *host;
59     struct client *client;
60     char *ibuf;
61     int ibufsize;
62     char *authentication; // Empty string or authentication string if set
63     enum {
64         Conn_Resolving,
65         Conn_Connecting,
66         Conn_Open,
67         Conn_Waiting,
68     } state;
69     struct connection *next; // next for same host or next in free list
70 };
71
72 static struct connection *connection_freelist = 0;
73
74 static void remove_connection_from_host(struct connection *con)
75 {
76     struct connection **conp = &con->host->connections;
77     assert(con);
78     while (*conp)
79     {
80         if (*conp == con)
81         {
82             *conp = (*conp)->next;
83             return;
84         }
85         conp = &(*conp)->next;
86     }
87     assert(*conp == 0);
88 }
89
90 // Close connection and recycle structure
91 void connection_destroy(struct connection *co)
92 {
93     if (co->link)
94     {
95         cs_close(co->link);
96         iochan_destroy(co->iochan);
97     }
98
99     yaz_log(YLOG_DEBUG, "Connection destroy %s", co->host->hostport);
100
101     remove_connection_from_host(co);
102     if (co->client)
103     {
104         client_disconnect(co->client);
105     }
106     co->next = connection_freelist;
107     connection_freelist = co;
108 }
109
110 // Creates a new connection for client, associated with the host of 
111 // client's database
112 struct connection *connection_create(struct client *cl)
113 {
114     struct connection *new;
115     struct host *host = client_get_host(cl);
116
117     if ((new = connection_freelist))
118         connection_freelist = new->next;
119     else
120     {
121         new = xmalloc(sizeof (struct connection));
122         new->ibuf = 0;
123         new->ibufsize = 0;
124     }
125     new->host = host;
126     new->next = new->host->connections;
127     new->host->connections = new;
128     new->client = cl;
129     new->authentication = "";
130     client_set_connection(cl, new);
131     new->link = 0;
132     new->state = Conn_Resolving;
133     if (host->ipport)
134         connection_connect(new);
135     return new;
136 }
137
138 static void connection_handler(IOCHAN i, int event)
139 {
140     struct connection *co = iochan_getdata(i);
141     struct client *cl = co->client;
142     struct session *se = 0;
143
144     if (cl)
145         se = client_get_session(cl);
146     else
147     {
148         yaz_log(YLOG_WARN, "Destroying orphan connection");
149         connection_destroy(co);
150         return;
151     }
152
153     if (event & EVENT_TIMEOUT)
154     {
155         if (co->state == Conn_Connecting)
156         {
157             yaz_log(YLOG_WARN,  "connect timeout %s", client_get_url(cl));
158             client_fatal(cl);
159         }
160         else
161         {
162             yaz_log(YLOG_LOG,  "idle timeout %s", client_get_url(cl));
163             connection_destroy(co);
164         }
165         return;
166     }
167     if (co->state == Conn_Connecting && event & EVENT_OUTPUT)
168     {
169         int errcode;
170         socklen_t errlen = sizeof(errcode);
171
172         if (getsockopt(cs_fileno(co->link), SOL_SOCKET, SO_ERROR, &errcode,
173             &errlen) < 0 || errcode != 0)
174         {
175             client_fatal(cl);
176             return;
177         }
178         else
179         {
180             yaz_log(YLOG_DEBUG, "Connect OK");
181             co->state = Conn_Open;
182             if (cl)
183                 client_set_state(cl, Client_Connected);
184             iochan_settimeout(i, 180);
185         }
186     }
187
188     else if (event & EVENT_INPUT)
189     {
190         int len = cs_get(co->link, &co->ibuf, &co->ibufsize);
191
192         if (len < 0)
193         {
194             yaz_log(YLOG_WARN|YLOG_ERRNO, "Error reading from %s", 
195                     client_get_url(cl));
196             connection_destroy(co);
197             return;
198         }
199         else if (len == 0)
200         {
201             yaz_log(YLOG_WARN, "EOF reading from %s", client_get_url(cl));
202             connection_destroy(co);
203             return;
204         }
205         else if (len > 1) // We discard input if we have no connection
206         {
207             co->state = Conn_Open;
208
209             if (client_is_our_response(cl))
210             {
211                 Z_APDU *a;
212
213                 odr_reset(global_parameters.odr_in);
214                 odr_setbuf(global_parameters.odr_in, co->ibuf, len, 0);
215                 if (!z_APDU(global_parameters.odr_in, &a, 0, 0))
216                 {
217                     client_fatal(cl);
218                     return;
219                 }
220                 switch (a->which)
221                 {
222                     case Z_APDU_initResponse:
223                         client_init_response(cl, a);
224                         break;
225                     case Z_APDU_searchResponse:
226                         client_search_response(cl, a);
227                         break;
228                     case Z_APDU_presentResponse:
229                         client_present_response(cl, a);
230                         break;
231                     case Z_APDU_close:
232                         client_close_response(cl, a);
233                         break;
234                     default:
235                         yaz_log(YLOG_WARN, 
236                                 "Unexpected Z39.50 response from %s",  
237                                 client_get_url(cl));
238                         client_fatal(cl);
239                         return;
240                 }
241                 // We aren't expecting staggered output from target
242                 // if (cs_more(t->link))
243                 //    iochan_setevent(i, EVENT_INPUT);
244             }
245             else  // we throw away response and go to idle mode
246             {
247                 yaz_log(YLOG_DEBUG, "Ignoring result of expired operation");
248                 client_set_state(cl, Client_Idle);
249             }
250         }
251         /* if len==1 we do nothing but wait for more input */
252     }
253     client_continue(cl);
254 }
255
256 // Disassociate connection from client
257 void connection_release(struct connection *co)
258 {
259     struct client *cl = co->client;
260
261     yaz_log(YLOG_DEBUG, "Connection release %s", co->host->hostport);
262     if (!cl)
263         return;
264     client_set_connection(cl, 0);
265     co->client = 0;
266 }
267
268 void connect_resolver_host(struct host *host)
269 {
270     struct connection *con = host->connections;
271     while (con)
272     {
273         if (con->state == Conn_Resolving)
274         {
275             if (!host->ipport) /* unresolved */
276             {
277                 connection_destroy(con);
278                 /* start all over .. at some point it will be NULL */
279                 con = host->connections;
280             }
281             else if (!con->client)
282             {
283                 yaz_log(YLOG_WARN, "connect_unresolved_host : ophan client");
284                 connection_destroy(con);
285                 /* start all over .. at some point it will be NULL */
286                 con = host->connections;
287             }
288             else
289             {
290                 connection_connect(con);
291                 con = con->next;
292             }
293         }
294     }
295 }
296
297 int connection_send_apdu(struct connection *co, Z_APDU *a)
298 {
299     char *buf;
300     int len, r;
301
302     if (!z_APDU(global_parameters.odr_out, &a, 0, 0))
303     {
304         odr_perror(global_parameters.odr_out, "Encoding APDU");
305         abort();
306     }
307     buf = odr_getbuf(global_parameters.odr_out, &len, 0);
308     r = cs_put(co->link, buf, len);
309     if (r < 0)
310     {
311         yaz_log(YLOG_WARN, "cs_put: %s", cs_errmsg(cs_errno(co->link)));
312         return -1;
313     }
314     else if (r == 1)
315     {
316         fprintf(stderr, "cs_put incomplete (ParaZ does not handle that)\n");
317         exit(1);
318     }
319     odr_reset(global_parameters.odr_out); /* release the APDU structure  */
320     co->state = Conn_Waiting;
321     iochan_setflags(co->iochan, EVENT_INPUT);
322     return 0;
323 }
324
325 struct host *connection_get_host(struct connection *con)
326 {
327     return con->host;
328 }
329
330 int connection_connect(struct connection *con)
331 {
332     COMSTACK link = 0;
333     struct host *host = connection_get_host(con);
334     void *addr;
335     int res;
336
337     struct session_database *sdb = client_get_database(con->client);
338     char *zproxy = session_setting_oneval(sdb, PZ_ZPROXY);
339
340     assert(host->ipport);
341     assert(con);
342
343     if (!(link = cs_create(tcpip_type, 0, PROTO_Z3950)))
344     {
345         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
346         return -1;
347     }
348     
349     if (!zproxy || 0 == strlen(zproxy)){
350         /* no Z39.50 proxy needed - direct connect */
351         yaz_log(YLOG_DEBUG, "Connection create %s", connection_get_url(con));
352         
353         if (!(addr = cs_straddr(link, host->ipport)))
354         {
355             yaz_log(YLOG_WARN|YLOG_ERRNO, 
356                     "Lookup of IP address %s failed", host->ipport);
357             return -1;
358         }
359         
360     } else {
361         /* Z39.50 proxy connect */
362         yaz_log(YLOG_DEBUG, "Connection create %s proxy %s", 
363                 connection_get_url(con), zproxy);
364         
365         if (!(addr = cs_straddr(link, zproxy)))
366         {
367             yaz_log(YLOG_WARN|YLOG_ERRNO, 
368                     "Lookup of ZProxy IP address %s failed", 
369                     zproxy);
370             return -1;
371         }
372     }
373     
374     res = cs_connect(link, addr);
375     if (res < 0)
376     {
377         yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s",
378                 connection_get_url(con));
379         return -1;
380     }
381     con->link = link;
382     con->state = Conn_Connecting;
383     con->iochan = iochan_create(cs_fileno(link), connection_handler, 0);
384     iochan_settimeout(con->iochan, 30);
385     iochan_setdata(con->iochan, con);
386     pazpar2_add_channel(con->iochan);
387
388     /* this fragment is bad DRY: from client_prep_connection */
389     client_set_state(con->client, Client_Connecting);
390     iochan_setflag(con->iochan, EVENT_OUTPUT);
391     return 0;
392 }
393
394 const char *connection_get_url(struct connection *co)
395 {
396     return client_get_url(co->client);
397 }
398
399 void connection_set_authentication(struct connection *co, char *auth)
400 {
401     co->authentication = auth;
402 }
403
404 // Ensure that client has a connection associated
405 int client_prep_connection(struct client *cl)
406 {
407     struct connection *co;
408     struct session *se = client_get_session(cl);
409     struct host *host = client_get_host(cl);
410
411     co = client_get_connection(cl);
412
413     yaz_log(YLOG_DEBUG, "Client prep %s", client_get_url(cl));
414
415     if (!co)
416     {
417         // See if someone else has an idle connection
418         // We should look at timestamps here to select the longest-idle connection
419         for (co = host->connections; co; co = co->next)
420             if (co->state == Conn_Open &&
421                 (!co->client || client_get_session(co->client) != se) &&
422                 !strcmp(co->authentication,
423                     session_setting_oneval(client_get_database(cl),
424                     PZ_AUTHENTICATION)))
425                 break;
426         if (co)
427         {
428             connection_release(co);
429             client_set_connection(cl, co);
430             co->client = cl;
431         }
432         else
433             co = connection_create(cl);
434     }
435     if (co)
436     {
437         if (co->state == Conn_Connecting)
438         {
439             client_set_state(cl, Client_Connecting);
440             iochan_setflag(co->iochan, EVENT_OUTPUT);
441         }
442         else if (co->state == Conn_Open)
443         {
444             if (client_get_state(cl) == Client_Error 
445                 || client_get_state(cl) == Client_Disconnected)
446                 client_set_state(cl, Client_Idle);
447             iochan_setflag(co->iochan, EVENT_OUTPUT);
448         }
449         return 1;
450     }
451     else
452         return 0;
453 }
454
455
456
457 /*
458  * Local variables:
459  * c-basic-offset: 4
460  * indent-tabs-mode: nil
461  * End:
462  * vim: shiftwidth=4 tabstop=8 expandtab
463  */