dafd7ef7ecbaaab4aa70c2443c408695559fffa5
[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.8  1995-06-05 10:52:22  quinn
8  * Added SCAN.
9  *
10  * Revision 1.7  1995/06/02  09:50:09  quinn
11  * Smallish.
12  *
13  * Revision 1.6  1995/05/31  08:29:21  quinn
14  * Nothing significant.
15  *
16  * Revision 1.5  1995/05/29  08:10:47  quinn
17  * Moved oid.c to util.
18  *
19  * Revision 1.4  1995/05/22  15:30:13  adam
20  * Client uses prefix query notation.
21  *
22  * Revision 1.3  1995/05/22  15:06:53  quinn
23  * *** empty log message ***
24  *
25  * Revision 1.2  1995/05/22  14:56:40  quinn
26  * *** empty log message ***
27  *
28  * Revision 1.1  1995/05/22  11:30:31  quinn
29  * Added prettier client.
30  *
31  *
32  */
33
34 /*
35  * This is the obligatory little toy client, whose primary purpose is
36  * to illustrate the use of the YAZ service-level API.
37  */
38
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <sys/time.h>
42 #include <assert.h>
43 #ifdef _AIX
44 #include <sys/select.h>
45 #endif
46
47 #include <comstack.h>
48 #include <tcpip.h>
49 #ifdef USE_XTIMOSI
50 #include <xmosi.h>
51 #endif
52
53 #include <proto.h>
54
55 #include <marcdisp.h>
56
57 #ifdef RPN_QUERY
58 #ifdef PREFIX_QUERY
59 #include <pquery.h>
60 #else
61 #include <yaz-ccl.h>
62 #endif
63 #endif
64
65 #include "../version.h"
66
67 #define C_PROMPT "Z> "
68
69 static ODR out, in, print;              /* encoding and decoding streams */
70 static COMSTACK conn = 0;               /* our z-association */
71 static Z_IdAuthentication *auth = 0;    /* our current auth definition */
72 static char database[512] = "Default";  /* Database name */
73 static int setnumber = 0;               /* current result set number */
74 static int smallSetUpperBound = 0;
75 static int largeSetLowerBound = 1;
76 static int mediumSetPresentNumber = 0;
77 static int setno = 1;                   /* current set offset */
78 static int protocol = PROTO_Z3950;      /* current app protocol */
79 static ODR_MEM session_mem;                /* memory handle for init-response */
80 static Z_InitResponse *session = 0;        /* session parameters */
81 #ifdef RPN_QUERY
82 #ifndef PREFIX_QUERY
83 static CCL_bibset bibset;               /* CCL bibset handle */
84 #endif
85 #endif
86
87 static void send_apdu(Z_APDU *a)
88 {
89     char *buf;
90     int len;
91
92     if (!z_APDU(out, &a, 0))
93     {
94         odr_perror(out, "Encoding APDU");
95         exit(1);
96     }
97     buf = odr_getbuf(out, &len, 0);
98     odr_reset(out); /* release the APDU */
99     if (cs_put(conn, buf, len) < 0)
100     {
101         fprintf(stderr, "cs_put: %s", cs_errlist[cs_errno(conn)]);
102         exit(1);
103     }
104 }
105
106 /* INIT SERVICE ------------------------------- */
107
108 static void send_initRequest()
109 {
110     Z_APDU *apdu = zget_APDU(out, Z_APDU_initRequest);
111     Z_InitRequest *req = apdu->u.initRequest;
112
113     ODR_MASK_SET(req->options, Z_Options_search);
114     ODR_MASK_SET(req->options, Z_Options_present);
115     ODR_MASK_SET(req->options, Z_Options_namedResultSets);
116     ODR_MASK_SET(req->options, Z_Options_triggerResourceCtrl);
117     ODR_MASK_SET(req->options, Z_Options_scan);
118
119     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_1);
120     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_2);
121
122     req->idAuthentication = auth;
123
124     send_apdu(apdu);
125     printf("Sent initrequest.\n");
126 }
127
128 static int process_initResponse(Z_InitResponse *res)
129 {
130     /* save parameters for later use */
131     session_mem = odr_extract_mem(in);
132     session = res;
133
134     if (!*res->result)
135         printf("Connection rejected by target.\n");
136     else
137         printf("Connection accepted by target.\n");
138     if (res->implementationId)
139         printf("ID     : %s\n", res->implementationId);
140     if (res->implementationName)
141         printf("Name   : %s\n", res->implementationName);
142     if (res->implementationVersion)
143         printf("Version: %s\n", res->implementationVersion);
144     if (res->userInformationField)
145     {
146         printf("UserInformationfield:\n");
147         if (!odr_external(print, (Odr_external**)&res-> userInformationField,
148             0))
149         {
150             odr_perror(print, "Printing userinfo\n");
151             odr_reset(print);
152         }
153         if (res->userInformationField->which == ODR_EXTERNAL_octet)
154         {
155             printf("Guessing visiblestring:\n");
156             printf("'%s'\n", res->userInformationField->u. octet_aligned->buf);
157         }
158     }
159     return 0;
160 }
161
162 int cmd_open(char *arg)
163 {
164     void *add;
165     char type[100], addr[100];
166     CS_TYPE t;
167
168     if (conn)
169     {
170         printf("Already connected.\n");
171         return 0;
172     }
173     if (!*arg || sscanf(arg, "%[^:]:%s", type, addr) < 2)
174     {
175         fprintf(stderr, "Usage: open (osi|tcp) ':' [tsel '/']host[':'port]\n");
176         return 0;
177     }
178 #ifdef USE_XTIMOSI
179     if (!strcmp(type, "osi"))
180     {
181         if (!(add = mosi_strtoaddr(addr)))
182         {
183             perror(arg);
184             return 0;
185         }
186         t = mosi_type;
187         protocol = PROTO_SR;
188     }
189     else
190 #endif
191     if (!strcmp(type, "tcp"))
192     {
193         if (!(add = tcpip_strtoaddr(addr)))
194         {
195             perror(arg);
196             return 0;
197         }
198         t = tcpip_type;
199         protocol = PROTO_Z3950;
200     }
201     else
202     {
203         fprintf(stderr, "Bad type: %s\n", type);
204         return 0;
205     }
206     if (!(conn = cs_create(t, 1, protocol)))
207     {
208         perror("cs_create");
209         return 0;
210     }
211     printf("Connecting...");
212     fflush(stdout);
213     if (cs_connect(conn, add) < 0)
214     {
215         perror("connect");
216         cs_close(conn);
217         conn = 0;
218         return 0;
219     }
220     printf("Ok.\n");
221     send_initRequest();
222     return 2;
223 }
224
225 int cmd_authentication(char *arg)
226 {
227     static Z_IdAuthentication au;
228     static char open[256];
229
230     if (!*arg)
231     {
232         printf("Auth field set to null\n");
233         auth = 0;
234         return 1;
235     }
236     auth = &au;
237     au.which = Z_IdAuthentication_open;
238     au.u.open = open;
239     strcpy(open, arg);
240     return 1;
241 }
242
243 /* SEARCH SERVICE ------------------------------ */
244
245 void display_record(Z_DatabaseRecord *p)
246 {
247     Odr_external *r = (Odr_external*) p;
248
249     if (r->direct_reference)
250     {
251         oident *ent = oid_getentbyoid(r->direct_reference);
252
253         printf("Record type: ");
254         if (ent)
255             printf("%s\n", ent->desc);
256         else if (!odr_oid(print, &r->direct_reference, 0))
257         {
258             odr_perror(print, "print oid");
259             odr_reset(print);
260         }
261     }
262 #if 1
263     if (r->which == ODR_EXTERNAL_octet && p->u.octet_aligned->len)
264     {
265 #if 1
266         marc_display ((char*)p->u.octet_aligned->buf, stdout);
267 #else
268         FILE *ofi = fopen("dump", "a");
269         assert(ofi);
270         fwrite(p->u.octet_aligned->buf, 1, p->u.octet_aligned->len, ofi);
271         fclose(ofi);
272         printf("dumped record\n");
273 #endif
274     }
275     else
276     {
277         printf("Unknown record representation.\n");
278         if (!odr_external(print, &r, 0))
279         {
280             odr_perror(print, "Printing external");
281             odr_reset(print);
282         }
283     }
284 #endif
285 }
286
287 static void display_diagrec(Z_DiagRec *p)
288 {
289     oident *ent;
290
291     printf("Diagnostic message from database.\n");
292     if (!(ent = oid_getentbyoid(p->diagnosticSetId)) ||
293         ent->class != CLASS_DIAGSET || ent->value != VAL_BIB1)
294         printf("Missing or unknown diagset\n");
295     printf("Error condition: %d", *p->condition);
296     printf(" -- %s\n", p->addinfo ? p->addinfo : "");
297 }
298
299 static void display_nameplusrecord(Z_NamePlusRecord *p)
300 {
301     if (p->databaseName)
302         printf("[%s]", p->databaseName);
303     if (p->which == Z_NamePlusRecord_surrogateDiagnostic)
304         display_diagrec(p->u.surrogateDiagnostic);
305     else
306         display_record(p->u.databaseRecord);
307 }
308
309 static void display_records(Z_Records *p)
310 {
311     int i;
312
313     if (p->which == Z_Records_NSD)
314         display_diagrec(p->u.nonSurrogateDiagnostic);
315     else
316     {
317         printf("Records: %d\n", p->u.databaseOrSurDiagnostics->num_records);
318         for (i = 0; i < p->u.databaseOrSurDiagnostics->num_records; i++)
319             display_nameplusrecord(p->u.databaseOrSurDiagnostics->records[i]);
320     }
321 }
322
323 static int send_searchRequest(char *arg)
324 {
325     Z_APDU *apdu = zget_APDU(out, Z_APDU_searchRequest);
326     Z_SearchRequest *req = apdu->u.searchRequest;
327     char *databaseNames = database;
328     Z_Query query;
329 #ifdef RPN_QUERY
330 #ifndef PREFIX_QUERY
331     struct ccl_rpn_node *rpn;
332     int error, pos;
333 #endif
334 #endif
335     char setstring[100];
336 #ifdef RPN_QUERY
337     Z_RPNQuery *RPNquery;
338     oident bib1;
339 #else
340     Odr_oct ccl_query;
341 #endif
342
343 #ifdef RPN_QUERY
344 #ifndef PREFIX_QUERY
345     rpn = ccl_find_str(bibset, arg, &error, &pos);
346     if (error)
347     {
348         printf("CCL ERROR: %s\n", ccl_err_msg(error));
349         return 0;
350     }
351 #endif
352 #endif
353
354     if (!strcmp(arg, "@big")) /* strictly for troublemaking */
355     {
356         static unsigned char big[2100];
357         static Odr_oct bigo;
358
359         /* send a very big referenceid to test transport stack etc. */
360         memset(big, 'A', 2100);
361         bigo.len = bigo.size = 2100;
362         bigo.buf = big;
363         req->referenceId = &bigo;
364     }
365
366     if (setnumber >= 0)
367     {
368         sprintf(setstring, "%d", ++setnumber);
369         req->resultSetName = setstring;
370     }
371     *req->smallSetUpperBound = smallSetUpperBound;
372     *req->largeSetLowerBound = largeSetLowerBound;
373     *req->mediumSetPresentNumber = mediumSetPresentNumber;
374     req->num_databaseNames = 1;
375     req->databaseNames = &databaseNames;
376
377     req->query = &query;
378
379 #ifdef RPN_QUERY
380     query.which = Z_Query_type_1;
381
382 #ifndef PREFIX_QUERY
383     assert((RPNquery = ccl_rpn_query(rpn)));
384 #else
385     RPNquery = p_query_rpn (out, arg);
386     if (!RPNquery)
387     {
388         printf("Prefix query error\n");
389         return 0;
390     }
391 #endif
392     bib1.proto = protocol;
393     bib1.class = CLASS_ATTSET;
394     bib1.value = VAL_BIB1;
395     RPNquery->attributeSetId = oid_getoidbyent(&bib1);
396     query.u.type_1 = RPNquery;
397 #else
398     query.which = Z_Query_type_2;
399     query.u.type_2 = &ccl_query;
400     ccl_query.buf = (unsigned char*) arg;
401     ccl_query.len = strlen(arg);
402 #endif
403
404     send_apdu(apdu);
405     setno = 1;
406     printf("Sent searchRequest.\n");
407     return 2;
408 }
409
410 static int process_searchResponse(Z_SearchResponse *res)
411 {
412     if (res->searchStatus)
413         printf("Search was a success.\n");
414     else
415         printf("Search was a bloomin' failure.\n");
416     printf("Number of hits: %d, setno %d\n",
417         *res->resultCount, setnumber);
418     printf("records returned: %d\n",
419         *res->numberOfRecordsReturned);
420     setno += *res->numberOfRecordsReturned;
421     if (res->records)
422         display_records(res->records);
423     return 0;
424 }
425
426 static int cmd_find(char *arg)
427 {
428     if (!*arg)
429     {
430         printf("Find what?\n");
431         return 0;
432     }
433     if (!conn)
434     {
435         printf("Not connected yet\n");
436         return 0;
437     }
438     if (!send_searchRequest(arg))
439         return 0;
440     return 2;
441 }
442
443 static int cmd_ssub(char *arg)
444 {
445     if (!(smallSetUpperBound = atoi(arg)))
446         return 0;
447     return 1;
448 }
449
450 static int cmd_lslb(char *arg)
451 {
452     if (!(largeSetLowerBound = atoi(arg)))
453         return 0;
454     return 1;
455 }
456
457 static int cmd_mspn(char *arg)
458 {
459     if (!(mediumSetPresentNumber = atoi(arg)))
460         return 0;
461     return 1;
462 }
463
464 static int cmd_status(char *arg)
465 {
466     printf("smallSetUpperBound: %d\n", smallSetUpperBound);
467     printf("largeSetLowerBound: %d\n", largeSetLowerBound);
468     printf("mediumSetPresentNumber: %d\n", mediumSetPresentNumber);
469     return 1;
470 }
471
472 static int cmd_base(char *arg)
473 {
474     if (!*arg)
475     {
476         printf("Usage: base <database>\n");
477         return 0;
478     }
479     strcpy(database, arg);
480     return 1;
481 }
482
483 static int cmd_setnames(char *arg)
484 {
485     if (setnumber < 0)
486     {
487         printf("Set numbering enabled.\n");
488         setnumber = 0;
489     }
490     else
491     {
492         printf("Set numbering disabled.\n");
493         setnumber = -1;
494     }
495     return 1;
496 }
497
498 /* PRESENT SERVICE ----------------------------- */
499
500 static int send_presentRequest(char *arg)
501 {
502     Z_APDU *apdu = zget_APDU(out, Z_APDU_presentRequest);
503     Z_PresentRequest *req = apdu->u.presentRequest;
504     int nos = 1;
505     char *p;
506     char setstring[100];
507
508     if ((p = strchr(arg, '+')))
509     {
510         nos = atoi(p + 1);
511         *p = 0;
512     }
513     if (*arg)
514         setno = atoi(arg);
515
516     if (setnumber >= 0)
517     {
518         sprintf(setstring, "%d", setnumber);
519         req->resultSetId = setstring;
520     }
521     req->resultSetStartPoint = &setno;
522     req->numberOfRecordsRequested = &nos;
523     send_apdu(apdu);
524     printf("Sent presentRequest (%d+%d).\n", setno, nos);
525     return 2;
526 }
527
528 static int cmd_show(char *arg)
529 {
530     if (!send_presentRequest(arg))
531         return 0;
532     return 2;
533 }
534
535 int cmd_quit(char *arg)
536 {
537     printf("See you later, alligator.\n");
538     exit(0);
539 }
540
541 int cmd_cancel(char *arg)
542 {
543     Z_APDU *apdu = zget_APDU(out, Z_APDU_triggerResourceControlRequest);
544     Z_TriggerResourceControlRequest *req =
545         apdu->u.triggerResourceControlRequest;
546     bool_t false = 0;
547     
548     if (!session)
549     {
550         printf("Session not initialized yet\n");
551         return 0;
552     }
553     if (!ODR_MASK_GET(session->options, Z_Options_triggerResourceCtrl))
554     {
555         printf("Target doesn't support cancel (trigger resource ctrl)\n");
556         return 0;
557     }
558     *req->requestedAction = Z_TriggerResourceCtrl_cancel;
559     req->resultSetWanted = &false;
560
561     send_apdu(apdu);
562     printf("Sent cancel request\n");
563     return 2;
564 }
565
566 int send_scanrequest(char *string, int pp, int num)
567 {
568     Z_APDU *apdu = zget_APDU(out, Z_APDU_scanRequest);
569     Z_ScanRequest *req = apdu->u.scanRequest;
570     char *db = database;
571     oident attset;
572     Z_AttributesPlusTerm sp;
573     Z_Term term;
574     Odr_oct trm;
575
576     req->num_databaseNames = 1;
577     req->databaseNames = &db;
578     attset.proto = protocol;
579     attset.class = CLASS_ATTSET;
580     attset.value = VAL_BIB1;
581     req->attributeSet = oid_getoidbyent(&attset);
582     req->termListAndStartPoint = &sp;
583     sp.num_attributes = 0;
584     sp.attributeList = ODR_NULLVAL;
585     sp.term = &term;
586     term.which = Z_Term_general;
587     term.u.general = &trm;
588     trm.buf = (unsigned char*) string;
589     trm.len = trm.size = strlen(string);
590     req->numberOfTermsRequested = &num;
591     req->preferredPositionInResponse = &pp;
592     send_apdu(apdu);
593     return 2;
594 }
595
596 void display_term(Z_TermInfo *t)
597 {
598     if (t->term->which == Z_Term_general)
599         printf("%.*s (%d)\n", t->term->u.general->len, t->term->u.general->buf,
600             t->globalOccurrences ? *t->globalOccurrences : -1);
601     else
602         printf("Term type not general.\n");
603 }
604
605 void process_scanResponse(Z_ScanResponse *res)
606 {
607     int i;
608
609     printf("SCAN: %d entries, position=%d\n", *res->numberOfEntriesReturned,
610         *res->positionOfTerm);
611     if (*res->scanStatus != Z_Scan_success)
612         printf("Scan returned code %d\n", *res->scanStatus);
613     if (!res->entries)
614         return;
615     if (res->entries->which == Z_ListEntries_entries)
616     {
617         Z_Entries *ent = res->entries->u.entries;
618
619         for (i = 0; i < ent->num_entries; i++)
620             if (ent->entries[i]->which == Z_Entry_termInfo)
621             {
622                 printf("%c ", i + 1 == *res->positionOfTerm ? '*' : ' ');
623                 display_term(ent->entries[i]->u.termInfo);
624             }
625             else
626                 display_diagrec(ent->entries[i]->u.surrogateDiagnostic);
627     }
628     else
629         display_diagrec(res->entries->u.nonSurrogateDiagnostics->diagRecs[0]);
630 }
631
632 int cmd_scan(char *arg)
633 {
634     if (!session)
635     {
636         printf("Session not initialized yet\n");
637         return 0;
638     }
639     if (!ODR_MASK_GET(session->options, Z_Options_scan))
640     {
641         printf("Target doesn't support scan\n");
642         return 0;
643     }
644     if (*arg)
645         if (send_scanrequest(arg, 5, 19) < 0)
646             return 0;
647     return 2;
648 }
649
650 static void initialize(void)
651 {
652 #ifdef RPN_QUERY
653 #ifndef PREFIX_QUERY
654     FILE *inf;
655 #endif
656 #endif
657
658     if (!(out = odr_createmem(ODR_ENCODE)) ||
659         !(in = odr_createmem(ODR_DECODE)) ||
660         !(print = odr_createmem(ODR_PRINT)))
661     {
662         fprintf(stderr, "failed to allocate ODR streams\n");
663         exit(1);
664     }
665     setvbuf(stdout, 0, _IONBF, 0);
666
667 #ifdef RPN_QUERY
668 #ifndef PREFIX_QUERY
669     bibset = ccl_qual_mk (); 
670     inf = fopen ("default.bib", "r");
671     if (inf)
672     {
673         ccl_qual_file (bibset, inf);
674         fclose (inf);
675     }
676 #endif
677 #endif
678 }
679
680 static int client(void)
681 {
682     static struct {
683         char *cmd;
684         int (*fun)(char *arg);
685         char *ad;
686     } cmd[] = {
687         {"open", cmd_open, "('tcp'|'osi')':'[<TSEL>'/']<HOST>[':'<PORT>]"},
688         {"quit", cmd_quit, ""},
689         {"find", cmd_find, "<CCL-QUERY>"},
690         {"base", cmd_base, "<BASE-NAME>"},
691         {"show", cmd_show, "<REC#>['+'<#RECS>]"},
692         {"scan", cmd_scan, "<TERM>"},
693         {"authentication", cmd_authentication, "<ACCTSTRING>"},
694         {"lslb", cmd_lslb, "<largeSetLowerBound>"},
695         {"ssub", cmd_ssub, "<smallSetUpperBound>"},
696         {"mspn", cmd_mspn, "<mediumSetPresentNumber>"},
697         {"status", cmd_status, ""},
698         {"setnames", cmd_setnames, ""},
699         {"cancel", cmd_cancel, ""},
700         {0,0}
701     };
702     char *netbuffer= 0;
703     int netbufferlen = 0;
704     int i;
705     Z_APDU *apdu;
706
707     while (1)
708     {
709         int res;
710         fd_set input;
711         char line[1024], word[1024], arg[1024];
712
713         FD_ZERO(&input);
714         FD_SET(0, &input);
715         if (conn)
716             FD_SET(cs_fileno(conn), &input);
717         if ((res = select(20, &input, 0, 0, 0)) < 0)
718         {
719             perror("select");
720             exit(1);
721         }
722         if (!res)
723             continue;
724         if (FD_ISSET(0, &input))
725         {
726             /* quick & dirty way to get a command line. */
727             if (!gets(line))
728                 break;
729             if ((res = sscanf(line, "%s %[^;]", word, arg)) <= 0)
730             {
731                 printf(C_PROMPT);
732                 continue;
733             }
734             if (res == 1)
735                 *arg = 0;
736             for (i = 0; cmd[i].cmd; i++)
737                 if (!strncmp(cmd[i].cmd, word, strlen(word)))
738                 {
739                     res = (*cmd[i].fun)(arg);
740                     break;
741                 }
742             if (!cmd[i].cmd) /* dump our help-screen */
743             {
744                 printf("Unknown command: %s.\n", word);
745                 printf("Currently recognized commands:\n");
746                 for (i = 0; cmd[i].cmd; i++)
747                     printf("   %s %s\n", cmd[i].cmd, cmd[i].ad);
748                 res = 1;
749             }
750             if (res < 2)
751                 printf(C_PROMPT);
752         }
753         if (conn && FD_ISSET(cs_fileno(conn), &input))
754         {
755             do
756             {
757                 if ((res = cs_get(conn, &netbuffer, &netbufferlen)) < 0)
758                 {
759                     perror("cs_get");
760                     exit(1);
761                 }
762                 if (!res)
763                 {
764                     printf("Target closed connection.\n");
765                     exit(1);
766                 }
767                 odr_reset(in); /* release APDU from last round */
768                 odr_setbuf(in, netbuffer, res, 0);
769                 if (!z_APDU(in, &apdu, 0))
770                 {
771                     odr_perror(in, "Decoding incoming APDU");
772                     exit(1);
773                 }
774 #if 0
775                 if (!z_APDU(print, &apdu, 0))
776                 {
777                     odr_perror(print, "Failed to print incoming APDU");
778                     odr_reset(print);
779                     continue;
780                 }
781 #endif
782                 switch(apdu->which)
783                 {
784                     case Z_APDU_initResponse:
785                         process_initResponse(apdu->u.initResponse);
786                         break;
787                     case Z_APDU_searchResponse:
788                         process_searchResponse(apdu->u.searchResponse);
789                         break;
790                     case Z_APDU_scanResponse:
791                         process_scanResponse(apdu->u.scanResponse);
792                         break;
793                     case Z_APDU_presentResponse:
794                         printf("Received presentResponse.\n");
795                         setno +=
796                             *apdu->u.presentResponse->numberOfRecordsReturned;
797                         if (apdu->u.presentResponse->records)
798                             display_records(apdu->u.presentResponse->records);
799                         else
800                             printf("No records.\n");
801                         break;
802                     default:
803                         printf("Received unknown APDU type (%d).\n", 
804                             apdu->which);
805                         exit(1);
806                 }
807                 printf("Z> ");
808                 fflush(stdout);
809             }
810             while (cs_more(conn));
811         }
812     }
813     return 0;
814 }
815
816 int main(int argc, char **argv)
817 {
818     initialize();
819     if (argc > 1)
820         cmd_open(argv[1]);
821     else
822         printf(C_PROMPT);
823     return client();
824 }