blob: 3c69dbf45baafd5602dc0eafab226dd333e232cb [file] [log] [blame]
/*
*------------------------------------------------------------------
* 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/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... */
if (CLIB_DEBUG > 2)
{
/* Add a bogus client (pid=0) so the svm won't be deallocated */
clib_warning
("[%d] adding fake client (pid=0) so '%s' won't be unlinked",
getpid (), db_rp->region_name);
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 *placeholder_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,
placeholder_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_set_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_set_len (oldvalue->value, vec_len (val));
notify_value (oldvalue, SVMDB_ACTION_SET);
}
else
{
svmdb_value_t *newvalue;
pool_get (shm->values, newvalue);
clib_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_set_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];
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)");
}));
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 = { 0 }, *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_clib_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));
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);
}
}));
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 = { 0 }, *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_clib_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_set_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];
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]);
}));
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);
clib_memset (newvalue, 0, sizeof (*newvalue));
newvalue->elsize = 1;
vec_alloc (newvalue->value, nbytes);
vec_set_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:
*/