Logo Search packages:      
Sourcecode: gnome-volume-manager version File versions

manager.c

/*
 * src/manager.c - GNOME Volume Manager
 *
 * Robert Love <rml@ximian.com>
 *
 * gnome-volume-manager is a simple policy engine that implements a state
 * machine in response to events from HAL.  Responding to these events,
 * gnome-volume-manager implements automount, autorun, autoplay, automatic
 * photo management, and so on.
 *
 * Licensed under the GNU GPL v2.  See COPYING.
 *
 * (C) Copyright 2004 Novell, Inc.
 */

#include "config.h"

#include <gnome.h>
#include <gconf/gconf-client.h>
#include <gdk/gdkx.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <libhal.h>
#include <signal.h>

#include "gvm.h"

#ifdef ENABLE_NLS
# include <libintl.h>
# define _(String) gettext (String)
# ifdef gettext_noop
#   define N_(String) gettext_noop (String)
# else
#   define N_(String) (String)
# endif
#else
# define _(String)
# define N_(String) (String)
#endif

#define GVM_DEBUG
#ifdef GVM_DEBUG
# define dbg(fmt,arg...) fprintf(stderr, "%s/%d: " fmt,__FILE__,__LINE__,##arg)
#else
# define dbg(fmt,arg...) do { } while(0)
#endif

#define warn(fmt,arg...) g_warning("%s/%d: " fmt,__FILE__,__LINE__,##arg)

#define NAUTILUS_COMMAND       BIN_NAUTILUS" -n --no-desktop %m"

static struct gvm_configuration config;
static LibHalContext *hal_ctx;

/** List of UDI's for volumes mounted by g-v-m that we need to apply policy to*/
static GSList *mounted_volumes_policy_queue = NULL;

/** List of UDI's of all volumes mounted during the lifetime of the program */
static GSList *all_mounted_volumes = NULL;

/*
 * gvm_load_config - synchronize gconf => config structure
 */
static void
gvm_load_config (void)
{
      config.automount_drives = gconf_client_get_bool (config.client,
                  GCONF_ROOT "automount_drives", NULL);
      config.automount_media = gconf_client_get_bool (config.client,
                  GCONF_ROOT "automount_media", NULL);
      config.autoplay_cda = gconf_client_get_bool (config.client,
                  GCONF_ROOT "autoplay_cda", NULL);
      config.autobrowse = gconf_client_get_bool (config.client,
                  GCONF_ROOT "autobrowse", NULL);
      config.autorun = gconf_client_get_bool(config.client,
                  GCONF_ROOT "autorun", NULL);
      config.autophoto = gconf_client_get_bool(config.client,
                  GCONF_ROOT "autophoto", NULL);
      config.autoplay_dvd = gconf_client_get_bool (config.client,
                  GCONF_ROOT "autoplay_dvd", NULL);
      config.autoplay_cda_command = gconf_client_get_string (config.client,
                  GCONF_ROOT "autoplay_cda_command", NULL);
      config.autorun_path = gconf_client_get_string (config.client,
                  GCONF_ROOT "autorun_path", NULL);
      config.autoplay_dvd_command = gconf_client_get_string (config.client,
                  GCONF_ROOT "autoplay_dvd_command", NULL);
      config.autoburn_cdr = gconf_client_get_bool (config.client,
                  GCONF_ROOT "autoburn_cdr", NULL);
      config.autoburn_cdr_command = gconf_client_get_string (config.client,
                  GCONF_ROOT "autoburn_cdr_command", NULL);
      config.autophoto_command = gconf_client_get_string (config.client,
                  GCONF_ROOT "autophoto_command", NULL);
      config.eject_command = gconf_client_get_string (config.client,
                  GCONF_ROOT "eject_command", NULL);

      /*
       * If all of the options that control our policy are disabled, then we
       * have no point in living.  Save the user some memory and exit.
       */
      if (!(config.automount_drives || config.autobrowse || config.autorun 
                  || config.autoplay_cda  || config.autoplay_dvd 
                  || config.autophoto)) {
            dbg ("daemon exit: no point living\n");
            exit (EXIT_SUCCESS);
      }
}

/*
 * gvm_config_changed - gconf_client_notify_add () call back to reload config
 */
