More options handling for scan.
[yaz-moved-to-github.git] / zoom / zoom-c.c
1 /*
2  * $Id: zoom-c.c,v 1.18 2002-01-03 12:18:38 adam Exp $
3  *
4  * ZOOM layer for C, connections, result sets, queries.
5  */
6 #include <assert.h>
7 #include <yaz/xmalloc.h>
8 #include <yaz/otherinfo.h>
9 #include <yaz/log.h>
10 #include <yaz/pquery.h>
11 #include <yaz/diagbib1.h>
12
13 #include "zoom-p.h"
14
15 #if HAVE_SYS_POLL_H
16 #include <sys/poll.h>
17 #endif
18
19 static ZOOM_Event ZOOM_Event_create (int kind)
20 {
21     ZOOM_Event event = xmalloc (sizeof(*event));
22     event->kind = kind;
23     event->next = 0;
24     event->prev = 0;
25     return event;
26 }
27
28 static void ZOOM_Event_destroy (ZOOM_Event event)
29 {
30     xfree (event);
31 }
32
33 static void ZOOM_connection_put_event (ZOOM_connection c, ZOOM_Event event)
34 {
35     // put in back of queue
36     if (c->m_queue_back)
37     {
38         c->m_queue_back->prev = event;
39         assert (c->m_queue_front);
40     }
41     else
42     {
43         assert (!c->m_queue_front);
44         c->m_queue_front = event;
45     }
46     event->next = c->m_queue_back;
47     event->prev = 0;
48     c->m_queue_back = event;
49 }
50
51 static ZOOM_Event ZOOM_connection_get_event(ZOOM_connection c)
52 {
53     // get from front of queue
54     ZOOM_Event event = c->m_queue_front;
55     if (!event)
56         return 0;
57     assert (c->m_queue_back);
58     c->m_queue_front = event->prev;
59     if (c->m_queue_front)
60     {
61         assert (c->m_queue_back);
62         c->m_queue_front->next = 0;
63     }
64     else
65         c->m_queue_back = 0;
66     c->last_event = event->kind;
67     return event;
68 }
69
70 static void clear_error (ZOOM_connection c)
71 {
72     c->error = ZOOM_ERROR_NONE;
73     xfree (c->addinfo);
74     c->addinfo = 0;
75 }
76
77 ZOOM_task ZOOM_connection_add_task (ZOOM_connection c, int which)
78 {
79     ZOOM_task *taskp = &c->tasks;
80     while (*taskp)
81         taskp = &(*taskp)->next;
82     *taskp = xmalloc (sizeof(**taskp));
83     (*taskp)->running = 0;
84     (*taskp)->which = which;
85     (*taskp)->next = 0;
86     clear_error (c);
87     return *taskp;
88 }
89
90 void ZOOM_connection_remove_task (ZOOM_connection c)
91 {
92     ZOOM_task task = c->tasks;
93
94     if (task)
95     {
96         c->tasks = task->next;
97         switch (task->which)
98         {
99         case ZOOM_TASK_SEARCH:
100             ZOOM_resultset_destroy (task->u.search.resultset);
101             break;
102         case ZOOM_TASK_RETRIEVE:
103             ZOOM_resultset_destroy (task->u.retrieve.resultset);
104             break;
105         case ZOOM_TASK_CONNECT:
106             break;
107         case ZOOM_TASK_SCAN:
108             ZOOM_scanset_destroy (task->u.scan.scan);
109             break;
110         default:
111             assert (0);
112         }
113         xfree (task);
114     }
115 }
116
117 void ZOOM_connection_remove_tasks (ZOOM_connection c)
118 {
119     while (c->tasks)
120         ZOOM_connection_remove_task(c);
121 }
122
123 static ZOOM_record record_cache_lookup (ZOOM_resultset r,
124                                          int pos,
125                                          const char *elementSetName);
126
127 ZOOM_connection ZOOM_connection_create (ZOOM_options options)
128 {
129     ZOOM_connection c = xmalloc (sizeof(*c));
130
131     c->cs = 0;
132     c->mask = 0;
133     c->state = STATE_IDLE;
134     c->error = ZOOM_ERROR_NONE;
135     c->addinfo = 0;
136     c->buf_in = 0;
137     c->len_in = 0;
138     c->buf_out = 0;
139     c->len_out = 0;
140     c->resultsets = 0;
141
142     c->options = ZOOM_options_create_with_parent(options);
143
144     c->host_port = 0;
145     c->proxy = 0;
146
147     c->cookie_out = 0;
148     c->cookie_in = 0;
149     c->tasks = 0;
150
151     c->odr_in = odr_createmem (ODR_DECODE);
152     c->odr_out = odr_createmem (ODR_ENCODE);
153
154     c->async = 0;
155     c->support_named_resultsets = 0;
156     c->last_event = ZOOM_EVENT_NONE;
157
158     c->m_queue_front = 0;
159     c->m_queue_back = 0;
160     return c;
161 }
162
163 /* set database names. Take local databases (if set); otherwise
164    take databases given in ZURL (if set); otherwise use Default */
165 static char **set_DatabaseNames (ZOOM_connection con, ZOOM_options options,
166                                  int *num)
167 {
168     char **databaseNames;
169     const char *c;
170     int no = 2;
171     const char *cp = ZOOM_options_get (options, "databaseName");
172     
173     if (!cp || !*cp)
174     {
175         cp = strchr (con->host_port, '/');
176         if (cp)
177             cp++;
178         }
179     if (cp)
180     {
181         c = cp;
182         while ((c = strchr(c, '+')))
183         {
184             c++;
185             no++;
186         }
187     }
188     else
189         cp = "Default";
190     databaseNames = odr_malloc (con->odr_out, no * sizeof(*databaseNames));
191     no = 0;
192     while (*cp)
193     {
194         c = strchr (cp, '+');
195         if (!c)
196             c = cp + strlen(cp);
197         else if (c == cp)
198         {
199             cp++;
200             continue;
201         }
202         /* cp ptr to first char of db name, c is char
203            following db name */
204         databaseNames[no] = odr_malloc (con->odr_out, 1+c-cp);
205         memcpy (databaseNames[no], cp, c-cp);
206         databaseNames[no++][c-cp] = '\0';
207         cp = c;
208         if (*cp)
209             cp++;
210     }
211     databaseNames[no] = NULL;
212     *num = no;
213     return databaseNames;
214 }
215
216 ZOOM_connection ZOOM_connection_new (const char *host, int portnum)
217 {
218     ZOOM_connection c = ZOOM_connection_create (0);
219
220     ZOOM_connection_connect (c, host, portnum);
221     return c;
222 }
223
224 void ZOOM_connection_connect(ZOOM_connection c,
225                               const char *host, int portnum)
226 {
227     const char *val;
228     ZOOM_task task;
229
230     val = ZOOM_options_get (c->options, "proxy");
231     if (val && *val)
232         c->proxy = xstrdup (val);
233     else
234         c->proxy = 0;
235
236     if (portnum)
237     {
238         char hostn[128];
239         sprintf (hostn, "%.80s:%d", host, portnum);
240         c->host_port = xstrdup(hostn);
241     }
242     else
243         c->host_port = xstrdup(host);
244
245     ZOOM_options_set(c->options, "host", c->host_port);
246
247     c->async = ZOOM_options_get_bool (c->options, "async", 0);
248     
249     task = ZOOM_connection_add_task (c, ZOOM_TASK_CONNECT);
250
251     if (!c->async)
252     {
253         while (ZOOM_event (1, &c))
254             ;
255     }
256 }
257
258 ZOOM_query ZOOM_query_create(void)
259 {
260     ZOOM_query s = xmalloc (sizeof(*s));
261
262     s->refcount = 1;
263     s->query = 0;
264     s->sort_spec = 0;
265     s->odr = odr_createmem (ODR_ENCODE);
266
267     return s;
268 }
269
270 void ZOOM_query_destroy(ZOOM_query s)
271 {
272     if (!s)
273         return;
274
275     (s->refcount)--;
276     yaz_log (LOG_DEBUG, "ZOOM_query_destroy count=%d", s->refcount);
277     if (s->refcount == 0)
278     {
279         odr_destroy (s->odr);
280         xfree (s);
281     }
282 }
283
284 int ZOOM_query_prefix(ZOOM_query s, const char *str)
285 {
286     s->query = odr_malloc (s->odr, sizeof(*s->query));
287     s->query->which = Z_Query_type_1;
288     s->query->u.type_1 =  p_query_rpn(s->odr, PROTO_Z3950, str);
289     if (!s->query->u.type_1)
290         return -1;
291     return 0;
292 }
293
294 int ZOOM_query_sortby(ZOOM_query s, const char *criteria)
295 {
296     s->sort_spec = yaz_sort_spec (s->odr, criteria);
297     if (!s->sort_spec)
298         return -1;
299     return 0;
300 }
301
302 static int do_write(ZOOM_connection c);
303
304 void ZOOM_connection_destroy(ZOOM_connection c)
305 {
306     ZOOM_resultset r;
307     if (!c)
308         return;
309     if (c->cs)
310         cs_close (c->cs);
311     for (r = c->resultsets; r; r = r->next)
312         r->connection = 0;
313
314     xfree (c->buf_in);
315     xfree (c->addinfo);
316     odr_destroy (c->odr_in);
317     odr_destroy (c->odr_out);
318     ZOOM_options_destroy (c->options);
319     ZOOM_connection_remove_tasks (c);
320     xfree (c->host_port);
321     xfree (c);
322 }
323
324 void ZOOM_resultset_addref (ZOOM_resultset r)
325 {
326     if (r)
327         (r->refcount)++;
328 }
329 ZOOM_resultset ZOOM_resultset_create ()
330 {
331     ZOOM_resultset r = xmalloc (sizeof(*r));
332
333     r->refcount = 1;
334     r->size = 0;
335     r->odr = odr_createmem (ODR_ENCODE);
336     r->start = 0;
337     r->piggyback = 1;
338     r->setname = 0;
339     r->count = 0;
340     r->record_cache = 0;
341     r->r_sort_spec = 0;
342     r->r_query = 0;
343     r->search = 0;
344     r->connection = 0;
345     r->next = 0;
346     return r;
347 }
348
349 ZOOM_resultset ZOOM_connection_search_pqf(ZOOM_connection c, const char *q)
350 {
351     ZOOM_resultset r;
352     ZOOM_query s = ZOOM_query_create();
353
354     ZOOM_query_prefix (s, q);
355
356     r = ZOOM_connection_search (c, s);
357     ZOOM_query_destroy (s);
358     return r;
359 }
360
361 ZOOM_resultset ZOOM_connection_search(ZOOM_connection c, ZOOM_query q)
362 {
363     ZOOM_resultset r = ZOOM_resultset_create ();
364     ZOOM_task task;
365     const char *cp;
366
367     r->r_sort_spec = q->sort_spec;
368     r->r_query = q->query;
369     r->search = q;
370
371     r->options = ZOOM_options_create_with_parent(c->options);
372
373     r->start = ZOOM_options_get_int(r->options, "start", 0);
374     r->count = ZOOM_options_get_int(r->options, "count", 0);
375     r->piggyback = ZOOM_options_get_bool (r->options, "piggyback", 1);
376     cp = ZOOM_options_get (r->options, "setname");
377     if (cp)
378         r->setname = xstrdup (cp);
379     
380     r->connection = c;
381
382     r->next = c->resultsets;
383     c->resultsets = r;
384
385     task = ZOOM_connection_add_task (c, ZOOM_TASK_SEARCH);
386     task->u.search.resultset = r;
387     ZOOM_resultset_addref (r);  
388
389     (q->refcount)++;
390
391     if (!c->async)
392     {
393         while (ZOOM_event (1, &c))
394             ;
395     }
396     return r;
397 }
398
399 void ZOOM_resultset_destroy(ZOOM_resultset r)
400 {
401     if (!r)
402         return;
403     (r->refcount)--;
404     yaz_log (LOG_DEBUG, "destroy r = %p count=%d", r, r->refcount);
405     if (r->refcount == 0)
406     {
407         if (r->connection)
408         {
409             /* remove ourselves from the resultsets in connection */
410             ZOOM_resultset *rp = &r->connection->resultsets;
411             while (1)
412             {
413                 assert (*rp);   /* we must be in this list!! */
414                 if (*rp == r)
415                 {   /* OK, we're here - take us out of it */
416                     *rp = (*rp)->next;
417                     break;
418                 }
419                 rp = &(*rp)->next;
420             }
421         }
422         ZOOM_query_destroy (r->search);
423         ZOOM_options_destroy (r->options);
424         odr_destroy (r->odr);
425         xfree (r->setname);
426         xfree (r);
427     }
428 }
429
430 size_t ZOOM_resultset_size (ZOOM_resultset r)
431 {
432     return r->size;
433 }
434
435 static void do_close (ZOOM_connection c)
436 {
437     if (c->cs)
438         cs_close(c->cs);
439     c->cs = 0;
440     c->mask = 0;
441     c->state = STATE_IDLE;
442 }
443
444 static void ZOOM_resultset_retrieve (ZOOM_resultset r,
445                                       int force_sync, int start, int count)
446 {
447     ZOOM_task task;
448     ZOOM_connection c;
449
450     if (!r)
451         return;
452     c = r->connection;
453     if (!c)
454         return;
455     task = ZOOM_connection_add_task (c, ZOOM_TASK_RETRIEVE);
456     task->u.retrieve.resultset = r;
457     task->u.retrieve.start = start;
458     task->u.retrieve.count = count;
459
460     ZOOM_resultset_addref (r);
461
462     if (!r->connection->async || force_sync)
463         while (r->connection && ZOOM_event (1, &r->connection))
464             ;
465 }
466
467 void ZOOM_resultset_records (ZOOM_resultset r, ZOOM_record *recs,
468                               size_t start, size_t count)
469 {
470     int force_present = 0;
471
472     if (!r)
473         return ;
474     if (count && recs)
475         force_present = 1;
476     ZOOM_resultset_retrieve (r, force_present, start, count);
477     if (force_present)
478     {
479         size_t i;
480         for (i = 0; i< count; i++)
481             recs[i] = ZOOM_resultset_record_immediate (r, i+start);
482     }
483 }
484
485 static int do_connect (ZOOM_connection c)
486 {
487     void *add;
488     const char *effective_host;
489
490     if (c->proxy)
491         effective_host = c->proxy;
492     else
493         effective_host = c->host_port;
494
495     yaz_log (LOG_DEBUG, "do_connect host=%s", effective_host);
496
497     assert (!c->cs);
498     c->cs = cs_create_host (effective_host, 0, &add);
499
500     if (c->cs)
501     {
502         int ret = cs_connect (c->cs, add);
503         yaz_log (LOG_DEBUG, "cs_connect returned %d", ret);
504         if (ret >= 0)
505         {
506             c->state = STATE_CONNECTING; 
507             c->mask = ZOOM_SELECT_READ | ZOOM_SELECT_WRITE | 
508                 ZOOM_SELECT_EXCEPT;
509             return 1;
510         }
511     }
512     c->state = STATE_IDLE;
513     c->error = ZOOM_ERROR_CONNECT;
514     return 0;
515 }
516
517 int z3950_connection_socket(ZOOM_connection c)
518 {
519     if (c->cs)
520         return cs_fileno(c->cs);
521     return -1;
522 }
523
524 int z3950_connection_mask(ZOOM_connection c)
525 {
526     if (c->cs)
527         return c->mask;
528     return 0;
529 }
530
531 static int encode_APDU(ZOOM_connection c, Z_APDU *a, ODR out)
532 {
533     char str[120];
534
535     assert (a);
536     sprintf (str, "send_APDU t=%p type=%d", c, a->which);
537     if (c->cookie_out)
538     {
539         Z_OtherInformation **oi;
540         yaz_oi_APDU(a, &oi);
541         yaz_oi_set_string_oidval(oi, out, VAL_COOKIE, 1, c->cookie_out);
542     }
543     if (!z_APDU(out, &a, 0, 0))
544     {
545         FILE *outf = fopen("/tmp/apdu.txt", "w");
546         if (outf)
547         {
548             ODR odr_pr = odr_createmem(ODR_PRINT);
549             fprintf (outf, "a=%p\n", a);
550             odr_setprint(odr_pr, outf);
551             z_APDU(odr_pr, &a, 0, 0);
552             odr_destroy(odr_pr);
553             fclose (outf);
554         }
555         c->error = ZOOM_ERROR_ENCODE;
556         do_close (c);
557         return -1;
558     }
559     
560     return 0;
561 }
562
563 static int send_APDU (ZOOM_connection c, Z_APDU *a)
564 {
565     ZOOM_Event event;
566     assert (a);
567     if (encode_APDU(c, a, c->odr_out))
568         return -1;
569     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
570     event = ZOOM_Event_create (ZOOM_EVENT_SEND_APDU);
571     ZOOM_connection_put_event (c, event);
572     odr_reset(c->odr_out);
573     do_write (c);
574     return 0;   
575 }
576
577 static int ZOOM_connection_send_init (ZOOM_connection c)
578 {
579     const char *impname;
580     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_initRequest);
581     Z_InitRequest *ireq = apdu->u.initRequest;
582     Z_IdAuthentication *auth = odr_malloc(c->odr_out, sizeof(*auth));
583     const char *auth_groupId = ZOOM_options_get (c->options, "group");
584     const char *auth_userId = ZOOM_options_get (c->options, "user");
585     const char *auth_password = ZOOM_options_get (c->options, "pass");
586     
587     ODR_MASK_SET(ireq->options, Z_Options_search);
588     ODR_MASK_SET(ireq->options, Z_Options_present);
589     ODR_MASK_SET(ireq->options, Z_Options_scan);
590     ODR_MASK_SET(ireq->options, Z_Options_sort);
591     ODR_MASK_SET(ireq->options, Z_Options_extendedServices);
592     ODR_MASK_SET(ireq->options, Z_Options_namedResultSets);
593     
594     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_1);
595     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_2);
596     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_3);
597     
598     impname = ZOOM_options_get (c->options, "implementationName");
599     ireq->implementationName =
600         odr_malloc (c->odr_out, 15 + (impname ? strlen(impname) : 0));
601     strcpy (ireq->implementationName, "");
602     if (impname)
603     {
604         strcat (ireq->implementationName, impname);
605         strcat (ireq->implementationName, "/");
606     }                                          
607     strcat (ireq->implementationName, "ZOOM-C/YAZ");
608     
609     *ireq->maximumRecordSize =
610         ZOOM_options_get_int (c->options, "maximumRecordSize", 1024*1024);
611     *ireq->preferredMessageSize =
612         ZOOM_options_get_int (c->options, "preferredMessageSize", 1024*1024);
613     
614     if (auth_groupId || auth_password)
615     {
616         Z_IdPass *pass = odr_malloc(c->odr_out, sizeof(*pass));
617         int i = 0;
618         pass->groupId = 0;
619         if (auth_groupId && *auth_groupId)
620         {
621             pass->groupId = odr_malloc(c->odr_out, strlen(auth_groupId)+1);
622             strcpy(pass->groupId, auth_groupId);
623             i++;
624         }
625         pass->userId = 0;
626         if (auth_userId && *auth_userId)
627         {
628             pass->userId = odr_malloc(c->odr_out, strlen(auth_userId)+1);
629             strcpy(pass->userId, auth_userId);
630             i++;
631         }
632         pass->password = 0;
633         if (auth_password && *auth_password)
634         {
635             pass->password = odr_malloc(c->odr_out, strlen(auth_password)+1);
636             strcpy(pass->password, auth_password);
637             i++;
638         }
639         if (i)
640         {
641             auth->which = Z_IdAuthentication_idPass;
642             auth->u.idPass = pass;
643             ireq->idAuthentication = auth;
644         }
645     }
646     else if (auth_userId)
647     {
648         auth->which = Z_IdAuthentication_open;
649         auth->u.open = odr_malloc(c->odr_out, strlen(auth_userId)+1);
650         strcpy(auth->u.open, auth_userId);
651         ireq->idAuthentication = auth;
652     }
653     if (c->proxy)
654         yaz_oi_set_string_oidval(&ireq->otherInfo, c->odr_out,
655                                  VAL_PROXY, 1, c->host_port);
656     assert (apdu);
657     send_APDU (c, apdu);
658     
659     return 0;
660 }
661
662 static int ZOOM_connection_send_search (ZOOM_connection c)
663 {
664     ZOOM_resultset r;
665     int lslb, ssub, mspn;
666     const char *syntax;
667     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_searchRequest);
668     Z_SearchRequest *search_req = apdu->u.searchRequest;
669     const char *elementSetName;
670     const char *smallSetElementSetName;
671     const char *mediumSetElementSetName;
672     const char *schema;
673
674     assert (c->tasks);
675     assert (c->tasks->which == ZOOM_TASK_SEARCH);
676
677     r = c->tasks->u.search.resultset;
678
679     elementSetName =
680         ZOOM_options_get (r->options, "elementSetName");
681     smallSetElementSetName  =
682         ZOOM_options_get (r->options, "smallSetElementSetName");
683     mediumSetElementSetName =
684         ZOOM_options_get (r->options, "mediumSetElementSetName");
685     schema =
686         ZOOM_options_get (r->options, "schema");
687
688     if (!smallSetElementSetName)
689         smallSetElementSetName = elementSetName;
690
691     if (!mediumSetElementSetName)
692         mediumSetElementSetName = elementSetName;
693
694     assert (r);
695     assert (r->r_query);
696
697     /* prepare query for the search request */
698     search_req->query = r->r_query;
699
700     search_req->databaseNames =
701         set_DatabaseNames (c, r->options, &search_req->num_databaseNames);
702
703     /* get syntax (no need to provide unless piggyback is in effect) */
704     syntax = ZOOM_options_get (r->options, "preferredRecordSyntax");
705
706     lslb = ZOOM_options_get_int (r->options, "largeSetLowerBound", -1);
707     ssub = ZOOM_options_get_int (r->options, "smallSetUpperBound", -1);
708     mspn = ZOOM_options_get_int (r->options, "mediumSetPresentNumber", -1);
709     if (lslb != -1 && ssub != -1 && mspn != -1)
710     {
711         /* So're a Z39.50 expert? Let's hope you don't do sort */
712         *search_req->largeSetLowerBound = lslb;
713         *search_req->smallSetUpperBound = ssub;
714         *search_req->mediumSetPresentNumber = mspn;
715     }
716     else if (r->start == 0 && r->count > 0
717              && r->piggyback && !r->r_sort_spec && !schema)
718     {
719         /* Regular piggyback - do it unless we're going to do sort */
720         *search_req->largeSetLowerBound = 2000000000;
721         *search_req->smallSetUpperBound = r->count;
722         *search_req->mediumSetPresentNumber = r->count;
723         smallSetElementSetName = 0;  /* no need to provide this */
724     }
725     else
726     {
727         /* non-piggyback. Need not provide elementsets or syntaxes .. */
728         smallSetElementSetName = 0;
729         mediumSetElementSetName = 0;
730         syntax = 0;
731     }
732     if (smallSetElementSetName && *smallSetElementSetName)
733     {
734         Z_ElementSetNames *esn = odr_malloc (c->odr_out, sizeof(*esn));
735         
736         esn->which = Z_ElementSetNames_generic;
737         esn->u.generic = odr_strdup (c->odr_out, smallSetElementSetName);
738         search_req->smallSetElementSetNames = esn;
739     }
740     if (mediumSetElementSetName && *mediumSetElementSetName)
741     {
742         Z_ElementSetNames *esn = odr_malloc (c->odr_out, sizeof(*esn));
743         
744         esn->which = Z_ElementSetNames_generic;
745         esn->u.generic = odr_strdup (c->odr_out, mediumSetElementSetName);
746         search_req->mediumSetElementSetNames = esn;
747     }
748     if (syntax)
749         search_req->preferredRecordSyntax =
750             yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, syntax);
751     
752     if (!r->setname)
753     {
754         if (c->support_named_resultsets)
755         {
756             char setname[14];
757             int ord;
758             /* find the lowest unused ordinal so that we re-use
759                result sets on the server. */
760             for (ord = 1; ; ord++)
761             {
762                 ZOOM_resultset rp;
763                 sprintf (setname, "%d", ord);
764                 for (rp = c->resultsets; rp; rp = rp->next)
765                     if (rp->setname && !strcmp (rp->setname, setname))
766                         break;
767                 if (!rp)
768                     break;
769             }
770             r->setname = xstrdup (setname);
771             yaz_log (LOG_DEBUG, "allocating %s", r->setname);
772         }
773         else
774             r->setname = xstrdup ("default");
775         ZOOM_options_set (r->options, "setname", r->setname);
776     }
777     search_req->resultSetName = odr_strdup(c->odr_out, r->setname);
778     /* send search request */
779     send_APDU (c, apdu);
780     r->r_query = 0;
781     return 1;
782 }
783
784 static void response_diag (ZOOM_connection c, Z_DiagRec *p)
785 {
786     Z_DefaultDiagFormat *r;
787     char *addinfo = 0;
788     
789     xfree (c->addinfo);
790     c->addinfo = 0;
791     if (p->which != Z_DiagRec_defaultFormat)
792     {
793         c->error = ZOOM_ERROR_DECODE;
794         return;
795     }
796     r = p->u.defaultFormat;
797     switch (r->which)
798     {
799     case Z_DefaultDiagFormat_v2Addinfo:
800         addinfo = r->u.v2Addinfo;
801         break;
802     case Z_DefaultDiagFormat_v3Addinfo:
803         addinfo = r->u.v3Addinfo;
804         break;
805     }
806     if (addinfo)
807         c->addinfo = xstrdup (addinfo);
808     c->error = *r->condition;
809 }
810
811 ZOOM_record ZOOM_record_clone (ZOOM_record srec)
812 {
813     char *buf;
814     int size;
815     ODR odr_enc;
816     ZOOM_record nrec;
817
818     odr_enc = odr_createmem(ODR_ENCODE);
819     if (!z_NamePlusRecord (odr_enc, &srec->npr, 0, 0))
820         return 0;
821     buf = odr_getbuf (odr_enc, &size, 0);
822     
823     nrec = xmalloc (sizeof(*nrec));
824     nrec->odr = odr_createmem(ODR_DECODE);
825     nrec->wrbuf_marc = 0;
826     odr_setbuf (nrec->odr, buf, size, 0);
827     z_NamePlusRecord (nrec->odr, &nrec->npr, 0, 0);
828     
829     odr_destroy (odr_enc);
830     return nrec;
831 }
832
833 ZOOM_record ZOOM_resultset_record_immediate (ZOOM_resultset s,size_t pos)
834 {
835     return record_cache_lookup (s, pos, 0);
836 }
837
838 ZOOM_record ZOOM_resultset_record (ZOOM_resultset r, size_t pos)
839 {
840     ZOOM_resultset_retrieve (r, 1, pos, 1);
841     return ZOOM_resultset_record_immediate (r, pos);
842 }
843
844 void ZOOM_record_destroy (ZOOM_record rec)
845 {
846     if (!rec)
847         return;
848     if (rec->wrbuf_marc)
849         wrbuf_free (rec->wrbuf_marc, 1);
850     odr_destroy (rec->odr);
851     xfree (rec);
852 }
853
854 void *ZOOM_record_get (ZOOM_record rec, const char *type, size_t *len)
855 {
856     Z_NamePlusRecord *npr;
857     if (!rec)
858         return 0;
859     npr = rec->npr;
860     if (!npr)
861         return 0;
862     if (!strcmp (type, "database"))
863     {
864         return npr->databaseName;
865     }
866     else if (!strcmp (type, "syntax"))
867     {
868         if (npr->which == Z_NamePlusRecord_databaseRecord)
869         {
870             Z_External *r = (Z_External *) npr->u.databaseRecord;
871             oident *ent = oid_getentbyoid(r->direct_reference);
872             if (ent)
873                 return ent->desc;
874         }
875         return "none";
876     }
877     else if (!strcmp (type, "render"))
878     {
879         if (npr->which == Z_NamePlusRecord_databaseRecord)
880         {
881             Z_External *r = (Z_External *) npr->u.databaseRecord;
882             oident *ent = oid_getentbyoid(r->direct_reference);
883             
884             if (r->which == Z_External_sutrs)
885             {
886                 *len = r->u.sutrs->len;
887                 return r->u.sutrs->buf;
888             }
889             else if (r->which == Z_External_octet)
890             {
891                 switch (ent->value)
892                 {
893                 case VAL_SOIF:
894                 case VAL_HTML:
895                 case VAL_SUTRS:
896                     break;
897                 case VAL_TEXT_XML:
898                 case VAL_APPLICATION_XML:
899                     break;
900                 default:
901                     if (!rec->wrbuf_marc)
902                         rec->wrbuf_marc = wrbuf_alloc();
903                     if (marc_display_wrbuf (r->u.octet_aligned->buf,
904                                         rec->wrbuf_marc, 0,
905                                             r->u.octet_aligned->len) > 0)
906                     {
907                         *len = wrbuf_len(rec->wrbuf_marc);
908                         return wrbuf_buf(rec->wrbuf_marc);
909                     }
910                 }
911                 *len = r->u.octet_aligned->len;
912                 return r->u.octet_aligned->buf;
913             }
914             else if (r->which == Z_External_grs1)
915             {
916                 *len = 5;
917                 return "GRS-1";
918             }
919         }
920         return 0;
921     }
922     else if (!strcmp (type, "raw"))
923     {
924         if (npr->which == Z_NamePlusRecord_databaseRecord)
925         {
926             *len = -1;
927             return (Z_External *) npr->u.databaseRecord;
928         }
929         return 0;
930     }
931     return 0;
932 }
933
934 static void record_cache_add (ZOOM_resultset r,
935                               Z_NamePlusRecord *npr,
936                               int pos,
937                               const char *elementSetName)
938 {
939     ZOOM_record_cache rc;
940
941     for (rc = r->record_cache; rc; rc = rc->next)
942     {
943         if (pos == rc->pos)
944         {
945             if ((!elementSetName && !rc->elementSetName)
946                 || (elementSetName && rc->elementSetName &&
947                     !strcmp (elementSetName, rc->elementSetName)))
948             {
949                 /* not destroying rc->npr (it's handled by nmem )*/
950                 rc->rec.npr = npr;
951                 /* keeping wrbuf_marc too */
952                 return;
953             }
954         }
955
956     }
957     rc = odr_malloc (r->odr, sizeof(*rc));
958     rc->rec.npr = npr; 
959     rc->rec.odr = 0;
960     rc->rec.wrbuf_marc = 0;
961     if (elementSetName)
962         rc->elementSetName = odr_strdup (r->odr, elementSetName);
963     else
964         rc->elementSetName = 0;
965     rc->pos = pos;
966     rc->next = r->record_cache;
967     r->record_cache = rc;
968 }
969
970 static ZOOM_record record_cache_lookup (ZOOM_resultset r,
971                                          int pos,
972                                          const char *elementSetName)
973 {
974     ZOOM_record_cache rc;
975
976     for (rc = r->record_cache; rc; rc = rc->next)
977     {
978         if (pos == rc->pos)
979         {
980             if ((!elementSetName && !rc->elementSetName)
981                 || (elementSetName && rc->elementSetName &&
982                     !strcmp (elementSetName, rc->elementSetName)))
983                 return &rc->rec;
984         }
985     }
986     return 0;
987 }
988                                              
989 static void handle_records (ZOOM_connection c, Z_Records *sr,
990                             int present_phase)
991 {
992     ZOOM_resultset resultset;
993
994     if (!c->tasks)
995         return ;
996     switch (c->tasks->which)
997     {
998     case ZOOM_TASK_SEARCH:
999         resultset = c->tasks->u.search.resultset;
1000         break;
1001     case ZOOM_TASK_RETRIEVE:
1002         resultset = c->tasks->u.retrieve.resultset;        
1003         break;
1004     default:
1005         return;
1006     }
1007     if (sr && sr->which == Z_Records_NSD)
1008     {
1009         Z_DiagRec dr, *dr_p = &dr;
1010         dr.which = Z_DiagRec_defaultFormat;
1011         dr.u.defaultFormat = sr->u.nonSurrogateDiagnostic;
1012         
1013         response_diag (c, dr_p);
1014     }
1015     else if (sr && sr->which == Z_Records_multipleNSD)
1016     {
1017         if (sr->u.multipleNonSurDiagnostics->num_diagRecs >= 1)
1018             response_diag(c, sr->u.multipleNonSurDiagnostics->diagRecs[0]);
1019         else
1020             c->error = ZOOM_ERROR_DECODE;
1021     }
1022     else 
1023     {
1024         if (resultset->count + resultset->start > resultset->size)
1025             resultset->count = resultset->size - resultset->start;
1026         if (resultset->count < 0)
1027             resultset->count = 0;
1028         if (sr && sr->which == Z_Records_DBOSD)
1029         {
1030             int i;
1031             NMEM nmem = odr_extract_mem (c->odr_in);
1032             Z_NamePlusRecordList *p =
1033                 sr->u.databaseOrSurDiagnostics;
1034             for (i = 0; i<p->num_records; i++)
1035             {
1036                 record_cache_add (resultset, p->records[i],
1037                                   i+ resultset->start, 0);
1038             }
1039             /* transfer our response to search_nmem .. we need it later */
1040             nmem_transfer (resultset->odr->mem, nmem);
1041             nmem_destroy (nmem);
1042             if (present_phase && p->num_records == 0)
1043             {
1044                 /* present response and we didn't get any records! */
1045                 c->error = ZOOM_ERROR_DECODE;
1046             }
1047         }
1048         else if (present_phase)
1049         {
1050             /* present response and we didn't get any records! */
1051             c->error = ZOOM_ERROR_DECODE;
1052         }
1053     }
1054 }
1055
1056 static void handle_present_response (ZOOM_connection c, Z_PresentResponse *pr)
1057 {
1058     handle_records (c, pr->records, 1);
1059 }
1060
1061 static void handle_search_response (ZOOM_connection c, Z_SearchResponse *sr)
1062 {
1063     ZOOM_resultset resultset;
1064
1065     yaz_log (LOG_DEBUG, "got search response");
1066
1067     if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
1068         return ;
1069
1070     resultset = c->tasks->u.search.resultset;
1071
1072     resultset->size = *sr->resultCount;
1073     handle_records (c, sr->records, 0);
1074 }
1075
1076 static void sort_response (ZOOM_connection c, Z_SortResponse *res)
1077 {
1078     if (res->diagnostics && res->num_diagnostics > 0)
1079         response_diag (c, res->diagnostics[0]);
1080 }
1081
1082 static int scan_response (ZOOM_connection c, Z_ScanResponse *res)
1083 {
1084     NMEM nmem = odr_extract_mem (c->odr_in);
1085     ZOOM_scanset scan;
1086
1087     if (!c->tasks || c->tasks->which != ZOOM_TASK_SCAN)
1088         return 0;
1089     scan = c->tasks->u.scan.scan;
1090
1091     if (res->entries && res->entries->nonsurrogateDiagnostics)
1092         response_diag(c, res->entries->nonsurrogateDiagnostics[0]);
1093     scan->scan_response = res;
1094     nmem_transfer (scan->odr->mem, nmem);
1095     if (res->stepSize)
1096         ZOOM_options_set_int (scan->options, "stepSize", *res->stepSize);
1097     if (res->positionOfTerm)
1098         ZOOM_options_set_int (scan->options, "position", *res->positionOfTerm);
1099     if (res->scanStatus)
1100         ZOOM_options_set_int (scan->options, "scanStatus", *res->scanStatus);
1101     if (res->numberOfEntriesReturned)
1102         ZOOM_options_set_int (scan->options, "number",
1103                               *res->numberOfEntriesReturned);
1104     nmem_destroy (nmem);
1105     return 1;
1106 }
1107
1108 static int send_sort (ZOOM_connection c)
1109 {
1110     ZOOM_resultset  resultset;
1111
1112     if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
1113         return 0;
1114
1115     resultset = c->tasks->u.search.resultset;
1116
1117     if (c->error)
1118     {
1119         resultset->r_sort_spec = 0;
1120         return 0;
1121     }
1122     if (resultset->r_sort_spec)
1123     {
1124         Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_sortRequest);
1125         Z_SortRequest *req = apdu->u.sortRequest;
1126         
1127         req->num_inputResultSetNames = 1;
1128         req->inputResultSetNames = (Z_InternationalString **)
1129             odr_malloc (c->odr_out, sizeof(*req->inputResultSetNames));
1130         req->inputResultSetNames[0] =
1131             odr_strdup (c->odr_out, resultset->setname);
1132         req->sortedResultSetName = odr_strdup (c->odr_out, resultset->setname);
1133         req->sortSequence = resultset->r_sort_spec;
1134         resultset->r_sort_spec = 0;
1135         send_APDU (c, apdu);
1136         return 1;
1137     }
1138     return 0;
1139 }
1140
1141 static int send_present (ZOOM_connection c)
1142 {
1143     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_presentRequest);
1144     Z_PresentRequest *req = apdu->u.presentRequest;
1145     int i = 0;
1146     const char *syntax = 
1147         ZOOM_options_get (c->options, "preferredRecordSyntax");
1148     const char *element =
1149         ZOOM_options_get (c->options, "elementSetName");
1150     const char *schema =
1151         ZOOM_options_get (c->options, "schema");
1152     ZOOM_resultset  resultset;
1153
1154     if (!c->tasks)
1155         return 0;
1156
1157     switch (c->tasks->which)
1158     {
1159     case ZOOM_TASK_SEARCH:
1160         resultset = c->tasks->u.search.resultset;
1161         break;
1162     case ZOOM_TASK_RETRIEVE:
1163         resultset = c->tasks->u.retrieve.resultset;
1164         resultset->start = c->tasks->u.retrieve.start;
1165         resultset->count = c->tasks->u.retrieve.count;
1166
1167         if (resultset->start >= resultset->size)
1168             return 0;
1169         if (resultset->start + resultset->count > resultset->size)
1170             resultset->count = resultset->size - resultset->start;
1171         break;
1172     default:
1173         return 0;
1174     }
1175
1176     if (c->error)                  /* don't continue on error */
1177         return 0;
1178     if (resultset->start < 0)
1179         return 0;
1180     for (i = 0; i<resultset->count; i++)
1181     {
1182         ZOOM_record rec =
1183             record_cache_lookup (resultset, i + resultset->start, 0);
1184         if (!rec)
1185             break;
1186     }
1187     if (i == resultset->count)
1188         return 0;
1189
1190     resultset->start += i;
1191     resultset->count -= i;
1192     *req->resultSetStartPoint = resultset->start + 1;
1193     *req->numberOfRecordsRequested = resultset->count;
1194     assert (*req->numberOfRecordsRequested > 0);
1195
1196     if (syntax && *syntax)
1197         req->preferredRecordSyntax =
1198             yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, syntax);
1199
1200     if (schema && *schema)
1201     {
1202         Z_RecordComposition *compo = odr_malloc (c->odr_out, sizeof(*compo));
1203
1204         req->recordComposition = compo;
1205         compo->which = Z_RecordComp_complex;
1206         compo->u.complex = (Z_CompSpec *)
1207             odr_malloc(c->odr_out, sizeof(*compo->u.complex));
1208         compo->u.complex->selectAlternativeSyntax = (bool_t *) 
1209             odr_malloc(c->odr_out, sizeof(bool_t));
1210         *compo->u.complex->selectAlternativeSyntax = 0;
1211
1212         compo->u.complex->generic = (Z_Specification *)
1213             odr_malloc(c->odr_out, sizeof(*compo->u.complex->generic));
1214
1215         compo->u.complex->generic->schema = (Odr_oid *)
1216             yaz_str_to_z3950oid (c->odr_out, CLASS_SCHEMA, schema);
1217
1218         if (!compo->u.complex->generic->schema)
1219         {
1220             /* OID wasn't a schema! Try record syntax instead. */
1221
1222             compo->u.complex->generic->schema = (Odr_oid *)
1223                 yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, schema);
1224         }
1225         if (element && *element)
1226         {
1227             compo->u.complex->generic->elementSpec = (Z_ElementSpec *)
1228                 odr_malloc(c->odr_out, sizeof(Z_ElementSpec));
1229             compo->u.complex->generic->elementSpec->which =
1230                 Z_ElementSpec_elementSetName;
1231             compo->u.complex->generic->elementSpec->u.elementSetName =
1232                 odr_strdup (c->odr_out, element);
1233         }
1234         else
1235             compo->u.complex->generic->elementSpec = 0;
1236         compo->u.complex->num_dbSpecific = 0;
1237         compo->u.complex->dbSpecific = 0;
1238         compo->u.complex->num_recordSyntax = 0;
1239         compo->u.complex->recordSyntax = 0;
1240     }
1241     else if (element && *element)
1242     {
1243         Z_ElementSetNames *esn = odr_malloc (c->odr_out, sizeof(*esn));
1244         Z_RecordComposition *compo = odr_malloc (c->odr_out, sizeof(*compo));
1245         
1246         esn->which = Z_ElementSetNames_generic;
1247         esn->u.generic = odr_strdup (c->odr_out, element);
1248         compo->which = Z_RecordComp_simple;
1249         compo->u.simple = esn;
1250         req->recordComposition = compo;
1251     }
1252     req->resultSetId = odr_strdup(c->odr_out, resultset->setname);
1253     send_APDU (c, apdu);
1254     return 1;
1255 }
1256
1257 ZOOM_scanset ZOOM_connection_scan (ZOOM_connection c, const char *start)
1258 {
1259     ZOOM_scanset scan = xmalloc (sizeof(*scan));
1260
1261     scan->connection = c;
1262     scan->odr = odr_createmem (ODR_DECODE);
1263     scan->options = ZOOM_options_create_with_parent (c->options);
1264     scan->refcount = 1;
1265     scan->scan_response = 0;
1266
1267     if ((scan->termListAndStartPoint =
1268          p_query_scan(scan->odr, PROTO_Z3950, &scan->attributeSet,
1269                       start)))
1270     {
1271         ZOOM_task task = ZOOM_connection_add_task (c, ZOOM_TASK_SCAN);
1272         task->u.scan.scan = scan;
1273         
1274         (scan->refcount)++;
1275         if (!c->async)
1276         {
1277             while (ZOOM_event (1, &c))
1278                 ;
1279         }
1280     }
1281     return scan;
1282 }
1283
1284 void ZOOM_scanset_destroy (ZOOM_scanset scan)
1285 {
1286     if (!scan)
1287         return;
1288     (scan->refcount)--;
1289     if (scan->refcount == 0)
1290     {
1291         odr_destroy (scan->odr);
1292         
1293         ZOOM_options_destroy (scan->options);
1294         xfree (scan);
1295     }
1296 }
1297
1298 int send_scan (ZOOM_connection c)
1299 {
1300     ZOOM_scanset scan;
1301     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_scanRequest);
1302     Z_ScanRequest *req = apdu->u.scanRequest;
1303     if (!c->tasks)
1304         return 0;
1305     assert (c->tasks->which == ZOOM_TASK_SCAN);
1306     scan = c->tasks->u.scan.scan;
1307
1308     req->termListAndStartPoint = scan->termListAndStartPoint;
1309     req->attributeSet = scan->attributeSet;
1310
1311     *req->numberOfTermsRequested =
1312         ZOOM_options_get_int(scan->options, "number", 10);
1313
1314     req->preferredPositionInResponse =
1315         odr_intdup (c->odr_out,
1316                     ZOOM_options_get_int(scan->options, "position", 1));
1317
1318     req->stepSize =
1319         odr_intdup (c->odr_out,
1320                     ZOOM_options_get_int(scan->options, "stepSize", 0));
1321     
1322     req->databaseNames = set_DatabaseNames (c, scan->options, 
1323                                             &req->num_databaseNames);
1324
1325     send_APDU (c, apdu);
1326
1327     return 1;
1328 }
1329
1330 size_t ZOOM_scanset_size (ZOOM_scanset scan)
1331 {
1332     if (!scan || !scan->scan_response || !scan->scan_response->entries)
1333         return 0;
1334     return scan->scan_response->entries->num_entries;
1335 }
1336
1337 const char *ZOOM_scanset_term (ZOOM_scanset scan, size_t pos,
1338                                int *occ, size_t *len)
1339 {
1340     const char *term = 0;
1341     size_t noent = ZOOM_scanset_size (scan);
1342     Z_ScanResponse *res = scan->scan_response;
1343     
1344     *len = 0;
1345     *occ = 0;
1346     if (pos >= noent)
1347         return 0;
1348     if (res->entries->entries[pos]->which == Z_Entry_termInfo)
1349     {
1350         Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
1351         
1352         if (t->term->which == Z_Term_general)
1353         {
1354             term = t->term->u.general->buf;
1355             *len = t->term->u.general->len;
1356         }
1357         *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
1358     }
1359     return term;
1360 }
1361
1362 const char *ZOOM_scanset_option_get (ZOOM_scanset scan, const char *key)
1363 {
1364     return ZOOM_options_get (scan->options, key);
1365 }
1366
1367 void ZOOM_scanset_option_set (ZOOM_scanset scan, const char *key,
1368                               const char *val)
1369 {
1370     ZOOM_options_set (scan->options, key, val);
1371 }
1372
1373 static int ZOOM_connection_exec_task (ZOOM_connection c)
1374 {
1375     ZOOM_task task = c->tasks;
1376
1377     yaz_log (LOG_DEBUG, "ZOOM_connection_exec_task");
1378     if (!task)
1379         return 0;
1380     if (c->error != ZOOM_ERROR_NONE ||
1381         (!c->cs && task->which != ZOOM_TASK_CONNECT))
1382     {
1383         ZOOM_connection_remove_tasks (c);
1384         return 0;
1385     }
1386     yaz_log (LOG_DEBUG, "ZOOM_connection_exec_task type=%d", task->which);
1387     if (task->running)
1388         return 0;
1389     task->running = 1;
1390     switch (task->which)
1391     {
1392     case ZOOM_TASK_SEARCH:
1393         /* see if search hasn't been sent yet. */
1394         if (ZOOM_connection_send_search (c))
1395             return 1;
1396         break;
1397     case ZOOM_TASK_RETRIEVE:
1398         if (send_present (c))
1399             return 1;
1400         break;
1401     case ZOOM_TASK_CONNECT:
1402         if (do_connect(c))
1403             return 1;
1404         break;
1405     case ZOOM_TASK_SCAN:
1406         if (send_scan(c))
1407             return 1;
1408     }
1409     ZOOM_connection_remove_task (c);
1410     return 0;
1411 }
1412
1413 static int send_sort_present (ZOOM_connection c)
1414 {
1415     int r = send_sort (c);
1416     if (!r)
1417         r = send_present (c);
1418     return r;
1419 }
1420
1421 static void handle_apdu (ZOOM_connection c, Z_APDU *apdu)
1422 {
1423     Z_InitResponse *initrs;
1424     
1425     yaz_log (LOG_DEBUG, "hande_apdu type=%d", apdu->which);
1426     c->mask = 0;
1427     switch(apdu->which)
1428     {
1429     case Z_APDU_initResponse:
1430         initrs = apdu->u.initResponse;
1431         if (!*initrs->result)
1432         {
1433             c->error = ZOOM_ERROR_INIT;
1434         }
1435         else
1436         {
1437             char *cookie =
1438                 yaz_oi_get_string_oidval (&apdu->u.initResponse->otherInfo,
1439                                           VAL_COOKIE, 1, 0);
1440             xfree (c->cookie_in);
1441             c->cookie_in = 0;
1442             if (cookie)
1443                 c->cookie_in = xstrdup(cookie);
1444             if (ODR_MASK_GET(initrs->options, Z_Options_namedResultSets) &&
1445                 ODR_MASK_GET(initrs->protocolVersion, Z_ProtocolVersion_3))
1446                 c->support_named_resultsets = 1;
1447             if (c->tasks)
1448             {
1449                 assert (c->tasks->which == ZOOM_TASK_CONNECT);
1450                 ZOOM_connection_remove_task (c);
1451             }
1452             ZOOM_connection_exec_task (c);
1453         }
1454         break;
1455     case Z_APDU_searchResponse:
1456         handle_search_response (c, apdu->u.searchResponse);
1457         if (!send_sort_present (c))
1458             ZOOM_connection_remove_task (c);
1459         break;
1460     case Z_APDU_presentResponse:
1461         handle_present_response (c, apdu->u.presentResponse);
1462         if (!send_present (c))
1463             ZOOM_connection_remove_task (c);
1464         break;
1465     case Z_APDU_sortResponse:
1466         sort_response (c, apdu->u.sortResponse);
1467         if (!send_present (c))
1468             ZOOM_connection_remove_task (c);
1469         break;
1470     case Z_APDU_scanResponse:
1471         scan_response (c, apdu->u.scanResponse);
1472         ZOOM_connection_remove_task (c);
1473     }
1474 }
1475
1476 static int do_read (ZOOM_connection c)
1477 {
1478     int r;
1479     Z_APDU *apdu;
1480     ZOOM_Event event;
1481     
1482     event = ZOOM_Event_create (ZOOM_EVENT_RECV_DATA);
1483     ZOOM_connection_put_event (c, event);
1484     
1485     r = cs_get (c->cs, &c->buf_in, &c->len_in);
1486     if (r == 1)
1487         return 0;
1488     if (r <= 0)
1489     {
1490         c->error= ZOOM_ERROR_CONNECTION_LOST;
1491         do_close (c);
1492     }
1493     else
1494     {
1495         ZOOM_Event event;
1496         odr_reset (c->odr_in);
1497         odr_setbuf (c->odr_in, c->buf_in, r, 0);
1498         event = ZOOM_Event_create (ZOOM_EVENT_RECV_APDU);
1499         ZOOM_connection_put_event (c, event);
1500         if (!z_APDU (c->odr_in, &apdu, 0, 0))
1501         {
1502             c->error = ZOOM_ERROR_DECODE;
1503             do_close (c);
1504         }
1505         else
1506         {
1507             handle_apdu (c, apdu);
1508         }
1509     }
1510     return 1;
1511 }
1512
1513 static int do_write_ex (ZOOM_connection c, char *buf_out, int len_out)
1514 {
1515     int r;
1516     ZOOM_Event event;
1517     
1518     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
1519     ZOOM_connection_put_event (c, event);
1520
1521     if ((r=cs_put (c->cs, buf_out, len_out)) < 0)
1522     {
1523         if (c->state == STATE_CONNECTING)
1524             c->error = ZOOM_ERROR_CONNECT;
1525         else
1526             c->error = ZOOM_ERROR_CONNECTION_LOST;
1527         do_close (c);
1528         return 1;
1529     }
1530     else if (r == 1)
1531     {
1532         c->state = STATE_ESTABLISHED;
1533         c->mask = ZOOM_SELECT_READ|ZOOM_SELECT_WRITE|ZOOM_SELECT_EXCEPT;
1534     }
1535     else
1536     {
1537         c->state = STATE_ESTABLISHED;
1538         c->mask = ZOOM_SELECT_READ|ZOOM_SELECT_EXCEPT;
1539     }
1540     return 0;
1541 }
1542
1543 static int do_write(ZOOM_connection c)
1544 {
1545     return do_write_ex (c, c->buf_out, c->len_out);
1546 }
1547
1548
1549 const char *ZOOM_connection_option_get (ZOOM_connection c, const char *key)
1550 {
1551     return ZOOM_options_get (c->options, key);
1552 }
1553
1554 void ZOOM_connection_option_set (ZOOM_connection c, const char *key,
1555                                   const char *val)
1556 {
1557     ZOOM_options_set (c->options, key, val);
1558 }
1559
1560 const char *ZOOM_resultset_option_get (ZOOM_resultset r, const char *key)
1561 {
1562     return ZOOM_options_get (r->options, key);
1563 }
1564
1565 void ZOOM_resultset_option_set (ZOOM_resultset r, const char *key,
1566                                   const char *val)
1567 {
1568     ZOOM_options_set (r->options, key, val);
1569 }
1570
1571
1572 int ZOOM_connection_errcode (ZOOM_connection c)
1573 {
1574     return ZOOM_connection_error (c, 0, 0);
1575 }
1576
1577 const char *ZOOM_connection_errmsg (ZOOM_connection c)
1578 {
1579     const char *msg;
1580     ZOOM_connection_error (c, &msg, 0);
1581     return msg;
1582 }
1583
1584 const char *ZOOM_connection_addinfo (ZOOM_connection c)
1585 {
1586     const char *addinfo;
1587     ZOOM_connection_error (c, 0, &addinfo);
1588     return addinfo;
1589 }
1590
1591 int ZOOM_connection_error (ZOOM_connection c, const char **cp,
1592                             const char **addinfo)
1593 {
1594     int error = c->error;
1595     if (cp)
1596     {
1597         switch (error)
1598         {
1599         case ZOOM_ERROR_NONE:
1600             *cp = "No error"; break;
1601         case ZOOM_ERROR_CONNECT:
1602             *cp = "Connect failed"; break;
1603         case ZOOM_ERROR_MEMORY:
1604             *cp = "Out of memory"; break;
1605         case ZOOM_ERROR_ENCODE:
1606             *cp = "Encoding failed"; break;
1607         case ZOOM_ERROR_DECODE:
1608             *cp = "Decoding failed"; break;
1609         case ZOOM_ERROR_CONNECTION_LOST:
1610             *cp = "Connection lost"; break;
1611         case ZOOM_ERROR_INIT:
1612             *cp = "Init rejected"; break;
1613         case ZOOM_ERROR_INTERNAL:
1614             *cp = "Internal failure"; break;
1615         case ZOOM_ERROR_TIMEOUT:
1616             *cp = "Timeout"; break;
1617         default:
1618             *cp = diagbib1_str (error);
1619         }
1620     }
1621     if (addinfo)
1622     {
1623         if (c->addinfo)
1624             *addinfo = c->addinfo;
1625         else
1626             *addinfo = "";
1627     }
1628     return c->error;
1629 }
1630
1631 int ZOOM_connection_do_io(ZOOM_connection c, int mask)
1632 {
1633     ZOOM_Event event = 0;
1634 #if 0
1635     int r = cs_look(c->cs);
1636     yaz_log (LOG_LOG, "ZOOM_connection_do_io c=%p mask=%d cs_look=%d",
1637              c, mask, r);
1638     
1639     if (r == CS_NONE)
1640     {
1641         event = ZOOM_Event_create (ZOOM_EVENT_IO_CONNECT);
1642         c->error = ZOOM_ERROR_CONNECT;
1643         do_close (c);
1644         ZOOM_connection_put_event (c, event);
1645     }
1646     else if (r == CS_CONNECT)
1647     {
1648         event = ZOOM_Event_create (ZOOM_EVENT_IO_CONNECT);
1649         yaz_log (LOG_LOG, "calling rcvconnect");
1650         if (cs_rcvconnect (c->cs) < 0)
1651         {
1652             c->error = ZOOM_ERROR_CONNECT;
1653             do_close (c);
1654             ZOOM_connection_put_event (c, event);
1655         }
1656         else
1657         {
1658             ZOOM_connection_put_event (c, event);
1659             ZOOM_connection_send_init (c);
1660         }
1661     }
1662     else
1663     {
1664         if (mask & ZOOM_SELECT_READ)
1665             do_read (c);
1666         if (c->cs && (mask & ZOOM_SELECT_WRITE))
1667             do_write (c);
1668     }   
1669 #else
1670     yaz_log (LOG_DEBUG, "ZOOM_connection_do_io c=%p mask=%d", c, mask);
1671     if (c->state == STATE_CONNECTING)
1672     {
1673         event = ZOOM_Event_create (ZOOM_EVENT_CONNECT);
1674         if (mask & ZOOM_SELECT_WRITE)
1675         {
1676             ZOOM_connection_put_event (c, event);
1677             ZOOM_connection_send_init (c);
1678         }
1679         else
1680         {
1681             c->error = ZOOM_ERROR_CONNECT;
1682             do_close (c);
1683             ZOOM_connection_put_event (c, event);
1684         }
1685     }
1686     else if (c->state == STATE_ESTABLISHED)
1687     {
1688         if (mask & ZOOM_SELECT_READ)
1689             do_read (c);
1690         if (c->cs && (mask & ZOOM_SELECT_WRITE))
1691             do_write (c);
1692     }
1693     else
1694     {
1695         event = ZOOM_Event_create (ZOOM_EVENT_UNKNOWN);
1696         ZOOM_connection_put_event (c, event);
1697         c->error = ZOOM_ERROR_INTERNAL;
1698         do_close (c);
1699     }
1700 #endif
1701     return 1;
1702 }
1703
1704 int ZOOM_connection_last_event(ZOOM_connection cs)
1705 {
1706     if (!cs)
1707         return ZOOM_EVENT_NONE;
1708     return cs->last_event;
1709 }
1710
1711 int ZOOM_event (int no, ZOOM_connection *cs)
1712 {
1713 #if HAVE_SYS_POLL_H
1714     struct pollfd pollfds[1024];
1715     ZOOM_connection poll_cs[1024];
1716 #else
1717     struct timeval tv;
1718     fd_set input, output, except;
1719 #endif
1720     int i, r, nfds;
1721     int max_fd = 0;
1722
1723     for (i = 0; i<no; i++)
1724     {
1725         ZOOM_connection c = cs[i];
1726         ZOOM_Event event;
1727         if (c && (event = ZOOM_connection_get_event(c)))
1728         {
1729             ZOOM_Event_destroy (event);
1730             return i+1;
1731         }
1732     }
1733     for (i = 0; i<no; i++)
1734     {
1735         ZOOM_connection c = cs[i];
1736         ZOOM_Event event;
1737         if (c && ZOOM_connection_exec_task (c))
1738         {
1739             if ((event = ZOOM_connection_get_event(c)))
1740             {
1741                 ZOOM_Event_destroy (event);
1742                 return i+1;
1743             }
1744         }
1745     }
1746 #if HAVE_SYS_POLL_H
1747
1748 #else
1749     tv.tv_sec = 15;
1750     tv.tv_usec = 0;
1751     
1752     FD_ZERO (&input);
1753     FD_ZERO (&output);
1754     FD_ZERO (&except);
1755 #endif
1756     nfds = 0;
1757     for (i = 0; i<no; i++)
1758     {
1759         ZOOM_connection c = cs[i];
1760         int fd, mask;
1761         
1762         if (!c)
1763             continue;
1764         fd = z3950_connection_socket(c);
1765         mask = z3950_connection_mask(c);
1766
1767         if (fd == -1)
1768             continue;
1769         if (max_fd < fd)
1770             max_fd = fd;
1771
1772 #if HAVE_SYS_POLL_H
1773         if (mask)
1774         {
1775             short poll_events = 0;
1776
1777             if (mask & ZOOM_SELECT_READ)
1778                 poll_events += POLLIN;
1779             if (mask & ZOOM_SELECT_WRITE)
1780                 poll_events += POLLOUT;
1781             if (mask & ZOOM_SELECT_EXCEPT)
1782                 poll_events += POLLERR;
1783             pollfds[nfds].fd = fd;
1784             pollfds[nfds].events = poll_events;
1785             pollfds[nfds].revents = 0;
1786             poll_cs[nfds] = c;
1787             nfds++;
1788         }
1789 #else
1790         if (mask & ZOOM_SELECT_READ)
1791         {
1792             FD_SET (fd, &input);
1793             nfds++;
1794         }
1795         if (mask & ZOOM_SELECT_WRITE)
1796         {
1797             FD_SET (fd, &output);
1798             nfds++;
1799         }
1800         if (mask & ZOOM_SELECT_EXCEPT)
1801         {
1802             FD_SET (fd, &except);
1803             nfds++;
1804         }
1805 #endif
1806     }
1807     if (!nfds)
1808         return 0;
1809 #if HAVE_SYS_POLL_H
1810     yaz_log (LOG_DEBUG, "poll start");
1811     r = poll (pollfds, nfds, 15000);
1812     yaz_log (LOG_DEBUG, "poll stop, returned r=%d", r);
1813     for (i = 0; i<nfds; i++)
1814     {
1815         ZOOM_connection c = poll_cs[i];
1816         if (r && c->mask)
1817         {
1818             int mask = 0;
1819             if (pollfds[i].revents & POLLIN)
1820                 mask += ZOOM_SELECT_READ;
1821             if (pollfds[i].revents & POLLOUT)
1822                 mask += ZOOM_SELECT_WRITE;
1823             if (pollfds[i].revents & POLLERR)
1824                 mask += ZOOM_SELECT_EXCEPT;
1825             if (mask)
1826                 ZOOM_connection_do_io(c, mask);
1827         }
1828         else if (r == 0 && c->mask)
1829         {
1830             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
1831             /* timeout and this connection was waiting */
1832             c->error = ZOOM_ERROR_TIMEOUT;
1833             do_close (c);
1834             ZOOM_connection_put_event(c, event);
1835         }
1836     }
1837 #else
1838     yaz_log (LOG_DEBUG, "select start");
1839     r = select (max_fd+1, &input, &output, &except, &tv);
1840     yaz_log (LOG_DEBUG, "select stop, returned r=%d", r);
1841     for (i = 0; i<no; i++)
1842     {
1843         ZOOM_connection c = cs[i];
1844         int fd, mask;
1845
1846         if (!c)
1847             continue;
1848         fd = z3950_connection_socket(c);
1849         mask = 0;
1850         if (r && c->mask)
1851         {
1852             /* no timeout and real socket */
1853             if (FD_ISSET(fd, &input))
1854                 mask += ZOOM_SELECT_READ;
1855             if (FD_ISSET(fd, &output))
1856                 mask += ZOOM_SELECT_WRITE;
1857             if (FD_ISSET(fd, &except))
1858                 mask += ZOOM_SELECT_EXCEPT;
1859             if (mask)
1860                 ZOOM_connection_do_io(c, mask);
1861         }
1862         if (r == 0 && c->mask)
1863         {
1864             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
1865             /* timeout and this connection was waiting */
1866             c->error = ZOOM_ERROR_TIMEOUT;
1867             do_close (c);
1868             yaz_log (LOG_DEBUG, "timeout");
1869             ZOOM_connection_put_event(c, event);
1870         }
1871     }
1872 #endif
1873     for (i = 0; i<no; i++)
1874     {
1875         ZOOM_connection c = cs[i];
1876         ZOOM_Event event;
1877         if (c && (event = ZOOM_connection_get_event(c)))
1878         {
1879             ZOOM_Event_destroy (event);
1880             return i+1;
1881         }
1882     }
1883     return 0;
1884 }