]> granicus.if.org Git - fortune-mod/blob - fortune-mod/util/strfile.c
consolidate includes.
[fortune-mod] / fortune-mod / util / strfile.c
1 /*      $NetBSD: strfile.c,v 1.3 1995/03/23 08:28:47 cgd Exp $  */
2
3 /*-
4  * Copyright (c) 1989, 1993
5  *      The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Ken Arnold.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *      This product includes software developed by the University of
21  *      California, Berkeley and its contributors.
22  * 4. Neither the name of the University nor the names of its contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38
39 /*
40  * Changes, September 1995, to make the damn thing actually sort instead
41  * of just pretending.  Amy A. Lewis
42  *
43  * And lots more.
44  *
45  * Fixed the special cases of %^J% (an empty fortune), no 'separator' at
46  * the end of the file, and a trailing newline at the end of the file, all
47  * of which produced total ballsup at one point or another.
48  *
49  * This included adding a routine to go back and write over the last pointer
50  * written or stored, for the case of an empty fortune.
51  *
52  * unstr also had to be modified (well, for *lots* of reasons, but this was
53  * one) to be certain to put the delimiters in the right places.
54  */
55
56 /*
57  *
58  #ifndef lint
59  static char copyright[] =
60  "@(#) Copyright (c) 1989, 1993\n\
61  The Regents of the University of California.  All rights reserved.\n";
62  #endif / * not lint * /
63
64  #ifndef lint
65  #if 0
66  static char sccsid[] = "@(#)strfile.c  8.1 (Berkeley) 5/31/93";
67  #else
68  static char rcsid[] = "$NetBSD: strfile.c,v 1.3 1995/03/23 08:28:47 cgd Exp $";
69  #endif
70  #endif / * not lint * /
71  *
72  *I haven't the faintest flipping idea what all that is, so kill the warnings
73  */
74
75 #include "fortune-mod-common.h"
76
77 /*
78  *    This program takes a file composed of strings separated by
79  * lines containing only the delimiting character (the default
80  * character is '%') and creates another file which consists of a table
81  * describing the file (structure from "strfile.h"), a table of seek
82  * pointers to the start of the strings, and the strings, each terminated
83  * by a null byte.  Usage:
84  *
85  *      % strfile [-iorsx] [ -cC ] sourcefile [ datafile ]
86  *
87  *      c - Change delimiting character from '%' to 'C'
88  *      s - Silent.  Give no summary of data processed at the end of
89  *          the run.
90  *      o - order the strings in alphabetic order
91  *      i - if ordering, ignore case
92  *      r - randomize the order of the strings
93  *      x - set rotated bit
94  *
95  *              Ken Arnold      Sept. 7, 1978 --
96  *
97  *      Added ordering options.
98  *
99  * Made ordering options do more than set the bloody flag, September 95 A. Lewis
100  *
101  * Always make sure that your loop control variables aren't set to bloody
102  * *zero* before distributing the bloody code, all right?
103  *
104  */
105
106 #define FALSE   0
107
108 #define STORING_PTRS    (Oflag || Rflag)
109 #define CHUNKSIZE       512
110
111 #define ALWAYS  1
112 #define ALLOC(ptr,sz)   if (ALWAYS) { \
113                         if (ptr == NULL) \
114                                 ptr = malloc((unsigned int) (CHUNKSIZE * sizeof *ptr)); \
115                         else if (((sz) + 1) % CHUNKSIZE == 0) \
116                                 ptr = realloc((void *) ptr, ((unsigned int) ((sz) + CHUNKSIZE) * sizeof *ptr)); \
117                         if (ptr == NULL) { \
118                                 fprintf(stderr, "out of space\n"); \
119                                 exit(1); \
120                         } \
121                 }
122
123 typedef struct
124 {
125     char first;
126     int32_t pos;
127 }
128 STR;
129
130 static char *Infile = NULL,            /* input file name */
131   Outfile[MAXPATHLEN] = "",     /* output file name */
132   Delimch = '%';                /* delimiting character */
133
134 static int Sflag = FALSE;              /* silent run flag */
135 static int Oflag = FALSE;              /* ordering flag */
136 static int Iflag = FALSE;              /* ignore case flag */
137 static int Rflag = FALSE;              /* randomize order flag */
138 static int Xflag = FALSE;              /* set rotated bit */
139 static long Num_pts = 0;               /* number of pointers/strings */
140
141 static int32_t *Seekpts;
142
143 static FILE *Sort_1, *Sort_2;          /* pointers for sorting */
144
145 static STRFILE Tbl;                    /* statistics table */
146
147 static STR *Firstch;                   /* first chars of each string */
148
149 static void __attribute__((noreturn)) usage(void)
150 {
151     fprintf(stderr,
152             "strfile [-iorsx] [-c char] sourcefile [datafile]\n");
153     exit(1);
154 }
155
156 /*
157  *    This routine evaluates arguments from the command line
158  */
159 static void getargs(int argc, char **argv)
160 {
161     int ch;
162
163     while ((ch = getopt(argc, argv, "c:iorsx")) != EOF)
164         switch (ch)
165           {
166           case 'c':             /* new delimiting char */
167               Delimch = *optarg;
168               if (!isascii(Delimch))
169               {
170                   printf("bad delimiting character: '\\%o\n'",
171                          (unsigned int)Delimch);
172               }
173               break;
174           case 'i':             /* ignore case in ordering */
175               Iflag++;
176               break;
177           case 'o':             /* order strings */
178               Oflag++;
179               break;
180           case 'r':             /* randomize pointers */
181               Rflag++;
182               break;
183           case 's':             /* silent */
184               Sflag++;
185               break;
186           case 'x':             /* set the rotated bit */
187               Xflag++;
188               break;
189           case '?':
190           default:
191               usage();
192           }
193     argv += optind;
194
195     if (*argv)
196     {
197         Infile = *argv;
198         if (*++argv)
199             (void) strcpy(Outfile, *argv);
200     }
201     if (!Infile)
202     {
203         puts("No input file name");
204         usage();
205     }
206     if (*Outfile == '\0')
207     {
208         strcpy(Outfile, Infile);
209         strcat(Outfile, ".dat");
210     }
211 }
212
213 /*
214  * add_offset:
215  *      Add an offset to the list, or write it out, as appropriate.
216  */
217 static void add_offset(FILE * fp, int32_t off)
218 {
219     if (!STORING_PTRS)
220     {
221         uint32_t net;
222         net = htonl((uint32_t)off);
223         fwrite(&net, 1, sizeof net, fp);
224     }
225     else
226     {
227         ALLOC(Seekpts, Num_pts + 1);
228         Seekpts[Num_pts] = off;
229     }
230     Num_pts++;
231 }
232
233 /*
234  * fix_last_offset:
235  *     Used when we have two separators in a row.
236  */
237 static void fix_last_offset(FILE * fp, int32_t off)
238 {
239     if (!STORING_PTRS)
240     {
241         uint32_t net = htonl((uint32_t)off);
242         fseek(fp, -(long)(sizeof net), SEEK_CUR);
243         fwrite(&net, 1, sizeof net, fp);
244     }
245     else
246         Seekpts[Num_pts - 1] = off;
247 }
248
249 /*
250  * cmp_str:
251  *      Compare two strings in the file
252  */
253 static int cmp_str(const void *v1, const void *v2)
254 {
255     register int c1, c2;
256     register int n1, n2;
257     register const STR *p1, *p2;
258
259 #define SET_N(nf,ch)    (nf = (ch == '\n'))
260 #define IS_END(ch,nf)   (ch == Delimch && nf)
261
262     p1 = (const STR *) v1;
263     p2 = (const STR *) v2;
264     c1 = p1->first;
265     c2 = p2->first;
266     if (c1 != c2)
267         return c1 - c2;
268
269     fseek(Sort_1, p1->pos, 0);
270     fseek(Sort_2, p2->pos, 0);
271
272     n1 = FALSE;
273     n2 = FALSE;
274     while (!isalnum(c1 = getc(Sort_1)) && c1 != '\0')
275         SET_N(n1, c1);
276     while (!isalnum(c2 = getc(Sort_2)) && c2 != '\0')
277         SET_N(n2, c2);
278
279     while (!IS_END(c1, n1) && !IS_END(c2, n2))
280     {
281         if (Iflag)
282         {
283             if (isupper(c1))
284                 c1 = tolower(c1);
285             if (isupper(c2))
286                 c2 = tolower(c2);
287         }
288         if (c1 != c2)
289             return c1 - c2;
290         SET_N(n1, c1);
291         SET_N(n2, c2);
292         c1 = getc(Sort_1);
293         c2 = getc(Sort_2);
294     }
295     if (IS_END(c1, n1))
296         c1 = 0;
297     if (IS_END(c2, n2))
298         c2 = 0;
299     return c1 - c2;
300 }
301
302 /*
303  * do_order:
304  *      Order the strings alphabetically (possibly ignoring case).
305  */
306 static void
307   do_order(void)
308 {
309     register long i;
310     register int32_t *lp;
311     register STR *fp;
312
313     Sort_1 = fopen(Infile, "r");
314     Sort_2 = fopen(Infile, "r");
315     qsort((char *) Firstch, (size_t)( (int) Num_pts - 1), sizeof *Firstch, cmp_str);
316 /*      i = Tbl.str_numstr;
317  * Fucking brilliant.  Tbl.str_numstr was initialized to zero, and is still zero
318  */
319     i = Num_pts - 1;
320     lp = Seekpts;
321     fp = Firstch;
322     while (i--)
323         *lp++ = fp++->pos;
324     fclose(Sort_1);
325     fclose(Sort_2);
326     Tbl.str_flags |= STR_ORDERED;
327 }
328
329 #if 0
330 static char *
331   unctrl(char c)
332 {
333     static char buf[3];
334
335     if (isprint(c))
336     {
337         buf[0] = c;
338         buf[1] = '\0';
339     }
340     else if (c == 0177)
341     {
342         buf[0] = '^';
343         buf[1] = '?';
344     }
345     else
346     {
347         buf[0] = '^';
348         buf[1] = c + 'A' - 1;
349     }
350     return buf;
351 }
352 #endif
353
354 /*
355  * randomize:
356  *      Randomize the order of the string table.  We must be careful
357  *      not to randomize across delimiter boundaries.  All
358  *      randomization is done within each block.
359  */
360 static void randomize(void)
361 {
362     register int cnt, i;
363     register int32_t tmp;
364     register int32_t *sp;
365
366     srandom((unsigned int) (time((time_t *) NULL) + getpid()));
367
368     Tbl.str_flags |= STR_RANDOM;
369 /*      cnt = Tbl.str_numstr;
370  * See comment above.  Isn't this stuff distributed worldwide?  How embarrassing!
371  */
372     cnt = (int)Num_pts;
373
374     /*
375      * move things around randomly
376      */
377
378     for (sp = Seekpts; cnt > 0; cnt--, sp++)
379     {
380         i = random() % cnt;
381         tmp = sp[0];
382         sp[0] = sp[i];
383         sp[i] = tmp;
384     }
385 }
386
387 /*
388  * main:
389  *      Drive the sucker.  There are two main modes -- either we store
390  *      the seek pointers, if the table is to be sorted or randomized,
391  *      or we write the pointer directly to the file, if we are to stay
392  *      in file order.  If the former, we allocate and re-allocate in
393  *      CHUNKSIZE blocks; if the latter, we just write each pointer,
394  *      and then seek back to the beginning to write in the table.
395  */
396 int main(int ac, char **av)
397 {
398     register char *sp;
399     register FILE *inf, *outf;
400     register int32_t last_off, length, pos, *p;
401     register int first, cnt;
402     register char *nsp;
403     register STR *fp;
404     static char string[257];
405     bool len_was_set = false;
406
407     getargs(ac, av);            /* evalute arguments */
408     if ((inf = fopen(Infile, "r")) == NULL)
409     {
410         perror(Infile);
411         exit(1);
412     }
413
414     if ((outf = fopen(Outfile, "w")) == NULL)
415     {
416         perror(Outfile);
417         exit(1);
418     }
419     if (!STORING_PTRS)
420         (void) fseek(outf, sizeof Tbl, 0);
421
422     /*
423      * Write the strings onto the file
424      */
425
426     Tbl.str_longlen = 0;
427     Tbl.str_shortlen = (unsigned int) 0xffffffff;
428     Tbl.str_delim = (uint8_t)Delimch;
429     Tbl.str_version = STRFILE_VERSION;
430     first = Oflag;
431     add_offset(outf, (int32_t)ftell(inf));
432     last_off = 0;
433     do
434     {
435         sp = fgets(string, 256, inf);
436         if (sp == NULL || STR_ENDSTRING(sp, Tbl))
437         {
438             pos = (int32_t)ftell(inf);
439             length = pos - last_off - (int32_t)(sp ? strlen(sp) : 0);
440             if (!length)
441                 /* Here's where we go back and fix things, if the
442                  * 'fortune' just read was the null string.
443                  * We had to make the assignment of last_off slightly
444                  * redundant to achieve this.
445                  */
446             {
447                 if (pos - last_off == 2)
448                     fix_last_offset(outf, pos);
449                 last_off = pos;
450                 continue;
451             }
452             last_off = pos;
453             add_offset(outf, pos);
454             if (! len_was_set)
455             {
456                 Tbl.str_longlen = (uint32_t)length;
457                 Tbl.str_shortlen = (uint32_t)length;
458                 len_was_set = true;
459             }
460             else
461             {
462                 if ((int)Tbl.str_longlen < length)
463                     Tbl.str_longlen = (uint32_t)length;
464                 if (Tbl.str_shortlen > (uint32_t)length)
465                     Tbl.str_shortlen = (uint32_t)length;
466             }
467             first = Oflag;
468         }
469         else if (first)
470         {
471             for (nsp = sp; !isalnum(*nsp); nsp++)
472                 continue;
473             ALLOC(Firstch, Num_pts);
474             fp = &Firstch[Num_pts - 1];
475             if (Iflag && isupper(*nsp))
476                 fp->first = (char)tolower(*nsp);
477             else
478                 fp->first = *nsp;
479             fp->pos = Seekpts[Num_pts - 1];
480             first = FALSE;
481         }
482     }
483     while (sp != NULL);
484
485     /*
486      * write the tables in
487      */
488
489     fclose(inf);
490
491     if (Oflag)
492         do_order();
493     else if (Rflag)
494         randomize();
495
496     if (Xflag)
497         Tbl.str_flags |= STR_ROTATED;
498
499     if (!Sflag)
500     {
501         printf("\"%s\" created\n", Outfile);
502         if (Num_pts == 1)
503             puts("There was no string");
504         else
505         {
506             if (Num_pts == 2)
507                 puts("There was 1 string");
508             else
509                 printf("There were %ld strings\n", Num_pts - 1);
510             printf("Longest string: %lu byte%s\n", (unsigned long)(Tbl.str_longlen),
511                 Tbl.str_longlen == 1 ? "" : "s");
512             printf("Shortest string: %lu byte%s\n", (unsigned long)(Tbl.str_shortlen),
513                 Tbl.str_shortlen == 1 ? "" : "s");
514         }
515     }
516
517     fseek(outf, (off_t) 0, 0);
518     Tbl.str_version = htonl(Tbl.str_version);
519     Tbl.str_numstr = htonl((uint32_t)(Num_pts - 1));
520     /* Look, Ma!  After using the variable three times, let's store
521      * something in it!
522      */
523     Tbl.str_longlen = htonl(Tbl.str_longlen);
524     Tbl.str_shortlen = htonl(Tbl.str_shortlen);
525     Tbl.str_flags = htonl(Tbl.str_flags);
526     fwrite(&Tbl.str_version,  sizeof Tbl.str_version,  1, outf);
527     fwrite(&Tbl.str_numstr,   sizeof Tbl.str_numstr,   1, outf);
528     fwrite(&Tbl.str_longlen,  sizeof Tbl.str_longlen,  1, outf);
529     fwrite(&Tbl.str_shortlen, sizeof Tbl.str_shortlen, 1, outf);
530     fwrite(&Tbl.str_flags,    sizeof Tbl.str_flags,    1, outf);
531     fwrite( Tbl.stuff,        sizeof Tbl.stuff,        1, outf);
532     if (STORING_PTRS)
533     {
534         for (p = Seekpts, cnt = (int)Num_pts; cnt--; ++p)
535         {
536             *p = (int32_t)htonl((uint32_t)*p);
537             fwrite(p, sizeof *p, 1, outf);
538         }
539     }
540     fclose(outf);
541     exit(0);
542 }