Add a method to create LVM2 Logical Volumes
authorDavid Zeuthen <davidz@redhat.com>
Tue, 12 Jan 2010 21:03:13 +0000 (16:03 -0500)
committerDavid Zeuthen <davidz@redhat.com>
Tue, 12 Jan 2010 21:03:13 +0000 (16:03 -0500)
data/org.freedesktop.UDisks.xml
src/daemon.h
src/device.c

index 7db6ff3..0696070 100644 (file)
         <doc:doc><doc:summary>The UUID of the logical volume to remove.</doc:summary></doc:doc>
       </arg>
       <arg name="options" direction="in" type="as">
-        <doc:doc><doc:summary>Options for remove the logical volume. Currently no options are supported.</doc:summary></doc:doc>
+        <doc:doc><doc:summary>Options used for the removal of the logical volume. Currently no options are supported.</doc:summary></doc:doc>
       </arg>
 
       <doc:doc>
 
     <!-- ************************************************************ -->
 
+    <method name="LinuxLvm2LVCreate">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg name="group_uuid" direction="in" type="s">
+        <doc:doc><doc:summary>The UUID of the volume group to create a logical volume in.</doc:summary></doc:doc>
+      </arg>
+      <arg name="name" direction="in" type="s">
+        <doc:doc><doc:summary>The name for the logical volume.</doc:summary></doc:doc>
+      </arg>
+      <arg name="size" direction="in" type="t">
+        <doc:doc><doc:summary>The size of the logical volume, in bytes.</doc:summary></doc:doc>
+      </arg>
+      <arg name="num_stripes" direction="in" type="u">
+        <doc:doc><doc:summary>Number of stripes to use.</doc:summary></doc:doc>
+      </arg>
+      <arg name="stripe_size" direction="in" type="t">
+        <doc:doc><doc:summary>The stripe size to use or 0 if @num_stripes is 0. This must be a power of two.</doc:summary></doc:doc>
+      </arg>
+      <arg name="num_mirrors" direction="in" type="u">
+        <doc:doc><doc:summary>Number of mirrors to use.</doc:summary></doc:doc>
+      </arg>
+      <arg name="options" direction="in" type="as">
+        <doc:doc><doc:summary>Options used when creating the logical volume. Currently no options are supported.</doc:summary></doc:doc>
+      </arg>
+      <arg name="fstype" direction="in" type="s">
+        <doc:doc><doc:summary>
+            The file system to create in new logical filesystem. Leave
+            blank to skip creating a file system. See the
+            <doc:ref type="method" to="Device.FilesystemCreate">Device.FilesystemCreate()</doc:ref> method
+            for details.
+        </doc:summary></doc:doc>
+      </arg>
+      <arg name="fsoptions" direction="in" type="as">
+        <doc:doc><doc:summary>
+            Options to use for file system creation. See the
+            <doc:ref type="method" to="Device.FilesystemCreate">Device.FilesystemCreate()</doc:ref> method
+            for details.
+        </doc:summary></doc:doc>
+      </arg>
+      <arg name="created_device" direction="out" type="o">
+        <doc:doc><doc:summary>The object path of the newly added logical volume.</doc:summary></doc:doc>
+      </arg>
+
+      <doc:doc>
+        <doc:description>
+          <doc:para>
+            Creates a new LVM2 logical volume.
+          </doc:para>
+        </doc:description>
+        <doc:permission>
+          The caller will need the following PolicyKit authorization:
+          <doc:list>
+            <doc:item>
+              <doc:term>org.freedesktop.udisks.linux-lvm2</doc:term>
+              <doc:definition>
+                Needed to configured Linux LVM2 devices.
+              </doc:definition>
+            </doc:item>
+          </doc:list>
+        </doc:permission>
+        <doc:errors>
+          <doc:error name="&ERROR_NOT_AUTHORIZED;">if the caller lacks the appropriate PolicyKit authorization</doc:error>
+          <doc:error name="&ERROR_BUSY;">if one of the given components are busy</doc:error>
+          <doc:error name="&ERROR_FAILED;">if the operation failed</doc:error>
+          <doc:error name="&ERROR_CANCELLED;">if the job was cancelled</doc:error>
+        </doc:errors>
+      </doc:doc>
+    </method>
+
+    <!-- ************************************************************ -->
+
     <method name="LinuxMdStart">
       <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
       <arg name="components" direction="in" type="ao">
