From c098de8ee990c7f8fb7e34a6afb5915d2930c5ef Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Sat, 12 May 2012 12:07:33 -0400 Subject: [PATCH] udisksctl: add 'smart-simulate' verb 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 --- data/org.freedesktop.UDisks2.xml | 12 +- data/org.freedesktop.udisks2.policy.in | 11 ++ doc/man/udisksctl.xml | 25 +++ src/udiskslinuxdriveata.c | 59 +++---- tools/udisksctl.c | 314 +++++++++++++++++++++++++++++++-- 5 files changed, 369 insertions(+), 52 deletions(-) diff --git a/data/org.freedesktop.UDisks2.xml b/data/org.freedesktop.UDisks2.xml index 784a9e9..a75bc81 100644 --- a/data/org.freedesktop.UDisks2.xml +++ b/data/org.freedesktop.UDisks2.xml @@ -384,16 +384,8 @@ 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: - -# export ATA_SMART_BLOB=/usr/share/doc/libatasmart-devel-0.17/Maxtor_96147H8--BAC51KJ0--2 ; \ - gdbus call --system --dest org.freedesktop.UDisks2 \ - --object-path /org/freedesktop/UDisks2/drives/WDC_WD1002FAEX_00Y9A0_WD_WCAW30039835 \ - --method org.freedesktop.UDisks2.Drive.Ata.SmartUpdate \ - "{'atasmart_blob': <'$ATA_SMART_BLOB'>}" - + kinds of SMART data. This option may be removed in the future + with it being considered an ABI break. --> diff --git a/data/org.freedesktop.udisks2.policy.in b/data/org.freedesktop.udisks2.policy.in index bcbeac3..2c60c3d 100644 --- a/data/org.freedesktop.udisks2.policy.in +++ b/data/org.freedesktop.udisks2.policy.in @@ -341,6 +341,17 @@ + + + <_description>Set SMART data from blob + <_message>Authentication is required to set SMART data from blob + + auth_admin + auth_admin + auth_admin_keep + + + <_description>Run SMART self-test diff --git a/doc/man/udisksctl.xml b/doc/man/udisksctl.xml index 3892692..4b8145f 100644 --- a/doc/man/udisksctl.xml +++ b/doc/man/udisksctl.xml @@ -73,6 +73,17 @@ udisksctl + smart-simulate + --file PATH + + --object-path OBJECT + --block-device DEVICE + + --no-user-interaction + + + + udisksctl monitor @@ -158,6 +169,20 @@ + + + + Sets SMART data from the libatasmart blob given by + FILE - see + /usr/share/doc/libatasmart-devel-VERSION/ + for blobs shipped with libatasmart. This is a debugging + feature used to check that applications act correctly when + a disk is failing. + + + + + Monitors the daemon for events. diff --git a/src/udiskslinuxdriveata.c b/src/udiskslinuxdriveata.c index f4d41ac..2513445 100644 --- a/src/udiskslinuxdriveata.c +++ b/src/udiskslinuxdriveata.c @@ -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, diff --git a/tools/udisksctl.c b/tools/udisksctl.c index 7a02817..3dd9203 100644 --- a/tools/udisksctl.c +++ b/tools/udisksctl.c @@ -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; -- 2.7.4