Coverage for drivers/scsiutil.py : 30%
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
30import traceback
32PREFIX_LEN = 4
33SUFFIX_LEN = 12
34SECTOR_SHIFT = 9
35SCSI_ID_BIN = '/usr/lib/udev/scsi_id'
38def gen_hash(st, len):
39 hs = 0
40 for i in st:
41 hs = ord(i) + (hs << 6) + (hs << 16) - hs
42 return str(hs)[0:len]
45def gen_uuid_from_serial(iqn, serial):
46 if len(serial) < SUFFIX_LEN:
47 raise util.CommandException(1)
48 prefix = gen_hash(iqn, PREFIX_LEN)
49 suffix = gen_hash(serial, SUFFIX_LEN)
50 str = prefix.encode("hex") + suffix.encode("hex")
51 return str[0:8] + '-' + str[8:12] + '-' + str[12:16] + '-' + str[16:20] + '-' + str[20:32]
54def gen_serial_from_uuid(iqn, uuid):
55 str = uuid.replace('-', '')
56 prefix = gen_hash(iqn, PREFIX_LEN)
57 if str[0:(PREFIX_LEN * 2)].decode("hex") != prefix:
58 raise util.CommandException(1)
59 return str[(PREFIX_LEN * 2):].decode("hex")
62def getsize(path):
63 dev = getdev(path)
64 sysfs = os.path.join('/sys/block', dev, 'size')
65 size = 0
66 if os.path.exists(sysfs):
67 try:
68 f = open(sysfs, 'r')
69 size = (int(f.readline()) << SECTOR_SHIFT)
70 f.close()
71 except:
72 pass
73 return size
76def getuniqueserial(path):
77 dev = getdev(path)
78 try:
79 cmd = ["md5sum"]
80 txt = util.pread3(cmd, getSCSIid(path))
81 return txt.split(' ')[0]
82 except:
83 return ''
86def gen_uuid_from_string(src_string):
87 if len(src_string) < (PREFIX_LEN + SUFFIX_LEN):
88 raise util.CommandException(1)
89 return (src_string[0:8] + '-' +
90 src_string[8:12] + '-' +
91 src_string[12:16] + '-' +
92 src_string[16:20] + '-' +
93 src_string[20:32])
96def SCSIid_sanitise(str):
97 text = str.strip()
98 return re.sub(r"\s+", "_", text)
101def getSCSIid(path):
102 """Get the SCSI id of a block device
104 Input:
105 path -- (str) path to block device; can be symlink
107 Return:
108 scsi_id -- (str) the device's SCSI id
110 Raise:
111 util.CommandException
112 """
114 if not path.startswith('/dev/'): 114 ↛ 115line 114 didn't jump to line 115, because the condition on line 114 was never true
115 util.SMlog("getSCSIid: fixing invalid input {}".format(path),
116 priority=util.LOG_WARNING)
117 path = '/dev/' + path.lstrip('/')
119 stdout = util.pread2([SCSI_ID_BIN, '-g', '--device', path])
121 return SCSIid_sanitise(stdout)
124def compareSCSIid_2_6_18(SCSIid, path):
125 serial = getserial(path)
126 len_serial = len(serial)
127 if (len_serial == 0) or (len_serial > (len(SCSIid) - 1)):
128 return False
129 list_SCSIid = list(SCSIid)
130 list_serial = list_SCSIid[1:(len_serial + 1)]
131 serial_2_6_18 = ''.join(list_serial)
132 if (serial == serial_2_6_18):
133 return True
134 else:
135 return False
138def getserial(path):
139 dev = os.path.join('/dev', getdev(path))
140 try:
141 cmd = ["sginfo", "-s", dev]
142 text = re.sub(r"\s+", "", util.pread2(cmd))
143 except:
144 raise xs_errors.XenError('EIO', \
145 opterr='An error occured querying device serial number [%s]' \
146 % dev)
147 try:
148 return text.split("'")[1]
149 except:
150 return ''
153def getmanufacturer(path):
154 cmd = ["sginfo", "-M", path]
155 try:
156 for line in filter(match_vendor, util.pread2(cmd).split('\n')):
157 return line.replace(' ', '').split(':')[-1]
158 except:
159 return ''
162def cacheSCSIidentifiers():
163 SCSI = {}
164 SYS_PATH = "/dev/disk/by-scsibus/*"
165 for node in glob.glob(SYS_PATH):
166 if not re.match(r'.*-\d+:\d+:\d+:\d+$', node): 166 ↛ 167line 166 didn't jump to line 167, because the condition on line 166 was never true
167 continue
168 dev = os.path.realpath(node)
169 HBTL = os.path.basename(node).split("-")[-1].split(":")
170 line = "NONE %s %s %s %s 0 %s" % \
171 (HBTL[0], HBTL[1], HBTL[2], HBTL[3], dev)
172 ids = line.split()
173 SCSI[ids[6]] = ids
174 return SCSI
177def scsi_dev_ctrl(ids, cmd):
178 f = None
179 for i in range(0, 10):
180 try:
181 str = "scsi %s-single-device %s %s %s %s" % \
182 (cmd, ids[1], ids[2], ids[3], ids[4])
183 util.SMlog(str)
184 f = open('/proc/scsi/scsi', 'w')
185 print(str, file=f)
186 f.close()
187 return
188 except IOError as e:
189 util.SMlog("SCSI_DEV_CTRL: Failure, %s [%d]" % (e.strerror, e.errno))
190 if f is not None:
191 f.close()
192 f = None
193 if e.errno == errno.ENXIO:
194 util.SMlog("Device has disappeared already")
195 return
196 time.sleep(6)
197 continue
198 raise xs_errors.XenError('EIO', \
199 opterr='An error occured during the scsi operation')
202def getdev(path):
203 realpath = os.path.realpath(path)
204 if match_dm(realpath): 204 ↛ 205line 204 didn't jump to line 205, because the condition on line 204 was never true
205 newpath = realpath.replace("/dev/mapper/", "/dev/disk/by-id/scsi-")
206 else:
207 newpath = path
208 return os.path.realpath(newpath).split('/')[-1]
211def get_devices_by_SCSIid(SCSIid):
212 """
213 Return the canonical device path(s) for a given SCSI ID
214 Args:
215 SCSIid: The SCSI ID of the devices to return
217 Returns:
218 List of canonicalised devices
219 """
220 scsid_path = os.path.join('/dev/disk/by-scsid', SCSIid)
221 devices = os.listdir(scsid_path)
222 if 'mapper' in devices: 222 ↛ 223line 222 didn't jump to line 223, because the condition on line 222 was never true
223 devices.remove('mapper')
224 return [os.path.realpath(os.path.join(scsid_path, d)) for
225 d in devices]
228def rawdev(dev):
229 device = getdev(dev)
230 if device.startswith('dm-') and device[3:].isdigit():
231 return device
233 return re.sub('[0-9]*$', '', device)
236def getSessionID(path):
237 for line in filter(match_session, util.listdir(path)):
238 return line.split('-')[-1]
241def match_session(s):
242 regex = re.compile("^SESSIONID-")
243 return regex.search(s, 0)
246def match_vendor(s):
247 regex = re.compile("^Vendor:")
248 return regex.search(s, 0)
251def match_dm(s):
252 regex = re.compile("mapper/")
253 return regex.search(s, 0)
256def match_sd(s):
257 regex = re.compile("/dev/sd")
258 return regex.search(s, 0)
261def _isSCSIdev(dev):
262 if match_dm(dev):
263 path = dev.replace("/dev/mapper/", "/dev/disk/by-id/scsi-")
264 else:
265 path = dev
266 return match_sd(os.path.realpath(path))
269def add_serial_record(session, sr_ref, devstring):
270 try:
271 conf = session.xenapi.SR.get_sm_config(sr_ref)
272 conf['devserial'] = devstring
273 session.xenapi.SR.set_sm_config(sr_ref, conf)
274 except:
275 pass
278def get_serial_record(session, sr_ref):
279 try:
280 conf = session.xenapi.SR.get_sm_config(sr_ref)
281 return conf['devserial']
282 except:
283 return ""
286def devlist_to_serialstring(devlist):
287 serial = ''
288 for dev in devlist:
289 try:
290 devserial = "scsi-%s" % getSCSIid(dev)
291 if not len(devserial) > 0:
292 continue
293 if len(serial):
294 serial += ','
295 serial += devserial
296 except:
297 pass
299 return serial
302def gen_synthetic_page_data(uuid):
303 # For generating synthetic page data for non-raw LUNs
304 # we set the vendor ID to XENSRC
305 # Note that the Page 80 serial number must be limited
306 # to 16 characters
307 page80 = b''
308 page80 += b'\x00\x80'
309 page80 += b'\x00\x12'
310 page80 += str.encode(uuid[0:16])
311 page80 += str.encode(" ")
313 page83 = b''
314 page83 += b'\x00\x83'
315 page83 += b'\x00\x31'
316 page83 += b'\x02\x01\x00\x2d'
317 page83 += str.encode("XENSRC ")
318 page83 += str.encode(uuid)
319 page83 += str.encode(" ")
320 return ["", base64.b64encode(page80).decode(),
321 base64.b64encode(page83).decode()]
324def gen_raw_page_data(path):
325 default = ""
326 page80 = ""
327 page83 = ""
328 try:
329 cmd = ["sg_inq", "-r", path]
330 text = util.pread2(cmd)
331 default = base64.b64encode(text)
333 cmd = ["sg_inq", "--page=0x80", "-r", path]
334 text = util.pread2(cmd)
335 page80 = base64.b64encode(text)
337 cmd = ["sg_inq", "--page=0x83", "-r", path]
338 text = util.pread2(cmd)
339 page83 = base64.b64encode(text)
340 except:
341 pass
342 return [default, page80, page83]
345def update_XS_SCSIdata(vdi_uuid, data):
346 # XXX: PR-1255: passing through SCSI data doesn't make sense when
347 # it will change over storage migration. It also doesn't make sense
348 # to preserve one array's identity and copy it when a VM moves to
349 # a new array because the drivers in the VM may attempt to contact
350 # the original array, fail and bluescreen.
352 xenstore_data = {}
353 xenstore_data["vdi-uuid"] = vdi_uuid
354 if len(data[0]): 354 ↛ 355line 354 didn't jump to line 355, because the condition on line 354 was never true
355 xenstore_data["scsi/0x12/default"] = data[0]
357 if len(data[1]): 357 ↛ 360line 357 didn't jump to line 360, because the condition on line 357 was never false
358 xenstore_data["scsi/0x12/0x80"] = data[1]
360 if len(data[2]): 360 ↛ 363line 360 didn't jump to line 363, because the condition on line 360 was never false
361 xenstore_data["scsi/0x12/0x83"] = data[2]
363 return xenstore_data
366def rescan(ids, fullrescan=True):
367 for id in ids:
368 refresh_HostID(id, fullrescan)
371def _genHostList(procname):
372 # loop through and check all adapters
373 ids = []
374 try:
375 for dir in util.listdir('/sys/class/scsi_host'):
376 filename = os.path.join('/sys/class/scsi_host', dir, 'proc_name')
377 if os.path.exists(filename):
378 f = open(filename, 'r')
379 if f.readline().find(procname) != -1:
380 ids.append(dir.replace("host", ""))
381 f.close()
382 except:
383 pass
384 return ids
387def _genReverseSCSIidmap(SCSIid, pathname="scsibus"):
388 util.SMlog("map_by_scsibus: sid=%s" % SCSIid)
390 devices = []
391 for link in glob.glob('/dev/disk/by-%s/%s-*' % (pathname, SCSIid)):
392 realpath = os.path.realpath(link)
393 if os.path.exists(realpath):
394 devices.append(realpath)
395 return devices
398def _genReverseSCSidtoLUNidmap(SCSIid):
399 devices = []
400 for link in glob.glob('/dev/disk/by-scsibus/%s-*' % SCSIid):
401 devices.append(link.split('-')[-1])
402 return devices
405def _dosgscan():
406 regex = re.compile(r"([^:]*):\s+scsi([0-9]+)\s+channel=([0-9]+)\s+id=([0-9]+)\s+lun=([0-9]+)")
407 scan = util.pread2(["/usr/bin/sg_scan"]).split('\n')
408 sgs = []
409 for line in scan:
410 m = regex.match(line)
411 if m:
412 device = m.group(1)
413 host = m.group(2)
414 channel = m.group(3)
415 sid = m.group(4)
416 lun = m.group(5)
417 sgs.append([device, host, channel, sid, lun])
418 return sgs
421def refresh_HostID(HostID, fullrescan):
422 LUNs = glob.glob('/sys/class/scsi_disk/%s*' % HostID)
423 li = []
424 for l in LUNs:
425 chan = re.sub(":[0-9]*$", '', os.path.basename(l))
426 if chan not in li: 426 ↛ 424line 426 didn't jump to line 424, because the condition on line 426 was never false
427 li.append(chan)
429 if len(li) and not fullrescan: 429 ↛ 430line 429 didn't jump to line 430, because the condition on line 429 was never true
430 for c in li:
431 if not refresh_scsi_channel(c):
432 fullrescan = True
434 if fullrescan: 434 ↛ exitline 434 didn't return from function 'refresh_HostID', because the condition on line 434 was never false
435 util.SMlog("Full rescan of HostID %s" % HostID)
436 path = '/sys/class/scsi_host/host%s/scan' % HostID
437 if os.path.exists(path): 437 ↛ 438line 437 didn't jump to line 438, because the condition on line 437 was never true
438 try:
439 scanstring = "- - -"
440 f = open(path, 'w')
441 f.write('%s\n' % scanstring)
442 f.close()
443 if len(li):
444 # Channels already exist, allow some time for
445 # undiscovered LUNs/channels to appear
446 time.sleep(2)
447 except:
448 pass
449 # Host Bus scan issued, now try to detect channels
450 if util.wait_for_path("/sys/class/scsi_disk/%s*" % HostID, 5): 450 ↛ exitline 450 didn't return from function 'refresh_HostID', because the condition on line 450 was never false
451 # At least one LUN is mapped
452 LUNs = glob.glob('/sys/class/scsi_disk/%s*' % HostID)
453 li = []
454 for l in LUNs:
455 chan = re.sub(":[0-9]*$", '', os.path.basename(l))
456 if chan not in li: 456 ↛ 454line 456 didn't jump to line 454, because the condition on line 456 was never false
457 li.append(chan)
458 for c in li:
459 refresh_scsi_channel(c)
462def refresh_scsi_channel(channel):
463 DEV_WAIT = 5
464 util.SMlog("Refreshing channel %s" % channel)
465 util.wait_for_path('/dev/disk/by-scsibus/*-%s*' % channel, DEV_WAIT)
466 LUNs = glob.glob('/dev/disk/by-scsibus/*-%s*' % channel)
467 try:
468 rootdevs = util.dom0_disks()
469 except:
470 util.SMlog("Failed to query root disk, failing operation")
471 return False
473 # a) Find a LUN to issue a Query LUNs command
474 li = []
475 Query = False
476 for lun in LUNs:
477 try:
478 hbtl = lun.split('-')[-1]
479 h = hbtl.split(':')
480 l = util.pread2(["/usr/bin/sg_luns", "-q", lun]).split('\n')
481 li = []
482 for i in l:
483 if len(i):
484 li.append(int(i[0:4], 16))
485 util.SMlog("sg_luns query returned %s" % li)
486 Query = True
487 break
488 except:
489 pass
490 if not Query:
491 util.SMlog("Failed to detect or query LUN on Channel %s" % channel)
492 return False
494 # b) Remove stale LUNs
495 current = glob.glob('/dev/disk/by-scsibus/*-%s:%s:%s*' % (h[0], h[1], h[2]))
496 for cur in current:
497 lunID = int(cur.split(':')[-1])
498 newhbtl = ['', h[0], h[1], h[2], str(lunID)]
499 if os.path.realpath(cur) in rootdevs:
500 # Don't touch the rootdev
501 if lunID in li:
502 li.remove(lunID)
503 continue
505 # Check if LUN is stale, and remove it
506 if not lunID in li:
507 util.SMlog("Stale LUN detected. Removing HBTL: %s" % newhbtl)
508 scsi_dev_ctrl(newhbtl, "remove")
509 util.wait_for_nopath(cur, DEV_WAIT)
510 continue
511 else:
512 li.remove(lunID)
514 # Check if the device is still present
515 if not os.path.exists(cur):
516 continue
518 # Query SCSIid, check it matches, if not, re-probe
519 cur_SCSIid = os.path.basename(cur).split("-%s:%s:%s" % (h[0], h[1], h[2]))[0]
520 real_SCSIid = getSCSIid(cur)
521 if cur_SCSIid != real_SCSIid:
522 util.SMlog("HBTL %s does not match, re-probing" % newhbtl)
523 scsi_dev_ctrl(newhbtl, "remove")
524 util.wait_for_nopath(cur, DEV_WAIT)
525 scsi_dev_ctrl(newhbtl, "add")
526 util.wait_for_path('/dev/disk/by-scsibus/%s-%s' % (real_SCSIid, hbtl), DEV_WAIT)
527 pass
529 # c) Probe for any LUNs that are not present in the system
530 for l in li:
531 newhbtl = ['', h[0], h[1], h[2], str(l)]
532 newhbtlstr = "%s:%s:%s:%s" % (h[0], h[1], h[2], str(l))
533 util.SMlog("Probing new HBTL: %s" % newhbtl)
534 scsi_dev_ctrl(newhbtl, "add")
535 util.wait_for_path('/dev/disk/by-scsibus/*-%s' % newhbtlstr, DEV_WAIT)
537 return True
540def refreshdev(pathlist):
541 """
542 Refresh block devices given a path list
543 """
544 for path in pathlist:
545 dev = getdev(path)
546 sysfs = os.path.join('/sys/block', dev, 'device/rescan')
547 if os.path.exists(sysfs):
548 try:
549 with open(sysfs, 'w') as f:
550 f.write('1')
551 except:
552 pass
555def refresh_lun_size_by_SCSIid(SCSIid):
556 """
557 Refresh all devices for the SCSIid.
558 Returns True if all known devices and the mapper device are up to date.
559 """
561 def get_primary_device(SCSIid):
562 mapperdevice = os.path.join('/dev/mapper', SCSIid)
563 if os.path.exists(mapperdevice):
564 return mapperdevice
565 else:
566 devices = get_devices_by_SCSIid(SCSIid)
567 if devices:
568 return devices[0]
569 else:
570 return None
572 def get_outdated_size_devices(currentcapacity, devices):
573 devicesthatneedrefresh = []
574 for device in devices:
575 if getsize(device) != currentcapacity:
576 devicesthatneedrefresh.append(device)
577 return devicesthatneedrefresh
579 def refresh_devices_if_needed(primarydevice, SCSIid, currentcapacity):
580 devices = get_devices_by_SCSIid(SCSIid)
581 if "/dev/mapper/" in primarydevice:
582 devices = set(devices + mpath_cli.list_paths(SCSIid))
583 devicesthatneedrefresh = get_outdated_size_devices(currentcapacity,
584 devices)
585 if devicesthatneedrefresh:
586 # timing out avoids waiting for min(dev_loss_tmo, fast_io_fail_tmo)
587 # if one or multiple devices don't answer
588 util.timeout_call(10, refreshdev, devicesthatneedrefresh)
589 if get_outdated_size_devices(currentcapacity,
590 devicesthatneedrefresh):
591 # in this state we shouldn't force resizing the mapper dev
592 raise util.SMException("Failed to get %s to agree on the "
593 "current capacity."
594 % devicesthatneedrefresh)
596 if any([getsize(x) != sg_readcap(x) for x in devices]):
597 raise util.SMException(f"Not all devices in {devices} agree on size and capacity")
599 def refresh_mapper_if_needed(primarydevice, SCSIid, currentcapacity):
600 if "/dev/mapper/" in primarydevice \
601 and get_outdated_size_devices(currentcapacity, [primarydevice]):
602 mpath_cli.resize_map(SCSIid)
603 if get_outdated_size_devices(currentcapacity, [primarydevice]):
604 raise util.SMException("Failed to get the mapper dev to agree "
605 "on the current capacity.")
607 try:
608 primarydevice = get_primary_device(SCSIid)
609 if primarydevice:
610 currentcapacity = sg_readcap(primarydevice)
611 refresh_devices_if_needed(primarydevice, SCSIid, currentcapacity)
612 refresh_mapper_if_needed(primarydevice, SCSIid, currentcapacity)
613 else:
614 util.SMlog("scsiutil.refresh_lun_size_by_SCSIid(%s) could not "
615 "find any devices for the SCSIid." % SCSIid)
616 return True
617 except:
618 util.logException(f"Error in scsiutil.refresh_lun_size_by_SCSIid({SCSIid}: {traceback.format_exc()})")
619 return False
622def refresh_lun_size_by_SCSIid_on_slaves(session, SCSIid):
623 for slave in util.get_all_slaves(session):
624 util.SMlog("Calling on-slave.refresh_lun_size_by_SCSIid(%s) on %s."
625 % (SCSIid, slave))
626 resulttext = session.xenapi.host.call_plugin(
627 slave,
628 "on-slave",
629 "refresh_lun_size_by_SCSIid",
630 {'SCSIid': SCSIid})
631 if "True" == resulttext:
632 util.SMlog("Calling on-slave.refresh_lun_size_by_SCSIid(%s) on"
633 " %s succeeded." % (SCSIid, slave))
634 else:
635 message = ("Failed in on-slave.refresh_lun_size_by_SCSIid(%s) "
636 "on %s." % (SCSIid, slave))
637 raise util.SMException("Slave %s failed in on-slave.refresh_lun_"
638 "size_by_SCSIid(%s) " % (slave, SCSIid))
641def remove_stale_luns(hostids, lunid, expectedPath, mpath):
642 try:
643 for hostid in hostids:
644 # get all LUNs of the format hostid:x:y:lunid
645 luns = glob.glob('/dev/disk/by-scsibus/*-%s:*:*:%s' % (hostid, lunid))
647 # try to get the scsiid for each of these luns
648 for lun in luns:
649 try:
650 getSCSIid(lun)
651 # if it works, we have a problem as this device should not
652 # be present and be valid on this system
653 util.SMlog("Warning! The lun %s should not be present and" \
654 " be valid on this system." % lun)
655 except:
656 # Now do the rest.
657 pass
659 # get the HBTL
660 basename = os.path.basename(lun)
661 hbtl_list = basename.split(':')
662 hbtl = basename.split('-')[1]
664 # the first one in scsiid-hostid
665 hbtl_list[0] = hbtl_list[0].split('-')[1]
667 expectedPath = expectedPath + '*' + hbtl
668 if not os.path.exists(expectedPath):
669 # wait for sometime and check if the expected path exists
670 # check if a rescan was done outside of this process
671 time.sleep(2)
673 if os.path.exists(expectedPath):
674 # do not remove device, this might be dangerous
675 util.SMlog("Path %s appeared before checking for " \
676 "stale LUNs, ignore this LUN %s." % (expectedPath, lun))
677 continue
679 # remove the scsi device
680 l = [os.path.realpath(lun), hbtl_list[0], hbtl_list[1], \
681 hbtl_list[2], hbtl_list[3]]
682 scsi_dev_ctrl(l, 'remove')
684 # if multipath is enabled, do a best effort cleanup
685 if mpath:
686 try:
687 path = os.path.basename(os.path.realpath(lun))
688 mpath_cli.remove_path(path)
689 except Exception as e:
690 util.SMlog("Failed to remove path %s, ignoring " \
691 "exception as path may not be present." % path)
692 except Exception as e:
693 util.SMlog("Exception removing stale LUNs, new devices may not come" \
694 " up properly! Error: %s" % str(e))
697def sg_readcap(device):
698 device = os.path.join('/dev', getdev(device))
699 readcapcommand = ['/usr/bin/sg_readcap', '-b', device]
700 (rc, stdout, stderr) = util.doexec(readcapcommand)
701 if rc == 6:
702 # retry one time for "Capacity data has changed"
703 (rc, stdout, stderr) = util.doexec(readcapcommand)
704 if rc != 0: 704 ↛ 705line 704 didn't jump to line 705, because the condition on line 704 was never true
705 util.SMlog(f"scsiutil.sg_readcap({device}) failed.\n{stdout}\n{stderr}")
706 raise util.SMException(f"scsiutil.sg_readcap({device}) failed.")
707 match = re.search('(^|.*\n)(0x[0-9a-fA-F]+) (0x[0-9a-fA-F]+)\n$', stdout)
708 if not match: 708 ↛ 709line 708 didn't jump to line 709, because the condition on line 708 was never true
709 raise util.SMException("scsiutil.sg_readcap(%s) failed to parse: %s"
710 % (device, stdout))
711 (blockcount, blocksize) = match.group(2, 3)
712 return (int(blockcount, 0) * int(blocksize, 0))