Add PartitionTable.CreatePartition() method
authorDavid Zeuthen <davidz@redhat.com>
Mon, 7 Nov 2011 20:19:11 +0000 (15:19 -0500)
committerDavid Zeuthen <davidz@redhat.com>
Mon, 7 Nov 2011 20:22:07 +0000 (15:22 -0500)
Right now it only works for 'gpt' (because parted(8) expects different
formats depending on the partition table type (!)) and it doesn't yet
set the partition type. Apart from that it works great.

Signed-off-by: David Zeuthen <davidz@redhat.com>
data/org.freedesktop.UDisks2.xml
src/udiskslinuxpartitiontable.c

index b742530..288a43b 100644 (file)
          scheme is unknown.
     -->
     <property name="Type" type="s" access="read"/>
+
+    <!--
+        CreatePartition:
+        @offset: The desired offset where the partition should be created, in bytes.
+        @size: The desired size of the partition, in bytes.
+        @type: The type of partition to create (cf. the #org.freedesktop.UDisks2.Partition:Type property) or blank to use the default for the partition table type and OS.
+        @name: The name for the new partition or blank if the partition table do not support names.
+        @options: Options (currently unused except for <link linkend="udisks-std-options">standard options</link>).
+        @created_partition: An object path to the created block device object implementing the #org.freedesktop.UDisks2.Partition interface.
+
+        Creates a new partition.
+
+        Note that the created partition won't necessarily be created
+        at the exact @offset due to disk geometry and other alignment
+        constraints. The new partition may also end up being slightly
+        larger or smaller than the requested @size bytes for the same
+        reasons.
+
+        The newly created partition will be wiped of known filesystem
+        signatures using the
+        <citerefentry><refentrytitle>wipefs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        command.
+    -->
+    <method name="CreatePartition">
+      <arg name="offset" direction="in" type="t"/>
+      <arg name="size" direction="in" type="t"/>
+      <arg name="type" direction="in" type="s"/>
+      <arg name="name" direction="in" type="s"/>
+      <arg name="options" direction="in" type="a{sv}"/>
+      <arg name="created_partition" direction="out" type="o"/>
+    </method>
   </interface>
 
   <!-- ********************************************************************** -->
index f52d2f2..940a10f 100644 (file)
@@ -123,7 +123,346 @@ udisks_linux_partition_table_update (UDisksLinuxPartitionTable  *table,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static gboolean
+ranges_overlap (guint64 a_offset, guint64 a_size,
+                guint64 b_offset, guint64 b_size)
+{
+  guint64 a1 = a_offset, a2 = a_offset + a_size;
+  guint64 b1 = b_offset, b2 = b_offset + b_size;
+  gboolean ret = FALSE;
+
+  /* There are only two cases when these intervals can overlap
+   *
+   * 1.  [a1-------a2]
+   *               [b1------b2]
+   *
+   * 2.            [a1-------a2]
+   *     [b1------b2]
+   */
+
+  if (a1 <= b1)
+    {
+      /* case 1 */
+      if (a2 > b1)
+        {
+          ret = TRUE;
+          goto out;
+        }
+    }
+  else
+    {
+      /* case 2 */
+      if (b2 > a1)
+        {
+          ret = TRUE;
+          goto out;
+        }
+    }
+
+ out:
+  return ret;
+}
+
+static gboolean
+have_partition_in_range (UDisksPartitionTable     *table,
+                         guint64                   start,
+                         guint64                   end)
+{
+  gboolean ret = FALSE;
+  UDisksObject *object = NULL;
+  UDisksDaemon *daemon = NULL;
+  GDBusObjectManager *object_manager = NULL;
+  const gchar *table_object_path;
+  GList *objects = NULL, *l;
+
+  object = g_object_ref (UDISKS_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (table))));
+  daemon = udisks_linux_block_object_get_daemon (UDISKS_LINUX_BLOCK_OBJECT (object));
+  object_manager = G_DBUS_OBJECT_MANAGER (udisks_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)
+    {
+      UDisksObject *i_object = UDISKS_OBJECT (l->data);
+      UDisksPartition *i_partition = NULL;
+
+      i_partition = udisks_object_get_partition (i_object);
+
+      if (i_partition == NULL)
+        goto cont;
+
+      if (g_strcmp0 (udisks_partition_get_table (i_partition), table_object_path) != 0)
+        goto cont;
+
+      if (!ranges_overlap (start, end - start,
+                           udisks_partition_get_offset (i_partition), udisks_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);
+  g_clear_object (&object);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  UDisksObject *partition_table_object;
+  guint64       pos_to_wait_for;
+} WaitForPartitionData;
+
+static gboolean
+wait_for_partition (UDisksDaemon *daemon,
+                    UDisksObject *object,
+                    gpointer      user_data)
+{
+  WaitForPartitionData *data = user_data;
+  gboolean ret = FALSE;
+  UDisksPartition *partition = NULL;
+  guint64 offset;
+  guint64 size;
+
+  partition = udisks_object_get_partition (object);
+  if (partition == NULL)
+    goto out;
+
+  if (g_strcmp0 (udisks_partition_get_table (partition),
+                 g_dbus_object_get_object_path (G_DBUS_OBJECT (data->partition_table_object))) != 0)
+    goto out;
+
+  offset = udisks_partition_get_offset (partition);
+  size = udisks_partition_get_size (partition);
+
+  if (data->pos_to_wait_for >= offset &&
+      data->pos_to_wait_for < offset + size)
+    {
+      ret = TRUE;
+    }
+
+ out:
+  g_clear_object (&partition);
+  return ret;
+}
+
+#define MIB_SIZE (1048576L)
+
+/* runs in thread dedicated to handling @invocation */
+static gboolean
+handle_create_partition (UDisksPartitionTable   *table,
+                         GDBusMethodInvocation  *invocation,
+                         guint64                 offset,
+                         guint64                 size,
+                         const gchar            *type,
+                         const gchar            *name,
+                         GVariant               *options)
+{
+  const gchar *action_id = NULL;
+  UDisksBlock *block = NULL;
+  UDisksObject *object = NULL;
+  UDisksDaemon *daemon = NULL;
+  gchar *error_message = NULL;
+  gchar *escaped_device = NULL;
+  gchar *command_line = NULL;
+  WaitForPartitionData *wait_data = NULL;
+  UDisksObject *partition_object = NULL;
+  UDisksBlock *partition_block = NULL;
+  gchar *escaped_partition_device = NULL;
+  const gchar *table_type;
+  GError *error;
+
+  object = g_object_ref (UDISKS_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (table))));
+  daemon = udisks_linux_block_object_get_daemon (UDISKS_LINUX_BLOCK_OBJECT (object));
+  block = udisks_object_get_block (object);
+  if (block == NULL)
+    {
+      g_dbus_method_invocation_return_error (invocation, UDISKS_ERROR, UDISKS_ERROR_FAILED,
+                                             "Partition table object is not a block device");
+      goto out;
+    }
+
+  if (have_partition_in_range (table, offset, offset + size))
+    {
+      g_dbus_method_invocation_return_error (invocation, UDISKS_ERROR, UDISKS_ERROR_FAILED,
+                                             "Requested range is already occupied by a partition");
+      goto out;
+    }
+
+  action_id = "org.freedesktop.udisks2.modify-device";
+  if (udisks_block_get_hint_system (block))
+    action_id = "org.freedesktop.udisks2.modify-device-system";
+  if (!udisks_daemon_util_check_authorization_sync (daemon,
+                                                    object,
+                                                    action_id,
+                                                    options,
+                                                    N_("Authentication is required to create a partition on $(udisks2.device)"),
+                                                    invocation))
+    goto out;
+
+  escaped_device = g_strescape (udisks_block_get_device (block), NULL);
+
+  table_type = udisks_partition_table_get_type_ (table);
+  wait_data = g_new0 (WaitForPartitionData, 1);
+#if 0
+  if (g_strcmp0 (table_type, "dos") == 0)
+    {
+      /* TODO: handle logical */
+      command_line = g_strdup_printf ("parted --align optimal --script \"%s\" "
+                                      "\"mkpart primary ext2 %" G_GUINT64_FORMAT "M %" G_GUINT64_FORMAT "M\"",
+                                      escaped_device,
+                                      offset / (1000L*1000L),
+                                      (offset + size) / (1000L*1000L));
+    }
+#endif
+  if (g_strcmp0 (table_type, "gpt") == 0)
+    {
+      guint64 start_mib;
+      guint64 end_mib;
+      gchar *escaped_name;
+      gchar *escaped_escaped_name;
+
+      /* bah, parted(8) is broken with empty names (it sets the name to 'ext2' in that case) */
+      if (strlen (name) == 0)
+        name = " ";
+
+      escaped_name = g_strescape (name, NULL);
+      escaped_escaped_name = g_strescape (escaped_name, NULL);
+
+      /* round to MiB for alignment purposes and round up so we _start_ and _end_ at MiB spots */
+      start_mib = offset / MIB_SIZE + 1L;
+      end_mib = start_mib + (size / MIB_SIZE + 1L);
+      /* reduce size until we are not overlapping
+       *  - neighboring partitions (leave 1MiB wiggle room at the end); or
+       *  - the end of the disk
+       */
+      while (end_mib > start_mib && (have_partition_in_range (table,
+                                                              start_mib * MIB_SIZE,
+                                                              (end_mib + 1) * MIB_SIZE) ||
+                                     (end_mib * MIB_SIZE > udisks_block_get_size (block))))
+        {
+          end_mib -= 1L;
+        }
+
+      wait_data->pos_to_wait_for = (start_mib + end_mib) * MIB_SIZE / 2L;
+      command_line = g_strdup_printf ("parted --align optimal --script \"%s\" "
+                                      "\"mkpart \\\"%s\\\" ext2 %" G_GUINT64_FORMAT "MiB %" G_GUINT64_FORMAT "MiB\"",
+                                      escaped_device,
+                                      escaped_escaped_name,
+                                      start_mib,
+                                      end_mib);
+      g_free (escaped_escaped_name);
+      g_free (escaped_name);
+    }
+  else
+    {
+      g_dbus_method_invocation_return_error (invocation, UDISKS_ERROR, UDISKS_ERROR_FAILED,
+                                             "Don't know how to create partitions this partition table of type `%s'",
+                                             table_type);
+      goto out;
+    }
+
+  if (!udisks_daemon_launch_spawned_job_sync (daemon,
+                                              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,
+                                             UDISKS_ERROR,
+                                             UDISKS_ERROR_FAILED,
+                                             "Error creating partition on %s: %s",
+                                             udisks_block_get_device (block),
+                                             error_message);
+      goto out;
+    }
+  /* this is sometimes needed because parted(8) does not generate the uevent itself */
+  udisks_linux_block_object_trigger_uevent (UDISKS_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 = udisks_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 = udisks_object_get_block (partition_object);
+  if (partition_block == NULL)
+    {
+      g_dbus_method_invocation_return_error (invocation, UDISKS_ERROR, UDISKS_ERROR_FAILED,
+                                             "Partition object is not a block device");
+      goto out;
+    }
+  escaped_partition_device = g_strescape (udisks_block_get_device (partition_block), NULL);
+
+  /* TODO: set partition type */
+
+  /* wipe the newly created partition */
+  if (!udisks_daemon_launch_spawned_job_sync (daemon,
+                                              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,
+                                             UDISKS_ERROR,
+                                             UDISKS_ERROR_FAILED,
+                                             "Error wiping newly created partition %s: %s",
+                                             udisks_block_get_device (partition_block),
+                                             error_message);
+      goto out;
+    }
+  /* this is sometimes needed because parted(8) does not generate the uevent itself */
+  udisks_linux_block_object_trigger_uevent (UDISKS_LINUX_BLOCK_OBJECT (partition_object));
+
+
+  udisks_partition_table_complete_create_partition (table,
+                                                    invocation,
+                                                    g_dbus_object_get_object_path (G_DBUS_OBJECT (partition_object)));
+
+ out:
+  g_free (escaped_partition_device);
+  g_free (wait_data);
+  g_clear_object (&partition_block);
+  g_clear_object (&partition_object);
+  g_free (command_line);
+  g_free (escaped_device);
+  g_free (error_message);
+  g_clear_object (&object);
+  g_clear_object (&block);
+  return TRUE; /* returning TRUE means that we handled the method invocation */
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 static void
 partition_table_iface_init (UDisksPartitionTableIface *iface)
 {
+  iface->handle_create_partition = handle_create_partition;
 }