summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Zeuthen <davidz@redhat.com>2010-10-06 15:13:46 -0400
committerDavid Zeuthen <davidz@redhat.com>2010-10-06 15:13:46 -0400
commit1331349d2e97bceaadf8d8cf0b6b5c11c83577a5 (patch)
tree56cee7e3234b6b2b8a72d4ed561c6b07b267c83b
parentd03c35c1d0a486a45e9206267aca155fc4448394 (diff)
Updates
Signed-off-by: David Zeuthen <davidz@redhat.com>
-rw-r--r--Makefile.am2
-rw-r--r--configure.ac3
-rw-r--r--data/stc.conf.in (renamed from stc/stc.conf.in)0
-rw-r--r--doc/stc-docs.xml1
-rw-r--r--doc/stc-sections.txt20
-rw-r--r--doc/stc.types1
-rw-r--r--stc/Makefile.am16
-rw-r--r--stc/stc.c440
-rw-r--r--stc/stc.h1
-rw-r--r--stc/stcenums.h21
-rw-r--r--stc/stcitem.c1812
-rw-r--r--stc/stcitem.h33
-rw-r--r--stc/stcmarshal.list3
-rw-r--r--stc/stcmonitor.c12
-rw-r--r--stc/stcoperation.c228
-rw-r--r--stc/stcoperation.h48
-rw-r--r--stc/stcprivate.h2
-rw-r--r--stc/stctypes.h3
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
diff --git a/stc/stc.c b/stc/stc.c
index 589f13c..7197f4b 100644
--- a/stc/stc.c
+++ b/stc/stc.c
@@ -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;
diff --git a/stc/stc.h b/stc/stc.h
index aba2fd1..fa606ab 100644
--- a/stc/stc.h
+++ b/stc/stc.h
@@ -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__ */