/* -*- Mode: C ; c-basic-offset: 2 -*- */ /* * LADI Session Handler (ladish) * * Copyright (C) 2010,2011,2012 Nedko Arnaudov * ************************************************************************** * This file contains implementation of code that interfaces ladiconfd through D-Bus ************************************************************************** * * LADI Session Handler 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; either version 2 of the License, or * (at your option) any later version. * * LADI Session Handler 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 LADI Session Handler. If not, see * or write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ /* This implementation subscribes for changes of all values. * It is suboptimal in general because of the increased overhead. * A smarter implementation will subscribe using filter based on arg0 (key). * Such smarter implementation seems to not be worth becase of * the limited key set that ladish uses. */ #include "conf_proxy.h" struct pair { struct list_head siblings; uint64_t version; char * key; char * value; size_t value_buffer_size; void (* callback)(void * context, const char * key, const char * value); void * callback_context; }; static struct list_head g_pairs; static struct pair * find_pair(const char * key) { struct list_head * node_ptr; struct pair * pair_ptr; list_for_each(node_ptr, &g_pairs) { pair_ptr = list_entry(node_ptr, struct pair, siblings); if (strcmp(pair_ptr->key, key) == 0) { return pair_ptr; } } return NULL; } static void on_value_changed(struct pair * pair_ptr, const char * value, uint64_t version, bool announce) { size_t len; log_info("'%s' <- '%s' (%"PRIu64")", pair_ptr->key, value, version); pair_ptr->version = version; len = strlen(value); if (len < pair_ptr->value_buffer_size) { ASSERT(pair_ptr->value != NULL); memcpy(pair_ptr->value, value, len + 1); } else { free(pair_ptr->value); pair_ptr->value = strdup(value); if (pair_ptr->value == NULL) { log_error("strdup(\"%s\") failed for key \"%s\" value", value, pair_ptr->key); } } if (announce && pair_ptr->callback != NULL) { pair_ptr->callback(pair_ptr->callback_context, pair_ptr->key, value); } } static void on_life_status_changed(bool appeared) { struct list_head * node_ptr; struct pair * pair_ptr; if (appeared) { log_info("confd activatation detected."); } else { log_info("confd deactivatation detected."); list_for_each(node_ptr, &g_pairs) { pair_ptr = list_entry(node_ptr, struct pair, siblings); pair_ptr->version = 0; } } } static void on_conf_changed(void * UNUSED(context), DBusMessage * message_ptr) { const char * key; const char * value; dbus_uint64_t version; struct pair * pair_ptr; if (!dbus_message_get_args( message_ptr, &cdbus_g_dbus_error, DBUS_TYPE_STRING, &key, DBUS_TYPE_STRING, &value, DBUS_TYPE_UINT64, &version, DBUS_TYPE_INVALID)) { log_error("Invalid parameters of \"changed\" signal: %s", cdbus_g_dbus_error.message); dbus_error_free(&cdbus_g_dbus_error); return; } pair_ptr = find_pair(key); if (pair_ptr == NULL) { /* we dont care about this key */ return; } if (pair_ptr->version >= version) { /* signal for either already known version of the key or a older one */ return; } if (pair_ptr->value != NULL && strcmp(value, pair_ptr->value) == 0) { /* the conf service should not send the signal when value is not changed, but in case that it does, ignore it. This can happen when confd is restarted */ return; } on_value_changed(pair_ptr, value, version, true); } /* this must be static because it is referenced by the * dbus helper layer when hooks are active */ static struct cdbus_signal_hook g_signal_hooks[] = { {"changed", on_conf_changed}, {NULL, NULL} }; bool conf_proxy_init(void) { INIT_LIST_HEAD(&g_pairs); if (!cdbus_register_service_lifetime_hook(cdbus_g_dbus_connection, CONF_SERVICE_NAME, on_life_status_changed)) { log_error("dbus_register_service_lifetime_hook() failed for confd service"); return false; } if (!cdbus_register_object_signal_hooks( cdbus_g_dbus_connection, CONF_SERVICE_NAME, CONF_OBJECT_PATH, CONF_IFACE, NULL, g_signal_hooks)) { cdbus_unregister_service_lifetime_hook(cdbus_g_dbus_connection, CONF_SERVICE_NAME); log_error("dbus_register_object_signal_hooks() failed for conf interface"); return false; } return true; } void conf_proxy_uninit(void) { cdbus_unregister_object_signal_hooks(cdbus_g_dbus_connection, CONF_SERVICE_NAME, CONF_OBJECT_PATH, CONF_IFACE); cdbus_unregister_service_lifetime_hook(cdbus_g_dbus_connection, CONF_SERVICE_NAME); } bool conf_register( const char * key, void (* callback)(void * context, const char * key, const char * value), void * callback_context) { const char * value; uint64_t version; struct pair * pair_ptr; pair_ptr = find_pair(key); if (pair_ptr != NULL) { log_error("key '%s' already registered", key); ASSERT_NO_PASS; return false; } if (!cdbus_call(0, CONF_SERVICE_NAME, CONF_OBJECT_PATH, CONF_IFACE, "get", "s", &key, "st", &value, &version)) { //log_error("conf::get() failed."); version = 0; value = NULL; } pair_ptr = malloc(sizeof(struct pair)); if (pair_ptr == NULL) { log_error("malloc() failed to allocate memory for pair struct"); return false; } pair_ptr->key = strdup(key); if (pair_ptr->key == NULL) { log_error("strdup(\"%s\") failed for key", key); free(pair_ptr); return false; } if (value != NULL) { pair_ptr->value_buffer_size = strlen(value) + 1; pair_ptr->value = malloc(pair_ptr->value_buffer_size); if (pair_ptr->value == NULL) { log_error("malloc(%zu) failed for value \"%s\"", pair_ptr->value_buffer_size, value); free(pair_ptr->key); free(pair_ptr); return false; } memcpy(pair_ptr->value, value, pair_ptr->value_buffer_size); } else { pair_ptr->value = NULL; pair_ptr->value_buffer_size = 0; } pair_ptr->version = version; pair_ptr->callback = callback; pair_ptr->callback_context = callback_context; list_add_tail(&pair_ptr->siblings, &g_pairs); if (callback != NULL) { callback(callback_context, key, value); } return true; } bool conf_set(const char * key, const char * value) { uint64_t version; struct pair * pair_ptr; pair_ptr = find_pair(key); if (pair_ptr != NULL && pair_ptr->value != NULL && strcmp(value, pair_ptr->value) == 0) { return true; /* not changed */ } if (!cdbus_call(0, CONF_SERVICE_NAME, CONF_OBJECT_PATH, CONF_IFACE, "set", "ss", &key, &value, "t", &version)) { log_error("conf::set() failed."); return false; } if (pair_ptr != NULL && pair_ptr->value != NULL && strcmp(value, pair_ptr->value) != 0) { /* record the new version and dont call the callback */ on_value_changed(pair_ptr, value, version, false); } return true; } bool conf_get(const char * key, const char ** value_ptr) { struct pair * pair_ptr; pair_ptr = find_pair(key); if (pair_ptr == NULL) { ASSERT_NO_PASS; /* call conf_register() first */ return false; } if (pair_ptr->value == NULL) { return false; /* malloc() failed */ } *value_ptr = pair_ptr->value; return true; } bool conf_string2bool(const char * value) { if (value[0] == 0 || strcasecmp(value, "false") == 0 || strcmp(value, "0") == 0) { return false; } return true; } const char * conf_bool2string(bool value) { return value ? "true" : "false"; } bool conf_set_bool(const char * key, bool value) { return conf_set(key, conf_bool2string(value)); } bool conf_get_bool(const char * key, bool * value_ptr) { const char * str; if (!conf_get(key, &str)) { return false; } *value_ptr = conf_string2bool(str); return true; } /* UINT_MAX is 10 chars + terminating nul */ #define UINT_STRING_MAX_SIZE 11 static const char * conf_uint2string(unsigned int value, char * string) { char * ptr; ptr = string + (UINT_STRING_MAX_SIZE - 1); *ptr = 0; do { ASSERT(ptr > string); /* UINT_STRING_MAX_SIZE is too small? */ ptr--; *ptr = '0' + value % 10; value /= 10; } while (value > 0); return ptr; } static bool conf_string2uint(const char * string, unsigned int * value_ptr) { unsigned int value; value = 0; do { if (*string < '0' || *string > '9') { return false; } value *= 10; value += *string - '0'; string++; } while (*string != 0); *value_ptr = value; return true; } bool conf_set_uint(const char * key, unsigned int value) { char buffer[UINT_STRING_MAX_SIZE]; return conf_set(key, conf_uint2string(value, buffer)); } bool conf_get_uint(const char * key, unsigned int * value_ptr) { const char * str; if (!conf_get(key, &str)) { return false; } return conf_string2uint(str, value_ptr); }