static void
gvm_config_changed (GConfClient *client __attribute__((__unused__)),
                guint id __attribute__((__unused__)),
                GConfEntry *entry __attribute__((__unused__)),
                gpointer data __attribute__((__unused__)))
{
      g_free (config.autoplay_cda_command);
      g_free (config.autorun_path);
      g_free (config.autoplay_dvd_command);
      g_free (config.autoburn_cdr_command);
      g_free (config.autophoto_command);
      g_free (config.eject_command);
    
      gvm_load_config ();
}

/*
 * gvm_init_config - initialize gconf client and load config data
 */
static void
gvm_init_config (void)
{
      config.client = gconf_client_get_default ();

      gconf_client_add_dir (config.client, GCONF_ROOT_SANS_SLASH,
                        GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);

      gvm_load_config ();

      gconf_client_notify_add (config.client, GCONF_ROOT_SANS_SLASH,
                         gvm_config_changed, NULL, NULL, NULL);
}

/*
 * gvm_run_command - run the given command, replacing %d with the device node,
 * %h with the HAL UDI and %m with the given path. Bails out if the command
 * requires a given %d,%h or %m and this is NULL.
 */
static void
gvm_run_command (const char *device, const char *command, 
             const char *path, const char *udi)
{
      char *argv[4];
      gchar *new_command;
      GError *error = NULL;
      GString *exec = g_string_new (NULL);
      char *p, *q;

      /* perform s/%d/device/, s/%m/path/ and s/%h/udi/ */
      new_command = g_strdup (command);
      q = new_command;
      p = new_command;
      while ((p = strchr (p, '%')) != NULL) {
            if (*(p + 1) == 'd') {
                  if (device == NULL)
                        goto error;
                  *p = '\0';
                  g_string_append (exec, q);
                  g_string_append (exec, device);
                  q = p + 2;
                  p = p + 2;
            } else if (*(p + 1) == 'm') {
                  if (path == NULL)
                        goto error;
                  *p = '\0';
                  g_string_append (exec, q);
                  g_string_append (exec, path);
                  q = p + 2;
                  p = p + 2;
            } else if (*(p + 1) == 'h') {
                  if (udi == NULL)
                        goto error;
                  *p = '\0';
                  g_string_append (exec, q);
                  g_string_append (exec, "\"");
                  g_string_append (exec, udi);
                  g_string_append (exec, "\"");
                  q = p + 2;
                  p = p + 2;
            }
      }
      g_string_append (exec, q);

      argv[0] = "/bin/sh";
      argv[1] = "-c";
      argv[2] = exec->str;
      argv[3] = NULL;

      g_spawn_async (g_get_home_dir (), argv, NULL, 0, NULL, NULL,
                   NULL, &error);
      if (error)
            warn ("failed to exec %s: %s\n", exec->str, error->message);

      g_string_free (exec, TRUE);
      g_free (new_command);
      return;

error:
      warn ("command '%s' required unavailable parameter; " 
            "%%d='%s' %%m='%s' %%h='%s'\n", command, device, path, udi);
      g_string_free (exec, TRUE);
      g_free (new_command);   
}

/*
 * gvm_ask_autorun - ask the user if they want to autorun a specific file
 *
 * Returns TRUE if the user selected 'Run' and FALSE otherwise
 */
static gboolean
gvm_ask_autorun (const char *path)
{
      GtkWidget *askme;
      gboolean retval;

      askme = gtk_message_dialog_new (NULL,
                              0, GTK_MESSAGE_WARNING,
                              GTK_BUTTONS_NONE,
                              _("Run command from inserted media?"));
      /*FIXME!! A better secondary label....*/
      gtk_message_dialog_format_secondary_text (GTK_DIALOG (askme),
                  _("The file \"%s\" on the inserted media is an auto-run"
                    "file."));

      gtk_dialog_add_buttons (GTK_DIALOG (askme),
                        GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
                        _("_Run Command"), GTK_RESPONSE_ACCEPT,
                        NULL);
      gtk_dialog_set_default_response (GTK_DIALOG (askme), GTK_RESPONSE_ACCEPT);

      switch (gtk_dialog_run (GTK_DIALOG (askme))) {
      case GTK_RESPONSE_ACCEPT:
            retval = TRUE;
            break;
      case GTK_RESPONSE_REJECT:
      default:
            retval = FALSE;
            break;
      }

      gtk_widget_destroy (askme);

      return retval;
}

/*
 * gvm_check_dvd - is this a Video DVD?  If so, do something about it.
 *
 * Returns TRUE if this was a Video DVD and FALSE otherwise.
 */
