static void storaged_linux_block_object_set_property (GObject *__object, guint prop_id, const GValue *value, GParamSpec *pspec) { StoragedLinuxBlockObject *object = STORAGED_LINUX_BLOCK_OBJECT (__object); switch (prop_id) { case PROP_DAEMON: g_assert (object->daemon == NULL); /* we don't take a reference to the daemon */ object->daemon = g_value_get_object (value); break; case PROP_DEVICE: g_assert (object->device == NULL); object->device = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } }
static gboolean filesystem_check (StoragedObject *object) { StoragedLinuxBlockObject *block_object = STORAGED_LINUX_BLOCK_OBJECT (object); gboolean ret = FALSE; gboolean detected_as_filesystem = FALSE; StoragedMountType mount_type; /* if blkid(8) has detected the device as a filesystem, trust that */ if (g_strcmp0 (storaged_block_get_id_usage (block_object->iface_block_device), "filesystem") == 0) { detected_as_filesystem = TRUE; /* except, if we are a whole-disk device and the kernel has already partitioned us... * in that case, don't pretend we're a filesystem * * (see partition_table_check() above for the similar case where we don't pretend * to be a partition table) */ if (g_strcmp0 (g_udev_device_get_devtype (block_object->device->udev_device), "disk") == 0 && disk_is_partitioned_by_kernel (block_object->device->udev_device)) { detected_as_filesystem = FALSE; } } if (drive_does_not_detect_media_change (block_object) || detected_as_filesystem || (storaged_mount_monitor_is_dev_in_use (block_object->mount_monitor, g_udev_device_get_device_number (block_object->device->udev_device), &mount_type) && mount_type == STORAGED_MOUNT_TYPE_FILESYSTEM)) ret = TRUE; return ret; }
static void storaged_linux_block_object_finalize (GObject *_object) { StoragedLinuxBlockObject *object = STORAGED_LINUX_BLOCK_OBJECT (_object); /* note: we don't hold a ref to block->daemon or block->mount_monitor */ g_signal_handlers_disconnect_by_func (object->mount_monitor, on_mount_monitor_mount_added, object); g_signal_handlers_disconnect_by_func (object->mount_monitor, on_mount_monitor_mount_removed, object); g_object_unref (object->device); if (object->iface_block_device != NULL) g_object_unref (object->iface_block_device); if (object->iface_partition != NULL) g_object_unref (object->iface_partition); if (object->iface_partition_table != NULL) g_object_unref (object->iface_partition_table); if (object->iface_filesystem != NULL) g_object_unref (object->iface_filesystem); if (object->iface_swapspace != NULL) g_object_unref (object->iface_swapspace); if (object->iface_encrypted != NULL) g_object_unref (object->iface_encrypted); if (object->iface_loop != NULL) g_object_unref (object->iface_loop); if (object->module_ifaces != NULL) g_hash_table_destroy (object->module_ifaces); if (G_OBJECT_CLASS (storaged_linux_block_object_parent_class)->finalize != NULL) G_OBJECT_CLASS (storaged_linux_block_object_parent_class)->finalize (_object); }
static void storaged_linux_block_object_constructed (GObject *_object) { StoragedLinuxBlockObject *object = STORAGED_LINUX_BLOCK_OBJECT (_object); GString *str; object->mount_monitor = storaged_daemon_get_mount_monitor (object->daemon); g_signal_connect (object->mount_monitor, "mount-added", G_CALLBACK (on_mount_monitor_mount_added), object); g_signal_connect (object->mount_monitor, "mount-removed", G_CALLBACK (on_mount_monitor_mount_removed), object); /* initial coldplug */ storaged_linux_block_object_uevent (object, "add", NULL); /* compute the object path */ str = g_string_new ("/org/storaged/Storaged/block_devices/"); storaged_safe_append_to_object_path (str, g_udev_device_get_name (object->device->udev_device)); g_dbus_object_skeleton_set_object_path (G_DBUS_OBJECT_SKELETON (object), str->str); g_string_free (str, TRUE); if (G_OBJECT_CLASS (storaged_linux_block_object_parent_class)->constructed != NULL) G_OBJECT_CLASS (storaged_linux_block_object_parent_class)->constructed (_object); }
static gboolean bcache_block_update (StoragedObject *object, const gchar *uevent_action, GDBusInterface *_iface) { return storaged_linux_block_bcache_update (STORAGED_LINUX_BLOCK_BCACHE (_iface), STORAGED_LINUX_BLOCK_OBJECT (object)); }
static gboolean btrfs_block_update (StoragedObject *object, const gchar *uevent_action, GDBusInterface *_iface) { return storaged_linux_filesystem_btrfs_update (STORAGED_LINUX_FILESYSTEM_BTRFS (_iface), STORAGED_LINUX_BLOCK_OBJECT (object)); }
static gboolean encrypted_update (StoragedObject *object, const gchar *uevent_action, GDBusInterface *_iface) { storaged_linux_encrypted_update (STORAGED_LINUX_ENCRYPTED (_iface), STORAGED_LINUX_BLOCK_OBJECT (object)); return TRUE; }
static gboolean loop_update (StoragedObject *object, const gchar *uevent_action, GDBusInterface *_iface) { storaged_linux_loop_update (STORAGED_LINUX_LOOP (_iface), STORAGED_LINUX_BLOCK_OBJECT (object)); return TRUE; }
static gboolean swapspace_update (StoragedObject *object, const gchar *uevent_action, GDBusInterface *_iface) { storaged_linux_swapspace_update (STORAGED_LINUX_SWAPSPACE (_iface), STORAGED_LINUX_BLOCK_OBJECT (object)); return TRUE; }
static gboolean filesystem_update (StoragedObject *object, const gchar *uevent_action, GDBusInterface *_iface) { storaged_linux_filesystem_update (STORAGED_LINUX_FILESYSTEM (_iface), STORAGED_LINUX_BLOCK_OBJECT (object)); return TRUE; }
static gboolean partition_update (StoragedObject *object, const gchar *uevent_action, GDBusInterface *_iface) { storaged_linux_partition_update (STORAGED_LINUX_PARTITION (_iface), STORAGED_LINUX_BLOCK_OBJECT (object)); return TRUE; }
static void on_mount_monitor_mount_removed (StoragedMountMonitor *monitor, StoragedMount *mount, gpointer user_data) { StoragedLinuxBlockObject *object = STORAGED_LINUX_BLOCK_OBJECT (user_data); if (storaged_mount_get_dev (mount) == g_udev_device_get_device_number (object->device->udev_device)) storaged_linux_block_object_uevent (object, NULL, NULL); }
/** * storaged_linux_block_object_new: * @daemon: A #StoragedDaemon. * @device: The #StoragedLinuxDevice for the device. * * Create a new block object. * * Returns: A #StoragedLinuxBlockObject object. Free with g_object_unref(). */ StoragedLinuxBlockObject * storaged_linux_block_object_new (StoragedDaemon *daemon, StoragedLinuxDevice *device) { g_return_val_if_fail (STORAGED_IS_DAEMON (daemon), NULL); return STORAGED_LINUX_BLOCK_OBJECT (g_object_new (STORAGED_TYPE_LINUX_BLOCK_OBJECT, "daemon", daemon, "device", device, NULL)); }
static gboolean have_partition_in_range (StoragedPartitionTable *table, StoragedObject *object, guint64 start, guint64 end, gboolean ignore_container) { gboolean ret = FALSE; StoragedDaemon *daemon = NULL; GDBusObjectManager *object_manager = NULL; const gchar *table_object_path; GList *objects = NULL, *l; daemon = storaged_linux_block_object_get_daemon (STORAGED_LINUX_BLOCK_OBJECT (object)); object_manager = G_DBUS_OBJECT_MANAGER (storaged_daemon_get_object_manager (daemon)); table_object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (object)); objects = g_dbus_object_manager_get_objects (object_manager); for (l = objects; l != NULL; l = l->next) { StoragedObject *i_object = STORAGED_OBJECT (l->data); StoragedPartition *i_partition = NULL; i_partition = storaged_object_get_partition (i_object); if (i_partition == NULL) goto cont; if (g_strcmp0 (storaged_partition_get_table (i_partition), table_object_path) != 0) goto cont; if (ignore_container && storaged_partition_get_is_container (i_partition)) goto cont; if (!ranges_overlap (start, end - start, storaged_partition_get_offset (i_partition), storaged_partition_get_size (i_partition))) goto cont; ret = TRUE; g_clear_object (&i_partition); goto out; cont: g_clear_object (&i_partition); } out: g_list_foreach (objects, (GFunc) g_object_unref, NULL); g_list_free (objects); return ret; }
static gboolean encrypted_check (StoragedObject *object) { StoragedLinuxBlockObject *block_object = STORAGED_LINUX_BLOCK_OBJECT (object); gboolean ret; ret = FALSE; if (g_strcmp0 (storaged_block_get_id_usage (block_object->iface_block_device), "crypto") == 0 && g_strcmp0 (storaged_block_get_id_type (block_object->iface_block_device), "crypto_LUKS") == 0) ret = TRUE; return ret; }
static gboolean loop_check (StoragedObject *object) { StoragedLinuxBlockObject *block_object = STORAGED_LINUX_BLOCK_OBJECT (object); gboolean ret; ret = FALSE; if (g_str_has_prefix (g_udev_device_get_name (block_object->device->udev_device), "loop") && g_strcmp0 (g_udev_device_get_devtype (block_object->device->udev_device), "disk") == 0) ret = TRUE; return ret; }
static gboolean partition_table_check (StoragedObject *object) { StoragedLinuxBlockObject *block_object = STORAGED_LINUX_BLOCK_OBJECT (object); gboolean ret = FALSE; /* only consider whole disks, never partitions */ if (g_strcmp0 (g_udev_device_get_devtype (block_object->device->udev_device), "disk") != 0) goto out; /* if blkid(8) already identified the device as a partition table, it's all good */ if (g_udev_device_has_property (block_object->device->udev_device, "ID_PART_TABLE_TYPE")) { /* however, if blkid(8) also think that we're a filesystem... then don't * mark us as a partition table ... except if we are partitioned by the * kernel * * (see filesystem_check() for the similar case where we don't pretend * to be a filesystem) */ if (g_strcmp0 (g_udev_device_get_property (block_object->device->udev_device, "ID_FS_USAGE"), "filesystem") == 0) { if (!disk_is_partitioned_by_kernel (block_object->device->udev_device)) { goto out; } } ret = TRUE; goto out; } /* Note that blkid(8) might not detect all partition table * formats that the kernel knows about.... so we need to * double check... * * Fortunately, note that the kernel guarantees that all children * block devices that are partitions are created before the uevent * for the parent block device.... so if the parent block device has * children... then it must be partitioned by the kernel, hence it * must contain a partition table. */ if (disk_is_partitioned_by_kernel (block_object->device->udev_device)) { ret = TRUE; goto out; } out: return ret; }
static gboolean btrfs_block_check (StoragedObject *object) { const gchar *fs_type = NULL; StoragedLinuxDevice *device = NULL; g_return_val_if_fail (STORAGED_IS_LINUX_BLOCK_OBJECT (object), FALSE); /* Check filesystem type from udev property. */ device = storaged_linux_block_object_get_device (STORAGED_LINUX_BLOCK_OBJECT (object)); fs_type = g_udev_device_get_property (device->udev_device, "ID_FS_TYPE"); return g_strcmp0 (fs_type, "btrfs") == 0; }
/** * storaged_linux_drive_object_get_block: * @object: A #StoragedLinuxDriveObject. * @get_hw: If the drive is multipath, set to %TRUE to get a path device instead of the multipath device. * * Gets a #StoragedLinuxBlockObject representing a block device associated with @object. * * Returns: A #StoragedLinuxBlockObject or %NULL. The returned object * must be freed with g_object_unref(). */ StoragedLinuxBlockObject * storaged_linux_drive_object_get_block (StoragedLinuxDriveObject *object, gboolean get_hw) { GDBusObjectManagerServer *object_manager; StoragedLinuxBlockObject *ret; GList *objects; GList *l; /* TODO: actually look at @get_hw */ ret = NULL; object_manager = storaged_daemon_get_object_manager (object->daemon); objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (object_manager)); for (l = objects; l != NULL; l = l->next) { GDBusObjectSkeleton *iter_object = G_DBUS_OBJECT_SKELETON (l->data); StoragedBlock *block; StoragedLinuxDevice *device; gboolean is_disk; if (!STORAGED_IS_LINUX_BLOCK_OBJECT (iter_object)) continue; device = storaged_linux_block_object_get_device (STORAGED_LINUX_BLOCK_OBJECT (iter_object)); is_disk = (g_strcmp0 (g_udev_device_get_devtype (device->udev_device), "disk") == 0); g_object_unref (device); if (!is_disk) continue; block = storaged_object_peek_block (STORAGED_OBJECT (iter_object)); if (g_strcmp0 (storaged_block_get_drive (block), g_dbus_object_get_object_path (G_DBUS_OBJECT (object))) == 0) { ret = g_object_ref (iter_object); goto out; } } out: g_list_foreach (objects, (GFunc) g_object_unref, NULL); g_list_free (objects); return ret; }
static gboolean swapspace_check (StoragedObject *object) { StoragedLinuxBlockObject *block_object = STORAGED_LINUX_BLOCK_OBJECT (object); gboolean ret; StoragedMountType mount_type; ret = FALSE; if ((g_strcmp0 (storaged_block_get_id_usage (block_object->iface_block_device), "other") == 0 && g_strcmp0 (storaged_block_get_id_type (block_object->iface_block_device), "swap") == 0) || (storaged_mount_monitor_is_dev_in_use (block_object->mount_monitor, g_udev_device_get_device_number (block_object->device->udev_device), &mount_type) && mount_type == STORAGED_MOUNT_TYPE_SWAP)) ret = TRUE; return ret; }
static gboolean bcache_block_check (StoragedObject *object) { const gchar *devname = NULL; StoragedLinuxDevice *device = NULL; gboolean rval = FALSE; g_return_val_if_fail (STORAGED_IS_LINUX_BLOCK_OBJECT (object), FALSE); /* Check device name */ device = storaged_linux_block_object_get_device (STORAGED_LINUX_BLOCK_OBJECT (object)); devname = g_strdup (g_udev_device_get_device_file (device->udev_device)); rval = g_str_has_prefix (devname, "/dev/bcache"); g_free ((gpointer) devname); return rval; }
static void storaged_linux_block_object_get_property (GObject *__object, guint prop_id, GValue *value, GParamSpec *pspec) { StoragedLinuxBlockObject *object = STORAGED_LINUX_BLOCK_OBJECT (__object); switch (prop_id) { case PROP_DAEMON: g_value_set_object (value, storaged_linux_block_object_get_daemon (object)); break; case PROP_DEVICE: g_value_set_object (value, storaged_linux_block_object_get_device (object)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } }
static gboolean partition_check (StoragedObject *object) { StoragedLinuxBlockObject *block_object = STORAGED_LINUX_BLOCK_OBJECT (object); gboolean ret = FALSE; /* could be partitioned by the kernel */ if (g_strcmp0 (g_udev_device_get_devtype (block_object->device->udev_device), "partition") == 0) { ret = TRUE; goto out; } /* if blkid(8) already identified the device as a partition, it's all good */ if (g_udev_device_has_property (block_object->device->udev_device, "ID_PART_ENTRY_SCHEME")) { ret = TRUE; goto out; } out: return ret; }
static gboolean handle_volume_group_create (StoragedManagerLVM2 *_object, GDBusMethodInvocation *invocation, const gchar *arg_name, const gchar *const *arg_blocks, GVariant *arg_options) { StoragedLinuxManagerLVM2 *manager = STORAGED_LINUX_MANAGER_LVM2(_object); uid_t caller_uid; GError *error = NULL; const gchar *message; const gchar *action_id; GList *blocks = NULL; GList *l; guint n; gchar *escaped_name = NULL; GString *str = NULL; gint status; gchar *error_message = NULL; StoragedObject *group_object = NULL; error = NULL; if (!storaged_daemon_util_get_caller_uid_sync (manager->daemon, invocation, NULL /* GCancellable */, &caller_uid, NULL, NULL, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); g_clear_error (&error); goto out; } message = N_("Authentication is required to create a volume group"); action_id = "org.storaged.Storaged.lvm2.manage-lvm"; if (!storaged_daemon_util_check_authorization_sync (manager->daemon, NULL, action_id, arg_options, message, invocation)) goto out; /* Collect and validate block objects * * Also, check we can open the block devices at the same time - this * is to avoid start deleting half the block devices while the other * half is already in use. */ for (n = 0; arg_blocks != NULL && arg_blocks[n] != NULL; n++) { StoragedObject *object = NULL; StoragedBlock *block = NULL; object = storaged_daemon_find_object (manager->daemon, arg_blocks[n]); if (object == NULL) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Invalid object path %s at index %u", arg_blocks[n], n); goto out; } block = storaged_object_get_block (object); if (block == NULL) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Object path %s for index %u is not a block device", arg_blocks[n], n); goto out; } if (!storaged_daemon_util_lvm2_block_is_unused (block, &error)) { g_dbus_method_invocation_take_error (invocation, error); goto out; } blocks = g_list_prepend (blocks, block); /* adopts ownership */ g_object_unref (object); } blocks = g_list_reverse (blocks); /* wipe existing devices */ for (l = blocks; l != NULL; l = l->next) { if (!storaged_daemon_util_lvm2_wipe_block (manager->daemon, STORAGED_BLOCK (l->data), &error)) { g_dbus_method_invocation_take_error (invocation, error); goto out; } } /* Create the volume group... */ escaped_name = storaged_daemon_util_escape_and_quote (arg_name); str = g_string_new ("vgcreate"); g_string_append_printf (str, " %s", escaped_name); for (l = blocks; l != NULL; l = l->next) { StoragedBlock *block = STORAGED_BLOCK (l->data); gchar *escaped_device; escaped_device = storaged_daemon_util_escape_and_quote (storaged_block_get_device (block)); g_string_append_printf (str, " %s", escaped_device); g_free (escaped_device); } if (!storaged_daemon_launch_spawned_job_sync (manager->daemon, NULL, "lvm-vg-create", caller_uid, NULL, /* cancellable */ 0, /* uid_t run_as_uid */ 0, /* uid_t run_as_euid */ &status, &error_message, NULL, /* input_string */ "%s", str->str)) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Error creating volume group: %s", error_message); g_free (error_message); goto out; } for (l = blocks; l != NULL; l = l->next) { StoragedBlock *block = STORAGED_BLOCK (l->data); StoragedObject *object_for_block; object_for_block = storaged_daemon_util_dup_object (block, &error); if (object_for_block != NULL) storaged_linux_block_object_trigger_uevent (STORAGED_LINUX_BLOCK_OBJECT (object_for_block)); g_object_unref (object_for_block); } /* ... then, sit and wait for the object to show up */ group_object = storaged_daemon_wait_for_object_sync (manager->daemon, wait_for_volume_group_object, (gpointer) arg_name, NULL, 10, /* timeout_seconds */ &error); if (group_object == NULL) { g_prefix_error (&error, "Error waiting for volume group object for %s", arg_name); g_dbus_method_invocation_take_error (invocation, error); goto out; } storaged_manager_lvm2_complete_volume_group_create (_object, invocation, g_dbus_object_get_object_path (G_DBUS_OBJECT (group_object))); out: if (str != NULL) g_string_free (str, TRUE); g_list_free_full (blocks, g_object_unref); g_free (escaped_name); return TRUE; /* returning TRUE means that we handled the method invocation */ }
static void update_with_variant (GPid pid, GVariant *info, GError *error, gpointer user_data) { StoragedLinuxVolumeGroupObject *object = user_data; StoragedDaemon *daemon; GDBusObjectManagerServer *manager; GVariantIter *iter; GHashTableIter volume_iter; gpointer key, value; GHashTable *new_lvs; GHashTable *new_pvs; GList *objects, *l; gboolean needs_polling = FALSE; daemon = storaged_linux_volume_group_object_get_daemon (object); manager = storaged_daemon_get_object_manager (daemon); if (error) { storaged_warning ("Failed to update LVM volume group %s: %s", storaged_linux_volume_group_object_get_name (object), error->message); g_object_unref (object); return; } storaged_linux_volume_group_update (STORAGED_LINUX_VOLUME_GROUP (object->iface_volume_group), info, &needs_polling); if (!g_dbus_object_manager_server_is_exported (manager, G_DBUS_OBJECT_SKELETON (object))) g_dbus_object_manager_server_export_uniquely (manager, G_DBUS_OBJECT_SKELETON (object)); new_lvs = g_hash_table_new (g_str_hash, g_str_equal); if (g_variant_lookup (info, "lvs", "aa{sv}", &iter)) { GVariant *lv_info = NULL; while (g_variant_iter_loop (iter, "@a{sv}", &lv_info)) { const gchar *name; StoragedLinuxLogicalVolumeObject *volume; g_variant_lookup (lv_info, "name", "&s", &name); update_operations (daemon, name, lv_info, &needs_polling); if (lv_is_pvmove_volume (name)) needs_polling = TRUE; if (storaged_daemon_util_lvm2_name_is_reserved (name)) continue; volume = g_hash_table_lookup (object->logical_volumes, name); if (volume == NULL) { volume = storaged_linux_logical_volume_object_new (daemon, object, name); storaged_linux_logical_volume_object_update (volume, lv_info, &needs_polling); storaged_linux_logical_volume_object_update_etctabs (volume); g_dbus_object_manager_server_export_uniquely (manager, G_DBUS_OBJECT_SKELETON (volume)); g_hash_table_insert (object->logical_volumes, g_strdup (name), g_object_ref (volume)); } else storaged_linux_logical_volume_object_update (volume, lv_info, &needs_polling); g_hash_table_insert (new_lvs, (gchar *)name, volume); } g_variant_iter_free (iter); } g_hash_table_iter_init (&volume_iter, object->logical_volumes); while (g_hash_table_iter_next (&volume_iter, &key, &value)) { const gchar *name = key; StoragedLinuxLogicalVolumeObject *volume = value; if (!g_hash_table_contains (new_lvs, name)) { g_dbus_object_manager_server_unexport (manager, g_dbus_object_get_object_path (G_DBUS_OBJECT (volume))); g_hash_table_iter_remove (&volume_iter); g_object_unref (G_OBJECT (volume)); } } storaged_volume_group_set_needs_polling (STORAGED_VOLUME_GROUP (object->iface_volume_group), needs_polling); /* Update block objects. */ new_pvs = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_variant_unref); if (g_variant_lookup (info, "pvs", "aa{sv}", &iter)) { const gchar *name; GVariant *pv_info; while (g_variant_iter_next (iter, "@a{sv}", &pv_info)) { if (g_variant_lookup (pv_info, "device", "&s", &name)) g_hash_table_insert (new_pvs, (gchar *)name, pv_info); else g_variant_unref (pv_info); } } objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (manager)); for (l = objects; l != NULL; l = l->next) { if (STORAGED_IS_LINUX_BLOCK_OBJECT (l->data)) update_block (STORAGED_LINUX_BLOCK_OBJECT (l->data), object, new_lvs, new_pvs); } g_list_free_full (objects, g_object_unref); g_hash_table_destroy (new_lvs); g_hash_table_destroy (new_pvs); g_object_unref (object); }
static gboolean handle_mdraid_create (StoragedManager *_object, GDBusMethodInvocation *invocation, const gchar *const *arg_blocks, const gchar *arg_level, const gchar *arg_name, guint64 arg_chunk, GVariant *arg_options) { StoragedLinuxManager *manager = STORAGED_LINUX_MANAGER (_object); StoragedObject *array_object = NULL; uid_t caller_uid; GError *error = NULL; const gchar *message; const gchar *action_id; guint num_devices = 0; GList *blocks = NULL; GList *l; guint n; gchar *escaped_name = NULL; GString *str = NULL; gint status; gchar *error_message = NULL; gchar *raid_device_file = NULL; struct stat statbuf; dev_t raid_device_num; error = NULL; if (!storaged_daemon_util_get_caller_uid_sync (manager->daemon, invocation, NULL /* GCancellable */, &caller_uid, NULL, NULL, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); g_clear_error (&error); goto out; } /* Translators: Shown in authentication dialog when the user * attempts to start a RAID Array. */ /* TODO: variables */ message = N_("Authentication is required to create a RAID array"); action_id = "org.storaged.Storaged.manage-md-raid"; if (!storaged_daemon_util_check_authorization_sync (manager->daemon, NULL, action_id, arg_options, message, invocation)) goto out; /* validate level */ for (n = 0; raid_level_whitelist[n] != NULL; n++) { if (g_strcmp0 (raid_level_whitelist[n], arg_level) == 0) break; } if (raid_level_whitelist[n] == NULL) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Unsupported RAID level %s", arg_level); goto out; } /* validate chunk (TODO: check that it's a power of 2) */ if ((arg_chunk & 0x0fff) != 0) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Chunk %" G_GUINT64_FORMAT " is not a multiple of 4KiB", arg_chunk); goto out; } /* validate name */ if (g_strcmp0 (arg_level, "raid1") == 0 && arg_chunk != 0) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Chunk must be zero for level 'raid1'"); goto out; } /* validate name */ if (strlen (arg_name) > 32) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Name is invalid"); goto out; } num_devices = g_strv_length ((gchar **) arg_blocks); /* validate number of devices */ if (num_devices < 2) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Must have at least two devices"); goto out; } /* Collect and validate block objects * * Also, check we can open the block devices at the same time - this * is to avoid start deleting half the block devices while the other * half is already in use. */ for (n = 0; arg_blocks != NULL && arg_blocks[n] != NULL; n++) { StoragedObject *object = NULL; StoragedBlock *block = NULL; gchar *device_file = NULL; int fd; object = storaged_daemon_find_object (manager->daemon, arg_blocks[n]); if (object == NULL) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Invalid object path %s at index %u", arg_blocks[n], n); goto out; } block = storaged_object_get_block (object); if (block == NULL) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Object path %s for index %u is not a block device", arg_blocks[n], n); goto out; } device_file = storaged_block_dup_device (block); fd = open (device_file, O_RDWR | O_EXCL); if (fd < 0) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Error opening device %s: %m", device_file); g_free (device_file); goto out; } close (fd); g_free (device_file); blocks = g_list_prepend (blocks, block); /* adopts ownership */ g_object_unref (object); } blocks = g_list_reverse (blocks); /* wipe existing devices */ for (l = blocks; l != NULL; l = l->next) { StoragedBlock *block = STORAGED_BLOCK (l->data); StoragedObject *object_for_block; gchar *escaped_device; object_for_block = storaged_daemon_util_dup_object (block, &error); if (object_for_block == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); g_clear_error (&error); goto out; } escaped_device = storaged_daemon_util_escape (storaged_block_get_device (block)); if (!storaged_daemon_launch_spawned_job_sync (manager->daemon, object_for_block, "format-erase", caller_uid, NULL, /* cancellable */ 0, /* uid_t run_as_uid */ 0, /* uid_t run_as_euid */ &status, &error_message, NULL, /* input_string */ "wipefs -a \"%s\"", escaped_device)) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Error wiping device %s to be used in a RAID array: %s", storaged_block_get_device (block), error_message); g_free (error_message); g_object_unref (object_for_block); g_free (escaped_device); goto out; } g_object_unref (object_for_block); g_free (escaped_device); } /* Create the array... */ escaped_name = storaged_daemon_util_escape (arg_name); str = g_string_new ("mdadm"); raid_device_file = storaged_daemon_util_get_free_mdraid_device (); if (raid_device_file == NULL) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Unable to find free MD device"); goto out; } g_string_append_printf (str, " --create %s", raid_device_file); g_string_append_printf (str, " --run"); if (arg_chunk > 0) g_string_append_printf (str, " --chunk %" G_GUINT64_FORMAT, (guint64) (arg_chunk / 1024LL)); g_string_append_printf (str, " --level %s", arg_level); if (strlen (arg_name) > 0) g_string_append_printf (str, " --name \"%s\"", escaped_name); g_string_append_printf (str, " --raid-devices %u", num_devices); for (l = blocks; l != NULL; l = l->next) { StoragedBlock *block = STORAGED_BLOCK (l->data); gchar *escaped_device; escaped_device = storaged_daemon_util_escape (storaged_block_get_device (block)); g_string_append_printf (str, " \"%s\"", escaped_device); g_free (escaped_device); } if (!storaged_daemon_launch_spawned_job_sync (manager->daemon, NULL, "mdraid-create", caller_uid, NULL, /* cancellable */ 0, /* uid_t run_as_uid */ 0, /* uid_t run_as_euid */ &status, &error_message, NULL, /* input_string */ "%s", str->str)) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Error creating RAID array: %s", error_message); g_free (error_message); goto out; } /* ... then, sit and wait for raid array object to show up */ array_object = storaged_daemon_wait_for_object_sync (manager->daemon, wait_for_array_object, raid_device_file, NULL, 10, /* timeout_seconds */ &error); if (array_object == NULL) { g_prefix_error (&error, "Error waiting for array object after creating %s", raid_device_file); g_dbus_method_invocation_take_error (invocation, error); goto out; } if (stat (raid_device_file, &statbuf) != 0) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Error calling stat(2) on %s: %m", raid_device_file); goto out; } if (!S_ISBLK (statbuf.st_mode)) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Device file %s is not a block device", raid_device_file); goto out; } raid_device_num = statbuf.st_rdev; /* update the mdraid file */ storaged_state_add_mdraid (storaged_daemon_get_state (manager->daemon), raid_device_num, caller_uid); /* ... wipe the created RAID array */ if (!storaged_daemon_launch_spawned_job_sync (manager->daemon, array_object, "format-erase", caller_uid, NULL, /* cancellable */ 0, /* uid_t run_as_uid */ 0, /* uid_t run_as_euid */ &status, &error_message, NULL, /* input_string */ "wipefs -a %s", raid_device_file)) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Error wiping raid device %s: %s", raid_device_file, error_message); goto out; } /* ... finally trigger uevents on the members - we want this so the * udev database is updated for them with e.g. ID_FS_TYPE. Ideally * mdadm(8) or whatever thing is writing out the RAID metadata would * ensure this, but that's not how things currently work :-/ */ for (l = blocks; l != NULL; l = l->next) { StoragedBlock *block = STORAGED_BLOCK (l->data); StoragedObject *object_for_block; object_for_block = storaged_daemon_util_dup_object (block, &error); if (object_for_block == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); g_clear_error (&error); goto out; } storaged_linux_block_object_trigger_uevent (STORAGED_LINUX_BLOCK_OBJECT (object_for_block)); g_object_unref (object_for_block); } /* ... and, we're done! */ storaged_manager_complete_mdraid_create (_object, invocation, g_dbus_object_get_object_path (G_DBUS_OBJECT (array_object))); out: g_free (raid_device_file); if (str != NULL) g_string_free (str, TRUE); g_list_free_full (blocks, g_object_unref); g_free (escaped_name); g_clear_object (&array_object); return TRUE; /* returning TRUE means that we handled the method invocation */ }
static StoragedObject * wait_for_loop_object (StoragedDaemon *daemon, gpointer user_data) { WaitForLoopData *data = user_data; StoragedObject *ret = NULL; StoragedObject *object = NULL; StoragedBlock *block; StoragedLoop *loop; StoragedLinuxDevice *device = NULL; GDir *dir; /* First see if we have the right loop object */ object = storaged_daemon_find_block_by_device_file (daemon, data->loop_device); if (object == NULL) goto out; block = storaged_object_peek_block (object); loop = storaged_object_peek_loop (object); if (block == NULL || loop == NULL) goto out; if (g_strcmp0 (storaged_loop_get_backing_file (loop), data->path) != 0) goto out; /* We also need to wait for all partitions to be in place in case * the loop device is partitioned... we can do it like this because * we are guaranteed that partitions are in sysfs when receiving the * uevent for the main block device... */ device = storaged_linux_block_object_get_device (STORAGED_LINUX_BLOCK_OBJECT (object)); if (device == NULL) goto out; dir = g_dir_open (g_udev_device_get_sysfs_path (device->udev_device), 0 /* flags */, NULL /* GError */); if (dir != NULL) { const gchar *name; const gchar *device_name; device_name = g_udev_device_get_name (device->udev_device); while ((name = g_dir_read_name (dir)) != NULL) { if (g_str_has_prefix (name, device_name)) { gchar *sysfs_path; StoragedObject *partition_object; sysfs_path = g_strconcat (g_udev_device_get_sysfs_path (device->udev_device), "/", name, NULL); partition_object = storaged_daemon_find_block_by_sysfs_path (daemon, sysfs_path); if (partition_object == NULL) { /* nope, not there, bail */ g_free (sysfs_path); g_dir_close (dir); goto out; } g_object_unref (partition_object); g_free (sysfs_path); } } g_dir_close (dir); } /* all, good return the loop object */ ret = g_object_ref (object); out: g_clear_object (&object); g_clear_object (&device); return ret; }
static StoragedObject * storaged_linux_partition_table_handle_create_partition (StoragedPartitionTable *table, GDBusMethodInvocation *invocation, guint64 offset, guint64 size, const gchar *type, const gchar *name, GVariant *options) { const gchar *action_id = NULL; const gchar *message = NULL; StoragedBlock *block = NULL; StoragedObject *object = NULL; StoragedDaemon *daemon = NULL; gchar *error_message = NULL; gchar *escaped_device = NULL; gchar *command_line = NULL; WaitForPartitionData *wait_data = NULL; StoragedObject *partition_object = NULL; StoragedBlock *partition_block = NULL; gchar *escaped_partition_device = NULL; const gchar *table_type; uid_t caller_uid; gid_t caller_gid; gboolean do_wipe = TRUE; GError *error; error = NULL; object = storaged_daemon_util_dup_object (table, &error); if (object == NULL) { g_dbus_method_invocation_take_error (invocation, error); goto out; } daemon = storaged_linux_block_object_get_daemon (STORAGED_LINUX_BLOCK_OBJECT (object)); block = storaged_object_get_block (object); if (block == NULL) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Partition table object is not a block device"); goto out; } error = NULL; if (!storaged_daemon_util_get_caller_uid_sync (daemon, invocation, NULL /* GCancellable */, &caller_uid, &caller_gid, NULL, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); goto out; } action_id = "org.storaged.Storaged.modify-device"; /* Translators: Shown in authentication dialog when the user * requests creating a new partition. * * Do not translate $(drive), it's a placeholder and * will be replaced by the name of the drive/device in question */ message = N_("Authentication is required to create a partition on $(drive)"); if (!storaged_daemon_util_setup_by_user (daemon, object, caller_uid)) { if (storaged_block_get_hint_system (block)) { action_id = "org.storaged.Storaged.modify-device-system"; } else if (!storaged_daemon_util_on_user_seat (daemon, object, caller_uid)) { action_id = "org.storaged.Storaged.modify-device-other-seat"; } } if (!storaged_daemon_util_check_authorization_sync (daemon, object, action_id, options, message, invocation)) goto out; escaped_device = storaged_daemon_util_escape_and_quote (storaged_block_get_device (block)); table_type = storaged_partition_table_get_type_ (table); wait_data = g_new0 (WaitForPartitionData, 1); if (g_strcmp0 (table_type, "dos") == 0) { guint64 start_mib; guint64 end_bytes; guint64 max_end_bytes; const gchar *part_type; char *endp; gint type_as_int; gboolean is_logical = FALSE; max_end_bytes = storaged_block_get_size (block); if (strlen (name) > 0) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "MBR partition table does not support names"); goto out; } /* Determine whether we are creating a primary, extended or logical partition */ type_as_int = strtol (type, &endp, 0); if (type[0] != '\0' && *endp == '\0' && (type_as_int == 0x05 || type_as_int == 0x0f || type_as_int == 0x85)) { part_type = "extended"; do_wipe = FALSE; // wiping an extended partition destroys it if (have_partition_in_range (table, object, offset, offset + size, FALSE)) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Requested range is already occupied by a partition"); goto out; } } else { if (have_partition_in_range (table, object, offset, offset + size, FALSE)) { if (have_partition_in_range (table, object, offset, offset + size, TRUE)) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Requested range is already occupied by a partition"); goto out; } else { StoragedPartition *container = find_container_partition (table, object, offset, offset + size); g_assert (container != NULL); is_logical = TRUE; part_type = "logical ext2"; max_end_bytes = (storaged_partition_get_offset(container) + storaged_partition_get_size(container)); } } else { part_type = "primary ext2"; } } /* Ensure we _start_ at MiB granularity since that ensures optimal IO... * Also round up size to nearest multiple of 512 */ start_mib = offset / MIB_SIZE + 1L; end_bytes = start_mib * MIB_SIZE + ((size + 511L) & (~511L)); /* Now reduce size until we are not * * - overlapping neighboring partitions; or * - exceeding the end of the disk */ while (end_bytes > start_mib * MIB_SIZE && (have_partition_in_range (table, object, start_mib * MIB_SIZE, end_bytes, is_logical) || end_bytes > max_end_bytes)) { /* TODO: if end_bytes is sufficiently big this could be *a lot* of loop iterations * and thus a potential DoS attack... */ end_bytes -= 512L; } wait_data->pos_to_wait_for = (start_mib*MIB_SIZE + end_bytes) / 2L; wait_data->ignore_container = is_logical; command_line = g_strdup_printf ("parted --align optimal --script %s " "\"mkpart %s %" G_GUINT64_FORMAT "MiB %" G_GUINT64_FORMAT "b\"", escaped_device, part_type, start_mib, end_bytes - 1); /* end_bytes is *INCLUSIVE* (!) */ } else if (g_strcmp0 (table_type, "gpt") == 0) { guint64 start_mib; guint64 end_bytes; gchar *escaped_name; gchar *escaped_escaped_name; /* GPT is easy, no extended/logical crap */ if (have_partition_in_range (table, object, offset, offset + size, FALSE)) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Requested range is already occupied by a partition"); goto out; } /* bah, parted(8) is broken with empty names (it sets the name to 'ext2' in that case) * TODO: file bug */ if (strlen (name) == 0) { name = " "; } escaped_name = storaged_daemon_util_escape (name); escaped_escaped_name = storaged_daemon_util_escape (escaped_name); /* Ensure we _start_ at MiB granularity since that ensures optimal IO... * Also round up size to nearest multiple of 512 */ start_mib = offset / MIB_SIZE + 1L; end_bytes = start_mib * MIB_SIZE + ((size + 511L) & (~511L)); /* Now reduce size until we are not * * - overlapping neighboring partitions; or * - exceeding the end of the disk (note: the 33 LBAs is the Secondary GPT) */ while (end_bytes > start_mib * MIB_SIZE && (have_partition_in_range (table, object, start_mib * MIB_SIZE, end_bytes, FALSE) || (end_bytes > storaged_block_get_size (block) - 33*512))) { /* TODO: if end_bytes is sufficiently big this could be *a lot* of loop iterations * and thus a potential DoS attack... */ end_bytes -= 512L; } wait_data->pos_to_wait_for = (start_mib*MIB_SIZE + end_bytes) / 2L; command_line = g_strdup_printf ("parted --align optimal --script %s " "\"mkpart \\\"%s\\\" ext2 %" G_GUINT64_FORMAT "MiB %" G_GUINT64_FORMAT "b\"", escaped_device, escaped_escaped_name, start_mib, end_bytes - 1); /* end_bytes is *INCLUSIVE* (!) */ g_free (escaped_escaped_name); g_free (escaped_name); } else { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Don't know how to create partitions this partition table of type `%s'", table_type); goto out; } if (!storaged_daemon_launch_spawned_job_sync (daemon, object, "partition-create", caller_uid, NULL, /* GCancellable */ 0, /* uid_t run_as_uid */ 0, /* uid_t run_as_euid */ NULL, /* gint *out_status */ &error_message, NULL, /* input_string */ "%s", command_line)) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Error creating partition on %s: %s", storaged_block_get_device (block), error_message); goto out; } /* this is sometimes needed because parted(8) does not generate the uevent itself */ storaged_linux_block_object_trigger_uevent (STORAGED_LINUX_BLOCK_OBJECT (object)); /* sit and wait for the partition to show up */ g_warn_if_fail (wait_data->pos_to_wait_for > 0); wait_data->partition_table_object = object; error = NULL; partition_object = storaged_daemon_wait_for_object_sync (daemon, wait_for_partition, wait_data, NULL, 30, &error); if (partition_object == NULL) { g_prefix_error (&error, "Error waiting for partition to appear: "); g_dbus_method_invocation_take_error (invocation, error); goto out; } partition_block = storaged_object_get_block (partition_object); if (partition_block == NULL) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Partition object is not a block device"); g_clear_object (&partition_object); goto out; } escaped_partition_device = storaged_daemon_util_escape_and_quote (storaged_block_get_device (partition_block)); /* TODO: set partition type */ /* wipe the newly created partition if wanted */ if (do_wipe) { if (!storaged_daemon_launch_spawned_job_sync (daemon, partition_object, "partition-create", caller_uid, NULL, /* GCancellable */ 0, /* uid_t run_as_uid */ 0, /* uid_t run_as_euid */ NULL, /* gint *out_status */ &error_message, NULL, /* input_string */ "wipefs -a %s", escaped_partition_device)) { g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED, "Error wiping newly created partition %s: %s", storaged_block_get_device (partition_block), error_message); g_clear_object (&partition_object); goto out; } } /* this is sometimes needed because parted(8) does not generate the uevent itself */ storaged_linux_block_object_trigger_uevent (STORAGED_LINUX_BLOCK_OBJECT (partition_object)); out: g_free (escaped_partition_device); g_free (wait_data); g_clear_object (&partition_block); g_free (command_line); g_free (escaped_device); g_free (error_message); g_clear_object (&object); g_clear_object (&block); return partition_object; }