Coverage for drivers/FileSR.py : 56%
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# FileSR: local-file storage repository
20from sm_typing import Dict, Optional, List, override
22import SR
23import VDI
24import SRCommand
25import contextlib
26import util
27import scsiutil
28import lock
29import os
30import errno
31import xs_errors
32import cleanup
33import blktap2
34import time
35import glob
36from uuid import uuid4
37from cowutil import getCowUtil, getImageStringFromVdiType, getVdiTypeFromImageFormat
38from vditype import VdiType, VdiTypeExtension, VDI_COW_TYPES, VDI_TYPE_TO_EXTENSION
39import xmlrpc.client
40import XenAPI # pylint: disable=import-error
41from constants import CBTLOG_TAG
43geneology: Dict[str, List[str]] = {}
44CAPABILITIES = ["SR_PROBE", "SR_UPDATE", \
45 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH", \
46 "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR",
47 "VDI_GENERATE_CONFIG", "ATOMIC_PAUSE", "VDI_CONFIG_CBT",
48 "VDI_ACTIVATE", "VDI_DEACTIVATE", "THIN_PROVISIONING"]
50CONFIGURATION = [
51 ['location', 'local directory path (required)'],
52 ['preferred-image-formats', 'list of preferred image formats to use (default: VHD,QCOW2)']
53]
55DRIVER_INFO = {
56 'name': 'Local Path VHD and QCOW2',
57 'description': 'SR plugin which represents disks as VHD and QCOW2 files stored on a local path',
58 'vendor': 'Citrix Systems Inc',
59 'copyright': '(C) 2008 Citrix Systems Inc',
60 'driver_version': '1.0',
61 'required_api_version': '1.0',
62 'capabilities': CAPABILITIES,
63 'configuration': CONFIGURATION
64 }
66JOURNAL_FILE_PREFIX = ".journal-"
68OPS_EXCLUSIVE = [
69 "sr_create", "sr_delete", "sr_probe", "sr_attach", "sr_detach",
70 "sr_scan", "vdi_init", "vdi_create", "vdi_delete", "vdi_attach",
71 "vdi_detach", "vdi_resize_online", "vdi_snapshot", "vdi_clone"]
73DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True}
76class FileSR(SR.SR):
77 """Local file storage repository"""
79 SR_TYPE = "file"
81 @override
82 @staticmethod
83 def handles(srtype) -> bool:
84 return srtype == 'file'
86 def _check_o_direct(self):
87 if self.sr_ref and self.session is not None:
88 other_config = self.session.xenapi.SR.get_other_config(self.sr_ref)
89 o_direct = other_config.get("o_direct")
90 self.o_direct = o_direct is not None and o_direct == "true"
91 else:
92 self.o_direct = True
94 def __init__(self, srcmd, sr_uuid):
95 # We call SR.SR.__init__ explicitly because
96 # "super" sometimes failed due to circular imports
97 SR.SR.__init__(self, srcmd, sr_uuid)
98 self.image_info = {}
99 self._init_image_formats()
100 self._check_o_direct()
102 @override
103 def load(self, sr_uuid) -> None:
104 self.ops_exclusive = OPS_EXCLUSIVE
105 self.lock = lock.Lock(lock.LOCK_TYPE_SR, self.uuid)
106 self.sr_vditype = SR.DEFAULT_TAP
107 if 'location' not in self.dconf or not self.dconf['location']: 107 ↛ 108line 107 didn't jump to line 108, because the condition on line 107 was never true
108 raise xs_errors.XenError('ConfigLocationMissing')
109 self.remotepath = self.dconf['location']
110 self.path = os.path.join(SR.MOUNT_BASE, sr_uuid)
111 self.linkpath = self.path
112 self.mountpoint = self.path
113 self.attached = False
114 self.driver_config = DRIVER_CONFIG
116 @override
117 def create(self, sr_uuid, size) -> None:
118 """ Create the SR. The path must not already exist, or if it does,
119 it must be empty. (This accounts for the case where the user has
120 mounted a device onto a directory manually and want to use this as the
121 root of a file-based SR.) """
122 try:
123 if util.ioretry(lambda: util.pathexists(self.remotepath)): 123 ↛ 124line 123 didn't jump to line 124, because the condition on line 123 was never true
124 if len(util.ioretry(lambda: util.listdir(self.remotepath))) != 0:
125 raise xs_errors.XenError('SRExists')
126 else:
127 try:
128 util.ioretry(lambda: os.mkdir(self.remotepath))
129 except util.CommandException as inst:
130 if inst.code == errno.EEXIST:
131 raise xs_errors.XenError('SRExists')
132 else:
133 raise xs_errors.XenError('FileSRCreate', \
134 opterr='directory creation failure %d' \
135 % inst.code)
136 except:
137 raise xs_errors.XenError('FileSRCreate')
139 @override
140 def delete(self, sr_uuid) -> None:
141 self.attach(sr_uuid)
142 cleanup.gc_force(self.session, self.uuid)
144 # check to make sure no VDIs are present; then remove old
145 # files that are non VDI's
146 try:
147 if util.ioretry(lambda: util.pathexists(self.path)):
148 #Load the VDI list
149 self._loadvdis()
150 for uuid in self.vdis:
151 if not self.vdis[uuid].deleted:
152 raise xs_errors.XenError('SRNotEmpty', \
153 opterr='VDIs still exist in SR')
155 # remove everything else, there are no vdi's
156 for name in util.ioretry(lambda: util.listdir(self.path)):
157 fullpath = os.path.join(self.path, name)
158 try:
159 util.ioretry(lambda: os.unlink(fullpath))
160 except util.CommandException as inst:
161 if inst.code != errno.ENOENT and \
162 inst.code != errno.EISDIR:
163 raise xs_errors.XenError('FileSRDelete', \
164 opterr='failed to remove %s error %d' \
165 % (fullpath, inst.code))
166 self.detach(sr_uuid)
167 except util.CommandException as inst:
168 self.detach(sr_uuid)
169 raise xs_errors.XenError('FileSRDelete', \
170 opterr='error %d' % inst.code)
172 @override
173 def attach(self, sr_uuid) -> None:
174 self.attach_and_bind(sr_uuid)
176 def attach_and_bind(self, sr_uuid, bind=True) -> None:
177 if not self._checkmount():
178 try:
179 util.ioretry(lambda: util.makedirs(self.path, mode=0o700))
180 except util.CommandException as inst:
181 if inst.code != errno.EEXIST:
182 raise xs_errors.XenError("FileSRCreate", \
183 opterr='fail to create mount point. Errno is %s' % inst.code)
184 try:
185 cmd = ["mount", self.remotepath, self.path]
186 if bind:
187 cmd.append("--bind")
188 util.pread(cmd)
189 os.chmod(self.path, mode=0o0700)
190 except util.CommandException as inst:
191 raise xs_errors.XenError('FileSRCreate', \
192 opterr='fail to mount FileSR. Errno is %s' % inst.code)
193 self.attached = True
195 @override
196 def detach(self, sr_uuid) -> None:
197 if self._checkmount():
198 try:
199 util.SMlog("Aborting GC/coalesce")
200 cleanup.abort(self.uuid)
201 os.chdir(SR.MOUNT_BASE)
202 util.pread(["umount", self.path])
203 os.rmdir(self.path)
204 except Exception as e:
205 raise xs_errors.XenError('SRInUse', opterr=str(e))
206 self.attached = False
208 @override
209 def scan(self, sr_uuid) -> None:
210 if not self._checkmount():
211 raise xs_errors.XenError('SRUnavailable', \
212 opterr='no such directory %s' % self.path)
214 if not self.vdis: 214 ↛ 217line 214 didn't jump to line 217, because the condition on line 214 was never false
215 self._loadvdis()
217 if not self.passthrough:
218 self.physical_size = self._getsize()
219 self.physical_utilisation = self._getutilisation()
221 for uuid in list(self.vdis.keys()):
222 if self.vdis[uuid].deleted: 222 ↛ 223line 222 didn't jump to line 223, because the condition on line 222 was never true
223 del self.vdis[uuid]
225 # CA-15607: make sure we are robust to the directory being unmounted beneath
226 # us (eg by a confused user). Without this we might forget all our VDI references
227 # which would be a shame.
228 # For SMB SRs, this path is mountpoint
229 mount_path = self.path
230 if self.handles("smb"): 230 ↛ 231line 230 didn't jump to line 231, because the condition on line 230 was never true
231 mount_path = self.mountpoint
233 if not self.handles("file") and not os.path.ismount(mount_path): 233 ↛ 234line 233 didn't jump to line 234, because the condition on line 233 was never true
234 util.SMlog("Error: FileSR.scan called but directory %s isn't a mountpoint" % mount_path)
235 raise xs_errors.XenError('SRUnavailable', \
236 opterr='not mounted %s' % mount_path)
238 self._kickGC()
240 # default behaviour from here on
241 super(FileSR, self).scan(sr_uuid)
243 @override
244 def update(self, sr_uuid) -> None:
245 if not self._checkmount():
246 raise xs_errors.XenError('SRUnavailable', \
247 opterr='no such directory %s' % self.path)
248 self._update(sr_uuid, 0)
250 def _update(self, sr_uuid, virt_alloc_delta):
251 valloc = int(self.session.xenapi.SR.get_virtual_allocation(self.sr_ref))
252 self.virtual_allocation = valloc + virt_alloc_delta
253 self.physical_size = self._getsize()
254 self.physical_utilisation = self._getutilisation()
255 self._db_update()
257 @override
258 def content_type(self, sr_uuid) -> str:
259 return super(FileSR, self).content_type(sr_uuid)
261 @override
262 def vdi(self, uuid) -> VDI.VDI:
263 return FileVDI(self, uuid)
265 def added_vdi(self, vdi):
266 self.vdis[vdi.uuid] = vdi
268 def deleted_vdi(self, uuid):
269 if uuid in self.vdis:
270 del self.vdis[uuid]
272 @override
273 def replay(self, uuid) -> None:
274 try:
275 file = open(self.path + "/filelog.txt", "r")
276 data = file.readlines()
277 file.close()
278 self._process_replay(data)
279 except:
280 raise xs_errors.XenError('SRLog')
282 def _loadvdis(self):
283 if self.vdis: 283 ↛ 284line 283 didn't jump to line 284, because the condition on line 283 was never true
284 return
286 self.image_info = {}
287 for vdi_type in VDI_COW_TYPES:
288 extension = VDI_TYPE_TO_EXTENSION[vdi_type]
290 pattern = os.path.join(self.path, "*%s" % extension)
291 image_info = {}
293 cowutil = getCowUtil(vdi_type)
294 try:
295 image_info = cowutil.getAllInfoFromVG(pattern, FileVDI.extractUuid)
296 except util.CommandException as inst:
297 raise xs_errors.XenError('SRScan', opterr="error VDI-scanning " \
298 "path %s (%s)" % (self.path, inst))
299 try:
300 vdi_uuids = [FileVDI.extractUuid(v) for v in util.ioretry(lambda: glob.glob(pattern))]
301 if len(image_info) != len(vdi_uuids):
302 util.SMlog("VDI scan of %s returns %d VDIs: %s" % (extension, len(image_info), sorted(image_info)))
303 util.SMlog("VDI list of %s returns %d VDIs: %s" % (extension, len(vdi_uuids), sorted(vdi_uuids)))
304 except:
305 pass
307 self.image_info.update(image_info)
309 for uuid, image_info in self.image_info.items():
310 if image_info.error: 310 ↛ 311line 310 didn't jump to line 311, because the condition on line 310 was never true
311 raise xs_errors.XenError('SRScan', opterr='uuid=%s' % uuid)
313 file_vdi = self.vdi(uuid)
314 file_vdi.cowutil = cowutil
315 self.vdis[uuid] = file_vdi
317 # Get the key hash of any encrypted VDIs:
318 vdi_path = os.path.join(self.path, image_info.path)
319 key_hash = cowutil.getKeyHash(vdi_path)
320 self.vdis[uuid].sm_config_override['key_hash'] = key_hash
322 # raw VDIs and CBT log files
323 files = util.ioretry(lambda: util.listdir(self.path)) 323 ↛ exitline 323 didn't run the lambda on line 323
324 for fn in files: 324 ↛ 325line 324 didn't jump to line 325, because the loop on line 324 never started
325 if fn.endswith(VdiTypeExtension.RAW):
326 uuid = fn[:-(len(VdiTypeExtension.RAW))]
327 self.vdis[uuid] = self.vdi(uuid)
328 elif fn.endswith(CBTLOG_TAG):
329 cbt_uuid = fn.split(".")[0]
330 # If an associated disk exists, update CBT status
331 # else create new VDI of type cbt_metadata
332 if cbt_uuid in self.vdis:
333 self.vdis[cbt_uuid].cbt_enabled = True
334 else:
335 new_vdi = self.vdi(cbt_uuid)
336 new_vdi.ty = "cbt_metadata"
337 new_vdi.cbt_enabled = True
338 self.vdis[cbt_uuid] = new_vdi
340 # Mark parent VDIs as Read-only and generate virtual allocation
341 self.virtual_allocation = 0
342 for uuid, vdi in self.vdis.items():
343 if vdi.parent: 343 ↛ 344line 343 didn't jump to line 344, because the condition on line 343 was never true
344 if vdi.parent in self.vdis:
345 self.vdis[vdi.parent].read_only = True
346 if vdi.parent in geneology:
347 geneology[vdi.parent].append(uuid)
348 else:
349 geneology[vdi.parent] = [uuid]
350 if not vdi.hidden: 350 ↛ 342line 350 didn't jump to line 342, because the condition on line 350 was never false
351 self.virtual_allocation += (vdi.size)
353 # now remove all hidden leaf nodes from self.vdis so that they are not
354 # introduced into the Agent DB when SR is synchronized. With the
355 # asynchronous GC, a deleted VDI might stay around until the next
356 # SR.scan, so if we don't ignore hidden leaves we would pick up
357 # freshly-deleted VDIs as newly-added VDIs
358 for uuid in list(self.vdis.keys()):
359 if uuid not in geneology and self.vdis[uuid].hidden: 359 ↛ 360line 359 didn't jump to line 360, because the condition on line 359 was never true
360 util.SMlog("Scan found hidden leaf (%s), ignoring" % uuid)
361 del self.vdis[uuid]
363 def _getsize(self):
364 path = self.path
365 if self.handles("smb"): 365 ↛ 366line 365 didn't jump to line 366, because the condition on line 365 was never true
366 path = self.linkpath
367 return util.get_fs_size(path)
369 def _getutilisation(self):
370 return util.get_fs_utilisation(self.path)
372 def _replay(self, logentry):
373 # all replay commands have the same 5,6,7th arguments
374 # vdi_command, sr-uuid, vdi-uuid
375 back_cmd = logentry[5].replace("vdi_", "")
376 target = self.vdi(logentry[7])
377 cmd = getattr(target, back_cmd)
378 args = []
379 for item in logentry[6:]:
380 item = item.replace("\n", "")
381 args.append(item)
382 ret = cmd( * args)
383 if ret:
384 print(ret)
386 def _compare_args(self, a, b):
387 try:
388 if a[2] != "log:":
389 return 1
390 if b[2] != "end:" and b[2] != "error:":
391 return 1
392 if a[3] != b[3]:
393 return 1
394 if a[4] != b[4]:
395 return 1
396 return 0
397 except:
398 return 1
400 def _process_replay(self, data):
401 logentries = []
402 for logentry in data:
403 logentry = logentry.split(" ")
404 logentries.append(logentry)
405 # we are looking for a log entry that has a log but no end or error
406 # wkcfix -- recreate (adjusted) logfile
407 index = 0
408 while index < len(logentries) - 1:
409 if self._compare_args(logentries[index], logentries[index + 1]):
410 self._replay(logentries[index])
411 else:
412 # skip the paired one
413 index += 1
414 # next
415 index += 1
417 def _kickGC(self):
418 util.SMlog("Kicking GC")
419 cleanup.start_gc_service(self.uuid)
421 def _isbind(self):
422 # os.path.ismount can't deal with bind mount
423 st1 = os.stat(self.path)
424 st2 = os.stat(self.remotepath)
425 return st1.st_dev == st2.st_dev and st1.st_ino == st2.st_ino
427 def _checkmount(self) -> bool:
428 mount_path = self.path
429 if self.handles("smb"): 429 ↛ 430line 429 didn't jump to line 430, because the condition on line 429 was never true
430 mount_path = self.mountpoint
432 return util.ioretry(lambda: util.pathexists(mount_path) and \
433 (util.ismount(mount_path) or \
434 util.pathexists(self.remotepath) and self._isbind()))
436 # Override in SharedFileSR.
437 def _check_hardlinks(self) -> bool:
438 return True
440class FileVDI(VDI.VDI):
441 def _resolve_vdi_context_from_path(self, vdi_uuid, maxretry=5, period=2.0):
442 vdi_formats = (
443 (VdiTypeExtension.VHD, VdiType.VHD),
444 (VdiTypeExtension.QCOW2, VdiType.QCOW2),
445 (VdiTypeExtension.RAW, VdiType.RAW),
446 (VdiTypeExtension.CBTLOG, VdiType.CBTLOG)
447 )
449 vdi_uuid_str = str(vdi_uuid)
450 for try_index in range(maxretry):
451 for vdi_extension, vdi_type in vdi_formats:
452 vdi_path = os.path.join(self.sr.path, vdi_uuid_str + vdi_extension)
454 if util.pathexists(vdi_path):
455 self.vdi_type = vdi_type
456 if vdi_type in (VdiType.RAW, VdiType.CBTLOG):
457 self.hidden = False
458 self.path = vdi_path
460 with contextlib.suppress(Exception):
461 self.cowutil = getCowUtil(self.vdi_type)
463 return True
465 util.SMlog("VDI %s not found, try %s of %s" % (vdi_uuid_str, try_index + 1, maxretry))
466 if try_index + 1 < maxretry:
467 time.sleep(period)
469 return False
471 @override
472 def load(self, vdi_uuid) -> None:
473 self.lock = self.sr.lock
475 self.sr.srcmd.params['o_direct'] = self.sr.o_direct
477 if self.sr.srcmd.cmd == "vdi_create":
478 image_format = None
479 self.key_hash = None
481 vdi_sm_config = self.sr.srcmd.params.get("vdi_sm_config")
482 if vdi_sm_config: 482 ↛ 486line 482 didn't jump to line 486, because the condition on line 482 was never false
483 image_format = self.sr.read_config_image_format(vdi_sm_config)
484 self.key_hash = vdi_sm_config.get("key_hash")
486 if not image_format: 486 ↛ 487line 486 didn't jump to line 487, because the condition on line 486 was never true
487 size = int(self.sr.srcmd.params['args'][0])
488 # In the case of vdi_create, the first parameter is size.
489 # We need it to validate the vdi_type choice
490 for image_format in self.sr.preferred_image_formats:
491 vdi_type = getVdiTypeFromImageFormat(image_format)
492 cowutil = getCowUtil(vdi_type)
493 try:
494 cowutil.validateAndRoundImageSize(size)
495 break
496 except xs_errors.SROSError:
497 util.SMlog(f"We won't be able to create the VDI with format {vdi_type}.")
498 # If the last one also fail we still give the vdi_type and cowutil,
499 # it will fail in the `create` function instead when re-running `validateAndRoundImageSize`
500 self.vdi_type = self.sr._resolve_vdi_type_from_image_format(image_format)
501 self.cowutil = getCowUtil(self.vdi_type)
503 self.path = os.path.join(self.sr.path, "%s%s" %
504 (vdi_uuid, VDI_TYPE_TO_EXTENSION[self.vdi_type]))
505 else:
506 found = self._resolve_vdi_context_from_path(vdi_uuid)
507 if not found: 507 ↛ 508line 507 didn't jump to line 508, because the condition on line 507 was never true
508 if self.sr.srcmd.cmd == "vdi_delete":
509 # Could be delete for CBT log file
510 self.path = os.path.join(self.sr.path, f"{vdi_uuid}.deleted")
511 return
512 if self.sr.srcmd.cmd == "vdi_attach_from_config":
513 return
514 raise xs_errors.XenError('VDIUnavailable',
515 opterr="VDI %s not found" % vdi_uuid)
517 image_info = VdiType.isCowImage(self.vdi_type) and self.sr.image_info.get(vdi_uuid)
518 if image_info:
519 # Image info already preloaded: use it instead of querying directly
520 self.utilisation = image_info.sizePhys
521 self.size = image_info.sizeVirt
522 self.hidden = image_info.hidden
523 if self.hidden: 523 ↛ 524line 523 didn't jump to line 524, because the condition on line 523 was never true
524 self.managed = False
525 self.parent = image_info.parentUuid
526 if self.parent: 526 ↛ 527line 526 didn't jump to line 527, because the condition on line 526 was never true
527 self.sm_config_override = {'vhd-parent': self.parent}
528 else:
529 self.sm_config_override = {'vhd-parent': None}
530 return
532 try:
533 # Change to the SR directory in case parent
534 # locator field path has changed
535 os.chdir(self.sr.path)
536 except Exception as chdir_exception:
537 util.SMlog("Unable to change to SR directory, SR unavailable, %s" %
538 str(chdir_exception))
539 raise xs_errors.XenError('SRUnavailable', opterr=str(chdir_exception))
541 if util.ioretry( 541 ↛ exitline 541 didn't return from function 'load', because the condition on line 541 was never false
542 lambda: util.pathexists(self.path),
543 errlist=[errno.EIO, errno.ENOENT]):
544 try:
545 st = util.ioretry(lambda: os.stat(self.path),
546 errlist=[errno.EIO, errno.ENOENT])
547 self.utilisation = int(st.st_size)
548 except util.CommandException as inst:
549 if inst.code == errno.EIO:
550 raise xs_errors.XenError('VDILoad', \
551 opterr='Failed load VDI information %s' % self.path)
552 else:
553 util.SMlog("Stat failed for %s, %s" % (
554 self.path, str(inst)))
555 raise xs_errors.XenError('VDIType', \
556 opterr='Invalid VDI type %s' % self.vdi_type)
558 if self.vdi_type == VdiType.RAW: 558 ↛ 559line 558 didn't jump to line 559, because the condition on line 558 was never true
559 self.exists = True
560 self.size = self.utilisation
561 return
563 if self.vdi_type == VdiType.CBTLOG: 563 ↛ 564line 563 didn't jump to line 564, because the condition on line 563 was never true
564 self.exists = True
565 self.size = self.utilisation
566 return
568 try:
569 # The VDI might be activated in R/W mode so the VHD footer
570 # won't be valid, use the back-up one instead.
571 image_info = self.cowutil.getInfo(self.path, FileVDI.extractUuid, useBackupFooter=True)
573 if image_info.parentUuid: 573 ↛ 574line 573 didn't jump to line 574, because the condition on line 573 was never true
574 self.parent = image_info.parentUuid
575 self.sm_config_override = {'vhd-parent': self.parent}
576 else:
577 self.parent = ""
578 self.sm_config_override = {'vhd-parent': None}
579 self.size = image_info.sizeVirt
580 self.hidden = image_info.hidden
581 if self.hidden: 581 ↛ 582line 581 didn't jump to line 582, because the condition on line 581 was never true
582 self.managed = False
583 self.exists = True
584 except util.CommandException as inst:
585 raise xs_errors.XenError('VDILoad', \
586 opterr='Failed load VDI information %s' % self.path)
588 @override
589 def update(self, sr_uuid, vdi_location) -> None:
590 self.load(vdi_location)
591 vdi_ref = self.sr.srcmd.params['vdi_ref']
592 self.sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
593 self._db_update()
595 @override
596 def create(self, sr_uuid, vdi_uuid, size) -> str:
597 if util.ioretry(lambda: util.pathexists(self.path)): 597 ↛ 598line 597 didn't jump to line 598, because the condition on line 597 was never true
598 raise xs_errors.XenError('VDIExists')
600 if VdiType.isCowImage(self.vdi_type):
601 try:
602 size = self.cowutil.validateAndRoundImageSize(int(size))
603 util.ioretry(lambda: self._create(size, self.path))
604 self.size = self.cowutil.getSizeVirt(self.path)
605 except util.CommandException as inst:
606 raise xs_errors.XenError('VDICreate',
607 opterr='error %d' % inst.code)
608 else:
609 f = open(self.path, 'w')
610 f.truncate(int(size))
611 f.close()
612 self.size = size
614 self.sr.added_vdi(self)
616 st = util.ioretry(lambda: os.stat(self.path))
617 self.utilisation = int(st.st_size)
618 if not hasattr(self, 'sm_config'): 618 ↛ 620line 618 didn't jump to line 620, because the condition on line 618 was never false
619 self.sm_config = {}
620 self.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
622 self._db_introduce()
623 self.sr._update(self.sr.uuid, self.size)
624 return super(FileVDI, self).get_params()
626 @override
627 def delete(self, sr_uuid, vdi_uuid, data_only=False) -> None:
628 if not util.ioretry(lambda: util.pathexists(self.path)):
629 return super(FileVDI, self).delete(sr_uuid, vdi_uuid, data_only)
631 if self.attached:
632 raise xs_errors.XenError('VDIInUse')
634 try:
635 util.force_unlink(self.path)
636 except Exception as e:
637 raise xs_errors.XenError(
638 'VDIDelete',
639 opterr='Failed to unlink file during deleting VDI: %s' % str(e))
641 self.sr.deleted_vdi(vdi_uuid)
642 # If this is a data_destroy call, don't remove from XAPI db
643 if not data_only:
644 self._db_forget()
645 self.sr._update(self.sr.uuid, -self.size)
646 self.sr.lock.cleanupAll(vdi_uuid)
647 self.sr._kickGC()
648 return super(FileVDI, self).delete(sr_uuid, vdi_uuid, data_only)
650 @override
651 def attach(self, sr_uuid, vdi_uuid) -> str:
652 if self.path is None:
653 self._resolve_vdi_context_from_path(vdi_uuid)
654 if not self._checkpath(self.path):
655 raise xs_errors.XenError('VDIUnavailable', \
656 opterr='VDI %s unavailable %s' % (vdi_uuid, self.path))
657 try:
658 self.attached = True
660 if not hasattr(self, 'xenstore_data'):
661 self.xenstore_data = {}
663 self.xenstore_data.update(scsiutil.update_XS_SCSIdata(vdi_uuid, \
664 scsiutil.gen_synthetic_page_data(vdi_uuid)))
666 if self.sr.handles("file"):
667 # XXX: PR-1255: if these are constants then they should
668 # be returned by the attach API call, not persisted in the
669 # pool database.
670 self.xenstore_data['storage-type'] = 'ext'
671 return super(FileVDI, self).attach(sr_uuid, vdi_uuid)
672 except util.CommandException as inst:
673 raise xs_errors.XenError('VDILoad', opterr='error %d' % inst.code)
675 @override
676 def detach(self, sr_uuid, vdi_uuid) -> None:
677 self.attached = False
679 @override
680 def resize(self, sr_uuid, vdi_uuid, size) -> str:
681 if not self.exists:
682 raise xs_errors.XenError('VDIUnavailable', \
683 opterr='VDI %s unavailable %s' % (vdi_uuid, self.path))
685 if not VdiType.isCowImage(self.vdi_type):
686 raise xs_errors.XenError('Unimplemented')
688 if self.hidden:
689 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
691 if size < self.size:
692 util.SMlog('vdi_resize: shrinking not supported: ' + \
693 '(current size: %d, new size: %d)' % (self.size, size))
694 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
696 if size == self.size:
697 return VDI.VDI.get_params(self)
699 # We already checked it is a cow image.
700 size = self.cowutil.validateAndRoundImageSize(int(size))
702 jFile = JOURNAL_FILE_PREFIX + self.uuid
703 try:
704 self.cowutil.setSizeVirt(self.path, size, jFile)
705 except:
706 # Revert the operation
707 self.cowutil.revert(self.path, jFile)
708 raise xs_errors.XenError('VDISize', opterr='resize operation failed')
710 old_size = self.size
711 self.size = self.cowutil.getSizeVirt(self.path)
712 st = util.ioretry(lambda: os.stat(self.path))
713 self.utilisation = int(st.st_size)
715 self._db_update()
716 self.sr._update(self.sr.uuid, self.size - old_size)
717 super(FileVDI, self).resize_cbt(self.sr.uuid, self.uuid, self.size)
718 return VDI.VDI.get_params(self)
720 @override
721 def clone(self, sr_uuid, vdi_uuid) -> str:
722 return self._do_snapshot(sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE)
724 @override
725 def compose(self, sr_uuid, vdi1, vdi2) -> None:
726 if not VdiType.isCowImage(self.vdi_type):
727 raise xs_errors.XenError('Unimplemented')
728 parent_fn = vdi1 + VDI_TYPE_TO_EXTENSION[self.vdi_type]
729 parent_path = os.path.join(self.sr.path, parent_fn)
730 assert(util.pathexists(parent_path))
731 self.cowutil.setParent(self.path, parent_path, False)
732 self.cowutil.setHidden(parent_path)
733 self.sr.session.xenapi.VDI.set_managed(self.sr.srcmd.params['args'][0], False)
734 # Tell tapdisk the chain has changed
735 if not blktap2.VDI.tap_refresh(self.session, sr_uuid, vdi2):
736 raise util.SMException("failed to refresh VDI %s" % self.uuid)
737 util.SMlog("VDI.compose: relinked %s->%s" % (vdi2, vdi1))
739 def reset_leaf(self, sr_uuid, vdi_uuid):
740 if not VdiType.isCowImage(self.vdi_type):
741 raise xs_errors.XenError('Unimplemented')
743 # safety check
744 if not self.cowutil.hasParent(self.path):
745 raise util.SMException("ERROR: VDI %s has no parent, " + \
746 "will not reset contents" % self.uuid)
748 self.cowutil.killData(self.path)
750 @override
751 def _do_snapshot(self, sr_uuid, vdi_uuid, snapType,
752 cloneOp=False, secondary=None, cbtlog=None, is_mirror_destination=False) -> str:
753 # If cbt enabled, save file consistency state
754 if cbtlog is not None: 754 ↛ 755line 754 didn't jump to line 755, because the condition on line 754 was never true
755 if blktap2.VDI.tap_status(self.session, vdi_uuid):
756 consistency_state = False
757 else:
758 consistency_state = True
759 util.SMlog("Saving log consistency state of %s for vdi: %s" %
760 (consistency_state, vdi_uuid))
761 else:
762 consistency_state = None
764 if not VdiType.isCowImage(self.vdi_type): 764 ↛ 765line 764 didn't jump to line 765, because the condition on line 764 was never true
765 raise xs_errors.XenError('Unimplemented')
767 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid): 767 ↛ 768line 767 didn't jump to line 768, because the condition on line 767 was never true
768 raise util.SMException("failed to pause VDI %s" % vdi_uuid)
769 try:
770 return self._snapshot(snapType, cbtlog, consistency_state, is_mirror_destination)
771 finally:
772 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary)
773 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
775 @override
776 def _rename(self, src, dst) -> None:
777 util.SMlog("FileVDI._rename %s to %s" % (src, dst))
778 util.ioretry(lambda: os.rename(src, dst))
780 def _link(self, src, dst):
781 util.SMlog("FileVDI._link %s to %s" % (src, dst))
782 os.link(src, dst)
784 def _unlink(self, path):
785 util.SMlog("FileVDI._unlink %s" % (path))
786 os.unlink(path)
788 def _create_new_parent(self, src, newsrc):
789 if self.sr._check_hardlinks():
790 self._link(src, newsrc)
791 else:
792 self._rename(src, newsrc)
794 def __fist_enospace(self):
795 raise util.CommandException(28, "cowutil snapshot", reason="No space")
797 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None, is_mirror_destination=False):
798 util.SMlog("FileVDI._snapshot for %s (type %s)" % (self.uuid, snap_type))
800 args = []
801 args.append("vdi_clone")
802 args.append(self.sr.uuid)
803 args.append(self.uuid)
805 dest = None
806 dst = None
807 extension = VDI_TYPE_TO_EXTENSION[self.vdi_type]
808 if snap_type == VDI.SNAPSHOT_DOUBLE: 808 ↛ 813line 808 didn't jump to line 813, because the condition on line 808 was never false
809 dest = util.gen_uuid()
810 dst = os.path.join(self.sr.path, dest + extension)
811 args.append(dest)
813 if self.hidden: 813 ↛ 814line 813 didn't jump to line 814, because the condition on line 813 was never true
814 raise xs_errors.XenError('VDIClone', opterr='hidden VDI')
816 depth = self.cowutil.getDepth(self.path)
817 if depth == -1: 817 ↛ 818line 817 didn't jump to line 818, because the condition on line 817 was never true
818 raise xs_errors.XenError('VDIUnavailable', \
819 opterr='failed to get image depth')
820 elif depth >= self.cowutil.getMaxChainLength(): 820 ↛ 821line 820 didn't jump to line 821, because the condition on line 820 was never true
821 raise xs_errors.XenError('SnapshotChainTooLong')
823 newuuid = util.gen_uuid()
824 src = self.path
825 newsrcname = newuuid + extension
826 newsrc = os.path.join(self.sr.path, newsrcname)
828 if not self._checkpath(src): 828 ↛ 829line 828 didn't jump to line 829, because the condition on line 828 was never true
829 raise xs_errors.XenError('VDIUnavailable', \
830 opterr='VDI %s unavailable %s' % (self.uuid, src))
832 # wkcfix: multiphase
833 util.start_log_entry(self.sr.path, self.path, args)
835 # We assume the filehandle has been released
836 try:
837 self._create_new_parent(src, newsrc)
839 # Create the snapshot under a temporary name, then rename
840 # it afterwards. This avoids a small window where it exists
841 # but is invalid. We do not need to do this for
842 # snap_type == VDI.SNAPSHOT_DOUBLE because dst never existed
843 # before so nobody will try to query it.
844 tmpsrc = "%s.%s" % (src, "new")
845 # Fault injection site to fail the snapshot with ENOSPACE
846 util.fistpoint.activate_custom_fn(
847 "FileSR_fail_snap1",
848 self.__fist_enospace)
849 util.ioretry(lambda: self._snap(tmpsrc, newsrcname, is_mirror_destination))
850 # SMB3 can return EACCES if we attempt to rename over the
851 # hardlink leaf too quickly after creating it.
852 util.ioretry(lambda: self._rename(tmpsrc, src),
853 errlist=[errno.EIO, errno.EACCES])
854 if snap_type == VDI.SNAPSHOT_DOUBLE: 854 ↛ 862line 854 didn't jump to line 862, because the condition on line 854 was never false
855 # Fault injection site to fail the snapshot with ENOSPACE
856 util.fistpoint.activate_custom_fn(
857 "FileSR_fail_snap2",
858 self.__fist_enospace)
859 util.ioretry(lambda: self._snap(dst, newsrcname))
860 # mark the original file (in this case, its newsrc)
861 # as hidden so that it does not show up in subsequent scans
862 util.ioretry(lambda: self._mark_hidden(newsrc))
864 #Verify parent locator field of both children and delete newsrc if unused
865 introduce_parent = True
866 try:
867 srcparent = self.cowutil.getParent(src, FileVDI.extractUuid)
868 dstparent = None
869 if snap_type == VDI.SNAPSHOT_DOUBLE: 869 ↛ 871line 869 didn't jump to line 871, because the condition on line 869 was never false
870 dstparent = self.cowutil.getParent(dst, FileVDI.extractUuid)
871 if srcparent != newuuid and \ 871 ↛ 875line 871 didn't jump to line 875, because the condition on line 871 was never true
872 (snap_type == VDI.SNAPSHOT_SINGLE or \
873 snap_type == VDI.SNAPSHOT_INTERNAL or \
874 dstparent != newuuid):
875 util.ioretry(lambda: self._unlink(newsrc))
876 introduce_parent = False
877 except Exception as e:
878 raise
880 # Introduce the new VDI records
881 leaf_vdi = None
882 if snap_type == VDI.SNAPSHOT_DOUBLE: 882 ↛ 903line 882 didn't jump to line 903, because the condition on line 882 was never false
883 leaf_vdi = VDI.VDI(self.sr, dest) # user-visible leaf VDI
884 leaf_vdi.read_only = False
885 leaf_vdi.location = dest
886 leaf_vdi.size = self.size
887 leaf_vdi.utilisation = self.utilisation
888 leaf_vdi.sm_config = {}
889 leaf_vdi.sm_config['vhd-parent'] = dstparent
890 # TODO: fix the raw snapshot case
891 leaf_vdi.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
892 # If the parent is encrypted set the key_hash
893 # for the new snapshot disk
894 vdi_ref = self.sr.srcmd.params['vdi_ref']
895 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
896 if "key_hash" in sm_config: 896 ↛ 897line 896 didn't jump to line 897, because the condition on line 896 was never true
897 leaf_vdi.sm_config['key_hash'] = sm_config['key_hash']
898 # If we have CBT enabled on the VDI,
899 # set CBT status for the new snapshot disk
900 if cbtlog: 900 ↛ 901line 900 didn't jump to line 901, because the condition on line 900 was never true
901 leaf_vdi.cbt_enabled = True
903 base_vdi = None
904 if introduce_parent: 904 ↛ 918line 904 didn't jump to line 918, because the condition on line 904 was never false
905 base_vdi = VDI.VDI(self.sr, newuuid) # readonly parent
906 base_vdi.label = "base copy"
907 base_vdi.read_only = True
908 base_vdi.location = newuuid
909 base_vdi.size = self.size
910 base_vdi.utilisation = self.utilisation
911 base_vdi.sm_config = {}
912 # TODO: fix the raw snapshot case
913 base_vdi.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
914 grandparent = self.cowutil.getParent(newsrc, FileVDI.extractUuid)
915 if grandparent: 915 ↛ 918line 915 didn't jump to line 918, because the condition on line 915 was never false
916 base_vdi.sm_config['vhd-parent'] = grandparent
918 try:
919 if snap_type == VDI.SNAPSHOT_DOUBLE: 919 ↛ 924line 919 didn't jump to line 924, because the condition on line 919 was never false
920 leaf_vdi_ref = leaf_vdi._db_introduce()
921 util.SMlog("vdi_clone: introduced VDI: %s (%s)" % \
922 (leaf_vdi_ref, dest))
924 if introduce_parent: 924 ↛ 928line 924 didn't jump to line 928, because the condition on line 924 was never false
925 base_vdi_ref = base_vdi._db_introduce()
926 self.session.xenapi.VDI.set_managed(base_vdi_ref, False)
927 util.SMlog("vdi_clone: introduced VDI: %s (%s)" % (base_vdi_ref, newuuid))
928 vdi_ref = self.sr.srcmd.params['vdi_ref']
929 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
930 sm_config['vhd-parent'] = srcparent
931 # TODO: fix the raw snapshot case
932 sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
933 self.session.xenapi.VDI.set_sm_config(vdi_ref, sm_config)
934 except Exception as e:
935 util.SMlog("vdi_clone: caught error during VDI.db_introduce: %s" % (str(e)))
936 # Note it's too late to actually clean stuff up here: the base disk has
937 # been marked as deleted already.
938 util.end_log_entry(self.sr.path, self.path, ["error"])
939 raise
940 except util.CommandException as inst:
941 # XXX: it might be too late if the base disk has been marked as deleted!
942 self._clonecleanup(src, dst, newsrc)
943 util.end_log_entry(self.sr.path, self.path, ["error"])
944 raise xs_errors.XenError('VDIClone',
945 opterr='VDI clone failed error %d' % inst.code)
947 # Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
948 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 948 ↛ 949line 948 didn't jump to line 949, because the condition on line 948 was never true
949 try:
950 self._cbt_snapshot(dest, cbt_consistency)
951 except:
952 # CBT operation failed.
953 util.end_log_entry(self.sr.path, self.path, ["error"])
954 raise
956 util.end_log_entry(self.sr.path, self.path, ["done"])
957 if snap_type != VDI.SNAPSHOT_INTERNAL: 957 ↛ 960line 957 didn't jump to line 960, because the condition on line 957 was never false
958 self.sr._update(self.sr.uuid, self.size)
959 # Return info on the new user-visible leaf VDI
960 ret_vdi = leaf_vdi
961 if not ret_vdi: 961 ↛ 962line 961 didn't jump to line 962, because the condition on line 961 was never true
962 ret_vdi = base_vdi
963 if not ret_vdi: 963 ↛ 964line 963 didn't jump to line 964, because the condition on line 963 was never true
964 ret_vdi = self
965 return ret_vdi.get_params()
967 @override
968 def get_params(self) -> str:
969 if not self._checkpath(self.path):
970 raise xs_errors.XenError('VDIUnavailable', \
971 opterr='VDI %s unavailable %s' % (self.uuid, self.path))
972 return super(FileVDI, self).get_params()
974 def _snap(self, child, parent, is_mirror_destination=False):
975 self.cowutil.snapshot(child, parent, self.vdi_type == VdiType.RAW, is_mirror_image=is_mirror_destination)
977 def _clonecleanup(self, src, dst, newsrc):
978 try:
979 if dst: 979 ↛ 983line 979 didn't jump to line 983, because the condition on line 979 was never false
980 util.ioretry(lambda: self._unlink(dst))
981 except util.CommandException as inst:
982 pass
983 try:
984 if util.ioretry(lambda: util.pathexists(newsrc)): 984 ↛ exitline 984 didn't return from function '_clonecleanup', because the condition on line 984 was never false
985 stats = os.stat(newsrc)
986 # Check if we have more than one link to newsrc
987 if (stats.st_nlink > 1):
988 util.ioretry(lambda: self._unlink(newsrc))
989 elif not self._is_hidden(newsrc): 989 ↛ exitline 989 didn't return from function '_clonecleanup', because the condition on line 989 was never false
990 self._rename(newsrc, src)
991 except util.CommandException as inst:
992 pass
994 def _checkpath(self, path):
995 try:
996 if not util.ioretry(lambda: util.pathexists(path)): 996 ↛ 997line 996 didn't jump to line 997, because the condition on line 996 was never true
997 return False
998 return True
999 except util.CommandException as inst:
1000 raise xs_errors.XenError('EIO', \
1001 opterr='IO error checking path %s' % path)
1003 def _create(self, size, path):
1004 self.cowutil.create(path, size, False)
1005 if self.key_hash: 1005 ↛ 1006line 1005 didn't jump to line 1006, because the condition on line 1005 was never true
1006 self.cowutil.setKey(path, self.key_hash)
1008 def _mark_hidden(self, path):
1009 self.cowutil.setHidden(path, True)
1010 self.hidden = 1
1012 def _is_hidden(self, path):
1013 return self.cowutil.getHidden(path) == 1
1015 @staticmethod
1016 def extractUuid(path: str) -> str:
1017 fileName = os.path.basename(path)
1018 return os.path.splitext(fileName)[0]
1020 @override
1021 def generate_config(self, sr_uuid, vdi_uuid) -> str:
1022 """
1023 Generate the XML config required to attach and activate
1024 a VDI for use when XAPI is not running. Attach and
1025 activation is handled by vdi_attach_from_config below.
1026 """
1027 util.SMlog("FileVDI.generate_config")
1028 if not util.pathexists(self.path): 1028 ↛ 1029line 1028 didn't jump to line 1029, because the condition on line 1028 was never true
1029 raise xs_errors.XenError('VDIUnavailable')
1030 resp = {}
1031 resp['device_config'] = self.sr.dconf
1032 resp['sr_uuid'] = sr_uuid
1033 resp['vdi_uuid'] = vdi_uuid
1034 resp['command'] = 'vdi_attach_from_config'
1035 # Return the 'config' encoded within a normal XMLRPC response so that
1036 # we can use the regular response/error parsing code.
1037 config = xmlrpc.client.dumps(tuple([resp]), "vdi_attach_from_config")
1038 return xmlrpc.client.dumps((config, ), "", True)
1040 @override
1041 def attach_from_config(self, sr_uuid, vdi_uuid) -> str:
1042 """
1043 Attach and activate a VDI using config generated by
1044 vdi_generate_config above. This is used for cases such as
1045 the HA state-file and the redo-log.
1046 """
1047 util.SMlog("FileVDI.attach_from_config")
1048 try:
1049 if not util.pathexists(self.sr.path):
1050 return self.sr.attach(sr_uuid)
1051 except:
1052 util.logException("FileVDI.attach_from_config")
1053 raise xs_errors.XenError(
1054 'SRUnavailable',
1055 opterr='Unable to attach from config'
1056 )
1057 return ''
1059 @override
1060 def _create_cbt_log(self) -> str:
1061 # Create CBT log file
1062 # Name: <vdi_uuid>.cbtlog
1063 #Handle if file already exists
1064 log_path = self._get_cbt_logpath(self.uuid)
1065 open_file = open(log_path, "w+")
1066 open_file.close()
1067 return super(FileVDI, self)._create_cbt_log()
1069 @override
1070 def _delete_cbt_log(self) -> None:
1071 logPath = self._get_cbt_logpath(self.uuid)
1072 try:
1073 os.remove(logPath)
1074 except OSError as e:
1075 if e.errno != errno.ENOENT:
1076 raise
1078 @override
1079 def _cbt_log_exists(self, logpath) -> bool:
1080 return util.pathexists(logpath)
1083class SharedFileSR(FileSR):
1084 """
1085 FileSR subclass for SRs that use shared network storage
1086 """
1088 def _check_writable(self):
1089 """
1090 Checks that the filesystem being used by the SR can be written to,
1091 raising an exception if it can't.
1092 """
1093 test_name = os.path.join(self.path, str(uuid4()))
1094 try:
1095 open(test_name, 'ab').close()
1096 except OSError as e:
1097 util.SMlog("Cannot write to SR file system: %s" % e)
1098 raise xs_errors.XenError('SharedFileSystemNoWrite')
1099 finally:
1100 util.force_unlink(test_name)
1102 def _raise_hardlink_error(self):
1103 raise OSError(524, "Unknown error 524")
1105 @override
1106 def _check_hardlinks(self) -> bool:
1107 hardlink_conf = self._read_hardlink_conf()
1108 if hardlink_conf is not None: 1108 ↛ 1109line 1108 didn't jump to line 1109, because the condition on line 1108 was never true
1109 return hardlink_conf
1111 test_name = os.path.join(self.path, str(uuid4()))
1112 open(test_name, 'ab').close()
1114 link_name = '%s.new' % test_name
1115 try:
1116 # XSI-1100: Let tests simulate failure of the link operation
1117 util.fistpoint.activate_custom_fn(
1118 "FileSR_fail_hardlink",
1119 self._raise_hardlink_error)
1121 os.link(test_name, link_name)
1122 self._write_hardlink_conf(supported=True)
1123 return True
1124 except OSError:
1125 self._write_hardlink_conf(supported=False)
1127 msg = "File system for SR %s does not support hardlinks, crash " \
1128 "consistency of snapshots cannot be assured" % self.uuid
1129 util.SMlog(msg, priority=util.LOG_WARNING)
1130 # Note: session can be not set during attach/detach_from_config calls.
1131 if self.session: 1131 ↛ 1140line 1131 didn't jump to line 1140, because the condition on line 1131 was never false
1132 try:
1133 self.session.xenapi.message.create(
1134 "sr_does_not_support_hardlinks", 2, "SR", self.uuid,
1135 msg)
1136 except XenAPI.Failure:
1137 # Might already be set and checking has TOCTOU issues
1138 pass
1139 finally:
1140 util.force_unlink(link_name)
1141 util.force_unlink(test_name)
1143 return False
1145 def _get_hardlink_conf_path(self):
1146 return os.path.join(self.path, 'sm-hardlink.conf')
1148 def _read_hardlink_conf(self) -> Optional[bool]:
1149 try:
1150 with open(self._get_hardlink_conf_path(), 'r') as f:
1151 try:
1152 return bool(int(f.read()))
1153 except Exception as e:
1154 # If we can't read, assume the file is empty and test for hardlink support.
1155 return None
1156 except IOError as e:
1157 if e.errno == errno.ENOENT:
1158 # If the config file doesn't exist, assume we want to support hardlinks.
1159 return None
1160 util.SMlog('Failed to read hardlink conf: {}'.format(e))
1161 # Can be caused by a concurrent access, not a major issue.
1162 return None
1164 def _write_hardlink_conf(self, supported):
1165 try:
1166 with open(self._get_hardlink_conf_path(), 'w') as f:
1167 f.write('1' if supported else '0')
1168 except Exception as e:
1169 # Can be caused by a concurrent access, not a major issue.
1170 util.SMlog('Failed to write hardlink conf: {}'.format(e))
1172if __name__ == '__main__': 1172 ↛ 1173line 1172 didn't jump to line 1173, because the condition on line 1172 was never true
1173 SRCommand.run(FileSR, DRIVER_INFO)
1174else:
1175 SR.registerSR(FileSR)