static gboolean
gvm_check_dvd (const char *device, const char *mount_point, const char *udi)
{
      char *path;
      gboolean retval;

      path = g_build_path (G_DIR_SEPARATOR_S, mount_point, "video_ts", NULL);
      retval = g_file_test (path, G_FILE_TEST_IS_DIR);
      g_free (path);

      /* try the other name, if needed */
      if (retval == FALSE) {
            path = g_build_path (G_DIR_SEPARATOR_S, mount_point,
                             "VIDEO_TS", NULL);
            retval = g_file_test (path, G_FILE_TEST_IS_DIR);
            g_free (path);
      }

      if (retval && config.autoplay_dvd)
            gvm_run_command (device, config.autoplay_dvd_command,
                         mount_point, udi);

      return retval;
}

/*
 * gvm_check_photos - check if this device is a digital camera or a storage
 * unit from a digital camera (e.g., a compact flash card).  If it is, then
 * ask the user if he wants to import the photos.
 *
 * Returns TRUE if there were photos on this device, FALSE otherwise
 *
 * FIXME: Should probably not prompt the user and just do it automatically.
 *        This now makes sense, as gphoto added an import mode.
 */
static gboolean
gvm_check_photos (const char *udi, const char *device, const char *mount_point)
{
      char *dcim_path;
      enum { IMPORT } action = -1;
      GtkWidget *askme;
      int retval = FALSE;

      dcim_path = g_build_path (G_DIR_SEPARATOR_S, mount_point, "dcim", NULL);

      if (!g_file_test (dcim_path, G_FILE_TEST_IS_DIR))
            goto out;

      retval = TRUE;
      dbg ("Photos detected: %s\n", dcim_path);

      /* add the "content.photos" capability to this device */
      if (!hal_device_add_capability (hal_ctx, udi, "content.photos"))
            warn ("failed to set content.photos on %s\n", device);

      if (config.autophoto) {
            askme = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_WARNING,
                                    GTK_BUTTONS_NONE,
                                    _("Import photos from device?"));
            gtk_message_dialog_format_secondary_text (GTK_DIALOG (askme),
                                            _("There are photos on the inserted media. "
                                              "Would you like to import these "
                                              "photos into your photo album?"));

            gtk_dialog_add_buttons (GTK_DIALOG (askme),
                              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                              _("_Import Photos"), IMPORT,
                              NULL);
            action = gtk_dialog_run (GTK_DIALOG (askme));
            gtk_widget_destroy (askme);

            if (action == IMPORT)
                  gvm_run_command (device, config.autophoto_command,
                               dcim_path, udi);
      }

out:
      g_free (dcim_path);
      return retval;
}


/*
 * gvm_check_camera - check if this device is a digital camera. If it is, then
 * ask the user if he wants to import the photos.
 *
 * Returns TRUE if this was a camera, FALSE otherwise
 */
static gboolean
gvm_check_camera (const char *udi)
{
      enum { IMPORT } action = -1;
      GtkWidget *askme;
      int retval = FALSE;

      /* see if it's a camera */
      if (!hal_device_query_capability(hal_ctx, udi, "camera"))
            goto out;

      retval = TRUE;
      dbg ("Camera detected: %s\n", udi);

      if (config.autophoto) {
            askme = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_WARNING,
                                    GTK_BUTTONS_NONE,
                                    _("Import photos from camera?"));
            gtk_message_dialog_format_secondary_text (GTK_DIALOG (askme),
                                            _("There are photos on the plugged-in camera. "
                                              "Would you like to import these "
                                              "photos into your photo album?"));
            gtk_dialog_add_buttons (GTK_DIALOG (askme),
                              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                              _("_Import Photos"), IMPORT,
                              NULL);
            action = gtk_dialog_run (GTK_DIALOG (askme));
            gtk_widget_destroy (askme);

            if (action == IMPORT)
                  gvm_run_command (NULL, config.autophoto_command,
                               NULL, udi);
      }

out:
      return retval;
}

/*
 * gvm_device_autorun - automatically execute stuff on the given UDI
 *
 * we currently autorun: autorun files, video DVD's, and digital photos
 */
