[gnome-disk-utility] First cut at LVM2 support



commit 32d5198c223dd7a9aa48daa8a0a27918dafdad80
Author: David Zeuthen <davidz redhat com>
Date:   Mon Jan 11 17:58:53 2010 -0500

    First cut at LVM2 support

 src/gdu-gtk/gdu-volume-grid.c                      |  128 ++-
 src/gdu/Makefile.am                                |    5 +
 src/gdu/gdu-callbacks.h                            |   17 +
 src/gdu/gdu-device.c                               | 1125 ++++++++++++--------
 src/gdu/gdu-device.h                               |   23 +
 src/gdu/gdu-linux-lvm2-volume-group.c              |  561 ++++++++++
 src/gdu/gdu-linux-lvm2-volume-group.h              |   70 ++
 src/gdu/gdu-linux-lvm2-volume-hole.c               |  331 ++++++
 src/gdu/gdu-linux-lvm2-volume-hole.h               |   61 ++
 src/gdu/gdu-linux-lvm2-volume.c                    |  430 ++++++++
 src/gdu/gdu-linux-lvm2-volume.h                    |   63 ++
 src/gdu/gdu-pool.c                                 |  240 +++++-
 src/gdu/gdu-pool.h                                 |   16 +
 src/gdu/gdu-private.h                              |   23 +
 src/gdu/gdu-types.h                                |    3 +
 src/gdu/gdu-volume-hole.c                          |    3 +-
 src/gdu/gdu.h                                      |    3 +
 src/palimpsest/Makefile.am                         |    1 +
 .../gdu-section-linux-lvm2-volume-group.c          |  383 +++++++
 .../gdu-section-linux-lvm2-volume-group.h          |   58 +
 src/palimpsest/gdu-section-volumes.c               |  210 ++++-
 src/palimpsest/gdu-shell.c                         |    5 +
 22 files changed, 3248 insertions(+), 511 deletions(-)
