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