index 026dd6a..ad7177a 100644 (file)
@@ -253,6 +253,18 @@ gboolean daemon_linux_lvm2_lv_remove (Daemon *daemon,
                                       char **options,
                                       DBusGMethodInvocation *context);
 
+gboolean daemon_linux_lvm2_lv_create (Daemon *daemon,
+                                      const gchar *group_uuid,
+                                      const gchar *name,
+                                      guint64 size,
+                                      guint num_stripes,
+                                      guint64 stripe_size,
+                                      guint num_mirrors,
+                                      char **options,
+                                      char *fstype,
+                                      char **fsoptions,
+                                      DBusGMethodInvocation *context);
+
 G_END_DECLS
 
 #endif /* __DAEMON_H__ */
index 3c92579..28ecd79 100644 (file)
@@ -11645,7 +11645,6 @@ daemon_linux_lvm2_vg_stop_authorized_cb (Daemon *daemon,
   /* Unfortunately vgchange does not (yet - file a bug) accept UUIDs - so find the VG name for this
    * UUID by looking at PVs
    */
-
   vg_name = find_lvm2_vg_name_for_uuid (daemon, uuid);
   if (vg_name == NULL)
     {
@@ -12338,3 +12337,359 @@ daemon_linux_lvm2_lv_remove (Daemon *daemon,
 
   return TRUE;
 }
+
+/*--------------------------------------------------------------------------------------------------------------*/
+
+typedef struct
+{
+  int refcount;
+
+  guint device_added_signal_handler_id;
+  guint device_added_timeout_id;
+
+  DBusGMethodInvocation *context;
+  Daemon *daemon;
+  gchar *vg_uuid;
+  gchar *lv_name;
+
+  char *fstype;
+  char **fsoptions;
+
+} CreateLvm2LVData;
+
+static CreateLvm2LVData *
+lvm2_lv_create_data_new (DBusGMethodInvocation *context,
+                         Daemon *daemon,
+                         const gchar *vg_uuid,
+                         const gchar *lv_name,
+                         const char *fstype,
+                         char **fsoptions)
+{
+  CreateLvm2LVData *data;
+
+  data = g_new0 (CreateLvm2LVData, 1);
+  data->refcount = 1;
+
+  data->context = context;
+  data->daemon = g_object_ref (daemon);
+  data->vg_uuid = g_strdup (vg_uuid);
+  data->lv_name = g_strdup (lv_name);
+  data->fstype = g_strdup (fstype);
+  data->fsoptions = g_strdupv (fsoptions);
+
+  return data;
+}
+
+static CreateLvm2LVData *
+lvm2_lv_create_data_ref (CreateLvm2LVData *data)
+{
+  data->refcount++;
+  return data;
+}
+
+static void
+lvm2_lv_create_data_unref (CreateLvm2LVData *data)
+{
+  data->refcount--;
+  if (data->refcount == 0)
+    {
+      g_object_unref (data->daemon);
+      g_free (data->vg_uuid);
+      g_free (data->lv_name);
+      g_free (data->fstype);
+      g_strfreev (data->fsoptions);
+      g_free (data);
+    }
+}
+
+static void
+lvm2_lv_create_filesystem_create_hook (DBusGMethodInvocation *context,
+                                       Device *device,
+                                       gboolean filesystem_create_succeeded,
+                                       gpointer user_data)
+{
+  if (!filesystem_create_succeeded)
+    {
+      /* dang.. FilesystemCreate already reported an error */
+    }
+  else
+    {
+      /* it worked.. */
+      dbus_g_method_return (context, device->priv->object_path);
+    }
+}
+
+static void
+lvm2_lv_create_found_device (Device *device,
+                             CreateLvm2LVData *data)
+{
+  if (strlen (data->fstype) > 0)
+    {
+      device_filesystem_create_internal (device,
+                                         data->fstype,
+                                         data->fsoptions,
+                                         lvm2_lv_create_filesystem_create_hook,
+                                         NULL,
+                                         data->context);
+    }
+  else
+    {
+      dbus_g_method_return (data->context, device->priv->object_path);
+    }
+}
+
+static void
+lvm2_lv_create_device_added_cb (Daemon *daemon,
+                                const char *object_path,
+                                gpointer user_data)
+{
+  CreateLvm2LVData *data = user_data;
+  Device *device;
+
+  /* check the device added is the partition we've created */
+  device = daemon_local_find_by_object_path (daemon, object_path);
+
+  /*  g_debug ("Looking at %d `%s' `%s'.\n"
+           "Want          `%s' `%s'.\n"
+           "------------------------------------\n",
+           device->priv->device_is_linux_lvm2_lv, device->priv->linux_lvm2_lv_group_uuid, device->priv->linux_lvm2_lv_name,
+           data->vg_uuid, data->lv_name);
+  */
+
+  if (device != NULL &&
+      device->priv->device_is_linux_lvm2_lv &&
+      g_strcmp0 (device->priv->linux_lvm2_lv_group_uuid, data->vg_uuid) == 0 &&
+      g_strcmp0 (device->priv->linux_lvm2_lv_name, data->lv_name) == 0)
+    {
+      /* yay! it is.. now create the file system if requested */
+      lvm2_lv_create_found_device (device, data);
+
+      g_signal_handler_disconnect (daemon, data->device_added_signal_handler_id);
+      g_source_remove (data->device_added_timeout_id);
+      lvm2_lv_create_data_unref (data);
+    }
+}
+
+static gboolean
+lvm2_lv_create_device_not_seen_cb (gpointer user_data)
+{
+  CreateLvm2LVData *data = user_data;
+
+  throw_error (data->context,
+               ERROR_FAILED,
+               "Error creating Logical Volume: timeout (10s) waiting for LV to show up");
+
+  g_signal_handler_disconnect (data->daemon, data->device_added_signal_handler_id);
+  lvm2_lv_create_data_unref (data);
+
+  return FALSE;
+}
+
+static void
+linux_lvm2_lv_create_completed_cb (DBusGMethodInvocation *context,
+                                   Device *device,
+                                   gboolean job_was_cancelled,
+                                   int status,
+                                   const char *stderr,
+                                   const char *stdout,
+                                   gpointer user_data)
+{
+  CreateLvm2LVData *data = user_data;
+
+  if (WEXITSTATUS (status) == 0 && !job_was_cancelled)
+    {
+      gboolean found_device;
+      GList *devices;
+      GList *l;
+
+      /* check if the device is already there */
+      found_device = FALSE;
+      devices = daemon_local_get_all_devices (data->daemon);
+      for (l = devices; l != NULL; l = l->next)
+        {
+          Device *d = DEVICE (l->data);
+
+          /*
+            g_debug ("Looking at %d `%s' `%s'.\n"
+                   "Want          `%s' `%s'.\n"
+                   "------------------------------------\n",
+                   d->priv->device_is_linux_lvm2_lv, d->priv->linux_lvm2_lv_group_uuid, d->priv->linux_lvm2_lv_name,
+                   data->vg_uuid, data->lv_name);
+          */
+
+          if (d->priv->device_is_linux_lvm2_lv &&
+              g_strcmp0 (d->priv->linux_lvm2_lv_group_uuid, data->vg_uuid) == 0 &&
+              g_strcmp0 (d->priv->linux_lvm2_lv_name, data->lv_name) == 0)
+            {
+              /* yay! it is.. now create the file system if requested */
+              lvm2_lv_create_found_device (d, data);
+              found_device = TRUE;
+              break;
+            }
+        }
+
+      if (!found_device)
+        {
+          /* otherwise sit around and wait for the new partition to appear */
+          data->device_added_signal_handler_id =
+            g_signal_connect_after (data->daemon,
+                                    "device-added",
+                                    G_CALLBACK (lvm2_lv_create_device_added_cb),
+                                    lvm2_lv_create_data_ref (data));
+
+          /* set up timeout for error reporting if waiting failed
+           *
+           * (the signal handler and the timeout handler share the ref to data
+           * as one will cancel the other)
+           */
+          data->device_added_timeout_id = g_timeout_add (10 * 1000,
+                                                         lvm2_lv_create_device_not_seen_cb,
+                                                         data);
+        }
+    }
+  else
+    {
+      if (job_was_cancelled)
+        {
+          throw_error (context, ERROR_CANCELLED, "Job was cancelled");
+        }
+      else
+        {
+          throw_error (context,
+                       ERROR_FAILED,
+                       "Error creating LVM2 Logical Volume: lvcreate exited with exit code %d: %s",
+                       WEXITSTATUS (status),
+                       stderr);
+        }
+    }
+}
+
+static void
+daemon_linux_lvm2_lv_create_authorized_cb (Daemon *daemon,
+                                           Device *device,
+                                           DBusGMethodInvocation *context,
+                                           const gchar *action_id,
+                                           guint num_user_data,
+                                           gpointer *user_data_elements)
+{
+  const gchar *group_uuid = user_data_elements[0];
+  const gchar *name = user_data_elements[1];
+  guint64 size = *((guint64 *) user_data_elements[2]);
+  guint num_stripes = GPOINTER_TO_UINT (user_data_elements[3]);
+  guint64 stripe_size = *((guint64 *) user_data_elements[4]);
+  guint num_mirrors = GPOINTER_TO_UINT (user_data_elements[5]);
+  /* TODO: use options: gchar **options = user_data_elements[6]; */
+  const gchar *fstype = user_data_elements[7];
+  gchar **fsoptions = user_data_elements[8];
+  const gchar *vg_name;
+  gchar **argv;
+  GString *s;
+
+  argv = NULL;
+  s = NULL;
+
+  /* Unfortunately lvcreate does not (yet - file a bug) accept UUIDs - so find the VG name for this
+   * UUID by looking at PVs
+   */
+  vg_name = find_lvm2_vg_name_for_uuid (daemon, group_uuid);
+  if (vg_name == NULL)
+    {
+      throw_error (context, ERROR_FAILED, "Cannot find VG with UUID `%s'", group_uuid);
+      goto out;
+    }
+
+  if (name == NULL || strlen (name) == 0)
+    {
+      throw_error (context, ERROR_FAILED, "Name cannot be blank");
+      goto out;
+    }
+
+  if (strstr (name, "\"") != NULL)
+    {
+      throw_error (context, ERROR_FAILED, "Name cannot contain the double-quote (\") character");
+      goto out;
+    }
+
+  s = g_string_new ("lvcreate ");
+
+  g_string_append_printf (s, "%s ", vg_name);
+  if (num_stripes > 0)
+    g_string_append_printf (s, "--stripes %d ", num_stripes);
+  if (stripe_size > 0)
+    g_string_append_printf (s, "--stripesize %" G_GUINT64_FORMAT " ", stripe_size);
+  if (num_mirrors > 0)
+    g_string_append_printf (s, "--mirrors %d ", num_mirrors);
+
+  size &= (~511);
+  g_string_append_printf (s, "--size %" G_GUINT64_FORMAT "b ", size);
+  if (name != NULL && strlen (name) > 0)
+    g_string_append_printf (s, "--name \"%s\"", name);
+
+  if (!g_shell_parse_argv (s->str, NULL, &argv, NULL))
+    {
+      throw_error (context, ERROR_FAILED, "Unable to parse command line `%s'", s->str);
+      goto out;
+    }
+
+  if (!job_new (context,
+                "LinuxLvm2LVCreate",
+                TRUE,
+                NULL,
+                argv,
+                NULL,
+                linux_lvm2_lv_create_completed_cb,
+                FALSE,
+                lvm2_lv_create_data_new (context, daemon, group_uuid, name, fstype, fsoptions),
+                (GDestroyNotify) lvm2_lv_create_data_unref))
+    {
+      goto out;
+    }
+
+ out:
+  if (s != NULL)
+    g_string_free (s, FALSE);
+  g_strfreev (argv);
+}
+
+gboolean
+daemon_linux_lvm2_lv_create (Daemon *daemon,
+                             const gchar *group_uuid,
+                             const gchar *name,
+                             guint64 size,
+                             guint num_stripes,
+                             guint64 stripe_size,
+                             guint num_mirrors,
+                             gchar **options,
+                             char *fstype,
+                             char **fsoptions,
+                             DBusGMethodInvocation *context)
+{
+  daemon_local_check_auth (daemon,
+                           NULL,
+                           "org.freedesktop.udisks.linux-lvm2",
+                           "LinuxLvm2LVCreate",
+                           TRUE,
+                           daemon_linux_lvm2_lv_create_authorized_cb,
+                           context,
+                           9,
+                           g_strdup (group_uuid),
+                           g_free,
+                           g_strdup (name),
+                           g_free,
+                           g_memdup (&size, sizeof (guint64)),
+                           g_free,
+                           GUINT_TO_POINTER (num_stripes),
+                           NULL,
+                           g_memdup (&stripe_size, sizeof (guint64)),
+                           g_free,
+                           GUINT_TO_POINTER (num_mirrors),
+                           NULL,
+                           g_strdupv (options),
+                           g_strfreev,
+                           g_strdup (fstype),
+                           g_free,
+                           g_strdupv (fsoptions),
+                           g_strfreev);
+
+  return TRUE;
+}