Functions connection_destroy, connection_release defined in connectin.h.
[pazpar2-moved-to-github.git] / src / connection.c
1 /* $Id: connection.c,v 1.2 2007-04-24 08:03:03 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 "parameters.h"
50
51
52 /** \brief Represents a physical, reusable  connection to a remote Z39.50 host
53  */
54 struct connection {
55     IOCHAN iochan;
56     COMSTACK link;
57     struct host *host;
58     struct client *client;
59     char *ibuf;
60     int ibufsize;
61     enum {
62         Conn_Resolving,
63         Conn_Connecting,
64         Conn_Open,
65         Conn_Waiting,
66     } state;
67     struct connection *next; // next for same host or next in free list
68 };
69
70 static struct connection *connection_freelist = 0;
71
72 static void remove_connection_from_host(struct connection *con)
73 {
74     struct connection **conp = &con->host->connections;
75     assert(con);
76     while (*conp)
77     {
78         if (*conp == con)
79         {
80             *conp = (*conp)->next;
81             return;
82         }
83         conp = &(*conp)->next;
84     }
85     assert(*conp == 0);
86 }
87
88 // Close connection and recycle structure
89 void connection_destroy(struct connection *co)
90 {
91     if (co->link)
92     {
93         cs_close(co->link);
94         iochan_destroy(co->iochan);
95     }
96
97     yaz_log(YLOG_DEBUG, "Connection destroy %s", co->host->hostport);
98
99     remove_connection_from_host(co);
100     if (co->client)
101     {
102         client_disconnect(co->client);
103     }
104     co->next = connection_freelist;
105     connection_freelist = co;
106 }
107
108 // Creates a new connection for client, associated with the host of 
109 // client's database
110 struct connection *connection_create(struct client *cl)
111 {
112     struct connection *new;
113     struct host *host = client_get_host(cl);
114
115     if ((new = connection_freelist))
116         connection_freelist = new->next;
117     else
118     {
119         new = xmalloc(sizeof (struct connection));
120         new->ibuf = 0;
121         new->ibufsize = 0;
122     }
123     new->host = host;
124     new->next = new->host->connections;
125     new->host->connections = new;
126     new->client = cl;
127     client_set_connection(cl, new);
128     new->link = 0;
129     new->state = Conn_Resolving;
130     if (host->ipport)
131         connection_connect(new);
132     return new;
133 }
134
135 static void connection_handler(IOCHAN i, int event)
136 {
137     struct connection *co = iochan_getdata(i);
138     struct client *cl = co->client;
139     struct session *se = 0;
140
141     if (cl)
142         se = client_get_session(cl);
143     else
144     {
145         yaz_log(YLOG_WARN, "Destroying orphan connection");
146         connection_destroy(co);
147         return;
148     }
149
150     if (co->state == Conn_Connecting && event & EVENT_OUTPUT)
151     {
152         int errcode;
153         socklen_t errlen = sizeof(errcode);
154
155         if (getsockopt(cs_fileno(co->link), SOL_SOCKET, SO_ERROR, &errcode,
156             &errlen) < 0 || errcode != 0)
157         {
158             client_fatal(cl);
159             return;
160         }
161         else
162         {
163             yaz_log(YLOG_DEBUG, "Connect OK");
164             co->state = Conn_Open;
165             if (cl)
166                 client_set_state(cl, Client_Connected);
167         }
168     }
169
170     else if (event & EVENT_INPUT)
171     {
172         int len = cs_get(co->link, &co->ibuf, &co->ibufsize);
173
174         if (len < 0)
175         {
176             yaz_log(YLOG_WARN|YLOG_ERRNO, "Error reading from %s", 
177                     client_get_url(cl));
178             connection_destroy(co);
179             return;
180         }
181         else if (len == 0)
182         {
183             yaz_log(YLOG_WARN, "EOF reading from %s", client_get_url(cl));
184             connection_destroy(co);
185             return;
186         }
187         else if (len > 1) // We discard input if we have no connection
188         {
189             co->state = Conn_Open;
190
191             if (client_is_our_response(cl))
192             {
193                 Z_APDU *a;
194
195                 odr_reset(global_parameters.odr_in);
196                 odr_setbuf(global_parameters.odr_in, co->ibuf, len, 0);
197                 if (!z_APDU(global_parameters.odr_in, &a, 0, 0))
198                 {
199                     client_fatal(cl);
200                     return;
201                 }
202                 switch (a->which)
203                 {
204                     case Z_APDU_initResponse:
205                         client_init_response(cl, a);
206                         break;
207                     case Z_APDU_searchResponse:
208                         client_search_response(cl, a);
209                         break;
210                     case Z_APDU_presentResponse:
211                         client_present_response(cl, a);
212                         break;
213                     case Z_APDU_close:
214                         client_close_response(cl, a);
215                         break;
216                     default:
217                         yaz_log(YLOG_WARN, 
218                                 "Unexpected Z39.50 response from %s",  
219                                 client_get_url(cl));
220                         client_fatal(cl);
221                         return;
222                 }
223                 // We aren't expecting staggered output from target
224                 // if (cs_more(t->link))
225                 //    iochan_setevent(i, EVENT_INPUT);
226             }
227             else  // we throw away response and go to idle mode
228             {
229                 yaz_log(YLOG_DEBUG, "Ignoring result of expired operation");
230                 client_set_state(cl, Client_Idle);
231             }
232         }
233         /* if len==1 we do nothing but wait for more input */
234     }
235     client_continue(cl);
236 }
237
238 // Disassociate connection from client
239 void connection_release(struct connection *co)
240 {
241     struct client *cl = co->client;
242
243     yaz_log(YLOG_DEBUG, "Connection release %s", co->host->hostport);
244     if (!cl)
245         return;
246     client_set_connection(cl, 0);
247     co->client = 0;
248 }
249
250 void connect_resolver_host(struct host *host)
251 {
252     struct connection *con = host->connections;
253     while (con)
254     {
255         if (con->state == Conn_Resolving)
256         {
257             if (!host->ipport) /* unresolved */
258             {
259                 connection_destroy(con);
260                 /* start all over .. at some point it will be NULL */
261                 con = host->connections;
262             }
263             else if (!con->client)
264             {
265                 yaz_log(YLOG_WARN, "connect_unresolved_host : ophan client");
266                 connection_destroy(con);
267                 /* start all over .. at some point it will be NULL */
268                 con = host->connections;
269             }
270             else
271             {
272                 connection_connect(con);
273                 con = con->next;
274             }
275         }
276     }
277 }
278
279 int connection_send_apdu(struct connection *co, Z_APDU *a)
280 {
281     char *buf;
282     int len, r;
283
284     if (!z_APDU(global_parameters.odr_out, &a, 0, 0))
285     {
286         odr_perror(global_parameters.odr_out, "Encoding APDU");
287         abort();
288     }
289     buf = odr_getbuf(global_parameters.odr_out, &len, 0);
290     r = cs_put(co->link, buf, len);
291     if (r < 0)
292     {
293         yaz_log(YLOG_WARN, "cs_put: %s", cs_errmsg(cs_errno(co->link)));
294         return -1;
295     }
296     else if (r == 1)
297     {
298         fprintf(stderr, "cs_put incomplete (ParaZ does not handle that)\n");
299         exit(1);
300     }
301     odr_reset(global_parameters.odr_out); /* release the APDU structure  */
302     co->state = Conn_Waiting;
303     iochan_setflags(co->iochan, EVENT_INPUT);
304     return 0;
305 }
306
307 struct host *connection_get_host(struct connection *con)
308 {
309     return con->host;
310 }
311
312 int connection_connect(struct connection *con)
313 {
314     COMSTACK link = 0;
315     struct host *host = connection_get_host(con);
316     void *addr;
317     int res;
318
319     assert(host->ipport);
320     assert(con);
321
322     if (!(link = cs_create(tcpip_type, 0, PROTO_Z3950)))
323     {
324         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
325         return -1;
326     }
327     
328     if (0 == strlen(global_parameters.zproxy_override)){
329         /* no Z39.50 proxy needed - direct connect */
330         yaz_log(YLOG_DEBUG, "Connection create %s", connection_get_url(con));
331         
332         if (!(addr = cs_straddr(link, host->ipport)))
333         {
334             yaz_log(YLOG_WARN|YLOG_ERRNO, 
335                     "Lookup of IP address %s failed", host->ipport);
336             return -1;
337         }
338         
339     } else {
340         /* Z39.50 proxy connect */
341         yaz_log(YLOG_DEBUG, "Connection create %s proxy %s", 
342                 connection_get_url(con), global_parameters.zproxy_override);
343         
344         if (!(addr = cs_straddr(link, global_parameters.zproxy_override)))
345         {
346             yaz_log(YLOG_WARN|YLOG_ERRNO, 
347                     "Lookup of IP address %s failed", 
348                     global_parameters.zproxy_override);
349             return -1;
350         }
351     }
352     
353     res = cs_connect(link, addr);
354     if (res < 0)
355     {
356         yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s",
357                 connection_get_url(con));
358         return -1;
359     }
360     con->link = link;
361     con->state = Conn_Connecting;
362     con->iochan = iochan_create(cs_fileno(link), connection_handler, 0);
363     iochan_setdata(con->iochan, con);
364     pazpar2_add_channel(con->iochan);
365
366     /* this fragment is bad DRY: from client_prep_connection */
367     client_set_state(con->client, Client_Connecting);
368     iochan_setflag(con->iochan, EVENT_OUTPUT);
369     return 0;
370 }
371
372 const char *connection_get_url(struct connection *co)
373 {
374     return client_get_url(co->client);
375 }
376
377 // Ensure that client has a connection associated
378 int client_prep_connection(struct client *cl)
379 {
380     struct connection *co;
381     struct session *se = client_get_session(cl);
382     struct host *host = client_get_host(cl);
383
384     co = client_get_connection(cl);
385
386     yaz_log(YLOG_DEBUG, "Client prep %s", client_get_url(cl));
387
388     if (!co)
389     {
390         // See if someone else has an idle connection
391         // We should look at timestamps here to select the longest-idle connection
392         for (co = host->connections; co; co = co->next)
393             if (co->state == Conn_Open && (!co->client || client_get_session(co->client) != se))
394                 break;
395         if (co)
396         {
397             connection_release(co);
398             client_set_connection(cl, co);
399             co->client = cl;
400         }
401         else
402             co = connection_create(cl);
403     }
404     if (co)
405     {
406         if (co->state == Conn_Connecting)
407         {
408             client_set_state(cl, Client_Connecting);
409             iochan_setflag(co->iochan, EVENT_OUTPUT);
410         }
411         else if (co->state == Conn_Open)
412         {
413             if (client_get_state(cl) == Client_Error 
414                 || client_get_state(cl) == Client_Disconnected)
415                 client_set_state(cl, Client_Idle);
416             iochan_setflag(co->iochan, EVENT_OUTPUT);
417         }
418         return 1;
419     }
420     else
421         return 0;
422 }
423
424
425
426 /*
427  * Local variables:
428  * c-basic-offset: 4
429  * indent-tabs-mode: nil
430  * End:
431  * vim: shiftwidth=4 tabstop=8 expandtab
432  */