[master 2/8] edd: Add EDD 4 code and some logging cleanups.

vathpela installerbot-noreply at redhat.com
Fri Oct 9 19:32:02 UTC 2015


From: Peter Jones <pjones at redhat.com>

This adds support for EDD4 types and fields (some of which the kernel
does not yet expose), as well as only logging relevant edd information.

Here's an example log from a system with good EDD data booted from USB,
installing to a locally connected SATA disk:

15:16:01,198 INFO blivet: edd: collected mbr signatures: {u'sda': '0x00022a9f', u'sdb': '0x946a5e4b'}
15:16:01,199 WARN blivet: edd: interface type USB is not implemented (/sys/firmware/edd/int13_dev80)
15:16:01,199 DEBUG blivet: edd: data extracted from 0x80:
	path: /sys/firmware/edd/int13_dev80, version: 0x21
	type: USB, mbr_signature: 0x946a5e4b, sectors: 15667200
	host_bus: PCI pci_dev: ff:ff.255 channel: 255
	usb_serial: 808463921
15:16:01,199 INFO blivet: edd: matched 0x80 to sdb using MBR sig
15:16:01,202 DEBUG blivet: edd: data extracted from 0x81:
	path: /sys/firmware/edd/int13_dev81, version: 0x30
	type: SATA, mbr_signature: 0x00022a9f, sectors: 312581808
	sysfs pci path: ../devices/pci0000:00/0000:00:1f.2/ata1/host1/target1:0:0/1:0:0:0/block/sda
	host_bus: PCI pci_dev: 00:1f.2 channel: 255
	ata_device: 0
15:16:01,202 INFO blivet: edd: matched 0x81 to sda using pci_dev

Isn't that nice?  It is nice.

Signed-off-by: Peter Jones <pjones at redhat.com>
---
 blivet/devicelibs/edd.py | 353 ++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 301 insertions(+), 52 deletions(-)

diff --git a/blivet/devicelibs/edd.py b/blivet/devicelibs/edd.py
index c585bf0..1cf8e51 100644
--- a/blivet/devicelibs/edd.py
+++ b/blivet/devicelibs/edd.py
@@ -28,56 +28,215 @@
 import os
 import re
 import struct
+import copy
 
 from .. import util
-from ..util import open # pylint: disable=redefined-builtin
 
 log = logging.getLogger("blivet")
 
-re_host_bus = re.compile(r'^PCI\S*\s*(\S*)\s*channel: (\S*)\s*$')
-re_interface_scsi = re.compile(r'^SCSI\s*id: (\S*)\s*lun: (\S*)\s*$')
+re_host_bus_pci = re.compile(r'^(PCIX|PCI|XPRS|HTPT)\s*(\S*)\s*channel: (\S*)\s*$')
+re_interface_atapi = re.compile(r'^ATAPI\s*device: (\S*)\s*lun: (\S*)\s*$')
 re_interface_ata = re.compile(r'^ATA\s*device: (\S*)\s*$')
-re_interface_sata = re.compile(r'^SATA\s*device: (\S*)\s*$')
+re_interface_scsi = re.compile(r'^SCSI\s*id: (\S*)\s*lun: (\S*)\s*$')
+re_interface_usb = re.compile(r'^USB\s*serial_number: (\S*)\s*$')
+re_interface_1394 = re.compile(r'^1394\s*eui: (\S*)\s*$')
+re_interface_fibre = re.compile(r'^FIBRE\s*wwid: (\S*)\s*lun: (\S*)\s*$')
+re_interface_i2o = re.compile(r'^I2O\s*identity_tag: (\S*)\s*$')
+# pretty sure the RAID definition using "identity_tag" is basically a kernel
+# bug, but it's part of the ABI now, so it sticks.  The format of the
+# scnprintf() is at least correct.
+re_interface_raid = re.compile(r'^RAID\s*identity_tag: (\S*)\s*$')
+re_interface_edd3_sata = re.compile(r'^SATA\s*device: (\S*)\s*$')
+# EDD 4 features from 2010 and later.  Awesomely, the "version" output from
+# int 13 AH=41h says: AH Version of extensions. Shall be set to 30h,
+# so there's no way to distinguish these from EDD 3, even thuogh SATA does
+# differ.  In theory, if we're on <4.0, pmp should always be all 0's.
+re_interface_edd4_sata = re.compile(r'^SATA\s*device: (\S*)\s*pmp: (\S*)\s*$')
+re_interface_sas = re.compile(r'^SAS\s*sas_address: (\S*)\s*lun: \(\S*\)\s*$')
+# to make life difficult, when it finds an unknown interface type string,
+# the kernel prints the values without the string.  But it does print the
+# anchoring tab that would go between them...
+re_interface_unknown = re.compile(r'^(\S*)\s*unknown: (\S*) (\S*)\s*$')
 
 class EddEntry(object):
     """ This object merely collects what the /sys/firmware/edd/* entries can
         provide.
     """
     def __init__(self, sysfspath):
-        self.type = None
+        # some misc data from various files...
+        self.sysfspath = sysfspath
+        """ sysfspath is the path we're probing
+        """
+
+        self.sysfslink = None
+        """ The path /sys/block/BLAH is a symlink link to once we've resolved
+            that this is a particular device.  Used for logging later.
+        """
+
+        self.version = util.get_sysfs_attr(sysfspath, "version")
+        """ The edd version this entry claims conformance with, from
+            /sys/firmware/edd/int13_devXX/version """
 
-        self.ata_device = None
-        self.channel = None
         self.mbr_sig = None
+        """ The MBR signature data from edd/int13_devXX/mbr_signature """
+
+        self.sectors = None
+        """ The number of sectors on the device from edd/int13_devXX/sectors """
+
+        # Now the data from edd/int13_devXX/host_bus
+        self.host_bus = None
+        """ The ID string for the host bus type, from
+            edd/int13_devXX/host_bus.
+        """
         self.pci_dev = None
+        """ The host bus bus:device.function, from edd/int13_devXX/host_bus.
+        """
+        self.channel = None
+        """ The host bus device's channel number edd/int13_devXX/host_bus.
+            The spec says:
+              Channel number. If more than one interface of the same type is
+              accessed through a single Bus, Slot, Function, then the channel
+              number shall identify each interface. If there is only one
+              interface, the content of this field shall be cleared to zero. If
+              there are two interfaces, such as an ATA Primary and Secondary
+              interface, the primary interface shall be zero, and the secondary
+              interface shall be one.
+
+              Values 00h through FEh shall represent a valid Channel Number.
+
+              Value FFh shall indicate that this field is not used.
+
+              If the device is connected to a SATA controller functioning in
+              non-PATA emulation mode, this byte shall be FFh.
+        """
+
+        # And now the various data from different formats of
+        # edd/int13_devXX/interface .
+        self.interface = None
+        """ interface is the actual contents of the interface file,
+            preserved for logging later.
+        """
+
+        self.type = None
+        """ The device type from edd/int13_devXX/interface.
+        """
+
+        self.atapi_device = None
+        """ The device number of the ATAPI device from
+            edd/int13_devXX/interface when self.type is ATAPI.
+        """
+        self.atapi_lun = None
+        """ The LUN of the ATAPI device from edd/int13_devXX/interface when
+            self.type is ATAPI.
+        """
+
+        self.ata_device = None
+        """ The device number from edd/int13_devXX/interface when self.type
+            is ATA or SATA (because Linux treats these the same.)
+        """
+        self.ata_pmp = None
+        """ The ATA port multiplier ID from edd/int13_devXX/interface when
+            self.type is SATA.
+        """
+
         self.scsi_id = None
+        """ The SCSI device ID from edd/int13_devXX/interface when
+            self.type is SCSI
+        """
         self.scsi_lun = None
