#!/usr/bin/python3
###########################################################################
# MicroHAL.py -                                                           #
# ------------------------------                                          #
# begin     : Tue Oct 30 2004                                             #
# copyright : (C) 2004 by Simon Edwards                                   #
# email     : simon@simonzone.com                                         #
#                                                                         #
###########################################################################
#                                                                         #
#   This program is free software; you can redistribute it and/or modify  #
#   it under the terms of the GNU General Public License as published by  #
#   the Free Software Foundation; either version 2 of the License, or     #
#   (at your option) any later version.                                   #
#                                                                         #
###########################################################################

import os
import os.path
from SimpleCommandRunner import *

############################################################################        
class MicroHAL__(object):

    # Major device numbers for Linux block devices that support partitions.
    partitionblockdevs = [
        3,  # IDE harddisks
        8,  # SCSI disks
        13, # 8-bit MFM/RLL/IDE controller
        14, # BIOS harddrive callback support {2.6}
        21, # Acorn MFM hard drive interface
        22, # Second IDE hard disk/CD-ROM interface
        28, # ACSI disk (68k/Atari)
        33, # Third IDE hard disk/CD-ROM interface
        34, # Fourth IDE hard disk/CD-ROM interface
        36, # MCA ESDI hard disk
        44, # Flash Translation Layer (FTL) filesystems
        45, # Parallel port IDE disk devices
        48, # Mylex DAC960 PCI RAID controller; first controller
        49, # Mylex DAC960 PCI RAID controller; second controller
        50, # Mylex DAC960 PCI RAID controller; third controller
        51, # Mylex DAC960 PCI RAID controller; fourth controller
        52, # Mylex DAC960 PCI RAID controller; fifth controller
        53, # Mylex DAC960 PCI RAID controller; sixth controller
        54, # Mylex DAC960 PCI RAID controller; seventh controller
        55, # Mylex DAC960 PCI RAID controller; eigth controller
        56, # Fifth IDE hard disk/CD-ROM interface
        57, # Sixth IDE hard disk/CD-ROM interface
        65, # SCSI disk devices (16-31)
        66, # SCSI disk devices (32-47)
        67, # SCSI disk devices (48-63)
        68, # SCSI disk devices (64-79)
        69, # SCSI disk devices (80-95)
        70, # SCSI disk devices (96-111)
        71, # SCSI disk devices (112-127)
        72, # Compaq Intelligent Drive Array, first controller
        73, # Compaq Intelligent Drive Array, second controller
        74, # Compaq Intelligent Drive Array, third controller
        75, # Compaq Intelligent Drive Array, fourth controller
        76, # Compaq Intelligent Drive Array, fifth controller
        77, # Compaq Intelligent Drive Array, sixth controller
        78, # Compaq Intelligent Drive Array, seventh controller
        79, # Compaq Intelligent Drive Array, eigth controller
        80, # I2O hard disk
        81, # I2O hard disk
        82, # I2O hard disk
        83, # I2O hard disk
        84, # I2O hard disk
        85, # I2O hard disk
        86, # I2O hard disk
        87, # I2O hard disk
        88, # Seventh IDE hard disk/CD-ROM interface
        89, # Eighth IDE hard disk/CD-ROM interface
        90, # Ninth IDE hard disk/CD-ROM interface
        91, # Tenth IDE hard disk/CD-ROM interface
        92, # PPDD encrypted disk driver
        95, # IBM S/390 DASD block storage
        101, # AMI HyperDisk RAID controller
        102, # Compressed block device
        104, # Compaq Next Generation Drive Array, first controller
        105, # Compaq Next Generation Drive Array, second controller
        106, # Compaq Next Generation Drive Array, third controller
        107, # Compaq Next Generation Drive Array, fourth controller
        108, # Compaq Next Generation Drive Array, fifth controller
        109, # Compaq Next Generation Drive Array, sixth controller
        110, # Compaq Next Generation Drive Array, seventh controller
        111, # Compaq Next Generation Drive Array, eigth controller
        112, # IBM iSeries virtual disk
        114, # IDE BIOS powered software RAID interfaces such as the Promise Fastrak
        128, # SCSI disk devices (128-143)
        129, # SCSI disk devices (144-159)
        130, # SCSI disk devices (160-175)
        131, # SCSI disk devices (176-191)
        132, # SCSI disk devices (192-207)
        133, # SCSI disk devices (208-223)
        134, # SCSI disk devices (224-239)
        135, # SCSI disk devices (240-255)
        136, # Mylex DAC960 PCI RAID controller; ninth controller
        137, # Mylex DAC960 PCI RAID controller; tenth controller
        138, # Mylex DAC960 PCI RAID controller; eleventh controller
        139, # Mylex DAC960 PCI RAID controller; twelfth controller
        140, # Mylex DAC960 PCI RAID controller; thirteenth controller
        141, # Mylex DAC960 PCI RAID controller; fourteenth controller
        142, # Mylex DAC960 PCI RAID controller; fifteenth controller
        143, # Mylex DAC960 PCI RAID controller; sixteenth controller
        160, # Promise SX8 8-port SATA Disks on First Controller
        161  # Promise SX8 8-port SATA Disks on Second Controller
        ]
        
    floppydevs = [
        2, # Floppy disks
        40 # Syquest EZ135 parallel port removable drive
    ]
    
    cdromsdevs = [
        11, # SCSI CD-ROM devices
        12, # MSCDEX CD-ROM callback support {2.6}
        15, # Sony CDU-31A/CDU-33A CD-ROM
        16, # GoldStar CD-ROM
        17, # Optics Storage CD-ROM
        18, # Sanyo CD-ROM
        20, # Hitachi CD-ROM (under development)
        23, # Mitsumi proprietary CD-ROM
        24, # Sony CDU-535 CD-ROM
        25, # First Matsushita (Panasonic/SoundBlaster) CD-ROM
        26, # Second Matsushita (Panasonic/SoundBlaster) CD-ROM
        27, # Third Matsushita (Panasonic/SoundBlaster) CD-ROM
        28, # Fourth Matsushita (Panasonic/SoundBlaster) CD-ROM
        29, # Aztech/Orchid/Okano/Wearnes CD-ROM
        30, # Philips LMS CM-205 CD-ROM
        32, # Philips LMS CM-206 CD-ROM
        41, # MicroSolutions BackPack parallel port CD-ROM
        46, # Parallel port ATAPI CD-ROM devices
        47, # Parallel port ATAPI disk devices
        48, # Mylex DAC960 PCI RAID controller; first controller
        113 # IBM iSeries virtual CD-ROM
    ]

    burnerpacketdevs = [
        97  # Packet writing for CD/DVD devices
    ]

    # We provide a mapping between filesystems and kernelmodules, so filesystems
    # that are built as modules can be loaded on demand. (In fact, mountconfig will
    # load all filesystem modules needed to be able to mount all fstab entries.) 
    FilesystemProcDriver = [
        # fstab name, /proc name, kernel module name
        ('auto','autofs','autofs4'),
        ('iso9660','iso9660','isofs'),
        ('nfs','nfsd','nfs')
    ]
        
    ############################################################################
    def __init__(self):
        self.devices = None
        self.supportedfs = None
        self.partitionsizelines = None
        
    ############################################################################
    def getDevices(self):
        if self.devices is None:
            self.devices = []
            # Scan through /sys/block for devices. Find out which disks are
            # installed and should be shown in the listview. For real
            # disks we put a 'group' in the listview and under the group
            # we list the partitions, whether they are mounted or not.
            # FIXME: Check if sysfs is mounted.
            blockdevices = os.listdir("/sys/block")
            blockdevices.sort()
            
            for blockdevice in blockdevices:
                # We are looking for block devices that represent hard disks or
                # things that have partitions.
                # Grab the major device number
                fhandle = open(os.path.join("/sys/block",blockdevice,"dev"))
                devnumbers = fhandle.read()
                fhandle.close()
                devnum = int(devnumbers.split(":")[0])
                # Is it on our list of partition devices?
                if devnum in MicroHAL.partitionblockdevs:
                    fulldevice = os.path.join("/dev",blockdevice)
                
                    # Check for removable devices.
                    fhandle = open(os.path.join("/sys/block",blockdevice,"removable"))
                    removable = fhandle.read().strip()=="1"
                    fhandle.close()
                    
                    if not removable:
                        newdisk = Disk()
                    else:
                        if os.readlink(os.path.join("/sys/block",blockdevice,"device")).split(os.path.sep)[5].startswith("usb"):
                            newdisk = USBDisk()
                        else:
                            newdisk = RemovableDisk()
                    newdisk.dev = fulldevice
                    newdisk.major = devnum
                    newdisk.removable = removable
                    newdisk.modelname =  self.getModelName(fulldevice)
                    
                    if not removable or isinstance(newdisk, USBDisk):
                        # We have a not removable block device or a USB Disk here.
                        partitions = os.listdir(os.path.join("/sys/block",blockdevice))
                        partitions.sort()
                        i = 1
                        for partition in partitions:
                            # Look for a partitions device names and not the other
                            # stuff that lives in the directory.
                            if partition.startswith(blockdevice):
                                fullpartition = os.path.join("/dev",partition)
                                newpartition = Partition()
                                newpartition.dev = fullpartition
                                newpartition.size = self.getPartitionSize(fullpartition)
                                newpartition.num = i
                                newdisk.partitions.append(newpartition)
                                i += 1
                    self.devices.append(newdisk)
    
                elif devnum in MicroHAL.cdromsdevs:
                    fulldevice = os.path.join("/dev",blockdevice)
                    newdisk = RemovableDisk()
                    newdisk.dev = fulldevice
                    newdisk.major = devnum
                    newdisk.modelname = self.getModelName(fulldevice)
                    self.devices.append(newdisk)
                    
                elif devnum in MicroHAL.burnerpacketdevs:
                    fulldevice = os.path.join("/dev",blockdevice)
                    newdisk = BurnerDisk(self)
                    newdisk.dev = fulldevice
                    newdisk.major = devnum
                    newdisk.modelname = self.getModelName(fulldevice)
                    
                    self.devices.append(newdisk)

        return self.devices[:]

    ############################################################################
    def getPartitionSize(self,devicename):
        partitionname = os.path.basename(devicename)
    
        if self.partitionsizelines is None:
            fhandle = open('/proc/partitions')
            self.partitionsizelines = fhandle.readlines()
            fhandle.close()        
    
        i = 0
        for line in self.partitionsizelines:
            if i>=2:
                (major, minor, blocks, name) = line.split()
                if name==partitionname:
                    blocks = int(blocks) # 1K blocks now.
                    if blocks<1024:
                        return str(blocks)+" Kb"
                    if blocks<1024*1024:
                        return str(round(float(blocks)/1024.0,1))+" Mb"
                    blocks /= 1024
                    if blocks<1024*1024:
                        return str(round(float(blocks)/1024.0,1))+" Gb"
                    blocks /= 1024
                    return str(round(float(blocks)/1024.0,1))+" Tb"
            i += 1
        return None
        
    ############################################################################
    def getIDEModel(self,devname):
        try:
            fhandle = open(os.path.join("/proc/ide",os.path.basename(devname),"model"))
            model = fhandle.read()
            fhandle.close()
            return model.strip()
        except (OSError, IOError):
            return None
    
    ############################################################################
    def getSCSIModel(self,devname):
        try:
            fhandle_model = open(os.path.join("/sys/block",os.path.basename(devname),"device/model"))
            fhandle_vendor = open(os.path.join("/sys/block",os.path.basename(devname),"device/vendor"))
            model = fhandle_model.read()[:-1]
            vendor = fhandle_vendor.read()[:-1]
            fhandle_model.close()
            fhandle_vendor.close()
        except (OSError, IOError):
            pass
        if len(model) + len(vendor) == 0:
            return None
        return vendor + " " + model
        
    ############################################################################
    def getModelName(self,devname):
        modelname = self.getIDEModel(devname)
        if modelname is None:
            modelname = self.getSCSIModel(devname)
            if modelname is None:
                modelname = devname
        return " '"+modelname+"'"
    
    ############################################################################
    def getSupportedFileSystems(self):
        if self.supportedfs is None:
            if os.path.isfile("/proc/filesystems"):
                fhandle = open("/proc/filesystems")
                self.supportedfs = []
                for fs in fhandle.readlines():
                    try:
                        self.supportedfs.append(fs.strip().split()[1])
                    except IndexError:
                        self.supportedfs.append(fs.strip().split()[0])
            # The following filesystems aren't found there, but usually they are
            # supported.
            self.supportedfs.extend(('swap','shm'))
        return self.supportedfs[:]
        
    ############################################################################
    def isSupportedFileSystem(self,fs):
        # Look up the /proc and kernel driver name for the given filesystem type.
        module = fs
        proc = fs
        for entry in self.FilesystemProcDriver:
            if entry[0]==fs:
                proc = entry[1]
                module = entry[2]
        
        if proc not in self.getSupportedFileSystems():
            # The filesystem is not supported by the running kernel,
            # but it might be built as module, so we try to load that.
            retval, msg = SimpleCommandRunner().run([b"/sbin/modprobe",module])
            if retval > 0:
                print(msg)
                print("Couldn't load driver " + module + " for filesystem " + fs)
            # Force refresh of list of supported filesystems
            self.supportedfs = None
        return proc in self.getSupportedFileSystems()
        
