278962172683096cb4adb1e9a8d29a034799cb37
[samba.git] / source4 / heimdal / lib / base / bsearch.c
1 /*
2  * Copyright (c) 2011, Secure Endpoints Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * - Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * - Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in
14  *   the documentation and/or other materials provided with the
15  *   distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28  * OF THE POSSIBILITY OF SUCH DAMAGE.
29  *
30  */
31
32 #include "baselocl.h"
33
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #ifdef HAVE_IO_H
37 #include <io.h>
38 #endif
39 #ifdef HAVE_UNISTD_H
40 #include <unistd.h>
41 #endif
42 #include <fcntl.h>
43 #include <ctype.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #ifdef HAVE_STRINGS_H
48 #include <strings.h>
49 #endif
50 #include <errno.h>
51 #include <assert.h>
52
53 /*
54  * This file contains functions for binary searching flat text in memory
55  * and in text files where each line is a [variable length] record.
56  * Each record has a key and an optional value separated from the key by
57  * unquoted whitespace.  Whitespace in the key, and leading whitespace
58  * for the value, can be quoted with backslashes (but CR and LF must be
59  * quoted in such a way that they don't appear in the quoted result).
60  *
61  * Binary searching a tree are normally a dead simple algorithm.  It
62  * turns out that binary searching flat text with *variable* length
63  * records is... tricky.  There's no indexes to record beginning bytes,
64  * thus any index selected during the search is likely to fall in the
65  * middle of a record.  When deciding to search a left sub-tree one
66  * might fail to find the last record in that sub-tree on account of the
67  * right boundary falling in the middle of it -- the chosen solution to
68  * this makes left sub-tree searches slightly less efficient than right
69  * sub-tree searches.
70  *
71  * If binary searching flat text in memory is tricky, using block-wise
72  * I/O instead is trickier!  But it's necessary in order to support
73  * large files (which we either can't or wouldn't want to read or map
74  * into memory).  Each block we read has to be large enough that the
75  * largest record can fit in it.  And each block might start and/or end
76  * in the middle of a record.  Here it is the right sub-tree searches
77  * that are less efficient than left sub-tree searches.
78  *
79  * bsearch_common() contains the common text block binary search code.
80  *
81  * _bsearch_text() is the interface for searching in-core text.
82  * _bsearch_file() is the interface for block-wise searching files.
83  */
84
85 struct bsearch_file_handle {
86     int fd;          /* file descriptor */
87     char *cache;     /* cache bytes */
88     char *page;      /* one double-size page worth of bytes */
89     size_t file_sz;  /* file size */
90     size_t cache_sz; /* cache size */
91     size_t page_sz;  /* page size */
92 };
93
94 /* Find a new-line */
95 static const char *
96 find_line(const char *buf, size_t i, size_t right)
97 {
98     if (i == 0)
99         return &buf[i];
100     for (; i < right; i++) {
101         if (buf[i] == '\n') {
102             if ((i + 1) < right)
103                 return &buf[i + 1];
104             return NULL;
105         }
106     }
107     return NULL;
108 }
109
110 /*
111  * Common routine for binary searching text in core.
112  *
113  * Perform a binary search of a char array containing a block from a
114  * text file where each line is a record (LF and CRLF supported).  Each
115  * record consists of a key followed by an optional value separated from
116  * the key by whitespace.  Whitespace can be quoted with backslashes.
117  * It's the caller's responsibility to encode/decode keys/values if
118  * quoting is desired; newlines should be encoded such that a newline
119  * does not appear in the result.
120  *
121  * All output arguments are optional.
122  *
123  * Returns 0 if key is found, -1 if not found, or an error code such as
124  * ENOMEM in case of error.
125  *
126  * Inputs:
127  *
128  * @buf          String to search
129  * @sz           Size of string to search
130  * @key          Key string to search for
131  * @buf_is_start True if the buffer starts with a record, false if it
132  *               starts in the middle of a record or if the caller
133  *               doesn't know.
134  *
135  * Outputs:
136  *
137  * @value        Location to store a copy of the value (caller must free)
138  * @location     Record location if found else the location where the
139  *               record should be inserted (index into @buf)
140  * @cmp          Set to less than or greater than 0 to indicate that a
141  *               key not found would have fit in an earlier or later
142  *               part of a file.  Callers should use this to decide
143  *               whether to read a block to the left or to the right and
144  *               search that.
145  * @loops        Location to store a count of bisections required for
146  *               search (useful for confirming logarithmic performance)
147  */
148 static int
149 bsearch_common(const char *buf, size_t sz, const char *key,
150                int buf_is_start, char **value, size_t *location,
151                int *cmp, size_t *loops)
152 {
153     const char *linep;
154     size_t key_start, key_len; /* key string in buf */
155     size_t val_start, val_len; /* value string in buf */
156     int key_cmp = -1;
157     size_t k;
158     size_t l;    /* left side of buffer for binary search */
159     size_t r;    /* right side of buffer for binary search */
160     size_t rmax; /* right side of buffer for binary search */
161     size_t i;    /* index into buffer, typically in the middle of l and r */
162     size_t loop_count = 0;
163     int ret = -1;
164
165     if (value)
166         *value = NULL;
167     if (cmp)
168         *cmp = 0;
169     if (loops)
170         *loops = 0;
171
172     /* Binary search; file should be sorted */
173     for (l = 0, r = rmax = sz, i = sz >> 1; i >= l && i < rmax; loop_count++) {
174         heim_assert(i < sz, "invalid aname2lname db index");
175
176         /* buf[i] is likely in the middle of a line; find the next line */
177         linep = find_line(buf, i, rmax);
178         k = linep ? linep - buf : i;
179         if (linep == NULL || k >= rmax) {
180             /*
181              * No new line found to the right; search to the left then
182              * but don't change rmax (this isn't optimal, but it's
183              * simple).
184              */
185             if (i == l)
186                 break;
187             r = i;
188             i = l + ((r - l) >> 1);
189             continue;
190         }
191         i = k;
192         heim_assert(i >= l && i < rmax, "invalid aname2lname db index");
193
194         /* Got a line; check it */
195
196         /* Search for and split on unquoted whitespace */
197         val_start = 0;
198         for (key_start = i, key_len = 0, val_len = 0, k = i; k < rmax; k++) {
199             if (buf[k] == '\\') {
200                 k++;
201                 continue;
202             }
203             if (buf[k] == '\r' || buf[k] == '\n') {
204                 /* We now know where the key ends, and there's no value */
205                 key_len = k - i;
206                 break;
207             }
208             if (!isspace((unsigned char)buf[k]))
209                 continue;
210
211             while (k < rmax && isspace((unsigned char)buf[k])) {
212                 key_len = k - i;
213                 k++;
214             }
215             if (k < rmax)
216                 val_start = k;
217             /* Find end of value */
218             for (; k < rmax && buf[k] != '\0'; k++) {
219                 if (buf[k] == '\r' || buf[k] == '\n') {
220                     val_len = k - val_start;
221                     break;
222                 }
223             }
224             break;
225         }
226
227         /*
228          * The following logic is for dealing with partial buffers,
229          * which we use for block-wise binary searches of large files
230          */
231         if (key_start == 0 && !buf_is_start) {
232             /*
233              * We're at the beginning of a block that might have started
234              * in the middle of a record whose "key" might well compare
235              * as greater than the key we're looking for, so we don't
236              * bother comparing -- we know key_cmp must be -1 here.
237              */
238             key_cmp = -1;
239             break;
240         }
241         if ((val_len && buf[val_start + val_len] != '\n') ||
242             (!val_len && buf[key_start + key_len] != '\n')) {
243             /*
244              * We're at the end of a block that ends in the middle of a
245              * record whose "key" might well compare as less than the
246              * key we're looking for, so we don't bother comparing -- we
247              * know key_cmp must be >= 0 but we can't tell.  Our caller
248              * will end up reading a double-size block to handle this.
249              */
250             key_cmp = 1;
251             break;
252         }
253
254         key_cmp = strncmp(key, &buf[key_start], key_len);
255         if (key_cmp == 0 && strlen(key) != key_len)
256             key_cmp = 1;
257         if (key_cmp < 0) {
258             /* search left */
259             r = rmax = (linep - buf);
260             i = l + ((r - l) >> 1);
261             if (location)
262                 *location = key_start;
263         } else if (key_cmp > 0) {
264             /* search right */
265             if (l == i)
266                 break; /* not found */
267             l = i;
268             i = l + ((r - l) >> 1);
269             if (location)
270                 *location = val_start + val_len;
271         } else {
272             /* match! */
273             if (location)
274                 *location = key_start;
275             ret = 0;
276             if (val_len && value) {
277                 /* Avoid strndup() so we don't need libroken here yet */
278                 *value = malloc(val_len + 1);
279                 if (!*value)
280                     ret = errno;
281                 (void) memcpy(*value, &buf[val_start], val_len);
282                 (*value)[val_len] = '\0';
283             }
284             break;
285         }
286     }
287
288     if (cmp)
289         *cmp = key_cmp;
290     if (loops)
291         *loops = loop_count;
292
293     return ret;
294 }
295
296 /*
297  * Binary search a char array containing sorted text records separated
298  * by new-lines (or CRLF).  Each record consists of a key and an
299  * optional value following the key, separated from the key by unquoted
300  * whitespace.
301  *
302  * All output arguments are optional.
303  *
304  * Returns 0 if key is found, -1 if not found, or an error code such as
305  * ENOMEM in case of error.
306  *
307  * Inputs:
308  *
309  * @buf      Char array pointer
310  * @buf_sz   Size of buf
311  * @key      Key to search for
312  *
313  * Outputs:
314  *
315  * @value    Location where to put the value, if any (caller must free)
316  * @location Record location if found else the location where the record
317  *           should be inserted (index into @buf)
318  * @loops    Location where to put a number of loops (or comparisons)
319  *           needed for the search (useful for benchmarking)
320  */
321 int
322 _bsearch_text(const char *buf, size_t buf_sz, const char *key,
323                char **value, size_t *location, size_t *loops)
324 {
325     return bsearch_common(buf, buf_sz, key, 1, value, location, NULL, loops);
326 }
327
328 #define MAX_BLOCK_SIZE (1024 * 1024)
329 #define DEFAULT_MAX_FILE_SIZE (1024 * 1024)
330 /*
331  * Open a file for binary searching.  The file will be read in entirely
332  * if it is smaller than @max_sz, else a cache of @max_sz bytes will be
333  * allocated.
334  *
335  * Returns 0 on success, else an error number or -1 if the file is empty.
336  *
337  * Inputs:
338  *
339  * @fname   Name of file to open
340  * @max_sz  Maximum size of cache to allocate, in bytes (if zero, default)
341  * @page_sz Page size (must be a power of two, larger than 256, smaller
342  *          than 1MB; if zero use default)
343  * 
344  * Outputs:
345  *
346  * @bfh     Handle for use with _bsearch_file() and _bsearch_file_close()
347  * @reads   Number of reads performed
348  */
349 int
350 _bsearch_file_open(const char *fname, size_t max_sz, size_t page_sz,
351                     bsearch_file_handle *bfh, size_t *reads)
352 {
353     bsearch_file_handle new_bfh = NULL;
354     struct stat st;
355     size_t i;
356     int fd;
357     int ret;
358
359     *bfh = NULL;
360
361     if (reads)
362         *reads = 0;
363
364     fd = open(fname, O_RDONLY);
365     if (fd == -1)
366         return errno;
367
368     if (fstat(fd, &st) == -1) {
369         ret = errno;
370         goto err;
371     }
372
373     if (st.st_size == 0) {
374         ret = -1; /* no data -> no binary search */
375         goto err;
376     }
377
378     /* Validate / default arguments */
379     if (max_sz == 0)
380         max_sz = DEFAULT_MAX_FILE_SIZE;
381     for (i = page_sz; i; i >>= 1) {
382         /* Make sure page_sz is a power of two */
383         if ((i % 2) && (i >> 1)) {
384             page_sz = 0;
385             break;
386         }
387     }
388     if (page_sz == 0)
389 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
390         page_sz = st.st_blksize;
391 #else
392         page_sz = 4096;
393 #endif
394     for (i = page_sz; i; i >>= 1) {
395         /* Make sure page_sz is a power of two */
396         if ((i % 2) && (i >> 1)) {
397             /* Can't happen! Filesystems always use powers of two! */
398             page_sz = 4096;
399             break;
400         }
401     }
402     if (page_sz > MAX_BLOCK_SIZE)
403         page_sz = MAX_BLOCK_SIZE;
404
405     new_bfh = calloc(1, sizeof (*new_bfh));
406     if (new_bfh == NULL) {
407         ret = ENOMEM;
408         goto err;
409     }
410
411     new_bfh->fd = fd;
412     new_bfh->page_sz = page_sz;
413     new_bfh->file_sz = st.st_size;
414
415     if (max_sz >= st.st_size) {
416         /* Whole-file method */
417         new_bfh->cache = malloc(st.st_size + 1);
418         if (new_bfh->cache) {
419             new_bfh->cache[st.st_size] = '\0';
420             new_bfh->cache_sz = st.st_size;
421             ret = read(fd, new_bfh->cache, st.st_size);
422             if (ret < 0) {
423                 ret = errno;
424                 goto err;
425             }
426             if (ret != st.st_size) {
427                 ret = EIO; /* XXX ??? */
428                 goto err;
429             }
430             if (reads)
431                 *reads = 1;
432             (void) close(fd);
433             new_bfh->fd = -1;
434             *bfh = new_bfh;
435             return 0;
436         }
437     }
438
439     /* Block-size method, or above malloc() failed */
440     new_bfh->page = malloc(new_bfh->page_sz << 1);
441     if (new_bfh->page == NULL) {
442         /* Can't even allocate a single double-size page! */
443         ret = ENOMEM;
444         goto err;
445     }
446
447     new_bfh->cache_sz = max_sz < st.st_size ? max_sz : st.st_size;
448     new_bfh->cache = malloc(new_bfh->cache_sz);
449     *bfh = new_bfh;
450
451     /*
452      * malloc() may have failed because we were asking for a lot of
453      * memory, but we may still be able to operate without a cache,
454      * so let's not fail.
455      */
456     if (new_bfh->cache == NULL) {
457         new_bfh->cache_sz = 0;
458         return 0;
459     }
460
461     /* Initialize cache */
462     for (i = 0; i < new_bfh->cache_sz; i += new_bfh->page_sz)
463         new_bfh->cache[i] = '\0';
464     return 0;
465
466 err:
467     (void) close(fd);
468     if (new_bfh) {
469         free(new_bfh->page);
470         free(new_bfh->cache);
471         free(new_bfh);
472     }
473     return ret;
474 }
475
476 /*
477  * Indicate whether the given binary search file handle will be searched
478  * with block-wise method.
479  */
480 void
481 _bsearch_file_info(bsearch_file_handle bfh,
482                     size_t *page_sz, size_t *max_sz, int *blockwise)
483 {
484     if (page_sz)
485         *page_sz = bfh->page_sz;
486     if (max_sz)
487         *max_sz = bfh->cache_sz;
488     if (blockwise)
489         *blockwise = (bfh->file_sz != bfh->cache_sz);
490 }
491
492 /*
493  * Close the given binary file search handle.
494  *
495  * Inputs:
496  *
497  * @bfh Pointer to variable containing handle to close.
498  */
499 void
500 _bsearch_file_close(bsearch_file_handle *bfh)
501 {
502     if (!*bfh)
503         return;
504     if ((*bfh)->fd >= 0)
505         (void) close((*bfh)->fd);
506     if ((*bfh)->page)
507         free((*bfh)->page);
508     if ((*bfh)->cache)
509         free((*bfh)->cache);
510     free(*bfh);
511     *bfh = NULL;
512 }
513
514 /*
515  * Private function to get a page from a cache.  The cache is a char
516  * array of 2^n - 1 double-size page worth of bytes, where n is the
517  * number of tree levels that the cache stores.  The cache can be
518  * smaller than n implies.
519  *
520  * The page may or may not be valid.  If the first byte of it is NUL
521  * then it's not valid, else it is.
522  *
523  * Returns 1 if page is in cache and valid, 0 if the cache is too small
524  * or the page is invalid.  The page address is output in @buf if the
525  * cache is large enough to contain it regardless of whether the page is
526  * valid.
527  *
528  * Inputs:
529  *
530  * @bfh      Binary search file handle
531  * @level    Level in the tree that we want a page for
532  * @page_idx Page number in the given level (0..2^level - 1)
533  *
534  * Outputs:
535  *
536  * @buf      Set to address of page if the cache is large enough
537  */
538 static int
539 get_page_from_cache(bsearch_file_handle bfh, size_t level, size_t page_idx,
540                     char **buf)
541 {
542     size_t idx = 0;
543     size_t page_sz;
544
545     page_sz = bfh->page_sz << 1; /* we use double-size pages in the cache */
546
547     *buf = NULL;
548
549     /*
550      * Compute index into cache.  The cache is basically an array of
551      * double-size pages.  The first (zeroth) double-size page in the
552      * cache will be the middle page of the file -- the root of the
553      * tree.  The next two double-size pages will be the left and right
554      * pages of the second level in the tree.  The next four double-size
555      * pages will be the four pages at the next level.  And so on for as
556      * many pages as fit in the cache.
557      *
558      * The page index is the number of the page at the given level.  We
559      * then compute (2^level - 1 + page index) * 2page size, check that
560      * we have that in the cache, check that the page has been read (it
561      * doesn't start with NUL).
562      */
563     if (level)
564         idx = (1 << level) - 1 + page_idx;
565     if (((idx + 1) * page_sz * 2) > bfh->cache_sz)
566         return 0;
567
568     *buf = &bfh->cache[idx * page_sz * 2];
569     if (bfh->cache[idx * page_sz * 2] == '\0')
570         return 0; /* cache[idx] == NUL -> page not loaded in cache */
571     return 1;
572 }
573
574 /*
575  * Private function to read a page of @page_sz from @fd at offset @off
576  * into @buf, outputing the number of bytes read, which will be the same
577  * as @page_sz unless the page being read is the last page, in which
578  * case the number of remaining bytes in the file will be output.
579  *
580  * Returns 0 on success or an errno value otherwise (EIO if reads are
581  * short).
582  *
583  * Inputs:
584  *
585  * @bfh        Binary search file handle
586  * @level      Level in the binary search tree that we're at
587  * @page_idx   Page "index" at the @level of the tree that we want
588  * @page       Actual page number that we want
589  * want_double Whether we need a page or double page read
590  *
591  * Outputs:
592  *
593  * @buf        Page read or cached
594  * @bytes      Bytes read (may be less than page or double page size in
595  *             the case of the last page, of course)
596  */
597 static int
598 read_page(bsearch_file_handle bfh, size_t level, size_t page_idx, size_t page,
599           int want_double, const char **buf, size_t *bytes)
600 {
601     int ret;
602     off_t off;
603     size_t expected;
604     size_t wanted;
605     char *page_buf;
606
607     /* Figure out where we're reading and how much */
608     off = page * bfh->page_sz;
609     if (off < 0)
610         return EOVERFLOW;
611
612     wanted = bfh->page_sz << want_double;
613     expected = ((bfh->file_sz - off) > wanted) ? wanted : bfh->file_sz - off;
614
615     if (get_page_from_cache(bfh, level, page_idx, &page_buf)) {
616         *buf = page_buf;
617         *bytes = expected;
618         return 0; /* found in cache */
619     }
620
621
622     *bytes = 0;
623     *buf = NULL;
624
625     /* OK, we have to read a page or double-size page */
626
627     if (page_buf)
628         want_double = 1; /* we'll be caching; we cache double-size pages */
629     else
630         page_buf = bfh->page; /* we won't cache this page */
631
632     wanted = bfh->page_sz << want_double;
633     expected = ((bfh->file_sz - off) > wanted) ? wanted : bfh->file_sz - off;
634
635 #ifdef HAVE_PREAD
636     ret = pread(bfh->fd, page_buf, expected, off);
637 #else
638     if (lseek(bfh->fd, off, SEEK_SET) == (off_t)-1)
639         return errno;
640     ret = read(bfh->fd, page_buf, expected);
641 #endif
642     if (ret < 0)
643         return errno;
644     
645     if (ret != expected)
646         return EIO; /* XXX ??? */
647
648     *buf = page_buf;
649     *bytes = expected;
650     return 0;
651 }
652
653 /*
654  * Perform a binary search of a file where each line is a record (LF and
655  * CRLF supported).  Each record consists of a key followed by an
656  * optional value separated from the key by whitespace.  Whitespace can
657  * be quoted with backslashes.  It's the caller's responsibility to
658  * encode/decode keys/values if quoting is desired; newlines should be
659  * encoded such that a newline does not appear in the result.
660  *
661  * The search is done with block-wise I/O (i.e., the whole file is not
662  * read into memory).
663  *
664  * All output arguments are optional.
665  *
666  * Returns 0 if key is found, -1 if not found, or an error code such as
667  * ENOMEM in case of error.
668  *
669  * NOTE: We could improve this by not freeing the buffer, instead
670  *       requiring that the caller provide it.  Further, we could cache
671  *       the top N levels of [double-size] pages (2^N - 1 pages), which
672  *       should speed up most searches by reducing the number of reads
673  *       by N.
674  *
675  * Inputs:
676  *
677  * @fd           File descriptor (file to search)
678  * @page_sz      Page size (if zero then the file's st_blksize will be used)
679  * @key          Key string to search for
680  *
681  * Outputs:
682  *
683  * @value        Location to store a copy of the value (caller must free)
684  * @location     Record location if found else the location where the
685  *               record should be inserted (index into @buf)
686  * @loops        Location to store a count of bisections required for
687  *               search (useful for confirming logarithmic performance)
688  * @reads        Location to store a count of pages read during search
689  *               (useful for confirming logarithmic performance)
690  */
691 int
692 _bsearch_file(bsearch_file_handle bfh, const char *key,
693                char **value, size_t *location, size_t *loops, size_t *reads)
694 {
695     int ret;
696     const char *buf;
697     size_t buf_sz;
698     size_t page, l, r;
699     size_t my_reads = 0;
700     size_t my_loops_total = 0;
701     size_t my_loops;
702     size_t level;        /* level in the tree */
703     size_t page_idx = 0; /* page number in the tree level */
704     size_t buf_location;
705     int cmp;
706     int buf_ends_in_eol = 0;
707     int buf_is_start = 0;
708
709     if (reads)
710         *reads = 0;
711
712     /* If whole file is in memory then search that and we're done */
713     if (bfh->file_sz == bfh->cache_sz)
714         return _bsearch_text(bfh->cache, bfh->cache_sz, key, value, location, loops);
715
716     /* Else block-wise binary search */
717
718     if (value)
719         *value = NULL;
720     if (loops)
721         *loops = 0;
722
723     l = 0;
724     r = (bfh->file_sz / bfh->page_sz) + 1;
725     for (level = 0, page = r >> 1; page >= l && page < r ; level++) {
726         ret = read_page(bfh, level, page_idx, page, 0, &buf, &buf_sz);
727         if (ret != 0)
728             return ret;
729         my_reads++;
730         if (buf[buf_sz - 1] == '\r' || buf[buf_sz - 1] == '\n')
731             buf_ends_in_eol = 1;
732         else
733             buf_ends_in_eol = 0;
734
735         buf_is_start = page == 0 ? 1 : 0;
736         ret = bsearch_common(buf, (size_t)buf_sz, key, buf_is_start,
737                              value, &buf_location, &cmp, &my_loops);
738         if (ret > 0)
739             return ret;
740         /* Found or no we update stats */
741         my_loops_total += my_loops;
742         if (loops)
743             *loops = my_loops_total;
744         if (reads)
745             *reads = my_reads;
746         if (location)
747             *location = page * bfh->page_sz + buf_location;
748         if (ret == 0)
749             return 0; /* found! */
750         /* Not found */
751         if (cmp < 0) {
752             /* Search left */
753             page_idx <<= 1;
754             r = page;
755             page = l + ((r - l) >> 1);
756             continue;
757         } else {
758             /*
759              * Search right, but first search the current and next
760              * blocks in case that the record we're looking for either
761              * straddles the boundary between this and the next record,
762              * or in case the record starts exactly at the next page.
763              */
764             heim_assert(cmp > 0, "cmp > 0");
765
766             if (!buf_ends_in_eol || page == l || page == (r - 1)) {
767                 ret = read_page(bfh, level, page_idx, page, 1, &buf, &buf_sz);
768                 if (ret != 0)
769                     return ret;
770                 my_reads++;
771
772                 buf_is_start = page == l ? 1 : 0;
773
774                 ret = bsearch_common(buf, (size_t)buf_sz, key, buf_is_start,
775                                      value, &buf_location, &cmp, &my_loops);
776                 if (ret > 0)
777                     return ret;
778                 my_loops_total += my_loops;
779                 if (loops)
780                     *loops = my_loops_total;
781                 if (reads)
782                     *reads = my_reads;
783                 if (location)
784                     *location = page * bfh->page_sz + buf_location;
785                 if (ret == 0)
786                     return 0;
787             }
788
789             /* Oh well, search right */
790             if (l == page && r == (l + 1))
791                 break;
792             page_idx = (page_idx << 1) + 1;
793             l = page;
794             page = l + ((r - l) >> 1);
795             continue;
796         }
797     }
798     return -1;
799 }
800
801
802 static int
803 stdb_open(void *plug, const char *dbtype, const char *dbname,
804              heim_dict_t options, void **db, heim_error_t *error)
805 {
806     bsearch_file_handle bfh;
807     char *p;
808     int ret;
809
810     if (error)
811         *error = NULL;
812     if (dbname == NULL || *dbname == '\0') {
813         if (error)
814             *error = heim_error_create(EINVAL,
815                                        N_("DB name required for sorted-text DB "
816                                           "plugin", ""));
817         return EINVAL;
818     }
819     p = strrchr(dbname, '.');
820     if (p == NULL || strcmp(p, ".txt") != 0) {
821         if (error)
822             *error = heim_error_create(ENOTSUP,
823                                        N_("Text file (name ending in .txt) "
824                                        "required for sorted-text DB plugin",
825                                        ""));
826         return ENOTSUP;
827     }
828
829     ret = _bsearch_file_open(dbname, 0, 0, &bfh, NULL);
830     if (ret)
831         return ret;
832
833     *db = bfh;
834     return 0;
835 }
836
837 static int
838 stdb_close(void *db, heim_error_t *error)
839 {
840     bsearch_file_handle bfh = db;
841
842     if (error)
843         *error = NULL;
844     _bsearch_file_close(&bfh);
845     return 0;
846 }
847
848 static heim_data_t
849 stdb_copy_value(void *db, heim_string_t table, heim_data_t key,
850                heim_error_t *error)
851 {
852     bsearch_file_handle bfh = db;
853     const char *k;
854     char *v;
855     heim_data_t value;
856     int ret;
857
858     if (error)
859         *error = NULL;
860
861     if (table == NULL)
862         table = HSTR("");
863
864     if (table != HSTR(""))
865         return NULL;
866
867     if (heim_get_tid(key) == HEIM_TID_STRING)
868         k = heim_string_get_utf8((heim_string_t)key);
869     else
870         k = (const char *)heim_data_get_ptr(key);
871     ret = _bsearch_file(bfh, k, &v, NULL, NULL, NULL);
872     if (ret != 0) {
873         if (ret > 0 && error)
874             *error = heim_error_create(ret, "%s", strerror(ret));
875         return NULL;
876     }
877     value = heim_data_create(v, strlen(v));
878     free(v);
879     /* XXX Handle ENOMEM */
880     return value;
881 }
882
883 struct heim_db_type heim_sorted_text_file_dbtype = {
884     1, stdb_open, NULL, stdb_close, NULL, NULL, NULL, NULL, NULL, NULL,
885     stdb_copy_value, NULL, NULL, NULL
886 };