How to Obtain Storage Information in Linux



Preface


The Intel® Mobile Internet Device (MID) platform provides a full internet experience in a pocket-sized form factor. Combining Moblin-based operating systems with the Intel® Atom™ processor, MIDs are able to run any application that has been built for the x86 architecture, including Adobe Flash 10* and Adobe AIR 1.5*. While the features of the devices that are and will be on the market vary depending on OEM and target market, there are several features that the devices share in common: small form factor, emphasis on internet connectivity rather than extensive storage, and alternate input methods.

Although there are ultra-mobile computing devices in the market that run Microsoft Windows XP* or Windows Vista* and that are built on the same hardware platform as those running Moblin-based operating systems such as Midinux or Ubuntu* Mobile Edition, this series of whitepapers focuses entirely on the Moblin-based MIDs. For information on how to retrieve this information from Windows operating systems, please consult that platform’s documentation, or see the Intel® Mobile Platform SDK.

The functionality described in this paper is also provided via the Platform Awareness Service, a light-weight, D-Bus initiated platform information provider. However, because the final software stack for each Moblin device is influenced heavily by the OEM and Service Providers, this service may not be pre-installed on some Moblin-based devices. We have therefore written these whitepapers in order to simplify access to this information on platforms that do not have this service installed.

For further information on how to use D-Bus to retrieve information from the Platform Awareness Service, please refer to the Platform Awareness Service DBus documentation.

Introduction


One of the more distinguishing differences between a desktop machine and a mobile device is the amount of storage that each device can hold. The latest desktop machines are built to be able to store multiple terabytes of information using several hard disks. The storage capacity in small form factor devices like the Intel® MID is limited both by space and by power consumption. Some MID devices have only 4 GB of storage, with 2 GB of space dedicated to the system, leaving the remaining 2 GB for user storage. Customers are expected to stream content directly over the internet, as well as access files stored on external storage devices such as mini SD cards. With such capacity constraints, software must be aware of available disk space in order to avoid disk space issues.

The purpose of this paper is to demonstrate one way of retrieving storage device information from Moblin-based Linux platforms.

How to get Storage Information in Linux


While there is a wealth of information available, we focus on three main pieces of information in order to keep things small and simple:

  • The mount points of the storage devices attached to the system;
  • Whether or not a device is removable (e.g., USB flash drive); and
  • How much space is available on a device

Using HAL

The Linux Hardware Abstraction Layer (HAL) can retrieve information about the hardware that makes up a computer system. For storage devices, HAL provides a list of devices that have been mounted, whether or not it’s removable, and the mount point(s) for each device. HAL can be queried through the command line using commands like hal-device, hal-find-by-property, hal-find-by-capability, and hal-get-property. One can get the same information inside of an application by using D-BUS. The HAL 0.5.10 Specification lists information that is available through HAL.

In order to better explain the structure of D-BUS calls necessary to retrieve this information, the illustration below shows what an example hard drive might look like to HAL, including its multiple volumes / partitions:



This picture represents a 150 GB hard drive visible to the system as /dev/sda. Since it is an internal hard drive, it is not removable. There are multiple partitions on this drive. An ext3-formatted 50 GB partition is mounted at /, while an NTFS-formatted 50 GB partition is mounted at /media/sda1.

Retrieve a List of Logical Storage Devices

In order to get information for any storage device, one must first find out where the volume devices are located in the file system. On any UNIX machine the location of such devices are called mount points (ex. “/”, “/media/disk”). In the illustration above, the second partition of the hard drive is found at ‘/dev/sda1’, but the partition’s file system is mapped to a mount point, ‘/media/sda1’. It is at a volume’s mount point where access and manipulation of that volume’s files takes place. I decided to use HAL to locate all of the storage volumes available to the system, and filter out those devices that are not currently mounted.

1. Find ALL volumes available to the system
In the getLogicalStorageDevices method below, the parameters are:

  • gchar*** mountPoints – upon return, mountPoints contains a list of mount paths (e.g. “/”, “/media/disk”), stored as string values
  • GError** error – upon return, error is not NULL, and contains error information if no paths are found

The code below connects to D-Bus and asks the HAL Manager to return a list of all devices that are of type “volume”. The list of devices that is returned gets passed to the mountPtFromDevice method to decide whether or not the device should be filtered. A valid volume device will be mounted, and will have a mount point that corresponds to it.