-        self.sectors = None
-        self.sysfslink = ""
-        self.sysfspath = sysfspath
-        self.version = util.get_sysfs_attr(sysfspath, "version")
+        """ The SCSI device LUN from edd/int13_devXX/interface when
+            self.type is SCSI
+        """
+
+        self.usb_serial = None
+        """ The USB storage device's serial number from
+            edd/int13_devXX/interface when self.type is USB.
+        """
+
+        self.ieee1394_eui64 = None
+        """ The Firewire/IEEE-1394 EUI-64 ID from edd/int13_devXX/interface
+            when self.type is 1394.
+        """
+
+        self.fibre_wwid = None
+        """ The FibreChannel WWID from edd/int13_devXX/interface when
+            self.type is FIBRE.
+        """
+        self.fibre_lun = None
+        """ The FibreChannel LUN from edd/int13_devXX/interface when
+            self.type is FIBRE.
+        """
+
+        self.i2o_identity = None
+        """ The I2O Identity from edd/int13_devXX/interface when self.type
+            is I2O.
+        """
+
+        self.sas_address = None
+        """ The SAS Address from edd/int13_devXX/interface when self.type
+            is SAS.
+        """
+        self.sas_lun = None
+        """ The SAS LUN from edd/int13_devXX/interface when self.type is SAS.
+        """
 
         self.load()
 
+    def _fmt(self, line_pad, separator):
+        s = "%(t)spath: %(sysfspath)s version: %(version)s %(nl)s" \
+            "mbr_signature: %(mbr_sig)s sectors: %(sectors)s"
+        if self.type != None:
+            s += " %(type)s"
+        if self.sysfslink != None:
+            s += "%(nl)s%(t)ssysfs pci path: %(sysfslink)s"
+        if any([self.host_bus, self.pci_dev, self.channel != None]):
+            s += "%(nl)s%(t)shost_bus: %(host_bus)s pci_dev: %(pci_dev)s "\
+                "channel: %(channel)s"
+        if self.interface != None:
+            s += "%(nl)s%(t)sinterface: \"%(interface)s\""
+        if any([self.atapi_device != None, self.atapi_lun != None]):
+            s += "%(nl)s%(t)satapi_device: %(atapi_device)s " \
+                 "atapi_lun: %(atapi_lun)s"
+        if self.ata_device != None:
+            s += "%(nl)s%(t)sata_device: %(ata_device)s"
+            if self.ata_pmp != None:
+                s += ", ata_pmp: %(ata_pmp)s"
+        if any([self.scsi_id != None, self.scsi_lun != None]):
+            s += "%(nl)s%(t)sscsi_id: %(scsi_id)s, scsi_lun: %(scsi_lun)s"
+        if self.usb_serial != None:
+            s += "%(nl)s%(t)susb_serial: %(usb_serial)s"
+        if self.ieee1394_eui64 != None:
+            s += "%(nl)s%(t)s1394_eui: %(ieee1394_eui64)s"
+        if any([self.fibre_wwid,self.fibre_lun]):
+            s += "%(nl)s%(t)sfibre wwid: %(fibre_wwid)s lun: %s(fibre_lun)s"
+        if self.i2o_identity != None:
+            s += "%(nl)s%(t)si2o_identity: %(i2o_identity)s"
+        if any([self.sas_address, self.sas_lun]):
+            s += "%(nl)s%(t)ssas_address: %(sas_address)s sas_lun: %(sas_lun)s"
+
+        d = copy.copy(self.__dict__)
+        d['t']=line_pad
+        d['nl']=separator
+
+        return s % d
+
     def __str__(self):
-        return \
-            "\tpath: %(sysfspath)s version: %(version)s\n" \
-            "\tsysfs pci path: %(sysfslink)s\n" \
-            "\ttype: %(type)s, ata_device: %(ata_device)s\n" \
-            "\tchannel: %(channel)s, mbr_signature: %(mbr_sig)s\n" \
-            "\tpci_dev: %(pci_dev)s, scsi_id: %(scsi_id)s," \
-            " scsi_lun: %(scsi_lun)s, sectors: %(sectors)s" % self.__dict__
+        return self._fmt('\t', '\n')
+
+    def __repr__(self):
+        return "<EddEntry%s>" % (self._fmt(' ', ''),)
 
     def load(self):
         interface = util.get_sysfs_attr(self.sysfspath, "interface")
