Cosmetic.
[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.31  1995-06-06 08:15:37  quinn
8  * Cosmetic.
9  *
10  * Revision 1.30  1995/06/05  10:53:32  quinn
11  * Added a better SCAN.
12  *
13  * Revision 1.29  1995/06/01  11:25:03  quinn
14  * Smallish.
15  *
16  * Revision 1.28  1995/06/01  11:21:01  quinn
17  * Attempting to fix a bug in pack-records. replaced break with continue
18  * for large records, according to standard.
19  *
20  * Revision 1.27  1995/05/29  08:12:06  quinn
21  * Moved oid to util
22  *
23  * Revision 1.26  1995/05/18  13:02:12  quinn
24  * Smallish.
25  *
26  * Revision 1.25  1995/05/17  08:42:26  quinn
27  * Transfer auth info to backend. Allow backend to reject init gracefully.
28  *
29  * Revision 1.24  1995/05/16  08:51:04  quinn
30  * License, documentation, and memory fixes
31  *
32  * Revision 1.23  1995/05/15  13:25:10  quinn
33  * Fixed memory bug.
34  *
35  * Revision 1.22  1995/05/15  11:56:39  quinn
36  * Asynchronous facilities. Restructuring of seshigh code.
37  *
38  * Revision 1.21  1995/05/02  08:53:19  quinn
39  * Trying in vain to fix comm with ISODE
40  *
41  * Revision 1.20  1995/04/20  15:13:00  quinn
42  * Cosmetic
43  *
44  * Revision 1.19  1995/04/18  08:15:34  quinn
45  * Added dynamic memory allocation on encoding (whew). Code is now somewhat
46  * neater. We'll make the same change for decoding one day.
47  *
48  * Revision 1.18  1995/04/17  11:28:25  quinn
49  * Smallish
50  *
51  * Revision 1.17  1995/04/10  10:23:36  quinn
52  * Some work to add scan and other things.
53  *
54  * Revision 1.16  1995/03/31  09:18:55  quinn
55  * Added logging.
56  *
57  * Revision 1.15  1995/03/30  14:03:23  quinn
58  * Added RFC1006 as separate library
59  *
60  * Revision 1.14  1995/03/30  12:18:17  quinn
61  * Fixed bug.
62  *
63  * Revision 1.13  1995/03/30  09:09:24  quinn
64  * Added state-handle and some support for asynchronous activities.
65  *
66  * Revision 1.12  1995/03/29  15:40:16  quinn
67  * Ongoing work. Statserv is now dynamic by default
68  *
69  * Revision 1.11  1995/03/28  09:16:21  quinn
70  * Added record packing to the search request
71  *
72  * Revision 1.10  1995/03/27  08:34:24  quinn
73  * Added dynamic server functionality.
74  * Released bindings to session.c (is now redundant)
75  *
76  * Revision 1.9  1995/03/22  15:01:26  quinn
77  * Adjusting record packing.
78  *
79  * Revision 1.8  1995/03/22  10:13:21  quinn
80  * Working on record packer
81  *
82  * Revision 1.7  1995/03/21  15:53:31  quinn
83  * Little changes.
84  *
85  * Revision 1.6  1995/03/21  12:30:09  quinn
86  * Beginning to add support for record packing.
87  *
88  * Revision 1.5  1995/03/17  10:44:13  quinn
89  * Added catch of null-string in makediagrec
90  *
91  * Revision 1.4  1995/03/17  10:18:08  quinn
92  * Added memory management.
93  *
94  * Revision 1.3  1995/03/16  17:42:39  quinn
95  * Little changes
96  *
97  * Revision 1.2  1995/03/16  13:29:01  quinn
98  * Partitioned server.
99  *
100  * Revision 1.1  1995/03/15  16:02:10  quinn
101  * Modded session.c to seshigh.c
102  *
103  */
104
105 /*
106  * Frontend server logic.
107  *
108  * This code receives incoming APDUs, and handles client requests by means
109  * of the backend API.
110  *
111  * Some of the code is getting quite involved, compared to simpler servers -
112  * primarily because it is asynchronous both in the communication with
113  * the user and the backend. We think the complexity will pay off in
114  * the form of greater flexibility when more asynchronous facilities
115  * are implemented.
116  *
117  * Memory management has become somewhat involved. In the simple case, where
118  * only one PDU is pending at a time, it will simply reuse the same memory,
119  * once it has found its working size. When we enable multiple concurrent
120  * operations, perhaps even with multiple parallel calls to the backend, it
121  * will maintain a pool of buffers for encoding and decoding, trying to
122  * minimize memory allocation/deallocation during normal operation.
123  *
124  * TODOs include (and will be done in order of public interest):
125  * 
126  * Support for EXPLAIN - provide simple meta-database system.
127  * Support for access control.
128  * Support for resource control.
129  * Support for extended services - primarily Item Order.
130  * Rest of Z39.50-1994
131  *
132  */
133
134 #include <stdlib.h>
135 #include <stdio.h>
136 #include <unistd.h>
137 #include <assert.h>
138
139 #include <dmalloc.h>
140 #include <comstack.h>
141 #include <eventl.h>
142 #include <session.h>
143 #include <proto.h>
144 #include <oid.h>
145 #include <log.h>
146 #include <statserv.h>
147 #include "../version.h"
148
149 #include <backend.h>
150
151 static int process_request(association *assoc);
152 void backend_response(IOCHAN i, int event);
153 static int process_response(association *assoc, request *req, Z_APDU *res);
154 static Z_APDU *process_initRequest(association *assoc, request *reqb);
155 static Z_APDU *process_searchRequest(association *assoc, request *reqb,
156     int *fd);
157 static Z_APDU *response_searchRequest(association *assoc, request *reqb,
158     bend_searchresult *bsrt, int *fd);
159 static Z_APDU *process_presentRequest(association *assoc, request *reqb,
160     int *fd);
161 static Z_APDU *process_scanRequest(association *assoc, request *reqb, int *fd);
162
163 static FILE *apduf = 0; /* for use in static mode */
164 static statserv_options_block *control_block = 0;
165
166 /*
167  * Create and initialize a new association-handle.
168  *  channel  : iochannel for the current line.
169  *  link     : communications channel.
170  * Returns: 0 or a new association handle.
171  */
172 association *create_association(IOCHAN channel, COMSTACK link)
173 {
174     association *new;
175
176     if (!control_block)
177         control_block = statserv_getcontrol();
178     if (!(new = malloc(sizeof(*new))))
179         return 0;
180     new->client_chan = channel;
181     new->client_link = link;
182     if (!(new->decode = odr_createmem(ODR_DECODE)) ||
183         !(new->encode = odr_createmem(ODR_ENCODE)))
184         return 0;
185     if (*control_block->apdufile)
186     {
187         char filename[256];
188         FILE *f;
189
190         strcpy(filename, control_block->apdufile);
191         if (!(new->print = odr_createmem(ODR_PRINT)))
192             return 0;
193         if (*control_block->apdufile != '-')
194         {
195             strcpy(filename, control_block->apdufile);
196             if (!control_block->dynamic)
197             {
198                 if (!apduf)
199                 {
200                     if (!(apduf = fopen(filename, "w")))
201                     {
202                         logf(LOG_WARN|LOG_ERRNO, "%s", filename);
203                         return 0;
204                     }
205                     setvbuf(apduf, 0, _IONBF, 0);
206                 }
207                 f = apduf;
208             }
209             else 
210             {
211                 sprintf(filename + strlen(filename), ".%d", getpid());
212                 if (!(f = fopen(filename, "w")))
213                 {
214                     logf(LOG_WARN|LOG_ERRNO, "%s", filename);
215                     return 0;
216                 }
217                 setvbuf(f, 0, _IONBF, 0);
218             }
219             odr_setprint(new->print, f);
220         }
221     }
222     else
223         new->print = 0;
224     new->input_buffer = 0;
225     new->input_buffer_len = 0;
226     new->backend = 0;
227     new->rejected = 0;
228     request_initq(&new->incoming);
229     request_initq(&new->outgoing);
230     new->proto = cs_getproto(link);
231     return new;
232 }
233
234 /*
235  * Free association and release resources.
236  */
237 void destroy_association(association *h)
238 {
239     odr_destroy(h->decode);
240     odr_destroy(h->encode);
241     if (h->print)
242         odr_destroy(h->print);
243     if (h->input_buffer)
244         free(h->input_buffer);
245     if (h->backend)
246         bend_close(h->backend);
247     while (request_deq(&h->incoming));
248     while (request_deq(&h->outgoing));
249     free(h);
250 }
251
252 /*
253  * This is where PDUs from the client are read and the further
254  * processing is initiated. Flow of control moves down through the
255  * various process_* functions below, until the encoded result comes back up
256  * to the output handler in here.
257  * 
258  *  h     : the I/O channel that has an outstanding event.
259  *  event : the current outstanding event.
260  */
261 void ir_session(IOCHAN h, int event)
262 {
263     int res;
264     association *assoc = iochan_getdata(h);
265     COMSTACK conn = assoc->client_link;
266     request *req;
267
268     assert(h && conn && assoc);
269     if (event & EVENT_INPUT || event & EVENT_WORK) /* input */
270     {
271         if (event & EVENT_INPUT)
272         {
273             logf(LOG_DEBUG, "ir_session (input)");
274             assert(assoc && conn);
275             /* We aren't speaking to this fellow */
276             if (assoc->rejected)
277             {
278                 logf(LOG_LOG, "Closed connection after reject");
279                 cs_close(conn);
280                 destroy_association(assoc);
281                 iochan_destroy(h);
282                 return;
283             }
284             if ((res = cs_get(conn, &assoc->input_buffer,
285                 &assoc->input_buffer_len)) <= 0)
286             {
287                 logf(LOG_LOG, "Connection closed by client");
288                 cs_close(conn);
289                 destroy_association(assoc);
290                 iochan_destroy(h);
291                 return;
292             }
293             else if (res == 1) /* incomplete read - wait for more  */
294                 return;
295             if (cs_more(conn)) /* more stuff - call us again later, please */
296                 iochan_setevent(h, EVENT_INPUT);
297                 
298             /* we got a complete PDU. Let's decode it */
299             logf(LOG_DEBUG, "Got PDU, %d bytes", res);
300             req = request_get(); /* get a new request structure */
301             odr_reset(assoc->decode);
302             odr_setbuf(assoc->decode, assoc->input_buffer, res, 0);
303             if (!z_APDU(assoc->decode, &req->request, 0))
304             {
305                 logf(LOG_WARN, "ODR error: %s",
306                     odr_errlist[odr_geterror(assoc->decode)]);
307                 cs_close(conn);
308                 destroy_association(assoc);
309                 iochan_destroy(h);
310                 return;
311             }
312             req->request_mem = odr_extract_mem(assoc->decode);
313             if (assoc->print && !z_APDU(assoc->print, &req->request, 0))
314             {
315                 logf(LOG_WARN, "ODR print error: %s", 
316                     odr_errlist[odr_geterror(assoc->print)]);
317                 odr_reset(assoc->print);
318             }
319             request_enq(&assoc->incoming, req);
320         }
321
322         /* can we do something yet? */
323         req = request_head(&assoc->incoming);
324         if (req->state == REQUEST_IDLE)
325             if (process_request(assoc) < 0)
326             {
327                 cs_close(conn);
328                 destroy_association(assoc);
329                 iochan_destroy(h);
330             }
331     }
332     if (event & EVENT_OUTPUT)
333     {
334         request *req = request_head(&assoc->outgoing);
335
336         logf(LOG_DEBUG, "ir_session (output)");
337         req->state = REQUEST_PENDING;
338         switch (res = cs_put(conn, req->response, req->len_response))
339         {
340             case -1:
341                 logf(LOG_LOG, "Connection closed by client");
342                 cs_close(conn);
343                 destroy_association(assoc);
344                 iochan_destroy(h);
345                 break;
346             case 0: /* all sent - release the request structure */
347                 logf(LOG_DEBUG, "Wrote PDU, %d bytes", req->len_response);
348                 odr_release_mem(req->request_mem);
349                 request_deq(&assoc->outgoing);
350                 request_release(req);
351                 if (!request_head(&assoc->outgoing))
352                     iochan_clearflag(h, EVENT_OUTPUT);
353                 break;
354             /* value of 1 -- partial send -- is simply ignored */
355         }
356     }
357     if (event & EVENT_EXCEPT)
358     {
359         logf(LOG_DEBUG, "ir_session (exception)");
360         cs_close(conn);
361         destroy_association(assoc);
362         iochan_destroy(h);
363     }
364 }
365
366 /*
367  * Initiate request processing.
368  */
369 static int process_request(association *assoc)
370 {
371     request *req = request_head(&assoc->incoming);
372     int fd = -1;
373     Z_APDU *res;
374
375     logf(LOG_DEBUG, "process_request");
376     assert(req && req->state == REQUEST_IDLE);
377     switch (req->request->which)
378     {
379         case Z_APDU_initRequest:
380             res = process_initRequest(assoc, req); break;
381         case Z_APDU_searchRequest:
382             res = process_searchRequest(assoc, req, &fd); break;
383         case Z_APDU_presentRequest:
384             res = process_presentRequest(assoc, req, &fd); break;
385         case Z_APDU_scanRequest:
386             res = process_scanRequest(assoc, req, &fd); break;
387         default:
388             logf(LOG_WARN, "Bad APDU received");
389             return -1;
390     }
391     if (res)
392     {
393         logf(LOG_DEBUG, "  result immediately available");
394         return process_response(assoc, req, res);
395     }
396     else if (fd < 0)
397     {
398         logf(LOG_WARN, "   bad result");
399         return -1;
400     }
401     else /* no result yet - one will be provided later */
402     {
403         IOCHAN chan;
404
405         /* Set up an I/O handler for the fd supplied by the backend */
406
407         logf(LOG_DEBUG, "   establishing handler for result");
408         req->state = REQUEST_PENDING;
409         if (!(chan = iochan_create(fd, backend_response, EVENT_INPUT)))
410             abort();
411         iochan_setdata(chan, assoc);
412         return 0;
413     }
414 }
415
416 /*
417  * Handle message from the backend.
418  */
419 void backend_response(IOCHAN i, int event)
420 {
421     association *assoc = iochan_getdata(i);
422     request *req = request_head(&assoc->incoming);
423     Z_APDU *res;
424     int fd;
425
426     logf(LOG_DEBUG, "backend_response");
427     assert(assoc && req && req->state != REQUEST_IDLE);
428     /* determine what it is we're waiting for */
429     switch (req->request->which)
430     {
431         case Z_APDU_searchRequest:
432             res = response_searchRequest(assoc, req, 0, &fd); break;
433 #if 0
434         case Z_APDU_presentRequest:
435             res = response_presentRequest(assoc, req, 0, &fd); break;
436         case Z_APDU_scanRequest:
437             res = response_scanRequest(assoc, req, 0, &fd); break;
438 #endif
439         default:
440             logf(LOG_WARN, "Serious programmer's lapse or bug");
441             abort();
442     }
443     if ((res && process_response(assoc, req, res) < 0) || fd < 0)
444     {
445         logf(LOG_LOG, "Fatal error when talking to backend");
446         cs_close(assoc->client_link);
447         destroy_association(assoc);
448         iochan_destroy(assoc->client_chan);
449         iochan_destroy(i);
450         return;
451     }
452     else if (!res) /* no result yet - try again later */
453     {
454         logf(LOG_DEBUG, "   no result yet");
455         iochan_setfd(i, fd); /* in case fd has changed */
456     }
457 }
458
459 /*
460  * Encode response, and transfer the request structure to the outgoing queue.
461  */
462 static int process_response(association *assoc, request *req, Z_APDU *res)
463 {
464     odr_setbuf(assoc->encode, req->response, req->size_response, 1);
465     if (!z_APDU(assoc->encode, &res, 0))
466     {
467         logf(LOG_WARN, "ODR error when encoding response: %s",
468             odr_errlist[odr_geterror(assoc->decode)]);
469         return -1;
470     }
471     req->response = odr_getbuf(assoc->encode, &req->len_response,
472         &req->size_response);
473     odr_setbuf(assoc->encode, 0, 0, 0); /* don't free if we abort later */
474     odr_reset(assoc->encode);
475     if (assoc->print && !z_APDU(assoc->print, &res, 0))
476     {
477         logf(LOG_WARN, "ODR print error: %s", 
478             odr_errlist[odr_geterror(assoc->print)]);
479         odr_reset(assoc->print);
480     }
481     /* change this when we make the backend reentrant */
482     assert(req == request_head(&assoc->incoming));
483     req->state = REQUEST_IDLE;
484     request_deq(&assoc->incoming);
485     request_enq(&assoc->outgoing, req);
486     /* turn the work over to the ir_session handler */
487     iochan_setflag(assoc->client_chan, EVENT_OUTPUT);
488     /* Is there more work to be done? give that to the input handler too */
489     if (request_head(&assoc->incoming))
490         iochan_setevent(assoc->client_chan, EVENT_WORK);
491     return 0;
492 }
493
494 /*
495  * Handle init request.
496  * At the moment, we don't check the protocol version or the options
497  * anywhere else in the code - we just try not to do anything that would
498  * break a naive client. We'll toss 'em into the association block when
499  * we need them there.
500  */
501 static Z_APDU *process_initRequest(association *assoc, request *reqb)
502 {
503     Z_InitRequest *req = reqb->request->u.initRequest;
504     Z_APDU *apdu = zget_APDU(assoc->encode, Z_APDU_initResponse);
505     Z_InitResponse *resp = apdu->u.initResponse;
506     bend_initrequest binitreq;
507     bend_initresult *binitres;
508
509     logf(LOG_LOG, "Got initRequest");
510     if (req->implementationId)
511         logf(LOG_LOG, "Id:        %s", req->implementationId);
512     if (req->implementationName)
513         logf(LOG_LOG, "Name:      %s", req->implementationName);
514     if (req->implementationVersion)
515         logf(LOG_LOG, "Version:   %s", req->implementationVersion);
516
517     binitreq.configname = "default-config";
518     binitreq.auth = req->idAuthentication;
519     if (!(binitres = bend_init(&binitreq)))
520     {
521         logf(LOG_WARN, "Bad response from backend.");
522         return 0;
523     }
524
525     assoc->backend = binitres->handle;
526     resp->referenceId = req->referenceId;
527     /* let's tell the client what we can do */
528     if (ODR_MASK_GET(req->options, Z_Options_search))
529         ODR_MASK_SET(resp->options, Z_Options_search);
530     if (ODR_MASK_GET(req->options, Z_Options_present))
531         ODR_MASK_SET(resp->options, Z_Options_present);
532 #if 0
533     if (ODR_MASK_GET(req->options, Z_Options_delSet))
534         ODR_MASK_SET(&options, Z_Options_delSet);
535 #endif
536     if (ODR_MASK_GET(req->options, Z_Options_namedResultSets))
537         ODR_MASK_SET(resp->options, Z_Options_namedResultSets);
538     if (ODR_MASK_GET(req->options, Z_Options_scan))
539         ODR_MASK_SET(resp->options, Z_Options_scan);
540     if (ODR_MASK_GET(req->options, Z_Options_concurrentOperations))
541         ODR_MASK_SET(resp->options, Z_Options_concurrentOperations);
542
543     if (ODR_MASK_GET(req->protocolVersion, Z_ProtocolVersion_1))
544         ODR_MASK_SET(resp->protocolVersion, Z_ProtocolVersion_1);
545     if (ODR_MASK_GET(req->protocolVersion, Z_ProtocolVersion_2))
546         ODR_MASK_SET(resp->protocolVersion, Z_ProtocolVersion_2);
547     assoc->maximumRecordSize = *req->maximumRecordSize;
548     if (assoc->maximumRecordSize > control_block->maxrecordsize)
549         assoc->maximumRecordSize = control_block->maxrecordsize;
550     assoc->preferredMessageSize = *req->preferredMessageSize;
551     if (assoc->preferredMessageSize > assoc->maximumRecordSize)
552         assoc->preferredMessageSize = assoc->maximumRecordSize;
553     resp->preferredMessageSize = &assoc->preferredMessageSize;
554     resp->maximumRecordSize = &assoc->maximumRecordSize;
555     resp->implementationName = "Index Data/YAZ Generic Frontend Server";
556     if (binitres->errcode)
557     {
558         logf(LOG_LOG, "Connection rejected by backend.");
559         *resp->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     logf(LOG_LOG, "Got scanrequest");
935     apdu.which = Z_APDU_scanResponse;
936     apdu.u.scanResponse = &res;
937     res.referenceId = req->referenceId;
938     res.stepSize = 0;
939     res.scanStatus = &scanStatus;
940     res.numberOfEntriesReturned = &numberOfEntriesReturned;
941     res.positionOfTerm = 0;
942     res.entries = &ents;
943     ents.which = Z_ListEntries_nonSurrogateDiagnostics;
944     res.attributeSet = 0;
945
946     if (req->attributeSet && (!(attent = oid_getentbyoid(req->attributeSet)) ||
947         attent->class != CLASS_ATTSET || attent->value != VAL_BIB1))
948         ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto, 121, 0);
949     else if (req->stepSize && *req->stepSize > 0)
950         ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto, 205, 0);
951     else
952     {
953         srq.num_bases = req->num_databaseNames;
954         srq.basenames = req->databaseNames;
955         srq.num_entries = *req->numberOfTermsRequested;
956         srq.term = req->termListAndStartPoint;
957         srq.term_position = req->preferredPositionInResponse ?
958             *req->preferredPositionInResponse : 1;
959         if (req->termListAndStartPoint->term->which == Z_Term_general)
960             logf(LOG_DEBUG, "  term: %.*s",
961                 req->termListAndStartPoint->term->u.general->len,
962                 req->termListAndStartPoint->term->u.general->buf);
963         if (!(srs = bend_scan(assoc->backend, &srq, 0)))
964             ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto, 2, 0);
965         else if (srs->errcode)
966             ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto,
967                 srs->errcode, srs->errstring);
968         else
969         {
970             int i;
971             static Z_Entries list;
972
973             if (srs->status == BEND_SCAN_PARTIAL)
974                 scanStatus = Z_Scan_partial_5;
975             else
976                 scanStatus = Z_Scan_success;
977             ents.which = Z_ListEntries_entries;
978             ents.u.entries = &list;
979             list.entries = tab;
980             for (i = 0; i < srs->num_entries; i++)
981             {
982                 Z_Entry *e;
983                 Z_TermInfo *t;
984                 Odr_oct *o;
985
986                 if (i >= SCAN_MAX_ENTRIES)
987                 {
988                     scanStatus = Z_Scan_partial_4;
989                     break;
990                 }
991                 list.entries[i] = e = odr_malloc(assoc->encode, sizeof(*e));
992                 e->which = Z_Entry_termInfo;
993                 e->u.termInfo = t = odr_malloc(assoc->encode, sizeof(*t));
994                 t->suggestedAttributes = 0;
995                 t->alternativeTerm = 0;
996                 t->byAttributes = 0;
997                 t->globalOccurrences = &srs->entries[i].occurrences;
998                 t->term = odr_malloc(assoc->encode, sizeof(*t->term));
999                 t->term->which = Z_Term_general;
1000                 t->term->u.general = o = odr_malloc(assoc->encode,
1001                     sizeof(Odr_oct));
1002                 o->buf = odr_malloc(assoc->encode, o->len = o->size =
1003                     strlen(srs->entries[i].term));
1004                 memcpy(o->buf, srs->entries[i].term, o->len);
1005             }
1006             list.num_entries = i;
1007             res.numberOfEntriesReturned = &list.num_entries;
1008             res.positionOfTerm = &srs->term_position;
1009         }
1010     }
1011
1012     return &apdu;
1013 }