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