############################################################################        
class Device(object):
    def __init__(self):
        self.dev = None
        self.major = None
        self.removable = None
        self.uuid = None
        self.label = None
        
    def getDev(self):
        return self.dev
        
    def getMajor(self):
        return self.major
        
    def getName(self):
        return self.dev
        
    def getUUID(self):
        if not self.uuid:
            return ""
        return self.uuid
        
    def getLabel(self):
        if not self.label:
            return ""
        return self.label
        
    def isRemovable(self):
        return self.removable
        
    def __str__(self):
        return "Name: %s, Device: %s, Major: %i, " % (self.getName(),
            self.getDev(),
            self.getMajor())
        
############################################################################        
class Disk(Device):
    def __init__(self):
        super(Disk,self).__init__()
        self.removable = False
        self.partitions = []
        self.modelname = None
        self.iconname = "hi16-hdd"
        
    def getModelName(self):
        return self.modelname
    
    def getName(self):
        if self.getModelName():  
            return i18n("Disk ")+self.getModelName()
        else:
            return i18n("Unknown Disk")
        
    def getPartitions(self):
        return self.partitions[:]
        
    def appendPartition(self,partition):
        self.partitions.append(partition)
        def cmpNum(a,b): return cmp(a.num,b.num)
        self.partitions.sort(cmpNum)
    
    def __str__(self):
        x = Device.__str__(self) + "Partitions: ["
        for part in self.partitions:
            x += "["
            x += str(part)
            x += "], "
        x += "],"
        return x
    
