1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
#
# edd.py
# BIOS EDD data parsing functions
#
# Copyright (C) 2010 Red Hat, Inc. All rights reserved.
#
# 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Author(s): Hans de Goede <hdegoede@redhat.com>
# Ales Kozumplik <akozumpl@redhat.com>
#
import glob
import logging
import os
import re
import struct
log = logging.getLogger("storage")
re_host_bus = re.compile(r'^PCI\s*(\S*)\s*channel: (\S*)\s*$')
re_interface_scsi = re.compile(r'^SCSI\s*id: (\S*)\s*lun: (\S*)\s*$')
re_interface_ata = re.compile(r'^ATA\s*device: (\S*)\s*$')
class EddEntry(object):
""" This object merely collects what the /sys/firmware/edd/* entries can
provide.
"""
def __init__(self, sysfspath):
self.type = None
self.ata_device = None
self.channel = None
self.mbr_signature = None
self.pci_dev = None
self.scsi_id = None
self.scsi_lun = None
self.sectors = None
self.load(sysfspath)
def __str__(self):
return \
"\ttype: %(type)s, ata_device: %(ata_device)s\n" \
"\tchannel: %(channel)s, mbr_signature: %(mbr_signature)s\n" \
"\tpci_dev: %(pci_dev)s, scsi_id: %(scsi_id)s\n" \
"\tscsi_lun: %(scsi_lun)s, sectors: %(sectors)s" % self.__dict__
def _read_file(self, filename):
contents = None
if os.path.exists(filename):
with open(filename) as f:
contents = f.read().rstrip()
return contents
def load(self, sysfspath):
interface = self._read_file(os.path.join(sysfspath, "interface"))
if interface:
self.type = interface.split()[0]
if self.type == "SCSI":
match = re_interface_scsi.match(interface)
self.scsi_id = int(match.group(1))
self.scsi_lun = int(match.group(2))
elif self.type == "ATA":
match = re_interface_ata.match(interface)
self.ata_device = int(match.group(1))
self.mbr_signature = self._read_file(
os.path.join(sysfspath, "mbr_signature"))
sectors = self._read_file(os.path.join(sysfspath, "sectors"))
if sectors:
self.sectors = int(sectors)
hbus = self._read_file(os.path.join(sysfspath, "host_bus"))
if hbus:
match = re_host_bus.match(hbus)
if match:
self.pci_dev = match.group(1)
self.channel = int(match.group(2))
else:
log.warning("edd: can not match host_bus: %s" % hbus)
class EddMatcher(object):
""" This object tries to match given entry to a disk device name.
Assuming, heuristic analysis and guessing hapens here.
"""
def __init__(self, edd_entry):
self.edd = edd_entry
def devname_from_pci_dev(self):
name = None
if self.edd.type == "ATA" and \
self.edd.channel is not None and \
self.edd.ata_device is not None:
path = "/sys/devices/pci0000:00/0000:%(pci_dev)s/host%(chan)d/"\
"target%(chan)d:0:%(dev)d/%(chan)d:0:%(dev)d:0/block" % {
'pci_dev' : self.edd.pci_dev,
'chan' : self.edd.channel,
'dev' : self.edd.ata_device
}
if os.path.isdir(path):
block_entries = os.listdir(path)
if len(block_entries) == 1:
name = block_entries[0]
else:
log.warning("edd: directory does not exist: %s" % path)
elif self.edd.type == "SCSI":
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
def match_via_mbrsigs(self, mbr_dict):
""" Try to match the edd entry based on its mbr signature.
This will obviously fail for a fresh drive/image, but in extreme
cases can also show false positives for randomly matching data.
"""
for (name, mbr_signature) in mbr_dict.items():
if mbr_signature == self.edd.mbr_signature:
return name
return None
def biosdev_to_edd_dir(biosdev):
return "/sys/firmware/edd/int13_dev%x" % biosdev
def collect_edd_data():
edd_data_dict = {}
# the hard drive numbering starts at 0x80 (128 decimal):
for biosdev in range(0x80, 0x80+16):
sysfspath = biosdev_to_edd_dir(biosdev)
if not os.path.exists(sysfspath):
break
edd_data_dict[biosdev] = EddEntry(sysfspath)
return edd_data_dict
def collect_mbrs(devices):
""" Read MBR signatures from devices.
Returns a dict mapping device names to their MBR signatures. It is not
guaranteed this will succeed, with a new disk for instance.
"""
mbr_dict = {}
for dev in devices:
try:
fd = os.open(dev.path, os.O_RDONLY)
# The signature is the unsigned integer at byte 440:
os.lseek(fd, 440, 0)
mbrsig = struct.unpack('I', os.read(fd, 4))
os.close(fd)
except OSError as e:
log.warning("edd: error reading 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)
continue
else:
for (dev_name, mbrsig_str_old) in mbr_dict.items():
if mbrsig_str_old == mbrsig_str:
log.error("edd: dupicite MBR signature %s for %s and %s" %
(mbrsig_str, dev_name, dev.name))
# this actually makes all the other data useless
return {}
# update the dictionary
mbr_dict[dev.name] = mbrsig_str
log.info("edd: collected mbr signatures: %s" % mbr_dict)
return mbr_dict
def get_edd_dict(devices):
""" Generates the 'device name' -> 'edd number' mapping.
The EDD kernel module that exposes /sys/firmware/edd is thoroughly
broken, the information there is incomplete and sometimes downright
wrong. So after we mine out all useful information that the files under
/sys/firmware/edd/int13_*/ can provide, we resort to heuristics and
guessing. Our first attempt is, by looking at the device type int
'interface', attempting to map pci device number, channel number etc. to
a sysfs path, check that the path really exists, then read the device
name (e.g 'sda') from there. Should this fail we try to match contents
of 'mbr_signature' to a real MBR signature found on the existing block
devices.
"""
mbr_dict = collect_mbrs(devices)
edd_entries_dict = collect_edd_data()
global edd_dict
for (edd_number, edd_entry) in edd_entries_dict.items():
log.debug("edd: data extracted from 0x%x:\n%s" % (edd_number, edd_entry))
matcher = EddMatcher(edd_entry)
# first try to match through the pci dev etc.
name = matcher.devname_from_pci_dev()
# next try to compare mbr signatures
if name:
log.debug("edd: matched 0x%x to %s using pci_dev" % (edd_number, name))
else:
name = matcher.match_via_mbrsigs(mbr_dict)
if name:
log.info("edd: matched 0x%x to %s using MBR sig" % (edd_number, name))
if name:
old_edd_number = edd_dict.get(name)
if old_edd_number:
log.info("edd: both edd entries 0x%x and 0x%x seem to map to %s" %
(old_edd_number, edd_number, name))
# 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)
return edd_dict
edd_dict = {}
|