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