############################################################################        
class RemovableDisk(Disk):
    def __init__(self):
        super(RemovableDisk,self).__init__()
        self.iconname = "hi16-cdrom"
        self.removable = True

    def getName(self):
        return "Optical Disk "+self.getModelName()
        
############################################################################        
class USBDisk(Disk):
    def __init__(self):
        super(USBDisk,self).__init__()
        self.iconname = "hi16-usbpen"
        self.removable = True
        
    def getName(self):
        return "Removable USB Disk "+self.getModelName()
        
############################################################################        
class BurnerDisk(RemovableDisk):
    def __init__(self):
        super(BurnerDisk,self).__init__()
        self.iconname = "hi16-burner"

    def getName(self):
        return "Burner "+self.modelname
        
############################################################################        
class Floppy(Device):
    def isRemovable(self):
        return True
    
    def getName(self):
        return "Floppy"
        
############################################################################        
class Partition(Device):
    def __init__(self):
        super(Partition,self).__init__()
        self.num = None
        self.size = None
        self.iconname = "hi16-hdd"
    
    def getName(self):
        return str(self.num)+" Partition "+ self.getSize()
        # A group item for all of the other kernel/system mount entries.
        
    def getSize(self):
        return self.size
    
    def __str__(self):
        return "Device: %s, Num: %i, Size: %s, Label: %s, UUID: %s" % (self.dev, self.num, self.getSize(), 
                                                self.getLabel(), self.getUUID())
    
