/* *------------------------------------------------------------------ * svmdb.c -- simple shared memory database * * Copyright (c) 2009 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *------------------------------------------------------------------ */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h> #include <netinet/in.h> #include <signal.h> #include <pthread.h> #include <unistd.h> #include <time.h> #include <fcntl.h> #include <string.h> #include <vppinfra/clib.h> #include <vppinfra/vec.h> #include <vppinfra/hash.h> #include <vppinfra/bitmap.h> #include <vppinfra/fifo.h> #include <vppinfra/time.h> #include <vppinfra/mheap.h> #include <vppinfra/heap.h> #include <vppinfra/pool.h> #include <vppinfra/format.h> #include <vppinfra/serialize.h> #include "svmdb.h" static void local_set_variable_nolock (svmdb_client_t * client, svmdb_namespace_t namespace, u8 * var, u8 * val, u32 elsize); always_inline void region_lock (svm_region_t * rp, int tag) { pthread_mutex_lock (&rp->mutex); #ifdef MUTEX_DEBUG rp->mutex_owner_pid = getpid (); rp->mutex_owner_tag = tag; #endif } always_inline void region_unlock (svm_region_t * rp) { #ifdef MUTEX_DEBUG rp->mutex_owner_pid = 0; rp->mutex_owner_tag = 0; #endif pthread_mutex_unlock (&rp->mutex); } svmdb_client_t * svmdb_map (svmdb_map_args_t * dba) { svmdb_client_t *client = 0; svm_map_region_args_t *a = 0; svm_region_t *db_rp; void *oldheap; svmdb_shm_hdr_t *hp = 0; vec_validate (client, 0); vec_validate (a, 0); svm_region_init_chroot_uid_gid (dba->root_path, dba->uid, dba->gid); a->root_path = dba->root_path; a->name = "/db"; a->size = dba->size ? dba->size : SVMDB_DEFAULT_SIZE; a->flags = SVM_FLAGS_MHEAP; a->uid = dba->uid; a->gid = dba->gid; db_rp = client->db_rp = svm_region_find_or_create (a); ASSERT (db_rp); vec_free (a); region_lock (client->db_rp, 10); /* Has someone else set up the shared-memory variable table? */ if (db_rp->user_ctx) { client->shm = (void *) db_rp->user_ctx; client->pid = getpid (); region_unlock (client->db_rp); ASSERT (client->shm->version == SVMDB_SHM_VERSION); return (client); } /* Nope, it's our problem... */ /* Add a bogus client (pid=0) so the svm won't be deallocated */ oldheap = svm_push_pvt_heap (db_rp); vec_add1 (client->db_rp->client_pids, 0); svm_pop_heap (oldheap); oldheap = svm_push_data_heap (db_rp); vec_validate (hp, 0); hp->version = SVMDB_SHM_VERSION; hp->namespaces[SVMDB_NAMESPACE_STRING] = hash_create_string (0, sizeof (uword)); hp->namespaces[SVMDB_NAMESPACE_VEC] = hash_create_string (0, sizeof (uword)); db_rp->user_ctx = hp; client->shm = hp; svm_pop_heap (oldheap); region_unlock (client->db_rp); client->pid = getpid (); return (client); } void svmdb_unmap (svmdb_client_t * client) { ASSERT (client); if (!svm_get_root_rp ()) return; svm_region_unmap ((void *) client->db_rp); svm_region_exit (); vec_free (client); } static void notify_value (svmdb_value_t * v, svmdb_action_t a) { int i; int rv; union sigval sv; u32 value; u32 *dead_registrations = 0; svmdb_notify_t *np; for (i = 0; i < vec_len (v->notifications); i++) { np = vec_elt_at_index (v->notifications, i); if (np->action == a) { value = (np->action << 28) | (np->opaque); sv.sival_ptr = (void *) (uword) value; do { rv = 0; if (sigqueue (np->pid, np->signum, sv) == 0) break; rv = errno; } while (rv == EAGAIN); if (rv == 0) continue; vec_add1 (dead_registrations, i); } } for (i = 0; i < vec_len (dead_registrations); i++) { np = vec_elt_at_index (v->notifications, dead_registrations[i]); clib_warning ("dead reg pid %d sig %d action %d opaque %x", np->pid, np->signum, np->action, np->opaque); vec_delete (v->notifications, 1, dead_registrations[i]); } vec_free (dead_registrations); } int svmdb_local_add_del_notification (svmdb_client_t * client, svmdb_notification_args_t * a) { uword *h; void *oldheap; hash_pair_t *hp; svmdb_shm_hdr_t *shm; u8 *dummy_value = 0; svmdb_value_t *value; svmdb_notify_t *np; int i; int rv = 0; ASSERT (a->elsize); region_lock (client->db_rp, 18); shm = client->shm; oldheap = svm_push_data_heap (client->db_rp); h = shm->namespaces[a->nspace]; hp = hash_get_pair_mem (h, a->var); if (hp == 0) { local_set_variable_nolock (client, a->nspace, (u8 *) a->var, dummy_value, a->elsize); /* might have moved */ h = shm->namespaces[a->nspace]; hp = hash_get_pair_mem (h, a->var); ASSERT (hp); } value = pool_elt_at_index (shm->values, hp->value[0]); for (i = 0; i < vec_len (value->notifications); i++) { np = vec_elt_at_index (value->notifications, i); if ((np->pid == client->pid) && (np->signum == a->signum) && (np->action == a->action) && (np->opaque == a->opaque)) { if (a->add_del == 0 /* delete */ ) { vec_delete (value->notifications, 1, i); goto out; } else { /* add */ clib_warning ("%s: ignore dup reg pid %d signum %d action %d opaque %x", a->var, client->pid, a->signum, a->action, a->opaque); rv = -2; goto out; } } } if (a->add_del == 0) { rv = -3; goto out; } vec_add2 (value->notifications, np, 1); np->pid = client->pid; np->signum = a->signum; np->action = a->action; np->opaque = a->opaque; out: svm_pop_heap (oldheap); region_unlock (client->db_rp); return rv; } static void local_unset_variable_nolock (svmdb_client_t * client, svmdb_namespace_t namespace, char *var) { uword *h; svmdb_value_t *oldvalue; hash_pair_t *hp; h = client->shm->namespaces[namespace]; hp = hash_get_pair_mem (h, var); if (hp) { oldvalue = pool_elt_at_index (client->shm->values, hp->value[0]); if (vec_len (oldvalue->notifications)) notify_value (oldvalue, SVMDB_ACTION_UNSET); /* zero length value means unset */ _vec_len (oldvalue->value) = 0; } client->shm->namespaces[namespace] = h; } void svmdb_local_unset_string_variable (svmdb_client_t * client, char *var) { void *oldheap; region_lock (client->db_rp, 11); oldheap = svm_push_data_heap (client->db_rp); local_unset_variable_nolock (client, SVMDB_NAMESPACE_STRING, var); svm_pop_heap (oldheap); region_unlock (client->db_rp); } static void local_set_variable_nolock (svmdb_client_t * client, svmdb_namespace_t namespace, u8 * var, u8 * val, u32 elsize) { uword *h; hash_pair_t *hp; u8 *name; svmdb_shm_hdr_t *shm; shm = client->shm; h = shm->namespaces[namespace]; hp = hash_get_pair_mem (h, var); if (hp) { svmdb_value_t *oldvalue; oldvalue = pool_elt_at_index (client->shm->values, hp->value[0]); vec_alloc (oldvalue->value, vec_len (val) * elsize); clib_memcpy (oldvalue->value, val, vec_len (val) * elsize); _vec_len (oldvalue->value) = vec_len (val); notify_value (oldvalue, SVMDB_ACTION_SET); } else { svmdb_value_t *newvalue; pool_get (shm->values, newvalue); memset (newvalue, 0, sizeof (*newvalue)); newvalue->elsize = elsize; vec_alloc (newvalue->value, vec_len (val) * elsize); clib_memcpy (newvalue->value, val, vec_len (val) * elsize); _vec_len (newvalue->value) = vec_len (val); name = format (0, "%s%c", var, 0); hash_set_mem (h, name, newvalue - shm->values); } shm->namespaces[namespace] = h; } void svmdb_local_set_string_variable (svmdb_client_t * client, char *var, char *val) { void *oldheap; region_lock (client->db_rp, 12); oldheap = svm_push_data_heap (client->db_rp); local_unset_variable_nolock (client, SVMDB_NAMESPACE_STRING, var); local_set_variable_nolock (client, SVMDB_NAMESPACE_STRING, (u8 *) var, (u8 *) val, 1 /* elsize */ ); svm_pop_heap (oldheap); region_unlock (client->db_rp); } static u8 * local_get_variable_nolock (svmdb_client_t * client, svmdb_namespace_t namespace, u8 * var) { uword *h; uword *p; svmdb_shm_hdr_t *shm; svmdb_value_t *oldvalue; shm = client->shm; h = shm->namespaces[namespace]; p = hash_get_mem (h, var); if (p) { oldvalue = pool_elt_at_index (shm->values, p[0]); notify_value (oldvalue, SVMDB_ACTION_GET); return (oldvalue->value); } return 0; } void * svmdb_local_get_variable_reference (svmdb_client_t * client, svmdb_namespace_t namespace, char *var) { u8 *rv; region_lock (client->db_rp, 19); rv = local_get_variable_nolock (client, namespace, (u8 *) var); region_unlock (client->db_rp); return (void *) rv; } char * svmdb_local_get_string_variable (svmdb_client_t * client, char *var) { u8 *rv = 0; region_lock (client->db_rp, 13); rv = local_get_variable_nolock (client, SVMDB_NAMESPACE_STRING, (u8 *) var); if (rv && vec_len (rv)) { rv = format (0, "%s", rv); vec_add1 (rv, 0); } region_unlock (client->db_rp); return ((char *) rv); } void svmdb_local_dump_strings (svmdb_client_t * client) { uword *h; u8 *key; u32 value; svmdb_shm_hdr_t *shm = client->shm; region_lock (client->db_rp, 14); h = client->shm->namespaces[SVMDB_NAMESPACE_STRING]; /* *INDENT-OFF* */ hash_foreach_mem(key, value, h, ({ svmdb_value_t *v = pool_elt_at_index (shm->values, value); fformat(stdout, "%s: %s\n", key, vec_len(v->value) ? v->value : (u8 *)"(nil)"); })); /* *INDENT-ON* */ region_unlock (client->db_rp); } int svmdb_local_serialize_strings (svmdb_client_t * client, char *filename) { uword *h; u8 *key; u32 value; svmdb_shm_hdr_t *shm = client->shm; serialize_main_t _sm, *sm = &_sm; clib_error_t *error = 0; u8 *sanitized_name = 0; int fd = 0; if (strstr (filename, "..") || index (filename, '/')) { error = clib_error_return (0, "Illegal characters in filename '%s'", filename); goto out; } sanitized_name = format (0, "/tmp/%s%c", filename, 0); fd = creat ((char *) sanitized_name, 0644); if (fd < 0) { error = clib_error_return_unix (0, "Create '%s'", sanitized_name); goto out; } serialize_open_unix_file_descriptor (sm, fd); region_lock (client->db_rp, 20); h = client->shm->namespaces[SVMDB_NAMESPACE_STRING]; serialize_likely_small_unsigned_integer (sm, hash_elts (h)); /* *INDENT-OFF* */ hash_foreach_mem(key, value, h, ({ svmdb_value_t *v = pool_elt_at_index (shm->values, value); /* Omit names with nil values */ if (vec_len(v->value)) { serialize_cstring (sm, (char *)key); serialize_cstring (sm, (char *)v->value); } })); /* *INDENT-ON* */ region_unlock (client->db_rp); serialize_close (sm); out: if (fd > 0 && close (fd) < 0) error = clib_error_return_unix (0, "close fd %d", fd); if (error) { clib_error_report (error); return -1; } return 0; } int svmdb_local_unserialize_strings (svmdb_client_t * client, char *filename) { serialize_main_t _sm, *sm = &_sm; void *oldheap; clib_error_t *error = 0; u8 *key, *value; int fd = 0; u32 nelts; int i; fd = open (filename, O_RDONLY); if (fd < 0) { error = clib_error_return_unix (0, "Failed to open '%s'", filename); goto out; } unserialize_open_unix_file_descriptor (sm, fd); region_lock (client->db_rp, 21); oldheap = svm_push_data_heap (client->db_rp); nelts = unserialize_likely_small_unsigned_integer (sm); for (i = 0; i < nelts; i++) { unserialize_cstring (sm, (char **) &key); unserialize_cstring (sm, (char **) &value); local_set_variable_nolock (client, SVMDB_NAMESPACE_STRING, key, value, 1 /* elsize */ ); vec_free (key); vec_free (value); } svm_pop_heap (oldheap); region_unlock (client->db_rp); serialize_close (sm); out: if (fd > 0 && close (fd) < 0) error = clib_error_return_unix (0, "close fd %d", fd); if (error) { clib_error_report (error); return -1; } return 0; } void svmdb_local_unset_vec_variable (svmdb_client_t * client, char *var) { void *oldheap; region_lock (client->db_rp, 15); oldheap = svm_push_data_heap (client->db_rp); local_unset_variable_nolock (client, SVMDB_NAMESPACE_VEC, var); svm_pop_heap (oldheap); region_unlock (client->db_rp); } void svmdb_local_set_vec_variable (svmdb_client_t * client, char *var, void *val_arg, u32 elsize) { u8 *val = (u8 *) val_arg; void *oldheap; region_lock (client->db_rp, 16); oldheap = svm_push_data_heap (client->db_rp); local_unset_variable_nolock (client, SVMDB_NAMESPACE_VEC, var); local_set_variable_nolock (client, SVMDB_NAMESPACE_VEC, (u8 *) var, val, elsize); svm_pop_heap (oldheap); region_unlock (client->db_rp); } void * svmdb_local_get_vec_variable (svmdb_client_t * client, char *var, u32 elsize) { u8 *rv = 0; u8 *copy = 0; region_lock (client->db_rp, 17); rv = local_get_variable_nolock (client, SVMDB_NAMESPACE_VEC, (u8 *) var); if (rv && vec_len (rv)) { /* Make a copy in process-local memory */ vec_alloc (copy, vec_len (rv) * elsize); clib_memcpy (copy, rv, vec_len (rv) * elsize); _vec_len (copy) = vec_len (rv); region_unlock (client->db_rp); return (copy); } region_unlock (client->db_rp); return (0); } void svmdb_local_dump_vecs (svmdb_client_t * client) { uword *h; u8 *key; u32 value; svmdb_shm_hdr_t *shm; region_lock (client->db_rp, 17); shm = client->shm; h = client->shm->namespaces[SVMDB_NAMESPACE_VEC]; /* *INDENT-OFF* */ hash_foreach_mem(key, value, h, ({ svmdb_value_t *v = pool_elt_at_index (shm->values, value); (void) fformat(stdout, "%s:\n %U (%.2f)\n", key, format_hex_bytes, v->value, vec_len(v->value)*v->elsize, ((f64 *)(v->value))[0]); })); /* *INDENT-ON* */ region_unlock (client->db_rp); } void * svmdb_local_find_or_add_vec_variable (svmdb_client_t * client, char *var, u32 nbytes) { void *oldheap; u8 *rv = 0; region_lock (client->db_rp, 18); oldheap = svm_push_data_heap (client->db_rp); rv = local_get_variable_nolock (client, SVMDB_NAMESPACE_VEC, (u8 *) var); if (rv) { goto out; } else { uword *h; u8 *name; svmdb_shm_hdr_t *shm; svmdb_value_t *newvalue; shm = client->shm; h = shm->namespaces[SVMDB_NAMESPACE_VEC]; pool_get (shm->values, newvalue); memset (newvalue, 0, sizeof (*newvalue)); newvalue->elsize = 1; vec_alloc (newvalue->value, nbytes); _vec_len (newvalue->value) = nbytes; name = format (0, "%s%c", var, 0); hash_set_mem (h, name, newvalue - shm->values); shm->namespaces[SVMDB_NAMESPACE_VEC] = h; rv = newvalue->value; } out: svm_pop_heap (oldheap); region_unlock (client->db_rp); return (rv); } /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */