Logo Search packages:      
Sourcecode: nut version File versions  Download package

main-hal.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;
      static      sigset_t          main_sigmask;
      static      struct      sigaction   main_sa;

      /* 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)
{
      sigemptyset(&main_sigmask);
      main_sa.sa_mask = main_sigmask;
      main_sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;

      main_sa.sa_handler = exit_cleanup;
      sigaction(SIGTERM, &main_sa, NULL);
      sigaction(SIGINT, &main_sa, NULL);
      sigaction(SIGQUIT, &main_sa, NULL);

/*    main_sa.sa_handler = SIG_IGN;
      sigaction(SIGHUP, &main_sa, NULL);
      sigaction(SIGPIPE, &main_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; */

      upsdrv_banner();

      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
       */
      sprintf(dbus_methods_introspection, "%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 */
      g_timeout_add_seconds (poll_interval,
                                          (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;
      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_INT32, 0);

            } 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, -1);
                              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, -1);
                              return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
                        }
                  }
            } else {
                  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
            }
      }
      dbus_send_reply(connection, message, DBUS_TYPE_INT32, 0);
      return DBUS_HANDLER_RESULT_HANDLED;
}

Generated by  Doxygen 1.6.0   Back to index