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