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 * 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; }