Implement ZOOM_connection_is_idle.
[yaz-moved-to-github.git] / src / zoom-c.c
1 /*
2  * Copyright (C) 1995-2006, Index Data ApS
3  * See the file LICENSE for details.
4  *
5  * $Id: zoom-c.c,v 1.91 2006-10-04 16:24:05 adam Exp $
6  */
7 /**
8  * \file zoom-c.c
9  * \brief Implements ZOOM C interface.
10  */
11
12 #include <assert.h>
13 #include <string.h>
14 #include <errno.h>
15 #include "zoom-p.h"
16
17 #include <yaz/yaz-util.h>
18 #include <yaz/xmalloc.h>
19 #include <yaz/otherinfo.h>
20 #include <yaz/log.h>
21 #include <yaz/pquery.h>
22 #include <yaz/marcdisp.h>
23 #include <yaz/diagbib1.h>
24 #include <yaz/charneg.h>
25 #include <yaz/ill.h>
26 #include <yaz/srw.h>
27 #include <yaz/cql.h>
28 #include <yaz/ccl.h>
29
30 #if HAVE_SYS_TYPES_H
31 #include <sys/types.h>
32 #endif
33 #if HAVE_SYS_TIME_H
34 #include <sys/time.h>
35 #endif
36 #if HAVE_SYS_POLL_H
37 #include <sys/poll.h>
38 #endif
39 #if HAVE_SYS_SELECT_H
40 #include <sys/select.h>
41 #endif
42 #ifdef WIN32
43 #if FD_SETSIZE < 512
44 #define FD_SETSIZE 512
45 #endif
46 #include <winsock.h>
47 #endif
48
49 static int log_api = 0;
50 static int log_details = 0;
51
52 typedef enum {
53     zoom_pending,
54     zoom_complete
55 } zoom_ret;
56
57 static zoom_ret ZOOM_connection_send_init(ZOOM_connection c);
58 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out);
59 static char *cql2pqf(ZOOM_connection c, const char *cql);
60
61 static void initlog(void)
62 {
63     static int log_level_initialized = 0;
64     if (!log_level_initialized)
65     {
66         log_api = yaz_log_module_level("zoom");
67         log_details = yaz_log_module_level("zoomdetails");
68         log_level_initialized = 1;
69     }
70 }
71
72 static ZOOM_Event ZOOM_Event_create(int kind)
73 {
74     ZOOM_Event event = (ZOOM_Event) xmalloc(sizeof(*event));
75     event->kind = kind;
76     event->next = 0;
77     event->prev = 0;
78     yaz_log(log_details, "ZOOM_Event_create(kind=%d)", kind);
79     return event;
80 }
81
82 static void ZOOM_Event_destroy(ZOOM_Event event)
83 {
84     xfree(event);
85 }
86
87 static void ZOOM_connection_put_event(ZOOM_connection c, ZOOM_Event event)
88 {
89     if (c->m_queue_back)
90     {
91         c->m_queue_back->prev = event;
92         assert(c->m_queue_front);
93     }
94     else
95     {
96         assert(!c->m_queue_front);
97         c->m_queue_front = event;
98     }
99     event->next = c->m_queue_back;
100     event->prev = 0;
101     c->m_queue_back = event;
102 }
103
104 static ZOOM_Event ZOOM_connection_get_event(ZOOM_connection c)
105 {
106     ZOOM_Event event = c->m_queue_front;
107     if (!event)
108     {
109         c->last_event = ZOOM_EVENT_NONE;
110         return 0;
111     }
112     assert(c->m_queue_back);
113     c->m_queue_front = event->prev;
114     if (c->m_queue_front)
115     {
116         assert(c->m_queue_back);
117         c->m_queue_front->next = 0;
118     }
119     else
120         c->m_queue_back = 0;
121     c->last_event = event->kind;
122     return event;
123 }
124
125
126 static void set_dset_error(ZOOM_connection c, int error,
127                            const char *dset,
128                            const char *addinfo, const char *addinfo2)
129 {
130     char *cp;
131
132     xfree(c->addinfo);
133     c->addinfo = 0;
134     c->error = error;
135     if (!c->diagset || strcmp(dset, c->diagset))
136     {
137         xfree(c->diagset);
138         c->diagset = xstrdup(dset);
139         /* remove integer part from SRW diagset .. */
140         if ((cp = strrchr(c->diagset, '/')))
141             *cp = '\0';
142     }
143     if (addinfo && addinfo2)
144     {
145         c->addinfo = (char*) xmalloc(strlen(addinfo) + strlen(addinfo2) + 2);
146         strcpy(c->addinfo, addinfo);
147         strcat(c->addinfo, addinfo2);
148     }
149     else if (addinfo)
150         c->addinfo = xstrdup(addinfo);
151     if (error)
152         yaz_log(log_api, "%p set_dset_error %s %s:%d %s %s",
153                 c, c->host_port ? c->host_port : "<>", dset, error,
154                 addinfo ? addinfo : "",
155                 addinfo2 ? addinfo2 : "");
156 }
157
158 #if YAZ_HAVE_XML2
159 static void set_HTTP_error(ZOOM_connection c, int error,
160                            const char *addinfo, const char *addinfo2)
161 {
162     set_dset_error(c, error, "HTTP", addinfo, addinfo2);
163 }
164 #endif
165
166 static void set_ZOOM_error(ZOOM_connection c, int error,
167                            const char *addinfo)
168 {
169     set_dset_error(c, error, "ZOOM", addinfo, 0);
170 }
171
172 static void clear_error(ZOOM_connection c)
173 {
174     /*
175      * If an error is tied to an operation then it's ok to clear: for
176      * example, a diagnostic returned from a search is cleared by a
177      * subsequent search.  However, problems such as Connection Lost
178      * or Init Refused are not cleared, because they are not
179      * recoverable: doing another search doesn't help.
180      */
181     switch (c->error)
182     {
183     case ZOOM_ERROR_CONNECT:
184     case ZOOM_ERROR_MEMORY:
185     case ZOOM_ERROR_DECODE:
186     case ZOOM_ERROR_CONNECTION_LOST:
187     case ZOOM_ERROR_INIT:
188     case ZOOM_ERROR_INTERNAL:
189     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
190         break;
191     default:
192         set_ZOOM_error(c, ZOOM_ERROR_NONE, 0);
193     }
194 }
195
196 ZOOM_task ZOOM_connection_add_task(ZOOM_connection c, int which)
197 {
198     ZOOM_task *taskp = &c->tasks;
199     while (*taskp)
200         taskp = &(*taskp)->next;
201     *taskp = (ZOOM_task) xmalloc(sizeof(**taskp));
202     (*taskp)->running = 0;
203     (*taskp)->which = which;
204     (*taskp)->next = 0;
205     clear_error(c);
206     return *taskp;
207 }
208
209 int ZOOM_connection_is_idle(ZOOM_connection c)
210 {
211     return c->tasks ? 0 : 1;
212 }
213
214 ZOOM_task ZOOM_connection_insert_task(ZOOM_connection c, int which)
215 {
216     ZOOM_task task = (ZOOM_task) xmalloc(sizeof(*task));
217
218     task->next = c->tasks;
219     c->tasks = task;
220
221     task->running = 0;
222     task->which = which;
223     clear_error(c);
224     return task;
225 }
226
227 void ZOOM_connection_remove_task(ZOOM_connection c)
228 {
229     ZOOM_task task = c->tasks;
230
231     if (task)
232     {
233         c->tasks = task->next;
234         switch (task->which)
235         {
236         case ZOOM_TASK_SEARCH:
237             ZOOM_resultset_destroy(task->u.search.resultset);
238             break;
239         case ZOOM_TASK_RETRIEVE:
240             ZOOM_resultset_destroy(task->u.retrieve.resultset);
241             break;
242         case ZOOM_TASK_CONNECT:
243             break;
244         case ZOOM_TASK_SCAN:
245             ZOOM_scanset_destroy(task->u.scan.scan);
246             break;
247         case ZOOM_TASK_PACKAGE:
248             ZOOM_package_destroy(task->u.package);
249             break;
250         case ZOOM_TASK_SORT:
251             ZOOM_resultset_destroy(task->u.sort.resultset);
252             ZOOM_query_destroy(task->u.sort.q);
253             break;
254         default:
255             assert(0);
256         }
257         xfree(task);
258
259         if (!c->tasks)
260         {
261             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_END);
262             ZOOM_connection_put_event(c, event);
263         }
264     }
265 }
266
267 static int ZOOM_connection_exec_task(ZOOM_connection c);
268
269 void ZOOM_connection_remove_tasks(ZOOM_connection c)
270 {
271     while (c->tasks)
272         ZOOM_connection_remove_task(c);
273 }
274
275 static ZOOM_record record_cache_lookup(ZOOM_resultset r, int pos);
276
277 ZOOM_API(ZOOM_connection)
278     ZOOM_connection_create(ZOOM_options options)
279 {
280     ZOOM_connection c = (ZOOM_connection) xmalloc(sizeof(*c));
281
282     initlog();
283
284     yaz_log(log_api, "%p ZOOM_connection_create", c);
285
286     c->proto = PROTO_Z3950;
287     c->cs = 0;
288     c->mask = 0;
289     c->reconnect_ok = 0;
290     c->state = STATE_IDLE;
291     c->addinfo = 0;
292     c->diagset = 0;
293     set_ZOOM_error(c, ZOOM_ERROR_NONE, 0);
294     c->buf_in = 0;
295     c->len_in = 0;
296     c->buf_out = 0;
297     c->len_out = 0;
298     c->resultsets = 0;
299
300     c->options = ZOOM_options_create_with_parent(options);
301
302     c->host_port = 0;
303     c->path = 0;
304     c->proxy = 0;
305     
306     c->charset = c->lang = 0;
307
308     c->cookie_out = 0;
309     c->cookie_in = 0;
310     c->client_IP = 0;
311     c->tasks = 0;
312
313     c->odr_in = odr_createmem(ODR_DECODE);
314     c->odr_out = odr_createmem(ODR_ENCODE);
315
316     c->async = 0;
317     c->support_named_resultsets = 0;
318     c->last_event = ZOOM_EVENT_NONE;
319
320     c->m_queue_front = 0;
321     c->m_queue_back = 0;
322     return c;
323 }
324
325
326 /* set database names. Take local databases (if set); otherwise
327    take databases given in ZURL (if set); otherwise use Default */
328 static char **set_DatabaseNames(ZOOM_connection con, ZOOM_options options,
329                                 int *num)
330 {
331     char **databaseNames;
332     const char *cp = ZOOM_options_get(options, "databaseName");
333     
334     if ((!cp || !*cp) && con->host_port)
335     {
336         if (strncmp(con->host_port, "unix:", 5) == 0)
337             cp = strchr(con->host_port+5, ':');
338         else
339             cp = strchr(con->host_port, '/');
340         if (cp)
341             cp++;
342     }
343     if (!cp)
344         cp = "Default";
345     nmem_strsplit(con->odr_out->mem, "+", cp,  &databaseNames, num);
346     return databaseNames;
347 }
348
349 ZOOM_API(ZOOM_connection)
350     ZOOM_connection_new(const char *host, int portnum)
351 {
352     ZOOM_connection c = ZOOM_connection_create(0);
353
354     ZOOM_connection_connect(c, host, portnum);
355     return c;
356 }
357
358 static zoom_sru_mode get_sru_mode_from_string(const char *s)
359 {
360     if (!s || !*s)
361         return zoom_sru_soap;
362     if (!yaz_matchstr(s, "soap"))
363         return zoom_sru_soap;
364     else if (!yaz_matchstr(s, "get"))
365         return zoom_sru_get;
366     else if (!yaz_matchstr(s, "post"))
367         return zoom_sru_post;
368     return zoom_sru_error;
369 }
370
371 ZOOM_API(void)
372     ZOOM_connection_connect(ZOOM_connection c,
373                             const char *host, int portnum)
374 {
375     const char *val;
376     ZOOM_task task;
377
378     initlog();
379
380     yaz_log(log_api, "%p ZOOM_connection_connect host=%s portnum=%d",
381             c, host, portnum);
382
383     set_ZOOM_error(c, ZOOM_ERROR_NONE, 0);
384     ZOOM_connection_remove_tasks(c);
385
386     if (c->cs)
387     {
388         yaz_log(log_details, "%p ZOOM_connection_connect reconnect ok", c);
389         c->reconnect_ok = 1;
390         return;
391     }
392     yaz_log(log_details, "%p ZOOM_connection_connect connect", c);
393     xfree(c->proxy);
394     val = ZOOM_options_get(c->options, "proxy");
395     if (val && *val)
396     {
397         yaz_log(log_details, "%p ZOOM_connection_connect proxy=%s", c, val);
398         c->proxy = xstrdup(val);
399     }
400     else
401         c->proxy = 0;
402
403     xfree(c->charset);
404     val = ZOOM_options_get(c->options, "charset");
405     if (val && *val)
406     {
407         yaz_log(log_details, "%p ZOOM_connection_connect charset=%s", c, val);
408         c->charset = xstrdup(val);
409     }
410     else
411         c->charset = 0;
412
413     xfree(c->lang);
414     val = ZOOM_options_get(c->options, "lang");
415     if (val && *val)
416     {
417         yaz_log(log_details, "%p ZOOM_connection_connect lang=%s", c, val);
418         c->lang = xstrdup(val);
419     }
420     else
421         c->lang = 0;
422
423     val = ZOOM_options_get(c->options, "sru");
424     c->sru_mode = get_sru_mode_from_string(val);
425
426     xfree(c->host_port);
427     if (portnum)
428     {
429         char hostn[128];
430         sprintf(hostn, "%.80s:%d", host, portnum);
431         c->host_port = xstrdup(hostn);
432     }
433     else
434         c->host_port = xstrdup(host);
435
436     {
437         /*
438          * If the "<scheme>:" part of the host string is preceded by one
439          * or more comma-separated <name>=<value> pairs, these are taken
440          * to be options to be set on the connection object.  Among other
441          * applications, this facility can be used to embed authentication
442          * in a host string:
443          *          user=admin,password=secret,tcp:localhost:9999
444          */
445         char *remainder = c->host_port;
446         char *pcolon = strchr(remainder, ':');
447         char *pcomma;
448         char *pequals;
449         while ((pcomma = strchr(remainder, ',')) != 0 &&
450                (pcolon == 0 || pcomma < pcolon)) {
451             *pcomma = '\0';
452             if ((pequals = strchr(remainder, '=')) != 0) {
453                 *pequals = '\0';
454                 /*printf("# setting '%s'='%s'\n", remainder, pequals+1);*/
455                 ZOOM_connection_option_set(c, remainder, pequals+1);
456             }
457             remainder = pcomma+1;
458         }
459
460         if (remainder != c->host_port) {
461             xfree(c->host_port);
462             c->host_port = xstrdup(remainder);
463             /*printf("# reset hp='%s'\n", remainder);*/
464         }
465     }
466
467     ZOOM_options_set(c->options, "host", c->host_port);
468
469     val = ZOOM_options_get(c->options, "cookie");
470     if (val && *val)
471     { 
472         yaz_log(log_details, "%p ZOOM_connection_connect cookie=%s", c, val);
473         c->cookie_out = xstrdup(val);
474     }
475
476     val = ZOOM_options_get(c->options, "clientIP");
477     if (val && *val)
478     {
479         yaz_log(log_details, "%p ZOOM_connection_connect clientIP=%s",
480                 c, val);
481         c->client_IP = xstrdup(val);
482     }
483
484     c->async = ZOOM_options_get_bool(c->options, "async", 0);
485     yaz_log(log_details, "%p ZOOM_connection_connect async=%d", c, c->async);
486  
487     task = ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
488
489     if (!c->async)
490     {
491         while (ZOOM_event(1, &c))
492             ;
493     }
494 }
495
496 ZOOM_API(ZOOM_query)
497     ZOOM_query_create(void)
498 {
499     ZOOM_query s = (ZOOM_query) xmalloc(sizeof(*s));
500
501     yaz_log(log_details, "%p ZOOM_query_create", s);
502     s->refcount = 1;
503     s->z_query = 0;
504     s->sort_spec = 0;
505     s->odr = odr_createmem(ODR_ENCODE);
506     s->query_string = 0;
507
508     return s;
509 }
510
511 ZOOM_API(void)
512     ZOOM_query_destroy(ZOOM_query s)
513 {
514     if (!s)
515         return;
516
517     (s->refcount)--;
518     yaz_log(log_details, "%p ZOOM_query_destroy count=%d", s, s->refcount);
519     if (s->refcount == 0)
520     {
521         odr_destroy(s->odr);
522         xfree(s);
523     }
524 }
525
526 ZOOM_API(int)
527     ZOOM_query_prefix(ZOOM_query s, const char *str)
528 {
529     s->query_string = odr_strdup(s->odr, str);
530     s->z_query = (Z_Query *) odr_malloc(s->odr, sizeof(*s->z_query));
531     s->z_query->which = Z_Query_type_1;
532     s->z_query->u.type_1 =  p_query_rpn(s->odr, PROTO_Z3950, str);
533     if (!s->z_query->u.type_1)
534     {
535         yaz_log(log_details, "%p ZOOM_query_prefix str=%s failed", s, str);
536         s->z_query = 0;
537         return -1;
538     }
539     yaz_log(log_details, "%p ZOOM_query_prefix str=%s", s, str);
540     return 0;
541 }
542
543 ZOOM_API(int)
544     ZOOM_query_cql(ZOOM_query s, const char *str)
545 {
546     Z_External *ext;
547
548     s->query_string = odr_strdup(s->odr, str);
549
550     ext = (Z_External *) odr_malloc(s->odr, sizeof(*ext));
551     ext->direct_reference = odr_getoidbystr(s->odr, "1.2.840.10003.16.2");
552     ext->indirect_reference = 0;
553     ext->descriptor = 0;
554     ext->which = Z_External_CQL;
555     ext->u.cql = s->query_string;
556     
557     s->z_query = (Z_Query *) odr_malloc(s->odr, sizeof(*s->z_query));
558     s->z_query->which = Z_Query_type_104;
559     s->z_query->u.type_104 =  ext;
560
561     yaz_log(log_details, "%p ZOOM_query_cql str=%s", s, str);
562
563     return 0;
564 }
565
566 /*
567  * Translate the CQL string client-side into RPN which is passed to
568  * the server.  This is useful for server's that don't themselves
569  * support CQL, for which ZOOM_query_cql() is useless.  `conn' is used
570  * only as a place to stash diagnostics if compilation fails; if this
571  * information is not needed, a null pointer may be used.
572  */
573 ZOOM_API(int)
574     ZOOM_query_cql2rpn(ZOOM_query s, const char *str, ZOOM_connection conn)
575 {
576     char *rpn;
577     int ret;
578     ZOOM_connection freeme = 0;
579
580     yaz_log(log_details, "%p ZOOM_query_cql2rpn str=%s conn=%p", s, str, conn);
581     if (conn == 0)
582         conn = freeme = ZOOM_connection_create(0);
583
584     rpn = cql2pqf(conn, str);
585     if (freeme != 0)
586         ZOOM_connection_destroy(freeme);
587     if (rpn == 0)
588         return -1;
589
590     ret = ZOOM_query_prefix(s, rpn);
591     xfree(rpn);
592     return ret;
593 }
594
595 /*
596  * Analogous in every way to ZOOM_query_cql2rpn(), except that there
597  * is no analogous ZOOM_query_ccl() that just sends uninterpreted CCL
598  * to the server, as the YAZ GFS doesn't know how to handle this.
599  */
600 ZOOM_API(int)
601     ZOOM_query_ccl2rpn(ZOOM_query s, const char *str, const char *config,
602                        int *ccl_error, const char **error_string,
603                        int *error_pos)
604 {
605     int ret;
606     struct ccl_rpn_node *rpn;
607     CCL_bibset bibset = ccl_qual_mk();
608
609     if (config)
610         ccl_qual_buf(bibset, config);
611
612     rpn = ccl_find_str(bibset, str, ccl_error, error_pos);
613     if (!rpn)
614     {
615         *error_string = ccl_err_msg(*ccl_error);
616         ret = -1;
617     }
618     else
619     {
620         WRBUF wr = wrbuf_alloc();
621         ccl_pquery(wr, rpn);
622         ccl_rpn_delete(rpn);
623         ret = ZOOM_query_prefix(s, wrbuf_buf(wr));
624         wrbuf_free(wr, 1);
625     }
626     ccl_qual_rm(&bibset);
627     return ret;
628 }
629
630 ZOOM_API(int)
631     ZOOM_query_sortby(ZOOM_query s, const char *criteria)
632 {
633     s->sort_spec = yaz_sort_spec(s->odr, criteria);
634     if (!s->sort_spec)
635     {
636         yaz_log(log_details, "%p ZOOM_query_sortby criteria=%s failed",
637                 s, criteria);
638         return -1;
639     }
640     yaz_log(log_details, "%p ZOOM_query_sortby criteria=%s", s, criteria);
641     return 0;
642 }
643
644 static zoom_ret do_write(ZOOM_connection c);
645
646 ZOOM_API(void)
647     ZOOM_connection_destroy(ZOOM_connection c)
648 {
649     ZOOM_resultset r;
650     if (!c)
651         return;
652     yaz_log(log_api, "%p ZOOM_connection_destroy", c);
653     if (c->cs)
654         cs_close(c->cs);
655     for (r = c->resultsets; r; r = r->next)
656         r->connection = 0;
657
658     xfree(c->buf_in);
659     xfree(c->addinfo);
660     xfree(c->diagset);
661     odr_destroy(c->odr_in);
662     odr_destroy(c->odr_out);
663     ZOOM_options_destroy(c->options);
664     ZOOM_connection_remove_tasks(c);
665     xfree(c->host_port);
666     xfree(c->path);
667     xfree(c->proxy);
668     xfree(c->charset);
669     xfree(c->lang);
670     xfree(c->cookie_out);
671     xfree(c->cookie_in);
672     xfree(c->client_IP);
673     xfree(c);
674 }
675
676 void ZOOM_resultset_addref(ZOOM_resultset r)
677 {
678     if (r)
679     {
680         (r->refcount)++;
681         yaz_log(log_details, "%p ZOOM_resultset_addref count=%d",
682                 r, r->refcount);
683     }
684 }
685
686 ZOOM_resultset ZOOM_resultset_create(void)
687 {
688     int i;
689     ZOOM_resultset r = (ZOOM_resultset) xmalloc(sizeof(*r));
690
691     initlog();
692
693     yaz_log(log_details, "%p ZOOM_resultset_create", r);
694     r->refcount = 1;
695     r->size = 0;
696     r->odr = odr_createmem(ODR_ENCODE);
697     r->piggyback = 1;
698     r->setname = 0;
699     r->schema = 0;
700     r->step = 0;
701     for (i = 0; i<RECORD_HASH_SIZE; i++)
702         r->record_hash[i] = 0;
703     r->r_sort_spec = 0;
704     r->query = 0;
705     r->connection = 0;
706     r->next = 0;
707     return r;
708 }
709
710 ZOOM_API(ZOOM_resultset)
711     ZOOM_connection_search_pqf(ZOOM_connection c, const char *q)
712 {
713     ZOOM_resultset r;
714     ZOOM_query s = ZOOM_query_create();
715
716     ZOOM_query_prefix(s, q);
717
718     r = ZOOM_connection_search(c, s);
719     ZOOM_query_destroy(s);
720     return r;
721 }
722
723 ZOOM_API(ZOOM_resultset)
724     ZOOM_connection_search(ZOOM_connection c, ZOOM_query q)
725 {
726     ZOOM_resultset r = ZOOM_resultset_create();
727     ZOOM_task task;
728     const char *cp;
729     int start, count;
730
731     yaz_log(log_api, "%p ZOOM_connection_search set %p query %p", c, r, q);
732     r->r_sort_spec = q->sort_spec;
733     r->query = q;
734
735     r->options = ZOOM_options_create_with_parent(c->options);
736
737     start = ZOOM_options_get_int(r->options, "start", 0);
738     count = ZOOM_options_get_int(r->options, "count", 0);
739     {
740         /* If "presentChunk" is defined use that; otherwise "step" */
741         const char *cp = ZOOM_options_get(r->options, "presentChunk");
742         r->step = ZOOM_options_get_int(r->options,
743                                        (cp != 0 ? "presentChunk": "step"), 0);
744     }
745     r->piggyback = ZOOM_options_get_bool(r->options, "piggyback", 1);
746     cp = ZOOM_options_get(r->options, "setname");
747     if (cp)
748         r->setname = xstrdup(cp);
749     cp = ZOOM_options_get(r->options, "schema");
750     if (cp)
751         r->schema = xstrdup(cp);
752     
753     r->connection = c;
754
755     r->next = c->resultsets;
756     c->resultsets = r;
757
758     if (c->host_port && c->proto == PROTO_HTTP)
759     {
760         if (!c->cs)
761         {
762             yaz_log(log_details, "ZOOM_connection_search: no comstack");
763             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
764         }
765         else
766         {
767             yaz_log(log_details, "ZOOM_connection_search: reconnect");
768             c->reconnect_ok = 1;
769         }
770     }
771
772     task = ZOOM_connection_add_task(c, ZOOM_TASK_SEARCH);
773     task->u.search.resultset = r;
774     task->u.search.start = start;
775     task->u.search.count = count;
776     ZOOM_resultset_addref(r);  
777
778     (q->refcount)++;
779
780     if (!c->async)
781     {
782         while (ZOOM_event(1, &c))
783             ;
784     }
785     return r;
786 }
787
788 /*
789  * This is the old result-set sorting API, which is maintained only
790  * for the sake of binary compatibility.  There is no reason ever to
791  * use this rather than ZOOM_resultset_sort1().
792  */
793 ZOOM_API(void)
794     ZOOM_resultset_sort(ZOOM_resultset r,
795                         const char *sort_type, const char *sort_spec)
796 {
797     (void) ZOOM_resultset_sort1(r, sort_type, sort_spec);
798 }
799
800 ZOOM_API(int)
801     ZOOM_resultset_sort1(ZOOM_resultset r,
802                          const char *sort_type, const char *sort_spec)
803 {
804     ZOOM_connection c = r->connection;
805     ZOOM_task task;
806     ZOOM_query newq;
807
808     newq = ZOOM_query_create();
809     if (ZOOM_query_sortby(newq, sort_spec) < 0)
810         return -1;
811
812     yaz_log(log_api, "%p ZOOM_resultset_sort r=%p sort_type=%s sort_spec=%s",
813             r, r, sort_type, sort_spec);
814     if (!c)
815         return 0;
816
817     if (c->host_port && c->proto == PROTO_HTTP)
818     {
819         if (!c->cs)
820         {
821             yaz_log(log_details, "%p ZOOM_resultset_sort: no comstack", r);
822             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
823         }
824         else
825         {
826             yaz_log(log_details, "%p ZOOM_resultset_sort: prepare reconnect",
827                     r);
828             c->reconnect_ok = 1;
829         }
830     }
831     
832     ZOOM_resultset_cache_reset(r);
833     task = ZOOM_connection_add_task(c, ZOOM_TASK_SORT);
834     task->u.sort.resultset = r;
835     task->u.sort.q = newq;
836
837     ZOOM_resultset_addref(r);  
838
839     if (!c->async)
840     {
841         while (ZOOM_event(1, &c))
842             ;
843     }
844
845     return 0;
846 }
847
848 ZOOM_API(void)
849     ZOOM_resultset_cache_reset(ZOOM_resultset r)
850 {
851     int i;
852     for (i = 0; i<RECORD_HASH_SIZE; i++)
853     {
854         ZOOM_record_cache rc;
855         for (rc = r->record_hash[i]; rc; rc = rc->next)
856         {
857             if (rc->rec.wrbuf_marc)
858                 wrbuf_free(rc->rec.wrbuf_marc, 1);
859             if (rc->rec.wrbuf_iconv)
860                 wrbuf_free(rc->rec.wrbuf_iconv, 1);
861             if (rc->rec.wrbuf_opac)
862                 wrbuf_free(rc->rec.wrbuf_opac, 1);
863         }
864         r->record_hash[i] = 0;
865     }
866 }
867
868 ZOOM_API(void)
869     ZOOM_resultset_destroy(ZOOM_resultset r)
870 {
871     if (!r)
872         return;
873     (r->refcount)--;
874     yaz_log(log_details, "%p ZOOM_resultset_destroy r=%p count=%d",
875             r, r, r->refcount);
876     if (r->refcount == 0)
877     {
878         ZOOM_resultset_cache_reset(r);
879
880         if (r->connection)
881         {
882             /* remove ourselves from the resultsets in connection */
883             ZOOM_resultset *rp = &r->connection->resultsets;
884             while (1)
885             {
886                 assert(*rp);   /* we must be in this list!! */
887                 if (*rp == r)
888                 {   /* OK, we're here - take us out of it */
889                     *rp = (*rp)->next;
890                     break;
891                 }
892                 rp = &(*rp)->next;
893             }
894         }
895         ZOOM_query_destroy(r->query);
896         ZOOM_options_destroy(r->options);
897         odr_destroy(r->odr);
898         xfree(r->setname);
899         xfree(r->schema);
900         xfree(r);
901     }
902 }
903
904 ZOOM_API(size_t)
905     ZOOM_resultset_size(ZOOM_resultset r)
906 {
907     return r->size;
908 }
909
910 static void do_close(ZOOM_connection c)
911 {
912     if (c->cs)
913         cs_close(c->cs);
914     c->cs = 0;
915     c->mask = 0;
916     c->state = STATE_IDLE;
917 }
918
919 static int ZOOM_test_reconnect(ZOOM_connection c)
920 {
921     if (!c->reconnect_ok)
922         return 0;
923     do_close(c);
924     c->reconnect_ok = 0;
925     c->tasks->running = 0;
926     ZOOM_connection_insert_task(c, ZOOM_TASK_CONNECT);
927     return 1;
928 }
929
930 static void ZOOM_resultset_retrieve(ZOOM_resultset r,
931                                     int force_sync, int start, int count)
932 {
933     ZOOM_task task;
934     ZOOM_connection c;
935     const char *cp;
936
937     if (!r)
938         return;
939     yaz_log(log_details, "%p ZOOM_resultset_retrieve force_sync=%d start=%d"
940             " count=%d", r, force_sync, start, count);
941     c = r->connection;
942     if (!c)
943         return;
944
945     if (c->host_port && c->proto == PROTO_HTTP)
946     {
947         if (!c->cs)
948         {
949             yaz_log(log_details, "%p ZOOM_resultset_retrieve: no comstack", r);
950             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
951         }
952         else
953         {
954             yaz_log(log_details, "%p ZOOM_resultset_retrieve: prepare "
955                     "reconnect", r);
956             c->reconnect_ok = 1;
957         }
958     }
959     task = ZOOM_connection_add_task(c, ZOOM_TASK_RETRIEVE);
960     task->u.retrieve.resultset = r;
961     task->u.retrieve.start = start;
962     task->u.retrieve.count = count;
963
964     cp = ZOOM_options_get(r->options, "schema");
965     if (cp)
966     {
967         if (!r->schema || strcmp(r->schema, cp))
968         {
969             xfree(r->schema);
970             r->schema = xstrdup(cp);
971         }
972     }
973
974     ZOOM_resultset_addref(r);
975
976     if (!r->connection->async || force_sync)
977         while (r->connection && ZOOM_event(1, &r->connection))
978             ;
979 }
980
981 ZOOM_API(void)
982     ZOOM_resultset_records(ZOOM_resultset r, ZOOM_record *recs,
983                            size_t start, size_t count)
984 {
985     int force_present = 0;
986
987     if (!r)
988         return ;
989     yaz_log(log_api, "%p ZOOM_resultset_records r=%p start=%ld count=%ld",
990             r, r, (long) start, (long) count);
991     if (count && recs)
992         force_present = 1;
993     ZOOM_resultset_retrieve(r, force_present, start, count);
994     if (force_present)
995     {
996         size_t i;
997         for (i = 0; i< count; i++)
998             recs[i] = ZOOM_resultset_record_immediate(r, i+start);
999     }
1000 }
1001
1002 static void get_cert(ZOOM_connection c)
1003 {
1004     char *cert_buf;
1005     int cert_len;
1006     
1007     if (cs_get_peer_certificate_x509(c->cs, &cert_buf, &cert_len))
1008     {
1009         ZOOM_connection_option_setl(c, "sslPeerCert",
1010                                     cert_buf, cert_len);
1011         xfree(cert_buf);
1012     }
1013 }
1014
1015 static zoom_ret do_connect(ZOOM_connection c)
1016 {
1017     void *add;
1018     const char *effective_host;
1019
1020     if (c->proxy)
1021         effective_host = c->proxy;
1022     else
1023         effective_host = c->host_port;
1024
1025     yaz_log(log_details, "%p do_connect effective_host=%s", c, effective_host);
1026
1027     if (c->cs)
1028         cs_close(c->cs);
1029     c->cs = cs_create_host(effective_host, 0, &add);
1030
1031     if (c->cs && c->cs->protocol == PROTO_HTTP)
1032     {
1033 #if YAZ_HAVE_XML2
1034         const char *path = 0;
1035
1036         c->proto = PROTO_HTTP;
1037         cs_get_host_args(c->host_port, &path);
1038         xfree(c->path);
1039         c->path = (char*) xmalloc(strlen(path)+2);
1040         c->path[0] = '/';
1041         strcpy(c->path+1, path);
1042 #else
1043         set_ZOOM_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, "SRW");
1044         do_close(c);
1045         return zoom_complete;
1046 #endif
1047     }
1048     if (c->cs)
1049     {
1050         int ret = cs_connect(c->cs, add);
1051         if (ret == 0)
1052         {
1053             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1054             ZOOM_connection_put_event(c, event);
1055             get_cert(c);
1056             if (c->proto == PROTO_Z3950)
1057                 ZOOM_connection_send_init(c);
1058             else
1059             {
1060                 /* no init request for SRW .. */
1061                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
1062                 ZOOM_connection_remove_task(c);
1063                 c->mask = 0;
1064                 ZOOM_connection_exec_task(c);
1065             }
1066             c->state = STATE_ESTABLISHED;
1067             return zoom_pending;
1068         }
1069         else if (ret > 0)
1070         {
1071             c->state = STATE_CONNECTING; 
1072             c->mask = ZOOM_SELECT_EXCEPT;
1073             if (c->cs->io_pending & CS_WANT_WRITE)
1074                 c->mask += ZOOM_SELECT_WRITE;
1075             if (c->cs->io_pending & CS_WANT_READ)
1076                 c->mask += ZOOM_SELECT_READ;
1077             return zoom_pending;
1078         }
1079     }
1080     c->state = STATE_IDLE;
1081     set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
1082     return zoom_complete;
1083 }
1084
1085 int z3950_connection_socket(ZOOM_connection c)
1086 {
1087     if (c->cs)
1088         return cs_fileno(c->cs);
1089     return -1;
1090 }
1091
1092 int z3950_connection_mask(ZOOM_connection c)
1093 {
1094     if (c->cs)
1095         return c->mask;
1096     return 0;
1097 }
1098
1099 static void otherInfo_attach(ZOOM_connection c, Z_APDU *a, ODR out)
1100 {
1101     int i;
1102     for (i = 0; i<200; i++)
1103     {
1104         size_t len;
1105         Z_OtherInformation **oi;
1106         char buf[80];
1107         const char *val;
1108         const char *cp;
1109         int oidval;
1110
1111         sprintf(buf, "otherInfo%d", i);
1112         val = ZOOM_options_get(c->options, buf);
1113         if (!val)
1114             break;
1115         cp = strchr(val, ':');
1116         if (!cp)
1117             continue;
1118         len = cp - val;
1119         if (len >= sizeof(buf))
1120             len = sizeof(buf)-1;
1121         memcpy(buf, val, len);
1122         buf[len] = '\0';
1123         oidval = oid_getvalbyname(buf);
1124         if (oidval == VAL_NONE)
1125             continue;
1126         
1127         yaz_oi_APDU(a, &oi);
1128         yaz_oi_set_string_oidval(oi, out, oidval, 1, cp+1);
1129     }
1130 }
1131
1132 static int encode_APDU(ZOOM_connection c, Z_APDU *a, ODR out)
1133 {
1134     assert(a);
1135     if (c->cookie_out)
1136     {
1137         Z_OtherInformation **oi;
1138         yaz_oi_APDU(a, &oi);
1139         yaz_oi_set_string_oidval(oi, out, VAL_COOKIE, 1, c->cookie_out);
1140     }
1141     if (c->client_IP)
1142     {
1143         Z_OtherInformation **oi;
1144         yaz_oi_APDU(a, &oi);
1145         yaz_oi_set_string_oidval(oi, out, VAL_CLIENT_IP, 1, c->client_IP);
1146     }
1147     otherInfo_attach(c, a, out);
1148     if (!z_APDU(out, &a, 0, 0))
1149     {
1150         FILE *outf = fopen("/tmp/apdu.txt", "a");
1151         if (a && outf)
1152         {
1153             ODR odr_pr = odr_createmem(ODR_PRINT);
1154             fprintf(outf, "a=%p\n", a);
1155             odr_setprint(odr_pr, outf);
1156             z_APDU(odr_pr, &a, 0, 0);
1157             odr_destroy(odr_pr);
1158         }
1159         yaz_log(log_api, "%p encoding_APDU: encoding failed", c);
1160         set_ZOOM_error(c, ZOOM_ERROR_ENCODE, 0);
1161         odr_reset(out);
1162         return -1;
1163     }
1164     yaz_log(log_details, "%p encoding_APDU encoding OK", c);
1165     return 0;
1166 }
1167
1168 static zoom_ret send_APDU(ZOOM_connection c, Z_APDU *a)
1169 {
1170     ZOOM_Event event;
1171     assert(a);
1172     if (encode_APDU(c, a, c->odr_out))
1173         return zoom_complete;
1174     yaz_log(log_details, "%p send APDU type=%d", c, a->which);
1175     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
1176     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
1177     ZOOM_connection_put_event(c, event);
1178     odr_reset(c->odr_out);
1179     return do_write(c);
1180 }
1181
1182 /* returns 1 if PDU was sent OK (still pending )
1183    0 if PDU was not sent OK (nothing to wait for) 
1184 */
1185
1186 static zoom_ret ZOOM_connection_send_init(ZOOM_connection c)
1187 {
1188     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_initRequest);
1189     Z_InitRequest *ireq = apdu->u.initRequest;
1190     Z_IdAuthentication *auth = (Z_IdAuthentication *)
1191         odr_malloc(c->odr_out, sizeof(*auth));
1192     const char *auth_groupId = ZOOM_options_get(c->options, "group");
1193     const char *auth_userId = ZOOM_options_get(c->options, "user");
1194     const char *auth_password = ZOOM_options_get(c->options, "password");
1195     char *version;
1196
1197     /* support the pass for backwards compatibility */
1198     if (!auth_password)
1199         auth_password = ZOOM_options_get(c->options, "pass");
1200         
1201     ODR_MASK_SET(ireq->options, Z_Options_search);
1202     ODR_MASK_SET(ireq->options, Z_Options_present);
1203     ODR_MASK_SET(ireq->options, Z_Options_scan);
1204     ODR_MASK_SET(ireq->options, Z_Options_sort);
1205     ODR_MASK_SET(ireq->options, Z_Options_extendedServices);
1206     ODR_MASK_SET(ireq->options, Z_Options_namedResultSets);
1207     
1208     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_1);
1209     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_2);
1210     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_3);
1211     
1212     /* Index Data's Z39.50 Implementor Id is 81 */
1213     ireq->implementationId =
1214         odr_prepend(c->odr_out,
1215                     ZOOM_options_get(c->options, "implementationId"),
1216                     odr_prepend(c->odr_out, "81", ireq->implementationId));
1217     
1218     ireq->implementationName = 
1219         odr_prepend(c->odr_out,
1220                     ZOOM_options_get(c->options, "implementationName"),
1221                     odr_prepend(c->odr_out, "ZOOM-C",
1222                                 ireq->implementationName));
1223     
1224     version = odr_strdup(c->odr_out, "$Revision: 1.91 $");
1225     if (strlen(version) > 10)   /* check for unexpanded CVS strings */
1226         version[strlen(version)-2] = '\0';
1227     ireq->implementationVersion = 
1228         odr_prepend(c->odr_out,
1229                     ZOOM_options_get(c->options, "implementationVersion"),
1230                     odr_prepend(c->odr_out, &version[11],
1231                                 ireq->implementationVersion));
1232     
1233     *ireq->maximumRecordSize =
1234         ZOOM_options_get_int(c->options, "maximumRecordSize", 1024*1024);
1235     *ireq->preferredMessageSize =
1236         ZOOM_options_get_int(c->options, "preferredMessageSize", 1024*1024);
1237     
1238     if (auth_groupId || auth_password)
1239     {
1240         Z_IdPass *pass = (Z_IdPass *) odr_malloc(c->odr_out, sizeof(*pass));
1241         int i = 0;
1242         pass->groupId = 0;
1243         if (auth_groupId && *auth_groupId)
1244         {
1245             pass->groupId = (char *)
1246                 odr_malloc(c->odr_out, strlen(auth_groupId)+1);
1247             strcpy(pass->groupId, auth_groupId);
1248             i++;
1249         }
1250         pass->userId = 0;
1251         if (auth_userId && *auth_userId)
1252         {
1253             pass->userId = (char *)
1254                 odr_malloc(c->odr_out, strlen(auth_userId)+1);
1255             strcpy(pass->userId, auth_userId);
1256             i++;
1257         }
1258         pass->password = 0;
1259         if (auth_password && *auth_password)
1260         {
1261             pass->password = (char *)
1262                 odr_malloc(c->odr_out, strlen(auth_password)+1);
1263             strcpy(pass->password, auth_password);
1264             i++;
1265         }
1266         if (i)
1267         {
1268             auth->which = Z_IdAuthentication_idPass;
1269             auth->u.idPass = pass;
1270             ireq->idAuthentication = auth;
1271         }
1272     }
1273     else if (auth_userId)
1274     {
1275         auth->which = Z_IdAuthentication_open;
1276         auth->u.open = (char *)
1277             odr_malloc(c->odr_out, strlen(auth_userId)+1);
1278         strcpy(auth->u.open, auth_userId);
1279         ireq->idAuthentication = auth;
1280     }
1281     if (c->proxy)
1282         yaz_oi_set_string_oidval(&ireq->otherInfo, c->odr_out,
1283                                  VAL_PROXY, 1, c->host_port);
1284     if (c->charset || c->lang)
1285     {
1286         Z_OtherInformation **oi;
1287         Z_OtherInformationUnit *oi_unit;
1288         
1289         yaz_oi_APDU(apdu, &oi);
1290         
1291         if ((oi_unit = yaz_oi_update(oi, c->odr_out, NULL, 0, 0)))
1292         {
1293             ODR_MASK_SET(ireq->options, Z_Options_negotiationModel);
1294             oi_unit->which = Z_OtherInfo_externallyDefinedInfo;
1295             oi_unit->information.externallyDefinedInfo =
1296                 yaz_set_proposal_charneg_list(c->odr_out, " ",
1297                                               c->charset, c->lang, 1);
1298         }
1299     }
1300     assert(apdu);
1301     return send_APDU(c, apdu);
1302 }
1303
1304 #if YAZ_HAVE_XML2
1305 static zoom_ret send_srw(ZOOM_connection c, Z_SRW_PDU *sr)
1306 {
1307     Z_GDU *gdu;
1308     ZOOM_Event event;
1309
1310     gdu = z_get_HTTP_Request_host_path(c->odr_out, c->host_port, c->path);
1311
1312     if (c->sru_mode == zoom_sru_get)
1313     {
1314         yaz_sru_get_encode(gdu->u.HTTP_Request, sr, c->odr_out, c->charset);
1315     }
1316     else if (c->sru_mode == zoom_sru_post)
1317     {
1318         yaz_sru_post_encode(gdu->u.HTTP_Request, sr, c->odr_out, c->charset);
1319     }
1320     else if (c->sru_mode == zoom_sru_soap)
1321     {
1322         yaz_sru_soap_encode(gdu->u.HTTP_Request, sr, c->odr_out, c->charset);
1323     }
1324     if (!z_GDU(c->odr_out, &gdu, 0, 0))
1325         return zoom_complete;
1326     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
1327         
1328     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
1329     ZOOM_connection_put_event(c, event);
1330     odr_reset(c->odr_out);
1331     return do_write(c);
1332 }
1333 #endif
1334
1335 #if YAZ_HAVE_XML2
1336 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
1337 {
1338     int i;
1339     int *start, *count;
1340     ZOOM_resultset resultset = 0;
1341     Z_SRW_PDU *sr = 0;
1342     const char *option_val = 0;
1343
1344     if (c->error)                  /* don't continue on error */
1345         return zoom_complete;
1346     assert(c->tasks);
1347     if (c->tasks->which == ZOOM_TASK_SEARCH)
1348     {
1349         resultset = c->tasks->u.search.resultset;
1350         resultset->setname = xstrdup("default");
1351         ZOOM_options_set(resultset->options, "setname", resultset->setname);
1352         start = &c->tasks->u.search.start;
1353         count = &c->tasks->u.search.count;
1354     }
1355     else if (c->tasks->which == ZOOM_TASK_RETRIEVE)
1356     {
1357         resultset = c->tasks->u.retrieve.resultset;
1358
1359         start = &c->tasks->u.retrieve.start;
1360         count = &c->tasks->u.retrieve.count;
1361         
1362         if (*start >= resultset->size)
1363             return zoom_complete;
1364         if (*start + *count > resultset->size)
1365             *count = resultset->size - *start;
1366
1367         for (i = 0; i < *count; i++)
1368         {
1369             ZOOM_record rec =
1370                 record_cache_lookup(resultset, i + *start);
1371             if (!rec)
1372                 break;
1373             else
1374             {
1375                 ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_RECV_RECORD);
1376                 ZOOM_connection_put_event(c, event);
1377             }
1378         }
1379         *start += i;
1380         *count -= i;
1381
1382         if (*count == 0)
1383             return zoom_complete;
1384     }
1385     assert(resultset->query);
1386         
1387     sr = yaz_srw_get(c->odr_out, Z_SRW_searchRetrieve_request);
1388
1389     if (resultset->query->z_query->which == Z_Query_type_104
1390         && resultset->query->z_query->u.type_104->which == Z_External_CQL)
1391     {
1392         sr->u.request->query_type = Z_SRW_query_type_cql;
1393         sr->u.request->query.cql =resultset->query->z_query->u.type_104->u.cql;
1394     }
1395     else if (resultset->query->z_query->which == Z_Query_type_1 &&
1396              resultset->query->z_query->u.type_1)
1397     {
1398         sr->u.request->query_type = Z_SRW_query_type_pqf;
1399         sr->u.request->query.pqf = resultset->query->query_string;
1400     }
1401     else
1402     {
1403         set_ZOOM_error(c, ZOOM_ERROR_UNSUPPORTED_QUERY, 0);
1404         return zoom_complete;
1405     }
1406     sr->u.request->startRecord = odr_intdup(c->odr_out, *start + 1);
1407     sr->u.request->maximumRecords = odr_intdup(
1408         c->odr_out, resultset->step>0 ? resultset->step : *count);
1409     sr->u.request->recordSchema = resultset->schema;
1410     
1411     option_val = ZOOM_resultset_option_get(resultset, "recordPacking");
1412     if (option_val)
1413         sr->u.request->recordPacking = odr_strdup(c->odr_out, option_val);
1414
1415     option_val = ZOOM_resultset_option_get(resultset, "extraArgs");
1416     if (option_val)
1417         sr->extra_args = odr_strdup(c->odr_out, option_val);
1418     return send_srw(c, sr);
1419 }
1420 #else
1421 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
1422 {
1423     return zoom_complete;
1424 }
1425 #endif
1426
1427 static zoom_ret ZOOM_connection_send_search(ZOOM_connection c)
1428 {
1429     ZOOM_resultset r;
1430     int lslb, ssub, mspn;
1431     const char *syntax;
1432     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_searchRequest);
1433     Z_SearchRequest *search_req = apdu->u.searchRequest;
1434     const char *elementSetName;
1435     const char *smallSetElementSetName;
1436     const char *mediumSetElementSetName;
1437
1438     assert(c->tasks);
1439     assert(c->tasks->which == ZOOM_TASK_SEARCH);
1440
1441     r = c->tasks->u.search.resultset;
1442
1443     yaz_log(log_details, "%p ZOOM_connection_send_search set=%p", c, r);
1444
1445     elementSetName =
1446         ZOOM_options_get(r->options, "elementSetName");
1447     smallSetElementSetName  =
1448         ZOOM_options_get(r->options, "smallSetElementSetName");
1449     mediumSetElementSetName =
1450         ZOOM_options_get(r->options, "mediumSetElementSetName");
1451
1452     if (!smallSetElementSetName)
1453         smallSetElementSetName = elementSetName;
1454
1455     if (!mediumSetElementSetName)
1456         mediumSetElementSetName = elementSetName;
1457
1458     assert(r);
1459     assert(r->query);
1460
1461     /* prepare query for the search request */
1462     search_req->query = r->query->z_query;
1463     if (!search_req->query)
1464     {
1465         set_ZOOM_error(c, ZOOM_ERROR_INVALID_QUERY, 0);
1466         return zoom_complete;
1467     }
1468
1469     search_req->databaseNames =
1470         set_DatabaseNames(c, r->options, &search_req->num_databaseNames);
1471
1472     /* get syntax (no need to provide unless piggyback is in effect) */
1473     syntax = ZOOM_options_get(r->options, "preferredRecordSyntax");
1474
1475     lslb = ZOOM_options_get_int(r->options, "largeSetLowerBound", -1);
1476     ssub = ZOOM_options_get_int(r->options, "smallSetUpperBound", -1);
1477     mspn = ZOOM_options_get_int(r->options, "mediumSetPresentNumber", -1);
1478     if (lslb != -1 && ssub != -1 && mspn != -1)
1479     {
1480         /* So're a Z39.50 expert? Let's hope you don't do sort */
1481         *search_req->largeSetLowerBound = lslb;
1482         *search_req->smallSetUpperBound = ssub;
1483         *search_req->mediumSetPresentNumber = mspn;
1484     }
1485     else if (c->tasks->u.search.start == 0 && c->tasks->u.search.count > 0
1486              && r->piggyback && !r->r_sort_spec && !r->schema)
1487     {
1488         /* Regular piggyback - do it unless we're going to do sort */
1489         *search_req->largeSetLowerBound = 2000000000;
1490         *search_req->smallSetUpperBound = 1;
1491         *search_req->mediumSetPresentNumber = 
1492             r->step>0 ? r->step : c->tasks->u.search.count;
1493     }
1494     else
1495     {
1496         /* non-piggyback. Need not provide elementsets or syntaxes .. */
1497         smallSetElementSetName = 0;
1498         mediumSetElementSetName = 0;
1499         syntax = 0;
1500     }
1501     if (smallSetElementSetName && *smallSetElementSetName)
1502     {
1503         Z_ElementSetNames *esn = (Z_ElementSetNames *)
1504             odr_malloc(c->odr_out, sizeof(*esn));
1505         
1506         esn->which = Z_ElementSetNames_generic;
1507         esn->u.generic = odr_strdup(c->odr_out, smallSetElementSetName);
1508         search_req->smallSetElementSetNames = esn;
1509     }
1510     if (mediumSetElementSetName && *mediumSetElementSetName)
1511     {
1512         Z_ElementSetNames *esn =(Z_ElementSetNames *)
1513             odr_malloc(c->odr_out, sizeof(*esn));
1514         
1515         esn->which = Z_ElementSetNames_generic;
1516         esn->u.generic = odr_strdup(c->odr_out, mediumSetElementSetName);
1517         search_req->mediumSetElementSetNames = esn;
1518     }
1519     if (syntax)
1520         search_req->preferredRecordSyntax =
1521             yaz_str_to_z3950oid(c->odr_out, CLASS_RECSYN, syntax);
1522     
1523     if (!r->setname)
1524     {
1525         if (c->support_named_resultsets)
1526         {
1527             char setname[14];
1528             int ord;
1529             /* find the lowest unused ordinal so that we re-use
1530                result sets on the server. */
1531             for (ord = 1; ; ord++)
1532             {
1533                 ZOOM_resultset rp;
1534                 sprintf(setname, "%d", ord);
1535                 for (rp = c->resultsets; rp; rp = rp->next)
1536                     if (rp->setname && !strcmp(rp->setname, setname))
1537                         break;
1538                 if (!rp)
1539                     break;
1540             }
1541             r->setname = xstrdup(setname);
1542             yaz_log(log_details, "%p ZOOM_connection_send_search: allocating "
1543                     "set %s", c, r->setname);
1544         }
1545         else
1546         {
1547             yaz_log(log_details, "%p ZOOM_connection_send_search: using "
1548                     "default set", c);
1549             r->setname = xstrdup("default");
1550         }
1551         ZOOM_options_set(r->options, "setname", r->setname);
1552     }
1553     search_req->resultSetName = odr_strdup(c->odr_out, r->setname);
1554     return send_APDU(c, apdu);
1555 }
1556
1557 static void response_default_diag(ZOOM_connection c, Z_DefaultDiagFormat *r)
1558 {
1559     int oclass;
1560     char *addinfo = 0;
1561
1562     switch (r->which)
1563     {
1564     case Z_DefaultDiagFormat_v2Addinfo:
1565         addinfo = r->u.v2Addinfo;
1566         break;
1567     case Z_DefaultDiagFormat_v3Addinfo:
1568         addinfo = r->u.v3Addinfo;
1569         break;
1570     }
1571     xfree(c->addinfo);
1572     c->addinfo = 0;
1573     set_dset_error(c, *r->condition,
1574                    yaz_z3950oid_to_str(r->diagnosticSetId, &oclass),
1575                    addinfo, 0);
1576 }
1577
1578 static void response_diag(ZOOM_connection c, Z_DiagRec *p)
1579 {
1580     if (p->which != Z_DiagRec_defaultFormat)
1581         set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
1582     else
1583         response_default_diag(c, p->u.defaultFormat);
1584 }
1585
1586 ZOOM_API(ZOOM_record)
1587     ZOOM_record_clone(ZOOM_record srec)
1588 {
1589     char *buf;
1590     int size;
1591     ODR odr_enc;
1592     ZOOM_record nrec;
1593
1594     odr_enc = odr_createmem(ODR_ENCODE);
1595     if (!z_NamePlusRecord(odr_enc, &srec->npr, 0, 0))
1596         return 0;
1597     buf = odr_getbuf(odr_enc, &size, 0);
1598     
1599     nrec = (ZOOM_record) xmalloc(sizeof(*nrec));
1600     nrec->odr = odr_createmem(ODR_DECODE);
1601     nrec->wrbuf_marc = 0;
1602     nrec->wrbuf_iconv = 0;
1603     nrec->wrbuf_opac = 0;
1604     odr_setbuf(nrec->odr, buf, size, 0);
1605     z_NamePlusRecord(nrec->odr, &nrec->npr, 0, 0);
1606     
1607     odr_destroy(odr_enc);
1608     return nrec;
1609 }
1610
1611 ZOOM_API(ZOOM_record)
1612     ZOOM_resultset_record_immediate(ZOOM_resultset s,size_t pos)
1613 {
1614     return record_cache_lookup(s, pos);
1615 }
1616
1617 ZOOM_API(ZOOM_record)
1618     ZOOM_resultset_record(ZOOM_resultset r, size_t pos)
1619 {
1620     ZOOM_record rec = ZOOM_resultset_record_immediate(r, pos);
1621
1622     if (!rec)
1623     {
1624         /*
1625          * MIKE: I think force_sync should always be zero, but I don't
1626          * want to make this change until I get the go-ahead from
1627          * Adam, in case something depends on the old synchronous
1628          * behaviour.
1629          */
1630         int force_sync = 1;
1631         if (getenv("ZOOM_RECORD_NO_FORCE_SYNC")) force_sync = 0;
1632         ZOOM_resultset_retrieve(r, force_sync, pos, 1);
1633         rec = ZOOM_resultset_record_immediate(r, pos);
1634     }
1635     return rec;
1636 }
1637
1638 ZOOM_API(void)
1639     ZOOM_record_destroy(ZOOM_record rec)
1640 {
1641     if (!rec)
1642         return;
1643     if (rec->wrbuf_marc)
1644         wrbuf_free(rec->wrbuf_marc, 1);
1645     if (rec->wrbuf_iconv)
1646         wrbuf_free(rec->wrbuf_iconv, 1);
1647     if (rec->wrbuf_opac)
1648         wrbuf_free(rec->wrbuf_opac, 1);
1649     odr_destroy(rec->odr);
1650     xfree(rec);
1651 }
1652
1653 static const char *marc_iconv_return(ZOOM_record rec, int marc_type,
1654                                      int *len,
1655                                      const char *buf, int sz,
1656                                      const char *record_charset)
1657 {
1658     char to[40];
1659     char from[40];
1660     yaz_iconv_t cd = 0;
1661     yaz_marc_t mt = yaz_marc_create();
1662
1663     *from = '\0';
1664     strcpy(to, "UTF-8");
1665     if (record_charset && *record_charset)
1666     {
1667         /* Use "from,to" or just "from" */
1668         const char *cp = strchr(record_charset, ',');
1669         int clen = strlen(record_charset);
1670         if (cp && cp[1])
1671         {
1672             strncpy( to, cp+1, sizeof(to)-1);
1673             to[sizeof(to)-1] = '\0';
1674             clen = cp - record_charset;
1675         }
1676         if (clen > sizeof(from)-1)
1677             clen = sizeof(from)-1;
1678         
1679         if (clen)
1680             strncpy(from, record_charset, clen);
1681         from[clen] = '\0';
1682     }
1683
1684     if (*from && *to)
1685     {
1686         cd = yaz_iconv_open(to, from);
1687         yaz_marc_iconv(mt, cd);
1688     }
1689
1690     yaz_marc_xml(mt, marc_type);
1691     if (!rec->wrbuf_marc)
1692         rec->wrbuf_marc = wrbuf_alloc();
1693     wrbuf_rewind(rec->wrbuf_marc);
1694     if (yaz_marc_decode_wrbuf(mt, buf, sz, rec->wrbuf_marc) > 0)
1695     {
1696         yaz_marc_destroy(mt);
1697         if (cd)
1698             yaz_iconv_close(cd);
1699         if (len)
1700             *len = wrbuf_len(rec->wrbuf_marc);
1701         return wrbuf_buf(rec->wrbuf_marc);
1702     }
1703     yaz_marc_destroy(mt);
1704     if (cd)
1705         yaz_iconv_close(cd);
1706     return 0;
1707 }
1708
1709 static const char *record_iconv_return(ZOOM_record rec, int *len,
1710                                        const char *buf, int sz,
1711                                        const char *record_charset)
1712 {
1713     char to[40];
1714     char from[40];
1715     yaz_iconv_t cd = 0;
1716
1717     *from = '\0';
1718     strcpy(to, "UTF-8");
1719     if (record_charset && *record_charset)
1720     {
1721         /* Use "from,to" or just "from" */
1722         const char *cp = strchr(record_charset, ',');
1723         int clen = strlen(record_charset);
1724         if (cp && cp[1])
1725         {
1726             strncpy( to, cp+1, sizeof(to)-1);
1727             to[sizeof(to)-1] = '\0';
1728             clen = cp - record_charset;
1729         }
1730         if (clen > sizeof(from)-1)
1731             clen = sizeof(from)-1;
1732         
1733         if (clen)
1734             strncpy(from, record_charset, clen);
1735         from[clen] = '\0';
1736     }
1737
1738     if (*from && *to && (cd = yaz_iconv_open(to, from)))
1739     {
1740         char outbuf[12];
1741         size_t inbytesleft = sz;
1742         const char *inp = buf;
1743         
1744         if (!rec->wrbuf_iconv)
1745             rec->wrbuf_iconv = wrbuf_alloc();
1746
1747         wrbuf_rewind(rec->wrbuf_iconv);
1748
1749         while (inbytesleft)
1750         {
1751             size_t outbytesleft = sizeof(outbuf);
1752             char *outp = outbuf;
1753             size_t r = yaz_iconv(cd, (char**) &inp,
1754                                  &inbytesleft, 
1755                                  &outp, &outbytesleft);
1756             if (r == (size_t) (-1))
1757             {
1758                 int e = yaz_iconv_error(cd);
1759                 if (e != YAZ_ICONV_E2BIG)
1760                     break;
1761             }
1762             wrbuf_write(rec->wrbuf_iconv, outbuf, outp - outbuf);
1763         }
1764         wrbuf_puts(rec->wrbuf_iconv, "");
1765         buf = wrbuf_buf(rec->wrbuf_iconv);
1766         sz = wrbuf_len(rec->wrbuf_iconv);
1767         yaz_iconv_close(cd);
1768     }
1769     if (len)
1770         *len = sz;
1771     return buf;
1772 }
1773
1774 ZOOM_API(const char *)
1775     ZOOM_record_get(ZOOM_record rec, const char *type_spec, int *len)
1776 {
1777     char type[40];
1778     char charset[40];
1779     char xpath[512];
1780     const char *cp;
1781     int i;
1782     Z_NamePlusRecord *npr;
1783     
1784     if (len)
1785         *len = 0; /* default return */
1786         
1787     if (!rec)
1788         return 0;
1789     npr = rec->npr;
1790     if (!npr)
1791         return 0;
1792
1793     cp = type_spec;
1794     for (i = 0; cp[i] && i < sizeof(type)-1; i++)
1795     {
1796         if (cp[i] == ';' || cp[i] == ' ')
1797             break;
1798         type[i] = cp[i];
1799     }
1800     type[i] = '\0';
1801     charset[0] = '\0';
1802     while (type_spec[i] == ';')
1803     {
1804         i++;
1805         while (type_spec[i] == ' ')
1806             i++;
1807         if (!strncmp(type_spec+i, "charset=", 8))
1808         {
1809             int j = 0;
1810             i = i + 8; /* skip charset= */
1811             for (j = 0; type_spec[i]  && j < sizeof(charset)-1; i++, j++)
1812             {
1813                 if (type_spec[i] == ';' || type_spec[i] == ' ')
1814                     break;
1815                 charset[j] = cp[i];
1816             }
1817             charset[j] = '\0';
1818         }
1819         else if (!strncmp(type_spec+i, "xpath=", 6))
1820         {
1821             int j = 0; 
1822             i = i + 6;
1823             for (j = 0; type_spec[i] && j < sizeof(xpath)-1; i++, j++)
1824                 xpath[j] = cp[i];
1825             xpath[j] = '\0';
1826         } 
1827         while (type_spec[i] == ' ')
1828             i++;
1829     }
1830     if (!strcmp(type, "database"))
1831     {
1832         if (len)
1833             *len = (npr->databaseName ? strlen(npr->databaseName) : 0);
1834         return npr->databaseName;
1835     }
1836     else if (!strcmp(type, "syntax"))
1837     {
1838         const char *desc = 0;   
1839         if (npr->which == Z_NamePlusRecord_databaseRecord)
1840         {
1841             Z_External *r = (Z_External *) npr->u.databaseRecord;
1842             oident *ent = oid_getentbyoid(r->direct_reference);
1843             if (ent)
1844                 desc = ent->desc;
1845         }
1846         if (!desc)
1847             desc = "none";
1848         if (len)
1849             *len = strlen(desc);
1850         return desc;
1851     }
1852     if (npr->which != Z_NamePlusRecord_databaseRecord)
1853         return 0;
1854
1855     /* from now on - we have a database record .. */
1856     if (!strcmp(type, "render"))
1857     {
1858         Z_External *r = (Z_External *) npr->u.databaseRecord;
1859         oident *ent = oid_getentbyoid(r->direct_reference);
1860
1861         /* render bibliographic record .. */
1862         if (r->which == Z_External_OPAC)
1863         {
1864             r = r->u.opac->bibliographicRecord;
1865             if (!r)
1866                 return 0;
1867             ent = oid_getentbyoid(r->direct_reference);
1868         }
1869         if (r->which == Z_External_sutrs)
1870             return record_iconv_return(rec, len,
1871                                        (char*) r->u.sutrs->buf,
1872                                        r->u.sutrs->len,
1873                                        charset);
1874         else if (r->which == Z_External_octet)
1875         {
1876             const char *ret_buf;
1877             switch (ent->value)
1878             {
1879             case VAL_SOIF:
1880             case VAL_HTML:
1881             case VAL_SUTRS:
1882                 break;
1883             case VAL_TEXT_XML:
1884             case VAL_APPLICATION_XML:
1885                 break;
1886             default:
1887                 ret_buf = marc_iconv_return(
1888                     rec, YAZ_MARC_LINE, len,
1889                     (const char *) r->u.octet_aligned->buf,
1890                     r->u.octet_aligned->len,
1891                     charset);
1892                 if (ret_buf)
1893                     return ret_buf;
1894             }
1895             return record_iconv_return(rec, len,
1896                                        (const char *) r->u.octet_aligned->buf,
1897                                        r->u.octet_aligned->len,
1898                                        charset);
1899         }
1900         else if (r->which == Z_External_grs1)
1901         {
1902             if (!rec->wrbuf_marc)
1903                 rec->wrbuf_marc = wrbuf_alloc();
1904             wrbuf_rewind(rec->wrbuf_marc);
1905             yaz_display_grs1(rec->wrbuf_marc, r->u.grs1, 0);
1906             return record_iconv_return(rec, len,
1907                                        wrbuf_buf(rec->wrbuf_marc),
1908                                        wrbuf_len(rec->wrbuf_marc),
1909                                        charset);
1910         }
1911         return 0;
1912     }
1913     else if (!strcmp(type, "xml"))
1914     {
1915         Z_External *r = (Z_External *) npr->u.databaseRecord;
1916         oident *ent = oid_getentbyoid(r->direct_reference);
1917
1918         /* render bibliographic record .. */
1919         if (r->which == Z_External_OPAC)
1920         {
1921             r = r->u.opac->bibliographicRecord;
1922             if (!r)
1923                 return 0;
1924             ent = oid_getentbyoid(r->direct_reference);
1925         }
1926         
1927         if (r->which == Z_External_sutrs)
1928             return record_iconv_return(rec, len,
1929                                        (const char *) r->u.sutrs->buf,
1930                                        r->u.sutrs->len,
1931                                        charset);
1932         else if (r->which == Z_External_octet)
1933         {
1934             const char *ret_buf;
1935             int marc_decode_type = YAZ_MARC_MARCXML;
1936
1937             switch (ent->value)
1938             {
1939             case VAL_SOIF:
1940             case VAL_HTML:
1941             case VAL_SUTRS:
1942                 break;
1943             case VAL_TEXT_XML:
1944             case VAL_APPLICATION_XML:
1945                 break;
1946             default:
1947                 ret_buf = marc_iconv_return(
1948                     rec, marc_decode_type, len,
1949                     (const char *) r->u.octet_aligned->buf,
1950                     r->u.octet_aligned->len,
1951                     charset);
1952                 if (ret_buf)
1953                     return ret_buf;
1954             }
1955             return record_iconv_return(rec, len,
1956                                        (const char *) r->u.octet_aligned->buf,
1957                                        r->u.octet_aligned->len,
1958                                        charset);
1959         }
1960         else if (r->which == Z_External_grs1)
1961         {
1962             if (len) *len = 5;
1963             return "GRS-1";
1964         }
1965         return 0;
1966     }
1967     else if (!strcmp(type, "raw"))
1968     {
1969         Z_External *r = (Z_External *) npr->u.databaseRecord;
1970         
1971         if (r->which == Z_External_sutrs)
1972         {
1973             if (len) *len = r->u.sutrs->len;
1974             return (const char *) r->u.sutrs->buf;
1975         }
1976         else if (r->which == Z_External_octet)
1977         {
1978             if (len) *len = r->u.octet_aligned->len;
1979             return (const char *) r->u.octet_aligned->buf;
1980         }
1981         else /* grs-1, explain, OPAC, ... */
1982         {
1983             if (len) *len = -1;
1984             return (const char *) npr->u.databaseRecord;
1985         }
1986         return 0;
1987     }
1988     else if (!strcmp (type, "ext"))
1989     {
1990         if (len) *len = -1;
1991         return (const char *) npr->u.databaseRecord;
1992     }
1993     else if (!strcmp (type, "opac"))
1994              
1995     {
1996         Z_External *r = (Z_External *) npr->u.databaseRecord;
1997         if (r->which == Z_External_OPAC)
1998         {
1999             if (!rec->wrbuf_opac)
2000                 rec->wrbuf_opac = wrbuf_alloc();
2001             wrbuf_rewind(rec->wrbuf_opac);
2002             yaz_display_OPAC(rec->wrbuf_opac, r->u.opac, 0);
2003             return record_iconv_return(rec, len,
2004                                        wrbuf_buf(rec->wrbuf_opac),
2005                                        wrbuf_len(rec->wrbuf_opac),
2006                                        charset);
2007         }
2008     }
2009     return 0;
2010 }
2011
2012 static int strcmp_null(const char *v1, const char *v2)
2013 {
2014     if (!v1 && !v2)
2015         return 0;
2016     if (!v1 || !v2)
2017         return -1;
2018     return strcmp(v1, v2);
2019 }
2020
2021 static size_t record_hash(int pos)
2022 {
2023     if (pos < 0)
2024         pos = 0;
2025     return pos % RECORD_HASH_SIZE;
2026 }
2027
2028 static void record_cache_add(ZOOM_resultset r, Z_NamePlusRecord *npr, 
2029                              int pos)
2030 {
2031     ZOOM_record_cache rc;
2032     const char *elementSetName =
2033         ZOOM_resultset_option_get(r, "elementSetName");
2034     const char *syntax = 
2035         ZOOM_resultset_option_get(r, "preferredRecordSyntax");
2036     
2037     ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_RECV_RECORD);
2038     ZOOM_connection_put_event(r->connection, event);
2039
2040     for (rc = r->record_hash[record_hash(pos)]; rc; rc = rc->next)
2041     {
2042         if (pos == rc->pos)
2043         {
2044             if (strcmp_null(r->schema, rc->schema))
2045                 continue;
2046             if (strcmp_null(elementSetName,rc->elementSetName))
2047                 continue;
2048             if (strcmp_null(syntax, rc->syntax))
2049                 continue;
2050             /* not destroying rc->npr (it's handled by nmem )*/
2051             rc->rec.npr = npr;
2052             /* keeping wrbuf_marc too */
2053             return;
2054         }
2055     }
2056     rc = (ZOOM_record_cache) odr_malloc(r->odr, sizeof(*rc));
2057     rc->rec.npr = npr; 
2058     rc->rec.odr = 0;
2059     rc->rec.wrbuf_marc = 0;
2060     rc->rec.wrbuf_iconv = 0;
2061     rc->rec.wrbuf_opac = 0;
2062     if (elementSetName)
2063         rc->elementSetName = odr_strdup(r->odr, elementSetName);
2064     else
2065         rc->elementSetName = 0;
2066
2067     if (syntax)
2068         rc->syntax = odr_strdup(r->odr, syntax);
2069     else
2070         rc->syntax = 0;
2071
2072     if (r->schema)
2073         rc->schema = odr_strdup(r->odr, r->schema);
2074     else
2075         rc->schema = 0;
2076
2077     rc->pos = pos;
2078     rc->next = r->record_hash[record_hash(pos)];
2079     r->record_hash[record_hash(pos)] = rc;
2080 }
2081
2082 static ZOOM_record record_cache_lookup(ZOOM_resultset r, int pos)
2083 {
2084     ZOOM_record_cache rc;
2085     const char *elementSetName =
2086         ZOOM_resultset_option_get(r, "elementSetName");
2087     const char *syntax = 
2088         ZOOM_resultset_option_get(r, "preferredRecordSyntax");
2089     
2090     for (rc = r->record_hash[record_hash(pos)]; rc; rc = rc->next)
2091     {
2092         if (pos == rc->pos)
2093         {
2094             if (strcmp_null(r->schema, rc->schema))
2095                 continue;
2096             if (strcmp_null(elementSetName,rc->elementSetName))
2097                 continue;
2098             if (strcmp_null(syntax, rc->syntax))
2099                 continue;
2100             return &rc->rec;
2101         }
2102     }
2103     return 0;
2104 }
2105                                              
2106 static void handle_records(ZOOM_connection c, Z_Records *sr,
2107                            int present_phase)
2108 {
2109     ZOOM_resultset resultset;
2110     int *start, *count;
2111
2112     if (!c->tasks)
2113         return ;
2114     switch (c->tasks->which)
2115     {
2116     case ZOOM_TASK_SEARCH:
2117         resultset = c->tasks->u.search.resultset;
2118         start = &c->tasks->u.search.start;
2119         count = &c->tasks->u.search.count;
2120         break;
2121     case ZOOM_TASK_RETRIEVE:
2122         resultset = c->tasks->u.retrieve.resultset;        
2123         start = &c->tasks->u.retrieve.start;
2124         count = &c->tasks->u.retrieve.count;
2125         break;
2126     default:
2127         return;
2128     }
2129     if (sr && sr->which == Z_Records_NSD)
2130         response_default_diag(c, sr->u.nonSurrogateDiagnostic);
2131     else if (sr && sr->which == Z_Records_multipleNSD)
2132     {
2133         if (sr->u.multipleNonSurDiagnostics->num_diagRecs >= 1)
2134             response_diag(c, sr->u.multipleNonSurDiagnostics->diagRecs[0]);
2135         else
2136             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
2137     }
2138     else 
2139     {
2140         if (*count + *start > resultset->size)
2141             *count = resultset->size - *start;
2142         if (*count < 0)
2143             *count = 0;
2144         if (sr && sr->which == Z_Records_DBOSD)
2145         {
2146             int i;
2147             NMEM nmem = odr_extract_mem(c->odr_in);
2148             Z_NamePlusRecordList *p =
2149                 sr->u.databaseOrSurDiagnostics;
2150             for (i = 0; i<p->num_records; i++)
2151             {
2152                 record_cache_add(resultset, p->records[i], i + *start);
2153             }
2154             *count -= i;
2155             *start += i;
2156             yaz_log(log_details, 
2157                     "handle_records resultset=%p start=%d count=%d",
2158                     resultset, *start, *count);
2159
2160             /* transfer our response to search_nmem .. we need it later */
2161             nmem_transfer(resultset->odr->mem, nmem);
2162             nmem_destroy(nmem);
2163             if (present_phase && p->num_records == 0)
2164             {
2165                 /* present response and we didn't get any records! */
2166                 Z_NamePlusRecord *myrec = 
2167                     zget_surrogateDiagRec(resultset->odr, 0, 14, 0);
2168                 record_cache_add(resultset, myrec, *start);
2169             }
2170         }
2171         else if (present_phase)
2172         {
2173             /* present response and we didn't get any records! */
2174             Z_NamePlusRecord *myrec = 
2175                 zget_surrogateDiagRec(resultset->odr, 0, 14, 0);
2176             record_cache_add(resultset, myrec, *start);
2177         }
2178     }
2179 }
2180
2181 static void handle_present_response(ZOOM_connection c, Z_PresentResponse *pr)
2182 {
2183     handle_records(c, pr->records, 1);
2184 }
2185
2186 static void handle_queryExpressionTerm(ZOOM_options opt, const char *name,
2187                                        Z_Term *term)
2188 {
2189     switch (term->which)
2190     {
2191     case Z_Term_general:
2192         ZOOM_options_setl(opt, name,
2193                           (const char *)(term->u.general->buf), 
2194                           term->u.general->len);
2195         break;
2196     case Z_Term_characterString:
2197         ZOOM_options_set(opt, name, term->u.characterString);
2198         break;
2199     case Z_Term_numeric:
2200         ZOOM_options_set_int(opt, name, *term->u.numeric);
2201         break;
2202     }
2203 }
2204
2205 static void handle_queryExpression(ZOOM_options opt, const char *name,
2206                                    Z_QueryExpression *exp)
2207 {
2208     char opt_name[80];
2209     
2210     switch (exp->which)
2211     {
2212     case Z_QueryExpression_term:
2213         if (exp->u.term && exp->u.term->queryTerm)
2214         {
2215             sprintf(opt_name, "%s.term", name);
2216             handle_queryExpressionTerm(opt, opt_name, exp->u.term->queryTerm);
2217         }
2218         break;
2219     case Z_QueryExpression_query:
2220         break;
2221     }
2222 }
2223
2224 static void handle_searchResult(ZOOM_connection c, ZOOM_resultset resultset,
2225                                 Z_OtherInformation *o)
2226 {
2227     int i;
2228     for (i = 0; o && i < o->num_elements; i++)
2229     {
2230         if (o->list[i]->which == Z_OtherInfo_externallyDefinedInfo)
2231         {
2232             Z_External *ext = o->list[i]->information.externallyDefinedInfo;
2233             
2234             if (ext->which == Z_External_searchResult1)
2235             {
2236                 int j;
2237                 Z_SearchInfoReport *sr = ext->u.searchResult1;
2238                 
2239                 if (sr->num)
2240                     ZOOM_options_set_int(
2241                         resultset->options, "searchresult.size", sr->num);
2242
2243                 for (j = 0; j < sr->num; j++)
2244                 {
2245                     Z_SearchInfoReport_s *ent =
2246                         ext->u.searchResult1->elements[j];
2247                     char pref[80];
2248                     
2249                     sprintf(pref, "searchresult.%d", j);
2250
2251                     if (ent->subqueryId)
2252                     {
2253                         char opt_name[80];
2254                         sprintf(opt_name, "%s.id", pref);
2255                         ZOOM_options_set(resultset->options, opt_name,
2256                                          ent->subqueryId);
2257                     }
2258                     if (ent->subqueryExpression)
2259                     {
2260                         char opt_name[80];
2261                         sprintf(opt_name, "%s.subquery", pref);
2262                         handle_queryExpression(resultset->options, opt_name,
2263                                                ent->subqueryExpression);
2264                     }
2265                     if (ent->subqueryInterpretation)
2266                     {
2267                         char opt_name[80];
2268                         sprintf(opt_name, "%s.interpretation", pref);
2269                         handle_queryExpression(resultset->options, opt_name,
2270                                                ent->subqueryInterpretation);
2271                     }
2272                     if (ent->subqueryRecommendation)
2273                     {
2274                         char opt_name[80];
2275                         sprintf(opt_name, "%s.recommendation", pref);
2276                         handle_queryExpression(resultset->options, opt_name,
2277                                                ent->subqueryRecommendation);
2278                     }
2279                     if (ent->subqueryCount)
2280                     {
2281                         char opt_name[80];
2282                         sprintf(opt_name, "%s.count", pref);
2283                         ZOOM_options_set_int(resultset->options, opt_name,
2284                                              *ent->subqueryCount);
2285                     }                                             
2286                 }
2287             }
2288         }
2289     }
2290 }
2291
2292 static void handle_search_response(ZOOM_connection c, Z_SearchResponse *sr)
2293 {
2294     ZOOM_resultset resultset;
2295     ZOOM_Event event;
2296
2297     if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
2298         return ;
2299
2300     event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
2301     ZOOM_connection_put_event(c, event);
2302
2303     resultset = c->tasks->u.search.resultset;
2304
2305     handle_searchResult(c, resultset, sr->additionalSearchInfo);
2306
2307     resultset->size = *sr->resultCount;
2308     handle_records(c, sr->records, 0);
2309 }
2310
2311 static void sort_response(ZOOM_connection c, Z_SortResponse *res)
2312 {
2313     if (res->diagnostics && res->num_diagnostics > 0)
2314         response_diag(c, res->diagnostics[0]);
2315 }
2316
2317 static int scan_response(ZOOM_connection c, Z_ScanResponse *res)
2318 {
2319     NMEM nmem = odr_extract_mem(c->odr_in);
2320     ZOOM_scanset scan;
2321
2322     if (!c->tasks || c->tasks->which != ZOOM_TASK_SCAN)
2323         return 0;
2324     scan = c->tasks->u.scan.scan;
2325
2326     if (res->entries && res->entries->nonsurrogateDiagnostics)
2327         response_diag(c, res->entries->nonsurrogateDiagnostics[0]);
2328     scan->scan_response = res;
2329     nmem_transfer(scan->odr->mem, nmem);
2330     if (res->stepSize)
2331         ZOOM_options_set_int(scan->options, "stepSize", *res->stepSize);
2332     if (res->positionOfTerm)
2333         ZOOM_options_set_int(scan->options, "position", *res->positionOfTerm);
2334     if (res->scanStatus)
2335         ZOOM_options_set_int(scan->options, "scanStatus", *res->scanStatus);
2336     if (res->numberOfEntriesReturned)
2337         ZOOM_options_set_int(scan->options, "number",
2338                              *res->numberOfEntriesReturned);
2339     nmem_destroy(nmem);
2340     return 1;
2341 }
2342
2343 static zoom_ret send_sort(ZOOM_connection c,
2344                           ZOOM_resultset resultset)
2345 {
2346     if (c->error)
2347         resultset->r_sort_spec = 0;
2348     if (resultset->r_sort_spec)
2349     {
2350         Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_sortRequest);
2351         Z_SortRequest *req = apdu->u.sortRequest;
2352         
2353         req->num_inputResultSetNames = 1;
2354         req->inputResultSetNames = (Z_InternationalString **)
2355             odr_malloc(c->odr_out, sizeof(*req->inputResultSetNames));
2356         req->inputResultSetNames[0] =
2357             odr_strdup(c->odr_out, resultset->setname);
2358         req->sortedResultSetName = odr_strdup(c->odr_out, resultset->setname);
2359         req->sortSequence = resultset->r_sort_spec;
2360         resultset->r_sort_spec = 0;
2361         return send_APDU(c, apdu);
2362     }
2363     return zoom_complete;
2364 }
2365
2366 static zoom_ret send_present(ZOOM_connection c)
2367 {
2368     Z_APDU *apdu = 0;
2369     Z_PresentRequest *req = 0;
2370     int i = 0;
2371     const char *syntax = 0;
2372     const char *elementSetName = 0;
2373     ZOOM_resultset  resultset;
2374     int *start, *count;
2375
2376     if (!c->tasks)
2377     {
2378         yaz_log(log_details, "%p send_present no tasks", c);
2379         return zoom_complete;
2380     }
2381     
2382     switch (c->tasks->which)
2383     {
2384     case ZOOM_TASK_SEARCH:
2385         resultset = c->tasks->u.search.resultset;
2386         start = &c->tasks->u.search.start;
2387         count = &c->tasks->u.search.count;
2388         break;
2389     case ZOOM_TASK_RETRIEVE:
2390         resultset = c->tasks->u.retrieve.resultset;
2391         start = &c->tasks->u.retrieve.start;
2392         count = &c->tasks->u.retrieve.count;
2393
2394         if (*start >= resultset->size)
2395         {
2396             yaz_log(log_details, "%p send_present start=%d >= size=%d",
2397                     c, *start, resultset->size);
2398             return zoom_complete;
2399         }
2400         if (*start + *count > resultset->size)
2401             *count = resultset->size - *start;
2402         break;
2403     default:
2404         return zoom_complete;
2405     }
2406     yaz_log(log_details, "%p send_present start=%d count=%d",
2407             c, *start, *count);
2408
2409     syntax = ZOOM_resultset_option_get(resultset, "preferredRecordSyntax");
2410     elementSetName = ZOOM_resultset_option_get(resultset, "elementSetName");
2411
2412     if (c->error)                  /* don't continue on error */
2413         return zoom_complete;
2414     if (*start < 0)
2415         return zoom_complete;
2416     yaz_log(log_details, "send_present resultset=%p start=%d count=%d",
2417             resultset, *start, *count);
2418
2419     for (i = 0; i < *count; i++)
2420     {
2421         ZOOM_record rec =
2422             record_cache_lookup(resultset, i + *start);
2423         if (!rec)
2424             break;
2425         else
2426         {
2427             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_RECV_RECORD);
2428             ZOOM_connection_put_event(c, event);
2429         }
2430     }
2431     *start += i;
2432     *count -= i;
2433
2434     if (*count == 0)
2435     {
2436         yaz_log(log_details, "%p send_present skip=%d no more to fetch", c, i);
2437         return zoom_complete;
2438     }
2439
2440     apdu = zget_APDU(c->odr_out, Z_APDU_presentRequest);
2441     req = apdu->u.presentRequest;
2442
2443     if (i)
2444         yaz_log(log_details, "%p send_present skip=%d", c, i);
2445
2446     *req->resultSetStartPoint = *start + 1;
2447     *req->numberOfRecordsRequested = resultset->step>0 ?
2448         resultset->step : *count;
2449     if (*req->numberOfRecordsRequested + *start > resultset->size)
2450         *req->numberOfRecordsRequested = resultset->size - *start;
2451     assert(*req->numberOfRecordsRequested > 0);
2452
2453     if (syntax && *syntax)
2454         req->preferredRecordSyntax =
2455             yaz_str_to_z3950oid(c->odr_out, CLASS_RECSYN, syntax);
2456
2457     if (resultset->schema && *resultset->schema)
2458     {
2459         Z_RecordComposition *compo = (Z_RecordComposition *)
2460             odr_malloc(c->odr_out, sizeof(*compo));
2461
2462         req->recordComposition = compo;
2463         compo->which = Z_RecordComp_complex;
2464         compo->u.complex = (Z_CompSpec *)
2465             odr_malloc(c->odr_out, sizeof(*compo->u.complex));
2466         compo->u.complex->selectAlternativeSyntax = (bool_t *) 
2467             odr_malloc(c->odr_out, sizeof(bool_t));
2468         *compo->u.complex->selectAlternativeSyntax = 0;
2469
2470         compo->u.complex->generic = (Z_Specification *)
2471             odr_malloc(c->odr_out, sizeof(*compo->u.complex->generic));
2472
2473         compo->u.complex->generic->which = Z_Schema_oid;
2474         compo->u.complex->generic->schema.oid = (Odr_oid *)
2475             yaz_str_to_z3950oid (c->odr_out, CLASS_SCHEMA, resultset->schema);
2476
2477         if (!compo->u.complex->generic->schema.oid)
2478         {
2479             /* OID wasn't a schema! Try record syntax instead. */
2480
2481             compo->u.complex->generic->schema.oid = (Odr_oid *)
2482                 yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, resultset->schema);
2483         }
2484         if (elementSetName && *elementSetName)
2485         {
2486             compo->u.complex->generic->elementSpec = (Z_ElementSpec *)
2487                 odr_malloc(c->odr_out, sizeof(Z_ElementSpec));
2488             compo->u.complex->generic->elementSpec->which =
2489                 Z_ElementSpec_elementSetName;
2490             compo->u.complex->generic->elementSpec->u.elementSetName =
2491                 odr_strdup(c->odr_out, elementSetName);
2492         }
2493         else
2494             compo->u.complex->generic->elementSpec = 0;
2495         compo->u.complex->num_dbSpecific = 0;
2496         compo->u.complex->dbSpecific = 0;
2497         compo->u.complex->num_recordSyntax = 0;
2498         compo->u.complex->recordSyntax = 0;
2499     }
2500     else if (elementSetName && *elementSetName)
2501     {
2502         Z_ElementSetNames *esn = (Z_ElementSetNames *)
2503             odr_malloc(c->odr_out, sizeof(*esn));
2504         Z_RecordComposition *compo = (Z_RecordComposition *)
2505             odr_malloc(c->odr_out, sizeof(*compo));
2506         
2507         esn->which = Z_ElementSetNames_generic;
2508         esn->u.generic = odr_strdup(c->odr_out, elementSetName);
2509         compo->which = Z_RecordComp_simple;
2510         compo->u.simple = esn;
2511         req->recordComposition = compo;
2512     }
2513     req->resultSetId = odr_strdup(c->odr_out, resultset->setname);
2514     return send_APDU(c, apdu);
2515 }
2516
2517 ZOOM_API(ZOOM_scanset)
2518     ZOOM_connection_scan(ZOOM_connection c, const char *start)
2519 {
2520     ZOOM_scanset s;
2521     ZOOM_query q = ZOOM_query_create();
2522
2523     ZOOM_query_prefix(q, start);
2524
2525     s = ZOOM_connection_scan1(c, q);
2526     ZOOM_query_destroy(q);
2527     return s;
2528
2529 }
2530
2531 ZOOM_API(ZOOM_scanset)
2532     ZOOM_connection_scan1(ZOOM_connection c, ZOOM_query q)
2533 {
2534     char *start;
2535     char *freeme = 0;
2536     ZOOM_scanset scan = 0;
2537
2538     /*
2539      * We need to check the query-type, so we can recognise CQL and
2540      * CCL and compile them into a form that we can use here.  The
2541      * ZOOM_query structure has no explicit `type' member, but
2542      * inspection of the ZOOM_query_prefix() and ZOOM_query_cql()
2543      * functions shows how the structure is set up in each case.
2544      */
2545     if (!q->z_query)
2546         return 0;
2547     else if (q->z_query->which == Z_Query_type_1) 
2548     {
2549         yaz_log(log_api, "%p ZOOM_connection_scan1 q=%p PQF '%s'",
2550                 c, q, q->query_string);
2551         start = q->query_string;
2552     } 
2553     else if (q->z_query->which == Z_Query_type_104)
2554     {
2555         yaz_log(log_api, "%p ZOOM_connection_scan1 q=%p CQL '%s'",
2556                 c, q, q->query_string);
2557         start = freeme = cql2pqf(c, q->query_string);
2558         if (start == 0)
2559             return 0;
2560     } 
2561     else
2562     {
2563         yaz_log(YLOG_FATAL, "%p ZOOM_connection_scan1 q=%p unknown type '%s'",
2564                 c, q, q->query_string);
2565         abort();
2566     }
2567
2568     scan = (ZOOM_scanset) xmalloc(sizeof(*scan));
2569     scan->connection = c;
2570     scan->odr = odr_createmem(ODR_DECODE);
2571     scan->options = ZOOM_options_create_with_parent(c->options);
2572     scan->refcount = 1;
2573     scan->scan_response = 0;
2574     scan->termListAndStartPoint =
2575         p_query_scan(scan->odr, PROTO_Z3950, &scan->attributeSet, start);
2576     xfree(freeme);
2577     if (scan->termListAndStartPoint != 0)
2578     {
2579         ZOOM_task task = ZOOM_connection_add_task(c, ZOOM_TASK_SCAN);
2580         task->u.scan.scan = scan;
2581         
2582         (scan->refcount)++;
2583         if (!c->async)
2584         {
2585             while (ZOOM_event(1, &c))
2586                 ;
2587         }
2588     }
2589     return scan;
2590 }
2591
2592 ZOOM_API(void)
2593     ZOOM_scanset_destroy(ZOOM_scanset scan)
2594 {
2595     if (!scan)
2596         return;
2597     (scan->refcount)--;
2598     if (scan->refcount == 0)
2599     {
2600         odr_destroy(scan->odr);
2601         
2602         ZOOM_options_destroy(scan->options);
2603         xfree(scan);
2604     }
2605 }
2606
2607 static zoom_ret send_package(ZOOM_connection c)
2608 {
2609     ZOOM_Event event;
2610
2611     yaz_log(log_details, "%p send_package", c);
2612     if (!c->tasks)
2613         return zoom_complete;
2614     assert (c->tasks->which == ZOOM_TASK_PACKAGE);
2615     
2616     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
2617     ZOOM_connection_put_event(c, event);
2618     
2619     c->buf_out = c->tasks->u.package->buf_out;
2620     c->len_out = c->tasks->u.package->len_out;
2621
2622     return do_write(c);
2623 }
2624
2625 static zoom_ret send_scan(ZOOM_connection c)
2626 {
2627     ZOOM_scanset scan;
2628     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_scanRequest);
2629     Z_ScanRequest *req = apdu->u.scanRequest;
2630
2631     yaz_log(log_details, "%p send_scan", c);
2632     if (!c->tasks)
2633         return zoom_complete;
2634     assert (c->tasks->which == ZOOM_TASK_SCAN);
2635     scan = c->tasks->u.scan.scan;
2636
2637     req->termListAndStartPoint = scan->termListAndStartPoint;
2638     req->attributeSet = scan->attributeSet;
2639
2640     *req->numberOfTermsRequested =
2641         ZOOM_options_get_int(scan->options, "number", 10);
2642
2643     req->preferredPositionInResponse =
2644         odr_intdup(c->odr_out,
2645                    ZOOM_options_get_int(scan->options, "position", 1));
2646
2647     req->stepSize =
2648         odr_intdup(c->odr_out,
2649                    ZOOM_options_get_int(scan->options, "stepSize", 0));
2650     
2651     req->databaseNames = set_DatabaseNames(c, scan->options, 
2652                                            &req->num_databaseNames);
2653
2654     return send_APDU(c, apdu);
2655 }
2656
2657 ZOOM_API(size_t)
2658     ZOOM_scanset_size(ZOOM_scanset scan)
2659 {
2660     if (!scan || !scan->scan_response || !scan->scan_response->entries)
2661         return 0;
2662     return scan->scan_response->entries->num_entries;
2663 }
2664
2665 ZOOM_API(const char *)
2666     ZOOM_scanset_term(ZOOM_scanset scan, size_t pos,
2667                       int *occ, int *len)
2668 {
2669     const char *term = 0;
2670     size_t noent = ZOOM_scanset_size(scan);
2671     Z_ScanResponse *res = scan->scan_response;
2672     
2673     *len = 0;
2674     *occ = 0;
2675     if (pos >= noent)
2676         return 0;
2677     if (res->entries->entries[pos]->which == Z_Entry_termInfo)
2678     {
2679         Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
2680         
2681         if (t->term->which == Z_Term_general)
2682         {
2683             term = (const char *) t->term->u.general->buf;
2684             *len = t->term->u.general->len;
2685         }
2686         *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
2687     }
2688     return term;
2689 }
2690
2691 ZOOM_API(const char *)
2692     ZOOM_scanset_display_term(ZOOM_scanset scan, size_t pos,
2693                               int *occ, int *len)
2694 {
2695     const char *term = 0;
2696     size_t noent = ZOOM_scanset_size(scan);
2697     Z_ScanResponse *res = scan->scan_response;
2698     
2699     *len = 0;
2700     *occ = 0;
2701     if (pos >= noent)
2702         return 0;
2703     if (res->entries->entries[pos]->which == Z_Entry_termInfo)
2704     {
2705         Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
2706
2707         if (t->displayTerm)
2708         {
2709             term = t->displayTerm;
2710             *len = strlen(term);
2711         }
2712         else if (t->term->which == Z_Term_general)
2713         {
2714             term = (const char *) t->term->u.general->buf;
2715             *len = t->term->u.general->len;
2716         }
2717         *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
2718     }
2719     return term;
2720 }
2721
2722 ZOOM_API(const char *)
2723     ZOOM_scanset_option_get(ZOOM_scanset scan, const char *key)
2724 {
2725     return ZOOM_options_get(scan->options, key);
2726 }
2727
2728 ZOOM_API(void)
2729     ZOOM_scanset_option_set(ZOOM_scanset scan, const char *key,
2730                             const char *val)
2731 {
2732     ZOOM_options_set(scan->options, key, val);
2733 }
2734
2735 static Z_APDU *create_es_package(ZOOM_package p, int type)
2736 {
2737     const char *str;
2738     Z_APDU *apdu = zget_APDU(p->odr_out, Z_APDU_extendedServicesRequest);
2739     Z_ExtendedServicesRequest *req = apdu->u.extendedServicesRequest;
2740     
2741     *req->function = Z_ExtendedServicesRequest_create;
2742     
2743     str = ZOOM_options_get(p->options, "package-name");
2744     if (str && *str)
2745         req->packageName = nmem_strdup(p->odr_out->mem, str);
2746     
2747     str = ZOOM_options_get(p->options, "user-id");
2748     if (str)
2749         req->userId = nmem_strdup(p->odr_out->mem, str);
2750     
2751     req->packageType = yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
2752                                               type);
2753
2754     str = ZOOM_options_get(p->options, "function");
2755     if (str)
2756     {
2757         if (!strcmp (str, "create"))
2758             *req->function = 1;
2759         if (!strcmp (str, "delete"))
2760             *req->function = 2;
2761         if (!strcmp (str, "modify"))
2762             *req->function = 3;
2763     }
2764     return apdu;
2765 }
2766
2767 static const char *ill_array_lookup(void *clientData, const char *idx)
2768 {
2769     ZOOM_package p = (ZOOM_package) clientData;
2770     return ZOOM_options_get(p->options, idx+4);
2771 }
2772
2773 static Z_External *encode_ill_request(ZOOM_package p)
2774 {
2775     ODR out = p->odr_out;
2776     ILL_Request *req;
2777     Z_External *r = 0;
2778     struct ill_get_ctl ctl;
2779         
2780     ctl.odr = p->odr_out;
2781     ctl.clientData = p;
2782     ctl.f = ill_array_lookup;
2783         
2784     req = ill_get_ILLRequest(&ctl, "ill", 0);
2785         
2786     if (!ill_Request(out, &req, 0, 0))
2787     {
2788         int ill_request_size;
2789         char *ill_request_buf = odr_getbuf(out, &ill_request_size, 0);
2790         if (ill_request_buf)
2791             odr_setbuf(out, ill_request_buf, ill_request_size, 1);
2792         return 0;
2793     }
2794     else
2795     {
2796         oident oid;
2797         int illRequest_size = 0;
2798         char *illRequest_buf = odr_getbuf(out, &illRequest_size, 0);
2799                 
2800         oid.proto = PROTO_GENERAL;
2801         oid.oclass = CLASS_GENERAL;
2802         oid.value = VAL_ISO_ILL_1;
2803                 
2804         r = (Z_External *) odr_malloc(out, sizeof(*r));
2805         r->direct_reference = odr_oiddup(out,oid_getoidbyent(&oid)); 
2806         r->indirect_reference = 0;
2807         r->descriptor = 0;
2808         r->which = Z_External_single;
2809                 
2810         r->u.single_ASN1_type =
2811             odr_create_Odr_oct(out,
2812                                (unsigned char *)illRequest_buf,
2813                                illRequest_size);
2814     }
2815     return r;
2816 }
2817
2818 static Z_ItemOrder *encode_item_order(ZOOM_package p)
2819 {
2820     Z_ItemOrder *req = (Z_ItemOrder *) odr_malloc(p->odr_out, sizeof(*req));
2821     const char *str;
2822     
2823     req->which = Z_IOItemOrder_esRequest;
2824     req->u.esRequest = (Z_IORequest *) 
2825         odr_malloc(p->odr_out,sizeof(Z_IORequest));
2826
2827     /* to keep part ... */
2828     req->u.esRequest->toKeep = (Z_IOOriginPartToKeep *)
2829         odr_malloc(p->odr_out,sizeof(Z_IOOriginPartToKeep));
2830     req->u.esRequest->toKeep->supplDescription = 0;
2831     req->u.esRequest->toKeep->contact = (Z_IOContact *)
2832         odr_malloc(p->odr_out, sizeof(*req->u.esRequest->toKeep->contact));
2833         
2834     str = ZOOM_options_get(p->options, "contact-name");
2835     req->u.esRequest->toKeep->contact->name = str ?
2836         nmem_strdup(p->odr_out->mem, str) : 0;
2837         
2838     str = ZOOM_options_get(p->options, "contact-phone");
2839     req->u.esRequest->toKeep->contact->phone = str ?
2840         nmem_strdup(p->odr_out->mem, str) : 0;
2841         
2842     str = ZOOM_options_get(p->options, "contact-email");
2843     req->u.esRequest->toKeep->contact->email = str ?
2844         nmem_strdup(p->odr_out->mem, str) : 0;
2845         
2846     req->u.esRequest->toKeep->addlBilling = 0;
2847         
2848     /* not to keep part ... */
2849     req->u.esRequest->notToKeep = (Z_IOOriginPartNotToKeep *)
2850         odr_malloc(p->odr_out,sizeof(Z_IOOriginPartNotToKeep));
2851         
2852     str = ZOOM_options_get(p->options, "itemorder-setname");
2853     if (!str)
2854         str = "default";
2855
2856     if (!*str) 
2857         req->u.esRequest->notToKeep->resultSetItem = 0;
2858     else
2859     {
2860         req->u.esRequest->notToKeep->resultSetItem = (Z_IOResultSetItem *)
2861             odr_malloc(p->odr_out, sizeof(Z_IOResultSetItem));
2862
2863         req->u.esRequest->notToKeep->resultSetItem->resultSetId =
2864             nmem_strdup(p->odr_out->mem, str);
2865         req->u.esRequest->notToKeep->resultSetItem->item =
2866             (int *) odr_malloc(p->odr_out, sizeof(int));
2867         
2868         str = ZOOM_options_get(p->options, "itemorder-item");
2869         *req->u.esRequest->notToKeep->resultSetItem->item =
2870             (str ? atoi(str) : 1);
2871     }
2872     req->u.esRequest->notToKeep->itemRequest = encode_ill_request(p);
2873     
2874     return req;
2875 }
2876
2877 Z_APDU *create_admin_package(ZOOM_package p, int type, 
2878                              Z_ESAdminOriginPartToKeep **toKeepP,
2879                              Z_ESAdminOriginPartNotToKeep **notToKeepP)
2880 {
2881     Z_APDU *apdu = create_es_package(p, VAL_ADMINSERVICE);
2882     if (apdu)
2883     {
2884         Z_ESAdminOriginPartToKeep  *toKeep;
2885         Z_ESAdminOriginPartNotToKeep  *notToKeep;
2886         Z_External *r = (Z_External *) odr_malloc(p->odr_out, sizeof(*r));
2887         const char *first_db = "Default";
2888         int num_db;
2889         char **db = set_DatabaseNames(p->connection, p->options, &num_db);
2890         if (num_db > 0)
2891             first_db = db[0];
2892             
2893         r->direct_reference =
2894             yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
2895                                    VAL_ADMINSERVICE);
2896         r->descriptor = 0;
2897         r->indirect_reference = 0;
2898         r->which = Z_External_ESAdmin;
2899         
2900         r->u.adminService = (Z_Admin *)
2901             odr_malloc(p->odr_out, sizeof(*r->u.adminService));
2902         r->u.adminService->which = Z_Admin_esRequest;
2903         r->u.adminService->u.esRequest = (Z_AdminEsRequest *)
2904             odr_malloc(p->odr_out, sizeof(*r->u.adminService->u.esRequest));
2905         
2906         toKeep = r->u.adminService->u.esRequest->toKeep =
2907             (Z_ESAdminOriginPartToKeep *) 
2908             odr_malloc(p->odr_out, sizeof(*r->u.adminService->u.esRequest->toKeep));
2909         toKeep->which = type;
2910         toKeep->databaseName = odr_strdup(p->odr_out, first_db);
2911         toKeep->u.create = odr_nullval();
2912         apdu->u.extendedServicesRequest->taskSpecificParameters = r;
2913         
2914         r->u.adminService->u.esRequest->notToKeep = notToKeep =
2915             (Z_ESAdminOriginPartNotToKeep *)
2916             odr_malloc(p->odr_out,
2917                        sizeof(*r->u.adminService->u.esRequest->notToKeep));
2918         notToKeep->which = Z_ESAdminOriginPartNotToKeep_recordsWillFollow;
2919         notToKeep->u.recordsWillFollow = odr_nullval();
2920         if (toKeepP)
2921             *toKeepP = toKeep;
2922         if (notToKeepP)
2923             *notToKeepP = notToKeep;
2924     }
2925     return apdu;
2926 }
2927
2928 static Z_APDU *create_xmlupdate_package(ZOOM_package p)
2929 {
2930     Z_APDU *apdu = create_es_package(p, VAL_XMLES);
2931     Z_ExtendedServicesRequest *req = apdu->u.extendedServicesRequest;
2932     Z_External *ext = (Z_External *) odr_malloc(p->odr_out, sizeof(*ext));
2933     const char *doc = ZOOM_options_get(p->options, "doc");
2934
2935     if (!doc)
2936         doc = "";
2937
2938     req->taskSpecificParameters = ext;
2939     ext->direct_reference = req->packageType;
2940     ext->descriptor = 0;
2941     ext->indirect_reference = 0;
2942     
2943     ext->which = Z_External_octet;
2944     ext->u.single_ASN1_type =
2945         odr_create_Odr_oct(p->odr_out, (const unsigned char *) doc,
2946                            strlen(doc));
2947     return apdu;
2948 }
2949
2950 static Z_APDU *create_update_package(ZOOM_package p)
2951 {
2952     Z_APDU *apdu = 0;
2953     const char *first_db = "Default";
2954     int num_db;
2955     char **db = set_DatabaseNames(p->connection, p->options, &num_db);
2956     const char *action = ZOOM_options_get(p->options, "action");
2957     const char *recordIdOpaque = ZOOM_options_get(p->options, "recordIdOpaque");
2958     const char *recordIdNumber = ZOOM_options_get(p->options, "recordIdNumber");
2959     const char *record_buf = ZOOM_options_get(p->options, "record");
2960     const char *syntax_str = ZOOM_options_get(p->options, "syntax");
2961     int syntax_oid = VAL_NONE;
2962     int action_no = -1;
2963     
2964     if (syntax_str)
2965         syntax_oid = oid_getvalbyname(syntax_str);
2966     if (!record_buf)
2967     {
2968         record_buf = "void";
2969         syntax_oid = VAL_SUTRS;
2970     }
2971     if (syntax_oid == VAL_NONE)
2972         syntax_oid = VAL_TEXT_XML;
2973     
2974     if (num_db > 0)
2975         first_db = db[0];
2976     
2977     if (!action)
2978         action = "specialUpdate";
2979     
2980     if (!strcmp(action, "recordInsert"))
2981         action_no = Z_IUOriginPartToKeep_recordInsert;
2982     else if (!strcmp(action, "recordReplace"))
2983         action_no = Z_IUOriginPartToKeep_recordReplace;
2984     else if (!strcmp(action, "recordDelete"))
2985         action_no = Z_IUOriginPartToKeep_recordDelete;
2986     else if (!strcmp(action, "elementUpdate"))
2987         action_no = Z_IUOriginPartToKeep_elementUpdate;
2988     else if (!strcmp(action, "specialUpdate"))
2989         action_no = Z_IUOriginPartToKeep_specialUpdate;
2990     else
2991         return 0;
2992
2993     apdu = create_es_package(p, VAL_DBUPDATE);
2994     if (apdu)
2995     {
2996         Z_IUOriginPartToKeep *toKeep;
2997         Z_IUSuppliedRecords *notToKeep;
2998         Z_External *r = (Z_External *)
2999             odr_malloc(p->odr_out, sizeof(*r));
3000         
3001         apdu->u.extendedServicesRequest->taskSpecificParameters = r;
3002         
3003         r->direct_reference =
3004             yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
3005                                    VAL_DBUPDATE);
3006         r->descriptor = 0;
3007         r->which = Z_External_update;
3008         r->indirect_reference = 0;
3009         r->u.update = (Z_IUUpdate *)
3010             odr_malloc(p->odr_out, sizeof(*r->u.update));
3011         
3012         r->u.update->which = Z_IUUpdate_esRequest;
3013         r->u.update->u.esRequest = (Z_IUUpdateEsRequest *)
3014             odr_malloc(p->odr_out, sizeof(*r->u.update->u.esRequest));
3015         toKeep = r->u.update->u.esRequest->toKeep = 
3016             (Z_IUOriginPartToKeep *)
3017             odr_malloc(p->odr_out, sizeof(*toKeep));
3018         
3019         toKeep->databaseName = odr_strdup(p->odr_out, first_db);
3020         toKeep->schema = 0;
3021         toKeep->elementSetName = 0;
3022         toKeep->actionQualifier = 0;
3023         toKeep->action = odr_intdup(p->odr_out, action_no);
3024         
3025         notToKeep = r->u.update->u.esRequest->notToKeep = 
3026             (Z_IUSuppliedRecords *)
3027             odr_malloc(p->odr_out, sizeof(*notToKeep));
3028         notToKeep->num = 1;
3029         notToKeep->elements = (Z_IUSuppliedRecords_elem **)
3030             odr_malloc(p->odr_out, sizeof(*notToKeep->elements));
3031         notToKeep->elements[0] = (Z_IUSuppliedRecords_elem *)
3032             odr_malloc(p->odr_out, sizeof(**notToKeep->elements));
3033         notToKeep->elements[0]->which = Z_IUSuppliedRecords_elem_opaque;
3034         if (recordIdOpaque)
3035         {
3036             notToKeep->elements[0]->u.opaque = 
3037                 odr_create_Odr_oct(p->odr_out,
3038                                    (const unsigned char *) recordIdOpaque,
3039                                    strlen(recordIdOpaque));
3040         }
3041         else if (recordIdNumber)
3042         {
3043             notToKeep->elements[0]->which = Z_IUSuppliedRecords_elem_number;
3044             
3045             notToKeep->elements[0]->u.number =
3046                 odr_intdup(p->odr_out, atoi(recordIdNumber));
3047         }
3048         else
3049             notToKeep->elements[0]->u.opaque = 0;
3050         notToKeep->elements[0]->supplementalId = 0;
3051         notToKeep->elements[0]->correlationInfo = 0;
3052         notToKeep->elements[0]->record =
3053             z_ext_record(p->odr_out, syntax_oid,
3054                          record_buf, strlen(record_buf));
3055     }
3056     if (0 && apdu)
3057     {
3058         ODR print = odr_createmem(ODR_PRINT);
3059
3060         z_APDU(print, &apdu, 0, 0);
3061         odr_destroy(print);
3062     }
3063     return apdu;
3064 }
3065
3066 ZOOM_API(void)
3067     ZOOM_package_send(ZOOM_package p, const char *type)
3068 {
3069     Z_APDU *apdu = 0;
3070     ZOOM_connection c;
3071     if (!p)
3072         return;
3073     c = p->connection;
3074     odr_reset(p->odr_out);
3075     xfree(p->buf_out);
3076     p->buf_out = 0;
3077     if (!strcmp(type, "itemorder"))
3078     {
3079         apdu = create_es_package(p, VAL_ITEMORDER);
3080         if (apdu)
3081         {
3082             Z_External *r = (Z_External *) odr_malloc(p->odr_out, sizeof(*r));
3083             
3084             r->direct_reference =
3085                 yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
3086                                        VAL_ITEMORDER);
3087             r->descriptor = 0;
3088             r->which = Z_External_itemOrder;
3089             r->indirect_reference = 0;
3090             r->u.itemOrder = encode_item_order(p);
3091
3092             apdu->u.extendedServicesRequest->taskSpecificParameters = r;
3093         }
3094     }
3095     else if (!strcmp(type, "create"))  /* create database */
3096     {
3097         apdu = create_admin_package(p, Z_ESAdminOriginPartToKeep_create,
3098                                     0, 0);
3099     }   
3100     else if (!strcmp(type, "drop"))  /* drop database */
3101     {
3102         apdu = create_admin_package(p, Z_ESAdminOriginPartToKeep_drop,
3103                                     0, 0);
3104     }
3105     else if (!strcmp(type, "commit"))  /* commit changes */
3106     {
3107         apdu = create_admin_package(p, Z_ESAdminOriginPartToKeep_commit,
3108                                     0, 0);
3109     }
3110     else if (!strcmp(type, "update")) /* update record(s) */
3111     {
3112         apdu = create_update_package(p);
3113     }
3114     else if (!strcmp(type, "xmlupdate"))
3115     {
3116         apdu = create_xmlupdate_package(p);
3117     }
3118     if (apdu)
3119     {
3120         if (encode_APDU(p->connection, apdu, p->odr_out) == 0)
3121         {
3122             char *buf;
3123
3124             ZOOM_task task = ZOOM_connection_add_task(c, ZOOM_TASK_PACKAGE);
3125             task->u.package = p;
3126             buf = odr_getbuf(p->odr_out, &p->len_out, 0);
3127             p->buf_out = (char *) xmalloc(p->len_out);
3128             memcpy(p->buf_out, buf, p->len_out);
3129             
3130             (p->refcount)++;
3131             if (!c->async)
3132             {
3133                 while (ZOOM_event(1, &c))
3134                     ;
3135             }
3136         }
3137     }
3138 }
3139
3140 ZOOM_API(ZOOM_package)
3141     ZOOM_connection_package(ZOOM_connection c, ZOOM_options options)
3142 {
3143     ZOOM_package p = (ZOOM_package) xmalloc(sizeof(*p));
3144
3145     p->connection = c;
3146     p->odr_out = odr_createmem(ODR_ENCODE);
3147     p->options = ZOOM_options_create_with_parent2(options, c->options);
3148     p->refcount = 1;
3149     p->buf_out = 0;
3150     p->len_out = 0;
3151     return p;
3152 }
3153
3154 ZOOM_API(void)
3155     ZOOM_package_destroy(ZOOM_package p)
3156 {
3157     if (!p)
3158         return;
3159     (p->refcount)--;
3160     if (p->refcount == 0)
3161     {
3162         odr_destroy(p->odr_out);
3163         xfree(p->buf_out);
3164         
3165         ZOOM_options_destroy(p->options);
3166         xfree(p);
3167     }
3168 }
3169
3170 ZOOM_API(const char *)
3171     ZOOM_package_option_get(ZOOM_package p, const char *key)
3172 {
3173     return ZOOM_options_get(p->options, key);
3174 }
3175
3176
3177 ZOOM_API(void)
3178     ZOOM_package_option_set(ZOOM_package p, const char *key,
3179                             const char *val)
3180 {
3181     ZOOM_options_set(p->options, key, val);
3182 }
3183
3184 static int ZOOM_connection_exec_task(ZOOM_connection c)
3185 {
3186     ZOOM_task task = c->tasks;
3187     zoom_ret ret = zoom_complete;
3188
3189     if (!task)
3190         return 0;
3191     yaz_log(log_details, "%p ZOOM_connection_exec_task type=%d run=%d",
3192             c, task->which, task->running);
3193     if (c->error != ZOOM_ERROR_NONE)
3194     {
3195         yaz_log(log_details, "%p ZOOM_connection_exec_task "
3196                 "removing tasks because of error = %d", c, c->error);
3197         ZOOM_connection_remove_tasks(c);
3198         return 0;
3199     }
3200     if (task->running)
3201     {
3202         yaz_log(log_details, "%p ZOOM_connection_exec_task "
3203                 "task already running", c);
3204         return 0;
3205     }
3206     task->running = 1;
3207     ret = zoom_complete;
3208     if (c->cs || task->which == ZOOM_TASK_CONNECT)
3209     {
3210         switch (task->which)
3211         {
3212         case ZOOM_TASK_SEARCH:
3213             if (c->proto == PROTO_HTTP)
3214                 ret = ZOOM_connection_srw_send_search(c);
3215             else
3216                 ret = ZOOM_connection_send_search(c);
3217             break;
3218         case ZOOM_TASK_RETRIEVE:
3219             if (c->proto == PROTO_HTTP)
3220                 ret = ZOOM_connection_srw_send_search(c);
3221             else
3222                 ret = send_present(c);
3223             break;
3224         case ZOOM_TASK_CONNECT:
3225             ret = do_connect(c);
3226             break;
3227         case ZOOM_TASK_SCAN:
3228             ret = send_scan(c);
3229             break;
3230         case ZOOM_TASK_PACKAGE:
3231             ret = send_package(c);
3232             break;
3233         case ZOOM_TASK_SORT:
3234             c->tasks->u.sort.resultset->r_sort_spec = 
3235                 c->tasks->u.sort.q->sort_spec;
3236             ret = send_sort(c, c->tasks->u.sort.resultset);
3237             break;
3238         }
3239     }
3240     else
3241     {
3242         yaz_log(log_details, "%p ZOOM_connection_exec_task "
3243                 "remove tasks because no connection exist", c);
3244         ZOOM_connection_remove_tasks(c);
3245     }
3246     if (ret == zoom_complete)
3247     {
3248         yaz_log(log_details, "%p ZOOM_connection_exec_task "
3249                 "task removed (complete)", c);
3250         ZOOM_connection_remove_task(c);
3251         return 0;
3252     }
3253     yaz_log(log_details, "%p ZOOM_connection_exec_task "
3254             "task pending", c);
3255     return 1;
3256 }
3257
3258 static zoom_ret send_sort_present(ZOOM_connection c)
3259 {
3260     zoom_ret r = zoom_complete;
3261
3262     if (c->tasks && c->tasks->which == ZOOM_TASK_SEARCH)
3263         r = send_sort(c, c->tasks->u.search.resultset);
3264     if (r == zoom_complete)
3265         r = send_present(c);
3266     return r;
3267 }
3268
3269 static int es_response(ZOOM_connection c,
3270                        Z_ExtendedServicesResponse *res)
3271 {
3272     if (!c->tasks || c->tasks->which != ZOOM_TASK_PACKAGE)
3273         return 0;
3274     if (res->diagnostics && res->num_diagnostics > 0)
3275         response_diag(c, res->diagnostics[0]);
3276     if (res->taskPackage &&
3277         res->taskPackage->which == Z_External_extendedService)
3278     {
3279         Z_TaskPackage *taskPackage = res->taskPackage->u.extendedService;
3280         Odr_oct *id = taskPackage->targetReference;
3281         
3282         if (id)
3283             ZOOM_options_setl(c->tasks->u.package->options,
3284                               "targetReference", (char*) id->buf, id->len);
3285     }
3286     if (res->taskPackage && 
3287         res->taskPackage->which == Z_External_octet)
3288     {
3289         Odr_oct *doc = res->taskPackage->u.octet_aligned;
3290         ZOOM_options_setl(c->tasks->u.package->options,
3291                           "xmlUpdateDoc", (char*) doc->buf, doc->len);
3292     }
3293     return 1;
3294 }
3295
3296 static void interpret_init_diag(ZOOM_connection c,
3297                                 Z_DiagnosticFormat *diag)
3298 {
3299     if (diag->num > 0)
3300     {
3301         Z_DiagnosticFormat_s *ds = diag->elements[0];
3302         if (ds->which == Z_DiagnosticFormat_s_defaultDiagRec)
3303             response_default_diag(c, ds->u.defaultDiagRec);
3304     }
3305 }
3306
3307
3308 static void interpret_otherinformation_field(ZOOM_connection c,
3309                                              Z_OtherInformation *ui)
3310 {
3311     int i;
3312     for (i = 0; i < ui->num_elements; i++)
3313     {
3314         Z_OtherInformationUnit *unit = ui->list[i];
3315         if (unit->which == Z_OtherInfo_externallyDefinedInfo &&
3316             unit->information.externallyDefinedInfo &&
3317             unit->information.externallyDefinedInfo->which ==
3318             Z_External_diag1) 
3319         {
3320             interpret_init_diag(c, unit->information.externallyDefinedInfo->u.diag1);
3321         } 
3322     }
3323 }
3324
3325 static void recv_apdu(ZOOM_connection c, Z_APDU *apdu)
3326 {
3327     Z_InitResponse *initrs;
3328     
3329     c->mask = 0;
3330     yaz_log(log_details, "%p recv_apdu apdu->which=%d", c, apdu->which);
3331     switch(apdu->which)
3332     {
3333     case Z_APDU_initResponse:
3334         yaz_log(log_api, "%p recv_apd: Received Init response", c);
3335         initrs = apdu->u.initResponse;
3336         ZOOM_connection_option_set(c, "serverImplementationId",
3337                                    initrs->implementationId ?
3338                                    initrs->implementationId : "");
3339         ZOOM_connection_option_set(c, "serverImplementationName",
3340                                    initrs->implementationName ?
3341                                    initrs->implementationName : "");
3342         ZOOM_connection_option_set(c, "serverImplementationVersion",
3343                                    initrs->implementationVersion ?
3344                                    initrs->implementationVersion : "");
3345         /* Set the three old options too, for old applications */
3346         ZOOM_connection_option_set(c, "targetImplementationId",
3347                                    initrs->implementationId ?
3348                                    initrs->implementationId : "");
3349         ZOOM_connection_option_set(c, "targetImplementationName",
3350                                    initrs->implementationName ?
3351                                    initrs->implementationName : "");
3352         ZOOM_connection_option_set(c, "targetImplementationVersion",
3353                                    initrs->implementationVersion ?
3354                                    initrs->implementationVersion : "");
3355         if (!*initrs->result)
3356         {
3357             Z_External *uif = initrs->userInformationField;
3358
3359             set_ZOOM_error(c, ZOOM_ERROR_INIT, 0); /* default error */
3360
3361             if (uif && uif->which == Z_External_userInfo1)
3362                 interpret_otherinformation_field(c, uif->u.userInfo1);
3363         }
3364         else
3365         {
3366             char *cookie =
3367                 yaz_oi_get_string_oidval(&apdu->u.initResponse->otherInfo,
3368                                          VAL_COOKIE, 1, 0);
3369             xfree(c->cookie_in);
3370             c->cookie_in = 0;
3371             if (cookie)
3372                 c->cookie_in = xstrdup(cookie);
3373             if (ODR_MASK_GET(initrs->options, Z_Options_namedResultSets) &&
3374                 ODR_MASK_GET(initrs->protocolVersion, Z_ProtocolVersion_3))
3375                 c->support_named_resultsets = 1;
3376             if (c->tasks)
3377             {
3378                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
3379                 ZOOM_connection_remove_task(c);
3380             }
3381             ZOOM_connection_exec_task(c);
3382         }
3383         if (ODR_MASK_GET(initrs->options, Z_Options_negotiationModel))
3384         {
3385             NMEM tmpmem = nmem_create();
3386             Z_CharSetandLanguageNegotiation *p =
3387                 yaz_get_charneg_record(initrs->otherInfo);
3388             
3389             if (p)
3390             {
3391                 char *charset = NULL, *lang = NULL;
3392                 int sel;
3393                 
3394                 yaz_get_response_charneg(tmpmem, p, &charset, &lang, &sel);
3395                 yaz_log(log_details, "%p recv_apdu target accepted: "
3396                         "charset %s, language %s, select %d",
3397                         c,
3398                         charset ? charset : "none", lang ? lang : "none", sel);
3399                 if (charset)
3400                     ZOOM_connection_option_set(c, "negotiation-charset",
3401                                                charset);
3402                 if (lang)
3403                     ZOOM_connection_option_set(c, "negotiation-lang",
3404                                                lang);
3405
3406                 ZOOM_connection_option_set(
3407                     c,  "negotiation-charset-in-effect-for-records",
3408                     (sel != 0) ? "1" : "0");
3409                 nmem_destroy(tmpmem);
3410             }
3411         }       
3412         break;
3413     case Z_APDU_searchResponse:
3414         yaz_log(log_api, "%p recv_apdu Search response", c);
3415         handle_search_response(c, apdu->u.searchResponse);
3416         if (send_sort_present(c) == zoom_complete)
3417             ZOOM_connection_remove_task(c);
3418         break;
3419     case Z_APDU_presentResponse:
3420         yaz_log(log_api, "%p recv_apdu Present response", c);
3421         handle_present_response(c, apdu->u.presentResponse);
3422         if (send_present(c) == zoom_complete)
3423             ZOOM_connection_remove_task(c);
3424         break;
3425     case Z_APDU_sortResponse:
3426         yaz_log(log_api, "%p recv_apdu Sort response", c);
3427         sort_response(c, apdu->u.sortResponse);
3428         if (send_present(c) == zoom_complete)
3429             ZOOM_connection_remove_task(c);
3430         break;
3431     case Z_APDU_scanResponse:
3432         yaz_log(log_api, "%p recv_apdu Scan response", c);
3433         scan_response(c, apdu->u.scanResponse);
3434         ZOOM_connection_remove_task(c);
3435         break;
3436     case Z_APDU_extendedServicesResponse:
3437         yaz_log(log_api, "%p recv_apdu Extended Services response", c);
3438         es_response(c, apdu->u.extendedServicesResponse);
3439         ZOOM_connection_remove_task(c);
3440         break;
3441     case Z_APDU_close:
3442         yaz_log(log_api, "%p recv_apdu Close PDU", c);
3443         if (!ZOOM_test_reconnect(c))
3444         {
3445             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
3446             do_close(c);
3447         }
3448         break;
3449     default:
3450         yaz_log(log_api, "%p Received unknown PDU", c);
3451         set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
3452         do_close(c);
3453     }
3454 }
3455
3456 #if YAZ_HAVE_XML2
3457 static void handle_srw_response(ZOOM_connection c,
3458                                 Z_SRW_searchRetrieveResponse *res)
3459 {
3460     ZOOM_resultset resultset = 0;
3461     int i;
3462     NMEM nmem;
3463     ZOOM_Event event;
3464     int *start;
3465
3466     if (!c->tasks)
3467         return;
3468
3469     if (c->tasks->which == ZOOM_TASK_SEARCH)
3470     {
3471         resultset = c->tasks->u.search.resultset;
3472         start = &c->tasks->u.search.start;
3473     }
3474     else if (c->tasks->which == ZOOM_TASK_RETRIEVE)
3475     {
3476         resultset = c->tasks->u.retrieve.resultset;
3477         start = &c->tasks->u.retrieve.start;
3478     }
3479     else
3480         return ;
3481
3482     event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
3483     ZOOM_connection_put_event(c, event);
3484
3485     resultset->size = 0;
3486
3487     yaz_log(log_details, "%p handle_srw_response got SRW response OK", c);
3488     
3489     if (res->numberOfRecords)
3490         resultset->size = *res->numberOfRecords;
3491
3492     for (i = 0; i<res->num_records; i++)
3493     {
3494         int pos;
3495
3496         Z_NamePlusRecord *npr = (Z_NamePlusRecord *)
3497             odr_malloc(c->odr_in, sizeof(Z_NamePlusRecord));
3498
3499         if (res->records[i].recordPosition && 
3500             *res->records[i].recordPosition > 0)
3501             pos = *res->records[i].recordPosition - 1;
3502         else
3503             pos = *start + i;
3504         
3505         npr->databaseName = 0;
3506         npr->which = Z_NamePlusRecord_databaseRecord;
3507         npr->u.databaseRecord = (Z_External *)
3508             odr_malloc(c->odr_in, sizeof(Z_External));
3509         npr->u.databaseRecord->descriptor = 0;
3510         npr->u.databaseRecord->direct_reference =
3511             yaz_oidval_to_z3950oid(c->odr_in, CLASS_RECSYN, VAL_TEXT_XML);
3512         npr->u.databaseRecord->which = Z_External_octet;
3513
3514         npr->u.databaseRecord->u.octet_aligned = (Odr_oct *)
3515             odr_malloc(c->odr_in, sizeof(Odr_oct));
3516         npr->u.databaseRecord->u.octet_aligned->buf = (unsigned char*)
3517             res->records[i].recordData_buf;
3518         npr->u.databaseRecord->u.octet_aligned->len = 
3519             npr->u.databaseRecord->u.octet_aligned->size = 
3520             res->records[i].recordData_len;
3521         record_cache_add(resultset, npr, pos);
3522     }
3523     if (res->num_diagnostics > 0)
3524     {
3525         const char *uri = res->diagnostics[0].uri;
3526         if (uri)
3527         {
3528             int code = 0;       
3529             const char *cp;
3530             if ((cp = strrchr(uri, '/')))
3531                 code = atoi(cp+1);
3532             set_dset_error(c, code, uri,
3533                            res->diagnostics[0].details, 0);
3534         }
3535     }
3536     nmem = odr_extract_mem(c->odr_in);
3537     nmem_transfer(resultset->odr->mem, nmem);
3538     nmem_destroy(nmem);
3539 }
3540 #endif
3541
3542 #if YAZ_HAVE_XML2
3543 static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
3544 {
3545     int ret = -1;
3546     const char *content_type = z_HTTP_header_lookup(hres->headers,
3547                                                     "Content-Type");
3548     const char *connection_head = z_HTTP_header_lookup(hres->headers,
3549                                                        "Connection");
3550     c->mask = 0;
3551     yaz_log(log_details, "%p handle_http", c);
3552
3553     if (content_type && !yaz_strcmp_del("text/xml", content_type, "; "))
3554     {
3555         Z_SOAP *soap_package = 0;
3556         ODR o = c->odr_in;
3557         Z_SOAP_Handler soap_handlers[2] = {
3558             {"http://www.loc.gov/zing/srw/", 0,
3559              (Z_SOAP_fun) yaz_srw_codec},
3560             {0, 0, 0}
3561         };
3562         ret = z_soap_codec(o, &soap_package,
3563                            &hres->content_buf, &hres->content_len,
3564                            soap_handlers);
3565         if (!ret && soap_package->which == Z_SOAP_generic &&
3566             soap_package->u.generic->no == 0)
3567         {
3568             Z_SRW_PDU *sr = (Z_SRW_PDU*) soap_package->u.generic->p;
3569             if (sr->which == Z_SRW_searchRetrieve_response)
3570                 handle_srw_response(c, sr->u.response);
3571             else
3572                 ret = -1;
3573         }
3574         else if (!ret && (soap_package->which == Z_SOAP_fault
3575                           || soap_package->which == Z_SOAP_error))
3576         {
3577             set_HTTP_error(c, hres->code,
3578                            soap_package->u.fault->fault_code,
3579                            soap_package->u.fault->fault_string);
3580         }
3581         else
3582             ret = -1;
3583     }
3584     if (ret)
3585     {
3586         if (hres->code != 200)
3587             set_HTTP_error(c, hres->code, 0, 0);
3588         else
3589             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
3590         do_close(c);
3591     }
3592     ZOOM_connection_remove_task(c);
3593     if (!strcmp(hres->version, "1.0"))
3594     {
3595         /* HTTP 1.0: only if Keep-Alive we stay alive.. */
3596         if (!connection_head || strcmp(connection_head, "Keep-Alive"))
3597             do_close(c);
3598     }
3599     else 
3600     {
3601         /* HTTP 1.1: only if no close we stay alive .. */
3602         if (connection_head && !strcmp(connection_head, "close"))
3603             do_close(c);
3604     }
3605 }
3606 #endif
3607
3608 static int do_read(ZOOM_connection c)
3609 {
3610     int r, more;
3611     ZOOM_Event event;
3612     
3613     event = ZOOM_Event_create(ZOOM_EVENT_RECV_DATA);
3614     ZOOM_connection_put_event(c, event);
3615     
3616     r = cs_get(c->cs, &c->buf_in, &c->len_in);
3617     more = cs_more(c->cs);
3618     yaz_log(log_details, "%p do_read len=%d more=%d", c, r, more);
3619     if (r == 1)
3620         return 0;
3621     if (r <= 0)
3622     {
3623         if (ZOOM_test_reconnect(c))
3624         {
3625             yaz_log(log_details, "%p do_read reconnect read", c);
3626         }
3627         else
3628         {
3629             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
3630             do_close(c);
3631         }
3632     }
3633     else
3634     {
3635         Z_GDU *gdu;
3636         ZOOM_Event event;
3637
3638         odr_reset(c->odr_in);
3639         odr_setbuf(c->odr_in, c->buf_in, r, 0);
3640         event = ZOOM_Event_create(ZOOM_EVENT_RECV_APDU);
3641         ZOOM_connection_put_event(c, event);
3642
3643         if (!z_GDU(c->odr_in, &gdu, 0, 0))
3644         {
3645             int x;
3646             int err = odr_geterrorx(c->odr_in, &x);
3647             char msg[60];
3648             const char *element = odr_getelement(c->odr_in);
3649             sprintf(msg, "ODR code %d:%d element=%-20s",
3650                     err, x, element ? element : "<unknown>");
3651             set_ZOOM_error(c, ZOOM_ERROR_DECODE, msg);
3652             do_close(c);
3653         }
3654         else if (gdu->which == Z_GDU_Z3950)
3655             recv_apdu(c, gdu->u.z3950);
3656         else if (gdu->which == Z_GDU_HTTP_Response)
3657         {
3658 #if YAZ_HAVE_XML2
3659             handle_http(c, gdu->u.HTTP_Response);
3660 #else
3661             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
3662             do_close(c);
3663 #endif
3664         }
3665         c->reconnect_ok = 0;
3666     }
3667     return 1;
3668 }
3669
3670 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out)
3671 {
3672     int r;
3673     ZOOM_Event event;
3674     
3675     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
3676     ZOOM_connection_put_event(c, event);
3677
3678     yaz_log(log_details, "%p do_write_ex len=%d", c, len_out);
3679     if ((r = cs_put(c->cs, buf_out, len_out)) < 0)
3680     {
3681         yaz_log(log_details, "%p do_write_ex write failed", c);
3682         if (ZOOM_test_reconnect(c))
3683         {
3684             return zoom_pending;
3685         }
3686         if (c->state == STATE_CONNECTING)
3687             set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
3688         else
3689             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
3690         do_close(c);
3691         return zoom_complete;
3692     }
3693     else if (r == 1)
3694     {    
3695         c->mask = ZOOM_SELECT_EXCEPT;
3696         if (c->cs->io_pending & CS_WANT_WRITE)
3697             c->mask += ZOOM_SELECT_WRITE;
3698         if (c->cs->io_pending & CS_WANT_READ)
3699             c->mask += ZOOM_SELECT_READ;
3700         yaz_log(log_details, "%p do_write_ex write incomplete mask=%d",
3701                 c, c->mask);
3702     }
3703     else
3704     {
3705         c->mask = ZOOM_SELECT_READ|ZOOM_SELECT_EXCEPT;
3706         yaz_log(log_details, "%p do_write_ex write complete mask=%d",
3707                 c, c->mask);
3708     }
3709     return zoom_pending;
3710 }
3711
3712 static zoom_ret do_write(ZOOM_connection c)
3713 {
3714     return do_write_ex(c, c->buf_out, c->len_out);
3715 }
3716
3717
3718 ZOOM_API(const char *)
3719     ZOOM_connection_option_get(ZOOM_connection c, const char *key)
3720 {
3721     return ZOOM_options_get(c->options, key);
3722 }
3723
3724 ZOOM_API(const char *)
3725     ZOOM_connection_option_getl(ZOOM_connection c, const char *key, int *lenp)
3726 {
3727     return ZOOM_options_getl(c->options, key, lenp);
3728 }
3729
3730 ZOOM_API(void)
3731     ZOOM_connection_option_set(ZOOM_connection c, const char *key,
3732                                const char *val)
3733 {
3734     ZOOM_options_set(c->options, key, val);
3735 }
3736
3737 ZOOM_API(void)
3738     ZOOM_connection_option_setl(ZOOM_connection c, const char *key,
3739                                 const char *val, int len)
3740 {
3741     ZOOM_options_setl(c->options, key, val, len);
3742 }
3743
3744 ZOOM_API(const char *)
3745     ZOOM_resultset_option_get(ZOOM_resultset r, const char *key)
3746 {
3747     return ZOOM_options_get(r->options, key);
3748 }
3749
3750 ZOOM_API(void)
3751     ZOOM_resultset_option_set(ZOOM_resultset r, const char *key,
3752                               const char *val)
3753 {
3754     ZOOM_options_set(r->options, key, val);
3755 }
3756
3757
3758 ZOOM_API(int)
3759     ZOOM_connection_errcode(ZOOM_connection c)
3760 {
3761     return ZOOM_connection_error(c, 0, 0);
3762 }
3763
3764 ZOOM_API(const char *)
3765     ZOOM_connection_errmsg(ZOOM_connection c)
3766 {
3767     const char *msg;
3768     ZOOM_connection_error(c, &msg, 0);
3769     return msg;
3770 }
3771
3772 ZOOM_API(const char *)
3773     ZOOM_connection_addinfo(ZOOM_connection c)
3774 {
3775     const char *addinfo;
3776     ZOOM_connection_error(c, 0, &addinfo);
3777     return addinfo;
3778 }
3779
3780 ZOOM_API(const char *)
3781     ZOOM_connection_diagset(ZOOM_connection c)
3782 {
3783     const char *diagset;
3784     ZOOM_connection_error_x(c, 0, 0, &diagset);
3785     return diagset;
3786 }
3787
3788 ZOOM_API(const char *)
3789     ZOOM_diag_str(int error)
3790 {
3791     switch (error)
3792     {
3793     case ZOOM_ERROR_NONE:
3794         return "No error";
3795     case ZOOM_ERROR_CONNECT:
3796         return "Connect failed";
3797     case ZOOM_ERROR_MEMORY:
3798         return "Out of memory";
3799     case ZOOM_ERROR_ENCODE:
3800         return "Encoding failed";
3801     case ZOOM_ERROR_DECODE:
3802         return "Decoding failed";
3803     case ZOOM_ERROR_CONNECTION_LOST:
3804         return "Connection lost";
3805     case ZOOM_ERROR_INIT:
3806         return "Init rejected";
3807     case ZOOM_ERROR_INTERNAL:
3808         return "Internal failure";
3809     case ZOOM_ERROR_TIMEOUT:
3810         return "Timeout";
3811     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
3812         return "Unsupported protocol";
3813     case ZOOM_ERROR_UNSUPPORTED_QUERY:
3814         return "Unsupported query type";
3815     case ZOOM_ERROR_INVALID_QUERY:
3816         return "Invalid query";
3817     case ZOOM_ERROR_CQL_PARSE:
3818         return "CQL parsing error";
3819     case ZOOM_ERROR_CQL_TRANSFORM:
3820         return "CQL transformation error";
3821     case ZOOM_ERROR_CCL_CONFIG:
3822         return "CCL configuration error";
3823     case ZOOM_ERROR_CCL_PARSE:
3824         return "CCL parsing error";
3825     default:
3826         return diagbib1_str(error);
3827     }
3828 }
3829
3830 ZOOM_API(int)
3831     ZOOM_connection_error_x(ZOOM_connection c, const char **cp,
3832                             const char **addinfo, const char **diagset)
3833 {
3834     int error = c->error;
3835     if (cp)
3836     {
3837         if (!c->diagset || !strcmp(c->diagset, "ZOOM"))
3838             *cp = ZOOM_diag_str(error);
3839         else if (!strcmp(c->diagset, "HTTP"))
3840             *cp = z_HTTP_errmsg(c->error);
3841         else if (!strcmp(c->diagset, "Bib-1"))
3842             *cp = ZOOM_diag_str(error);
3843         else if (!strcmp(c->diagset, "info:srw/diagnostic/1"))
3844             *cp = yaz_diag_srw_str(c->error);
3845         else
3846             *cp = "Unknown error and diagnostic set";
3847     }
3848     if (addinfo)
3849         *addinfo = c->addinfo ? c->addinfo : "";
3850     if (diagset)
3851         *diagset = c->diagset ? c->diagset : "";
3852     return c->error;
3853 }
3854
3855 ZOOM_API(int)
3856     ZOOM_connection_error(ZOOM_connection c, const char **cp,
3857                           const char **addinfo)
3858 {
3859     return ZOOM_connection_error_x(c, cp, addinfo, 0);
3860 }
3861
3862 static void ZOOM_connection_do_io(ZOOM_connection c, int mask)
3863 {
3864     ZOOM_Event event = 0;
3865     int r = cs_look(c->cs);
3866     yaz_log(log_details, "%p ZOOM_connection_do_io mask=%d cs_look=%d",
3867             c, mask, r);
3868     
3869     if (r == CS_NONE)
3870     {
3871         event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
3872         set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
3873         do_close(c);
3874         ZOOM_connection_put_event(c, event);
3875     }
3876     else if (r == CS_CONNECT)
3877     {
3878         int ret = ret = cs_rcvconnect(c->cs);
3879         yaz_log(log_details, "%p ZOOM_connection_do_io "
3880                 "cs_rcvconnect returned %d", c, ret);
3881         if (ret == 1)
3882         {
3883             c->mask = ZOOM_SELECT_EXCEPT;
3884             if (c->cs->io_pending & CS_WANT_WRITE)
3885                 c->mask += ZOOM_SELECT_WRITE;
3886             if (c->cs->io_pending & CS_WANT_READ)
3887                 c->mask += ZOOM_SELECT_READ;
3888         }
3889         else if (ret == 0)
3890         {
3891             event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
3892             ZOOM_connection_put_event(c, event);
3893             get_cert(c);
3894             if (c->proto == PROTO_Z3950)
3895                 ZOOM_connection_send_init(c);
3896             else
3897             {
3898                 /* no init request for SRW .. */
3899                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
3900                 ZOOM_connection_remove_task(c);
3901                 c->mask = 0;
3902                 ZOOM_connection_exec_task(c);
3903             }
3904             c->state = STATE_ESTABLISHED;
3905         }
3906         else
3907         {
3908             set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
3909             do_close(c);
3910         }
3911     }
3912     else
3913     {
3914         if (mask & ZOOM_SELECT_EXCEPT)
3915         {
3916             if (ZOOM_test_reconnect(c))
3917             {
3918                 event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
3919                 ZOOM_connection_put_event(c, event);
3920             }
3921             else
3922             {
3923                 set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
3924                 do_close(c);
3925             }
3926             return;
3927         }
3928         if (mask & ZOOM_SELECT_READ)
3929             do_read(c);
3930         if (c->cs && (mask & ZOOM_SELECT_WRITE))
3931             do_write(c);
3932     }
3933 }
3934
3935 ZOOM_API(int)
3936     ZOOM_connection_last_event(ZOOM_connection cs)
3937 {
3938     if (!cs)
3939         return ZOOM_EVENT_NONE;
3940     return cs->last_event;
3941 }
3942
3943 ZOOM_API(int)
3944     ZOOM_event(int no, ZOOM_connection *cs)
3945 {
3946     int timeout = 30;      /* default timeout in seconds */
3947     int timeout_set = 0;   /* whether it was overriden at all */
3948 #if HAVE_SYS_POLL_H
3949     struct pollfd pollfds[1024];
3950     ZOOM_connection poll_cs[1024];
3951 #else
3952     struct timeval tv;
3953     fd_set input, output, except;
3954 #endif
3955     int i, r, nfds;
3956     int max_fd = 0;
3957
3958     yaz_log(log_details, "ZOOM_event(no=%d,cs=%p)", no, cs);
3959     for (i = 0; i<no; i++)
3960     {
3961         ZOOM_connection c = cs[i];
3962         ZOOM_Event event;
3963         if (c && (event = ZOOM_connection_get_event(c)))
3964         {
3965             ZOOM_Event_destroy(event);
3966             return i+1;
3967         }
3968     }
3969     for (i = 0; i<no; i++)
3970     {
3971         ZOOM_connection c = cs[i];
3972         if (c)
3973         {
3974             ZOOM_Event event;
3975             ZOOM_connection_exec_task(c);
3976             if ((event = ZOOM_connection_get_event(c)))
3977             {
3978                 ZOOM_Event_destroy(event);
3979                 return i+1;
3980             }
3981         }
3982     }
3983 #if HAVE_SYS_POLL_H
3984
3985 #else
3986     FD_ZERO(&input);
3987     FD_ZERO(&output);
3988     FD_ZERO(&except);
3989 #endif
3990     nfds = 0;
3991     for (i = 0; i<no; i++)
3992     {
3993         ZOOM_connection c = cs[i];
3994         int fd, mask;
3995         int this_timeout;
3996         
3997         if (!c)
3998             continue;
3999         fd = z3950_connection_socket(c);
4000         mask = z3950_connection_mask(c);
4001
4002         if (fd == -1)
4003             continue;
4004         if (max_fd < fd)
4005             max_fd = fd;
4006         
4007         /* -1 is used for indefinite timeout (no timeout), so -2 here. */
4008         this_timeout = ZOOM_options_get_int(c->options, "timeout", -2);
4009         if (this_timeout != -2)
4010         {
4011             /* ensure the minimum timeout is used */
4012             if (!timeout_set)
4013                 timeout = this_timeout;
4014             else if (this_timeout != -1 && this_timeout < timeout)
4015                 timeout = this_timeout;
4016             timeout_set = 1;
4017         }               
4018 #if HAVE_SYS_POLL_H
4019         if (mask)
4020         {
4021             short poll_events = 0;
4022
4023             if (mask & ZOOM_SELECT_READ)
4024                 poll_events += POLLIN;
4025             if (mask & ZOOM_SELECT_WRITE)
4026                 poll_events += POLLOUT;
4027             if (mask & ZOOM_SELECT_EXCEPT)
4028                 poll_events += POLLERR;
4029             pollfds[nfds].fd = fd;
4030             pollfds[nfds].events = poll_events;
4031             pollfds[nfds].revents = 0;
4032             poll_cs[nfds] = c;
4033             nfds++;
4034         }
4035 #else
4036         if (mask & ZOOM_SELECT_READ)
4037         {
4038             FD_SET(fd, &input);
4039             nfds++;
4040         }
4041         if (mask & ZOOM_SELECT_WRITE)
4042         {
4043             FD_SET(fd, &output);
4044             nfds++;
4045         }
4046         if (mask & ZOOM_SELECT_EXCEPT)
4047         {
4048             FD_SET(fd, &except);
4049             nfds++;
4050         }
4051 #endif
4052     }
4053     if (!nfds)
4054         return 0;
4055
4056 #if HAVE_SYS_POLL_H
4057     r = poll(pollfds, nfds, (timeout == -1 ? -1 : timeout * 1000));
4058     for (i = 0; i<nfds; i++)
4059     {
4060         ZOOM_connection c = poll_cs[i];
4061         if (r && c->mask)
4062         {
4063             int mask = 0;
4064             if (pollfds[i].revents & POLLIN)
4065                 mask += ZOOM_SELECT_READ;
4066             if (pollfds[i].revents & POLLOUT)
4067                 mask += ZOOM_SELECT_WRITE;
4068             if (pollfds[i].revents & POLLERR)
4069                 mask += ZOOM_SELECT_EXCEPT;
4070             if (mask)
4071                 ZOOM_connection_do_io(c, mask);
4072         }
4073         else if (r == 0 && c->mask)
4074         {
4075             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
4076             /* timeout and this connection was waiting */
4077             set_ZOOM_error(c, ZOOM_ERROR_TIMEOUT, 0);
4078             do_close(c);
4079             ZOOM_connection_put_event(c, event);
4080         }
4081     }
4082 #else
4083     tv.tv_sec = timeout;
4084     tv.tv_usec = 0;
4085     r = select(max_fd+1, &input, &output, &except, (timeout == -1 ? 0 : &tv));
4086     for (i = 0; i<no; i++)
4087     {
4088         ZOOM_connection c = cs[i];
4089         int fd, mask;
4090
4091         if (!c)
4092             continue;
4093         fd = z3950_connection_socket(c);
4094         mask = 0;
4095         if (r && c->mask)
4096         {
4097             /* no timeout and real socket */
4098             if (FD_ISSET(fd, &input))
4099                 mask += ZOOM_SELECT_READ;
4100             if (FD_ISSET(fd, &output))
4101                 mask += ZOOM_SELECT_WRITE;
4102             if (FD_ISSET(fd, &except))
4103                 mask += ZOOM_SELECT_EXCEPT;
4104             if (mask)
4105                 ZOOM_connection_do_io(c, mask);
4106         }
4107         if (r == 0 && c->mask)
4108         {
4109             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
4110             /* timeout and this connection was waiting */
4111             set_ZOOM_error(c, ZOOM_ERROR_TIMEOUT, 0);
4112             do_close(c);
4113             ZOOM_connection_put_event(c, event);
4114         }
4115     }
4116 #endif
4117     for (i = 0; i<no; i++)
4118     {
4119         ZOOM_connection c = cs[i];
4120         ZOOM_Event event;
4121         if (c && (event = ZOOM_connection_get_event(c)))
4122         {
4123             ZOOM_Event_destroy(event);
4124             return i+1;
4125         }
4126     }
4127     return 0;
4128 }
4129
4130
4131 /*
4132  * Returns an xmalloc()d string containing RPN that corresponds to the
4133  * CQL passed in.  On error, sets the Connection object's error state
4134  * and returns a null pointer.
4135  * ### We could cache CQL parser and/or transformer in Connection.
4136  */
4137 static char *cql2pqf(ZOOM_connection c, const char *cql)
4138 {
4139     CQL_parser parser;
4140     int error;
4141     struct cql_node *node;
4142     const char *cqlfile;
4143     static cql_transform_t trans;
4144     char pqfbuf[512];
4145
4146     parser = cql_parser_create();
4147     if ((error = cql_parser_string(parser, cql)) != 0) {
4148         cql_parser_destroy(parser);
4149         set_ZOOM_error(c, ZOOM_ERROR_CQL_PARSE, cql);
4150         return 0;
4151     }
4152
4153     node = cql_parser_result(parser);
4154     /* ### Do not call cql_parser_destroy() yet: it destroys `node'! */
4155
4156     cqlfile = ZOOM_connection_option_get(c, "cqlfile");
4157     if (cqlfile == 0) {
4158         cql_parser_destroy(parser);
4159         cql_node_destroy(node);
4160         set_ZOOM_error(c, ZOOM_ERROR_CQL_TRANSFORM, "no CQL transform file");
4161         return 0;
4162     }
4163
4164     if ((trans = cql_transform_open_fname(cqlfile)) == 0) {
4165         char buf[512];        
4166         cql_parser_destroy(parser);
4167         cql_node_destroy(node);
4168         sprintf(buf, "can't open CQL transform file '%.200s': %.200s",
4169                 cqlfile, strerror(errno));
4170         set_ZOOM_error(c, ZOOM_ERROR_CQL_TRANSFORM, buf);
4171         return 0;
4172     }
4173
4174     error = cql_transform_buf(trans, node, pqfbuf, sizeof pqfbuf);
4175     cql_parser_destroy(parser);
4176     cql_node_destroy(node);
4177     if (error != 0) {
4178         char buf[512];
4179         const char *addinfo;
4180         error = cql_transform_error(trans, &addinfo);
4181         cql_transform_close(trans);
4182         sprintf(buf, "%.200s (addinfo=%.200s)", cql_strerror(error), addinfo);
4183         set_ZOOM_error(c, ZOOM_ERROR_CQL_TRANSFORM, buf);
4184         return 0;
4185     }
4186
4187     cql_transform_close(trans);
4188     return xstrdup(pqfbuf);
4189 }
4190
4191 /*
4192  * Local variables:
4193  * c-basic-offset: 4
4194  * indent-tabs-mode: nil
4195  * End:
4196  * vim: shiftwidth=4 tabstop=8 expandtab
4197  */
4198