static void
gvm_device_autorun (const char *udi)
{
      char *device = NULL, *mount_point = NULL;
      gboolean autorun_succeeded = FALSE;

      device = hal_device_get_property_string (hal_ctx, udi, "block.device");
      if (!device) {
            warn ("cannot get block.device\n");
            goto out;
      }

      mount_point = hal_device_get_property_string (hal_ctx, udi, 
                                          "volume.mount_point");
      if (!mount_point) {
            warn ("cannot get volume.mount_point\n");
            goto out;
      }

      if (gvm_check_dvd (device, mount_point, udi))
            goto out;

      if (gvm_check_photos (udi, device, mount_point))
            goto out;

      if (config.autorun == TRUE && config.autorun_path) {
            char **autorun_fns;
            int i;

            autorun_fns = g_strsplit (config.autorun_path, ":", -1);

            for (i = 0; autorun_fns[i]; i++) {
                  char *path, *argv[2];

                  path = g_strdup_printf ("%s/%s", mount_point,
                                    autorun_fns[i]);
                  argv[0] = path;
                  argv[1] = NULL;

                  if (access (path, X_OK))
                        continue;

                  if (gvm_ask_autorun (path)) {
                        GError *error = NULL;

                        g_spawn_async (g_get_home_dir (), argv, NULL,
                                     0, NULL, NULL, NULL, &error);
                        if (error)
                              warn ("failed to exec %s: %s\n", path,
                                    error->message);
                        else
                              autorun_succeeded = TRUE;
                        
                        g_free (path);
                        break;
                  }

                  g_free (path);
            }

            g_strfreev (autorun_fns);
      }
      
      if ((config.autobrowse == TRUE) && (autorun_succeeded == FALSE)) {
            gvm_run_command (device, NAUTILUS_COMMAND, mount_point, udi);
      }

out:
      hal_free_string (device);
      hal_free_string (mount_point);
}

/*
 * gvm_device_mount - use BIN_MOUNT to mount the given device node.
 *
 * Note that this requires that the given device node is in /etc/fstab.  This
 * is intentional.
 *
 * @return TRUE iff the mount was succesful
 */
static gboolean
gvm_device_mount (char *device)
{
      char *argv[3];
      GError *error = NULL;
      gint exit_status;

      argv[0] = BIN_MOUNT;
      argv[1] = device;
      argv[2] = NULL;

      if (!g_spawn_sync (g_get_home_dir (), argv, NULL, 0, NULL,
                     NULL, NULL, NULL, &exit_status, &error)) {
            warn ("failed to exec " BIN_MOUNT ": %s\n", error->message);
            return FALSE;
      }

      return (exit_status == 0);
}

/*
 * gvm_device_unmount - use BIN_UMOUNT to unmount the given device node.
 *
 * Note that this requires that the given device node is in /etc/fstab.  This
 * is intentional.
 *
 * @return TRUE iff the mount was succesful
 */
static gboolean
gvm_device_unmount (char *device)
{
      char *argv[3];
      GError *error = NULL;
      gint exit_status;

      argv[0] = BIN_UMOUNT;
      argv[1] = device;
      argv[2] = NULL;

      if (!g_spawn_sync (g_get_home_dir (), argv, NULL, 0, NULL,
                     NULL, NULL, NULL, &exit_status, &error)) {
            warn ("failed to exec " BIN_MOUNT ": %s\n", error->message);
            return FALSE;
      }

      return (exit_status == 0);
}

/*
 * gvm_run_cdplay - if so configured, execute the user-specified CD player on
 * the given device node
 */
static void
gvm_run_cdplayer (const char *device, const char *mount_point, const char *udi)
{
      if (config.autoplay_cda)
            gvm_run_command (device, config.autoplay_cda_command,
                         mount_point, udi);
}

/*
 * gvm_ask_mixed - if a mixed mode CD (CD Plus) is inserted, we can either
 * mount the data tracks or play the audio tracks.  How we handle that depends
 * on the user's configuration.  If the configuration allows either option,
 * we ask.
 */
