/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2007-2010 David Zeuthen * * 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 */ #include "config.h" #include #include #include #include "stcitem.h" #include "stcmonitor.h" #include "stcmount.h" #include "stcmountmonitor.h" #include "stcerror.h" #include "stcoperation.h" #include "stcprivate.h" /** * SECTION:stcitem * @title: StcItem * @short_description: Configuration Item * * Object corresponding to a configuration item in * e.g. /etc/stc.conf. You cannot instantiate * this type yourself – you must use e.g. #StcMonitor to obtain * instances. */ /** * StcItem: * * The #StcItem structure contains only private data and should * only be accessed using the provided API. */ struct _StcItem { GObject parent_instance; StcMonitor *monitor; StcItemType type; gchar *id; gchar *comment; gchar *target; GHashTable *options; gchar **depends; /* memoized */ gchar **option_keys; /* current state */ StcItemState state; gchar **slave_devices; gchar *device; gchar **mount_paths; /* private */ gchar *sort_key; gchar *path; gint line_no; }; typedef struct _StcItemClass StcItemClass; struct _StcItemClass { GObjectClass parent_class; void (*changed) (StcItem *item); }; enum { CHANGED_SIGNAL, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE (StcItem, stc_item, G_TYPE_OBJECT); static void stc_item_finalize (GObject *object) { StcItem *item = STC_ITEM (object); if (item->monitor != NULL) g_object_remove_weak_pointer (G_OBJECT (item->monitor), (gpointer*) &item->monitor); g_free (item->id); g_free (item->comment); g_free (item->target); g_hash_table_unref (item->options); g_strfreev (item->depends); g_free (item->option_keys); g_free (item->sort_key); g_free (item->path); g_strfreev (item->slave_devices); g_free (item->device); g_strfreev (item->mount_paths); if (G_OBJECT_CLASS (stc_item_parent_class)->finalize) G_OBJECT_CLASS (stc_item_parent_class)->finalize (object); } static void stc_item_init (StcItem *item) { } static void stc_item_class_init (StcItemClass *klass) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = stc_item_finalize; /** * StcItem::changed: * @item: The item that changed. * * Emitted when an item changes. */ signals[CHANGED_SIGNAL] = g_signal_new ("changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (StcItemClass, changed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 0); } /** * stc_item_get_item_type: * @item: A #StcItem. * * Gets the #StcItemType for @item. * * Returns: A value from the #StcItemType enumeration. */ StcItemType stc_item_get_item_type (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), STC_ITEM_TYPE_INVALID); return item->type; } const gchar * stc_item_get_id (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), STC_ITEM_TYPE_INVALID); return item->id; } const gchar * stc_item_get_comment (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), NULL); return item->comment; } const gchar * stc_item_get_target (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), NULL); return item->target; } StcItemState stc_item_get_state (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), -1); return item->state; } StcItem * _stc_item_new (StcMonitor *monitor, StcItemType type, const gchar *id, const gchar *target, const gchar *comment, GHashTable *options, const gchar *const *depends, const gchar *sort_key, const gchar *path, gint line_no) { StcItem *item; item = STC_ITEM (g_object_new (STC_TYPE_ITEM, NULL)); item->monitor = monitor; g_object_add_weak_pointer (G_OBJECT (item->monitor), (gpointer*) &item->monitor); item->id = g_strdup (id); item->target = g_strdup (target); item->comment = g_strdup (comment); item->type = type; item->options = g_hash_table_ref (options); item->depends = g_strdupv ((gchar **) depends); item->sort_key = g_strdup (sort_key); item->path = g_strdup (path); item->line_no = line_no; return item; } static gboolean strv_equal (gchar **a, gchar **b) { guint n; if (a == NULL && b != NULL) return FALSE; if (a != NULL && b == NULL) return FALSE; if (a == NULL && b == NULL) return TRUE; g_assert (a != NULL); g_assert (b != NULL); for (n = 0; a[n] != NULL || b[n] != NULL; n++) { if (g_strcmp0 (a[n], b[n]) != 0) return FALSE; } return TRUE; } static gboolean hash_tables_equal (GHashTable *a, GHashTable *b) { gboolean ret; GHashTableIter iter; const gchar *key; const gchar *value; const gchar *other_value; ret = FALSE; g_hash_table_iter_init (&iter, a); while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) { other_value = g_hash_table_lookup (b, key); if (g_strcmp0 (value, other_value) != 0) goto out; } g_hash_table_iter_init (&iter, b); while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) { other_value = g_hash_table_lookup (a, key); if (g_strcmp0 (value, other_value) != 0) goto out; } ret = TRUE; out: return ret; } gboolean _stc_item_update (StcItem *item, StcItem *other) { gboolean changed; g_return_val_if_fail (STC_IS_ITEM (item), FALSE); g_return_val_if_fail (STC_IS_ITEM (other), FALSE); changed = FALSE; g_assert_cmpstr (item->id, ==, other->id); if (g_strcmp0 (item->target, other->target) != 0) { g_free (item->target); item->target = g_strdup (other->target); changed = TRUE; } if (g_strcmp0 (item->comment, other->comment) != 0) { g_free (item->comment); item->comment = g_strdup (other->comment); changed = TRUE; } if (!hash_tables_equal (item->options, other->options)) { g_hash_table_unref (item->options); item->options = g_hash_table_ref (other->options); changed = TRUE; } if (!strv_equal (item->depends, other->depends)) { g_strfreev (item->depends); item->depends = g_strdupv (other->depends); changed = TRUE; } return changed; } /* ---------------------------------------------------------------------------------------------------- */ /* TODO: need something like this in gudev */ static GList * _g_udev_client_query_by_property (GUdevClient *client, const gchar *subsystem, const gchar *key, const gchar *value) { GList *ret; GList *devices; GList *l; ret = NULL; devices = g_udev_client_query_by_subsystem (client, subsystem); for (l = devices; l != NULL; l = l->next) { GUdevDevice *device = G_UDEV_DEVICE (l->data); if (g_strcmp0 (g_udev_device_get_property (device, key), value) == 0) { ret = g_list_prepend (ret, g_object_ref (device)); } } g_list_foreach (devices, (GFunc) g_object_unref, NULL); g_list_free (devices); ret = g_list_reverse (ret); return ret; } /* ---------------------------------------------------------------------------------------------------- */ static GUdevDevice * _stc_item_filesystem_get_device (StcItem *item, gchar ***out_mount_paths) { GUdevDevice *ret; GUdevDevice *device; GList *devices; GList *l; gchar **mount_paths; g_return_val_if_fail (STC_IS_ITEM (item), NULL); g_return_val_if_fail (item->type == STC_ITEM_TYPE_FILESYSTEM, NULL); ret = NULL; mount_paths = NULL; if (g_str_has_prefix (item->target, "Device=")) { 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 = g_object_ref (device); g_object_unref (device); } } else if (g_str_has_prefix (item->target, "UUID=")) { devices = _g_udev_client_query_by_property (_stc_monitor_get_gudev_client (item->monitor), "block", "ID_FS_UUID", item->target + sizeof "UUID=" - 1); for (l = devices; l != NULL; l = l->next) { device = G_UDEV_DEVICE (l->data); if (g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "filesystem") == 0) { ret = g_object_ref (device); break; } } g_list_foreach (devices, (GFunc) g_object_unref, NULL); g_list_free (devices); } else if (g_str_has_prefix (item->target, "Label=")) { devices = _g_udev_client_query_by_property (_stc_monitor_get_gudev_client (item->monitor), "block", "ID_FS_LABEL", item->target + sizeof "Label=" - 1); for (l = devices; l != NULL; l = l->next) { device = G_UDEV_DEVICE (l->data); if (g_strcmp0 (g_udev_device_get_property (device, "ID_FS_USAGE"), "filesystem") == 0) { ret = g_object_ref (device); break; } } g_list_foreach (devices, (GFunc) g_object_unref, NULL); g_list_free (devices); } else { g_warning ("Unsupported target type %s for Filesystem", item->target); } if (ret != NULL) { GList *mounts; 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) { GPtrArray *p; p = g_ptr_array_new (); for (l = mounts; l != NULL; l = l->next) { _StcMount *mount = _STC_MOUNT (l->data); g_ptr_array_add (p, g_strdup (_stc_mount_get_mount_path (mount))); } g_ptr_array_add (p, NULL); mount_paths = (gchar **) g_ptr_array_free (p, FALSE); } g_list_foreach (mounts, (GFunc) g_object_unref, NULL); g_list_free (mounts); } if (out_mount_paths != NULL) *out_mount_paths = mount_paths; else g_strfreev (mount_paths); 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 gboolean _strv_has_str (gchar **strv, const gchar *str) { guint n; g_return_val_if_fail (str != NULL, FALSE); if (strv == NULL) return FALSE; for (n = 0; strv[n] != NULL; n++) if (g_strcmp0 (strv[n], str) == 0) return TRUE; return FALSE; } static StcItemState _stc_item_update_state_filesystem (StcItem *item) { StcItemState ret; GUdevDevice *device; gchar **mount_paths; ret = STC_ITEM_STATE_NOT_STARTED; g_free (item->device); item->device = NULL; g_strfreev (item->mount_paths); item->mount_paths = NULL; device = _stc_item_filesystem_get_device (item, &mount_paths); if (device != NULL) { ret = STC_ITEM_STATE_CAN_START; item->device = g_strdup (g_udev_device_get_device_file (device)); if (mount_paths != 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 (_strv_has_str (mount_paths, desired_mount_path)) { ret = STC_ITEM_STATE_STARTED; } item->mount_paths = mount_paths; /* adopt */ } g_object_unref (device); } return ret; } static gchar * _strdup_without_dashes (const gchar *s) { GString *str; guint n; if (s == NULL) return NULL; str = g_string_new (NULL); for (n = 0; s[n] != '\0'; n++) { gint c = s[n]; if (c == '-') continue; g_string_append_c (str, c); } return g_string_free (str, FALSE); } static GUdevDevice * _stc_item_luks_get_devices (StcItem *item, GUdevDevice **out_unlocked_device) { GUdevDevice *device_with_crypto; GUdevDevice *unlocked_device; GUdevDevice *device; GList *devices; GList *l; device_with_crypto = NULL; unlocked_device = NULL; if (g_str_has_prefix (item->target, "Device=")) { 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) { device_with_crypto = g_object_ref (device); } g_object_unref (device); } } else if (g_str_has_prefix (item->target, "UUID=")) { devices = _g_udev_client_query_by_property (_stc_monitor_get_gudev_client (item->monitor), "block", "ID_FS_UUID", item->target + sizeof "UUID=" - 1); for (l = devices; l != NULL; l = l->next) { device = G_UDEV_DEVICE (l->data); 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) { device_with_crypto = g_object_ref (device); break; } } g_list_foreach (devices, (GFunc) g_object_unref, NULL); g_list_free (devices); } else { g_warning ("Unsupported target type %s for LUKS", item->target); } 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 (_stc_monitor_get_gudev_client (item->monitor), "block"); for (l = devices; l != NULL; l = l->next) { const gchar *dm_uuid; device = G_UDEV_DEVICE (l->data); dm_uuid = g_udev_device_get_property (device, "DM_UUID"); if (dm_uuid != NULL && g_str_has_prefix (dm_uuid, uuid_prefix)) { unlocked_device = g_object_ref (device); break; } } g_list_foreach (devices, (GFunc) g_object_unref, NULL); g_list_free (devices); g_free (uuid_prefix); } 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_luks (StcItem *item) { StcItemState ret; GUdevDevice *device_with_crypto; GUdevDevice *unlocked_device; ret = STC_ITEM_STATE_NOT_STARTED; g_strfreev (item->slave_devices); item->slave_devices = NULL; g_free (item->device); item->device = NULL; device_with_crypto = _stc_item_luks_get_devices (item, &unlocked_device); /* figure out if IS_STARTED */ if (device_with_crypto != NULL) { 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) { item->device = g_strdup (g_udev_device_get_device_file (unlocked_device)); g_object_unref (unlocked_device); ret = STC_ITEM_STATE_STARTED; } g_object_unref (device_with_crypto); } 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) { GPtrArray *p; GList *l; p = g_ptr_array_new (); for (l = component_devices; l != NULL; l = l->next) { GUdevDevice *slave_device = G_UDEV_DEVICE (l->data); g_ptr_array_add (p, g_strdup (g_udev_device_get_device_file (slave_device))); } 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 == num_devices) ret = STC_ITEM_STATE_CAN_START; } else if (g_strcmp0 (md_level, "raid1") == 0) { 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 (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 (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_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; } gboolean _stc_item_update_state (StcItem *item) { StcItemState state; guint n; gboolean changed; g_return_val_if_fail (STC_IS_ITEM (item), FALSE); state = STC_ITEM_STATE_STARTED; /* first check all deps */ 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 this item is NOT_STARTED */ state = STC_ITEM_STATE_NOT_STARTED; goto dep_check_out; } depend_item_state = stc_item_get_state (depend_item); g_object_unref (depend_item); 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; case STC_ITEM_STATE_CAN_START: case STC_ITEM_STATE_CAN_START_DEGRADED: /* if we are already CAN_START_DEGRADED, stay there... otherwise * we want to be DEGRADED if the dependent is DEGRADED... */ if (state == STC_ITEM_STATE_CAN_START_DEGRADED) ; else state = depend_item_state; 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; } /* ---------------------------------------------------------------------------------------------------- */ const gchar * _stc_item_get_sort_key (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), NULL); return item->sort_key; } const gchar * _stc_item_get_path (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), NULL); return item->path; } gint _stc_item_get_line_no (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), -1); return item->line_no; } const gchar * const * stc_item_get_dependencies (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), NULL); return (const gchar * const *) item->depends; } const gchar* const * stc_item_get_option_keys (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), NULL); if (item->option_keys == NULL) { GHashTableIter iter; const gchar *key; GPtrArray *p; p = g_ptr_array_new (); g_hash_table_iter_init (&iter, item->options); while (g_hash_table_iter_next (&iter, (gpointer) &key, NULL)) g_ptr_array_add (p, (gpointer) key); g_ptr_array_add (p, NULL); item->option_keys = (gchar **) g_ptr_array_free (p, FALSE); } return (const gchar *const *) item->option_keys; } const gchar * stc_item_get_option (StcItem *item, const gchar *key) { 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 *const * stc_item_get_mount_paths (StcItem *item) { g_return_val_if_fail (STC_IS_ITEM (item), NULL); return (const gchar *const *) item->mount_paths; } /* ---------------------------------------------------------------------------------------------------- */ 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_paths; gboolean ret; ret = FALSE; mount_paths = NULL; device = NULL; device = _stc_item_filesystem_get_device (item, &mount_paths); if (device != NULL) { const gchar *o_mount_path; const gchar *device_file; const gchar *options; gchar *q_mount_path; gchar *q_device; gchar *q_options; gchar *command_line; const gchar *desired_mount_path; desired_mount_path = g_hash_table_lookup (item->options, "Filesystem:mount_path"); g_assert (desired_mount_path != NULL); if (mount_paths != NULL && _strv_has_str (mount_paths, desired_mount_path)) { g_set_error (error, STC_ERROR, STC_ERROR_FAILED, "Device %s is already mounted at %s", g_udev_device_get_device_file (device), desired_mount_path); goto out; } o_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 (o_mount_path != NULL); g_assert (device_file != NULL); q_mount_path = g_shell_quote (o_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); if (!run_command_line_sync_simple (command_line, NULL, cancellable, error)) { g_free (command_line); goto out; } 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: if (device != NULL) g_object_unref (device); g_strfreev (mount_paths); 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 *name; gchar *q_name; gchar *passphrase; ret = FALSE; command_line = NULL; q_device = NULL; name = 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)); name = g_strdup (g_hash_table_lookup (item->options, "LUKS:mapping_name")); if (name == NULL) name = g_strdup_printf ("stc-luks-uuid-%s", g_udev_device_get_property (device_with_crypto, "ID_FS_UUID")); q_name = g_shell_quote (name); 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 (name); 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 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_paths; ret = FALSE; device = _stc_item_filesystem_get_device (item, &mount_paths); 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 (_strv_has_str (mount_paths, opt_mount_path)) { gchar *q_mount_path; gchar *command_line; q_mount_path = g_shell_quote (opt_mount_path); command_line = g_strdup_printf ("umount %s", q_mount_path); g_free (q_mount_path); if (!run_command_line_sync_simple (command_line, NULL, cancellable, error)) { g_free (command_line); goto out; } g_free (command_line); } else { g_set_error (error, STC_ERROR, STC_ERROR_FAILED, "Device is not mounted at %s", opt_mount_path); g_object_unref (device); g_strfreev (mount_paths); goto out; } g_object_unref (device); g_strfreev (mount_paths); } 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; } /* ---------------------------------------------------------------------------------------------------- */