+        # save this so we can log it from the matcher.
+        self.interface = interface
         if interface:
             try:
                 self.type = interface.split()[0]
-                unsupported = ("ATAPI", "USB", "1394", "FIBRE", "I2O", "RAID")
-                if self.type in unsupported:
-                    log.warning("edd: interface type %s is not implemented (%s)",
-                                self.type, self.sysfspath)
-                    log.warning("edd: interface details: %s", interface)
+                if self.type == "ATAPI":
+                    match = re_interface_atapi.match(interface)
+                    self.atapi_device = int(match.group(1))
+                    self.atapi_lun = int(match.group(2))
                 elif self.type == "ATA":
                     match = re_interface_ata.match(interface)
                     self.ata_device = int(match.group(1))
@@ -85,9 +244,42 @@ def load(self):
                     match = re_interface_scsi.match(interface)
                     self.scsi_id = int(match.group(1))
                     self.scsi_lun = int(match.group(2))
+                elif self.type == "USB":
+                    match = re_interface_usb.match(interface)
+                    self.usb_serial = int(match.group(1), base=16)
+                elif self.type == "1394":
+                    match = re_interface_1394.match(interface)
+                    self.ieee1394_eui64 = int(match.group(1), base=16)
+                elif self.type == "FIBRE":
+                    match = re_interface_fibre.match(interface)
+                    self.fibre_wwid = int(match.group(1), base=16)
+                    self.fibre_lun = int(match.group(2), base=16)
+                elif self.type == "I2O":
+                    match = re_interface_i2o.match(interface)
+                    self.i2o_identity = int(match.group(1), base=16)
+                elif self.type == "RAID":
+                    match = re_interface_raid.match(interface)
+                    self.raid_array = int(match.group(1), base=16)
                 elif self.type == "SATA":
-                    match = re_interface_sata.match(interface)
-                    self.ata_device = int(match.group(1))
+                    match = re_interface_edd4_sata.match(interface)
+                    if match:
+                        self.ata_device = int(match.group(1))
+                        self.ata_pmp = int(match.group(2))
+                    else:
+                        match = re_interface_edd3_sata.match(interface)
+                        self.ata_device = int(match.group(1))
+                elif self.type == "SAS":
+                    sas_match = re_interface_sas.match(interface)
+                    unknown_match = re_interface_unknown.match(interface)
+                    if sas_match:
+                        self.sas_address = int(sas_match.group(1), base=16)
+                        self.sas_lun = int(sas_match.group(2), base=16)
+                    elif unknown_match:
+                        self.sas_address = int(unknown_match.group(1), base=16)
+                        self.sas_lun = int(unknown_match.group(2), base=16)
+                    else:
+                        log.warning("edd: can not match interface for %s: %s",
+                                    self.sysfspath, interface)
                 else:
                     log.warning("edd: can not match interface for %s: %s",
                                 self.sysfspath, interface)
@@ -104,13 +296,14 @@ def load(self):
             self.sectors = int(sectors)
         hbus = util.get_sysfs_attr(self.sysfspath, "host_bus")
         if hbus:
-            match = re_host_bus.match(hbus)
+            match = re_host_bus_pci.match(hbus)
             if match:
