Imported Upstream version 3.1.0
[debian/amanda] / ndmp-src / ndml_bstf.c
1 /*
2  * Copyright 2000
3  *      Traakan, Inc., Los Altos, CA
4  *      All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice unmodified, this list of conditions, and the following
11  *    disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28
29 /*
30  * Project:  NDMJOB
31  * Ident:    $Id: $
32  *
33  * Description:
34  *      Binary Search Text File (BSTF)
35  *
36  *      Use conventional binary search method on a sorted text
37  *      file. The file MUST be sorted in ascending, lexicographic
38  *      order. This is the default order of sort(1).
39  */
40
41
42 #include "ndmlib.h"
43
44
45
46 #ifdef SELF_TEST
47 int     n_seek, n_align, n_line, n_getc;
48 #endif
49
50
51 #define MIN_DELTA       2048
52
53
54
55 /*
56  * ndmbstf_first()
57  * ndmbstf_first_with_bounds()
58  *
59  * Returns:
60  *      <0      Error
61  *         -1   EOF
62  *         -2   Malformed line/file
63  *         -3   fseek() to determine file size failed
64  *         -4   fseek() to hop failed
65  *      =0      First line found in buf[], does not match key
66  *      >0      Length of first matching line in buf[]
67  */
68
69 int
70 ndmbstf_first (
71   FILE *fp,                     /* the file to search */
72   char *key,                    /* what we're looking for */
73   char *buf,                    /* returned line */
74   unsigned max_buf)             /* maximum lenght of buf (sizeof (buf)) */
75 {
76         return ndmbstf_first_with_bounds (fp, key, buf, max_buf, 0, 0);
77 }
78
79 int
80 ndmbstf_first_with_bounds (
81   FILE *fp,                     /* the file to search */
82   char *key,                    /* what we're looking for */
83   char *buf,                    /* returned line */
84   unsigned max_buf,             /* maximum lenght of buf (sizeof (buf)) */
85   off_t lower_bound,            /* offset, to skip headers, usually 0 */
86   off_t upper_bound)            /* 0->don't know, >0 limit */
87 {
88         off_t           off;
89         off_t           lower, upper;           /* bounds */
90         off_t           delta;
91         int             rc, buf_len;
92
93         if (upper_bound == 0) {
94                 off_t   end_off;
95
96                 /*
97                  * Determine the file size using fseek()/ftell()
98                  */
99
100                 fseeko (fp, 0, SEEK_END);
101                 end_off = ftello (fp);
102                 if (end_off == -1)
103                         return -3;
104                 upper_bound = end_off;
105         }
106
107         /*
108          * Set lower and upper bounds of the binary search
109          */
110         lower = lower_bound;
111         upper = upper_bound;
112         for (;;) {
113                 /*
114                  * Determine the delta (distance) between the current
115                  * lower and upper bounds. If delta is small, it is more
116                  * efficient to do a linear search than to continue
117                  * seeking. This is because stdio will pre-read
118                  * portions no matter what and fseek()ing will discard
119                  * the pre-read portions. MIN_DELTA is the approximation
120                  * of the stdio pre-read size. Better to just
121                  * linearly process the pre-read portion in the
122                  * hopes that our answer is already sitting in the
123                  * stdio buffer.
124                  */
125                 delta = upper - lower;
126                 if (delta <= MIN_DELTA)
127                         break;
128
129                 /*
130                  * Seek to the first line after the midpoint
131                  * between the lower and upper bounds.
132                  */
133
134                 off = lower + delta / 2;
135                 rc = ndmbstf_seek_and_align (fp, &off);
136                 if (rc < 0) {
137                         if (rc == EOF) {
138                                 /*
139                                  * Alignment found a very long line without
140                                  * a \n at the end of the file. All we
141                                  * can do now is try a linear search
142                                  * from the current lower bound.
143                                  */
144                         }
145                         return -4;      /* fseek() for hop failed */
146                 }
147
148                 /*
149                  * Read the next line up into buf[].
150                  */
151
152                 buf_len = ndmbstf_getline (fp, buf, max_buf);
153                 if (buf_len <= 0) {
154                         /*
155                          * EOF, or malformed line. All we can do now
156                          * is try a linear search from the current
157                          * lower bound.
158                          */
159                         break;
160                 }
161
162                 /*
163                  * This is the crucial point.
164                  *
165                  *   buf[]  contains a line just read.
166                  *   off    points the the line we just read.
167                  *   key[]  is what we're looking for.
168                  *
169                  * Is the objective line (what we're trying to find)
170                  * somewhere between lower..off or between off..upper.
171                  */
172                 rc = ndmbstf_compare (key, buf);
173                 if (rc > 0) {
174                         /* key>buf. Objective somewhere in off..upper */
175                         lower = off;
176                 } else {
177                         /*
178                          * key<=buf. Objective somewhere in lower..off
179                          * Notice that if key==buf, there still might
180                          * be a line ==key before the one we just
181                          * read. There might be hundreds of such
182                          * lines. The objective is the FIRST line
183                          * greater than or equal to the key.
184                          * This might be it. It might not.
185                          * So, keep searching.
186                          */
187                         upper = off;
188                 }
189         }
190
191         /*
192          * Do an unbounded linear search begining at the
193          * lower bound.
194          */
195
196         off = lower;
197         rc = ndmbstf_seek_and_align (fp, &off);
198         if (rc < 0) {
199                 if (rc == EOF) {
200                         /*
201                          * Alignment found a very long line without
202                          * a \n at the end of the file. All we
203                          * can do is give up.
204                          */
205                         return -2;
206                 }
207                 return -4;      /* fseek() for hop failed */
208         }
209
210         /*
211          * Read the next line up into buf[].
212          */
213         for (;;) {
214                 buf_len = ndmbstf_getline (fp, buf, max_buf);
215
216                 if (buf_len <= 0) {
217                         if (buf_len == EOF)
218                                 break;          /* at end of file */
219                         return -2;
220                 }
221
222                 rc = ndmbstf_compare (key, buf);
223                 if (rc == 0) {
224                         /* match */
225                         return buf_len;
226                 }
227                 if (rc < 0)
228                         return 0;       /* have line but it doesn't match */
229         }
230
231         return EOF;
232 }
233
234 int
235 ndmbstf_next (
236   FILE *fp,                     /* the file to search */
237   char *key,                    /* what we're looking for */
238   char *buf,                    /* returned line */
239   unsigned max_buf)             /* maximum lenght of buf (sizeof (buf)) */
240 {
241         int             rc, buf_len;
242
243         /*
244          * Read the next line up into buf[].
245          */
246         buf_len = ndmbstf_getline (fp, buf, max_buf);
247
248         if (buf_len <= 0) {
249                 if (buf_len == EOF)
250                         return EOF;             /* at end of file */
251                 return -2;                      /* malformed line */
252         }
253
254         rc = ndmbstf_compare (key, buf);
255         if (rc == 0) {
256                 /* match */
257                 return buf_len;
258         } else {
259                 return 0;       /* have line but it doesn't match */
260         }
261 }
262
263
264 /*
265  * ndmbstr_getline()
266  *
267  * Returns:
268  *      <0      Error
269  *         -1   EOF
270  *         -2   Malformed line
271  *      >=0     Length of buf
272  */
273
274 int
275 ndmbstf_getline (FILE *fp, char *buf, unsigned max_buf)
276 {
277         char *          p = buf;
278         char *          p_end = buf + max_buf - 2;
279         int             c;
280
281         while ((c = getc(fp)) != EOF) {
282 #ifdef SELF_TEST
283                 n_getc++;
284 #endif
285                 if (c == '\n')
286                         break;
287                 if (p < p_end)
288                         *p++ = c;
289         }
290         *p = 0;
291
292         if (c == EOF) {
293                 if (p > buf)
294                         return -2;      /* malformed line */
295                 return EOF;
296         }
297
298 #ifdef SELF_TEST
299         n_line++;
300 #endif
301         return p - buf;
302 }
303
304 int
305 ndmbstf_seek_and_align (FILE *fp, off_t *off)
306 {
307         int             c;
308
309         if (fseeko (fp, *off, SEEK_SET) == -1) {
310                 return -2;
311         }
312 #ifdef SELF_TEST
313         n_seek++;
314 #endif
315
316         /*
317          * There is a slim chance that we're at the
318          * true begining of a line. Too slim.
319          * Scan forward discarding the trailing
320          * portion of the line we just fseek()ed
321          * to, and leave the stdio stream positioned
322          * for the subsequent line. Notice
323          * we keep off upated so that it reflects
324          * the seek position of the stdio stream.
325          */
326
327         while ((c = getc(fp)) != EOF) {
328                 *off += 1;
329 #ifdef SELF_TEST
330                 n_align++;
331 #endif
332                 if (c == '\n')
333                         break;
334         }
335         if (c == EOF) {
336                 /* at end of file */
337                 return EOF;
338         }
339
340         return 0;
341 }
342
343
344
345 /*
346  * ndmbstf_compare()
347  *
348  * Given a key[] and a buf[], return whether or not they match.
349  * This effectively is strncmp (key, buf, strlen(key)).
350  * Because of the cost of the call to strlen(), we implement
351  * it ndmbstf_compare() here with the exact semantics.
352  *
353  * Return values are:
354  *      <0      No match, key[] less than buf[]
355  *      =0      Match, the key[] is a prefix for buf[]
356  *      >0      No match, key[] greater than buf[]
357  */
358
359 int
360 ndmbstf_compare (char *key, char *buf)
361 {
362         char *          p = key;
363         char *          q = buf;
364
365         while (*p != 0 && *p == *q) {
366                 p++;
367                 q++;
368         }
369
370         if (*p == 0)
371                 return 0;       /* entire key matched */
372         else
373                 return *p - *q;
374 }
375
376
377 /*
378  * ndmbstf_match()
379  *
380  * A simple wrapper around ndmbstf_compare() with an
381  * easy-to-use return value..
382  *
383  * Returns:
384  *      !0      match
385  *      =0      no match
386  */
387
388 int
389 ndmbstf_match (char *key, char *buf)
390 {
391         return ndmbstf_compare (key, buf) == 0;
392 }
393
394
395
396
397 #ifdef SELF_TEST
398 int
399 main (int ac, char *av[])
400 {
401         int             i;
402         FILE *          fp;
403         int             verbose = 1;
404         int             total_n_match = 0;
405         int             total_n_no_match = 0;
406         int             total_n_error = 0;
407
408         i = 1;
409         if (i < ac && strcmp (av[i], "-q") == 0) {
410                 i++;
411                 verbose = 0;
412         }
413
414         if (ac < i+2) {
415                 printf ("usage: %s [-q] FILE KEY ...\n", av[0]);
416                 return 1;
417         }
418
419         fp = fopen (av[i], "r");
420         if (!fp) {
421                 perror (av[i]);
422                 return 2;
423         }
424
425         for (i++; i < ac; i++) {
426                 char            buf[512];
427                 char *          key = av[i];
428                 int             n_match = 0;
429                 int             rc;
430
431                 rc = ndmbstf_first (fp, key, buf, sizeof buf);
432                 if (rc < 0) {
433                         printf ("Key '%s' err=%d\n", key, rc);
434                         total_n_error++;
435                         continue;
436                 }
437
438                 if (rc == 0) {
439                         printf ("Key '%s' no matches\n", key);
440                         total_n_no_match++;
441                         continue;
442                 }
443
444                 if (verbose)
445                         printf ("Key '%s'\n", key);
446                 for (; rc > 0; rc = ndmbstf_next (fp, key, buf, sizeof buf)) {
447                         n_match++;
448                         if (verbose)
449                                 printf ("    %2d: '%s'\n", n_match, buf);
450                 }
451                 total_n_match += n_match;
452         }
453
454         fclose (fp);
455
456         printf ("n_match=%d n_miss=%d n_error=%d\n",
457                 total_n_match, total_n_no_match, total_n_error);
458         printf ("n_seek=%d n_align=%d n_line=%d\n", n_seek, n_align, n_line);
459
460         return 0;
461 }
462 #endif /* SELF_TEST */