udisksctl: add 'smart-simulate' verb
authorDavid Zeuthen <davidz@redhat.com>
Sat, 12 May 2012 16:07:33 +0000 (12:07 -0400)
committerDavid Zeuthen <davidz@redhat.com>
Sat, 12 May 2012 16:07:33 +0000 (12:07 -0400)
This makes it a lot easier to simulate failing disks. Also add proper
polkit support so the user don't have to run it as root.

Signed-off-by: David Zeuthen <davidz@redhat.com>
data/org.freedesktop.UDisks2.xml
data/org.freedesktop.udisks2.policy.in
doc/man/udisksctl.xml
src/udiskslinuxdriveata.c
tools/udisksctl.c

index 784a9e9..a75bc81 100644 (file)
 
         The option @atasmart_blob can be used to inject libatasmart
         compatible blobs for testing how clients react to different
-        kinds of SMART data. Only uid 0 may use this. This option may
-        be removed in the future with it being considered an ABI break
-        - it only exists for testing purposes. Example:
-<programlisting>
-# export ATA_SMART_BLOB=/usr/share/doc/libatasmart-devel-0.17/Maxtor_96147H8-&#45;BAC51KJ0-&#45;2 ; \
-  gdbus call -&#45;system -&#45;dest org.freedesktop.UDisks2 \
-             -&#45;object-path /org/freedesktop/UDisks2/drives/WDC_WD1002FAEX_00Y9A0_WD_WCAW30039835 \
-             -&#45;method org.freedesktop.UDisks2.Drive.Ata.SmartUpdate \
-             "{'atasmart_blob': &#60;'$ATA_SMART_BLOB'&#62;}"
-</programlisting>
+        kinds of SMART data. This option may be removed in the future
+        with it being considered an ABI break.
     -->
     <method name="SmartUpdate">
       <arg name="options" direction="in" type="a{sv}"/>
index bcbeac3..2c60c3d 100644 (file)
     </defaults>
   </action>
 
+  <!-- Set SMART data from blob -->
+  <action id="org.freedesktop.udisks2.ata-smart-simulate">
+    <_description>Set SMART data from blob</_description>
+    <_message>Authentication is required to set SMART data from blob</_message>
+    <defaults>
+      <allow_any>auth_admin</allow_any>
+      <allow_inactive>auth_admin</allow_inactive>
+      <allow_active>auth_admin_keep</allow_active>
+    </defaults>
+  </action>
+
   <!-- Start and abort SMART self-tests -->
   <action id="org.freedesktop.udisks2.ata-smart-selftest">
     <_description>Run SMART self-test</_description>
index 3892692..4b8145f 100644 (file)
 
     <cmdsynopsis>
       <command>udisksctl</command>
+      <arg choice="plain">smart-simulate </arg>
+      <arg choice="plain">--file <replaceable>PATH</replaceable></arg>
+      <group choice="req">
+        <arg choice="plain">--object-path <replaceable>OBJECT</replaceable></arg>
+        <arg choice="plain">--block-device <replaceable>DEVICE</replaceable></arg>
+      </group>
+      <arg choice="opt">--no-user-interaction</arg>
+    </cmdsynopsis>
+
+    <cmdsynopsis>
+      <command>udisksctl</command>
       <arg choice="plain">monitor</arg>
     </cmdsynopsis>
 
       </varlistentry>
 
       <varlistentry>
+        <term><option>smart-simulate</option></term>
+        <listitem>
+          <para>
+            Sets SMART data from the libatasmart blob given by
+            <replaceable>FILE</replaceable> - see
+            <filename>/usr/share/doc/libatasmart-devel-VERSION/</filename>
+            for blobs shipped with libatasmart. This is a debugging
+            feature used to check that applications act correctly when
+            a disk is failing.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><option>monitor</option></term>
         <listitem><para>
           Monitors the daemon for events.