---
diff --git a/src/gdu-gtk/gdu-volume-grid.c b/src/gdu-gtk/gdu-volume-grid.c
index de72344..ecaebc4 100644
--- a/src/gdu-gtk/gdu-volume-grid.c
+++ b/src/gdu-gtk/gdu-volume-grid.c
@@ -1211,18 +1211,20 @@ render_element (GduVolumeGrid *grid,
                 cairo_show_text (cr, text);
 
         } else { /* render descriptive text + icons for the presentable */
-                gchar *s;
-                gchar *s1;
+                GString *str;
                 cairo_text_extents_t te;
-                cairo_text_extents_t te1;
                 GduDevice *d;
                 gdouble text_height;
+                gdouble y;
                 gboolean render_padlock_closed;
                 gboolean render_padlock_open;
                 gboolean render_job_in_progress;
                 GPtrArray *pixbufs_to_render;
                 guint icon_offset;
                 guint n;
+                guint64 size;
+                gchar *size_str;
+                gchar **lines;
 
                 render_padlock_closed = FALSE;
                 render_padlock_open = FALSE;
@@ -1233,37 +1235,47 @@ render_element (GduVolumeGrid *grid,
                 if (d != NULL && gdu_device_job_in_progress (d))
                         render_job_in_progress = TRUE;
 
-                s = NULL;
-                s1 = NULL;
+                str = g_string_new (NULL);
+
+                size = gdu_presentable_get_size (element->presentable);
+                size_str = gdu_util_get_size_for_display (size,
+                                                          FALSE,
+                                                          FALSE);
+
                 if (d != NULL && g_strcmp0 (gdu_device_id_get_usage (d), "filesystem") == 0) {
                         gchar *fstype_str;
                         gchar *size_str;
 
-                        s = g_strdup (gdu_device_id_get_label (d));
                         fstype_str = gdu_util_get_fstype_for_display (gdu_device_id_get_type (d),
                                                                       gdu_device_id_get_version (d),
                                                                       FALSE);
                         size_str = gdu_util_get_size_for_display (gdu_device_get_size (d),
                                                                   FALSE,
                                                                   FALSE);
-                        s1 = g_strdup_printf ("%s %s", size_str, fstype_str);
+
+                        g_string_append_printf (str, "%s\n", gdu_device_id_get_label (d));
+                        g_string_append_printf (str, "%s %s", size_str, fstype_str);
+
                         g_free (fstype_str);
-                        g_free (size_str);
                 } else if (d != NULL && gdu_device_is_partition (d) &&
                            (g_strcmp0 (gdu_device_partition_get_type (d), "0x05") == 0 ||
                             g_strcmp0 (gdu_device_partition_get_type (d), "0x0f") == 0 ||
                             g_strcmp0 (gdu_device_partition_get_type (d), "0x85") == 0)) {
-                        /* Translators: shown in the grid for an extended MS-DOS partition */
-                        s = g_strdup (_("Extended"));
-                        s1 = gdu_util_get_size_for_display (gdu_presentable_get_size (element->presentable),
-                                                            FALSE,
-                                                            FALSE);
+
+                        g_string_append_printf (str,
+                                                "%s\n%s",
+                                                /* Translators: shown in the grid for an extended MS-DOS partition */
+                                                _("Extended"),
+                                                size_str);
+
                 } else if (d != NULL && g_strcmp0 (gdu_device_id_get_usage (d), "crypto") == 0) {
-                        /* Translators: shown in the grid for an encrypted LUKS volume */
-                        s = g_strdup (_("Encrypted"));
-                        s1 = gdu_util_get_size_for_display (gdu_presentable_get_size (element->presentable),
-                                                            FALSE,
-                                                            FALSE);
+
+                        g_string_append_printf (str,
+                                                "%s\n%s",
+                                                /* Translators: shown in the grid for an encrypted LUKS volume */
+                                                _("Encrypted"),
+                                                size_str);
+
                         if (g_strcmp0 (gdu_device_luks_get_holder (d), "/") == 0) {
                                 render_padlock_closed = TRUE;
                         } else {
@@ -1271,25 +1283,42 @@ render_element (GduVolumeGrid *grid,
                         }
                 } else if (d != NULL && g_strcmp0 (gdu_device_id_get_usage (d), "raid") == 0 &&
                            gdu_device_is_linux_md_component (d)) {
-                        s1 = g_strdup (gdu_device_linux_md_component_get_name (d));
+
+                        g_string_append_printf (str,
+                                                "%s\n%s",
+                                                /* Translators: shown in the grid for an RAID Component */
+                                                _("RAID Component"),
+                                                gdu_device_linux_md_component_get_name (d));
+
                 } else if (!gdu_presentable_is_allocated (element->presentable)) {
-                        /* Translators: shown in the grid for space that is not claimed by any partition */
-                        s = g_strdup (_("Free"));
-                        s1 = gdu_util_get_size_for_display (gdu_presentable_get_size (element->presentable),
-                                                            FALSE,
-                                                            FALSE);
+
+                        g_string_append_printf (str,
+                                                "%s\n%s",
+                                                /* Translators: shown for free/unallocated space */
+                                                _("Free"),
+                                                size_str);
+
                 } else if (!gdu_presentable_is_recognized (element->presentable)) {
-                        /* Translators: shown in the grid when we don't know the purpose/usage of the partition */
-                        s = g_strdup (_("Unknown"));
-                        s1 = gdu_util_get_size_for_display (gdu_presentable_get_size (element->presentable),
-                                                            FALSE,
-                                                            FALSE);
+
+                        g_string_append_printf (str,
+                                                "%s\n%s",
+                                                /* Translators: shown when we don't know contents of the volume */
+                                                _("Unknown"),
+                                                size_str);
+                } else {
+                        gchar *presentable_name;
+
+                        /* FALLBACK */
+                        presentable_name = gdu_presentable_get_name (element->presentable);
+                        if (size > 0) {
+                                g_string_append_printf (str, "%s\n%s", presentable_name, size_str);
+                        } else {
+                                g_string_append (str, presentable_name);
+                        }
+                        g_free (presentable_name);
                 }
+                g_free (size_str);
 
-                if (s == NULL)
-                        s = gdu_presentable_get_name (element->presentable);
-                if (s1 == NULL)
-                        s1 = g_strdup ("");
 
                 if (is_selected) {
                         if (is_grid_focused) {
@@ -1311,21 +1340,28 @@ render_element (GduVolumeGrid *grid,
                                         CAIRO_FONT_WEIGHT_NORMAL);
                 cairo_set_font_size (cr, 8.0);
 
-                cairo_text_extents (cr, s, &te);
-                cairo_text_extents (cr, s1, &te1);
+                lines = g_strsplit (str->str, "\n", 0);
+                g_string_free (str, TRUE);
+                text_height = 0.0;
+                for (n = 0; lines[n] != NULL; n++) {
+                        const gchar *line = lines[n];
+                        cairo_text_extents (cr, line, &te);
+                        text_height += te.height + 4;
+                }
 
-                text_height = te.height + te1.height;
+                y = element->y + element->height/2.0 - text_height/2.0;
+                for (n = 0; lines[n] != NULL; n++) {
+                        const gchar *line = lines[n];
+                        cairo_text_extents (cr, line, &te);
+                        cairo_move_to (cr,
+                                       ceil (element->x + element->width / 2 - te.width/2  - te.x_bearing),
+                                       ceil (y - te.y_bearing));
+                        cairo_show_text (cr, line);
 
-                cairo_move_to (cr,
-                               ceil (element->x + element->width / 2 - te.width/2  - te.x_bearing),
-                               ceil (element->y + element->height / 2 - 2 - text_height/2 - te.y_bearing));
-                cairo_show_text (cr, s);
-                cairo_move_to (cr,
-                               ceil (element->x + element->width / 2 - te1.width/2  - te1.x_bearing),
-                               ceil (element->y + element->height / 2 + 2 - te1.y_bearing));
-                cairo_show_text (cr, s1);
-                g_free (s);
-                g_free (s1);
+                        y += te.height + 4;
+                }
+
+                g_strfreev (lines);
 
                 /* OK, done with the text - now render spinner and icons */
                 icon_offset = 0;
diff --git a/src/gdu/Makefile.am b/src/gdu/Makefile.am
index 7c4f404..d9dcbb1 100644
--- a/src/gdu/Makefile.am
+++ b/src/gdu/Makefile.am
@@ -43,6 +43,8 @@ libgduinclude_HEADERS =              			\
 	gdu-port.h					\
 	gdu-drive.h					\
 	gdu-linux-md-drive.h				\
+	gdu-linux-lvm2-volume-group.h			\
+	gdu-linux-lvm2-volume.h				\
 	gdu-error.h					\
 	gdu-known-filesystem.h				\
 	gdu-pool.h					\
@@ -67,6 +69,9 @@ libgdu_la_SOURCES =                                					\
 	gdu-port.c				gdu-port.h				\
 	gdu-drive.c				gdu-drive.h				\
 	gdu-linux-md-drive.c			gdu-linux-md-drive.h			\
+	gdu-linux-lvm2-volume-group.c		gdu-linux-lvm2-volume-group.h		\
+	gdu-linux-lvm2-volume.c			gdu-linux-lvm2-volume.h			\
+	gdu-linux-lvm2-volume-hole.c		gdu-linux-lvm2-volume-hole.h		\
 	gdu-volume.c				gdu-volume.h				\
 	gdu-presentable.c			gdu-presentable.h			\
 	gdu-volume-hole.c			gdu-volume-hole.h			\
diff --git a/src/gdu/gdu-callbacks.h b/src/gdu/gdu-callbacks.h
index 4217806..e5dcd36 100644
--- a/src/gdu/gdu-callbacks.h
+++ b/src/gdu/gdu-callbacks.h
@@ -147,6 +147,10 @@ typedef void (*GduDeviceDriveBenchmarkCompletedFunc) (GduDevice    *device,
                                                       GError       *error,
                                                       gpointer      user_data);
 
+typedef void (*GduDeviceLinuxLvm2LVStopCompletedFunc) (GduDevice  *device,
+                                                       GError     *error,
+                                                       gpointer    user_data);
+
 /* ---------------------------------------------------------------------------------------------------- */
 /* GduPool */
 
@@ -160,6 +164,18 @@ typedef void (*GduPoolLinuxMdCreateCompletedFunc) (GduPool    *pool,
                                                    GError     *error,
                                                    gpointer    user_data);
 
+typedef void (*GduPoolLinuxLvm2VGStartCompletedFunc) (GduPool    *pool,
+                                                      GError     *error,
+                                                      gpointer    user_data);
+
+typedef void (*GduPoolLinuxLvm2VGStopCompletedFunc) (GduPool    *pool,
+                                                     GError     *error,
+                                                     gpointer    user_data);
+
+typedef void (*GduPoolLinuxLvm2LVStartCompletedFunc) (GduPool    *pool,
+                                                      GError     *error,
+                                                      gpointer    user_data);
+
 /* ---------------------------------------------------------------------------------------------------- */
 /* GduDrive */
 
@@ -172,6 +188,7 @@ typedef void (*GduDriveDeactivateFunc) (GduDrive  *drive,
                                         GError    *error,
                                         gpointer   user_data);
 
+
 /* ---------------------------------------------------------------------------------------------------- */
 
 G_END_DECLS
diff --git a/src/gdu/gdu-device.c b/src/gdu/gdu-device.c
index 970c63d..c906440 100644
--- a/src/gdu/gdu-device.c
+++ b/src/gdu/gdu-device.c
@@ -44,483 +44,550 @@
 
 typedef struct
 {
-        char *native_path;
-
-        guint64  device_detection_time;
-        guint64  device_media_detection_time;
-        gint64   device_major;
-        gint64   device_minor;
-        char    *device_file;
-        char   **device_file_by_id;
-        char   **device_file_by_path;
-        gboolean device_is_system_internal;
-        gboolean device_is_partition;
-        gboolean device_is_partition_table;
-        gboolean device_is_removable;
-        gboolean device_is_media_available;
-        gboolean device_is_media_change_detected;
-        gboolean device_is_media_change_detection_polling;
-        gboolean device_is_media_change_detection_inhibitable;
-        gboolean device_is_media_change_detection_inhibited;
-        gboolean device_is_read_only;
-        gboolean device_is_drive;
-        gboolean device_is_optical_disc;
-        gboolean device_is_luks;
-        gboolean device_is_luks_cleartext;
-        gboolean device_is_mounted;
-        gboolean device_is_linux_md_component;
-        gboolean device_is_linux_md;
-        char   **device_mount_paths;
-        uid_t    device_mounted_by_uid;
-        gboolean device_presentation_hide;
-        gboolean device_presentation_nopolicy;
-        char    *device_presentation_name;
-        char    *device_presentation_icon_name;
-        guint64  device_size;
-        guint64  device_block_size;
-
-        gboolean job_in_progress;
-        char    *job_id;
-        uid_t    job_initiated_by_uid;
-        gboolean job_is_cancellable;
-        double   job_percentage;
-
-        char    *id_usage;
-        char    *id_type;
-        char    *id_version;
-        char    *id_uuid;
-        char    *id_label;
-
-        char    *partition_slave;
-        char    *partition_scheme;
-        int      partition_number;
-        char    *partition_type;
-        char    *partition_label;
-        char    *partition_uuid;
-        char   **partition_flags;
-        guint64  partition_offset;
-        guint64  partition_size;
-
-        char    *partition_table_scheme;
-        int      partition_table_count;
-
-        char    *luks_holder;
-
-        char    *luks_cleartext_slave;
-        uid_t    luks_cleartext_unlocked_by_uid;
-
-        char    *drive_vendor;
-        char    *drive_model;
-        char    *drive_revision;
-        char    *drive_serial;
-        char    *drive_wwn;
-        char    *drive_connection_interface;
-        guint64  drive_connection_speed;
-        char   **drive_media_compatibility;
-        char    *drive_media;
-        gboolean drive_is_media_ejectable;
-        gboolean drive_can_detach;
-        gboolean drive_can_spindown;
-        gboolean drive_is_rotational;
-        guint    drive_rotation_rate;
-        char    *drive_write_cache;
-        char    *drive_adapter;
-        char   **drive_ports;
-
-        gboolean optical_disc_is_blank;
-        gboolean optical_disc_is_appendable;
-        gboolean optical_disc_is_closed;
-        guint optical_disc_num_tracks;
-        guint optical_disc_num_audio_tracks;
-        guint optical_disc_num_sessions;
-
-        gboolean drive_ata_smart_is_available;
-        guint64 drive_ata_smart_time_collected;
-        gchar *drive_ata_smart_status;
-        gchar *drive_ata_smart_blob;
-        gsize drive_ata_smart_blob_size;
-
-        char    *linux_md_component_level;
-        int      linux_md_component_position;
-        int      linux_md_component_num_raid_devices;
-        char    *linux_md_component_uuid;
-        char    *linux_md_component_home_host;
-        char    *linux_md_component_name;
-        char    *linux_md_component_version;
-        char    *linux_md_component_holder;
-        char   **linux_md_component_state;
-
-        char    *linux_md_state;
-        char    *linux_md_level;
-        int      linux_md_num_raid_devices;
-        char    *linux_md_uuid;
-        char    *linux_md_home_host;
-        char    *linux_md_name;
-        char    *linux_md_version;
-        char   **linux_md_slaves;
-        gboolean linux_md_is_degraded;
-        char    *linux_md_sync_action;
-        double   linux_md_sync_percentage;
-        guint64  linux_md_sync_speed;
+  char *native_path;
+
+  guint64 device_detection_time;
+  guint64 device_media_detection_time;
+  gint64 device_major;
+  gint64 device_minor;
+  char *device_file;
+  char **device_file_by_id;
+  char **device_file_by_path;
+  gboolean device_is_system_internal;
+  gboolean device_is_partition;
+  gboolean device_is_partition_table;
+  gboolean device_is_removable;
+  gboolean device_is_media_available;
+  gboolean device_is_media_change_detected;
+  gboolean device_is_media_change_detection_polling;
+  gboolean device_is_media_change_detection_inhibitable;
+  gboolean device_is_media_change_detection_inhibited;
+  gboolean device_is_read_only;
+  gboolean device_is_drive;
+  gboolean device_is_optical_disc;
+  gboolean device_is_luks;
+  gboolean device_is_luks_cleartext;
+  gboolean device_is_mounted;
+  gboolean device_is_linux_md_component;
+  gboolean device_is_linux_md;
+  gboolean device_is_linux_lvm2_lv;
+  gboolean device_is_linux_lvm2_pv;
+  char **device_mount_paths;
+  uid_t device_mounted_by_uid;
+  gboolean device_presentation_hide;
+  gboolean device_presentation_nopolicy;
+  char *device_presentation_name;
+  char *device_presentation_icon_name;
+  guint64 device_size;
+  guint64 device_block_size;
+
+  gboolean job_in_progress;
+  char *job_id;
+  uid_t job_initiated_by_uid;
+  gboolean job_is_cancellable;
+  double job_percentage;
+
+  char *id_usage;
+  char *id_type;
+  char *id_version;
+  char *id_uuid;
+  char *id_label;
+
+  char *partition_slave;
+  char *partition_scheme;
+  int partition_number;
+  char *partition_type;
+  char *partition_label;
+  char *partition_uuid;
+  char **partition_flags;
+  guint64 partition_offset;
+  guint64 partition_size;
+
+  char *partition_table_scheme;
+  int partition_table_count;
+
+  char *luks_holder;
+
+  char *luks_cleartext_slave;
+  uid_t luks_cleartext_unlocked_by_uid;
+
+  char *drive_vendor;
+  char *drive_model;
+  char *drive_revision;
+  char *drive_serial;
+  char *drive_wwn;
+  char *drive_connection_interface;
+  guint64 drive_connection_speed;
+  char **drive_media_compatibility;
+  char *drive_media;
+  gboolean drive_is_media_ejectable;
+  gboolean drive_can_detach;
+  gboolean drive_can_spindown;
+  gboolean drive_is_rotational;
+  guint drive_rotation_rate;
+  char *drive_write_cache;
+  char *drive_adapter;
+  char **drive_ports;
+
+  gboolean optical_disc_is_blank;
+  gboolean optical_disc_is_appendable;
+  gboolean optical_disc_is_closed;
+  guint optical_disc_num_tracks;
+  guint optical_disc_num_audio_tracks;
+  guint optical_disc_num_sessions;
+
+  gboolean drive_ata_smart_is_available;
+  guint64 drive_ata_smart_time_collected;
+  gchar *drive_ata_smart_status;
+  gchar *drive_ata_smart_blob;
+  gsize drive_ata_smart_blob_size;
+
+  char *linux_md_component_level;
+  int linux_md_component_position;
+  int linux_md_component_num_raid_devices;
+  char *linux_md_component_uuid;
+  char *linux_md_component_home_host;
+  char *linux_md_component_name;
+  char *linux_md_component_version;
+  char *linux_md_component_holder;
+  char **linux_md_component_state;
+
+  char *linux_md_state;
+  char *linux_md_level;
+  int linux_md_num_raid_devices;
+  char *linux_md_uuid;
+  char *linux_md_home_host;
+  char *linux_md_name;
+  char *linux_md_version;
+  char **linux_md_slaves;
+  gboolean linux_md_is_degraded;
+  char *linux_md_sync_action;
+  double linux_md_sync_percentage;
+  guint64 linux_md_sync_speed;
+
+  gchar *linux_lvm2_lv_name;
+  gchar *linux_lvm2_lv_uuid;
+  gchar *linux_lvm2_lv_group_name;
+  gchar *linux_lvm2_lv_group_uuid;
+
+  gchar *linux_lvm2_pv_uuid;
+  guint  linux_lvm2_pv_num_metadata_areas;
+  gchar *linux_lvm2_pv_group_name;
+  gchar *linux_lvm2_pv_group_uuid;
+  guint64 linux_lvm2_pv_group_size;
+  guint64 linux_lvm2_pv_group_unallocated_size;
+  guint64 linux_lvm2_pv_group_sequence_number;
+  guint64 linux_lvm2_pv_group_extent_size;
+  char **linux_lvm2_pv_group_physical_volumes;
+  char **linux_lvm2_pv_group_logical_volumes;
 } DeviceProperties;
 
 static void
-collect_props (const char *key, const GValue *value, DeviceProperties *props)
-{
-        gboolean handled = TRUE;
-
-        if (strcmp (key, "NativePath") == 0)
-                props->native_path = g_strdup (g_value_get_string (value));
-
-        else if (strcmp (key, "DeviceDetectionTime") == 0)
-                props->device_detection_time = g_value_get_uint64 (value);
-        else if (strcmp (key, "DeviceMediaDetectionTime") == 0)
-                props->device_media_detection_time = g_value_get_uint64 (value);
-        else if (strcmp (key, "DeviceMajor") == 0)
-                props->device_major = g_value_get_int64 (value);
-        else if (strcmp (key, "DeviceMinor") == 0)
-                props->device_minor = g_value_get_int64 (value);
-        else if (strcmp (key, "DeviceFile") == 0)
-                props->device_file = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DeviceFileById") == 0)
-                props->device_file_by_id = g_strdupv (g_value_get_boxed (value));
-        else if (strcmp (key, "DeviceFileByPath") == 0)
-                props->device_file_by_path = g_strdupv (g_value_get_boxed (value));
-        else if (strcmp (key, "DeviceIsSystemInternal") == 0)
-                props->device_is_system_internal = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsPartition") == 0)
-                props->device_is_partition = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsPartitionTable") == 0)
-                props->device_is_partition_table = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsRemovable") == 0)
-                props->device_is_removable = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsMediaAvailable") == 0)
-                props->device_is_media_available = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsMediaChangeDetected") == 0)
-                props->device_is_media_change_detected = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsMediaChangeDetectionPolling") == 0)
-                props->device_is_media_change_detection_polling = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsMediaChangeDetectionInhibitable") == 0)
-                props->device_is_media_change_detection_inhibitable = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsMediaChangeDetectionInhibited") == 0)
-                props->device_is_media_change_detection_inhibited = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsReadOnly") == 0)
-                props->device_is_read_only = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsDrive") == 0)
-                props->device_is_drive = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsOpticalDisc") == 0)
-                props->device_is_optical_disc = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsLuks") == 0)
-                props->device_is_luks = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsLuksCleartext") == 0)
-                props->device_is_luks_cleartext = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsLinuxMdComponent") == 0)
-                props->device_is_linux_md_component = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsLinuxMd") == 0)
-                props->device_is_linux_md = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceIsMounted") == 0)
-                props->device_is_mounted = g_value_get_boolean (value);
-        else if (strcmp (key, "DeviceMountPaths") == 0)
-                props->device_mount_paths = g_strdupv (g_value_get_boxed (value));
-        else if (strcmp (key, "DeviceMountedByUid") == 0)
-                props->device_mounted_by_uid = g_value_get_uint (value);
-        else if (strcmp (key, "DevicePresentationHide") == 0)
-                props->device_presentation_hide = g_value_get_boolean (value);
-        else if (strcmp (key, "DevicePresentationNopolicy") == 0)
-                props->device_presentation_nopolicy = g_value_get_boolean (value);
-        else if (strcmp (key, "DevicePresentationName") == 0)
-                props->device_presentation_name = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DevicePresentationIconName") == 0)
-                props->device_presentation_icon_name = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DeviceSize") == 0)
-                props->device_size = g_value_get_uint64 (value);
-        else if (strcmp (key, "DeviceBlockSize") == 0)
-                props->device_block_size = g_value_get_uint64 (value);
-
-        else if (strcmp (key, "JobInProgress") == 0)
-                props->job_in_progress = g_value_get_boolean (value);
-        else if (strcmp (key, "JobId") == 0)
-                props->job_id = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "JobInitiatedByUid") == 0)
-                props->job_initiated_by_uid = g_value_get_uint (value);
-        else if (strcmp (key, "JobIsCancellable") == 0)
-                props->job_is_cancellable = g_value_get_boolean (value);
-        else if (strcmp (key, "JobPercentage") == 0)
-                props->job_percentage = g_value_get_double (value);
-
-        else if (strcmp (key, "IdUsage") == 0)
-                props->id_usage = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "IdType") == 0)
-                props->id_type = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "IdVersion") == 0)
-                props->id_version = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "IdUuid") == 0)
-                props->id_uuid = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "IdLabel") == 0)
-                props->id_label = g_strdup (g_value_get_string (value));
-
-        else if (strcmp (key, "PartitionSlave") == 0)
-                props->partition_slave = g_strdup (g_value_get_boxed (value));
-        else if (strcmp (key, "PartitionScheme") == 0)
-                props->partition_scheme = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "PartitionNumber") == 0)
-                props->partition_number = g_value_get_int (value);
-        else if (strcmp (key, "PartitionType") == 0)
-                props->partition_type = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "PartitionLabel") == 0)
-                props->partition_label = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "PartitionUuid") == 0)
-                props->partition_uuid = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "PartitionFlags") == 0)
-                props->partition_flags = g_strdupv (g_value_get_boxed (value));
-        else if (strcmp (key, "PartitionOffset") == 0)
-                props->partition_offset = g_value_get_uint64 (value);
-        else if (strcmp (key, "PartitionSize") == 0)
-                props->partition_size = g_value_get_uint64 (value);
-
-        else if (strcmp (key, "PartitionTableScheme") == 0)
-                props->partition_table_scheme = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "PartitionTableCount") == 0)
-                props->partition_table_count = g_value_get_int (value);
-
-        else if (strcmp (key, "LuksHolder") == 0)
-                props->luks_holder = g_strdup (g_value_get_boxed (value));
-
-        else if (strcmp (key, "LuksCleartextSlave") == 0)
-                props->luks_cleartext_slave = g_strdup (g_value_get_boxed (value));
-        else if (strcmp (key, "LuksCleartextUnlockedByUid") == 0)
-                props->luks_cleartext_unlocked_by_uid = g_value_get_uint (value);
-
-        else if (strcmp (key, "DriveVendor") == 0)
-                props->drive_vendor = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DriveModel") == 0)
-                props->drive_model = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DriveRevision") == 0)
-                props->drive_revision = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DriveSerial") == 0)
-                props->drive_serial = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DriveWwn") == 0)
-                props->drive_wwn = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DriveConnectionInterface") == 0)
-                props->drive_connection_interface = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DriveConnectionSpeed") == 0)
-                props->drive_connection_speed = g_value_get_uint64 (value);
-        else if (strcmp (key, "DriveMediaCompatibility") == 0)
-                props->drive_media_compatibility = g_strdupv (g_value_get_boxed (value));
-        else if (strcmp (key, "DriveMedia") == 0)
-                props->drive_media = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DriveIsMediaEjectable") == 0)
-                props->drive_is_media_ejectable = g_value_get_boolean (value);
-        else if (strcmp (key, "DriveCanDetach") == 0)
-                props->drive_can_detach = g_value_get_boolean (value);
-        else if (strcmp (key, "DriveCanSpindown") == 0)
-                props->drive_can_spindown = g_value_get_boolean (value);
-        else if (strcmp (key, "DriveIsRotational") == 0)
-                props->drive_is_rotational = g_value_get_boolean (value);
-        else if (strcmp (key, "DriveRotationRate") == 0)
-                props->drive_rotation_rate = g_value_get_uint (value);
-        else if (strcmp (key, "DriveWriteCache") == 0)
-                props->drive_write_cache = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DriveAdapter") == 0)
-                props->drive_adapter = g_strdup (g_value_get_boxed (value));
-        else if (strcmp (key, "DrivePorts") == 0) {
-                guint n;
-                GPtrArray *object_paths;
-
-                object_paths = g_value_get_boxed (value);
-
-                props->drive_ports = g_new0 (char *, object_paths->len + 1);
-                for (n = 0; n < object_paths->len; n++)
-                        props->drive_ports[n] = g_strdup (object_paths->pdata[n]);
-                props->drive_ports[n] = NULL;
-        }
-
-        else if (strcmp (key, "OpticalDiscIsBlank") == 0)
-                props->optical_disc_is_blank = g_value_get_boolean (value);
-        else if (strcmp (key, "OpticalDiscIsAppendable") == 0)
-                props->optical_disc_is_appendable = g_value_get_boolean (value);
-        else if (strcmp (key, "OpticalDiscIsClosed") == 0)
-                props->optical_disc_is_closed = g_value_get_boolean (value);
-        else if (strcmp (key, "OpticalDiscNumTracks") == 0)
-                props->optical_disc_num_tracks = g_value_get_uint (value);
-        else if (strcmp (key, "OpticalDiscNumAudioTracks") == 0)
-                props->optical_disc_num_audio_tracks = g_value_get_uint (value);
-        else if (strcmp (key, "OpticalDiscNumSessions") == 0)
-                props->optical_disc_num_sessions = g_value_get_uint (value);
-
-        else if (strcmp (key, "DriveAtaSmartIsAvailable") == 0)
-                props->drive_ata_smart_is_available = g_value_get_boolean (value);
-        else if (strcmp (key, "DriveAtaSmartTimeCollected") == 0)
-                props->drive_ata_smart_time_collected = g_value_get_uint64 (value);
-        else if (strcmp (key, "DriveAtaSmartStatus") == 0)
-                props->drive_ata_smart_status = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "DriveAtaSmartBlob") == 0) {
-                GArray *a = g_value_get_boxed (value);
-                g_free (props->drive_ata_smart_blob);
-                props->drive_ata_smart_blob = g_memdup (a->data, a->len);
-                props->drive_ata_smart_blob_size = a->len;
-        }
-
-        else if (strcmp (key, "LinuxMdComponentLevel") == 0)
-                props->linux_md_component_level = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdComponentPosition") == 0)
-                props->linux_md_component_position = g_value_get_int (value);
-        else if (strcmp (key, "LinuxMdComponentNumRaidDevices") == 0)
-                props->linux_md_component_num_raid_devices = g_value_get_int (value);
-        else if (strcmp (key, "LinuxMdComponentUuid") == 0)
-                props->linux_md_component_uuid = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdComponentHomeHost") == 0)
-                props->linux_md_component_home_host = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdComponentName") == 0)
-                props->linux_md_component_name = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdComponentVersion") == 0)
-                props->linux_md_component_version = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdComponentHolder") == 0)
-                props->linux_md_component_holder = g_strdup (g_value_get_boxed (value));
-        else if (strcmp (key, "LinuxMdComponentState") == 0)
-                props->linux_md_component_state = g_strdupv (g_value_get_boxed (value));
-
-        else if (strcmp (key, "LinuxMdState") == 0)
-                props->linux_md_state = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdLevel") == 0)
-                props->linux_md_level = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdNumRaidDevices") == 0)
-                props->linux_md_num_raid_devices = g_value_get_int (value);
-        else if (strcmp (key, "LinuxMdUuid") == 0)
-                props->linux_md_uuid = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdHomeHost") == 0)
-                props->linux_md_home_host = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdName") == 0)
-                props->linux_md_name = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdVersion") == 0)
-                props->linux_md_version = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdSlaves") == 0) {
-                guint n;
-                GPtrArray *object_paths;
-
-                object_paths = g_value_get_boxed (value);
-
-                props->linux_md_slaves = g_new0 (char *, object_paths->len + 1);
-                for (n = 0; n < object_paths->len; n++)
-                        props->linux_md_slaves[n] = g_strdup (object_paths->pdata[n]);
-                props->linux_md_slaves[n] = NULL;
-        }
-        else if (strcmp (key, "LinuxMdIsDegraded") == 0)
-                props->linux_md_is_degraded = g_value_get_boolean (value);
-        else if (strcmp (key, "LinuxMdSyncAction") == 0)
-                props->linux_md_sync_action = g_strdup (g_value_get_string (value));
-        else if (strcmp (key, "LinuxMdSyncPercentage") == 0)
-                props->linux_md_sync_percentage = g_value_get_double (value);
-        else if (strcmp (key, "LinuxMdSyncSpeed") == 0)
-                props->linux_md_sync_speed = g_value_get_uint64 (value);
-
-        else
-                handled = FALSE;
-
-        if (!handled)
-                g_debug ("unhandled property '%s'", key);
+collect_props (const char *key,
+               const GValue *value,
+               DeviceProperties *props)
+{
+  gboolean handled = TRUE;
+
+  if (strcmp (key, "NativePath") == 0)
+    props->native_path = g_strdup (g_value_get_string (value));
+
+  else if (strcmp (key, "DeviceDetectionTime") == 0)
+    props->device_detection_time = g_value_get_uint64 (value);
+  else if (strcmp (key, "DeviceMediaDetectionTime") == 0)
+    props->device_media_detection_time = g_value_get_uint64 (value);
+  else if (strcmp (key, "DeviceMajor") == 0)
+    props->device_major = g_value_get_int64 (value);
+  else if (strcmp (key, "DeviceMinor") == 0)
+    props->device_minor = g_value_get_int64 (value);
+  else if (strcmp (key, "DeviceFile") == 0)
+    props->device_file = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DeviceFileById") == 0)
+    props->device_file_by_id = g_strdupv (g_value_get_boxed (value));
+  else if (strcmp (key, "DeviceFileByPath") == 0)
+    props->device_file_by_path = g_strdupv (g_value_get_boxed (value));
+  else if (strcmp (key, "DeviceIsSystemInternal") == 0)
+    props->device_is_system_internal = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsPartition") == 0)
+    props->device_is_partition = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsPartitionTable") == 0)
+    props->device_is_partition_table = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsRemovable") == 0)
+    props->device_is_removable = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsMediaAvailable") == 0)
+    props->device_is_media_available = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsMediaChangeDetected") == 0)
+    props->device_is_media_change_detected = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsMediaChangeDetectionPolling") == 0)
+    props->device_is_media_change_detection_polling = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsMediaChangeDetectionInhibitable") == 0)
+    props->device_is_media_change_detection_inhibitable = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsMediaChangeDetectionInhibited") == 0)
+    props->device_is_media_change_detection_inhibited = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsReadOnly") == 0)
+    props->device_is_read_only = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsDrive") == 0)
+    props->device_is_drive = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsOpticalDisc") == 0)
+    props->device_is_optical_disc = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsLuks") == 0)
+    props->device_is_luks = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsLuksCleartext") == 0)
+    props->device_is_luks_cleartext = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsLinuxMdComponent") == 0)
+    props->device_is_linux_md_component = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsLinuxMd") == 0)
+    props->device_is_linux_md = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsLinuxLvm2LV") == 0)
+    props->device_is_linux_lvm2_lv = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsLinuxLvm2PV") == 0)
+    props->device_is_linux_lvm2_pv = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceIsMounted") == 0)
+    props->device_is_mounted = g_value_get_boolean (value);
+  else if (strcmp (key, "DeviceMountPaths") == 0)
+    props->device_mount_paths = g_strdupv (g_value_get_boxed (value));
+  else if (strcmp (key, "DeviceMountedByUid") == 0)
+    props->device_mounted_by_uid = g_value_get_uint (value);
+  else if (strcmp (key, "DevicePresentationHide") == 0)
+    props->device_presentation_hide = g_value_get_boolean (value);
+  else if (strcmp (key, "DevicePresentationNopolicy") == 0)
+    props->device_presentation_nopolicy = g_value_get_boolean (value);
+  else if (strcmp (key, "DevicePresentationName") == 0)
+    props->device_presentation_name = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DevicePresentationIconName") == 0)
+    props->device_presentation_icon_name = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DeviceSize") == 0)
+    props->device_size = g_value_get_uint64 (value);
+  else if (strcmp (key, "DeviceBlockSize") == 0)
+    props->device_block_size = g_value_get_uint64 (value);
+
+  else if (strcmp (key, "JobInProgress") == 0)
+    props->job_in_progress = g_value_get_boolean (value);
+  else if (strcmp (key, "JobId") == 0)
+    props->job_id = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "JobInitiatedByUid") == 0)
+    props->job_initiated_by_uid = g_value_get_uint (value);
+  else if (strcmp (key, "JobIsCancellable") == 0)
+    props->job_is_cancellable = g_value_get_boolean (value);
+  else if (strcmp (key, "JobPercentage") == 0)
+    props->job_percentage = g_value_get_double (value);
+
+  else if (strcmp (key, "IdUsage") == 0)
+    props->id_usage = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "IdType") == 0)
+    props->id_type = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "IdVersion") == 0)
+    props->id_version = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "IdUuid") == 0)
+    props->id_uuid = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "IdLabel") == 0)
+    props->id_label = g_strdup (g_value_get_string (value));
+
+  else if (strcmp (key, "PartitionSlave") == 0)
+    props->partition_slave = g_strdup (g_value_get_boxed (value));
+  else if (strcmp (key, "PartitionScheme") == 0)
+    props->partition_scheme = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "PartitionNumber") == 0)
+    props->partition_number = g_value_get_int (value);
+  else if (strcmp (key, "PartitionType") == 0)
+    props->partition_type = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "PartitionLabel") == 0)
+    props->partition_label = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "PartitionUuid") == 0)
+    props->partition_uuid = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "PartitionFlags") == 0)
+    props->partition_flags = g_strdupv (g_value_get_boxed (value));
+  else if (strcmp (key, "PartitionOffset") == 0)
+    props->partition_offset = g_value_get_uint64 (value);
+  else if (strcmp (key, "PartitionSize") == 0)
+    props->partition_size = g_value_get_uint64 (value);
+
+  else if (strcmp (key, "PartitionTableScheme") == 0)
+    props->partition_table_scheme = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "PartitionTableCount") == 0)
+    props->partition_table_count = g_value_get_int (value);
+
+  else if (strcmp (key, "LuksHolder") == 0)
+    props->luks_holder = g_strdup (g_value_get_boxed (value));
+
+  else if (strcmp (key, "LuksCleartextSlave") == 0)
+    props->luks_cleartext_slave = g_strdup (g_value_get_boxed (value));
+  else if (strcmp (key, "LuksCleartextUnlockedByUid") == 0)
+    props->luks_cleartext_unlocked_by_uid = g_value_get_uint (value);
+
+  else if (strcmp (key, "DriveVendor") == 0)
+    props->drive_vendor = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DriveModel") == 0)
+    props->drive_model = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DriveRevision") == 0)
+    props->drive_revision = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DriveSerial") == 0)
+    props->drive_serial = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DriveWwn") == 0)
+    props->drive_wwn = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DriveConnectionInterface") == 0)
+    props->drive_connection_interface = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DriveConnectionSpeed") == 0)
+    props->drive_connection_speed = g_value_get_uint64 (value);
+  else if (strcmp (key, "DriveMediaCompatibility") == 0)
+    props->drive_media_compatibility = g_strdupv (g_value_get_boxed (value));
+  else if (strcmp (key, "DriveMedia") == 0)
+    props->drive_media = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DriveIsMediaEjectable") == 0)
+    props->drive_is_media_ejectable = g_value_get_boolean (value);
+  else if (strcmp (key, "DriveCanDetach") == 0)
+    props->drive_can_detach = g_value_get_boolean (value);
+  else if (strcmp (key, "DriveCanSpindown") == 0)
+    props->drive_can_spindown = g_value_get_boolean (value);
+  else if (strcmp (key, "DriveIsRotational") == 0)
+    props->drive_is_rotational = g_value_get_boolean (value);
+  else if (strcmp (key, "DriveRotationRate") == 0)
+    props->drive_rotation_rate = g_value_get_uint (value);
+  else if (strcmp (key, "DriveWriteCache") == 0)
+    props->drive_write_cache = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DriveAdapter") == 0)
+    props->drive_adapter = g_strdup (g_value_get_boxed (value));
+  else if (strcmp (key, "DrivePorts") == 0)
+    {
+      guint n;
+      GPtrArray *object_paths;
+
+      object_paths = g_value_get_boxed (value);
+
+      props->drive_ports = g_new0 (char *, object_paths->len + 1);
+      for (n = 0; n < object_paths->len; n++)
+        props->drive_ports[n] = g_strdup (object_paths->pdata[n]);
+      props->drive_ports[n] = NULL;
+    }
+
+  else if (strcmp (key, "OpticalDiscIsBlank") == 0)
+    props->optical_disc_is_blank = g_value_get_boolean (value);
+  else if (strcmp (key, "OpticalDiscIsAppendable") == 0)
+    props->optical_disc_is_appendable = g_value_get_boolean (value);
+  else if (strcmp (key, "OpticalDiscIsClosed") == 0)
+    props->optical_disc_is_closed = g_value_get_boolean (value);
+  else if (strcmp (key, "OpticalDiscNumTracks") == 0)
+    props->optical_disc_num_tracks = g_value_get_uint (value);
+  else if (strcmp (key, "OpticalDiscNumAudioTracks") == 0)
+    props->optical_disc_num_audio_tracks = g_value_get_uint (value);
+  else if (strcmp (key, "OpticalDiscNumSessions") == 0)
+    props->optical_disc_num_sessions = g_value_get_uint (value);
+
+  else if (strcmp (key, "DriveAtaSmartIsAvailable") == 0)
+    props->drive_ata_smart_is_available = g_value_get_boolean (value);
+  else if (strcmp (key, "DriveAtaSmartTimeCollected") == 0)
+    props->drive_ata_smart_time_collected = g_value_get_uint64 (value);
+  else if (strcmp (key, "DriveAtaSmartStatus") == 0)
+    props->drive_ata_smart_status = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "DriveAtaSmartBlob") == 0)
+    {
+      GArray *a = g_value_get_boxed (value);
+      g_free (props->drive_ata_smart_blob);
+      props->drive_ata_smart_blob = g_memdup (a->data, a->len);
+      props->drive_ata_smart_blob_size = a->len;
+    }
+
+  else if (strcmp (key, "LinuxMdComponentLevel") == 0)
+    props->linux_md_component_level = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdComponentPosition") == 0)
+    props->linux_md_component_position = g_value_get_int (value);
+  else if (strcmp (key, "LinuxMdComponentNumRaidDevices") == 0)
+    props->linux_md_component_num_raid_devices = g_value_get_int (value);
+  else if (strcmp (key, "LinuxMdComponentUuid") == 0)
+    props->linux_md_component_uuid = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdComponentHomeHost") == 0)
+    props->linux_md_component_home_host = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdComponentName") == 0)
+    props->linux_md_component_name = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdComponentVersion") == 0)
+    props->linux_md_component_version = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdComponentHolder") == 0)
+    props->linux_md_component_holder = g_strdup (g_value_get_boxed (value));
+  else if (strcmp (key, "LinuxMdComponentState") == 0)
+    props->linux_md_component_state = g_strdupv (g_value_get_boxed (value));
+
+  else if (strcmp (key, "LinuxMdState") == 0)
+    props->linux_md_state = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdLevel") == 0)
+    props->linux_md_level = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdNumRaidDevices") == 0)
+    props->linux_md_num_raid_devices = g_value_get_int (value);
+  else if (strcmp (key, "LinuxMdUuid") == 0)
+    props->linux_md_uuid = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdHomeHost") == 0)
+    props->linux_md_home_host = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdName") == 0)
+    props->linux_md_name = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdVersion") == 0)
+    props->linux_md_version = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdSlaves") == 0)
+    {
+      guint n;
+      GPtrArray *object_paths;
+
+      object_paths = g_value_get_boxed (value);
+
+      props->linux_md_slaves = g_new0 (char *, object_paths->len + 1);
+      for (n = 0; n < object_paths->len; n++)
+        props->linux_md_slaves[n] = g_strdup (object_paths->pdata[n]);
+      props->linux_md_slaves[n] = NULL;
+    }
+  else if (strcmp (key, "LinuxMdIsDegraded") == 0)
+    props->linux_md_is_degraded = g_value_get_boolean (value);
+  else if (strcmp (key, "LinuxMdSyncAction") == 0)
+    props->linux_md_sync_action = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxMdSyncPercentage") == 0)
+    props->linux_md_sync_percentage = g_value_get_double (value);
+  else if (strcmp (key, "LinuxMdSyncSpeed") == 0)
+    props->linux_md_sync_speed = g_value_get_uint64 (value);
+
+  else if (strcmp (key, "LinuxLvm2LVName") == 0)
+    props->linux_lvm2_lv_name = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxLvm2LVUuid") == 0)
+    props->linux_lvm2_lv_uuid = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxLvm2LVGroupName") == 0)
+    props->linux_lvm2_lv_group_name = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxLvm2LVGroupUuid") == 0)
+    props->linux_lvm2_lv_group_uuid = g_strdup (g_value_get_string (value));
+
+  else if (strcmp (key, "LinuxLvm2PVUuid") == 0)
+    props->linux_lvm2_pv_uuid = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxLvm2PVNumMetadataAreas") == 0)
+    props->linux_lvm2_pv_num_metadata_areas = g_value_get_uint (value);
+  else if (strcmp (key, "LinuxLvm2PVGroupName") == 0)
+    props->linux_lvm2_pv_group_name = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxLvm2PVGroupUuid") == 0)
+    props->linux_lvm2_pv_group_uuid = g_strdup (g_value_get_string (value));
+  else if (strcmp (key, "LinuxLvm2PVGroupSize") == 0)
+    props->linux_lvm2_pv_group_size = g_value_get_uint64 (value);
+  else if (strcmp (key, "LinuxLvm2PVGroupUnallocatedSize") == 0)
+    props->linux_lvm2_pv_group_unallocated_size = g_value_get_uint64 (value);
+  else if (strcmp (key, "LinuxLvm2PVGroupSequenceNumber") == 0)
+    props->linux_lvm2_pv_group_sequence_number = g_value_get_uint64 (value);
+  else if (strcmp (key, "LinuxLvm2PVGroupExtentSize") == 0)
+    props->linux_lvm2_pv_group_extent_size = g_value_get_uint64 (value);
+  else if (strcmp (key, "LinuxLvm2PVGroupPhysicalVolumes") == 0)
+    props->linux_lvm2_pv_group_physical_volumes = g_strdupv (g_value_get_boxed (value));
+  else if (strcmp (key, "LinuxLvm2PVGroupLogicalVolumes") == 0)
+    props->linux_lvm2_pv_group_logical_volumes = g_strdupv (g_value_get_boxed (value));
+
+  else
+    handled = FALSE;
+
+  if (!handled)
+    g_warning ("unhandled property '%s'", key);
 }
 
 static void
 device_properties_free (DeviceProperties *props)
 {
-        g_free (props->native_path);
-        g_free (props->device_file);
-        g_strfreev (props->device_file_by_id);
-        g_strfreev (props->device_file_by_path);
-        g_strfreev (props->device_mount_paths);
-        g_free (props->device_presentation_name);
-        g_free (props->device_presentation_icon_name);
-        g_free (props->job_id);
-        g_free (props->id_usage);
-        g_free (props->id_type);
-        g_free (props->id_version);
-        g_free (props->id_uuid);
-        g_free (props->id_label);
-        g_free (props->partition_slave);
-        g_free (props->partition_type);
-        g_free (props->partition_label);
-        g_free (props->partition_uuid);
-        g_strfreev (props->partition_flags);
-        g_free (props->partition_table_scheme);
-        g_free (props->luks_holder);
-        g_free (props->luks_cleartext_slave);
-        g_free (props->drive_model);
-        g_free (props->drive_vendor);
-        g_free (props->drive_revision);
-        g_free (props->drive_serial);
-        g_free (props->drive_wwn);
-        g_free (props->drive_connection_interface);
-        g_strfreev (props->drive_media_compatibility);
-        g_free (props->drive_media);
-        g_free (props->drive_write_cache);
-        g_free (props->drive_adapter);
-        g_strfreev (props->drive_ports);
-
-        g_free (props->drive_ata_smart_status);
-        g_free (props->drive_ata_smart_blob);
-
-        g_free (props->linux_md_component_level);
-        g_free (props->linux_md_component_uuid);
-        g_free (props->linux_md_component_home_host);
-        g_free (props->linux_md_component_name);
-        g_free (props->linux_md_component_version);
-        g_free (props->linux_md_component_holder);
-        g_strfreev (props->linux_md_component_state);
-
-        g_free (props->linux_md_state);
-        g_free (props->linux_md_level);
-        g_free (props->linux_md_uuid);
-        g_free (props->linux_md_home_host);
-        g_free (props->linux_md_name);
-        g_free (props->linux_md_version);
-        g_strfreev (props->linux_md_slaves);
-        g_free (props->linux_md_sync_action);
-        g_free (props);
+  g_free (props->native_path);
+  g_free (props->device_file);
+  g_strfreev (props->device_file_by_id);
+  g_strfreev (props->device_file_by_path);
+  g_strfreev (props->device_mount_paths);
+  g_free (props->device_presentation_name);
+  g_free (props->device_presentation_icon_name);
+  g_free (props->job_id);
+  g_free (props->id_usage);
+  g_free (props->id_type);
+  g_free (props->id_version);
+  g_free (props->id_uuid);
+  g_free (props->id_label);
+  g_free (props->partition_slave);
+  g_free (props->partition_type);
+  g_free (props->partition_label);
+  g_free (props->partition_uuid);
+  g_strfreev (props->partition_flags);
+  g_free (props->partition_table_scheme);
+  g_free (props->luks_holder);
+  g_free (props->luks_cleartext_slave);
+  g_free (props->drive_model);
+  g_free (props->drive_vendor);
+  g_free (props->drive_revision);
+  g_free (props->drive_serial);
+  g_free (props->drive_wwn);
+  g_free (props->drive_connection_interface);
+  g_strfreev (props->drive_media_compatibility);
+  g_free (props->drive_media);
+  g_free (props->drive_write_cache);
+  g_free (props->drive_adapter);
+  g_strfreev (props->drive_ports);
+
+  g_free (props->drive_ata_smart_status);
+  g_free (props->drive_ata_smart_blob);
+
+  g_free (props->linux_md_component_level);
+  g_free (props->linux_md_component_uuid);
+  g_free (props->linux_md_component_home_host);
+  g_free (props->linux_md_component_name);
+  g_free (props->linux_md_component_version);
+  g_free (props->linux_md_component_holder);
+  g_strfreev (props->linux_md_component_state);
+
+  g_free (props->linux_md_state);
+  g_free (props->linux_md_level);
+  g_free (props->linux_md_uuid);
+  g_free (props->linux_md_home_host);
+  g_free (props->linux_md_name);
+  g_free (props->linux_md_version);
+  g_strfreev (props->linux_md_slaves);
+  g_free (props->linux_md_sync_action);
+
+  g_free (props->linux_lvm2_lv_name);
+  g_free (props->linux_lvm2_lv_uuid);
+  g_free (props->linux_lvm2_lv_group_name);
+  g_free (props->linux_lvm2_lv_group_uuid);
+
+  g_free (props->linux_lvm2_pv_uuid);
+  g_free (props->linux_lvm2_pv_group_name);
+  g_free (props->linux_lvm2_pv_group_uuid);
+  g_strfreev (props->linux_lvm2_pv_group_physical_volumes);
+  g_strfreev (props->linux_lvm2_pv_group_logical_volumes);
+
+  g_free (props);
 }
 
 static DeviceProperties *
 device_properties_get (DBusGConnection *bus,
                        const char *object_path)
 {
-        DeviceProperties *props;
-        GError *error;
-        GHashTable *hash_table;
-        DBusGProxy *prop_proxy;
-        const char *ifname = "org.freedesktop.UDisks.Device";
-
-        props = g_new0 (DeviceProperties, 1);
-
-	prop_proxy = dbus_g_proxy_new_for_name (bus,
-                                                "org.freedesktop.UDisks",
-                                                object_path,
-                                                "org.freedesktop.DBus.Properties");
-        error = NULL;
-        if (!dbus_g_proxy_call (prop_proxy,
-                                "GetAll",
-                                &error,
-                                G_TYPE_STRING,
-                                ifname,
-                                G_TYPE_INVALID,
-                                dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE),
-                                &hash_table,
-                                G_TYPE_INVALID)) {
-                g_debug ("Error calling GetAll() when retrieving properties for %s: %s", object_path, error->message);
-                g_error_free (error);
-
-                device_properties_free (props);
-                props = NULL;
-                goto out;
-        }
-        g_debug ("yay, got props for %s", object_path);
+  DeviceProperties *props;
+  GError *error;
+  GHashTable *hash_table;
+  DBusGProxy *prop_proxy;
+  const char *ifname = "org.freedesktop.UDisks.Device";
 
