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