gboolean getLogicalStorageDevices(MIDPlatformSvc *obj, gchar***mountPoints, GError **error)
{
    DBusGConnection *systemBus;
    DBusGProxy *proxy;
    char **deviceList;
    char **devices_ptr;
    int i = 0;
       
    // Initialize the System DBus connection
    systemBus = dbus_g_bus_get(DBUS_BUS_SYSTEM, error);
       
    if (systemBus== NULL) {
        dbg("Unable to connect to dbus");
        g_error_free(*error);
        return FALSE;
    }
 
    proxy = dbus_g_proxy_new_for_name(systemBus,
                                      "org.freedesktop.Hal",
                                      "/org/freedesktop/Hal/Manager",
                                      "org.freedesktop.Hal.Manager" );
 
// ask for all devices of type “volume”
// deviceList will contain the identifier for all devices that 
// provide the HAL volume capability
    dbus_g_proxy_call(proxy, "FindDeviceByCapability", error, 
                      G_TYPE_STRING, "volume", G_TYPE_INVALID,     // param #1
                      G_TYPE_STRV, &deviceList, G_TYPE_INVALID);   // ret val
       
    int len = g_strv_length(deviceList);
    int count;
 
    if (len == 0) {        // could not find any volumes mounted on machine
        g_set_error (error,
                     STORAGE_DEVICE_DNE,            /* error domain */
                     1,                             /* error code */
                     "Could not find any volumes mounted.n");
        return FALSE;
    }
 
// for each device in the deviceList, pass the identifier 
// to mountPtFromDevice, to see if it’s a volume object 
// we’re interested in
    gboolean gotNewMountPt = FALSE;
    gchar *mount_point;
    for (devices_ptr = deviceList; *devices_ptr; devices_ptr++){
        gotNewMountPt = mountPtFromDevice(*devices_ptr, 
                                          &(m_points[i]), systemBus, error);
        // if TRUE...
        if (gotNewMountPt == TRUE) {     // move to next member in array
            i++;
        }
    }
    g_strfreev (deviceList);
    g_object_unref (proxy);
       
    *mountPoints = m_points;
    return TRUE;
}



2. Filter out each device that does not belong
In the mountPtFromDevice method below, the parameters are:

  • char* deviceName - the UDI (Unique Device ID) of a HAL volume device
  • gchar ** mount_point - upon successful return, contains the device’s mount point (e.g. “/”)
  • DBusGConnection *bus - allows connection with the D-Bus system bus
  • GError** error - upon return, error contains valid data if no paths are found

This method queries HAL about certain properties that pertain to the volume device that is identified by the deviceName parameter. If the device’s volume.is_mounted property is FALSE the function returns FALSE. Otherwise the device’s (volume.mount_point) is returned in the mount_point parameter.

gboolean mountPtFromDevice(char* deviceName, gchar **mount_point, DBusGConnection *bus, GError **error) {
    DBusGProxy *proxy;
    gboolean isMounted = FALSE;
    gboolean isReadOnly = TRUE;
    gchar *mt_point_tmp;
 
    // connect to the HAL device using the UDI deviceName
    proxy = dbus_g_proxy_new_for_name(bus,    // volume
                                     "org.freedesktop.Hal",
                                     deviceName,
                                     "org.freedesktop.Hal.Device" );
 
    // find out if the volume device is mounted
    dbus_g_proxy_call(proxy, "GetPropertyString", error,
                 G_TYPE_STRING, "volume.is_mounted", G_TYPE_INVALID,     // param #1
                 G_TYPE_BOOLEAN, &isMounted, G_TYPE_INVALID);            // ret val
 
    // if the device is not mounted, we’re not interested
    if (!isMounted) return FALSE;
 
    // grab the mount point from the volume.mount_point property
    dbus_g_proxy_call(proxy, "GetPropertyString", error,
         G_TYPE_STRING, "volume.mount_point", G_TYPE_INVALID,             // param #1
         G_TYPE_STRING, &mt_point_tmp, G_TYPE_INVALID);                   // ret val
 
    *mount_point = malloc(strlen(mt_point_tmp) + 1);
    strcpy(*mount_point, mt_point_tmp);
    g_object_unref(proxy);
 
    return TRUE;
}



Is the Device Removable?
With a valid mount path from my code above, it can be determined whether or not a device is removable, like a USB flash drive or external hard drive.

1. Get All Devices that use the Mount Path
In the isRemovable method below, the parameters are:

  • char* mountPath - the logical mount path of the device (e.g. “/”, “/media/disk”)
  • gboolean* isRemovable - upon successful return, indicates whether or not the device is removable
  • GError** error - upon return, error contains valid data if the mountPath provided is not found on the system

The code below connects to the D-Bus and asks the HAL Manager to return a list of all volume devices that are connected to the desired mountPath. Each volume identifier that is returned (there should be only one) gets passed on to the deviceIsRemovable method which determines whether or not each device is removable.