index f4d41ac..2513445 100644 (file)
@@ -581,6 +581,8 @@ handle_smart_update (UDisksDriveAta        *_drive,
   gboolean nowakeup = FALSE;
   const gchar *atasmart_blob = NULL;
   GError *error;
+  const gchar *message;
+  const gchar *action_id;
 
   daemon = NULL;
 
@@ -606,42 +608,28 @@ handle_smart_update (UDisksDriveAta        *_drive,
   g_variant_lookup (options, "nowakeup", "b", &nowakeup);
   g_variant_lookup (options, "atasmart_blob", "s", &atasmart_blob);
 
+  /* Translators: Shown in authentication dialog when the user
+   * refreshes SMART data from a disk.
+   *
+   * Do not translate $(udisks2.device), it's a placeholder and
+   * will be replaced by the name of the drive/device in question
+   */
+  message = N_("Authentication is required to update SMART data from $(udisks2.device)");
+  action_id = "org.freedesktop.udisks2.ata-smart-update";
+
   if (atasmart_blob != NULL)
     {
-      uid_t caller_uid;
-      error = NULL;
-      if (!udisks_daemon_util_get_caller_uid_sync (daemon, invocation, NULL /* GCancellable */, &caller_uid, NULL, NULL, &error))
-        {
-          g_dbus_method_invocation_return_gerror (invocation, error);
-          g_error_free (error);
-          goto out;
-        }
-      if (caller_uid != 0)
-        {
-          g_dbus_method_invocation_return_error (invocation,
-                                                 UDISKS_ERROR,
-                                                 UDISKS_ERROR_FAILED,
-                                                 "Only root can update SMART data from a blob");
-          goto out;
-        }
+      /* Translators: Shown in authentication dialog when the user
+       * tries to simulate SMART data from a libatasmart blob.
+       *
+       * Do not translate $(udisks2.device), it's a placeholder and
+       * will be replaced by the name of the drive/device in question
+       */
+      message = N_("Authentication is required to set SMART data from a blob on $(udisks2.device)");
+      action_id = "org.freedesktop.udisks2.ata-smart-simulate";
     }
   else
     {
-      /* Check that the user is actually authorized */
-      if (!udisks_daemon_util_check_authorization_sync (daemon,
-                                                        UDISKS_OBJECT (block_object),
-                                                        "org.freedesktop.udisks2.ata-smart-update",
-                                                        options,
-                                                        /* Translators: Shown in authentication dialog when the user
-                                                         * refreshes SMART data from a disk.
-                                                         *
-                                                         * Do not translate $(udisks2.device), it's a placeholder and
-                                                         * will be replaced by the name of the drive/device in question
-                                                         */
-                                                        N_("Authentication is required to update SMART data from $(udisks2.device)"),
-                                                        invocation))
-        goto out;
-
       if (!udisks_drive_ata_get_smart_supported (UDISKS_DRIVE_ATA (drive)))
         {
           g_dbus_method_invocation_return_error (invocation,
@@ -661,6 +649,15 @@ handle_smart_update (UDisksDriveAta        *_drive,
         }
     }
 
+  /* Check that the user is authorized */
+  if (!udisks_daemon_util_check_authorization_sync (daemon,
+                                                    UDISKS_OBJECT (block_object),
+                                                    action_id,
+                                                    options,
+                                                    message,
+                                                    invocation))
+    goto out;
+
   error = NULL;
   if (!udisks_linux_drive_ata_refresh_smart_sync (drive,
                                                   nowakeup,
index 7a02817..3dd9203 100644 (file)
@@ -1728,6 +1728,287 @@ handle_command_loop (gint        *argc,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static gchar   *opt_smart_simulate_file = NULL;
+static gchar   *opt_smart_simulate_object_path = NULL;
+static gchar   *opt_smart_simulate_device = NULL;
+static gboolean opt_smart_simulate_no_user_interaction = FALSE;
+
+static const GOptionEntry command_smart_simulate_entries[] =
+{
+  {
+    "file",
+    'f',
+    0,
+    G_OPTION_ARG_FILENAME,
+    &opt_smart_simulate_file,
+    "File with libatasmart blob",
+    NULL
+  },
+  {
+    "object-path",
+    'p',
+    0,
+    G_OPTION_ARG_STRING,
+    &opt_smart_simulate_object_path,
+    "Object path for ATA device",
+    NULL
+  },
+  {
+    "block-device",
+    'b',
+    0,
+    G_OPTION_ARG_STRING,
+    &opt_smart_simulate_device,
+    "Device file for ATA device",
+    NULL
+  },
+  {
+    "no-user-interaction",
+    0, /* no short option */
+    0,
+    G_OPTION_ARG_NONE,
+    &opt_smart_simulate_no_user_interaction,
+    "Do not authenticate the user if needed",
+    NULL
+  },
+  {
+    NULL
+  }
+};
+
+static gint
+handle_command_smart_simulate (gint        *argc,
+                               gchar      **argv[],
+                               gboolean     request_completion,
+                               const gchar *completion_cur,
+                               const gchar *completion_prev)
+{
+  gint ret;
+  GOptionContext *o;
+  gchar *s;
+  gboolean complete_objects;
+  gboolean complete_devices;
+  gboolean complete_files;
+  GList *l;
+  GList *objects;
+  UDisksObject *object;
+  UDisksDriveAta *ata;
+  guint n;
+  GVariant *options;
+  GVariantBuilder builder;
+  GError *error;
+
+  ret = 1;
+  opt_smart_simulate_object_path = NULL;
+  opt_smart_simulate_device = NULL;
+  object = NULL;
+  options = NULL;
+
+  modify_argv0_for_command (argc, argv, "smart-simulate");
+
+  o = g_option_context_new (NULL);
+  if (request_completion)
+    g_option_context_set_ignore_unknown_options (o, TRUE);
+  g_option_context_set_help_enabled (o, FALSE);
+  g_option_context_set_summary (o, "Set SMART data for drive.");
+  g_option_context_add_main_entries (o,
+                                     command_smart_simulate_entries,
+                                     NULL /* GETTEXT_PACKAGE*/);
+
+  complete_objects = FALSE;
+  if (request_completion && (g_strcmp0 (completion_prev, "--object-path") == 0 || g_strcmp0 (completion_prev, "-p") == 0))
+    {
+      complete_objects = TRUE;
+      remove_arg ((*argc) - 1, argc, argv);
+    }
+
+  complete_devices = FALSE;
+  if (request_completion && (g_strcmp0 (completion_prev, "--block-device") == 0 || g_strcmp0 (completion_prev, "-b") == 0))
+    {
+      complete_devices = TRUE;
+      remove_arg ((*argc) - 1, argc, argv);
+    }
+
+  complete_files = FALSE;
+  if (request_completion && (g_strcmp0 (completion_prev, "--file") == 0 || g_strcmp0 (completion_prev, "-f") == 0))
+    {
+      complete_files = TRUE;
+      remove_arg ((*argc) - 1, argc, argv);
+    }
+
+  if (!g_option_context_parse (o, argc, argv, NULL))
+    {
+      if (!request_completion)
+        {
+          s = g_option_context_get_help (o, FALSE, NULL);
+          g_printerr ("%s", s);
+          g_free (s);
+          goto out;
+        }
+    }
+
+  if (request_completion)
+    {
+      if (opt_smart_simulate_file == NULL && !complete_files && !complete_objects && !complete_devices)
+        {
+          g_print ("--file \n");
+        }
+
+      if (complete_files)
+        {
+          g_print ("@FILES@");
+          goto out;
+        }
+
+      if ((opt_smart_simulate_object_path == NULL && !complete_objects) &&
+          (opt_smart_simulate_device == NULL && !complete_devices))
+        {
+          g_print ("--object-path \n"
+                   "--block-device \n");
+        }
+
+      if (complete_objects)
+        {
+          const gchar *object_path;
+          objects = g_dbus_object_manager_get_objects (udisks_client_get_object_manager (client));
+          for (l = objects; l != NULL; l = l->next)
+            {
+              object = UDISKS_OBJECT (l->data);
+              ata = udisks_object_peek_drive_ata (object);
+              if (ata != NULL)
+                {
+                  object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
+                  g_assert (g_str_has_prefix (object_path, "/org/freedesktop/UDisks2/"));
+                  g_print ("%s \n", object_path + sizeof ("/org/freedesktop/UDisks2/") - 1);
+                }
+            }
+          g_list_foreach (objects, (GFunc) g_object_unref, NULL);
+          g_list_free (objects);
+        }
+
+      if (complete_devices)
+        {
+          objects = g_dbus_object_manager_get_objects (udisks_client_get_object_manager (client));
+          for (l = objects; l != NULL; l = l->next)
+            {
+              object = UDISKS_OBJECT (l->data);
+              ata = udisks_object_peek_drive_ata (object);
+              if (ata != NULL)
+                {
+                  const gchar * const *symlinks;
+                  UDisksBlock *block;
+                  block = udisks_client_get_block_for_drive (client, udisks_object_peek_drive (object), TRUE);
+                  g_print ("%s \n", udisks_block_get_device (block));
+                  symlinks = udisks_block_get_symlinks (block);
+                  for (n = 0; symlinks != NULL && symlinks[n] != NULL; n++)
+                    g_print ("%s \n", symlinks[n]);
+                }
+            }
+          g_list_foreach (objects, (GFunc) g_object_unref, NULL);
+          g_list_free (objects);
+        }
+      goto out;
+    }
+
+  if (opt_smart_simulate_file == NULL)
+    {
+      s = g_option_context_get_help (o, FALSE, NULL);
+      g_printerr ("%s", s);
+      g_free (s);
+      goto out;
+    }
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+  if (opt_smart_simulate_no_user_interaction)
+    {
+      g_variant_builder_add (&builder,
+                             "{sv}",
+                             "auth.no_user_interaction", g_variant_new_boolean (TRUE));
+    }
+  g_variant_builder_add (&builder,
+                         "{sv}",
+                         "atasmart_blob", g_variant_new_string (opt_smart_simulate_file));
+  options = g_variant_builder_end (&builder);
+  g_variant_ref_sink (options);
+
+  if (opt_smart_simulate_object_path != NULL)
+    {
+      object = lookup_object_by_path (opt_smart_simulate_object_path);
+      if (object == NULL)
+        {
+          g_printerr ("Error looking up object with path %s\n", opt_smart_simulate_object_path);
+          goto out;
+        }
+    }
+  else if (opt_smart_simulate_device != NULL)
+    {
+      UDisksObject *block_object;
+      UDisksDrive *drive;
+      block_object = lookup_object_by_device (opt_smart_simulate_device);
+      if (block_object == NULL)
+        {
+          g_printerr ("Error looking up object for device %s\n", opt_smart_simulate_device);
+          goto out;
+        }
+      drive = udisks_client_get_drive_for_block (client, udisks_object_peek_block (block_object));
+      object = (UDisksObject *) g_dbus_interface_dup_object (G_DBUS_INTERFACE (drive));
+      g_object_unref (block_object);
+    }
+  else
+    {
+      s = g_option_context_get_help (o, FALSE, NULL);
+      g_printerr ("%s", s);
+      g_free (s);
+      goto out;
+    }
+
+  if (udisks_object_peek_drive_ata (object) == NULL)
+    {
+      g_printerr ("Device %s is not an ATA device\n",
+                  udisks_block_get_device (udisks_object_peek_block (object)));
+      g_object_unref (object);
+      goto out;
+    }
+
+ try_again:
+  error = NULL;
+  if (!udisks_drive_ata_call_smart_update_sync (udisks_object_peek_drive_ata (object),
+                                                options,
+                                                NULL,                       /* GCancellable */
+                                                &error))
+    {
+      if (error->domain == UDISKS_ERROR &&
+          error->code == UDISKS_ERROR_NOT_AUTHORIZED_CAN_OBTAIN &&
+          setup_local_polkit_agent ())
+        {
+          g_error_free (error);
+          goto try_again;
+        }
+      g_dbus_error_strip_remote_error (error);
+      g_printerr ("Error updating SMART data: %s (%s, %d)\n",
+                  error->message, g_quark_to_string (error->domain), error->code);
+      g_clear_error (&error);
+      g_object_unref (object);
+      goto out;
+    }
+
+  g_object_unref (object);
+
+
+  ret = 0;
+
+ out:
+  if (options != NULL)
+    g_variant_unref (options);
+  g_option_context_free (o);
+  g_free (opt_smart_simulate_file);
+  g_free (opt_smart_simulate_object_path);
+  g_free (opt_smart_simulate_device);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 static gchar *opt_info_object = NULL;
 static gchar *opt_info_device = NULL;
 static gchar *opt_info_drive = NULL;
@@ -2563,17 +2844,18 @@ usage (gint *argc, gchar **argv[], gboolean use_stdout)
   g_option_context_parse (o, argc, argv, NULL);
   program_name = g_path_get_basename ((*argv)[0]);
   s = g_strdup_printf ("Commands:\n"
-                       "  help         Shows this information\n"
-                       "  info         Shows information about an object\n"
-                       "  dump         Shows information about all objects\n"
-                       "  status       Shows high-level status\n"
-                       "  monitor      Monitor changes to objects\n"
-                       "  mount        Mount a filesystem\n"
-                       "  unmount      Unmount a filesystem\n"
-                       "  unlock       Unlock an encrypted device\n"
-                       "  lock         Lock an encrypted device\n"
-                       "  loop-setup   Set-up a loop device\n"
-                       "  loop-delete  Delete a loop device\n"
+                       "  help            Shows this information\n"
+                       "  info            Shows information about an object\n"
+                       "  dump            Shows information about all objects\n"
+                       "  status          Shows high-level status\n"
+                       "  monitor         Monitor changes to objects\n"
+                       "  mount           Mount a filesystem\n"
+                       "  unmount         Unmount a filesystem\n"
+                       "  unlock          Unlock an encrypted device\n"
+                       "  lock            Lock an encrypted device\n"
+                       "  loop-setup      Set-up a loop device\n"
+                       "  loop-delete     Delete a loop device\n"
+                       "  smart-simulate  Set SMART data for a drive\n"
                        "\n"
                        "Use \"%s COMMAND --help\" to get help on each command.\n",
                        program_name);
@@ -2762,6 +3044,15 @@ main (int argc,
                                  g_strcmp0 (command, "loop-setup") == 0);
       goto out;
     }
+  else if (g_strcmp0 (command, "smart-simulate") == 0)
+    {
+      ret = handle_command_smart_simulate (&argc,
+                                           &argv,
+                                           request_completion,
+                                           completion_cur,
+                                           completion_prev);
+      goto out;
+    }
   else if (g_strcmp0 (command, "dump") == 0)
     {
       ret = handle_command_dump (&argc,
@@ -2865,6 +3156,7 @@ main (int argc,
                    "unlock \n"
                    "loop-setup \n"
                    "loop-delete \n"
+                   "smart-simulate \n"
                    );
           ret = 0;
           goto out;