nut-debian/drivers/main-hal.c
2010-03-26 00:20:59 +01:00

572 lines
15 KiB
C

/* main-hal.c - Network UPS Tools driver core for HAL
Copyright (C) 2006 Arnaud Quette <aquette.dev@gmail.com>
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
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
*/
/* TODO list:
* -more cleanup
* - dstate-hal: expose all data on org.freedesktop.NUT to prepare
* a full HAL/DBus enabled nut (remove the need of {d,s}state layer
* - use HAL logging functions
*/
#include "main-hal.h"
#include "dstate-hal.h"
#include <hal/libhal.h>
#ifdef HAVE_POLKIT
#include <libpolkit.h>
#endif
/* HAL specific */
extern LibHalContext *halctx;
extern char *udi;
/* data which may be useful to the drivers */
int upsfd = -1;
char *device_path = NULL;
const char *progname = NULL, *upsname = NULL,
*device_name = NULL;
/* may be set by the driver to wake up while in dstate_poll_fds */
int extrafd = -1;
/* for ser_open */
int do_lock_port = 1;
/* set by the drivers */
int experimental_driver = 0;
int broken_driver = 0;
static vartab_t *vartab_h = NULL;
/* variables possibly set by the global part of ups.conf */
unsigned int poll_interval = 2;
static char *chroot_path = NULL;
/* signal handling */
int exit_flag = 0;
/* everything else */
static char *pidfn = NULL;
GMainLoop *gmain;
char *dbus_methods_introspection;
/* retrieve the value of variable <var> if possible */
char *getval(const char *var)
{
vartab_t *tmp = vartab_h;
while (tmp) {
if (!strcasecmp(tmp->var, var))
return(tmp->val);
tmp = tmp->next;
}
return NULL;
}
/* see if <var> has been defined, even if no value has been given to it */
int testvar(const char *var)
{
vartab_t *tmp = vartab_h;
while (tmp) {
if (!strcasecmp(tmp->var, var))
return tmp->found;
tmp = tmp->next;
}
return 0; /* not found */
}
/* callback from driver - create the table for -x/conf entries */
void addvar(int vartype, const char *name, const char *desc)
{
vartab_t *tmp, *last;
tmp = last = vartab_h;
while (tmp) {
last = tmp;
tmp = tmp->next;
}
tmp = xmalloc(sizeof(vartab_t));
tmp->vartype = vartype;
tmp->var = xstrdup(name);
tmp->val = NULL;
tmp->desc = xstrdup(desc);
tmp->found = 0;
tmp->next = NULL;
if (last)
last->next = tmp;
else
vartab_h = tmp;
}
static void vartab_free(void)
{
vartab_t *tmp, *next;
tmp = vartab_h;
while (tmp) {
next = tmp->next;
free(tmp->var);
free(tmp->val);
free(tmp->desc);
free(tmp);
tmp = next;
}
}
static void exit_cleanup(int sig)
{
upsdebugx(2, "exit_cleanup(%i", sig);
exit_flag = sig;
upsdrv_cleanup();
free(chroot_path);
free(device_path);
if (pidfn) {
unlink(pidfn);
free(pidfn);
}
dstate_free();
vartab_free();
/* break the main loop */
g_main_loop_quit(gmain);
}
static void setup_signals(void)
{
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sa.sa_handler = exit_cleanup;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
/* sa.sa_handler = SIG_IGN;
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);*/
}
/* (*GSourceFunc) wrapper */
static gboolean update_data (gpointer data)
{
upsdrv_updateinfo();
return (exit_flag == 0);
}
int main(int argc, char **argv)
{
DBusError dbus_error;
DBusConnection *dbus_connection;
struct passwd *new_uid = NULL;
char *hal_debug_level;
/* int i, do_forceshutdown = 0; */
if (experimental_driver) {
printf("Warning: This is an experimental driver.\n");
printf("Some features may not function correctly.\n\n");
}
progname = xbasename(argv[0]);
open_syslog(progname);
dbus_methods_introspection = xmalloc(1024);
/* Register the basic supported method (Shutdown)
* Leave other methods registration to driver core calls to
* dstate_addcmd(), at initinfo() time
*/
snprintf(dbus_methods_introspection, 1024, "%s",
" <method name=\"Shutdown\">\n"
/* " <arg name=\"shutdown_type\" direction=\"in\" type=\"s\"/>\n" */
" <arg name=\"return_code\" direction=\"out\" type=\"i\"/>\n"
" </method>\n");
/* initialise HAL and DBus interface*/
halctx = NULL;
udi = getenv ("UDI");
if (udi == NULL) {
fprintf(stderr, "Error: UDI is null.\n");
exit(EXIT_FAILURE);
}
dbus_error_init (&dbus_error);
if ((halctx = libhal_ctx_init_direct (&dbus_error)) == NULL) {
fprintf(stderr, "Error: can't initialise libhal.\n");
exit(EXIT_FAILURE);
}
if (dbus_error_is_set (&dbus_error))
{
fatalx(EXIT_FAILURE, "Error in context creation: %s\n", dbus_error.message);
dbus_error_free (&dbus_error);
}
if ((dbus_connection = libhal_ctx_get_dbus_connection(halctx)) == NULL) {
fprintf(stderr, "Error: can't get DBus connection.\n");
exit(EXIT_FAILURE);
}
/* FIXME: rework HAL param interface! or get path/regex from UDI
* Example:
* /org/freedesktop/Hal/devices/usb_device_463_ffff_1H2E300AH
* => linux.device_file = /dev/bus/usb/002/065
*/
/* FIXME: the naming should be abstracted to os.device_file! */
device_path = xstrdup("auto");
/* FIXME: bridge debug/warning on HAL equivalent (need them
* to externalize these in a lib... */
hal_debug_level = getenv ("NUT_HAL_DEBUG");
if (hal_debug_level == NULL)
nut_debug_level = 0;
else
nut_debug_level = atoi(hal_debug_level);
/* Sleep 2 seconds to be able to view the device through usbfs! */
sleep(2);
/* build the driver's extra (-x) variable table */
upsdrv_makevartable();
/* Switch to the HAL user */
new_uid = get_user_pwent(HAL_USER);
become_user(new_uid);
/* Only switch to statepath if we're not powering off */
/* This avoid case where ie /var is umounted */
/* ?! Not needed for HAL !?
if (!do_forceshutdown)
if (chdir(dflt_statepath()))
fatal_with_errno(EXIT_FAILURE, "Can't chdir to %s", dflt_statepath());
*/
setup_signals();
/* clear out callback handler data */
memset(&upsh, '\0', sizeof(upsh));
upsdrv_initups();
#if 0
if (do_forceshutdown)
forceshutdown();
#endif
/* get the supported data and commands before allowing connections */
upsdrv_initinfo();
upsdrv_updateinfo();
/* now we can start servicing requests */
dstate_init(NULL, NULL);
/* Commit DBus methods */
if (!libhal_device_claim_interface(halctx, udi, DBUS_INTERFACE,
dbus_methods_introspection, &dbus_error)) {
fprintf(stderr, "Cannot claim interface: %s\n", dbus_error.message);
}
else
fprintf(stdout, "Claimed the following DBus interfaces on %s:\n%s",
DBUS_INTERFACE, dbus_methods_introspection);
/* Complete DBus binding */
dbus_connection_setup_with_g_main(dbus_connection, NULL);
dbus_connection_add_filter(dbus_connection, dbus_filter_function, NULL, NULL);
dbus_connection_set_exit_on_disconnect(dbus_connection, 0);
dbus_init_local();
/* end of HAL init */
#if 0
/* publish the top-level data: version number, driver name */
dstate_setinfo("driver.version", "%s", UPS_VERSION);
dstate_setinfo("driver.name", "%s", progname);
/* The poll_interval may have been changed from the default */
dstate_setinfo("driver.parameter.pollinterval", "%d", poll_interval);
/* FIXME: needed? */
if (nut_debug_level == 0) {
background();
writepid(pidfn);
}
#endif
/* End HAL init */
dbus_error_init (&dbus_error);
if (!libhal_device_addon_is_ready (halctx, udi, &dbus_error)) {
fprintf(stderr, "Error (libhal): device addon is not ready\n");
exit(EXIT_FAILURE);
}
/* add a timer for data update */
#ifdef HAVE_GLIB_2_14
g_timeout_add_seconds (poll_interval,
#else
g_timeout_add (1000 * poll_interval, /* seconds */
#endif
(GSourceFunc)update_data,
NULL);
/* setup and run the main loop */
gmain = g_main_loop_new(NULL, FALSE);
g_main_loop_run(gmain);
/* reached upon addon exit */
upslogx(LOG_INFO, "Signal %d: exiting", exit_flag);
exit(EXIT_SUCCESS);
}
/********************************************************************
* DBus interface functions and data
*******************************************************************/
static DBusHandlerResult dbus_filter_function_local(DBusConnection *connection,
DBusMessage *message,
void *user_data)
{
if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
upsdebugx(1, "DBus daemon disconnected. Trying to reconnect...");
dbus_connection_unref(connection);
g_timeout_add(5000, (GSourceFunc)dbus_init_local, NULL);
}
return DBUS_HANDLER_RESULT_HANDLED;
}
/* returns FALSE on success because it's used as a callback */
gboolean dbus_init_local(void)
{
DBusConnection *dbus_connection;
DBusError dbus_error;
dbus_error_init(&dbus_error);
dbus_connection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error);
if (dbus_error_is_set(&dbus_error)) {
upsdebugx(1, "Cannot get D-Bus connection");
/* dbus_error_free (&dbus_error); */
return TRUE;
}
dbus_connection_setup_with_g_main(dbus_connection, NULL);
dbus_connection_add_filter(dbus_connection, dbus_filter_function_local,
NULL, NULL);
dbus_connection_set_exit_on_disconnect(dbus_connection, 0);
return FALSE;
}
#ifdef HAVE_POLKIT
/**
* dbus_is_privileged:
* @connection: connection to D-Bus
* @message: Message
* @error: the error
*
* Returns: TRUE if the caller is privileged
*
* checks if caller of message possesses the CPUFREQ_POLKIT_PRIVILGE
*/
static gboolean
dbus_is_privileged (DBusConnection *connection, DBusMessage *message, DBusError *error)
{
gboolean ret;
char *polkit_result;
const char *invoked_by_syscon_name;
ret = FALSE;
polkit_result = NULL;
/* FIXME: CPUFREQ_POLKIT_PRIVILEGE, CPUFREQ_ERROR_GENERAL */
invoked_by_syscon_name = dbus_message_get_sender (message);
polkit_result = libhal_device_is_caller_privileged (halctx,
udi,
CPUFREQ_POLKIT_PRIVILEGE,
invoked_by_syscon_name,
error);
if (polkit_result == NULL) {
dbus_raise_error (connection, message, CPUFREQ_ERROR_GENERAL,
"Cannot determine if caller is privileged");
}
else {
if (strcmp (polkit_result, "yes") != 0) {
dbus_raise_error (connection, message,
"org.freedesktop.Hal.Device.PermissionDeniedByPolicy",
"%s %s <-- (action, result)",
CPUFREQ_POLKIT_PRIVILEGE, polkit_result);
}
else
ret = TRUE;
}
if (polkit_result != NULL)
libhal_free_string (polkit_result);
return ret;
}
#endif
/**
* dbus_send_reply:
* @connection: connection to D-Bus
* @message: Message
* @type: the type of data param
* @data: data to send
*
* Returns: TRUE/FALSE
*
* sends a reply to message with the given data and its dbus_type
*/
static gboolean dbus_send_reply(DBusConnection *connection, DBusMessage *message,
int dbus_type, void *data)
{
DBusMessage *reply;
if ((reply = dbus_message_new_method_return(message)) == NULL) {
upslogx(LOG_WARNING, "Could not allocate memory for the DBus reply");
return FALSE;
}
if (data != NULL)
dbus_message_append_args(reply, dbus_type, data, DBUS_TYPE_INVALID);
if (!dbus_connection_send(connection, reply, NULL)) {
upslogx(LOG_WARNING, "Could not sent reply");
return FALSE;
}
dbus_connection_flush(connection);
dbus_message_unref(reply);
return TRUE;
}
/**
* dbus_get_argument:
* @connection: connection to D-Bus
* @message: Message
* @dbus_error: the D-Bus error
* @type: the type of arg param
* @arg: the value to get from the message
*
* Returns: TRUE/FALSE
*
* gets one argument from message with the given dbus_type and stores it in arg
*/
static gboolean dbus_get_argument(DBusConnection *connection, DBusMessage *message,
DBusError *dbus_error, int dbus_type, void *arg)
{
dbus_message_get_args(message, dbus_error, dbus_type, arg,
DBUS_TYPE_INVALID);
if (dbus_error_is_set(dbus_error)) {
upslogx(LOG_WARNING, "Could not get argument of DBus message: %s",
dbus_error->message);
dbus_error_free(dbus_error);
return FALSE;
}
return TRUE;
}
/**
* dbus_filter_function:
* @connection: connection to D-Bus
* @message: message
* @user_data: pointer to the data
*
* Returns: the result
*
* @raises UnknownMethod
*
* D-Bus filter function
*/
DBusHandlerResult dbus_filter_function(DBusConnection *connection,
DBusMessage *message,
void *user_data)
{
DBusError dbus_error;
int retcode = -1;
const char *member = dbus_message_get_member(message);
const char *path = dbus_message_get_path(message);
/* int ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; */
/* upsdebugx(2, "Received DBus message with member %s path %s", member, path); */
fprintf(stdout, "Received DBus message with member %s on path %s\n", member, path);
dbus_error_init(&dbus_error);
if (dbus_error_is_set (&dbus_error))
{
fprintf (stderr, "an error occurred: %s\n", dbus_error.message);
/* dbus_error_free (&dbus_error); */
}
else
{
#ifdef HAVE_POLKIT
if (!dbus_is_privileged(connection, message, &dbus_error))
return DBUS_HANDLER_RESULT_HANDLED;
#endif
if (dbus_message_is_method_call(message, DBUS_INTERFACE, "Shutdown")) {
fprintf(stdout, "executing Shutdown\n");
upsdrv_shutdown();
dbus_send_reply(connection, message, DBUS_TYPE_INVALID, NULL);
} else if (dbus_message_is_method_call(message,
DBUS_INTERFACE, "SetBeeper")) {
fprintf(stdout, "executing SetBeeper\n");
gboolean b_enable;
if (!dbus_get_argument(connection, message, &dbus_error,
DBUS_TYPE_BOOLEAN, &b_enable)) {
fprintf(stderr, "Error receiving boolean argument\n");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
fprintf(stdout, "Received argument: %s\n", (b_enable==TRUE)?"true":"false");
if (b_enable==TRUE) {
if (upsh.instcmd("beeper.enable", NULL) != STAT_INSTCMD_HANDLED) {
dbus_send_reply(connection, message, DBUS_TYPE_INT32, &retcode);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
else {
if (upsh.instcmd("beeper.disable", NULL) != STAT_INSTCMD_HANDLED) {
dbus_send_reply(connection, message, DBUS_TYPE_INT32, &retcode);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
} else {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
dbus_send_reply(connection, message, DBUS_TYPE_INVALID, NULL);
return DBUS_HANDLER_RESULT_HANDLED;
}