static void
gvm_ask_mixed (const char *udi)
{
      enum { MOUNT, PLAY } action = -1;
      char *device = NULL, *mount_point = NULL;

      device = hal_device_get_property_string (hal_ctx, udi, "block.device");
      if (!device) {
            warn ("cannot get block.device\n");
            goto out;
      }

      if (config.automount_media && config.autoplay_cda) {
            GtkWidget *askme;

            askme = gtk_message_dialog_new (NULL, 0,
                                    GTK_MESSAGE_WARNING,
                                    GTK_BUTTONS_NONE,
                                    _("Browse files or play tracks from disc?"));

            gtk_message_dialog_format_secondary_text (GTK_DIALOG (askme),
                                            _("There are both audio tracks and "
                                              "data files on the inserted disc. "
                                              "Choose if you want to play the audio "
                                              "tracks to listen to music or browse "
                                              "the files to manage the stored data."));
            gtk_dialog_add_buttons (GTK_DIALOG (askme),
                              _("_Browse Files"), MOUNT,
                              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                              _("_Play Tracks"), PLAY,
                              NULL);
            action = gtk_dialog_run (GTK_DIALOG (askme));
            gtk_widget_destroy (askme);
      } else if (config.automount_media)
            action = MOUNT;
      else if (config.autoplay_cda)
            action = PLAY;

      switch (action) {
      case MOUNT:
            gvm_device_mount (device);
            mounted_volumes_policy_queue = g_slist_append (mounted_volumes_policy_queue, g_strdup (udi));
            break;
      case PLAY:
            gvm_run_cdplayer (device, device, udi);
            break;
      default:
            break;
      }

out:
      hal_free_string (device);
      hal_free_string (mount_point);
}

/*
 * gvm_run_cdburner - execute the user-specified CD burner command on the
 * given device node, if so configured
 */
static void
gvm_run_cdburner (const char *device, const char *mount, const char *udi)
{
      if (config.autoburn_cdr)
            gvm_run_command (device, config.autoburn_cdr_command, mount, udi);
}

/*
 * gvm_device_is_writer - is this device capable of writing CDs?
 */
static gboolean
gvm_device_is_writer (const char *udi)
{
      if ((hal_device_get_property_bool (hal_ctx, udi, "storage.cdrom.cdr")) ||
          (hal_device_get_property_bool (hal_ctx, udi, "storage.cdrom.cdrw")) ||
          (hal_device_get_property_bool (hal_ctx, udi, "storage.cdrom.dvdr")) ||
          (hal_device_get_property_bool (hal_ctx, udi, "storage.cdrom.dvdram")) ||
          (hal_device_get_property_bool (hal_ctx, udi, "storage.cdrom.dvdplusr")) ||
          (hal_device_get_property_bool (hal_ctx, udi, "storage.cdrom.dvdplusrw")))
            return TRUE;

      return FALSE;
}

/*
 * gvm_cdrom_policy - There has been a media change event on the CD-ROM
 * associated with the given UDI.  Enforce policy.
 */
static void
gvm_cdrom_policy (const char *udi)
{
      char *device = NULL;
      char *drive_udi = NULL;
      dbus_bool_t has_audio;
      dbus_bool_t has_data;
      dbus_bool_t is_blank;

      has_audio = hal_device_get_property_bool (hal_ctx, udi,
                                      "volume.disc.has_audio");
      has_data = hal_device_get_property_bool (hal_ctx, udi,
                                      "volume.disc.has_data");
      is_blank = hal_device_get_property_bool (hal_ctx, udi,
                                      "volume.disc.is_blank");
      drive_udi = hal_device_get_property_string(hal_ctx, udi,
                  "info.parent");

      device = hal_device_get_property_string (hal_ctx, udi, "block.device");
      if (!device) {
            warn ("cannot get block.device\n");
            goto out;
      }

      if (has_audio && (!has_data)) {
            gvm_run_cdplayer (device, device, udi);
      } else if (has_audio && has_data) {
            gvm_ask_mixed (udi);
      } else if (has_data) {
            if (config.automount_media) {
                  gvm_device_mount (device);
                  mounted_volumes_policy_queue = g_slist_append (mounted_volumes_policy_queue, g_strdup (udi));
            }
      } else if (is_blank) {
            if (gvm_device_is_writer (drive_udi))
                  gvm_run_cdburner (device, device, udi);
      }

      /** @todo enforce policy for all the new disc types now supported */

out:
      hal_free_string (device);
      hal_free_string (drive_udi);
}

/*
 * gvm_media_changed - generic media change handler.
 *
 * This is called on a UDI and the media's parent device in response to a media
 * change event.  We have to decipher the storage media type to run the
 * appropriate media-present check.  Then, if there is indeed media in the
 * drive, we enforce the appropriate policy.
 *
 * At the moment, we only handle CD-ROM and DVD drives.
 */
