Added a nmem-handle to the http_session to simplify MM. Used to allocate session-
[pazpar2-moved-to-github.git] / src / settings.c
1 // $Id: settings.c,v 1.9 2007-04-10 00:53:24 quinn Exp $
2 // This module implements a generic system of settings (attribute-value) that can 
3 // be associated with search targets. The system supports both default values,
4 // per-target overrides, and per-user settings.
5
6 #include <string.h>
7 #include <stdio.h>
8 #include <sys/types.h>
9 #include <dirent.h>
10 #include <stdlib.h>
11 #include <sys/stat.h>
12
13 #include <libxml/parser.h>
14 #include <libxml/tree.h>
15
16 #include <yaz/nmem.h>
17 #include <yaz/log.h>
18
19 #include "pazpar2.h"
20 #include "database.h"
21 #include "settings.h"
22
23 static NMEM nmem = 0;
24
25 // Used for initializing setting_dictionary with pazpar2-specific settings
26 static char *hard_settings[] = {
27     "pz:piggyback",
28     "pz:elements",
29     "pz:requestsyntax",
30     "pz:cclmap:",
31     "pz:encoding",
32     "pz:xslt",
33     "pz:nativesyntax",
34     "pz:authentication",
35     "pz:allow",
36     "pz:maxrecs",
37     0
38 };
39
40 struct setting_dictionary
41 {
42     char **dict;
43     int size;
44     int num;
45 };
46
47 static struct setting_dictionary *dictionary = 0;
48
49 int settings_offset(const char *name)
50 {
51     int i;
52
53     if (!name)
54         name = "";
55     for (i = 0; i < dictionary->num; i++)
56         if (!strcmp(name, dictionary->dict[i]))
57             return i;
58     return -1;
59 }
60
61 // Ignores everything after second colon, if present
62 // A bit of a hack to support the pz:cclmap: scheme (and more to come?)
63 static int settings_offset_cprefix(const char *name)
64 {
65     const char *p;
66     int maxlen = 100;
67     int i;
68
69     if (!strncmp("pz:", name, 3) && (p = strchr(name + 3, ':')))
70         maxlen = (p - name) + 1;
71     for (i = 0; i < dictionary->num; i++)
72         if (!strncmp(name, dictionary->dict[i], maxlen))
73             return i;
74     return -1;
75 }
76
77 char *settings_name(int offset)
78 {
79     return dictionary->dict[offset];
80 }
81
82 static int isdir(const char *path)
83 {
84     struct stat st;
85
86     if (stat(path, &st) < 0)
87     {
88         yaz_log(YLOG_FATAL|YLOG_ERRNO, "%s", path);
89         exit(1);
90     }
91     return st.st_mode & S_IFDIR;
92 }
93
94 // Read settings from an XML file, calling handler function for each setting
95 static void read_settings_file(const char *path,
96         void (*fun)(struct setting *set))
97 {
98     xmlDoc *doc = xmlParseFile(path);
99     xmlNode *n;
100     xmlChar *namea, *targeta, *valuea, *usera, *precedencea;
101
102     if (!doc)
103     {
104         yaz_log(YLOG_FATAL, "Failed to parse %s", path);
105         exit(1);
106     }
107     n = xmlDocGetRootElement(doc);
108     namea = xmlGetProp(n, (xmlChar *) "name");
109     targeta = xmlGetProp(n, (xmlChar *) "target");
110     valuea = xmlGetProp(n, (xmlChar *) "value");
111     usera = xmlGetProp(n, (xmlChar *) "user");
112     precedencea = xmlGetProp(n, (xmlChar *) "precedence");
113     for (n = n->children; n; n = n->next)
114     {
115         if (n->type != XML_ELEMENT_NODE)
116             continue;
117         if (!strcmp((const char *) n->name, "set"))
118         {
119             char *name, *target, *value, *user, *precedence;
120
121             name = (char *) xmlGetProp(n, (xmlChar *) "name");
122             target = (char *) xmlGetProp(n, (xmlChar *) "target");
123             value = (char *) xmlGetProp(n, (xmlChar *) "value");
124             user = (char *) xmlGetProp(n, (xmlChar *) "user");
125             precedence = (char *) xmlGetProp(n, (xmlChar *) "precedence");
126
127             if ((!name && !namea) || (!value && !valuea) || (!target && !targeta))
128             {
129                 yaz_log(YLOG_FATAL, "set must specify name, value, and target");
130                 exit(1);
131             }
132             else
133             {
134                 struct setting set;
135                 char nameb[1024];
136                 char targetb[1024];
137                 char userb[1024];
138                 char valueb[1024];
139
140                 // Copy everything into a temporary buffer -- we decide
141                 // later if we are keeping it.
142                 if (precedence)
143                     set.precedence = atoi((char *) precedence);
144                 else if (precedencea)
145                     set.precedence = atoi((char *) precedencea);
146                 else
147                     set.precedence = 0;
148                 set.user = userb;
149                 if (user)
150                     strcpy(userb, user);
151                 else if (usera)
152                     strcpy(userb, (const char *) usera);
153                 else
154                     set.user = "";
155                 if (target)
156                     strcpy(targetb, target);
157                 else
158                     strcpy(targetb, (const char *) targeta);
159                 set.target = targetb;
160                 if (name)
161                     strcpy(nameb, name);
162                 else
163                     strcpy(nameb, (const char *) namea);
164                 set.name = nameb;
165                 if (value)
166                     strcpy(valueb, value);
167                 else
168                     strcpy(valueb, (const char *) valuea);
169                 set.value = valueb;
170                 set.next = 0;
171                 (*fun)(&set);
172             }
173             xmlFree(name);
174             xmlFree(precedence);
175             xmlFree(value);
176             xmlFree(user);
177             xmlFree(target);
178         }
179         else
180         {
181             yaz_log(YLOG_FATAL, "Unknown element %s in settings file", (char*) n->name);
182             exit(1);
183         }
184     }
185     xmlFree(namea);
186     xmlFree(precedencea);
187     xmlFree(valuea);
188     xmlFree(usera);
189     xmlFree(targeta);
190 }
191  
192 // Recursively read files in a directory structure, calling 
193 // callback for each one
194 static void read_settings(const char *path,
195                 void (*fun)(struct setting *set))
196 {
197     DIR *d;
198     struct dirent *de;
199
200     if (!(d = opendir(path)))
201     {
202         yaz_log(YLOG_FATAL|YLOG_ERRNO, "%s", path);
203         exit(1);
204     }
205     while ((de = readdir(d)))
206     {
207         char tmp[1024];
208         if (*de->d_name == '.' || !strcmp(de->d_name, "CVS"))
209             continue;
210         sprintf(tmp, "%s/%s", path, de->d_name);
211         if (isdir(tmp))
212             read_settings(tmp, fun);
213         else
214         {
215             char *dot;
216             if ((dot = rindex(de->d_name, '.')) && !strcmp(dot + 1, "xml"))
217                 read_settings_file(tmp, fun);
218         }
219     }
220     closedir(d);
221 }
222
223 // Callback. Adds a new entry to the dictionary if necessary
224 // This is used in pass 1 to determine layout of dictionary
225 static void prepare_dictionary(struct setting *set)
226 {
227     int i;
228     char *p;
229
230     if (!strncmp(set->name, "pz:", 3) && (p = strchr(set->name + 3, ':')))
231         *(p + 1) = '\0';
232     for (i = 0; i < dictionary->num; i++)
233         if (!strcmp(dictionary->dict[i], set->name))
234             return;
235     if (!strncmp(set->name, "pz:", 3)) // Probably a typo in config fle
236     {
237         yaz_log(YLOG_FATAL, "Unknown pz: setting '%s'", set->name);
238         exit(1);
239     }
240     // Create a new dictionary entry
241     // Grow dictionary if necessary
242     if (!dictionary->size)
243         dictionary->dict = nmem_malloc(nmem, (dictionary->size = 50) * sizeof(char*));
244     else if (dictionary->num + 1 > dictionary->size)
245     {
246         char **tmp = nmem_malloc(nmem, dictionary->size * 2 * sizeof(char*));
247         memcpy(tmp, dictionary->dict, dictionary->size * sizeof(char*));
248         dictionary->dict = tmp;
249         dictionary->size *= 2;
250     }
251     dictionary->dict[dictionary->num++] = nmem_strdup(nmem, set->name);
252 }
253
254 // This is called from grep_databases -- adds/overrides setting for a target
255 // This is also where the rules for precedence of settings are implemented
256 static void update_database(void *context, struct database *db)
257 {
258     struct setting *set = (struct setting *) context;
259     struct setting *s, **sp;
260     int offset;
261
262     if (!db->settings)
263     {
264         db->settings = nmem_malloc(nmem, sizeof(struct settings*) * dictionary->num);
265         memset(db->settings, 0, sizeof(struct settings*) * dictionary->num);
266     }
267     if ((offset = settings_offset_cprefix(set->name)) < 0)
268         abort(); // Should never get here
269
270     // First we determine if this setting is overriding  any existing settings
271     // with the same name.
272     for (s = db->settings[offset], sp = &db->settings[offset]; s;
273             sp = &s->next, s = s->next)
274         if (!strcmp(s->user, set->user) && !strcmp(s->name, set->name))
275         {
276             if (s->precedence < set->precedence)
277                 // We discard the value (nmem keeps track of the space)
278                 *sp = (*sp)->next;
279             else if (s->precedence > set->precedence)
280                 // Db contains a higher-priority setting. Abort 
281                 break;
282             if (*s->target == '*' && *set->target != '*')
283                 // target-specific value trumps wildcard. Delete.
284                 *sp = (*sp)->next;
285             else if (*s->target != '*' && *set->target == '*')
286                 // Db already contains higher-priority setting. Abort
287                 break;
288         }
289     if (!s) // s will be null when there are no higher-priority settings -- we add one
290     {
291         struct setting *new = nmem_malloc(nmem, sizeof(*new));
292
293         memset(new, 0, sizeof(*new));
294         new->precedence = set->precedence;
295         new->target = nmem_strdup(nmem, set->target);
296         new->name = nmem_strdup(nmem, set->name);
297         new->value = nmem_strdup(nmem, set->value);
298         new->user = nmem_strdup(nmem, set->user);
299         new->next = db->settings[offset];
300         db->settings[offset] = new;
301     }
302 }
303
304 // Callback -- updates database records with dictionary entries as appropriate
305 // This is used in pass 2 to assign name/value pairs to databases
306 static void update_databases(struct setting *set)
307 {
308     struct database_criterion crit;
309     struct database_criterion_value val;
310
311     // Update all databases which match pattern in set->target
312     crit.name = "id";
313     crit.values = &val;
314     crit.next = 0;
315     val.value = set->target;
316     val.next = 0;
317     grep_databases(set, &crit, update_database);
318 }
319
320 // This simply copies the 'hard' (application-specific) settings
321 // to the settings dictionary.
322 static void initialize_hard_settings(struct setting_dictionary *dict)
323 {
324     dict->dict = nmem_malloc(nmem, sizeof(hard_settings) - sizeof(char*));
325     dict->size = (sizeof(hard_settings) - sizeof(char*)) / sizeof(char*);
326     memcpy(dict->dict, hard_settings, dict->size * sizeof(char*));
327     dict->num = dict->size;
328 }
329
330 // If we ever decide we need to be able to specify multiple settings directories,
331 // the two calls to read_settings must be split -- so the dictionary is prepared
332 // for the contents of every directory before the databases are updated.
333 void settings_read(const char *path)
334 {
335     struct setting_dictionary *new;
336     if (!nmem)
337         nmem = nmem_create();
338     else
339         nmem_reset(nmem);
340     new = nmem_malloc(nmem, sizeof(*new));
341     memset(new, 0, sizeof(*new));
342     initialize_hard_settings(new);
343     dictionary = new;
344     read_settings(path, prepare_dictionary);
345     read_settings(path, update_databases);
346 }
347
348 /*
349  * Local variables:
350  * c-basic-offset: 4
351  * indent-tabs-mode: nil
352  * End:
353  * vim: shiftwidth=4 tabstop=8 expandtab
354  */