Logging channels and mutexes + a few other things
[pazpar2-moved-to-github.git] / src / database.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2010 Index Data
3
4 Pazpar2 is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20 #if HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <libxml/parser.h>
25 #include <libxml/tree.h>
26 #include <assert.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <yaz/log.h>
30 #include <yaz/nmem.h>
31
32 #include "session.h"
33 #include "host.h"
34 #include "pazpar2_config.h"
35 #include "settings.h"
36 #include "http.h"
37 #include "zeerex.h"
38 #include "database.h"
39
40 #include <sys/types.h>
41 #if HAVE_SYS_SOCKET_H
42 #include <sys/socket.h>
43 #endif
44 #if HAVE_NETDB_H
45 #include <netdb.h>
46 #endif
47 #if HAVE_NETINET_IN_H
48 #include <netinet/in.h>
49 #endif
50
51 enum pazpar2_database_criterion_type {
52     PAZPAR2_STRING_MATCH,
53     PAZPAR2_SUBSTRING_MATCH
54 };
55
56 struct database_criterion_value {
57     char *value;
58     struct database_criterion_value *next;
59 };
60
61 struct database_criterion {
62     char *name;
63     enum pazpar2_database_criterion_type type;
64     struct database_criterion_value *values;
65     struct database_criterion *next;
66 };
67
68
69 struct database_hosts {
70     struct host *hosts;
71     YAZ_MUTEX mutex;
72 };
73
74 static xmlDoc *get_explain_xml(struct conf_targetprofiles *targetprofiles,
75                                const char *id)
76 {
77     struct stat st;
78     char *dir;
79     char path[256];
80     char ide[256];
81     if (targetprofiles->type != Targetprofiles_local)
82     {
83         yaz_log(YLOG_FATAL, "Only supports local type");
84         return 0;
85     }
86     dir = targetprofiles->src;
87     urlencode(id, ide);
88     sprintf(path, "%s/%s", dir, ide);
89     if (!stat(path, &st))
90         return xmlParseFile(path);
91     else
92         return 0;
93 }
94
95 // Create a new host structure for hostport
96 static struct host *create_host(const char *hostport, iochan_man_t iochan_man)
97 {
98     struct host *host;
99
100     host = xmalloc(sizeof(struct host));
101     host->hostport = xstrdup(hostport);
102     host->connections = 0;
103     host->ipport = 0;
104     host->mutex = 0;
105
106     if (host_getaddrinfo(host, iochan_man))
107     {
108         xfree(host->hostport);
109         xfree(host);
110         return 0;
111     }
112     yaz_mutex_create(&host->mutex);
113     yaz_mutex_set_name(host->mutex, "host");
114
115     return host;
116 }
117
118 static struct host *find_host(database_hosts_t hosts,
119                               const char *hostport, iochan_man_t iochan_man)
120 {
121     struct host *p;
122     yaz_mutex_enter(hosts->mutex);
123     for (p = hosts->hosts; p; p = p->next)
124         if (!strcmp(p->hostport, hostport))
125             break;
126     if (!p)
127     {
128         p = create_host(hostport, iochan_man);
129         if (p)
130         {
131             p->next = hosts->hosts;
132             hosts->hosts = p;
133         }
134     }
135     yaz_mutex_leave(hosts->mutex);
136     return p;
137 }
138
139 int resolve_database(struct conf_service *service, struct database *db)
140 {
141     if (db->host == 0)
142     {
143         struct host *host;
144         char *p;
145         char hostport[256];
146         strcpy(hostport, db->url);
147         if ((p = strchr(hostport, '/')))
148             *p = '\0';
149         if (!(host = find_host(service->server->database_hosts,
150                                hostport, service->server->iochan_man)))
151             return -1;
152         db->host = host;
153     }
154     return 0;
155 }
156
157 void resolve_databases(struct conf_service *service)
158 {
159     struct database *db = service->databases;
160     for (; db; db = db->next)
161         resolve_database(service, db);
162 }
163
164 struct database *new_database(const char *id, NMEM nmem)
165 {
166     struct database *db;
167     char hostport[256];
168     char *dbname;
169     struct setting *idset;
170
171     if (strlen(id) > 255)
172         return 0;
173     strcpy(hostport, id);
174     if ((dbname = strchr(hostport, '/')))
175         *(dbname++) = '\0';
176     else
177         dbname = "";
178     db = nmem_malloc(nmem, sizeof(*db));
179     memset(db, 0, sizeof(*db));
180     db->host = 0;
181     db->url = nmem_strdup(nmem, id);
182     db->databases = nmem_malloc(nmem, 2 * sizeof(char *));
183     db->databases[0] = nmem_strdup(nmem, dbname);
184     db->databases[1] = 0;
185     db->errors = 0;
186     db->explain = 0;
187
188     db->num_settings = PZ_NEGOTIATION_CHARSET+1;
189     db->settings = nmem_malloc(nmem, sizeof(struct settings*) * 
190                                db->num_settings);
191     memset(db->settings, 0, sizeof(struct settings*) * db->num_settings);
192     idset = nmem_malloc(nmem, sizeof(*idset));
193     idset->precedence = 0;
194     idset->name = "pz:id";
195     idset->target = idset->value = db->url;
196     idset->next = 0;
197     db->settings[PZ_ID] = idset;
198     db->next = 0;
199
200     return db;
201 }
202
203 static struct database *load_database(const char *id,
204                                       struct conf_service *service)
205 {
206     struct database *db;
207     struct zr_explain *explain = 0;
208     xmlDoc *doc = 0;
209
210     if (service->targetprofiles 
211         && (doc = get_explain_xml(service->targetprofiles, id)))
212     {
213         explain = zr_read_xml(service->nmem, xmlDocGetRootElement(doc));
214         if (!explain)
215             return 0;
216     }
217     db = new_database(id, service->nmem);
218     db->explain = explain;
219     db->next = service->databases;
220     service->databases = db;
221
222     return db;
223 }
224
225 // Return a database structure by ID. Load and add to list if necessary
226 // new==1 just means we know it's not in the list
227 struct database *find_database(const char *id, struct conf_service *service)
228 {
229     struct database *p;
230     for (p = service->databases; p; p = p->next)
231         if (!strcmp(p->url, id))
232             return p;
233     return load_database(id, service);
234 }
235
236 // This whole session_grep database thing should be moved elsewhere
237
238 int match_zurl(const char *zurl, const char *pattern)
239 {
240     int len;
241
242     if (!strcmp(pattern, "*"))
243         return 1;
244     else if (!strncmp(pattern, "*/", 2))   // host wildcard.. what the heck is that for?
245     {
246         char *db = strchr(zurl, '/');
247         if (!db)
248             return 0;
249         if (!strcmp(pattern + 2, db))
250             return 1;
251         else
252             return 0;
253     }
254     else if (*(pattern + (len = strlen(pattern) - 1)) == '*')  // db wildcard
255     {
256         if (!strncmp(pattern, zurl, len))
257             return 1;
258         else
259             return 2;
260     }
261     else if (!strcmp(pattern, zurl))
262         return 1;
263     else
264         return 0;
265 }
266
267 // This will be generalized at some point
268 static int match_criterion(struct setting **settings,
269                            struct conf_service *service, 
270                            struct database_criterion *c)
271 {
272     int offset = settings_lookup_offset(service, c->name);
273     struct database_criterion_value *v;
274
275     if (offset < 0)
276     {
277         yaz_log(YLOG_WARN, "Criterion not found: %s", c->name);
278         return 0;
279     }
280     if (!settings[offset])
281         return 0;
282     for (v = c->values; v; v = v->next)
283     {
284         if (c->type == PAZPAR2_STRING_MATCH)
285         {
286             if (offset == PZ_ID)
287             {
288                 if (match_zurl(settings[offset]->value, v->value))
289                     break;
290             }
291             else 
292             {
293                 if (!strcmp(settings[offset]->value, v->value))
294                     break;
295             }
296         }            
297         else if (c->type == PAZPAR2_SUBSTRING_MATCH)
298         {
299             if (strstr(settings[offset]->value, v->value))
300                 break;
301         }
302     }
303     if (v)
304         return 1;
305     else
306         return 0;
307 }
308
309 // parses crit1=val1,crit2=val2|val3,...
310 static struct database_criterion *create_database_criterion(NMEM m,
311                                                             const char *buf)
312 {
313     struct database_criterion *res = 0;
314     char **values;
315     int num;
316     int i;
317
318     if (!buf || !*buf)
319         return 0;
320     nmem_strsplit(m, ",", buf,  &values, &num);
321     for (i = 0; i < num; i++)
322     {
323         char **subvalues;
324         int subnum;
325         int subi;
326         struct database_criterion *new = nmem_malloc(m, sizeof(*new));
327         char *eq;
328         if ((eq = strchr(values[i], '=')))
329             new->type = PAZPAR2_STRING_MATCH;
330         else if ((eq = strchr(values[i], '~')))
331             new->type = PAZPAR2_SUBSTRING_MATCH;
332         else
333         {
334             yaz_log(YLOG_WARN, "Missing equal-sign/tilde in filter");
335             return 0;
336         }
337         *(eq++) = '\0';
338         new->name = values[i];
339         nmem_strsplit(m, "|", eq, &subvalues, &subnum);
340         new->values = 0;
341         for (subi = 0; subi < subnum; subi++)
342         {
343             struct database_criterion_value *newv
344                 = nmem_malloc(m, sizeof(*newv));
345             newv->value = subvalues[subi];
346             newv->next = new->values;
347             new->values = newv;
348         }
349         new->next = res;
350         res = new;
351     }
352     return res;
353 }
354
355 static int database_match_criteria(struct setting **settings,
356                                    struct conf_service *service,
357                                    struct database_criterion *cl)
358 {
359     for (; cl; cl = cl->next)
360         if (!match_criterion(settings, service, cl))
361             break;
362     if (cl) // one of the criteria failed to match -- skip this db
363         return 0;
364     else
365         return 1;
366 }
367
368 // Cycles through databases, calling a handler function on the ones for
369 // which all criteria matched.
370 int session_grep_databases(struct session *se, const char *filter,
371                            void (*fun)(void *context, struct session_database *db))
372 {
373     struct session_database *p;
374     NMEM nmem = nmem_create();
375     int i = 0;
376     struct database_criterion *cl = create_database_criterion(nmem, filter);
377
378     for (p = se->databases; p; p = p->next)
379     {
380         if (p->settings && p->settings[PZ_ALLOW] && *p->settings[PZ_ALLOW]->value == '0')
381             continue;
382         if (!p->settings[PZ_NAME])
383             continue;
384         if (database_match_criteria(p->settings, se->service, cl))
385         {
386             (*fun)(se, p);
387             i++;
388         }
389     }
390     nmem_destroy(nmem);
391     return i;
392 }
393
394 int predef_grep_databases(void *context, struct conf_service *service,
395                           void (*fun)(void *context, struct database *db))
396 {
397     struct database *p;
398     int i = 0;
399
400     for (p = service->databases; p; p = p->next)
401         if (database_match_criteria(p->settings, service, 0))
402         {
403             (*fun)(context, p);
404             i++;
405         }
406     return i;
407 }
408
409 database_hosts_t database_hosts_create(void)
410 {
411     database_hosts_t p = xmalloc(sizeof(*p));
412     p->hosts = 0;
413     p->mutex = 0;
414     yaz_mutex_create(&p->mutex);
415     yaz_mutex_set_name(p->mutex, "database");
416     return p;
417 }
418
419 void database_hosts_destroy(database_hosts_t *pp)
420 {
421     if (*pp)
422     {
423         struct host *p = (*pp)->hosts;
424         while (p)
425         {
426             struct host *p_next = p->next;
427             yaz_mutex_destroy(&p->mutex);
428             xfree(p->ipport);
429             xfree(p->hostport);
430             xfree(p);
431             p = p_next;
432         }
433         yaz_mutex_destroy(&(*pp)->mutex);
434         xfree(*pp);
435     }
436 }
437
438 /*
439  * Local variables:
440  * c-basic-offset: 4
441  * c-file-style: "Stroustrup"
442  * indent-tabs-mode: nil
443  * End:
444  * vim: shiftwidth=4 tabstop=8 expandtab
445  */
446