static void
gvm_media_changed (const char *udi, const char *storage_device, 
               const char *device)
{
      char *media_type;

      /* Refuse to enforce policy on removable media if drive is locked */
      if (hal_device_property_exists (
                hal_ctx, storage_device, "info.locked") &&
          hal_device_get_property_bool (
                hal_ctx, storage_device, "info.locked")) {
            dbg ("Drive with udi %s is locked through hal; "
                 "skipping policy\n", storage_device);
            return;
      }

      /*
       * Get HAL's interpretation of our media type.  Note that we must check
       * the storage device and not this UDI
       */
      media_type = hal_device_get_property_string (hal_ctx, storage_device, 
                                         "storage.drive_type");
      if (!media_type) {
            warn ("cannot get storage.drive_type\n");
            return;
      }

      if (!g_strcasecmp (media_type, "cdrom")) {
            gvm_cdrom_policy (udi);
      } else {
            dbg ("Added: %s\n", device);
            
            if (config.automount_drives) {
                  gvm_device_mount (device);
                  mounted_volumes_policy_queue = g_slist_append (mounted_volumes_policy_queue, g_strdup (udi));
            }
      }

      hal_free_string (media_type);
}

/** Invoked when a device is added to the Global Device List. 
 *
 *  @param  ctx                 LibHal context
 *  @param  udi                 Universal Device Id
 */
static void
hal_device_added (LibHalContext *ctx __attribute__((__unused__)), 
              const char *udi)
{
      char *device = NULL, *storage_device = NULL;

      dbg ("New Device: %s\n", udi);

      gvm_check_camera (udi);

      if (!hal_device_query_capability(hal_ctx, udi, "block"))
            goto out;
      
      /* is this a mountable volume ? */
      if (!hal_device_get_property_bool (hal_ctx, udi, 
                                 "block.is_volume"))
            goto out;
      
      /* if it is a volume, it must have a device node */
      device = hal_device_get_property_string (hal_ctx, udi, 
                                     "block.device");
      if (!device) {
            dbg ("cannot get block.device\n");
            goto out;
      }
      
      /* get the backing storage device */
      storage_device = hal_device_get_property_string (
            hal_ctx, udi,
            "block.storage_device");
      if (!storage_device) {
            dbg ("cannot get block.storage_device\n");
            goto out;
      }
      
      /*
       * Does this device support removable media?  Note that we
       * check storage_device and not our own UDI
       */
      if (hal_device_get_property_bool (hal_ctx, storage_device,
                                "storage.removable")) {
            /* we handle media change events separately */
            dbg ("Changed: %s\n", device);
            gvm_media_changed (udi, storage_device, device);
            goto out;
      }
      
      /* folks, we have a new device! */
      dbg ("Added: %s\n", device);
      
      if (config.automount_drives) {
            gvm_device_mount (device);
            mounted_volumes_policy_queue = g_slist_append (mounted_volumes_policy_queue, g_strdup (udi));
      }
      
out:
      hal_free_string (device);
      hal_free_string (storage_device);
}

/** Invoked when a device is removed from the Global Device List. 
 *
 *  @param  ctx                 LibHal context
 *  @param  udi                 Universal Device Id
 */
static void
hal_device_removed (LibHalContext *ctx __attribute__((__unused__)), 
                const char *udi)
{
      dbg ("Device removed: %s\n", udi);
}

/** Invoked when device in the Global Device List acquires a new capability.
 *
 *  @param  ctx                 LibHal context
 *  @param  udi                 Universal Device Id
 *  @param  capability          Name of capability
 */
static void
hal_device_new_capability (LibHalContext *ctx __attribute__((__unused__)),
                     const char *udi __attribute__((__unused__)), 
                     const char *capability __attribute__((__unused__)))
{
}

/** Invoked when device in the Global Device List loses a capability.
 *
 *  @param  ctx                 LibHal context
 *  @param  udi                 Universal Device Id
 *  @param  capability          Name of capability
 */
static void
hal_device_lost_capability (LibHalContext *ctx __attribute__((__unused__)),
                      const char *udi __attribute__((__unused__)), 
                      const char *capability __attribute__((__unused__)))
{
}

/** Invoked when a property of a device in the Global Device List is
 *  changed, and we have we have subscribed to changes for that device.
 *
 *  @param  ctx                 LibHal context
 *  @param  udi                 Univerisal Device Id
 *  @param  key                 Key of property
 */
