/* * Copyright 2008 Red Hat, Inc. * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This Program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this Program; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * */ #ifdef HAVE_CONFIG_H #include "../config.h" #endif #include #include #include #include #include #include #ifdef HAVE_DIRSRV_SLAPI_PLUGIN_H #include #include #include #else #include #endif #include #include "backend.h" #include "dispatch.h" #include "map.h" #include "portmap.h" #include "wrap.h" /* The singleton for the cache. */ struct { char *master; struct domain { char *name; struct map { /* Map name and order. */ char *name; time_t last_changed; /* Individual map entries. */ struct map_entry { /* Links to previous and next nodes in the * list. */ struct map_entry *prev, *next; /* The name of the backend entry for this * entry, and the names of others which * contributed to the value. */ char *id, **related_ids; /* Key and value. */ char *key; unsigned int key_len; char *value; unsigned int value_len; } *entries; /* Search trees to speed up searches for entries. */ void *key_tree; void *id_tree; /* Callback data supplied by the map writer. */ void *backend_data; void (*free_backend_data)(void *backend_data); } *maps; int n_maps; } *domains; int n_domains; struct wrapped_rwlock *lock; } map_data; static void * xmemdup(char *region, int size) { char *ret; ret = malloc(size + 1); if (ret != NULL) { memcpy(ret, region, size); ret[size] = '\0'; } return ret; } /* Comparison functions used by the tree storage facility. */ static int t_compare_entry_by_id(const void *p1, const void *p2) { const struct map_entry *e1, *e2; e1 = p1; e2 = p2; return strcmp(e1->id, e2->id); } static int t_compare_entry_by_key(const void *p1, const void *p2) { const struct map_entry *e1, *e2; unsigned int key_len; int eq; e1 = p1; e2 = p2; if (e1->key_len == e2->key_len) { return memcmp(e1->key, e2->key, e1->key_len); } else { key_len = (e1->key_len < e2->key_len) ? e1->key_len : e2->key_len; eq = memcmp(e1->key, e2->key, key_len); if (eq != 0) { return eq; } else { return (e1->key_len < e2->key_len) ? -1 : 1; } } } /* Utility function - find the domain record for the named domain. */ static struct domain * map_data_find_domain(struct plugin_state *state, const char *domain_name) { int i; for (i = 0; i < map_data.n_domains; i++) { if (strcmp(domain_name, map_data.domains[i].name) == 0) { return &map_data.domains[i]; } } return NULL; } /* Utility function - find the map record for the named map in the named * domain. */ static struct map * map_data_find_map(struct plugin_state *state, const char *domain_name, const char *map_name) { int i; struct domain *domain; domain = map_data_find_domain(state, domain_name); if (domain != NULL) { for (i = 0; i < domain->n_maps; i++) { if (strcmp(map_name, domain->maps[i].name) == 0) { return &domain->maps[i]; } } } return NULL; } /* Utility functions - find a specific entry in the named map in the named * domain. */ static struct map_entry * map_data_find_map_entry(struct plugin_state *state, struct map *map, unsigned int key_len, const char *key) { struct map_entry **entry, entry_template; void **p; if (map == NULL) { return NULL; } entry_template.key = (char *) key; entry_template.key_len = key_len; entry = tfind(&entry_template, &map->key_tree, t_compare_entry_by_key); return entry ? *entry : NULL; } static struct map_entry * map_data_find_entry(struct plugin_state *state, const char *domain_name, const char *map_name, unsigned int key_len, const char *key) { struct map *map; map = map_data_find_map(state, domain_name, map_name); return map_data_find_map_entry(state, map, key_len, key); } static struct map_entry * map_data_find_map_entry_id(struct plugin_state *state, struct map *map, const char *id) { struct map_entry **entry, entry_template; void **p; if (map == NULL) { return NULL; } entry_template.id = (char *) id; entry = tfind(&entry_template, &map->id_tree, t_compare_entry_by_id); return entry ? *entry : NULL; } /* Handlers for the list of related IDs. On this side we actually store them * as one contiguous chunk of memory. */ static bool_t map_id_in_id_list(const char *id, char **id_list) { while ((*id_list) != NULL) { if (strcmp(id, (*id_list)) == 0) { return TRUE; } id_list++; } return FALSE; } /* Duplicate a passed-in list of strings. The result is stored in one chunk of * memory: pointers first, then the individual strings. */ static char ** map_id_dup_id_list(const char **in_id_list) { int i, l; char **ret, *s; /* Handle the NULL case. */ if (in_id_list == NULL) { return NULL; } /* Count the amount of space needed for the strings. */ for (i = 0, l = 0; (in_id_list[i] != NULL); i++) { l += (strlen(in_id_list[i]) + 1); } /* No strings = no list. */ if (i == 0) { return NULL; } /* Allocate space for the array of pointers (with NULL terminator) and * then the string data. */ ret = malloc((i + 1) * sizeof(char *) + l); if (ret != NULL) { /* Figure out where the string data will start. */ s = (char *) &ret[i + 1]; for (i = 0; (in_id_list[i] != NULL); i++) { /* Set the address of this string, copy the data * around, and then prepare the address of the next * string. */ ret[i] = s; strcpy(s, in_id_list[i]); s += (strlen(s) + 1); } /* NULL-terminate the array. */ ret[i] = NULL; } return ret; } /* Free the list -- since it's one chunk of memory, we shouldn't try anything * complicated. */ static void map_id_free_id_list(char **id_list) { free(id_list); } /* Iterate through every entry in every map, calling the callback if "all" is * true, or if "id" is not NULL and matches the entry's ID, or if the * "related_id" is not NULL and in the list of related IDs. If the callback * returns FALSE, then we abort and return FALSE, otherwise we return TRUE. */ static bool_t map_data_foreach_entry(struct plugin_state *state, bool_t all, const char *id, const char *related_id, bool_t (*fn)(const char *domain, const char *map, const char *key, unsigned int key_len, const char *value, unsigned int value_len, const char *id, void *cbdata), void *cbdata) { int i, j; struct domain *domain; struct map *map; struct map_entry *entry; for (i = 0; i < map_data.n_domains; i++) { domain = &map_data.domains[i]; for (j = 0; j < domain->n_maps; j++) { map = &domain->maps[j]; for (entry = map->entries; entry != NULL; entry = entry->next) { if (all || ((id != NULL) && (strcmp(id, entry->id) == 0)) || ((related_id != NULL) && (map_id_in_id_list(related_id, entry->related_ids)))) { if (!(*fn)(domain->name, map->name, entry->key, entry->key_len, entry->value, entry->value_len, entry->id, cbdata)) { return FALSE; } } } } } return TRUE; } /* Iterate over every entry which matches the corresponding ID. */ bool_t map_data_foreach_entry_id(struct plugin_state *state, const char *id, bool_t (*fn)(const char *domain, const char *map, const char *key, unsigned int key_len, const char *value, unsigned int value_len, const char *id, void *cbdata), void *cbdata) { return map_data_foreach_entry(state, FALSE, id, NULL, fn, cbdata); } /* Iterate over every entry which lists the ID as a related ID. */ bool_t map_data_foreach_entry_related_id(struct plugin_state *state, const char *related_id, bool_t (*fn)(const char *domain, const char *map, const char *key, unsigned int key_len, const char *value, unsigned int value_len, const char *id, void *cbdata), void *cbdata) { return map_data_foreach_entry(state, FALSE, NULL, related_id, fn, cbdata); } /* Iterate over all maps, calling the callback with information about the map * and whatever the caller originally told us to keep track of when the map was * first set up. */ bool_t map_data_foreach_map(struct plugin_state *state, const char *domain_name, bool_t (*fn)(const char *domain, const char *map, void *backend_data, void *cbdata), void *cbdata) { int i, j; struct domain *domain; struct map *map; struct map_entry *entry; for (i = 0; i < map_data.n_domains; i++) { domain = &map_data.domains[i]; if ((domain_name != NULL) && (strcmp(domain->name, domain_name) != 0)) { continue; } for (j = 0; j < domain->n_maps; j++) { map = &domain->maps[j]; if (!(*fn)(domain->name, map->name, map->backend_data, cbdata)) { return FALSE; } } } return TRUE; } /* Query function: check if we have a record for the domain. Return that * information in "supported", and return TRUE unless we ran into internal * errors. */ bool_t map_supports_domain(struct plugin_state *state, const char *domain_name, bool_t *supported) { *supported = (map_data_find_domain(state, domain_name) != NULL); return TRUE; } /* Query function: check if we have a record for the map in the domain. Return * that information in "supported", and return TRUE unless we ran into internal * errors. */ bool_t map_supports_map(struct plugin_state *state, const char *domain_name, const char *map_name, bool_t *supported) { *supported = (map_data_find_map(state, domain_name, map_name) != NULL); return TRUE; } /* Query function: return the name of this master. */ int map_master_name(struct plugin_state *state, const char **master) { char *tmp, hostname[HOST_NAME_MAX + 1]; if (backend_read_master_name(state, &tmp) == 0) { free(map_data.master); map_data.master = strdup(tmp); backend_free_master_name(state, tmp); } else { memset(hostname, '\0', sizeof(hostname)); if (gethostname(hostname, sizeof(hostname)) != 0) { snprintf(hostname, sizeof(hostname), "%s", "localhost"); } free(map_data.master); map_data.master = strdup(hostname); } *master = map_data.master; return 0; } /* Query function: return an indication of the map's age. Should never * decrease. */ bool_t map_order(struct plugin_state *state, const char *domain_name, const char *map_name, unsigned int *order) { struct map *map; map = map_data_find_map(state, domain_name, map_name); if (map != NULL) { *order = map->last_changed & 0xffffffff; return TRUE; } else { return FALSE; } } /* Query function: return the entry value which matches the key. Return TRUE * if we can find a value which corresponds to the key. */ bool_t map_match(struct plugin_state *state, const char *domain_name, const char *map_name, unsigned int key_len, char *key, unsigned int *value_len, char **value) { struct map_entry *entry; entry = map_data_find_entry(state, domain_name, map_name, key_len, key); if (entry == NULL) { return FALSE; } *value_len = entry->value_len; *value = entry->value; return TRUE; } /* Query function: return the first entry's key and value for a map. Return * FALSE if there's no domain or map. */ bool_t map_first(struct plugin_state *state, const char *domain_name, const char *map_name, unsigned int *first_key_len, char **first_key, unsigned int *first_value_len, char **first_value) { struct map *map; struct map_entry *entry; map = map_data_find_map(state, domain_name, map_name); if (map == NULL) { return FALSE; } entry = map->entries; if (entry == NULL) { return FALSE; } *first_key_len = entry->key_len; *first_key = entry->key; *first_value_len = entry->value_len; *first_value = entry->value; return TRUE; } /* Query function: return the successor entry's key and value for a map. * Return FALSE if there's no domain or map, or if the predecessor was the last * key in the map. */ bool_t map_next(struct plugin_state *state, const char *domain_name, const char *map_name, unsigned int prev_len, const char *prev, unsigned int *next_key_len, char **next_key, unsigned int *next_value_len, char **next_value) { struct map_entry *entry; entry = map_data_find_entry(state, domain_name, map_name, prev_len, prev); if ((entry == NULL) || (entry->next == NULL)) { return FALSE; } *next_key_len = entry->next->key_len; *next_key = entry->next->key; *next_value_len = entry->next->value_len; *next_value = entry->next->value; return TRUE; } /* Remove all of the entries in a map. */ static void map_data_clear_map_map(struct plugin_state *state, struct map *map) { struct map_entry *entry, *next; /* Clear the entries list. */ if (map != NULL) { for (entry = map->entries; entry != NULL; entry = next) { tdelete(entry, &map->id_tree, t_compare_entry_by_id); tdelete(entry, &map->key_tree, t_compare_entry_by_key); next = entry->next; free(entry->id); entry->key_len = 0; free(entry->key); entry->value_len = 0; free(entry->value); free(entry); } map->entries = NULL; map->key_tree = NULL; map->id_tree = NULL; } } void map_data_clear_map(struct plugin_state *state, const char *domain_name, const char *map_name) { struct map *map; map = map_data_find_map(state, domain_name, map_name); map_data_clear_map_map(state, map); } /* Remove a map from the configuration, removing its domain record if the map * was the only one that the domain contained. */ void map_data_unset_map(struct plugin_state *state, const char *domain_name, const char *map_name) { struct domain *domain; struct map *map; int i; /* Check that we have a domain record that matches. */ domain = map_data_find_domain(state, domain_name); if (domain == NULL) { return; } /* Locate the map, remove it from the array of maps. */ memset(&map, 0, sizeof(map)); for (i = 0; i < domain->n_maps; i++) { if (strcmp(domain->maps[i].name, map_name) == 0) { map = &domain->maps[i]; /* Free the contents. */ free(map->name); if (map->free_backend_data != NULL) { map->free_backend_data(map->backend_data); } map_data_clear_map_map(state, map); /* Close the hole in the array. */ domain->n_maps--; if (i != domain->n_maps) { memcpy(&domain->maps[i], &domain->maps[i + 1], sizeof(map) * (domain->n_maps - i)); } break; } } /* If the domain now contains no maps, remove it, too .*/ if (domain->n_maps == 0) { /* Locate the domain, remove it from the array of domains. */ for (i = 0; i < map_data.n_domains; i++) { if (strcmp(map_data.domains[i].name, domain_name) == 0) { domain = &map_data.domains[i]; /* Free the components. */ free(domain->name); free(domain->maps); /* Fill in the hole in the domains array. */ map_data.n_domains--; if (i != map_data.n_domains) { memcpy(&map_data.domains[i], &map_data.domains[i + 1], sizeof(*domain) * (map_data.n_domains - i)); } break; } } } } /* Add a map structure, adding a domain for it if necessary. */ void map_data_set_map(struct plugin_state *state, const char *domain_name, const char *map_name, void *backend_data, void (*free_backend_data)(void *backend_data)) { struct domain *domain, *domains; struct map *map, *maps; int i; /* Locate the domain for this map. */ domain = NULL; for (i = 0; i < map_data.n_domains; i++) { if (strcmp(map_data.domains[i].name, domain_name) == 0) { domain = &map_data.domains[i]; break; } } /* If we have to, then add to the domain array. */ if (domain == NULL) { /* Allocate space. */ domains = malloc(sizeof(*domain) * (map_data.n_domains + 1)); if (domains != NULL) { /* Populate the new domain. */ domain = &domains[map_data.n_domains]; memset(domain, 0, sizeof(*domain)); domain->name = strdup(domain_name); if (domain->name != NULL) { /* Copy in existing data. */ memcpy(domains, map_data.domains, sizeof(*domain) * map_data.n_domains); /* Switcheroo. */ free(map_data.domains); map_data.domains = domains; map_data.n_domains++; } else { free(domains); /* XXX */ return; } } } /* Check if the map's already been defined in the domain. */ map = NULL; for (i = 0; i < domain->n_maps; i++) { if (strcmp(domain->maps[i].name, map_name) == 0) { map = &domain->maps[i]; break; } } /* We need to either create a new map entry or mess with an old one. */ if (map == NULL) { /* Allocate space. */ maps = malloc(sizeof(*map) * (domain->n_maps + 1)); if (maps != NULL) { /* Populate the new map. */ map = &maps[domain->n_maps]; memset(map, 0, sizeof(*map)); map->name = strdup(map_name); map->backend_data = backend_data; map->free_backend_data = free_backend_data; if (map->name != NULL) { /* Copy in existing data. */ memcpy(maps, domain->maps, sizeof(*map) * domain->n_maps); /* Switcheroo. */ free(domain->maps); domain->maps = maps; domain->n_maps++; } else { free(maps); /* XXX */ return; } } } else { /* There's already a map there, we just need to update the * data we're keeping track of for the backend. */ if (map->free_backend_data != NULL) { map->free_backend_data(map->backend_data); } map->backend_data = backend_data; map->free_backend_data = free_backend_data; } } /* Remove an entry from a map. */ static void map_data_unset_map_entry(struct plugin_state *state, struct map *map, struct map_entry *entry) { struct map_entry *prev, *next; if ((map != NULL) && (entry != NULL)) { prev = entry->prev; next = entry->next; if (prev != NULL) { prev->next = next; } if (next != NULL) { next->prev = prev; } if (map->entries == entry) { map->entries = next; } tdelete(entry, &map->key_tree, t_compare_entry_by_key); tdelete(entry, &map->id_tree, t_compare_entry_by_id); map_id_free_id_list(entry->related_ids); free(entry->id); free(entry->key); free(entry->value); free(entry); } } /* Remove the entry from the map which matches the passed-in key. */ void map_data_unset_entry_key(struct plugin_state *state, const char *domain_name, const char *map_name, unsigned int key_len, const char *key) { struct map *map; struct map_entry *entry; map = map_data_find_map(state, domain_name, map_name); entry = map_data_find_map_entry(state, map, key_len, key); map_data_unset_map_entry(state, map, entry); map->last_changed = time(NULL); } /* Remove the entry from the map which matches the passed-in ID. */ void map_data_unset_entry_id(struct plugin_state *state, const char *domain_name, const char *map_name, const char *id) { struct map *map; struct map_entry *entry; map = map_data_find_map(state, domain_name, map_name); entry = map_data_find_map_entry_id(state, map, id); map_data_unset_map_entry(state, map, entry); map->last_changed = time(NULL); } /* Add an entry to a map. */ void map_data_set_entry(struct plugin_state *state, const char *domain_name, const char *map_name, const char *id, const char **related_ids, unsigned int key_len, char *key, unsigned int value_len, char *value) { struct map *map; struct map_entry *entry; map = map_data_find_map(state, domain_name, map_name); if (key_len == (unsigned int) -1) { key_len = strlen(key); } if (value_len == (unsigned int) -1) { value_len = strlen(value); } if (map != NULL) { entry = map_data_find_map_entry_id(state, map, id); if (entry != NULL) { /* There's already an entry with this ID, so let's * replace its key and value. */ tdelete(entry, &map->key_tree, t_compare_entry_by_key); tdelete(entry, &map->id_tree, t_compare_entry_by_id); free(entry->key); entry->key = xmemdup(key, key_len); entry->key_len = key_len; free(entry->value); entry->value = xmemdup(value, value_len); entry->value_len = value_len; free(entry->id); entry->id = strdup(id); map_id_free_id_list(entry->related_ids); entry->related_ids = map_id_dup_id_list(related_ids); tsearch(entry, &map->key_tree, t_compare_entry_by_key); tsearch(entry, &map->id_tree, t_compare_entry_by_id); } else { /* There's no entry with this ID, so create one. */ entry = malloc(sizeof(*entry)); if (entry != NULL) { memset(entry, 0, sizeof(*entry)); entry->key = xmemdup(key, key_len); entry->key_len = key_len; entry->value = xmemdup(value, value_len); entry->value_len = value_len; entry->id = strdup(id); entry->related_ids = map_id_dup_id_list(related_ids); entry->next = map->entries; if (map->entries != NULL) { map->entries->prev = entry; } map->entries = entry; tsearch(entry, &map->key_tree, t_compare_entry_by_key); tsearch(entry, &map->id_tree, t_compare_entry_by_id); } else { /* XXX */ } } map->last_changed = time(NULL); } } int map_startup(struct plugin_state *state) { backend_startup(state); return 0; } int map_init(struct slapi_pblock *pb, struct plugin_state *state) { memset(&map_data, 0, sizeof(map_data)); map_data.lock = wrap_new_rwlock(); if (map_data.lock == NULL) { return -1; } backend_init(pb, state); return 0; } void map_rdlock(void) { wrap_rwlock_rdlock(map_data.lock); } void map_wrlock(void) { wrap_rwlock_wrlock(map_data.lock); } void map_unlock(void) { wrap_rwlock_unlock(map_data.lock); }