Smallish.
[yaz-moved-to-github.git] / server / seshigh.c
1 /*
2  * Copyright (c) 1995, Index Data
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: seshigh.c,v $
7  * Revision 1.29  1995-06-01 11:25:03  quinn
8  * Smallish.
9  *
10  * Revision 1.28  1995/06/01  11:21:01  quinn
11  * Attempting to fix a bug in pack-records. replaced break with continue
12  * for large records, according to standard.
13  *
14  * Revision 1.27  1995/05/29  08:12:06  quinn
15  * Moved oid to util
16  *
17  * Revision 1.26  1995/05/18  13:02:12  quinn
18  * Smallish.
19  *
20  * Revision 1.25  1995/05/17  08:42:26  quinn
21  * Transfer auth info to backend. Allow backend to reject init gracefully.
22  *
23  * Revision 1.24  1995/05/16  08:51:04  quinn
24  * License, documentation, and memory fixes
25  *
26  * Revision 1.23  1995/05/15  13:25:10  quinn
27  * Fixed memory bug.
28  *
29  * Revision 1.22  1995/05/15  11:56:39  quinn
30  * Asynchronous facilities. Restructuring of seshigh code.
31  *
32  * Revision 1.21  1995/05/02  08:53:19  quinn
33  * Trying in vain to fix comm with ISODE
34  *
35  * Revision 1.20  1995/04/20  15:13:00  quinn
36  * Cosmetic
37  *
38  * Revision 1.19  1995/04/18  08:15:34  quinn
39  * Added dynamic memory allocation on encoding (whew). Code is now somewhat
40  * neater. We'll make the same change for decoding one day.
41  *
42  * Revision 1.18  1995/04/17  11:28:25  quinn
43  * Smallish
44  *
45  * Revision 1.17  1995/04/10  10:23:36  quinn
46  * Some work to add scan and other things.
47  *
48  * Revision 1.16  1995/03/31  09:18:55  quinn
49  * Added logging.
50  *
51  * Revision 1.15  1995/03/30  14:03:23  quinn
52  * Added RFC1006 as separate library
53  *
54  * Revision 1.14  1995/03/30  12:18:17  quinn
55  * Fixed bug.
56  *
57  * Revision 1.13  1995/03/30  09:09:24  quinn
58  * Added state-handle and some support for asynchronous activities.
59  *
60  * Revision 1.12  1995/03/29  15:40:16  quinn
61  * Ongoing work. Statserv is now dynamic by default
62  *
63  * Revision 1.11  1995/03/28  09:16:21  quinn
64  * Added record packing to the search request
65  *
66  * Revision 1.10  1995/03/27  08:34:24  quinn
67  * Added dynamic server functionality.
68  * Released bindings to session.c (is now redundant)
69  *
70  * Revision 1.9  1995/03/22  15:01:26  quinn
71  * Adjusting record packing.
72  *
73  * Revision 1.8  1995/03/22  10:13:21  quinn
74  * Working on record packer
75  *
76  * Revision 1.7  1995/03/21  15:53:31  quinn
77  * Little changes.
78  *
79  * Revision 1.6  1995/03/21  12:30:09  quinn
80  * Beginning to add support for record packing.
81  *
82  * Revision 1.5  1995/03/17  10:44:13  quinn
83  * Added catch of null-string in makediagrec
84  *
85  * Revision 1.4  1995/03/17  10:18:08  quinn
86  * Added memory management.
87  *
88  * Revision 1.3  1995/03/16  17:42:39  quinn
89  * Little changes
90  *
91  * Revision 1.2  1995/03/16  13:29:01  quinn
92  * Partitioned server.
93  *
94  * Revision 1.1  1995/03/15  16:02:10  quinn
95  * Modded session.c to seshigh.c
96  *
97  */
98
99 /*
100  * Frontend server logic.
101  *
102  * This code receives incoming APDUs, and handles client requests by means
103  * of the backend API.
104  *
105  * Some of the code is getting quite involved, compared to simpler servers -
106  * primarily because it is asynchronous both in the communication with
107  * the user and the backend. We think the complexity will pay off in
108  * the form of greater flexibility when more asynchronous facilities
109  * are implemented.
110  *
111  * Memory management has become somewhat involved. In the simple case, where
112  * only one PDU is pending at a time, it will simply reuse the same memory,
113  * once it has found its working size. When we enable multiple concurrent
114  * operations, perhaps even with multiple parallel calls to the backend, it
115  * will maintain a pool of buffers for encoding and decoding, trying to
116  * minimize memory allocation/deallocation during normal operation.
117  *
118  * TODOs include (and will be done in order of public interest):
119  * 
120  * Support for EXPLAIN - provide simple meta-database system.
121  * Support for access control.
122  * Support for resource control.
123  * Support for extended services - primarily Item Order.
124  * Rest of Z39.50-1994
125  *
126  */
127
128 #include <stdlib.h>
129 #include <stdio.h>
130 #include <unistd.h>
131 #include <assert.h>
132
133 #include <dmalloc.h>
134 #include <comstack.h>
135 #include <eventl.h>
136 #include <session.h>
137 #include <proto.h>
138 #include <oid.h>
139 #include <log.h>
140 #include <statserv.h>
141 #include "../version.h"
142
143 #include <backend.h>
144
145 static int process_request(association *assoc);
146 void backend_response(IOCHAN i, int event);
147 static int process_response(association *assoc, request *req, Z_APDU *res);
148 static Z_APDU *process_initRequest(association *assoc, request *reqb);
149 static Z_APDU *process_searchRequest(association *assoc, request *reqb,
150     int *fd);
151 static Z_APDU *response_searchRequest(association *assoc, request *reqb,
152     bend_searchresult *bsrt, int *fd);
153 static Z_APDU *process_presentRequest(association *assoc, request *reqb,
154     int *fd);
155 static Z_APDU *process_scanRequest(association *assoc, request *reqb, int *fd);
156
157 static FILE *apduf = 0; /* for use in static mode */
158 static statserv_options_block *control_block = 0;
159
160 /*
161  * Create and initialize a new association-handle.
162  *  channel  : iochannel for the current line.
163  *  link     : communications channel.
164  * Returns: 0 or a new association handle.
165  */
166 association *create_association(IOCHAN channel, COMSTACK link)
167 {
168     association *new;
169
170     if (!control_block)
171         control_block = statserv_getcontrol();
172     if (!(new = malloc(sizeof(*new))))
173         return 0;
174     new->client_chan = channel;
175     new->client_link = link;
176     if (!(new->decode = odr_createmem(ODR_DECODE)) ||
177         !(new->encode = odr_createmem(ODR_ENCODE)))
178         return 0;
179     if (*control_block->apdufile)
180     {
181         char filename[256];
182         FILE *f;
183
184         strcpy(filename, control_block->apdufile);
185         if (!(new->print = odr_createmem(ODR_PRINT)))
186             return 0;
187         if (*control_block->apdufile != '-')
188         {
189             strcpy(filename, control_block->apdufile);
190             if (!control_block->dynamic)
191             {
192                 if (!apduf)
193                 {
194                     if (!(apduf = fopen(filename, "w")))
195                     {
196                         logf(LOG_WARN|LOG_ERRNO, "%s", filename);
197                         return 0;
198                     }
199                     setvbuf(apduf, 0, _IONBF, 0);
200                 }
201                 f = apduf;
202             }
203             else 
204             {
205                 sprintf(filename + strlen(filename), ".%d", getpid());
206                 if (!(f = fopen(filename, "w")))
207                 {
208                     logf(LOG_WARN|LOG_ERRNO, "%s", filename);
209                     return 0;
210                 }
211                 setvbuf(f, 0, _IONBF, 0);
212             }
213             odr_setprint(new->print, f);
214         }
215     }
216     else
217         new->print = 0;
218     new->input_buffer = 0;
219     new->input_buffer_len = 0;
220     new->backend = 0;
221     new->rejected = 0;
222     request_initq(&new->incoming);
223     request_initq(&new->outgoing);
224     new->proto = cs_getproto(link);
225     return new;
226 }
227
228 /*
229  * Free association and release resources.
230  */
231 void destroy_association(association *h)
232 {
233     odr_destroy(h->decode);
234     odr_destroy(h->encode);
235     if (h->print)
236         odr_destroy(h->print);
237     if (h->input_buffer)
238         free(h->input_buffer);
239     if (h->backend)
240         bend_close(h->backend);
241     while (request_deq(&h->incoming));
242     while (request_deq(&h->outgoing));
243     free(h);
244 }
245
246 /*
247  * This is where PDUs from the client are read and the further
248  * processing is initiated. Flow of control moves down through the
249  * various process_* functions below, until the encoded result comes back up
250  * to the output handler in here.
251  * 
252  *  h     : the I/O channel that has an outstanding event.
253  *  event : the current outstanding event.
254  */
255 void ir_session(IOCHAN h, int event)
256 {
257     int res;
258     association *assoc = iochan_getdata(h);
259     COMSTACK conn = assoc->client_link;
260     request *req;
261
262     assert(h && conn && assoc);
263     if (event & EVENT_INPUT || event & EVENT_WORK) /* input */
264     {
265         if (event & EVENT_INPUT)
266         {
267             logf(LOG_DEBUG, "ir_session (input)");
268             assert(assoc && conn);
269             if (assoc->rejected)
270             {
271                 logf(LOG_LOG, "Closed connection after reject");
272                 cs_close(conn);
273                 destroy_association(assoc);
274                 iochan_destroy(h);
275                 return;
276             }
277             if ((res = cs_get(conn, &assoc->input_buffer,
278                 &assoc->input_buffer_len)) <= 0)
279             {
280                 logf(LOG_LOG, "Connection closed by client");
281                 cs_close(conn);
282                 destroy_association(assoc);
283                 iochan_destroy(h);
284                 return;
285             }
286             else if (res == 1) /* incomplete read - wait for more  */
287                 return;
288             if (cs_more(conn)) /* more stuff - call us again later, please */
289                 iochan_setevent(h, EVENT_INPUT);
290                 
291             /* we got a complete PDU. Let's decode it */
292             req = request_get(); /* get a new request structure */
293             odr_reset(assoc->decode);
294             odr_setbuf(assoc->decode, assoc->input_buffer, res, 0);
295             if (!z_APDU(assoc->decode, &req->request, 0))
296             {
297                 logf(LOG_WARN, "ODR error: %s",
298                     odr_errlist[odr_geterror(assoc->decode)]);
299                 cs_close(conn);
300                 destroy_association(assoc);
301                 iochan_destroy(h);
302                 return;
303             }
304             req->request_mem = odr_extract_mem(assoc->decode);
305             if (assoc->print && !z_APDU(assoc->print, &req->request, 0))
306             {
307                 logf(LOG_WARN, "ODR print error: %s", 
308                     odr_errlist[odr_geterror(assoc->print)]);
309                 odr_reset(assoc->print);
310             }
311             request_enq(&assoc->incoming, req);
312         }
313
314         /* can we do something yet? */
315         req = request_head(&assoc->incoming);
316         if (req->state == REQUEST_IDLE)
317             if (process_request(assoc) < 0)
318             {
319                 cs_close(conn);
320                 destroy_association(assoc);
321                 iochan_destroy(h);
322             }
323     }
324     if (event & EVENT_OUTPUT)
325     {
326         request *req = request_head(&assoc->outgoing);
327
328         logf(LOG_DEBUG, "ir_session (output)");
329         req->state = REQUEST_PENDING;
330         switch (res = cs_put(conn, req->response, req->len_response))
331         {
332             case -1:
333                 logf(LOG_LOG, "Connection closed by client");
334                 cs_close(conn);
335                 destroy_association(assoc);
336                 iochan_destroy(h);
337                 break;
338             case 0: /* all sent - release the request structure */
339                 odr_release_mem(req->request_mem);
340                 request_deq(&assoc->outgoing);
341                 request_release(req);
342                 if (!request_head(&assoc->outgoing))
343                     iochan_clearflag(h, EVENT_OUTPUT);
344                 break;
345             /* value of 1 -- partial send -- is simply ignored */
346         }
347     }
348     if (event & EVENT_EXCEPT)
349     {
350         logf(LOG_DEBUG, "ir_session (exception)");
351         cs_close(conn);
352         destroy_association(assoc);
353         iochan_destroy(h);
354     }
355 }
356
357 /*
358  * Initiate request processing.
359  */
360 static int process_request(association *assoc)
361 {
362     request *req = request_head(&assoc->incoming);
363     int fd = -1;
364     Z_APDU *res;
365
366     logf(LOG_DEBUG, "process_request");
367     assert(req && req->state == REQUEST_IDLE);
368     switch (req->request->which)
369     {
370         case Z_APDU_initRequest:
371             res = process_initRequest(assoc, req); break;
372         case Z_APDU_searchRequest:
373             res = process_searchRequest(assoc, req, &fd); break;
374         case Z_APDU_presentRequest:
375             res = process_presentRequest(assoc, req, &fd); break;
376         case Z_APDU_scanRequest:
377             res = process_scanRequest(assoc, req, &fd); break;
378         default:
379             logf(LOG_WARN, "Bad APDU");
380             return -1;
381     }
382     if (res)
383     {
384         logf(LOG_DEBUG, "  result immediately available");
385         return process_response(assoc, req, res);
386     }
387     else if (fd < 0)
388     {
389         logf(LOG_WARN, "   bad result");
390         return -1;
391     }
392     else /* no result yet - one will be provided later */
393     {
394         IOCHAN chan;
395
396         /* Set up an I/O handler for the fd supplied by the backend */
397
398         logf(LOG_DEBUG, "   establishing handler for result");
399         req->state = REQUEST_PENDING;
400         if (!(chan = iochan_create(fd, backend_response, EVENT_INPUT)))
401             abort();
402         iochan_setdata(chan, assoc);
403         return 0;
404     }
405 }
406
407 /*
408  * Handle message from the backend.
409  */
410 void backend_response(IOCHAN i, int event)
411 {
412     association *assoc = iochan_getdata(i);
413     request *req = request_head(&assoc->incoming);
414     Z_APDU *res;
415     int fd;
416
417     logf(LOG_DEBUG, "backend_response");
418     assert(assoc && req && req->state != REQUEST_IDLE);
419     /* determine what it is we're waiting for */
420     switch (req->request->which)
421     {
422         case Z_APDU_searchRequest:
423             res = response_searchRequest(assoc, req, 0, &fd); break;
424 #if 0
425         case Z_APDU_presentRequest:
426             res = response_presentRequest(assoc, req, 0, &fd); break;
427         case Z_APDU_scanRequest:
428             res = response_scanRequest(assoc, req, 0, &fd); break;
429 #endif
430         default:
431             logf(LOG_WARN, "Serious programmer's lapse or bug");
432             abort();
433     }
434     if ((res && process_response(assoc, req, res) < 0) || fd < 0)
435     {
436         logf(LOG_LOG, "Fatal error when talking to backend");
437         cs_close(assoc->client_link);
438         destroy_association(assoc);
439         iochan_destroy(assoc->client_chan);
440         iochan_destroy(i);
441         return;
442     }
443     else if (!res) /* no result yet - try again later */
444     {
445         logf(LOG_DEBUG, "   no result yet");
446         iochan_setfd(i, fd); /* in case fd has changed */
447     }
448 }
449
450 /*
451  * Encode response, and transfer the request structure to the outgoing queue.
452  */
453 static int process_response(association *assoc, request *req, Z_APDU *res)
454 {
455     odr_setbuf(assoc->encode, req->response, req->size_response, 1);
456     if (!z_APDU(assoc->encode, &res, 0))
457     {
458         logf(LOG_WARN, "ODR error when encoding response: %s",
459             odr_errlist[odr_geterror(assoc->decode)]);
460         return -1;
461     }
462     req->response = odr_getbuf(assoc->encode, &req->len_response,
463         &req->size_response);
464     odr_setbuf(assoc->encode, 0, 0, 0); /* don't free if we abort later */
465     odr_reset(assoc->encode);
466     if (assoc->print && !z_APDU(assoc->print, &res, 0))
467     {
468         logf(LOG_WARN, "ODR print error: %s", 
469             odr_errlist[odr_geterror(assoc->print)]);
470         odr_reset(assoc->print);
471     }
472     /* change this when we make the backend reentrant */
473     assert(req == request_head(&assoc->incoming));
474     req->state = REQUEST_IDLE;
475     request_deq(&assoc->incoming);
476     request_enq(&assoc->outgoing, req);
477     /* turn the work over to the ir_session handler */
478     iochan_setflag(assoc->client_chan, EVENT_OUTPUT);
479     /* Is there more work to be done? give that to the input handler too */
480     if (request_head(&assoc->incoming))
481         iochan_setevent(assoc->client_chan, EVENT_WORK);
482     return 0;
483 }
484
485 /*
486  * Handle init request.
487  * At the moment, we don't check the protocol version or the options
488  * anywhere else in the code - we just don't do anything that would
489  * break a naive client.
490  */
491 static Z_APDU *process_initRequest(association *assoc, request *reqb)
492 {
493     Z_InitRequest *req = reqb->request->u.initRequest;
494     static Z_APDU apdu;
495     static Z_InitResponse resp;
496     static bool_t result = 1;
497     static Odr_bitmask options, protocolVersion;
498     bend_initrequest binitreq;
499     bend_initresult *binitres;
500
501     logf(LOG_LOG, "Got initRequest");
502     if (req->implementationId)
503         logf(LOG_LOG, "Id:        %s", req->implementationId);
504     if (req->implementationName)
505         logf(LOG_LOG, "Name:      %s", req->implementationName);
506     if (req->implementationVersion)
507         logf(LOG_LOG, "Version:   %s", req->implementationVersion);
508
509     binitreq.configname = "default-config";
510     binitreq.auth = req->idAuthentication;
511     if (!(binitres = bend_init(&binitreq)))
512     {
513         logf(LOG_WARN, "Bad response from backend.");
514         return 0;
515     }
516
517     assoc->backend = binitres->handle;
518     apdu.which = Z_APDU_initResponse;
519     apdu.u.initResponse = &resp;
520     resp.referenceId = req->referenceId;
521     /* let's tell the client what we can do */
522     ODR_MASK_ZERO(&options);
523     if (ODR_MASK_GET(req->options, Z_Options_search))
524         ODR_MASK_SET(&options, Z_Options_search);
525     if (ODR_MASK_GET(req->options, Z_Options_present))
526         ODR_MASK_SET(&options, Z_Options_present);
527 #if 0
528     if (ODR_MASK_GET(req->options, Z_Options_delSet))
529         ODR_MASK_SET(&options, Z_Options_delSet);
530 #endif
531     if (ODR_MASK_GET(req->options, Z_Options_namedResultSets))
532         ODR_MASK_SET(&options, Z_Options_namedResultSets);
533     if (ODR_MASK_GET(req->options, Z_Options_scan))
534         ODR_MASK_SET(&options, Z_Options_scan);
535     if (ODR_MASK_GET(req->options, Z_Options_concurrentOperations))
536         ODR_MASK_SET(&options, Z_Options_concurrentOperations);
537     resp.options = &options;
538
539     ODR_MASK_ZERO(&protocolVersion);
540     if (ODR_MASK_GET(req->protocolVersion, Z_ProtocolVersion_1))
541         ODR_MASK_SET(&protocolVersion, Z_ProtocolVersion_1);
542     if (ODR_MASK_GET(req->protocolVersion, Z_ProtocolVersion_2))
543         ODR_MASK_SET(&protocolVersion, Z_ProtocolVersion_2);
544     resp.protocolVersion = &protocolVersion;
545     assoc->maximumRecordSize = *req->maximumRecordSize;
546     if (assoc->maximumRecordSize > control_block->maxrecordsize)
547         assoc->maximumRecordSize = control_block->maxrecordsize;
548     assoc->preferredMessageSize = *req->preferredMessageSize;
549     if (assoc->preferredMessageSize > assoc->maximumRecordSize)
550         assoc->preferredMessageSize = assoc->maximumRecordSize;
551     resp.preferredMessageSize = &assoc->preferredMessageSize;
552     resp.maximumRecordSize = &assoc->maximumRecordSize;
553     resp.result = &result;
554     resp.implementationName = "Index Data/YAZ Generic Frontend Server";
555     resp.userInformationField = 0;
556     if (binitres->errcode)
557     {
558         logf(LOG_LOG, "Connection rejected by backend.");
559         result = 0;
560         assoc->rejected = 1;
561     }
562     return &apdu;
563 }
564
565 /*
566  * These functions should be merged.
567  */
568
569 /*
570  * nonsurrogate diagnostic record.
571  */
572 static Z_Records *diagrec(oid_proto proto, int error, char *addinfo)
573 {
574     static Z_Records rec;
575     oident bib1;
576     static Z_DiagRec dr;
577     static int err;
578
579     bib1.proto = proto;
580     bib1.class = CLASS_DIAGSET;
581     bib1.value = VAL_BIB1;
582
583     logf(LOG_DEBUG, "Diagnostic: %d -- %s", error, addinfo ? addinfo :
584         "NULL");
585     err = error;
586     rec.which = Z_Records_NSD;
587     rec.u.nonSurrogateDiagnostic = &dr;
588     dr.diagnosticSetId = oid_getoidbyent(&bib1);
589     dr.condition = &err;
590     dr.addinfo = addinfo ? addinfo : "";
591     return &rec;
592 }
593
594 /*
595  * surrogate diagnostic.
596  */
597 static Z_NamePlusRecord *surrogatediagrec(oid_proto proto, char *dbname,
598                                             int error, char *addinfo)
599 {
600     static Z_NamePlusRecord rec;
601     static Z_DiagRec dr;
602     static int err;
603     oident bib1;
604
605     bib1.proto = proto;
606     bib1.class = CLASS_DIAGSET;
607     bib1.value = VAL_BIB1;
608
609     logf(LOG_DEBUG, "SurrogateDiagnotic: %d -- %s", error, addinfo);
610     err = error;
611     rec.databaseName = dbname;
612     rec.which = Z_NamePlusRecord_surrogateDiagnostic;
613     rec.u.surrogateDiagnostic = &dr;
614     dr.diagnosticSetId = oid_getoidbyent(&bib1);
615     dr.condition = &err;
616     dr.addinfo = addinfo ? addinfo : "";
617     return &rec;
618 }
619
620 /*
621  * multiple nonsurrogate diagnostics.
622  */
623 static Z_DiagRecs *diagrecs(oid_proto proto, int error, char *addinfo)
624 {
625     static Z_DiagRecs recs;
626     static Z_DiagRec *recp[1], rec;
627     static int err;
628     oident bib1;
629
630     logf(LOG_DEBUG, "DiagRecs: %d -- %s", error, addinfo);
631     bib1.proto = proto;
632     bib1.class = CLASS_DIAGSET;
633     bib1.value = VAL_BIB1;
634
635     err = error;
636     recs.num_diagRecs = 1;
637     recs.diagRecs = recp;
638     recp[0] = &rec;
639     rec.diagnosticSetId = oid_getoidbyent(&bib1);
640     rec.condition = &err;
641     rec.addinfo = addinfo ? addinfo : "";
642     return &recs;
643 }
644
645 #define MAX_RECORDS 256
646
647 static Z_Records *pack_records(association *a, char *setname, int start,
648                                 int *num, Z_ElementSetNames *esn,
649                                 int *next, int *pres)
650 {
651     int recno, total_length = 0, toget = *num;
652     static Z_Records records;
653     static Z_NamePlusRecordList reclist;
654     static Z_NamePlusRecord *list[MAX_RECORDS];
655     oident recform;
656     Odr_oid *oid;
657
658     records.which = Z_Records_DBOSD;
659     records.u.databaseOrSurDiagnostics = &reclist;
660     reclist.num_records = 0;
661     reclist.records = list;
662     *pres = Z_PRES_SUCCESS;
663     *num = 0;
664     *next = 0;
665
666     recform.proto = a->proto;
667     recform.class = CLASS_RECSYN;
668     recform.value = VAL_USMARC;
669     if (!(oid = odr_oiddup(a->encode, oid_getoidbyent(&recform))))
670         return 0;
671
672     logf(LOG_DEBUG, "Request to pack %d+%d", start, toget);
673     logf(LOG_DEBUG, "pms=%d, mrs=%d", a->preferredMessageSize,
674         a->maximumRecordSize);
675     for (recno = start; reclist.num_records < toget; recno++)
676     {
677         bend_fetchrequest freq;
678         bend_fetchresult *fres;
679         Z_NamePlusRecord *thisrec;
680         Z_DatabaseRecord *thisext;
681
682         if (reclist.num_records == MAX_RECORDS - 1)
683         {
684             *pres = Z_PRES_PARTIAL_2;
685             break;
686         }
687         freq.setname = setname;
688         freq.number = recno;
689         if (!(fres = bend_fetch(a->backend, &freq, 0)))
690         {
691             *pres = Z_PRES_FAILURE;
692             return diagrec(a->proto, 2, "Backend interface problem");
693         }
694         /* backend should be able to signal whether error is system-wide
695            or only pertaining to current record */
696         if (fres->errcode)
697         {
698             *pres = Z_PRES_FAILURE;
699             return diagrec(a->proto, fres->errcode, fres->errstring);
700         }
701         logf(LOG_DEBUG, "  fetched record, len=%d, total=%d",
702             fres->len, total_length);
703         if (fres->len + total_length > a->preferredMessageSize)
704         {
705             /* record is small enough, really */
706             if (fres->len <= a->preferredMessageSize)
707             {
708                 logf(LOG_DEBUG, "  Dropped last normal-sized record");
709                 *pres = Z_PRES_PARTIAL_2;
710                 break;
711             }
712             /* record can only be fetched by itself */
713             if (fres->len < a->maximumRecordSize)
714             {
715                 logf(LOG_DEBUG, "  Record > prefmsgsz");
716                 if (toget > 1)
717                 {
718                     logf(LOG_DEBUG, "  Dropped it");
719                     reclist.records[reclist.num_records] =
720                          surrogatediagrec(a->proto, fres->basename, 16, 0);
721                     reclist.num_records++;
722                     total_length += 10; /* totally arbitrary */
723                     continue;
724                 }
725             }
726             else /* too big entirely */
727             {
728                 logf(LOG_DEBUG, "Record > maxrcdsz");
729                 reclist.records[reclist.num_records] =
730                     surrogatediagrec(a->proto, fres->basename, 17, 0);
731                 reclist.num_records++;
732                 total_length += 10; /* totally arbitrary */
733                 continue;
734             }
735         }
736         if (!(thisrec = odr_malloc(a->encode, sizeof(*thisrec))))
737             return 0;
738         if (!(thisrec->databaseName = odr_malloc(a->encode,
739             strlen(fres->basename) + 1)))
740             return 0;
741         strcpy(thisrec->databaseName, fres->basename);
742         thisrec->which = Z_NamePlusRecord_databaseRecord;
743         if (!(thisrec->u.databaseRecord = thisext = odr_malloc(a->encode,
744             sizeof(Z_DatabaseRecord))))
745             return 0;
746         thisext->direct_reference = oid; /* should be OID for current MARC */
747         thisext->indirect_reference = 0;
748         thisext->descriptor = 0;
749         thisext->which = ODR_EXTERNAL_octet;
750         if (!(thisext->u.octet_aligned = odr_malloc(a->encode,
751             sizeof(Odr_oct))))
752             return 0;
753         if (!(thisext->u.octet_aligned->buf = odr_malloc(a->encode, fres->len)))
754             return 0;
755         memcpy(thisext->u.octet_aligned->buf, fres->record, fres->len);
756         thisext->u.octet_aligned->len = thisext->u.octet_aligned->size =
757             fres->len;
758         reclist.records[reclist.num_records] = thisrec;
759         reclist.num_records++;
760         total_length += fres->len;
761         *next = fres->last_in_set ? 0 : recno + 1;
762     }
763     *num = reclist.num_records;
764     return &records;
765 }
766
767 static Z_APDU *process_searchRequest(association *assoc, request *reqb,
768     int *fd)
769 {
770     Z_SearchRequest *req = reqb->request->u.searchRequest;
771     bend_searchrequest bsrq;
772     bend_searchresult *bsrt;
773
774     logf(LOG_LOG, "Got SearchRequest.");
775
776     bsrq.setname = req->resultSetName;
777     bsrq.replace_set = *req->replaceIndicator;
778     bsrq.num_bases = req->num_databaseNames;
779     bsrq.basenames = req->databaseNames;
780     bsrq.query = req->query;
781
782     if (!(bsrt = bend_search(assoc->backend, &bsrq, fd)))
783         return 0;
784     return response_searchRequest(assoc, reqb, bsrt, fd);
785 }
786
787 bend_searchresult *bend_searchresponse(void *handle) {return 0;}
788
789 /*
790  * Prepare a searchresponse based on the backend results. We probably want
791  * to look at making the fetching of records nonblocking as well, but
792  * so far, we'll keep things simple.
793  * If bsrt is null, that means we're called in response to a communications
794  * event, and we'll have to get the response for ourselves.
795  */
796 static Z_APDU *response_searchRequest(association *assoc, request *reqb,
797     bend_searchresult *bsrt, int *fd)
798 {
799     Z_SearchRequest *req = reqb->request->u.searchRequest;
800     static Z_APDU apdu;
801     static Z_SearchResponse resp;
802     static int nulint = 0;
803     static bool_t sr = 1;
804     static int next = 0;
805     static int none = Z_RES_NONE;
806
807     apdu.which = Z_APDU_searchResponse;
808     apdu.u.searchResponse = &resp;
809     resp.referenceId = req->referenceId;
810     *fd = -1;
811     if (!bsrt && !(bsrt = bend_searchresponse(assoc->backend)))
812     {
813         logf(LOG_FATAL, "Bad result from backend");
814         return 0;
815     }
816     else if (bsrt->errcode)
817     {
818         resp.records = diagrec(assoc->proto, bsrt->errcode,
819             bsrt->errstring);
820         resp.resultCount = &nulint;
821         resp.numberOfRecordsReturned = &nulint;
822         resp.nextResultSetPosition = &nulint;
823         resp.searchStatus = &nulint;
824         resp.resultSetStatus = &none;
825         resp.presentStatus = 0;
826     }
827     else
828     {
829         int toget;
830         Z_ElementSetNames *setnames;
831         int presst = 0;
832
833         resp.records = 0;
834         resp.resultCount = &bsrt->hits;
835
836         /* how many records does the user agent want, then? */
837         if (bsrt->hits <= *req->smallSetUpperBound)
838         {
839             toget = bsrt->hits;
840             setnames = req->smallSetElementSetNames;
841         }
842         else if (bsrt->hits < *req->largeSetLowerBound)
843         {
844             toget = *req->mediumSetPresentNumber;
845             if (toget > bsrt->hits)
846                 toget = bsrt->hits;
847             setnames = req->mediumSetElementSetNames;
848         }
849         else
850             toget = 0;
851
852         if (toget && !resp.records)
853         {
854             resp.records = pack_records(assoc, req->resultSetName, 1,
855                 &toget, setnames, &next, &presst);
856             if (!resp.records)
857                 return 0;
858             resp.numberOfRecordsReturned = &toget;
859             resp.nextResultSetPosition = &next;
860             resp.searchStatus = &sr;
861             resp.resultSetStatus = 0;
862             resp.presentStatus = &presst;
863         }
864         else
865         {
866             resp.numberOfRecordsReturned = &nulint;
867             resp.nextResultSetPosition = &next;
868             resp.searchStatus = &sr;
869             resp.resultSetStatus = 0;
870             resp.presentStatus = 0;
871         }
872     }
873     return &apdu;
874 }
875
876 /*
877  * Maybe we got a little over-friendly when we designed bend_fetch to
878  * get only one record at a time. Some backends can optimise multiple-record
879  * fetches, and at any rate, there is some overhead involved in
880  * all that selecting and hopping around. Problem is, of course, that the
881  * frontend can't know ahead of time how many records it'll need to
882  * fill the negotiated PDU size. Annoying. Segmentation or not, Z/SR
883  * is downright lousy as a bulk data transfer protocol.
884  *
885  * To start with, we'll do the fetching of records from the backend
886  * in one operation: To save some trips in and out of the event-handler,
887  * and to simplify the interface to pack_records. At any rate, asynch
888  * operation is more fun in operations that have an unpredictable execution
889  * speed - which is normally more true for search than for present.
890  */
891 static Z_APDU *process_presentRequest(association *assoc, request *reqb,
892     int *fd)
893 {
894     Z_PresentRequest *req = reqb->request->u.presentRequest;
895     static Z_APDU apdu;
896     static Z_PresentResponse resp;
897     static int presst, next, num;
898
899     logf(LOG_LOG, "Got PresentRequest.");
900     apdu.which = Z_APDU_presentResponse;
901     apdu.u.presentResponse = &resp;
902     resp.referenceId = req->referenceId;
903
904     num = *req->numberOfRecordsRequested;
905     resp.records = pack_records(assoc, req->resultSetId,
906         *req->resultSetStartPoint, &num, req->elementSetNames, &next, &presst);
907     if (!resp.records)
908         return 0;
909     resp.numberOfRecordsReturned = &num;
910     resp.presentStatus = &presst;
911     resp.nextResultSetPosition = &next;
912
913     return &apdu;
914 }
915
916 /*
917  * Scan was implemented rather in a hurry, and with support for only the basic
918  * elements of the service in the backend API. Suggestions are welcome.
919  */
920 static Z_APDU *process_scanRequest(association *assoc, request *reqb, int *fd)
921 {
922     Z_ScanRequest *req = reqb->request->u.scanRequest;
923     static Z_APDU apdu;
924     static Z_ScanResponse res;
925     static int scanStatus = Z_Scan_failure;
926     static int numberOfEntriesReturned = 0;
927     oident *attent;
928     static Z_ListEntries ents;
929 #define SCAN_MAX_ENTRIES 200
930     static Z_Entry *tab[SCAN_MAX_ENTRIES];
931     bend_scanrequest srq;
932     bend_scanresult *srs;
933
934     apdu.which = Z_APDU_scanResponse;
935     apdu.u.scanResponse = &res;
936     res.referenceId = req->referenceId;
937     res.stepSize = 0;
938     res.scanStatus = &scanStatus;
939     res.numberOfEntriesReturned = &numberOfEntriesReturned;
940     res.positionOfTerm = 0;
941     res.entries = &ents;
942     ents.which = Z_ListEntries_nonSurrogateDiagnostics;
943     res.attributeSet = 0;
944
945     if (req->attributeSet && (!(attent = oid_getentbyoid(req->attributeSet)) ||
946         attent->class != CLASS_ATTSET || attent->value != VAL_BIB1))
947         ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto, 121, 0);
948     else if (req->stepSize && *req->stepSize > 0)
949         ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto, 205, 0);
950     else
951     {
952         srq.num_bases = req->num_databaseNames;
953         srq.basenames = req->databaseNames;
954         srq.num_entries = *req->numberOfTermsRequested;
955         srq.term = req->termListAndStartPoint;
956         srq.term_position = req->preferredPositionInResponse ?
957             *req->preferredPositionInResponse : 1;
958         if (!(srs = bend_scan(assoc->backend, &srq, 0)))
959             ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto, 2, 0);
960         else if (srs->errcode)
961             ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto,
962                 srs->errcode, srs->errstring);
963         else
964         {
965             int i;
966             static Z_Entries list;
967
968             if (srs->status == BEND_SCAN_PARTIAL)
969                 scanStatus = Z_Scan_partial_5;
970             else
971                 scanStatus = Z_Scan_success;
972             ents.which = Z_ListEntries_entries;
973             ents.u.entries = &list;
974             list.entries = tab;
975             for (i = 0; i < srs->num_entries; i++)
976             {
977                 Z_Entry *e;
978                 Z_TermInfo *t;
979                 Odr_oct *o;
980
981                 if (i >= SCAN_MAX_ENTRIES)
982                 {
983                     scanStatus = Z_Scan_partial_4;
984                     break;
985                 }
986                 list.entries[i] = e = odr_malloc(assoc->encode, sizeof(*e));
987                 e->which = Z_Entry_termInfo;
988                 e->u.termInfo = t = odr_malloc(assoc->encode, sizeof(*t));
989                 t->suggestedAttributes = 0;
990                 t->alternativeTerm = 0;
991                 t->byAttributes = 0;
992                 t->globalOccurrences = &srs->entries[i].occurrences;
993                 t->term = odr_malloc(assoc->encode, sizeof(*t->term));
994                 t->term->which = Z_Term_general;
995                 t->term->u.general = o = odr_malloc(assoc->encode,
996                     sizeof(Odr_oct));
997                 o->buf = odr_malloc(assoc->encode, o->len = o->size =
998                     strlen(srs->entries[i].term));
999                 memcpy(o->buf, srs->entries[i].term, o->len);
1000             }
1001             list.num_entries = i;
1002             res.numberOfEntriesReturned = &list.num_entries;
1003             res.positionOfTerm = &srs->term_position;
1004         }
1005     }
1006
1007     return &apdu;
1008 }