-                self.pci_dev = match.group(1)
-                self.channel = int(match.group(2))
+                self.host_bus = match.group(1)
+                self.pci_dev = match.group(2)
+                self.channel = int(match.group(3))
             else:
                 log.warning("edd: can not match host_bus for %s: %s",
-                    self.sysfspath, hbus)
+                            self.sysfspath, hbus)
 
 class EddMatcher(object):
     """ This object tries to match given entry to a disk device name.
@@ -141,6 +334,17 @@ def devname_from_ata_pci_dev(self):
             # for ATA (non-SATA) devices.  Also in EDD 3, SATA port multipliers
             # aren't represented in any way.
             #
+            # In EDD 4, which unfortunately says to leave 0x30 as the version
+            # number, the port multiplier id is an additional field on the
+            # interface.  So basically we should try to use the value the
+            # kernel gives us*, but we can't trust it.  Thankfully there
+            # won't be a devX.Y.Z (i.e. a port multiplier device) in sysfs
+            # that collides with devX.Z (a non-port-multiplied device),
+            # so if we get a value from the kernel, we can try with and
+            # without it.
+            #
+            # * When the kernel finally learns of these facts...
+            #
             if components[4] != '0000:%s' % (self.edd.pci_dev,):
                 continue
             if not components[5].startswith('ata'):
@@ -189,45 +393,90 @@ def devname_from_ata_pci_dev(self):
                         self.edd.sysfslink = link
                         return path.split('/')[-1]
                 else:
-                    log.warning("edd: ATA Port multipliers are unsupported")
-                    continue;
+                    pmp = int(match.group(1))
+                    if self.edd.ata_pmp == pmp:
+                        self.edd.sysfslink = link
+                        return path.split('/')[-1]
         return None
 
     def devname_from_scsi_pci_dev(self):
         name = None
-        path = "/sys/devices/pci0000:00/0000:%(pci_dev)s/host%(chan)d/"\
-            "target%(chan)d:0:%(dev)d/%(chan)d:0:%(dev)d:%(lun)d/block" % {
+        tmpl0 = "/sys/devices/pci0000:00/0000:%(pci_dev)s/" \
+                "host%(chan)d/target%(chan)d:0:%(dev)d/" \
+                "%(chan)d:0:%(dev)d:%(lun)d/block"
+        # channel appears to be a total like on VirtIO SCSI devices.
+        tmpl1 = "/sys/devices/pci0000:00/0000:%(pci_dev)s/virtio*/" \
+                "host*/target*:0:%(dev)d/*:0:%(dev)d:%(lun)d/block"
+        args = {
             'pci_dev' : self.edd.pci_dev,
             'chan' : self.edd.channel,
             'dev' : self.edd.scsi_id,
             'lun' : self.edd.scsi_lun,
             }
+        path = tmpl0 % args
+        pattern = tmpl1 % args
+        matching_paths = glob.glob(pattern)
         if os.path.isdir(path):
             block_entries = os.listdir(path)
             if len(block_entries) == 1:
+                self.edd.sysfslink = "..%s/%s" % (
+                                path[len("/sys"):],
+                                block_entries[0])
+                name = block_entries[0]
+        elif len(matching_paths) > 1:
+            log.error("edd: Too many devices match for pci dev "\
+                        "%(pci_dev)s channel %(chan)s scsi "\
+                        "id %(dev)s lun %(lun)s: " % args)
+            for matching_path in matching_paths:
+                log.error("edd:   %s" % (matching_path,))
+        elif len(matching_paths) == 1 and os.path.exists(matching_paths[0]):
+            block_entries = os.listdir(matching_paths[0])
+            if len(block_entries) == 1:
+                self.edd.sysfslink = "..%s/%s" % (
+                                matching_paths[0][len("/sys"):],
+                                block_entries[0])
                 name = block_entries[0]
         else:
             log.warning("edd: directory does not exist: %s", path)
+        self.edd.sysfslink = path
         return name
 
     def devname_from_virt_pci_dev(self):
-        name = None
-        pattern = "/sys/devices/pci0000:00/0000:%(pci_dev)s/virtio*/block" % \
-            {'pci_dev' : self.edd.pci_dev}
-        matching_paths = glob.glob(pattern)
-        if len(matching_paths) != 1 or not os.path.exists(matching_paths[0]):
-            return None
-        block_entries = os.listdir(matching_paths[0])
-        if len(block_entries) == 1:
-            name = block_entries[0]
-        return name
+        args = {
+            'pci_dev' : self.edd.pci_dev
+            }
+        pattern = "/sys/devices/pci0000:00/0000:%(pci_dev)s/virtio*"
+        matching_paths = glob.glob(pattern % args)
+
+        if len(matching_paths) == 1 and os.path.exists(matching_paths[0]):
+            # Normal VirtIO devices just have the block link right there...
+            newpath = os.path.join(matching_paths[0], 'block')
+            block_entries = []
+            if os.path.exists(newpath):
+                block_entries = os.listdir(newpath)
+            if len(block_entries) == 1:
+                self.edd.sysfslink = "..%s/%s" % (
+                        matching_paths[0][len("/sys"):],
+                        block_entries[0])
+                return block_entries[0]
+            else:
+                # Virtio SCSI looks like scsi but with a virtio%d/ stuck in
+                # the middle.
+                return self.devname_from_scsi_pci_dev()
+
+        return None
 
     def devname_from_pci_dev(self):
         name = self.devname_from_virt_pci_dev()
         if not name is None:
             return name
+
+        unsupported = ("ATAPI", "USB", "1394", "I2O", "RAID", "FIBRE", "SAS")
+        if self.edd.type in unsupported:
+            log.warning("edd: interface type %s is not implemented (%s)",
+                        self.edd.type, self.edd.sysfspath)
+            log.warning("edd: interface details: %s", self.edd.interface)
         if self.edd.type in ("ATA", "SATA") and \
-                self.edd.channel is not None and \
                 self.edd.ata_device is not None:
             name = self.devname_from_ata_pci_dev()
         elif self.edd.type == "SCSI":
@@ -276,14 +525,15 @@ def collect_mbrs(devices):
             mbrsig = struct.unpack('I', util.eintr_retry_call(os.read, fd, 4))
             util.eintr_ignore(os.close, fd)
         except OSError as e:
-            log.warning("edd: error reading mbrsig from disk %s: %s",
-                        dev.name, str(e))
+            log.error("edd: could not read mbrsig from disk %s: %s",
+                      dev.name, str(e))
             continue
 
         mbrsig_str = "0x%08x" % mbrsig
         # sanity check
         if mbrsig_str == '0x00000000':
-            log.info("edd: MBR signature on %s is zero. new disk image?", dev.name)
+            log.info("edd: MBR signature on %s is zero. new disk image?",
+                     dev.name)
             continue
         else:
             for (dev_name, mbrsig_str_old) in mbr_dict.items():
@@ -313,13 +563,14 @@ def get_edd_dict(devices):
     """
     mbr_dict = collect_mbrs(devices)
     edd_entries_dict = collect_edd_data()
+    edd_dict = {}
     for (edd_number, edd_entry) in edd_entries_dict.items():
         matcher = EddMatcher(edd_entry)
         # first try to match through the pci dev etc.
         name = matcher.devname_from_pci_dev()
         log.debug("edd: data extracted from 0x%x:\n%s", edd_number, edd_entry)
         if name:
-            log.info("edd: matched 0x%x to %s using pci_dev", edd_number, name)
+            log.info("edd: matched 0x%x to %s using PCI dev", edd_number, name)
         # next try to compare mbr signatures
         else:
             name = matcher.match_via_mbrsigs(mbr_dict)
@@ -334,8 +585,6 @@ def get_edd_dict(devices):
                 # this means all the other data can be confused and useless
                 return {}
             edd_dict[name] = edd_number
-            continue
-        log.error("edd: unable to match edd entry 0x%x", edd_number)
+        else:
+            log.error("edd: unable to match edd entry 0x%x", edd_number)
     return edd_dict
-
-edd_dict = {}


-- 
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/2c5bbf3f9a734240ac9f8621ccd7e27f97a806a7


More information about the anaconda-patches mailing list