gboolean isRemovable(MIDPlatformSvc *obj, char* mountPath, gboolean *isRemovable, GError **error)
{
    DBusGConnection * systemBus;
    DBusGProxy *proxy;
    char **deviceList;
    char **devices_ptr;
       
    // Initialize the System DBus connection
    systemBus = dbus_g_bus_get(DBUS_BUS_SYSTEM, error);
       
    if (systemBus== NULL) {
        dbg("Unable to connect to dbus");
        g_error_free(*error);
        return FALSE;
    }
 
    proxy = dbus_g_proxy_new_for_name(  systemBus,
                                        "org.freedesktop.Hal",
                                        "/org/freedesktop/Hal/Manager",
                                        "org.freedesktop.Hal.Manager" );
 
// get a “list” of UDIs for devices that have the following string match:
//     volume.mount_point = mountPath
// NOTE: (there should be only one device with this match)
    dbus_g_proxy_call(proxy, "FindDeviceStringMatch", error, 
           G_TYPE_STRING, "volume.mount_point",                   // param #1
           G_TYPE_STRING, mountPath, G_TYPE_INVALID,              // param #2
           G_TYPE_STRV, &deviceList, G_TYPE_INVALID);             // ret val
 
    int len = g_strv_length(deviceList);
    int count;
 
    if (len == 0) {                          
    // no devices for that mount path
        g_set_error (error,
                     STORAGE_DEVICE_DNE,                 /* error domain */
                     1,                                  /* error code */
                     "Could not find a device with that mount path.n");
        return FALSE;
    }
    
    error = NULL;
    *isRemovable = deviceIsRemovable(deviceList[0], systemBus, error);
    
    // if deviceIsRemovable returned an error of any kind
    if (error != NULL)
        return FALSE;
 
    g_strfreev (deviceList);
    g_object_unref (proxy);
 
    return TRUE;
}



2. Find out if each Device is Removable
In the deviceIsRemovable method below, the parameters are:

  • char* deviceName - the UDI (Unique Device ID) of a HAL volume device
  • DBusGConnection *bus - allows connection with the D-Bus system bus
  • GError** error - upon return, error contains valid data if deviceName was not found on the system
  • returns TRUE / FALSE

This method queries HAL about certain properties that pertain to the volume whose identifier is passed in the deviceName parameter. The volume’s info.parent property identifies the storage device that owns the volume. Once we have that identifier, we can retrieve the storage.removable property of the storage device.

gboolean deviceIsRemovable(char* deviceName, DBusGConnection *bus, GError **error) {
    DBusGProxy *proxy;
    char *storageName;
    gboolean isRemovable;
 
    // connect to the volume device associated with deviceName
    proxy = dbus_g_proxy_new_for_name(  bus,
                                        "org.freedesktop.Hal",
                                        deviceName,
                                        "org.freedesktop.Hal.Device" );
    // find the parent of the volume device:
    // this will give you the storage device
    if (!dbus_g_proxy_call(proxy, "GetPropertyString", error, 
            G_TYPE_STRING, "info.parent", G_TYPE_INVALID,        // param #1
            G_TYPE_STRING, &storageName, G_TYPE_INVALID)) {        // ret val
        // did not find a storage device mapping to deviceName
        return FALSE;
    }
 
    if (proxy != NULL)
        g_object_unref(proxy);
 
    // connect to the storage device associated with storageName
    proxy = dbus_g_proxy_new_for_name( bus,
                                       "org.freedesktop.Hal",
                                       storageName,
                                       "org.freedesktop.Hal.Device" );
    // ask for the value of the storage device’s 
    // storage.removable property. Returns boolean
    if (!dbus_g_proxy_call(proxy, "GetPropertyString", error,
            G_TYPE_STRING, "storage.removable", G_TYPE_INVALID,        // param #1
            G_TYPE_BOOLEAN, &isRemovable, G_TYPE_INVALID)) {           // ret val
        return FALSE;
    }
 
    if (proxy != NULL)
        g_object_unref(proxy);
 
    return isRemovable;
}



Available Disk Space
With a valid mount path from my code above, it can be determined how much disc space is available under that path (e.g. “/”, “/media/disk”).

statvfs is a common Linux API that can be used to retrieve this kind of file system information (see the Linux statvfs man page for more information). The statvfs structure is used to store all file system related information.

In the getAvailableSpace method below, the parameters are:

  • char* mountPath - the logical mount path of the device (e.g. “/”, “/media/disk”)
  • gdouble* spaceMB – upon successful return, contains the amount of disk space available (in megabytes)
  • GError** error - upon return, error contains valid data if the specified mountPath is not found

To calculate the amount of disk space available, I use a simple equation:

FREE_SPACE_MB = FREE_BLOCKS_AVAILABLE * BLOCK_SIZE (BYTES) / NUM_BYTES_IN_MB



#include <sys/statvfs.h>
 
#define BYTES_IN_MB 1048576.0
 
gboolean getAvailableSpace(MIDPlatformSvc *obj, char* mountPath, gdouble *spaceMB, GError **error)
{
    struct statvfs sb;
 
    if (statvfs(mountPath, &sb) == -1) {
        g_set_error (error,
                     STORAGE_DEVICE_DNE,                 /* error domain */
                     1,                                 /* error code */
                     "Could not find a device with that mount path.n");
    }
 
    // available space in MB
    gdouble free_space = 
        (double) sb.f_bavail * ((double)sb.f_bsize / (double)BYTES_IN_MB);
   
    *spaceMB = free_space;
    return TRUE;
}



Writing Storage-Aware Applications

Because storage space is so valuable on smaller handheld devices, it’s important to write applications with a small storage footprint. It’s also important to know how much space is available on the device. Some devices may have flash SSD, compact memory, or external drives, and knowing where they are can help an application to know how much information it can cache locally. Hopefully this paper covered enough detail to get you started on writing your own location-aware applications for Linux.

Categories:
For more complete information about compiler optimizations, see our Optimization Notice.