diff --git a/.travis.yml b/.travis.yml index 900fff6344..24ce367024 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,6 +58,7 @@ addons: - libxpm-dev - libxml2-utils - libmodbus-dev + - libdbus-1-dev - libnss3-dev - libssl-dev # NOTE: Keep the list above in sync with replicas like deps_driverlibs_cross_i386 below @@ -397,6 +398,7 @@ _matrix_linux_gnustd_nowarn_x86_32bit: - libxpm-dev:i386 - libxml2-utils:i386 - libmodbus-dev:i386 + - libdbus-1-dev:i386 - libnss3-dev:i386 - libssl-dev:i386 # See comments above about perl diff --git a/ci_build.sh b/ci_build.sh index d907273fe3..6f4667e8d5 100755 --- a/ci_build.sh +++ b/ci_build.sh @@ -315,6 +315,7 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-sp CONFIG_OPTS+=("--with-udev-dir=${BUILD_PREFIX}/etc/udev") CONFIG_OPTS+=("--with-devd-dir=${BUILD_PREFIX}/etc/devd") CONFIG_OPTS+=("--with-hotplug-dir=${BUILD_PREFIX}/etc/hotplug") + CONFIG_OPTS+=("--with-dbus-dir=${BUILD_PREFIX}/etc/dbus-1") # Some OSes have broken cppunit support, it crashes either build/link # or at run-time. While distros take time to figure out fixes, we can diff --git a/configure.ac b/configure.ac index 47c4271cf6..1d7b5faed5 100644 --- a/configure.ac +++ b/configure.ac @@ -109,6 +109,11 @@ if test ! -d "${udevdir}"; then fi fi +dbusdir="/etc/dbus-1" +if test ! -d "${dbusdir}"; then + dbusdir='' +fi + devddir='/usr/local/etc/devd' if test ! -d "${devddir}"; then devddir='/etc/devd' @@ -380,6 +385,8 @@ NUT_CHECK_LIBPOWERMAN NUT_ARG_WITH([modbus], [build and install modbus drivers], [auto]) NUT_CHECK_LIBMODBUS NUT_CHECK_LIBAVAHI +NUT_ARG_WITH([dbus], [build and install dbus support], [auto]) +NUT_CHECK_LIBDBUS dnl ---------------------------------------------------------------------- dnl additional USB-related checks @@ -696,6 +703,23 @@ NUT_REPORT_FEATURE( [Define to enable I2C support] ) +dnl ---------------------------------------------------------------------- +dnl checks related to --with-dbus + +dnl ${nut_with_dbus}: any value except "yes" or "no" is treated as "auto". +if test "${nut_with_dbus}" = "yes" -a "${nut_have_dbus}" != "yes"; then + AC_MSG_ERROR([dbus-1 libraries not found, required for dbus support]) +fi + +if test "${nut_with_dbus}" != "no"; then + nut_with_dbus="${nut_have_dbus}" +fi + +NUT_REPORT_FEATURE([build dbus support], [${nut_with_dbus}], [], + [WITH_DBUS], [Define to enable dbus support]) +AM_CONDITIONAL([HAVE_DBUS], [test "${nut_have_dbus}" = "yes"]) + + dnl ---------------------------------------------------------------------- dnl Check for with-ssl, and --with-nss or --with-openssl dnl Only one can be enabled at a time, with a preference for OpenSSL @@ -1796,6 +1820,34 @@ else fi AM_CONDITIONAL(WITH_UDEV, test -n "${udevdir}") +AC_MSG_CHECKING(whether to install dbus rules) +AC_ARG_WITH(dbus-dir, + AS_HELP_STRING([--with-dbus-dir=PATH], [where to install dbus rules (/etc/dbus-1)]), +[ + case "${withval}" in + yes) + if test -z "${dbusdir}"; then + AC_MSG_RESULT(no) + AC_MSG_ERROR([dbus directory requested but not found]) + fi + ;; + auto) + ;; + no) + dbusdir="" + ;; + *) + dbusdir="${withval}" + ;; + esac +], []) +if test -n "${dbusdir}"; then + AC_MSG_RESULT(using ${dbusdir}) +else + AC_MSG_RESULT(no) +fi +AM_CONDITIONAL(WITH_DBUS, test -n "${dbusdir}") + dnl FreeBSD devd support: AC_MSG_CHECKING(whether to install FreeBSD devd.conf file) @@ -2018,6 +2070,8 @@ AC_SUBST(LIBWRAP_CFLAGS) AC_SUBST(LIBWRAP_LIBS) AC_SUBST(LIBLTDL_CFLAGS) AC_SUBST(LIBLTDL_LIBS) +AC_SUBST(LIBDBUS_CFLAGS) +AC_SUBST(LIBDBUS_LIBS) AC_SUBST(DRIVER_BUILD_LIST) AC_SUBST(DRIVER_MAN_LIST) AC_SUBST(DRIVER_INSTALL_TARGET) @@ -2048,6 +2102,7 @@ AC_SUBST(systemdtmpfilesdir) AC_SUBST(auglensdir) AC_SUBST(hotplugdir) AC_SUBST(udevdir) +AC_SUBST(dbusdir) dnl Filter through known variants first, so automatic choices can be made. dnl Note that clang identifies as gcc-compatible so should be probed first. @@ -2212,6 +2267,8 @@ AC_CONFIG_FILES([ scripts/augeas/nuthostsconf.aug scripts/augeas/nutupssetconf.aug scripts/avahi/nut.service + scripts/dbus/Makefile + scripts/dbus/nut-dbus.conf scripts/devd/Makefile scripts/devd/nut-usb.conf scripts/hotplug/Makefile diff --git a/docs/configure.txt b/docs/configure.txt index 5b16676f50..85d592b3a0 100644 --- a/docs/configure.txt +++ b/docs/configure.txt @@ -162,6 +162,11 @@ Core and Client parts. Enable libltdl (Libtool dlopen abstraction) support. This is required to build nut-scanner. + --with-dbus (default: auto-detect) + +Enable dbus support. upsd will provide device information on the +local system bus. + Other configuration options --------------------------- diff --git a/docs/nut.dict b/docs/nut.dict index 2bef6c8584..fb1393448f 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1527,6 +1527,7 @@ datasheet datastale dayofweek dblatex +dbus dcd dcn ddl diff --git a/m4/nut_check_libdbus.m4 b/m4/nut_check_libdbus.m4 new file mode 100644 index 0000000000..9f997bf914 --- /dev/null +++ b/m4/nut_check_libdbus.m4 @@ -0,0 +1,67 @@ +dnl Check for LIBDBUS compiler flags. On success, set nut_have_dbus="yes" +dnl and set LIBDBUS_CFLAGS and LIBDBUS_LIBS. On failure, set +dnl nut_have_dbus="no". This macro can be run multiple times, but will +dnl do the checking only once. + +AC_DEFUN([NUT_CHECK_LIBDBUS], +[ +if test -z "${nut_have_dbus_seen}"; then + nut_have_dbus_seen=yes + + dnl save CFLAGS and LIBS + CFLAGS_ORIG="${CFLAGS}" + LIBS_ORIG="${LIBS}" + + dnl See which version of the dbus library (if any) is installed + AC_MSG_CHECKING(for libdbus version via pkg-config) + DBUS_VERSION="`pkg-config --silence-errors --modversion dbus-1 2>/dev/null`" + if test "$?" != "0" -o -z "${DBUS_VERSION}"; then + DBUS_VERSION="none" + fi + AC_MSG_RESULT(${DBUS_VERSION} found) + + AC_MSG_CHECKING(for libdbus cflags) + AC_ARG_WITH(dbus-includes, + AS_HELP_STRING([@<:@--with-dbus-includes=CFLAGS@:>@], [include flags for the dbus library]), + [ + case "${withval}" in + yes|no) + AC_MSG_ERROR(invalid option --with(out)-dbus-includes - see docs/configure.txt) + ;; + *) + CFLAGS="${withval}" + ;; + esac + ], [CFLAGS="`pkg-config --silence-errors --cflags dbus-1 2>/dev/null`"]) + AC_MSG_RESULT([${CFLAGS}]) + + AC_MSG_CHECKING(for libdbus ldflags) + AC_ARG_WITH(dbus-libs, + AS_HELP_STRING([@<:@--with-dbus-libs=LIBS@:>@], [linker flags for the dbus library]), + [ + case "${withval}" in + yes|no) + AC_MSG_ERROR(invalid option --with(out)-dbus-libs - see docs/configure.txt) + ;; + *) + LIBS="${withval}" + ;; + esac + ], [LIBS="`pkg-config --silence-errors --libs dbus-1 2>/dev/null`"]) + AC_MSG_RESULT([${LIBS}]) + + dnl check if dbus is usable + AC_CHECK_HEADERS(dbus/dbus.h, [nut_have_dbus=yes], [nut_have_dbus=no], [AC_INCLUDES_DEFAULT]) + AC_CHECK_FUNCS(dbus_bus_get, [], [nut_have_dbus=no]) + + if test "${nut_have_dbus}" = "yes"; then + AC_DEFINE(HAVE_DBUS, 1, [Define if you have Freedesktop libdbus installed]) + LIBDBUS_CFLAGS="${CFLAGS}" + LIBDBUS_LIBS="${LIBS}" + fi + + dnl restore original CFLAGS and LIBS + CFLAGS="${CFLAGS_ORIG}" + LIBS="${LIBS_ORIG}" +fi +]) diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 17b1dd28fb..2d45a8b2c6 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -25,4 +25,4 @@ EXTRA_DIST = README \ Windows/halt.c \ Windows/Makefile -SUBDIRS = augeas devd hotplug python systemd udev ufw Solaris upsdrvsvcctl +SUBDIRS = augeas dbus devd hotplug python systemd udev ufw Solaris upsdrvsvcctl diff --git a/scripts/dbus/.gitignore b/scripts/dbus/.gitignore new file mode 100644 index 0000000000..9336ad6541 --- /dev/null +++ b/scripts/dbus/.gitignore @@ -0,0 +1 @@ +/nut-dbus.conf diff --git a/scripts/dbus/Makefile.am b/scripts/dbus/Makefile.am new file mode 100644 index 0000000000..b365d0e4b8 --- /dev/null +++ b/scripts/dbus/Makefile.am @@ -0,0 +1,10 @@ + +if WITH_DBUS + dbusrulesdir = $(dbusdir)/system.d + dbusrules_DATA = nut-dbus.conf +endif + +EXTRA_DIST = README + +DISTCLEANFILES = nut-dbus.conf + diff --git a/scripts/dbus/README b/scripts/dbus/README new file mode 100644 index 0000000000..20c3fcf091 --- /dev/null +++ b/scripts/dbus/README @@ -0,0 +1,30 @@ +Desc: DBus access descriptor +File: scripts/dbus/README +Date: 8 March 2018 +Auth: Emilien Kia + +This document introduces the DBus right rules for NUT. +It describes how upsd cn register names and paths on system bus and +how client can send requests to methods of objects. + +Installation +------------ + +For most users, these files will be automatically installed in +/etc/dbus-1 upon "make install", if that directory exists and if +the feature (DBus) has been enabled at configure time. You can +specify an alternate directory with ./configure --with-dbus-dir=DIR. + +Manual installation +------------------- + +To install them manually, copy the rules file(s) to /etc/dbus-1/system.d +using the command(s): + +$ cp -f nut-dbus.conf /etc/dbus-1/system.d/nut-dbus.conf + +You will need to refresh the bus to avoid a reboot for these rules to be +active. You can do so using: + +$ systemctl reload dbus.service + diff --git a/scripts/dbus/nut-dbus.conf.in b/scripts/dbus/nut-dbus.conf.in new file mode 100644 index 0000000000..ad30799805 --- /dev/null +++ b/scripts/dbus/nut-dbus.conf.in @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/Makefile.am b/server/Makefile.am index 8146568a09..ed8d69dd6c 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -10,6 +10,9 @@ endif if WITH_SSL AM_CFLAGS += $(LIBSSL_CFLAGS) endif +if WITH_DBUS + AM_CFLAGS += $(LIBDBUS_CFLAGS) +endif LDADD = ../common/libcommon.la ../common/libparseconf.la $(NETLIBS) if WITH_WRAP LDADD += $(LIBWRAP_LIBS) @@ -17,14 +20,17 @@ endif if WITH_SSL LDADD += $(LIBSSL_LIBS) endif +if WITH_DBUS + LDADD += $(LIBDBUS_LIBS) +endif sbin_PROGRAMS = upsd EXTRA_PROGRAMS = sockdebug -upsd_SOURCES = upsd.c user.c conf.c netssl.c sstate.c desc.c \ - netget.c netmisc.c netlist.c netuser.c netset.c netinstcmd.c \ +upsd_SOURCES = upsd.c user.c conf.c netssl.c sstate.c desc.c \ + netget.c netmisc.c netlist.c netuser.c netset.c netinstcmd.c dbus.c \ conf.h nut_ctype.h desc.h netcmds.h neterr.h netget.h netinstcmd.h \ netlist.h netmisc.h netset.h netuser.h netssl.h sstate.h stype.h upsd.h \ - upstype.h user-data.h user.h + upstype.h user-data.h user.h dbus.h sockdebug_SOURCES = sockdebug.c diff --git a/server/dbus.c b/server/dbus.c new file mode 100644 index 0000000000..c081d71382 --- /dev/null +++ b/server/dbus.c @@ -0,0 +1,649 @@ +/* dbus.c - dbus communication functions + + Copyright (C) 2018 Emilien Kia + + 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; either version 2 of the License, or + (at your option) any later version. + + 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 +*/ + +#include "dbus.h" + +#ifdef WITH_DBUS + +#include "common.h" +#include "timehead.h" +#include "upstype.h" +#include "extstate.h" +#include "state.h" +#include "upsd.h" +#include "netset.h" + +#include +#include +#include + + +static const char* empty_string = ""; + +/* Optimized building of string by multiple appending. + */ +typedef struct { + char* str; /* Pointer to allocated buffer. */ + size_t size; /* Size of allocated buffer. */ + size_t len; /* Length of string. */ +} str_builder; + +#if 0 /* Unused */ +static void str_builder_init(str_builder* strbld, unsigned char size) +{ + strbld->str = (char*)xmalloc(size); + strbld->size = size; + strbld->len = 0; +} +#endif /* Unsed str_builder_init */ + +static void str_builder_init_str(str_builder* strbld, const char* str) +{ + strbld->len = strbld->size = strlen(str); + strbld->str = xstrdup(str); +} + +static void str_builder_free(str_builder* strbld) +{ + free(strbld->str); + strbld->str = NULL; + strbld->size = strbld->len = 0; +} + +static void str_builder_append(str_builder* strbld, const char* str) +{ + size_t len = strlen(str); + if (strbld->size < strbld->len+len+1) { + strbld->size = (strbld->len+len)*2 + 1; + strbld->str = xrealloc(strbld->str, strbld->size); + } + memcpy(strbld->str+strbld->len, str, len +1); + strbld->len += len; +} + +static void str_builder_append_args(str_builder* strbld, const char* fmt, ...) +{ + va_list ap; + char buffer[LARGEBUF]; + + va_start(ap, fmt); + vsnprintf(buffer, LARGEBUF-1, fmt, ap); + va_end(ap); + + str_builder_append(strbld, buffer); +} + +/** DBus error management.*/ +DBusError upsd_dbus_err; + +/** DBus connection. */ +DBusConnection* upsd_dbus_conn; + + + +static void dbus_send_error_reply(DBusConnection *connection, DBusMessage *request, const char* errname, const char* errdesc) { + DBusMessage *reply; + reply = dbus_message_new_error(request, errname, errdesc); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static void dbus_read_arg_basic (DBusMessageIter* iter, void* value) { + DBusMessageIter variant; + if (dbus_message_iter_get_arg_type(iter)==DBUS_TYPE_VARIANT) { + dbus_message_iter_recurse (iter, &variant); + dbus_message_iter_get_basic (&variant, value); + } else { + dbus_message_iter_get_basic (iter, value); + } +} + +static int dbus_add_variant_string(DBusMessageIter* iter, const char* value) { + DBusMessageIter variant; + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "s", &variant)) { + upsdebugx(1, "dbus_add_variant_string dbus_message_iter_open_container (variant(s))"); + return 0; + } + if (!dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &value)) { + upsdebugx(1, "dbus_add_variant_string dbus_message_iter_append_basic(string)"); + return 0; + } + dbus_message_iter_close_container(iter, &variant); + return 1; +} + +static int dbus_add_dict_entry_string_variant_string(DBusMessageIter* iter, const char* name, const char* value) { + DBusMessageIter entry, variant; + + /* Dict entry */ + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry)) { + upsdebugx(1, "dbus_add_dict_entry_string_variant_string dbus_message_iter_open_container entry"); + return 0; + } + + /* Entry name */ + if (!dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name)) { + upsdebugx(1, "dbus_add_dict_entry_string_variant_string dbus_message_iter_append_basic name"); + dbus_message_iter_abandon_container(iter, &entry); + return 0; + } + + /* Entry value variant. */ + if (!dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "s", &variant)) { + upsdebugx(1, "dbus_add_dict_entry_string_variant_string dbus_message_iter_open_container variant"); + dbus_message_iter_abandon_container(iter, &entry); + return 0; + } + + /* Entry value variant value. */ + if (!dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &value)) { + upsdebugx(1, "dbus_add_dict_entry_string_variant_string dbus_message_iter_append_basic value"); + dbus_message_iter_abandon_container(&entry, &variant); + dbus_message_iter_abandon_container(iter, &entry); + return 0; + } + + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(iter, &entry); + return 1; +} + +/* HERE */ + + +static const char* dbus_nut_device_interface_name = DBUS_INTERFACE_NUT_DEVICE; + +static const char *server_introspection_data_begin = DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + " \n" + " \n" + " \n" + " \n"; +static const char *server_introspection_node_data = " \n"; +static const char *server_introspection_data_end = " \n"; + + +static void dbus_respond_to_server_introspect(DBusConnection *connection, DBusMessage *request) { + DBusMessage *reply; + str_builder buffer; + upstype_t *ups; + + str_builder_init_str(&buffer, server_introspection_data_begin); + ups = firstups; + while (ups) { + str_builder_append_args(&buffer, server_introspection_node_data, ups->name); + ups = ups->next; + } + str_builder_append(&buffer, server_introspection_data_end); + + reply = dbus_message_new_method_return(request); + dbus_message_append_args(reply, DBUS_TYPE_STRING, &buffer.str, DBUS_TYPE_INVALID); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + str_builder_free(&buffer); +} + +static const char *device_introspection_data_begin = DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"; + +static const char *device_introspection_prop_read = " \n"; +static const char *device_introspection_prop_rw = " \n"; + +static const char *device_introspection_data_end = + " \n" + " \n"; + +static void dbus_respond_to_device_introspect_var(st_tree_t* node, str_builder* buffer) { + if (node!=NULL && buffer!=NULL) { + if (node->left!=NULL) { + dbus_respond_to_device_introspect_var(node->left, buffer); + } + if (node->flags & ST_FLAG_RW) { + str_builder_append_args(buffer, device_introspection_prop_rw, node->var); + } else { + str_builder_append_args(buffer, device_introspection_prop_read, node->var); + } + if (node->right!=NULL) { + dbus_respond_to_device_introspect_var(node->right, buffer); + } + } +} + +static void dbus_respond_to_device_introspect(DBusConnection *connection, DBusMessage *request, const char* device) { + DBusMessage *reply; + str_builder buffer; + upstype_t* ups = get_ups_ptr(device); + if (ups==NULL) { + dbus_send_error_reply(connection, request, DBUS_ERROR_UNKNOWN_OBJECT, "Device name is not known"); + return; + } + + str_builder_init_str(&buffer, device_introspection_data_begin); + dbus_respond_to_device_introspect_var(ups->inforoot, &buffer); + str_builder_append(&buffer, device_introspection_data_end); + + reply = dbus_message_new_method_return(request); + dbus_message_append_args(reply, DBUS_TYPE_STRING, &buffer.str, DBUS_TYPE_INVALID); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + str_builder_free(&buffer); +} + +static int dbus_respond_to_device_getall_var(st_tree_t* node, DBusMessageIter* iter) { + if (node!=NULL && iter!=NULL) { + if (node->left!=NULL) { + return dbus_respond_to_device_getall_var(node->left, iter); + } + if(!dbus_add_dict_entry_string_variant_string(iter, node->var, node->val)) { + return 0; + } + if (node->right!=NULL) { + return dbus_respond_to_device_getall_var(node->right, iter); + } + } + return 1; +} + +static void dbus_respond_to_device_getall(DBusConnection *connection, DBusMessage *request, const char* device) { + DBusMessage *reply; + DBusMessageIter args, array; + + char *interface; + + upstype_t* ups = get_ups_ptr(device); + if (ups==NULL) { + dbus_send_error_reply(connection, request, DBUS_ERROR_UNKNOWN_OBJECT, "Device name is not known"); + return; + } + + /* Read parameters. */ + dbus_message_get_args(request, &upsd_dbus_err, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID); + if (dbus_error_is_set(&upsd_dbus_err)) { + dbus_send_error_reply(connection, request, DBUS_ERROR_INVALID_ARGS, "Illegal arguments to " DBUS_INTERFACE_PROPERTIES "::GetAll(s)->a{sv}"); + return; + } + + /* Create reply message. */ + reply = dbus_message_new_method_return(request); + dbus_message_iter_init_append(reply, &args); + if (!dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &array)) { + upsdebugx(LOG_ERR, "dbus_respond_to_device_getall dbus_message_iter_open_container arr"); + dbus_message_unref(reply); + return; + } + + if(!dbus_respond_to_device_getall_var(ups->inforoot, &array)) { + upsdebugx(LOG_ERR, "dbus_respond_to_device_getall dbus_respond_to_device_getall_var"); + dbus_message_iter_abandon_container(&args, &array); + dbus_message_unref(reply); + return; + } + + dbus_message_iter_close_container(&args, &array); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static void dbus_respond_to_device_get(DBusConnection *connection, DBusMessage *request, const char* device) { + DBusMessage *reply; + DBusMessageIter value; + upstype_t* ups; + st_tree_t *var; + char *interface, *name; + + ups = get_ups_ptr(device); + if (ups==NULL) { + dbus_send_error_reply(connection, request, DBUS_ERROR_UNKNOWN_OBJECT, "Device name is not known"); + return; + } + + /* Read parameters. */ + dbus_message_get_args(request, &upsd_dbus_err, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); + if (dbus_error_is_set(&upsd_dbus_err)) { + dbus_send_error_reply(connection, request, DBUS_ERROR_INVALID_ARGS, "Illegal arguments to " DBUS_INTERFACE_PROPERTIES "::Get(s,s)->v"); + return; + } + + /* Create reply message. */ + reply = dbus_message_new_method_return(request); + + dbus_message_iter_init_append(reply, &value); + + if (0==strcmp(name, "name")) { + if(!dbus_add_variant_string(&value, ups->name)) { + dbus_message_unref(reply); + dbus_send_error_reply(connection, request, DBUS_ERROR_FAILED, "System error in invocation of " DBUS_INTERFACE_PROPERTIES "::Get(s,s)->v"); + return; + } + } else if (0==strcmp(name, "fn")) { + if(!dbus_add_variant_string(&value, ups->fn)) { + dbus_message_unref(reply); + dbus_send_error_reply(connection, request, DBUS_ERROR_FAILED, "System error in invocation of " DBUS_INTERFACE_PROPERTIES "::Get(s,s)->v"); + return; + } + } else if (0==strcmp(name, "desc")) { + if(!dbus_add_variant_string(&value, ups->desc)) { + dbus_message_unref(reply); + dbus_send_error_reply(connection, request, DBUS_ERROR_FAILED, "System error in invocation of " DBUS_INTERFACE_PROPERTIES "::Get(s,s)->v"); + return; + } + } else { + /* Verify var presence. */ + var = state_tree_find(ups->inforoot, name); + if (var==NULL) { + dbus_message_unref(reply); + dbus_send_error_reply(connection, request, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property name for " DBUS_INTERFACE_PROPERTIES "::Get(s,s)->v"); + return; + } + if(!dbus_add_variant_string(&value, var->val ? var->val : empty_string)) { + dbus_message_unref(reply); + dbus_send_error_reply(connection, request, DBUS_ERROR_FAILED, "System error in invocation of " DBUS_INTERFACE_PROPERTIES "::Get(s,s)->v"); + return; + } + } + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static void dbus_respond_to_device_set(DBusConnection *connection, DBusMessage *request, const char* device) { + DBusMessage *reply; + DBusMessageIter args; + + char *interface, *name, *val; + + upstype_t* ups; + + ups = get_ups_ptr(device); + if (ups==NULL) + { + dbus_send_error_reply(connection, request, DBUS_ERROR_UNKNOWN_OBJECT, "Device name is not known"); + return; + } + + /* Read parameters. */ + if (!dbus_message_iter_init (request, &args)) { + dbus_send_error_reply(connection, request, DBUS_ERROR_INVALID_ARGS, "Illegal arguments to " DBUS_INTERFACE_PROPERTIES "::Set(s,s,v)"); + return; + } + + dbus_message_iter_get_basic (&args, &interface); + + if (!dbus_message_iter_next (&args)) { + dbus_send_error_reply(connection, request, DBUS_ERROR_INVALID_ARGS, "Illegal arguments count to " DBUS_INTERFACE_PROPERTIES "::Set(s,s,v)"); + return; + } + + dbus_message_iter_get_basic (&args, &name); + + if (!dbus_message_iter_next (&args)) { + dbus_send_error_reply(connection, request, DBUS_ERROR_INVALID_ARGS, "Illegal arguments count to " DBUS_INTERFACE_PROPERTIES "::Set(s,s,v)"); + return; + } + + dbus_read_arg_basic (&args, &val); + + /* Check variable new value. */ + switch (set_var_check_val(ups, name, val)) + { + case SET_VAR_CHECK_VAL_VAR_NOT_SUPPORTED: + dbus_send_error_reply(connection, request, DBUS_ERROR_UNKNOWN_PROPERTY, "Property not supported for " DBUS_INTERFACE_PROPERTIES "::Set(s,s,v)"); + return; + case SET_VAR_CHECK_VAL_READONLY: + dbus_send_error_reply(connection, request, DBUS_ERROR_PROPERTY_READ_ONLY, "Property is read-only for " DBUS_INTERFACE_PROPERTIES "::Set(s,s,v)"); + return; + case SET_VAR_CHECK_VAL_SET_FAILED: + dbus_send_error_reply(connection, request, DBUS_ERROR_INVALID_ARGS, "Property value is malformed for " DBUS_INTERFACE_PROPERTIES "::Set(s,s,v)"); + return; + case SET_VAR_CHECK_VAL_TOO_LONG: + dbus_send_error_reply(connection, request, DBUS_ERROR_INVALID_ARGS, "Property value is too long for " DBUS_INTERFACE_PROPERTIES "::Set(s,s,v)"); + return; + case SET_VAR_CHECK_VAL_INVALID_VALUE: + dbus_send_error_reply(connection, request, DBUS_ERROR_INVALID_ARGS, "Property value is not valid enum value for " DBUS_INTERFACE_PROPERTIES "::Set(s,s,v)"); + return; + default: + /* Do nothing, continue. */ + break; + } + + /* Really do the set operation. */ + + if (!do_set_var(ups, name, val)) { + dbus_send_error_reply(connection, request, DBUS_ERROR_FAILED , "Failed to set new value for " DBUS_INTERFACE_PROPERTIES "::Set(s,s,v)"); + return; + } + + reply = dbus_message_new_method_return(request); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +void dbus_notify_property_change(upstype_t* ups, const char* name, const char* value) { + DBusMessage *signal; + DBusMessageIter args, array; + char buffer[SMALLBUF]; + snprintf(buffer, SMALLBUF-1, DBUS_NUT_UPSD_PATH"/%s", ups->name); + + if (ups!=NULL) { + signal = dbus_message_new_signal(buffer, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged"); + dbus_message_iter_init_append(signal, &args); + + /* Interface name */ + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &dbus_nut_device_interface_name)) { + upslogx(LOG_ERR, "Cannot construct DBus property change notification, out of memory."); + dbus_message_unref(signal); + return; + } + + /* Changed properties (a{sv}). */ + if (!dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &array)) { + upslogx(LOG_ERR, "Cannot construct DBus property change notification, out of memory."); + dbus_message_unref(signal); + return; + } + if(!dbus_add_dict_entry_string_variant_string(&array, name, value)) { + upslogx(LOG_ERR, "Cannot construct DBus property change notification, out of memory."); + dbus_message_iter_abandon_container(&args, &array); + dbus_message_unref(signal); + return; + } + dbus_message_iter_close_container(&args, &array); + + /* Invalidated properties (as). Just add the array without any content (for now). */ + if (!dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "s", &array)) { + upslogx(LOG_ERR, "Cannot construct DBus property change notification, out of memory."); + dbus_message_unref(signal); + return; + } + dbus_message_iter_close_container(&args, &array); + + /* send the message and flush the connection */ + if (!dbus_connection_send(upsd_dbus_conn, signal, NULL)) { + upslogx(LOG_ERR, "Cannot send DBus property change notification."); + } + dbus_connection_flush(upsd_dbus_conn); + dbus_message_unref(signal); + } +} + +#if 0 /* Unused debug code */ +static void dbus_dump_message(DBusMessage* msg) { + switch (dbus_message_get_type(msg)) + { + case DBUS_MESSAGE_TYPE_METHOD_CALL: + printf("method call : "); + break; + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + printf("method return : "); + break; + case DBUS_MESSAGE_TYPE_ERROR: + printf("error : "); + break; + case DBUS_MESSAGE_TYPE_SIGNAL: + printf("signal : "); + break; + default: + printf("other : "); + break; + } + char ** path; + if (dbus_message_get_path_decomposed(msg, &path)) { + printf(" path="); + for (char** p = path; *p != NULL; ++p) { + printf("%s/", *p); + } + dbus_free_string_array(path); + } + printf(" interface=%s", dbus_message_get_interface(msg)); + printf(" member=%s", dbus_message_get_member(msg)); + printf(" destination=%s", dbus_message_get_destination(msg)); + printf(" sender=%s", dbus_message_get_sender(msg)); + printf(" signature=%s", dbus_message_get_signature(msg)); + printf("\n"); +} +#endif /* 0 : Unused dbus_dump_message */ + +static DBusHandlerResult dbus_messages(DBusConnection *connection, DBusMessage *message, void *user_data) { + /* dbus_dump_message(message); */ + const char *path = dbus_message_get_path(message); + + if (0==strcmp(DBUS_NUT_UPSD_PATH, path)) { + /* Request on Upsd object itself. */ + if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { + dbus_respond_to_server_introspect(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } + } else { + /* Request on Upsd sub object (device). */ + char ** splitpath; + if (dbus_message_get_path_decomposed(message, &splitpath) && splitpath!=NULL && + splitpath[0]!=NULL && splitpath[1]!=NULL && splitpath[2]!=NULL && splitpath[3]!=NULL) { + if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { + dbus_respond_to_device_introspect(connection, message, splitpath[3]); + } else if (dbus_message_is_method_call(message, DBUS_INTERFACE_PROPERTIES, "Get")) { + dbus_respond_to_device_get(connection, message, splitpath[3]); + } else if (dbus_message_is_method_call(message, DBUS_INTERFACE_PROPERTIES, "GetAll")) { + dbus_respond_to_device_getall(connection, message, splitpath[3]); + } else if (dbus_message_is_method_call(message, DBUS_INTERFACE_PROPERTIES, "Set")) { + dbus_respond_to_device_set(connection, message, splitpath[3]); + } + dbus_free_string_array(splitpath); + return DBUS_HANDLER_RESULT_HANDLED; + } + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int dbus_init() +{ + int ret; + DBusObjectPathVTable vtable; + + /* initialise the errors */ + dbus_error_init(&upsd_dbus_err); + + /* connect to the bus */ + upsd_dbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &upsd_dbus_err); + if (dbus_error_is_set(&upsd_dbus_err)) { + upslogx(LOG_WARNING, "DBus connection error (%s)\n", upsd_dbus_err.message); + dbus_error_free(&upsd_dbus_err); + } + if (NULL == upsd_dbus_conn) { + return 0; + } + + ret = dbus_bus_request_name(upsd_dbus_conn, DBUS_NUT_UPSD_NAME, + DBUS_NAME_FLAG_REPLACE_EXISTING , &upsd_dbus_err); + if (dbus_error_is_set(&upsd_dbus_err)) { + upslogx(LOG_WARNING, "DBus name error (%s)\n", upsd_dbus_err.message); + dbus_error_free(&upsd_dbus_err); + } + if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) { + return 0; + } + + vtable.message_function = dbus_messages; + vtable.unregister_function = NULL; + dbus_connection_try_register_fallback(upsd_dbus_conn, + DBUS_NUT_UPSD_PATH, &vtable, NULL, &upsd_dbus_err); + if (dbus_error_is_set(&upsd_dbus_err)) { + upslogx(LOG_WARNING, "DBus object error (%s)\n", upsd_dbus_err.message); + dbus_error_free(&upsd_dbus_err); + return 0; + } + return 1; +} + +void dbus_cleanup() +{ + /*dbus_connection_close(upsd_dbus_conn);*/ +} + +void dbus_loop() +{ + dbus_connection_read_write_dispatch(upsd_dbus_conn, 100); +} + +#else /* WITH_DBUS */ + +int dbus_init() +{ + return 1; +} + +void dbus_cleanup() +{ +} + +void dbus_loop() +{ +} + +void dbus_notify_property_change(upstype_t* ups, const char* name, const char* value) +{ +} + +#endif /* WITH_DBUS */ diff --git a/server/dbus.h b/server/dbus.h new file mode 100644 index 0000000000..300616a7a8 --- /dev/null +++ b/server/dbus.h @@ -0,0 +1,49 @@ +/* dbus.h - dbus communication functions + + Copyright (C) 2018 Emilien Kia + + 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; either version 2 of the License, or + (at your option) any later version. + + 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 +*/ + +#ifndef DBUS_H_SEEN +#define DBUS_H_SEEN + +#include "common.h" + +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +#define DBUS_INTERFACE_NUT_DEVICE "org.networkupstools.Device" +#define DBUS_NUT_UPSD_PATH "/org/networkupstools/Upsd" +#define DBUS_NUT_UPSD_NAME "org.networkupstools.Upsd" + +typedef struct upstype_s upstype_t; + +int dbus_init(); +void dbus_cleanup(); +void dbus_loop(); + +void dbus_notify_property_change(upstype_t* ups, const char* name, const char* value); + +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif + +#endif /* UPSD_H_SEEN */ diff --git a/server/netset.c b/server/netset.c index 72cbc357dd..63c9b64199 100644 --- a/server/netset.c +++ b/server/netset.c @@ -30,40 +30,21 @@ static void set_var(nut_ctype_t *client, const char *upsname, const char *var, const char *newval, const char *tracking_id) { - upstype_t *ups; const char *val; const enum_t *etmp; const range_t *rtmp; char cmd[SMALLBUF], esc[SMALLBUF]; int have_tracking_id = 0; - ups = get_ups_ptr(upsname); - - if (!ups) { - send_err(client, NUT_ERR_UNKNOWN_UPS); - return; - } - - if (!ups_available(ups, client)) - return; - - /* make sure this user is allowed to do SET */ - if (!user_checkaction(client->username, client->password, "SET")) { - send_err(client, NUT_ERR_ACCESS_DENIED); - return; - } - val = sstate_getinfo(ups, var); if (!val) { - send_err(client, NUT_ERR_VAR_NOT_SUPPORTED); - return; + return SET_VAR_CHECK_VAL_VAR_NOT_SUPPORTED; } /* make sure this variable is writable (RW) */ if ((sstate_getflags(ups, var) & ST_FLAG_RW) == 0) { - send_err(client, NUT_ERR_READONLY); - return; + return SET_VAR_CHECK_VAL_READONLY; } /* see if the new value is allowed for this variable */ @@ -75,16 +56,11 @@ static void set_var(nut_ctype_t *client, const char *upsname, const char *var, /* check for insanity from the driver */ if (aux < 1) { - upslogx(LOG_WARNING, "UPS [%s]: auxdata for %s is invalid", - ups->name, var); - - send_err(client, NUT_ERR_SET_FAILED); - return; + return SET_VAR_CHECK_VAL_SET_FAILED; } if (aux < (int) strlen(newval)) { - send_err(client, NUT_ERR_TOO_LONG); - return; + return SET_VAR_CHECK_VAL_TOO_LONG; } } @@ -105,8 +81,7 @@ static void set_var(nut_ctype_t *client, const char *upsname, const char *var, } if (!found) { - send_err(client, NUT_ERR_INVALID_VALUE); - return; + return SET_VAR_CHECK_VAL_INVALID_VALUE; } } @@ -128,9 +103,58 @@ static void set_var(nut_ctype_t *client, const char *upsname, const char *var, } if (!found) { + return SET_VAR_CHECK_VAL_INVALID_VALUE; + } + } + + return SET_VAR_CHECK_VAL_OK; +} + +static void set_var(nut_ctype_t *client, const char *upsname, const char *var, + const char *newval) +{ + upstype_t *ups; + set_var_check_val_t check; + + ups = get_ups_ptr(upsname); + + if (!ups) { + send_err(client, NUT_ERR_UNKNOWN_UPS); + return; + } + + if (!ups_available(ups, client)) + return; + + /* make sure this user is allowed to do SET */ + if (!user_checkaction(client->username, client->password, "SET")) { + send_err(client, NUT_ERR_ACCESS_DENIED); + return; + } + + check = set_var_check_val(ups, var, newval); + switch(check) + { + case SET_VAR_CHECK_VAL_VAR_NOT_SUPPORTED: + send_err(client, NUT_ERR_VAR_NOT_SUPPORTED); + return; + case SET_VAR_CHECK_VAL_READONLY: + send_err(client, NUT_ERR_READONLY); + return; + case SET_VAR_CHECK_VAL_SET_FAILED: + upslogx(LOG_WARNING, "UPS [%s]: auxdata for %s is invalid", + ups->name, var); + send_err(client, NUT_ERR_SET_FAILED); + return; + case SET_VAR_CHECK_VAL_TOO_LONG: + send_err(client, NUT_ERR_TOO_LONG); + return; + case SET_VAR_CHECK_VAL_INVALID_VALUE: send_err(client, NUT_ERR_INVALID_VALUE); return; - } + default: + /* Do nothing, continue. */ + break; } /* must be OK now */ @@ -166,6 +190,16 @@ static void set_var(nut_ctype_t *client, const char *upsname, const char *var, sendback(client, "OK\n"); } +int do_set_var(upstype_t *ups, const char *var, const char *newval) +{ + char cmd[SMALLBUF], esc[SMALLBUF]; + + snprintf(cmd, sizeof(cmd), "SET %s \"%s\"\n", + var, pconf_encode(newval, esc, sizeof(esc))); + + return sstate_sendline(ups, cmd); +} + void net_set(nut_ctype_t *client, size_t numarg, const char **arg) { char tracking_id[UUID4_LEN] = ""; diff --git a/server/netset.h b/server/netset.h index 1306126b4a..bbd9bfc4ba 100644 --- a/server/netset.h +++ b/server/netset.h @@ -31,6 +31,19 @@ extern "C" { /* *INDENT-ON* */ #endif +typedef enum +{ + SET_VAR_CHECK_VAL_OK = 0, + SET_VAR_CHECK_VAL_VAR_NOT_SUPPORTED, + SET_VAR_CHECK_VAL_READONLY, + SET_VAR_CHECK_VAL_SET_FAILED, + SET_VAR_CHECK_VAL_TOO_LONG, + SET_VAR_CHECK_VAL_INVALID_VALUE +} set_var_check_val_t; + +set_var_check_val_t set_var_check_val(upstype_t *ups, const char *var, const char *newval); +int do_set_var(upstype_t *ups, const char *var, const char *newval); + void net_set(nut_ctype_t *client, size_t numarg, const char **arg); #ifdef __cplusplus diff --git a/server/sstate.c b/server/sstate.c index d27e355dca..fa11164e87 100644 --- a/server/sstate.c +++ b/server/sstate.c @@ -27,6 +27,7 @@ #include "sstate.h" #include "upsd.h" #include "upstype.h" +#include "dbus.h" #include #include @@ -95,7 +96,9 @@ static int parse_args(upstype_t *ups, size_t numargs, char **arg) /* SETINFO */ if (!strcasecmp(arg[0], "SETINFO")) { - state_setinfo(&ups->inforoot, arg[1], arg[2]); + if (state_setinfo(&ups->inforoot, arg[1], arg[2])) { + dbus_notify_property_change(ups, arg[1], arg[2]); + } return 1; } diff --git a/server/upsd.c b/server/upsd.c index a296933dad..e96dc1c9a1 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -42,6 +42,7 @@ #include "sstate.h" #include "desc.h" #include "neterr.h" +#include "dbus.h" #ifdef HAVE_WRAP #include @@ -1040,7 +1041,12 @@ static void mainloop(void) upsdebugx(2, "%s: polling %d filedescriptors", __func__, nfds); +#ifdef WITH_DBUS + /* Poll less longuer to process dbus more frequently.*/ + ret = poll(fds, nfds, 100); +#else /* WITH_DBUS */ ret = poll(fds, nfds, 2000); +#endif /* WITH_DBUS */ if (ret == 0) { upsdebugx(2, "%s: no data available", __func__); @@ -1386,10 +1392,15 @@ int main(int argc, char **argv) /* initialize SSL (keyfile must be readable by nut user) */ ssl_init(); + /* initialize dbus connection. */ + dbus_init(); + while (!exit_flag) { mainloop(); + dbus_loop(); } + dbus_cleanup(); ssl_cleanup(); upslogx(LOG_INFO, "Signal %d: exiting", exit_flag);