Resource system uses only one log debug level.
[egate.git] / res+log / gw-res.c
1 /*
2  * Implementation of resource management.
3  *
4  * Europagate, 1994-1995.
5  *
6  * $Log: gw-res.c,v $
7  * Revision 1.6  1995/04/19 12:12:07  adam
8  * Resource system uses only one log debug level.
9  *
10  * Revision 1.5  1995/02/23  08:32:22  adam
11  * Changed header.
12  *
13  * Revision 1.3  1995/02/21  14:00:11  adam
14  * Minor changes.
15  *
16  * Revision 1.2  1995/02/16  13:21:30  adam
17  * A few logging messages added.
18  *
19  * Revision 1.1.1.1  1995/02/09  17:27:12  adam
20  * Initial version of email gateway under CVS control.
21  *
22  * Initial:       Dec  8, 94 (Adam Dickmeiss)
23  */
24 #include <assert.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdarg.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <time.h>
32
33 #if HAVE_FLOCK
34 #include <sys/file.h>
35 #else
36 #include <sys/types.h>
37 #endif
38
39 #include <gw-log.h>
40 #include "gw-resp.h"
41
42 /*
43    symtab_open: Create empty symbol table.
44      symbol table handle is returned.
45  */
46
47 static struct res_symtab *symtab_open (void)
48 {
49     struct res_symtab *symtab;
50
51     symtab = malloc (sizeof(*symtab));
52     if (!symtab)
53         return NULL;
54     symtab->next = NULL;
55     return symtab;
56 }
57
58 /*
59    symtab_close: Delete symbol table.
60  */
61 static void symtab_close (struct res_symtab *symtab)
62 {
63     struct res_sym_entry *entry, *entry1;
64
65     for (entry = symtab->next; entry; entry = entry1)
66     {
67         entry1 = entry->next;
68         free (entry);
69     }
70     free (symtab);
71 }
72
73 /*
74    symtab_override: Add symbol to table. 'rl' holds symbol
75      table entry info. If the symbol is already present it
76      will override old info.
77  */
78 static int symtab_override (struct res_symtab *symtab,
79                             struct res_line_info *rl)
80 {
81     struct res_sym_entry *entry;
82
83     for (entry = symtab->next; entry; entry=entry->next)
84         if (!strcmp (entry->info->name, rl->name))
85         {
86             entry->info = rl;
87             return 1;
88         }
89     entry = malloc (sizeof(*entry));
90     if (!entry)
91         return -1;
92     entry->next = symtab->next;
93     symtab->next = entry;
94     entry->info = rl;
95     return 0;
96 }
97
98 /*
99    symtab_lookup: Symbol table lookup. If successful info is returned; 
100      otherwise NULL is returned.
101  */
102 static struct res_line_info *symtab_lookup (struct res_symtab *symtab, 
103                                             const char *name)
104 {
105     struct res_sym_entry *entry;
106
107     for (entry = symtab->next; entry; entry=entry->next)
108         if (!strcmp (entry->info->name, name))
109             return entry->info;
110     return NULL;
111 }
112
113 /*
114    lock_file: File locking using fcntl.
115  */
116 #if !HAVE_FLOCK
117 static void lock_file (int fd, int type)
118 {
119     struct flock area;
120     area.l_type = type;
121     area.l_whence = SEEK_SET;
122     area.l_start = 0L;
123     area.l_len = 0L;
124     fcntl (fd, F_SETLKW, &area);
125 }
126 #endif
127
128 /*
129    gw_res_init: A resource handle is returned by this function
130      describing empty resources.
131  */
132 GwRes gw_res_init (void)
133 {
134     GwRes p;
135
136     if (!(p = malloc (sizeof(*p))))
137         return p;
138     p->files = NULL;
139     p->symtab = symtab_open ();
140     if (!p->symtab)
141     {
142         free (p);
143         return NULL;
144     }
145     return p;
146 }
147
148 /*
149    gw_res_close: The resources described by 'id' are freed.
150       No further references to 'id' are allowed.
151  */
152 void gw_res_close (GwRes id)
153 {
154     struct res_line_info *rl, *rl1;
155     struct res_file_info *rf, *rf1;
156     
157     assert (id);
158
159     symtab_close (id->symtab);
160     for (rf = id->files; rf; rf = rf1)
161     {
162         for (rl = rf->lines; rl; rl = rl1)
163         {
164             free (rl->name);
165             free (rl->value);
166             rl1 = rl->next;
167             free (rl);
168         }
169         free (rf->fname);
170         rf1 = rf->next;
171         free (rf);
172     }
173     free (id);
174 }
175
176 /*
177    add_name: add a node with line information.
178  */
179 static struct res_line_info *add_name (enum res_kind kind, 
180                                        const char *name, const char *value)
181 {
182     struct res_line_info *rl;
183
184     if (!(rl = malloc (sizeof(*rl))))
185         return NULL;
186     rl->next = NULL;
187     rl->kind = kind;
188     if (name)
189     {
190         rl->name = gw_strdup (name);
191         if (!rl->name)
192         {
193             free (rl);
194             return NULL;
195         }
196     }
197     else
198         rl->name = NULL;
199     if (value)
200     {
201         rl->value = gw_strdup (value);
202         if (!rl->value)
203         {
204             free (rl->name);
205             free (rl);
206             return NULL;
207         }
208     }
209     else
210         rl->value = NULL;
211     return rl;
212 }
213
214 /*
215    gw_res_merge: The resources described by 'id' are merged by the contents
216      of  'filename'. If a resource is duplicated (in both resources 'id'
217      and the file) the resource is set to the value specified in 'filename'.
218      This function returns 0 on success; -1 on failure ('filename'
219      could not be read)
220  */
221 int gw_res_merge (GwRes id, const char *filename)
222 {
223     FILE *inf;
224     char buffer[1024];
225     char value[2048];
226     struct res_file_info *ri;
227     struct res_line_info **rlp, *rl;
228     struct res_line_info *rl_last = NULL;
229     int err = 0;
230
231     assert (id);
232     assert (filename);
233     gw_log (RES_DEBUG, "res", "gw_res_merge");
234     gw_log (RES_DEBUG, "res", "checking %s", filename);
235     if (!(inf = fopen (filename, "r")))
236         return -1;
237 #if HAVE_FLOCK
238     flock (fileno(inf), LOCK_SH);
239 #else
240     lock_file (fileno (inf), F_RDLCK);
241 #endif
242     if (!(ri = malloc (sizeof (*ri))))
243     {
244         fclose (inf);
245         return -1;
246     }
247     if (!(ri->fname = gw_strdup (filename)))
248     {
249         free (ri);
250         fclose (inf);
251         return -2;
252     }
253     gw_log (RES_DEBUG, "res", "reading %s", filename);
254     ri->next = id->files;
255     id->files = ri;
256     rlp = &ri->lines;
257     ri->lines = NULL;
258     while (fgets (buffer, sizeof(buffer)-1, inf))
259     {
260         char *cp;
261         while ((cp = strchr (buffer, '\n')))
262             *cp = '\0';
263         if (*buffer == '#')
264         {   /* comment line */
265             rl = add_name (comment, NULL, buffer);
266             if (!rl)
267             {
268                 err = -2;
269                 break;
270             }
271             *rlp = rl;
272             rlp = &rl->next;
273         }
274         else if (*buffer == '\0' || *buffer == ' ' || *buffer == '\t')
275         {
276             int i = 0;
277             while (buffer[i] == ' ' || buffer[i] == '\t')
278                 i++;
279             if (buffer[i] == '\0')
280             {   /* empty line */
281                 rl = add_name (blank, NULL, NULL);
282                 if (!rl)
283                 {
284                     err = -2;
285                     break;
286                 }
287                 *rlp = rl;
288                 rlp = &rl->next;
289             }
290             else
291             {   /* continuation line */
292                 int j = strlen (buffer)-1;
293                 /* strip trailing blanks */
294                 while (buffer[j] == '\t' || buffer[j] == ' ')
295                     --j;
296                 buffer[j+1] = '\0';
297                 if (rl_last)
298                 {
299                     if (strlen(value)+strlen(buffer+i) >= sizeof(value)-2)
300                     {
301                         gw_log (GW_LOG_WARN, "res", "Resource `%s' is "
302                                 " truncated", rl_last->name);
303                     }
304                     else
305                     {
306                         /* effectively add one blank, then buffer */
307                         strcat (value, " ");
308                         strcat (value, buffer+i);
309                     }
310                 }
311                 else
312                     gw_log (GW_LOG_WARN, "res", "Resource file has bad "
313                             "continuation line");
314             }
315         }
316         else 
317         {   /* resource line */
318             int i = 0;
319             if (rl_last)
320             {
321                 rl_last->value = gw_strdup (value);
322                 rl_last = NULL;
323             }
324             while (buffer[i] && buffer[i] != ':')
325                 i++;
326             if (buffer[i] == ':')
327             {
328                 int j = strlen(buffer)-1;
329                 buffer[i++] = '\0';            /* terminate name */
330                 while (buffer[i] == ' ' || buffer[i] == '\t')
331                     i++;                       /* skip blanks before */
332                 while (buffer[j] == '\t' || buffer[j] == ' ')
333                     --j;                       /* skip blanks after */
334                 buffer[j+1] = '\0';            /* terminate value */
335                 strcpy (value, buffer+i);
336                 rl_last = add_name (resource, buffer, NULL);
337                 if (!rl_last)
338                     err = -2;
339                 else
340                 {
341                     *rlp = rl_last;
342                     rlp = &rl_last->next;
343                 }
344             }
345         }
346     }
347     if (rl_last)
348         rl_last->value = gw_strdup (value);
349 #if HAVE_FLOCK
350     flock (fileno (inf), LOCK_UN);
351 #else
352     lock_file (fileno (inf), F_UNLCK);
353 #endif
354     fclose (inf);
355     gw_log (RES_DEBUG, "res", "close of %s", filename);
356     for (rl = ri->lines; rl; rl = rl->next)
357     {
358         switch (rl->kind)
359         {
360         case comment:
361             gw_log (RES_DEBUG, "res", "%s", rl->value);
362             break;
363         case resource:
364             gw_log (RES_DEBUG, "res", "%s: %s", rl->name, rl->value);
365             if (symtab_override (id->symtab, rl) < 0)
366                 err = -2;
367             break;
368         case blank:
369             gw_log (RES_DEBUG, "res", "");
370             break;
371         default:
372             assert (0);
373         }
374     }
375     gw_log (RES_DEBUG, "res", "gw_res_merge returned %d", err);
376     return err;
377 }
378
379 /*
380    gw_res_get: The resource with name 'name' is checked in the resources
381      represented by 'id'. If the resource is present a pointer to the
382      value (null-terminated string) is returned. If the value is not
383      present the value of 'def' is returned.
384  */
385 const char *gw_res_get (GwRes id, const char *name, const char *def)
386 {
387     struct res_line_info *rl;
388
389     assert (id);
390     rl = symtab_lookup (id->symtab, name);
391     if (!rl)
392         return def;
393     return rl->value;
394 }
395
396 /*
397    gw_res_put: Change a resource - modify if it exists - add if not
398      already there. The resource will have impact on the file name
399      'fname'. Use gw_res_commit (see below) to actually write to the
400      resource file.
401  */
402 int gw_res_put (GwRes id, const char *name, const char *value, 
403                 const char *fname)
404 {
405     struct res_file_info *ri;
406     struct res_line_info **rlp;
407     assert (id);
408     assert (fname);
409
410     for (ri = id->files; ri; ri = ri->next)
411         if (!strcmp (ri->fname, fname))
412             break;
413     if (!ri)
414     {
415         if (!(ri = malloc (sizeof (*ri))))
416             return -1;
417         if (!(ri->fname = gw_strdup (fname)))
418         {
419             free (ri);
420             return -1;
421         }
422         ri->next = id->files;
423         id->files = ri;
424         ri->lines = NULL;
425     }
426     for (rlp = &ri->lines; *rlp; rlp = &(*rlp)->next)
427         if (!strcmp ((*rlp)->name, name))
428             break;
429     if (*rlp)
430     {
431         char *new_val = gw_strdup (value);
432         if (!new_val)
433             return -1;
434         free ((*rlp)->value);
435         (*rlp)->value = new_val;
436     }
437     else
438     {
439         *rlp = add_name (resource, name, value);
440         if (!*rlp)
441             return -1;
442         (*rlp)->next = NULL;
443         if (symtab_override (id->symtab, *rlp) < 0)
444             return -1;
445     }
446     return 0;
447 }
448
449 /*
450    gw_res_commit: Write the resource file 'fname'. If resources
451      are modified/added then these will be written now.
452  */
453 int gw_res_commit (GwRes id, const char *fname)
454 {
455     struct res_file_info *ri;
456     struct res_line_info *rl;
457     FILE *out;
458     int i, pos;
459
460     assert (id);
461     assert (fname);
462
463     for (ri = id->files; ri; ri = ri->next)
464         if (!strcmp (ri->fname, fname))
465             break;
466     if (!ri)
467         return -1;
468     if (!(out = fopen (fname, "w")))
469         return -1;
470 #if HAVE_FLOCK
471     flock (fileno (out), LOCK_EX);
472 #else
473     lock_file (fileno (out), F_WRLCK);
474 #endif
475     for (rl = ri->lines; rl; rl = rl->next)
476         switch (rl->kind)
477         {
478         case comment:
479             fputs (rl->value, out);
480         case blank:
481             fputc ('\n', out);
482             break;
483         case resource:
484             fprintf (out, "%s: ", rl->name);
485             pos = strlen(rl->name)+2;
486             i = 0;
487             while (1)
488             {
489                 int left = 78-pos;
490                 if ((int) strlen(rl->value+i) <= left)
491                     break;
492                 while (left > 0)
493                 {
494                     if (rl->value[i+left] == ' ')
495                         break;
496                     
497                     --left;
498                 }
499                 if (left > 0)
500                 {
501                     int j;
502                     for (j = 0; j<left; j++)
503                         fputc (rl->value[i+j], out);
504                     i += left+1;
505                 }
506                 else
507                     break;
508                 fprintf (out, "\n ");
509                 pos = 2;
510             }
511             fprintf (out, "%s\n", rl->value+i);
512             break;
513         default:
514             assert (0);
515         }
516     fflush (out);
517 #if HAVE_FLOCK
518     flock (fileno (out), LOCK_UN);
519 #else
520     lock_file (fileno (out), F_UNLCK);
521 #endif
522     fclose (out);
523     return 0;
524 }
525
526 /*
527    gw_res_trav: Traverse resources associated with file 'fname'. For
528      each resource the handler 'tf' is invoked with name and value.
529  */
530 int gw_res_trav (GwRes id, const char *fname, void (*tf)(const char *name,
531                                                          const char *value))
532 {
533     assert (id);
534     assert (tf);
535
536     if (fname)
537     {
538         struct res_file_info *ri;
539         struct res_line_info *rl;
540
541         for (ri = id->files; ri; ri = ri->next)
542             if (!strcmp (ri->fname, fname))
543                 break;
544         if (!ri)
545             return -1;
546         for (rl = ri->lines; rl; rl = rl->next)
547             if (rl->kind == resource)
548                 (*tf) (rl->name, rl->value);
549     }
550     else
551     {
552         struct res_sym_entry *entry;
553         
554         for (entry = id->symtab->next; entry; entry=entry->next)
555             (*tf) (entry->info->name, entry->info->value);
556     }
557     return 0;
558 }
559
560
561