addrutil: Re-indent.
[wk-misc.git] / addrutil.c
1 /* [addrutil.c wk 07.03.97] Tool to mess with address lists
2  *      Copyright (c) 1997, 2003 Werner Koch (dd9jn)
3  *      Copyright (C) 2000 OpenIT GmbH
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18  */
19
20 /* $Id: addrutil.c,v 1.5 2005-07-13 13:25:32 werner Exp $ */
21
22 /* How to use:
23
24 This utility works on databases using this format:  Plain text file,
25 a hash mark in the first column denotes a comment line.  Fieldnames must start
26 with a letter in the first column and be terminated by a colon.  The
27 value of a fields starts after the colon, leding white spaces are ignored,
28 the value may be continued on the next line by prepending it with at least
29 one white space.  The first fieldname in a file acts as the record separator.
30 Fieldnames are case insensitive, duplkicated fieldnames are allowed (but not
31 for the first field) and inetrnally index by appendig a number.
32 Here is an example:
33 == addr.db ==============
34 # My address database (this is a comment)
35
36 Name: Alyssa Hacker
37 Email: alyssa@foo.net
38 Street: Cambridge Road 1
39 City: Foovillage
40
41 Name: Ben Bitfiddle
42 Street: Emacs Road 20
43 City: 234567  Gnutown
44 Email: ben@bar.org
45 Phone: 02222-33333
46
47 ===========================================
48
49 This tool may be used to insert the values into a TeX file.  Here is an
50 example for such an TeX template:
51 == letter.tex ========
52 \documentclass[a4paper]{letter}
53 \usepackage[latin1]{inputenc}
54 \usepackage{ifthen}
55 \usepackage{german}
56 \begin{document}
57
58 %% the next line contains a pseudo field which marks the
59 %% start of a block which will be repeated for each record from the
60 %% database.  You way want to view this as a "while (not-database); do"
61 % @@begin-record-block@@
62
63 \begin{letter}{
64 @@Name@@ \\
65 @@Street@@ \\
66 @@City@@
67 }
68
69 \opening{Dear @@Email@@}
70 %% The variable substitution above knows about a special format, e.g.:
71 %% @@Email:N=,@@
72 %% where included linefeeds are replaced by the string after the equal
73 %% sign.
74
75 we are glad to invite you to our annual bit party at the Bit breweries,
76 located in Bitburg/Eifel.
77
78 \closing{Happy hacking}
79
80 \end{letter}
81
82 %% We are ready with this one record, and start over with the next one.
83 %% You may want to view this next-record statement as the closing "done"
84 %% done statement for the above while.
85 % @@next-record@@
86
87 \end{document}
88 ======================
89
90 To send this letter to all the folks from the addr.db you whould use these
91 commands:
92
93  $ addrutil -T letter.tex addr.db >a.tex && latex a.tex
94
95  */
96
97 #include <stdio.h>
98 #include <stdlib.h>
99 #include <string.h>
100 #include <stdarg.h>
101 #include <errno.h>
102 #include <ctype.h>
103
104 #define PGMNAME "addrutil"
105 #define VERSION "0.71"
106 #define FIELDNAMELEN 40         /* max. length of a fieldname */
107
108 #ifdef __GNUC__
109 #define INLINE __inline__
110 #else
111 #define INLINE
112 #endif
113
114
115 typedef struct outfield_struct
116 {
117   struct outfield_struct *next;
118   char name[1];
119 } *OUTFIELD;
120
121
122 static struct
123 {
124   int verbose;
125   int debug;
126   int checkonly;
127   int format;
128   const char *texfile;
129   int sortmode;
130   OUTFIELD outfields;
131 } opt;
132
133
134 typedef struct data_struct
135 {
136   struct data_struct *next;
137   int activ;                    /* True if slot is in use. */
138   int index;                    /* Index number of this item.  */
139   size_t size;                  /* Available length of D. */
140   size_t used;                  /* Used length of D. */
141   char d[1];                    /* (this is not a string) */
142 } *DATA;
143
144 static DATA unused_data;        /* LL of unused data blocks. */
145
146 typedef struct field_struct
147 {
148   struct field_struct *nextfield;
149   int valid;                    /* In current record.  */
150   DATA data;                    /* Data storage for this field.  */
151   char name[1];                 /* Extended to the correct length.  */
152 } *FIELD;
153
154
155 typedef struct sort_struct
156 {
157   struct sort_struct *next;
158   long offset;                  /* of the record.  */
159   char d[1];                    /* Concatenated data used for sort.  */
160 } *SORT;
161
162
163 typedef struct namebucket_struct
164 {
165   struct namebucket_struct *next;
166   FIELD ptr;
167 } *NAMEBUCKET;
168
169 #define NO_NAMEBUCKETS 51
170 static NAMEBUCKET namebuckets[NO_NAMEBUCKETS];
171
172
173 static FIELD fieldlist;         /* Description of the record. */
174                                 /* The first field ist the record marker.  */
175 static FIELD next_field;        /* Used by GetFirst/NextField(). */
176 static OUTFIELD next_outfield;
177 static SORT sortlist;           /* Used when opt.sortmode activ.  */
178 static ulong output_count;
179 static long start_of_record;    /* Fileoffset of the current record.  */
180 static int new_record_flag;
181 static struct
182 {
183   FILE *fp;
184   int in_record_block;
185   long begin_block;
186   long end_block;
187 } tex;
188
189
190 typedef struct
191 {
192   int *argc;                    /* Pointer to argc (value subject to change) */
193   char ***argv;                 /* Pointer to argv (value subject to change) */
194   unsigned flags;               /* Global flags (DO NOT CHANGE) */
195   int err;                      /* Print error about last option */
196                                 /*   1 = warning, 2 = abort */
197   int r_opt;                    /* Return option */
198   int r_type;                   /* Type of return value:   */
199                                 /*   0 = no argument found */
200   union
201   {
202     int ret_int;
203     long ret_long;
204     ulong ret_ulong;
205     char *ret_str;
206   } r;                          /* Return values */
207   struct
208   {
209     int index;
210     int inarg;
211     int stopped;
212     const char *last;
213   } internal;
214 } ARGPARSE_ARGS;
215
216 typedef struct
217 {
218   int short_opt;
219   const char *long_opt;
220   unsigned flags;
221   const char *description;      /* Optional option description.  */
222 } ARGPARSE_OPTS;
223
224
225 static void set_opt_arg (ARGPARSE_ARGS * arg, unsigned flags, char *s);
226 static void show_help (ARGPARSE_OPTS * opts, unsigned flags);
227 static void show_version (void);
228
229 static INLINE unsigned long HashName (const unsigned char *s);
230 static void HashInfos (void);
231 static void Err (int rc, const char *s, ...);
232 static void Process (const char *filename);
233 static FIELD StoreFieldname (const char *fname, long offset);
234 static DATA ExpandDataSlot (FIELD field, DATA data);
235 static void NewRecord (long);
236 static FIELD GetFirstField (void);
237 static FIELD GetNextField (void);
238 static void FinishRecord (void);
239 static void PrintFormat2 (int flush);
240 static void PrintTexFile (int);
241 static int ProcessTexOp (const char *op);
242 static void DoSort (void);
243 static int DoSortFnc (const void *arg_a, const void *arg_b);
244
245 const char *CopyRight (int level);
246
247 static void
248 ShowCopyRight (int level)
249 {
250   static int sentinel = 0;
251
252   if (sentinel)
253     return;
254
255   sentinel++;
256   if (!level)
257     {
258       fputs (CopyRight (level), stderr);
259       putc ('\n', stderr);
260       fputs (CopyRight (31), stderr);
261       fprintf (stderr, "%s (%s)\n", CopyRight (32), CopyRight (24));
262       fflush (stderr);
263     }
264   else if (level == 1)
265     {
266       fputs (CopyRight (level), stderr);
267       putc ('\n', stderr);
268       exit (1);
269     }
270   else if (level == 2)
271     {
272       puts (CopyRight (level));
273       exit (0);
274     }
275   sentinel--;
276 }
277
278
279 const char *
280 CopyRight (int level)
281 {
282   const char *p;
283   switch (level)
284     {
285     case 10:
286     case 0:
287       p = "addrutil - v" VERSION "; " "Copyright (C) 2003 Werner Koch";
288       break;
289     case 13:
290       p = "addrutil";
291       break;
292     case 14:
293       p = VERSION;
294       break;
295     case 1:
296     case 11:
297       p = "Usage: addrutil [options] [files] (-h for help)";
298       break;
299     case 2:
300     case 12:
301       p =
302         "\nSyntax: addrutil [options] [files]\n"
303         "Handle address database files\n";
304       break;
305     case 19:
306       p =                       /* Footer */
307         "Format modes:  0   Colon delimited fields\n"
308         "               1   Colon delimited name=fields pairs\n"
309         "               2   `Name',`Street',`City' formatted for labels\n"
310         "               3   Addrutil format\n"
311         "               4   Semicolon delimited format\n" "";
312       break;
313     default:
314       p = "";
315     }
316   ShowCopyRight (level);
317   return p;
318 }
319
320
321 static void *
322 xmalloc (size_t n)
323 {
324   void *p = malloc (n);
325   if (!p)
326     {
327       fprintf (stderr, PGMNAME ": out of memory\n");
328       exit (2);
329     }
330   return p;
331 }
332
333 static void *
334 xcalloc (size_t n, size_t m)
335 {
336   void *p = calloc (n, m);
337   if (!p)
338     {
339       fprintf (stderr, PGMNAME ": out of memory\n");
340       exit (2);
341     }
342   return p;
343 }
344
345
346 static void
347 StripTrailingWSpaces (char *str)
348 {
349   char *p;
350   char *mark;
351
352   /* find last non space character */
353   for (mark = NULL, p = str; *p; p++)
354     {
355       if (isspace (*(unsigned char *) p))
356         {
357           if (!mark)
358             mark = p;
359         }
360       else
361         mark = NULL;
362     }
363   if (mark)
364     {
365       *mark = '\0';             /* remove trailing spaces */
366     }
367 }
368
369
370
371 static int
372 ArgParse (ARGPARSE_ARGS * arg, ARGPARSE_OPTS * opts)
373 {
374   int index;
375   int argc;
376   char **argv;
377   char *s, *s2;
378   int i;
379
380   if (!(arg->flags & (1 << 15)))
381     {                           /* initialize this instance */
382       arg->internal.index = 0;
383       arg->internal.last = NULL;
384       arg->internal.inarg = 0;
385       arg->internal.stopped = 0;
386       arg->err = 0;
387       arg->flags |= 1 << 15;    /* mark initialized */
388       if (*arg->argc < 0)
389         abort ();               /*Invalid argument for ArgParse */
390     }
391   argc = *arg->argc;
392   argv = *arg->argv;
393   index = arg->internal.index;
394
395   if (arg->err)
396     {                           /* last option was erroneous */
397       /* FIXME: We could give more help on the option if opts->desription
398        * is used. Another possibility ist, to autogenerate the help
399        * from these descriptions. */
400       if (arg->r_opt == -3)
401         s = PGMNAME ": missing argument for option \"%.50s\"";
402       else
403         s = PGMNAME ": invalid option \"%.50s\"";
404       fprintf (stderr, s, arg->internal.last ? arg->internal.last : "[??]");
405       if (arg->err != 1)
406         exit (2);
407       arg->err = 0;
408     }
409
410   if (!index && argc && !(arg->flags & (1 << 4)))
411     {                           /* skip the first entry */
412       argc--;
413       argv++;
414       index++;
415     }
416
417 next_one:
418   if (!argc)
419     {                           /* no more args */
420       arg->r_opt = 0;
421       goto leave;               /* ready */
422     }
423
424   s = *argv;
425   arg->internal.last = s;
426
427   if (arg->internal.stopped && (arg->flags & (1 << 1)))
428     {
429       arg->r_opt = -1;          /* not an option but a argument */
430       arg->r_type = 2;
431       arg->r.ret_str = s;
432       argc--;
433       argv++;
434       index++;                  /* set to next one */
435     }
436   else if (arg->internal.stopped)
437     {                           /* ready */
438       arg->r_opt = 0;
439       goto leave;
440     }
441   else if (*s == '-' && s[1] == '-')
442     {                           /* long option */
443       arg->internal.inarg = 0;
444       if (!s[2] && !(arg->flags & (1 << 3)))
445         {                       /* stop option processing */
446           arg->internal.stopped = 1;
447           argc--;
448           argv++;
449           index++;
450           goto next_one;
451         }
452
453       for (i = 0; opts[i].short_opt; i++)
454         if (opts[i].long_opt && !strcmp (opts[i].long_opt, s + 2))
455           break;
456
457       if (!opts[i].short_opt && !strcmp ("help", s + 2))
458         show_help (opts, arg->flags);
459       else if (!opts[i].short_opt && !strcmp ("version", s + 2))
460         show_version ();
461       else if (!opts[i].short_opt && !strcmp ("warranty", s + 2))
462         {
463           puts (CopyRight (10));
464           puts (CopyRight (31));
465           exit (0);
466         }
467
468       arg->r_opt = opts[i].short_opt;
469       if (!opts[i].short_opt)
470         {
471           arg->r_opt = -2;      /* unknown option */
472           arg->r.ret_str = s + 2;
473         }
474       else if ((opts[i].flags & 7))
475         {
476           s2 = argv[1];
477           if (!s2 && (opts[i].flags & 8))
478             {                   /* no argument but it is okay */
479               arg->r_type = 0;  /* because it is optional */
480             }
481           else if (!s2)
482             {
483               arg->r_opt = -3;  /* missing argument */
484             }
485           else if (*s2 == '-' && (opts[i].flags & 8))
486             {
487               /* the argument is optional and the next seems to be
488                * an option. We do not check this possible option
489                * but assume no argument */
490               arg->r_type = 0;
491             }
492           else
493             {
494               set_opt_arg (arg, opts[i].flags, s2);
495               argc--;
496               argv++;
497               index++;          /* skip one */
498             }
499         }
500       else
501         {                       /* does not take an argument */
502           arg->r_type = 0;
503         }
504       argc--;
505       argv++;
506       index++;                  /* set to next one */
507     }
508   else if ((*s == '-' && s[1]) || arg->internal.inarg)
509     {                           /* short option */
510       int dash_kludge = 0;
511       i = 0;
512       if (!arg->internal.inarg)
513         {
514           arg->internal.inarg++;
515           if (arg->flags & (1 << 5))
516             {
517               for (i = 0; opts[i].short_opt; i++)
518                 if (opts[i].long_opt && !strcmp (opts[i].long_opt, s + 1))
519                   {
520                     dash_kludge = 1;
521                     break;
522                   }
523             }
524         }
525       s += arg->internal.inarg;
526
527       if (!dash_kludge)
528         {
529           for (i = 0; opts[i].short_opt; i++)
530             if (opts[i].short_opt == *s)
531               break;
532         }
533
534       if (!opts[i].short_opt && *s == 'h')
535         show_help (opts, arg->flags);
536
537       arg->r_opt = opts[i].short_opt;
538       if (!opts[i].short_opt)
539         {
540           arg->r_opt = -2;              /* unknown option */
541           arg->internal.inarg++;        /* point to the next arg */
542           arg->r.ret_str = s;
543         }
544       else if ((opts[i].flags & 7))
545         {
546           if (s[1] && !dash_kludge)
547             {
548               s2 = s + 1;
549               set_opt_arg (arg, opts[i].flags, s2);
550             }
551           else
552             {
553               s2 = argv[1];
554               if (!s2 && (opts[i].flags & 8))
555                 {                       /* no argument but it is okay */
556                   arg->r_type = 0;      /* because it is optional */
557                 }
558               else if (!s2)
559                 {
560                   arg->r_opt = -3;      /* missing argument */
561                 }
562               else if (*s2 == '-' && s2[1] && (opts[i].flags & 8))
563                 {
564                   /* the argument is optional and the next seems to be
565                    * an option. We do not check this possible option
566                    * but assume no argument */
567                   arg->r_type = 0;
568                 }
569               else
570                 {
571                   set_opt_arg (arg, opts[i].flags, s2);
572                   argc--;
573                   argv++;
574                   index++;      /* skip one */
575                 }
576             }
577           s = "x";              /* so that !s[1] yields false */
578         }
579       else
580         {                       /* does not take an argument */
581           arg->r_type = 0;
582           arg->internal.inarg++;/* point to the next arg */
583         }
584       if (!s[1] || dash_kludge)
585         {                       /* no more concatenated short options */
586           arg->internal.inarg = 0;
587           argc--;
588           argv++;
589           index++;
590         }
591     }
592   else if (arg->flags & (1 << 2))
593     {
594       arg->r_opt = -1;          /* not an option but a argument */
595       arg->r_type = 2;
596       arg->r.ret_str = s;
597       argc--;
598       argv++;
599       index++;                  /* set to next one */
600     }
601   else
602     {
603       arg->internal.stopped = 1;/* stop option processing */
604       goto next_one;
605     }
606
607 leave:
608   *arg->argc = argc;
609   *arg->argv = argv;
610   arg->internal.index = index;
611   return arg->r_opt;
612 }
613
614
615
616 static void
617 set_opt_arg (ARGPARSE_ARGS * arg, unsigned flags, char *s)
618 {
619   int base = (flags & 16) ? 0 : 10;
620
621   switch (arg->r_type = (flags & 7))
622     {
623     case 1:                     /* takes int argument */
624       arg->r.ret_int = (int) strtol (s, NULL, base);
625       break;
626     default:
627     case 2:                     /* takes string argument */
628       arg->r.ret_str = s;
629       break;
630     case 3:                     /* takes long argument   */
631       arg->r.ret_long = strtol (s, NULL, base);
632       break;
633     case 4:                     /* takes ulong argument  */
634       arg->r.ret_ulong = strtoul (s, NULL, base);
635       break;
636     }
637 }
638
639 static void
640 show_help (ARGPARSE_OPTS * opts, unsigned flags)
641 {
642   const char *s;
643
644   puts (CopyRight (10));
645   s = CopyRight (12);
646   if (*s == '\n')
647     s++;
648   puts (s);
649   if (opts[0].description)
650     {                           /* auto format the option description */
651       int i, j, indent;
652       /* get max. length of long options */
653       for (i = indent = 0; opts[i].short_opt; i++)
654         {
655           if (opts[i].long_opt)
656             if ((j = strlen (opts[i].long_opt)) > indent && j < 35)
657               indent = j;
658         }
659       /* example: " -v, --verbose   Viele Sachen ausgeben" */
660       indent += 10;
661       puts ("Options:");
662       for (i = 0; opts[i].short_opt; i++)
663         {
664           if (opts[i].short_opt < 256)
665             printf (" -%c", opts[i].short_opt);
666           else
667             fputs ("   ", stdout);
668           j = 3;
669           if (opts[i].long_opt)
670             j += printf ("%c --%s   ", opts[i].short_opt < 256 ? ',' : ' ',
671                          opts[i].long_opt);
672           for (; j < indent; j++)
673             putchar (' ');
674           if ((s = opts[i].description))
675             {
676               for (; *s; s++)
677                 {
678                   if (*s == '\n')
679                     {
680                       if (s[1])
681                         {
682                           putchar ('\n');
683                           for (j = 0; j < indent; j++)
684                             putchar (' ');
685                         }
686                     }
687                   else
688                     putchar (*s);
689                 }
690             }
691           putchar ('\n');
692         }
693       if (flags & 32)
694         puts ("\n(A single dash may be used instead of the double ones)");
695     }
696   if ((s = CopyRight (19)))
697     {                           /* bug reports to ... */
698       putchar ('\n');
699       fputs (s, stdout);
700     }
701   fflush (stdout);
702   exit (0);
703 }
704
705 static void
706 show_version ()
707 {
708   const char *s;
709   printf ("%s version %s (%s", CopyRight (13), CopyRight (14),
710           CopyRight (45));
711   if ((s = CopyRight (24)) && *s)
712     {
713       printf (", %s)\n", s);
714     }
715   else
716     {
717       printf (")\n");
718     }
719   fflush (stdout);
720   exit (0);
721 }
722
723
724
725 int
726 main (int argc, char **argv)
727 {
728   ARGPARSE_OPTS opts[] = {
729     {'f', "format", 1, "use output format N"},
730     {'s', "sort", 0, "sort the file"},
731     {'F', "field", 2, "output this field"},
732     {'T', "tex-file", 2, "use TeX file as template"},
733     {'c', "check-only", 0, "do only a syntax check"},
734     {'v', "verbose", 0, "verbose"},
735     {'d', "debug", 0, "increase the debug level"},
736     {0}
737   };
738   ARGPARSE_ARGS pargs = { &argc, &argv, 0 };
739   int org_argc;
740   char **org_argv;
741   OUTFIELD of, of2;
742
743   while (ArgParse (&pargs, opts))
744     {
745       switch (pargs.r_opt)
746         {
747         case 'v':
748           opt.verbose++;
749           break;
750         case 'd':
751           opt.debug++;
752           break;
753         case 'c':
754           opt.checkonly++;
755           break;
756         case 's':
757           opt.sortmode = 1;
758           break;
759         case 'f':
760           opt.format = pargs.r.ret_int;
761           break;
762         case 'T':
763           opt.texfile = pargs.r.ret_str;
764           break;
765         case 'F':
766           of = xmalloc (sizeof *of + strlen (pargs.r.ret_str));
767           of->next = NULL;
768           strcpy (of->name, pargs.r.ret_str);
769           if (!(of2 = opt.outfields))
770             opt.outfields = of;
771           else
772             {
773               for (; of2->next; of2 = of2->next)
774                 ;
775               of2->next = of;
776             }
777           break;
778         default:
779           pargs.err = 2;
780           break;
781         }
782     }
783
784   if (opt.texfile)
785     {
786       tex.fp = fopen (opt.texfile, "r");
787       if (!tex.fp)
788         {
789           fprintf (stderr, PGMNAME ": failed to open `%s': %s\n",
790                    opt.texfile, strerror (errno));
791           exit (1);
792         }
793     }
794
795   if (opt.sortmode && argc != 1)
796     {
797       fprintf (stderr,
798                PGMNAME ": sorry, sorting is only available for one file\n");
799       exit (1);
800     }
801
802   org_argc = argc;
803   org_argv = argv;
804
805 pass_two:
806   if (!argc)
807     Process (NULL);
808   else
809     {
810       for (; argc; argc--, argv++)
811         Process (*argv);
812     }
813   if (opt.texfile)
814     {
815       if (tex.in_record_block && opt.sortmode != 1)
816         {
817           PrintTexFile (1);
818         }
819     }
820   else if (opt.format == 2 && opt.sortmode != 1)
821     PrintFormat2 (1);           /* flush */
822
823   if (opt.sortmode == 1 && sortlist)
824     {
825       DoSort ();
826       argc = org_argc;
827       argv = org_argv;
828       opt.sortmode = 2;
829       goto pass_two;
830     }
831   else if (opt.sortmode == 2)
832     {
833       /* FIXME: cleanup the sort infos */
834     }
835
836   if (opt.debug)
837     {
838       FIELD f;
839       DATA d;
840       FILE *fp = stderr;
841       int n;
842
843       fputs ("--- Begin fieldlist ---\n", fp);
844       for (f = fieldlist; f; f = f->nextfield)
845         {
846           n = fprintf (fp, "%.20s:", f->name);
847           for (d = f->data; d; d = d->next)
848             fprintf (fp, "%*s idx=%-3d used=%-3d size=%-3d %s\n",
849                      d == f->data ? 0 : n, "", d->index, d->used, d->size,
850                      d->activ ? "activ" : "not-active");
851           if (!f->data)
852             putc ('\n', fp);
853         }
854       fputs ("--- End fieldlist ---\n", fp);
855       HashInfos ();
856     }
857   return 0;
858 }
859
860 static INLINE unsigned long
861 HashName (const unsigned char *s)
862 {
863   unsigned long hashVal = 0, carry;
864
865   if (s)
866     for (; *s; s++)
867       {
868         hashVal = (hashVal << 4) + toupper (*s);
869         if ((carry = (hashVal & 0xf0000000)))
870           {
871             hashVal ^= (carry >> 24);
872             hashVal ^= carry;
873           }
874       }
875
876   return hashVal % NO_NAMEBUCKETS;
877 }
878
879 static void
880 HashInfos ()
881 {
882   int i, sum, perBucket, n;
883   NAMEBUCKET r;
884
885   perBucket = sum = 0;
886   for (i = 0; i < NO_NAMEBUCKETS; i++)
887     {
888       for (n = 0, r = namebuckets[i]; r; r = r->next)
889         n++;
890       sum += n;
891       if (n > perBucket)
892         perBucket = n;
893     }
894   fprintf (stderr,
895            "%d entries in %d hash buckets; max. %d entr%s per hash bucket\n",
896            sum, NO_NAMEBUCKETS, perBucket, perBucket == 1 ? "y" : "ies");
897 }
898
899
900 static void
901 Err (int rc, const char *s, ...)
902 {
903   va_list arg_ptr;
904   FILE *fp = stderr;
905
906   va_start (arg_ptr, s);
907   vfprintf (fp, s, arg_ptr);
908   putc ('\n', fp);
909   va_end (arg_ptr);
910   if (rc)
911     exit (rc);
912 }
913
914
915 static void
916 Process (const char *filename)
917 {
918   FILE *fp;
919   int c;
920   unsigned long lineno = 0;
921   long lineoff = 0;             /* offset of the current line */
922   int newline;
923   int comment = 0;
924   int linewrn = 0;
925   unsigned char fname[FIELDNAMELEN + 1];
926   int fnameidx = 0;
927   int index;                    /* current index */
928   enum
929   { sINIT,                      /* no record yet */
930     sFIELD,                     /* inside a fieldname */
931     sDATABEG,                   /* waiting for start of value */
932     sDATA                       /* storing a value */
933   } state = sINIT;
934   FIELD f = NULL;               /* current field */
935   DATA d = NULL;                /* current data slot */
936   SORT sort = sortlist;
937   int pending_lf = 0;
938   int skip_kludge = 0;
939
940   if (filename)
941     {
942       fp = fopen (filename, "r");
943       if (!fp)
944         {
945           fprintf (stderr, PGMNAME ": failed to open `%s': %s\n",
946                    filename, strerror (errno));
947           exit (1);
948         }
949     }
950   else
951     {
952       fp = stdin;
953       filename = "[stdin]";
954     }
955
956   if (opt.sortmode == 2)
957     {
958       if (!sort)
959         return;                 /* nothing to sort */
960     next_sortrecord:
961       if (!sort)
962         goto ready;
963       clearerr (fp);
964       if (fseek (fp, sort->offset, SEEK_SET))
965         {
966           fprintf (stderr, PGMNAME ": error seekung to %ld\n", sort->offset);
967           exit (2);
968         }
969       sort = sort->next;
970       state = sINIT;
971       skip_kludge = 1;
972     }
973
974   /* Read the file byte by byte; do not impose a limit on the
975    * line length. Fieldnames are up to FIELDNAMELEN bytes long.
976    */
977   lineno++;
978   newline = 1;
979   while ((c = getc (fp)) != EOF)
980     {
981       if (c == '\n')
982         {
983           switch (state)
984             {
985             case sFIELD:
986               Err (2, "%s:%ld: fieldname not terminated", filename, lineno);
987               break;
988             case sDATA:
989               pending_lf++;
990               break;
991             default:
992               break;
993             }
994           lineno++;
995           lineoff = ftell (fp) - 1;
996           newline = 1;
997           comment = 0;
998           linewrn = 0;
999           continue;
1000         }
1001       else if (comment)
1002         continue;
1003
1004       if (newline)
1005         {                       /* at first column */
1006           if (c == '#')
1007             comment = 1;        /* bybass the entire line */
1008           else if (c == ' ' || c == '\t')
1009             {
1010               switch (state)
1011                 {
1012                 case sINIT:
1013                   break;        /* nothing to do */
1014                 case sFIELD:
1015                   abort ();
1016                 case sDATABEG:
1017                   break;
1018                 case sDATA:
1019                   state = sDATABEG;
1020                   break;
1021                 }
1022             }
1023           else if (c == ':')
1024             Err (2, "%s:%ld: line starts with a colon", filename, lineno);
1025           else
1026             {
1027               switch (state)
1028                 {
1029                 case sDATABEG:
1030                 case sDATA:
1031                   /*FinishField(); */
1032                   /* fall thru */
1033                 case sINIT:     /* start of a fieldname */
1034                   fnameidx = 0;
1035                   fname[fnameidx++] = c;
1036                   state = sFIELD;
1037                   break;
1038                 case sFIELD:
1039                   abort ();
1040                 }
1041             }
1042           newline = 0;
1043         }
1044       else
1045         {
1046           switch (state)
1047             {
1048             case sINIT:
1049               if (!linewrn)
1050                 {
1051                   Err (0, "%s:%lu: warning: garbage detected",
1052                        filename, lineno);
1053                   linewrn++;
1054                 }
1055               break;
1056             case sFIELD:
1057               if (c == ':')
1058                 {
1059                   char *p;
1060
1061                   fname[fnameidx] = 0;
1062                   StripTrailingWSpaces (fname);
1063                   if ((p = strrchr (fname, '.')))
1064                     {
1065                       *p++ = 0;
1066                       StripTrailingWSpaces (fname);
1067                       index = atoi (p);
1068                       if (index < 0 || index > 255)
1069                         Err (2, "%s:%lu: invalid index of fieldname",
1070                              filename, lineno);
1071                     }
1072                   else
1073                     index = 0;  /* must calculate an index */
1074                   if (!*fname)
1075                     Err (2, "%s:%lu: empty fieldname", filename, lineno);
1076                   new_record_flag = 0;
1077                   f = StoreFieldname (fname, lineoff);
1078                   if (opt.sortmode == 2 && new_record_flag && !skip_kludge)
1079                     goto next_sortrecord;
1080                   skip_kludge = 0;
1081                   if (!index)
1082                     { /* detect the index */
1083                       /* first a shortcut: */
1084                       if ((d = f->data) && d->index == 1 && !d->activ)
1085                         index = 1;      /* that's it */
1086                       else
1087                         { /* find the highest unused index */
1088                           for (index = 1; d;)
1089                             {
1090                               if (d->index == index)
1091                                 {
1092                                   if (d->activ)
1093                                     {
1094                                       index++;
1095                                       d = f->data;
1096                                     }
1097                                   else
1098                                     break;
1099                                 }
1100                               else
1101                                 d = d->next;
1102                             }
1103                         }
1104                     }
1105                   else
1106                     { /* find a data slot for the given index. */
1107                       for (d = f->data; d; d = d->next)
1108                         if (d->index == index)
1109                           break;
1110                       if (d && d->activ)
1111                         Err (0, "%s:%lu: warning: %s.%d redefined",
1112                              filename, lineno, fname, index);
1113                     }
1114                   if (!d)
1115                     { /* create a new slot */
1116                       if ((d = unused_data))
1117                         unused_data = d->next;
1118                       else
1119                         {
1120                           d = xmalloc (sizeof *d + 100);
1121                           d->size = 100 + 1;
1122                         }
1123                       d->index = index;
1124                       d->next = NULL;
1125                       if (!f->data)
1126                         f->data = d;
1127                       else
1128                         {
1129                           DATA d2;
1130                           for (d2 = f->data; d2->next; d2 = d2->next)
1131                             ;
1132                           d2->next = d;
1133                         }
1134                     }
1135                   d->activ = 1;
1136                   d->used = 0;  /* used length */
1137                   pending_lf = 0;
1138                   state = sDATABEG;
1139                 }
1140               else
1141                 {
1142                   if (fnameidx >= FIELDNAMELEN)
1143                     Err (2, "%s:%ld: fieldname too long", filename, lineno);
1144                   fname[fnameidx++] = c;
1145                 }
1146               break;
1147             case sDATABEG:
1148               if (c == ' ' || c == '\t')
1149                 break;
1150               state = sDATA;
1151               /* fall thru */
1152             case sDATA:
1153               if (!d)
1154                 abort ();
1155               for (; pending_lf; pending_lf--)
1156                 {
1157                   if (d->used >= d->size)
1158                     d = ExpandDataSlot (f, d);
1159                   d->d[d->used++] = '\n';
1160                 }
1161               if (d->used >= d->size)
1162                 d = ExpandDataSlot (f, d);
1163               d->d[d->used++] = c;
1164               break;
1165             } /* end switch state after first column */
1166         }
1167     }
1168   if (ferror (fp))
1169     {
1170       fprintf (stderr, PGMNAME ":%s:%lu: read error: %s\n",
1171                filename, lineno, strerror (errno));
1172       exit (2);
1173     }
1174   if (!newline)
1175     {
1176       Err (0, "%s: warning: last line not terminated by a LF", filename);
1177     }
1178   if (opt.sortmode == 2)
1179     goto next_sortrecord;
1180
1181 ready:
1182   FinishRecord ();
1183   lineno--;
1184   if (opt.verbose)
1185     Err (0, "%s: %lu line%s processed", filename, lineno,
1186          lineno == 1 ? "" : "s");
1187
1188   if (fp != stdin)
1189     fclose (fp);
1190 }
1191
1192
1193 /*
1194  * Handle the fieldname.
1195  *
1196  * If we already have a field with this name in the current record, we
1197  * append a counter to the field (e.g. "Phone.1", "Phone.2", ... )
1198  * where a counter of 1 is same as the filed without a
1199  * count. Filednames are NOT casesensitiv. index o means: calculate an
1200  * index if this is an unknown field.
1201  *
1202  * Returns: a pointer to the field
1203  */
1204 static FIELD
1205 StoreFieldname (const char *fname, long offset)
1206 {
1207   unsigned long hash;
1208   NAMEBUCKET buck;
1209   FIELD fdes, f2;
1210
1211   for (buck = namebuckets[hash = HashName (fname)]; buck; buck = buck->next)
1212     if (!strcasecmp (buck->ptr->name, fname))
1213       {
1214         fdes = buck->ptr;
1215         break;
1216       }
1217
1218   if (buck && fdes == fieldlist)
1219     NewRecord (offset);
1220   else if (!buck)
1221     { /* A new fieldname.  */
1222       fdes = xcalloc (1, sizeof *fdes + strlen (fname));
1223       strcpy (fdes->name, fname);
1224       /* Create a hash entry to speed up field access.  */
1225       buck = xcalloc (1, sizeof *buck);
1226       buck->ptr = fdes;
1227       buck->next = namebuckets[hash];
1228       namebuckets[hash] = buck;
1229       /* Link the field into the record description.  */
1230       if (!fieldlist)
1231         fieldlist = fdes;
1232       else
1233         {
1234           for (f2 = fieldlist; f2->nextfield; f2 = f2->nextfield)
1235             ;
1236           f2->nextfield = fdes;
1237         }
1238     }
1239   fdes->valid = 1; /* This is in the current record.  */
1240   return fdes;
1241 }
1242
1243
1244
1245 /*
1246  * Replace the data slot DATA by an larger one.
1247  */
1248 static DATA
1249 ExpandDataSlot (FIELD field, DATA data)
1250 {
1251   DATA d, d2;
1252
1253   for (d = unused_data; d; d = d->next)
1254     if (d->size > data->size)
1255       break;
1256   if (!d)
1257     {
1258       d = xmalloc (sizeof *d + data->size + 200);
1259       d->size = data->size + 200 + 1;
1260     }
1261   memcpy (d->d, data->d, data->used);
1262   d->used = data->used;
1263   d->index = data->index;
1264   d->activ = data->activ;
1265   d->next = data->next;
1266   /* Link it into the field list.   */
1267   if (field->data == data)
1268     field->data = d;
1269   else
1270     {
1271       for (d2 = field->data; d2; d2 = d2->next)
1272         if (d2->next == data)
1273           break;
1274       if (!d2)
1275         abort (); /* ExpandDataSlot: data not linked to field.  */
1276       d2->next = d;
1277     }
1278   data->next = unused_data;
1279   unused_data = data;
1280   return d;
1281 }
1282
1283
1284 /*
1285  * Begin a new record after closing the last one.
1286  */
1287 static void
1288 NewRecord (long offset)
1289 {
1290   FinishRecord ();
1291   start_of_record = offset;
1292   new_record_flag = 1;
1293 }
1294
1295
1296 static FIELD
1297 GetFirstField ()
1298 {
1299   FIELD f;
1300   OUTFIELD of;
1301
1302   if (opt.outfields)
1303     {
1304       of = opt.outfields;
1305       for (f = fieldlist; f; f = f->nextfield)
1306         if (!strcmp (f->name, of->name))
1307           {
1308             next_outfield = of;
1309             return f;
1310           }
1311       next_outfield = NULL;
1312       return NULL;
1313     }
1314   return (next_field = fieldlist);
1315 }
1316
1317 static FIELD
1318 GetNextField ()
1319 {
1320   FIELD f;
1321   OUTFIELD of;
1322
1323   if (opt.outfields)
1324     {
1325       if (next_outfield && (of = next_outfield->next))
1326         {
1327           for (f = fieldlist; f; f = f->nextfield)
1328             if (!strcmp (f->name, of->name))
1329               {
1330                 next_outfield = of;
1331                 return f;
1332               }
1333         }
1334       next_outfield = NULL;
1335       return NULL;
1336     }
1337   return next_field ? (next_field = next_field->nextfield) : NULL;
1338 }
1339
1340
1341 /*
1342  * If we are in a record: close the current record.
1343  */
1344 static void
1345 FinishRecord ()
1346 {
1347   FIELD f;
1348   DATA d = NULL;
1349   int any = 0;
1350   size_t n;
1351   char *p;
1352   int indent;
1353
1354   if (!opt.checkonly && fieldlist && fieldlist->valid)
1355     {
1356       /* There is a valid record */
1357       if (opt.sortmode == 1)
1358         { /* Store only.  */
1359           SORT sort;
1360
1361           n = 0;
1362           for (f = fieldlist; f; f = f->nextfield)
1363             if (f->valid)
1364               for (d = f->data; d; d = d->next)
1365                 if (d->activ)
1366                   {
1367                     n = d->used;
1368                     goto okay;
1369                   }
1370         okay:
1371           sort = xcalloc (1, sizeof *sort + n + 1);
1372           sort->offset = start_of_record;
1373           memcpy (sort->d, d->d, n);
1374           sort->d[n] = 0; /* Make it a string.  */
1375           sort->next = sortlist;
1376           sortlist = sort;
1377         }
1378       else if (opt.texfile)
1379         {
1380           PrintTexFile (0);
1381         }
1382       else if (opt.format == 0)
1383         {
1384           for (f = GetFirstField (); f; f = GetNextField ())
1385             {
1386               if (f->valid)
1387                 {
1388                   int need_tab = 0;
1389                   for (d = f->data; d; d = d->next)
1390                     {
1391                       if (d->activ)
1392                         {
1393                           const char *s;
1394                           int i;
1395
1396                           if (need_tab)
1397                             putchar ('\t');
1398                           else if (any)
1399                             putchar (':');
1400                           for (i = 0, s = d->d; i < d->used; s++, i++)
1401                             {
1402                               if (*s == '%')
1403                                 fputs ("%25", stdout);
1404                               else if (*s == ':')
1405                                 fputs ("%3A", stdout);
1406                               else if (*s == '\n')
1407                                 fputs ("%0A", stdout);
1408                               else if (*s == '\t')
1409                                 putchar (' ');
1410                               else
1411                                 putchar (*s);
1412                             }
1413                           any = 1;
1414                           need_tab = 1;
1415                         }
1416                       else if (any && !need_tab)
1417                         putchar (':');
1418                     }
1419                 }
1420               else
1421                 {
1422                   if (any)
1423                     putchar (':');
1424                   else
1425                     any++;
1426                 }
1427             }
1428           putchar ('\n');
1429         }
1430       else if (opt.format == 1)
1431         {
1432           for (f = GetFirstField (); f; f = GetNextField ())
1433             {
1434               if (f->valid)
1435                 {
1436                   for (d = f->data; d; d = d->next)
1437                     if (d->activ)
1438                       {
1439                         if (d->index != 1)
1440                           printf ("%s%s.%d='%.*s'", any ? ":" : "",
1441                                   f->name, d->index, (int) d->used, d->d);
1442                         else
1443                           printf ("%s%s='%.*s'", any ? ":" : "",
1444                                   f->name, (int) d->used, d->d);
1445                         any = 1;
1446                       }
1447                 }
1448             }
1449           putchar ('\n');
1450         }
1451       else if (opt.format == 2)
1452         {
1453           PrintFormat2 (0);
1454         }
1455       else if (opt.format == 3)
1456         {
1457           for (f = GetFirstField (); f; f = GetNextField ())
1458             {
1459               if (f->valid)
1460                 {
1461                   for (d = f->data; d; d = d->next)
1462                     if (d->activ)
1463                       {
1464                         any = 1;
1465                         indent = printf ("%s: ", f->name);
1466                         for (n = 0, p = d->d; n < d->used; n++, p++)
1467                           if (*p == '\n')
1468                             break;
1469                         if (n < d->used) /* Multi-line output.  */
1470                           {
1471                             for (n = 0, p = d->d; n < d->used; n++, p++)
1472                               {
1473                                 putchar (*p);
1474                                 if (*p == '\n')
1475                                   printf ("%*s", indent, "");
1476                               }
1477                           }
1478                         else /* Singe-line output.  */
1479                           {
1480                             printf ("%.*s", (int) d->used, d->d);
1481                           }
1482                         putchar ('\n');
1483                       }
1484                 }
1485             }
1486           if (any)
1487             putchar ('\n');
1488         }
1489       else if (opt.format == 4) /* ';' delimited */
1490         {
1491           for (f = GetFirstField (); f; f = GetNextField ())
1492             {
1493               if (any)
1494                 putchar (';');
1495               if (f->valid)
1496                 {
1497                   int any2 = 0;
1498                   for (d = f->data; d; d = d->next)
1499                     if (d->activ)
1500                       {
1501                         if (any2)
1502                           putchar ('|');
1503                         any = 1;
1504                         any2 = 1;
1505                         for (n = 0, p = d->d; n < d->used; n++, p++)
1506                           {
1507                             if (*p == '\n')
1508                               putchar (' ');
1509                             else if (*p == ';')
1510                               putchar (',');
1511                             else
1512                               putchar (*p);
1513                           }
1514                       }
1515                 }
1516             }
1517           if (any)
1518             putchar ('\n');
1519         }
1520     }
1521   output_count++;
1522   for (f = fieldlist; f; f = f->nextfield)
1523     {
1524       f->valid = 0;
1525       /* Set the data blocks inactive.  */
1526       for (d = f->data; d; d = d->next)
1527         d->activ = 0;
1528     }
1529 }
1530
1531
1532
1533 static void
1534 PrintFormat2 (int flushit)
1535 {
1536   static int pending = 0;
1537   static int totlines = 0;
1538   static char *names[] = { "Name", "Street", "City", NULL };
1539   NAMEBUCKET buck;
1540   FIELD f = NULL;
1541   DATA d;
1542   int n, len, lines = 0;
1543   const char *name;
1544   static char buffers[3][40];
1545
1546   if (pending && totlines > 58)
1547     {
1548       putchar ('\f');
1549       totlines = 0;
1550     }
1551   if (flushit && pending)
1552     {
1553       for (n = 0; (name = names[n]); n++)
1554         {
1555           printf ("%-40s\n", buffers[n]);
1556           lines++;
1557           totlines++;
1558         }
1559     }
1560
1561   for (n = 0; !flushit && (name = names[n]); n++)
1562     {
1563       for (buck = namebuckets[HashName (name)]; buck; buck = buck->next)
1564         if (!strcasecmp (buck->ptr->name, name))
1565           {
1566             f = buck->ptr;
1567             break;
1568           }
1569       if (!f)
1570         continue;
1571
1572       for (d = f->data; d; d = d->next)
1573         if (d->activ && d->index == 1)
1574           break;
1575       if (!d)
1576         continue;
1577       if ((len = (int) d->used) > 38)
1578         len = 38;
1579
1580       if (!pending)
1581         sprintf (buffers[n], "%.*s", len, d->d);
1582       else
1583         {
1584           printf ("%-40s%.*s\n", buffers[n], len, d->d);
1585           lines++;
1586           totlines++;
1587         }
1588     }
1589   if (pending)
1590     {
1591       for (; lines < 5; lines++, totlines++)
1592         putchar ('\n');
1593     }
1594   if (flushit)
1595     {
1596       pending = 0;
1597       totlines = 0;
1598     }
1599   else
1600     pending = !pending;
1601 }
1602
1603
1604 static void
1605 PrintTexFile (int flushit)
1606 {
1607   char pseudo_op[200];
1608   int c, pseudo_op_idx = 0;
1609   int state = 0;
1610
1611   if (flushit && tex.end_block)
1612     {
1613       if (fseek (tex.fp, tex.end_block, SEEK_SET))
1614         {
1615           fprintf (stderr, PGMNAME ": error seeking to offset %ld\n",
1616                    tex.end_block);
1617           exit (1);
1618         }
1619     }
1620
1621   while ((c = getc (tex.fp)) != EOF)
1622     {
1623       switch (state)
1624         {
1625         case 0:
1626           if (c == '@')
1627             state = 1;
1628           else
1629             putchar (c);
1630           break;
1631         case 1:
1632           if (c == '@')
1633             {
1634               state = 2;
1635               pseudo_op_idx = 0;
1636             }
1637           else
1638             {
1639               putchar ('@');
1640               ungetc (c, tex.fp);
1641               state = 0;
1642             }
1643           break;
1644         case 2:         /* pseudo-op start */
1645           if (pseudo_op_idx >= sizeof (pseudo_op) - 1)
1646             {
1647               fprintf (stderr, PGMNAME ": pseudo-op too long\n");
1648               exit (1);
1649             }
1650           else if (c == '\n')
1651             {
1652               fprintf (stderr, PGMNAME ": invalid pseudo-op - ignored\n");
1653               pseudo_op[pseudo_op_idx] = 0;
1654               fputs (pseudo_op, stdout);
1655               putchar ('\n');
1656               state = 0;
1657             }
1658           else if (c == '@'
1659                    && pseudo_op_idx && pseudo_op[pseudo_op_idx - 1] == '@')
1660             {
1661               pseudo_op[pseudo_op_idx - 1] = 0;
1662               state = 0;
1663               if (!flushit && ProcessTexOp (pseudo_op))
1664                 return;
1665             }
1666           else
1667             pseudo_op[pseudo_op_idx++] = c;
1668           break;
1669
1670         default:
1671           abort ();
1672         }
1673     }
1674   if (c == EOF)
1675     {
1676       if (ferror (tex.fp))
1677         {
1678           fprintf (stderr, PGMNAME ":%s: read error\n", opt.texfile);
1679           exit (1);
1680         }
1681       else if (state)
1682         {
1683           fprintf (stderr, PGMNAME ":%s: unclosed pseudo-op\n", opt.texfile);
1684         }
1685     }
1686
1687 }
1688
1689
1690 static int
1691 ProcessTexOp (const char *op)
1692 {
1693   NAMEBUCKET buck;
1694   FIELD f;
1695   DATA d;
1696
1697   if (!strcasecmp (op, "begin-record-block"))
1698     {
1699       tex.in_record_block = 1;
1700       tex.begin_block = ftell (tex.fp);
1701     }
1702   else if (!strcasecmp (op, "end-record-block"))
1703     {
1704       tex.in_record_block = 0;
1705     }
1706   else if (!strcasecmp (op, "next-record") && tex.in_record_block)
1707     {
1708       tex.end_block = ftell (tex.fp);
1709       if (fseek (tex.fp, tex.begin_block, SEEK_SET))
1710         {
1711           fprintf (stderr, PGMNAME ": error seeking to offset %ld\n",
1712                    tex.begin_block);
1713           exit (1);
1714         }
1715       return 1;
1716     }
1717   else if (!tex.in_record_block)
1718     {
1719       fprintf (stderr,
1720                PGMNAME ": pseudo op '%s' not allowed in this context\n", op);
1721     }
1722   else /* Take it as the key to the record data. */
1723     {
1724       char *p = strchr (op, ':');
1725
1726       if (p)
1727         *p++ = 0; /* Strip modifier. */
1728
1729       f = NULL;
1730       for (buck = namebuckets[HashName (op)]; buck; buck = buck->next)
1731         if (!strcasecmp (buck->ptr->name, op))
1732           {
1733             f = buck->ptr;
1734             break;
1735           }
1736       if (f) /* We have an entry with this name.  */
1737         {
1738           for (d = f->data; d; d = d->next)
1739             if (d->activ)
1740               {
1741                 printf ("%s", d->index > 1 ? "\\par " : "");
1742                 if (p && !strncmp (p, "N=", 2))
1743                   {
1744                     size_t n;
1745
1746                     for (n = 0; n < d->used; n++)
1747                       if (d->d[n] == '\r')
1748                         ;
1749                       else if (d->d[n] == '\n')
1750                         fputs (p + 2, stdout);
1751                       else
1752                         putchar (((unsigned char *) d->d)[n]);
1753                   }
1754                 else
1755                   printf ("%.*s", (int) d->used, d->d);
1756               }
1757         }
1758     }
1759   return 0;
1760 }
1761
1762
1763 /*
1764  * Sort the sortlist
1765  */
1766 static void
1767 DoSort ()
1768 {
1769   size_t i, n;
1770   SORT s, *array;
1771
1772   for (n = 0, s = sortlist; s; s = s->next)
1773     n++;
1774   if (!n)
1775     return;
1776   array = xmalloc ((n + 1) * sizeof *array);
1777   for (n = 0, s = sortlist; s; s = s->next)
1778     array[n++] = s;
1779   array[n] = NULL;
1780   qsort (array, n, sizeof *array, DoSortFnc);
1781   sortlist = array[0];
1782   for (i = 0; i < n; i++)
1783     array[i]->next = array[i + 1];
1784 }
1785
1786 static int
1787 DoSortFnc (const void *arg_a, const void *arg_b)
1788 {
1789   SORT a = *(SORT *) arg_a;
1790   SORT b = *(SORT *) arg_b;
1791   return strcmp (a->d, b->d);
1792 }
1793
1794 /*
1795 Local Variables:
1796 compile-command: "cc -Wall -O2 -o addrutil addrutil.c"
1797 End:
1798 */