Added Explain record syntax to the format command.
[yaz-moved-to-github.git] / client / client.c
1 /*
2  * Copyright (c) 1995, Index Data.
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: client.c,v $
7  * Revision 1.28  1995-12-14 11:09:31  quinn
8  * Added Explain record syntax to the format command.
9  *
10  * Revision 1.27  1995/12/12  16:37:02  quinn
11  * Added destroy element to data1_node.
12  *
13  * Revision 1.26  1995/12/12  14:11:00  quinn
14  * Minimal.
15  *
16  * Revision 1.25  1995/11/13  09:27:22  quinn
17  * Fiddling with the variant stuff.
18  *
19  * Revision 1.24  1995/10/30  12:41:13  quinn
20  * Added hostname lookup for server.
21  *
22  * Revision 1.23  1995/10/18  16:12:30  quinn
23  * Better diagnostics.
24  *
25  * Revision 1.22  1995/10/11  14:49:12  quinn
26  * Smallish.
27  *
28  * Revision 1.21  1995/09/29  17:01:47  quinn
29  * More Windows work
30  *
31  * Revision 1.20  1995/08/29  14:24:13  quinn
32  * Added second half of close-handshake
33  *
34  * Revision 1.19  1995/08/29  11:17:28  quinn
35  * Added code to receive close
36  *
37  * Revision 1.18  1995/08/28  12:21:27  quinn
38  * Client can now ask for simple element set names.
39  *
40  * Revision 1.17  1995/08/17  12:45:02  quinn
41  * Fixed minor problems with GRS-1. Added support in c&s.
42  *
43  * Revision 1.16  1995/08/15  12:00:04  quinn
44  * Updated External
45  *
46  * Revision 1.15  1995/06/22  09:28:03  quinn
47  * Fixed bug in SUTRS processing.
48  *
49  * Revision 1.14  1995/06/19  12:37:41  quinn
50  * Added BER dumper.
51  *
52  * Revision 1.13  1995/06/16  10:29:11  quinn
53  * *** empty log message ***
54  *
55  * Revision 1.12  1995/06/15  07:44:57  quinn
56  * Moving to v3.
57  *
58  * Revision 1.11  1995/06/14  15:26:40  quinn
59  * *** empty log message ***
60  *
61  * Revision 1.10  1995/06/06  14:56:58  quinn
62  * Better diagnostics.
63  *
64  * Revision 1.9  1995/06/06  08:15:19  quinn
65  * Cosmetic.
66  *
67  * Revision 1.8  1995/06/05  10:52:22  quinn
68  * Added SCAN.
69  *
70  * Revision 1.7  1995/06/02  09:50:09  quinn
71  * Smallish.
72  *
73  * Revision 1.6  1995/05/31  08:29:21  quinn
74  * Nothing significant.
75  *
76  * Revision 1.5  1995/05/29  08:10:47  quinn
77  * Moved oid.c to util.
78  *
79  * Revision 1.4  1995/05/22  15:30:13  adam
80  * Client uses prefix query notation.
81  *
82  * Revision 1.3  1995/05/22  15:06:53  quinn
83  * *** empty log message ***
84  *
85  * Revision 1.2  1995/05/22  14:56:40  quinn
86  * *** empty log message ***
87  *
88  * Revision 1.1  1995/05/22  11:30:31  quinn
89  * Added prettier client.
90  *
91  *
92  */
93
94 /*
95  * This is the obligatory little toy client, whose primary purpose is
96  * to illustrate the use of the YAZ service-level API.
97  */
98
99 #include <stdio.h>
100 #include <stdlib.h>
101 #ifdef WINDOWS
102 #include <time.h>
103 #else
104 #include <sys/time.h>
105 #endif
106 #include <assert.h>
107 #ifdef _AIX
108 #include <sys/select.h>
109 #endif
110
111 #include <comstack.h>
112 #include <tcpip.h>
113 #ifdef USE_XTIMOSI
114 #include <xmosi.h>
115 #endif
116
117 #include <proto.h>
118 #include <marcdisp.h>
119 #include <diagbib1.h>
120
121 #ifdef RPN_QUERY
122 #ifdef PREFIX_QUERY
123 #include <pquery.h>
124 #else
125 #include <yaz-ccl.h>
126 #endif
127 #endif
128
129 #define C_PROMPT "Z> "
130
131 static ODR out, in, print;              /* encoding and decoding streams */
132 static COMSTACK conn = 0;               /* our z-association */
133 static Z_IdAuthentication *auth = 0;    /* our current auth definition */
134 static char database[512] = "Default";  /* Database name */
135 static int setnumber = 0;               /* current result set number */
136 static int smallSetUpperBound = 0;
137 static int largeSetLowerBound = 1;
138 static int mediumSetPresentNumber = 0;
139 static Z_ElementSetNames *elementSetNames = 0; 
140 static int setno = 1;                   /* current set offset */
141 static int protocol = PROTO_Z3950;      /* current app protocol */
142 static int recordsyntax = VAL_USMARC;
143 static int sent_close = 0;
144 static ODR_MEM session_mem;                /* memory handle for init-response */
145 static Z_InitResponse *session = 0;        /* session parameters */
146 static char last_scan[512] = "0";
147 static char last_cmd[100] = "?";
148 #ifdef RPN_QUERY
149 #ifndef PREFIX_QUERY
150 static CCL_bibset bibset;               /* CCL bibset handle */
151 #endif
152 #endif
153
154 static void send_apdu(Z_APDU *a)
155 {
156     char *buf;
157     int len;
158
159     if (!z_APDU(out, &a, 0))
160     {
161         odr_perror(out, "Encoding APDU");
162         exit(1);
163     }
164     buf = odr_getbuf(out, &len, 0);
165     odr_reset(out); /* release the APDU */
166     if (cs_put(conn, buf, len) < 0)
167     {
168         fprintf(stderr, "cs_put: %s", cs_errmsg(cs_errno(conn)));
169         exit(1);
170     }
171 }
172
173 /* INIT SERVICE ------------------------------- */
174
175 static void send_initRequest()
176 {
177     Z_APDU *apdu = zget_APDU(out, Z_APDU_initRequest);
178     Z_InitRequest *req = apdu->u.initRequest;
179
180     ODR_MASK_SET(req->options, Z_Options_search);
181     ODR_MASK_SET(req->options, Z_Options_present);
182     ODR_MASK_SET(req->options, Z_Options_namedResultSets);
183     ODR_MASK_SET(req->options, Z_Options_triggerResourceCtrl);
184     ODR_MASK_SET(req->options, Z_Options_scan);
185
186     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_1);
187     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_2);
188     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_3);
189
190     *req->maximumRecordSize = 1024*1024;
191
192     req->idAuthentication = auth;
193
194     send_apdu(apdu);
195     printf("Sent initrequest.\n");
196 }
197
198 static int process_initResponse(Z_InitResponse *res)
199 {
200     /* save session parameters for later use */
201     session_mem = odr_extract_mem(in);
202     session = res;
203
204     if (!*res->result)
205         printf("Connection rejected by target.\n");
206     else
207         printf("Connection accepted by target.\n");
208     if (res->implementationId)
209         printf("ID     : %s\n", res->implementationId);
210     if (res->implementationName)
211         printf("Name   : %s\n", res->implementationName);
212     if (res->implementationVersion)
213         printf("Version: %s\n", res->implementationVersion);
214     if (res->userInformationField)
215     {
216         printf("UserInformationfield:\n");
217         if (!z_External(print, (Z_External**)&res-> userInformationField,
218             0))
219         {
220             odr_perror(print, "Printing userinfo\n");
221             odr_reset(print);
222         }
223         if (res->userInformationField->which == Z_External_octet)
224         {
225             printf("Guessing visiblestring:\n");
226             printf("'%s'\n", res->userInformationField->u. octet_aligned->buf);
227         }
228     }
229     return 0;
230 }
231
232 int cmd_open(char *arg)
233 {
234     void *add;
235     char type[100], addr[100];
236     CS_TYPE t;
237
238     if (conn)
239     {
240         printf("Already connected.\n");
241         return 0;
242     }
243     if (!*arg || sscanf(arg, "%[^:]:%s", type, addr) < 2)
244     {
245         fprintf(stderr, "Usage: open (osi|tcp) ':' [tsel '/']host[':'port]\n");
246         return 0;
247     }
248 #ifdef USE_XTIMOSI
249     if (!strcmp(type, "osi"))
250     {
251         if (!(add = mosi_strtoaddr(addr)))
252         {
253             perror(arg);
254             return 0;
255         }
256         t = mosi_type;
257         protocol = PROTO_SR;
258     }
259     else
260 #endif
261     if (!strcmp(type, "tcp"))
262     {
263         if (!(add = tcpip_strtoaddr(addr)))
264         {
265             perror(arg);
266             return 0;
267         }
268         t = tcpip_type;
269         protocol = PROTO_Z3950;
270     }
271     else
272     {
273         fprintf(stderr, "Bad type: %s\n", type);
274         return 0;
275     }
276     if (!(conn = cs_create(t, 1, protocol)))
277     {
278         perror("cs_create");
279         return 0;
280     }
281     printf("Connecting...");
282     fflush(stdout);
283     if (cs_connect(conn, add) < 0)
284     {
285         perror("connect");
286         cs_close(conn);
287         conn = 0;
288         return 0;
289     }
290     printf("Ok.\n");
291     send_initRequest();
292     return 2;
293 }
294
295 int cmd_authentication(char *arg)
296 {
297     static Z_IdAuthentication au;
298     static char open[256];
299
300     if (!*arg)
301     {
302         printf("Auth field set to null\n");
303         auth = 0;
304         return 1;
305     }
306     auth = &au;
307     au.which = Z_IdAuthentication_open;
308     au.u.open = open;
309     strcpy(open, arg);
310     return 1;
311 }
312
313 /* SEARCH SERVICE ------------------------------ */
314
315 static void display_variant(Z_Variant *v, int level)
316 {
317     int i;
318
319     for (i = 0; i < v->num_triples; i++)
320     {
321         printf("%*sclass=%d,type=%d", level * 4, "", *v->triples[i]->class,
322             *v->triples[i]->type);
323         if (v->triples[i]->which == Z_Triple_internationalString)
324             printf(",value=%s\n", v->triples[i]->value.internationalString);
325         else
326             printf("\n");
327     }
328 }
329
330 static void display_grs1(Z_GenericRecord *r, int level)
331 {
332     int i;
333
334     if (!r)
335         return;
336     for (i = 0; i < r->num_elements; i++)
337     {
338         Z_TaggedElement *t;
339
340         printf("%*s", level * 4, "");
341         t = r->elements[i];
342         printf("(");
343         if (t->tagType)
344             printf("%d,", *t->tagType);
345         else
346             printf("?,");
347         if (t->tagValue->which == Z_StringOrNumeric_numeric)
348             printf("%d) ", *t->tagValue->u.numeric);
349         else
350             printf("%s) ", t->tagValue->u.string);
351         if (t->content->which == Z_ElementData_subtree)
352         {
353             printf("\n");
354             display_grs1(t->content->u.subtree, level+1);
355         }
356         else if (t->content->which == Z_ElementData_string)
357             printf("%s\n", t->content->u.string);
358         else if (t->content->which == Z_ElementData_numeric)
359             printf("%d\n", *t->content->u.numeric);
360         else if (t->content->which == Z_ElementData_noDataRequested)
361             printf("[No data requested]\n");
362         else if (t->content->which == Z_ElementData_elementEmpty)
363             printf("[Element empty]\n");
364         else if (t->content->which == Z_ElementData_elementNotThere)
365             printf("[Element not there]\n");
366         else
367             printf("??????\n");
368         if (t->appliedVariant)
369             display_variant(t->appliedVariant, level+1);
370         if (t->metaData && t->metaData->supportedVariants)
371         {
372             int c;
373
374             printf("%*s---- variant list\n", (level+1)*4, "");
375             for (c = 0; c < t->metaData->num_supportedVariants; c++)
376             {
377                 printf("%*svariant #%d\n", (level+1)*4, "", c);
378                 display_variant(t->metaData->supportedVariants[c], level + 2);
379             }
380         }
381     }
382 }
383
384 static void display_record(Z_DatabaseRecord *p)
385 {
386     Z_External *r = (Z_External*) p;
387     oident *ent = oid_getentbyoid(r->direct_reference);
388
389     if (r->direct_reference)
390     {
391         printf("Record type: ");
392         if (ent)
393             printf("%s\n", ent->desc);
394         else if (!odr_oid(print, &r->direct_reference, 0))
395         {
396             odr_perror(print, "print oid");
397             odr_reset(print);
398         }
399     }
400     if (r->which == Z_External_octet && p->u.octet_aligned->len)
401         marc_display ((char*)p->u.octet_aligned->buf, stdout);
402     else if (ent->value == VAL_SUTRS)
403     {
404         if (r->which != Z_External_sutrs)
405         {
406             printf("Expecting single SUTRS type for SUTRS.\n");
407             return;
408         }
409         printf("%.*s", r->u.sutrs->len, r->u.sutrs->buf);
410     }
411     else if (ent->value == VAL_GRS1)
412     {
413         if (r->which != Z_External_grs1)
414         {
415             printf("Expecting single GRS type for GRS.\n");
416             return;
417         }
418         display_grs1(r->u.grs1, 0);
419     }
420     else 
421     {
422         printf("Unknown record representation.\n");
423         if (!z_External(print, &r, 0))
424         {
425             odr_perror(print, "Printing external");
426             odr_reset(print);
427         }
428     }
429 }
430
431 static void display_diagrec(Z_DiagRec *p)
432 {
433     oident *ent;
434 #ifdef Z_95
435     Z_DefaultDiagFormat *r;
436 #else
437     Z_DiagRec *r = p;
438 #endif
439
440     printf("Diagnostic message from database:\n");
441 #ifdef Z_95
442     if (p->which != Z_DiagRec_defaultFormat)
443     {
444         printf("Diagnostic record not in default format.\n");
445         return;
446     }
447     else
448         r = p->u.defaultFormat;
449 #endif
450     if (!(ent = oid_getentbyoid(r->diagnosticSetId)) ||
451         ent->class != CLASS_DIAGSET || ent->value != VAL_BIB1)
452         printf("Missing or unknown diagset\n");
453     printf("    [%d] %s", *r->condition, diagbib1_str(*r->condition));
454     if (r->addinfo && *r->addinfo)
455         printf(" -- %s\n", r->addinfo);
456     else
457         printf("\n");
458 }
459
460 static void display_nameplusrecord(Z_NamePlusRecord *p)
461 {
462     if (p->databaseName)
463         printf("[%s]", p->databaseName);
464     if (p->which == Z_NamePlusRecord_surrogateDiagnostic)
465         display_diagrec(p->u.surrogateDiagnostic);
466     else
467         display_record(p->u.databaseRecord);
468 }
469
470 static void display_records(Z_Records *p)
471 {
472     int i;
473
474     if (p->which == Z_Records_NSD)
475         display_diagrec(p->u.nonSurrogateDiagnostic);
476     else
477     {
478         printf("Records: %d\n", p->u.databaseOrSurDiagnostics->num_records);
479         for (i = 0; i < p->u.databaseOrSurDiagnostics->num_records; i++)
480             display_nameplusrecord(p->u.databaseOrSurDiagnostics->records[i]);
481     }
482 }
483
484 static int send_searchRequest(char *arg)
485 {
486     Z_APDU *apdu = zget_APDU(out, Z_APDU_searchRequest);
487     Z_SearchRequest *req = apdu->u.searchRequest;
488     char *databaseNames = database;
489     Z_Query query;
490 #ifdef RPN_QUERY
491 #ifndef PREFIX_QUERY
492     struct ccl_rpn_node *rpn;
493     int error, pos;
494 #endif
495 #endif
496     char setstring[100];
497 #ifdef RPN_QUERY
498     Z_RPNQuery *RPNquery;
499     oident bib1;
500 #else
501     Odr_oct ccl_query;
502 #endif
503
504 #ifdef RPN_QUERY
505 #ifndef PREFIX_QUERY
506     rpn = ccl_find_str(bibset, arg, &error, &pos);
507     if (error)
508     {
509         printf("CCL ERROR: %s\n", ccl_err_msg(error));
510         return 0;
511     }
512 #endif
513 #endif
514
515     if (!strcmp(arg, "@big")) /* strictly for troublemaking */
516     {
517         static unsigned char big[2100];
518         static Odr_oct bigo;
519
520         /* send a very big referenceid to test transport stack etc. */
521         memset(big, 'A', 2100);
522         bigo.len = bigo.size = 2100;
523         bigo.buf = big;
524         req->referenceId = &bigo;
525     }
526
527     if (setnumber >= 0)
528     {
529         sprintf(setstring, "%d", ++setnumber);
530         req->resultSetName = setstring;
531     }
532     *req->smallSetUpperBound = smallSetUpperBound;
533     *req->largeSetLowerBound = largeSetLowerBound;
534     *req->mediumSetPresentNumber = mediumSetPresentNumber;
535     if (smallSetUpperBound > 0 || (largeSetLowerBound > 1 &&
536         mediumSetPresentNumber > 0))
537     {
538         oident prefsyn;
539
540         prefsyn.proto = protocol;
541         prefsyn.class = CLASS_RECSYN;
542         prefsyn.value = recordsyntax;
543         req->preferredRecordSyntax = odr_oiddup(out, oid_getoidbyent(&prefsyn));
544         req->smallSetElementSetNames =
545             req->mediumSetElementSetNames = elementSetNames;
546     }
547     req->num_databaseNames = 1;
548     req->databaseNames = &databaseNames;
549
550     req->query = &query;
551
552 #ifdef RPN_QUERY
553     query.which = Z_Query_type_1;
554
555 #ifndef PREFIX_QUERY
556     assert((RPNquery = ccl_rpn_query(rpn)));
557 #else
558     RPNquery = p_query_rpn (out, arg);
559     if (!RPNquery)
560     {
561         printf("Prefix query error\n");
562         return 0;
563     }
564 #endif
565     bib1.proto = protocol;
566     bib1.class = CLASS_ATTSET;
567     bib1.value = VAL_BIB1;
568     RPNquery->attributeSetId = oid_getoidbyent(&bib1);
569     query.u.type_1 = RPNquery;
570 #else
571     query.which = Z_Query_type_2;
572     query.u.type_2 = &ccl_query;
573     ccl_query.buf = (unsigned char*) arg;
574     ccl_query.len = strlen(arg);
575 #endif
576
577     send_apdu(apdu);
578     setno = 1;
579     printf("Sent searchRequest.\n");
580     return 2;
581 }
582
583 static int process_searchResponse(Z_SearchResponse *res)
584 {
585     if (*res->searchStatus)
586         printf("Search was a success.\n");
587     else
588         printf("Search was a bloomin' failure.\n");
589     printf("Number of hits: %d, setno %d\n",
590         *res->resultCount, setnumber);
591     printf("records returned: %d\n",
592         *res->numberOfRecordsReturned);
593     setno += *res->numberOfRecordsReturned;
594     if (res->records)
595         display_records(res->records);
596     return 0;
597 }
598
599 static int cmd_find(char *arg)
600 {
601     if (!*arg)
602     {
603         printf("Find what?\n");
604         return 0;
605     }
606     if (!conn)
607     {
608         printf("Not connected yet\n");
609         return 0;
610     }
611     if (!send_searchRequest(arg))
612         return 0;
613     return 2;
614 }
615
616 static int cmd_ssub(char *arg)
617 {
618     if (!(smallSetUpperBound = atoi(arg)))
619         return 0;
620     return 1;
621 }
622
623 static int cmd_lslb(char *arg)
624 {
625     if (!(largeSetLowerBound = atoi(arg)))
626         return 0;
627     return 1;
628 }
629
630 static int cmd_mspn(char *arg)
631 {
632     if (!(mediumSetPresentNumber = atoi(arg)))
633         return 0;
634     return 1;
635 }
636
637 static int cmd_status(char *arg)
638 {
639     printf("smallSetUpperBound: %d\n", smallSetUpperBound);
640     printf("largeSetLowerBound: %d\n", largeSetLowerBound);
641     printf("mediumSetPresentNumber: %d\n", mediumSetPresentNumber);
642     return 1;
643 }
644
645 static int cmd_base(char *arg)
646 {
647     if (!*arg)
648     {
649         printf("Usage: base <database>\n");
650         return 0;
651     }
652     strcpy(database, arg);
653     return 1;
654 }
655
656 static int cmd_setnames(char *arg)
657 {
658     if (setnumber < 0)
659     {
660         printf("Set numbering enabled.\n");
661         setnumber = 0;
662     }
663     else
664     {
665         printf("Set numbering disabled.\n");
666         setnumber = -1;
667     }
668     return 1;
669 }
670
671 /* PRESENT SERVICE ----------------------------- */
672
673 static int send_presentRequest(char *arg)
674 {
675     Z_APDU *apdu = zget_APDU(out, Z_APDU_presentRequest);
676     Z_PresentRequest *req = apdu->u.presentRequest;
677     Z_RecordComposition compo;
678     oident prefsyn;
679     int nos = 1;
680     char *p;
681     char setstring[100];
682
683     if ((p = strchr(arg, '+')))
684     {
685         nos = atoi(p + 1);
686         *p = 0;
687     }
688     if (*arg)
689         setno = atoi(arg);
690
691     if (setnumber >= 0)
692     {
693         sprintf(setstring, "%d", setnumber);
694         req->resultSetId = setstring;
695     }
696     req->resultSetStartPoint = &setno;
697     req->numberOfRecordsRequested = &nos;
698     prefsyn.proto = protocol;
699     prefsyn.class = CLASS_RECSYN;
700     prefsyn.value = recordsyntax;
701     req->preferredRecordSyntax = oid_getoidbyent(&prefsyn);
702     if (elementSetNames)
703     {
704         req->recordComposition = &compo;
705         compo.which = Z_RecordComp_simple;
706         compo.u.simple = elementSetNames;
707     }
708     send_apdu(apdu);
709     printf("Sent presentRequest (%d+%d).\n", setno, nos);
710     return 2;
711 }
712
713 void process_close(Z_Close *req)
714 {
715     Z_APDU *apdu = zget_APDU(out, Z_APDU_close);
716     Z_Close *res = apdu->u.close;
717
718     static char *reasons[] =
719     {
720         "finished",
721         "shutdown",
722         "system problem",
723         "cost limit reached",
724         "resources",
725         "security violation",
726         "protocolError",
727         "lack of activity",
728         "peer abort",
729         "unspecified"
730     };
731
732     printf("Reason: %s, message: %s\n", reasons[*req->closeReason],
733         req->diagnosticInformation ? req->diagnosticInformation : "NULL");
734     if (sent_close)
735     {
736         printf("Goodbye.\n");
737         exit(0);
738     }
739     *res->closeReason = Z_Close_finished;
740     send_apdu(apdu);
741     printf("Sent response.\n");
742     sent_close = 1;
743 }
744
745 static int cmd_show(char *arg)
746 {
747     if (!send_presentRequest(arg))
748         return 0;
749     return 2;
750 }
751
752 int cmd_quit(char *arg)
753 {
754     printf("See you later, alligator.\n");
755     exit(0);
756 }
757
758 int cmd_cancel(char *arg)
759 {
760     Z_APDU *apdu = zget_APDU(out, Z_APDU_triggerResourceControlRequest);
761     Z_TriggerResourceControlRequest *req =
762         apdu->u.triggerResourceControlRequest;
763     bool_t false = 0;
764     
765     if (!session)
766     {
767         printf("Session not initialized yet\n");
768         return 0;
769     }
770     if (!ODR_MASK_GET(session->options, Z_Options_triggerResourceCtrl))
771     {
772         printf("Target doesn't support cancel (trigger resource ctrl)\n");
773         return 0;
774     }
775     *req->requestedAction = Z_TriggerResourceCtrl_cancel;
776     req->resultSetWanted = &false;
777
778     send_apdu(apdu);
779     printf("Sent cancel request\n");
780     return 2;
781 }
782
783 int send_scanrequest(char *string, int pp, int num)
784 {
785     Z_APDU *apdu = zget_APDU(out, Z_APDU_scanRequest);
786     Z_ScanRequest *req = apdu->u.scanRequest;
787     char *db = database;
788     oident attset;
789
790     req->num_databaseNames = 1;
791     req->databaseNames = &db;
792     attset.proto = protocol;
793     attset.class = CLASS_ATTSET;
794     attset.value = VAL_BIB1;
795     req->attributeSet = oid_getoidbyent(&attset);
796     req->termListAndStartPoint = p_query_scan(out, string);
797     req->numberOfTermsRequested = &num;
798     req->preferredPositionInResponse = &pp;
799     send_apdu(apdu);
800     return 2;
801 }
802
803 void display_term(Z_TermInfo *t)
804 {
805     if (t->term->which == Z_Term_general)
806     {
807         printf("%.*s (%d)\n", t->term->u.general->len, t->term->u.general->buf,
808             t->globalOccurrences ? *t->globalOccurrences : -1);
809         sprintf(last_scan, "%.*s", t->term->u.general->len,
810             t->term->u.general->buf);
811     }
812     else
813         printf("Term type not general.\n");
814 }
815
816 void process_scanResponse(Z_ScanResponse *res)
817 {
818     int i;
819
820     printf("SCAN: %d entries, position=%d\n", *res->numberOfEntriesReturned,
821         *res->positionOfTerm);
822     if (*res->scanStatus != Z_Scan_success)
823         printf("Scan returned code %d\n", *res->scanStatus);
824     if (!res->entries)
825         return;
826     if (res->entries->which == Z_ListEntries_entries)
827     {
828         Z_Entries *ent = res->entries->u.entries;
829
830         for (i = 0; i < ent->num_entries; i++)
831             if (ent->entries[i]->which == Z_Entry_termInfo)
832             {
833                 printf("%c ", i + 1 == *res->positionOfTerm ? '*' : ' ');
834                 display_term(ent->entries[i]->u.termInfo);
835             }
836             else
837                 display_diagrec(ent->entries[i]->u.surrogateDiagnostic);
838     }
839     else
840         display_diagrec(res->entries->u.nonSurrogateDiagnostics->diagRecs[0]);
841 }
842
843 int cmd_scan(char *arg)
844 {
845     if (!session)
846     {
847         printf("Session not initialized yet\n");
848         return 0;
849     }
850     if (!ODR_MASK_GET(session->options, Z_Options_scan))
851     {
852         printf("Target doesn't support scan\n");
853         return 0;
854     }
855     if (*arg)
856     {
857         if (send_scanrequest(arg, 5, 20) < 0)
858             return 0;
859     }
860     else
861         if (send_scanrequest(last_scan, 1, 20) < 0)
862             return 0;
863     return 2;
864 }
865
866 int cmd_format(char *arg)
867 {
868     if (!arg || !*arg)
869     {
870         printf("Usage: format <recordsyntax>\n");
871         return 0;
872     }
873     if (!strcmp(arg, "sutrs"))
874     {
875         printf("Preferred format is SUTRS.\n");
876         recordsyntax = VAL_SUTRS;
877         return 1;
878     }
879     else if (!strcmp(arg, "usmarc"))
880     {
881         printf("Preferred format is USMARC\n");
882         recordsyntax = VAL_USMARC;
883         return 1;
884     }
885     else if (!strcmp(arg, "danmarc"))
886     {
887         printf("Preferred format is DANMARC\n");
888         recordsyntax = VAL_DANMARC;
889         return 1;
890     }
891     else if (!strcmp(arg, "grs1"))
892     {
893         printf("Preferred format is GRS1\n");
894         recordsyntax = VAL_GRS1;
895         return 1;
896     }
897     else if (!strcmp(arg, "explain"))
898     {
899         printf("Preferred format is Explain\n");
900         recordsyntax = VAL_EXPLAIN;
901         return 1;
902     }
903     else
904     {
905         printf("Specify one of {sutrs,usmarc,danmarc,grs1}.\n");
906         return 0;
907     }
908 }
909
910 int cmd_elements(char *arg)
911 {
912     static Z_ElementSetNames esn;
913     static char what[100];
914
915     if (!arg || !*arg)
916     {
917         printf("Usage: elements <esn>\n");
918         return 0;
919     }
920     strcpy(what, arg);
921     esn.which = Z_ElementSetNames_generic;
922     esn.u.generic = what;
923     elementSetNames = &esn;
924     return 1;
925 }
926
927 int cmd_close(char *arg)
928 {
929     Z_APDU *apdu = zget_APDU(out, Z_APDU_close);
930     Z_Close *req = apdu->u.close;
931
932     *req->closeReason = Z_Close_finished;
933     send_apdu(apdu);
934     printf("Sent close request.\n");
935     sent_close = 1;
936     return 2;
937 }
938
939 static void initialize(void)
940 {
941 #ifdef RPN_QUERY
942 #ifndef PREFIX_QUERY
943     FILE *inf;
944 #endif
945 #endif
946
947     if (!(out = odr_createmem(ODR_ENCODE)) ||
948         !(in = odr_createmem(ODR_DECODE)) ||
949         !(print = odr_createmem(ODR_PRINT)))
950     {
951         fprintf(stderr, "failed to allocate ODR streams\n");
952         exit(1);
953     }
954     setvbuf(stdout, 0, _IONBF, 0);
955
956 #ifdef RPN_QUERY
957 #ifndef PREFIX_QUERY
958     bibset = ccl_qual_mk (); 
959     inf = fopen ("default.bib", "r");
960     if (inf)
961     {
962         ccl_qual_file (bibset, inf);
963         fclose (inf);
964     }
965 #endif
966 #endif
967 }
968
969 static int client(void)
970 {
971     static struct {
972         char *cmd;
973         int (*fun)(char *arg);
974         char *ad;
975     } cmd[] = {
976         {"open", cmd_open, "('tcp'|'osi')':'[<TSEL>'/']<HOST>[':'<PORT>]"},
977         {"quit", cmd_quit, ""},
978         {"find", cmd_find, "<CCL-QUERY>"},
979         {"base", cmd_base, "<BASE-NAME>"},
980         {"show", cmd_show, "<REC#>['+'<#RECS>]"},
981         {"scan", cmd_scan, "<TERM>"},
982         {"authentication", cmd_authentication, "<ACCTSTRING>"},
983         {"lslb", cmd_lslb, "<largeSetLowerBound>"},
984         {"ssub", cmd_ssub, "<smallSetUpperBound>"},
985         {"mspn", cmd_mspn, "<mediumSetPresentNumber>"},
986         {"status", cmd_status, ""},
987         {"setnames", cmd_setnames, ""},
988         {"cancel", cmd_cancel, ""},
989         {"format", cmd_format, "<recordsyntax>"},
990         {"elements", cmd_elements, "<elementSetName>"},
991         {"close", cmd_close, ""},
992         {0,0}
993     };
994     char *netbuffer= 0;
995     int netbufferlen = 0;
996     int i;
997     Z_APDU *apdu;
998
999     while (1)
1000     {
1001         int res;
1002         fd_set input;
1003         char line[1024], word[1024], arg[1024];
1004
1005         FD_ZERO(&input);
1006         FD_SET(0, &input);
1007         if (conn)
1008             FD_SET(cs_fileno(conn), &input);
1009         if ((res = select(20, &input, 0, 0, 0)) < 0)
1010         {
1011             perror("select");
1012             exit(1);
1013         }
1014         if (!res)
1015             continue;
1016         if (FD_ISSET(0, &input))
1017         {
1018             /* quick & dirty way to get a command line. */
1019             if (!gets(line))
1020                 break;
1021             if ((res = sscanf(line, "%s %[^;]", word, arg)) <= 0)
1022             {
1023                 strcpy(word, last_cmd);
1024                 *arg = '\0';
1025             }
1026             else if (res == 1)
1027                 *arg = 0;
1028             strcpy(last_cmd, word);
1029             for (i = 0; cmd[i].cmd; i++)
1030                 if (!strncmp(cmd[i].cmd, word, strlen(word)))
1031                 {
1032                     res = (*cmd[i].fun)(arg);
1033                     break;
1034                 }
1035             if (!cmd[i].cmd) /* dump our help-screen */
1036             {
1037                 printf("Unknown command: %s.\n", word);
1038                 printf("Currently recognized commands:\n");
1039                 for (i = 0; cmd[i].cmd; i++)
1040                     printf("   %s %s\n", cmd[i].cmd, cmd[i].ad);
1041                 res = 1;
1042             }
1043             if (res < 2)
1044                 printf(C_PROMPT);
1045         }
1046         if (conn && FD_ISSET(cs_fileno(conn), &input))
1047         {
1048             do
1049             {
1050                 if ((res = cs_get(conn, &netbuffer, &netbufferlen)) < 0)
1051                 {
1052                     perror("cs_get");
1053                     exit(1);
1054                 }
1055                 if (!res)
1056                 {
1057                     printf("Target closed connection.\n");
1058                     exit(1);
1059                 }
1060                 odr_reset(in); /* release APDU from last round */
1061                 odr_setbuf(in, netbuffer, res, 0);
1062                 if (!z_APDU(in, &apdu, 0))
1063                 {
1064                     odr_perror(in, "Decoding incoming APDU");
1065                     fprintf(stderr, "[Near %d]\n", odr_offset(in));
1066                     fprintf(stderr, "Packet dump:\n---------\n");
1067                     odr_dumpBER(stderr, netbuffer, res);
1068                     fprintf(stderr, "---------\n");
1069                     exit(1);
1070                 }
1071 #if 0
1072                 if (!z_APDU(print, &apdu, 0))
1073                 {
1074                     odr_perror(print, "Failed to print incoming APDU");
1075                     odr_reset(print);
1076                     continue;
1077                 }
1078 #endif
1079                 switch(apdu->which)
1080                 {
1081                     case Z_APDU_initResponse:
1082                         process_initResponse(apdu->u.initResponse);
1083                         break;
1084                     case Z_APDU_searchResponse:
1085                         process_searchResponse(apdu->u.searchResponse);
1086                         break;
1087                     case Z_APDU_scanResponse:
1088                         process_scanResponse(apdu->u.scanResponse);
1089                         break;
1090                     case Z_APDU_presentResponse:
1091                         printf("Received presentResponse.\n");
1092                         setno +=
1093                             *apdu->u.presentResponse->numberOfRecordsReturned;
1094                         if (apdu->u.presentResponse->records)
1095                             display_records(apdu->u.presentResponse->records);
1096                         else
1097                             printf("No records.\n");
1098                         break;
1099                     case Z_APDU_close:
1100                         printf("Target has closed the association.\n");
1101                         process_close(apdu->u.close);
1102                         break;
1103                     default:
1104                         printf("Received unknown APDU type (%d).\n", 
1105                             apdu->which);
1106                         exit(1);
1107                 }
1108                 printf(C_PROMPT);
1109                 fflush(stdout);
1110             }
1111             while (cs_more(conn));
1112         }
1113     }
1114     return 0;
1115 }
1116
1117 int main(int argc, char **argv)
1118 {
1119     initialize();
1120     if (argc > 1)
1121         cmd_open(argv[1]);
1122     else
1123         printf(C_PROMPT);
1124     return client();
1125 }