Implemented numeric sorting (sort by year). Bug #820.
[pazpar2-moved-to-github.git] / src / connection.c
1 /* $Id: connection.c,v 1.7 2007-07-12 08:01:06 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         connection_destroy(co);
149         return;
150     }
151
152     if (event & EVENT_TIMEOUT)
153     {
154         if (co->state == Conn_Connecting)
155         {
156             yaz_log(YLOG_WARN,  "connect timeout %s", client_get_url(cl));
157             client_fatal(cl);
158         }
159         else
160         {
161             yaz_log(YLOG_LOG,  "idle timeout %s", client_get_url(cl));
162             connection_destroy(co);
163         }
164         return;
165     }
166     if (co->state == Conn_Connecting && event & EVENT_OUTPUT)
167     {
168         int errcode;
169         socklen_t errlen = sizeof(errcode);
170
171         if (getsockopt(cs_fileno(co->link), SOL_SOCKET, SO_ERROR, &errcode,
172             &errlen) < 0 || errcode != 0)
173         {
174             client_fatal(cl);
175             return;
176         }
177         else
178         {
179             yaz_log(YLOG_DEBUG, "Connect OK");
180             co->state = Conn_Open;
181             if (cl)
182                 client_set_state(cl, Client_Connected);
183             iochan_settimeout(i, 180);
184         }
185     }
186
187     else if (event & EVENT_INPUT)
188     {
189         int len = cs_get(co->link, &co->ibuf, &co->ibufsize);
190
191         if (len < 0)
192         {
193             yaz_log(YLOG_WARN|YLOG_ERRNO, "Error reading from %s", 
194                     client_get_url(cl));
195             connection_destroy(co);
196             return;
197         }
198         else if (len == 0)
199         {
200             yaz_log(YLOG_WARN, "EOF reading from %s", client_get_url(cl));
201             connection_destroy(co);
202             return;
203         }
204         else if (len > 1) // We discard input if we have no connection
205         {
206             co->state = Conn_Open;
207
208             if (client_is_our_response(cl))
209             {
210                 Z_APDU *a;
211                 struct session_database *sdb = client_get_database(cl);
212                 const char *apdulog = session_setting_oneval(sdb, PZ_APDULOG);
213
214                 odr_reset(global_parameters.odr_in);
215                 odr_setbuf(global_parameters.odr_in, co->ibuf, len, 0);
216                 if (!z_APDU(global_parameters.odr_in, &a, 0, 0))
217                 {
218                     client_fatal(cl);
219                     return;
220                 }
221
222                 if (apdulog && *apdulog && *apdulog != '0')
223                 {
224                     ODR p = odr_createmem(ODR_PRINT);
225                     yaz_log(YLOG_LOG, "recv APDU %s", client_get_url(cl));
226                     
227                     odr_setprint(p, yaz_log_file());
228                     z_APDU(p, &a, 0, 0);
229                     odr_setprint(p, stderr);
230                     odr_destroy(p);
231                 }
232                 switch (a->which)
233                 {
234                     case Z_APDU_initResponse:
235                         client_init_response(cl, a);
236                         break;
237                     case Z_APDU_searchResponse:
238                         client_search_response(cl, a);
239                         break;
240                     case Z_APDU_presentResponse:
241                         client_present_response(cl, a);
242                         break;
243                     case Z_APDU_close:
244                         client_close_response(cl, a);
245                         break;
246                     default:
247                         yaz_log(YLOG_WARN, 
248                                 "Unexpected Z39.50 response from %s",  
249                                 client_get_url(cl));
250                         client_fatal(cl);
251                         return;
252                 }
253                 // We aren't expecting staggered output from target
254                 // if (cs_more(t->link))
255                 //    iochan_setevent(i, EVENT_INPUT);
256             }
257             else  // we throw away response and go to idle mode
258             {
259                 yaz_log(YLOG_DEBUG, "Ignoring result of expired operation");
260                 client_set_state(cl, Client_Idle);
261             }
262         }
263         /* if len==1 we do nothing but wait for more input */
264     }
265     client_continue(cl);
266 }
267
268 // Disassociate connection from client
269 void connection_release(struct connection *co)
270 {
271     struct client *cl = co->client;
272
273     yaz_log(YLOG_DEBUG, "Connection release %s", co->host->hostport);
274     if (!cl)
275         return;
276     client_set_connection(cl, 0);
277     co->client = 0;
278 }
279
280 void connect_resolver_host(struct host *host)
281 {
282     struct connection *con = host->connections;
283     while (con)
284     {
285         if (con->state == Conn_Resolving)
286         {
287             if (!host->ipport) /* unresolved */
288             {
289                 connection_destroy(con);
290                 /* start all over .. at some point it will be NULL */
291                 con = host->connections;
292             }
293             else if (!con->client)
294             {
295                 yaz_log(YLOG_WARN, "connect_unresolved_host : ophan client");
296                 connection_destroy(con);
297                 /* start all over .. at some point it will be NULL */
298                 con = host->connections;
299             }
300             else
301             {
302                 connection_connect(con);
303                 con = con->next;
304             }
305         }
306     }
307 }
308
309 int connection_send_apdu(struct connection *co, Z_APDU *a)
310 {
311     char *buf;
312     int len, r;
313
314     if (!z_APDU(global_parameters.odr_out, &a, 0, 0))
315     {
316         odr_perror(global_parameters.odr_out, "Encoding APDU");
317         abort();
318     }
319     buf = odr_getbuf(global_parameters.odr_out, &len, 0);
320     r = cs_put(co->link, buf, len);
321     if (r < 0)
322     {
323         yaz_log(YLOG_WARN, "cs_put: %s", cs_errmsg(cs_errno(co->link)));
324         return -1;
325     }
326     else if (r == 1)
327     {
328         fprintf(stderr, "cs_put incomplete (ParaZ does not handle that)\n");
329         exit(1);
330     }
331     odr_reset(global_parameters.odr_out); /* release the APDU structure  */
332     co->state = Conn_Waiting;
333     iochan_setflags(co->iochan, EVENT_INPUT);
334     return 0;
335 }
336
337 struct host *connection_get_host(struct connection *con)
338 {
339     return con->host;
340 }
341
342 int connection_connect(struct connection *con)
343 {
344     COMSTACK link = 0;
345     struct host *host = connection_get_host(con);
346     void *addr;
347     int res;
348
349     struct session_database *sdb = client_get_database(con->client);
350     char *zproxy = session_setting_oneval(sdb, PZ_ZPROXY);
351
352     assert(host->ipport);
353     assert(con);
354
355     if (!(link = cs_create(tcpip_type, 0, PROTO_Z3950)))
356     {
357         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
358         return -1;
359     }
360     
361     if (!zproxy || 0 == strlen(zproxy)){
362         /* no Z39.50 proxy needed - direct connect */
363         yaz_log(YLOG_DEBUG, "Connection create %s", connection_get_url(con));
364         
365         if (!(addr = cs_straddr(link, host->ipport)))
366         {
367             yaz_log(YLOG_WARN|YLOG_ERRNO, 
368                     "Lookup of IP address %s failed", host->ipport);
369             return -1;
370         }
371         
372     } else {
373         /* Z39.50 proxy connect */
374         yaz_log(YLOG_DEBUG, "Connection create %s proxy %s", 
375                 connection_get_url(con), zproxy);
376         
377         if (!(addr = cs_straddr(link, zproxy)))
378         {
379             yaz_log(YLOG_WARN|YLOG_ERRNO, 
380                     "Lookup of ZProxy IP address %s failed", 
381                     zproxy);
382             return -1;
383         }
384     }
385     
386     res = cs_connect(link, addr);
387     if (res < 0)
388     {
389         yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s",
390                 connection_get_url(con));
391         return -1;
392     }
393     con->link = link;
394     con->state = Conn_Connecting;
395     con->iochan = iochan_create(cs_fileno(link), connection_handler, 0);
396     iochan_settimeout(con->iochan, 30);
397     iochan_setdata(con->iochan, con);
398     pazpar2_add_channel(con->iochan);
399
400     /* this fragment is bad DRY: from client_prep_connection */
401     client_set_state(con->client, Client_Connecting);
402     iochan_setflag(con->iochan, EVENT_OUTPUT);
403     return 0;
404 }
405
406 const char *connection_get_url(struct connection *co)
407 {
408     return client_get_url(co->client);
409 }
410
411 void connection_set_authentication(struct connection *co, char *auth)
412 {
413     co->authentication = auth;
414 }
415
416 // Ensure that client has a connection associated
417 int client_prep_connection(struct client *cl)
418 {
419     struct connection *co;
420     struct session *se = client_get_session(cl);
421     struct host *host = client_get_host(cl);
422
423     co = client_get_connection(cl);
424
425     yaz_log(YLOG_DEBUG, "Client prep %s", client_get_url(cl));
426
427     if (!co)
428     {
429         // See if someone else has an idle connection
430         // We should look at timestamps here to select the longest-idle connection
431         for (co = host->connections; co; co = co->next)
432             if (co->state == Conn_Open &&
433                 (!co->client || client_get_session(co->client) != se) &&
434                 !strcmp(co->authentication,
435                     session_setting_oneval(client_get_database(cl),
436                     PZ_AUTHENTICATION)))
437                 break;
438         if (co)
439         {
440             connection_release(co);
441             client_set_connection(cl, co);
442             co->client = cl;
443         }
444         else
445             co = connection_create(cl);
446     }
447     if (co)
448     {
449         if (co->state == Conn_Connecting)
450         {
451             client_set_state(cl, Client_Connecting);
452             iochan_setflag(co->iochan, EVENT_OUTPUT);
453         }
454         else if (co->state == Conn_Open)
455         {
456             if (client_get_state(cl) == Client_Error 
457                 || client_get_state(cl) == Client_Disconnected)
458                 client_set_state(cl, Client_Idle);
459             iochan_setflag(co->iochan, EVENT_OUTPUT);
460         }
461         return 1;
462     }
463     else
464         return 0;
465 }
466
467
468
469 /*
470  * Local variables:
471  * c-basic-offset: 4
472  * indent-tabs-mode: nil
473  * End:
474  * vim: shiftwidth=4 tabstop=8 expandtab
475  */