udevil/src/device-info.c
2013-11-19 04:56:09 -07:00

1457 lines
53 KiB
C

/* *************************************************************************
* 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;
}