/* ************************************************************************* * device-info.c GPL v3 Copyright 2012 * contains code excerpts from udisks v1.0.4 ************************************************************************** */ #include "device-info.h" 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); } } /* 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 gint ptr_str_array_compare (const gchar **a, const gchar **b) { return g_strcmp0 (*a, *b); } 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 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; } gboolean info_is_system_internal( device_t *device ) { const char *value; if ( value = udev_device_get_property_value( device->udevice, "UDISKS_SYSTEM_INTERNAL" ) ) return atoi( value ) != 0; /* A Linux MD device is system internal if, and only if * * - a single component is system internal * - there are no components * SKIP THIS TEST */ /* a partition is system internal only if the drive it belongs to is system internal */ //TODO /* a LUKS cleartext device is system internal only if the underlying crypto-text * device is system internal * SKIP THIS TEST */ // devices with removable media are never system internal if ( device->device_is_removable ) return FALSE; /* devices on certain buses are never system internal */ if ( device->drive_connection_interface != NULL ) { if (strcmp (device->drive_connection_interface, "ata_serial_esata") == 0 || strcmp (device->drive_connection_interface, "sdio") == 0 || strcmp (device->drive_connection_interface, "usb") == 0 || strcmp (device->drive_connection_interface, "firewire") == 0) return FALSE; } return TRUE; } void info_drive_connection( device_t *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->native_path); do { p = sysfs_resolve_link (s, "subsystem"); if ( !device->device_is_removable && sysfs_get_int( s, "removable") != 0 ) device->device_is_removable = TRUE; 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->drive_vendor == NULL) { device->drive_vendor = _dupv8 (vendor); } 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->drive_model == NULL) { device->drive_model = _dupv8 (model); } 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->drive_vendor != NULL && strcmp (device->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->drive_model == NULL) { device->drive_model = _dupv8 (model); } 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->drive_serial == NULL) { /* this is formatted as a hexnumber; drop the leading 0x */ device->drive_serial = _dupv8 (serial + 2); } 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->drive_revision == NULL) { device->drive_revision = _dupv8 (revision); } 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->drive_vendor == NULL ) { device->drive_vendor = g_strdup( "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->drive_connection_interface = g_strdup( connection_interface ); device->drive_connection_speed = connection_speed; } g_free (s); } static const struct { const char *udev_property; const char *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 char *udev_property; const char *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 }, }; void info_drive_properties ( device_t *device ) { GPtrArray *media_compat_array; const char *media_in_drive; gboolean drive_is_ejectable; gboolean drive_can_detach; char *decoded_string; guint n; const char *value; // drive identification device->device_is_drive = sysfs_file_exists( device->native_path, "range" ); // vendor if ( value = udev_device_get_property_value( device->udevice, "ID_VENDOR_ENC" ) ) { decoded_string = decode_udev_encoded_string ( value ); g_strstrip (decoded_string); device->drive_vendor = decoded_string; } else if ( value = udev_device_get_property_value( device->udevice, "ID_VENDOR" ) ) { device->drive_vendor = g_strdup( value ); } // model if ( value = udev_device_get_property_value( device->udevice, "ID_MODEL_ENC" ) ) { decoded_string = decode_udev_encoded_string ( value ); g_strstrip (decoded_string); device->drive_model = decoded_string; } else if ( value = udev_device_get_property_value( device->udevice, "ID_MODEL" ) ) { device->drive_model = g_strdup( value ); } // revision device->drive_revision = g_strdup( udev_device_get_property_value( device->udevice, "ID_REVISION" ) ); // serial if ( value = udev_device_get_property_value( device->udevice, "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->drive_serial = g_strdup( value ); } else if ( value = udev_device_get_property_value( device->udevice, "ID_SERIAL_SHORT" ) ) { device->drive_serial = g_strdup( value ); } // wwn if ( value = udev_device_get_property_value( device->udevice, "ID_WWN_WITH_EXTENSION" ) ) { device->drive_wwn = g_strdup( value + 2 ); } else if ( value = udev_device_get_property_value( device->udevice, "ID_WWN" ) ) { device->drive_wwn = g_strdup( value + 2 ); } /* pick up some things (vendor, model, connection_interface, connection_speed) * not (yet) exported by udev helpers */ //update_drive_properties_from_sysfs (device); info_drive_connection( device ); // is_ejectable if ( value = udev_device_get_property_value( device->udevice, "ID_DRIVE_EJECTABLE" ) ) { drive_is_ejectable = atoi( value ) != 0; } else { drive_is_ejectable = FALSE; drive_is_ejectable |= ( udev_device_get_property_value( device->udevice, "ID_CDROM" ) != NULL ); drive_is_ejectable |= ( udev_device_get_property_value( device->udevice, "ID_DRIVE_FLOPPY_ZIP" ) != NULL ); drive_is_ejectable |= ( udev_device_get_property_value( device->udevice, "ID_DRIVE_FLOPPY_JAZ" ) != NULL ); } device->drive_is_media_ejectable = drive_is_ejectable; // drive_media_compatibility media_compat_array = g_ptr_array_new (); for (n = 0; drive_media_mapping[n].udev_property != NULL; n++) { if ( udev_device_get_property_value( device->udevice, drive_media_mapping[n].udev_property ) == NULL ) 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->drive_connection_interface, "sdio") == 0) { gchar *type; type = sysfs_get_string (device->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->drive_media_compatibility = g_strjoinv( " ", (gchar**)media_compat_array->pdata ); // drive_media media_in_drive = NULL; if (device->device_is_media_available) { for (n = 0; media_mapping[n].udev_property != NULL; n++) { if ( udev_device_get_property_value( device->udevice, media_mapping[n].udev_property ) == NULL ) continue; // should this be media_mapping[n] ? doesn't matter, same? 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->drive_media = g_strdup( media_in_drive ); g_ptr_array_free (media_compat_array, TRUE); // drive_can_detach // right now, we only offer to detach USB devices drive_can_detach = FALSE; if (g_strcmp0 (device->drive_connection_interface, "usb") == 0) { drive_can_detach = TRUE; } if ( value = udev_device_get_property_value( device->udevice, "ID_DRIVE_DETACHABLE" ) ) { drive_can_detach = atoi( value ) != 0; } device->drive_can_detach = drive_can_detach; } void info_device_properties( device_t *device ) { const char* value; device->native_path = g_strdup( udev_device_get_syspath( device->udevice ) ); device->devnode = g_strdup( udev_device_get_devnode( device->udevice ) ); device->major = g_strdup( udev_device_get_property_value( device->udevice, "MAJOR") ); device->minor = g_strdup( udev_device_get_property_value( device->udevice, "MINOR") ); if ( !device->native_path || !device->devnode || !device->major || !device->minor ) { if ( device->native_path ) g_free( device->native_path ); device->native_path = NULL; return; } //by id - would need to read symlinks in /dev/disk/by-id // is_removable may also be set in info_drive_connection walking up sys tree device->device_is_removable = sysfs_get_int( device->native_path, "removable"); device->device_presentation_hide = g_strdup( udev_device_get_property_value( device->udevice, "UDISKS_PRESENTATION_HIDE") ); device->device_presentation_nopolicy = g_strdup( udev_device_get_property_value( device->udevice, "UDISKS_PRESENTATION_NOPOLICY") ); device->device_presentation_name = g_strdup( udev_device_get_property_value( device->udevice, "UDISKS_PRESENTATION_NAME") ); device->device_presentation_icon_name = g_strdup( udev_device_get_property_value( device->udevice, "UDISKS_PRESENTATION_ICON_NAME") ); device->device_automount_hint = g_strdup( udev_device_get_property_value( device->udevice, "UDISKS_AUTOMOUNT_HINT") ); // filesystem properties gchar *decoded_string; const gchar *partition_scheme; gint partition_type = 0; partition_scheme = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_SCHEME"); if ( value = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_TYPE") ) partition_type = atoi( value ); if (g_strcmp0 (partition_scheme, "mbr") == 0 && (partition_type == 0x05 || partition_type == 0x0f || partition_type == 0x85)) { } else { device->id_usage = g_strdup( udev_device_get_property_value( device->udevice, "ID_FS_USAGE" ) ); device->id_type = g_strdup( udev_device_get_property_value( device->udevice, "ID_FS_TYPE" ) ); device->id_version = g_strdup( udev_device_get_property_value( device->udevice, "ID_FS_VERSION" ) ); device->id_uuid = g_strdup( udev_device_get_property_value( device->udevice, "ID_FS_UUID" ) ); if ( value = udev_device_get_property_value( device->udevice, "ID_FS_LABEL_ENC" ) ) { decoded_string = decode_udev_encoded_string ( value ); g_strstrip (decoded_string); device->id_label = decoded_string; } else if ( value = udev_device_get_property_value( device->udevice, "ID_FS_LABEL" ) ) { device->id_label = g_strdup( value ); } } // device_is_media_available gboolean media_available = FALSE; if ( ( device->id_usage && device->id_usage[0] != '\0' ) || ( device->id_type && device->id_type[0] != '\0' ) || ( device->id_uuid && device->id_uuid[0] != '\0' ) || ( device->id_label && device->id_label[0] != '\0' ) ) { media_available = TRUE; } else if ( g_str_has_prefix( device->devnode, "/dev/loop" ) ) media_available = FALSE; else if ( device->device_is_removable ) { gboolean is_cd, is_floppy; if ( value = udev_device_get_property_value( device->udevice, "ID_CDROM" ) ) is_cd = atoi( value ) != 0; else is_cd = FALSE; if ( value = udev_device_get_property_value( device->udevice, "ID_DRIVE_FLOPPY" ) ) is_floppy = atoi( value ) != 0; else is_floppy = FALSE; if ( !is_cd && !is_floppy ) { // this test is limited for non-root - user may not have read // access to device file even if media is present int fd; fd = open( device->devnode, O_RDONLY ); if ( fd >= 0 ) { media_available = TRUE; close( fd ); } } else if ( value = udev_device_get_property_value( device->udevice, "ID_CDROM_MEDIA" ) ) media_available = ( atoi( value ) == 1 ); } else if ( value = udev_device_get_property_value( device->udevice, "ID_CDROM_MEDIA" ) ) media_available = ( atoi( value ) == 1 ); else media_available = TRUE; device->device_is_media_available = media_available; /* device_size, device_block_size and device_is_read_only properties */ if (device->device_is_media_available) { guint64 block_size; device->device_size = sysfs_get_uint64( device->native_path, "size") * ((guint64) 512); device->device_is_read_only = (sysfs_get_int (device->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->native_path, "queue/hw_sector_size"); if (block_size == 0) block_size = 512; device->device_block_size = block_size; } else { device->device_size = device->device_block_size = 0; device->device_is_read_only = FALSE; } // links struct udev_list_entry *entry = udev_device_get_devlinks_list_entry( device->udevice ); while ( entry ) { const char *entry_name = udev_list_entry_get_name( entry ); if ( entry_name && ( g_str_has_prefix( entry_name, "/dev/disk/by-id/" ) || g_str_has_prefix( entry_name, "/dev/disk/by-uuid/" ) ) ) { device->device_by_id = g_strdup( entry_name ); break; } entry = udev_list_entry_get_next( entry ); } } gchar* info_mount_points( device_t *device, GList* devmounts ) { gchar *contents; gchar **lines; GError *error; guint n; GList* mounts = NULL; if ( !device->major || !device->minor ) return NULL; guint dmajor = atoi( device->major ); guint dminor = atoi( device->minor ); // if we have the mount point list, use this instead of reading mountinfo if ( devmounts ) { GList* l; for ( l = devmounts; l; l = l->next ) { if ( ((devmount_t*)l->data)->major == dmajor && ((devmount_t*)l->data)->minor == dminor ) { return g_strdup( ((devmount_t*)l->data)->mount_points ); } } return NULL; } contents = NULL; lines = NULL; error = NULL; if (!g_file_get_contents ("/proc/self/mountinfo", &contents, NULL, &error)) { g_warning ("Error reading /proc/self/mountinfo: %s", error->message); g_error_free (error); return NULL; } /* See Documentation/filesystems/proc.txt for the format of /proc/self/mountinfo * * Note that things like space are encoded as \020. */ lines = g_strsplit (contents, "\n", 0); for (n = 0; lines[n] != NULL; n++) { guint mount_id; guint parent_id; guint major, minor; gchar encoded_root[PATH_MAX]; gchar encoded_mount_point[PATH_MAX]; gchar *mount_point; //dev_t dev; if (strlen (lines[n]) == 0) continue; if (sscanf (lines[n], "%d %d %d:%d %s %s", &mount_id, &parent_id, &major, &minor, encoded_root, encoded_mount_point) != 6) { g_warning ("Error reading /proc/self/mountinfo: Error parsing line '%s'", lines[n]); continue; } /* ignore mounts where only a subtree of a filesystem is mounted */ if (g_strcmp0 (encoded_root, "/") != 0) continue; /* Temporary work-around for btrfs, see * * https://github.com/IgnorantGuru/spacefm/issues/165 * http://article.gmane.org/gmane.comp.file-systems.btrfs/2851 * https://bugzilla.redhat.com/show_bug.cgi?id=495152#c31 */ if ( major == 0 ) { const gchar *sep; sep = strstr( lines[n], " - " ); if ( sep != NULL ) { gchar typebuf[PATH_MAX]; gchar mount_source[PATH_MAX]; struct stat statbuf; if ( sscanf( sep + 3, "%s %s", typebuf, mount_source ) == 2 && !g_strcmp0( typebuf, "btrfs" ) && g_str_has_prefix( mount_source, "/dev/" ) && stat( mount_source, &statbuf ) == 0 && S_ISBLK( statbuf.st_mode ) ) { major = major( statbuf.st_rdev ); minor = minor( statbuf.st_rdev ); } } } if ( major != dmajor || minor != dminor ) continue; mount_point = g_strcompress (encoded_mount_point); if ( mount_point && mount_point[0] != '\0' ) { if ( !g_list_find( mounts, mount_point ) ) { mounts = g_list_prepend( mounts, mount_point ); } else g_free (mount_point); } } g_free (contents); g_strfreev (lines); if ( mounts ) { gchar *points, *old_points; GList* l; // Sort the list to ensure that shortest mount paths appear first mounts = g_list_sort( mounts, (GCompareFunc) g_strcmp0 ); points = g_strdup( (gchar*)mounts->data ); l = mounts; while ( l = l->next ) { old_points = points; points = g_strdup_printf( "%s, %s", old_points, (gchar*)l->data ); g_free( old_points ); } g_list_foreach( mounts, (GFunc)g_free, NULL ); g_list_free( mounts ); return points; } else return NULL; } void info_partition_table( device_t *device ) { gboolean is_partition_table = FALSE; const char* value; /* 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 ( ( value = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_TABLE" ) ) && atoi( value ) == 1 ) { device->partition_table_scheme = g_strdup( udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_TABLE_SCHEME" ) ); device->partition_table_count = g_strdup( udev_device_get_property_value( device->udevice, "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->native_path); if ((dir = g_dir_open (device->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->partition_table_scheme = g_strdup( "" ); device->partition_table_count = g_strdup_printf( "%d", partition_count ); is_partition_table = TRUE; } } g_free (s); } device->device_is_partition_table = is_partition_table; if (!is_partition_table) { if ( device->partition_table_scheme ) g_free( device->partition_table_scheme ); device->partition_table_scheme = NULL; if ( device->partition_table_count ) g_free( device->partition_table_count ); device->partition_table_count = NULL; } } void info_partition( device_t *device ) { gboolean 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 ( udev_device_get_property_value( device->udevice,"UDISKS_PARTITION" ) ) { const gchar *size; const gchar *scheme; const gchar *type; const gchar *label; const gchar *uuid; const gchar *flags; const gchar *offset; const gchar *alignment_offset; const gchar *slave_sysfs_path; const gchar *number; scheme = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_SCHEME"); size = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_SIZE"); type = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_TYPE"); label = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_LABEL"); uuid = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_UUID"); flags = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_FLAGS"); offset = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_OFFSET"); alignment_offset = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_ALIGNMENT_OFFSET"); number = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_NUMBER"); slave_sysfs_path = udev_device_get_property_value( device->udevice, "UDISKS_PARTITION_SLAVE"); if (slave_sysfs_path != NULL && scheme != NULL && number != NULL && atoi( number ) > 0) { device->partition_scheme = g_strdup( scheme ); device->partition_size = g_strdup( size ); device->partition_type = g_strdup( type ); device->partition_label = g_strdup( label ); device->partition_uuid = g_strdup( uuid ); device->partition_flags = g_strdup( flags ); device->partition_offset = g_strdup( offset ); device->partition_alignment_offset = g_strdup( alignment_offset ); device->partition_number = g_strdup( number ); 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->native_path, "start")) { guint64 size; guint64 offset; guint64 alignment_offset; gchar *s; guint n; size = sysfs_get_uint64 (device->native_path, "size"); alignment_offset = sysfs_get_uint64 (device->native_path, "alignment_offset"); device->partition_size = g_strdup_printf( "%lu", size * 512 ); device->partition_alignment_offset = g_strdup_printf( "%lu", alignment_offset ); offset = sysfs_get_uint64 (device->native_path, "start") * device->device_block_size; device->partition_offset = g_strdup_printf( "%lu", offset ); s = device->native_path; for (n = strlen (s) - 1; n >= 0 && g_ascii_isdigit (s[n]); n--) ; device->partition_number = g_strdup_printf( "%ld", 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->device_is_partition = is_partition; if (!is_partition) { device->partition_scheme = NULL; device->partition_size = NULL; device->partition_type = NULL; device->partition_label = NULL; device->partition_uuid = NULL; device->partition_flags = NULL; device->partition_offset = NULL; device->partition_alignment_offset = NULL; device->partition_number = NULL; } else { device->device_is_drive = FALSE; } } void info_optical_disc( device_t *device ) { const char *cdrom_disc_state; const char* optical_state = udev_device_get_property_value( device->udevice, "ID_CDROM"); if ( optical_state && atoi( optical_state ) != 0 ) { device->device_is_optical_disc = TRUE; device->optical_disc_num_tracks = g_strdup( udev_device_get_property_value( device->udevice, "ID_CDROM_MEDIA_TRACK_COUNT") ); device->optical_disc_num_audio_tracks = g_strdup( udev_device_get_property_value( device->udevice, "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO") ); device->optical_disc_num_sessions = g_strdup( udev_device_get_property_value( device->udevice, "ID_CDROM_MEDIA_SESSION_COUNT") ); cdrom_disc_state = udev_device_get_property_value( device->udevice, "ID_CDROM_MEDIA_STATE"); device->optical_disc_is_blank = ( g_strcmp0( cdrom_disc_state, "blank" ) == 0); device->optical_disc_is_appendable = ( g_strcmp0( cdrom_disc_state, "appendable" ) == 0); device->optical_disc_is_closed = ( g_strcmp0( cdrom_disc_state, "complete" ) == 0); } else device->device_is_optical_disc = FALSE; } void device_free( device_t *device ) { if ( !device ) return; g_free( device->native_path ); g_free( device->major ); g_free( device->minor ); g_free( device->mount_points ); g_free( device->devnode ); g_free( device->device_presentation_hide ); g_free( device->device_presentation_nopolicy ); g_free( device->device_presentation_name ); g_free( device->device_presentation_icon_name ); g_free( device->device_automount_hint ); g_free( device->device_by_id ); g_free( device->id_usage ); g_free( device->id_type ); g_free( device->id_version ); g_free( device->id_uuid ); g_free( device->id_label ); g_free( device->drive_vendor ); g_free( device->drive_model ); g_free( device->drive_revision ); g_free( device->drive_serial ); g_free( device->drive_wwn ); g_free( device->drive_connection_interface ); g_free( device->drive_media_compatibility ); g_free( device->drive_media ); g_free( device->partition_scheme ); g_free( device->partition_number ); g_free( device->partition_type ); g_free( device->partition_label ); g_free( device->partition_uuid ); g_free( device->partition_flags ); g_free( device->partition_offset ); g_free( device->partition_size ); g_free( device->partition_alignment_offset ); g_free( device->partition_table_scheme ); g_free( device->partition_table_count ); g_free( device->optical_disc_num_tracks ); g_free( device->optical_disc_num_audio_tracks ); g_free( device->optical_disc_num_sessions ); g_slice_free( device_t, device ); } device_t *device_alloc( struct udev_device *udevice ) { device_t *device = g_slice_new0( device_t ); device->udevice = udevice; device->native_path = NULL; device->major = NULL; device->minor = NULL; device->mount_points = NULL; device->devnode = NULL; device->device_is_system_internal = TRUE; device->device_is_partition = FALSE; device->device_is_partition_table = FALSE; device->device_is_removable = FALSE; device->device_is_media_available = FALSE; device->device_is_read_only = FALSE; device->device_is_drive = FALSE; device->device_is_optical_disc = FALSE; device->device_is_mounted = FALSE; device->device_presentation_hide = NULL; device->device_presentation_nopolicy = NULL; device->device_presentation_name = NULL; device->device_presentation_icon_name = NULL; device->device_automount_hint = NULL; device->device_by_id = NULL; device->device_size = 0; device->device_block_size = 0; device->id_usage = NULL; device->id_type = NULL; device->id_version = NULL; device->id_uuid = NULL; device->id_label = NULL; device->drive_vendor = NULL; device->drive_model = NULL; device->drive_revision = NULL; device->drive_serial = NULL; device->drive_wwn = NULL; device->drive_connection_interface = NULL; device->drive_connection_speed = 0; device->drive_media_compatibility = NULL; device->drive_media = NULL; device->drive_is_media_ejectable = FALSE; device->drive_can_detach = FALSE; device->partition_scheme = NULL; device->partition_number = NULL; device->partition_type = NULL; device->partition_label = NULL; device->partition_uuid = NULL; device->partition_flags = NULL; device->partition_offset = NULL; device->partition_size = NULL; device->partition_alignment_offset = NULL; device->partition_table_scheme = NULL; device->partition_table_count = NULL; device->optical_disc_is_blank = FALSE; device->optical_disc_is_appendable = FALSE; device->optical_disc_is_closed = FALSE; device->optical_disc_num_tracks = NULL; device->optical_disc_num_audio_tracks = NULL; device->optical_disc_num_sessions = NULL; return device; } gboolean device_get_info( device_t *device, GList* devmounts ) { info_device_properties( device ); if ( !device->native_path ) return FALSE; info_drive_properties( device ); device->device_is_system_internal = info_is_system_internal( device ); device->mount_points = info_mount_points( device, devmounts ); device->device_is_mounted = ( device->mount_points != NULL ); info_partition_table( device ); info_partition( device ); info_optical_disc( device ); return TRUE; } char* device_show_info( device_t *device ) { // no translate gchar* line[140]; int i = 0; //line[i++] = g_strdup_printf("Showing information for %s\n", device->devnode ); char* bdev = g_path_get_basename( device->devnode ); line[i++] = g_strdup_printf("Showing information for /org/freedesktop/UDisks/devices/%s\n", bdev ); g_free( bdev ); line[i++] = g_strdup_printf(" native-path: %s\n", device->native_path ); line[i++] = g_strdup_printf(" device: %s:%s\n", device->major, device->minor ); line[i++] = g_strdup_printf(" device-file: %s\n", device->devnode ); line[i++] = g_strdup_printf(" presentation: %s\n", device->devnode ); if ( device->device_by_id ) line[i++] = g_strdup_printf(" by-id: %s\n", device->device_by_id ); line[i++] = g_strdup_printf(" system internal: %d\n", device->device_is_system_internal ); line[i++] = g_strdup_printf(" removable: %d\n", device->device_is_removable); line[i++] = g_strdup_printf(" has media: %d\n", device->device_is_media_available); line[i++] = g_strdup_printf(" is read only: %d\n", device->device_is_read_only ); line[i++] = g_strdup_printf(" is mounted: %d\n", device->device_is_mounted ); line[i++] = g_strdup_printf(" mount paths: %s\n", device->mount_points ? device->mount_points : "" ); line[i++] = g_strdup_printf(" presentation hide: %s\n", device->device_presentation_hide ? device->device_presentation_hide : "0" ); line[i++] = g_strdup_printf(" presentation nopolicy: %s\n", device->device_presentation_nopolicy ? device->device_presentation_nopolicy : "0" ); line[i++] = g_strdup_printf(" presentation name: %s\n", device->device_presentation_name ? device->device_presentation_name : "" ); line[i++] = g_strdup_printf(" presentation icon: %s\n", device->device_presentation_icon_name ? device->device_presentation_icon_name : "" ); line[i++] = g_strdup_printf(" automount hint: %s\n", device->device_automount_hint ? device->device_automount_hint : "" ); line[i++] = g_strdup_printf(" size: %" G_GUINT64_FORMAT "\n", device->device_size); line[i++] = g_strdup_printf(" block size: %" G_GUINT64_FORMAT "\n", device->device_block_size); line[i++] = g_strdup_printf(" usage: %s\n", device->id_usage ? device->id_usage : "" ); line[i++] = g_strdup_printf(" type: %s\n", device->id_type ? device->id_type : "" ); line[i++] = g_strdup_printf(" version: %s\n", device->id_version ? device->id_version : "" ); line[i++] = g_strdup_printf(" uuid: %s\n", device->id_uuid ? device->id_uuid : "" ); line[i++] = g_strdup_printf(" label: %s\n", device->id_label ? device->id_label : "" ); if (device->device_is_partition_table) { line[i++] = g_strdup_printf(" partition table:\n"); line[i++] = g_strdup_printf(" scheme: %s\n", device->partition_table_scheme ? device->partition_table_scheme : "" ); line[i++] = g_strdup_printf(" count: %s\n", device->partition_table_count ? device->partition_table_count : "0" ); } if (device->device_is_partition) { line[i++] = g_strdup_printf(" partition:\n"); line[i++] = g_strdup_printf(" scheme: %s\n", device->partition_scheme ? device->partition_scheme : "" ); line[i++] = g_strdup_printf(" number: %s\n", device->partition_number ? device->partition_number : "" ); line[i++] = g_strdup_printf(" type: %s\n", device->partition_type ? device->partition_type : "" ); line[i++] = g_strdup_printf(" flags: %s\n", device->partition_flags ? device->partition_flags : "" ); line[i++] = g_strdup_printf(" offset: %s\n", device->partition_offset ? device->partition_offset : "" ); line[i++] = g_strdup_printf(" alignment offset: %s\n", device->partition_alignment_offset ? device->partition_alignment_offset : "" ); line[i++] = g_strdup_printf(" size: %s\n", device->partition_size ? device->partition_size : "" ); line[i++] = g_strdup_printf(" label: %s\n", device->partition_label ? device->partition_label : "" ); line[i++] = g_strdup_printf(" uuid: %s\n", device->partition_uuid ? device->partition_uuid : "" ); } if (device->device_is_optical_disc) { line[i++] = g_strdup_printf(" optical disc:\n"); line[i++] = g_strdup_printf(" blank: %d\n", device->optical_disc_is_blank); line[i++] = g_strdup_printf(" appendable: %d\n", device->optical_disc_is_appendable); line[i++] = g_strdup_printf(" closed: %d\n", device->optical_disc_is_closed); line[i++] = g_strdup_printf(" num tracks: %s\n", device->optical_disc_num_tracks ? device->optical_disc_num_tracks : "0" ); line[i++] = g_strdup_printf(" num audio tracks: %s\n", device->optical_disc_num_audio_tracks ? device->optical_disc_num_audio_tracks : "0" ); line[i++] = g_strdup_printf(" num sessions: %s\n", device->optical_disc_num_sessions ? device->optical_disc_num_sessions : "0" ); } if (device->device_is_drive) { line[i++] = g_strdup_printf(" drive:\n"); line[i++] = g_strdup_printf(" vendor: %s\n", device->drive_vendor ? device->drive_vendor : "" ); line[i++] = g_strdup_printf(" model: %s\n", device->drive_model ? device->drive_model : "" ); line[i++] = g_strdup_printf(" revision: %s\n", device->drive_revision ? device->drive_revision : "" ); line[i++] = g_strdup_printf(" serial: %s\n", device->drive_serial ? device->drive_serial : "" ); line[i++] = g_strdup_printf(" WWN: %s\n", device->drive_wwn ? device->drive_wwn : "" ); line[i++] = g_strdup_printf(" detachable: %d\n", device->drive_can_detach); line[i++] = g_strdup_printf(" ejectable: %d\n", device->drive_is_media_ejectable); line[i++] = g_strdup_printf(" media: %s\n", device->drive_media ? device->drive_media : "" ); line[i++] = g_strdup_printf(" compat: %s\n", device->drive_media_compatibility ? device->drive_media_compatibility : "" ); if ( device->drive_connection_interface == NULL || strlen (device->drive_connection_interface) == 0 ) line[i++] = g_strdup_printf(" interface: (unknown)\n"); else line[i++] = g_strdup_printf(" interface: %s\n", device->drive_connection_interface); if (device->drive_connection_speed == 0) line[i++] = g_strdup_printf(" if speed: (unknown)\n"); else line[i++] = g_strdup_printf(" if speed: %" G_GINT64_FORMAT " bits/s\n", device->drive_connection_speed); } line[i] = NULL; gchar* output = g_strjoinv( NULL, line ); i = 0; while ( line[i] ) g_free( line[i++] ); return output; }