/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2008 David Zeuthen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "daemon.h" #include "device.h" #include "device-private.h" #include "marshal.h" #include "mount.h" #include "mount-monitor.h" #include "mount-file.h" #include "inhibitor.h" #include "poller.h" #include "adapter.h" #include "port.h" #include "profile.h" /*--------------------------------------------------------------------------------------------------------------*/ #include "device-glue.h" static void device_class_init (DeviceClass *klass); static void device_init (Device *seat); static void device_finalize (GObject *object); static void polling_inhibitor_disconnected_cb (Inhibitor *inhibitor, Device *device); static void spindown_inhibitor_disconnected_cb (Inhibitor *inhibitor, Device *device); static gboolean update_info (Device *device); static void update_info_in_idle (Device *device); static void drain_pending_changes (Device *device, gboolean force_update); static gboolean device_local_is_busy (Device *device, gboolean check_partitions, gboolean check_mounted, GError **error); static gboolean device_local_partitions_are_busy (Device *device); static gboolean device_local_logical_partitions_are_busy (Device *device); static gboolean device_has_logical_partitions (Device *device); static gboolean luks_get_uid_from_dm_name (const char *dm_name, uid_t *out_uid); /* Returns the cleartext device. If device==NULL, unlocking failed and an error has * been reported back to the caller */ typedef void (*UnlockEncryptionHookFunc) (DBusGMethodInvocation *context, Device *device, gpointer user_data); static gboolean device_luks_unlock_internal (Device *device, const char *secret, char **options, UnlockEncryptionHookFunc hook_func, gpointer hook_user_data, DBusGMethodInvocation *context); /* if filesystem_create_succeeded==FALSE, mkfs failed and an error has been reported back to the caller */ typedef void (*FilesystemCreateHookFunc) (DBusGMethodInvocation *context, Device *device, gboolean filesystem_create_succeeded, gpointer user_data); static gboolean device_filesystem_create_internal (Device *device, const char *fstype, char **options, FilesystemCreateHookFunc hook_func, gpointer hook_user_data, DBusGMethodInvocation *context); typedef void (*ForceRemovalCompleteFunc) (Device *device, gboolean success, gpointer user_data); static void force_removal (Device *device, ForceRemovalCompleteFunc callback, gpointer user_data); static void force_unmount (Device *device, ForceRemovalCompleteFunc callback, gpointer user_data); static void force_luks_teardown (Device *device, Device *cleartext_device, ForceRemovalCompleteFunc callback, gpointer user_data); static gboolean ptr_array_has_string (GPtrArray *p, const gchar *str) { guint n; gboolean ret; ret = FALSE; for (n = 0; n < p->len; n++) { if (g_strcmp0 (p->pdata[n], str) == 0) { ret = TRUE; goto out; } } out: return ret; } /* TODO: this is kinda a hack */ static const gchar * get_dmmp_device_node (Device *device) { static gchar buf[1024]; g_assert (device->priv->device_is_linux_dmmp); g_snprintf (buf, sizeof (buf), "/dev/mapper/%s", device->priv->linux_dmmp_name); return buf; } enum { PROP_0, PROP_NATIVE_PATH, PROP_DEVICE_AUTOMOUNT_HINT, PROP_DEVICE_DETECTION_TIME, PROP_DEVICE_MEDIA_DETECTION_TIME, PROP_DEVICE_MAJOR, PROP_DEVICE_MINOR, PROP_DEVICE_FILE, PROP_DEVICE_FILE_PRESENTATION, PROP_DEVICE_FILE_BY_ID, PROP_DEVICE_FILE_BY_PATH, PROP_DEVICE_IS_SYSTEM_INTERNAL, PROP_DEVICE_IS_PARTITION, PROP_DEVICE_IS_PARTITION_TABLE, PROP_DEVICE_IS_REMOVABLE, PROP_DEVICE_IS_MEDIA_AVAILABLE, PROP_DEVICE_IS_MEDIA_CHANGE_DETECTED, PROP_DEVICE_IS_MEDIA_CHANGE_DETECTION_POLLING, PROP_DEVICE_IS_MEDIA_CHANGE_DETECTION_INHIBITABLE, PROP_DEVICE_IS_MEDIA_CHANGE_DETECTION_INHIBITED, PROP_DEVICE_IS_READ_ONLY, PROP_DEVICE_IS_DRIVE, PROP_DEVICE_IS_OPTICAL_DISC, PROP_DEVICE_IS_LUKS, PROP_DEVICE_IS_LUKS_CLEARTEXT, PROP_DEVICE_IS_LINUX_MD_COMPONENT, PROP_DEVICE_IS_LINUX_MD, PROP_DEVICE_IS_LINUX_LVM2_LV, PROP_DEVICE_IS_LINUX_LVM2_PV, PROP_DEVICE_IS_LINUX_DMMP, PROP_DEVICE_IS_LINUX_DMMP_COMPONENT, PROP_DEVICE_IS_LINUX_LOOP, PROP_DEVICE_SIZE, PROP_DEVICE_BLOCK_SIZE, PROP_DEVICE_IS_MOUNTED, PROP_DEVICE_MOUNT_PATHS, PROP_DEVICE_MOUNTED_BY_UID, PROP_DEVICE_PRESENTATION_HIDE, PROP_DEVICE_PRESENTATION_NOPOLICY, PROP_DEVICE_PRESENTATION_NAME, PROP_DEVICE_PRESENTATION_ICON_NAME, PROP_JOB_IN_PROGRESS, PROP_JOB_ID, PROP_JOB_INITIATED_BY_UID, PROP_JOB_IS_CANCELLABLE, PROP_JOB_PERCENTAGE, PROP_ID_USAGE, PROP_ID_TYPE, PROP_ID_VERSION, PROP_ID_UUID, PROP_ID_LABEL, PROP_PARTITION_SLAVE, PROP_PARTITION_SCHEME, PROP_PARTITION_TYPE, PROP_PARTITION_LABEL, PROP_PARTITION_UUID, PROP_PARTITION_FLAGS, PROP_PARTITION_NUMBER, PROP_PARTITION_OFFSET, PROP_PARTITION_SIZE, PROP_PARTITION_ALIGNMENT_OFFSET, PROP_PARTITION_TABLE_SCHEME, PROP_PARTITION_TABLE_COUNT, PROP_LUKS_HOLDER, PROP_LUKS_CLEARTEXT_SLAVE, PROP_LUKS_CLEARTEXT_UNLOCKED_BY_UID, PROP_DRIVE_VENDOR, PROP_DRIVE_MODEL, PROP_DRIVE_REVISION, PROP_DRIVE_SERIAL, PROP_DRIVE_WWN, PROP_DRIVE_CONNECTION_INTERFACE, PROP_DRIVE_CONNECTION_SPEED, PROP_DRIVE_MEDIA_COMPATIBILITY, PROP_DRIVE_MEDIA, PROP_DRIVE_IS_MEDIA_EJECTABLE, PROP_DRIVE_CAN_DETACH, PROP_DRIVE_CAN_SPINDOWN, PROP_DRIVE_IS_ROTATIONAL, PROP_DRIVE_ROTATION_RATE, PROP_DRIVE_WRITE_CACHE, PROP_DRIVE_ADAPTER, PROP_DRIVE_PORTS, PROP_DRIVE_SIMILAR_DEVICES, PROP_OPTICAL_DISC_IS_BLANK, PROP_OPTICAL_DISC_IS_APPENDABLE, PROP_OPTICAL_DISC_IS_CLOSED, PROP_OPTICAL_DISC_NUM_TRACKS, PROP_OPTICAL_DISC_NUM_AUDIO_TRACKS, PROP_OPTICAL_DISC_NUM_SESSIONS, PROP_DRIVE_ATA_SMART_IS_AVAILABLE, PROP_DRIVE_ATA_SMART_TIME_COLLECTED, PROP_DRIVE_ATA_SMART_STATUS, PROP_DRIVE_ATA_SMART_BLOB, PROP_LINUX_MD_COMPONENT_LEVEL, PROP_LINUX_MD_COMPONENT_POSITION, PROP_LINUX_MD_COMPONENT_NUM_RAID_DEVICES, PROP_LINUX_MD_COMPONENT_UUID, PROP_LINUX_MD_COMPONENT_HOME_HOST, PROP_LINUX_MD_COMPONENT_NAME, PROP_LINUX_MD_COMPONENT_VERSION, PROP_LINUX_MD_COMPONENT_HOLDER, PROP_LINUX_MD_COMPONENT_STATE, PROP_LINUX_MD_STATE, PROP_LINUX_MD_LEVEL, PROP_LINUX_MD_NUM_RAID_DEVICES, PROP_LINUX_MD_UUID, PROP_LINUX_MD_HOME_HOST, PROP_LINUX_MD_NAME, PROP_LINUX_MD_VERSION, PROP_LINUX_MD_SLAVES, PROP_LINUX_MD_IS_DEGRADED, PROP_LINUX_MD_SYNC_ACTION, PROP_LINUX_MD_SYNC_PERCENTAGE, PROP_LINUX_MD_SYNC_SPEED, PROP_LINUX_LVM2_LV_NAME, PROP_LINUX_LVM2_LV_UUID, PROP_LINUX_LVM2_LV_GROUP_NAME, PROP_LINUX_LVM2_LV_GROUP_UUID, PROP_LINUX_LVM2_PV_UUID, PROP_LINUX_LVM2_PV_NUM_METADATA_AREAS, PROP_LINUX_LVM2_PV_GROUP_NAME, PROP_LINUX_LVM2_PV_GROUP_UUID, PROP_LINUX_LVM2_PV_GROUP_SIZE, PROP_LINUX_LVM2_PV_GROUP_UNALLOCATED_SIZE, PROP_LINUX_LVM2_PV_GROUP_SEQUENCE_NUMBER, PROP_LINUX_LVM2_PV_GROUP_EXTENT_SIZE, PROP_LINUX_LVM2_PV_GROUP_PHYSICAL_VOLUMES, PROP_LINUX_LVM2_PV_GROUP_LOGICAL_VOLUMES, PROP_LINUX_DMMP_COMPONENT_HOLDER, PROP_LINUX_DMMP_NAME, PROP_LINUX_DMMP_SLAVES, PROP_LINUX_DMMP_PARAMETERS, PROP_LINUX_LOOP_FILENAME, }; enum { CHANGED_SIGNAL, JOB_CHANGED_SIGNAL, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE (Device, device, G_TYPE_OBJECT) #define DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_DEVICE, DevicePrivate)) static GObject * device_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { Device *device; device = DEVICE (G_OBJECT_CLASS (device_parent_class)->constructor (type, n_construct_properties, construct_properties)); return G_OBJECT (device); } static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { Device *device = DEVICE (object); switch (prop_id) { case PROP_NATIVE_PATH: g_value_set_string (value, device->priv->native_path); break; case PROP_DEVICE_DETECTION_TIME: g_value_set_uint64 (value, device->priv->device_detection_time); break; case PROP_DEVICE_MEDIA_DETECTION_TIME: g_value_set_uint64 (value, device->priv->device_media_detection_time); break; case PROP_DEVICE_MAJOR: g_value_set_int64 (value, major (device->priv->dev)); break; case PROP_DEVICE_MINOR: g_value_set_int64 (value, minor (device->priv->dev)); break; case PROP_DEVICE_FILE: g_value_set_string (value, device->priv->device_file); break; case PROP_DEVICE_AUTOMOUNT_HINT: g_value_set_string (value, device->priv->device_automount_hint); break; case PROP_DEVICE_FILE_PRESENTATION: if (device->priv->device_file_presentation != NULL) g_value_set_string (value, device->priv->device_file_presentation); else g_value_set_string (value, device->priv->device_file); break; case PROP_DEVICE_FILE_BY_ID: g_value_set_boxed (value, device->priv->device_file_by_id); break; case PROP_DEVICE_FILE_BY_PATH: g_value_set_boxed (value, device->priv->device_file_by_path); break; case PROP_DEVICE_IS_SYSTEM_INTERNAL: g_value_set_boolean (value, device->priv->device_is_system_internal); break; case PROP_DEVICE_IS_PARTITION: g_value_set_boolean (value, device->priv->device_is_partition); break; case PROP_DEVICE_IS_PARTITION_TABLE: g_value_set_boolean (value, device->priv->device_is_partition_table); break; case PROP_DEVICE_IS_REMOVABLE: g_value_set_boolean (value, device->priv->device_is_removable); break; case PROP_DEVICE_IS_MEDIA_AVAILABLE: g_value_set_boolean (value, device->priv->device_is_media_available); break; case PROP_DEVICE_IS_MEDIA_CHANGE_DETECTED: g_value_set_boolean (value, device->priv->device_is_media_change_detected); break; case PROP_DEVICE_IS_MEDIA_CHANGE_DETECTION_POLLING: g_value_set_boolean (value, device->priv->device_is_media_change_detection_polling); break; case PROP_DEVICE_IS_MEDIA_CHANGE_DETECTION_INHIBITABLE: g_value_set_boolean (value, device->priv->device_is_media_change_detection_inhibitable); break; case PROP_DEVICE_IS_MEDIA_CHANGE_DETECTION_INHIBITED: g_value_set_boolean (value, device->priv->device_is_media_change_detection_inhibited); break; case PROP_DEVICE_IS_READ_ONLY: g_value_set_boolean (value, device->priv->device_is_read_only); break; case PROP_DEVICE_IS_DRIVE: g_value_set_boolean (value, device->priv->device_is_drive); break; case PROP_DEVICE_IS_OPTICAL_DISC: g_value_set_boolean (value, device->priv->device_is_optical_disc); break; case PROP_DEVICE_IS_LUKS: g_value_set_boolean (value, device->priv->device_is_luks); break; case PROP_DEVICE_IS_LUKS_CLEARTEXT: g_value_set_boolean (value, device->priv->device_is_luks_cleartext); break; case PROP_DEVICE_IS_LINUX_MD_COMPONENT: g_value_set_boolean (value, device->priv->device_is_linux_md_component); break; case PROP_DEVICE_IS_LINUX_MD: g_value_set_boolean (value, device->priv->device_is_linux_md); break; case PROP_DEVICE_IS_LINUX_LVM2_LV: g_value_set_boolean (value, device->priv->device_is_linux_lvm2_lv); break; case PROP_DEVICE_IS_LINUX_LVM2_PV: g_value_set_boolean (value, device->priv->device_is_linux_lvm2_pv); break; case PROP_DEVICE_IS_LINUX_DMMP: g_value_set_boolean (value, device->priv->device_is_linux_dmmp); break; case PROP_DEVICE_IS_LINUX_DMMP_COMPONENT: g_value_set_boolean (value, device->priv->device_is_linux_dmmp_component); break; case PROP_DEVICE_IS_LINUX_LOOP: g_value_set_boolean (value, device->priv->device_is_linux_loop); break; case PROP_DEVICE_SIZE: g_value_set_uint64 (value, device->priv->device_size); break; case PROP_DEVICE_BLOCK_SIZE: g_value_set_uint64 (value, device->priv->device_block_size); break; case PROP_DEVICE_IS_MOUNTED: g_value_set_boolean (value, device->priv->device_is_mounted); break; case PROP_DEVICE_MOUNT_PATHS: g_value_set_boxed (value, device->priv->device_mount_paths); break; case PROP_DEVICE_MOUNTED_BY_UID: g_value_set_uint (value, device->priv->device_mounted_by_uid); break; case PROP_DEVICE_PRESENTATION_HIDE: g_value_set_boolean (value, device->priv->device_presentation_hide); break; case PROP_DEVICE_PRESENTATION_NOPOLICY: g_value_set_boolean (value, device->priv->device_presentation_nopolicy); break; case PROP_DEVICE_PRESENTATION_NAME: g_value_set_string (value, device->priv->device_presentation_name); break; case PROP_DEVICE_PRESENTATION_ICON_NAME: g_value_set_string (value, device->priv->device_presentation_icon_name); break; case PROP_JOB_IN_PROGRESS: g_value_set_boolean (value, device->priv->job_in_progress); break; case PROP_JOB_ID: g_value_set_string (value, device->priv->job_id); break; case PROP_JOB_INITIATED_BY_UID: g_value_set_uint (value, device->priv->job_initiated_by_uid); break; case PROP_JOB_IS_CANCELLABLE: g_value_set_boolean (value, device->priv->job_is_cancellable); break; case PROP_JOB_PERCENTAGE: g_value_set_double (value, device->priv->job_percentage); break; case PROP_ID_USAGE: g_value_set_string (value, device->priv->id_usage); break; case PROP_ID_TYPE: g_value_set_string (value, device->priv->id_type); break; case PROP_ID_VERSION: g_value_set_string (value, device->priv->id_version); break; case PROP_ID_UUID: g_value_set_string (value, device->priv->id_uuid); break; case PROP_ID_LABEL: g_value_set_string (value, device->priv->id_label); break; case PROP_PARTITION_SLAVE: if (device->priv->partition_slave != NULL) g_value_set_boxed (value, device->priv->partition_slave); else g_value_set_boxed (value, "/"); break; case PROP_PARTITION_SCHEME: g_value_set_string (value, device->priv->partition_scheme); break; case PROP_PARTITION_TYPE: g_value_set_string (value, device->priv->partition_type); break; case PROP_PARTITION_LABEL: g_value_set_string (value, device->priv->partition_label); break; case PROP_PARTITION_UUID: g_value_set_string (value, device->priv->partition_uuid); break; case PROP_PARTITION_FLAGS: g_value_set_boxed (value, device->priv->partition_flags); break; case PROP_PARTITION_NUMBER: g_value_set_int (value, device->priv->partition_number); break; case PROP_PARTITION_OFFSET: g_value_set_uint64 (value, device->priv->partition_offset); break; case PROP_PARTITION_SIZE: g_value_set_uint64 (value, device->priv->partition_size); break; case PROP_PARTITION_ALIGNMENT_OFFSET: g_value_set_uint64 (value, device->priv->partition_alignment_offset); break; case PROP_PARTITION_TABLE_SCHEME: g_value_set_string (value, device->priv->partition_table_scheme); break; case PROP_PARTITION_TABLE_COUNT: g_value_set_int (value, device->priv->partition_table_count); break; case PROP_LUKS_HOLDER: if (device->priv->luks_holder != NULL) g_value_set_boxed (value, device->priv->luks_holder); else g_value_set_boxed (value, "/"); break; case PROP_LUKS_CLEARTEXT_SLAVE: if (device->priv->luks_cleartext_slave != NULL) g_value_set_boxed (value, device->priv->luks_cleartext_slave); else g_value_set_boxed (value, "/"); break; case PROP_LUKS_CLEARTEXT_UNLOCKED_BY_UID: g_value_set_uint (value, device->priv->luks_cleartext_unlocked_by_uid); break; case PROP_DRIVE_VENDOR: g_value_set_string (value, device->priv->drive_vendor); break; case PROP_DRIVE_MODEL: g_value_set_string (value, device->priv->drive_model); break; case PROP_DRIVE_REVISION: g_value_set_string (value, device->priv->drive_revision); break; case PROP_DRIVE_SERIAL: g_value_set_string (value, device->priv->drive_serial); break; case PROP_DRIVE_WWN: g_value_set_string (value, device->priv->drive_wwn); break; case PROP_DRIVE_CONNECTION_INTERFACE: g_value_set_string (value, device->priv->drive_connection_interface); break; case PROP_DRIVE_CONNECTION_SPEED: g_value_set_uint64 (value, device->priv->drive_connection_speed); break; case PROP_DRIVE_MEDIA_COMPATIBILITY: g_value_set_boxed (value, device->priv->drive_media_compatibility); break; case PROP_DRIVE_MEDIA: g_value_set_string (value, device->priv->drive_media); break; case PROP_DRIVE_IS_MEDIA_EJECTABLE: g_value_set_boolean (value, device->priv->drive_is_media_ejectable); break; case PROP_DRIVE_CAN_DETACH: g_value_set_boolean (value, device->priv->drive_can_detach); break; case PROP_DRIVE_CAN_SPINDOWN: g_value_set_boolean (value, device->priv->drive_can_spindown); break; case PROP_DRIVE_IS_ROTATIONAL: g_value_set_boolean (value, device->priv->drive_is_rotational); break; case PROP_DRIVE_WRITE_CACHE: g_value_set_string (value, device->priv->drive_write_cache); break; case PROP_DRIVE_ROTATION_RATE: g_value_set_uint (value, device->priv->drive_rotation_rate); break; case PROP_DRIVE_ADAPTER: if (device->priv->drive_adapter != NULL) g_value_set_boxed (value, device->priv->drive_adapter); else g_value_set_boxed (value, "/"); break; case PROP_DRIVE_PORTS: g_value_set_boxed (value, device->priv->drive_ports); break; case PROP_DRIVE_SIMILAR_DEVICES: g_value_set_boxed (value, device->priv->drive_similar_devices); break; case PROP_OPTICAL_DISC_IS_BLANK: g_value_set_boolean (value, device->priv->optical_disc_is_blank); break; case PROP_OPTICAL_DISC_IS_APPENDABLE: g_value_set_boolean (value, device->priv->optical_disc_is_appendable); break; case PROP_OPTICAL_DISC_IS_CLOSED: g_value_set_boolean (value, device->priv->optical_disc_is_closed); break; case PROP_OPTICAL_DISC_NUM_TRACKS: g_value_set_uint (value, device->priv->optical_disc_num_tracks); break; case PROP_OPTICAL_DISC_NUM_AUDIO_TRACKS: g_value_set_uint (value, device->priv->optical_disc_num_audio_tracks); break; case PROP_OPTICAL_DISC_NUM_SESSIONS: g_value_set_uint (value, device->priv->optical_disc_num_sessions); break; case PROP_DRIVE_ATA_SMART_IS_AVAILABLE: g_value_set_boolean (value, device->priv->drive_ata_smart_is_available); break; case PROP_DRIVE_ATA_SMART_TIME_COLLECTED: g_value_set_uint64 (value, device->priv->drive_ata_smart_time_collected); break; case PROP_DRIVE_ATA_SMART_STATUS: { const gchar *status; if (device->priv->drive_ata_smart_status == (SkSmartOverall) - 1) status = ""; else status = sk_smart_overall_to_string (device->priv->drive_ata_smart_status); g_value_set_string (value, status); } break; case PROP_DRIVE_ATA_SMART_BLOB: { GArray *a; a = g_array_new (FALSE, FALSE, 1); if (device->priv->drive_ata_smart_blob != NULL) { g_array_append_vals (a, device->priv->drive_ata_smart_blob, device->priv->drive_ata_smart_blob_size); } g_value_set_boxed (value, a); g_array_unref (a); } break; case PROP_LINUX_MD_COMPONENT_LEVEL: g_value_set_string (value, device->priv->linux_md_component_level); break; case PROP_LINUX_MD_COMPONENT_POSITION: g_value_set_int (value, device->priv->linux_md_component_position); break; case PROP_LINUX_MD_COMPONENT_NUM_RAID_DEVICES: g_value_set_int (value, device->priv->linux_md_component_num_raid_devices); break; case PROP_LINUX_MD_COMPONENT_UUID: g_value_set_string (value, device->priv->linux_md_component_uuid); break; case PROP_LINUX_MD_COMPONENT_HOME_HOST: g_value_set_string (value, device->priv->linux_md_component_home_host); break; case PROP_LINUX_MD_COMPONENT_NAME: g_value_set_string (value, device->priv->linux_md_component_name); break; case PROP_LINUX_MD_COMPONENT_VERSION: g_value_set_string (value, device->priv->linux_md_component_version); break; case PROP_LINUX_MD_COMPONENT_HOLDER: if (device->priv->linux_md_component_holder != NULL) g_value_set_boxed (value, device->priv->linux_md_component_holder); else g_value_set_boxed (value, "/"); break; case PROP_LINUX_MD_COMPONENT_STATE: g_value_set_boxed (value, device->priv->linux_md_component_state); break; case PROP_LINUX_MD_STATE: g_value_set_string (value, device->priv->linux_md_state); break; case PROP_LINUX_MD_LEVEL: g_value_set_string (value, device->priv->linux_md_level); break; case PROP_LINUX_MD_NUM_RAID_DEVICES: g_value_set_int (value, device->priv->linux_md_num_raid_devices); break; case PROP_LINUX_MD_UUID: g_value_set_string (value, device->priv->linux_md_uuid); break; case PROP_LINUX_MD_HOME_HOST: g_value_set_string (value, device->priv->linux_md_home_host); break; case PROP_LINUX_MD_NAME: g_value_set_string (value, device->priv->linux_md_name); break; case PROP_LINUX_MD_VERSION: g_value_set_string (value, device->priv->linux_md_version); break; case PROP_LINUX_MD_SLAVES: g_value_set_boxed (value, device->priv->linux_md_slaves); break; case PROP_LINUX_MD_IS_DEGRADED: g_value_set_boolean (value, device->priv->linux_md_is_degraded); break; case PROP_LINUX_MD_SYNC_ACTION: g_value_set_string (value, device->priv->linux_md_sync_action); break; case PROP_LINUX_MD_SYNC_PERCENTAGE: g_value_set_double (value, device->priv->linux_md_sync_percentage); break; case PROP_LINUX_MD_SYNC_SPEED: g_value_set_uint64 (value, device->priv->linux_md_sync_speed); break; case PROP_LINUX_LVM2_LV_NAME: g_value_set_string (value, device->priv->linux_lvm2_lv_name); break; case PROP_LINUX_LVM2_LV_UUID: g_value_set_string (value, device->priv->linux_lvm2_lv_uuid); break; case PROP_LINUX_LVM2_LV_GROUP_NAME: g_value_set_string (value, device->priv->linux_lvm2_lv_group_name); break; case PROP_LINUX_LVM2_LV_GROUP_UUID: g_value_set_string (value, device->priv->linux_lvm2_lv_group_uuid); break; case PROP_LINUX_LVM2_PV_UUID: g_value_set_string (value, device->priv->linux_lvm2_pv_uuid); break; case PROP_LINUX_LVM2_PV_NUM_METADATA_AREAS: g_value_set_uint (value, device->priv->linux_lvm2_pv_num_metadata_areas); break; case PROP_LINUX_LVM2_PV_GROUP_NAME: g_value_set_string (value, device->priv->linux_lvm2_pv_group_name); break; case PROP_LINUX_LVM2_PV_GROUP_UUID: g_value_set_string (value, device->priv->linux_lvm2_pv_group_uuid); break; case PROP_LINUX_LVM2_PV_GROUP_SIZE: g_value_set_uint64 (value, device->priv->linux_lvm2_pv_group_size); break; case PROP_LINUX_LVM2_PV_GROUP_UNALLOCATED_SIZE: g_value_set_uint64 (value, device->priv->linux_lvm2_pv_group_unallocated_size); break; case PROP_LINUX_LVM2_PV_GROUP_SEQUENCE_NUMBER: g_value_set_uint64 (value, device->priv->linux_lvm2_pv_group_sequence_number); break; case PROP_LINUX_LVM2_PV_GROUP_EXTENT_SIZE: g_value_set_uint64 (value, device->priv->linux_lvm2_pv_group_extent_size); break; case PROP_LINUX_LVM2_PV_GROUP_PHYSICAL_VOLUMES: g_value_set_boxed (value, device->priv->linux_lvm2_pv_group_physical_volumes); break; case PROP_LINUX_LVM2_PV_GROUP_LOGICAL_VOLUMES: g_value_set_boxed (value, device->priv->linux_lvm2_pv_group_logical_volumes); break; case PROP_LINUX_DMMP_COMPONENT_HOLDER: if (device->priv->linux_dmmp_component_holder != NULL) g_value_set_boxed (value, device->priv->linux_dmmp_component_holder); else g_value_set_boxed (value, "/"); break; case PROP_LINUX_DMMP_NAME: g_value_set_string (value, device->priv->linux_dmmp_name); break; case PROP_LINUX_DMMP_PARAMETERS: g_value_set_string (value, device->priv->linux_dmmp_parameters); break; case PROP_LINUX_DMMP_SLAVES: g_value_set_boxed (value, device->priv->linux_dmmp_slaves); break; case PROP_LINUX_LOOP_FILENAME: g_value_set_string (value, device->priv->linux_loop_filename); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void device_class_init (DeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructor = device_constructor; object_class->finalize = device_finalize; object_class->get_property = get_property; g_type_class_add_private (klass, sizeof(DevicePrivate)); signals[CHANGED_SIGNAL] = g_signal_new ("changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[JOB_CHANGED_SIGNAL] = g_signal_new ("job-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, marshal_VOID__BOOLEAN_STRING_UINT_BOOLEAN_DOUBLE, G_TYPE_NONE, 5, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_BOOLEAN, G_TYPE_DOUBLE); dbus_g_object_type_install_info (TYPE_DEVICE, &dbus_glib_device_object_info); g_object_class_install_property (object_class, PROP_NATIVE_PATH, g_param_spec_string ("native-path", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_AUTOMOUNT_HINT, g_param_spec_string ("device-automount-hint", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_DETECTION_TIME, g_param_spec_uint64 ("device-detection-time", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_MEDIA_DETECTION_TIME, g_param_spec_uint64 ("device-media-detection-time", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_MAJOR, g_param_spec_int64 ("device-major", NULL, NULL, -G_MAXINT64, G_MAXINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_MINOR, g_param_spec_int64 ("device-minor", NULL, NULL, -G_MAXINT64, G_MAXINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_FILE, g_param_spec_string ("device-file", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_FILE_PRESENTATION, g_param_spec_string ("device-file-presentation", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_FILE_BY_ID, g_param_spec_boxed ("device-file-by-id", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", G_TYPE_STRING), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_FILE_BY_PATH, g_param_spec_boxed ("device-file-by-path", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", G_TYPE_STRING), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_SYSTEM_INTERNAL, g_param_spec_boolean ("device-is-system-internal", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_PARTITION, g_param_spec_boolean ("device-is-partition", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_PARTITION_TABLE, g_param_spec_boolean ("device-is-partition-table", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_REMOVABLE, g_param_spec_boolean ("device-is-removable", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_MEDIA_AVAILABLE, g_param_spec_boolean ("device-is-media-available", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_MEDIA_CHANGE_DETECTED, g_param_spec_boolean ("device-is-media-change-detected", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_MEDIA_CHANGE_DETECTION_POLLING, g_param_spec_boolean ("device-is-media-change-detection-polling", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_MEDIA_CHANGE_DETECTION_INHIBITABLE, g_param_spec_boolean ("device-is-media-change-detection-inhibitable", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_MEDIA_CHANGE_DETECTION_INHIBITED, g_param_spec_boolean ("device-is-media-change-detection-inhibited", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_READ_ONLY, g_param_spec_boolean ("device-is-read-only", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_DRIVE, g_param_spec_boolean ("device-is-drive", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_OPTICAL_DISC, g_param_spec_boolean ("device-is-optical-disc", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_LUKS, g_param_spec_boolean ("device-is-luks", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_LUKS_CLEARTEXT, g_param_spec_boolean ("device-is-luks-cleartext", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_LINUX_MD_COMPONENT, g_param_spec_boolean ("device-is-linux-md-component", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_LINUX_MD, g_param_spec_boolean ("device-is-linux-md", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_LINUX_LVM2_LV, g_param_spec_boolean ("device-is-linux-lvm2-lv", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_LINUX_LVM2_PV, g_param_spec_boolean ("device-is-linux-lvm2-pv", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_LINUX_DMMP, g_param_spec_boolean ("device-is-linux-dmmp", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_LINUX_DMMP_COMPONENT, g_param_spec_boolean ("device-is-linux-dmmp-component", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_LINUX_LOOP, g_param_spec_boolean ("device-is-linux-loop", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_SIZE, g_param_spec_uint64 ("device-size", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_BLOCK_SIZE, g_param_spec_uint64 ("device-block-size", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_IS_MOUNTED, g_param_spec_boolean ("device-is-mounted", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_MOUNT_PATHS, g_param_spec_boxed ("device-mount-paths", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", G_TYPE_STRING), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_MOUNTED_BY_UID, g_param_spec_uint ("device-mounted-by-uid", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_PRESENTATION_HIDE, g_param_spec_boolean ("device-presentation-hide", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_PRESENTATION_NOPOLICY, g_param_spec_boolean ("device-presentation-nopolicy", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_PRESENTATION_NAME, g_param_spec_string ("device-presentation-name", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DEVICE_PRESENTATION_ICON_NAME, g_param_spec_string ("device-presentation-icon-name", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_JOB_IN_PROGRESS, g_param_spec_boolean ("job-in-progress", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_JOB_ID, g_param_spec_string ("job-id", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_JOB_INITIATED_BY_UID, g_param_spec_uint ("job-initiated-by-uid", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_JOB_IS_CANCELLABLE, g_param_spec_boolean ("job-is-cancellable", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_JOB_PERCENTAGE, g_param_spec_double ("job-percentage", NULL, NULL, -1, 100, -1, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_ID_USAGE, g_param_spec_string ("id-usage", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_ID_TYPE, g_param_spec_string ("id-type", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_ID_VERSION, g_param_spec_string ("id-version", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_ID_UUID, g_param_spec_string ("id-uuid", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_ID_LABEL, g_param_spec_string ("id-label", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_SLAVE, g_param_spec_boxed ("partition-slave", NULL, NULL, DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_SCHEME, g_param_spec_string ("partition-scheme", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_TYPE, g_param_spec_string ("partition-type", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_LABEL, g_param_spec_string ("partition-label", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_UUID, g_param_spec_string ("partition-uuid", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_FLAGS, g_param_spec_boxed ("partition-flags", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", G_TYPE_STRING), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_NUMBER, g_param_spec_int ("partition-number", NULL, NULL, 0, G_MAXINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_OFFSET, g_param_spec_uint64 ("partition-offset", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_SIZE, g_param_spec_uint64 ("partition-size", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_ALIGNMENT_OFFSET, g_param_spec_uint64 ("partition-alignment-offset", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_TABLE_SCHEME, g_param_spec_string ("partition-table-scheme", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_PARTITION_TABLE_COUNT, g_param_spec_int ("partition-table-count", NULL, NULL, 0, G_MAXINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LUKS_HOLDER, g_param_spec_boxed ("luks-holder", NULL, NULL, DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LUKS_CLEARTEXT_SLAVE, g_param_spec_boxed ("luks-cleartext-slave", NULL, NULL, DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LUKS_CLEARTEXT_UNLOCKED_BY_UID, g_param_spec_uint ("luks-cleartext-unlocked-by-uid", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_VENDOR, g_param_spec_string ("drive-vendor", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_MODEL, g_param_spec_string ("drive-model", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_REVISION, g_param_spec_string ("drive-revision", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_SERIAL, g_param_spec_string ("drive-serial", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_WWN, g_param_spec_string ("drive-wwn", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_CONNECTION_INTERFACE, g_param_spec_string ("drive-connection-interface", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_CONNECTION_SPEED, g_param_spec_uint64 ("drive-connection-speed", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_MEDIA_COMPATIBILITY, g_param_spec_boxed ("drive-media-compatibility", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", G_TYPE_STRING), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_MEDIA, g_param_spec_string ("drive-media", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_IS_MEDIA_EJECTABLE, g_param_spec_boolean ("drive-is-media-ejectable", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_CAN_DETACH, g_param_spec_boolean ("drive-can-detach", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_CAN_SPINDOWN, g_param_spec_boolean ("drive-can-spindown", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_IS_ROTATIONAL, g_param_spec_boolean ("drive-is-rotational", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_ROTATION_RATE, g_param_spec_uint ("drive-rotation-rate", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_WRITE_CACHE, g_param_spec_string ("drive-write-cache", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_ADAPTER, g_param_spec_boxed ("drive-adapter", NULL, NULL, DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_PORTS, g_param_spec_boxed ("drive-ports", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_SIMILAR_DEVICES, g_param_spec_boxed ("drive-similar-devices", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_OPTICAL_DISC_IS_BLANK, g_param_spec_boolean ("optical-disc-is-blank", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_OPTICAL_DISC_IS_APPENDABLE, g_param_spec_boolean ("optical-disc-is-appendable", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_OPTICAL_DISC_IS_CLOSED, g_param_spec_boolean ("optical-disc-is-closed", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_OPTICAL_DISC_NUM_TRACKS, g_param_spec_uint ("optical-disc-num-tracks", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_OPTICAL_DISC_NUM_AUDIO_TRACKS, g_param_spec_uint ("optical-disc-num-audio-tracks", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_OPTICAL_DISC_NUM_SESSIONS, g_param_spec_uint ("optical-disc-num-sessions", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_ATA_SMART_IS_AVAILABLE, g_param_spec_boolean ("drive-ata-smart-is-available", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_ATA_SMART_TIME_COLLECTED, g_param_spec_uint64 ("drive-ata-smart-time-collected", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_ATA_SMART_STATUS, g_param_spec_string ("drive-ata-smart-status", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DRIVE_ATA_SMART_BLOB, g_param_spec_boxed ("drive-ata-smart-blob", NULL, NULL, dbus_g_type_get_collection ("GArray", G_TYPE_UCHAR), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_COMPONENT_LEVEL, g_param_spec_string ("linux-md-component-level", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_COMPONENT_POSITION, g_param_spec_int ("linux-md-component-position", NULL, NULL, 0, G_MAXINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_COMPONENT_NUM_RAID_DEVICES, g_param_spec_int ("linux-md-component-num-raid-devices", NULL, NULL, 0, G_MAXINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_COMPONENT_UUID, g_param_spec_string ("linux-md-component-uuid", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_COMPONENT_HOME_HOST, g_param_spec_string ("linux-md-component-home-host", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_COMPONENT_NAME, g_param_spec_string ("linux-md-component-name", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_COMPONENT_VERSION, g_param_spec_string ("linux-md-component-version", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_COMPONENT_HOLDER, g_param_spec_boxed ("linux-md-component-holder", NULL, NULL, DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_COMPONENT_STATE, g_param_spec_boxed ("linux-md-component-state", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", G_TYPE_STRING), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_STATE, g_param_spec_string ("linux-md-state", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_LEVEL, g_param_spec_string ("linux-md-level", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_NUM_RAID_DEVICES, g_param_spec_int ("linux-md-num-raid-devices", NULL, NULL, 0, G_MAXINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_UUID, g_param_spec_string ("linux-md-uuid", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_HOME_HOST, g_param_spec_string ("linux-md-home-host", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_NAME, g_param_spec_string ("linux-md-name", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_VERSION, g_param_spec_string ("linux-md-version", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_SLAVES, g_param_spec_boxed ("linux-md-slaves", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_IS_DEGRADED, g_param_spec_boolean ("linux-md-is-degraded", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_SYNC_ACTION, g_param_spec_string ("linux-md-sync-action", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_SYNC_PERCENTAGE, g_param_spec_double ("linux-md-sync-percentage", NULL, NULL, 0.0, 100.0, 0.0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_MD_SYNC_SPEED, g_param_spec_uint64 ("linux-md-sync-speed", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_LV_NAME, g_param_spec_string ("linux-lvm2-lv-name", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_LV_UUID, g_param_spec_string ("linux-lvm2-lv-uuid", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_LV_GROUP_NAME, g_param_spec_string ("linux-lvm2-lv-group-name", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_LV_GROUP_UUID, g_param_spec_string ("linux-lvm2-lv-group-uuid", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_PV_UUID, g_param_spec_string ("linux-lvm2-pv-uuid", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_PV_NUM_METADATA_AREAS, g_param_spec_uint ("linux-lvm2-pv-num-metadata-areas", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_PV_GROUP_NAME, g_param_spec_string ("linux-lvm2-pv-group-name", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_PV_GROUP_UUID, g_param_spec_string ("linux-lvm2-pv-group-uuid", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_PV_GROUP_SIZE, g_param_spec_uint64 ("linux-lvm2-pv-group-size", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_PV_GROUP_UNALLOCATED_SIZE, g_param_spec_uint64 ("linux-lvm2-pv-group-unallocated-size", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_PV_GROUP_SEQUENCE_NUMBER, g_param_spec_uint64 ("linux-lvm2-pv-group-sequence-number", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_PV_GROUP_EXTENT_SIZE, g_param_spec_uint64 ("linux-lvm2-pv-group-extent-size", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_PV_GROUP_PHYSICAL_VOLUMES, g_param_spec_boxed ("linux-lvm2-pv-group-physical-volumes", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", G_TYPE_STRING), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LVM2_PV_GROUP_LOGICAL_VOLUMES, g_param_spec_boxed ("linux-lvm2-pv-group-logical-volumes", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", G_TYPE_STRING), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_DMMP_COMPONENT_HOLDER, g_param_spec_boxed ("linux-dmmp-component-holder", NULL, NULL, DBUS_TYPE_G_OBJECT_PATH, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_DMMP_NAME, g_param_spec_string ("linux-dmmp-name", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_DMMP_PARAMETERS, g_param_spec_string ("linux-dmmp-parameters", NULL, NULL, NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_DMMP_SLAVES, g_param_spec_boxed ("linux-dmmp-slaves", NULL, NULL, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LINUX_LOOP_FILENAME, g_param_spec_string ("linux-loop-filename", NULL, NULL, NULL, G_PARAM_READABLE)); } static void device_init (Device *device) { device->priv = DEVICE_GET_PRIVATE (device); device->priv->device_file_by_id = g_ptr_array_new (); device->priv->device_file_by_path = g_ptr_array_new (); device->priv->device_mount_paths = g_ptr_array_new (); device->priv->partition_flags = g_ptr_array_new (); device->priv->drive_media_compatibility = g_ptr_array_new (); device->priv->drive_ports = g_ptr_array_new (); device->priv->drive_similar_devices = g_ptr_array_new (); device->priv->linux_md_component_state = g_ptr_array_new (); device->priv->linux_md_slaves = g_ptr_array_new (); device->priv->linux_lvm2_pv_group_physical_volumes = g_ptr_array_new (); device->priv->linux_lvm2_pv_group_logical_volumes = g_ptr_array_new (); device->priv->linux_dmmp_slaves = g_ptr_array_new (); device->priv->slaves_objpath = g_ptr_array_new (); device->priv->holders_objpath = g_ptr_array_new (); device->priv->drive_ata_smart_status = -1; } static void device_finalize (GObject *object) { Device *device; GList *l; g_return_if_fail (object != NULL); g_return_if_fail (IS_DEVICE (object)); device = DEVICE (object); g_return_if_fail (device->priv != NULL); /* g_debug ("finalizing %s", device->priv->native_path); */ g_object_unref (device->priv->d); g_object_unref (device->priv->daemon); g_free (device->priv->object_path); g_free (device->priv->native_path); for (l = device->priv->polling_inhibitors; l != NULL; l = l->next) { Inhibitor *inhibitor = INHIBITOR (l->data); g_signal_handlers_disconnect_by_func (inhibitor, polling_inhibitor_disconnected_cb, device); g_object_unref (inhibitor); } g_list_free (device->priv->polling_inhibitors); for (l = device->priv->spindown_inhibitors; l != NULL; l = l->next) { Inhibitor *inhibitor = INHIBITOR (l->data); g_signal_handlers_disconnect_by_func (inhibitor, spindown_inhibitor_disconnected_cb, device); g_object_unref (inhibitor); } g_list_free (device->priv->spindown_inhibitors); if (device->priv->linux_md_poll_timeout_id > 0) g_source_remove (device->priv->linux_md_poll_timeout_id); if (device->priv->emit_changed_idle_id > 0) g_source_remove (device->priv->emit_changed_idle_id); /* free properties */ g_free (device->priv->device_file); g_free (device->priv->device_file_presentation); g_ptr_array_foreach (device->priv->device_file_by_id, (GFunc) g_free, NULL); g_ptr_array_foreach (device->priv->device_file_by_path, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->device_file_by_id, TRUE); g_ptr_array_free (device->priv->device_file_by_path, TRUE); g_ptr_array_free (device->priv->device_mount_paths, TRUE); g_free (device->priv->device_presentation_name); g_free (device->priv->device_presentation_icon_name); g_free (device->priv->id_usage); g_free (device->priv->id_type); g_free (device->priv->id_version); g_free (device->priv->id_uuid); g_free (device->priv->id_label); g_free (device->priv->partition_slave); g_free (device->priv->partition_scheme); g_free (device->priv->partition_type); g_free (device->priv->partition_label); g_free (device->priv->partition_uuid); g_ptr_array_foreach (device->priv->partition_flags, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->partition_flags, TRUE); g_free (device->priv->partition_table_scheme); g_free (device->priv->luks_holder); g_free (device->priv->luks_cleartext_slave); g_free (device->priv->drive_vendor); g_free (device->priv->drive_model); g_free (device->priv->drive_revision); g_free (device->priv->drive_serial); g_free (device->priv->drive_wwn); g_free (device->priv->drive_connection_interface); g_ptr_array_foreach (device->priv->drive_media_compatibility, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->drive_media_compatibility, TRUE); g_free (device->priv->drive_media); g_free (device->priv->drive_write_cache); g_free (device->priv->drive_adapter); g_ptr_array_foreach (device->priv->drive_ports, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->drive_ports, TRUE); g_ptr_array_foreach (device->priv->drive_similar_devices, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->drive_similar_devices, TRUE); g_free (device->priv->linux_md_component_level); g_free (device->priv->linux_md_component_uuid); g_free (device->priv->linux_md_component_home_host); g_free (device->priv->linux_md_component_name); g_free (device->priv->linux_md_component_version); g_free (device->priv->linux_md_component_holder); g_ptr_array_foreach (device->priv->linux_md_component_state, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->linux_md_component_state, TRUE); g_free (device->priv->linux_md_state); g_free (device->priv->linux_md_level); g_free (device->priv->linux_md_uuid); g_free (device->priv->linux_md_home_host); g_free (device->priv->linux_md_name); g_free (device->priv->linux_md_version); g_ptr_array_foreach (device->priv->linux_md_slaves, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->linux_md_slaves, TRUE); g_free (device->priv->linux_dmmp_component_holder); g_free (device->priv->linux_dmmp_name); g_ptr_array_foreach (device->priv->linux_dmmp_slaves, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->linux_dmmp_slaves, TRUE); g_free (device->priv->linux_dmmp_parameters); g_free (device->priv->linux_loop_filename); g_free (device->priv->linux_lvm2_lv_name); g_free (device->priv->linux_lvm2_lv_uuid); g_free (device->priv->linux_lvm2_lv_group_name); g_free (device->priv->linux_lvm2_lv_group_uuid); g_free (device->priv->linux_lvm2_pv_uuid); g_free (device->priv->linux_lvm2_pv_group_name); g_free (device->priv->linux_lvm2_pv_group_uuid); g_ptr_array_foreach (device->priv->linux_lvm2_pv_group_physical_volumes, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->linux_lvm2_pv_group_physical_volumes, TRUE); g_ptr_array_foreach (device->priv->linux_lvm2_pv_group_logical_volumes, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->linux_lvm2_pv_group_logical_volumes, TRUE); g_free (device->priv->drive_ata_smart_blob); g_free (device->priv->dm_name); g_ptr_array_foreach (device->priv->slaves_objpath, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->slaves_objpath, TRUE); g_ptr_array_foreach (device->priv->holders_objpath, (GFunc) g_free, NULL); g_ptr_array_free (device->priv->holders_objpath, TRUE); G_OBJECT_CLASS (device_parent_class)->finalize (object); } /** * compute_object_path: * @native_path: Either an absolute sysfs path or the basename * * Maps @native_path to the D-Bus object path for the device. * * Returns: A valid D-Bus object path. Free with g_free(). */ static char * compute_object_path (const char *native_path) { const gchar *basename; GString *s; guint n; g_return_val_if_fail (native_path != NULL, NULL); basename = strrchr (native_path, '/'); if (basename != NULL) { basename++; } else { basename = native_path; } s = g_string_new ("/org/freedesktop/UDisks/devices/"); for (n = 0; basename[n] != '\0'; n++) { gint c = basename[n]; /* D-Bus spec sez: * * Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_" */ if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { g_string_append_c (s, c); } else { /* Escape bytes not in [A-Z][a-z][0-9] as _ */ g_string_append_printf (s, "_%02x", c); } } return g_string_free (s, FALSE); } static gboolean register_disks_device (Device *device) { GError *error = NULL; device->priv->system_bus_connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); if (device->priv->system_bus_connection == NULL) { if (error != NULL) { g_critical ("error getting system bus: %s", error->message); g_error_free (error); } goto error; } device->priv->object_path = compute_object_path (device->priv->native_path); /* safety first */ if (dbus_g_connection_lookup_g_object (device->priv->system_bus_connection, device->priv->object_path) != NULL) { g_error ("**** HACK: Wanting to register object at path `%s' but there is already an " "object there. This is an internal error in the daemon. Aborting.\n", device->priv->object_path); } dbus_g_connection_register_g_object (device->priv->system_bus_connection, device->priv->object_path, G_OBJECT (device)); return TRUE; error: return FALSE; } static double sysfs_get_double (const char *dir, const char *attribute) { double result; char *contents; char *filename; result = 0.0; filename = g_build_filename (dir, attribute, NULL); if (g_file_get_contents (filename, &contents, NULL, NULL)) { result = atof (contents); g_free (contents); } g_free (filename); return result; } static char * sysfs_get_string (const char *dir, const char *attribute) { char *result; char *filename; result = NULL; filename = g_build_filename (dir, attribute, NULL); if (!g_file_get_contents (filename, &result, NULL, NULL)) { result = g_strdup (""); } g_free (filename); return result; } static int sysfs_get_int (const char *dir, const char *attribute) { int result; char *contents; char *filename; result = 0; filename = g_build_filename (dir, attribute, NULL); if (g_file_get_contents (filename, &contents, NULL, NULL)) { result = strtol (contents, NULL, 0); g_free (contents); } g_free (filename); return result; } static guint64 sysfs_get_uint64 (const char *dir, const char *attribute) { guint64 result; char *contents; char *filename; result = 0; filename = g_build_filename (dir, attribute, NULL); if (g_file_get_contents (filename, &contents, NULL, NULL)) { result = strtoll (contents, NULL, 0); g_free (contents); } g_free (filename); return result; } static gboolean sysfs_file_exists (const char *dir, const char *attribute) { gboolean result; char *filename; result = FALSE; filename = g_build_filename (dir, attribute, NULL); if (g_file_test (filename, G_FILE_TEST_EXISTS)) { result = TRUE; } g_free (filename); return result; } static void device_generate_kernel_change_event (Device *device) { FILE *f; char *filename; filename = g_build_filename (device->priv->native_path, "uevent", NULL); f = fopen (filename, "w"); if (f == NULL) { g_warning ("error opening %s for writing: %m", filename); } else { if (fputs ("change", f) == EOF) { g_warning ("error writing 'change' to %s: %m", filename); } fclose (f); } g_free (filename); } static char * _dupv8 (const char *s) { const char *end_valid; if (!g_utf8_validate (s, -1, &end_valid)) { g_print ("**** NOTE: The string '%s' is not valid UTF-8. Invalid characters begins at '%s'\n", s, end_valid); return g_strndup (s, end_valid - s); } else { return g_strdup (s); } } static char * sysfs_resolve_link (const char *sysfs_path, const char *name) { char *full_path; char link_path[PATH_MAX]; char resolved_path[PATH_MAX]; ssize_t num; gboolean found_it; found_it = FALSE; full_path = g_build_filename (sysfs_path, name, NULL); //g_debug ("name='%s'", name); //g_debug ("full_path='%s'", full_path); num = readlink (full_path, link_path, sizeof(link_path) - 1); if (num != -1) { char *absolute_path; link_path[num] = '\0'; //g_debug ("link_path='%s'", link_path); absolute_path = g_build_filename (sysfs_path, link_path, NULL); //g_debug ("absolute_path='%s'", absolute_path); if (realpath (absolute_path, resolved_path) != NULL) { //g_debug ("resolved_path='%s'", resolved_path); found_it = TRUE; } g_free (absolute_path); } g_free (full_path); if (found_it) return g_strdup (resolved_path); else return NULL; } /* unescapes things like \x20 to " " and ensures the returned string is valid UTF-8. * * see volume_id_encode_string() in extras/volume_id/lib/volume_id.c in the * udev tree for the encoder */ static gchar * decode_udev_encoded_string (const gchar *str) { GString *s; gchar *ret; const gchar *end_valid; guint n; s = g_string_new (NULL); for (n = 0; str[n] != '\0'; n++) { if (str[n] == '\\') { gint val; if (str[n + 1] != 'x' || str[n + 2] == '\0' || str[n + 3] == '\0') { g_print ("**** NOTE: malformed encoded string '%s'\n", str); break; } val = (g_ascii_xdigit_value (str[n + 2]) << 4) | g_ascii_xdigit_value (str[n + 3]); g_string_append_c (s, val); n += 3; } else { g_string_append_c (s, str[n]); } } if (!g_utf8_validate (s->str, -1, &end_valid)) { g_print ("**** NOTE: The string '%s' is not valid UTF-8. Invalid characters begins at '%s'\n", s->str, end_valid); ret = g_strndup (s->str, end_valid - s->str); g_string_free (s, TRUE); } else { ret = g_string_free (s, FALSE); } return ret; } static gboolean poll_syncing_md_device (gpointer user_data) { Device *device = DEVICE (user_data); g_print ("**** POLL SYNCING MD %s\n", device->priv->native_path); device->priv->linux_md_poll_timeout_id = 0; daemon_local_synthesize_changed (device->priv->daemon, device); return FALSE; } static GList * dup_list_from_ptrarray (GPtrArray *p) { GList *ret; guint n; ret = NULL; for (n = 0; n < p->len; n++) ret = g_list_prepend (ret, g_strdup (((gchar **) p->pdata)[n])); return ret; } static gint ptr_str_array_compare (const gchar **a, const gchar **b) { return g_strcmp0 (*a, *b); } static void diff_sorted_lists (GList *list1, GList *list2, GCompareFunc compare, GList **added, GList **removed) { int order; *added = *removed = NULL; while (list1 != NULL && list2 != NULL) { order = (*compare) (list1->data, list2->data); if (order < 0) { *removed = g_list_prepend (*removed, list1->data); list1 = list1->next; } else if (order > 0) { *added = g_list_prepend (*added, list2->data); list2 = list2->next; } else { /* same item */ list1 = list1->next; list2 = list2->next; } } while (list1 != NULL) { *removed = g_list_prepend (*removed, list1->data); list1 = list1->next; } while (list2 != NULL) { *added = g_list_prepend (*added, list2->data); list2 = list2->next; } } /* ---------------------------------------------------------------------------------------------------- */ /* update id_* properties */ static gboolean update_info_presentation (Device *device) { gboolean hide; gboolean nopolicy; const gchar *automount_hint; hide = FALSE; if (g_udev_device_has_property (device->priv->d, "UDISKS_PRESENTATION_HIDE")) hide = g_udev_device_get_property_as_boolean (device->priv->d, "UDISKS_PRESENTATION_HIDE"); device_set_device_presentation_hide (device, hide); nopolicy = FALSE; if (g_udev_device_has_property (device->priv->d, "UDISKS_PRESENTATION_NOPOLICY")) nopolicy = g_udev_device_get_property_as_boolean (device->priv->d, "UDISKS_PRESENTATION_NOPOLICY"); device_set_device_presentation_nopolicy (device, nopolicy); device_set_device_presentation_name (device, g_udev_device_get_property (device->priv->d, "UDISKS_PRESENTATION_NAME")); device_set_device_presentation_icon_name (device, g_udev_device_get_property (device->priv->d, "UDISKS_PRESENTATION_ICON_NAME")); automount_hint = ""; if (g_udev_device_has_property (device->priv->d, "UDISKS_AUTOMOUNT_HINT")) automount_hint = g_udev_device_get_property (device->priv->d, "UDISKS_AUTOMOUNT_HINT"); device_set_device_automount_hint (device, automount_hint); return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* update id_* properties */ static gboolean update_info_id (Device *device) { gchar *decoded_string; const gchar *partition_scheme; gint partition_type; partition_scheme = g_udev_device_get_property (device->priv->d, "UDISKS_PARTITION_SCHEME"); partition_type = g_udev_device_get_property_as_int (device->priv->d, "UDISKS_PARTITION_TYPE"); if (g_strcmp0 (partition_scheme, "mbr") == 0 && (partition_type == 0x05 || partition_type == 0x0f || partition_type == 0x85)) { device_set_id_usage (device, ""); device_set_id_type (device, ""); device_set_id_version (device, ""); device_set_id_label (device, ""); device_set_id_uuid (device, ""); goto out; } device_set_id_usage (device, g_udev_device_get_property (device->priv->d, "ID_FS_USAGE")); device_set_id_type (device, g_udev_device_get_property (device->priv->d, "ID_FS_TYPE")); device_set_id_version (device, g_udev_device_get_property (device->priv->d, "ID_FS_VERSION")); if (g_udev_device_has_property (device->priv->d, "ID_FS_LABEL_ENC")) { decoded_string = decode_udev_encoded_string (g_udev_device_get_property (device->priv->d, "ID_FS_LABEL_ENC")); device_set_id_label (device, decoded_string); g_free (decoded_string); } else { device_set_id_label (device, g_udev_device_get_property (device->priv->d, "ID_FS_LABEL")); } device_set_id_uuid (device, g_udev_device_get_property (device->priv->d, "ID_FS_UUID")); out: return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* update partition_table_* properties */ static gboolean update_info_partition_table (Device *device) { gboolean is_partition_table; is_partition_table = FALSE; /* Check if udisks-part-id identified the device as a partition table.. this includes * identifying partition tables set up by kpartx for multipath etc. */ if (g_udev_device_get_property_as_boolean (device->priv->d, "UDISKS_PARTITION_TABLE")) { device_set_partition_table_scheme (device, g_udev_device_get_property (device->priv->d, "UDISKS_PARTITION_TABLE_SCHEME")); device_set_partition_table_count (device, g_udev_device_get_property_as_int (device->priv->d, "UDISKS_PARTITION_TABLE_COUNT")); is_partition_table = TRUE; } /* Note that udisks-part-id might not detect all partition table * formats.. so in the negative case, also double check with * information in sysfs. * * The kernel guarantees that all childs are created before the * uevent for the parent is created. So if we have childs, we must * be a partition table. * * To detect a child we check for the existance of a subdir that has * the parents name as a prefix (e.g. for parent sda then sda1, * sda2, sda3 ditto md0, md0p1 etc. etc. will work). */ if (!is_partition_table) { gchar *s; GDir *dir; s = g_path_get_basename (device->priv->native_path); if ((dir = g_dir_open (device->priv->native_path, 0, NULL)) != NULL) { guint partition_count; const gchar *name; partition_count = 0; while ((name = g_dir_read_name (dir)) != NULL) { if (g_str_has_prefix (name, s)) { partition_count++; } } g_dir_close (dir); if (partition_count > 0) { device_set_partition_table_scheme (device, ""); device_set_partition_table_count (device, partition_count); is_partition_table = TRUE; } } g_free (s); } device_set_device_is_partition_table (device, is_partition_table); if (!is_partition_table) { /* otherwise, clear all the data */ device_set_partition_table_scheme (device, NULL); device_set_partition_table_count (device, 0); } return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* update partition_* properties */ static gboolean update_info_partition (Device *device) { gboolean is_partition; is_partition = FALSE; /* Check if udisks-part-id identified the device as a partition.. this includes * identifying partitions set up by kpartx for multipath */ if (g_udev_device_has_property (device->priv->d, "UDISKS_PARTITION")) { guint64 size; const gchar *scheme; const gchar *type; const gchar *label; const gchar *uuid; const gchar* const *flags; guint64 offset; guint64 alignment_offset; const gchar *slave_sysfs_path; gint number; scheme = g_udev_device_get_property (device->priv->d, "UDISKS_PARTITION_SCHEME"); size = g_udev_device_get_property_as_uint64 (device->priv->d, "UDISKS_PARTITION_SIZE"); type = g_udev_device_get_property (device->priv->d, "UDISKS_PARTITION_TYPE"); label = g_udev_device_get_property (device->priv->d, "UDISKS_PARTITION_LABEL"); uuid = g_udev_device_get_property (device->priv->d, "UDISKS_PARTITION_UUID"); flags = g_udev_device_get_property_as_strv (device->priv->d, "UDISKS_PARTITION_FLAGS"); offset = g_udev_device_get_property_as_uint64 (device->priv->d, "UDISKS_PARTITION_OFFSET"); alignment_offset = g_udev_device_get_property_as_uint64 (device->priv->d, "UDISKS_PARTITION_ALIGNMENT_OFFSET"); number = g_udev_device_get_property_as_int (device->priv->d, "UDISKS_PARTITION_NUMBER"); slave_sysfs_path = g_udev_device_get_property (device->priv->d, "UDISKS_PARTITION_SLAVE"); if (slave_sysfs_path != NULL && scheme != NULL && number > 0) { gchar *s; device_set_partition_scheme (device, scheme); device_set_partition_size (device, size); device_set_partition_type (device, type); device_set_partition_label (device, label); device_set_partition_uuid (device, uuid); device_set_partition_flags (device, (gchar **) flags); device_set_partition_offset (device, offset); device_set_partition_alignment_offset (device, alignment_offset); device_set_partition_number (device, number); s = compute_object_path (slave_sysfs_path); device_set_partition_slave (device, s); g_free (s); is_partition = TRUE; } } /* Also handle the case where we are partitioned by the kernel and don't have * any UDISKS_PARTITION_* properties. * * This works without any udev UDISKS_PARTITION_* properties and is * there for maximum compatibility since udisks-part-id only knows a * limited set of partition table formats. */ if (!is_partition && sysfs_file_exists (device->priv->native_path, "start")) { guint64 size; guint64 offset; guint64 alignment_offset; gchar *s; guint n; device_set_device_is_partition (device, TRUE); size = sysfs_get_uint64 (device->priv->native_path, "size"); alignment_offset = sysfs_get_uint64 (device->priv->native_path, "alignment_offset"); device_set_partition_size (device, size * 512); /* device->priv->device_block_size; */ device_set_partition_alignment_offset (device, alignment_offset); offset = sysfs_get_uint64 (device->priv->native_path, "start") * device->priv->device_block_size; device_set_partition_offset (device, offset); s = device->priv->native_path; for (n = strlen (s) - 1; n >= 0 && g_ascii_isdigit (s[n]); n--) ; device_set_partition_number (device, strtol (s + n + 1, NULL, 0)); s = g_strdup (device->priv->native_path); for (n = strlen (s) - 1; n >= 0 && s[n] != '/'; n--) s[n] = '\0'; s[n] = '\0'; device_set_partition_slave (device, compute_object_path (s)); g_free (s); is_partition = TRUE; } device_set_device_is_partition (device, is_partition); if (!is_partition) { /* otherwise, clear all the data */ device_set_partition_scheme (device, NULL); device_set_partition_size (device, device->priv->device_size); device_set_partition_type (device, NULL); device_set_partition_label (device, NULL); device_set_partition_uuid (device, NULL); device_set_partition_flags (device, NULL); } else { device_set_device_is_drive (device, FALSE); } return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* this function sets * * - drive_vendor (unless set already) * - drive_model (unless set already) * - connection_interface (if we can figure that out) * - connection_speed (if we can figure that out) * * All this should really come from udev properties but right now it isn't. */ static void update_drive_properties_from_sysfs (Device *device) { char *s; char *p; char *q; char *model; char *vendor; char *subsystem; char *serial; char *revision; const char *connection_interface; guint64 connection_speed; connection_interface = NULL; connection_speed = 0; /* walk up the device tree to figure out the subsystem */ s = g_strdup (device->priv->native_path); do { p = sysfs_resolve_link (s, "subsystem"); if (p != NULL) { subsystem = g_path_get_basename (p); g_free (p); if (strcmp (subsystem, "scsi") == 0) { connection_interface = "scsi"; connection_speed = 0; /* continue walking up the chain; we just use scsi as a fallback */ /* grab the names from SCSI since the names from udev currently * - replaces whitespace with _ * - is missing for e.g. Firewire */ vendor = sysfs_get_string (s, "vendor"); if (vendor != NULL) { g_strstrip (vendor); /* Don't overwrite what we set earlier from ID_VENDOR */ if (device->priv->drive_vendor == NULL) { q = _dupv8 (vendor); device_set_drive_vendor (device, q); g_free (q); } g_free (vendor); } model = sysfs_get_string (s, "model"); if (model != NULL) { g_strstrip (model); /* Don't overwrite what we set earlier from ID_MODEL */ if (device->priv->drive_model == NULL) { q = _dupv8 (model); device_set_drive_model (device, q); g_free (q); } g_free (model); } /* TODO: need to improve this code; we probably need the kernel to export more * information before we can properly get the type and speed. */ if (device->priv->drive_vendor != NULL && strcmp (device->priv->drive_vendor, "ATA") == 0) { connection_interface = "ata"; break; } } else if (strcmp (subsystem, "usb") == 0) { double usb_speed; /* both the interface and the device will be 'usb'. However only * the device will have the 'speed' property. */ usb_speed = sysfs_get_double (s, "speed"); if (usb_speed > 0) { connection_interface = "usb"; connection_speed = usb_speed * (1000 * 1000); break; } } else if (strcmp (subsystem, "firewire") == 0 || strcmp (subsystem, "ieee1394") == 0) { /* TODO: krh has promised a speed file in sysfs; theoretically, the speed can * be anything from 100, 200, 400, 800 and 3200. Till then we just hardcode * a resonable default of 400 Mbit/s. */ connection_interface = "firewire"; connection_speed = 400 * (1000 * 1000); break; } else if (strcmp (subsystem, "mmc") == 0) { /* TODO: what about non-SD, e.g. MMC? Is that another bus? */ connection_interface = "sdio"; /* Set vendor name. According to this MMC document * * http://www.mmca.org/membership/IAA_Agreement_10_12_06.pdf * * - manfid: the manufacturer id * - oemid: the customer of the manufacturer * * Apparently these numbers are kept secret. It would be nice * to map these into names for setting the manufacturer of the drive, * e.g. Panasonic, Sandisk etc. */ model = sysfs_get_string (s, "name"); if (model != NULL) { g_strstrip (model); /* Don't overwrite what we set earlier from ID_MODEL */ if (device->priv->drive_model == NULL) { q = _dupv8 (model); device_set_drive_model (device, q); g_free (q); } g_free (model); } serial = sysfs_get_string (s, "serial"); if (serial != NULL) { g_strstrip (serial); /* Don't overwrite what we set earlier from ID_SERIAL */ if (device->priv->drive_serial == NULL) { /* this is formatted as a hexnumber; drop the leading 0x */ q = _dupv8 (serial + 2); device_set_drive_serial (device, q); g_free (q); } g_free (serial); } /* TODO: use hwrev and fwrev files? */ revision = sysfs_get_string (s, "date"); if (revision != NULL) { g_strstrip (revision); /* Don't overwrite what we set earlier from ID_REVISION */ if (device->priv->drive_revision == NULL) { q = _dupv8 (revision); device_set_drive_revision (device, q); g_free (q); } g_free (revision); } /* TODO: interface speed; the kernel driver knows; would be nice * if it could export it */ } else if (strcmp (subsystem, "platform") == 0) { const gchar *sysfs_name; sysfs_name = g_strrstr (s, "/"); if (g_str_has_prefix (sysfs_name + 1, "floppy.")) { device_set_drive_vendor (device, "Floppy Drive"); connection_interface = "platform"; } } g_free (subsystem); } /* advance up the chain */ p = g_strrstr (s, "/"); if (p == NULL) break; *p = '\0'; /* but stop at the root */ if (strcmp (s, "/sys/devices") == 0) break; } while (TRUE); if (connection_interface != NULL) { device_set_drive_connection_interface (device, connection_interface); device_set_drive_connection_speed (device, connection_speed); } g_free (s); } static const struct { const gchar *udev_property; const gchar *media_name; } drive_media_mapping[] = { { "ID_DRIVE_FLASH", "flash" }, { "ID_DRIVE_FLASH_CF", "flash_cf" }, { "ID_DRIVE_FLASH_MS", "flash_ms" }, { "ID_DRIVE_FLASH_SM", "flash_sm" }, { "ID_DRIVE_FLASH_SD", "flash_sd" }, { "ID_DRIVE_FLASH_SDHC", "flash_sdhc" }, { "ID_DRIVE_FLASH_MMC", "flash_mmc" }, { "ID_DRIVE_FLOPPY", "floppy" }, { "ID_DRIVE_FLOPPY_ZIP", "floppy_zip" }, { "ID_DRIVE_FLOPPY_JAZ", "floppy_jaz" }, { "ID_CDROM", "optical_cd" }, { "ID_CDROM_CD_R", "optical_cd_r" }, { "ID_CDROM_CD_RW", "optical_cd_rw" }, { "ID_CDROM_DVD", "optical_dvd" }, { "ID_CDROM_DVD_R", "optical_dvd_r" }, { "ID_CDROM_DVD_RW", "optical_dvd_rw" }, { "ID_CDROM_DVD_RAM", "optical_dvd_ram" }, { "ID_CDROM_DVD_PLUS_R", "optical_dvd_plus_r" }, { "ID_CDROM_DVD_PLUS_RW", "optical_dvd_plus_rw" }, { "ID_CDROM_DVD_PLUS_R_DL", "optical_dvd_plus_r_dl" }, { "ID_CDROM_DVD_PLUS_RW_DL", "optical_dvd_plus_rw_dl" }, { "ID_CDROM_BD", "optical_bd" }, { "ID_CDROM_BD_R", "optical_bd_r" }, { "ID_CDROM_BD_RE", "optical_bd_re" }, { "ID_CDROM_HDDVD", "optical_hddvd" }, { "ID_CDROM_HDDVD_R", "optical_hddvd_r" }, { "ID_CDROM_HDDVD_RW", "optical_hddvd_rw" }, { "ID_CDROM_MO", "optical_mo" }, { "ID_CDROM_MRW", "optical_mrw" }, { "ID_CDROM_MRW_W", "optical_mrw_w" }, { NULL, NULL }, }; static const struct { const gchar *udev_property; const gchar *media_name; } media_mapping[] = { { "ID_DRIVE_MEDIA_FLASH", "flash" }, { "ID_DRIVE_MEDIA_FLASH_CF", "flash_cf" }, { "ID_DRIVE_MEDIA_FLASH_MS", "flash_ms" }, { "ID_DRIVE_MEDIA_FLASH_SM", "flash_sm" }, { "ID_DRIVE_MEDIA_FLASH_SD", "flash_sd" }, { "ID_DRIVE_MEDIA_FLASH_SDHC", "flash_sdhc" }, { "ID_DRIVE_MEDIA_FLASH_MMC", "flash_mmc" }, { "ID_DRIVE_MEDIA_FLOPPY", "floppy" }, { "ID_DRIVE_MEDIA_FLOPPY_ZIP", "floppy_zip" }, { "ID_DRIVE_MEDIA_FLOPPY_JAZ", "floppy_jaz" }, { "ID_CDROM_MEDIA_CD", "optical_cd" }, { "ID_CDROM_MEDIA_CD_R", "optical_cd_r" }, { "ID_CDROM_MEDIA_CD_RW", "optical_cd_rw" }, { "ID_CDROM_MEDIA_DVD", "optical_dvd" }, { "ID_CDROM_MEDIA_DVD_R", "optical_dvd_r" }, { "ID_CDROM_MEDIA_DVD_RW", "optical_dvd_rw" }, { "ID_CDROM_MEDIA_DVD_RAM", "optical_dvd_ram" }, { "ID_CDROM_MEDIA_DVD_PLUS_R", "optical_dvd_plus_r" }, { "ID_CDROM_MEDIA_DVD_PLUS_RW", "optical_dvd_plus_rw" }, { "ID_CDROM_MEDIA_DVD_PLUS_R_DL", "optical_dvd_plus_r_dl" }, { "ID_CDROM_MEDIA_DVD_PLUS_RW_DL", "optical_dvd_plus_rw_dl" }, { "ID_CDROM_MEDIA_BD", "optical_bd" }, { "ID_CDROM_MEDIA_BD_R", "optical_bd_r" }, { "ID_CDROM_MEDIA_BD_RE", "optical_bd_re" }, { "ID_CDROM_MEDIA_HDDVD", "optical_hddvd" }, { "ID_CDROM_MEDIA_HDDVD_R", "optical_hddvd_r" }, { "ID_CDROM_MEDIA_HDDVD_RW", "optical_hddvd_rw" }, { "ID_CDROM_MEDIA_MO", "optical_mo" }, { "ID_CDROM_MEDIA_MRW", "optical_mrw" }, { "ID_CDROM_MEDIA_MRW_W", "optical_mrw_w" }, { NULL, NULL }, }; /* update drive_* properties */ static gboolean update_info_drive (Device *device) { GPtrArray *media_compat_array; const gchar *media_in_drive; gboolean drive_is_ejectable; gboolean drive_can_detach; gchar *decoded_string; guint n; if (g_udev_device_has_property (device->priv->d, "ID_VENDOR_ENC")) { decoded_string = decode_udev_encoded_string (g_udev_device_get_property (device->priv->d, "ID_VENDOR_ENC")); g_strstrip (decoded_string); device_set_drive_vendor (device, decoded_string); g_free (decoded_string); } else if (g_udev_device_has_property (device->priv->d, "ID_VENDOR")) { device_set_drive_vendor (device, g_udev_device_get_property (device->priv->d, "ID_VENDOR")); } if (g_udev_device_has_property (device->priv->d, "ID_MODEL_ENC")) { decoded_string = decode_udev_encoded_string (g_udev_device_get_property (device->priv->d, "ID_MODEL_ENC")); g_strstrip (decoded_string); device_set_drive_model (device, decoded_string); g_free (decoded_string); } else if (g_udev_device_has_property (device->priv->d, "ID_MODEL")) { device_set_drive_model (device, g_udev_device_get_property (device->priv->d, "ID_MODEL")); } if (g_udev_device_has_property (device->priv->d, "ID_REVISION")) device_set_drive_revision (device, g_udev_device_get_property (device->priv->d, "ID_REVISION")); if (g_udev_device_has_property (device->priv->d, "ID_SCSI_SERIAL")) { /* scsi_id sometimes use the WWN as the serial - annoying - see * http://git.kernel.org/?p=linux/hotplug/udev.git;a=commit;h=4e9fdfccbdd16f0cfdb5c8fa8484a8ba0f2e69d3 * for details */ device_set_drive_serial (device, g_udev_device_get_property (device->priv->d, "ID_SCSI_SERIAL")); } else if (g_udev_device_has_property (device->priv->d, "ID_SERIAL_SHORT")) { device_set_drive_serial (device, g_udev_device_get_property (device->priv->d, "ID_SERIAL_SHORT")); } if (g_udev_device_has_property (device->priv->d, "ID_WWN_WITH_EXTENSION")) device_set_drive_wwn (device, g_udev_device_get_property (device->priv->d, "ID_WWN_WITH_EXTENSION") + 2); else if (g_udev_device_has_property (device->priv->d, "ID_WWN")) device_set_drive_wwn (device, g_udev_device_get_property (device->priv->d, "ID_WWN") + 2); /* pick up some things (vendor, model, connection_interface, connection_speed) * not (yet) exported by udev helpers */ update_drive_properties_from_sysfs (device); if (g_udev_device_has_property (device->priv->d, "ID_DRIVE_EJECTABLE")) { drive_is_ejectable = g_udev_device_get_property_as_boolean (device->priv->d, "ID_DRIVE_EJECTABLE"); } else { drive_is_ejectable = FALSE; drive_is_ejectable |= g_udev_device_has_property (device->priv->d, "ID_CDROM"); drive_is_ejectable |= g_udev_device_has_property (device->priv->d, "ID_DRIVE_FLOPPY_ZIP"); drive_is_ejectable |= g_udev_device_has_property (device->priv->d, "ID_DRIVE_FLOPPY_JAZ"); } device_set_drive_is_media_ejectable (device, drive_is_ejectable); media_compat_array = g_ptr_array_new (); for (n = 0; drive_media_mapping[n].udev_property != NULL; n++) { if (!g_udev_device_has_property (device->priv->d, drive_media_mapping[n].udev_property)) continue; g_ptr_array_add (media_compat_array, (gpointer) drive_media_mapping[n].media_name); } /* special handling for SDIO since we don't yet have a sdio_id helper in udev to set properties */ if (g_strcmp0 (device->priv->drive_connection_interface, "sdio") == 0) { gchar *type; type = sysfs_get_string (device->priv->native_path, "../../type"); g_strstrip (type); if (g_strcmp0 (type, "MMC") == 0) { g_ptr_array_add (media_compat_array, "flash_mmc"); } else if (g_strcmp0 (type, "SD") == 0) { g_ptr_array_add (media_compat_array, "flash_sd"); } else if (g_strcmp0 (type, "SDHC") == 0) { g_ptr_array_add (media_compat_array, "flash_sdhc"); } g_free (type); } g_ptr_array_sort (media_compat_array, (GCompareFunc) ptr_str_array_compare); g_ptr_array_add (media_compat_array, NULL); device_set_drive_media_compatibility (device, (GStrv) media_compat_array->pdata); media_in_drive = NULL; if (device->priv->device_is_media_available) { for (n = 0; media_mapping[n].udev_property != NULL; n++) { if (!g_udev_device_has_property (device->priv->d, media_mapping[n].udev_property)) continue; media_in_drive = drive_media_mapping[n].media_name; break; } /* If the media isn't set (from e.g. udev rules), just pick the first one in media_compat - note * that this may be NULL (if we don't know what media is compatible with the drive) which is OK. */ if (media_in_drive == NULL) media_in_drive = ((const gchar **) media_compat_array->pdata)[0]; } device_set_drive_media (device, media_in_drive); g_ptr_array_free (media_compat_array, TRUE); /* right now, we only offer to detach USB devices */ drive_can_detach = FALSE; if (g_strcmp0 (device->priv->drive_connection_interface, "usb") == 0) { drive_can_detach = TRUE; } if (g_udev_device_has_property (device->priv->d, "ID_DRIVE_DETACHABLE")) { drive_can_detach = g_udev_device_get_property_as_boolean (device->priv->d, "ID_DRIVE_DETACHABLE"); } device_set_drive_can_detach (device, drive_can_detach); /* rotational is in sysfs */ device_set_drive_is_rotational (device, g_udev_device_get_sysfs_attr_as_boolean (device->priv->d, "queue/rotational")); if (g_udev_device_has_property (device->priv->d, "ID_ATA_ROTATION_RATE_RPM")) { device_set_drive_rotation_rate (device, g_udev_device_get_property_as_int (device->priv->d, "ID_ATA_ROTATION_RATE_RPM")); } if (g_udev_device_get_property_as_boolean (device->priv->d, "ID_ATA_WRITE_CACHE")) { if (g_udev_device_get_property_as_boolean (device->priv->d, "ID_ATA_WRITE_CACHE_ENABLED")) { device_set_drive_write_cache (device, "enabled"); } else { device_set_drive_write_cache (device, "disabled"); } } return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* update drive_can_spindown property */ static gboolean update_info_drive_can_spindown (Device *device) { gboolean drive_can_spindown; /* Right now we only know how to spin down ATA devices (including those USB devices * that can do ATA SMART) * * This would probably also work for SCSI devices (since the helper is doing SCSI * STOP (which translated in libata to ATA's STANDBY IMMEDIATE) - but that needs * testing... */ drive_can_spindown = FALSE; if (g_strcmp0 (device->priv->drive_connection_interface, "ata") == 0 || device->priv->drive_ata_smart_is_available) { drive_can_spindown = TRUE; } if (g_udev_device_has_property (device->priv->d, "ID_DRIVE_CAN_SPINDOWN")) { drive_can_spindown = g_udev_device_get_property_as_boolean (device->priv->d, "ID_DRIVE_CAN_SPINDOWN"); } device_set_drive_can_spindown (device, drive_can_spindown); return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* update device_is_optical_disc and optical_disc_* properties */ static gboolean update_info_optical_disc (Device *device) { const gchar *cdrom_disc_state; gint cdrom_track_count; gint cdrom_track_count_audio; gint cdrom_session_count; /* device_is_optical_disc and optical_disc_* */ if (g_udev_device_has_property (device->priv->d, "ID_CDROM_MEDIA")) { device_set_device_is_optical_disc (device, TRUE); cdrom_track_count = 0; cdrom_track_count_audio = 0; cdrom_session_count = 0; if (g_udev_device_has_property (device->priv->d, "ID_CDROM_MEDIA_TRACK_COUNT")) cdrom_track_count = g_udev_device_get_property_as_int (device->priv->d, "ID_CDROM_MEDIA_TRACK_COUNT"); if (g_udev_device_has_property (device->priv->d, "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO")) cdrom_track_count_audio = g_udev_device_get_property_as_int (device->priv->d, "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO"); if (g_udev_device_has_property (device->priv->d, "ID_CDROM_MEDIA_SESSION_COUNT")) cdrom_session_count = g_udev_device_get_property_as_int (device->priv->d, "ID_CDROM_MEDIA_SESSION_COUNT"); device_set_optical_disc_num_tracks (device, cdrom_track_count); device_set_optical_disc_num_audio_tracks (device, cdrom_track_count_audio); device_set_optical_disc_num_sessions (device, cdrom_session_count); cdrom_disc_state = g_udev_device_get_property (device->priv->d, "ID_CDROM_MEDIA_STATE"); device_set_optical_disc_is_blank (device, g_strcmp0 (cdrom_disc_state, "blank") == 0); device_set_optical_disc_is_appendable (device, g_strcmp0 (cdrom_disc_state, "appendable") == 0); device_set_optical_disc_is_closed (device, g_strcmp0 (cdrom_disc_state, "complete") == 0); } else { device_set_device_is_optical_disc (device, FALSE); device_set_optical_disc_num_tracks (device, 0); device_set_optical_disc_num_audio_tracks (device, 0); device_set_optical_disc_num_sessions (device, 0); device_set_optical_disc_is_blank (device, FALSE); device_set_optical_disc_is_appendable (device, FALSE); device_set_optical_disc_is_closed (device, FALSE); } return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* update device_is_luks and luks_holder properties */ static gboolean update_info_luks (Device *device) { if (g_strcmp0 (device->priv->id_type, "crypto_LUKS") == 0 && device->priv->holders_objpath->len == 1) { device_set_device_is_luks (device, TRUE); device_set_luks_holder (device, device->priv->holders_objpath->pdata[0]); } else { device_set_device_is_luks (device, FALSE); device_set_luks_holder (device, NULL); } return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* update device_is_luks_cleartext and luks_cleartext_* properties */ static gboolean update_info_luks_cleartext (Device *device) { uid_t unlocked_by_uid; const gchar *dkd_dm_name; const gchar *dkd_dm_target_types; gboolean ret; ret = FALSE; dkd_dm_name = g_udev_device_get_property (device->priv->d, "DM_NAME"); dkd_dm_target_types = g_udev_device_get_property (device->priv->d, "UDISKS_DM_TARGETS_TYPE"); if (dkd_dm_name != NULL && g_strcmp0 (dkd_dm_target_types, "crypt") == 0 && device->priv->slaves_objpath->len == 1) { /* TODO: might be racing with setting is_drive earlier */ device_set_device_is_drive (device, FALSE); if (g_str_has_prefix (dkd_dm_name, "temporary-cryptsetup-")) { /* ignore temporary devices created by /sbin/cryptsetup */ goto out; } device_set_device_is_luks_cleartext (device, TRUE); device_set_luks_cleartext_slave (device, ((gchar **) device->priv->slaves_objpath->pdata)[0]); if (luks_get_uid_from_dm_name (dkd_dm_name, &unlocked_by_uid)) { device_set_luks_cleartext_unlocked_by_uid (device, unlocked_by_uid); } /* TODO: export this at some point */ device_set_dm_name (device, dkd_dm_name); } else { device_set_device_is_luks_cleartext (device, FALSE); device_set_luks_cleartext_slave (device, NULL); } ret = TRUE; out: return ret; } /* ---------------------------------------------------------------------------------------------------- */ #ifdef HAVE_LVM2 static gchar * extract_lvm_uuid (const gchar *s) { GString *str; if (s == NULL || strlen (s) < 32) return NULL; str = g_string_new_len (s, 6); g_string_append_c (str, '-'); s += 6; g_string_append_len (str, s, 4); g_string_append_c (str, '-'); s += 4; g_string_append_len (str, s, 4); g_string_append_c (str, '-'); s += 4; g_string_append_len (str, s, 4); g_string_append_c (str, '-'); s += 4; g_string_append_len (str, s, 4); g_string_append_c (str, '-'); s += 4; g_string_append_len (str, s, 4); g_string_append_c (str, '-'); s += 4; g_string_append_len (str, s, 6); return g_string_free (str, FALSE); } /* update device_is_linux_lvm2_lv and linux_lvm2_lv_* properties */ static gboolean update_info_linux_lvm2_lv (Device *device) { const gchar *lv_name; const gchar *vg_name; const gchar *uuid; gchar *lv_uuid; gchar *vg_uuid; gboolean is_lv; is_lv = FALSE; lv_uuid = NULL; vg_uuid = NULL; lv_name = g_udev_device_get_property (device->priv->d, "DM_LV_NAME"); vg_name = g_udev_device_get_property (device->priv->d, "DM_VG_NAME"); if (lv_name == NULL || vg_name == NULL) goto out; uuid = g_udev_device_get_sysfs_attr (device->priv->d, "dm/uuid"); if (uuid == NULL || !g_str_has_prefix (uuid, "LVM-")) goto out; vg_uuid = extract_lvm_uuid (uuid + 4); if (vg_uuid == NULL) goto out; lv_uuid = extract_lvm_uuid (uuid + 4 + 32); if (lv_uuid == NULL) goto out; is_lv = TRUE; device_set_linux_lvm2_lv_name (device, lv_name); device_set_linux_lvm2_lv_uuid (device, lv_uuid); device_set_linux_lvm2_lv_group_name (device, vg_name); device_set_linux_lvm2_lv_group_uuid (device, vg_uuid); device_set_device_is_drive (device, FALSE); device_set_device_is_partition (device, FALSE); out: device_set_device_is_linux_lvm2_lv (device, is_lv); g_free (vg_uuid); g_free (lv_uuid); return TRUE; } #endif /* ---------------------------------------------------------------------------------------------------- */ #ifdef HAVE_DMMP /* update device_is_linux_dmmp and linux_dmmp_* properties */ static gboolean update_info_linux_dmmp (Device *device) { const gchar *dm_name; const gchar* const *target_types; const gchar* const *target_parameters; gchar *decoded_params; gboolean is_dmmp; guint n; GPtrArray *p; Device *component; gchar *s; is_dmmp = FALSE; p = NULL; decoded_params = NULL; dm_name = g_udev_device_get_property (device->priv->d, "DM_NAME"); if (dm_name == NULL) goto out; target_types = g_udev_device_get_property_as_strv (device->priv->d, "UDISKS_DM_TARGETS_TYPE"); if (target_types == NULL || g_strcmp0 (target_types[0], "multipath") != 0) goto out; if (device->priv->slaves_objpath->len == 0) goto out; target_parameters = g_udev_device_get_property_as_strv (device->priv->d, "UDISKS_DM_TARGETS_PARAMS"); if (target_parameters == NULL || g_strv_length ((gchar **) target_parameters) != 1) goto out; decoded_params = decode_udev_encoded_string (target_parameters[0]); device_set_linux_dmmp_name (device, dm_name); device_set_linux_dmmp_parameters (device, decoded_params); p = g_ptr_array_new (); component = NULL; for (n = 0; n < device->priv->slaves_objpath->len; n++) { const gchar *component_objpath = device->priv->slaves_objpath->pdata[n]; if (component == NULL) { component = daemon_local_find_by_object_path (device->priv->daemon, component_objpath); } g_ptr_array_add (p, (gpointer) component_objpath); } g_ptr_array_add (p, NULL); device_set_linux_dmmp_slaves (device, (GStrv) p->pdata); if (component == NULL) goto out; /* Copy only drive properties used for identification to the multipath device. Yes, * this means, we'll get serial/wwn clashes but this is already so for each path. * * Also, clients *should* be smart about things and special-handle linux_dmmp and * linux_dmmp_component devices. */ device_set_drive_vendor (device, component->priv->drive_vendor); device_set_drive_model (device, component->priv->drive_model); device_set_drive_revision (device, component->priv->drive_revision); device_set_drive_serial (device, component->priv->drive_serial); device_set_drive_wwn (device, component->priv->drive_wwn); /* connection interface */ device_set_drive_connection_interface (device, "virtual_multipath"); device_set_drive_connection_speed (device, 0); s = g_strdup_printf ("/dev/mapper/%s", dm_name); device_set_device_file_presentation (device, s); g_free (s); is_dmmp = TRUE; out: g_free (decoded_params); if (p != NULL) g_ptr_array_free (p, TRUE); device_set_device_is_linux_dmmp (device, is_dmmp); return TRUE; } #endif /* ---------------------------------------------------------------------------------------------------- */ #ifdef HAVE_DMMP /* updates device_is_partition and partition_* properties for dm-0 "partitions" on a multi-path device */ static gboolean update_info_partition_on_linux_dmmp (Device *device) { const gchar *dm_name; const gchar* const *targets_type; const gchar* const *targets_params; gchar *params; gint linear_slave_major; gint linear_slave_minor; guint64 offset_sectors; Device *linear_slave; gchar *s; params = NULL; dm_name = g_udev_device_get_property (device->priv->d, "DM_NAME"); if (dm_name == NULL) goto out; targets_type = g_udev_device_get_property_as_strv (device->priv->d, "UDISKS_DM_TARGETS_TYPE"); /* If we ever need this for other types than "linear", remember to update udisks-dm-export.c as well. */ if (targets_type == NULL || !(g_strcmp0 (targets_type[0], "linear") == 0 || g_strcmp0 (targets_type[0], "multipath") == 0)) goto out; targets_params = g_udev_device_get_property_as_strv (device->priv->d, "UDISKS_DM_TARGETS_PARAMS"); if (targets_params == NULL) goto out; params = decode_udev_encoded_string (targets_params[0]); if (sscanf (params, "%d:%d %" G_GUINT64_FORMAT, &linear_slave_major, &linear_slave_minor, &offset_sectors) != 3) goto out; linear_slave = daemon_local_find_by_dev (device->priv->daemon, makedev (linear_slave_major, linear_slave_minor)); if (linear_slave == NULL) goto out; if (!linear_slave->priv->device_is_linux_dmmp) goto out; /* The Partition* properties has been set as part of * update_info_partition() by reading UDISKS_PARTITION_* * properties.. so here we bascially just update the presentation * device file name and and whether the device is a drive. */ s = g_strdup_printf ("/dev/mapper/%s", dm_name); device_set_device_file_presentation (device, s); g_free (s); device_set_device_is_drive (device, FALSE); out: g_free (params); return TRUE; } #endif /* ---------------------------------------------------------------------------------------------------- */ #ifdef HAVE_DMMP /* update device_is_linux_dmmp_component and linux_dmmp_component_* properties */ static gboolean update_info_linux_dmmp_component (Device *device) { gboolean is_dmmp_component; is_dmmp_component = FALSE; if (device->priv->holders_objpath->len == 1) { Device *holder; holder = daemon_local_find_by_object_path (device->priv->daemon, device->priv->holders_objpath->pdata[0]); if (holder != NULL && holder->priv->device_is_linux_dmmp) { is_dmmp_component = TRUE; device_set_linux_dmmp_component_holder (device, holder->priv->object_path); } } device_set_device_is_linux_dmmp_component (device, is_dmmp_component); return TRUE; } #endif /* ---------------------------------------------------------------------------------------------------- */ #ifdef HAVE_LVM2 /* update device_is_linux_lvm2_pv and linux_lvm2_pv_* properties */ static gboolean update_info_linux_lvm2_pv (Device *device) { const gchar *pv_uuid; guint64 pv_num_mda; const gchar *vg_name; const gchar *vg_uuid; gboolean is_pv; guint64 vg_size; guint64 vg_unallocated_size; guint64 vg_extent_size; guint64 vg_seqnum; const gchar* const *vg_pvs; const gchar* const *vg_lvs; pv_uuid = g_udev_device_get_property (device->priv->d, "UDISKS_LVM2_PV_UUID"); pv_num_mda = g_udev_device_get_property_as_uint64 (device->priv->d, "UDISKS_LVM2_PV_NUM_MDA"); vg_name = g_udev_device_get_property (device->priv->d, "UDISKS_LVM2_PV_VG_NAME"); vg_uuid = g_udev_device_get_property (device->priv->d, "UDISKS_LVM2_PV_VG_UUID"); vg_size = g_udev_device_get_property_as_uint64 (device->priv->d, "UDISKS_LVM2_PV_VG_SIZE"); vg_unallocated_size = g_udev_device_get_property_as_uint64 (device->priv->d, "UDISKS_LVM2_PV_VG_FREE_SIZE"); vg_extent_size = g_udev_device_get_property_as_uint64 (device->priv->d, "UDISKS_LVM2_PV_VG_EXTENT_SIZE"); vg_seqnum = g_udev_device_get_property_as_uint64 (device->priv->d, "UDISKS_LVM2_PV_VG_SEQNUM"); vg_pvs = g_udev_device_get_property_as_strv (device->priv->d, "UDISKS_LVM2_PV_VG_PV_LIST"); vg_lvs = g_udev_device_get_property_as_strv (device->priv->d, "UDISKS_LVM2_PV_VG_LV_LIST"); is_pv = FALSE; if (pv_uuid == NULL) goto out; is_pv = TRUE; device_set_linux_lvm2_pv_uuid (device, pv_uuid); device_set_linux_lvm2_pv_num_metadata_areas (device, pv_num_mda); device_set_linux_lvm2_pv_group_name (device, vg_name); device_set_linux_lvm2_pv_group_uuid (device, vg_uuid); device_set_linux_lvm2_pv_group_size (device, vg_size); device_set_linux_lvm2_pv_group_unallocated_size (device, vg_unallocated_size); device_set_linux_lvm2_pv_group_extent_size (device, vg_extent_size); device_set_linux_lvm2_pv_group_sequence_number (device, vg_seqnum); device_set_linux_lvm2_pv_group_physical_volumes (device, (GStrv) vg_pvs); device_set_linux_lvm2_pv_group_logical_volumes (device, (GStrv) vg_lvs); out: device_set_device_is_linux_lvm2_pv (device, is_pv); return TRUE; } #endif /* ---------------------------------------------------------------------------------------------------- */ /* update device_is_linux_md_component and linux_md_component_* properties */ static gboolean update_info_linux_md_component (Device *device) { if (g_strcmp0 (device->priv->id_type, "linux_raid_member") == 0) { const gchar *md_comp_level; gint md_comp_num_raid_devices; const gchar *md_comp_uuid; const gchar *md_comp_home_host; const gchar *md_comp_name; const gchar *md_comp_version; gchar *md_name; gchar *s; int md_comp_position; md_comp_position = -1; device_set_device_is_linux_md_component (device, TRUE); /* linux_md_component_holder and linux_md_component_state */ if (device->priv->holders_objpath->len == 1) { Device *holder; gchar **state_tokens; device_set_linux_md_component_holder (device, device->priv->holders_objpath->pdata[0]); state_tokens = NULL; holder = daemon_local_find_by_object_path (device->priv->daemon, device->priv->holders_objpath->pdata[0]); if (holder != NULL && holder->priv->device_is_linux_md) { gchar *dev_name; gchar *md_dev_path; gchar *state_contents; gchar *slot_contents; gint slot_number; gchar *endp; dev_name = g_path_get_basename (device->priv->native_path); md_dev_path = g_strdup_printf ("%s/md/dev-%s", holder->priv->native_path, dev_name); state_contents = sysfs_get_string (md_dev_path, "state"); g_strstrip (state_contents); state_tokens = g_strsplit (state_contents, ",", 0); slot_contents = sysfs_get_string (md_dev_path, "slot"); g_strstrip (slot_contents); slot_number = strtol (slot_contents, &endp, 0); if (endp != NULL && *endp == '\0') { md_comp_position = slot_number; } g_free (slot_contents); g_free (state_contents); g_free (md_dev_path); g_free (dev_name); } device_set_linux_md_component_state (device, state_tokens); g_strfreev (state_tokens); } else { /* no holder, nullify properties */ device_set_linux_md_component_holder (device, NULL); device_set_linux_md_component_state (device, NULL); } md_comp_level = g_udev_device_get_property (device->priv->d, "MD_LEVEL"); md_comp_num_raid_devices = g_udev_device_get_property_as_int (device->priv->d, "MD_DEVICES"); md_comp_uuid = g_udev_device_get_property (device->priv->d, "MD_UUID"); md_name = g_strdup (g_udev_device_get_property (device->priv->d, "MD_NAME")); s = NULL; if (md_name != NULL) s = strstr (md_name, ":"); if (s != NULL) { *s = '\0'; md_comp_home_host = md_name; md_comp_name = s + 1; } else { md_comp_home_host = ""; md_comp_name = md_name; } md_comp_version = device->priv->id_version; device_set_linux_md_component_level (device, md_comp_level); device_set_linux_md_component_position (device, md_comp_position); device_set_linux_md_component_num_raid_devices (device, md_comp_num_raid_devices); device_set_linux_md_component_uuid (device, md_comp_uuid); device_set_linux_md_component_home_host (device, md_comp_home_host); device_set_linux_md_component_name (device, md_comp_name); device_set_linux_md_component_version (device, md_comp_version); g_free (md_name); } else { device_set_device_is_linux_md_component (device, FALSE); device_set_linux_md_component_level (device, NULL); device_set_linux_md_component_position (device, -1); device_set_linux_md_component_num_raid_devices (device, 0); device_set_linux_md_component_uuid (device, NULL); device_set_linux_md_component_home_host (device, NULL); device_set_linux_md_component_name (device, NULL); device_set_linux_md_component_version (device, NULL); device_set_linux_md_component_holder (device, NULL); device_set_linux_md_component_state (device, NULL); } return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* update device_is_linux_md and linux_md_* properties */ static gboolean update_info_linux_md (Device *device) { gboolean ret; guint n; gchar *s; gchar *p; ret = FALSE; if (sysfs_file_exists (device->priv->native_path, "md")) { gchar *uuid; gint num_raid_devices; gchar *raid_level; gchar *array_state; Device *slave; GPtrArray *md_slaves; const gchar *md_name; const gchar *md_home_host; device_set_device_is_linux_md (device, TRUE); /* figure out if the array is active */ array_state = sysfs_get_string (device->priv->native_path, "md/array_state"); if (array_state == NULL) { g_print ("**** NOTE: Linux MD array %s has no array_state file'; removing\n", device->priv->native_path); goto out; } g_strstrip (array_state); /* ignore clear arrays since these have no devices, no size, no level */ if (strcmp (array_state, "clear") == 0) { g_print ("**** NOTE: Linux MD array %s is 'clear'; removing\n", device->priv->native_path); g_free (array_state); goto out; } device_set_linux_md_state (device, array_state); g_free (array_state); /* find a slave from the array */ slave = NULL; for (n = 0; n < device->priv->slaves_objpath->len; n++) { const gchar *slave_objpath; slave_objpath = device->priv->slaves_objpath->pdata[n]; slave = daemon_local_find_by_object_path (device->priv->daemon, slave_objpath); if (slave != NULL) break; } uuid = g_strdup (g_udev_device_get_property (device->priv->d, "MD_UUID")); num_raid_devices = sysfs_get_int (device->priv->native_path, "md/raid_disks"); raid_level = g_strstrip (sysfs_get_string (device->priv->native_path, "md/level")); if (slave != NULL) { /* if the UUID isn't set by the udev rules (array may be inactive) get it from a slave */ if (uuid == NULL || strlen (uuid) == 0) { g_free (uuid); uuid = g_strdup (slave->priv->linux_md_component_uuid); } /* ditto for raid level */ if (raid_level == NULL || strlen (raid_level) == 0) { g_free (raid_level); raid_level = g_strdup (slave->priv->linux_md_component_level); } /* and num_raid_devices too */ if (device->priv->linux_md_num_raid_devices == 0) { num_raid_devices = slave->priv->linux_md_component_num_raid_devices; } } device_set_linux_md_uuid (device, uuid); device_set_linux_md_num_raid_devices (device, num_raid_devices); device_set_linux_md_level (device, raid_level); g_free (raid_level); g_free (uuid); /* infer the array name and homehost */ p = g_strdup (g_udev_device_get_property (device->priv->d, "MD_NAME")); s = NULL; if (p != NULL) s = strstr (p, ":"); if (s != NULL) { *s = '\0'; md_home_host = p; md_name = s + 1; } else { md_home_host = ""; md_name = p; } device_set_linux_md_home_host (device, md_home_host); device_set_linux_md_name (device, md_name); g_free (p); s = g_strstrip (sysfs_get_string (device->priv->native_path, "md/metadata_version")); device_set_linux_md_version (device, s); g_free (s); /* Go through all block slaves and build up the linux_md_slaves property * * Also update the slaves since the slave state may have changed. */ md_slaves = g_ptr_array_new (); for (n = 0; n < device->priv->slaves_objpath->len; n++) { Device *slave_device; const gchar *slave_objpath; slave_objpath = device->priv->slaves_objpath->pdata[n]; g_ptr_array_add (md_slaves, (gpointer) slave_objpath); slave_device = daemon_local_find_by_object_path (device->priv->daemon, slave_objpath); if (slave_device != NULL) { update_info (slave_device); } } g_ptr_array_sort (md_slaves, (GCompareFunc) ptr_str_array_compare); g_ptr_array_add (md_slaves, NULL); device_set_linux_md_slaves (device, (GStrv) md_slaves->pdata); g_ptr_array_free (md_slaves, TRUE); /* TODO: may race */ device_set_drive_vendor (device, "Linux"); if (device->priv->linux_md_level != NULL) s = g_strdup_printf ("Software RAID %s", device->priv->linux_md_level); else s = g_strdup_printf ("Software RAID"); device_set_drive_model (device, s); g_free (s); device_set_drive_revision (device, device->priv->linux_md_version); device_set_drive_connection_interface (device, "virtual"); device_set_drive_serial (device, device->priv->linux_md_uuid); /* RAID-0 can never resync or run degraded */ if (g_strcmp0 (device->priv->linux_md_level, "raid0") == 0 || g_strcmp0 (device->priv->linux_md_level, "linear") == 0) { device_set_linux_md_sync_action (device, "idle"); device_set_linux_md_is_degraded (device, FALSE); } else { gchar *degraded_file; gint num_degraded_devices; degraded_file = sysfs_get_string (device->priv->native_path, "md/degraded"); if (degraded_file == NULL) { num_degraded_devices = 0; } else { num_degraded_devices = strtol (degraded_file, NULL, 0); } g_free (degraded_file); device_set_linux_md_is_degraded (device, (num_degraded_devices > 0)); s = g_strstrip (sysfs_get_string (device->priv->native_path, "md/sync_action")); device_set_linux_md_sync_action (device, s); g_free (s); if (device->priv->linux_md_sync_action == NULL || strlen (device->priv->linux_md_sync_action) == 0) { device_set_linux_md_sync_action (device, "idle"); } /* if not idle; update percentage and speed */ if (g_strcmp0 (device->priv->linux_md_sync_action, "idle") != 0) { char *s; guint64 done; guint64 remaining; s = g_strstrip (sysfs_get_string (device->priv->native_path, "md/sync_completed")); if (sscanf (s, "%" G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "", &done, &remaining) == 2) { device_set_linux_md_sync_percentage (device, 100.0 * ((double) done) / ((double) remaining)); } else { g_warning ("cannot parse md/sync_completed for %s: '%s'", device->priv->native_path, s); } g_free (s); device_set_linux_md_sync_speed (device, 1000L * sysfs_get_uint64 (device->priv->native_path, "md/sync_speed")); /* Since the kernel doesn't emit uevents while the job is pending, set up * a timeout for every two seconds to synthesize the change event so we can * refresh the completed/speed properties. */ if (device->priv->linux_md_poll_timeout_id == 0) { device->priv->linux_md_poll_timeout_id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, 2, poll_syncing_md_device, g_object_ref (device), g_object_unref); } } else { device_set_linux_md_sync_percentage (device, 0.0); device_set_linux_md_sync_speed (device, 0); } } } else { device_set_device_is_linux_md (device, FALSE); device_set_linux_md_state (device, NULL); device_set_linux_md_level (device, NULL); device_set_linux_md_num_raid_devices (device, 0); device_set_linux_md_uuid (device, NULL); device_set_linux_md_home_host (device, NULL); device_set_linux_md_name (device, NULL); device_set_linux_md_version (device, NULL); device_set_linux_md_slaves (device, NULL); device_set_linux_md_is_degraded (device, FALSE); device_set_linux_md_sync_action (device, NULL); device_set_linux_md_sync_percentage (device, 0.0); device_set_linux_md_sync_speed (device, 0); } ret = TRUE; out: return ret; } /* ---------------------------------------------------------------------------------------------------- */ /* update drive_ata_smart_* properties */ static gboolean update_info_drive_ata_smart (Device *device) { gboolean ata_smart_is_available; ata_smart_is_available = FALSE; if (device->priv->device_is_drive && g_udev_device_has_property (device->priv->d, "UDISKS_ATA_SMART_IS_AVAILABLE")) ata_smart_is_available = g_udev_device_get_property_as_boolean (device->priv->d, "UDISKS_ATA_SMART_IS_AVAILABLE"); device_set_drive_ata_smart_is_available (device, ata_smart_is_available); /* NOTE: we don't collect ATA SMART data here, we only set whether the device is ATA SMART capable; * collecting data is done in separate routines, see the * device_drive_ata_smart_refresh_data() function for details. */ return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* device_is_system_internal */ static gboolean update_info_is_system_internal (Device *device) { gboolean is_system_internal; if (g_udev_device_has_property (device->priv->d, "UDISKS_SYSTEM_INTERNAL")) { is_system_internal = g_udev_device_get_property_as_boolean (device->priv->d, "UDISKS_SYSTEM_INTERNAL"); goto determined; } /* start out by assuming the device is system internal, then adjust depending on what kind of * device we are dealing with */ is_system_internal = TRUE; /* A Linux MD device is system internal if, and only if * * - a single component is system internal * - there are no components */ if (device->priv->device_is_linux_md) { is_system_internal = FALSE; if (device->priv->slaves_objpath->len == 0) { is_system_internal = TRUE; } else { guint n; for (n = 0; n < device->priv->slaves_objpath->len; n++) { const gchar *slave_objpath; Device *slave; slave_objpath = device->priv->slaves_objpath->pdata[n]; slave = daemon_local_find_by_object_path (device->priv->daemon, slave_objpath); if (slave == NULL) continue; if (slave->priv->device_is_system_internal) { is_system_internal = TRUE; break; } } } goto determined; } /* a partition is system internal only if the drive it belongs to is system internal */ if (device->priv->device_is_partition) { Device *enclosing_device; enclosing_device = daemon_local_find_by_object_path (device->priv->daemon, device->priv->partition_slave); if (enclosing_device != NULL) { is_system_internal = enclosing_device->priv->device_is_system_internal; } else { is_system_internal = TRUE; } goto determined; } /* a LUKS cleartext device is system internal only if the underlying crypto-text * device is system internal */ if (device->priv->device_is_luks_cleartext) { Device *enclosing_device; enclosing_device = daemon_local_find_by_object_path (device->priv->daemon, device->priv->luks_cleartext_slave); if (enclosing_device != NULL) { is_system_internal = enclosing_device->priv->device_is_system_internal; } else { is_system_internal = TRUE; } goto determined; } /* devices with removable media are never system internal */ if (device->priv->device_is_removable) { is_system_internal = FALSE; goto determined; } /* devices on certain buses are never system internal */ if (device->priv->device_is_drive && device->priv->drive_connection_interface != NULL) { if (strcmp (device->priv->drive_connection_interface, "ata_serial_esata") == 0 || strcmp (device->priv->drive_connection_interface, "sdio") == 0 || strcmp (device->priv->drive_connection_interface, "usb") == 0 || strcmp (device->priv->drive_connection_interface, "firewire") == 0) { is_system_internal = FALSE; } else { is_system_internal = TRUE; } goto determined; } determined: device_set_device_is_system_internal (device, is_system_internal); return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* device_is_mounted, device_mount, device_mounted_by_uid */ static gboolean update_info_mount_state (Device *device) { MountMonitor *monitor; GList *mounts; gboolean was_mounted; mounts = NULL; /* defer setting the mount point until FilesystemMount returns and * the mounts file is written */ if (device->priv->job_in_progress && g_strcmp0 (device->priv->job_id, "FilesystemMount") == 0) goto out; monitor = daemon_local_get_mount_monitor (device->priv->daemon); mounts = mount_monitor_get_mounts_for_dev (monitor, device->priv->dev); was_mounted = device->priv->device_is_mounted; if (mounts != NULL) { GList *l; guint n; gchar **mount_paths; mount_paths = g_new0 (gchar *, g_list_length (mounts) + 1); for (l = mounts, n = 0; l != NULL; l = l->next, n++) { mount_paths[n] = g_strdup (mount_get_mount_path (MOUNT (l->data))); } device_set_device_is_mounted (device, TRUE); device_set_device_mount_paths (device, mount_paths); if (!was_mounted) { uid_t mounted_by_uid; if (!mount_file_has_device (device->priv->device_file, &mounted_by_uid, NULL)) mounted_by_uid = 0; device_set_device_mounted_by_uid (device, mounted_by_uid); } g_strfreev (mount_paths); } else { gboolean remove_dir_on_unmount; gchar *old_mount_path; old_mount_path = NULL; if (device->priv->device_mount_paths->len > 0) old_mount_path = g_strdup (((gchar **) device->priv->device_mount_paths->pdata)[0]); device_set_device_is_mounted (device, FALSE); device_set_device_mount_paths (device, NULL); device_set_device_mounted_by_uid (device, 0); /* clean up stale mount directory */ remove_dir_on_unmount = FALSE; if (was_mounted && mount_file_has_device (device->priv->device_file, NULL, &remove_dir_on_unmount)) { mount_file_remove (device->priv->device_file, old_mount_path); if (remove_dir_on_unmount) { if (g_rmdir (old_mount_path) != 0) { g_warning ("Error removing dir '%s' on unmount: %m", old_mount_path); } } } g_free (old_mount_path); } out: g_list_foreach (mounts, (GFunc) g_object_unref, NULL); g_list_free (mounts); return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* device_is_media_change_detected, device_is_media_change_detection_* properties */ static gboolean update_info_media_detection (Device *device) { gboolean detected; gboolean polling; gboolean inhibitable; gboolean inhibited; detected = FALSE; polling = FALSE; inhibitable = FALSE; inhibited = FALSE; if (device->priv->device_is_removable) { guint64 evt_media_change; GUdevDevice *parent; evt_media_change = sysfs_get_uint64 (device->priv->native_path, "../../evt_media_change"); if (evt_media_change & 1) { /* SATA AN capabable drive */ polling = FALSE; detected = TRUE; goto determined; } parent = g_udev_device_get_parent_with_subsystem (device->priv->d, "platform", NULL); if (parent != NULL) { /* never poll PC floppy drives, they are noisy (fdo #22149) */ if (g_str_has_prefix (g_udev_device_get_name (parent), "floppy.")) { g_object_unref (parent); goto determined; } g_object_unref (parent); } /* assume the device needs polling */ polling = TRUE; inhibitable = TRUE; /* custom udev rules might want to disable polling for known-broken * devices (fdo #26508) */ if (g_udev_device_has_property (device->priv->d, "UDISKS_DISABLE_POLLING") && g_udev_device_get_property_as_boolean (device->priv->d, "UDISKS_DISABLE_POLLING")) polling = FALSE; if (device->priv->polling_inhibitors != NULL || daemon_local_has_polling_inhibitors (device->priv->daemon)) { detected = FALSE; inhibited = TRUE; } else { detected = TRUE; inhibited = FALSE; } } determined: device_set_device_is_media_change_detected (device, detected); device_set_device_is_media_change_detection_polling (device, polling); device_set_device_is_media_change_detection_inhibitable (device, inhibitable); device_set_device_is_media_change_detection_inhibited (device, inhibited); return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* drive_adapter property */ static gboolean update_info_drive_adapter (Device *device) { Adapter *adapter; const gchar *adapter_object_path; adapter_object_path = NULL; adapter = daemon_local_find_enclosing_adapter (device->priv->daemon, device->priv->native_path); if (adapter != NULL) { adapter_object_path = adapter_local_get_object_path (adapter); } device_set_drive_adapter (device, adapter_object_path); return TRUE; } /* drive_ports property */ static gboolean update_info_drive_ports (Device *device) { GList *ports; GList *l; GPtrArray *p; ports = daemon_local_find_enclosing_ports (device->priv->daemon, device->priv->native_path); p = g_ptr_array_new (); for (l = ports; l != NULL; l = l->next) { Port *port = PORT (l->data); g_ptr_array_add (p, (gpointer) port_local_get_object_path (port)); } g_ptr_array_add (p, NULL); device_set_drive_ports (device, (GStrv) p->pdata); g_ptr_array_unref (p); g_list_free (ports); return TRUE; } /* drive_similar_devices property */ static gboolean update_info_drive_similar_devices (Device *device) { GList *devices; GList *l; GPtrArray *p; p = g_ptr_array_new (); if (!device->priv->device_is_drive) goto out; /* We need non-empty SERIAL and WWN for this to work */ if ((device->priv->drive_serial == NULL || strlen (device->priv->drive_serial) == 0) || (device->priv->drive_wwn == NULL || strlen (device->priv->drive_wwn) == 0)) goto out; /* TODO: this might be slow - if so, use a hash on the Daemon class */ devices = daemon_local_get_all_devices (device->priv->daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (!d->priv->device_is_drive) continue; if (d == device) continue; #if 0 g_debug ("looking at %s:\n" " %s\n" " %s\n" " %s\n" " %s\n", d->priv->device_file, d->priv->drive_serial, device->priv->drive_serial, d->priv->drive_wwn, device->priv->drive_wwn); #endif /* current policy is that *both* SERIAL and WWN must match */ if (g_strcmp0 (d->priv->drive_serial, device->priv->drive_serial) == 0 && g_strcmp0 (d->priv->drive_wwn, device->priv->drive_wwn) == 0) { g_ptr_array_add (p, d->priv->object_path); /* ensure that the device we added also exists in its own drive_similar_devices property */ if (!ptr_array_has_string (d->priv->drive_similar_devices, device->priv->object_path)) { //g_debug ("\n************************************************************\nforcing update in idle"); update_info_in_idle (d); } else { //g_debug ("\n************************************************************\nNOT forcing update in idle"); } } } out: g_ptr_array_add (p, NULL); device_set_drive_similar_devices (device, (GStrv) p->pdata); g_ptr_array_free (p, TRUE); return TRUE; } /* ---------------------------------------------------------------------------------------------------- */ /* update device_is_linux_loop and linux_loop_* properties */ static gboolean update_info_linux_loop (Device *device) { gboolean ret; gint fd; struct loop_info64 loop_info_buf; gboolean is_loop; gchar *s; gchar *s2; is_loop = FALSE; ret = FALSE; fd = -1; if (!g_str_has_prefix (device->priv->native_path, "/sys/devices/virtual/block/loop")) { ret = TRUE; goto out; } fd = open (device->priv->device_file, O_RDONLY); if (fd < 0) goto out; if (ioctl (fd, LOOP_GET_STATUS64, &loop_info_buf) != 0) goto out; /* TODO: is lo_file_name really NUL-terminated? */ device_set_linux_loop_filename (device, (const gchar *) loop_info_buf.lo_file_name); ret = TRUE; is_loop = TRUE; device_set_drive_vendor (device, "Linux"); s2 = g_path_get_basename ((gchar *) loop_info_buf.lo_file_name); s = g_strdup_printf ("Loop: %s", s2); g_free (s2); device_set_drive_model (device, s); g_free (s); out: if (fd > 0) close (fd); device_set_device_is_linux_loop (device, is_loop); return ret; } /* ---------------------------------------------------------------------------------------------------- */ typedef struct { guint idle_id; Device *device; } UpdateInfoInIdleData; static void update_info_in_idle_device_unreffed (gpointer user_data, GObject *where_the_object_was) { UpdateInfoInIdleData *data = user_data; g_source_remove (data->idle_id); } static void update_info_in_idle_data_free (UpdateInfoInIdleData *data) { g_object_weak_unref (G_OBJECT (data->device), update_info_in_idle_device_unreffed, data); g_free (data); } static gboolean update_info_in_idle_cb (gpointer user_data) { UpdateInfoInIdleData *data = user_data; /* this indirectly calls update_info and also removes the device * if it wants to be removed (e.g. if update_info() returns FALSE) */ daemon_local_synthesize_changed (data->device->priv->daemon, data->device); return FALSE; /* remove source */ } /** * update_info_in_idle: * @device: A #Device. * * Like update_info() but does the update in idle. Takes a weak ref to * @device and cancels the update if @device is unreffed. */ static void update_info_in_idle (Device *device) { UpdateInfoInIdleData *data; data = g_new0 (UpdateInfoInIdleData, 1); data->device = device; data->idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE, update_info_in_idle_cb, data, (GDestroyNotify) update_info_in_idle_data_free); g_object_weak_ref (G_OBJECT (device), update_info_in_idle_device_unreffed, data); } /** * update_info: * @device: the device * * Update information about the device. * * If one or more properties changed, the changes are scheduled to be emitted. Use * drain_pending_changes() to force emitting the pending changes (which is useful * before returning the result of an operation). * * Returns: #TRUE to keep (or add) the device; #FALSE to ignore (or remove) the device **/ static gboolean update_info (Device *device) { char *s; guint n; gboolean ret; char *path; GDir *dir; const char *name; GList *l; GList *old_slaves_objpath; GList *old_holders_objpath; GList *cur_slaves_objpath; GList *cur_holders_objpath; GList *added_objpath; GList *removed_objpath; GPtrArray *symlinks_by_id; GPtrArray *symlinks_by_path; GPtrArray *slaves; GPtrArray *holders; gint major; gint minor; gboolean media_available; ret = FALSE; PROFILE ("update_info(device=%s) start", device->priv->native_path); g_print ("**** UPDATING %s\n", device->priv->native_path); /* need the slaves/holders to synthesize 'change' events if a device goes away (since the kernel * doesn't do generate these) */ old_slaves_objpath = dup_list_from_ptrarray (device->priv->slaves_objpath); old_holders_objpath = dup_list_from_ptrarray (device->priv->holders_objpath); /* drive identification */ if (sysfs_file_exists (device->priv->native_path, "range")) { device_set_device_is_drive (device, TRUE); } else { device_set_device_is_drive (device, FALSE); } if (!g_udev_device_has_property (device->priv->d, "MAJOR") || !g_udev_device_has_property (device->priv->d, "MINOR")) { g_warning ("No major/minor for %s", device->priv->native_path); goto out; } /* ignore dm devices that are suspended */ if (g_str_has_prefix (g_udev_device_get_name (device->priv->d), "dm-")) { if (g_strcmp0 (g_udev_device_get_property (device->priv->d, "DM_SUSPENDED"), "1") == 0) goto out; } major = g_udev_device_get_property_as_int (device->priv->d, "MAJOR"); minor = g_udev_device_get_property_as_int (device->priv->d, "MINOR"); device->priv->dev = makedev (major, minor); device_set_device_file (device, g_udev_device_get_device_file (device->priv->d)); if (device->priv->device_file == NULL) { g_warning ("No device file for %s", device->priv->native_path); goto out; } const char * const * symlinks; symlinks = g_udev_device_get_device_file_symlinks (device->priv->d); symlinks_by_id = g_ptr_array_new (); symlinks_by_path = g_ptr_array_new (); for (n = 0; symlinks[n] != NULL; n++) { if (g_str_has_prefix (symlinks[n], "/dev/disk/by-id/") || g_str_has_prefix (symlinks[n], "/dev/disk/by-uuid/")) { g_ptr_array_add (symlinks_by_id, (gpointer) symlinks[n]); } else if (g_str_has_prefix (symlinks[n], "/dev/disk/by-path/")) { g_ptr_array_add (symlinks_by_path, (gpointer) symlinks[n]); } } g_ptr_array_sort (symlinks_by_id, (GCompareFunc) ptr_str_array_compare); g_ptr_array_sort (symlinks_by_path, (GCompareFunc) ptr_str_array_compare); g_ptr_array_add (symlinks_by_id, NULL); g_ptr_array_add (symlinks_by_path, NULL); device_set_device_file_by_id (device, (GStrv) symlinks_by_id->pdata); device_set_device_file_by_path (device, (GStrv) symlinks_by_path->pdata); g_ptr_array_free (symlinks_by_id, TRUE); g_ptr_array_free (symlinks_by_path, TRUE); device_set_device_is_removable (device, (sysfs_get_int (device->priv->native_path, "removable") != 0)); /* device_is_media_available and device_media_detection_time property */ if (device->priv->device_is_removable) { media_available = FALSE; if (!g_udev_device_get_property_as_boolean (device->priv->d, "ID_CDROM") && !g_udev_device_get_property_as_boolean (device->priv->d, "ID_DRIVE_FLOPPY")) { int fd; fd = open (device->priv->device_file, O_RDONLY); if (fd >= 0) { media_available = TRUE; close (fd); } } else { if (g_udev_device_get_property_as_boolean (device->priv->d, "ID_CDROM_MEDIA")) { media_available = TRUE; } else { media_available = FALSE; } } } else { media_available = TRUE; } device_set_device_is_media_available (device, media_available); if (media_available) { if (device->priv->device_media_detection_time == 0) device_set_device_media_detection_time (device, (guint64) time (NULL)); } else { device_set_device_media_detection_time (device, 0); } /* device_size, device_block_size and device_is_read_only properties */ if (device->priv->device_is_media_available) { guint64 block_size; device_set_device_size (device, sysfs_get_uint64 (device->priv->native_path, "size") * ((guint64) 512)); device_set_device_is_read_only (device, (sysfs_get_int (device->priv->native_path, "ro") != 0)); /* This is not available on all devices so fall back to 512 if unavailable. * * Another way to get this information is the BLKSSZGET ioctl but we don't want * to open the device. Ideally vol_id would export it. */ block_size = sysfs_get_uint64 (device->priv->native_path, "queue/hw_sector_size"); if (block_size == 0) block_size = 512; device_set_device_block_size (device, block_size); } else { device_set_device_size (device, 0); device_set_device_block_size (device, 0); device_set_device_is_read_only (device, FALSE); } /* Maintain (non-exported) properties holders and slaves for the holders resp. slaves * directories in sysfs. The entries in these arrays are object paths - we ignore * an entry unless it corresponds to an device in our local database. */ path = g_build_filename (device->priv->native_path, "slaves", NULL); slaves = g_ptr_array_new (); if ((dir = g_dir_open (path, 0, NULL)) != NULL) { while ((name = g_dir_read_name (dir)) != NULL) { Device *device2; s = compute_object_path (name); device2 = daemon_local_find_by_object_path (device->priv->daemon, s); if (device2 != NULL) { //g_debug ("%s has slave %s", device->priv->object_path, s); g_ptr_array_add (slaves, s); } else { //g_debug ("%s has non-existant slave %s", device->priv->object_path, s); g_free (s); } } g_dir_close (dir); } g_free (path); g_ptr_array_sort (slaves, (GCompareFunc) ptr_str_array_compare); g_ptr_array_add (slaves, NULL); device_set_slaves_objpath (device, (GStrv) slaves->pdata); g_ptr_array_foreach (slaves, (GFunc) g_free, NULL); g_ptr_array_free (slaves, TRUE); path = g_build_filename (device->priv->native_path, "holders", NULL); holders = g_ptr_array_new (); if ((dir = g_dir_open (path, 0, NULL)) != NULL) { while ((name = g_dir_read_name (dir)) != NULL) { Device *device2; s = compute_object_path (name); device2 = daemon_local_find_by_object_path (device->priv->daemon, s); if (device2 != NULL) { //g_debug ("%s has holder %s", device->priv->object_path, s); g_ptr_array_add (holders, s); } else { //g_debug ("%s has non-existant holder %s", device->priv->object_path, s); g_free (s); } } g_dir_close (dir); } g_free (path); g_ptr_array_sort (holders, (GCompareFunc) ptr_str_array_compare); g_ptr_array_add (holders, NULL); device_set_holders_objpath (device, (GStrv) holders->pdata); g_ptr_array_foreach (holders, (GFunc) g_free, NULL); g_ptr_array_free (holders, TRUE); /* ------------------------------------- */ /* Now set all properties from udev data */ /* ------------------------------------- */ /* at this point we have * * - device_file * - device_file_by_id * - device_file_by_path * - device_size * - device_block_size * - device_is_removable * - device_is_read_only * - device_is_drive * - device_is_media_available * - device_is_partition * - device_is_partition_table * - slaves_objpath * - holders_objpath * * - partition_number * - partition_slave * */ /* device_is_linux_loop and linux_loop_* properties */ if (!update_info_linux_loop (device)) goto out; /* partition_* properties */ if (!update_info_partition (device)) goto out; /* partition_table_* properties */ if (!update_info_partition_table (device)) goto out; /* device_presentation_hide, device_presentation_name and device_presentation_icon_name properties */ if (!update_info_presentation (device)) goto out; /* id_* properties */ if (!update_info_id (device)) goto out; /* drive_* properties */ if (!update_info_drive (device)) goto out; /* device_is_optical_disc and optical_disc_* properties */ if (!update_info_optical_disc (device)) goto out; /* device_is_luks and luks_holder */ if (!update_info_luks (device)) goto out; /* device_is_luks_cleartext and luks_cleartext_* properties */ if (!update_info_luks_cleartext (device)) goto out; #ifdef HAVE_LVM2 /* device_is_linux_lvm2_lv and linux_lvm2_lv_* properties */ if (!update_info_linux_lvm2_lv (device)) goto out; /* device_is_linux_lvm2_pv and linux_lvm2_pv_* properties */ if (!update_info_linux_lvm2_pv (device)) goto out; #endif #ifdef HAVE_DMMP /* device_is_linux_dmmp and linux_dmmp_* properties */ if (!update_info_linux_dmmp (device)) goto out; /* device_is_partition and partition_* properties for dm-0 "partitions" on a multi-path device */ if (!update_info_partition_on_linux_dmmp (device)) goto out; /* device_is_linux_dmmp_component and linux_dmmp_component_* properties */ if (!update_info_linux_dmmp_component (device)) goto out; #endif /* device_is_linux_md_component and linux_md_component_* properties */ if (!update_info_linux_md_component (device)) goto out; /* device_is_linux_md and linux_md_* properties */ if (!update_info_linux_md (device)) goto out; /* drive_ata_smart_* properties */ if (!update_info_drive_ata_smart (device)) goto out; /* drive_can_spindown property */ if (!update_info_drive_can_spindown (device)) goto out; /* device_is_system_internal property */ if (!update_info_is_system_internal (device)) goto out; /* device_is_mounted, device_mount, device_mounted_by_uid */ if (!update_info_mount_state (device)) goto out; /* device_is_media_change_detected, device_is_media_change_detection_* properties */ if (!update_info_media_detection (device)) goto out; /* drive_adapter proprety */ if (!update_info_drive_adapter (device)) goto out; /* drive_ports property */ if (!update_info_drive_ports (device)) goto out; /* drive_similar_devices property */ if (!update_info_drive_similar_devices (device)) goto out; ret = TRUE; out: /* Now check if holders/ or slaves/ has changed since last update. We compute * the delta and do update_info() on each holder/slave that has been * added/removed. * * Note that this won't trigger an endless loop since we look at the diffs. * * We have to do this because the kernel doesn't generate any 'change' event * when slaves/ or holders/ change. This is unfortunate because we *need* such * a change event to update properties devices (for example: luks_holder). * * We do the update in idle because the update may depend on the device * currently being processed being added. */ cur_slaves_objpath = dup_list_from_ptrarray (device->priv->slaves_objpath); cur_holders_objpath = dup_list_from_ptrarray (device->priv->holders_objpath); old_slaves_objpath = g_list_sort (old_slaves_objpath, (GCompareFunc) g_strcmp0); old_holders_objpath = g_list_sort (old_holders_objpath, (GCompareFunc) g_strcmp0); cur_slaves_objpath = g_list_sort (cur_slaves_objpath, (GCompareFunc) g_strcmp0); cur_holders_objpath = g_list_sort (cur_holders_objpath, (GCompareFunc) g_strcmp0); diff_sorted_lists (old_slaves_objpath, cur_slaves_objpath, (GCompareFunc) g_strcmp0, &added_objpath, &removed_objpath); for (l = added_objpath; l != NULL; l = l->next) { const gchar *objpath2 = l->data; Device *device2; //g_debug ("### %s added slave %s", device->priv->object_path, objpath2); device2 = daemon_local_find_by_object_path (device->priv->daemon, objpath2); if (device2 != NULL) { update_info_in_idle (device2); } else { g_print ("**** NOTE: %s added non-existant slave %s\n", device->priv->object_path, objpath2); } } for (l = removed_objpath; l != NULL; l = l->next) { const gchar *objpath2 = l->data; Device *device2; //g_debug ("### %s removed slave %s", device->priv->object_path, objpath2); device2 = daemon_local_find_by_object_path (device->priv->daemon, objpath2); if (device2 != NULL) { update_info_in_idle (device2); } else { //g_debug ("### %s removed non-existant slave %s", device->priv->object_path, objpath2); } } g_list_free (added_objpath); g_list_free (removed_objpath); diff_sorted_lists (old_holders_objpath, cur_holders_objpath, (GCompareFunc) g_strcmp0, &added_objpath, &removed_objpath); for (l = added_objpath; l != NULL; l = l->next) { const gchar *objpath2 = l->data; Device *device2; //g_debug ("### %s added holder %s", device->priv->object_path, objpath2); device2 = daemon_local_find_by_object_path (device->priv->daemon, objpath2); if (device2 != NULL) { update_info_in_idle (device2); } else { g_print ("**** NOTE: %s added non-existant holder %s\n", device->priv->object_path, objpath2); } } for (l = removed_objpath; l != NULL; l = l->next) { const gchar *objpath2 = l->data; Device *device2; //g_debug ("### %s removed holder %s", device->priv->object_path, objpath2); device2 = daemon_local_find_by_object_path (device->priv->daemon, objpath2); if (device2 != NULL) { update_info_in_idle (device2); } else { //g_debug ("### %s removed non-existant holder %s", device->priv->object_path, objpath2); } } g_list_free (added_objpath); g_list_free (removed_objpath); g_list_foreach (old_slaves_objpath, (GFunc) g_free, NULL); g_list_free (old_slaves_objpath); g_list_foreach (old_holders_objpath, (GFunc) g_free, NULL); g_list_free (old_holders_objpath); g_list_foreach (cur_slaves_objpath, (GFunc) g_free, NULL); g_list_free (cur_slaves_objpath); g_list_foreach (cur_holders_objpath, (GFunc) g_free, NULL); g_list_free (cur_holders_objpath); PROFILE ("update_info(device=%s) end", device->priv->native_path); return ret; } /** * device_local_is_busy: * @device: A #Device. * @check_partitions: Whether to check if partitions is busy if @device is a partition table * @check_mounted: Whether to check if device has mounted file systems * @error: Either %NULL or a #GError to set to #ERROR_BUSY and an appropriate * message, e.g. "Device is busy" or "A partition on the device is busy" if the device is busy. * * Checks if @device is busy. * * Returns: %TRUE if the device or, if @check_partitions is %TRUE, a partition on the device is busy. */ static gboolean device_local_is_busy (Device *device, gboolean check_partitions, gboolean check_mounted, GError **error) { gboolean ret; ret = TRUE; /* busy if a job is pending */ if (device->priv->job != NULL) { g_set_error (error, ERROR, ERROR_BUSY, "A job is pending on %s", device->priv->device_file); goto out; } /* or if we're mounted */ if (check_mounted && device->priv->device_is_mounted) { g_set_error (error, ERROR, ERROR_BUSY, "%s is mounted", device->priv->device_file); goto out; } /* or if another block device is using/holding us (e.g. if holders/ is non-empty in sysfs) */ if (device->priv->holders_objpath->len > 0) { if (device->priv->device_is_linux_dmmp) { /* This is OK */ } else { g_set_error (error, ERROR, ERROR_BUSY, "One or more block devices are holding %s", device->priv->device_file); goto out; } } /* If we are an extended partition, we are also busy if one or more logical partitions are busy * even if @check_partitions is FALSE... This is because an extended partition only really is * a place holder. */ if (g_strcmp0 (device->priv->partition_scheme, "mbr") == 0 && device->priv->partition_type != NULL) { gint partition_type; partition_type = strtol (device->priv->partition_type, NULL, 0); if (partition_type == 0x05 || partition_type == 0x0f || partition_type == 0x85) { Device *drive_device; drive_device = daemon_local_find_by_object_path (device->priv->daemon, device->priv->partition_slave); if (device_local_logical_partitions_are_busy (drive_device)) { g_set_error (error, ERROR, ERROR_BUSY, "%s is an MS-DOS extended partition and one or more " "logical partitions are busy", device->priv->device_file); goto out; } } } /* if we are a partition table, we are busy if one of our partitions are busy */ if (check_partitions && device->priv->device_is_partition_table) { if (device_local_partitions_are_busy (device)) { g_set_error (error, ERROR, ERROR_BUSY, "One or more partitions are busy on %s", device->priv->device_file); goto out; } } ret = FALSE; out: return ret; } /* note: this only checks whether the actual partitions are busy; * caller will need to check the main device itself too */ static gboolean device_local_partitions_are_busy (Device *device) { gboolean ret; GList *l; GList *devices; ret = FALSE; devices = daemon_local_get_all_devices (device->priv->daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (d->priv->device_is_partition && d->priv->partition_slave != NULL && g_strcmp0 (d->priv->partition_slave, device->priv->object_path) == 0) { if (device_local_is_busy (d, FALSE, TRUE, NULL)) { ret = TRUE; break; } } } g_list_free (devices); return ret; } static gboolean device_local_logical_partitions_are_busy (Device *device) { gboolean ret; GList *l; GList *devices; ret = FALSE; devices = daemon_local_get_all_devices (device->priv->daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (d->priv->device_is_partition && d->priv->partition_slave != NULL && g_strcmp0 (d->priv->partition_slave, device->priv->object_path) == 0 && g_strcmp0 (d->priv->partition_scheme, "mbr") == 0 && d->priv->partition_number >= 5) { if (device_local_is_busy (d, FALSE, TRUE, NULL)) { ret = TRUE; break; } } } g_list_free (devices); return ret; } static gboolean device_has_logical_partitions (Device *device) { gboolean ret; GList *l; GList *devices; ret = FALSE; devices = daemon_local_get_all_devices (device->priv->daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (d->priv->device_is_partition && d->priv->partition_slave != NULL && g_strcmp0 (d->priv->partition_slave, device->priv->object_path) == 0 && g_strcmp0 (d->priv->partition_scheme, "mbr") == 0 && d->priv->partition_number >= 5) { ret = TRUE; } } g_list_free (devices); return ret; } void device_removed (Device *device) { guint n; device->priv->removed = TRUE; dbus_g_connection_unregister_g_object (device->priv->system_bus_connection, G_OBJECT (device)); g_assert (dbus_g_connection_lookup_g_object (device->priv->system_bus_connection, device->priv->object_path) == NULL); /* device is now removed; update all slaves and holders */ for (n = 0; n < device->priv->slaves_objpath->len; n++) { const gchar *objpath2 = ((gchar **) device->priv->slaves_objpath->pdata)[n]; Device *device2; device2 = daemon_local_find_by_object_path (device->priv->daemon, objpath2); if (device2 != NULL) { update_info (device2); } } for (n = 0; n < device->priv->holders_objpath->len; n++) { const gchar *objpath2 = ((gchar **) device->priv->holders_objpath->pdata)[n]; Device *device2; device2 = daemon_local_find_by_object_path (device->priv->daemon, objpath2); if (device2 != NULL) { update_info (device2); } } /* If the device is busy, we possibly need to clean up if the * device itself is busy. This includes * * - force unmounting the device and/or all it's partitions * * - tearing down a luks mapping if it's a cleartext device * backed by a crypted device * * but see force_removal() for details. * * This is the normally the path where the enclosing device is * removed. Compare with device_changed() for the * other path. */ force_removal (device, NULL, NULL); } Device * device_new (Daemon *daemon, GUdevDevice *d) { Device *device; const char *native_path; device = NULL; native_path = g_udev_device_get_sysfs_path (d); /* ignore ram devices */ if (g_str_has_prefix (native_path, "/sys/devices/virtual/block/ram")) goto out; PROFILE ("device_new(native_path=%s): start", native_path); device = DEVICE (g_object_new (TYPE_DEVICE, NULL)); device->priv->d = g_object_ref (d); device->priv->daemon = g_object_ref (daemon); device->priv->native_path = g_strdup (native_path); /* TODO: we might want to get this from udev or the kernel... to get the time when the device * was initially detected... as opposed to this value which is when the device was detected * by our daemon... but this will do for now... */ device->priv->device_detection_time = (guint64) time (NULL); PROFILE ("device_new(native_path=%s): update_info", native_path); if (!update_info (device)) { g_object_unref (device); device = NULL; goto out; } PROFILE ("device_new(native_path=%s): register_disks_device", native_path); if (!register_disks_device (DEVICE (device))) { g_object_unref (device); device = NULL; goto out; } /* if just added, update the smart data if applicable */ if (device->priv->drive_ata_smart_is_available) { PROFILE ("device_new(native_path=%s): refresh ATA SMART DATA", native_path); gchar *ata_smart_refresh_data_options[] = { NULL }; device_drive_ata_smart_refresh_data (device, ata_smart_refresh_data_options, NULL); } PROFILE ("device_new(native_path=%s): end", native_path); out: return device; } static void drain_pending_changes (Device *device, gboolean force_update) { gboolean emit_changed; emit_changed = FALSE; /* the update-in-idle is set up if, and only if, there are pending changes - so * we should emit a 'change' event only if it is set up */ if (device->priv->emit_changed_idle_id != 0) { g_source_remove (device->priv->emit_changed_idle_id); device->priv->emit_changed_idle_id = 0; emit_changed = TRUE; } if ((!device->priv->removed) && (emit_changed || force_update)) { if (device->priv->object_path != NULL) { g_print ("**** EMITTING CHANGED for %s\n", device->priv->native_path); g_signal_emit_by_name (device, "changed"); g_signal_emit_by_name (device->priv->daemon, "device-changed", device->priv->object_path); } } } static void emit_job_changed (Device *device) { drain_pending_changes (device, FALSE); if (!device->priv->removed) { g_print ("**** EMITTING JOB-CHANGED for %s\n", device->priv->native_path); g_signal_emit_by_name (device->priv->daemon, "device-job-changed", device->priv->object_path, device->priv->job_in_progress, device->priv->job_id, device->priv->job_initiated_by_uid, device->priv->job_is_cancellable, device->priv->job_percentage, NULL); g_signal_emit (device, signals[JOB_CHANGED_SIGNAL], 0, device->priv->job_in_progress, device->priv->job_id, device->priv->job_initiated_by_uid, device->priv->job_is_cancellable, device->priv->job_percentage); } } /* called by the daemon on the 'change' uevent */ gboolean device_changed (Device *device, GUdevDevice *d, gboolean synthesized) { gboolean keep_device; g_object_unref (device->priv->d); device->priv->d = g_object_ref (d); keep_device = update_info (device); /* this 'change' event might prompt us to remove the device */ if (!keep_device) goto out; /* no, it's good .. keep it.. and always force a 'change' signal if the event isn't synthesized */ drain_pending_changes (device, !synthesized); /* Check if media was removed. If so, we possibly need to clean up * if the device itself is busy. This includes * * - force unmounting the device * * - tearing down a luks mapping if it's a cleartext device * backed by a crypted device * * but see force_removal() for details. * * This is the normally the path where the media is removed but the enclosing * device is still present. Compare with device_removed() for * the other path. */ if (!device->priv->device_is_media_available) { GList *l; GList *devices; force_removal (device, NULL, NULL); /* check all partitions */ devices = daemon_local_get_all_devices (device->priv->daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (d->priv->device_is_partition && d->priv->partition_slave != NULL && strcmp (d->priv->partition_slave, device->priv->object_path) == 0) { force_removal (d, NULL, NULL); } } g_list_free (devices); } out: return keep_device; } /*--------------------------------------------------------------------------------------------------------------*/ const char * device_local_get_object_path (Device *device) { return device->priv->object_path; } const char * device_local_get_native_path (Device *device) { return device->priv->native_path; } dev_t device_local_get_dev (Device *device) { return device->priv->dev; } const char * device_local_get_device_file (Device *device) { return device->priv->device_file; } /*--------------------------------------------------------------------------------------------------------------*/ static gboolean throw_error (DBusGMethodInvocation *context, int error_code, const char *format, ...) { GError *error; va_list args; char *message; va_start (args, format); message = g_strdup_vprintf (format, args); va_end (args); if (context == NULL) { g_warning ("%s", message); return TRUE; } error = g_error_new (ERROR, error_code, "%s", message); dbus_g_method_return_error (context, error); g_error_free (error); g_free (message); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef void (*JobCompletedFunc) (DBusGMethodInvocation *context, Device *device, gboolean was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data); struct Job { char *job_id; Device *device; DBusGMethodInvocation *context; JobCompletedFunc job_completed_func; GPid pid; gpointer user_data; GDestroyNotify user_data_destroy_func; gboolean was_cancelled; gboolean udev_settle; int status; int stderr_fd; GIOChannel *error_channel; guint error_channel_source_id; GString *error_string; int stdout_fd; GIOChannel *out_channel; guint out_channel_source_id; GString *stdout_string; int stdout_string_cursor; char *stdin_str; char *stdin_cursor; int stdin_fd; GIOChannel *in_channel; guint in_channel_source_id; }; static void job_free (Job *job) { if (job->user_data_destroy_func != NULL) job->user_data_destroy_func (job->user_data); if (job->device != NULL) g_object_unref (job->device); if (job->stderr_fd >= 0) close (job->stderr_fd); if (job->stdout_fd >= 0) close (job->stdout_fd); if (job->stdin_fd >= 0) { close (job->stdin_fd); g_source_remove (job->in_channel_source_id); g_io_channel_unref (job->in_channel); } g_source_remove (job->error_channel_source_id); g_source_remove (job->out_channel_source_id); g_io_channel_unref (job->error_channel); g_io_channel_unref (job->out_channel); g_string_free (job->error_string, TRUE); /* scrub stdin (may contain secrets) */ if (job->stdin_str != NULL) { memset (job->stdin_str, '\0', strlen (job->stdin_str)); } g_string_free (job->stdout_string, TRUE); g_free (job->stdin_str); g_free (job->job_id); g_free (job); } static void job_complete (Job *job) { if (job->device != NULL && job->job_id != NULL) { job->device->priv->job_in_progress = FALSE; g_free (job->device->priv->job_id); job->device->priv->job_id = NULL; job->device->priv->job_initiated_by_uid = 0; job->device->priv->job_is_cancellable = FALSE; job->device->priv->job_percentage = -1.0; job->device->priv->job = NULL; } job->job_completed_func (job->context, job->device, job->was_cancelled, job->status, job->error_string->str, job->stdout_string->str, job->user_data); if (job->device != NULL && job->job_id != NULL) { emit_job_changed (job->device); } job_free (job); } static void job_udevadm_settle_child_cb (GPid pid, gint status, gpointer user_data) { Job *job = user_data; job_complete (job); } static void job_child_watch_cb (GPid pid, int status, gpointer user_data) { char *buf; gsize buf_size; Job *job = user_data; if (g_io_channel_read_to_end (job->error_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) { g_string_append_len (job->error_string, buf, buf_size); g_free (buf); } if (g_io_channel_read_to_end (job->out_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) { g_string_append_len (job->stdout_string, buf, buf_size); g_free (buf); } PROFILE ("job finish (id=%s, pid=%i, device=%s)", job->job_id, job->pid, job->device ? job->device->priv->device_file : "none"); g_print ("helper(pid %5d): completed with exit code %d\n", job->pid, WEXITSTATUS (status)); job->status = status; /* if requested, run 'udevadm settle' on success */ if (!job->was_cancelled && WIFEXITED (status) && WEXITSTATUS (status) == 0 && job->udev_settle) { GError *error; gchar *argv[] = { "udevadm", "settle", "--quiet", NULL }; GPid udevadm_pid; error = NULL; if (!g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &udevadm_pid, &error)) { g_warning ("Error running 'udevadm settle --quiet': %s", error->message); g_error_free (error); job_complete (job); } else { g_child_watch_add (udevadm_pid, job_udevadm_settle_child_cb, job); } } else { /* return immediately on error */ job_complete (job); } } static void job_cancel (Device *device) { g_return_if_fail (device->priv->job != NULL); device->priv->job->was_cancelled = TRUE; /* TODO: maybe wait and user a bigger hammer? (SIGKILL) */ kill (device->priv->job->pid, SIGTERM); } static gboolean job_read_error (GIOChannel *channel, GIOCondition condition, gpointer user_data) { char buf[1024]; gsize bytes_read; Job *job = user_data; g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); g_string_append_len (job->error_string, buf, bytes_read); return TRUE; } static gboolean job_write_in (GIOChannel *channel, GIOCondition condition, gpointer user_data) { Job *job = user_data; gsize bytes_written; if (job->stdin_cursor == NULL || job->stdin_cursor[0] == '\0') { /* nothing left to write; remove ourselves */ return FALSE; } g_io_channel_write_chars (channel, job->stdin_cursor, strlen (job->stdin_cursor), &bytes_written, NULL); g_io_channel_flush (channel, NULL); job->stdin_cursor += bytes_written; return TRUE; } static gboolean job_read_out (GIOChannel *channel, GIOCondition condition, gpointer user_data) { char *s; char *line; char buf[1024]; gsize bytes_read; Job *job = user_data; g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); g_string_append_len (job->stdout_string, buf, bytes_read); do { gsize line_len; s = strstr (job->stdout_string->str + job->stdout_string_cursor, "\n"); if (s == NULL) break; line_len = s - (job->stdout_string->str + job->stdout_string_cursor); line = g_strndup (job->stdout_string->str + job->stdout_string_cursor, line_len); job->stdout_string_cursor += line_len + 1; //g_print ("helper(pid %5d): '%s'\n", job->pid, line); if (strlen (line) < 256) { double cur_percentage; ; if (sscanf (line, "udisks-helper-progress: %lg", &cur_percentage) == 1) { if (job->device != NULL && job->job_id != NULL) { job->device->priv->job_percentage = cur_percentage; emit_job_changed (job->device); } } } g_free (line); } while (TRUE); return TRUE; } static void job_local_start (Device *device, const char *job_id) { if (device->priv->job != NULL || device->priv->job_in_progress) { g_warning ("There is already a job running"); goto out; } g_free (device->priv->job_id); device->priv->job_id = g_strdup (job_id); device->priv->job_initiated_by_uid = 0; device->priv->job_in_progress = TRUE; device->priv->job_is_cancellable = FALSE; device->priv->job_percentage = -1.0; emit_job_changed (device); out: ; } static void job_local_end (Device *device) { if (!device->priv->job_in_progress || device->priv->job != NULL) { g_warning ("There is no job running"); goto out; } device->priv->job_in_progress = FALSE; g_free (device->priv->job_id); device->priv->job_id = NULL; device->priv->job_initiated_by_uid = 0; device->priv->job_is_cancellable = FALSE; device->priv->job_percentage = -1.0; emit_job_changed (device); out: ; } static gboolean job_new (DBusGMethodInvocation *context, const char *job_id, gboolean is_cancellable, Device *device, char **argv, const char *stdin_str, JobCompletedFunc job_completed_func, gboolean udev_settle, /* if TRUE, runs udevsettle before returning if the command succeeded */ gpointer user_data, GDestroyNotify user_data_destroy_func) { Job *job; gboolean ret; GError *error; ret = FALSE; job = NULL; PROFILE ("job_new(id=%s, device=%s): start", job_id ? job_id : argv[0], device ? device->priv->device_file : "none"); if (device != NULL) { if (device->priv->job != NULL || device->priv->job_in_progress) { throw_error (context, ERROR_BUSY, "There is already a job running"); goto out; } } job = g_new0 (Job, 1); job->context = context; job->device = device != NULL ? DEVICE (g_object_ref (device)) : NULL; job->job_completed_func = job_completed_func; job->user_data = user_data; job->user_data_destroy_func = user_data_destroy_func; job->stderr_fd = -1; job->stdout_fd = -1; job->stdin_fd = -1; job->stdin_str = g_strdup (stdin_str); job->stdin_cursor = job->stdin_str; job->stdout_string = g_string_sized_new (1024); job->job_id = g_strdup (job_id); job->udev_settle = udev_settle; if (device != NULL && job_id != NULL) { g_free (job->device->priv->job_id); job->device->priv->job_id = g_strdup (job_id); } error = NULL; if (!g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &(job->pid), stdin_str != NULL ? &(job->stdin_fd) : NULL, &(job->stdout_fd), &(job->stderr_fd), &error)) { throw_error (context, ERROR_FAILED, "Error starting job: %s", error->message); g_error_free (error); goto out; } g_child_watch_add (job->pid, job_child_watch_cb, job); job->error_string = g_string_new (""); job->error_channel = g_io_channel_unix_new (job->stderr_fd); error = NULL; if (g_io_channel_set_flags (job->error_channel, G_IO_FLAG_NONBLOCK, &error) != G_IO_STATUS_NORMAL) { g_warning ("Cannon set stderr fd for child to be non blocking: %s", error->message); g_error_free (error); } job->error_channel_source_id = g_io_add_watch (job->error_channel, G_IO_IN, job_read_error, job); job->out_channel = g_io_channel_unix_new (job->stdout_fd); error = NULL; if (g_io_channel_set_flags (job->out_channel, G_IO_FLAG_NONBLOCK, &error) != G_IO_STATUS_NORMAL) { g_warning ("Cannon set stdout fd for child to be non blocking: %s", error->message); g_error_free (error); } job->out_channel_source_id = g_io_add_watch (job->out_channel, G_IO_IN, job_read_out, job); if (job->stdin_fd >= 0) { job->in_channel = g_io_channel_unix_new (job->stdin_fd); if (g_io_channel_set_flags (job->in_channel, G_IO_FLAG_NONBLOCK, &error) != G_IO_STATUS_NORMAL) { g_warning ("Cannon set stdin fd for child to be non blocking: %s", error->message); g_error_free (error); } job->in_channel_source_id = g_io_add_watch (job->in_channel, G_IO_OUT, job_write_in, job); } ret = TRUE; if (device != NULL && job_id != NULL) { device->priv->job_in_progress = TRUE; device->priv->job_is_cancellable = is_cancellable; device->priv->job_percentage = -1.0; device->priv->job_initiated_by_uid = 0; if (context != NULL) { daemon_local_get_uid (device->priv->daemon, &(device->priv->job_initiated_by_uid), context); } device->priv->job = job; emit_job_changed (device); } if (device != NULL) { g_print ("helper(pid %5d): launched job %s on %s\n", job->pid, argv[0], device->priv->device_file); } else { g_print ("helper(pid %5d): launched job %s on daemon\n", job->pid, argv[0]); } out: if (!ret && job != NULL) job_free (job); PROFILE ("job_new(id=%s, device=%s): end", job_id, device ? device->priv->device_file : "none"); return ret; } /*--------------------------------------------------------------------------------------------------------------*/ /* exported methods */ typedef struct { char *mount_point; gboolean remove_dir_on_unmount; } MountData; static MountData * filesystem_mount_data_new (const char *mount_point, gboolean remove_dir_on_unmount) { MountData *data; data = g_new0 (MountData, 1); data->mount_point = g_strdup (mount_point); data->remove_dir_on_unmount = remove_dir_on_unmount; return data; } static void filesystem_mount_data_free (MountData *data) { g_free (data->mount_point); g_free (data); } static gboolean is_device_in_fstab (Device *device, char **out_mount_point) { GList *l; GList *mount_points; gboolean ret; ret = FALSE; mount_points = g_unix_mount_points_get (NULL); for (l = mount_points; l != NULL; l = l->next) { GUnixMountPoint *mount_point = l->data; char canonical_device_file[PATH_MAX]; char *device_path; char *s; device_path = g_strdup (g_unix_mount_point_get_device_path (mount_point)); /* get the canonical path; e.g. resolve * * /dev/disk/by-path/pci-0000:00:1d.7-usb-0:3:1.0-scsi-0:0:0:3-part5 * UUID=78af6939-adac-4ea5-a2a8-576e141da010 * LABEL=foobar * * into something like /dev/sde5. */ if (g_str_has_prefix (device_path, "UUID=")) { s = device_path; device_path = g_strdup_printf ("/dev/disk/by-uuid/%s", device_path + 5); g_free (s); } else if (g_str_has_prefix (device_path, "LABEL=")) { s = device_path; device_path = g_strdup_printf ("/dev/disk/by-label/%s", device_path + 6); g_free (s); } if (realpath (device_path, canonical_device_file) == NULL) { g_free (device_path); continue; } g_free (device_path); if (strcmp (device->priv->device_file, canonical_device_file) == 0) { ret = TRUE; if (out_mount_point != NULL) *out_mount_point = g_strdup (g_unix_mount_point_get_mount_path (mount_point)); break; } } g_list_foreach (mount_points, (GFunc) g_unix_mount_point_free, NULL); g_list_free (mount_points); return ret; } typedef struct { const char *fstype; const char * const *defaults; const char * const *allow; const char * const *allow_uid_self; const char * const *allow_gid_self; } FSMountOptions; /* ---------------------- vfat -------------------- */ static const char *vfat_defaults[] = { "uid=", "gid=", "shortname=mixed", "dmask=0077", "utf8=1", "showexec", NULL }; static const char *vfat_allow[] = { "flush", "utf8=", "shortname=", "umask=", "dmask=", "fmask=", "codepage=", "iocharset=", "usefree", "showexec", NULL }; static const char *vfat_allow_uid_self[] = { "uid=", NULL }; static const char *vfat_allow_gid_self[] = { "gid=", NULL }; /* ---------------------- ntfs -------------------- */ /* this is assuming that ntfs-3g is used */ static const char *ntfs_defaults[] = { "uid=", "gid=", "dmask=0077", "fmask=0177", NULL }; static const char *ntfs_allow[] = { "umask=", "dmask=", "fmask=", NULL }; static const char *ntfs_allow_uid_self[] = { "uid=", NULL }; static const char *ntfs_allow_gid_self[] = { "gid=", NULL }; /* ---------------------- iso9660 -------------------- */ static const char *iso9660_defaults[] = { "uid=", "gid=", "iocharset=utf8", "mode=0400", "dmode=0500", NULL }; static const char *iso9660_allow[] = { "norock", "nojoliet", "iocharset=", "mode=", "dmode=", NULL }; static const char *iso9660_allow_uid_self[] = { "uid=", NULL }; static const char *iso9660_allow_gid_self[] = { "gid=", NULL }; /* ---------------------- udf -------------------- */ static const char *udf_defaults[] = { "uid=", "gid=", "iocharset=utf8", "umask=0077", NULL }; static const char *udf_allow[] = { "iocharset=", "umask=", "mode=", "dmode=", NULL }; static const char *udf_allow_uid_self[] = { "uid=", NULL }; static const char *udf_allow_gid_self[] = { "gid=", NULL }; /* ------------------------------------------------ */ /* TODO: support context= */ static const char *any_allow[] = { "exec", "noexec", "nodev", "nosuid", "atime", "noatime", "nodiratime", "ro", "rw", "sync", "dirsync", NULL }; static const FSMountOptions fs_mount_options[] = { { "vfat", vfat_defaults, vfat_allow, vfat_allow_uid_self, vfat_allow_gid_self }, { "ntfs", ntfs_defaults, ntfs_allow, ntfs_allow_uid_self, ntfs_allow_gid_self }, { "iso9660", iso9660_defaults, iso9660_allow, iso9660_allow_uid_self, iso9660_allow_gid_self }, { "udf", udf_defaults, udf_allow, udf_allow_uid_self, udf_allow_gid_self }, }; static const gchar *well_known_filesystems[] = { "btrfs", "ext2", "ext3", "ext4", "udf", "iso9660", "xfs", "jfs", "nilfs", "reiserfs", "reiser4", "msdos", "umsdos", "vfat", "exfat", "ntfs", NULL, }; /* ------------------------------------------------ */ static int num_fs_mount_options = sizeof(fs_mount_options) / sizeof(FSMountOptions); static const FSMountOptions * find_mount_options_for_fs (const char *fstype) { int n; const FSMountOptions *fsmo; for (n = 0; n < num_fs_mount_options; n++) { fsmo = fs_mount_options + n; if (strcmp (fsmo->fstype, fstype) == 0) goto out; } fsmo = NULL; out: return fsmo; } static gid_t find_primary_gid (uid_t uid) { struct passwd *pw; gid_t gid; gid = (gid_t) - 1; pw = getpwuid (uid); if (pw == NULL) { g_warning ("Couldn't look up uid %d: %m", uid); goto out; } gid = pw->pw_gid; out: return gid; } static gboolean is_uid_in_gid (uid_t uid, gid_t gid) { gboolean ret; struct passwd *pw; static gid_t supplementary_groups[128]; int num_supplementary_groups = 128; int n; /* TODO: use some #define instead of harcoding some random number like 128 */ ret = FALSE; pw = getpwuid (uid); if (pw == NULL) { g_warning ("Couldn't look up uid %d: %m", uid); goto out; } if (pw->pw_gid == gid) { ret = TRUE; goto out; } if (getgrouplist (pw->pw_name, pw->pw_gid, supplementary_groups, &num_supplementary_groups) < 0) { g_warning ("Couldn't find supplementary groups for uid %d: %m", uid); goto out; } for (n = 0; n < num_supplementary_groups; n++) { if (supplementary_groups[n] == gid) { ret = TRUE; goto out; } } out: return ret; } static gboolean is_mount_option_allowed (const FSMountOptions *fsmo, const char *option, uid_t caller_uid) { int n; char *endp; uid_t uid; gid_t gid; gboolean allowed; const char *ep; gsize ep_len; allowed = FALSE; /* first run through the allowed mount options */ if (fsmo != NULL) { for (n = 0; fsmo->allow != NULL && fsmo->allow[n] != NULL; n++) { ep = strstr (fsmo->allow[n], "="); if (ep != NULL && ep[1] == '\0') { ep_len = ep - fsmo->allow[n] + 1; if (strncmp (fsmo->allow[n], option, ep_len) == 0) { allowed = TRUE; goto out; } } else { if (strcmp (fsmo->allow[n], option) == 0) { allowed = TRUE; goto out; } } } } for (n = 0; any_allow[n] != NULL; n++) { ep = strstr (any_allow[n], "="); if (ep != NULL && ep[1] == '\0') { ep_len = ep - any_allow[n] + 1; if (strncmp (any_allow[n], option, ep_len) == 0) { allowed = TRUE; goto out; } } else { if (strcmp (any_allow[n], option) == 0) { allowed = TRUE; goto out; } } } /* .. then check for mount options where the caller is allowed to pass * in his own uid */ if (fsmo != NULL) { for (n = 0; fsmo->allow_uid_self != NULL && fsmo->allow_uid_self[n] != NULL; n++) { const char *r_mount_option = fsmo->allow_uid_self[n]; if (g_str_has_prefix (option, r_mount_option)) { uid = strtol (option + strlen (r_mount_option), &endp, 10); if (*endp != '\0') continue; if (uid == caller_uid) { allowed = TRUE; goto out; } } } } /* .. ditto for gid */ if (fsmo != NULL) { for (n = 0; fsmo->allow_gid_self != NULL && fsmo->allow_gid_self[n] != NULL; n++) { const char *r_mount_option = fsmo->allow_gid_self[n]; if (g_str_has_prefix (option, r_mount_option)) { gid = strtol (option + strlen (r_mount_option), &endp, 10); if (*endp != '\0') continue; if (is_uid_in_gid (caller_uid, gid)) { allowed = TRUE; goto out; } } } } out: return allowed; } static char ** prepend_default_mount_options (Device *device, const FSMountOptions *fsmo, uid_t caller_uid, char **given_options) { GPtrArray *options; int n; char *s; gid_t gid; /* static default options from FSMountOptions */ options = g_ptr_array_new (); if (fsmo != NULL) { for (n = 0; fsmo->defaults != NULL && fsmo->defaults[n] != NULL; n++) { const char *option = fsmo->defaults[n]; if (strcmp (option, "uid=") == 0) { s = g_strdup_printf ("uid=%d", caller_uid); g_ptr_array_add (options, s); } else if (strcmp (option, "gid=") == 0) { gid = find_primary_gid (caller_uid); if (gid != (gid_t) - 1) { s = g_strdup_printf ("gid=%d", gid); g_ptr_array_add (options, s); } } else { g_ptr_array_add (options, g_strdup (option)); } } } /* dynamic default options */ /* some broken DVDs come with 0400 directory permissions, making them * unreadable; overwrite readonly UDF media with a 0500 dmode. */ if (g_strcmp0 (device->priv->id_type, "udf") == 0 && device->priv->device_is_optical_disc && device->priv->drive_media != NULL && strstr(device->priv->drive_media, "_rw") == NULL && strstr(device->priv->drive_media, "_ram") == NULL) { g_ptr_array_add (options, g_strdup("dmode=0500")); } /* user supplied options */ for (n = 0; given_options[n] != NULL; n++) { g_ptr_array_add (options, g_strdup (given_options[n])); } g_ptr_array_add (options, NULL); return (char **) g_ptr_array_free (options, FALSE); } static void unlock_cd_tray (Device *device) { /* Unlock CD tray to keep the hardware eject button working */ if (g_udev_device_has_property (device->priv->d, "ID_CDROM")) { gint fd; g_print ("**** Unlocking CD-ROM door for %s\n", device->priv->device_file); fd = open (device->priv->device_file, O_RDONLY); if (fd > 0) { if (ioctl (fd, CDROM_LOCKDOOR, 0) != 0) g_warning ("Could not unlock CD-ROM door: %s", strerror (errno)); close (fd); } else { g_warning ("Could not open CD-ROM device: %s", strerror (errno)); } } } static void filesystem_mount_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { MountData *data = (MountData *) user_data; uid_t uid; daemon_local_get_uid (device->priv->daemon, &uid, context); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { update_info (device); drain_pending_changes (device, FALSE); /* If the kernel and device support sending EJECT_REQUEST change uevents * and we use in-kernel polling, keep the door locked, as udev calls * eject on pressing the button. Otherwise unlock it, to keep the * hardware button working without userspace support */ if (!device->priv->using_in_kernel_polling) unlock_cd_tray (device); dbus_g_method_return (context, data->mount_point); } else { if (data->remove_dir_on_unmount) { mount_file_remove (device->priv->device_file, data->mount_point); if (g_rmdir (data->mount_point) != 0) { g_warning ("Error removing dir in late mount error path: %m"); } } if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else if (WEXITSTATUS (status) == 32) { throw_error (context, ERROR_FILESYSTEM_DRIVER_MISSING, "Error mounting: %s", stderr); } else { throw_error (context, ERROR_FAILED, "Error mounting: mount exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static gboolean is_in_filesystem_file (const gchar *filesystems_file, const gchar *fstype) { gchar *filesystems; GError *error; gboolean ret; gchar **lines; guint n; ret = FALSE; filesystems = NULL; lines = NULL; error = NULL; if (!g_file_get_contents (filesystems_file, &filesystems, NULL, /* gsize *out_length */ &error)) { g_warning ("Error reading %s: %s (%s %d)", filesystems_file, error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); goto out; } lines = g_strsplit (filesystems, "\n", -1); for (n = 0; lines != NULL && lines[n] != NULL && !ret; n++) { gchar **tokens; gint num_tokens; g_strdelimit (lines[n], " \t", ' '); g_strstrip (lines[n]); tokens = g_strsplit (lines[n], " ", -1); num_tokens = g_strv_length (tokens); if (num_tokens == 1 && g_strcmp0 (tokens[0], fstype) == 0) { ret = TRUE; } g_strfreev (tokens); } out: g_strfreev (lines); g_free (filesystems); return ret; } static gboolean is_well_known_filesystem (const gchar *fstype) { gboolean ret; guint n; ret = FALSE; for (n = 0; well_known_filesystems[n] != NULL; n++) { if (g_strcmp0 (well_known_filesystems[n], fstype) == 0) { ret = TRUE; goto out; } } out: return ret; } /* this is not a very efficient implementation but it's very rarely * called so no real point in optimizing it... */ static gboolean is_allowed_filesystem (const gchar *fstype) { return is_well_known_filesystem (fstype) || is_in_filesystem_file ("/proc/filesystems", fstype) || is_in_filesystem_file ("/etc/filesystems", fstype); } static void device_filesystem_mount_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *filesystem_type = user_data_elements[0]; gchar **given_options = user_data_elements[1]; int n; GString *s; char *argv[10]; char *mount_point; char *fstype; char *mount_options; GError *error; uid_t caller_uid; gboolean remove_dir_on_unmount; const FSMountOptions *fsmo; char **options; char uid_buf[32]; fstype = NULL; options = NULL; mount_options = NULL; mount_point = NULL; remove_dir_on_unmount = FALSE; error = NULL; /* If the user requests the filesystem type, error out unless the * filesystem type is * * - well-known [1]; or * - in the /proc/filesystems file; or * - in the /etc/filesystems file * * in that order. We do this because mount(8) on Linux allows * loading any arbitrary kernel module (when invoked as root) by * passing something appropriate to the -t option. So we have to * validate whatever we pass... * * See https://bugs.freedesktop.org/show_bug.cgi?id=32232 for more * details. * * [1] : since /etc/filesystems may be horribly out of date and not * contain e.g. ext4 */ if (filesystem_type != NULL && strlen (filesystem_type) > 0 && g_strcmp0 (filesystem_type, "auto") != 0) { if (!is_allowed_filesystem (filesystem_type)) { throw_error (context, ERROR_FAILED, "Requested filesystem type is neither well-known nor " "in /proc/filesystems nor in /etc/filesystems"); goto out; } } daemon_local_get_uid (device->priv->daemon, &caller_uid, context); if (device->priv->id_usage == NULL || strcmp (device->priv->id_usage, "filesystem") != 0) { if ((g_strcmp0 (filesystem_type, "auto") == 0 || g_strcmp0 (filesystem_type, "") == 0) && device->priv->id_usage == NULL) { /* if we don't know the usage of the device and 'auto' or '' is passed for fstype * then just try that.. this is to make, for example, mounting /dev/fd0 work (we * don't probe such devices for filesystems in udev) */ } else { throw_error (context, ERROR_FAILED, "Not a mountable file system"); goto out; } } if (device_local_is_busy (device, FALSE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } /* Check if the device is referenced in /etc/fstab; if so, attempt to * mount the device as the user */ if (is_device_in_fstab (device, &mount_point)) { n = 0; snprintf (uid_buf, sizeof uid_buf, "%d", caller_uid); argv[n++] = "udisks-helper-fstab-mounter"; argv[n++] = "mount"; argv[n++] = device->priv->device_file; argv[n++] = uid_buf; argv[n++] = NULL; goto run_job; } /* set the fstype */ fstype = NULL; if (strlen (filesystem_type) == 0) { if (device->priv->id_type != NULL && strlen (device->priv->id_type) > 0) { fstype = g_strdup (device->priv->id_type); } else { fstype = g_strdup ("auto"); } } else { fstype = g_strdup (filesystem_type); } fsmo = find_mount_options_for_fs (fstype); /* always prepend some reasonable default mount options; these are * chosen here; the user can override them if he wants to */ options = prepend_default_mount_options (device, fsmo, caller_uid, given_options); /* validate mount options and check for authorizations */ s = g_string_new ("uhelper=udisks,nodev,nosuid"); for (n = 0; options[n] != NULL; n++) { const char *option = options[n]; /* avoid attacks like passing "shortname=lower,uid=0" as a single mount option */ if (strstr (option, ",") != NULL) { throw_error (context, ERROR_INVALID_OPTION, "Malformed mount option: ", option); g_string_free (s, TRUE); goto out; } /* first check if the mount option is allowed */ if (!is_mount_option_allowed (fsmo, option, caller_uid)) { throw_error (context, ERROR_INVALID_OPTION, "Mount option %s is not allowed", option); g_string_free (s, TRUE); goto out; } g_string_append_c (s, ','); g_string_append (s, option); } mount_options = g_string_free (s, FALSE); g_print ("**** USING MOUNT OPTIONS '%s' FOR DEVICE %s\n", mount_options, device->priv->device_file); if (device->priv->device_is_mounted) { throw_error (context, ERROR_FAILED, "Device is already mounted"); goto out; } /* Determine the mount point to use. * * TODO: use characteristics of the drive such as the name, connection etc. * to get better names (/media/disk is kinda lame). */ if (device->priv->id_label != NULL && strlen (device->priv->id_label) > 0) { GString * s; s = g_string_new ("/media/"); for (n = 0; device->priv->id_label[n] != '\0'; n++) { gint c = device->priv->id_label[n]; if (c == '/') g_string_append_c (s, '_'); else g_string_append_c (s, c); } mount_point = g_string_free (s, FALSE); } else if (device->priv->id_uuid != NULL && strlen (device->priv->id_uuid) > 0) { GString * s; s = g_string_new ("/media/"); for (n = 0; device->priv->id_uuid[n] != '\0'; n++) { gint c = device->priv->id_uuid[n]; if (c == '/') g_string_append_c (s, '_'); else g_string_append_c (s, c); } mount_point = g_string_free (s, FALSE); } else { mount_point = g_strdup ("/media/disk"); } try_another_mount_point: /* ... then uniqify the mount point and mkdir it */ if (g_file_test (mount_point, G_FILE_TEST_EXISTS)) { char *s = mount_point; /* TODO: append numbers instead of _, __ and so on */ mount_point = g_strdup_printf ("%s_", mount_point); g_free (s); goto try_another_mount_point; } remove_dir_on_unmount = TRUE; if (g_mkdir (mount_point, 0700) != 0) { throw_error (context, ERROR_FAILED, "Error creating moint point: %m"); goto out; } /* now that we have a mount point, immediately add it to the * /var/lib/udisks/mtab file. * * If mounting fails we'll clean it up in filesystem_mount_completed_cb. If it * hangs we'll clean it up the next time we start up. */ mount_file_add (device->priv->device_file, mount_point, caller_uid, remove_dir_on_unmount); n = 0; argv[n++] = "mount"; argv[n++] = "-t"; argv[n++] = fstype; argv[n++] = "-o"; argv[n++] = mount_options; argv[n++] = device->priv->device_file; argv[n++] = mount_point; argv[n++] = NULL; run_job: error = NULL; if (!job_new (context, "FilesystemMount", FALSE, device, argv, NULL, filesystem_mount_completed_cb, FALSE, filesystem_mount_data_new (mount_point, remove_dir_on_unmount), (GDestroyNotify) filesystem_mount_data_free)) { if (remove_dir_on_unmount) { mount_file_remove (device->priv->device_file, mount_point); if (g_rmdir (mount_point) != 0) { g_warning ("Error removing dir in early mount error path: %m"); } } goto out; } out: g_free (fstype); g_free (mount_options); g_free (mount_point); g_strfreev (options); } gboolean device_filesystem_mount (Device *device, const char *filesystem_type, char **given_options, DBusGMethodInvocation *context) { const gchar *action_id; gboolean auth_no_user_interaction; gchar **options_to_pass; guint n; guint m; if (is_device_in_fstab (device, NULL)) { action_id = NULL; } else { if (device->priv->device_is_system_internal) action_id = "org.freedesktop.udisks.filesystem-mount-system-internal"; else action_id = "org.freedesktop.udisks.filesystem-mount"; } auth_no_user_interaction = FALSE; options_to_pass = g_strdupv (given_options); for (n = 0; options_to_pass != NULL && options_to_pass[n] != NULL; n++) { if (g_strcmp0 (options_to_pass[n], "auth_no_user_interaction") == 0) { auth_no_user_interaction = TRUE; g_free (options_to_pass[n]); for (m = n; options_to_pass[m + 1] != NULL; m++) options_to_pass[m] = options_to_pass[m + 1]; options_to_pass[m] = NULL; break; } } daemon_local_check_auth (device->priv->daemon, device, action_id, "FilesystemMount", !auth_no_user_interaction, device_filesystem_mount_authorized_cb, context, 2, g_strdup (filesystem_type), g_free, options_to_pass, g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void filesystem_unmount_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* update_info_mount_state() will update the mounts file and clean up the directory if needed */ update_info (device); if (context != NULL) dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { if (strstr (stderr, "device is busy") != NULL) { throw_error (context, ERROR_BUSY, "Cannot unmount because file system on device is busy"); } else { throw_error (context, ERROR_FAILED, "Error unmounting: umount exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } } static void device_filesystem_unmount_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gchar **options = user_data_elements[0]; int n; char *argv[16]; gboolean force_unmount; char *mount_path; uid_t uid; gchar uid_buf[32]; mount_path = NULL; if (!device->priv->device_is_mounted || device->priv->device_mount_paths->len == 0) { throw_error (context, ERROR_FAILED, "Device is not mounted"); goto out; } force_unmount = FALSE; for (n = 0; options[n] != NULL; n++) { char *option = options[n]; if (strcmp ("force", option) == 0) { force_unmount = TRUE; } else { throw_error (context, ERROR_INVALID_OPTION, "Unknown option %s", option); goto out; } } daemon_local_get_uid (device->priv->daemon, &uid, context); g_snprintf (uid_buf, sizeof uid_buf, "%d", uid); if (!mount_file_has_device (device->priv->device_file, NULL, NULL)) { if (is_device_in_fstab (device, &mount_path)) { n = 0; argv[n++] = "udisks-helper-fstab-mounter"; if (force_unmount) argv[n++] = "force_unmount"; else argv[n++] = "unmount"; argv[n++] = device->priv->device_file; argv[n++] = uid_buf; argv[n++] = NULL; goto run_job; } /* otherwise the user will have the .unmount-others authorization per the logic in * device_filesystem_unmount() */ } mount_path = g_strdup (((gchar **) device->priv->device_mount_paths->pdata)[0]); n = 0; argv[n++] = "umount"; if (force_unmount) { /* on Linux we currently only have lazy unmount to emulate this */ argv[n++] = "-l"; } argv[n++] = mount_path; argv[n++] = NULL; run_job: if (!job_new (context, "FilesystemUnmount", FALSE, device, argv, NULL, filesystem_unmount_completed_cb, FALSE, g_strdup (mount_path), g_free)) { goto out; } out: g_free (mount_path); } gboolean device_filesystem_unmount (Device *device, char **options, DBusGMethodInvocation *context) { const gchar *action_id; uid_t uid_of_mount; if (!device->priv->device_is_mounted || device->priv->device_mount_paths->len == 0) { throw_error (context, ERROR_FAILED, "Device is not mounted"); goto out; } /* if device is in /etc/fstab, then we'll run unmount as the calling user */ action_id = NULL; if (!mount_file_has_device (device->priv->device_file, &uid_of_mount, NULL)) { if (context != NULL && !is_device_in_fstab (device, NULL)) { action_id = "org.freedesktop.udisks.filesystem-unmount-others"; } } else { uid_t uid; daemon_local_get_uid (device->priv->daemon, &uid, context); if (context != NULL && uid_of_mount != uid) { action_id = "org.freedesktop.udisks.filesystem-unmount-others"; } } daemon_local_check_auth (device->priv->daemon, device, action_id, "FilesystemUnmount", TRUE, device_filesystem_unmount_authorized_cb, context, 1, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static uid_t get_uid_for_pid (pid_t pid) { uid_t ret; char proc_name[32]; struct stat statbuf; ret = 0; snprintf (proc_name, sizeof(proc_name), "/proc/%d/stat", pid); if (stat (proc_name, &statbuf) == 0) { ret = statbuf.st_uid; } return ret; } static char * get_command_line_for_pid (pid_t pid) { char proc_name[32]; char *buf; gsize len; char *ret; unsigned int n; ret = NULL; snprintf (proc_name, sizeof(proc_name), "/proc/%d/cmdline", pid); if (g_file_get_contents (proc_name, &buf, &len, NULL)) { for (n = 0; n < len; n++) { if (buf[n] == '\0') buf[n] = ' '; } g_strstrip (buf); ret = buf; } return ret; } static void lsof_parse (const char *stdout, GPtrArray *processes) { int n; char **tokens; tokens = g_strsplit (stdout, "\n", 0); for (n = 0; tokens[n] != NULL; n++) { pid_t pid; uid_t uid; char *command_line; GValue elem = { 0 }; if (strlen (tokens[n]) == 0) continue; pid = strtol (tokens[n], NULL, 0); uid = get_uid_for_pid (pid); command_line = get_command_line_for_pid (pid); g_value_init (&elem, LSOF_DATA_STRUCT_TYPE); g_value_take_boxed (&elem, dbus_g_type_specialized_construct (LSOF_DATA_STRUCT_TYPE)); dbus_g_type_struct_set (&elem, 0, pid, 1, uid, 2, command_line != NULL ? command_line : "", G_MAXUINT); g_ptr_array_add (processes, g_value_get_boxed (&elem)); g_free (command_line); } g_strfreev (tokens); } static void filesystem_list_open_files_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if ((WEXITSTATUS (status) == 0 || WEXITSTATUS (status) == 1) && !job_was_cancelled) { GPtrArray *processes; processes = g_ptr_array_new (); lsof_parse (stdout, processes); dbus_g_method_return (context, processes); g_ptr_array_foreach (processes, (GFunc) g_value_array_free, NULL); g_ptr_array_free (processes, TRUE); } else { throw_error (context, ERROR_FAILED, "Error listing open files: lsof exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } static void device_filesystem_list_open_files_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { int n; char *argv[16]; if (!device->priv->device_is_mounted || device->priv->device_mount_paths->len == 0) { throw_error (context, ERROR_FAILED, "Device is not mounted"); goto out; } n = 0; argv[n++] = "lsof"; argv[n++] = "-t"; argv[n++] = ((gchar **) device->priv->device_mount_paths->pdata)[0]; argv[n++] = NULL; if (!job_new (context, NULL, /* don't run this as a job */ FALSE, device, argv, NULL, filesystem_list_open_files_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean device_filesystem_list_open_files (Device *device, DBusGMethodInvocation *context) { if (!device->priv->device_is_mounted || device->priv->device_mount_paths->len == 0) { throw_error (context, ERROR_FAILED, "Device is not mounted"); goto out; } daemon_local_check_auth (device->priv->daemon, device, device->priv->device_is_system_internal ? "org.freedesktop.udisks.filesystem-lsof-system-internal" : "org.freedesktop.udisks.filesystem-lsof", "FilesystemListOpenFiles", TRUE, device_filesystem_list_open_files_authorized_cb, context, 0); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void drive_eject_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* TODO: probably wait for has_media to change to FALSE */ if (context != NULL) dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error ejecting: eject exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_drive_eject_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gchar **options = user_data_elements[0]; int n; char *argv[16]; GError *error; gboolean unmount = FALSE; error = NULL; if (!device->priv->device_is_drive) { throw_error (context, ERROR_FAILED, "Device is not a drive"); goto out; } if (!device->priv->device_is_media_available) { throw_error (context, ERROR_FAILED, "No media in drive"); goto out; } for (n = 0; options[n] != NULL; n++) { const char *option = options[n]; if (strcmp ("unmount", option) == 0) { unmount = TRUE; } else { throw_error (context, ERROR_INVALID_OPTION, "Unknown option %s", option); goto out; } } /* If we specify the unmount option, don't check if the device is mounted */ if (device_local_is_busy (device, TRUE, !unmount, &error)) { throw_error (context, ERROR_BUSY, error->message); g_error_free (error); goto out; } /* eject already unmounts the file systems on the ejected drive, so we do not * need any particular handling of the unmount flag here */ n = 0; argv[n++] = "eject"; argv[n++] = device->priv->device_file; argv[n++] = NULL; if (!job_new (context, "DriveEject", FALSE, device, argv, NULL, drive_eject_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean device_drive_eject (Device *device, char **options, DBusGMethodInvocation *context) { if (!device->priv->device_is_drive) { throw_error (context, ERROR_FAILED, "Device is not a drive"); goto out; } if (!device->priv->device_is_media_available) { throw_error (context, ERROR_FAILED, "No media in drive"); goto out; } daemon_local_check_auth (device->priv->daemon, device, context ? "org.freedesktop.udisks.drive-eject" : NULL, "DriveEject", TRUE, device_drive_eject_authorized_cb, context, 1, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void drive_detach_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* TODO: probably wait for has_media to change to FALSE */ dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error detaching: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_drive_detach_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gchar **options = user_data_elements[0]; int n; char *argv[16]; GError *error; error = NULL; if (!device->priv->device_is_drive) { throw_error (context, ERROR_FAILED, "Device is not a drive"); goto out; } if (!device->priv->drive_can_detach) { throw_error (context, ERROR_FAILED, "Device is not detachable"); goto out; } if (device_local_is_busy (device, TRUE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } for (n = 0; options[n] != NULL; n++) { const char *option = options[n]; throw_error (context, ERROR_INVALID_OPTION, "Unknown option %s", option); goto out; } n = 0; argv[n++] = "udisks-helper-drive-detach"; argv[n++] = device->priv->device_file; argv[n++] = NULL; if (!job_new (context, "DriveDetach", FALSE, device, argv, NULL, drive_detach_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean device_drive_detach (Device *device, char **options, DBusGMethodInvocation *context) { if (!device->priv->device_is_drive) { throw_error (context, ERROR_FAILED, "Device is not a drive"); goto out; } if (!device->priv->drive_can_detach) { throw_error (context, ERROR_FAILED, "Device is not detachable"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.drive-detach", "DriveDetach", TRUE, device_drive_detach_authorized_cb, context, 1, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void filesystem_check_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WIFEXITED (status) && !job_was_cancelled) { int rc; gboolean fs_is_clean; fs_is_clean = FALSE; rc = WEXITSTATUS (status); if ((rc == 0) || (((rc & 1) != 0) && ((rc & 4) == 0))) { fs_is_clean = TRUE; } dbus_g_method_return (context, fs_is_clean); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error fsck'ing: fsck exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_filesystem_check_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { /* TODO: use options! */ //gchar **options = user_data_elements[0]; int n; char *argv[16]; /* TODO: change when we have a file system that supports online fsck */ if (device->priv->device_is_mounted) { throw_error (context, ERROR_BUSY, "Device is mounted and no online capability in fsck tool for file system"); goto out; } n = 0; argv[n++] = "fsck"; argv[n++] = "-a"; argv[n++] = device->priv->device_file; argv[n++] = NULL; if (!job_new (context, "FilesystemCheck", FALSE, device, argv, NULL, filesystem_check_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean device_filesystem_check (Device *device, char **options, DBusGMethodInvocation *context) { daemon_local_check_auth (device->priv->daemon, device, device->priv->device_is_system_internal ? "org.freedesktop.udisks.filesystem-check-system-internal" : "org.freedesktop.udisks.filesystem-check", "FilesystemCheck", TRUE, device_filesystem_check_authorized_cb, context, 1, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void partition_delete_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { Device *enclosing_device = DEVICE (user_data); /* poke the kernel about the enclosing disk so we can reread the partitioning table */ device_generate_kernel_change_event (enclosing_device); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error erasing: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_partition_delete_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gchar **options = user_data_elements[0]; int n; int m; char *argv[16]; GError *error; char *offset_as_string; char *size_as_string; char *part_number_as_string; Device *enclosing_device; const gchar *partition_scheme; gint partition_type; offset_as_string = NULL; size_as_string = NULL; part_number_as_string = NULL; error = NULL; if (device_local_is_busy (device, FALSE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } if (!device->priv->device_is_partition) { throw_error (context, ERROR_FAILED, "Device is not a partition"); goto out; } enclosing_device = daemon_local_find_by_object_path (device->priv->daemon, device->priv->partition_slave); if (enclosing_device == NULL) { throw_error (context, ERROR_FAILED, "Cannot find enclosing device"); goto out; } if (device_local_is_busy (enclosing_device, FALSE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } /* don't allow deleting an extended partition if we have any logical partitions */ partition_scheme = g_udev_device_get_property (device->priv->d, "UDISKS_PARTITION_SCHEME"); partition_type = g_udev_device_get_property_as_int (device->priv->d, "UDISKS_PARTITION_TYPE"); if (g_strcmp0 (partition_scheme, "mbr") == 0 && (partition_type == 0x05 || partition_type == 0x0f || partition_type == 0x85)) { if (device_has_logical_partitions (enclosing_device)) { throw_error (context, ERROR_FAILED, "Cannot delete extended partition while logical partitions exist"); goto out; } } offset_as_string = g_strdup_printf ("%" G_GINT64_FORMAT "", device->priv->partition_offset); size_as_string = g_strdup_printf ("%" G_GINT64_FORMAT "", device->priv->partition_size); part_number_as_string = g_strdup_printf ("%d", device->priv->partition_number); n = 0; argv[n++] = "udisks-helper-delete-partition"; if (enclosing_device->priv->device_is_linux_dmmp) argv[n++] = (gchar *) get_dmmp_device_node (enclosing_device); else argv[n++] = enclosing_device->priv->device_file; argv[n++] = device->priv->device_file; argv[n++] = offset_as_string; argv[n++] = size_as_string; argv[n++] = part_number_as_string; for (m = 0; options[m] != NULL; m++) { if (n >= (int) sizeof(argv) - 1) { throw_error (context, ERROR_FAILED, "Too many options"); goto out; } /* the helper will validate each option */ argv[n++] = (char *) options[m]; } argv[n++] = NULL; if (!job_new (context, "PartitionDelete", TRUE, device, argv, NULL, partition_delete_completed_cb, FALSE, g_object_ref (enclosing_device), g_object_unref)) { goto out; } out: g_free (offset_as_string); g_free (size_as_string); g_free (part_number_as_string); } gboolean device_partition_delete (Device *device, char **options, DBusGMethodInvocation *context) { daemon_local_check_auth (device->priv->daemon, device, device->priv->device_is_system_internal ? "org.freedesktop.udisks.change-system-internal" : "org.freedesktop.udisks.change", "PartitionDelete", TRUE, device_partition_delete_authorized_cb, context, 1, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { FilesystemCreateHookFunc hook_func; gpointer hook_user_data; } MkfsData; static void mkfs_data_unref (MkfsData *data) { g_free (data); } static void filesystem_create_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { MkfsData *data = user_data; /* poke the kernel so we can reread the data */ device_generate_kernel_change_event (device); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { if (data->hook_func != NULL) data->hook_func (context, device, TRUE, data->hook_user_data); else dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else if (WEXITSTATUS (status) == 3) { throw_error (context, ERROR_FILESYSTEM_TOOLS_MISSING, "Error creating file system: Cannot run mkfs: %s", stderr); } else { throw_error (context, ERROR_FAILED, "Error creating file system: helper exited with exit code %d: %s\n%s", WEXITSTATUS (status), stderr, stdout); } if (data->hook_func != NULL) data->hook_func (context, device, FALSE, data->hook_user_data); } } typedef struct { int refcount; DBusGMethodInvocation *context; Device *device; char *passphrase; char **options; char *fstype; FilesystemCreateHookFunc mkfs_hook_func; gpointer mkfs_hook_user_data; guint device_changed_signal_handler_id; guint device_changed_timeout_id; } MkfsLuksData; static MkfsLuksData * mkfse_data_ref (MkfsLuksData *data) { data->refcount++; return data; } static void mkfse_data_unref (MkfsLuksData *data) { data->refcount--; if (data->refcount == 0) { if (data->passphrase != NULL) { memset (data->passphrase, '\0', strlen (data->passphrase)); g_free (data->passphrase); } if (data->device != NULL) g_object_unref (data->device); g_strfreev (data->options); g_free (data->fstype); g_free (data); } } static void filesystem_create_wait_for_cleartext_device_hook (DBusGMethodInvocation *context, Device *device, gpointer user_data) { MkfsLuksData *data = user_data; if (device == NULL) { /* Dang, unlocking failed. The unlock method have already thrown an exception for us. */ } else { /* We're unlocked.. awesome.. Now we can _finally_ create the file system. * What a ride. We're returning to exactly to where we came from. Back to * the source. Only the device is different. */ device_filesystem_create_internal (device, data->fstype, data->options, data->mkfs_hook_func, data->mkfs_hook_user_data, data->context); mkfse_data_unref (data); } } static void filesystem_create_wait_for_luks_device_changed_cb (Daemon *daemon, const char *object_path, gpointer user_data) { MkfsLuksData *data = user_data; Device *device; /* check if we're now a LUKS crypto device */ device = daemon_local_find_by_object_path (daemon, object_path); if (device == data->device && (device->priv->id_usage != NULL && strcmp (device->priv->id_usage, "crypto") == 0) && (device->priv->id_type != NULL && strcmp (device->priv->id_type, "crypto_LUKS") == 0)) { /* yay! we are now set up the corresponding cleartext device */ device_luks_unlock_internal (data->device, data->passphrase, NULL, filesystem_create_wait_for_cleartext_device_hook, data, data->context); g_signal_handler_disconnect (daemon, data->device_changed_signal_handler_id); g_source_remove (data->device_changed_timeout_id); } } static gboolean filesystem_create_wait_for_luks_device_not_seen_cb (gpointer user_data) { MkfsLuksData *data = user_data; throw_error (data->context, ERROR_FAILED, "Error creating luks encrypted file system: timeout (10s) waiting for luks device to show up"); g_signal_handler_disconnect (data->device->priv->daemon, data->device_changed_signal_handler_id); mkfse_data_unref (data); return FALSE; } static void filesystem_create_create_luks_device_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { MkfsLuksData *data = user_data; /* poke the kernel so we can reread the data (new uuid etc.) */ device_generate_kernel_change_event (device); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* OK! So we've got ourselves an luks device. Let's set it up so we can create a file * system. Sit and wait for the change event to appear so we can setup with the right UUID. */ data->device_changed_signal_handler_id = g_signal_connect_after (device->priv->daemon, "device-changed", (GCallback) filesystem_create_wait_for_luks_device_changed_cb, mkfse_data_ref (data)); /* set up timeout for error reporting if waiting failed * * (the signal handler and the timeout handler share the ref to data * as one will cancel the other) */ data->device_changed_timeout_id = g_timeout_add (10 * 1000, filesystem_create_wait_for_luks_device_not_seen_cb, data); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error creating file system: cryptsetup exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static gboolean device_filesystem_create_internal (Device *device, const char *fstype, char **options, FilesystemCreateHookFunc hook_func, gpointer hook_user_data, DBusGMethodInvocation *context) { int n, m; char *argv[128]; GError *error; char *s; char *options_for_stdin; char *passphrase_stdin; MkfsData *mkfs_data; options_for_stdin = NULL; passphrase_stdin = NULL; error = NULL; if (device_local_is_busy (device, TRUE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } if (strlen (fstype) == 0) { throw_error (context, ERROR_FAILED, "fstype not specified"); goto out; } /* search for luks_encrypt= and do a detour if that's specified */ for (n = 0; options[n] != NULL; n++) { if (g_str_has_prefix (options[n], "luks_encrypt=")) { MkfsLuksData *mkfse_data; /* So this is a request to create an luks device to put the * file system on; save all options for mkfs (except luks_encrypt=) for * later invocation once we have a cleartext device. */ mkfse_data = g_new0 (MkfsLuksData, 1); mkfse_data->refcount = 1; mkfse_data->context = context; mkfse_data->device = g_object_ref (device); mkfse_data->passphrase = g_strdup (options[n] + sizeof("luks_encrypt=") - 1); mkfse_data->mkfs_hook_func = hook_func; mkfse_data->mkfs_hook_user_data = hook_user_data; mkfse_data->fstype = g_strdup (fstype); mkfse_data->options = g_strdupv (options); g_free (mkfse_data->options[n]); for (m = n; mkfse_data->options[m] != NULL; m++) { mkfse_data->options[m] = mkfse_data->options[m + 1]; } passphrase_stdin = g_strdup_printf ("%s\n", mkfse_data->passphrase); n = 0; argv[n++] = "cryptsetup"; argv[n++] = "-q"; argv[n++] = "luksFormat"; argv[n++] = device->priv->device_file; argv[n++] = NULL; error = NULL; if (!job_new (context, "LuksFormat", TRUE, device, argv, passphrase_stdin, filesystem_create_create_luks_device_completed_cb, FALSE, mkfse_data, (GDestroyNotify) mkfse_data_unref)) { goto out; } goto out; } } mkfs_data = g_new (MkfsData, 1); mkfs_data->hook_func = hook_func; mkfs_data->hook_user_data = hook_user_data; /* pass options on stdin as it may contain secrets */ s = g_strjoinv ("\n", options); options_for_stdin = g_strconcat (s, "\n\n", NULL); g_free (s); n = 0; argv[n++] = "udisks-helper-mkfs"; argv[n++] = (char *) fstype; argv[n++] = device->priv->device_file; argv[n++] = device->priv->device_is_partition_table ? "1" : "0"; argv[n++] = NULL; if (!job_new (context, "FilesystemCreate", TRUE, device, argv, options_for_stdin, filesystem_create_completed_cb, FALSE, mkfs_data, (GDestroyNotify) mkfs_data_unref)) { goto out; } out: g_free (options_for_stdin); if (passphrase_stdin != NULL) { memset (passphrase_stdin, '\0', strlen (passphrase_stdin)); g_free (passphrase_stdin); } return TRUE; } static void device_filesystem_create_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *fstype = user_data_elements[0]; gchar **options = user_data_elements[1]; device_filesystem_create_internal (device, fstype, options, NULL, NULL, context); } gboolean device_filesystem_create (Device *device, const char *fstype, char **options, DBusGMethodInvocation *context) { daemon_local_check_auth (device->priv->daemon, device, device->priv->device_is_system_internal ? "org.freedesktop.udisks.change-system-internal" : "org.freedesktop.udisks.change", "FilesystemCreate", TRUE, device_filesystem_create_authorized_cb, context, 2, g_strdup (fstype), g_free, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void device_job_cancel_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { if (!device->priv->job_in_progress) { throw_error (context, ERROR_FAILED, "There is no job to cancel"); goto out; } if (!device->priv->job_is_cancellable) { throw_error (context, ERROR_FAILED, "Job cannot be cancelled"); goto out; } job_cancel (device); /* TODO: wait returning once the job is actually cancelled? */ dbus_g_method_return (context); out: ; } gboolean device_job_cancel (Device *device, DBusGMethodInvocation *context) { uid_t uid; const gchar *action_id; if (!device->priv->job_in_progress) { throw_error (context, ERROR_FAILED, "There is no job to cancel"); goto out; } if (!device->priv->job_is_cancellable) { throw_error (context, ERROR_FAILED, "Job cannot be cancelled"); goto out; } daemon_local_get_uid (device->priv->daemon, &uid, context); action_id = NULL; if (device->priv->job_initiated_by_uid != uid) { action_id = "org.freedesktop.udisks.cancel-job-others"; } daemon_local_check_auth (device->priv->daemon, device, action_id, "JobCancel", TRUE, device_job_cancel_authorized_cb, context, 0); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { int refcount; guint device_added_signal_handler_id; guint device_added_timeout_id; DBusGMethodInvocation *context; Device *device; guint64 offset; guint64 size; guint64 created_offset; guint64 created_size; char *fstype; char **fsoptions; } CreatePartitionData; static CreatePartitionData * partition_create_data_new (DBusGMethodInvocation *context, Device *device, guint64 offset, guint64 size, const char *fstype, char **fsoptions) { CreatePartitionData *data; data = g_new0 (CreatePartitionData, 1); data->refcount = 1; data->context = context; data->device = g_object_ref (device); data->offset = offset; data->size = size; data->fstype = g_strdup (fstype); data->fsoptions = g_strdupv (fsoptions); return data; } static CreatePartitionData * partition_create_data_ref (CreatePartitionData *data) { data->refcount++; return data; } static void partition_create_data_unref (CreatePartitionData *data) { data->refcount--; if (data->refcount == 0) { g_object_unref (data->device); g_free (data->fstype); g_strfreev (data->fsoptions); g_free (data); } } static void partition_create_filesystem_create_hook (DBusGMethodInvocation *context, Device *device, gboolean filesystem_create_succeeded, gpointer user_data) { if (!filesystem_create_succeeded) { /* dang.. FilesystemCreate already reported an error */ } else { /* it worked.. */ dbus_g_method_return (context, device->priv->object_path); } } static void partition_create_found_device (Device *device, CreatePartitionData *data) { if (strlen (data->fstype) > 0) { device_filesystem_create_internal (device, data->fstype, data->fsoptions, partition_create_filesystem_create_hook, NULL, data->context); } else { dbus_g_method_return (data->context, device->priv->object_path); } } static void partition_create_device_added_cb (Daemon *daemon, const char *object_path, gpointer user_data) { CreatePartitionData *data = user_data; Device *device; /* check the device added is the partition we've created */ device = daemon_local_find_by_object_path (daemon, object_path); if (device != NULL && device->priv->device_is_partition && strcmp (device->priv->partition_slave, data->device->priv->object_path) == 0 && data->created_offset == device->priv->partition_offset && data->created_size == device->priv->partition_size) { /* yay! it is.. now create the file system if requested */ partition_create_found_device (device, data); g_signal_handler_disconnect (daemon, data->device_added_signal_handler_id); g_source_remove (data->device_added_timeout_id); partition_create_data_unref (data); } } static gboolean partition_create_device_not_seen_cb (gpointer user_data) { CreatePartitionData *data = user_data; throw_error (data->context, ERROR_FAILED, "Error creating partition: timeout (10s) waiting for partition to show up"); g_signal_handler_disconnect (data->device->priv->daemon, data->device_added_signal_handler_id); partition_create_data_unref (data); return FALSE; } static void partition_create_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { CreatePartitionData *data = user_data; /* poke the kernel so we can reread the data */ device_generate_kernel_change_event (device); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { int n; int m; guint64 offset; guint64 size; char **tokens; /* Find the * * job-create-partition-offset: * job-create-partition-size: * * lines and parse the new start and end. We need this * for waiting on the created partition (since the requested * start and size passed may not be honored due to disk/cylinder/sector * alignment reasons). */ offset = 0; size = 0; m = 0; tokens = g_strsplit (stderr, "\n", 0); for (n = 0; tokens[n] != NULL; n++) { char *line = tokens[n]; char *endp; if (m == 2) break; if (g_str_has_prefix (line, "job-create-partition-offset: ")) { offset = strtoll (line + sizeof("job-create-partition-offset: ") - 1, &endp, 10); if (*endp == '\0') m++; } else if (g_str_has_prefix (line, "job-create-partition-size: ")) { size = strtoll (line + sizeof("job-create-partition-size: ") - 1, &endp, 10); if (*endp == '\0') m++; } } g_strfreev (tokens); if (m != 2) { throw_error (context, ERROR_FAILED, "Error creating partition: internal error, expected to find new " "start and end but m=%d", m); } else { gboolean found_device; GList *devices; GList *l; data->created_offset = offset; data->created_size = size; /* check if the device is already there */ found_device = FALSE; devices = daemon_local_get_all_devices (device->priv->daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (d->priv->device_is_partition && strcmp (d->priv->partition_slave, data->device->priv->object_path) == 0 && data->created_offset == d->priv->partition_offset && data->created_size == d->priv->partition_size) { /* yay! it is.. now create the file system if requested */ partition_create_found_device (d, data); found_device = TRUE; break; } } if (!found_device) { /* otherwise sit around and wait for the new partition to appear */ data->device_added_signal_handler_id = g_signal_connect_after (device->priv->daemon, "device-added", (GCallback) partition_create_device_added_cb, partition_create_data_ref (data)); /* set up timeout for error reporting if waiting failed * * (the signal handler and the timeout handler share the ref to data * as one will cancel the other) */ data->device_added_timeout_id = g_timeout_add (10 * 1000, partition_create_device_not_seen_cb, data); } } } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error creating partition: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_partition_create_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { guint64 offset = *((guint64*) user_data_elements[0]); guint64 size = *((guint64*) user_data_elements[1]); ; const char *type = user_data_elements[2]; const char *label = user_data_elements[3]; char **flags = user_data_elements[4]; char **options = user_data_elements[5]; const char *fstype = user_data_elements[6]; char **fsoptions = user_data_elements[7]; int n; int m; char *argv[128]; GError *error; char *offset_as_string; char *size_as_string; char *flags_as_string; offset_as_string = NULL; size_as_string = NULL; flags_as_string = NULL; error = NULL; if (!device->priv->device_is_partition_table) { throw_error (context, ERROR_FAILED, "Device is not partitioned"); goto out; } if (device_local_is_busy (device, FALSE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } /* partutil.c / libparted will check there are no partitions in the requested slice */ offset_as_string = g_strdup_printf ("%" G_GINT64_FORMAT "", offset); size_as_string = g_strdup_printf ("%" G_GINT64_FORMAT "", size); /* TODO: check that neither of the flags include ',' */ flags_as_string = g_strjoinv (",", flags); n = 0; argv[n++] = "udisks-helper-create-partition"; if (device->priv->device_is_linux_dmmp) argv[n++] = (gchar *) get_dmmp_device_node (device); else argv[n++] = device->priv->device_file; argv[n++] = offset_as_string; argv[n++] = size_as_string; argv[n++] = (char *) type; argv[n++] = (char *) label; argv[n++] = (char *) flags_as_string; for (m = 0; options[m] != NULL; m++) { if (n >= (int) sizeof(argv) - 1) { throw_error (context, ERROR_FAILED, "Too many options"); goto out; } /* the helper will validate each option */ argv[n++] = (char *) options[m]; } argv[n++] = NULL; if (!job_new (context, "PartitionCreate", TRUE, device, argv, NULL, partition_create_completed_cb, FALSE, partition_create_data_new (context, device, offset, size, fstype, fsoptions), (GDestroyNotify) partition_create_data_unref)) { goto out; } out: g_free (offset_as_string); g_free (size_as_string); g_free (flags_as_string); } gboolean device_partition_create (Device *device, guint64 offset, guint64 size, const char *type, const char *label, char **flags, char **options, const char *fstype, char **fsoptions, DBusGMethodInvocation *context) { if (!device->priv->device_is_partition_table) { throw_error (context, ERROR_FAILED, "Device is not partitioned"); goto out; } daemon_local_check_auth (device->priv->daemon, device, device->priv->device_is_system_internal ? "org.freedesktop.udisks.change-system-internal" : "org.freedesktop.udisks.change", "PartitionCreate", TRUE, device_partition_create_authorized_cb, context, 8, g_memdup (&offset, sizeof(guint64)), g_free, g_memdup (&size, sizeof(guint64)), g_free, g_strdup (type), g_free, g_strdup (label), g_free, g_strdupv (flags), g_strfreev, g_strdupv (options), g_strfreev, g_strdup (fstype), g_free, g_strdupv (fsoptions), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { DBusGMethodInvocation *context; Device *device; Device *enclosing_device; char *type; char *label; char **flags; } ModifyPartitionData; static ModifyPartitionData * partition_modify_data_new (DBusGMethodInvocation *context, Device *device, Device *enclosing_device, const char *type, const char *label, char **flags) { ModifyPartitionData *data; data = g_new0 (ModifyPartitionData, 1); data->context = context; data->device = g_object_ref (device); data->enclosing_device = g_object_ref (enclosing_device); data->type = g_strdup (type); data->label = g_strdup (label); data->flags = g_strdupv (flags); return data; } static void partition_modify_data_unref (ModifyPartitionData *data) { g_object_unref (data->device); g_object_unref (data->enclosing_device); g_free (data->type); g_free (data->label); g_free (data->flags); g_free (data); } static void partition_modify_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { ModifyPartitionData *data = user_data; /* poke the kernel so we can reread the data */ device_generate_kernel_change_event (data->enclosing_device); device_generate_kernel_change_event (data->device); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* update local copy, don't wait for the kernel */ device_set_partition_type (device, data->type); device_set_partition_label (device, data->label); device_set_partition_flags (device, data->flags); drain_pending_changes (device, FALSE); dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error modifying partition: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_partition_modify_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const char *type = user_data_elements[0]; const char *label = user_data_elements[1]; char **flags = user_data_elements[2]; int n; char *argv[128]; GError *error; char *offset_as_string; char *size_as_string; char *flags_as_string; Device *enclosing_device; offset_as_string = NULL; size_as_string = NULL; flags_as_string = NULL; error = NULL; if (!device->priv->device_is_partition) { throw_error (context, ERROR_FAILED, "Device is not a partition"); goto out; } enclosing_device = daemon_local_find_by_object_path (device->priv->daemon, device->priv->partition_slave); if (enclosing_device == NULL) { throw_error (context, ERROR_FAILED, "Cannot find enclosing device"); goto out; } if (device_local_is_busy (enclosing_device, FALSE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } if (strlen (type) == 0) { throw_error (context, ERROR_FAILED, "type not specified"); goto out; } offset_as_string = g_strdup_printf ("%" G_GINT64_FORMAT "", device->priv->partition_offset); size_as_string = g_strdup_printf ("%" G_GINT64_FORMAT "", device->priv->partition_size); /* TODO: check that neither of the flags include ',' */ flags_as_string = g_strjoinv (",", flags); n = 0; argv[n++] = "udisks-helper-modify-partition"; argv[n++] = enclosing_device->priv->device_file; argv[n++] = offset_as_string; argv[n++] = size_as_string; argv[n++] = (char *) type; argv[n++] = (char *) label; argv[n++] = (char *) flags_as_string; argv[n++] = NULL; if (!job_new (context, "PartitionModify", TRUE, device, argv, NULL, partition_modify_completed_cb, FALSE, partition_modify_data_new (context, device, enclosing_device, type, label, flags), (GDestroyNotify) partition_modify_data_unref)) { goto out; } out: g_free (offset_as_string); g_free (size_as_string); g_free (flags_as_string); } gboolean device_partition_modify (Device *device, const char *type, const char *label, char **flags, DBusGMethodInvocation *context) { if (!device->priv->device_is_partition) { throw_error (context, ERROR_FAILED, "Device is not a partition"); goto out; } daemon_local_check_auth (device->priv->daemon, device, device->priv->device_is_system_internal ? "org.freedesktop.udisks.change-system-internal" : "org.freedesktop.udisks.change", "PartitionModify", TRUE, device_partition_modify_authorized_cb, context, 3, g_strdup (type), g_free, g_strdup (label), g_free, g_strdupv (flags), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { int refcount; guint device_changed_signal_handler_id; guint device_changed_timeout_id; DBusGMethodInvocation *context; Device *device; char *scheme; } CreatePartitionTableData; static CreatePartitionTableData * partition_table_create_data_new (DBusGMethodInvocation *context, Device *device, const char *scheme) { CreatePartitionTableData *data; data = g_new0 (CreatePartitionTableData, 1); data->refcount = 1; data->context = context; data->device = g_object_ref (device); data->scheme = g_strdup (scheme); return data; } static CreatePartitionTableData * partition_table_create_data_ref (CreatePartitionTableData *data) { data->refcount++; return data; } static void partition_table_create_data_unref (CreatePartitionTableData *data) { data->refcount--; if (data->refcount == 0) { g_object_unref (data->device); g_free (data->scheme); g_free (data); } } static void partition_table_create_device_changed_cb (Daemon *daemon, const char *object_path, gpointer user_data) { CreatePartitionTableData *data = user_data; Device *device; device = daemon_local_find_by_object_path (daemon, object_path); if (device == data->device) { if (g_strcmp0 (device->priv->partition_table_scheme, data->scheme) == 0 || (device->priv->partition_table_scheme == NULL && g_strcmp0 (data->scheme, "none") == 0)) { dbus_g_method_return (data->context); g_signal_handler_disconnect (daemon, data->device_changed_signal_handler_id); g_source_remove (data->device_changed_timeout_id); partition_table_create_data_unref (data); } } } static gboolean partition_table_create_device_not_changed_cb (gpointer user_data) { CreatePartitionTableData *data = user_data; throw_error (data->context, ERROR_FAILED, "Error creating partition table: timeout (10s) waiting for change"); g_signal_handler_disconnect (data->device->priv->daemon, data->device_changed_signal_handler_id); partition_table_create_data_unref (data); return FALSE; } static void partition_table_create_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { CreatePartitionTableData *data = user_data; /* poke the kernel so we can reread the data */ device_generate_kernel_change_event (device); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { if (g_strcmp0 (device->priv->partition_table_scheme, data->scheme) == 0) { dbus_g_method_return (context); } else { /* sit around and wait for the new partition table to appear */ data->device_changed_signal_handler_id = g_signal_connect_after (device->priv->daemon, "device-changed", (GCallback) partition_table_create_device_changed_cb, partition_table_create_data_ref (data)); /* set up timeout for error reporting if waiting failed * * (the signal handler and the timeout handler share the ref to data * as one will cancel the other) */ data->device_changed_timeout_id = g_timeout_add (10 * 1000, partition_table_create_device_not_changed_cb, data); } } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error creating partition table: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_partition_table_create_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const char *scheme = user_data_elements[0]; char **options = user_data_elements[1]; int n; int m; char *argv[128]; GError *error; error = NULL; if (device_local_is_busy (device, TRUE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } if (strlen (scheme) == 0) { throw_error (context, ERROR_FAILED, "type not specified"); goto out; } n = 0; argv[n++] = "udisks-helper-create-partition-table"; if (device->priv->device_is_linux_dmmp) argv[n++] = (gchar *) get_dmmp_device_node (device); else argv[n++] = device->priv->device_file; argv[n++] = (char *) scheme; for (m = 0; options[m] != NULL; m++) { if (n >= (int) sizeof(argv) - 1) { throw_error (context, ERROR_FAILED, "Too many options"); goto out; } /* the helper will validate each option */ argv[n++] = (char *) options[m]; } argv[n++] = NULL; if (!job_new (context, "PartitionTableCreate", TRUE, device, argv, NULL, partition_table_create_completed_cb, FALSE, partition_table_create_data_new (context, device, scheme), (GDestroyNotify) partition_table_create_data_unref)) { goto out; } out: ; } gboolean device_partition_table_create (Device *device, const char *scheme, char **options, DBusGMethodInvocation *context) { daemon_local_check_auth (device->priv->daemon, device, device->priv->device_is_system_internal ? "org.freedesktop.udisks.change-system-internal" : "org.freedesktop.udisks.change", "PartitionTableCreate", TRUE, device_partition_table_create_authorized_cb, context, 2, g_strdup (scheme), g_free, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static Device * find_cleartext_device (Device *device) { GList *devices; GList *l; Device *ret; ret = NULL; /* check that there isn't a cleartext device already */ devices = daemon_local_get_all_devices (device->priv->daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (d->priv->device_is_luks_cleartext && d->priv->luks_cleartext_slave != NULL && strcmp (d->priv->luks_cleartext_slave, device->priv->object_path) == 0) { ret = d; goto out; } } out: g_list_free (devices); return ret; } typedef struct { int refcount; gulong device_added_signal_handler_id; gulong device_changed_signal_handler_id; guint device_added_timeout_id; DBusGMethodInvocation *context; Device *device; UnlockEncryptionHookFunc hook_func; gpointer hook_user_data; } UnlockEncryptionData; static UnlockEncryptionData * unlock_encryption_data_new (DBusGMethodInvocation *context, Device *device, UnlockEncryptionHookFunc hook_func, gpointer hook_user_data) { UnlockEncryptionData *data; data = g_new0 (UnlockEncryptionData, 1); data->refcount = 1; data->context = context; data->device = g_object_ref (device); data->hook_func = hook_func; data->hook_user_data = hook_user_data; return data; } static UnlockEncryptionData * unlock_encryption_data_ref (UnlockEncryptionData *data) { data->refcount++; return data; } static void unlock_encryption_data_unref (UnlockEncryptionData *data) { data->refcount--; if (data->refcount == 0) { g_object_unref (data->device); g_free (data); } } static void luks_unlock_device_added_cb (Daemon *daemon, const char *object_path, gpointer user_data) { UnlockEncryptionData *data = user_data; Device *device; /* check the device is a cleartext partition for us */ device = daemon_local_find_by_object_path (daemon, object_path); if (device != NULL && device->priv->device_is_luks_cleartext && strcmp (device->priv->luks_cleartext_slave, data->device->priv->object_path) == 0) { g_signal_handler_disconnect (daemon, data->device_added_signal_handler_id); g_signal_handler_disconnect (daemon, data->device_changed_signal_handler_id); g_source_remove (data->device_added_timeout_id); /* update and emit a Changed() signal on the holder since the luks-holder * property indicates the cleartext device */ update_info (data->device); drain_pending_changes (data->device, FALSE); if (data->hook_func != NULL) { data->hook_func (data->context, device, data->hook_user_data); } else { dbus_g_method_return (data->context, object_path); } unlock_encryption_data_unref (data); } } static gboolean luks_unlock_device_not_seen_cb (gpointer user_data) { UnlockEncryptionData *data = user_data; g_signal_handler_disconnect (data->device->priv->daemon, data->device_added_signal_handler_id); g_signal_handler_disconnect (data->device->priv->daemon, data->device_changed_signal_handler_id); throw_error (data->context, ERROR_FAILED, "Error unlocking device: timeout (10s) waiting for cleartext device to show up"); if (data->hook_func != NULL) { data->hook_func (data->context, NULL, data->hook_user_data); } unlock_encryption_data_unref (data); return FALSE; } static void luks_unlock_start_waiting_for_cleartext_device (UnlockEncryptionData *data) { Device *cleartext_device; cleartext_device = find_cleartext_device (data->device); if (cleartext_device != NULL) { /* update and emit a Changed() signal on the holder since the luks-holder * property indicates the cleartext device */ update_info (data->device); drain_pending_changes (data->device, FALSE); if (data->hook_func != NULL) { data->hook_func (data->context, cleartext_device, data->hook_user_data); } else { dbus_g_method_return (data->context, cleartext_device->priv->object_path); } unlock_encryption_data_unref (data); } else { /* sit around wait for the cleartext device to appear */ data->device_added_signal_handler_id = g_signal_connect_after (data->device->priv->daemon, "device-added", (GCallback) luks_unlock_device_added_cb, data); data->device_changed_signal_handler_id = g_signal_connect_after (data->device->priv->daemon, "device-changed", (GCallback) luks_unlock_device_added_cb, data); /* set up timeout for error reporting if waiting failed */ data->device_added_timeout_id = g_timeout_add (15 * 1000, luks_unlock_device_not_seen_cb, data); /* Note that the signal and timeout handlers share the ref to data - one will cancel the other */ } } static void luks_unlock_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { UnlockEncryptionData *data = user_data; if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { luks_unlock_start_waiting_for_cleartext_device (unlock_encryption_data_ref (data)); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error unlocking device: cryptsetup exited with exit code %d: %s", WEXITSTATUS (status), stderr); } if (data->hook_func != NULL) { data->hook_func (data->context, NULL, data->hook_user_data); } } } static gboolean device_luks_unlock_internal (Device *device, const char *secret, char **options, UnlockEncryptionHookFunc hook_func, gpointer hook_user_data, DBusGMethodInvocation *context) { int n; char *argv[10]; char *luks_name; GError *error; char *secret_as_stdin; uid_t uid; luks_name = NULL; secret_as_stdin = NULL; error = NULL; daemon_local_get_uid (device->priv->daemon, &uid, context); if (device_local_is_busy (device, FALSE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } if (device->priv->id_usage == NULL || strcmp (device->priv->id_usage, "crypto") != 0) { throw_error (context, ERROR_FAILED, "Not a LUKS device"); goto out; } if (find_cleartext_device (device) != NULL) { throw_error (context, ERROR_FAILED, "Cleartext device is already unlocked"); goto out; } luks_name = g_strdup_printf ("udisks-luks-uuid-%s-uid%d", device->priv->id_uuid, uid); secret_as_stdin = g_strdup_printf ("%s\n", secret); n = 0; argv[n++] = "cryptsetup"; argv[n++] = "-T"; argv[n++] = "1"; argv[n++] = "luksOpen"; argv[n++] = device->priv->device_file; argv[n++] = luks_name; argv[n++] = NULL; /* yay, so it turns out /sbin/cryptsetup returns way too early; what happens is this * * - invoke /sbin/cryptsetup * - temporary dm node with name temporary-cryptsetup-* appears. We ignore these, * see above * - temporary dm node removed * - /sbin/cryptsetup returns with success (brings us here) * - proper dm node appears * - with the name we requested, e.g. udisks-luks-uuid-%s-uid%d * - proper dm node disappears * - proper dm node reappears * * Obiviously /sbin/cryptsetup shouldn't return before the dm node we are * looking for is really there or ready to use. But that's not how things * work. * * This bug has been reported here: * * https://bugzilla.redhat.com/show_bug.cgi?id=530721 * * WORKAROUND: wait for the udev queue to settle before returning. Long * term fix is device-mapper/udev integration. */ if (!job_new (context, "LuksUnlock", FALSE, device, argv, secret_as_stdin, luks_unlock_completed_cb, TRUE, /* see note above */ unlock_encryption_data_new (context, device, hook_func, hook_user_data), (GDestroyNotify) unlock_encryption_data_unref)) { goto out; } out: /* scrub the secret */ if (secret_as_stdin != NULL) { memset (secret_as_stdin, '\0', strlen (secret_as_stdin)); } g_free (secret_as_stdin); g_free (luks_name); return TRUE; } static void device_luks_unlock_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const char *secret = user_data_elements[0]; char **options = user_data_elements[1]; device_luks_unlock_internal (device, secret, options, NULL, NULL, context); } gboolean device_luks_unlock (Device *device, const char *secret, char **options, DBusGMethodInvocation *context) { if (device->priv->id_usage == NULL || strcmp (device->priv->id_usage, "crypto") != 0) { throw_error (context, ERROR_FAILED, "Not a LUKS device"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.luks-unlock", "LuksUnlock", TRUE, device_luks_unlock_authorized_cb, context, 2, g_strdup (secret), g_free, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { int refcount; DBusGMethodInvocation *context; Device *luks_device; Device *cleartext_device; guint device_removed_signal_handler_id; guint device_removed_timeout_id; } LockEncryptionData; static LockEncryptionData * lock_encryption_data_new (DBusGMethodInvocation *context, Device *luks_device, Device *cleartext_device) { LockEncryptionData *data; data = g_new0 (LockEncryptionData, 1); data->refcount = 1; data->context = context; data->luks_device = g_object_ref (luks_device); data->cleartext_device = g_object_ref (cleartext_device); return data; } static LockEncryptionData * lock_encryption_data_ref (LockEncryptionData *data) { data->refcount++; return data; } static void lock_encryption_data_unref (LockEncryptionData *data) { data->refcount--; if (data->refcount == 0) { g_object_unref (data->luks_device); g_object_unref (data->cleartext_device); g_free (data); } } static void luks_lock_wait_for_cleartext_device_removed_cb (Daemon *daemon, const char *object_path, gpointer user_data) { Device *device; LockEncryptionData *data = user_data; device = daemon_local_find_by_object_path (daemon, object_path); if (device == data->cleartext_device) { job_local_end (data->luks_device); /* update and emit a Changed() signal on the holder since the luks-holder * property indicates the cleartext device */ update_info (data->luks_device); drain_pending_changes (data->luks_device, FALSE); dbus_g_method_return (data->context); g_signal_handler_disconnect (daemon, data->device_removed_signal_handler_id); g_source_remove (data->device_removed_timeout_id); lock_encryption_data_unref (data); } } static gboolean luks_lock_wait_for_cleartext_device_not_seen_cb (gpointer user_data) { LockEncryptionData *data = user_data; job_local_end (data->luks_device); throw_error (data->context, ERROR_FAILED, "Error locking luks device: timeout (10s) waiting for cleartext device to be removed"); g_signal_handler_disconnect (data->cleartext_device->priv->daemon, data->device_removed_signal_handler_id); lock_encryption_data_unref (data); return FALSE; } static void luks_lock_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { LockEncryptionData *data = user_data; if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* if device is already removed, just return */ if (data->cleartext_device->priv->removed) { /* update and emit a Changed() signal on the holder since the luks-holder * property indicates the cleartext device */ update_info (data->luks_device); drain_pending_changes (data->luks_device, FALSE); dbus_g_method_return (context); } else { /* otherwise sit and wait for the device to disappear */ data->device_removed_signal_handler_id = g_signal_connect_after (device->priv->daemon, "device-removed", (GCallback) luks_lock_wait_for_cleartext_device_removed_cb, lock_encryption_data_ref (data)); /* set up timeout for error reporting if waiting failed * * (the signal handler and the timeout handler share the ref to data * as one will cancel the other) */ data->device_removed_timeout_id = g_timeout_add (10 * 1000, luks_lock_wait_for_cleartext_device_not_seen_cb, data); job_local_start (device, "LuksLock"); } } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error locking device: cryptsetup exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static gboolean luks_get_uid_from_dm_name (const char *dm_name, uid_t *out_uid) { int n; gboolean ret; uid_t uid; char *endp; ret = FALSE; if (!g_str_has_prefix (dm_name, "udisks-luks-uuid")) goto out; /* determine who unlocked the device */ for (n = strlen (dm_name) - 1; n >= 0; n--) { if (dm_name[n] == '-') break; } if (strncmp (dm_name + n, "-uid", 4) != 0) goto out; uid = strtol (dm_name + n + 4, &endp, 10); if (endp == NULL || *endp != '\0') goto out; if (out_uid != NULL) *out_uid = uid; ret = TRUE; out: return ret; } static void device_luks_lock_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { /* TODO: use options */ //char **options = user_data_elements[0]; Device *cleartext_device; int n; char *argv[10]; if (device->priv->id_usage == NULL || strcmp (device->priv->id_usage, "crypto") != 0) { throw_error (context, ERROR_FAILED, "Not a LUKS crypto device"); goto out; } cleartext_device = find_cleartext_device (device); if (cleartext_device == NULL) { throw_error (context, ERROR_FAILED, "Cleartext device is not unlocked"); goto out; } if (cleartext_device->priv->dm_name == NULL || strlen (cleartext_device->priv->dm_name) == 0) { throw_error (context, ERROR_FAILED, "Cannot determine device-mapper name"); goto out; } n = 0; argv[n++] = "cryptsetup"; argv[n++] = "luksClose"; argv[n++] = cleartext_device->priv->dm_name; argv[n++] = NULL; if (!job_new (context, "LuksLock", FALSE, device, argv, NULL, luks_lock_completed_cb, FALSE, lock_encryption_data_new (context, device, cleartext_device), (GDestroyNotify) lock_encryption_data_unref)) { goto out; } out: ; } gboolean device_luks_lock (Device *device, char **options, DBusGMethodInvocation *context) { uid_t unlocked_by_uid; uid_t uid; Device *cleartext_device; const gchar *action_id; daemon_local_get_uid (device->priv->daemon, &uid, context); if (device->priv->id_usage == NULL || strcmp (device->priv->id_usage, "crypto") != 0) { throw_error (context, ERROR_FAILED, "Not a LUKS crypto device"); goto out; } cleartext_device = find_cleartext_device (device); if (cleartext_device == NULL) { throw_error (context, ERROR_FAILED, "Cleartext device is not unlocked"); goto out; } if (cleartext_device->priv->dm_name == NULL || strlen (cleartext_device->priv->dm_name) == 0) { throw_error (context, ERROR_FAILED, "Cannot determine device-mapper name"); goto out; } /* see if we (e.g. udisks) set up this clear text device */ if (!luks_get_uid_from_dm_name (cleartext_device->priv->dm_name, &unlocked_by_uid)) { /* nope.. so assume uid 0 set it up.. we still allow locking * the device... given enough privilege */ unlocked_by_uid = 0; } /* require authorization if unlocked by someone else */ action_id = NULL; if (unlocked_by_uid != uid) { action_id = "org.freedesktop.udisks.luks-lock-others"; } daemon_local_check_auth (device->priv->daemon, device, action_id, "LuksLock", TRUE, device_luks_lock_authorized_cb, context, 1, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void luks_change_passphrase_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else if (WEXITSTATUS (status) == 3) { throw_error (context, ERROR_FILESYSTEM_TOOLS_MISSING, "Error changing fs label: tool not available: %s", stderr); } else { throw_error (context, ERROR_FAILED, "Error changing secret on device: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_luks_change_passphrase_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const char *old_secret = user_data_elements[0]; const char *new_secret = user_data_elements[1]; int n; char *argv[10]; char *secrets_as_stdin; secrets_as_stdin = NULL; if (device->priv->id_usage == NULL || strcmp (device->priv->id_usage, "crypto") != 0) { throw_error (context, ERROR_FAILED, "Not a LUKS crypto device"); goto out; } secrets_as_stdin = g_strdup_printf ("%s\n%s\n", old_secret, new_secret); n = 0; argv[n++] = "udisks-helper-change-luks-password"; argv[n++] = device->priv->device_file; argv[n++] = NULL; if (!job_new (context, "LuksChangePassphrase", FALSE, device, argv, secrets_as_stdin, luks_change_passphrase_completed_cb, FALSE, NULL, NULL)) { goto out; } out: /* scrub the secrets */ if (secrets_as_stdin != NULL) { memset (secrets_as_stdin, '\0', strlen (secrets_as_stdin)); } g_free (secrets_as_stdin); } gboolean device_luks_change_passphrase (Device *device, const char *old_secret, const char *new_secret, DBusGMethodInvocation *context) { /* No need to check for busy; we can actually do this while the device is unlocked as * only LUKS metadata is modified. */ if (device->priv->id_usage == NULL || strcmp (device->priv->id_usage, "crypto") != 0) { throw_error (context, ERROR_FAILED, "Not a LUKS crypto device"); goto out; } daemon_local_check_auth (device->priv->daemon, device, device->priv->device_is_system_internal ? "org.freedesktop.udisks.change-system-internal" : "org.freedesktop.udisks.change", "LuksChangePassphrase", TRUE, device_luks_change_passphrase_authorized_cb, context, 2, g_strdup (old_secret), g_free, g_strdup (new_secret), g_free); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void filesystem_set_label_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { char *new_label = user_data; /* poke the kernel so we can reread the data */ device_generate_kernel_change_event (device); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* update local copy, don't wait for the kernel */ device_set_id_label (device, new_label); drain_pending_changes (device, FALSE); dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error changing fslabel: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_filesystem_set_label_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *new_label = user_data_elements[0]; int n; char *argv[10]; const Filesystem *fs_details; GError *error; error = NULL; if (device->priv->id_usage == NULL || strcmp (device->priv->id_usage, "filesystem") != 0) { throw_error (context, ERROR_FAILED, "Not a mountable file system"); goto out; } fs_details = daemon_local_get_fs_details (device->priv->daemon, device->priv->id_type); if (fs_details == NULL) { throw_error (context, ERROR_BUSY, "Unknown filesystem"); goto out; } if (!fs_details->supports_online_label_rename) { if (device_local_is_busy (device, FALSE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } } n = 0; argv[n++] = "udisks-helper-change-filesystem-label"; argv[n++] = device->priv->device_file; argv[n++] = device->priv->id_type; argv[n++] = (char *) new_label; argv[n++] = NULL; if (!job_new (context, "FilesystemSetLabel", FALSE, device, argv, NULL, filesystem_set_label_completed_cb, FALSE, g_strdup (new_label), g_free)) { goto out; } out: ; } gboolean device_filesystem_set_label (Device *device, const char *new_label, DBusGMethodInvocation *context) { const Filesystem *fs_details; GError *error; error = NULL; if (device->priv->id_usage == NULL || strcmp (device->priv->id_usage, "filesystem") != 0) { throw_error (context, ERROR_FAILED, "Not a mountable file system"); goto out; } fs_details = daemon_local_get_fs_details (device->priv->daemon, device->priv->id_type); if (fs_details == NULL) { throw_error (context, ERROR_BUSY, "Unknown filesystem"); goto out; } if (!fs_details->supports_online_label_rename) { if (device_local_is_busy (device, FALSE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } } daemon_local_check_auth (device->priv->daemon, device, device->priv->device_is_system_internal ? "org.freedesktop.udisks.change-system-internal" : "org.freedesktop.udisks.change", "FilesystemSetLabel", TRUE, device_filesystem_set_label_authorized_cb, context, 1, g_strdup (new_label), g_free); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ /* may be called with context==NULL */ static void drive_ata_smart_refresh_data_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { gint rc; SkDisk *d; gchar *blob; gsize blob_size; time_t time_collected; SkSmartOverall overall; PROFILE ("drive_ata_smart_refresh_data_completed_cb(device=%s) start", device->priv->native_path); d = NULL; blob = NULL; if (job_was_cancelled || stdout == NULL) { if (job_was_cancelled) { if (context != NULL) throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { if (context != NULL) throw_error (context, ERROR_FAILED, "Error retrieving ATA SMART data: no output", WEXITSTATUS (status), stderr); } goto out; } rc = WEXITSTATUS (status); if (rc != 0) { if (rc == 2) { if (context != NULL) { throw_error (context, ERROR_ATA_SMART_WOULD_WAKEUP, "Error retrieving ATA SMART data: %s", stderr); } } else { if (context != NULL) { throw_error (context, ERROR_FAILED, "Error retrieving ATA SMART data: helper failed with exit code %d: %s", rc, stderr); } } goto out; } PROFILE ("drive_ata_smart_refresh_data_completed_cb(device=%s) decode blob", device->priv->native_path); blob = (gchar *) g_base64_decode (stdout, &blob_size); PROFILE ("drive_ata_smart_refresh_data_completed_cb(device=%s) set blob", device->priv->native_path); if (blob == NULL) { if (context != NULL) { throw_error (context, ERROR_FAILED, "Error decoding ATA SMART data: invalid base64 format: %s", stdout); } else { g_warning ("Error decoding ATA SMART data: invalid base64 format: %s", stdout); } goto out; } if (sk_disk_open (NULL, &d) != 0) { if (context != NULL) { throw_error (context, ERROR_FAILED, "unable to open a SkDisk"); } goto out; } if (sk_disk_set_blob (d, blob, blob_size) != 0) { if (context != NULL) { throw_error (context, ERROR_FAILED, "error parsing blob: %s", strerror (errno)); } goto out; } PROFILE ("drive_ata_smart_refresh_data_completed_cb(device=%s) time collected", device->priv->native_path); time_collected = time (NULL); device_set_drive_ata_smart_time_collected (device, time_collected); PROFILE ("drive_ata_smart_refresh_data_completed_cb(device=%s) overall smart status", device->priv->native_path); if (sk_disk_smart_get_overall (d, &overall) != 0) overall = -1; device_set_drive_ata_smart_status (device, overall); device_set_drive_ata_smart_blob_steal (device, blob, blob_size); blob = NULL; /* emit change event since we've updated the smart data */ PROFILE ("drive_ata_smart_refresh_data_completed_cb(device=%s) drain pending changes", device->priv->native_path); drain_pending_changes (device, FALSE); if (context != NULL) dbus_g_method_return (context); out: g_free (blob); if (d != NULL) sk_disk_free (d); PROFILE ("drive_ata_smart_refresh_data_completed_cb(device=%s) end", device->priv->native_path); } static void device_drive_ata_smart_refresh_data_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { char **options = user_data_elements[0]; int n; char *argv[10]; const char *simuldata; gboolean nowakeup; uid_t caller_uid; PROFILE ("device_drive_ata_smart_refresh_data_authorized_cb(device=%s) start", device->priv->native_path); daemon_local_get_uid (device->priv->daemon, &caller_uid, context); simuldata = NULL; nowakeup = FALSE; for (n = 0; options[n] != NULL; n++) { if (g_str_has_prefix (options[n], "simulate=")) { if (context != NULL) { if (caller_uid != 0) { throw_error (context, ERROR_FAILED, "Only uid 0 may use the simulate= option"); goto out; } } simuldata = (const char *) options[n] + 9; device_set_drive_ata_smart_is_available (device, TRUE); } else if (strcmp (options[n], "nowakeup") == 0) { nowakeup = TRUE; } } if (!device->priv->drive_ata_smart_is_available) { throw_error (context, ERROR_FAILED, "Device does not support ATA SMART"); goto out; } if (simuldata != NULL) { n = 0; argv[n++] = "base64"; /* provided by coreutils */ argv[n++] = (char *) simuldata; argv[n++] = NULL; } else { n = 0; argv[n++] = "udisks-helper-ata-smart-collect"; argv[n++] = device->priv->device_file; argv[n++] = nowakeup ? "1" : "0"; argv[n++] = NULL; } if (!job_new (context, NULL, /* don't run this as a job */ FALSE, device, argv, NULL, drive_ata_smart_refresh_data_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; PROFILE ("device_drive_ata_smart_refresh_data_authorized_cb(device=%s) end", device->priv->native_path); } /* may be called with context==NULL */ gboolean device_drive_ata_smart_refresh_data (Device *device, char **options, DBusGMethodInvocation *context) { const gchar *action_id; action_id = NULL; if (context != NULL) { action_id = "org.freedesktop.udisks.drive-ata-smart-refresh"; } daemon_local_check_auth (device->priv->daemon, device, action_id, "DriveAtaSmartRefreshData", TRUE, device_drive_ata_smart_refresh_data_authorized_cb, context, 1, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void drive_ata_smart_initiate_selftest_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { char *options[] = { NULL }; /* no matter what happened, refresh the data */ device_drive_ata_smart_refresh_data (device, options, NULL); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error running self test: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_drive_ata_smart_initiate_selftest_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *test = user_data_elements[0]; /* TODO: use options */ //gchar **options = user_data_elements[1]; int n; char *argv[10]; const gchar *job_name; if (g_strcmp0 (test, "short") == 0) { job_name = "DriveAtaSmartSelftestShort"; } else if (g_strcmp0 (test, "extended") == 0) { job_name = "DriveAtaSmartSelftestExtended"; } else if (g_strcmp0 (test, "conveyance") == 0) { job_name = "DriveAtaSmartSelftestConveyance"; } else { throw_error (context, ERROR_FAILED, "Malformed test"); goto out; } n = 0; argv[n++] = "udisks-helper-ata-smart-selftest"; argv[n++] = device->priv->device_file; argv[n++] = (char *) test; argv[n++] = NULL; if (!job_new (context, job_name, TRUE, device, argv, NULL, drive_ata_smart_initiate_selftest_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean device_drive_ata_smart_initiate_selftest (Device *device, const char *test, gchar **options, DBusGMethodInvocation *context) { if (!device->priv->drive_ata_smart_is_available) { throw_error (context, ERROR_FAILED, "Device does not support ATA SMART"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.drive-ata-smart-selftest", "DriveAtaSmartInitiateSelftest", TRUE, device_drive_ata_smart_initiate_selftest_authorized_cb, context, 2, g_strdup (test), g_free, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_md_stop_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* the kernel side of md currently doesn't emit a 'changed' event so * generate one such that the md device can disappear from our * database */ device_generate_kernel_change_event (device); dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error stopping array: mdadm exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_linux_md_stop_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { /* TODO: use options */ //gchar **options = user_data_elements[0]; int n; char *argv[10]; n = 0; argv[n++] = "mdadm"; argv[n++] = "--stop"; argv[n++] = device->priv->device_file; argv[n++] = NULL; if (!job_new (context, "LinuxMdStop", TRUE, device, argv, NULL, linux_md_stop_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean device_linux_md_stop (Device *device, char **options, DBusGMethodInvocation *context) { if (!device->priv->device_is_linux_md) { throw_error (context, ERROR_FAILED, "Device is not a Linux md drive"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.linux-md", "LinuxMdStop", TRUE, device_linux_md_stop_authorized_cb, context, 1, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_md_check_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { guint64 num_errors; num_errors = sysfs_get_uint64 (device->priv->native_path, "md/mismatch_cnt"); dbus_g_method_return (context, num_errors); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error checking array: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_linux_md_check_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gchar **options = user_data_elements[0]; int n, m; char *argv[128]; const gchar *job_name; if (!device->priv->device_is_linux_md) { throw_error (context, ERROR_FAILED, "Device is not a Linux md drive"); goto out; } if (g_strcmp0 (device->priv->linux_md_sync_action, "idle") != 0) { throw_error (context, ERROR_FAILED, "Array is not idle"); goto out; } n = 0; argv[n++] = "udisks-helper-linux-md-check"; argv[n++] = device->priv->device_file; argv[n++] = device->priv->native_path; for (m = 0; options[m] != NULL; m++) { if (n >= (int) sizeof(argv) - 1) { throw_error (context, ERROR_FAILED, "Too many options"); goto out; } /* the helper will validate each option */ argv[n++] = (char *) options[m]; } argv[n++] = NULL; job_name = "LinuxMdCheck"; for (n = 0; options != NULL && options[n] != NULL; n++) if (strcmp (options[n], "repair") == 0) job_name = "LinuxMdRepair"; if (!job_new (context, job_name, TRUE, device, argv, NULL, linux_md_check_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean device_linux_md_check (Device *device, char **options, DBusGMethodInvocation *context) { guint n; const gchar *job_name; job_name = "LinuxMdCheck"; for (n = 0; options != NULL && options[n] != NULL; n++) if (strcmp (options[n], "repair") == 0) job_name = "LinuxMdRepair"; if (!device->priv->device_is_linux_md) { throw_error (context, ERROR_FAILED, "Device is not a Linux md drive"); goto out; } if (g_strcmp0 (device->priv->linux_md_sync_action, "idle") != 0) { throw_error (context, ERROR_FAILED, "Array is not idle"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.linux-md", job_name, TRUE, device_linux_md_check_authorized_cb, context, 1, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_md_add_spare_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { Device *slave = DEVICE (user_data); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* the slave got new metadata on it; reread that */ device_generate_kernel_change_event (slave); /* the kernel side of md currently doesn't emit a 'changed' event so * generate one since state may have changed (e.g. rebuild started etc.) */ device_generate_kernel_change_event (device); dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error adding spare: mdadm exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_linux_md_add_spare_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { char *component = user_data_elements[0]; /* TODO: use options */ //char **options = user_data_elements[1]; int n; char *argv[10]; GError *error; Device *slave; error = NULL; slave = daemon_local_find_by_object_path (device->priv->daemon, component); if (slave == NULL) { throw_error (context, ERROR_FAILED, "Component doesn't exist"); goto out; } /* it's fine if the given device isn't a Linux md component _yet_; think * hot adding a new disk if an old one failed */ if (device_local_is_busy (slave, TRUE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } /* TODO: check component size is OK */ n = 0; argv[n++] = "mdadm"; argv[n++] = "--manage"; argv[n++] = device->priv->device_file; argv[n++] = "--add"; argv[n++] = slave->priv->device_file; argv[n++] = "--force"; argv[n++] = NULL; if (!job_new (context, "LinuxMdAddSpare", TRUE, device, argv, NULL, linux_md_add_spare_completed_cb, FALSE, g_object_ref (slave), g_object_unref)) { goto out; } out: ; } gboolean device_linux_md_add_spare (Device *device, char *component, char **options, DBusGMethodInvocation *context) { if (!device->priv->device_is_linux_md) { throw_error (context, ERROR_FAILED, "Device is not a Linux md drive"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.linux-md", "LinuxMdAddSpare", TRUE, device_linux_md_add_spare_authorized_cb, context, 2, g_strdup (component), g_free, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_md_expand_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* the kernel side of md currently doesn't emit a 'changed' event so * generate one since state may have changed (e.g. rebuild started etc.) */ device_generate_kernel_change_event (device); dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error expanding array: helper script exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_linux_md_expand_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gchar **components = user_data_elements[0]; /* TODO: use options */ //char **options = user_data_elements[1]; guint n; GError *error; GPtrArray *args; gint new_num_raid_devices; gchar *backup_filename; gchar *md_basename; error = NULL; args = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (args, g_strdup ("udisks-helper-mdadm-expand")); g_ptr_array_add (args, g_strdup (device->priv->device_file)); new_num_raid_devices = device->priv->linux_md_num_raid_devices + g_strv_length (components); g_ptr_array_add (args, g_strdup_printf ("%d", new_num_raid_devices)); /* TODO: choose a better name and better location */ md_basename = g_path_get_basename (device->priv->device_file); backup_filename = g_strdup_printf ("/root/udisks-mdadm-expand-backup-file-%s-at-%" G_GUINT64_FORMAT, md_basename, (guint64) time (NULL)); g_free (md_basename); g_ptr_array_add (args, backup_filename); for (n = 0; components != NULL && components[n] != NULL; n++) { Device *slave; slave = daemon_local_find_by_object_path (device->priv->daemon, components[n]); if (slave == NULL) { throw_error (context, ERROR_FAILED, "Component with object path %s doesn't exist", components[n]); goto out; } if (device_local_is_busy (slave, TRUE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } g_ptr_array_add (args, g_strdup (slave->priv->device_file)); } g_ptr_array_add (args, NULL); if (!job_new (context, "LinuxMdExpand", TRUE, device, (char **) args->pdata, NULL, linux_md_expand_completed_cb, FALSE, NULL, NULL)) { goto out; } out: g_ptr_array_free (args, TRUE); } gboolean device_linux_md_expand (Device *device, GPtrArray *components, char **options, DBusGMethodInvocation *context) { gchar **strv; guint n; if (!device->priv->device_is_linux_md) { throw_error (context, ERROR_FAILED, "Device is not a Linux md drive"); goto out; } strv = (gchar **) g_new0 (gchar*, components->len + 1); for (n = 0; n < components->len; n++) strv[n] = g_strdup (components->pdata[n]); daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.linux-md", "LinuxMdExpand", TRUE, device_linux_md_expand_authorized_cb, context, 2, strv, g_strfreev, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { int refcount; DBusGMethodInvocation *context; Device *slave; char **options; guint device_changed_signal_handler_id; guint device_changed_timeout_id; } RemoveComponentData; static RemoveComponentData * remove_component_data_new (DBusGMethodInvocation *context, Device *slave, char **options) { RemoveComponentData *data; data = g_new0 (RemoveComponentData, 1); data->refcount = 1; data->context = context; data->slave = g_object_ref (slave); data->options = g_strdupv (options); return data; } static RemoveComponentData * remove_component_data_ref (RemoveComponentData *data) { data->refcount++; return data; } static void remove_component_data_unref (RemoveComponentData *data) { data->refcount--; if (data->refcount == 0) { g_object_unref (data->slave); g_free (data->options); g_free (data); } } static void linux_md_remove_component_device_changed_cb (Daemon *daemon, const char *object_path, gpointer user_data) { RemoveComponentData *data = user_data; Device *device; GError *error; error = NULL; device = daemon_local_find_by_object_path (daemon, object_path); if (device == data->slave) { if (device_local_is_busy (data->slave, FALSE, TRUE, &error)) { dbus_g_method_return_error (data->context, error); g_error_free (error); } else { gchar *fs_create_options[] = { NULL }; /* yay! now scrub it! */ device_filesystem_create (data->slave, "empty", fs_create_options, data->context); /* TODO: leaking data? */ g_signal_handler_disconnect (daemon, data->device_changed_signal_handler_id); g_source_remove (data->device_changed_timeout_id); } } } static gboolean linux_md_remove_component_device_not_seen_cb (gpointer user_data) { RemoveComponentData *data = user_data; throw_error (data->context, ERROR_FAILED, "Error removing component: timeout (10s) waiting for slave to stop being busy"); g_signal_handler_disconnect (data->slave->priv->daemon, data->device_changed_signal_handler_id); remove_component_data_unref (data); return FALSE; } static void linux_md_remove_component_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { RemoveComponentData *data = user_data; /* the slave got new metadata on it; reread that */ device_generate_kernel_change_event (data->slave); /* the kernel side of md currently doesn't emit a 'changed' event so * generate one since state may have changed (e.g. rebuild started etc.) */ device_generate_kernel_change_event (device); if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { /* wait for the slave to be busy, then start erasing it */ data->device_changed_signal_handler_id = g_signal_connect_after (device->priv->daemon, "device-changed", (GCallback) linux_md_remove_component_device_changed_cb, remove_component_data_ref (data)); /* set up timeout for error reporting if waiting failed * * (the signal handler and the timeout handler share the ref to data * as one will cancel the other) */ data->device_changed_timeout_id = g_timeout_add (10 * 1000, linux_md_remove_component_device_not_seen_cb, data); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error removing component: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_linux_md_remove_component_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { char *component = user_data_elements[0]; char **options = user_data_elements[1]; int n, m; char *argv[128]; Device *slave; slave = daemon_local_find_by_object_path (device->priv->daemon, component); if (slave == NULL) { throw_error (context, ERROR_FAILED, "Component doesn't exist"); goto out; } /* check that it really is a component of the md device */ for (n = 0; n < (int) device->priv->linux_md_slaves->len; n++) { if (strcmp (component, device->priv->linux_md_slaves->pdata[n]) == 0) break; } if (n == (int) device->priv->linux_md_slaves->len) { throw_error (context, ERROR_FAILED, "Component isn't part of the running array"); goto out; } n = 0; argv[n++] = "udisks-helper-linux-md-remove-component"; argv[n++] = device->priv->device_file; argv[n++] = slave->priv->device_file; for (m = 0; options[m] != NULL; m++) { if (n >= (int) sizeof(argv) - 1) { throw_error (context, ERROR_FAILED, "Too many options"); goto out; } /* the helper will validate each option */ argv[n++] = (char *) options[m]; } argv[n++] = NULL; if (!job_new (context, "LinuxMdRemoveComponent", TRUE, device, argv, NULL, linux_md_remove_component_completed_cb, FALSE, remove_component_data_new (context, slave, options), (GDestroyNotify) remove_component_data_unref)) { goto out; } out: ; } gboolean device_linux_md_remove_component (Device *device, char *component, char **options, DBusGMethodInvocation *context) { if (!device->priv->device_is_linux_md) { throw_error (context, ERROR_FAILED, "Device is not a Linux md drive"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.linux-md", "LinuxMdRemoveComponent", TRUE, device_linux_md_remove_component_authorized_cb, context, 2, g_strdup (component), g_free, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { int refcount; guint device_added_signal_handler_id; guint device_added_timeout_id; DBusGMethodInvocation *context; Daemon *daemon; char *uuid; } LinuxMdStartData; static LinuxMdStartData * linux_md_start_data_new (DBusGMethodInvocation *context, Daemon *daemon, const char *uuid) { LinuxMdStartData *data; data = g_new0 (LinuxMdStartData, 1); data->refcount = 1; data->context = context; data->daemon = g_object_ref (daemon); data->uuid = g_strdup (uuid); return data; } static LinuxMdStartData * linux_md_start_data_ref (LinuxMdStartData *data) { data->refcount++; return data; } static void linux_md_start_data_unref (LinuxMdStartData *data) { data->refcount--; if (data->refcount == 0) { g_object_unref (data->daemon); g_free (data->uuid); g_free (data); } } static void linux_md_start_device_added_cb (Daemon *daemon, const char *object_path, gpointer user_data) { LinuxMdStartData *data = user_data; Device *device; /* check the device is the one we're looking for */ device = daemon_local_find_by_object_path (daemon, object_path); if (device != NULL && device->priv->device_is_linux_md) { /* TODO: actually check this properly by looking at slaves vs. components */ /* yay! it is.. return value to the user */ dbus_g_method_return (data->context, object_path); g_signal_handler_disconnect (daemon, data->device_added_signal_handler_id); g_source_remove (data->device_added_timeout_id); linux_md_start_data_unref (data); } } static gboolean linux_md_start_device_not_seen_cb (gpointer user_data) { LinuxMdStartData *data = user_data; throw_error (data->context, ERROR_FAILED, "Error assembling array: timeout (10s) waiting for array to show up"); g_signal_handler_disconnect (data->daemon, data->device_added_signal_handler_id); linux_md_start_data_unref (data); return FALSE; } /* NOTE: This is job completion callback from a method on the daemon, not the device. */ static void linux_md_start_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { LinuxMdStartData *data = user_data; if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { GList *l; GList *devices; char *objpath; /* see if the component appeared already */ objpath = NULL; devices = daemon_local_get_all_devices (data->daemon); for (l = devices; l != NULL; l = l->next) { Device *device = DEVICE (l->data); if (device->priv->device_is_linux_md) { /* TODO: check properly */ /* yup, return to caller */ objpath = device->priv->object_path; break; } } g_list_free (devices); if (objpath != NULL) { dbus_g_method_return (context, objpath); } else { /* sit around and wait for the md array to appear */ /* sit around wait for the cleartext device to appear */ data->device_added_signal_handler_id = g_signal_connect_after (data->daemon, "device-added", (GCallback) linux_md_start_device_added_cb, linux_md_start_data_ref (data)); /* set up timeout for error reporting if waiting failed * * (the signal handler and the timeout handler share the ref to data * as one will cancel the other) */ data->device_added_timeout_id = g_timeout_add (10 * 1000, linux_md_start_device_not_seen_cb, data); } } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error assembling array: mdadm exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } /* NOTE: This is a method on the daemon, not the device. */ static void daemon_linux_md_start_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gchar **components_as_strv = user_data_elements[0]; /* TODO: use options */ //gchar **options = user_data_elements[1]; int n; int m; char *argv[128]; GError *error; char *uuid; char *md_device_file; uuid = NULL; md_device_file = NULL; error = NULL; /* check that all given components exist, that they are indeed linux-md-components and * that their uuid agrees */ for (n = 0; components_as_strv[n] != NULL; n++) { Device *slave; const char *component_objpath = components_as_strv[n]; slave = daemon_local_find_by_object_path (daemon, component_objpath); if (slave == NULL) { throw_error (context, ERROR_FAILED, "Component %s doesn't exist", component_objpath); goto out; } if (!slave->priv->device_is_linux_md_component) { throw_error (context, ERROR_FAILED, "%s is not a linux-md component", component_objpath); goto out; } if (n == 0) { uuid = g_strdup (slave->priv->linux_md_component_uuid); if (uuid == NULL) { throw_error (context, ERROR_FAILED, "no uuid for one of the components"); goto out; } } else { const char *this_uuid; this_uuid = slave->priv->linux_md_component_uuid; if (this_uuid == NULL || strcmp (uuid, this_uuid) != 0) { throw_error (context, ERROR_FAILED, "uuid mismatch between given components"); goto out; } } if (device_local_is_busy (slave, FALSE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } } /* find an unused md minor... Man, I wish mdadm could do this itself; this is slightly racy */ for (n = 0; TRUE; n++) { char *native_path; char *array_state; /* TODO: move to /sys/class/block instead */ native_path = g_strdup_printf ("/sys/block/md%d", n); if (!sysfs_file_exists (native_path, "md/array_state")) { /* Apparently this slot is free since there is no such file. So let's peruse it. */ g_free (native_path); break; } else { array_state = sysfs_get_string (native_path, "md/array_state"); g_strstrip (array_state); if (strcmp (array_state, "clear") == 0) { /* It's clear! Let's use it! */ g_free (array_state); g_free (native_path); break; } g_free (array_state); } g_free (native_path); } md_device_file = g_strdup_printf ("/dev/md%d", n); n = 0; argv[n++] = "mdadm"; argv[n++] = "--assemble"; argv[n++] = md_device_file; argv[n++] = "--run"; for (m = 0; components_as_strv[m] != NULL; m++) { Device *slave; const char *component_objpath = components_as_strv[m]; slave = daemon_local_find_by_object_path (daemon, component_objpath); if (slave == NULL) { throw_error (context, ERROR_FAILED, "Component %s doesn't exist", component_objpath); goto out; } if (n >= (int) sizeof(argv) - 1) { throw_error (context, ERROR_FAILED, "Too many components"); goto out; } argv[n++] = (char *) slave->priv->device_file; } argv[n++] = NULL; if (!job_new (context, "LinuxMdStart", TRUE, NULL, argv, NULL, linux_md_start_completed_cb, FALSE, linux_md_start_data_new (context, daemon, uuid), (GDestroyNotify) linux_md_start_data_unref)) { goto out; } out: g_free (uuid); g_free (md_device_file); } /* NOTE: This is a method on the daemon, not the device. */ gboolean daemon_linux_md_start (Daemon *daemon, GPtrArray *components, char **options, DBusGMethodInvocation *context) { gchar **components_as_strv; guint n; components_as_strv = g_new0 (gchar *, components->len + 1); for (n = 0; n < components->len; n++) components_as_strv[n] = g_strdup (components->pdata[n]); daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-md", "LinuxMdStart", TRUE, daemon_linux_md_start_authorized_cb, context, 2, components_as_strv, g_strfreev, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { int refcount; guint device_added_signal_handler_id; guint device_added_timeout_id; DBusGMethodInvocation *context; Daemon *daemon; char *first_component_objpath; } LinuxMdCreateData; static LinuxMdCreateData * linux_md_create_data_new (DBusGMethodInvocation *context, Daemon *daemon, const char *first_component_objpath) { LinuxMdCreateData *data; data = g_new0 (LinuxMdCreateData, 1); data->refcount = 1; data->context = context; data->daemon = g_object_ref (daemon); data->first_component_objpath = g_strdup (first_component_objpath); return data; } static LinuxMdCreateData * linux_md_create_data_ref (LinuxMdCreateData *data) { data->refcount++; return data; } static void linux_md_create_data_unref (LinuxMdCreateData *data) { data->refcount--; if (data->refcount == 0) { g_object_unref (data->daemon); g_free (data->first_component_objpath); g_free (data); } } static void linux_md_create_device_added_cb (Daemon *daemon, const char *object_path, gpointer user_data) { LinuxMdCreateData *data = user_data; Device *device; /* check the device is the one we're looking for */ device = daemon_local_find_by_object_path (daemon, object_path); if (device != NULL && device->priv->device_is_linux_md) { /* TODO: actually check this properly by looking at slaves vs. components */ /* yay! it is.. return value to the user */ dbus_g_method_return (data->context, object_path); g_signal_handler_disconnect (daemon, data->device_added_signal_handler_id); g_source_remove (data->device_added_timeout_id); linux_md_create_data_unref (data); } } static gboolean linux_md_create_device_not_seen_cb (gpointer user_data) { LinuxMdCreateData *data = user_data; throw_error (data->context, ERROR_FAILED, "Error assembling array: timeout (10s) waiting for array to show up"); g_signal_handler_disconnect (data->daemon, data->device_added_signal_handler_id); linux_md_create_data_unref (data); return FALSE; } /* NOTE: This is job completion callback from a method on the daemon, not the device. */ static void linux_md_create_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { LinuxMdCreateData *data = user_data; if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { GList *l; GList *devices; char *objpath; /* see if the component appeared already */ objpath = NULL; devices = daemon_local_get_all_devices (data->daemon); for (l = devices; l != NULL; l = l->next) { Device *device = DEVICE (l->data); if (device->priv->device_is_linux_md) { /* TODO: check properly */ /* yup, return to caller */ objpath = device->priv->object_path; break; } } g_list_free (devices); if (objpath != NULL) { dbus_g_method_return (context, objpath); } else { /* sit around and wait for the md array to appear */ /* sit around wait for the cleartext device to appear */ data->device_added_signal_handler_id = g_signal_connect_after (data->daemon, "device-added", (GCallback) linux_md_create_device_added_cb, linux_md_create_data_ref (data)); /* set up timeout for error reporting if waiting failed * * (the signal handler and the timeout handler share the ref to data * as one will cancel the other) */ data->device_added_timeout_id = g_timeout_add (10 * 1000, linux_md_create_device_not_seen_cb, data); } } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error assembling array: mdadm exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } /* NOTE: This is a method on the daemon, not the device. */ static void daemon_linux_md_create_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gchar **components_as_strv = user_data_elements[0]; gchar *level = user_data_elements[1]; guint64 stripe_size = *((guint64*) user_data_elements[2]); gchar *name = user_data_elements[3]; /* TODO: use options */ //gchar **options = user_data_elements[4]; int n; int m; char *argv[128]; GError *error; gchar *md_device_file; gchar *num_raid_devices_as_str; gchar *stripe_size_as_str; gboolean use_bitmap; gboolean use_chunk; md_device_file = NULL; num_raid_devices_as_str = NULL; stripe_size_as_str = NULL; error = NULL; /* sanity-check level */ use_bitmap = FALSE; use_chunk = FALSE; if (g_strcmp0 (level, "raid0") == 0) { use_chunk = TRUE; } else if (g_strcmp0 (level, "raid1") == 0) { if (stripe_size > 0) { throw_error (context, ERROR_FAILED, "Stripe size doesn't make sense for RAID-1"); goto out; } } else if (g_strcmp0 (level, "raid4") == 0 || g_strcmp0 (level, "raid5") == 0 || g_strcmp0 (level, "raid6") == 0 || g_strcmp0 (level, "raid10") == 0) { use_bitmap = TRUE; use_chunk = TRUE; } else { throw_error (context, ERROR_FAILED, "Invalid level `%s'", level); goto out; } /* check that all given components exist and that they are not busy */ for (n = 0; components_as_strv[n] != NULL; n++) { Device *slave; const char *component_objpath = components_as_strv[n]; slave = daemon_local_find_by_object_path (daemon, component_objpath); if (slave == NULL) { throw_error (context, ERROR_FAILED, "Component %s doesn't exist", component_objpath); goto out; } if (device_local_is_busy (slave, FALSE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } } /* find an unused md minor... Man, I wish mdadm could do this itself; this is slightly racy */ for (n = 0; TRUE; n++) { char *native_path; char *array_state; /* TODO: move to /sys/class/block instead */ native_path = g_strdup_printf ("/sys/block/md%d", n); if (!sysfs_file_exists (native_path, "md/array_state")) { /* Apparently this slot is free since there is no such file. So let's peruse it. */ g_free (native_path); break; } else { array_state = sysfs_get_string (native_path, "md/array_state"); g_strstrip (array_state); if (strcmp (array_state, "clear") == 0) { /* It's clear! Let's use it! */ g_free (array_state); g_free (native_path); break; } g_free (array_state); } g_free (native_path); } md_device_file = g_strdup_printf ("/dev/md%d", n); num_raid_devices_as_str = g_strdup_printf ("%d", g_strv_length (components_as_strv)); if (stripe_size > 0) stripe_size_as_str = g_strdup_printf ("%d", ((gint) stripe_size) / 1024); n = 0; argv[n++] = "mdadm"; argv[n++] = "--create"; argv[n++] = md_device_file; argv[n++] = "--level"; argv[n++] = level; argv[n++] = "--raid-devices"; argv[n++] = num_raid_devices_as_str; argv[n++] = "--metadata"; argv[n++] = "1.2"; argv[n++] = "--name"; argv[n++] = name; argv[n++] = "--homehost"; argv[n++] = ""; if (use_bitmap) { argv[n++] = "--bitmap"; argv[n++] = "internal"; } if (use_chunk && stripe_size_as_str != NULL) { argv[n++] = "--chunk"; argv[n++] = stripe_size_as_str; } for (m = 0; components_as_strv[m] != NULL; m++) { Device *slave; const char *component_objpath = components_as_strv[m]; slave = daemon_local_find_by_object_path (daemon, component_objpath); if (slave == NULL) { throw_error (context, ERROR_FAILED, "Component %s doesn't exist", component_objpath); goto out; } if (n >= (int) sizeof(argv) - 1) { throw_error (context, ERROR_FAILED, "Too many components"); goto out; } argv[n++] = (char *) slave->priv->device_file; } argv[n++] = NULL; //for (m = 0; argv[m] != NULL; m++) // g_debug ("arg[%d] = `%s'", m, argv[m]); if (!job_new (context, "LinuxMdCreate", TRUE, NULL, argv, NULL, linux_md_create_completed_cb, FALSE, linux_md_create_data_new (context, daemon, components_as_strv[0]), (GDestroyNotify) linux_md_create_data_unref)) { goto out; } out: g_free (md_device_file); g_free (num_raid_devices_as_str); g_free (stripe_size_as_str); } /* NOTE: This is a method on the daemon, not the device. */ gboolean daemon_linux_md_create (Daemon *daemon, GPtrArray *components, char *level, guint64 stripe_size, char *name, char **options, DBusGMethodInvocation *context) { gchar **components_as_strv; guint n; components_as_strv = g_new0 (gchar *, components->len + 1); for (n = 0; n < components->len; n++) components_as_strv[n] = g_strdup (components->pdata[n]); daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-md", "LinuxMdCreate", TRUE, daemon_linux_md_create_authorized_cb, context, 4, components_as_strv, g_strfreev, g_strdup (level), g_free, g_memdup (&stripe_size, sizeof(guint64)), g_free, g_strdup (name), g_free, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { char *mount_path; ForceRemovalCompleteFunc fr_callback; gpointer fr_user_data; } ForceUnmountData; static ForceUnmountData * force_unmount_data_new (const gchar *mount_path, ForceRemovalCompleteFunc fr_callback, gpointer fr_user_data) { ForceUnmountData *data; data = g_new0 (ForceUnmountData, 1); data->mount_path = g_strdup (mount_path); data->fr_callback = fr_callback; data->fr_user_data = fr_user_data; return data; } static void force_unmount_data_unref (ForceUnmountData *data) { g_free (data->mount_path); g_free (data); } static void force_unmount_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { ForceUnmountData *data = user_data; if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { g_print ("**** NOTE: Successfully force unmounted device %s\n", device->priv->device_file); /* update_info_mount_state() will update the mounts file and clean up the directory if needed */ update_info (device); if (data->fr_callback != NULL) data->fr_callback (device, TRUE, data->fr_user_data); } else { g_print ("**** NOTE: force unmount failed: %s\n", stderr); if (data->fr_callback != NULL) data->fr_callback (device, FALSE, data->fr_user_data); } } static void force_unmount (Device *device, ForceRemovalCompleteFunc callback, gpointer user_data) { int n; char *argv[16]; const gchar *mount_path; mount_path = ((gchar **) device->priv->device_mount_paths->pdata)[0]; n = 0; argv[n++] = "umount"; /* on Linux, we only have lazy unmount for now */ argv[n++] = "-l"; argv[n++] = (gchar *) mount_path; argv[n++] = NULL; if (!job_new (NULL, "ForceUnmount", FALSE, device, argv, NULL, force_unmount_completed_cb, FALSE, force_unmount_data_new (mount_path, callback, user_data), (GDestroyNotify) force_unmount_data_unref)) { g_warning ("Couldn't spawn unmount for force unmounting %s", mount_path); if (callback != NULL) callback (device, FALSE, user_data); } } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { Device *device; char *dm_name; ForceRemovalCompleteFunc fr_callback; gpointer fr_user_data; } ForceLuksTeardownData; static void force_luks_teardown_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { ForceLuksTeardownData *data = user_data; if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { g_print ("**** NOTE: Successfully teared down luks device %s\n", device->priv->device_file); if (data->fr_callback != NULL) data->fr_callback (device, TRUE, data->fr_user_data); } else { g_print ("**** NOTE: force luks teardown failed: %s\n", stderr); if (data->fr_callback != NULL) data->fr_callback (device, FALSE, data->fr_user_data); } } static ForceLuksTeardownData * force_luks_teardown_data_new (Device *device, const char *dm_name, ForceRemovalCompleteFunc fr_callback, gpointer fr_user_data) { ForceLuksTeardownData *data; data = g_new0 (ForceLuksTeardownData, 1); data->device = g_object_ref (device); data->dm_name = g_strdup (dm_name); data->fr_callback = fr_callback; data->fr_user_data = fr_user_data; return data; } static void force_luks_teardown_data_unref (ForceLuksTeardownData *data) { if (data->device != NULL) g_object_unref (data->device); g_free (data->dm_name); g_free (data); } static void force_luks_teardown_cleartext_done (Device *device, gboolean success, gpointer user_data) { int n; char *argv[16]; ForceLuksTeardownData *data = user_data; if (!success) { if (data->fr_callback != NULL) data->fr_callback (data->device, FALSE, data->fr_user_data); force_luks_teardown_data_unref (data); goto out; } /* ok, clear text device is out of the way; now tear it down */ n = 0; argv[n++] = "cryptsetup"; argv[n++] = "luksClose"; argv[n++] = data->dm_name; argv[n++] = NULL; //g_debug ("doing cryptsetup luksClose %s", data->dm_name); if (!job_new (NULL, "ForceLuksTeardown", FALSE, data->device, argv, NULL, force_luks_teardown_completed_cb, FALSE, data, (GDestroyNotify) force_luks_teardown_data_unref)) { g_warning ("Couldn't spawn cryptsetup for force teardown for device %s", data->dm_name); if (data->fr_callback != NULL) data->fr_callback (data->device, FALSE, data->fr_user_data); force_luks_teardown_data_unref (data); } out: ; } static void force_luks_teardown (Device *device, Device *cleartext_device, ForceRemovalCompleteFunc callback, gpointer user_data) { /* first we gotta force remove the clear text device */ force_removal (cleartext_device, force_luks_teardown_cleartext_done, force_luks_teardown_data_new (device, cleartext_device->priv->dm_name, callback, user_data)); } /*--------------------------------------------------------------------------------------------------------------*/ static void force_removal (Device *device, ForceRemovalCompleteFunc callback, gpointer user_data) { //g_debug ("in force removal for %s", device->priv->device_file); /* Device is going bye bye. If this device is * * - Mounted by us, then forcibly unmount it. * * - If it's a luks device, check if there's cleartext * companion. If so, tear it down if it was setup by us. * */ if (device->priv->device_is_mounted && device->priv->device_mount_paths->len > 0) { gboolean remove_dir_on_unmount; if (mount_file_has_device (device->priv->device_file, NULL, &remove_dir_on_unmount) || is_device_in_fstab (device, NULL)) { g_print ("**** NOTE: Force unmounting device %s\n", device->priv->device_file); force_unmount (device, callback, user_data); goto pending; } } if (device->priv->id_usage != NULL && device->priv->luks_holder != NULL && strcmp (device->priv->id_usage, "crypto") == 0) { Device *d; /* look for cleartext device */ d = daemon_local_find_by_object_path (device->priv->daemon, device->priv->luks_holder); if (strcmp (d->priv->object_path, device->priv->luks_holder) == 0) { /* Check whether it is set up by us */ if (d->priv->dm_name != NULL && g_str_has_prefix (d->priv->dm_name, "udisks-luks-uuid-")) { g_print ("**** NOTE: Force luks teardown device %s (cleartext %s)\n", device->priv->device_file, d->priv->device_file); /* Gotcha */ force_luks_teardown (device, d, callback, user_data); goto pending; } } } /* nothing to force remove */ if (callback != NULL) callback (device, TRUE, user_data); pending: ; } /*--------------------------------------------------------------------------------------------------------------*/ static void polling_inhibitor_disconnected_cb (Inhibitor *inhibitor, Device *device) { device->priv->polling_inhibitors = g_list_remove (device->priv->polling_inhibitors, inhibitor); g_signal_handlers_disconnect_by_func (inhibitor, polling_inhibitor_disconnected_cb, device); g_object_unref (inhibitor); update_info (device); drain_pending_changes (device, FALSE); daemon_local_update_poller (device->priv->daemon); } static void device_drive_inhibit_polling_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gchar **options = user_data_elements[0]; Inhibitor *inhibitor; guint n; for (n = 0; options[n] != NULL; n++) { const char *option = options[n]; throw_error (context, ERROR_INVALID_OPTION, "Unknown option %s", option); goto out; } inhibitor = inhibitor_new (context); device->priv->polling_inhibitors = g_list_prepend (device->priv->polling_inhibitors, inhibitor); g_signal_connect (inhibitor, "disconnected", G_CALLBACK (polling_inhibitor_disconnected_cb), device); update_info (device); drain_pending_changes (device, FALSE); daemon_local_update_poller (device->priv->daemon); dbus_g_method_return (context, inhibitor_get_cookie (inhibitor)); out: ; } gboolean device_drive_inhibit_polling (Device *device, char **options, DBusGMethodInvocation *context) { if (!device->priv->device_is_drive) { throw_error (context, ERROR_FAILED, "Device is not a drive"); goto out; } if (!device->priv->device_is_media_change_detection_inhibitable) { throw_error (context, ERROR_FAILED, "Media detection cannot be inhibited"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.inhibit-polling", "DriveInhibitPolling", TRUE, device_drive_inhibit_polling_authorized_cb, context, 1, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ gboolean device_drive_uninhibit_polling (Device *device, char *cookie, DBusGMethodInvocation *context) { const gchar *sender; Inhibitor *inhibitor; GList *l; sender = dbus_g_method_get_sender (context); inhibitor = NULL; for (l = device->priv->polling_inhibitors; l != NULL; l = l->next) { Inhibitor *i = INHIBITOR (l->data); if (g_strcmp0 (inhibitor_get_unique_dbus_name (i), sender) == 0 && g_strcmp0 (inhibitor_get_cookie (i), cookie) == 0) { inhibitor = i; break; } } if (inhibitor == NULL) { throw_error (context, ERROR_FAILED, "No such inhibitor"); goto out; } device->priv->polling_inhibitors = g_list_remove (device->priv->polling_inhibitors, inhibitor); g_object_unref (inhibitor); update_info (device); drain_pending_changes (device, FALSE); daemon_local_update_poller (device->priv->daemon); dbus_g_method_return (context); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void drive_poll_media_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { device_generate_kernel_change_event (device); dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error detaching: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_drive_poll_media_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { int n; char *argv[16]; n = 0; argv[n++] = "udisks-helper-drive-poll"; argv[n++] = device->priv->device_file; argv[n++] = NULL; if (!job_new (context, "DrivePollMedia", FALSE, device, argv, NULL, drive_poll_media_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean device_drive_poll_media (Device *device, DBusGMethodInvocation *context) { if (!device->priv->device_is_drive) { throw_error (context, ERROR_FAILED, "Device is not a drive"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.inhibit-polling", "DrivePollMedia", TRUE, device_drive_poll_media_authorized_cb, context, 0); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void spindown_inhibitor_disconnected_cb (Inhibitor *inhibitor, Device *device) { device->priv->spindown_inhibitors = g_list_remove (device->priv->spindown_inhibitors, inhibitor); g_signal_handlers_disconnect_by_func (inhibitor, spindown_inhibitor_disconnected_cb, device); g_object_unref (inhibitor); update_info (device); drain_pending_changes (device, FALSE); daemon_local_update_spindown (device->priv->daemon); } static void device_drive_set_spindown_timeout_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gint timeout_seconds = GPOINTER_TO_INT (user_data_elements[0]); gchar **options = user_data_elements[1]; Inhibitor *inhibitor; guint n; if (!device->priv->device_is_drive) { throw_error (context, ERROR_FAILED, "Device is not a drive"); goto out; } if (!device->priv->drive_can_spindown) { throw_error (context, ERROR_FAILED, "Cannot spindown device"); goto out; } if (timeout_seconds < 1) { throw_error (context, ERROR_FAILED, "Timeout seconds must be at least 1"); goto out; } for (n = 0; options[n] != NULL; n++) { const char *option = options[n]; throw_error (context, ERROR_INVALID_OPTION, "Unknown option %s", option); goto out; } inhibitor = inhibitor_new (context); g_object_set_data (G_OBJECT (inhibitor), "spindown-timeout-seconds", GINT_TO_POINTER (timeout_seconds)); device->priv->spindown_inhibitors = g_list_prepend (device->priv->spindown_inhibitors, inhibitor); g_signal_connect (inhibitor, "disconnected", G_CALLBACK (spindown_inhibitor_disconnected_cb), device); update_info (device); drain_pending_changes (device, FALSE); daemon_local_update_spindown (device->priv->daemon); dbus_g_method_return (context, inhibitor_get_cookie (inhibitor)); out: ; } gboolean device_drive_set_spindown_timeout (Device *device, int timeout_seconds, char **options, DBusGMethodInvocation *context) { if (!device->priv->device_is_drive) { throw_error (context, ERROR_FAILED, "Device is not a drive"); goto out; } if (!device->priv->drive_can_spindown) { throw_error (context, ERROR_FAILED, "Cannot spindown device"); goto out; } if (timeout_seconds < 1) { throw_error (context, ERROR_FAILED, "Timeout seconds must be at least 1"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.drive-set-spindown", "DriveSetSpindownTimeout", TRUE, device_drive_set_spindown_timeout_authorized_cb, context, 2, GINT_TO_POINTER (timeout_seconds), NULL, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ gboolean device_drive_unset_spindown_timeout (Device *device, char *cookie, DBusGMethodInvocation *context) { const gchar *sender; Inhibitor *inhibitor; GList *l; sender = dbus_g_method_get_sender (context); inhibitor = NULL; for (l = device->priv->spindown_inhibitors; l != NULL; l = l->next) { Inhibitor *i = INHIBITOR (l->data); if (g_strcmp0 (inhibitor_get_unique_dbus_name (i), sender) == 0 && g_strcmp0 (inhibitor_get_cookie (i), cookie) == 0) { inhibitor = i; break; } } if (inhibitor == NULL) { throw_error (context, ERROR_FAILED, "No such spindown configurator"); goto out; } device->priv->spindown_inhibitors = g_list_remove (device->priv->spindown_inhibitors, inhibitor); g_object_unref (inhibitor); update_info (device); drain_pending_changes (device, FALSE); daemon_local_update_spindown (device->priv->daemon); dbus_g_method_return (context); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void drive_benchmark_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const gchar *stderr, const gchar *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { GPtrArray *read_transfer_rate_array; GPtrArray *write_transfer_rate_array; GPtrArray *access_time_array; gchar **lines; guint n; GType elem_gtype; elem_gtype = dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT64, G_TYPE_DOUBLE, G_TYPE_INVALID); read_transfer_rate_array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_value_array_free); write_transfer_rate_array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_value_array_free); access_time_array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_value_array_free); lines = g_strsplit (stdout, "\n", 0); for (n = 0; lines != NULL && lines[n] != NULL; n++) { const gchar *line = lines[n]; guint64 offset; gdouble rate; gdouble access_time; GValue elem = { 0 }; if (sscanf (line, "read_transfer_rate: offset %" G_GUINT64_FORMAT " rate %lf", &offset, &rate) == 2) { g_value_init (&elem, elem_gtype); g_value_take_boxed (&elem, dbus_g_type_specialized_construct (elem_gtype)); dbus_g_type_struct_set (&elem, 0, offset, 1, rate, G_MAXUINT); g_ptr_array_add (read_transfer_rate_array, g_value_get_boxed (&elem)); } else if (sscanf (line, "write_transfer_rate: offset %" G_GUINT64_FORMAT " rate %lf", &offset, &rate) == 2) { g_value_init (&elem, elem_gtype); g_value_take_boxed (&elem, dbus_g_type_specialized_construct (elem_gtype)); dbus_g_type_struct_set (&elem, 0, offset, 1, rate, G_MAXUINT); g_ptr_array_add (write_transfer_rate_array, g_value_get_boxed (&elem)); } else if (sscanf (line, "access_time: offset %" G_GUINT64_FORMAT " time %lf", &offset, &access_time) == 2) { g_value_init (&elem, elem_gtype); g_value_take_boxed (&elem, dbus_g_type_specialized_construct (elem_gtype)); dbus_g_type_struct_set (&elem, 0, offset, 1, access_time, G_MAXUINT); g_ptr_array_add (access_time_array, g_value_get_boxed (&elem)); } else { g_warning ("unhandled line %d: `%s'", n, line); } } g_strfreev (lines); dbus_g_method_return (context, read_transfer_rate_array, write_transfer_rate_array, access_time_array); g_ptr_array_unref (read_transfer_rate_array); g_ptr_array_unref (write_transfer_rate_array); g_ptr_array_unref (access_time_array); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error benchmarking: helper exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void device_drive_benchmark_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gboolean do_write_benchmark = GPOINTER_TO_INT (user_data_elements[0]); gchar **options = user_data_elements[1]; gchar *argv[16]; guint n; if (!device->priv->device_is_drive) { throw_error (context, ERROR_FAILED, "Device is not a drive"); goto out; } if (do_write_benchmark) { if (device->priv->device_is_partition_table) { throw_error (context, ERROR_FAILED, "A partition table was detected - write benchmarking requires " "the disk to be completely empty"); goto out; } if (device->priv->id_usage != NULL) { throw_error (context, ERROR_FAILED, "The disk seems to have usage `%s' - write benchmarking requires " "the disk to be completely empty", device->priv->id_usage); goto out; } } for (n = 0; options[n] != NULL; n++) { const char *option = options[n]; throw_error (context, ERROR_INVALID_OPTION, "Unknown option %s", option); goto out; } n = 0; argv[n++] = "udisks-helper-drive-benchmark"; argv[n++] = device->priv->device_file; argv[n++] = do_write_benchmark ? "1" : "0"; argv[n++] = NULL; if (!job_new (context, "DriveBenchmark", TRUE, device, argv, NULL, drive_benchmark_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean device_drive_benchmark (Device *device, gboolean do_write_benchmark, char **options, DBusGMethodInvocation *context) { if (!device->priv->device_is_drive) { throw_error (context, ERROR_FAILED, "Device is not a drive"); goto out; } daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.change", "DriveBenchmark", TRUE, device_drive_benchmark_authorized_cb, context, 2, GINT_TO_POINTER (do_write_benchmark), NULL, g_strdupv (options), g_strfreev); out: return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static const gchar * find_lvm2_vg_name_for_uuid (Daemon *daemon, const gchar *uuid) { GList *l; GList *devices; const gchar *vg_name; vg_name = NULL; devices = daemon_local_get_all_devices (daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (d->priv->device_is_linux_lvm2_pv && g_strcmp0 (uuid, d->priv->linux_lvm2_pv_group_uuid) == 0) { vg_name = d->priv->linux_lvm2_pv_group_name; break; } } g_list_free (devices); return vg_name; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_lvm2_vg_stop_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error stopping LVM2 Volume Group: vgchange exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void daemon_linux_lvm2_vg_stop_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *uuid = user_data_elements[0]; const gchar *vg_name; /* TODO: use options */ //gchar **options = user_data_elements[1]; guint n; gchar *argv[10]; /* Unfortunately vgchange does not (yet - file a bug) accept UUIDs - so find the VG name for this * UUID by looking at PVs */ vg_name = find_lvm2_vg_name_for_uuid (daemon, uuid); if (vg_name == NULL) { throw_error (context, ERROR_FAILED, "Cannot find VG with UUID `%s'", uuid); goto out; } n = 0; argv[n++] = "vgchange"; argv[n++] = "-an"; argv[n++] = (gchar *) vg_name; argv[n++] = NULL; if (!job_new (context, "LinuxLvm2VGStop", TRUE, NULL, argv, NULL, linux_lvm2_vg_stop_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean daemon_linux_lvm2_vg_stop (Daemon *daemon, const gchar *uuid, char **options, DBusGMethodInvocation *context) { daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-lvm2", "LinuxLvm2VGStop", TRUE, daemon_linux_lvm2_vg_stop_authorized_cb, context, 2, g_strdup (uuid), g_free, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_lvm2_vg_start_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error starting LVM2 Volume Group: vgchange exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void daemon_linux_lvm2_vg_start_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *uuid = user_data_elements[0]; const gchar *vg_name; /* TODO: use options */ //gchar **options = user_data_elements[1]; guint n; gchar *argv[10]; /* Unfortunately vgchange does not (yet - file a bug) accept UUIDs - so find the VG name for this * UUID by looking at PVs */ vg_name = find_lvm2_vg_name_for_uuid (daemon, uuid); if (vg_name == NULL) { throw_error (context, ERROR_FAILED, "Cannot find VG with UUID `%s'", uuid); goto out; } n = 0; argv[n++] = "vgchange"; argv[n++] = "-ay"; argv[n++] = (gchar *) vg_name; argv[n++] = NULL; if (!job_new (context, "LinuxLvm2VGStart", TRUE, NULL, argv, NULL, linux_lvm2_vg_start_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean daemon_linux_lvm2_vg_start (Daemon *daemon, const gchar *uuid, char **options, DBusGMethodInvocation *context) { daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-lvm2", "LinuxLvm2VGStart", TRUE, daemon_linux_lvm2_vg_start_authorized_cb, context, 2, g_strdup (uuid), g_free, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static gchar * find_lvm2_lv_name_for_uuids (Daemon *daemon, const gchar *group_uuid, const gchar *uuid) { GList *l; GList *devices; gchar *ret; ret = NULL; devices = daemon_local_get_all_devices (daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (d->priv->device_is_linux_lvm2_pv && g_strcmp0 (group_uuid, d->priv->linux_lvm2_pv_group_uuid) == 0) { guint n; for (n = 0; n < d->priv->linux_lvm2_pv_group_logical_volumes->len; n++) { const gchar *lv_data = d->priv->linux_lvm2_pv_group_logical_volumes->pdata[n]; gchar **tokens; guint m; tokens = g_strsplit (lv_data, ";", 0); for (m = 0; tokens != NULL && tokens[m] != NULL; m++) { if (g_str_has_prefix (tokens[m], "uuid=") && g_strcmp0 (tokens[m] + 5, uuid) == 0) { guint c; for (c = 0; tokens[c] != NULL; c++) { if (g_str_has_prefix (tokens[c], "name=")) { ret = g_strdup_printf ("%s/%s", d->priv->linux_lvm2_pv_group_name, tokens[c] + 5); break; } } } } g_strfreev (tokens); if (ret != NULL) break; } } if (ret != NULL) break; } g_list_free (devices); return ret; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_lvm2_lv_start_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error starting LVM2 Logical Volume: lvchange exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void daemon_linux_lvm2_lv_start_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *group_uuid = user_data_elements[0]; const gchar *uuid = user_data_elements[1]; gchar *lv_name; /* TODO: use options */ //gchar **options = user_data_elements[2]; guint n; gchar *argv[10]; /* Unfortunately vgchange does not (yet - file a bug) accept UUIDs - so find the VG name for this * UUID by looking at PVs */ lv_name = find_lvm2_lv_name_for_uuids (daemon, group_uuid, uuid); if (lv_name == NULL) { throw_error (context, ERROR_FAILED, "Cannot find Logical Volume with Group UUID `%s' and UUID `%s'", group_uuid, uuid); goto out; } n = 0; argv[n++] = "lvchange"; argv[n++] = "-ay"; argv[n++] = lv_name; argv[n++] = NULL; if (!job_new (context, "LinuxLvm2LVStart", TRUE, NULL, argv, NULL, linux_lvm2_lv_start_completed_cb, FALSE, NULL, NULL)) { goto out; } out: g_free (lv_name); } gboolean daemon_linux_lvm2_lv_start (Daemon *daemon, const gchar *group_uuid, const gchar *uuid, char **options, DBusGMethodInvocation *context) { daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-lvm2", "LinuxLvm2LVStart", TRUE, daemon_linux_lvm2_lv_start_authorized_cb, context, 3, g_strdup (group_uuid), g_free, g_strdup (uuid), g_free, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_lvm2_lv_stop_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error stopping LVM2 Logical Volume: lvchange exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void daemon_linux_lvm2_lv_stop_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { gchar *name; /* TODO: use options */ //gchar **options = user_data_elements[0]; guint n; gchar *argv[10]; name = NULL; if (!device->priv->device_is_linux_lvm2_lv) { throw_error (context, ERROR_FAILED, "Device is not a Linux LVM2 Logical Volume"); goto out; } /* Unfortunately lvchange does not (yet - file a bug) accept UUIDs */ name = g_strdup_printf ("%s/%s", device->priv->linux_lvm2_lv_group_name, device->priv->linux_lvm2_lv_name); n = 0; argv[n++] = "lvchange"; argv[n++] = "-an"; argv[n++] = name; argv[n++] = NULL; if (!job_new (context, "LinuxLvm2LVStop", TRUE, NULL, argv, NULL, linux_lvm2_lv_stop_completed_cb, FALSE, NULL, NULL)) { goto out; } out: g_free (name); } gboolean device_linux_lvm2_lv_stop (Device *device, char **options, DBusGMethodInvocation *context) { daemon_local_check_auth (device->priv->daemon, device, "org.freedesktop.udisks.linux-lvm2", "LinuxLvm2LVStop", TRUE, daemon_linux_lvm2_lv_stop_authorized_cb, context, 1, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_lvm2_vg_set_name_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error setting name for LVM2 Volume Group: vgrename exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void daemon_linux_lvm2_vg_set_name_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *uuid = user_data_elements[0]; const gchar *new_name = user_data_elements[1]; const gchar *vg_name; guint n; gchar *argv[10]; /* Unfortunately vgchange does not (yet - file a bug) accept UUIDs - so find the VG name for this * UUID by looking at PVs */ vg_name = find_lvm2_vg_name_for_uuid (daemon, uuid); if (vg_name == NULL) { throw_error (context, ERROR_FAILED, "Cannot find VG with UUID `%s'", uuid); goto out; } n = 0; argv[n++] = "vgrename"; argv[n++] = (gchar *) vg_name; argv[n++] = (gchar *) new_name; argv[n++] = NULL; if (!job_new (context, "LinuxLvm2VGSetName", TRUE, NULL, argv, NULL, linux_lvm2_vg_set_name_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean daemon_linux_lvm2_vg_set_name (Daemon *daemon, const gchar *uuid, const gchar *new_name, DBusGMethodInvocation *context) { daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-lvm2", "LinuxLvm2VGSetName", TRUE, daemon_linux_lvm2_vg_set_name_authorized_cb, context, 2, g_strdup (uuid), g_free, g_strdup (new_name), g_free); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_lvm2_lv_set_name_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error setting name for LVM2 Logical Volume: lvrename exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void daemon_linux_lvm2_lv_set_name_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *group_uuid = user_data_elements[0]; const gchar *uuid = user_data_elements[1]; const gchar *new_name = user_data_elements[2]; const gchar *vg_name; gchar *lv_name; guint n; gchar *argv[10]; /* Unfortunately lvchange does not (yet - file a bug) accept UUIDs - so find the LV name for this * UUID by looking at PVs */ lv_name = NULL; vg_name = find_lvm2_vg_name_for_uuid (daemon, group_uuid); if (vg_name == NULL) { throw_error (context, ERROR_FAILED, "Cannot find VG with UUID `%s'", group_uuid); goto out; } lv_name = find_lvm2_lv_name_for_uuids (daemon, group_uuid, uuid); if (lv_name == NULL) { throw_error (context, ERROR_FAILED, "Cannot find LV with UUID `%s'", uuid); goto out; } n = 0; argv[n++] = "lvrename"; argv[n++] = (gchar *) vg_name; argv[n++] = lv_name; argv[n++] = (gchar *) new_name; argv[n++] = NULL; if (!job_new (context, "LinuxLvm2LVSetName", TRUE, NULL, argv, NULL, linux_lvm2_lv_set_name_completed_cb, FALSE, NULL, NULL)) { goto out; } out: g_free (lv_name); } gboolean daemon_linux_lvm2_lv_set_name (Daemon *daemon, const gchar *group_uuid, const gchar *uuid, const gchar *new_name, DBusGMethodInvocation *context) { daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-lvm2", "LinuxLvm2LVSetName", TRUE, daemon_linux_lvm2_lv_set_name_authorized_cb, context, 3, g_strdup (group_uuid), g_free, g_strdup (uuid), g_free, g_strdup (new_name), g_free); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_lvm2_lv_remove_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error removing LVM2 Logical Volume: lvremove exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void daemon_linux_lvm2_lv_remove_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *group_uuid = user_data_elements[0]; const gchar *uuid = user_data_elements[1]; /* TODO: use options: gchar **options = user_data_elements[2]; */ gchar *lv_name; guint n; gchar *argv[10]; /* Unfortunately lvchange does not (yet - file a bug) accept UUIDs - so find the LV name for this * UUID by looking at PVs */ lv_name = find_lvm2_lv_name_for_uuids (daemon, group_uuid, uuid); if (lv_name == NULL) { throw_error (context, ERROR_FAILED, "Cannot find LV with UUID `%s'", uuid); goto out; } n = 0; argv[n++] = "lvremove"; argv[n++] = lv_name; argv[n++] = "--force"; argv[n++] = NULL; if (!job_new (context, "LinuxLvm2LVRemove", TRUE, NULL, argv, NULL, linux_lvm2_lv_remove_completed_cb, FALSE, NULL, NULL)) { goto out; } out: g_free (lv_name); } gboolean daemon_linux_lvm2_lv_remove (Daemon *daemon, const gchar *group_uuid, const gchar *uuid, gchar **options, DBusGMethodInvocation *context) { daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-lvm2", "LinuxLvm2LVRemove", TRUE, daemon_linux_lvm2_lv_remove_authorized_cb, context, 3, g_strdup (group_uuid), g_free, g_strdup (uuid), g_free, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { int refcount; guint device_added_signal_handler_id; guint device_changed_signal_handler_id; guint device_added_timeout_id; DBusGMethodInvocation *context; Daemon *daemon; gchar *vg_uuid; gchar *lv_name; char *fstype; char **fsoptions; } CreateLvm2LVData; static CreateLvm2LVData * lvm2_lv_create_data_new (DBusGMethodInvocation *context, Daemon *daemon, const gchar *vg_uuid, const gchar *lv_name, const char *fstype, char **fsoptions) { CreateLvm2LVData *data; data = g_new0 (CreateLvm2LVData, 1); data->refcount = 1; data->context = context; data->daemon = g_object_ref (daemon); data->vg_uuid = g_strdup (vg_uuid); data->lv_name = g_strdup (lv_name); data->fstype = g_strdup (fstype); data->fsoptions = g_strdupv (fsoptions); return data; } static CreateLvm2LVData * lvm2_lv_create_data_ref (CreateLvm2LVData *data) { data->refcount++; return data; } static void lvm2_lv_create_data_unref (CreateLvm2LVData *data) { data->refcount--; if (data->refcount == 0) { g_object_unref (data->daemon); g_free (data->vg_uuid); g_free (data->lv_name); g_free (data->fstype); g_strfreev (data->fsoptions); g_free (data); } } static void lvm2_lv_create_filesystem_create_hook (DBusGMethodInvocation *context, Device *device, gboolean filesystem_create_succeeded, gpointer user_data) { if (!filesystem_create_succeeded) { /* dang.. FilesystemCreate already reported an error */ } else { /* it worked.. */ dbus_g_method_return (context, device->priv->object_path); } } static void lvm2_lv_create_found_device (Device *device, CreateLvm2LVData *data) { if (strlen (data->fstype) > 0) { device_filesystem_create_internal (device, data->fstype, data->fsoptions, lvm2_lv_create_filesystem_create_hook, NULL, data->context); } else { dbus_g_method_return (data->context, device->priv->object_path); } } static gboolean str_has_lv_uuid (const gchar *str, const gchar *lv_uuid) { gchar **tokens; guint n; tokens = g_strsplit (str, ";", 0); for (n = 0; tokens != NULL && tokens[n] != NULL; n++) { if (g_str_has_prefix (tokens[n], "uuid=") && g_strcmp0 (tokens[n] + 5, lv_uuid) == 0) { return TRUE; } } return FALSE; } static Device * lvm2_lv_create_has_lv (CreateLvm2LVData *data) { GList *devices; Device *ret; GList *l; ret = NULL; devices = daemon_local_get_all_devices (data->daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (d->priv->device_is_linux_lvm2_lv && g_strcmp0 (d->priv->linux_lvm2_lv_group_uuid, data->vg_uuid) == 0 && g_strcmp0 (d->priv->linux_lvm2_lv_name, data->lv_name) == 0) { GList *m; const gchar *lv_uuid; lv_uuid = d->priv->linux_lvm2_lv_uuid; /* OK, we've found the LV... now check that one of more PVs actually reference this LV */ for (m = devices; m != NULL; m = m->next) { Device *pv = DEVICE (m->data); if (pv->priv->device_is_linux_lvm2_pv && g_strcmp0 (pv->priv->linux_lvm2_pv_group_uuid, data->vg_uuid) == 0) { guint n; for (n = 0; n < pv->priv->linux_lvm2_pv_group_logical_volumes->len; n++) { const gchar *str = pv->priv->linux_lvm2_pv_group_logical_volumes->pdata[n]; if (str_has_lv_uuid (str, lv_uuid)) { /* Return the LV, not the PV */ ret = d; break; } } } } /* for all PVs */ break; } /* if (found LV) */ } return ret; } static void lvm2_lv_create_device_added_cb (Daemon *daemon, const char *object_path, gpointer user_data) { CreateLvm2LVData *data = user_data; Device *device; g_debug ("added %s", object_path); device = lvm2_lv_create_has_lv (data); if (device != NULL) { /* yay! it is.. now create the file system if requested */ lvm2_lv_create_found_device (device, data); g_signal_handler_disconnect (daemon, data->device_added_signal_handler_id); g_signal_handler_disconnect (daemon, data->device_changed_signal_handler_id); g_source_remove (data->device_added_timeout_id); lvm2_lv_create_data_unref (data); } } static void lvm2_lv_create_device_changed_cb (Daemon *daemon, const char *object_path, gpointer user_data) { CreateLvm2LVData *data = user_data; Device *device; g_debug ("changed %s", object_path); device = lvm2_lv_create_has_lv (data); if (device != NULL) { /* yay! it is.. now create the file system if requested */ lvm2_lv_create_found_device (device, data); g_signal_handler_disconnect (daemon, data->device_added_signal_handler_id); g_signal_handler_disconnect (daemon, data->device_changed_signal_handler_id); g_source_remove (data->device_added_timeout_id); lvm2_lv_create_data_unref (data); } } static gboolean lvm2_lv_create_device_not_seen_cb (gpointer user_data) { CreateLvm2LVData *data = user_data; throw_error (data->context, ERROR_FAILED, "Error creating Logical Volume: timeout (10s) waiting for LV to show up"); g_signal_handler_disconnect (data->daemon, data->device_added_signal_handler_id); g_signal_handler_disconnect (data->daemon, data->device_changed_signal_handler_id); lvm2_lv_create_data_unref (data); return FALSE; } static void linux_lvm2_lv_create_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { CreateLvm2LVData *data = user_data; if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { Device *d; d = lvm2_lv_create_has_lv (data); if (d != NULL) { /* yay! it is.. now create the file system if requested */ lvm2_lv_create_found_device (device, data); } else { /* otherwise sit around and wait for the new LV to appear */ data->device_added_signal_handler_id = g_signal_connect_after (data->daemon, "device-added", G_CALLBACK (lvm2_lv_create_device_added_cb), data); data->device_changed_signal_handler_id = g_signal_connect_after (data->daemon, "device-changed", G_CALLBACK (lvm2_lv_create_device_changed_cb), data); data->device_added_timeout_id = g_timeout_add (10 * 1000, lvm2_lv_create_device_not_seen_cb, data); lvm2_lv_create_data_ref (data); } } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error creating LVM2 Logical Volume: lvcreate exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void daemon_linux_lvm2_lv_create_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *group_uuid = user_data_elements[0]; const gchar *name = user_data_elements[1]; guint64 size = *((guint64 *) user_data_elements[2]); guint num_stripes = GPOINTER_TO_UINT (user_data_elements[3]); guint64 stripe_size = *((guint64 *) user_data_elements[4]); guint num_mirrors = GPOINTER_TO_UINT (user_data_elements[5]); /* TODO: use options: gchar **options = user_data_elements[6]; */ const gchar *fstype = user_data_elements[7]; gchar **fsoptions = user_data_elements[8]; const gchar *vg_name; gchar **argv; GString *s; argv = NULL; s = NULL; /* Unfortunately lvcreate does not (yet - file a bug) accept UUIDs - so find the VG name for this * UUID by looking at PVs */ vg_name = find_lvm2_vg_name_for_uuid (daemon, group_uuid); if (vg_name == NULL) { throw_error (context, ERROR_FAILED, "Cannot find VG with UUID `%s'", group_uuid); goto out; } if (name == NULL || strlen (name) == 0) { throw_error (context, ERROR_FAILED, "Name cannot be blank"); goto out; } if (strstr (name, "\"") != NULL) { throw_error (context, ERROR_FAILED, "Name cannot contain the double-quote (\") character"); goto out; } s = g_string_new ("lvcreate "); g_string_append_printf (s, "%s ", vg_name); if (num_stripes > 0) g_string_append_printf (s, "--stripes %d ", num_stripes); if (stripe_size > 0) g_string_append_printf (s, "--stripesize %" G_GUINT64_FORMAT " ", stripe_size); if (num_mirrors > 0) g_string_append_printf (s, "--mirrors %d ", num_mirrors); size &= (~511); g_string_append_printf (s, "--size %" G_GUINT64_FORMAT "b ", size); if (name != NULL && strlen (name) > 0) g_string_append_printf (s, "--name \"%s\"", name); if (!g_shell_parse_argv (s->str, NULL, &argv, NULL)) { throw_error (context, ERROR_FAILED, "Unable to parse command line `%s'", s->str); goto out; } if (!job_new (context, "LinuxLvm2LVCreate", TRUE, NULL, argv, NULL, linux_lvm2_lv_create_completed_cb, FALSE, lvm2_lv_create_data_new (context, daemon, group_uuid, name, fstype, fsoptions), (GDestroyNotify) lvm2_lv_create_data_unref)) { goto out; } out: if (s != NULL) g_string_free (s, FALSE); g_strfreev (argv); } gboolean daemon_linux_lvm2_lv_create (Daemon *daemon, const gchar *group_uuid, const gchar *name, guint64 size, guint num_stripes, guint64 stripe_size, guint num_mirrors, gchar **options, char *fstype, char **fsoptions, DBusGMethodInvocation *context) { daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-lvm2", "LinuxLvm2LVCreate", TRUE, daemon_linux_lvm2_lv_create_authorized_cb, context, 9, g_strdup (group_uuid), g_free, g_strdup (name), g_free, g_memdup (&size, sizeof (guint64)), g_free, GUINT_TO_POINTER (num_stripes), NULL, g_memdup (&stripe_size, sizeof (guint64)), g_free, GUINT_TO_POINTER (num_mirrors), NULL, g_strdupv (options), g_strfreev, g_strdup (fstype), g_free, g_strdupv (fsoptions), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_lvm2_vg_add_pv_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error adding PV for LVM2 Volume Group: vgextend exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void daemon_linux_lvm2_vg_add_pv_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *uuid = user_data_elements[0]; const gchar *physical_volume = user_data_elements[1]; /* TODO: use options: gchar **options = user_data_elements[2]; */ const gchar *vg_name; guint n; gchar *argv[10]; Device *pv; GError *error; /* Unfortunately vgchange does not (yet - file a bug) accept UUIDs - so find the VG name for this * UUID by looking at PVs */ vg_name = find_lvm2_vg_name_for_uuid (daemon, uuid); if (vg_name == NULL) { throw_error (context, ERROR_FAILED, "Cannot find VG with UUID `%s'", uuid); goto out; } pv = daemon_local_find_by_object_path (daemon, physical_volume); if (pv == NULL) { throw_error (context, ERROR_FAILED, "physical volume doesn't exist"); goto out; } error = NULL; if (device_local_is_busy (pv, TRUE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } n = 0; argv[n++] = "vgextend"; argv[n++] = (gchar *) vg_name; argv[n++] = (gchar *) pv->priv->device_file; argv[n++] = NULL; if (!job_new (context, "LinuxLvm2VGAddPV", TRUE, NULL, argv, NULL, linux_lvm2_vg_add_pv_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean daemon_linux_lvm2_vg_add_pv (Daemon *daemon, const gchar *uuid, const gchar *object_path, gchar **options, DBusGMethodInvocation *context) { daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-lvm2", "LinuxLvm2VGAddPV", TRUE, daemon_linux_lvm2_vg_add_pv_authorized_cb, context, 3, g_strdup (uuid), g_free, g_strdup (object_path), g_free, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/ static void linux_lvm2_vg_remove_pv_completed_cb (DBusGMethodInvocation *context, Device *device, gboolean job_was_cancelled, int status, const char *stderr, const char *stdout, gpointer user_data) { if (WEXITSTATUS (status) == 0 && !job_was_cancelled) { dbus_g_method_return (context); } else { if (job_was_cancelled) { throw_error (context, ERROR_CANCELLED, "Job was cancelled"); } else { throw_error (context, ERROR_FAILED, "Error removing PV for LVM2 Volume Group: vgreduce exited with exit code %d: %s", WEXITSTATUS (status), stderr); } } } static void daemon_linux_lvm2_vg_remove_pv_authorized_cb (Daemon *daemon, Device *device, DBusGMethodInvocation *context, const gchar *action_id, guint num_user_data, gpointer *user_data_elements) { const gchar *vg_uuid = user_data_elements[0]; const gchar *pv_uuid = user_data_elements[1]; /* TODO: use options: gchar **options = user_data_elements[2]; */ const gchar *vg_name; guint n; gchar *argv[10]; Device *pv; GError *error; GList *devices; GList *l; /* Unfortunately vgchange does not (yet - file a bug) accept UUIDs - so find the VG name for this * UUID by looking at PVs */ pv = NULL; vg_name = find_lvm2_vg_name_for_uuid (daemon, vg_uuid); if (vg_name == NULL) { throw_error (context, ERROR_FAILED, "Cannot find VG with UUID `%s'", vg_uuid); goto out; } devices = daemon_local_get_all_devices (daemon); for (l = devices; l != NULL; l = l->next) { Device *d = DEVICE (l->data); if (d->priv->device_is_linux_lvm2_pv && g_strcmp0 (d->priv->linux_lvm2_pv_uuid, pv_uuid) == 0) { pv = d; break; } } if (pv == NULL) { throw_error (context, ERROR_FAILED, "Cannot find PV with UUID `%s'", pv_uuid); goto out; } error = NULL; if (device_local_is_busy (pv, TRUE, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); goto out; } n = 0; argv[n++] = "vgreduce"; argv[n++] = (gchar *) vg_name; argv[n++] = (gchar *) pv->priv->device_file; argv[n++] = NULL; if (!job_new (context, "LinuxLvm2VGRemovePV", TRUE, NULL, argv, NULL, linux_lvm2_vg_remove_pv_completed_cb, FALSE, NULL, NULL)) { goto out; } out: ; } gboolean daemon_linux_lvm2_vg_remove_pv (Daemon *daemon, const gchar *vg_uuid, const gchar *pv_uuid, gchar **options, DBusGMethodInvocation *context) { daemon_local_check_auth (daemon, NULL, "org.freedesktop.udisks.linux-lvm2", "LinuxLvm2VGRemovePV", TRUE, daemon_linux_lvm2_vg_remove_pv_authorized_cb, context, 3, g_strdup (vg_uuid), g_free, g_strdup (pv_uuid), g_free, g_strdupv (options), g_strfreev); return TRUE; } /*--------------------------------------------------------------------------------------------------------------*/