-        g_hash_table_foreach (hash_table, (GHFunc) collect_props, props);
+  props = g_new0 (DeviceProperties, 1);
 
-        g_hash_table_unref (hash_table);
+  prop_proxy
+    = dbus_g_proxy_new_for_name (bus, "org.freedesktop.UDisks", object_path, "org.freedesktop.DBus.Properties");
+  error = NULL;
+  if (!dbus_g_proxy_call (prop_proxy,
+                          "GetAll",
+                          &error,
+                          G_TYPE_STRING,
+                          ifname,
+                          G_TYPE_INVALID,
+                          dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE),
+                          &hash_table,
+                          G_TYPE_INVALID))
+    {
+      g_warning ("Couldn't call GetAll() to get properties for %s: %s", object_path, error->message);
+      g_error_free (error);
 
-out:
-        g_object_unref (prop_proxy);
-        return props;
+      device_properties_free (props);
+      props = NULL;
+      goto out;
+    }
+
+  g_hash_table_foreach (hash_table, (GHFunc) collect_props, props);
+
+  g_hash_table_unref (hash_table);
+
+ out:
+  g_object_unref (prop_proxy);
+  return props;
 }
 
 /* --- SUCKY CODE END --- */
@@ -849,6 +916,18 @@ gdu_device_is_linux_md (GduDevice *device)
 }
 
 gboolean
+gdu_device_is_linux_lvm2_lv (GduDevice *device)
+{
+        return device->priv->props->device_is_linux_lvm2_lv;
+}
+
+gboolean
+gdu_device_is_linux_lvm2_pv (GduDevice *device)
+{
+        return device->priv->props->device_is_linux_lvm2_pv;
+}
+
+gboolean
 gdu_device_is_mounted (GduDevice *device)
 {
         return device->priv->props->device_is_mounted;
@@ -1302,6 +1381,92 @@ gdu_device_linux_md_get_sync_speed (GduDevice *device)
         return device->priv->props->linux_md_sync_speed;
 }
 
+const char *
+gdu_device_linux_lvm2_lv_get_name (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_lv_name;
+}
+
+const char *
+gdu_device_linux_lvm2_lv_get_uuid (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_lv_uuid;
+}
+
+const char *
+gdu_device_linux_lvm2_lv_get_group_name (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_lv_group_name;
+}
+
+const char *
+gdu_device_linux_lvm2_lv_get_group_uuid (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_lv_group_uuid;
+}
+
+
+const char *
+gdu_device_linux_lvm2_pv_get_uuid (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_pv_uuid;
+}
+
+guint
+gdu_device_linux_lvm2_pv_get_num_metadata_areas (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_pv_num_metadata_areas;
+}
+
+const char *
+gdu_device_linux_lvm2_pv_get_group_name (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_pv_group_name;
+}
+
+const char *
+gdu_device_linux_lvm2_pv_get_group_uuid (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_pv_group_uuid;
+}
+
+guint64
+gdu_device_linux_lvm2_pv_get_group_size (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_pv_group_size;
+}
+
+guint64
+gdu_device_linux_lvm2_pv_get_group_unallocated_size (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_pv_group_unallocated_size;
+}
+
+guint64
+gdu_device_linux_lvm2_pv_get_group_extent_size (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_pv_group_extent_size;
+}
+
+guint64
+gdu_device_linux_lvm2_pv_get_group_sequence_number (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_pv_group_sequence_number;
+}
+
+gchar **
+gdu_device_linux_lvm2_pv_get_group_physical_volumes (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_pv_group_physical_volumes;
+}
+
+gchar **
+gdu_device_linux_lvm2_pv_get_group_logical_volumes (GduDevice *device)
+{
+        return device->priv->props->linux_lvm2_pv_group_logical_volumes;
+}
+
+
 /* ---------------------------------------------------------------------------------------------------- */
 
 gboolean