static void
hal_property_modified (LibHalContext *ctx __attribute__((__unused__)),
                   const char *udi, 
                   const char *key,
                   dbus_bool_t is_removed __attribute__((__unused__)), 
                   dbus_bool_t is_added __attribute__((__unused__)))
{
      dbus_bool_t val;
      GSList *i;
      GSList *next;

      if (g_strcasecmp (key, "volume.is_mounted") != 0)
            return;
      
      val = hal_device_get_property_bool (hal_ctx, udi, key);
      if (val == TRUE) {
            GSList *policy_udi;

            dbg ("Mounted: %s\n", udi);

            /* add to list of all volumes mounted during lifetime */
            all_mounted_volumes = g_slist_append (all_mounted_volumes,
                                          g_strdup (udi));

            policy_udi = g_slist_find_custom (mounted_volumes_policy_queue, 
                                      udi, 
                                      g_ascii_strcasecmp);
            if (policy_udi != NULL) {
                  g_free (policy_udi->data);
                  mounted_volumes_policy_queue = g_slist_delete_link (mounted_volumes_policy_queue, 
                                                          policy_udi);
                  gvm_device_autorun (udi);
            }

      } else {
            dbg ("Unmounted: %s\n", udi);

            /* remove from list of all volumes mounted during lifetime */

            for (i=all_mounted_volumes; i != NULL; i = next) {
                  next = g_slist_next (i);
                  if (strcmp (udi, (const char *)i->data) == 0) {
                        g_free (i->data);
                        all_mounted_volumes = 
                              g_slist_delete_link (
                                    all_mounted_volumes, i);
                        break;
                  }
            }

      }
}

/** Invoked when a device in the GDL emits a condition that cannot be
 *  expressed in a property (like when the processor is overheating)
 *
 *  @param  ctx                 LibHal context
 *  @param  udi                 Univerisal Device Id
 *  @param  condition_name      Name of condition
 *  @param  message             D-BUS message with parameters
 */
static void
hal_device_condition (LibHalContext *ctx __attribute__((__unused__)),
                  const char *udi __attribute__((__unused__)), 
                  const char *condition_name __attribute__((__unused__)),
                  DBusMessage * message __attribute__((__unused__)))
{
}

/** Invoked by libhal for integration with our mainloop. 
 *
 *  @param  ctx                 LibHal context
 *  @param  dbus_connection     D-BUS connection to integrate
 */
static void
hal_mainloop_integration (LibHalContext *ctx __attribute__((__unused__)),
                    DBusConnection * dbus_connection)
{
      dbus_connection_setup_with_g_main (dbus_connection, NULL);
}

/** Internal HAL initialization function
 *
 * @functions                 The LibHalFunctions to register as callbacks.
 * @return              The LibHalContext of the HAL connection or
 *                      NULL on error.
 */
static LibHalContext *
gvm_do_hal_init (LibHalFunctions *functions)
{
      LibHalContext *ctx;
      char **devices;
      int nr;

      ctx = hal_initialize (functions, FALSE);
      if (!ctx) {
            warn ("failed to initialize HAL!\n");
            return NULL;
      }

      if (hal_device_property_watch_all (ctx)) {
            warn ("failed to watch all HAL properties!\n");
            hal_shutdown (ctx);
            return NULL;
      }

      /*
       * Do something to ping the HAL daemon - the above functions will
       * succeed even if hald is not running, so long as DBUS is.  But we
       * want to exit silently if hald is not running, to behave on
       * pre-2.6 systems.
       */
      devices = hal_get_all_devices (ctx, &nr);
      if (!devices) {
            warn ("seems that HAL is not running\n");
            hal_shutdown (ctx);
            return NULL;
      }
      hal_free_string_array (devices);

      return ctx;
}

/** Attempt to mount all volumes; should be called on startup.
 *
 *  @param  ctx                 LibHal context
 */
static void mount_all (LibHalContext *ctx)
{
      int i;
      int num_volumes;
      char **volumes;
      char *udi;
      char *device_file;

      if (!config.automount_media)
            return;

      volumes = hal_find_device_by_capability (ctx, "volume", &num_volumes);
      for (i = 0; i < num_volumes; i++) {
            udi = volumes [i];

            /* don't attempt to mount already mounted volumes */
            if (!hal_device_property_exists (ctx, udi, 
                                    "volume.is_mounted") ||
                hal_device_get_property_bool (ctx, udi, 
                                      "volume.is_mounted"))
                  continue;

            /* only mount if the block device got a sensible filesystem */
            if (!hal_device_property_exists (ctx, udi, 
                                    "volume.fsusage") ||
                strcmp (hal_device_get_property_string (ctx, udi, 
                                              "volume.fsusage"), 
                      "filesystem") != 0)
                  continue;

            device_file = hal_device_get_property_string (ctx, udi,
                                                "block.device");

            if (device_file != NULL ) {

                  dbg ("mount_all: mounting %s\n", device_file);

                  gvm_device_mount (device_file);

                  hal_free_string (device_file);
            } else
                  warn ("no device_file for udi=%s\n", udi);
      }

      hal_free_string_array (volumes);
}

