diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | configure.ac | 3 | ||||
-rw-r--r-- | data/stc.conf.in (renamed from stc/stc.conf.in) | 0 | ||||
-rw-r--r-- | doc/stc-docs.xml | 1 | ||||
-rw-r--r-- | doc/stc-sections.txt | 20 | ||||
-rw-r--r-- | doc/stc.types | 1 | ||||
-rw-r--r-- | stc/Makefile.am | 16 | ||||
-rw-r--r-- | stc/stc.c | 440 | ||||
-rw-r--r-- | stc/stc.h | 1 | ||||
-rw-r--r-- | stc/stcenums.h | 21 | ||||
-rw-r--r-- | stc/stcitem.c | 1812 | ||||
-rw-r--r-- | stc/stcitem.h | 33 | ||||
-rw-r--r-- | stc/stcmarshal.list | 3 | ||||
-rw-r--r-- | stc/stcmonitor.c | 12 | ||||
-rw-r--r-- | stc/stcoperation.c | 228 | ||||
-rw-r--r-- | stc/stcoperation.h | 48 | ||||
-rw-r--r-- | stc/stcprivate.h | 2 | ||||
-rw-r--r-- | stc/stctypes.h | 3 |
18 files changed, 2443 insertions, 203 deletions
diff --git a/Makefile.am b/Makefile.am index d203f89..8e325e0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,2 +1,2 @@ -SUBDIRS = stc doc +SUBDIRS = stc data doc diff --git a/configure.ac b/configure.ac index 068f15d..a692cfd 100644 --- a/configure.ac +++ b/configure.ac @@ -98,7 +98,8 @@ AC_OUTPUT([ Makefile stc/Makefile stc/stc-1.pc -stc/stc.conf +data/Makefile +data/stc.conf doc/Makefile ]) diff --git a/stc/stc.conf.in b/data/stc.conf.in index 3292a60..3292a60 100644 --- a/stc/stc.conf.in +++ b/data/stc.conf.in diff --git a/doc/stc-docs.xml b/doc/stc-docs.xml index 3c26e36..2a26fa0 100644 --- a/doc/stc-docs.xml +++ b/doc/stc-docs.xml @@ -19,6 +19,7 @@ <xi:include href="xml/stcerror.xml"/> <xi:include href="xml/stcitem.xml"/> <xi:include href="xml/stcmonitor.xml"/> + <xi:include href="xml/stcoperation.xml"/> </part> <part> diff --git a/doc/stc-sections.txt b/doc/stc-sections.txt index 10f6c6e..4fecd5b 100644 --- a/doc/stc-sections.txt +++ b/doc/stc-sections.txt @@ -30,6 +30,11 @@ stc_item_get_option stc_item_get_option_keys stc_item_get_dependencies stc_item_get_state +stc_item_get_device +stc_item_get_mount_path +stc_item_get_slave_devices +stc_item_start_sync +stc_item_stop_sync <SUBSECTION Standard> STC_TYPE_ITEM STC_ITEM @@ -43,6 +48,21 @@ STC_TYPE_ITEM_STATE </SECTION> <SECTION> +<FILE>stcoperation</FILE> +<TITLE>StcOperation</TITLE> +StcOperation +stc_operation_new +stc_operation_request_passphrase +stc_operation_may_start_degraded +<SUBSECTION Standard> +STC_TYPE_OPERATION +STC_OPERATION +STC_IS_OPERATION +<SUBSECTION Private> +stc_operation_get_type +</SECTION> + +<SECTION> <FILE>stcerror</FILE> <TITLE>StcError</TITLE> STC_ERROR diff --git a/doc/stc.types b/doc/stc.types index c8825e2..15a8430 100644 --- a/doc/stc.types +++ b/doc/stc.types @@ -1,2 +1,3 @@ stc_item_get_type stc_monitor_get_type +stc_operation_get_type diff --git a/stc/Makefile.am b/stc/Makefile.am index 9ea6aaa..e3d8bbc 100644 --- a/stc/Makefile.am +++ b/stc/Makefile.am @@ -51,6 +51,7 @@ libstc_1include_HEADERS= \ stcerror.h \ stcitem.h \ stcmonitor.h \ + stcoperation.h \ $(NULL) libstc_1_la_SOURCES = \ @@ -59,6 +60,7 @@ libstc_1_la_SOURCES = \ stcerror.h stcerror.c \ stcitem.h stcitem.c \ stcmonitor.h stcmonitor.c \ + stcoperation.h stcoperation.c \ stcprivate.h \ stcmount.h stcmount.c \ stcmountmonitor.h stcmountmonitor.c \ @@ -130,26 +132,12 @@ pkgconfig_DATA = stc-1.pc # ---------------------------------------------------------------------------------------------------- -configdir = $(sysconfdir) -config_DATA = stc.conf - -# ---------------------------------------------------------------------------------------------------- - CLEANFILES = EXTRA_DIST = \ $(profile_SCRIPTS) \ - stc.conf.in \ stc-1.pc.in \ $(NULL) clean-local : rm -f *~ $(BUILT_SOURCES) - -# By default, /etc/stc.conf and /etc/stc.conf.d are world-readable. Individual files -# in /etc/stc.conf.d can be made readable only by uid 0 etc. -# -install-exec-hook: - -chmod 644 $(DESTDIR)$(sysconfdir)/stc.conf - mkdir -p $(DESTDIR)$(sysconfdir)/stc.conf.d - -chmod 755 $(DESTDIR)$(sysconfdir)/stc.conf.d @@ -25,6 +25,8 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <termios.h> +#include <unistd.h> #include "stc.h" @@ -285,6 +287,9 @@ handle_command_list (gint *argc, const gchar *target; const gchar *const *options; const gchar *const *deps; + const gchar *const *slave_devices; + const gchar *device; + const gchar *mount_path; type = stc_item_get_item_type (item); id = stc_item_get_id (item); @@ -387,6 +392,47 @@ handle_command_list (gint *argc, enum_to_str (STC_TYPE_ITEM_STATE, stc_item_get_state (item)), _color_get (_COLOR_RESET)); + slave_devices = stc_item_get_slave_devices (item); + device = stc_item_get_device (item); + mount_path = stc_item_get_mount_path (item); + if (slave_devices != NULL) + { + g_print (" %sSlave Devices:%s ", + _color_get (_COLOR_FG_WHITE), + _color_get (_COLOR_RESET)); + for (n = 0; slave_devices[n] != NULL; n++) + { + if (n != 0) + g_print ("\n "); + g_print ("%s%s%s%s", + _color_get (_COLOR_FG_YELLOW), + _color_get (_COLOR_BOLD_ON), + slave_devices[n], + _color_get (_COLOR_RESET)); + } + g_print ("\n"); + } + if (device != NULL) + { + g_print (" %sDevice:%s %s%s%s%s\n", + _color_get (_COLOR_FG_WHITE), + _color_get (_COLOR_RESET), + _color_get (_COLOR_FG_YELLOW), + _color_get (_COLOR_BOLD_ON), + device, + _color_get (_COLOR_RESET)); + } + if (mount_path != NULL) + { + g_print (" %sMount Path:%s %s%s%s%s\n", + _color_get (_COLOR_FG_WHITE), + _color_get (_COLOR_RESET), + _color_get (_COLOR_FG_YELLOW), + _color_get (_COLOR_BOLD_ON), + mount_path, + _color_get (_COLOR_RESET)); + } + g_print ("\n"); } g_list_foreach (items, (GFunc) g_object_unref, NULL); @@ -403,6 +449,384 @@ handle_command_list (gint *argc, /* ---------------------------------------------------------------------------------------------------- */ +static gchar * +ask_user (const gchar *info, + const gchar *prompt, + gboolean echo_on) +{ + gchar *ret; + const gchar *tty_name; + GString *str; + FILE *tty; + struct termios ts, ots; + + ret = NULL; + tty = NULL; + + if (!_color_stdin_is_tty) + goto out; + + tty_name = ctermid (NULL); + if (tty_name == NULL) + goto out; + + tty = fopen (tty_name, "r+"); + if (tty == NULL) + goto out; + + fprintf (tty, "%s", info); + fprintf (tty, "%s", prompt); + fflush (tty); + + setbuf (tty, NULL); + + /* TODO: We really ought to block SIGINT and STGSTP (and probably + * other signals too) so we can restore the terminal (since we + * turn off echoing). See e.g. Advanced Programming in the + * UNIX Environment 2nd edition (Steves and Rago) section + * 18.10, pg 660 where this is suggested. See also various + * getpass(3) implementations + * + * Anyway, On modern Linux not doing this doesn't seem to be a + * problem - looks like modern shells restore echoing anyway + * on the first input. So maybe it's not even worth solving + * the problem. + */ + + if (!echo_on) + { + tcgetattr (fileno (tty), &ts); + ots = ts; + ts.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + tcsetattr (fileno (tty), TCSAFLUSH, &ts); + } + + str = g_string_new (NULL); + while (TRUE) + { + gint c; + c = getc (tty); + if (c == '\n') + { + /* ok, done */ + break; + } + else if (c == EOF) + { + if (!echo_on) + { + tcsetattr (fileno (tty), TCSAFLUSH, &ots); + } + g_error ("Got unexpected EOF while reading from controlling terminal."); + abort (); + break; + } + else + { + g_string_append_c (str, c); + } + } + if (!echo_on) + { + tcsetattr (fileno (tty), TCSAFLUSH, &ots); + } + putc ('\n', tty); + + ret = g_strdup (str->str); + + memset (str->str, '\0', str->len); + g_string_free (str, TRUE); + + out: + if (tty != NULL) + fclose (tty); + + return ret; +} + +static gboolean +on_may_start_degraded (StcOperation *operation, + StcItem *item, + gpointer user_data) +{ + gboolean ret; + gchar *s; + GString *str; + const gchar *const *slave_devices; + guint num_slave_devices; + guint n; + + ret = FALSE; + + slave_devices = stc_item_get_slave_devices (item); + g_assert (slave_devices != NULL); + num_slave_devices = g_strv_length ((gchar **) slave_devices); + g_assert_cmpint (num_slave_devices, >, 0); + str = g_string_new (NULL); + g_string_append_printf (str, + "Item %s%s%s%s of type %s%s%s%s can only start degraded (", + _color_get (_COLOR_FG_BLUE), + _color_get (_COLOR_BOLD_ON), + stc_item_get_id (item), + _color_get (_COLOR_RESET), + _color_get (_COLOR_FG_CYAN), + _color_get (_COLOR_BOLD_ON), + enum_to_str (STC_TYPE_ITEM_TYPE, stc_item_get_item_type (item)), + _color_get (_COLOR_RESET)); + for (n = 0; n < num_slave_devices; n++) + { + if (n > 0) + g_string_append (str, ", "); + g_string_append_printf (str, + "%s%s%s%s", + _color_get (_COLOR_FG_YELLOW), + _color_get (_COLOR_BOLD_ON), + slave_devices[n], + _color_get (_COLOR_RESET)); + } + g_string_append (str, ")\n"); + s = ask_user (str->str, + "OK to start degraded (yes/no)? ", + TRUE); + + for (n = 0; s[n] != 0; n++) + s[n] = g_ascii_tolower (s[n]); + if (g_strcmp0 (s, "yes") == 0) + ret = TRUE; + g_free (s); + + g_string_free (str, TRUE); + + return ret; +} + +static gchar * +on_request_passphrase (StcOperation *operation, + StcItem *item, + gpointer user_data) +{ + gchar *ret; + GString *str; + const gchar *const *slave_devices; + guint num_slave_devices; + guint n; + + ret = NULL; + + slave_devices = stc_item_get_slave_devices (item); + g_assert (slave_devices != NULL); + num_slave_devices = g_strv_length ((gchar **) slave_devices); + g_assert_cmpint (num_slave_devices, >, 0); + str = g_string_new (NULL); + g_string_append_printf (str, + "Passphrase needed for item %s%s%s%s of type %s%s%s%s (", + _color_get (_COLOR_FG_BLUE), + _color_get (_COLOR_BOLD_ON), + stc_item_get_id (item), + _color_get (_COLOR_RESET), + _color_get (_COLOR_FG_CYAN), + _color_get (_COLOR_BOLD_ON), + enum_to_str (STC_TYPE_ITEM_TYPE, stc_item_get_item_type (item)), + _color_get (_COLOR_RESET)); + for (n = 0; n < num_slave_devices; n++) + { + if (n > 0) + g_string_append (str, ", "); + g_string_append_printf (str, + "%s%s%s%s", + _color_get (_COLOR_FG_YELLOW), + _color_get (_COLOR_BOLD_ON), + slave_devices[n], + _color_get (_COLOR_RESET)); + } + g_string_append (str, ")\n"); + ret = ask_user (str->str, + "Passphrase: ", + FALSE); + g_string_free (str, TRUE); + return ret; +} + + + +static gchar *opt_start_stop_id = NULL; + +static const GOptionEntry command_start_stop_entries[] = +{ + { "id", 'i', 0, G_OPTION_ARG_STRING, &opt_start_stop_id, "Configuration item", NULL}, + { NULL } +}; + +static gint +handle_command_start_stop (gint *argc, + gchar **argv[], + gboolean request_completion, + const gchar *completion_cur, + const gchar *completion_prev, + gboolean is_start) +{ + gint ret; + GOptionContext *o; + gchar *s; + StcMonitor *monitor; + StcItem *item; + GList *items; + GList *l; + gboolean complete_ids; + GError *error; + StcOperation *operation; + + ret = 1; + monitor = NULL; + item = NULL; + opt_start_stop_id = NULL; + operation = NULL; + + modify_argv0_for_command (argc, argv, is_start ? "start" : "stop"); + + 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, is_start ? "Start configuration item." : "Stop configuration item"); + g_option_context_add_main_entries (o, command_start_stop_entries, NULL /* GETTEXT_PACKAGE*/); + + complete_ids = FALSE; + if (request_completion && (g_strcmp0 (completion_prev, "--id") == 0 || g_strcmp0 (completion_prev, "-i") == 0)) + { + complete_ids = 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 && + (opt_start_stop_id == NULL && !complete_ids)) + { + g_print ("--id \n"); + } + + monitor = stc_monitor_new (error_handler, NULL); + + if (complete_ids) + { + items = stc_monitor_get_items (monitor); + for (l = items; l != NULL; l = l->next) + { + StcItem *item = STC_ITEM (l->data); + StcItemState state; + state = stc_item_get_state (item); + if (is_start) + { + if (state == STC_ITEM_STATE_CAN_START_DEPENDENCIES || + state == STC_ITEM_STATE_CAN_START_DEGRADED || + state == STC_ITEM_STATE_CAN_START) + { + g_print ("%s \n", stc_item_get_id (item)); + } + } + else + { + if (state == STC_ITEM_STATE_STARTED) + { + g_print ("%s \n", stc_item_get_id (item)); + } + } + } + g_list_foreach (items, (GFunc) g_object_unref, NULL); + g_list_free (items); + } + + /* done with completion */ + if (request_completion) + goto out; + + if (opt_start_stop_id != NULL) + { + item = stc_monitor_get_item_by_id (monitor, opt_start_stop_id); + if (item == NULL) + { + g_printerr ("Error looking up item with id %s\n", opt_start_stop_id); + goto out; + } + } + else + { + s = g_option_context_get_help (o, FALSE, NULL); + g_printerr ("%s", s); + g_free (s); + goto out; + } + + operation = stc_operation_new (); + g_signal_connect (operation, + "may-start-degraded", + G_CALLBACK (on_may_start_degraded), + NULL); + g_signal_connect (operation, + "request-passphrase", + G_CALLBACK (on_request_passphrase), + NULL); + + if (is_start) + { + error = NULL; + if (!stc_item_start_sync (item, + operation, + NULL, /* GCancellable* */ + &error)) + { + g_printerr ("Error starting configuration item with id %s: %s (%s, %d)\n", + stc_item_get_id (item), + error->message, + g_quark_to_string (error->domain), + error->code); + g_error_free (error); + goto out; + } + } + else + { + error = NULL; + if (!stc_item_stop_sync (item, + operation, + NULL, /* GCancellable* */ + &error)) + { + g_printerr ("Error stopping configuration item with id %s: %s (%s, %d)\n", + stc_item_get_id (item), + error->message, + g_quark_to_string (error->domain), + error->code); + g_error_free (error); + goto out; + } + } + + ret = 0; + + out: + if (operation != NULL) + g_object_unref (operation); + if (item != NULL) + g_object_unref (item); + if (monitor != NULL) + g_object_unref (monitor); + g_option_context_free (o); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + static void usage (gint *argc, gchar **argv[], gboolean use_stdout) { @@ -419,8 +843,8 @@ usage (gint *argc, gchar **argv[], gboolean use_stdout) " help Shows this information\n" //" info Shows information about an object\n" " list Lists all configuration entries\n" - //" apply Mount a device\n" - //" unapply Unmount a device\n" + " start Start (e.g. mount/assemble) a configuration item\n" + " stop Stop (e.g. unmount/disassemble) a configuration item\n" "\n" "Use \"%s COMMAND --help\" to get help on each command.\n", program_name); @@ -576,18 +1000,16 @@ main (int argc, char *argv[]) completion_prev); goto out; } -#if 0 - else if (g_strcmp0 (command, "apply") == 0 || g_strcmp0 (command, "unapply") == 0) + else if (g_strcmp0 (command, "start") == 0 || g_strcmp0 (command, "stop") == 0) { - ret = handle_command_apply_unapply (&argc, + ret = handle_command_start_stop (&argc, &argv, request_completion, completion_cur, completion_prev, - g_strcmp0 (command, "apply") == 0); + g_strcmp0 (command, "start") == 0); goto out; } -#endif else if (g_strcmp0 (command, "complete") == 0 && argc == 4 && !request_completion) { const gchar *completion_line; @@ -656,8 +1078,8 @@ main (int argc, char *argv[]) g_print ("help \n" //"info \n" "list \n" - //"apply \n" - //"unapply \n" + "start \n" + "stop \n" ); ret = 0; goto out; @@ -34,6 +34,7 @@ #include <stc/stcerror.h> #include <stc/stcitem.h> #include <stc/stcmonitor.h> +#include <stc/stcoperation.h> #undef __STC_INSIDE_STC_H__ #endif /* __STC_H__ */ diff --git a/stc/stcenums.h b/stc/stcenums.h index 28b5ad6..394378a 100644 --- a/stc/stcenums.h +++ b/stc/stcenums.h @@ -69,20 +69,21 @@ typedef enum /** * StcItemState: - * @STC_ITEM_STATE_NOT_APPLIED: Configuration item is not applied. - * @STC_ITEM_STATE_CAN_APPLY_DEGRADED: Like %STC_ITEM_STATE_CAN_APPLY but the device will be started degraded. - * @STC_ITEM_STATE_CAN_APPLY: The configuration item is not applied but it can be applied (e.g. all requisite devices are present and all dependencies are fulfilled). - * @STC_ITEM_STATE_APPLIED: The configuration item is applied. + * @STC_ITEM_STATE_NOT_STARTED: The object or one of its dependencies has not been started and cannot be started at this time. + * @STC_ITEM_STATE_CAN_START_DEPENDENCIES: One or more dependent objects are has not been started (but can be started). + * @STC_ITEM_STATE_CAN_START_DEGRADED: Like %STC_ITEM_STATE_CAN_START but the object will be started degraded. + * @STC_ITEM_STATE_CAN_START: The object is not running but it can be started (e.g. all requisite objects are present and all dependencies are fulfilled). + * @STC_ITEM_STATE_STARTED: The object has been started. * - * Enumeration describing the current state of a configuration - * item + * Enumeration describing the current state of the object described by a #StcItem. */ typedef enum { - STC_ITEM_STATE_NOT_APPLIED, - STC_ITEM_STATE_CAN_APPLY_DEGRADED, - STC_ITEM_STATE_CAN_APPLY, - STC_ITEM_STATE_APPLIED, + STC_ITEM_STATE_NOT_STARTED, + STC_ITEM_STATE_CAN_START_DEPENDENCIES, + STC_ITEM_STATE_CAN_START_DEGRADED, + STC_ITEM_STATE_CAN_START, + STC_ITEM_STATE_STARTED, } StcItemState; G_END_DECLS diff --git a/stc/stcitem.c b/stc/stcitem.c index c4fd4ae..2e9cd89 100644 --- a/stc/stcitem.c +++ b/stc/stcitem.c @@ -22,10 +22,16 @@ #include "config.h" +#include <sys/types.h> +#include <sys/wait.h> +#include <string.h> + #include "stcitem.h" #include "stcmonitor.h" #include "stcmount.h" #include "stcmountmonitor.h" +#include "stcerror.h" +#include "stcoperation.h" #include "stcprivate.h" /** @@ -58,11 +64,15 @@ struct _StcItem GHashTable *options; gchar **depends; - StcItemState state; - /* memoized */ gchar **option_keys; + /* current state */ + StcItemState state; + gchar **slave_devices; + gchar *device; + gchar *mount_path; + /* private */ gchar *sort_key; gchar *path; @@ -106,6 +116,10 @@ stc_item_finalize (GObject *object) g_free (item->sort_key); g_free (item->path); + g_strfreev (item->slave_devices); + g_free (item->device); + g_free (item->mount_path); + if (G_OBJECT_CLASS (stc_item_parent_class)->finalize) G_OBJECT_CLASS (stc_item_parent_class)->finalize (object); } @@ -212,8 +226,6 @@ _stc_item_new (StcMonitor *monitor, item->path = g_strdup (path); item->line_no = line_no; - _stc_item_update_state (item); - return item; } @@ -314,11 +326,6 @@ _stc_item_update (StcItem *item, StcItem *other) changed = TRUE; } - if (changed) - { - _stc_item_update_state (item); - } - return changed; } @@ -353,36 +360,38 @@ _g_udev_client_query_by_property (GUdevClient *client, return ret; } -static StcItemState -_stc_item_update_state_filesystem (StcItem *item, - GUdevClient *gudev_client, - _StcMountMonitor *mount_monitor) +/* ---------------------------------------------------------------------------------------------------- */ + +static GUdevDevice * +_stc_item_filesystem_get_device (StcItem *item, + gchar **out_mount_path) { - StcItemState ret; + GUdevDevice *ret; GUdevDevice *device; - GUdevDevice *device_with_fs; GList *devices; GList *l; + gchar *mount_path; - ret = STC_ITEM_STATE_NOT_APPLIED; + g_return_val_if_fail (STC_IS_ITEM (item), NULL); + g_return_val_if_fail (item->type == STC_ITEM_TYPE_FILESYSTEM, NULL); - device_with_fs = NULL; + ret = NULL; + mount_path = NULL; if (g_str_has_prefix (item->target, "Device=")) { - device = g_udev_client_query_by_device_file (gudev_client, + device = g_udev_client_query_by_device_file (_stc_monitor_get_gudev_client (item->monitor), item->target + sizeof "Device=" - 1); if (device != NULL) { if (g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "filesystem") == 0) - ret = STC_ITEM_STATE_CAN_APPLY; - device_with_fs = g_object_ref (device); + ret = g_object_ref (device); g_object_unref (device); } } else if (g_str_has_prefix (item->target, "UUID=")) { - devices = _g_udev_client_query_by_property (gudev_client, + devices = _g_udev_client_query_by_property (_stc_monitor_get_gudev_client (item->monitor), "block", "ID_FS_UUID", item->target + sizeof "UUID=" - 1); @@ -391,8 +400,7 @@ _stc_item_update_state_filesystem (StcItem *item, device = G_UDEV_DEVICE (l->data); if (g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "filesystem") == 0) { - device_with_fs = g_object_ref (device); - ret = STC_ITEM_STATE_CAN_APPLY; + ret = g_object_ref (device); break; } } @@ -401,7 +409,7 @@ _stc_item_update_state_filesystem (StcItem *item, } else if (g_str_has_prefix (item->target, "Label=")) { - devices = _g_udev_client_query_by_property (gudev_client, + devices = _g_udev_client_query_by_property (_stc_monitor_get_gudev_client (item->monitor), "block", "ID_FS_LABEL", item->target + sizeof "Label=" - 1); @@ -410,8 +418,7 @@ _stc_item_update_state_filesystem (StcItem *item, device = G_UDEV_DEVICE (l->data); if (g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "filesystem") == 0) { - device_with_fs = g_object_ref (device); - ret = STC_ITEM_STATE_CAN_APPLY; + ret = g_object_ref (device); break; } } @@ -423,30 +430,179 @@ _stc_item_update_state_filesystem (StcItem *item, g_warning ("Unsupported target type %s for Filesystem", item->target); } - /* figure out if IS_APPLIED */ - if (device_with_fs != NULL) + if (ret != NULL) { GList *mounts; - mounts = _stc_mount_monitor_get_mounts_for_dev (mount_monitor, - g_udev_device_get_device_number (device_with_fs)); + GList *l; + + mounts = _stc_mount_monitor_get_mounts_for_dev (_stc_monitor_get_mount_monitor (item->monitor), + g_udev_device_get_device_number (device)); if (mounts != NULL) { - const gchar *desired_mount_path; - desired_mount_path = g_hash_table_lookup (item->options, "Filesystem:mount_path"); - g_assert (desired_mount_path != NULL); for (l = mounts; l != NULL; l = l->next) { _StcMount *mount = _STC_MOUNT (l->data); - if (g_strcmp0 (desired_mount_path, _stc_mount_get_mount_path (mount)) == 0) - { - ret = STC_ITEM_STATE_APPLIED; - break; - } + /* we only return the first mount path right now */ + mount_path = g_strdup (_stc_mount_get_mount_path (mount)); + break; } } g_list_foreach (mounts, (GFunc) g_object_unref, NULL); g_list_free (mounts); - g_object_unref (device_with_fs); + } + + if (out_mount_path != NULL) + *out_mount_path = mount_path; + else + g_free (mount_path); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GUdevDevice * +_stc_item_mdraid_get_devices (StcItem *item, + GList **out_component_devices, + gchar **out_md_level, + gint *out_md_devices) +{ + GUdevDevice *ret; + gint md_devices; + gchar *md_level; + GList *ret_component_devices; + GList *devices; + GList *l; + GUdevDevice *device; + + md_devices = 0; + md_level = NULL; + ret_component_devices = NULL; + ret = NULL; + + if (g_str_has_prefix (item->target, "Name=")) + { + devices = _g_udev_client_query_by_property (_stc_monitor_get_gudev_client (item->monitor), + "block", + "MD_NAME", + item->target + sizeof "Name=" - 1); + for (l = devices; l != NULL; l = l->next) + { + device = G_UDEV_DEVICE (l->data); + /* only component devices has MD_EVENTS */ + if (g_udev_device_has_property (device, "MD_EVENTS") && + g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "raid") == 0 && + g_strcmp0 (g_udev_device_get_property (device, "ID_FS_TYPE"), "linux_raid_member") == 0) + { + if (md_devices == 0) + md_devices = g_udev_device_get_property_as_int (device, "MD_DEVICES"); + if (md_level == NULL) + md_level = g_strdup (g_udev_device_get_property (device, "MD_LEVEL")); + ret_component_devices = g_list_prepend (ret_component_devices, g_object_ref (device)); + } + else if (!g_udev_device_has_property (device, "MD_EVENTS")) + { + if (ret == NULL) + ret = g_object_ref (device); + } + } + g_list_foreach (devices, (GFunc) g_object_unref, NULL); + g_list_free (devices); + } + else if (g_str_has_prefix (item->target, "UUID=")) + { + devices = _g_udev_client_query_by_property (_stc_monitor_get_gudev_client (item->monitor), + "block", + "MD_UUID", + item->target + sizeof "UUID=" - 1); + for (l = devices; l != NULL; l = l->next) + { + device = G_UDEV_DEVICE (l->data); + /* only component devices has MD_EVENTS */ + if (g_udev_device_has_property (device, "MD_EVENTS") && + g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "raid") == 0 && + g_strcmp0 (g_udev_device_get_property (device, "ID_FS_TYPE"), "linux_raid_member") == 0) + { + if (md_devices == 0) + md_devices = g_udev_device_get_property_as_int (device, "MD_DEVICES"); + if (md_level == NULL) + md_level = g_strdup (g_udev_device_get_property (device, "MD_LEVEL")); + ret_component_devices = g_list_prepend (ret_component_devices, g_object_ref (device)); + } + else if (!g_udev_device_has_property (device, "MD_EVENTS")) + { + if (ret == NULL) + ret = g_object_ref (device); + } + } + g_list_foreach (devices, (GFunc) g_object_unref, NULL); + g_list_free (devices); + } + else + { + g_warning ("Unsupported target type %s for LUKS", item->target); + } + + ret_component_devices = g_list_reverse (ret_component_devices); + if (out_component_devices != NULL) + { + *out_component_devices = ret_component_devices; + } + else + { + g_list_foreach (ret_component_devices, (GFunc) g_object_unref, NULL); + g_list_free (ret_component_devices); + } + + if (out_md_level != NULL) + *out_md_level = md_level; + else + g_free (md_level); + + if (out_md_devices != NULL) + *out_md_devices = md_devices; + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static StcItemState +_stc_item_update_state_filesystem (StcItem *item) +{ + StcItemState ret; + GUdevDevice *device; + gchar *mount_path; + + ret = STC_ITEM_STATE_NOT_STARTED; + + g_free (item->device); + item->device = NULL; + g_free (item->mount_path); + item->mount_path = NULL; + + device = _stc_item_filesystem_get_device (item, &mount_path); + if (device != NULL) + { + + ret = STC_ITEM_STATE_CAN_START; + + item->device = g_strdup (g_udev_device_get_device_file (device)); + + if (mount_path != NULL) + { + const gchar *desired_mount_path; + desired_mount_path = g_hash_table_lookup (item->options, "Filesystem:mount_path"); + g_assert (desired_mount_path != NULL); + if (g_strcmp0 (desired_mount_path, mount_path) == 0) + { + item->mount_path = g_strdup (mount_path); + ret = STC_ITEM_STATE_STARTED; + } + g_free (mount_path); + } + + g_object_unref (device); } return ret; @@ -473,30 +629,28 @@ _strdup_without_dashes (const gchar *s) return g_string_free (str, FALSE); } -static StcItemState -_stc_item_update_state_luks (StcItem *item, - GUdevClient *gudev_client) +static GUdevDevice * +_stc_item_luks_get_devices (StcItem *item, + GUdevDevice **out_unlocked_device) { - StcItemState ret; - GUdevDevice *device; GUdevDevice *device_with_crypto; + GUdevDevice *unlocked_device; + GUdevDevice *device; GList *devices; GList *l; - ret = STC_ITEM_STATE_NOT_APPLIED; - device_with_crypto = NULL; + unlocked_device = NULL; if (g_str_has_prefix (item->target, "Device=")) { - device = g_udev_client_query_by_device_file (gudev_client, + device = g_udev_client_query_by_device_file (_stc_monitor_get_gudev_client (item->monitor), item->target + sizeof "Device=" - 1); if (device != NULL) { if (g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "crypto") == 0 && g_strcmp0 (g_udev_device_get_property (device, "ID_FS_TYPE"), "crypto_LUKS") == 0) { - ret = STC_ITEM_STATE_CAN_APPLY; device_with_crypto = g_object_ref (device); } g_object_unref (device); @@ -504,7 +658,7 @@ _stc_item_update_state_luks (StcItem *item, } else if (g_str_has_prefix (item->target, "UUID=")) { - devices = _g_udev_client_query_by_property (gudev_client, + devices = _g_udev_client_query_by_property (_stc_monitor_get_gudev_client (item->monitor), "block", "ID_FS_UUID", item->target + sizeof "UUID=" - 1); @@ -514,7 +668,6 @@ _stc_item_update_state_luks (StcItem *item, if (g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "crypto") == 0 && g_strcmp0 (g_udev_device_get_property (device, "ID_FS_TYPE"), "crypto_LUKS") == 0) { - ret = STC_ITEM_STATE_CAN_APPLY; device_with_crypto = g_object_ref (device); break; } @@ -527,17 +680,17 @@ _stc_item_update_state_luks (StcItem *item, g_warning ("Unsupported target type %s for LUKS", item->target); } - /* figure out if IS_APPLIED */ if (device_with_crypto != NULL) { gchar *uuid_prefix; gchar *luks_uuid; + /* if a LUKS device is unlocked, then DM_UUID is set to uuid_prefix plus the name - so * just check the prefix */ luks_uuid = _strdup_without_dashes (g_udev_device_get_property (device_with_crypto, "ID_FS_UUID")); uuid_prefix = g_strdup_printf ("CRYPT-LUKS1-%s", luks_uuid); - devices = g_udev_client_query_by_subsystem (gudev_client, "block"); + devices = g_udev_client_query_by_subsystem (_stc_monitor_get_gudev_client (item->monitor), "block"); for (l = devices; l != NULL; l = l->next) { const gchar *dm_uuid; @@ -545,172 +698,233 @@ _stc_item_update_state_luks (StcItem *item, dm_uuid = g_udev_device_get_property (device, "DM_UUID"); if (dm_uuid != NULL && g_str_has_prefix (dm_uuid, uuid_prefix)) { - ret = STC_ITEM_STATE_APPLIED; + unlocked_device = g_object_ref (device); break; } } g_list_foreach (devices, (GFunc) g_object_unref, NULL); g_list_free (devices); g_free (uuid_prefix); - g_object_unref (device_with_crypto); } - return ret; + if (out_unlocked_device != NULL) + *out_unlocked_device = unlocked_device != NULL ? g_object_ref (unlocked_device) : NULL; + else + if (unlocked_device != NULL) + g_object_unref (unlocked_device); + + return device_with_crypto; } static StcItemState -_stc_item_update_state_md_raid (StcItem *item, - GUdevClient *gudev_client) +_stc_item_update_state_luks (StcItem *item) { StcItemState ret; - GUdevDevice *device; - GList *devices; - GList *l; - gint num_components_seen; - gint md_devices; - gchar *md_level; - gboolean array_seen; + GUdevDevice *device_with_crypto; + GUdevDevice *unlocked_device; - ret = STC_ITEM_STATE_NOT_APPLIED; + ret = STC_ITEM_STATE_NOT_STARTED; - num_components_seen = 0; - array_seen = FALSE; - md_devices = 0; - md_level = NULL; + g_strfreev (item->slave_devices); + item->slave_devices = NULL; + g_free (item->device); + item->device = NULL; - if (g_str_has_prefix (item->target, "Name=")) + device_with_crypto = _stc_item_luks_get_devices (item, &unlocked_device); + + /* figure out if IS_STARTED */ + if (device_with_crypto != NULL) { - devices = _g_udev_client_query_by_property (gudev_client, - "block", - "MD_NAME", - item->target + sizeof "Name=" - 1); - for (l = devices; l != NULL; l = l->next) + item->slave_devices = g_new0 (gchar *, 2); + item->slave_devices[0] = g_strdup (g_udev_device_get_device_file (device_with_crypto)); + + ret = STC_ITEM_STATE_CAN_START; + + if (unlocked_device != NULL) { - device = G_UDEV_DEVICE (l->data); - /* only component devices has MD_EVENTS */ - if (g_udev_device_has_property (device, "MD_EVENTS") && - g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "raid") == 0 && - g_strcmp0 (g_udev_device_get_property (device, "ID_FS_TYPE"), "linux_raid_member") == 0) - { - num_components_seen += 1; - if (md_devices == 0) - md_devices = g_udev_device_get_property_as_int (device, "MD_DEVICES"); - if (md_level == NULL) - md_level = g_strdup (g_udev_device_get_property (device, "MD_LEVEL")); - } - else if (!g_udev_device_has_property (device, "MD_EVENTS")) - { - array_seen = TRUE; - } + item->device = g_strdup (g_udev_device_get_device_file (unlocked_device)); + g_object_unref (unlocked_device); + ret = STC_ITEM_STATE_STARTED; } - g_list_foreach (devices, (GFunc) g_object_unref, NULL); - g_list_free (devices); + g_object_unref (device_with_crypto); } - else if (g_str_has_prefix (item->target, "UUID=")) + + return ret; +} + +static StcItemState +_stc_item_update_state_md_raid (StcItem *item) +{ + StcItemState ret; + GUdevDevice *array_device; + GList *component_devices; + gint num_components; + gint num_devices; + gchar *md_level; + + ret = STC_ITEM_STATE_NOT_STARTED; + + g_strfreev (item->slave_devices); + item->slave_devices = NULL; + g_free (item->device); + item->device = NULL; + + num_components = 0; + num_devices = 0; + md_level = NULL; + + /* TODO: this might not be quite right... + * + * We also need to look at event numbers to determine if the array + * can be started non-degraded - e.g. even if $MD_DEVICES + * components are available, it may start degraded because one or + * more components are out of sync... + * + * Maybe it's not worth bothering about, though, clearly this is + * something the admin will be told about once the array is running + * and something he can easily fix... + */ + + array_device = _stc_item_mdraid_get_devices (item, &component_devices, &md_level, &num_devices); + num_components = g_list_length (component_devices); + if (num_components > 0) { - devices = _g_udev_client_query_by_property (gudev_client, - "block", - "MD_UUID", - item->target + sizeof "UUID=" - 1); - for (l = devices; l != NULL; l = l->next) + GPtrArray *p; + GList *l; + p = g_ptr_array_new (); + for (l = component_devices; l != NULL; l = l->next) { - device = G_UDEV_DEVICE (l->data); - /* only component devices has MD_EVENTS */ - if (g_udev_device_has_property (device, "MD_EVENTS") && - g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "raid") == 0 && - g_strcmp0 (g_udev_device_get_property (device, "ID_FS_TYPE"), "linux_raid_member") == 0) - { - num_components_seen += 1; - if (md_devices == 0) - md_devices = g_udev_device_get_property_as_int (device, "MD_DEVICES"); - if (md_level == NULL) - md_level = g_strdup (g_udev_device_get_property (device, "MD_LEVEL")); - } - else if (!g_udev_device_has_property (device, "MD_EVENTS")) - { - array_seen = TRUE; - } + GUdevDevice *slave_device = G_UDEV_DEVICE (l->data); + g_ptr_array_add (p, g_strdup (g_udev_device_get_device_file (slave_device))); } - g_list_foreach (devices, (GFunc) g_object_unref, NULL); - g_list_free (devices); - } - else - { - g_warning ("Unsupported target type %s for LUKS", item->target); + g_ptr_array_add (p, NULL); + item->slave_devices = (gchar **) g_ptr_array_free (p, FALSE); } if (g_strcmp0 (md_level, "raid0") == 0) { - if (num_components_seen == md_devices) - ret = STC_ITEM_STATE_CAN_APPLY; + if (num_components == num_devices) + ret = STC_ITEM_STATE_CAN_START; } else if (g_strcmp0 (md_level, "raid1") == 0) { - if (num_components_seen >= 1) - ret = STC_ITEM_STATE_CAN_APPLY_DEGRADED; - if (num_components_seen == md_devices) - ret = STC_ITEM_STATE_CAN_APPLY; + if (num_components >= 1) + ret = STC_ITEM_STATE_CAN_START_DEGRADED; + if (num_components == num_devices) + ret = STC_ITEM_STATE_CAN_START; } else if (g_strcmp0 (md_level, "raid5") == 0) { - if (md_devices - num_components_seen <= 1) - ret = STC_ITEM_STATE_CAN_APPLY_DEGRADED; - if (num_components_seen == md_devices) - ret = STC_ITEM_STATE_CAN_APPLY; + if (num_devices - num_components <= 1) + ret = STC_ITEM_STATE_CAN_START_DEGRADED; + if (num_components == num_devices) + ret = STC_ITEM_STATE_CAN_START; } else if (g_strcmp0 (md_level, "raid6") == 0) { - if (md_devices - num_components_seen <= 2) - ret = STC_ITEM_STATE_CAN_APPLY_DEGRADED; - if (num_components_seen == md_devices) - ret = STC_ITEM_STATE_CAN_APPLY; + if (num_devices - num_components <= 2) + ret = STC_ITEM_STATE_CAN_START_DEGRADED; + if (num_components == num_devices) + ret = STC_ITEM_STATE_CAN_START; } else if (md_level != NULL) { g_warning ("Unhandled md_level string `%s'", md_level); } - if (array_seen) - ret = STC_ITEM_STATE_APPLIED; + if (array_device != NULL) + { + ret = STC_ITEM_STATE_STARTED; + item->device = g_strdup (g_udev_device_get_device_file (array_device)); + } + g_list_foreach (component_devices, (GFunc) g_object_unref, NULL); + g_list_free (component_devices); + if (array_device != NULL) + g_object_unref (array_device); g_free (md_level); return ret; } -void +gboolean _stc_item_update_state (StcItem *item) { - GUdevClient *gudev_client; - _StcMountMonitor *mount_monitor; StcItemState state; + guint n; + gboolean changed; - g_return_if_fail (STC_IS_ITEM (item)); + g_return_val_if_fail (STC_IS_ITEM (item), FALSE); - state = STC_ITEM_STATE_NOT_APPLIED; - gudev_client = _stc_monitor_get_gudev_client (item->monitor); - mount_monitor = _stc_monitor_get_mount_monitor (item->monitor); + state = STC_ITEM_STATE_STARTED; - switch (item->type) + /* first check all deps */ + for (n = 0; item->depends != NULL && item->depends[n] != NULL; n++) { - case STC_ITEM_TYPE_FILESYSTEM: - state = _stc_item_update_state_filesystem (item, gudev_client, mount_monitor); - break; + StcItem *depend_item; + StcItemState depend_item_state; - case STC_ITEM_TYPE_LUKS: - state = _stc_item_update_state_luks (item, gudev_client); - break; + depend_item = stc_monitor_get_item_by_id (item->monitor, item->depends[n]); + if (depend_item == NULL) + { + /* broken dep - means this item is NOT_STARTED */ + state = STC_ITEM_STATE_NOT_STARTED; + goto dep_check_out; + } - case STC_ITEM_TYPE_MD_RAID: - state = _stc_item_update_state_md_raid (item, gudev_client); - break; + depend_item_state = stc_item_get_state (depend_item); + g_object_unref (depend_item); - default: - g_assert_not_reached (); - break; + switch (depend_item_state) + { + case STC_ITEM_STATE_NOT_STARTED: + /* means we are NOT_STARTED too */ + state = STC_ITEM_STATE_NOT_STARTED; + goto dep_check_out; + + case STC_ITEM_STATE_STARTED: + /* all good */ + break; + + default: + /* means we need to wait for the dep... but we need to keep checking in + * case it gets worse + */ + state = STC_ITEM_STATE_CAN_START_DEPENDENCIES; + break; + } } + dep_check_out: + /* we only need to examine our own state if all dependencies are fulfilled */ + if (state == STC_ITEM_STATE_STARTED) + { + switch (item->type) + { + case STC_ITEM_TYPE_FILESYSTEM: + state = _stc_item_update_state_filesystem (item); + break; + + case STC_ITEM_TYPE_LUKS: + state = _stc_item_update_state_luks (item); + break; + + case STC_ITEM_TYPE_MD_RAID: + state = _stc_item_update_state_md_raid (item); + break; + + default: + g_assert_not_reached (); + break; + } + } + + changed = FALSE; + if (item->state != state) + changed = TRUE; item->state = state; + + return changed; } /* ---------------------------------------------------------------------------------------------------- */ @@ -771,3 +985,1287 @@ stc_item_get_option (StcItem *item, g_return_val_if_fail (STC_IS_ITEM (item), NULL); return g_hash_table_lookup (item->options, key); } + +const gchar *const * +stc_item_get_slave_devices (StcItem *item) +{ + g_return_val_if_fail (STC_IS_ITEM (item), NULL); + return (const gchar *const * ) item->slave_devices; +} + +const gchar * +stc_item_get_device (StcItem *item) +{ + g_return_val_if_fail (STC_IS_ITEM (item), NULL); + return item->device; +} + +const gchar * +stc_item_get_mount_path (StcItem *item) +{ + g_return_val_if_fail (STC_IS_ITEM (item), NULL); + return item->mount_path; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GSimpleAsyncResult *simple; + + GMainContext *main_context; + + GCancellable *cancellable; + gulong cancellable_handler_id; + + gchar *input_string; + const gchar *input_string_cursor; + + GPid child_pid; + gint child_stdin_fd; + gint child_stdout_fd; + gint child_stderr_fd; + + GIOChannel *child_stdin_channel; + GIOChannel *child_stdout_channel; + GIOChannel *child_stderr_channel; + + GSource *child_watch_source; + GSource *child_stdin_source; + GSource *child_stdout_source; + GSource *child_stderr_source; + + GString *child_stdout; + GString *child_stderr; +} JobData; + +static void +child_watch_from_release_cb (GPid pid, + gint status, + gpointer user_data) +{ + //g_debug ("in child_watch_from_release_cb"); +} + +static void +job_data_free (JobData *data) +{ + g_object_unref (data->simple); + + /* Nuke the child, if necessary */ + if (data->child_watch_source != NULL) + { + g_source_destroy (data->child_watch_source); + data->child_watch_source = NULL; + } + + if (data->child_pid != 0) + { + GSource *source; + + //g_debug ("ugh, need to kill %d", (gint) data->child_pid); + kill (data->child_pid, SIGTERM); + + /* OK, we need to reap for the child ourselves - we don't want + * to use waitpid() because that might block the calling + * thread (the child might handle SIGTERM and use several + * seconds for cleanup/rollback). + * + * So we use GChildWatch instead. + * + * Note that we might be called from the finalizer so avoid + * taking references to ourselves. We do need to pass the + * GSource so we can nuke it once handled. + */ + source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (source, + (GSourceFunc) child_watch_from_release_cb, + source, + (GDestroyNotify) g_source_destroy); + g_source_attach (source, data->main_context); + g_source_unref (source); + + data->child_pid = 0; + } + + if (data->child_stdout != NULL) + { + g_string_free (data->child_stdout, TRUE); + data->child_stdout = NULL; + } + + if (data->child_stderr != NULL) + { + g_string_free (data->child_stderr, TRUE); + data->child_stderr = NULL; + } + + if (data->child_stdin_channel != NULL) + { + g_io_channel_unref (data->child_stdin_channel); + data->child_stdin_channel = NULL; + } + if (data->child_stdout_channel != NULL) + { + g_io_channel_unref (data->child_stdout_channel); + data->child_stdout_channel = NULL; + } + if (data->child_stderr_channel != NULL) + { + g_io_channel_unref (data->child_stderr_channel); + data->child_stderr_channel = NULL; + } + + if (data->child_stdin_source != NULL) + { + g_source_destroy (data->child_stdin_source); + data->child_stdin_source = NULL; + } + if (data->child_stdout_source != NULL) + { + g_source_destroy (data->child_stdout_source); + data->child_stdout_source = NULL; + } + if (data->child_stderr_source != NULL) + { + g_source_destroy (data->child_stderr_source); + data->child_stderr_source = NULL; + } + + if (data->child_stdin_fd != -1) + { + g_warn_if_fail (close (data->child_stdin_fd) == 0); + data->child_stdin_fd = -1; + } + if (data->child_stdout_fd != -1) + { + g_warn_if_fail (close (data->child_stdout_fd) == 0); + data->child_stdout_fd = -1; + } + if (data->child_stderr_fd != -1) + { + g_warn_if_fail (close (data->child_stderr_fd) == 0); + data->child_stderr_fd = -1; + } + + if (data->cancellable_handler_id > 0) + { + g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); + data->cancellable_handler_id = 0; + } + + if (data->main_context != NULL) + g_main_context_unref (data->main_context); + + /* input string may contain key material - nuke contents */ + if (data->input_string != NULL) + { + memset (data->input_string, '\0', strlen (data->input_string)); + g_free (data->input_string); + } + + g_free (data); +} + +/* -------------------- */ + +/* called in the thread where @cancellable was cancelled */ +static void +on_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + JobData *data = user_data; + GError *error; + + error = NULL; + g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); + g_simple_async_result_set_from_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + job_data_free (data); + g_error_free (error); +} + +static gboolean +read_child_stderr (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + JobData *data = user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stderr, buf, bytes_read); + return TRUE; +} + +static gboolean +read_child_stdout (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + JobData *data = user_data; + gchar buf[1024]; + gsize bytes_read; + + g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); + g_string_append_len (data->child_stdout, buf, bytes_read); + return TRUE; +} + +static gboolean +write_child_stdin (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + JobData *data = user_data; + gsize bytes_written; + + if (data->input_string_cursor == NULL || *data->input_string_cursor == '\0') + { + /* nothing left to write; close our end so the child will get EOF */ + g_io_channel_unref (data->child_stdin_channel); + g_source_destroy (data->child_stdin_source); + g_warn_if_fail (close (data->child_stdin_fd) == 0); + data->child_stdin_channel = NULL; + data->child_stdin_source = NULL; + data->child_stdin_fd = -1; + return FALSE; + } + + g_io_channel_write_chars (channel, + data->input_string_cursor, + strlen (data->input_string_cursor), + &bytes_written, + NULL); + g_io_channel_flush (channel, NULL); + data->input_string_cursor += bytes_written; + + /* keep writing */ + return TRUE; +} + +typedef struct +{ + gint child_exit_status; + gchar *child_stdout; + gchar *child_stderr; +} RunCommandLineResult; + +static void +run_command_line_result_free (RunCommandLineResult *data) +{ + g_free (data->child_stdout); + g_free (data->child_stderr); + g_free (data); +} + +static void +child_watch_cb (GPid pid, + gint status, + gpointer user_data) +{ + JobData *data = user_data; + RunCommandLineResult *result_data; + gchar *buf; + gsize buf_size; + + if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stdout, buf, buf_size); + g_free (buf); + } + if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) + { + g_string_append_len (data->child_stderr, buf, buf_size); + g_free (buf); + } + + result_data = g_new0 (RunCommandLineResult, 1); + result_data->child_exit_status = status; + result_data->child_stdout = g_strdup (data->child_stdout->str); + result_data->child_stderr = g_strdup (data->child_stderr->str); + g_simple_async_result_set_op_res_gpointer (data->simple, + result_data, + (GDestroyNotify) run_command_line_result_free); + g_simple_async_result_complete_in_idle (data->simple); + job_data_free (data); +} + +static void +run_command_line (const gchar *command_line, + const gchar *input_string, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + JobData *data; + GError *error; + gint child_argc; + gchar **child_argv; + + data = g_new0 (JobData, 1); + data->main_context = g_main_context_get_thread_default (); + if (data->main_context != NULL) + g_main_context_ref (data->main_context); + data->input_string = g_strdup (input_string); + data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL; + + data->child_stdout = g_string_new (NULL); + data->child_stderr = g_string_new (NULL); + data->child_stdin_fd = -1; + data->child_stdout_fd = -1; + data->child_stderr_fd = -1; + + data->simple = g_simple_async_result_new (NULL, + callback, + user_data, + run_command_line); + + /* could already be cancelled */ + error = NULL; + if (g_cancellable_set_error_if_cancelled (cancellable, &error)) + { + g_simple_async_result_set_from_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + g_error_free (error); + job_data_free (data); + goto out; + } + + if (data->cancellable != NULL) + data->cancellable_handler_id = g_cancellable_connect (data->cancellable, + G_CALLBACK (on_cancelled), + data, + NULL); + + error = NULL; + if (!g_shell_parse_argv (command_line, + &child_argc, + &child_argv, + &error)) + { + g_prefix_error (&error, + "Error parsing command-line `%s': ", + command_line); + g_simple_async_result_set_from_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + g_error_free (error); + job_data_free (data); + goto out; + } + + error = NULL; + if (!g_spawn_async_with_pipes (NULL, /* working directory */ + child_argv, + NULL, /* envp */ + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, /* child_setup */ + NULL, /* child_setup's user_data */ + &(data->child_pid), + data->input_string != NULL ? &(data->child_stdin_fd) : NULL, + &(data->child_stdout_fd), + &(data->child_stderr_fd), + &error)) + { + g_prefix_error (&error, + "Error spawning command-line `%s': ", + command_line); + g_simple_async_result_set_from_error (data->simple, error); + g_simple_async_result_complete_in_idle (data->simple); + g_object_unref (data->simple); + g_error_free (error); + job_data_free (data); + g_strfreev (child_argv); + goto out; + } + g_strfreev (child_argv); + + data->child_watch_source = g_child_watch_source_new (data->child_pid); + g_source_set_callback (data->child_watch_source, (GSourceFunc) child_watch_cb, data, NULL); + g_source_attach (data->child_watch_source, data->main_context); + g_source_unref (data->child_watch_source); + + if (data->child_stdin_fd != -1) + { + data->input_string_cursor = data->input_string; + + data->child_stdin_channel = g_io_channel_unix_new (data->child_stdin_fd); + g_io_channel_set_flags (data->child_stdin_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stdin_source = g_io_create_watch (data->child_stdin_channel, G_IO_OUT); + g_source_set_callback (data->child_stdin_source, (GSourceFunc) write_child_stdin, data, NULL); + g_source_attach (data->child_stdin_source, data->main_context); + g_source_unref (data->child_stdin_source); + } + + data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); + g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); + g_source_set_callback (data->child_stdout_source, (GSourceFunc) read_child_stdout, data, NULL); + g_source_attach (data->child_stdout_source, data->main_context); + g_source_unref (data->child_stdout_source); + + data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); + g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); + data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); + g_source_set_callback (data->child_stderr_source, (GSourceFunc) read_child_stderr, data, NULL); + g_source_attach (data->child_stderr_source, data->main_context); + g_source_unref (data->child_stderr_source); + + out: + ; +} + +static gboolean +run_command_line_finish (GAsyncResult *res, + gint *out_exit_status, + gchar **out_stdout, + gchar **out_stderr, + GError **error) +{ + GSimpleAsyncResult *simple; + RunCommandLineResult *result_data; + gboolean ret; + + simple = G_SIMPLE_ASYNC_RESULT (res); + + ret = FALSE; + + if (g_simple_async_result_propagate_error (simple, error)) + goto out; + + result_data = g_simple_async_result_get_op_res_gpointer (simple); + if (out_exit_status != NULL) + *out_exit_status = result_data->child_exit_status; + /* steal the strings */ + if (out_stdout != NULL) + { + *out_stdout = result_data->child_stdout; + result_data->child_stdout = NULL; + } + if (out_stderr != NULL) + { + *out_stderr = result_data->child_stderr; + result_data->child_stderr = NULL; + } + + ret = TRUE; + +out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef struct +{ + GAsyncResult *res; + GMainContext *context; + GMainLoop *loop; +} RunCommandLineSyncData; + +static void +run_command_line_sync_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + RunCommandLineSyncData *data = user_data; + data->res = g_object_ref (res); + g_main_loop_quit (data->loop); +} + +static gboolean +run_command_line_sync (const gchar *command_line, + const gchar *input_string, + gint *out_exit_status, + gchar **out_stdout, + gchar **out_stderr, + GCancellable *cancellable, + GError **error) +{ + gboolean ret; + RunCommandLineSyncData *data; + + data = g_new0 (RunCommandLineSyncData, 1); + data->context = g_main_context_new (); + data->loop = g_main_loop_new (data->context, FALSE); + + g_main_context_push_thread_default (data->context); + + run_command_line (command_line, + input_string, + cancellable, + run_command_line_sync_cb, + data); + + g_main_loop_run (data->loop); + + ret = run_command_line_finish (data->res, + out_exit_status, + out_stdout, + out_stderr, + error); + + g_main_context_pop_thread_default (data->context); + + g_main_context_unref (data->context); + g_main_loop_unref (data->loop); + g_object_unref (data->res); + g_free (data); + + return ret; +} + +static gboolean +run_command_line_sync_simple (const gchar *command_line, + const gchar *input_string, + GCancellable *cancellable, + GError **error) +{ + gint child_exit_status; + gchar *child_stdout; + gchar *child_stderr; + gboolean ret; + + ret = FALSE; + child_stdout = NULL; + child_stderr = NULL; + + if (!run_command_line_sync (command_line, + input_string, + &child_exit_status, + &child_stdout, + &child_stderr, + cancellable, + error)) + goto out; + + if (WIFEXITED (child_exit_status)) + { + if (WEXITSTATUS (child_exit_status) != 0) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Command-line `%s' exited with exit code %d.\n" + "stdout: %s\n" + "stderr: %s\n", + command_line, + WEXITSTATUS (child_exit_status), + child_stdout, + child_stderr); + goto out; + } + } + else if (WIFSIGNALED (child_exit_status)) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Command-line `%s' was signaled with signal %d\n" + "stdout: %s\n" + "stderr: %s\n", + command_line, + WTERMSIG (child_exit_status), + child_stdout, + child_stderr); + goto out; + } + + ret = TRUE; + + out: + g_free (child_stdout); + g_free (child_stderr); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +_stc_item_start_filesystem (StcItem *item, + StcOperation *operation, + GCancellable *cancellable, + GError **error) +{ + GUdevDevice *device; + gchar *mount_path; + gboolean ret; + + ret = FALSE; + + device = _stc_item_filesystem_get_device (item, &mount_path); + + if (device != NULL) + { + const gchar *mount_path; + const gchar *device_file; + const gchar *options; + gchar *q_mount_path; + gchar *q_device; + gchar *q_options; + gchar *command_line; + + mount_path = g_hash_table_lookup (item->options, "Filesystem:mount_path"); + device_file = g_udev_device_get_device_file (device); + options = g_hash_table_lookup (item->options, "Filesystem:options"); + g_assert (mount_path != NULL); + g_assert (device_file != NULL); + + q_mount_path = g_shell_quote (mount_path); + q_device = g_shell_quote (device_file); + if (options != NULL) + q_options = g_shell_quote (options); + else + q_options = NULL; + + if (q_options != NULL) + command_line = g_strdup_printf ("mount -o %s %s %s", q_options, q_device, q_mount_path); + else + command_line = g_strdup_printf ("mount %s %s", q_device, q_mount_path); + g_free (q_mount_path); + g_free (q_device); + g_free (q_options); + + ret = run_command_line_sync_simple (command_line, + NULL, + cancellable, + error); + g_free (command_line); + } + else + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Did not find a device file corresponding to Filesystem item %s", + item->id); + goto out; + } + + ret = TRUE; + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +_stc_item_start_luks (StcItem *item, + StcOperation *operation, + GCancellable *cancellable, + GError **error) +{ + GUdevDevice *device_with_crypto; + GUdevDevice *unlocked_device; + gboolean ret; + gchar *command_line; + gchar *q_device; + gchar *q_name; + gchar *passphrase; + + ret = FALSE; + command_line = NULL; + q_device = NULL; + q_name = NULL; + passphrase = NULL; + + device_with_crypto = _stc_item_luks_get_devices (item, &unlocked_device); + + if (device_with_crypto == NULL) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Cannot find device for item"); + goto out; + } + + if (unlocked_device != NULL) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Cannot unlock device. Device is already unlocked (device %s)", + g_udev_device_get_device_file (unlocked_device)); + goto out; + } + + q_device = g_shell_quote (g_udev_device_get_device_file (device_with_crypto)); + /* TODO: give an option to specify the mapping name */ + q_name = g_strdup_printf ("stc-luks-uuid-%s", + g_udev_device_get_property (device_with_crypto, "ID_FS_UUID")); + + command_line = g_strdup_printf ("cryptsetup luksOpen %s %s", q_device, q_name); + + passphrase = g_strdup (g_hash_table_lookup (item->options, "LUKS:passphrase")); + if (passphrase == NULL) + { + passphrase = stc_operation_request_passphrase (operation, item); + } + if (passphrase == NULL) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Cannot obtain passphrase for unlocking device %s", + g_udev_device_get_device_file (device_with_crypto)); + goto out; + } + + if (!run_command_line_sync_simple (command_line, + passphrase, + cancellable, + error)) + goto out; + + /* wait for the device to actually appear */ + if (!run_command_line_sync_simple ("udevadm settle", + NULL, + cancellable, + error)) + goto out; + + ret = TRUE; + + out: + if (passphrase != NULL) + { + memset (passphrase, '\0', strlen (passphrase)); + g_free (passphrase); + } + g_free (command_line); + g_free (q_name); + g_free (q_device); + if (device_with_crypto != NULL) + g_object_unref (device_with_crypto); + if (unlocked_device != NULL) + g_object_unref (unlocked_device); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gchar * +get_free_md_device (StcItem *item) +{ + gchar *ret; + guint n; + + ret = NULL; + + /* find an unused md minor... Man, I wish mdadm could do this itself; this is slightly racy */ + for (n = 127; n >= 0 && ret == NULL; n--) + { + gchar *sysfs_path; + GUdevDevice *device; + gchar *array_state; + + array_state = NULL; + sysfs_path = NULL; + device = NULL; + + sysfs_path = g_strdup_printf ("/sys/block/md%d", n); + device = g_udev_client_query_by_sysfs_path (_stc_monitor_get_gudev_client (item->monitor), sysfs_path); + if (device == NULL) + { + /* Apparently this md device is free since it doesn't exist. Let's use it. */ + ret = g_strdup_printf ("/dev/md%d", n); + goto found; + } + + array_state = g_strdup (g_udev_device_get_sysfs_attr (device, "md/array_state")); + if (array_state == NULL) + { + /* Apparently this md device is free since there is no such file. Use it. */ + ret = g_strdup_printf ("/dev/md%d", n); + goto found; + } + g_strstrip (array_state); + if (g_strcmp0 (array_state, "clear") == 0) + { + /* It's clear! Let's use it! */ + ret = g_strdup_printf ("/dev/md%d", n); + goto found; + } + + found: + g_free (array_state); + g_free (sysfs_path); + if (device != NULL) + g_object_unref (device); + } + + return ret; +} + +static gboolean +_stc_item_start_md_raid (StcItem *item, + StcOperation *operation, + GCancellable *cancellable, + GError **error) +{ + GUdevDevice *array_device; + GList *component_devices; + gint num_devices; + gchar *md_level; + gboolean ret; + GString *str; + gchar *free_md_device; + gchar *command_line; + GList *l; + + ret = FALSE; + free_md_device = NULL; + command_line = NULL; + + array_device = _stc_item_mdraid_get_devices (item, &component_devices, &md_level, &num_devices); + + if (array_device != NULL) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "MDRaid Item %s appears to already be running (raid device %s)", + item->id, + g_udev_device_get_device_file (array_device)); + goto out; + } + + if (item->state == STC_ITEM_STATE_CAN_START_DEGRADED) + { + gboolean may_start_degraded; + may_start_degraded = stc_operation_may_start_degraded (operation, item); + if (!may_start_degraded) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Refusing to start array in degraded mode"); + goto out; + } + } + + free_md_device = get_free_md_device (item); + if (free_md_device == NULL) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Unable to find free /dev/md<N> device"); + goto out; + } + + str = g_string_new ("mdadm --assemble --run "); + g_string_append (str, free_md_device); + for (l = component_devices; l != NULL; l = l->next) + { + GUdevDevice *device = G_UDEV_DEVICE (l->data); + g_string_append_c (str, ' '); + g_string_append (str, g_udev_device_get_device_file (device)); + } + command_line = g_string_free (str, FALSE); + + if (!run_command_line_sync_simple (command_line, + NULL, + cancellable, + error)) + goto out; + + /* wait for the device to actually appear */ + if (!run_command_line_sync_simple ("udevadm settle", + NULL, + cancellable, + error)) + goto out; + + ret = TRUE; + + out: + g_free (free_md_device); + g_free (command_line); + g_list_foreach (component_devices, (GFunc) g_object_unref, NULL); + g_list_free (component_devices); + if (array_device != NULL) + g_object_unref (array_device); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +gboolean +stc_item_start_sync (StcItem *item, + StcOperation *operation, + GCancellable *cancellable, + GError **error) +{ + guint n; + gboolean ret; + + g_return_val_if_fail (STC_IS_ITEM (item), FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; + + /* first ensure that all dependencies are started */ + for (n = 0; item->depends != NULL && item->depends[n] != NULL; n++) + { + StcItem *depend_item; + StcItemState depend_item_state; + + depend_item = stc_monitor_get_item_by_id (item->monitor, item->depends[n]); + if (depend_item == NULL) + { + /* broken dep - means we need to fail the whole thing */ + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Unresolved dependency on item with id %s", + item->depends[n]); + goto out; + } + + depend_item_state = stc_item_get_state (depend_item); + switch (depend_item_state) + { + case STC_ITEM_STATE_NOT_STARTED: + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Cannot start dependency with id %s", + stc_item_get_id (depend_item)); + g_object_unref (depend_item); + goto out; + + case STC_ITEM_STATE_STARTED: + /* all good */ + break; + + default: + if (!stc_item_start_sync (depend_item, + operation, + cancellable, + error)) + { + g_prefix_error (error, "Error starting dependency with id %s: ", + stc_item_get_id (depend_item)); + g_object_unref (depend_item); + goto out; + } + break; + } + + g_object_unref (depend_item); + } + + switch (item->type) + { + case STC_ITEM_TYPE_FILESYSTEM: + ret = _stc_item_start_filesystem (item, operation, cancellable, error); + break; + + case STC_ITEM_TYPE_LUKS: + ret = _stc_item_start_luks (item, operation, cancellable, error); + break; + + case STC_ITEM_TYPE_MD_RAID: + ret = _stc_item_start_md_raid (item, operation, cancellable, error); + break; + + default: + g_assert_not_reached (); + break; + } + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +_stc_item_stop_filesystem (StcItem *item, + StcOperation *operation, + GCancellable *cancellable, + GError **error) +{ + gboolean ret; + GUdevDevice *device; + gchar *mount_path; + + ret = FALSE; + + device = _stc_item_filesystem_get_device (item, &mount_path); + if (device != NULL) + { + const gchar *opt_mount_path; + + opt_mount_path = g_hash_table_lookup (item->options, "Filesystem:mount_path"); + g_assert (opt_mount_path != NULL); + + if (g_strcmp0 (opt_mount_path, mount_path) == 0) + { + gchar *q_mount_path; + gchar *command_line; + + q_mount_path = g_shell_quote (mount_path); + command_line = g_strdup_printf ("umount %s", q_mount_path); + g_free (q_mount_path); + + ret = run_command_line_sync_simple (command_line, + NULL, + cancellable, + error); + g_free (command_line); + } + else + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Device is mounted at %s but Filesystem:mount_path says %s", + mount_path, + opt_mount_path); + g_object_unref (device); + g_free (mount_path); + goto out; + } + g_object_unref (device); + g_free (mount_path); + } + else + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Did not find a device file corresponding to Filesystem item %s", + item->id); + goto out; + } + + ret = TRUE; + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +_stc_item_stop_luks (StcItem *item, + StcOperation *operation, + GCancellable *cancellable, + GError **error) +{ + GUdevDevice *device_with_crypto; + GUdevDevice *unlocked_device; + gboolean ret; + gchar *command_line; + gchar *q_name; + const gchar *name; + + ret = FALSE; + command_line = NULL; + q_name = NULL; + + device_with_crypto = _stc_item_luks_get_devices (item, &unlocked_device); + + if (device_with_crypto == NULL) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Cannot find device for item"); + goto out; + } + + if (unlocked_device == NULL) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "It doesn't seem like the device %s is unlocked", + g_udev_device_get_device_file (device_with_crypto)); + goto out; + } + + name = g_udev_device_get_property (unlocked_device, "DM_NAME"); + if (name == NULL) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Unlocked device %s does not have a DM_NAME property", + g_udev_device_get_device_file (unlocked_device)); + goto out; + } + q_name = g_shell_quote (name); + + command_line = g_strdup_printf ("cryptsetup luksClose %s", q_name); + if (!run_command_line_sync_simple (command_line, + NULL, + cancellable, + error)) + goto out; + + ret = TRUE; + + out: + g_free (command_line); + g_free (q_name); + if (device_with_crypto != NULL) + g_object_unref (device_with_crypto); + if (unlocked_device != NULL) + g_object_unref (unlocked_device); + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +_stc_item_stop_md_raid (StcItem *item, + StcOperation *operation, + GCancellable *cancellable, + GError **error) +{ + GUdevDevice *array_device; + gboolean ret; + gchar *command_line; + + ret = FALSE; + command_line = NULL; + + array_device = _stc_item_mdraid_get_devices (item, + NULL, /* &component_devices */ + NULL, /* &md_level */ + NULL); /* &num_devices */ + + if (array_device == NULL) + { + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "MDRaid Item %s does not appear to be running", + item->id); + goto out; + } + + command_line = g_strdup_printf ("mdadm --stop %s", g_udev_device_get_device_file (array_device)); + if (!run_command_line_sync_simple (command_line, + NULL, + cancellable, + error)) + goto out; + + ret = TRUE; + + out: + g_free (command_line); + if (array_device != NULL) + g_object_unref (array_device); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +gboolean +stc_item_stop_sync (StcItem *item, + StcOperation *operation, + GCancellable *cancellable, + GError **error) +{ + guint n; + gboolean ret; + + g_return_val_if_fail (STC_IS_ITEM (item), FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ret = FALSE; + + /* first stop the item */ + switch (item->type) + { + case STC_ITEM_TYPE_FILESYSTEM: + ret = _stc_item_stop_filesystem (item, operation, cancellable, error); + break; + + case STC_ITEM_TYPE_LUKS: + ret = _stc_item_stop_luks (item, operation, cancellable, error); + break; + + case STC_ITEM_TYPE_MD_RAID: + ret = _stc_item_stop_md_raid (item, operation, cancellable, error); + break; + + default: + g_assert_not_reached (); + break; + } + + if (!ret) + goto out; + + /* and then stop all dependencies */ + for (n = 0; item->depends != NULL && item->depends[n] != NULL; n++) + { + StcItem *depend_item; + StcItemState depend_item_state; + + depend_item = stc_monitor_get_item_by_id (item->monitor, item->depends[n]); + if (depend_item == NULL) + { + /* broken dep - means we need to fail the whole thing */ + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Unresolved dependency on item with id %s", + item->depends[n]); + ret = FALSE; + goto out; + } + + depend_item_state = stc_item_get_state (depend_item); + switch (depend_item_state) + { + case STC_ITEM_STATE_STARTED: + if (!stc_item_stop_sync (depend_item, + operation, + cancellable, + error)) + { + g_prefix_error (error, "Error stopping dependency with id %s: ", + stc_item_get_id (depend_item)); + g_object_unref (depend_item); + ret = FALSE; + goto out; + } + break; + + default: + g_set_error (error, + STC_ERROR, + STC_ERROR_FAILED, + "Error stopping dependency with id %s: expected item to be running but state is %d", + stc_item_get_id (depend_item), + depend_item_state); + ret = FALSE; + goto out; + } + + g_object_unref (depend_item); + } + + ret = TRUE; + + out: + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ diff --git a/stc/stcitem.h b/stc/stcitem.h index a38a4fe..4422b1f 100644 --- a/stc/stcitem.h +++ b/stc/stcitem.h @@ -36,16 +36,29 @@ G_BEGIN_DECLS #define STC_ITEM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), STC_TYPE_ITEM, StcItem)) #define STC_IS_ITEM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), STC_TYPE_ITEM)) -GType stc_item_get_type (void) G_GNUC_CONST; -StcItemType stc_item_get_item_type (StcItem *item); -const gchar *stc_item_get_id (StcItem *item); -const gchar *stc_item_get_nick_name (StcItem *item); -const gchar *stc_item_get_target (StcItem *item); -const gchar* const *stc_item_get_option_keys (StcItem *item); -const gchar *stc_item_get_option (StcItem *item, - const gchar *key); -const gchar* const *stc_item_get_dependencies (StcItem *item); -StcItemState stc_item_get_state (StcItem *item); +GType stc_item_get_type (void) G_GNUC_CONST; +StcItemType stc_item_get_item_type (StcItem *item); +const gchar *stc_item_get_id (StcItem *item); +const gchar *stc_item_get_nick_name (StcItem *item); +const gchar *stc_item_get_target (StcItem *item); +const gchar* const *stc_item_get_option_keys (StcItem *item); +const gchar *stc_item_get_option (StcItem *item, + const gchar *key); +const gchar* const *stc_item_get_dependencies (StcItem *item); +StcItemState stc_item_get_state (StcItem *item); +const gchar* const *stc_item_get_slave_devices (StcItem *item); +const gchar *stc_item_get_device (StcItem *item); +const gchar *stc_item_get_mount_path (StcItem *item); + +gboolean stc_item_start_sync (StcItem *item, + StcOperation *operation, + GCancellable *cancellable, + GError **error); + +gboolean stc_item_stop_sync (StcItem *item, + StcOperation *operation, + GCancellable *cancellable, + GError **error); G_END_DECLS diff --git a/stc/stcmarshal.list b/stc/stcmarshal.list index e0edfb9..628f65b 100644 --- a/stc/stcmarshal.list +++ b/stc/stcmarshal.list @@ -1 +1,4 @@ VOID:STRING,INT,BOXED +STRING:OBJECT +BOOL:OBJECT + diff --git a/stc/stcmonitor.c b/stc/stcmonitor.c index 694d899..c8ae687 100644 --- a/stc/stcmonitor.c +++ b/stc/stcmonitor.c @@ -1098,6 +1098,18 @@ stc_monitor_update (StcMonitor *monitor) /* make the items appear in the order they were declared */ monitor->items = g_list_sort (monitor->items, _item_cmp_func); + /* TODO: detect dep cycles and also keep a list of items where deps + * always come before the item with the deps. We need to use this + * list when updating the state. + */ + + /* OK, all of our items are now set. Now we can update their state. */ + for (l = monitor->items; l != NULL; l = l->next) + { + StcItem *item = STC_ITEM (l->data); + _stc_item_update_state (item); + } + /* emit signals only when our monitor object has been completely updated */ for (l = items_removed; l != NULL; l = l->next) { diff --git a/stc/stcoperation.c b/stc/stcoperation.c new file mode 100644 index 0000000..7ef12e3 --- /dev/null +++ b/stc/stcoperation.c @@ -0,0 +1,228 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2007-2010 David Zeuthen <zeuthen@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen <zeuthen@gmail.com> + */ + +#include "config.h" + +#include <string.h> + +#include "stcerror.h" +#include "stcoperation.h" +#include "stcmarshal.h" +#include "stcprivate.h" +#include "stcitem.h" +#include "stcmount.h" + +/** + * SECTION:stcoperation + * @title: StcOperation + * @short_description: Object used for interaction when starting/stopping devices + * + * Object used for interaction when starting/stopping devices. You can + * instantiate a #StcOperation and connect to signals such as + * #StcOperation::request-passphrase to provide information obtained + * from the user or stable storage. + * + * If stc_item_start_sync() was used, signals are emitted in the same + * thread. If stc_item_start() was used, signals are emitted in a + * private thread. Either way, you are allowed to block while + * interacting with the user (just be careful since you may be in + * another thread - e.g. you may need to signal a UI thread to put up + * a dialog etc.). + */ + +/** + * StcOperation: + * + * The #StcOperation structure contains only private data and should + * only be accessed using the provided API. + */ +struct _StcOperation +{ + GObject parent_instance; + +}; + +typedef struct _StcOperationClass StcOperationClass; + +struct _StcOperationClass +{ + GObjectClass parent_class; + + gchar *(*request_passphrase) (StcOperation *operation, + StcItem *item); + gboolean (*may_start_degraded) (StcOperation *operation, + StcItem *item); +}; + +enum +{ + REQUEST_PASSPHRASE_SIGNAL, + MAY_START_DEGRADED_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (StcOperation, stc_operation, G_TYPE_OBJECT); + +static void +stc_operation_init (StcOperation *operation) +{ +} + +static gboolean +signal_accumulator_string_null (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer user_data) +{ + gboolean continue_emission; + const gchar *str; + + continue_emission = TRUE; + + str = g_value_get_string (handler_return); + if (str != NULL) + { + g_value_set_string (return_accu, str); + continue_emission = FALSE; + } + + return continue_emission; +} + +static void +stc_operation_class_init (StcOperationClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + + /** + * StcOperation::request-passphrase: + * @operation: The #StcOperation emitting the signal. + * @item: The item needing the passphrase. + * + * Emitted when a passphrase is needed to start @item. + * + * Returns: The passphrase for @item (in which case no further + * signal handlers will be called) or %NULL if no + * passphrase was found (in which case further signal + * handlers will be called). + */ + signals[REQUEST_PASSPHRASE_SIGNAL] = g_signal_new ("request-passphrase", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StcOperationClass, request_passphrase), + signal_accumulator_string_null, + NULL, + _stc_marshal_STRING__OBJECT, + G_TYPE_STRING, + 1, + STC_TYPE_ITEM); + + /** + * StcOperation::may-start-degraded: + * @operation: The #StcOperation emitting the signal. + * @item: The item to start degraded. + * + * Emitted when checking if it is OK to start @item degraded. + * + * Returns: %TRUE to allow to start @item degraded (in which case no + * further signal handlers will be called) or %FALSE if not + * (in which case further signal handlers will be called). + */ + signals[MAY_START_DEGRADED_SIGNAL] = g_signal_new ("may-start-degraded", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StcOperationClass, may_start_degraded), + g_signal_accumulator_true_handled, + NULL, + _stc_marshal_BOOL__OBJECT, + G_TYPE_BOOLEAN, + 1, + STC_TYPE_ITEM); +} + +/** + * stc_operation_new: + * + * Creates a new #StcOperation object. + * + * Returns: A #StcOperation. Free with g_object_unref(). + */ +StcOperation * +stc_operation_new (void) +{ + StcOperation *operation; + operation = STC_OPERATION (g_object_new (STC_TYPE_OPERATION, + NULL)); + return operation; +} + +/** + * stc_operation_request_passphrase: + * @operation: A #StcOperation. + * @item: A #StcItem. + * + * Emits the #StcOperation::request-passphrase signal on @operation. + * + * Returns: The return value of the signal + */ +gchar * +stc_operation_request_passphrase (StcOperation *operation, + StcItem *item) +{ + gchar *ret; + + g_return_val_if_fail (STC_IS_OPERATION (operation), NULL); + g_return_val_if_fail (STC_IS_ITEM (item), NULL); + + g_signal_emit (operation, signals[REQUEST_PASSPHRASE_SIGNAL], 0, item, &ret); + + return ret; +} + +/** + * stc_operation_may_start_degraded: + * @operation: A #StcOperation. + * @item: A #StcItem. + * + * Emits the #StcOperation::may-start-degraded signal on @operation. + * + * Returns: The return value of the signal. + */ +gboolean +stc_operation_may_start_degraded (StcOperation *operation, + StcItem *item) +{ + gboolean ret; + + g_return_val_if_fail (STC_IS_OPERATION (operation), FALSE); + g_return_val_if_fail (STC_IS_ITEM (item), FALSE); + + g_signal_emit (operation, signals[MAY_START_DEGRADED_SIGNAL], 0, item, &ret); + + return ret; +} + + diff --git a/stc/stcoperation.h b/stc/stcoperation.h new file mode 100644 index 0000000..551a253 --- /dev/null +++ b/stc/stcoperation.h @@ -0,0 +1,48 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2007-2010 David Zeuthen <zeuthen@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen <zeuthen@gmail.com> + */ + + +#if !defined (__STC_INSIDE_STC_H__) && !defined (STC_COMPILATION) +#error "Only <stc/stc.h> can be included directly." +#endif + +#ifndef __STC_OPERATION_H__ +#define __STC_OPERATION_H__ + +#include <stc/stctypes.h> + +G_BEGIN_DECLS + +#define STC_TYPE_OPERATION (stc_operation_get_type ()) +#define STC_OPERATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), STC_TYPE_OPERATION, StcOperation)) +#define STC_IS_OPERATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), STC_TYPE_OPERATION)) + +GType stc_operation_get_type (void) G_GNUC_CONST; +StcOperation *stc_operation_new (void); +gchar *stc_operation_request_passphrase (StcOperation *operation, + StcItem *item); +gboolean stc_operation_may_start_degraded (StcOperation *operation, + StcItem *item); + +G_END_DECLS + +#endif /* __STC_OPERATION_H__ */ diff --git a/stc/stcprivate.h b/stc/stcprivate.h index f9e76e0..20e97e7 100644 --- a/stc/stcprivate.h +++ b/stc/stcprivate.h @@ -59,7 +59,7 @@ const gchar *_stc_item_get_path (StcItem *item); gint _stc_item_get_line_no (StcItem *item); gboolean _stc_item_update (StcItem *item, StcItem *other); -void _stc_item_update_state (StcItem *item); +gboolean _stc_item_update_state (StcItem *item); GUdevClient *_stc_monitor_get_gudev_client (StcMonitor *monitor); diff --git a/stc/stctypes.h b/stc/stctypes.h index eaa560e..7756682 100644 --- a/stc/stctypes.h +++ b/stc/stctypes.h @@ -38,6 +38,9 @@ typedef struct _StcItem StcItem; struct _StcMonitor; typedef struct _StcMonitor StcMonitor; +struct _StcOperation; +typedef struct _StcOperation StcOperation; + G_END_DECLS #endif /* __STC_TYPES_H__ */ |