Coverage for drivers/scsiutil.py : 29%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/python3
2#
3# Copyright (C) Citrix Systems Inc.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published
7# by the Free Software Foundation; version 2.1 only.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17#
18# Miscellaneous scsi utility functions
19#
21import util
22import os
23import re
24import xs_errors
25import base64
26import time
27import errno
28import glob
29import mpath_cli
31PREFIX_LEN = 4
32SUFFIX_LEN = 12
33SECTOR_SHIFT = 9
34SCSI_ID_BIN = '/usr/lib/udev/scsi_id'
37def gen_hash(st, len):
38 hs = 0
39 for i in st:
40 hs = ord(i) + (hs << 6) + (hs << 16) - hs
41 return str(hs)[0:len]
44def gen_uuid_from_serial(iqn, serial):
45 if len(serial) < SUFFIX_LEN:
46 raise util.CommandException(1)
47 prefix = gen_hash(iqn, PREFIX_LEN)
48 suffix = gen_hash(serial, SUFFIX_LEN)
49 str = prefix.encode("hex") + suffix.encode("hex")
50 return str[0:8] + '-' + str[8:12] + '-' + str[12:16] + '-' + str[16:20] + '-' + str[20:32]
53def gen_serial_from_uuid(iqn, uuid):
54 str = uuid.replace('-', '')
55 prefix = gen_hash(iqn, PREFIX_LEN)
56 if str[0:(PREFIX_LEN * 2)].decode("hex") != prefix:
57 raise util.CommandException(1)
58 return str[(PREFIX_LEN * 2):].decode("hex")
61def getsize(path):
62 dev = getdev(path)
63 sysfs = os.path.join('/sys/block', dev, 'size')
64 size = 0
65 if os.path.exists(sysfs):
66 try:
67 f = open(sysfs, 'r')
68 size = (int(f.readline()) << SECTOR_SHIFT)
69 f.close()
70 except:
71 pass
72 return size
75def getuniqueserial(path):
76 dev = getdev(path)
77 try:
78 cmd = ["md5sum"]
79 txt = util.pread3(cmd, getSCSIid(path))
80 return txt.split(' ')[0]
81 except:
82 return ''
85def gen_uuid_from_string(src_string):
86 if len(src_string) < (PREFIX_LEN + SUFFIX_LEN):
87 raise util.CommandException(1)
88 return (src_string[0:8] + '-' +
89 src_string[8:12] + '-' +
90 src_string[12:16] + '-' +
91 src_string[16:20] + '-' +
92 src_string[20:32])
95def SCSIid_sanitise(str):
96 text = str.strip()
97 return re.sub(r"\s+", "_", text)
100def getSCSIid(path):
101 """Get the SCSI id of a block device
103 Input:
104 path -- (str) path to block device; can be symlink
106 Return:
107 scsi_id -- (str) the device's SCSI id
109 Raise:
110 util.CommandException
111 """
113 if not path.startswith('/dev/'): 113 ↛ 114line 113 didn't jump to line 114, because the condition on line 113 was never true
114 util.SMlog("getSCSIid: fixing invalid input {}".format(path),
115 priority=util.LOG_WARNING)
116 path = '/dev/' + path.lstrip('/')
118 stdout = util.pread2([SCSI_ID_BIN, '-g', '--device', path])
120 return SCSIid_sanitise(stdout)
123def compareSCSIid_2_6_18(SCSIid, path):
124 serial = getserial(path)
125 len_serial = len(serial)
126 if (len_serial == 0) or (len_serial > (len(SCSIid) - 1)):
127 return False
128 list_SCSIid = list(SCSIid)
129 list_serial = list_SCSIid[1:(len_serial + 1)]
130 serial_2_6_18 = ''.join(list_serial)
131 if (serial == serial_2_6_18):
132 return True
133 else:
134 return False
137def getserial(path):
138 dev = os.path.join('/dev', getdev(path))
139 try:
140 cmd = ["sginfo", "-s", dev]
141 text = re.sub(r"\s+", "", util.pread2(cmd))
142 except:
143 raise xs_errors.XenError('EIO', \
144 opterr='An error occured querying device serial number [%s]' \
145 % dev)
146 try:
147 return text.split("'")[1]
148 except:
149 return ''
152def getmanufacturer(path):
153 cmd = ["sginfo", "-M", path]
154 try:
155 for line in filter(match_vendor, util.pread2(cmd).split('\n')):
156 return line.replace(' ', '').split(':')[-1]
157 except:
158 return ''
161def cacheSCSIidentifiers():
162 SCSI = {}
163 SYS_PATH = "/dev/disk/by-scsibus/*"
164 for node in glob.glob(SYS_PATH):
165 if not re.match(r'.*-\d+:\d+:\d+:\d+$', node): 165 ↛ 166line 165 didn't jump to line 166, because the condition on line 165 was never true
166 continue
167 dev = os.path.realpath(node)
168 HBTL = os.path.basename(node).split("-")[-1].split(":")
169 line = "NONE %s %s %s %s 0 %s" % \
170 (HBTL[0], HBTL[1], HBTL[2], HBTL[3], dev)
171 ids = line.split()
172 SCSI[ids[6]] = ids
173 return SCSI
176def scsi_dev_ctrl(ids, cmd):
177 f = None
178 for i in range(0, 10):
179 try:
180 str = "scsi %s-single-device %s %s %s %s" % \
181 (cmd, ids[1], ids[2], ids[3], ids[4])
182 util.SMlog(str)
183 f = open('/proc/scsi/scsi', 'w')
184 print(str, file=f)
185 f.close()
186 return
187 except IOError as e:
188 util.SMlog("SCSI_DEV_CTRL: Failure, %s [%d]" % (e.strerror, e.errno))
189 if f is not None:
190 f.close()
191 f = None
192 if e.errno == errno.ENXIO:
193 util.SMlog("Device has disappeared already")
194 return
195 time.sleep(6)
196 continue
197 raise xs_errors.XenError('EIO', \
198 opterr='An error occured during the scsi operation')
201def getdev(path):
202 realpath = os.path.realpath(path)
203 if match_dm(realpath): 203 ↛ 204line 203 didn't jump to line 204, because the condition on line 203 was never true
204 newpath = realpath.replace("/dev/mapper/", "/dev/disk/by-id/scsi-")
205 else:
206 newpath = path
207 return os.path.realpath(newpath).split('/')[-1]
210def get_devices_by_SCSIid(SCSIid):
211 devices = os.listdir(os.path.join('/dev/disk/by-scsid', SCSIid))
212 if 'mapper' in devices:
213 devices.remove('mapper')
214 return devices
217def rawdev(dev):
218 device = getdev(dev)
219 if device.startswith('dm-') and device[3:].isdigit():
220 return device
222 return re.sub('[0-9]*$', '', device)
225def getSessionID(path):
226 for line in filter(match_session, util.listdir(path)):
227 return line.split('-')[-1]
230def match_session(s):
231 regex = re.compile("^SESSIONID-")
232 return regex.search(s, 0)
235def match_vendor(s):
236 regex = re.compile("^Vendor:")
237 return regex.search(s, 0)
240def match_dm(s):
241 regex = re.compile("mapper/")
242 return regex.search(s, 0)
245def match_sd(s):
246 regex = re.compile("/dev/sd")
247 return regex.search(s, 0)
250def _isSCSIdev(dev):
251 if match_dm(dev):
252 path = dev.replace("/dev/mapper/", "/dev/disk/by-id/scsi-")
253 else:
254 path = dev
255 return match_sd(os.path.realpath(path))
258def add_serial_record(session, sr_ref, devstring):
259 try:
260 conf = session.xenapi.SR.get_sm_config(sr_ref)
261 conf['devserial'] = devstring
262 session.xenapi.SR.set_sm_config(sr_ref, conf)
263 except:
264 pass
267def get_serial_record(session, sr_ref):
268 try:
269 conf = session.xenapi.SR.get_sm_config(sr_ref)
270 return conf['devserial']
271 except:
272 return ""
275def devlist_to_serialstring(devlist):
276 serial = ''
277 for dev in devlist:
278 try:
279 devserial = "scsi-%s" % getSCSIid(dev)
280 if not len(devserial) > 0:
281 continue
282 if len(serial):
283 serial += ','
284 serial += devserial
285 except:
286 pass
288 return serial
291def gen_synthetic_page_data(uuid):
292 # For generating synthetic page data for non-raw LUNs
293 # we set the vendor ID to XENSRC
294 # Note that the Page 80 serial number must be limited
295 # to 16 characters
296 page80 = ""
297 page80 += "\x00\x80"
298 page80 += "\x00\x12"
299 page80 += uuid[0:16]
300 page80 += " "
302 page83 = ""
303 page83 += "\x00\x83"
304 page83 += "\x00\x31"
305 page83 += "\x02\x01\x00\x2d"
306 page83 += "XENSRC "
307 page83 += uuid
308 page83 += " "
309 return ["", base64.b64encode(str.encode(page80)).decode(),
310 base64.b64encode(str.encode(page83)).decode()]
313def gen_raw_page_data(path):
314 default = ""
315 page80 = ""
316 page83 = ""
317 try:
318 cmd = ["sg_inq", "-r", path]
319 text = util.pread2(cmd)
320 default = base64.b64encode(text)
322 cmd = ["sg_inq", "--page=0x80", "-r", path]
323 text = util.pread2(cmd)
324 page80 = base64.b64encode(text)
326 cmd = ["sg_inq", "--page=0x83", "-r", path]
327 text = util.pread2(cmd)
328 page83 = base64.b64encode(text)
329 except:
330 pass
331 return [default, page80, page83]
334def update_XS_SCSIdata(vdi_uuid, data):
335 # XXX: PR-1255: passing through SCSI data doesn't make sense when
336 # it will change over storage migration. It also doesn't make sense
337 # to preserve one array's identity and copy it when a VM moves to
338 # a new array because the drivers in the VM may attempt to contact
339 # the original array, fail and bluescreen.
341 xenstore_data = {}
342 xenstore_data["vdi-uuid"] = vdi_uuid
343 if len(data[0]): 343 ↛ 344line 343 didn't jump to line 344, because the condition on line 343 was never true
344 xenstore_data["scsi/0x12/default"] = data[0]
346 if len(data[1]): 346 ↛ 349line 346 didn't jump to line 349, because the condition on line 346 was never false
347 xenstore_data["scsi/0x12/0x80"] = data[1]
349 if len(data[2]): 349 ↛ 352line 349 didn't jump to line 352, because the condition on line 349 was never false
350 xenstore_data["scsi/0x12/0x83"] = data[2]
352 return xenstore_data
355def rescan(ids, fullrescan=True):
356 for id in ids:
357 refresh_HostID(id, fullrescan)
360def _genArrayIdentifier(dev):
361 try:
362 cmd = ["sg_inq", "--page=0xc8", "-r", dev]
363 id = util.pread2(cmd)
364 return id.encode("hex")[180:212]
365 except:
366 return ""
369def _genHostList(procname):
370 # loop through and check all adapters
371 ids = []
372 try:
373 for dir in util.listdir('/sys/class/scsi_host'):
374 filename = os.path.join('/sys/class/scsi_host', dir, 'proc_name')
375 if os.path.exists(filename):
376 f = open(filename, 'r')
377 if f.readline().find(procname) != -1:
378 ids.append(dir.replace("host", ""))
379 f.close()
380 except:
381 pass
382 return ids
385def _genReverseSCSIidmap(SCSIid, pathname="scsibus"):
386 util.SMlog("map_by_scsibus: sid=%s" % SCSIid)
388 devices = []
389 for link in glob.glob('/dev/disk/by-%s/%s-*' % (pathname, SCSIid)):
390 realpath = os.path.realpath(link)
391 if os.path.exists(realpath):
392 devices.append(realpath)
393 return devices
396def _genReverseSCSidtoLUNidmap(SCSIid):
397 devices = []
398 for link in glob.glob('/dev/disk/by-scsibus/%s-*' % SCSIid):
399 devices.append(link.split('-')[-1])
400 return devices
403def _dosgscan():
404 regex = re.compile(r"([^:]*):\s+scsi([0-9]+)\s+channel=([0-9]+)\s+id=([0-9]+)\s+lun=([0-9]+)")
405 scan = util.pread2(["/usr/bin/sg_scan"]).split('\n')
406 sgs = []
407 for line in scan:
408 m = regex.match(line)
409 if m:
410 device = m.group(1)
411 host = m.group(2)
412 channel = m.group(3)
413 sid = m.group(4)
414 lun = m.group(5)
415 sgs.append([device, host, channel, sid, lun])
416 return sgs
419def refresh_HostID(HostID, fullrescan):
420 LUNs = glob.glob('/sys/class/scsi_disk/%s*' % HostID)
421 li = []
422 for l in LUNs:
423 chan = re.sub(":[0-9]*$", '', os.path.basename(l))
424 if chan not in li: 424 ↛ 422line 424 didn't jump to line 422, because the condition on line 424 was never false
425 li.append(chan)
427 if len(li) and not fullrescan: 427 ↛ 428line 427 didn't jump to line 428, because the condition on line 427 was never true
428 for c in li:
429 if not refresh_scsi_channel(c):
430 fullrescan = True
432 if fullrescan: 432 ↛ exitline 432 didn't return from function 'refresh_HostID', because the condition on line 432 was never false
433 util.SMlog("Full rescan of HostID %s" % HostID)
434 path = '/sys/class/scsi_host/host%s/scan' % HostID
435 if os.path.exists(path): 435 ↛ 436line 435 didn't jump to line 436, because the condition on line 435 was never true
436 try:
437 scanstring = "- - -"
438 f = open(path, 'w')
439 f.write('%s\n' % scanstring)
440 f.close()
441 if len(li):
442 # Channels already exist, allow some time for
443 # undiscovered LUNs/channels to appear
444 time.sleep(2)
445 except:
446 pass
447 # Host Bus scan issued, now try to detect channels
448 if util.wait_for_path("/sys/class/scsi_disk/%s*" % HostID, 5): 448 ↛ exitline 448 didn't return from function 'refresh_HostID', because the condition on line 448 was never false
449 # At least one LUN is mapped
450 LUNs = glob.glob('/sys/class/scsi_disk/%s*' % HostID)
451 li = []
452 for l in LUNs:
453 chan = re.sub(":[0-9]*$", '', os.path.basename(l))
454 if chan not in li: 454 ↛ 452line 454 didn't jump to line 452, because the condition on line 454 was never false
455 li.append(chan)
456 for c in li:
457 refresh_scsi_channel(c)
460def refresh_scsi_channel(channel):
461 DEV_WAIT = 5
462 util.SMlog("Refreshing channel %s" % channel)
463 util.wait_for_path('/dev/disk/by-scsibus/*-%s*' % channel, DEV_WAIT)
464 LUNs = glob.glob('/dev/disk/by-scsibus/*-%s*' % channel)
465 try:
466 rootdevs = util.dom0_disks()
467 except:
468 util.SMlog("Failed to query root disk, failing operation")
469 return False
471 # a) Find a LUN to issue a Query LUNs command
472 li = []
473 Query = False
474 for lun in LUNs:
475 try:
476 hbtl = lun.split('-')[-1]
477 h = hbtl.split(':')
478 l = util.pread2(["/usr/bin/sg_luns", "-q", lun]).split('\n')
479 li = []
480 for i in l:
481 if len(i):
482 li.append(int(i[0:4], 16))
483 util.SMlog("sg_luns query returned %s" % li)
484 Query = True
485 break
486 except:
487 pass
488 if not Query:
489 util.SMlog("Failed to detect or query LUN on Channel %s" % channel)
490 return False
492 # b) Remove stale LUNs
493 current = glob.glob('/dev/disk/by-scsibus/*-%s:%s:%s*' % (h[0], h[1], h[2]))
494 for cur in current:
495 lunID = int(cur.split(':')[-1])
496 newhbtl = ['', h[0], h[1], h[2], str(lunID)]
497 if os.path.realpath(cur) in rootdevs:
498 # Don't touch the rootdev
499 if lunID in li:
500 li.remove(lunID)
501 continue
503 # Check if LUN is stale, and remove it
504 if not lunID in li:
505 util.SMlog("Stale LUN detected. Removing HBTL: %s" % newhbtl)
506 scsi_dev_ctrl(newhbtl, "remove")
507 util.wait_for_nopath(cur, DEV_WAIT)
508 continue
509 else:
510 li.remove(lunID)
512 # Check if the device is still present
513 if not os.path.exists(cur):
514 continue
516 # Query SCSIid, check it matches, if not, re-probe
517 cur_SCSIid = os.path.basename(cur).split("-%s:%s:%s" % (h[0], h[1], h[2]))[0]
518 real_SCSIid = getSCSIid(cur)
519 if cur_SCSIid != real_SCSIid:
520 util.SMlog("HBTL %s does not match, re-probing" % newhbtl)
521 scsi_dev_ctrl(newhbtl, "remove")
522 util.wait_for_nopath(cur, DEV_WAIT)
523 scsi_dev_ctrl(newhbtl, "add")
524 util.wait_for_path('/dev/disk/by-scsibus/%s-%s' % (real_SCSIid, hbtl), DEV_WAIT)
525 pass
527 # c) Probe for any LUNs that are not present in the system
528 for l in li:
529 newhbtl = ['', h[0], h[1], h[2], str(l)]
530 newhbtlstr = "%s:%s:%s:%s" % (h[0], h[1], h[2], str(l))
531 util.SMlog("Probing new HBTL: %s" % newhbtl)
532 scsi_dev_ctrl(newhbtl, "add")
533 util.wait_for_path('/dev/disk/by-scsibus/*-%s' % newhbtlstr, DEV_WAIT)
535 return True
538def refreshdev(pathlist):
539 """
540 Refresh block devices given a path list
541 """
542 for path in pathlist:
543 dev = getdev(path)
544 sysfs = os.path.join('/sys/block', dev, 'device/rescan')
545 if os.path.exists(sysfs):
546 try:
547 with open(sysfs, 'w') as f:
548 f.write('1')
549 except:
550 pass
553def refresh_lun_size_by_SCSIid(SCSIid):
554 """
555 Refresh all devices for the SCSIid.
556 Returns True if all known devices and the mapper device are up to date.
557 """
559 def get_primary_device(SCSIid):
560 mapperdevice = os.path.join('/dev/mapper', SCSIid)
561 if os.path.exists(mapperdevice):
562 return mapperdevice
563 else:
564 devices = get_devices_by_SCSIid(SCSIid)
565 if devices:
566 return devices[0]
567 else:
568 return None
570 def get_outdated_size_devices(currentcapacity, devices):
571 devicesthatneedrefresh = []
572 for device in devices:
573 if getsize(device) != currentcapacity:
574 devicesthatneedrefresh.append(device)
575 return devicesthatneedrefresh
577 def refresh_devices_if_needed(primarydevice, SCSIid, currentcapacity):
578 devices = get_devices_by_SCSIid(SCSIid)
579 if "/dev/mapper/" in primarydevice:
580 devices = set(devices + mpath_cli.list_paths(SCSIid))
581 devicesthatneedrefresh = get_outdated_size_devices(currentcapacity,
582 devices)
583 if devicesthatneedrefresh:
584 # timing out avoids waiting for min(dev_loss_tmo, fast_io_fail_tmo)
585 # if one or multiple devices don't answer
586 util.timeout_call(10, refreshdev, devicesthatneedrefresh)
587 if get_outdated_size_devices(currentcapacity,
588 devicesthatneedrefresh):
589 # in this state we shouldn't force resizing the mapper dev
590 raise util.SMException("Failed to get %s to agree on the "
591 "current capacity."
592 % devicesthatneedrefresh)
594 def refresh_mapper_if_needed(primarydevice, SCSIid, currentcapacity):
595 if "/dev/mapper/" in primarydevice \
596 and get_outdated_size_devices(currentcapacity, [primarydevice]):
597 mpath_cli.resize_map(SCSIid)
598 if get_outdated_size_devices(currentcapacity, [primarydevice]):
599 raise util.SMException("Failed to get the mapper dev to agree "
600 "on the current capacity.")
602 try:
603 primarydevice = get_primary_device(SCSIid)
604 if primarydevice:
605 currentcapacity = sg_readcap(primarydevice)
606 refresh_devices_if_needed(primarydevice, SCSIid, currentcapacity)
607 refresh_mapper_if_needed(primarydevice, SCSIid, currentcapacity)
608 else:
609 util.SMlog("scsiutil.refresh_lun_size_by_SCSIid(%s) could not "
610 "find any devices for the SCSIid." % SCSIid)
611 return True
612 except:
613 util.logException("Error in scsiutil.refresh_lun_size_by_SCSIid(%s)"
614 % SCSIid)
615 return False
618def refresh_lun_size_by_SCSIid_on_slaves(session, SCSIid):
619 for slave in util.get_all_slaves(session):
620 util.SMlog("Calling on-slave.refresh_lun_size_by_SCSIid(%s) on %s."
621 % (SCSIid, slave))
622 resulttext = session.xenapi.host.call_plugin(
623 slave,
624 "on-slave",
625 "refresh_lun_size_by_SCSIid",
626 {'SCSIid': SCSIid})
627 if "True" == resulttext:
628 util.SMlog("Calling on-slave.refresh_lun_size_by_SCSIid(%s) on"
629 " %s succeeded." % (SCSIid, slave))
630 else:
631 message = ("Failed in on-slave.refresh_lun_size_by_SCSIid(%s) "
632 "on %s." % (SCSIid, slave))
633 raise util.SMException("Slave %s failed in on-slave.refresh_lun_"
634 "size_by_SCSIid(%s) " % (slave, SCSIid))
637def remove_stale_luns(hostids, lunid, expectedPath, mpath):
638 try:
639 for hostid in hostids:
640 # get all LUNs of the format hostid:x:y:lunid
641 luns = glob.glob('/dev/disk/by-scsibus/*-%s:*:*:%s' % (hostid, lunid))
643 # try to get the scsiid for each of these luns
644 for lun in luns:
645 try:
646 getSCSIid(lun)
647 # if it works, we have a problem as this device should not
648 # be present and be valid on this system
649 util.SMlog("Warning! The lun %s should not be present and" \
650 " be valid on this system." % lun)
651 except:
652 # Now do the rest.
653 pass
655 # get the HBTL
656 basename = os.path.basename(lun)
657 hbtl_list = basename.split(':')
658 hbtl = basename.split('-')[1]
660 # the first one in scsiid-hostid
661 hbtl_list[0] = hbtl_list[0].split('-')[1]
663 expectedPath = expectedPath + '*' + hbtl
664 if not os.path.exists(expectedPath):
665 # wait for sometime and check if the expected path exists
666 # check if a rescan was done outside of this process
667 time.sleep(2)
669 if os.path.exists(expectedPath):
670 # do not remove device, this might be dangerous
671 util.SMlog("Path %s appeared before checking for " \
672 "stale LUNs, ignore this LUN %s." % (expectedPath, lun))
673 continue
675 # remove the scsi device
676 l = [os.path.realpath(lun), hbtl_list[0], hbtl_list[1], \
677 hbtl_list[2], hbtl_list[3]]
678 scsi_dev_ctrl(l, 'remove')
680 # if multipath is enabled, do a best effort cleanup
681 if mpath:
682 try:
683 path = os.path.basename(os.path.realpath(lun))
684 mpath_cli.remove_path(path)
685 except Exception as e:
686 util.SMlog("Failed to remove path %s, ignoring " \
687 "exception as path may not be present." % path)
688 except Exception as e:
689 util.SMlog("Exception removing stale LUNs, new devices may not come" \
690 " up properly! Error: %s" % str(e))
693def sg_readcap(device):
694 device = os.path.join('/dev', getdev(device))
695 readcapcommand = ['/usr/bin/sg_readcap', '-b', device]
696 (rc, stdout, stderr) = util.doexec(readcapcommand)
697 if rc == 6:
698 # retry one time for "Capacity data has changed"
699 (rc, stdout, stderr) = util.doexec(readcapcommand)
700 if rc != 0: 700 ↛ 701line 700 didn't jump to line 701, because the condition on line 700 was never true
701 raise util.SMException("scsiutil.sg_readcap(%s) failed" % (device))
702 match = re.search('(^|.*\n)(0x[0-9a-fA-F]+) (0x[0-9a-fA-F]+)\n$', stdout)
703 if not match: 703 ↛ 704line 703 didn't jump to line 704, because the condition on line 703 was never true
704 raise util.SMException("scsiutil.sg_readcap(%s) failed to parse: %s"
705 % (device, stdout))
706 (blockcount, blocksize) = match.group(2, 3)
707 return (int(blockcount, 0) * int(blocksize, 0))