/** Unmount all volumes that were mounted during the lifetime of this
 *  g-v-m instance
 *
 *  @param  ctx                 LibHal context
 */
static void
unmount_all (LibHalContext *ctx)
{
      GSList *i;
      char *device_file;
      char *udi;

      dbg ("unmounting all volumes that we saw mounted in our life\n");

      for (i = all_mounted_volumes; i != NULL; i = g_slist_next (i)) {

            udi = i->data;

            device_file = hal_device_get_property_string (ctx, udi,
                                                "block.device");
            if (device_file != NULL ) {

                  dbg ("unmount_all: unmounting %s\n", device_file);

                  gvm_device_unmount (device_file);
                  hal_free_string (device_file);
            } else {
                  warn ("no device_file for udi=%s\n", udi);
            }
      }
}


static int sigterm_unix_signal_pipe_fds[2];
static GIOChannel *sigterm_iochn;

static void 
handle_sigterm (int value)
{
      static char marker[1] = {'S'};

      /* write a 'S' character to the other end to tell about
       * the signal. Note that 'the other end' is a GIOChannel thingy
       * that is only called from the mainloop - thus this is how we
       * defer this since UNIX signal handlers are evil
       *
       * Oh, and write(2) is indeed reentrant */
      write (sigterm_unix_signal_pipe_fds[1], marker, 1);
}

static gboolean
sigterm_iochn_data (GIOChannel *source, 
                GIOCondition condition, 
                gpointer user_data)
{
      GError *err = NULL;
      gchar data[1];
      gsize bytes_read;

      /* Empty the pipe */
      if (G_IO_STATUS_NORMAL != 
          g_io_channel_read_chars (source, data, 1, &bytes_read, &err)) {
            warn ("Error emptying callout notify pipe: %s",
                  err->message);
            g_error_free (err);
            goto out;
      }

      dbg ("Received SIGTERM, initiating shutdown\n");
      unmount_all (hal_ctx);
      gtk_main_quit();

out:
      return TRUE;
}

static void
gvm_die (GnomeClient *client, gpointer user_data) 
{
      dbg ("Received 'die', initiating shutdown\n");
      unmount_all (hal_ctx);
      gtk_main_quit ();
}

int
main (int argc, char *argv[])
{
      GnomeClient *client;
      LibHalFunctions hal_functions = { hal_mainloop_integration,
                                hal_device_added,
                                hal_device_removed,
                                hal_device_new_capability,
                                hal_device_lost_capability,
                                hal_property_modified,
                                hal_device_condition };

      gnome_program_init (PACKAGE, VERSION, LIBGNOMEUI_MODULE,
                      argc, argv, GNOME_PARAM_NONE);

      bindtextdomain(PACKAGE, GNOMELOCALEDIR);
      bind_textdomain_codeset(PACKAGE, "UTF-8");
      textdomain(PACKAGE);

      client = gnome_master_client ();
      if (gvm_get_clipboard ())
            gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
      else {
            gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
            warn ("already running?\n");
            return 1;
      }

      gtk_signal_connect (GTK_OBJECT (client), "die",
                      GTK_SIGNAL_FUNC (gvm_die), NULL);

      hal_ctx = gvm_do_hal_init (&hal_functions);
      if (!hal_ctx)
            return 1;

      gvm_init_config ();

      /* SIGTERM handling via pipes  */
      if (pipe (sigterm_unix_signal_pipe_fds) != 0) {
            warn ("Could not setup pipe, errno=%d", errno);
            return 1;
      }
      sigterm_iochn = g_io_channel_unix_new (sigterm_unix_signal_pipe_fds[0]);
      if (sigterm_iochn == NULL) {
            warn ("Could not create GIOChannel");
            return 1;
      }
      g_io_add_watch (sigterm_iochn, G_IO_IN, sigterm_iochn_data, NULL);
      signal (SIGTERM, handle_sigterm);

      mount_all (hal_ctx);

      gtk_main ();

      return 0;
}

Generated by  Doxygen 1.6.0   Back to index