############################################################################        
class FakeSystemDevice(object):
    def getName(self): return "System"
    def getIconName(self): return "hi16-blockdevice"
    
############################################################################
class MicroHAL(object):

    # Major device numbers for Linux block devices that support partitions.
    partitionblockdevs = [
        3,  # IDE harddisks
        8,  # SCSI disks
        13, # 8-bit MFM/RLL/IDE controller
        14, # BIOS harddrive callback support {2.6}
        21, # Acorn MFM hard drive interface
        22, # Second IDE hard disk/CD-ROM interface
        28, # ACSI disk (68k/Atari)
        33, # Third IDE hard disk/CD-ROM interface
        34, # Fourth IDE hard disk/CD-ROM interface
        36, # MCA ESDI hard disk
        44, # Flash Translation Layer (FTL) filesystems
        45, # Parallel port IDE disk devices
        48, # Mylex DAC960 PCI RAID controller; first controller
        49, # Mylex DAC960 PCI RAID controller; second controller
        50, # Mylex DAC960 PCI RAID controller; third controller
        51, # Mylex DAC960 PCI RAID controller; fourth controller
        52, # Mylex DAC960 PCI RAID controller; fifth controller
        53, # Mylex DAC960 PCI RAID controller; sixth controller
        54, # Mylex DAC960 PCI RAID controller; seventh controller
        55, # Mylex DAC960 PCI RAID controller; eigth controller
        56, # Fifth IDE hard disk/CD-ROM interface
        57, # Sixth IDE hard disk/CD-ROM interface
        65, # SCSI disk devices (16-31)
        66, # SCSI disk devices (32-47)
        67, # SCSI disk devices (48-63)
        68, # SCSI disk devices (64-79)
        69, # SCSI disk devices (80-95)
        70, # SCSI disk devices (96-111)
        71, # SCSI disk devices (112-127)
        72, # Compaq Intelligent Drive Array, first controller
        73, # Compaq Intelligent Drive Array, second controller
        74, # Compaq Intelligent Drive Array, third controller
        75, # Compaq Intelligent Drive Array, fourth controller
        76, # Compaq Intelligent Drive Array, fifth controller
        77, # Compaq Intelligent Drive Array, sixth controller
        78, # Compaq Intelligent Drive Array, seventh controller
        79, # Compaq Intelligent Drive Array, eigth controller
        80, # I2O hard disk
        81, # I2O hard disk
        82, # I2O hard disk
        83, # I2O hard disk
        84, # I2O hard disk
        85, # I2O hard disk
        86, # I2O hard disk
        87, # I2O hard disk
        88, # Seventh IDE hard disk/CD-ROM interface
        89, # Eighth IDE hard disk/CD-ROM interface
        90, # Ninth IDE hard disk/CD-ROM interface
        91, # Tenth IDE hard disk/CD-ROM interface
        92, # PPDD encrypted disk driver
        95, # IBM S/390 DASD block storage
        101, # AMI HyperDisk RAID controller
        102, # Compressed block device
        104, # Compaq Next Generation Drive Array, first controller
        105, # Compaq Next Generation Drive Array, second controller
        106, # Compaq Next Generation Drive Array, third controller
        107, # Compaq Next Generation Drive Array, fourth controller
        108, # Compaq Next Generation Drive Array, fifth controller
        109, # Compaq Next Generation Drive Array, sixth controller
        110, # Compaq Next Generation Drive Array, seventh controller
        111, # Compaq Next Generation Drive Array, eigth controller
        112, # IBM iSeries virtual disk
        114, # IDE BIOS powered software RAID interfaces such as the Promise Fastrak
        128, # SCSI disk devices (128-143)
        129, # SCSI disk devices (144-159)
        130, # SCSI disk devices (160-175)
        131, # SCSI disk devices (176-191)
        132, # SCSI disk devices (192-207)
        133, # SCSI disk devices (208-223)
        134, # SCSI disk devices (224-239)
        135, # SCSI disk devices (240-255)
        136, # Mylex DAC960 PCI RAID controller; ninth controller
        137, # Mylex DAC960 PCI RAID controller; tenth controller
        138, # Mylex DAC960 PCI RAID controller; eleventh controller
        139, # Mylex DAC960 PCI RAID controller; twelfth controller
        140, # Mylex DAC960 PCI RAID controller; thirteenth controller
        141, # Mylex DAC960 PCI RAID controller; fourteenth controller
        142, # Mylex DAC960 PCI RAID controller; fifteenth controller
        143, # Mylex DAC960 PCI RAID controller; sixteenth controller
        160, # Promise SX8 8-port SATA Disks on First Controller
        161  # Promise SX8 8-port SATA Disks on Second Controller
        ]
        
    floppydevs = [
        2, # Floppy disks
        40 # Syquest EZ135 parallel port removable drive
    ]
    
    cdromsdevs = [
        11, # SCSI CD-ROM devices
        12, # MSCDEX CD-ROM callback support {2.6}
        15, # Sony CDU-31A/CDU-33A CD-ROM
        16, # GoldStar CD-ROM
        17, # Optics Storage CD-ROM
        18, # Sanyo CD-ROM
        20, # Hitachi CD-ROM (under development)
        23, # Mitsumi proprietary CD-ROM
        24, # Sony CDU-535 CD-ROM
        25, # First Matsushita (Panasonic/SoundBlaster) CD-ROM
        26, # Second Matsushita (Panasonic/SoundBlaster) CD-ROM
        27, # Third Matsushita (Panasonic/SoundBlaster) CD-ROM
        28, # Fourth Matsushita (Panasonic/SoundBlaster) CD-ROM
        29, # Aztech/Orchid/Okano/Wearnes CD-ROM
        30, # Philips LMS CM-205 CD-ROM
        32, # Philips LMS CM-206 CD-ROM
        41, # MicroSolutions BackPack parallel port CD-ROM
        46, # Parallel port ATAPI CD-ROM devices
        47, # Parallel port ATAPI disk devices
        48, # Mylex DAC960 PCI RAID controller; first controller
        113 # IBM iSeries virtual CD-ROM
    ]

    burnerpacketdevs = [
        97  # Packet writing for CD/DVD devices
    ]

    # We provide a mapping between filesystems and kernelmodules, so filesystems
    # that are built as modules can be loaded on demand. (In fact, mountconfig will
    # load all filesystem modules needed to be able to mount all fstab entries.) 
    FilesystemProcDriver = [
        # fstab name, /proc name, kernel module name
        ('auto','autofs','autofs4'),
        ('iso9660','iso9660','isofs'),
        ('nfs','nfsd','nfs')
    ]

    ############################################################################
    def __init__(self):
        self.devices = None
        self.supportedfs = None

    ############################################################################
    def getDevices(self):
        if self.devices is None:
            self.devices = []
    
            retval, msg = SimpleCommandRunner().run([b"/usr/bin/lshal"])
            if retval is None or retval > 0:
                return []
            
            partition_to_uid = {}
            uid_to_disk = {}
            
            READING_TOP = 0
            READING_DEVICE = 1
            state = READING_TOP
            
            parsed_hash = None
            current_uid = None
            
            for line in msg.split('\n'):
                
                if state==READING_TOP:
                    if line.startswith("udi ="):
                        parsed_hash = {}
                        current_uid = self._parseString(line[6:])
                        state = READING_DEVICE
                        
                elif state==READING_DEVICE:
                    if line=="" or not line.startswith("  "):
                        # Detect the end of this block of device data.
                        state = READING_TOP
                        
                        if "info.category" in parsed_hash:
                        
                            new_device = None
                            
                            capabilities_string = " ".join(parsed_hash["info.capabilities"])
                            capabilities = self._parseStringList(capabilities_string)
                            
                            category = self._parseString(' '.join(parsed_hash["info.category"]))
                            if category=="volume":
                                # Is it a volume?
                                
                                is_disc = parsed_hash.get("volume.is_disc")
                                if is_disc is not None and is_disc[0]=='true':
                                    continue
                                
                                is_partition = parsed_hash.get("volume.is_partition")
                                if is_partition is not None:
                                    is_partition = is_partition[0]

                                if is_partition=='true':
                                    new_device = Partition()
                                    new_device.num = int(parsed_hash["volume.partition.number"][0])
                                    partition_to_uid[new_device] = current_uid
                            
                                    if "info.parent" in parsed_hash:
                                        parent_uid = self._parseString(' '.join(parsed_hash["info.parent"]))
                                        partition_to_uid[new_device] = parent_uid
                                
                                else:
                                    new_device = Disk()
                                    uid_to_disk[current_uid] = new_device
                                    
                                if "volume.uuid" in parsed_hash:
                                    new_device.uuid = self._parseString(' '.join(parsed_hash["volume.uuid"]))
                                
                                if "volume.label" in parsed_hash:
                                    new_device.label = self._parseString(parsed_hash["volume.label"][0])
                                    # If HAL returns label beginning with '#', it usually means that the
                                    # actual label contains an Unix path. So we replace '#' with '/'.
                                    if len(new_device.label) and new_device.label[0]=='%':
                                        new_device.label = new_device.label.replace('%', '/')
                                
                                if "volume.size" in parsed_hash:
                                    size = parsed_hash["volume.size"][0]
                                    new_device.size = self.formatSizeBytes(int(size))
                                else:
                                    new_device.size = "?"
                                
                                
                            # is it a storage device?
                            elif category=="storage":
                                storage_model = self._parseString(' '.join(parsed_hash["storage.model"]))
                                storage_removable = parsed_hash["storage.removable"][0]=="true"
    
                                if "storage.cdrom" in capabilities:
                                    
                                    if "storage.cdrom.cdrw" in parsed_hash \
                                            and parsed_hash["storage.cdrom.cdrw"][0]=="true":
                                        new_device = BurnerDisk()     
                                    else:
                                        new_device=  RemovableDisk()
                                    
                                elif "storage.floppy" in capabilities:
                                    new_device = FloppyDevice()
                                else:
                                    if "storage.bus" in parsed_hash \
                                            and self._parseString(' '.join(parsed_hash["storage.bus"]))=="usb":
                                        
                                        new_device = USBDisk()
                                    else:
                                        new_device = Disk()
                                    
                                new_device.modelname = storage_model
                                uid_to_disk[current_uid] = new_device
                            else:
                                # Not interesting, skip it.
                                continue
                            
                            # Handle the generic properties.
                            new_device.dev = self._parseString(' '.join(parsed_hash["block.device"]))
                            new_device.major = int(parsed_hash["block.major"][0])
                        
                            self.devices.append(new_device)
                                
                    else:
                        # Keep on accumulating info about this device.
                        parts = line.split()
                        parsed_hash[ parts[0] ] = parts[2:]
            
            # Attach the partitions to thier devices.
            for partition in list(partition_to_uid.keys()):
                parent = partition_to_uid[partition]
                if parent in list(uid_to_disk.keys()):
                    parent_device = uid_to_disk[parent]
                    parent_device.appendPartition(partition)
                    self.devices.remove(partition)

        return self.devices[:]

    ############################################################################
    def _parseStringList(self,source_string):
        STATE_TOP = 0
        STATE_STRING = 1
        
        state = STATE_TOP
        current_string = ""
        string_list = []
        for c in source_string:
            if state==STATE_TOP:
                if c=='}':
                    break
                if c=="'":
                    state = STATE_STRING
            else:
                if c=="'":
                    state = STATE_TOP
                    string_list.append(current_string)
                    current_string = ""
                else:
                    current_string += c
        
        return string_list

    ############################################################################
    def _parseString(self,source_string):
        STATE_TOP = 0
        STATE_STRING = 1
        
        state = STATE_TOP
        current_string = ""
        for c in source_string:
            if state==STATE_TOP:
                if c=="'":
                    state = STATE_STRING
            else:
                if c=="'":
                    break
                else:
                    current_string += c
        return current_string
        
    ############################################################################
    def formatSizeBytes(self,size):
        if size<1024:
            return str(size+" B")
        if size<1024*1042:
            return str(round(float(size)/1024.0,1))+" Kb"
        size /= 1024
        if size<1024*1024:
            return str(round(float(size)/1024.0,1))+" Mb"
        size /= 1024
        if size<1024*1024:
            return str(round(float(size)/1024.0,1))+" Gb"
        size /= 1024
        return str(round(float(size)/1024.0,1))+" Tb"
        
    ############################################################################
    def getSupportedFileSystems(self):
        if self.supportedfs is None:
            if os.path.isfile("/proc/filesystems"):
                fhandle = open("/proc/filesystems")
                self.supportedfs = []
                for fs in fhandle.readlines():
                    try:
                        self.supportedfs.append(fs.strip().split()[1])
                    except IndexError:
                        self.supportedfs.append(fs.strip().split()[0])
            # The following filesystems aren't found there, but usually they are
            # supported.
            self.supportedfs.extend(('swap','shm'))
        return self.supportedfs[:]
        
    ############################################################################
    def isSupportedFileSystem(self,fs):
        # Look up the /proc and kernel driver name for the given filesystem type.
        module = fs
        proc = fs
        for entry in self.FilesystemProcDriver:
            if entry[0]==fs:
                proc = entry[1]
                module = entry[2]
        
        if proc not in self.getSupportedFileSystems():
            # The filesystem is not supported by the running kernel,
            # but it might be built as module, so we try to load that.
            retval, msg = SimpleCommandRunner().run([b"/sbin/modprobe",module.encode('utf-8')])
            if retval > 0:
                print(msg)
                print("Couldn't load driver " + module + " for filesystem " + fs)
            # Force refresh of list of supported filesystems
            self.supportedfs = None
        return proc in self.getSupportedFileSystems()
        
    ############################################################################
    def getDeviceByLabel(self, label):
        for device in self.getDevices():
            if device.getLabel()==label:
                return device
            
            if isinstance(device,Disk):
                for partition in device.getPartitions():
                    if partition.getLabel()==label:
                        return partition
        return None
        
    def getLabelByDevice(self, device):
        for item in self.getDevices():
            for partition in item.partitions:
                if partition.dev==device:
                    return partition.label
        print("No Label found for ",device)
        return ""
        
    def getUUIDByDevice(self, device):
        for item in self.getDevices():
            for partition in item.partitions:
                #print partition, partition.getUUID()
                if partition.dev==device:
                    return partition.uuid
        print("No UUID found for ",device)
        return ""
        
    def getDeviceByUUID(self, uuid):
        for device in self.getDevices():
            if device.getUUID()==uuid:
                return device
            
            if isinstance(device,Disk):
                for partition in device.getPartitions():
                    if partition.getUUID()==uuid:
                        return partition
                
        return None
    
############################################################################
if __name__=='__main__':
    hal = MicroHAL()
    for item in hal.getDevices():
        print((str(item)))
        
    print()
    
    #"""
    for item in hal.getDevices():
        for partition in item.partitions:
            print(partition, partition.getLabel())
    #"""
    #realhal = RealHAL()
    #for item in realhal.getDevices():
    #    print(str(item))
        
    print()


