More example code to handle Extended Services Update.
[yaz-moved-to-github.git] / ztest / ztest.c
1 /*
2  * Copyright (c) 1995-2002, Index Data.
3  * See the file LICENSE for details.
4  *
5  * $Id: ztest.c,v 1.49 2002-01-21 12:54:06 adam Exp $
6  */
7
8 /*
9  * Demonstration of simple server
10  */
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <ctype.h>
15
16 #include <yaz/backend.h>
17 #include <yaz/log.h>
18
19 #if YAZ_MODULE_ill
20 #include <yaz/ill.h>
21 #endif
22
23 Z_GenericRecord *read_grs1(FILE *f, ODR o);
24
25 int ztest_search (void *handle, bend_search_rr *rr);
26 int ztest_sort (void *handle, bend_sort_rr *rr);
27 int ztest_present (void *handle, bend_present_rr *rr);
28 int ztest_esrequest (void *handle, bend_esrequest_rr *rr);
29 int ztest_delete (void *handle, bend_delete_rr *rr);
30
31 int ztest_search (void *handle, bend_search_rr *rr)
32 {
33     if (rr->num_bases != 1)
34     {
35         rr->errcode = 23;
36         return 0;
37     }
38     if (strcmp (rr->basenames[0], "Default"))
39     {
40         rr->errcode = 109;
41         rr->errstring = rr->basenames[0];
42         return 0;
43     }
44     rr->hits = rand() % 22;
45     return 0;
46 }
47
48 int ztest_present (void *handle, bend_present_rr *rr)
49 {
50     return 0;
51 }
52
53 int ztest_esrequest (void *handle, bend_esrequest_rr *rr)
54 {
55     /* user-defined handle - created in bend_init */
56     int *counter = (int*) handle;  
57
58     yaz_log(LOG_LOG, "ESRequest no %d", *counter);
59
60     (*counter)++;
61
62     if (rr->esr->packageName)
63         yaz_log(LOG_LOG, "packagename: %s", rr->esr->packageName);
64     yaz_log(LOG_LOG, "Waitaction: %d", *rr->esr->waitAction);
65
66
67     yaz_log(LOG_LOG, "function: %d", *rr->esr->function);
68
69     if (!rr->esr->taskSpecificParameters)
70     {
71         yaz_log (LOG_WARN, "No task specific parameters");
72     }
73     else if (rr->esr->taskSpecificParameters->which == Z_External_itemOrder)
74     {
75         Z_ItemOrder *it = rr->esr->taskSpecificParameters->u.itemOrder;
76         yaz_log (LOG_LOG, "Received ItemOrder");
77         if (it->which == Z_IOItemOrder_esRequest)
78         {
79             Z_IORequest *ir = it->u.esRequest;
80             Z_IOOriginPartToKeep *k = ir->toKeep;
81             Z_IOOriginPartNotToKeep *n = ir->notToKeep;
82             
83             if (k && k->contact)
84             {
85                 if (k->contact->name)
86                     yaz_log(LOG_LOG, "contact name %s", k->contact->name);
87                 if (k->contact->phone)
88                     yaz_log(LOG_LOG, "contact phone %s", k->contact->phone);
89                 if (k->contact->email)
90                     yaz_log(LOG_LOG, "contact email %s", k->contact->email);
91             }
92             if (k->addlBilling)
93             {
94                 yaz_log(LOG_LOG, "Billing info (not shown)");
95             }
96             
97             if (n->resultSetItem)
98             {
99                 yaz_log(LOG_LOG, "resultsetItem");
100                 yaz_log(LOG_LOG, "setId: %s", n->resultSetItem->resultSetId);
101                 yaz_log(LOG_LOG, "item: %d", *n->resultSetItem->item);
102             }
103 #if YAZ_MODULE_ill
104             if (n->itemRequest)
105             {
106                 Z_External *r = (Z_External*) n->itemRequest;
107                 ILL_ItemRequest *item_req = 0;
108                 ILL_APDU *ill_apdu = 0;
109                 if (r->direct_reference)
110                 {
111                     oident *ent = oid_getentbyoid(r->direct_reference);
112                     if (ent)
113                         yaz_log(LOG_LOG, "OID %s", ent->desc);
114                     if (ent && ent->value == VAL_TEXT_XML)
115                     {
116                         yaz_log (LOG_LOG, "ILL XML request");
117                         if (r->which == Z_External_octet)
118                             yaz_log (LOG_LOG, "%.*s", r->u.octet_aligned->len,
119                                      r->u.octet_aligned->buf); 
120                     }
121                     if (ent && ent->value == VAL_ISO_ILL_1)
122                     {
123                         yaz_log (LOG_LOG, "Decode ItemRequest begin");
124                         if (r->which == ODR_EXTERNAL_single)
125                         {
126                             odr_setbuf(rr->decode,
127                                        (char *) r->u.single_ASN1_type->buf,
128                                        r->u.single_ASN1_type->len, 0);
129                             
130                             if (!ill_ItemRequest (rr->decode, &item_req, 0, 0))
131                             {
132                                 yaz_log (LOG_LOG,
133                                     "Couldn't decode ItemRequest %s near %d",
134                                        odr_errmsg(odr_geterror(rr->decode)),
135                                        odr_offset(rr->decode));
136                             }
137                             else
138                                 yaz_log(LOG_LOG, "Decode ItemRequest OK");
139                             if (rr->print)
140                             {
141                                 ill_ItemRequest (rr->print, &item_req, 0,
142                                     "ItemRequest");
143                                 odr_reset (rr->print);
144                             }
145                         }
146                         if (!item_req && r->which == ODR_EXTERNAL_single)
147                         {
148                             yaz_log (LOG_LOG, "Decode ILL APDU begin");
149                             odr_setbuf(rr->decode,
150                                        (char*) r->u.single_ASN1_type->buf,
151                                        r->u.single_ASN1_type->len, 0);
152                             
153                             if (!ill_APDU (rr->decode, &ill_apdu, 0, 0))
154                             {
155                                 yaz_log (LOG_LOG,
156                                     "Couldn't decode ILL APDU %s near %d",
157                                        odr_errmsg(odr_geterror(rr->decode)),
158                                        odr_offset(rr->decode));
159                                 yaz_log(LOG_LOG, "PDU dump:");
160                                 odr_dumpBER(yaz_log_file(),
161                                      (char *) r->u.single_ASN1_type->buf,
162                                      r->u.single_ASN1_type->len);
163                             }
164                             else
165                                 yaz_log(LOG_LOG, "Decode ILL APDU OK");
166                             if (rr->print)
167                             {
168                                 ill_APDU (rr->print, &ill_apdu, 0,
169                                     "ILL APDU");
170                                 odr_reset (rr->print);
171                             }
172                         }
173                     }
174                 }
175                 if (item_req)
176                 {
177                     yaz_log (LOG_LOG, "ILL protocol version = %d",
178                              *item_req->protocol_version_num);
179                 }
180             }
181 #endif
182             if (k)
183             {
184
185                 Z_External *ext = (Z_External *)
186                     odr_malloc (rr->stream, sizeof(*ext));
187                 Z_IUOriginPartToKeep *keep = (Z_IUOriginPartToKeep *)
188                     odr_malloc (rr->stream, sizeof(*keep));
189                 Z_IOTargetPart *targetPart = (Z_IOTargetPart *)
190                     odr_malloc (rr->stream, sizeof(*targetPart));
191
192                 rr->taskPackage = (Z_TaskPackage *)
193                     odr_malloc (rr->stream, sizeof(*rr->taskPackage));
194                 rr->taskPackage->packageType =
195                     odr_oiddup (rr->stream, rr->esr->packageType);
196                 rr->taskPackage->packageName = 0;
197                 rr->taskPackage->userId = 0;
198                 rr->taskPackage->retentionTime = 0;
199                 rr->taskPackage->permissions = 0;
200                 rr->taskPackage->description = 0;
201                 rr->taskPackage->targetReference = (Odr_oct *)
202                     odr_malloc (rr->stream, sizeof(Odr_oct));
203                 rr->taskPackage->targetReference->buf =
204                     (unsigned char *) odr_strdup (rr->stream, "911");
205                 rr->taskPackage->targetReference->len =
206                     rr->taskPackage->targetReference->size =
207                     strlen((char *) (rr->taskPackage->targetReference->buf));
208                 rr->taskPackage->creationDateTime = 0;
209                 rr->taskPackage->taskStatus = odr_intdup(rr->stream, 0);
210                 rr->taskPackage->packageDiagnostics = 0;
211                 rr->taskPackage->taskSpecificParameters = ext;
212
213                 ext->direct_reference =
214                     odr_oiddup (rr->stream, rr->esr->packageType);
215                 ext->indirect_reference = 0;
216                 ext->descriptor = 0;
217                 ext->which = Z_External_itemOrder;
218                 ext->u.itemOrder = (Z_ItemOrder *)
219                     odr_malloc (rr->stream, sizeof(*ext->u.update));
220                 ext->u.itemOrder->which = Z_IOItemOrder_taskPackage;
221                 ext->u.itemOrder->u.taskPackage =  (Z_IOTaskPackage *)
222                     odr_malloc (rr->stream, sizeof(Z_IOTaskPackage));
223                 ext->u.itemOrder->u.taskPackage->originPart = k;
224                 ext->u.itemOrder->u.taskPackage->targetPart = targetPart;
225
226                 targetPart->itemRequest = 0;
227                 targetPart->statusOrErrorReport = 0;
228                 targetPart->auxiliaryStatus = 0;
229             }
230         }
231     }
232     else if (rr->esr->taskSpecificParameters->which == Z_External_update)
233     {
234         Z_IUUpdate *up = rr->esr->taskSpecificParameters->u.update;
235         yaz_log (LOG_LOG, "Received DB Update");
236         if (up->which == Z_IUUpdate_esRequest)
237         {
238             Z_IUUpdateEsRequest *esRequest = up->u.esRequest;
239             Z_IUOriginPartToKeep *toKeep = esRequest->toKeep;
240             Z_IUSuppliedRecords *notToKeep = esRequest->notToKeep;
241             
242             yaz_log (LOG_LOG, "action");
243             if (toKeep->action)
244             {
245                 switch (*toKeep->action)
246                 {
247                 case Z_IUOriginPartToKeep_recordInsert:
248                     yaz_log (LOG_LOG, " recordInsert");
249                     break;
250                 case Z_IUOriginPartToKeep_recordReplace:
251                     yaz_log (LOG_LOG, " recordReplace");
252                     break;
253                 case Z_IUOriginPartToKeep_recordDelete:
254                     yaz_log (LOG_LOG, " recordDelete");
255                     break;
256                 case Z_IUOriginPartToKeep_elementUpdate:
257                     yaz_log (LOG_LOG, " elementUpdate");
258                     break;
259                 case Z_IUOriginPartToKeep_specialUpdate:
260                     yaz_log (LOG_LOG, " specialUpdate");
261                     break;
262                 default:
263                     yaz_log (LOG_LOG, " unknown (%d)", *toKeep->action);
264                 }
265             }
266             if (toKeep->databaseName)
267             {
268                 yaz_log (LOG_LOG, "database: %s", toKeep->databaseName);
269                 if (!strcmp(toKeep->databaseName, "fault"))
270                 {
271                     rr->errcode = 109;
272                     rr->errstring = toKeep->databaseName;
273                 }
274                 if (!strcmp(toKeep->databaseName, "accept"))
275                     rr->errcode = -1;
276             }
277             if (toKeep)
278             {
279                 Z_External *ext = (Z_External *)
280                     odr_malloc (rr->stream, sizeof(*ext));
281                 Z_IUOriginPartToKeep *keep = (Z_IUOriginPartToKeep *)
282                     odr_malloc (rr->stream, sizeof(*keep));
283                 Z_IUTargetPart *targetPart = (Z_IUTargetPart *)
284                     odr_malloc (rr->stream, sizeof(*targetPart));
285
286                 rr->taskPackage = (Z_TaskPackage *)
287                     odr_malloc (rr->stream, sizeof(*rr->taskPackage));
288                 rr->taskPackage->packageType =
289                     odr_oiddup (rr->stream, rr->esr->packageType);
290                 rr->taskPackage->packageName = 0;
291                 rr->taskPackage->userId = 0;
292                 rr->taskPackage->retentionTime = 0;
293                 rr->taskPackage->permissions = 0;
294                 rr->taskPackage->description = 0;
295                 rr->taskPackage->targetReference = (Odr_oct *)
296                     odr_malloc (rr->stream, sizeof(Odr_oct));
297                 rr->taskPackage->targetReference->buf =
298                     (unsigned char *) odr_strdup (rr->stream, "123");
299                 rr->taskPackage->targetReference->len =
300                     rr->taskPackage->targetReference->size =
301                     strlen((char *) (rr->taskPackage->targetReference->buf));
302                 rr->taskPackage->creationDateTime = 0;
303                 rr->taskPackage->taskStatus = odr_intdup(rr->stream, 0);
304                 rr->taskPackage->packageDiagnostics = 0;
305                 rr->taskPackage->taskSpecificParameters = ext;
306
307                 ext->direct_reference =
308                     odr_oiddup (rr->stream, rr->esr->packageType);
309                 ext->indirect_reference = 0;
310                 ext->descriptor = 0;
311                 ext->which = Z_External_update;
312                 ext->u.update = (Z_IUUpdate *)
313                     odr_malloc (rr->stream, sizeof(*ext->u.update));
314                 ext->u.update->which = Z_IUUpdate_taskPackage;
315                 ext->u.update->u.taskPackage =  (Z_IUUpdateTaskPackage *)
316                     odr_malloc (rr->stream, sizeof(Z_IUUpdateTaskPackage));
317                 ext->u.update->u.taskPackage->originPart = keep;
318                 ext->u.update->u.taskPackage->targetPart = targetPart;
319
320                 keep->action = (int *) odr_malloc (rr->stream, sizeof(int));
321                 *keep->action = *toKeep->action;
322                 keep->databaseName =
323                     odr_strdup (rr->stream, toKeep->databaseName);
324                 keep->schema = 0;
325                 keep->elementSetName = 0;
326                 keep->actionQualifier = 0;
327
328                 targetPart->updateStatus = odr_intdup (rr->stream, 1);
329                 targetPart->num_globalDiagnostics = 0;
330                 targetPart->globalDiagnostics = (Z_DiagRec **) odr_nullval();
331                 targetPart->num_taskPackageRecords = 1;
332                 targetPart->taskPackageRecords = 
333                     (Z_IUTaskPackageRecordStructure **)
334                     odr_malloc (rr->stream,
335                                 sizeof(Z_IUTaskPackageRecordStructure *));
336                 targetPart->taskPackageRecords[0] =
337                     (Z_IUTaskPackageRecordStructure *)
338                     odr_malloc (rr->stream,
339                                 sizeof(Z_IUTaskPackageRecordStructure));
340                 
341                 targetPart->taskPackageRecords[0]->which =
342                     Z_IUTaskPackageRecordStructure_record;
343                 targetPart->taskPackageRecords[0]->u.record = 
344                     z_ext_record (rr->stream, VAL_SUTRS, "test", 4);
345                 targetPart->taskPackageRecords[0]->correlationInfo = 0; 
346                 targetPart->taskPackageRecords[0]->recordStatus =
347                     odr_intdup (rr->stream,
348                                 Z_IUTaskPackageRecordStructure_success);  
349                 targetPart->taskPackageRecords[0]->num_supplementalDiagnostics
350                     = 0;
351
352                 targetPart->taskPackageRecords[0]->supplementalDiagnostics = 0;
353             }
354             if (notToKeep)
355             {
356                 int i;
357                 for (i = 0; i < notToKeep->num; i++)
358                 {
359                     Z_External *rec = notToKeep->elements[i]->record;
360
361                     if (rec->direct_reference)
362                     {
363                         struct oident *oident;
364                         oident = oid_getentbyoid(rec->direct_reference);
365                         if (oident)
366                             yaz_log (LOG_LOG, "record %d type %s", i,
367                                      oident->desc);
368                     }
369                     switch (rec->which)
370                     {
371                     case Z_External_sutrs:
372                         if (rec->u.octet_aligned->len > 170)
373                             yaz_log (LOG_LOG, "%d bytes:\n%.168s ...",
374                                      rec->u.sutrs->len,
375                                      rec->u.sutrs->buf);
376                         else
377                             yaz_log (LOG_LOG, "%d bytes:\n%s",
378                                      rec->u.sutrs->len,
379                                      rec->u.sutrs->buf);
380                         break;
381                     case Z_External_octet        :
382                         if (rec->u.octet_aligned->len > 170)
383                             yaz_log (LOG_LOG, "%d bytes:\n%.168s ...",
384                                      rec->u.octet_aligned->len,
385                                      rec->u.octet_aligned->buf);
386                         else
387                             yaz_log (LOG_LOG, "%d bytes\n%s",
388                                      rec->u.octet_aligned->len,
389                                      rec->u.octet_aligned->buf);
390                     }
391                 }
392             }
393         }
394     }
395     else
396     {
397         yaz_log (LOG_WARN, "Unknown Extended Service(%d)",
398                  rr->esr->taskSpecificParameters->which);
399         
400     }
401     return 0;
402 }
403
404 int ztest_delete (void *handle, bend_delete_rr *rr)
405 {
406     if (rr->num_setnames == 1 && !strcmp (rr->setnames[0], "1"))
407         rr->delete_status = Z_DeleteStatus_success;
408     else
409         rr->delete_status = Z_DeleteStatus_resultSetDidNotExist;
410     return 0;
411 }
412
413 /* Our sort handler really doesn't sort... */
414 int ztest_sort (void *handle, bend_sort_rr *rr)
415 {
416     rr->errcode = 0;
417     rr->sort_status = Z_SortStatus_success;
418     return 0;
419 }
420
421 static int atoin (const char *buf, int n)
422 {
423     int val = 0;
424     while (--n >= 0)
425     {
426         if (isdigit(*buf))
427             val = val*10 + (*buf - '0');
428         buf++;
429     }
430     return val;
431 }
432
433 char *marc_read(FILE *inf, ODR odr)
434 {
435     char length_str[5];
436     size_t size;
437     char *buf;
438
439     if (fread (length_str, 1, 5, inf) != 5)
440         return NULL;
441     size = atoin (length_str, 5);
442     if (size <= 6)
443         return NULL;
444     if (!(buf = (char*) odr_malloc (odr, size+1)))
445         return NULL;
446     if (fread (buf+5, 1, size-5, inf) != (size-5))
447     {
448         xfree (buf);
449         return NULL;
450     }
451     memcpy (buf, length_str, 5);
452     buf[size] = '\0';
453     return buf;
454 }
455
456 static char *dummy_database_record (int num, ODR odr)
457 {
458     FILE *inf = fopen ("dummy-records", "r");
459     char *buf = 0;
460
461     if (!inf)
462         return NULL;
463     if (num == 98) 
464     {   /* this will generate a very bad MARC record (testing only) */
465         buf = (char*) odr_malloc(odr, 2101);
466         memset(buf, '7', 2100);
467         buf[2100] = '\0';
468     }
469     else
470     {
471         /* OK, try to get proper MARC records from the file */
472         while (--num >= 0)
473         {
474             buf = marc_read (inf, odr);
475             if (!buf)
476                 break;
477         }
478     }
479     fclose(inf);
480     return buf;
481 }
482
483 static Z_GenericRecord *dummy_grs_record (int num, ODR o)
484 {
485     FILE *f = fopen("dummy-grs", "r");
486     char line[512];
487     Z_GenericRecord *r = 0;
488     int n;
489
490     if (!f)
491         return 0;
492     while (fgets(line, 512, f))
493         if (*line == '#' && sscanf(line, "#%d", &n) == 1 && n == num)
494         {
495             r = read_grs1(f, o);
496             break;
497         }
498     fclose(f);
499     return r;
500 }
501
502 int ztest_fetch(void *handle, bend_fetch_rr *r)
503 {
504     char *cp;
505     r->errstring = 0;
506     r->last_in_set = 0;
507     r->basename = "DUMMY";
508     r->output_format = r->request_format;  
509     if (r->request_format == VAL_SUTRS)
510     {
511 #if 0
512 /* this section returns a huge record (for testing non-blocking write, etc) */
513         r->len = 980000;
514         r->record = odr_malloc (r->stream, r->len);
515         memset (r->record, 'x', r->len);
516 #else
517 /* this section returns a small record */
518         char buf[100];
519
520         sprintf(buf, "This is dummy SUTRS record number %d\n", r->number);
521
522         r->len = strlen(buf);
523         r->record = (char *) odr_malloc (r->stream, r->len+1);
524         strcpy(r->record, buf);
525 #endif
526     }
527     else if (r->request_format == VAL_GRS1)
528     {
529         r->len = -1;
530         r->record = (char*) dummy_grs_record(r->number, r->stream);
531         if (!r->record)
532         {
533             r->errcode = 13;
534             return 0;
535         }
536     }
537     else if ((cp = dummy_database_record(r->number, r->stream)))
538     {
539         r->len = strlen(cp);
540         r->record = cp;
541         r->output_format = VAL_USMARC;
542     }
543     else
544     {
545         r->errcode = 13;
546         return 0;
547     }
548     r->errcode = 0;
549     return 0;
550 }
551
552 /*
553  * silly dummy-scan what reads words from a file.
554  */
555 int ztest_scan(void *handle, bend_scan_rr *q)
556 {
557     static FILE *f = 0;
558     static struct scan_entry list[200];
559     static char entries[200][80];
560     int hits[200];
561     char term[80], *p;
562     int i, pos;
563     int term_position_req = q->term_position;
564     int num_entries_req = q->num_entries;
565
566     q->errcode = 0;
567     q->errstring = 0;
568     q->entries = list;
569     q->status = BEND_SCAN_SUCCESS;
570     if (!f && !(f = fopen("dummy-words", "r")))
571     {
572         perror("dummy-words");
573         exit(1);
574     }
575     if (q->term->term->which != Z_Term_general)
576     {
577         q->errcode = 229; /* unsupported term type */
578         return 0;
579     }
580     if (*q->step_size != 0)
581     {
582         q->errcode = 205; /*Only zero step size supported for Scan */
583         return 0;
584     }
585     if (q->term->term->u.general->len >= 80)
586     {
587         q->errcode = 11; /* term too long */
588         return 0;
589     }
590     if (q->num_entries > 200)
591     {
592         q->errcode = 31;
593         return 0;
594     }
595     memcpy(term, q->term->term->u.general->buf, q->term->term->u.general->len);
596     term[q->term->term->u.general->len] = '\0';
597     for (p = term; *p; p++)
598         if (islower(*p))
599             *p = toupper(*p);
600
601     fseek(f, 0, SEEK_SET);
602     q->num_entries = 0;
603
604     for (i = 0, pos = 0; fscanf(f, " %79[^:]:%d", entries[pos], &hits[pos]) == 2;
605         i++, pos < 199 ? pos++ : (pos = 0))
606     {
607         if (!q->num_entries && strcmp(entries[pos], term) >= 0) /* s-point fnd */
608         {
609             if ((q->term_position = term_position_req) > i + 1)
610             {
611                 q->term_position = i + 1;
612                 q->status = BEND_SCAN_PARTIAL;
613             }
614             for (; q->num_entries < q->term_position; q->num_entries++)
615             {
616                 int po;
617
618                 po = pos - q->term_position + q->num_entries+1; /* find pos */
619                 if (po < 0)
620                     po += 200;
621
622                 if (!strcmp (term, "SD") && q->num_entries == 2)
623                 {
624                     list[q->num_entries].term = entries[pos];
625                     list[q->num_entries].occurrences = -1;
626                     list[q->num_entries].errcode = 233;
627                     list[q->num_entries].errstring = "SD for Scan Term";
628                 }
629                 else
630                 {
631                     list[q->num_entries].term = entries[po];
632                     list[q->num_entries].occurrences = hits[po];
633                 }
634             }
635         }
636         else if (q->num_entries)
637         {
638             list[q->num_entries].term = entries[pos];
639             list[q->num_entries].occurrences = hits[pos];
640             q->num_entries++;
641         }
642         if (q->num_entries >= num_entries_req)
643             break;
644     }
645     if (feof(f))
646         q->status = BEND_SCAN_PARTIAL;
647     return 0;
648 }
649
650 bend_initresult *bend_init(bend_initrequest *q)
651 {
652     bend_initresult *r = (bend_initresult *) odr_malloc (q->stream, sizeof(*r));
653     int *counter = (int *) xmalloc (sizeof(int));
654
655     *counter = 0;
656     r->errcode = 0;
657     r->errstring = 0;
658     r->handle = counter;         /* user handle, in this case a simple int */
659     q->bend_sort = ztest_sort;              /* register sort handler */
660     q->bend_search = ztest_search;          /* register search handler */
661     q->bend_present = ztest_present;        /* register present handle */
662     q->bend_esrequest = ztest_esrequest;
663     q->bend_delete = ztest_delete;
664     q->bend_fetch = ztest_fetch;
665     q->bend_scan = ztest_scan;
666     return r;
667 }
668
669 void bend_close(void *handle)
670 {
671     xfree (handle);              /* release our user-defined handle */
672     return;
673 }
674
675 int main(int argc, char **argv)
676 {
677     return statserv_main(argc, argv, bend_init, bend_close);
678 }