571 lines
15 KiB
C
571 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_G_TIMEOUT_ADD_SECONDS
|
|
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;
|
|
}
|