@@ -2445,3 +2610,43 @@ void gdu_device_op_drive_benchmark (GduDevice                             *devic
 
 
 /* -------------------------------------------------------------------------------- */
+
+typedef struct {
+        GduDevice *device;
+        GduDeviceLinuxLvm2LVStopCompletedFunc callback;
+        gpointer user_data;
+} LinuxLvm2LVStopData;
+
+static void
+op_stop_linux_lvm2_lv_array_cb (DBusGProxy *proxy, GError *error, gpointer user_data)
+{
+        LinuxLvm2LVStopData *data = user_data;
+        _gdu_error_fixup (error);
+        if (data->callback != NULL)
+                data->callback (data->device, error, data->user_data);
+        g_object_unref (data->device);
+        g_free (data);
+}
+
+void
+gdu_device_op_linux_lvm2_lv_stop (GduDevice                             *device,
+                                  GduDeviceLinuxLvm2LVStopCompletedFunc  callback,
+                                  gpointer                               user_data)
+{
+        char *options[16];
+        LinuxLvm2LVStopData *data;
+
+        data = g_new0 (LinuxLvm2LVStopData, 1);
+        data->device = g_object_ref (device);
+        data->callback = callback;
+        data->user_data = user_data;
+
+        options[0] = NULL;
+
+        org_freedesktop_UDisks_Device_linux_lvm2_lv_stop_async (device->priv->proxy,
+                                                                (const char **) options,
+                                                                op_stop_linux_lvm2_lv_array_cb,
+                                                                data);
+}
+
+/* -------------------------------------------------------------------------------- */
diff --git a/src/gdu/gdu-device.h b/src/gdu/gdu-device.h
index 5cebf67..f3e5291 100644
--- a/src/gdu/gdu-device.h
+++ b/src/gdu/gdu-device.h
@@ -89,6 +89,8 @@ gboolean gdu_device_is_luks (GduDevice *device);
 gboolean gdu_device_is_luks_cleartext (GduDevice *device);
 gboolean gdu_device_is_linux_md_component (GduDevice *device);
 gboolean gdu_device_is_linux_md (GduDevice *device);
+gboolean gdu_device_is_linux_lvm2_lv (GduDevice *device);
+gboolean gdu_device_is_linux_lvm2_pv (GduDevice *device);
 gboolean gdu_device_is_mounted (GduDevice *device);
 const char *gdu_device_get_mount_path (GduDevice *device);
 char **gdu_device_get_mount_paths (GduDevice *device);
@@ -177,6 +179,21 @@ const char *gdu_device_linux_md_get_sync_action (GduDevice *device);
 double      gdu_device_linux_md_get_sync_percentage (GduDevice *device);
 guint64     gdu_device_linux_md_get_sync_speed (GduDevice *device);
 
+const char *gdu_device_linux_lvm2_lv_get_name (GduDevice *device);
+const char *gdu_device_linux_lvm2_lv_get_uuid (GduDevice *device);
+const char *gdu_device_linux_lvm2_lv_get_group_name (GduDevice *device);
+const char *gdu_device_linux_lvm2_lv_get_group_uuid (GduDevice *device);
+
+const char *gdu_device_linux_lvm2_pv_get_uuid (GduDevice *device);
+guint       gdu_device_linux_lvm2_pv_get_num_metadata_areas (GduDevice *device);
+const char *gdu_device_linux_lvm2_pv_get_group_name (GduDevice *device);
+const char *gdu_device_linux_lvm2_pv_get_group_uuid (GduDevice *device);
+guint64     gdu_device_linux_lvm2_pv_get_group_size (GduDevice *device);
+guint64     gdu_device_linux_lvm2_pv_get_group_unallocated_size (GduDevice *device);
+guint64     gdu_device_linux_lvm2_pv_get_group_extent_size (GduDevice *device);
+guint64     gdu_device_linux_lvm2_pv_get_group_sequence_number (GduDevice *device);
+gchar     **gdu_device_linux_lvm2_pv_get_group_physical_volumes (GduDevice *device);
+gchar     **gdu_device_linux_lvm2_pv_get_group_logical_volumes (GduDevice *device);
 
 gboolean      gdu_device_drive_ata_smart_get_is_available (GduDevice *device);
 guint64       gdu_device_drive_ata_smart_get_time_collected (GduDevice *device);
@@ -358,6 +375,12 @@ void gdu_device_op_drive_benchmark (GduDevice                             *devic
                                     GduDeviceDriveBenchmarkCompletedFunc   callback,
                                     gpointer                               user_data);
 
+/* ---------------------------------------------------------------------------------------------------- */
+
+void gdu_device_op_linux_lvm2_lv_stop     (GduDevice                             *device,
+                                           GduDeviceLinuxLvm2LVStopCompletedFunc  callback,
+                                           gpointer                               user_data);
+
 G_END_DECLS
 
 #endif /* __GDU_DEVICE_H */
diff --git a/src/gdu/gdu-linux-lvm2-volume-group.c b/src/gdu/gdu-linux-lvm2-volume-group.c
new file mode 100644
index 0000000..a88b9ac
--- /dev/null
+++ b/src/gdu/gdu-linux-lvm2-volume-group.c
@@ -0,0 +1,561 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include <string.h>
+#include <dbus/dbus-glib.h>
+
+#include "gdu-private.h"
+#include "gdu-util.h"
+#include "gdu-pool.h"
+#include "gdu-device.h"
+#include "gdu-linux-lvm2-volume-group.h"
+#include "gdu-linux-lvm2-volume.h"
+#include "gdu-presentable.h"
+
+struct _GduLinuxLvm2VolumeGroupPrivate
+{
+        GList *slaves;
+
+        GduPool *pool;
+
+        gchar *uuid;
+
+        /* list of PV GduDevice objects that makes up this VG */
+        GList *pv_devices;
+        /* the PV for which we get VG data from (highest seqnum) */
+        GduDevice *pv;
+
+        gchar *id;
+
+        GduPresentable *enclosing_presentable;
+};
+
+static GObjectClass *parent_class = NULL;
+
+static void gdu_linux_lvm2_volume_group_presentable_iface_init (GduPresentableIface *iface);
+
+static gboolean gdu_linux_lvm2_volume_group_is_active (GduDrive *drive);
+static gboolean gdu_linux_lvm2_volume_group_is_activatable (GduDrive *drive);
+static gboolean gdu_linux_lvm2_volume_group_can_deactivate (GduDrive *drive);
+static gboolean gdu_linux_lvm2_volume_group_can_activate (GduDrive *drive, gboolean *out_degraded);
+
+static void on_device_added (GduPool *pool, GduDevice *device, gpointer user_data);
+static void on_device_removed (GduPool *pool, GduDevice *device, gpointer user_data);
+static void on_device_changed (GduPool *pool, GduDevice *device, gpointer user_data);
+
+G_DEFINE_TYPE_WITH_CODE (GduLinuxLvm2VolumeGroup, gdu_linux_lvm2_volume_group, GDU_TYPE_DRIVE,
+                         G_IMPLEMENT_INTERFACE (GDU_TYPE_PRESENTABLE,
+                                                gdu_linux_lvm2_volume_group_presentable_iface_init))
+
+static void
+gdu_linux_lvm2_volume_group_finalize (GObject *object)
+{
+        GduLinuxLvm2VolumeGroup *vg = GDU_LINUX_LVM2_VOLUME_GROUP (object);
+
+        //g_debug ("##### finalized linux-lvm2 volume group '%s' %p", vg->priv->id, vg);
+
+        if (vg->priv->pool != NULL) {
+                g_signal_handlers_disconnect_by_func (vg->priv->pool, on_device_added, vg);
+                g_signal_handlers_disconnect_by_func (vg->priv->pool, on_device_removed, vg);
+                g_signal_handlers_disconnect_by_func (vg->priv->pool, on_device_changed, vg);
+                g_object_unref (vg->priv->pool);
+        }
+
+        g_free (vg->priv->id);
+        g_free (vg->priv->uuid);
+
+        if (vg->priv->enclosing_presentable != NULL)
+                g_object_unref (vg->priv->enclosing_presentable);
+
+        g_list_foreach (vg->priv->pv_devices, (GFunc) g_object_unref, NULL);
+        g_list_free (vg->priv->pv_devices);
+        if (vg->priv->pv != NULL)
+                g_object_unref (vg->priv->pv);
+
+        if (G_OBJECT_CLASS (parent_class)->finalize)
+                (* G_OBJECT_CLASS (parent_class)->finalize) (G_OBJECT (vg));
+}
+
+static void
+gdu_linux_lvm2_volume_group_class_init (GduLinuxLvm2VolumeGroupClass *klass)
+{
+        GObjectClass *gobject_class = (GObjectClass *) klass;
+        GduDriveClass *drive_class = (GduDriveClass *) klass;
+
+        parent_class = g_type_class_peek_parent (klass);
+
+        gobject_class->finalize = gdu_linux_lvm2_volume_group_finalize;
+
+        g_type_class_add_private (klass, sizeof (GduLinuxLvm2VolumeGroupPrivate));
+
+        drive_class->is_active             = gdu_linux_lvm2_volume_group_is_active;
+        drive_class->is_activatable        = gdu_linux_lvm2_volume_group_is_activatable;
+        drive_class->can_deactivate        = gdu_linux_lvm2_volume_group_can_deactivate;
+        drive_class->can_activate          = gdu_linux_lvm2_volume_group_can_activate;
+}
+
+static void
+gdu_linux_lvm2_volume_group_init (GduLinuxLvm2VolumeGroup *vg)
+{
+        vg->priv = G_TYPE_INSTANCE_GET_PRIVATE (vg, GDU_TYPE_LINUX_LVM2_VOLUME_GROUP, GduLinuxLvm2VolumeGroupPrivate);
+}
+
+static void
+emit_changed (GduLinuxLvm2VolumeGroup *vg)
+{
+        //g_debug ("emitting changed for uuid '%s'", vg->priv->uuid);
+        g_signal_emit_by_name (vg, "changed");
+        g_signal_emit_by_name (vg->priv->pool, "presentable-changed", vg);
+}
+
+static gboolean
+find_pvs (GduLinuxLvm2VolumeGroup *vg)
+{
+        GList *devices;
+        GList *l;
+        guint64 seq_num;
+        GduDevice *pv_to_use;
+        gboolean emitted_changed;
+
+        emitted_changed = FALSE;
+
+        /* TODO: do incremental list management instead of recomputing on
+         * each add/remove/change event
+         */
+
+        /* out with the old.. */
+        g_list_foreach (vg->priv->pv_devices, (GFunc) g_object_unref, NULL);
+        g_list_free (vg->priv->pv_devices);
+        vg->priv->pv_devices = NULL;
+
+        /* find all GduDevice objects for LVs that are part of this VG */
+        devices = gdu_pool_get_devices (vg->priv->pool);
+        for (l = devices; l != NULL; l = l->next) {
+                GduDevice *d = GDU_DEVICE (l->data);
+                if (gdu_device_is_linux_lvm2_pv (d) &&
+                    g_strcmp0 (gdu_device_linux_lvm2_pv_get_group_uuid (d), vg->priv->uuid) == 0) {
+                        vg->priv->pv_devices = g_list_prepend (vg->priv->pv_devices, g_object_ref (d));
+                }
+        }
+        g_list_foreach (devices, (GFunc) g_object_unref, NULL);
+        g_list_free (devices);
+
+        /* Find the PV with the highest sequence number and use that */
+        seq_num = 0;
+        pv_to_use = NULL;
+        for (l = vg->priv->pv_devices; l != NULL; l = l->next) {
+                GduDevice *d = GDU_DEVICE (l->data);
+                if (pv_to_use == NULL || gdu_device_linux_lvm2_pv_get_group_sequence_number (d) > seq_num) {
+                        pv_to_use = d;
+                }
+        }
+
+        if (pv_to_use == NULL) {
+                /* ok, switch to the new LV */
+                if (vg->priv->pv != NULL)
+                        g_object_unref (vg->priv->pv);
+                vg->priv->pv = NULL;
+
+                /* emit changed since data might have changed */
+                emit_changed (vg);
+                emitted_changed = TRUE;
+
+        } else if (vg->priv->pv == NULL ||
+            (gdu_device_linux_lvm2_pv_get_group_sequence_number (pv_to_use) >
+             gdu_device_linux_lvm2_pv_get_group_sequence_number (vg->priv->pv))) {
+                /* ok, switch to the new PV */
+                if (vg->priv->pv != NULL)
+                        g_object_unref (vg->priv->pv);
+                vg->priv->pv = g_object_ref (pv_to_use);
+
+                /* emit changed since data might have changed */
+                emit_changed (vg);
+                emitted_changed = TRUE;
+        }
+
+        return emitted_changed;
+}
+
+static void
+on_device_added (GduPool *pool, GduDevice *device, gpointer user_data)
+{
+        GduLinuxLvm2VolumeGroup *vg = GDU_LINUX_LVM2_VOLUME_GROUP (user_data);
+        find_pvs (vg);
+}
+
+static void
+on_device_removed (GduPool *pool, GduDevice *device, gpointer user_data)
+{
+        GduLinuxLvm2VolumeGroup *vg = GDU_LINUX_LVM2_VOLUME_GROUP (user_data);
+        find_pvs (vg);
+}
+
+static void
+on_device_changed (GduPool *pool, GduDevice *device, gpointer user_data)
+{
+        GduLinuxLvm2VolumeGroup *vg = GDU_LINUX_LVM2_VOLUME_GROUP (user_data);
+        gboolean emitted_changed;
+
+        emitted_changed = find_pvs (vg);
+
+        /* propagate change events from LVs and PVs as change events on the VG */
+        if (!emitted_changed) {
+                if ((gdu_device_is_linux_lvm2_pv (device) &&
+                     g_strcmp0 (gdu_device_linux_lvm2_pv_get_group_uuid (device), vg->priv->uuid) == 0) ||
+                    (gdu_device_is_linux_lvm2_lv (device) &&
+                     g_strcmp0 (gdu_device_linux_lvm2_lv_get_group_uuid (device), vg->priv->uuid) == 0)) {
+
+                        emit_changed (vg);
+                }
+        }
+}
+
+/**
+ * _gdu_linux_lvm2_volume_group_new:
+ * @pool: A #GduPool.
+ * @uuid: The UUID for the volume group.
+ * @enclosing_presentable: The enclosing presentable
+ *
+ * Creates a new #GduLinuxLvm2VolumeGroup. Note that only one of @uuid and
+ * @device_file may be %NULL.
+ */
+GduLinuxLvm2VolumeGroup *
+_gdu_linux_lvm2_volume_group_new (GduPool        *pool,
+                                  const gchar    *uuid,
+                                  GduPresentable *enclosing_presentable)
+{
+        GduLinuxLvm2VolumeGroup *vg;
+
+        vg = GDU_LINUX_LVM2_VOLUME_GROUP (g_object_new (GDU_TYPE_LINUX_LVM2_VOLUME_GROUP, NULL));
+        vg->priv->pool = g_object_ref (pool);
+        vg->priv->uuid = g_strdup (uuid);
+
+        vg->priv->id = g_strdup_printf ("linux_lvm2_volume_group_%s_enclosed_by_%s",
+                                        uuid,
+                                        enclosing_presentable != NULL ? gdu_presentable_get_id (enclosing_presentable) : "(none)");
+
+        vg->priv->enclosing_presentable =
+                enclosing_presentable != NULL ? g_object_ref (enclosing_presentable) : NULL;
+
+        g_signal_connect (vg->priv->pool, "device-added", G_CALLBACK (on_device_added), vg);
+        g_signal_connect (vg->priv->pool, "device-removed", G_CALLBACK (on_device_removed), vg);
+        g_signal_connect (vg->priv->pool, "device-changed", G_CALLBACK (on_device_changed), vg);
+        find_pvs (vg);
+
+        return vg;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* GduPresentable methods */
+
+static const gchar *
+gdu_linux_lvm2_volume_group_get_id (GduPresentable *presentable)
+{
+        GduLinuxLvm2VolumeGroup *vg = GDU_LINUX_LVM2_VOLUME_GROUP (presentable);
+
+        return vg->priv->id;
+}
+
+static GduDevice *
+gdu_linux_lvm2_volume_group_get_device (GduPresentable *presentable)
+{
+        return NULL;
+}
+
+static GduPresentable *
+gdu_linux_lvm2_volume_group_get_enclosing_presentable (GduPresentable *presentable)
+{
+        GduLinuxLvm2VolumeGroup *vg = GDU_LINUX_LVM2_VOLUME_GROUP (presentable);
+        if (vg->priv->enclosing_presentable != NULL)
+                return g_object_ref (vg->priv->enclosing_presentable);
+        return NULL;
+}
+
+static gchar *
+get_names_and_desc (GduPresentable  *presentable,
+                    gchar          **out_vpd_name,
+                    gchar          **out_desc)
+{
+        GduLinuxLvm2VolumeGroup *vg = GDU_LINUX_LVM2_VOLUME_GROUP (presentable);
+        gchar *ret;
+        gchar *ret_desc;
+        gchar *ret_vpd;
+        guint64 size;
+        gchar *size_str;
+
+        ret = NULL;
+        ret_desc = NULL;
+        ret_vpd = NULL;
+        size = 0;
+        size_str = NULL;
+
+        if (vg->priv->pv != NULL) {
+                ret = g_strdup (gdu_device_linux_lvm2_pv_get_group_name (vg->priv->pv));
+                size = gdu_device_linux_lvm2_pv_get_group_size (vg->priv->pv);
+                size_str = gdu_util_get_size_for_display (size, FALSE, FALSE);
+        } else {
+                ret = g_strdup (_("Volume Group"));
+        }
+
+        /* Translators: Description */
+        ret_desc = g_strdup (_("Volume Group"));
+
+        if (size_str != NULL) {
+                /* Translators: VPD name - first %s is the size e.g. '45 GB' */
+                ret_vpd = g_strdup_printf (_("%s LVM2 Volume Group"), size_str);
+        } else {
+                /* Translators: VPD name when size is not known */
+                ret_vpd = g_strdup (_("LVM2 Volume Group"));
+        }
+
+        if (out_desc != NULL)
+                *out_desc = ret_desc;
+        else
+                g_free (ret_desc);
+
+        if (out_vpd_name != NULL)
+                *out_vpd_name = ret_vpd;
+        else
+                g_free (ret_vpd);
+
+        g_free (size_str);
+
+        return ret;
+}
+
+static char *
+gdu_linux_lvm2_volume_group_get_name (GduPresentable *presentable)
+{
+        return get_names_and_desc (presentable, NULL, NULL);
+}
+
+static gchar *
+gdu_linux_lvm2_volume_group_get_description (GduPresentable *presentable)
+{
+        gchar *desc;
+        gchar *name;
+
+        name = get_names_and_desc (presentable, NULL, &desc);
+        g_free (name);
+
+        return desc;
+}
+
+static gchar *
+gdu_linux_lvm2_volume_group_get_vpd_name (GduPresentable *presentable)
+{
+        gchar *vpd_name;
+        gchar *name;
+
+        name = get_names_and_desc (presentable, &vpd_name, NULL);
+        g_free (name);
+
+        return vpd_name;
+}
+
+static GIcon *
+gdu_linux_lvm2_volume_group_get_icon (GduPresentable *presentable)
+{
+        return g_themed_icon_new_with_default_fallbacks ("gdu-raid-array");
+}
+
+static guint64
+gdu_linux_lvm2_volume_group_get_offset (GduPresentable *presentable)
+{
+        return 0;
+}
+
+static guint64
+gdu_linux_lvm2_volume_group_get_size (GduPresentable *presentable)
+{
+        GduLinuxLvm2VolumeGroup *vg = GDU_LINUX_LVM2_VOLUME_GROUP (presentable);
+        guint64 ret;
+
+        if (vg->priv->pv != NULL) {
+                ret = gdu_device_linux_lvm2_pv_get_group_size (vg->priv->pv);
+        } else {
+                ret = 0;
+        }
+
+        return ret;
+}
+
+static GduPool *
+gdu_linux_lvm2_volume_group_get_pool (GduPresentable *presentable)
+{
+        GduLinuxLvm2VolumeGroup *vg = GDU_LINUX_LVM2_VOLUME_GROUP (presentable);
+        return g_object_ref (vg->priv->pool);
+}
+
+static gboolean
+gdu_linux_lvm2_volume_group_is_allocated (GduPresentable *presentable)
+{
+        return TRUE;
+}
+
+static gboolean
+gdu_linux_lvm2_volume_group_is_recognized (GduPresentable *presentable)
+{
+        /* TODO: maybe we need to return FALSE sometimes */
+        return TRUE;
+}
+
+static void
+gdu_linux_lvm2_volume_group_presentable_iface_init (GduPresentableIface *iface)
+{
+        iface->get_id                    = gdu_linux_lvm2_volume_group_get_id;
+        iface->get_device                = gdu_linux_lvm2_volume_group_get_device;
+        iface->get_enclosing_presentable = gdu_linux_lvm2_volume_group_get_enclosing_presentable;
+        iface->get_name                  = gdu_linux_lvm2_volume_group_get_name;
+        iface->get_description           = gdu_linux_lvm2_volume_group_get_description;
+        iface->get_vpd_name              = gdu_linux_lvm2_volume_group_get_vpd_name;
+        iface->get_icon                  = gdu_linux_lvm2_volume_group_get_icon;
+        iface->get_offset                = gdu_linux_lvm2_volume_group_get_offset;
+        iface->get_size                  = gdu_linux_lvm2_volume_group_get_size;
+        iface->get_pool                  = gdu_linux_lvm2_volume_group_get_pool;
+        iface->is_allocated              = gdu_linux_lvm2_volume_group_is_allocated;
+        iface->is_recognized             = gdu_linux_lvm2_volume_group_is_recognized;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void
+_gdu_linux_lvm2_volume_group_rewrite_enclosing_presentable (GduLinuxLvm2VolumeGroup *vg)
+{
+        if (vg->priv->enclosing_presentable != NULL) {
+                const gchar *enclosing_presentable_id;
+                GduPresentable *new_enclosing_presentable;
+
+                enclosing_presentable_id = gdu_presentable_get_id (vg->priv->enclosing_presentable);
+
+                new_enclosing_presentable = gdu_pool_get_presentable_by_id (vg->priv->pool,
+                                                                            enclosing_presentable_id);
+                if (new_enclosing_presentable == NULL) {
+                        g_warning ("Error rewriting enclosing_presentable for %s, no such id %s",
+                                   vg->priv->id,
+                                   enclosing_presentable_id);
+                        goto out;
+                }
+
+                g_object_unref (vg->priv->enclosing_presentable);
+                vg->priv->enclosing_presentable = new_enclosing_presentable;
+        }
+
+ out:
+        ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* GduDrive virtual method overrides */
+
+static gboolean
+gdu_linux_lvm2_volume_group_is_active (GduDrive *drive)
+{
+        return TRUE;
+}
+
+static gboolean
+gdu_linux_lvm2_volume_group_is_activatable (GduDrive *drive)
+{
+        return FALSE;
+}
+
+static gboolean
+gdu_linux_lvm2_volume_group_can_deactivate (GduDrive *drive)
+{
+        return FALSE;
+}
+
+static gboolean
+gdu_linux_lvm2_volume_group_can_activate (GduDrive *drive,
+                                          gboolean *out_degraded)
+{
+        return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * gdu_linux_lvm2_volume_group_get_lv_device:
+ * @vg: A #GduLinuxLvm2VolumeGroup.
+ *
+ * Returns a #GduDevice for a PV in @vg with the most up to date volume group info.
+ *
+ * Returns: A #GduDevice (free with g_object_unref()) or %NULL if not found.
+ */
+GduDevice *
+gdu_linux_lvm2_volume_group_get_pv_device (GduLinuxLvm2VolumeGroup *vg)
+{
+        if (vg->priv->pv != NULL) {
+                return g_object_ref (vg->priv->pv);
+        } else {
+                return NULL;
+        }
+}
+
+GduLinuxLvm2VolumeGroupState
+gdu_linux_lvm2_volume_group_get_state (GduLinuxLvm2VolumeGroup *vg)
+{
+        GList *lvs;
+        GList *l;
+        guint num_lvs;
+        guint num_running_lvs;
+        GduLinuxLvm2VolumeGroupState ret;
+
+        lvs = gdu_pool_get_enclosed_presentables (vg->priv->pool, GDU_PRESENTABLE (vg));
+        num_lvs = 0;
+        num_running_lvs = 0;
+        for (l = lvs; l != NULL; l = l->next) {
+                GduPresentable *p = GDU_PRESENTABLE (l->data);
+
+                if (GDU_IS_LINUX_LVM2_VOLUME (p)) {
+                        GduDevice *d;
+
+                        d = gdu_presentable_get_device (p);
+                        if (d != NULL) {
+                                num_running_lvs++;
+                                g_object_unref (d);
+                        }
+                        num_lvs++;
+                }
+        }
+        g_list_foreach (lvs, (GFunc) g_object_unref, NULL);
+        g_list_free (lvs);
+
+        if (num_running_lvs == 0)
+                ret = GDU_LINUX_LVM2_VOLUME_GROUP_STATE_NOT_RUNNING;
+        else if (num_running_lvs == num_lvs)
+                ret = GDU_LINUX_LVM2_VOLUME_GROUP_STATE_RUNNING;
+        else
+                ret = GDU_LINUX_LVM2_VOLUME_GROUP_STATE_PARTIALLY_RUNNING;
+
+        return ret;
+}
+
+const gchar *
+gdu_linux_lvm2_volume_group_get_uuid (GduLinuxLvm2VolumeGroup *vg)
+{
+        return vg->priv->uuid;
+}
diff --git a/src/gdu/gdu-linux-lvm2-volume-group.h b/src/gdu/gdu-linux-lvm2-volume-group.h
new file mode 100644
index 0000000..824f23d
--- /dev/null
+++ b/src/gdu/gdu-linux-lvm2-volume-group.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#if !defined (__GDU_INSIDE_GDU_H) && !defined (GDU_COMPILATION)
+#error "Only <gdu/gdu.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __GDU_LINUX_LVM2_VOLUME_GROUP_H
+#define __GDU_LINUX_LVM2_VOLUME_GROUP_H
+
+#include <gdu/gdu-types.h>
+#include <gdu/gdu-callbacks.h>
+#include <gdu/gdu-drive.h>
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_LINUX_LVM2_VOLUME_GROUP         (gdu_linux_lvm2_volume_group_get_type ())
+#define GDU_LINUX_LVM2_VOLUME_GROUP(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_LINUX_LVM2_VOLUME_GROUP, GduLinuxLvm2VolumeGroup))
+#define GDU_LINUX_LVM2_VOLUME_GROUP_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GDU_LINUX_LVM2_VOLUME_GROUP,  GduLinuxLvm2VolumeGroupClass))
+#define GDU_IS_LINUX_LVM2_VOLUME_GROUP(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_LINUX_LVM2_VOLUME_GROUP))
+#define GDU_IS_LINUX_LVM2_VOLUME_GROUP_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDU_TYPE_LINUX_LVM2_VOLUME_GROUP))
+#define GDU_LINUX_LVM2_VOLUME_GROUP_GET_CLASS(k) (G_TYPE_INSTANCE_GET_CLASS ((k), GDU_TYPE_LINUX_LVM2_VOLUME_GROUP, GduLinuxLvm2VolumeGroupClass))
+
+typedef struct _GduLinuxLvm2VolumeGroupClass    GduLinuxLvm2VolumeGroupClass;
+typedef struct _GduLinuxLvm2VolumeGroupPrivate  GduLinuxLvm2VolumeGroupPrivate;
+
+struct _GduLinuxLvm2VolumeGroup
+{
+        GduDrive parent;
+
+        /*< private >*/
+        GduLinuxLvm2VolumeGroupPrivate *priv;
+};
+
+struct _GduLinuxLvm2VolumeGroupClass
+{
+        GduDriveClass parent_class;
+};
+
+typedef enum {
+        GDU_LINUX_LVM2_VOLUME_GROUP_STATE_NOT_RUNNING,
+        GDU_LINUX_LVM2_VOLUME_GROUP_STATE_PARTIALLY_RUNNING,
+        GDU_LINUX_LVM2_VOLUME_GROUP_STATE_RUNNING,
+} GduLinuxLvm2VolumeGroupState;
+
+GType                         gdu_linux_lvm2_volume_group_get_type      (void);
+const gchar                  *gdu_linux_lvm2_volume_group_get_uuid      (GduLinuxLvm2VolumeGroup *vg);
+GduLinuxLvm2VolumeGroupState  gdu_linux_lvm2_volume_group_get_state     (GduLinuxLvm2VolumeGroup *vg);
+GduDevice                    *gdu_linux_lvm2_volume_group_get_pv_device (GduLinuxLvm2VolumeGroup *vg);
+
+G_END_DECLS
+
+#endif /* __GDU_LINUX_LVM2_VOLUME_GROUP_H */
diff --git a/src/gdu/gdu-linux-lvm2-volume-hole.c b/src/gdu/gdu-linux-lvm2-volume-hole.c
new file mode 100644
index 0000000..298c821
--- /dev/null
+++ b/src/gdu/gdu-linux-lvm2-volume-hole.c
@@ -0,0 +1,331 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include <string.h>
+#include <dbus/dbus-glib.h>
+
+#include "gdu-private.h"
+#include "gdu-util.h"
+#include "gdu-pool.h"
+#include "gdu-device.h"
+#include "gdu-linux-lvm2-volume-group.h"
+#include "gdu-linux-lvm2-volume-hole.h"
+#include "gdu-presentable.h"
+
+struct _GduLinuxLvm2VolumeHolePrivate
+{
+        GduPool *pool;
+
+        gchar *id;
+
+        GduPresentable *enclosing_presentable;
+};
+
+static GObjectClass *parent_class = NULL;
+
+static void gdu_linux_lvm2_volume_hole_presentable_iface_init (GduPresentableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GduLinuxLvm2VolumeHole, gdu_linux_lvm2_volume_hole, GDU_TYPE_VOLUME_HOLE,
+                         G_IMPLEMENT_INTERFACE (GDU_TYPE_PRESENTABLE,
+                                                gdu_linux_lvm2_volume_hole_presentable_iface_init))
+
+static void on_presentable_changed (GduPresentable *presentable, gpointer user_data);
+
+static void
+gdu_linux_lvm2_volume_hole_finalize (GObject *object)
+{
+        GduLinuxLvm2VolumeHole *volume_hole = GDU_LINUX_LVM2_VOLUME_HOLE (object);
+
+        //g_debug ("##### finalized linux-lvm2 volume_hole '%s' %p", volume_hole->priv->id, lv);
+
+        g_free (volume_hole->priv->id);
+
+        if (volume_hole->priv->enclosing_presentable != NULL) {
+                g_signal_handlers_disconnect_by_func (volume_hole->priv->enclosing_presentable,
+                                                      on_presentable_changed,
+                                                      volume_hole);
+                g_object_unref (volume_hole->priv->enclosing_presentable);
+        }
+
+        if (G_OBJECT_CLASS (parent_class)->finalize)
+                (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+gdu_linux_lvm2_volume_hole_class_init (GduLinuxLvm2VolumeHoleClass *klass)
+{
+        GObjectClass *gobject_class = (GObjectClass *) klass;
+
+        parent_class = g_type_class_peek_parent (klass);
+
+        gobject_class->finalize = gdu_linux_lvm2_volume_hole_finalize;
+
+        g_type_class_add_private (klass, sizeof (GduLinuxLvm2VolumeHolePrivate));
+}
+
+static void
+gdu_linux_lvm2_volume_hole_init (GduLinuxLvm2VolumeHole *volume_hole)
+{
+        volume_hole->priv = G_TYPE_INSTANCE_GET_PRIVATE (volume_hole, GDU_TYPE_LINUX_LVM2_VOLUME_HOLE, GduLinuxLvm2VolumeHolePrivate);
+}
+
+/**
+ * _gdu_linux_lvm2_volume_hole_new:
+ * @pool: A #GduPool.
+ * @enclosing_presentable: The enclosing presentable.
+ *
+ * Creates a new #GduLinuxLvm2VolumeHole.
+ */
+GduLinuxLvm2VolumeHole *
+_gdu_linux_lvm2_volume_hole_new (GduPool        *pool,
+                                 GduPresentable *enclosing_presentable)
+{
+        GduLinuxLvm2VolumeHole *volume_hole;
+
+        volume_hole = GDU_LINUX_LVM2_VOLUME_HOLE (g_object_new (GDU_TYPE_LINUX_LVM2_VOLUME_HOLE, NULL));
+        volume_hole->priv->pool = g_object_ref (pool);
+
+        volume_hole->priv->id = g_strdup_printf ("linux_lvm2_volume_hole_enclosed_by_%s",
+                                                 enclosing_presentable != NULL ? gdu_presentable_get_id (enclosing_presentable) : "(none)");
+
+        volume_hole->priv->enclosing_presentable =
+                enclosing_presentable != NULL ? g_object_ref (enclosing_presentable) : NULL;
+
+        /* Track the VG since we get the amount of free space from there */
+        if (volume_hole->priv->enclosing_presentable != NULL) {
+                g_signal_connect (volume_hole->priv->enclosing_presentable,
+                                  "changed",
+                                  G_CALLBACK (on_presentable_changed),
+                                  volume_hole);
+        }
+
+        return volume_hole;
+}
+
+static void
+on_presentable_changed (GduPresentable *presentable, gpointer user_data)
+{
+        GduLinuxLvm2VolumeHole *volume_hole = GDU_LINUX_LVM2_VOLUME_HOLE (user_data);
+
+        g_signal_emit_by_name (volume_hole, "changed");
+        g_signal_emit_by_name (volume_hole->priv->pool, "presentable-changed", volume_hole);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* GduPresentable methods */
+
+static const gchar *
+gdu_linux_lvm2_volume_hole_get_id (GduPresentable *presentable)
+{
+        GduLinuxLvm2VolumeHole *volume_hole = GDU_LINUX_LVM2_VOLUME_HOLE (presentable);
+
+        return volume_hole->priv->id;
+}
+
+static GduDevice *
+gdu_linux_lvm2_volume_hole_get_device (GduPresentable *presentable)
+{
+        return NULL;
+}
+
+static GduPresentable *
+gdu_linux_lvm2_volume_hole_get_enclosing_presentable (GduPresentable *presentable)
+{
+        GduLinuxLvm2VolumeHole *volume_hole = GDU_LINUX_LVM2_VOLUME_HOLE (presentable);
+
+        if (volume_hole->priv->enclosing_presentable != NULL)
+                return g_object_ref (volume_hole->priv->enclosing_presentable);
+        return NULL;
+}
+
+static guint64
+gdu_linux_lvm2_volume_hole_get_size (GduPresentable *presentable)
+{
+        GduLinuxLvm2VolumeHole *volume_hole = GDU_LINUX_LVM2_VOLUME_HOLE (presentable);
+        GduDevice *pv_device;
+        guint64 ret;
+
+        ret = 0;
+
+        pv_device = gdu_linux_lvm2_volume_group_get_pv_device (GDU_LINUX_LVM2_VOLUME_GROUP (volume_hole->priv->enclosing_presentable));
+        if (pv_device != NULL) {
+                ret = gdu_device_linux_lvm2_pv_get_group_unallocated_size (pv_device);
+                g_object_unref (pv_device);
+        }
+
+        return ret;
+}
+
+static gchar *
+get_names_and_desc (GduPresentable  *presentable,
+                    gchar          **out_vpd_name,
+                    gchar          **out_desc)
+{
+        gchar *ret;
+        gchar *ret_desc;
+        gchar *ret_vpd;
+        guint64 size;
+        gchar *strsize;
+
+        ret = NULL;
+        ret_desc = NULL;
+        ret_vpd = NULL;
+
+        size = gdu_linux_lvm2_volume_hole_get_size (presentable);
+        strsize = gdu_util_get_size_for_display (size, FALSE, FALSE);
+
+        /* Translators: label for an unallocated space in a LVM2 volume group.
+         * %s is the size, formatted like '45 GB'
+         */
+        ret = g_strdup_printf (_("%s Free"), strsize);
+        g_free (strsize);
+
+        /* Translators: Description */
+        ret_desc = g_strdup (_("Unallocated Space"));
+
+        /* Translators: VPD name */
+        ret_vpd = g_strdup (_("LVM2 VG Unallocated Space"));
+
+        if (out_desc != NULL)
+                *out_desc = ret_desc;
+        else
+                g_free (ret_desc);
+
+        if (out_vpd_name != NULL)
+                *out_vpd_name = ret_vpd;
+        else
+                g_free (ret_vpd);
+
+        return ret;
+}
+
+static char *
+gdu_linux_lvm2_volume_hole_get_name (GduPresentable *presentable)
+{
+        return get_names_and_desc (presentable, NULL, NULL);
+}
+
+static gchar *
+gdu_linux_lvm2_volume_hole_get_description (GduPresentable *presentable)
+{
+        gchar *desc;
+        gchar *name;
+
+        name = get_names_and_desc (presentable, NULL, &desc);
+        g_free (name);
+
+        return desc;
+}
+
+static gchar *
+gdu_linux_lvm2_volume_hole_get_vpd_name (GduPresentable *presentable)
+{
+        gchar *vpd_name;
+        gchar *name;
+
+        name = get_names_and_desc (presentable, &vpd_name, NULL);
+        g_free (name);
+
+        return vpd_name;
+}
+
+static GIcon *
+gdu_linux_lvm2_volume_hole_get_icon (GduPresentable *presentable)
+{
+        return g_themed_icon_new_with_default_fallbacks ("gdu-raid-array");
+}
+
+static guint64
+gdu_linux_lvm2_volume_hole_get_offset (GduPresentable *presentable)
+{
+        /* Halfway to G_MAXUINT64 - should guarantee that we're always at the end... */
+        return G_MAXINT64;
+}
+
+static GduPool *
+gdu_linux_lvm2_volume_hole_get_pool (GduPresentable *presentable)
+{
+        GduLinuxLvm2VolumeHole *volume_hole = GDU_LINUX_LVM2_VOLUME_HOLE (presentable);
+        return g_object_ref (volume_hole->priv->pool);
+}
+
+static gboolean
+gdu_linux_lvm2_volume_hole_is_allocated (GduPresentable *presentable)
+{
+        return FALSE;
+}
+
+static gboolean
+gdu_linux_lvm2_volume_hole_is_recognized (GduPresentable *presentable)
+{
+        /* TODO: maybe we need to return FALSE sometimes */
+        return TRUE;
+}
+
+static void
+gdu_linux_lvm2_volume_hole_presentable_iface_init (GduPresentableIface *iface)
+{
+        iface->get_id                    = gdu_linux_lvm2_volume_hole_get_id;
+        iface->get_device                = gdu_linux_lvm2_volume_hole_get_device;
+        iface->get_enclosing_presentable = gdu_linux_lvm2_volume_hole_get_enclosing_presentable;
+        iface->get_name                  = gdu_linux_lvm2_volume_hole_get_name;
+        iface->get_description           = gdu_linux_lvm2_volume_hole_get_description;
+        iface->get_vpd_name              = gdu_linux_lvm2_volume_hole_get_vpd_name;
+        iface->get_icon                  = gdu_linux_lvm2_volume_hole_get_icon;
+        iface->get_offset                = gdu_linux_lvm2_volume_hole_get_offset;
+        iface->get_size                  = gdu_linux_lvm2_volume_hole_get_size;
+        iface->get_pool                  = gdu_linux_lvm2_volume_hole_get_pool;
+        iface->is_allocated              = gdu_linux_lvm2_volume_hole_is_allocated;
+        iface->is_recognized             = gdu_linux_lvm2_volume_hole_is_recognized;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void
+_gdu_linux_lvm2_volume_hole_rewrite_enclosing_presentable (GduLinuxLvm2VolumeHole *volume_hole)
+{
+        if (volume_hole->priv->enclosing_presentable != NULL) {
+                const gchar *enclosing_presentable_id;
+                GduPresentable *new_enclosing_presentable;
+
+                enclosing_presentable_id = gdu_presentable_get_id (volume_hole->priv->enclosing_presentable);
+
+                new_enclosing_presentable = gdu_pool_get_presentable_by_id (volume_hole->priv->pool,
+                                                                            enclosing_presentable_id);
+                if (new_enclosing_presentable == NULL) {
+                        g_warning ("Error rewriting enclosing_presentable for %s, no such id %s",
+                                   volume_hole->priv->id,
+                                   enclosing_presentable_id);
+                        goto out;
+                }
+
+                g_object_unref (volume_hole->priv->enclosing_presentable);
+                volume_hole->priv->enclosing_presentable = new_enclosing_presentable;
+        }
+
+ out:
+        ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/gdu/gdu-linux-lvm2-volume-hole.h b/src/gdu/gdu-linux-lvm2-volume-hole.h
new file mode 100644
index 0000000..b2ed174
--- /dev/null
+++ b/src/gdu/gdu-linux-lvm2-volume-hole.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#if !defined (__GDU_INSIDE_GDU_H) && !defined (GDU_COMPILATION)
+#error "Only <gdu/gdu.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __GDU_LINUX_LVM2_VOLUME_HOLE_H
+#define __GDU_LINUX_LVM2_VOLUME_HOLE_H
+
+#include <gdu/gdu-types.h>
+#include <gdu/gdu-callbacks.h>
+#include <gdu/gdu-volume-hole.h>
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_LINUX_LVM2_VOLUME_HOLE         (gdu_linux_lvm2_volume_hole_get_type ())
+#define GDU_LINUX_LVM2_VOLUME_HOLE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_LINUX_LVM2_VOLUME_HOLE, GduLinuxLvm2VolumeHole))
+#define GDU_LINUX_LVM2_VOLUME_HOLE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GDU_LINUX_LVM2_VOLUME_HOLE,  GduLinuxLvm2VolumeHoleClass))
+#define GDU_IS_LINUX_LVM2_VOLUME_HOLE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_LINUX_LVM2_VOLUME_HOLE))
+#define GDU_IS_LINUX_LVM2_VOLUME_HOLE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDU_TYPE_LINUX_LVM2_VOLUME_HOLE))
+#define GDU_LINUX_LVM2_VOLUME_HOLE_GET_CLASS(k) (G_TYPE_INSTANCE_GET_CLASS ((k), GDU_TYPE_LINUX_LVM2_VOLUME_HOLE, GduLinuxLvm2VolumeHoleClass))
+
+typedef struct _GduLinuxLvm2VolumeHoleClass    GduLinuxLvm2VolumeHoleClass;
+typedef struct _GduLinuxLvm2VolumeHolePrivate  GduLinuxLvm2VolumeHolePrivate;
+
+struct _GduLinuxLvm2VolumeHole
+{
+        GduVolumeHole parent;
+
+        /*< private >*/
+        GduLinuxLvm2VolumeHolePrivate *priv;
+};
+
+struct _GduLinuxLvm2VolumeHoleClass
+{
+        GduVolumeHoleClass parent_class;
+};
+
+GType      gdu_linux_lvm2_volume_hole_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GDU_LINUX_LVM2_VOLUME_HOLE_H */
diff --git a/src/gdu/gdu-linux-lvm2-volume.c b/src/gdu/gdu-linux-lvm2-volume.c
new file mode 100644
index 0000000..e483c38
--- /dev/null
+++ b/src/gdu/gdu-linux-lvm2-volume.c
@@ -0,0 +1,430 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include <string.h>
+#include <dbus/dbus-glib.h>
+
+#include "gdu-private.h"
+#include "gdu-util.h"
+#include "gdu-pool.h"
+#include "gdu-device.h"
+#include "gdu-linux-lvm2-volume.h"
+#include "gdu-presentable.h"
+
+struct _GduLinuxLvm2VolumePrivate
+{
+        GduPool *pool;
+
+        gchar *name;
+
+        gchar *group_uuid;
+        gchar *uuid;
+
+        guint64 offset;
+        guint64 size;
+
+        /* the GduDevice for the LV / mapped device (if activated) */
+        GduDevice *lv;
+
+        gchar *id;
+
+        GduPresentable *enclosing_presentable;
+};
+
+static GObjectClass *parent_class = NULL;
+
+static void gdu_linux_lvm2_volume_presentable_iface_init (GduPresentableIface *iface);
+
+static void on_device_added (GduPool *pool, GduDevice *device, gpointer user_data);
+static void on_device_removed (GduPool *pool, GduDevice *device, gpointer user_data);
+static void on_device_changed (GduPool *pool, GduDevice *device, gpointer user_data);
+
+G_DEFINE_TYPE_WITH_CODE (GduLinuxLvm2Volume, gdu_linux_lvm2_volume, GDU_TYPE_VOLUME,
+                         G_IMPLEMENT_INTERFACE (GDU_TYPE_PRESENTABLE,
+                                                gdu_linux_lvm2_volume_presentable_iface_init))
+
+static void
+gdu_linux_lvm2_volume_finalize (GObject *object)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (object);
+
+        //g_debug ("##### finalized linux-lvm2 volume '%s' %p", lv->priv->id, lv);
+
+        if (volume->priv->pool != NULL) {
+                g_signal_handlers_disconnect_by_func (volume->priv->pool, on_device_added, volume);
+                g_signal_handlers_disconnect_by_func (volume->priv->pool, on_device_removed, volume);
+                g_signal_handlers_disconnect_by_func (volume->priv->pool, on_device_changed, volume);
+                g_object_unref (volume->priv->pool);
+        }
+
+        g_free (volume->priv->id);
+        g_free (volume->priv->name);
+        g_free (volume->priv->group_uuid);
+        g_free (volume->priv->uuid);
+
+        if (volume->priv->enclosing_presentable != NULL)
+                g_object_unref (volume->priv->enclosing_presentable);
+
+        if (volume->priv->lv != NULL)
+                g_object_unref (volume->priv->lv);
+
+        if (G_OBJECT_CLASS (parent_class)->finalize)
+                (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+gdu_linux_lvm2_volume_class_init (GduLinuxLvm2VolumeClass *klass)
+{
+        GObjectClass *gobject_class = (GObjectClass *) klass;
+
+        parent_class = g_type_class_peek_parent (klass);
+
+        gobject_class->finalize = gdu_linux_lvm2_volume_finalize;
+
+        g_type_class_add_private (klass, sizeof (GduLinuxLvm2VolumePrivate));
+}
+
+static void
+gdu_linux_lvm2_volume_init (GduLinuxLvm2Volume *volume)
+{
+        volume->priv = G_TYPE_INSTANCE_GET_PRIVATE (volume, GDU_TYPE_LINUX_LVM2_VOLUME, GduLinuxLvm2VolumePrivate);
+}
+
+static void
+emit_changed (GduLinuxLvm2Volume *volume)
+{
+        //g_debug ("emitting changed for uuid '%s'", volume->priv->uuid);
+        g_signal_emit_by_name (volume, "changed");
+        g_signal_emit_by_name (volume->priv->pool, "presentable-changed", volume);
+}
+
+static gboolean
+find_lv (GduLinuxLvm2Volume *volume)
+{
+        GList *devices;
+        GList *l;
+        GduDevice *lv_to_use;
+        gboolean emitted_changed;
+
+        emitted_changed = FALSE;
+
+        /* find all GduDevice objects for LVs that are part of this VG */
+        lv_to_use = NULL;
+        devices = gdu_pool_get_devices (volume->priv->pool);
+        for (l = devices; l != NULL; l = l->next) {
+                GduDevice *d = GDU_DEVICE (l->data);
+                if (gdu_device_is_linux_lvm2_lv (d) &&
+                    g_strcmp0 (gdu_device_linux_lvm2_lv_get_uuid (d), volume->priv->uuid) == 0) {
+                        lv_to_use = d;
+                        break;
+                }
+        }
+        g_list_foreach (devices, (GFunc) g_object_unref, NULL);
+        g_list_free (devices);
+
+        /* Find the PV with the highest sequence number and use that */
+
+        if (volume->priv->lv != lv_to_use) {
+                if (volume->priv->lv != NULL)
+                        g_object_unref (volume->priv->lv);
+                volume->priv->lv = lv_to_use != NULL ? g_object_ref (lv_to_use) : NULL;
+                emit_changed (volume);
+                emitted_changed = TRUE;
+        }
+
+        return emitted_changed;
+}
+
+static void
+on_device_added (GduPool *pool, GduDevice *device, gpointer user_data)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (user_data);
+        find_lv (volume);
+}
+
+static void
+on_device_removed (GduPool *pool, GduDevice *device, gpointer user_data)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (user_data);
+        find_lv (volume);
+}
+
+static void
+on_device_changed (GduPool *pool, GduDevice *device, gpointer user_data)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (user_data);
+        gboolean emitted_changed;
+
+        emitted_changed = find_lv (volume);
+
+        if (!emitted_changed && device == volume->priv->lv)
+                emit_changed (volume);
+}
+
+/**
+ * _gdu_linux_lvm2_volume_new:
+ * @pool: A #GduPool.
+ * @name: The name of the logical volume.
+ * @group_uuid: The UUID of the group that the logical volume belongs to.
+ * @uuid: The UUID of the logical volume.
+ * @offset: The offset of the logical volume.
+ * @size: The size of the logical volume.
+ * @enclosing_presentable: The enclosing presentable.
+ *
+ * Creates a new #GduLinuxLvm2Volume.
+ *
+ * Note that @offset is only used for laying out the volumes in e.g. a grid - it doesn't really
+ * make sense to talk about the offset of a LV in LVM2.
+ */
+GduLinuxLvm2Volume *
+_gdu_linux_lvm2_volume_new (GduPool        *pool,
+                            const gchar    *name,
+                            const gchar    *group_uuid,
+                            const gchar    *uuid,
+                            guint64         offset,
+                            guint64         size,
+                            GduPresentable *enclosing_presentable)
+{
+        GduLinuxLvm2Volume *volume;
+
+        volume = GDU_LINUX_LVM2_VOLUME (g_object_new (GDU_TYPE_LINUX_LVM2_VOLUME, NULL));
+        volume->priv->pool = g_object_ref (pool);
+        volume->priv->name = g_strdup (name);
+        volume->priv->group_uuid = g_strdup (group_uuid);
+        volume->priv->uuid = g_strdup (uuid);
+        volume->priv->offset = offset;
+        volume->priv->size = size;
+
+        volume->priv->id = g_strdup_printf ("linux_lvm2_volume_%s_enclosed_by_%s",
+                                        uuid,
+                                        enclosing_presentable != NULL ? gdu_presentable_get_id (enclosing_presentable) : "(none)");
+
+        volume->priv->enclosing_presentable =
+                enclosing_presentable != NULL ? g_object_ref (enclosing_presentable) : NULL;
+
+        g_signal_connect (volume->priv->pool, "device-added", G_CALLBACK (on_device_added), volume);
+        g_signal_connect (volume->priv->pool, "device-removed", G_CALLBACK (on_device_removed), volume);
+        g_signal_connect (volume->priv->pool, "device-changed", G_CALLBACK (on_device_changed), volume);
+        find_lv (volume);
+
+        return volume;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* GduPresentable methods */
+
+static const gchar *
+gdu_linux_lvm2_volume_get_id (GduPresentable *presentable)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (presentable);
+
+        return volume->priv->id;
+}
+
+static GduDevice *
+gdu_linux_lvm2_volume_get_device (GduPresentable *presentable)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (presentable);
+
+        if (volume->priv->lv != NULL)
+                return g_object_ref (volume->priv->lv);
+        return NULL;
+}
+
+static GduPresentable *
+gdu_linux_lvm2_volume_get_enclosing_presentable (GduPresentable *presentable)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (presentable);
+
+        if (volume->priv->enclosing_presentable != NULL)
+                return g_object_ref (volume->priv->enclosing_presentable);
+        return NULL;
+}
+
+static gchar *
+get_names_and_desc (GduPresentable  *presentable,
+                    gchar          **out_vpd_name,
+                    gchar          **out_desc)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (presentable);
+        gchar *ret;
+        gchar *ret_desc;
+        gchar *ret_vpd;
+
+        ret = NULL;
+        ret_desc = NULL;
+        ret_vpd = NULL;
+
+        ret = g_strdup (volume->priv->name);
+
+        /* Translators: Description */
+        ret_desc = g_strdup (_("Logical Volume"));
+
+        /* Translators: VPD name */
+        ret_vpd = g_strdup (_("LVM2 Logical Volume"));
+
+        if (out_desc != NULL)
+                *out_desc = ret_desc;
+        else
+                g_free (ret_desc);
+
+        if (out_vpd_name != NULL)
+                *out_vpd_name = ret_vpd;
+        else
+                g_free (ret_vpd);
+
+        return ret;
+}
+
+static char *
+gdu_linux_lvm2_volume_get_name (GduPresentable *presentable)
+{
+        return get_names_and_desc (presentable, NULL, NULL);
+}
+
+static gchar *
+gdu_linux_lvm2_volume_get_description (GduPresentable *presentable)
+{
+        gchar *desc;
+        gchar *name;
+
+        name = get_names_and_desc (presentable, NULL, &desc);
+        g_free (name);
+
+        return desc;
+}
+
+static gchar *
+gdu_linux_lvm2_volume_get_vpd_name (GduPresentable *presentable)
+{
+        gchar *vpd_name;
+        gchar *name;
+
+        name = get_names_and_desc (presentable, &vpd_name, NULL);
+        g_free (name);
+
+        return vpd_name;
+}
+
+static GIcon *
+gdu_linux_lvm2_volume_get_icon (GduPresentable *presentable)
+{
+        return g_themed_icon_new_with_default_fallbacks ("gdu-raid-array");
+}
+
+static guint64
+gdu_linux_lvm2_volume_get_offset (GduPresentable *presentable)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (presentable);
+
+        return volume->priv->offset;
+}
+
+static guint64
+gdu_linux_lvm2_volume_get_size (GduPresentable *presentable)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (presentable);
+
+        return volume->priv->size;
+}
+
+static GduPool *
+gdu_linux_lvm2_volume_get_pool (GduPresentable *presentable)
+{
+        GduLinuxLvm2Volume *volume = GDU_LINUX_LVM2_VOLUME (presentable);
+        return g_object_ref (volume->priv->pool);
+}
+
+static gboolean
+gdu_linux_lvm2_volume_is_allocated (GduPresentable *presentable)
+{
+        return TRUE;
+}
+
+static gboolean
+gdu_linux_lvm2_volume_is_recognized (GduPresentable *presentable)
+{
+        /* TODO: maybe we need to return FALSE sometimes */
+        return TRUE;
+}
+
+static void
+gdu_linux_lvm2_volume_presentable_iface_init (GduPresentableIface *iface)
+{
+        iface->get_id                    = gdu_linux_lvm2_volume_get_id;
+        iface->get_device                = gdu_linux_lvm2_volume_get_device;
+        iface->get_enclosing_presentable = gdu_linux_lvm2_volume_get_enclosing_presentable;
+        iface->get_name                  = gdu_linux_lvm2_volume_get_name;
+        iface->get_description           = gdu_linux_lvm2_volume_get_description;
+        iface->get_vpd_name              = gdu_linux_lvm2_volume_get_vpd_name;
+        iface->get_icon                  = gdu_linux_lvm2_volume_get_icon;
+        iface->get_offset                = gdu_linux_lvm2_volume_get_offset;
+        iface->get_size                  = gdu_linux_lvm2_volume_get_size;
+        iface->get_pool                  = gdu_linux_lvm2_volume_get_pool;
+        iface->is_allocated              = gdu_linux_lvm2_volume_is_allocated;
+        iface->is_recognized             = gdu_linux_lvm2_volume_is_recognized;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void
+_gdu_linux_lvm2_volume_rewrite_enclosing_presentable (GduLinuxLvm2Volume *volume)
+{
+        if (volume->priv->enclosing_presentable != NULL) {
+                const gchar *enclosing_presentable_id;
+                GduPresentable *new_enclosing_presentable;
+
+                enclosing_presentable_id = gdu_presentable_get_id (volume->priv->enclosing_presentable);
+
+                new_enclosing_presentable = gdu_pool_get_presentable_by_id (volume->priv->pool,
+                                                                            enclosing_presentable_id);
+                if (new_enclosing_presentable == NULL) {
+                        g_warning ("Error rewriting enclosing_presentable for %s, no such id %s",
+                                   volume->priv->id,
+                                   enclosing_presentable_id);
+                        goto out;
+                }
+
+                g_object_unref (volume->priv->enclosing_presentable);
+                volume->priv->enclosing_presentable = new_enclosing_presentable;
+        }
+
+ out:
+        ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+const gchar *
+gdu_linux_lvm2_volume_get_uuid (GduLinuxLvm2Volume *volume)
+{
+        return volume->priv->uuid;
+}
+
+const gchar *
+gdu_linux_lvm2_volume_get_group_uuid (GduLinuxLvm2Volume *volume)
+{
+        return volume->priv->group_uuid;
+}
+
diff --git a/src/gdu/gdu-linux-lvm2-volume.h b/src/gdu/gdu-linux-lvm2-volume.h
new file mode 100644
index 0000000..018a6d9
--- /dev/null
+++ b/src/gdu/gdu-linux-lvm2-volume.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#if !defined (__GDU_INSIDE_GDU_H) && !defined (GDU_COMPILATION)
+#error "Only <gdu/gdu.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __GDU_LINUX_LVM2_VOLUME_H
+#define __GDU_LINUX_LVM2_VOLUME_H
+
+#include <gdu/gdu-types.h>
+#include <gdu/gdu-callbacks.h>
+#include <gdu/gdu-volume.h>
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_LINUX_LVM2_VOLUME         (gdu_linux_lvm2_volume_get_type ())
+#define GDU_LINUX_LVM2_VOLUME(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_LINUX_LVM2_VOLUME, GduLinuxLvm2Volume))
+#define GDU_LINUX_LVM2_VOLUME_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GDU_LINUX_LVM2_VOLUME,  GduLinuxLvm2VolumeClass))
+#define GDU_IS_LINUX_LVM2_VOLUME(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_LINUX_LVM2_VOLUME))
+#define GDU_IS_LINUX_LVM2_VOLUME_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GDU_TYPE_LINUX_LVM2_VOLUME))
+#define GDU_LINUX_LVM2_VOLUME_GET_CLASS(k) (G_TYPE_INSTANCE_GET_CLASS ((k), GDU_TYPE_LINUX_LVM2_VOLUME, GduLinuxLvm2VolumeClass))
+
+typedef struct _GduLinuxLvm2VolumeClass    GduLinuxLvm2VolumeClass;
+typedef struct _GduLinuxLvm2VolumePrivate  GduLinuxLvm2VolumePrivate;
+
+struct _GduLinuxLvm2Volume
+{
+        GduVolume parent;
+
+        /*< private >*/
+        GduLinuxLvm2VolumePrivate *priv;
+};
+
+struct _GduLinuxLvm2VolumeClass
+{
+        GduVolumeClass parent_class;
+};
+
+GType        gdu_linux_lvm2_volume_get_type (void);
+const gchar *gdu_linux_lvm2_volume_get_uuid (GduLinuxLvm2Volume *volume);
+const gchar *gdu_linux_lvm2_volume_get_group_uuid (GduLinuxLvm2Volume *volume);
+
+G_END_DECLS
+
+#endif /* __GDU_LINUX_LVM2_VOLUME_H */
diff --git a/src/gdu/gdu-pool.c b/src/gdu/gdu-pool.c
index fb8e858..2c57c51 100644
--- a/src/gdu/gdu-pool.c
+++ b/src/gdu/gdu-pool.c
@@ -39,6 +39,9 @@
 #include "gdu-hub.h"
 #include "gdu-known-filesystem.h"
 #include "gdu-private.h"
+#include "gdu-linux-lvm2-volume-group.h"
+#include "gdu-linux-lvm2-volume.h"
+#include "gdu-linux-lvm2-volume-hole.h"
 
 #include "gdu-ssh-bridge.h"
 #include "gdu-error.h"
@@ -838,6 +841,7 @@ get_holes_for_drive (GduPool   *pool,
         return ret;
 }
 
+
 static void
 recompute_presentables (GduPool *pool)
 {
@@ -851,6 +855,7 @@ recompute_presentables (GduPool *pool)
         GList *removed_presentables;
         GHashTable *hash_map_from_drive_to_extended_partition;
         GHashTable *hash_map_from_linux_md_uuid_to_drive;
+        GHashTable *hash_map_from_linux_lvm2_group_uuid_to_vg;
         GHashTable *hash_map_from_adapter_objpath_to_hub;
         GHashTable *hash_map_from_expander_objpath_to_hub;
 
@@ -879,6 +884,11 @@ recompute_presentables (GduPool *pool)
                                                                       NULL,
                                                                       NULL);
 
+        hash_map_from_linux_lvm2_group_uuid_to_vg = g_hash_table_new_full (g_str_hash,
+                                                                           g_str_equal,
+                                                                           NULL,
+                                                                           NULL);
+
         hash_map_from_adapter_objpath_to_hub = g_hash_table_new_full (g_str_hash,
                                                                       g_str_equal,
                                                                       NULL,
@@ -1135,11 +1145,100 @@ recompute_presentables (GduPool *pool)
                         volume = _gdu_volume_new_from_device (pool, device, enclosing_luks_device);
                         new_presentables = g_list_prepend (new_presentables, volume);
 
+                } else if (gdu_device_is_linux_lvm2_lv (device)) {
+
+                        /* Do nothing - this is handled when creating the Lvm2VolumeGroup object below */
+
                 } else {
-                        g_debug ("Don't know how to handle device %s", gdu_device_get_device_file (device));
+                        g_warning ("Don't know how to handle device %s", gdu_device_get_device_file (device));
+                }
+
+                /* Ensure we have a GduLinuxLvm2VolumeGroup even if the volume group isn't running */
+                if (gdu_device_is_linux_lvm2_pv (device)) {
+                        GduLinuxLvm2VolumeGroup *vg;
+                        const gchar *vg_uuid;
+
+                        vg_uuid = gdu_device_linux_lvm2_pv_get_group_uuid (device);
+
+                        /* First, see if we have a volume group for this UUID already */
+                        vg = g_hash_table_lookup (hash_map_from_linux_lvm2_group_uuid_to_vg, vg_uuid);
+                        if (vg == NULL) {
+                                gchar **lvs;
+                                guint n;
+                                guint64 offset;
+                                guint64 unallocated_size;
+
+                                /* otherwise create one */
+                                vg = _gdu_linux_lvm2_volume_group_new (pool, vg_uuid, pool->priv->machine);
+                                g_hash_table_insert (hash_map_from_linux_lvm2_group_uuid_to_vg, (gpointer) vg_uuid, vg);
+                                new_presentables = g_list_prepend (new_presentables, vg);
+
+                                /* and create logical volume objects as well */
+                                lvs = gdu_device_linux_lvm2_pv_get_group_logical_volumes (device);
+                                offset = 0;
+                                for (n = 0; lvs != NULL && lvs[n] != NULL; n++) {
+                                        const gchar *lv_desc = lvs[n];
+                                        gchar **tokens;
+                                        gchar *name;
+                                        gchar *uuid;
+                                        guint64 size;
+                                        guint m;
+
+                                        tokens = g_strsplit (lv_desc, ";", 0);
+                                        for (m = 0; tokens[m] != NULL; m++) {
+                                                /* TODO: we need to unescape values */
+
+                                                if (g_str_has_prefix (tokens[m], "name="))
+                                                        name = g_strdup (tokens[m] + 5);
+                                                else if (g_str_has_prefix (tokens[m], "uuid="))
+                                                        uuid = g_strdup (tokens[m] + 5);
+                                                else if (g_str_has_prefix (tokens[m], "size="))
+                                                        size = g_ascii_strtoull (tokens[m] + 5, NULL, 10);
+                                        }
+
+                                        if (name != NULL && uuid != NULL && size > 0) {
+                                                GduLinuxLvm2Volume *volume;
+
+                                                volume = _gdu_linux_lvm2_volume_new (pool,
+                                                                                     name,
+                                                                                     vg_uuid,
+                                                                                     uuid,
+                                                                                     offset,
+                                                                                     size,
+                                                                                     GDU_PRESENTABLE (vg));
+                                                new_presentables = g_list_prepend (new_presentables, volume);
+
+                                                offset += size;
+
+                                        } else {
+                                                g_warning ("Malformed LMV2 LV in group with UUID %s: "
+                                                           "pos=%d name=%s uuid=%s size=%" G_GUINT64_FORMAT,
+                                                           vg_uuid,
+                                                           n,
+                                                           name,
+                                                           uuid,
+                                                           size);
+                                        }
+
+                                        g_free (name);
+                                        g_free (uuid);
+                                        g_strfreev (tokens);
+                                } /* foreach LV in VG */
+
+                                /* Create a GduLinuxLvm2VolumeHole for unallocated space - TODO: use 1% or
+                                 * something based on extent size... instead of 1MB
+                                 */
+                                unallocated_size = gdu_device_linux_lvm2_pv_get_group_unallocated_size (device);
+                                if (unallocated_size >= 1000 * 1000) {
+                                        GduLinuxLvm2VolumeHole *volume_hole;
+                                        volume_hole = _gdu_linux_lvm2_volume_hole_new (pool,
+                                                                                       GDU_PRESENTABLE (vg));
+                                        new_presentables = g_list_prepend (new_presentables, volume_hole);
+                                }
+                        }
                 }
 
-                /* Ensure we have a GduLinuxMdDrive for non-running arrays */
+                /* Ensure we have a GduLinuxMdDrive for each non-running arrays */
                 if (gdu_device_is_linux_md_component (device)) {
                         const gchar *uuid;
 
@@ -1180,6 +1279,7 @@ recompute_presentables (GduPool *pool)
         g_list_free (new_partitioned_drives);
         g_hash_table_unref (hash_map_from_drive_to_extended_partition);
         g_hash_table_unref (hash_map_from_linux_md_uuid_to_drive);
+        g_hash_table_unref (hash_map_from_linux_lvm2_group_uuid_to_vg);
         g_hash_table_unref (hash_map_from_adapter_objpath_to_hub);
         g_hash_table_unref (hash_map_from_expander_objpath_to_hub);
 
@@ -1224,6 +1324,12 @@ recompute_presentables (GduPool *pool)
                         _gdu_volume_rewrite_enclosing_presentable (GDU_VOLUME (p));
                 else if (GDU_IS_VOLUME_HOLE (p))
                         _gdu_volume_hole_rewrite_enclosing_presentable (GDU_VOLUME_HOLE (p));
+                else if (GDU_IS_LINUX_LVM2_VOLUME_GROUP (p))
+                        _gdu_linux_lvm2_volume_group_rewrite_enclosing_presentable (GDU_LINUX_LVM2_VOLUME_GROUP (p));
+                else if (GDU_IS_LINUX_LVM2_VOLUME (p))
+                        _gdu_linux_lvm2_volume_rewrite_enclosing_presentable (GDU_LINUX_LVM2_VOLUME (p));
+                else if (GDU_IS_LINUX_LVM2_VOLUME_HOLE (p))
+                        _gdu_linux_lvm2_volume_hole_rewrite_enclosing_presentable (GDU_LINUX_LVM2_VOLUME_HOLE (p));
 
                 g_debug ("Added presentable %s %p", gdu_presentable_get_id (p), p);
 
@@ -2653,6 +2759,136 @@ gdu_pool_op_linux_md_create (GduPool *pool,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+typedef struct {
+        GduPool *pool;
+        GduPoolLinuxLvm2VGStartCompletedFunc callback;
+        gpointer user_data;
+} LinuxLvm2VGStartData;
+
+static void
+op_linux_lvm2_vg_start_cb (DBusGProxy *proxy, GError *error, gpointer user_data)
+{
+        LinuxLvm2VGStartData *data = user_data;
+        _gdu_error_fixup (error);
+        if (data->callback != NULL)
+                data->callback (data->pool, error, data->user_data);
+        g_object_unref (data->pool);
+        g_free (data);
+}
+
+void
+gdu_pool_op_linux_lvm2_vg_start (GduPool *pool,
+                                 const gchar *uuid,
+                                 GduPoolLinuxLvm2VGStartCompletedFunc callback,
+                                 gpointer user_data)
+{
+        LinuxLvm2VGStartData *data;
+        char *options[16];
+
+        options[0] = NULL;
+
+        data = g_new0 (LinuxLvm2VGStartData, 1);
+        data->pool = g_object_ref (pool);
+        data->callback = callback;
+        data->user_data = user_data;
+
+        org_freedesktop_UDisks_linux_lvm2_vg_start_async (pool->priv->proxy,
+                                                          uuid,
+                                                          (const char **) options,
+                                                          op_linux_lvm2_vg_start_cb,
+                                                          data);
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+        GduPool *pool;
+        GduPoolLinuxLvm2VGStopCompletedFunc callback;
+        gpointer user_data;
+} LinuxLvm2VGStopData;
+
+static void
+op_linux_lvm2_vg_stop_cb (DBusGProxy *proxy, GError *error, gpointer user_data)
+{
+        LinuxLvm2VGStopData *data = user_data;
+        _gdu_error_fixup (error);
+        if (data->callback != NULL)
+                data->callback (data->pool, error, data->user_data);
+        g_object_unref (data->pool);
+        g_free (data);
+}
+
+void
+gdu_pool_op_linux_lvm2_vg_stop (GduPool *pool,
+                                const gchar *uuid,
+                                GduPoolLinuxLvm2VGStopCompletedFunc callback,
+                                gpointer user_data)
+{
+        LinuxLvm2VGStopData *data;
+        char *options[16];
+
+        options[0] = NULL;
+
+        data = g_new0 (LinuxLvm2VGStopData, 1);
+        data->pool = g_object_ref (pool);
+        data->callback = callback;
+        data->user_data = user_data;
+
+        org_freedesktop_UDisks_linux_lvm2_vg_stop_async (pool->priv->proxy,
+                                                          uuid,
+                                                          (const char **) options,
+                                                          op_linux_lvm2_vg_stop_cb,
+                                                          data);
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+        GduPool *pool;
+        GduPoolLinuxLvm2LVStartCompletedFunc callback;
+        gpointer user_data;
+} LinuxLvm2LVStartData;
+
+static void
+op_linux_lvm2_lv_start_cb (DBusGProxy *proxy, GError *error, gpointer user_data)
+{
+        LinuxLvm2LVStartData *data = user_data;
+        _gdu_error_fixup (error);
+        if (data->callback != NULL)
+                data->callback (data->pool, error, data->user_data);
+        g_object_unref (data->pool);
+        g_free (data);
+}
+
+void
+gdu_pool_op_linux_lvm2_lv_start (GduPool *pool,
+                                 const gchar *group_uuid,
+                                 const gchar *uuid,
+                                 GduPoolLinuxLvm2LVStartCompletedFunc callback,
+                                 gpointer user_data)
+{
+        LinuxLvm2LVStartData *data;
+        char *options[16];
+
+        options[0] = NULL;
+
+        data = g_new0 (LinuxLvm2LVStartData, 1);
+        data->pool = g_object_ref (pool);
+        data->callback = callback;
+        data->user_data = user_data;
+
+        org_freedesktop_UDisks_linux_lvm2_lv_start_async (pool->priv->proxy,
+                                                          group_uuid,
+                                                          uuid,
+                                                          (const char **) options,
+                                                          op_linux_lvm2_lv_start_cb,
+                                                          data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 /**
  * gdu_pool_get_daemon_version:
  * @pool: A #GduPool.
diff --git a/src/gdu/gdu-pool.h b/src/gdu/gdu-pool.h
index 9605402..c9135e3 100644
--- a/src/gdu/gdu-pool.h
+++ b/src/gdu/gdu-pool.h
@@ -133,6 +133,22 @@ void gdu_pool_op_linux_md_create (GduPool     *pool,
                                   GduPoolLinuxMdCreateCompletedFunc callback,
                                   gpointer user_data);
 
+void gdu_pool_op_linux_lvm2_vg_start (GduPool *pool,
+                                      const gchar *uuid,
+                                      GduPoolLinuxLvm2VGStartCompletedFunc callback,
+                                      gpointer user_data);
+
+void gdu_pool_op_linux_lvm2_vg_stop (GduPool *pool,
+                                     const gchar *uuid,
+                                     GduPoolLinuxLvm2VGStopCompletedFunc callback,
+                                     gpointer user_data);
+
+void gdu_pool_op_linux_lvm2_lv_start (GduPool *pool,
+                                      const gchar *group_uuid,
+                                      const gchar *uuid,
+                                      GduPoolLinuxLvm2VGStartCompletedFunc callback,
+                                      gpointer user_data);
+
 G_END_DECLS
 
 #endif /* __GDU_POOL_H */
diff --git a/src/gdu/gdu-private.h b/src/gdu/gdu-private.h
index ac5a2c0..406ba2d 100644
--- a/src/gdu/gdu-private.h
+++ b/src/gdu/gdu-private.h
@@ -123,6 +123,29 @@ gboolean    _gdu_port_changed               (GduPort   *port);
 
 GduMachine *_gdu_machine_new (GduPool *pool);
 
+GduLinuxLvm2VolumeGroup *
+_gdu_linux_lvm2_volume_group_new (GduPool        *pool,
+                                  const gchar    *vg_uuid,
+                                  GduPresentable *enclosing_presentable);
+
+void _gdu_linux_lvm2_volume_group_rewrite_enclosing_presentable (GduLinuxLvm2VolumeGroup *vg);
+
+GduLinuxLvm2Volume *_gdu_linux_lvm2_volume_new (GduPool        *pool,
+                                                const gchar    *name,
+                                                const gchar    *group_uuid,
+                                                const gchar    *uuid,
+                                                guint64         offset,
+                                                guint64         size,
+                                                GduPresentable *enclosing_presentable);
+
+void _gdu_linux_lvm2_volume_rewrite_enclosing_presentable (GduLinuxLvm2Volume *volume);
+
+GduLinuxLvm2VolumeHole *_gdu_linux_lvm2_volume_hole_new (GduPool        *pool,
+                                                         GduPresentable *enclosing_presentable);
+
+void _gdu_linux_lvm2_volume_hole_rewrite_enclosing_presentable (GduLinuxLvm2VolumeHole *volume_hole);
+
+
 void _gdu_hub_rewrite_enclosing_presentable (GduHub *hub);
 void _gdu_drive_rewrite_enclosing_presentable (GduDrive *drive);
 void _gdu_linux_md_drive_rewrite_enclosing_presentable (GduLinuxMdDrive *drive);
diff --git a/src/gdu/gdu-types.h b/src/gdu/gdu-types.h
index 8de117f..7a768f4 100644
--- a/src/gdu/gdu-types.h
+++ b/src/gdu/gdu-types.h
@@ -43,8 +43,11 @@ typedef struct _GduPresentable            GduPresentable; /* Dummy typedef */
 
 typedef struct _GduDrive                  GduDrive;
 typedef struct _GduLinuxMdDrive           GduLinuxMdDrive;
+typedef struct _GduLinuxLvm2VolumeGroup   GduLinuxLvm2VolumeGroup;
 typedef struct _GduVolume                 GduVolume;
 typedef struct _GduVolumeHole             GduVolumeHole;
+typedef struct _GduLinuxLvm2Volume        GduLinuxLvm2Volume;
+typedef struct _GduLinuxLvm2VolumeHole    GduLinuxLvm2VolumeHole;
 typedef struct _GduHub                    GduHub;
 typedef struct _GduMachine                GduMachine;
 
diff --git a/src/gdu/gdu-volume-hole.c b/src/gdu/gdu-volume-hole.c
index b3c5dfe..c4425cf 100644
--- a/src/gdu/gdu-volume-hole.c
+++ b/src/gdu/gdu-volume-hole.c
@@ -66,7 +66,8 @@ gdu_volume_hole_finalize (GduVolumeHole *volume_hole)
 {
         //g_debug ("finalized volume_hole '%s' %p", volume_hole->priv->id, volume_hole);
 
-        g_object_unref (volume_hole->priv->pool);
+        if (volume_hole->priv->pool != NULL)
+                g_object_unref (volume_hole->priv->pool);
 
         if (volume_hole->priv->enclosing_presentable != NULL)
                 g_object_unref (volume_hole->priv->enclosing_presentable);
diff --git a/src/gdu/gdu.h b/src/gdu/gdu.h
index 8c805ec..f642059 100644
--- a/src/gdu/gdu.h
+++ b/src/gdu/gdu.h
@@ -30,6 +30,9 @@
 
 #include <gdu/gdu-types.h>
 #include <gdu/gdu-linux-md-drive.h>
+#include <gdu/gdu-linux-lvm2-volume-group.h>
+#include <gdu/gdu-linux-lvm2-volume.h>
+#include <gdu/gdu-linux-lvm2-volume-hole.h>
 #include <gdu/gdu-device.h>
 #include <gdu/gdu-adapter.h>
 #include <gdu/gdu-expander.h>
diff --git a/src/palimpsest/Makefile.am b/src/palimpsest/Makefile.am
index eb4a9c0..95baa9b 100644
--- a/src/palimpsest/Makefile.am
+++ b/src/palimpsest/Makefile.am
@@ -8,6 +8,7 @@ palimpsest_SOURCES = 									\
 	gdu-shell.h				gdu-shell.c				\
 	gdu-section.h				gdu-section.c				\
 	gdu-section-linux-md-drive.h		gdu-section-linux-md-drive.c		\
+	gdu-section-linux-lvm2-volume-group.h	gdu-section-linux-lvm2-volume-group.c	\
 	gdu-section-drive.h			gdu-section-drive.c			\
 	gdu-section-volumes.h			gdu-section-volumes.c			\
 	gdu-section-hub.h			gdu-section-hub.c			\
diff --git a/src/palimpsest/gdu-section-linux-lvm2-volume-group.c b/src/palimpsest/gdu-section-linux-lvm2-volume-group.c
new file mode 100644
index 0000000..fa501be
--- /dev/null
+++ b/src/palimpsest/gdu-section-linux-lvm2-volume-group.c
@@ -0,0 +1,383 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-section-linux-md-drive.c
+ *
+ * Copyright (C) 2007 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+
+#include <string.h>
+#include <dbus/dbus-glib.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <gdu/gdu.h>
+#include <gdu-gtk/gdu-gtk.h>
+
+#include "gdu-section-drive.h"
+#include "gdu-section-linux-lvm2-volume-group.h"
+
+struct _GduSectionLinuxLvm2VolumeGroupPrivate
+{
+        GduDetailsElement *name_element;
+        GduDetailsElement *state_element;
+        GduDetailsElement *capacity_element;
+        GduDetailsElement *extent_size_element;
+        GduDetailsElement *unallocated_size_element;
+        GduDetailsElement *num_pvs_element;
+
+        GduButtonElement *vg_start_button;
+        GduButtonElement *vg_stop_button;
+};
+
+G_DEFINE_TYPE (GduSectionLinuxLvm2VolumeGroup, gdu_section_linux_lvm2_volume_group, GDU_TYPE_SECTION)
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gdu_section_linux_lvm2_volume_group_finalize (GObject *object)
+{
+        //GduSectionLinuxLvm2VolumeGroup *section = GDU_SECTION_LINUX_LVM2_VOLUME_GROUP (object);
+
+        if (G_OBJECT_CLASS (gdu_section_linux_lvm2_volume_group_parent_class)->finalize != NULL)
+                G_OBJECT_CLASS (gdu_section_linux_lvm2_volume_group_parent_class)->finalize (object);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+lvm2_vg_start_op_callback (GduPool   *pool,
+                           GError    *error,
+                           gpointer   user_data)
+{
+        GduShell *shell = GDU_SHELL (user_data);
+
+        if (error != NULL) {
+                GtkWidget *dialog;
+                dialog = gdu_error_dialog_new (GTK_WINDOW (gdu_shell_get_toplevel (shell)),
+                                               NULL,
+                                               _("Error starting Volume Group"),
+                                               error);
+                gtk_widget_show_all (dialog);
+                gtk_window_present (GTK_WINDOW (dialog));
+                gtk_dialog_run (GTK_DIALOG (dialog));
+                gtk_widget_destroy (dialog);
+                g_error_free (error);
+        }
+        g_object_unref (shell);
+}
+
+static void
+on_lvm2_vg_start_button_clicked (GduButtonElement *button_element,
+                                 gpointer          user_data)
+{
+        GduSectionLinuxLvm2VolumeGroup *section = GDU_SECTION_LINUX_LVM2_VOLUME_GROUP (user_data);
+        GduLinuxLvm2VolumeGroup *vg;
+        GduPool *pool;
+        const gchar *uuid;
+
+        vg = GDU_LINUX_LVM2_VOLUME_GROUP (gdu_section_get_presentable (GDU_SECTION (section)));
+        pool = gdu_presentable_get_pool (GDU_PRESENTABLE (vg));
+
+        uuid = gdu_linux_lvm2_volume_group_get_uuid (vg);
+
+        gdu_pool_op_linux_lvm2_vg_start (pool,
+                                        uuid,
+                                        lvm2_vg_start_op_callback,
+                                        g_object_ref (gdu_section_get_shell (GDU_SECTION (section))));
+
+        g_object_unref (pool);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+lvm2_vg_stop_op_callback (GduPool   *pool,
+                          GError    *error,
+                          gpointer   user_data)
+{
+        GduShell *shell = GDU_SHELL (user_data);
+
+        if (error != NULL) {
+                GtkWidget *dialog;
+                dialog = gdu_error_dialog_new (GTK_WINDOW (gdu_shell_get_toplevel (shell)),
+                                               NULL,
+                                               _("Error stopping Volume Group"),
+                                               error);
+                gtk_widget_show_all (dialog);
+                gtk_window_present (GTK_WINDOW (dialog));
+                gtk_dialog_run (GTK_DIALOG (dialog));
+                gtk_widget_destroy (dialog);
+                g_error_free (error);
+        }
+        g_object_unref (shell);
+}
+
+static void
+on_lvm2_vg_stop_button_clicked (GduButtonElement *button_element,
+                                gpointer          user_data)
+{
+        GduSectionLinuxLvm2VolumeGroup *section = GDU_SECTION_LINUX_LVM2_VOLUME_GROUP (user_data);
+        GduLinuxLvm2VolumeGroup *vg;
+        GduPool *pool;
+        const gchar *uuid;
+
+        vg = GDU_LINUX_LVM2_VOLUME_GROUP (gdu_section_get_presentable (GDU_SECTION (section)));
+        pool = gdu_presentable_get_pool (GDU_PRESENTABLE (vg));
+
+        uuid = gdu_linux_lvm2_volume_group_get_uuid (vg);
+
+        gdu_pool_op_linux_lvm2_vg_stop (pool,
+                                        uuid,
+                                        lvm2_vg_stop_op_callback,
+                                        g_object_ref (gdu_section_get_shell (GDU_SECTION (section))));
+
+        g_object_unref (pool);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gdu_section_linux_lvm2_volume_group_update (GduSection *_section)
+{
+        GduSectionLinuxLvm2VolumeGroup *section = GDU_SECTION_LINUX_LVM2_VOLUME_GROUP (_section);
+        GduPresentable *p;
+        GduLinuxLvm2VolumeGroup *vg;
+        GduDevice *pv_device;
+        const gchar *name;
+        guint64 size;
+        guint64 unallocated_size;
+        guint64 extent_size;
+        gchar **pvs;
+        gchar *s;
+        GduLinuxLvm2VolumeGroupState state;
+        gchar *state_str;
+        gboolean show_vg_start_button;
+        gboolean show_vg_stop_button;
+
+        show_vg_start_button = FALSE;
+        show_vg_stop_button = FALSE;
+
+        state_str = NULL;
+
+        p = gdu_section_get_presentable (_section);
+        vg = GDU_LINUX_LVM2_VOLUME_GROUP (p);
+
+        pv_device = gdu_linux_lvm2_volume_group_get_pv_device (vg);
+        if (pv_device == NULL)
+                goto out;
+
+        name = gdu_device_linux_lvm2_pv_get_group_name (pv_device);
+        size = gdu_device_linux_lvm2_pv_get_group_size (pv_device);
+        unallocated_size = gdu_device_linux_lvm2_pv_get_group_unallocated_size (pv_device);
+        extent_size = gdu_device_linux_lvm2_pv_get_group_extent_size (pv_device);
+        pvs = gdu_device_linux_lvm2_pv_get_group_physical_volumes (pv_device);
+
+        gdu_details_element_set_text (section->priv->name_element, name);
+        s = gdu_util_get_size_for_display (size, FALSE, TRUE);
+        gdu_details_element_set_text (section->priv->capacity_element, s);
+        g_free (s);
+        s = gdu_util_get_size_for_display (unallocated_size, FALSE, TRUE);
+        gdu_details_element_set_text (section->priv->unallocated_size_element, s);
+        g_free (s);
+        /* Use the nerd units here (MiB) since that's what LVM defaults to (divisble by sector size etc.) */
+        s = gdu_util_get_size_for_display (extent_size, TRUE, TRUE);
+        gdu_details_element_set_text (section->priv->extent_size_element, s);
+        g_free (s);
+        s = g_strdup_printf ("%d", g_strv_length (pvs));
+        gdu_details_element_set_text (section->priv->num_pvs_element, s);
+        g_free (s);
+
+        state = gdu_linux_lvm2_volume_group_get_state (vg);
+
+        switch (state) {
+        case GDU_LINUX_LVM2_VOLUME_GROUP_STATE_NOT_RUNNING:
+                state_str = g_strdup (_("Not Running"));
+                show_vg_start_button = TRUE;
+                break;
+        case GDU_LINUX_LVM2_VOLUME_GROUP_STATE_PARTIALLY_RUNNING:
+                state_str = g_strdup (_("Partially Running"));
+                show_vg_start_button = TRUE;
+                show_vg_stop_button = TRUE;
+                break;
+        case GDU_LINUX_LVM2_VOLUME_GROUP_STATE_RUNNING:
+                state_str = g_strdup (_("Running"));
+                show_vg_stop_button = TRUE;
+                break;
+        default:
+                state_str = g_strdup_printf (_("Unknown (%d)"), state);
+                show_vg_start_button = TRUE;
+                show_vg_stop_button = TRUE;
+                break;
+        }
+        gdu_details_element_set_text (section->priv->state_element, state_str);
+
+
+ out:
+        gdu_button_element_set_visible (section->priv->vg_start_button, show_vg_start_button);
+        gdu_button_element_set_visible (section->priv->vg_stop_button, show_vg_stop_button);
+
+        if (pv_device != NULL)
+                g_object_unref (pv_device);
+        g_free (state_str);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gdu_section_linux_lvm2_volume_group_constructed (GObject *object)
+{
+        GduSectionLinuxLvm2VolumeGroup *section = GDU_SECTION_LINUX_LVM2_VOLUME_GROUP (object);
+        GtkWidget *align;
+        GtkWidget *label;
+        GtkWidget *table;
+        GtkWidget *vbox;
+        gchar *s;
+        GduPresentable *p;
+        GduDevice *d;
+        GPtrArray *elements;
+        GduDetailsElement *element;
+        GduButtonElement *button_element;
+
+        p = gdu_section_get_presentable (GDU_SECTION (section));
+        d = gdu_presentable_get_device (p);
+
+        gtk_box_set_spacing (GTK_BOX (section), 12);
+
+        /*------------------------------------- */
+
+        label = gtk_label_new (NULL);
+        gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+        s = g_strconcat ("<b>", _("Volume Group"), "</b>", NULL);
+        gtk_label_set_markup (GTK_LABEL (label), s);
+        g_free (s);
+        gtk_box_pack_start (GTK_BOX (section), label, FALSE, FALSE, 0);
+
+        align = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+        gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 12, 0);
+        gtk_box_pack_start (GTK_BOX (section), align, FALSE, FALSE, 0);
+
+        vbox = gtk_vbox_new (FALSE, 6);
+        gtk_container_add (GTK_CONTAINER (align), vbox);
+
+        elements = g_ptr_array_new_with_free_func (g_object_unref);
+
+        element = gdu_details_element_new (_("Name:"), NULL, NULL);
+        g_ptr_array_add (elements, element);
+        section->priv->name_element = element;
+
+        element = gdu_details_element_new (_("Extent Size:"), NULL, NULL);
+        g_ptr_array_add (elements, element);
+        section->priv->extent_size_element = element;
+
+        element = gdu_details_element_new (_("Physical Volumes:"), NULL, NULL);
+        g_ptr_array_add (elements, element);
+        section->priv->num_pvs_element = element;
+
+        element = gdu_details_element_new (_("Capacity:"), NULL, NULL);
+        g_ptr_array_add (elements, element);
+        section->priv->capacity_element = element;
+
+        element = gdu_details_element_new (_("State:"), NULL, NULL);
+        g_ptr_array_add (elements, element);
+        section->priv->state_element = element;
+
+        element = gdu_details_element_new (_("Unallocated:"), NULL, NULL);
+        g_ptr_array_add (elements, element);
+        section->priv->unallocated_size_element = element;
+
+        table = gdu_details_table_new (2, elements);
+        g_ptr_array_unref (elements);
+        gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
+
+        /* -------------------------------------------------------------------------------- */
+
+        align = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+        gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 12, 0);
+        gtk_box_pack_start (GTK_BOX (vbox), align, FALSE, FALSE, 0);
+
+        table = gdu_button_table_new (2, NULL);
+        gtk_container_add (GTK_CONTAINER (align), table);
+        elements = g_ptr_array_new_with_free_func (g_object_unref);
+
+        button_element = gdu_button_element_new ("gdu-raid-array-start",
+                                                 _("St_art Volume Group"),
+                                                 _("Activate all LVs in the VG"));
+        g_signal_connect (button_element,
+                          "clicked",
+                          G_CALLBACK (on_lvm2_vg_start_button_clicked),
+                          section);
+        section->priv->vg_start_button = button_element;
+        g_ptr_array_add (elements, button_element);
+
+        button_element = gdu_button_element_new ("gdu-raid-array-stop",
+                                                 _("St_op Volume Group"),
+                                                 _("Deactivate all LVs in the VG"));
+        g_signal_connect (button_element,
+                          "clicked",
+                          G_CALLBACK (on_lvm2_vg_stop_button_clicked),
+                          section);
+        section->priv->vg_stop_button = button_element;
+        g_ptr_array_add (elements, button_element);
+
+        gdu_button_table_set_elements (GDU_BUTTON_TABLE (table), elements);
+        g_ptr_array_unref (elements);
+
+        /* -------------------------------------------------------------------------------- */
+
+        gtk_widget_show_all (GTK_WIDGET (section));
+
+        if (d != NULL)
+                g_object_unref (d);
+
+        if (G_OBJECT_CLASS (gdu_section_linux_lvm2_volume_group_parent_class)->constructed != NULL)
+                G_OBJECT_CLASS (gdu_section_linux_lvm2_volume_group_parent_class)->constructed (object);
+}
+
+static void
+gdu_section_linux_lvm2_volume_group_class_init (GduSectionLinuxLvm2VolumeGroupClass *klass)
+{
+        GObjectClass *gobject_class;
+        GduSectionClass *section_class;
+
+        gobject_class = G_OBJECT_CLASS (klass);
+        section_class = GDU_SECTION_CLASS (klass);
+
+        gobject_class->finalize    = gdu_section_linux_lvm2_volume_group_finalize;
+        gobject_class->constructed = gdu_section_linux_lvm2_volume_group_constructed;
+        section_class->update      = gdu_section_linux_lvm2_volume_group_update;
+
+        g_type_class_add_private (klass, sizeof (GduSectionLinuxLvm2VolumeGroupPrivate));
+}
+
+static void
+gdu_section_linux_lvm2_volume_group_init (GduSectionLinuxLvm2VolumeGroup *section)
+{
+        section->priv = G_TYPE_INSTANCE_GET_PRIVATE (section, GDU_TYPE_SECTION_LINUX_LVM2_VOLUME_GROUP, GduSectionLinuxLvm2VolumeGroupPrivate);
+}
+
+GtkWidget *
+gdu_section_linux_lvm2_volume_group_new (GduShell       *shell,
+                                         GduPresentable *presentable)
+{
+        return GTK_WIDGET (g_object_new (GDU_TYPE_SECTION_LINUX_LVM2_VOLUME_GROUP,
+                                         "shell", shell,
+                                         "presentable", presentable,
+                                         NULL));
+}
diff --git a/src/palimpsest/gdu-section-linux-lvm2-volume-group.h b/src/palimpsest/gdu-section-linux-lvm2-volume-group.h
new file mode 100644
index 0000000..8511c04
--- /dev/null
+++ b/src/palimpsest/gdu-section-linux-lvm2-volume-group.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-section-linux-md-drive.h
+ *
+ * Copyright (C) 2007 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+#include "gdu-section.h"
+
+#ifndef GDU_SECTION_LINUX_LVM2_VOLUME_GROUP_H
+#define GDU_SECTION_LINUX_LVM2_VOLUME_GROUP_H
+
+#define GDU_TYPE_SECTION_LINUX_LVM2_VOLUME_GROUP             (gdu_section_linux_lvm2_volume_group_get_type ())
+#define GDU_SECTION_LINUX_LVM2_VOLUME_GROUP(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDU_TYPE_SECTION_LINUX_LVM2_VOLUME_GROUP, GduSectionLinuxLvm2VolumeGroup))
+#define GDU_SECTION_LINUX_LVM2_VOLUME_GROUP_CLASS(obj)       (G_TYPE_CHECK_CLASS_CAST ((obj), GDU_SECTION_LINUX_LVM2_VOLUME_GROUP,  GduSectionLinuxLvm2VolumeGroupClass))
+#define GDU_IS_SECTION_LINUX_LVM2_VOLUME_GROUP(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDU_TYPE_SECTION_LINUX_LVM2_VOLUME_GROUP))
+#define GDU_IS_SECTION_LINUX_LVM2_VOLUME_GROUP_CLASS(obj)    (G_TYPE_CHECK_CLASS_TYPE ((obj), GDU_TYPE_SECTION_LINUX_LVM2_VOLUME_GROUP))
+#define GDU_SECTION_LINUX_LVM2_VOLUME_GROUP_GET_CLASS        (G_TYPE_INSTANCE_GET_CLASS ((obj), GDU_TYPE_SECTION_LINUX_LVM2_VOLUME_GROUP, GduSectionLinuxLvm2VolumeGroupClass))
+
+typedef struct _GduSectionLinuxLvm2VolumeGroupClass       GduSectionLinuxLvm2VolumeGroupClass;
+typedef struct _GduSectionLinuxLvm2VolumeGroup            GduSectionLinuxLvm2VolumeGroup;
+
+struct _GduSectionLinuxLvm2VolumeGroupPrivate;
+typedef struct _GduSectionLinuxLvm2VolumeGroupPrivate     GduSectionLinuxLvm2VolumeGroupPrivate;
+
+struct _GduSectionLinuxLvm2VolumeGroup
+{
+        GduSection parent;
+
+        /* private */
+        GduSectionLinuxLvm2VolumeGroupPrivate *priv;
+};
+
+struct _GduSectionLinuxLvm2VolumeGroupClass
+{
+        GduSectionClass parent_class;
+};
+
+GType            gdu_section_linux_lvm2_volume_group_get_type (void);
+GtkWidget       *gdu_section_linux_lvm2_volume_group_new      (GduShell       *shell,
+                                                               GduPresentable *presentable);
+
+#endif /* GDU_SECTION_LINUX_LVM2_VOLUME_GROUP_H */
diff --git a/src/palimpsest/gdu-section-volumes.c b/src/palimpsest/gdu-section-volumes.c
index 35d56f4..ff33248 100644
--- a/src/palimpsest/gdu-section-volumes.c
+++ b/src/palimpsest/gdu-section-volumes.c
@@ -38,6 +38,10 @@ struct _GduSectionVolumesPrivate
         GtkWidget *details_table;
         GtkWidget *button_table;
 
+        /* elements for LVM2 Logical Volumes */
+        GduDetailsElement *lvm2_name_element;
+        GduDetailsElement *lvm2_state_element;
+
         /* shared between all volume types */
         GduDetailsElement *usage_element;
         GduDetailsElement *capacity_element;
@@ -64,6 +68,9 @@ struct _GduSectionVolumesPrivate
         GduButtonElement *luks_unlock_button;
         GduButtonElement *luks_forget_passphrase_button;
         GduButtonElement *luks_change_passphrase_button;
+        GduButtonElement *lvm2_create_lv_button;
+        GduButtonElement *lvm2_lv_start_button;
+        GduButtonElement *lvm2_lv_stop_button;
 };
 
 G_DEFINE_TYPE (GduSectionVolumes, gdu_section_volumes, GDU_TYPE_SECTION)
@@ -1357,6 +1364,125 @@ on_fs_mount_point_element_activated (GduDetailsElement *element,
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
+on_lvm2_create_lv_button_clicked (GduButtonElement *button_element,
+                                  gpointer          user_data)
+{
+        GduSectionVolumes *section = GDU_SECTION_VOLUMES (user_data);
+        GtkWidget *dialog;
+        GError *error;
+
+        error = g_error_new (GDU_ERROR,
+                             GDU_ERROR_NOT_SUPPORTED,
+                             _("Not yet implemented"));
+        dialog = gdu_error_dialog_new (GTK_WINDOW (gdu_shell_get_toplevel (gdu_section_get_shell (GDU_SECTION (section)))),
+                                       gdu_section_get_presentable (GDU_SECTION (section)),
+                                       _("There was an error creating a Logical Volume"),
+                                       error);
+        gtk_widget_show_all (dialog);
+        gtk_window_present (GTK_WINDOW (dialog));
+        gtk_dialog_run (GTK_DIALOG (dialog));
+        gtk_widget_destroy (dialog);
+        g_error_free (error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+lvm2_lv_stop_op_callback (GduDevice *device,
+                          GError    *error,
+                          gpointer   user_data)
+{
+        GduShell *shell = GDU_SHELL (user_data);
+
+        if (error != NULL) {
+                GtkWidget *dialog;
+                dialog = gdu_error_dialog_new_for_volume (GTK_WINDOW (gdu_shell_get_toplevel (shell)),
+                                                          device,
+                                                          _("Error stopping Logical Volume"),
+                                                          error);
+                gtk_widget_show_all (dialog);
+                gtk_window_present (GTK_WINDOW (dialog));
+                gtk_dialog_run (GTK_DIALOG (dialog));
+                gtk_widget_destroy (dialog);
+                g_error_free (error);
+        }
+        g_object_unref (shell);
+}
+
+static void
+on_lvm2_lv_stop_button_clicked (GduButtonElement *button_element,
+                                gpointer          user_data)
+{
+        GduSectionVolumes *section = GDU_SECTION_VOLUMES (user_data);
+        GduLinuxLvm2Volume *volume;
+        GduDevice *d;
+
+        volume = GDU_LINUX_LVM2_VOLUME (gdu_volume_grid_get_selected (GDU_VOLUME_GRID (section->priv->grid)));
+        d = gdu_presentable_get_device (GDU_PRESENTABLE (volume));
+        if (d == NULL)
+                goto out;
+
+        gdu_device_op_linux_lvm2_lv_stop (d,
+                                          lvm2_lv_stop_op_callback,
+                                          g_object_ref (gdu_section_get_shell (GDU_SECTION (section))));
+
+ out:
+        if (d != NULL)
+                g_object_unref (d);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+lvm2_lv_start_op_callback (GduPool   *pool,
+                           GError    *error,
+                           gpointer   user_data)
+{
+        GduShell *shell = GDU_SHELL (user_data);
+
+        if (error != NULL) {
+                GtkWidget *dialog;
+                dialog = gdu_error_dialog_new (GTK_WINDOW (gdu_shell_get_toplevel (shell)),
+                                               NULL,
+                                               _("Error starting Logical Volume"),
+                                               error);
+                gtk_widget_show_all (dialog);
+                gtk_window_present (GTK_WINDOW (dialog));
+                gtk_dialog_run (GTK_DIALOG (dialog));
+                gtk_widget_destroy (dialog);
+                g_error_free (error);
+        }
+        g_object_unref (shell);
+}
+
+static void
+on_lvm2_lv_start_button_clicked (GduButtonElement *button_element,
+                                 gpointer          user_data)
+{
+        GduSectionVolumes *section = GDU_SECTION_VOLUMES (user_data);
+        GduLinuxLvm2Volume *volume;
+        GduPool *pool;
+        const gchar *group_uuid;
+        const gchar *uuid;
+
+        volume = GDU_LINUX_LVM2_VOLUME (gdu_volume_grid_get_selected (GDU_VOLUME_GRID (section->priv->grid)));
+        pool = gdu_presentable_get_pool (GDU_PRESENTABLE (volume));
+
+        group_uuid = gdu_linux_lvm2_volume_get_group_uuid (volume);
+        uuid = gdu_linux_lvm2_volume_get_uuid (volume);
+
+        gdu_pool_op_linux_lvm2_lv_start (pool,
+                                         group_uuid,
+                                         uuid,
+                                         lvm2_lv_start_op_callback,
+                                         g_object_ref (gdu_section_get_shell (GDU_SECTION (section))));
+
+        g_object_unref (pool);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
 gdu_section_volumes_update (GduSection *_section)
 {
         GduSectionVolumes *section = GDU_SECTION_VOLUMES (_section);
@@ -1378,6 +1504,9 @@ gdu_section_volumes_update (GduSection *_section)
         gboolean show_luks_unlock_button;
         gboolean show_luks_forget_passphrase_button;
         gboolean show_luks_change_passphrase_button;
+        gboolean show_lvm2_create_lv_button;
+        gboolean show_lvm2_lv_start_button;
+        gboolean show_lvm2_lv_stop_button;
         GduKnownFilesystem *kfs;
         GPtrArray *elements;
 
@@ -1398,6 +1527,9 @@ gdu_section_volumes_update (GduSection *_section)
         show_luks_unlock_button = FALSE;
         show_luks_forget_passphrase_button = FALSE;
         show_luks_change_passphrase_button = FALSE;
+        show_lvm2_create_lv_button = FALSE;
+        show_lvm2_lv_start_button = FALSE;
+        show_lvm2_lv_stop_button = FALSE;
 
         v = gdu_volume_grid_get_selected (GDU_VOLUME_GRID (section->priv->grid));
 
@@ -1423,6 +1555,8 @@ gdu_section_volumes_update (GduSection *_section)
                 g_object_unref (section->priv->cur_volume);
         section->priv->cur_volume = v != NULL ? g_object_ref (v) : NULL;
 
+        section->priv->lvm2_name_element = NULL;
+        section->priv->lvm2_state_element = NULL;
         section->priv->usage_element = NULL;
         section->priv->capacity_element = NULL;
         section->priv->partition_type_element = NULL;
@@ -1436,6 +1570,13 @@ gdu_section_volumes_update (GduSection *_section)
 
         elements = g_ptr_array_new_with_free_func (g_object_unref);
 
+        if (GDU_IS_LINUX_LVM2_VOLUME (v)) {
+                section->priv->lvm2_name_element = gdu_details_element_new (_("Volume Name:"), NULL, NULL);
+                g_ptr_array_add (elements, section->priv->lvm2_name_element);
+                section->priv->lvm2_state_element = gdu_details_element_new (_("State:"), NULL, NULL);
+                g_ptr_array_add (elements, section->priv->lvm2_state_element);
+        }
+
         section->priv->usage_element = gdu_details_element_new (_("Usage:"), NULL, NULL);
         g_ptr_array_add (elements, section->priv->usage_element);
         g_signal_connect (section->priv->usage_element,
@@ -1484,6 +1625,21 @@ gdu_section_volumes_update (GduSection *_section)
         /* ---------------------------------------------------------------------------------------------------- */
         /* reset all elements */
 
+        if (GDU_IS_LINUX_LVM2_VOLUME (v)) {
+                gchar *lv_name;
+                lv_name = gdu_presentable_get_name (v);
+                gdu_details_element_set_text (section->priv->lvm2_name_element, lv_name);
+                g_free (lv_name);
+                gdu_details_element_set_text (section->priv->lvm2_state_element,
+                                              d != NULL ?
+                                              C_("LVM2 LV State", "Running") :
+                                              C_("LVM2 LV State", "Not Running"));
+                if (d != NULL)
+                        show_lvm2_lv_stop_button = TRUE;
+                else
+                        show_lvm2_lv_start_button = TRUE;
+        }
+
         if (section->priv->usage_element != NULL) {
                 gdu_details_element_set_text (section->priv->usage_element, "â??");
                 gdu_details_element_set_action_text (section->priv->usage_element, NULL);
@@ -1594,7 +1750,9 @@ gdu_section_volumes_update (GduSection *_section)
         /* ---------------------------------------------------------------------------------------------------- */
         /* populate according to usage */
 
-        show_format_button = TRUE;
+        if (d != NULL)
+                show_format_button = TRUE;
+
         if (g_strcmp0 (id_usage, "filesystem") == 0) {
                 const gchar *label;
 
@@ -1690,6 +1848,13 @@ gdu_section_volumes_update (GduSection *_section)
                 gdu_details_element_set_text (section->priv->usage_element, _("Container for Logical Partitions"));
 
                 show_format_button = FALSE;
+
+        } else if (GDU_IS_LINUX_LVM2_VOLUME_HOLE (v)) {
+                gdu_details_element_set_text (section->priv->usage_element, _("Unallocated Space"));
+                gdu_details_element_set_text (section->priv->device_element, "â??");
+                show_lvm2_create_lv_button = TRUE;
+                show_format_button = FALSE;
+
         } else if (GDU_IS_VOLUME_HOLE (v)) {
                 GduDevice *drive_device;
                 gdu_details_element_set_text (section->priv->usage_element, _("Unallocated Space"));
@@ -1728,6 +1893,10 @@ gdu_section_volumes_update (GduSection *_section)
         gdu_button_element_set_visible (section->priv->luks_unlock_button, show_luks_unlock_button);
         gdu_button_element_set_visible (section->priv->luks_forget_passphrase_button, show_luks_forget_passphrase_button);
         gdu_button_element_set_visible (section->priv->luks_change_passphrase_button, show_luks_change_passphrase_button);
+        gdu_button_element_set_visible (section->priv->lvm2_create_lv_button, show_lvm2_create_lv_button);
+        gdu_button_element_set_visible (section->priv->lvm2_lv_start_button, show_lvm2_lv_start_button);
+        gdu_button_element_set_visible (section->priv->lvm2_lv_stop_button, show_lvm2_lv_stop_button);
+
 
         if (d != NULL)
                 g_object_unref (d);
@@ -1754,6 +1923,7 @@ static void
 gdu_section_volumes_constructed (GObject *object)
 {
         GduSectionVolumes *section = GDU_SECTION_VOLUMES (object);
+        GduPresentable *presentable;
         GPtrArray *button_elements;
         GduButtonElement *button_element;
         GtkWidget *grid;
@@ -1767,9 +1937,15 @@ gdu_section_volumes_constructed (GObject *object)
 
         /*------------------------------------- */
 
+        presentable = gdu_section_get_presentable (GDU_SECTION (section));
+
         label = gtk_label_new (NULL);
         gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-        s = g_strconcat ("<b>", _("_Volumes"), "</b>", NULL);
+        if (GDU_IS_LINUX_LVM2_VOLUME_GROUP (presentable)) {
+                s = g_strconcat ("<b>", _("Logical _Volumes"), "</b>", NULL);
+        } else {
+                s = g_strconcat ("<b>", _("_Volumes"), "</b>", NULL);
+        }
         gtk_label_set_markup (GTK_LABEL (label), s);
         gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
         g_free (s);
@@ -1930,6 +2106,36 @@ gdu_section_volumes_constructed (GObject *object)
         g_ptr_array_add (button_elements, button_element);
         section->priv->luks_change_passphrase_button = button_element;
 
+        button_element = gdu_button_element_new (GTK_STOCK_ADD,
+                                                 _("_Create Logical Volume"),
+                                                 _("Create a new logical volume"));
+        g_signal_connect (button_element,
+                          "clicked",
+                          G_CALLBACK (on_lvm2_create_lv_button_clicked),
+                          section);
+        g_ptr_array_add (button_elements, button_element);
+        section->priv->lvm2_create_lv_button = button_element;
+
+        button_element = gdu_button_element_new ("gdu-raid-array-start",
+                                                 _("S_tart Volume"),
+                                                 _("Activate the Logical Volume"));
+        g_signal_connect (button_element,
+                          "clicked",
+                          G_CALLBACK (on_lvm2_lv_start_button_clicked),
+                          section);
+        section->priv->lvm2_lv_start_button = button_element;
+        g_ptr_array_add (button_elements, button_element);
+
+        button_element = gdu_button_element_new ("gdu-raid-array-stop",
+                                                 _("Sto_p Volume"),
+                                                 _("Deactivate the Logical Volume"));
+        g_signal_connect (button_element,
+                          "clicked",
+                          G_CALLBACK (on_lvm2_lv_stop_button_clicked),
+                          section);
+        section->priv->lvm2_lv_stop_button = button_element;
+        g_ptr_array_add (button_elements, button_element);
+
         gdu_button_table_set_elements (GDU_BUTTON_TABLE (section->priv->button_table), button_elements);
         g_ptr_array_unref (button_elements);
 
diff --git a/src/palimpsest/gdu-shell.c b/src/palimpsest/gdu-shell.c
index c1da1d7..17d9cbd 100644
--- a/src/palimpsest/gdu-shell.c
+++ b/src/palimpsest/gdu-shell.c
@@ -36,6 +36,7 @@
 #include "gdu-shell.h"
 
 #include "gdu-section-linux-md-drive.h"
+#include "gdu-section-linux-lvm2-volume-group.h"
 #include "gdu-section-drive.h"
 #include "gdu-section-volumes.h"
 #include "gdu-section-hub.h"
@@ -232,6 +233,10 @@ compute_sections_to_show (GduShell *shell)
                         sections_to_show = g_list_append (sections_to_show, (gpointer) GDU_TYPE_SECTION_LINUX_MD_DRIVE);
                         sections_to_show = g_list_append (sections_to_show, (gpointer) GDU_TYPE_SECTION_VOLUMES);
 
+                } else if (GDU_IS_LINUX_LVM2_VOLUME_GROUP (shell->priv->presentable_now_showing)) {
+
+                        sections_to_show = g_list_append (sections_to_show, (gpointer) GDU_TYPE_SECTION_LINUX_LVM2_VOLUME_GROUP);
+                        sections_to_show = g_list_append (sections_to_show, (gpointer) GDU_TYPE_SECTION_VOLUMES);
 
                 } else if (GDU_IS_DRIVE (shell->priv->presentable_now_showing)) {
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]