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