3 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
4 # Copyright (c) 2007, 2008 Red Hat, Inc.
5 # Copyright (c) 2008 Daniel P. Berrange
6 # Copyright (c) 2008 David P. Huff
8 # This program is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by the Free
10 # Software Foundation; version 2 of the License
12 # This program is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, write to the Free Software Foundation, Inc., 59
19 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 from mic.utils import runner
25 from mic.utils.errors import MountError, CreatorError
26 from mic.utils.fs_related import *
27 from mic.utils.gpt_parser import GptParser
29 # Overhead of the MBR partitioning scheme (just one sector)
31 # Overhead of the GPT partitioning scheme
34 # Size of a sector in bytes
37 class PartitionedMount(Mount):
38 def __init__(self, mountdir, skipformat = False):
39 Mount.__init__(self, mountdir)
45 self.unmountOrder = []
46 self.parted = find_binary_path("parted")
47 self.kpartx = find_binary_path("kpartx")
48 self.mkswap = find_binary_path("mkswap")
49 self.dmsetup = find_binary_path("dmsetup")
51 self.mountcmd = find_binary_path("mount")
52 self.umountcmd = find_binary_path("umount")
53 self.skipformat = skipformat
54 self.snapshot_created = self.skipformat
55 # Size of a sector used in calculations
56 self.sector_size = SECTOR_SIZE
57 self._partitions_layed_out = False
59 def __add_disk(self, disk_name):
60 """ Add a disk 'disk_name' to the internal list of disks. Note,
61 'disk_name' is the name of the disk in the target system
64 if disk_name in self.disks:
65 # We already have this disk
68 assert not self._partitions_layed_out
70 self.disks[disk_name] = \
71 { 'disk': None, # Disk object
72 'mapped': False, # True if kpartx mapping exists
73 'numpart': 0, # Number of allocate partitions
74 'partitions': [], # Indexes to self.partitions
75 'offset': 0, # Offset of next partition (in sectors)
76 # Minimum required disk size to fit all partitions (in bytes)
78 'ptable_format': "msdos" } # Partition table format
80 def add_disk(self, disk_name, disk_obj):
81 """ Add a disk object which have to be partitioned. More than one disk
82 can be added. In case of multiple disks, disk partitions have to be
83 added for each disk separately with 'add_partition()". """
85 self.__add_disk(disk_name)
86 self.disks[disk_name]['disk'] = disk_obj
88 def __add_partition(self, part):
89 """ This is a helper function for 'add_partition()' which adds a
90 partition to the internal list of partitions. """
92 assert not self._partitions_layed_out
94 self.partitions.append(part)
95 self.__add_disk(part['disk_name'])
97 def add_partition(self, size, disk_name, mountpoint, fstype = None,
98 label=None, fsopts = None, boot = False, align = None,
100 """ Add the next partition. Partitions have to be added in the
101 first-to-last order. """
103 ks_pnum = len(self.partitions)
105 # Converting MB to sectors for parted
106 size = size * 1024 * 1024 / self.sector_size
108 # We need to handle subvolumes for btrfs
109 if fstype == "btrfs" and fsopts and fsopts.find("subvol=") != -1:
110 self.btrfscmd=find_binary_path("btrfs")
112 opts = fsopts.split(",")
114 if opt.find("subvol=") != -1:
115 subvol = opt.replace("subvol=", "").strip()
118 raise MountError("No subvolume: %s" % fsopts)
119 self.subvolumes.append({'size': size, # In sectors
120 'mountpoint': mountpoint, # Mount relative to chroot
121 'fstype': fstype, # Filesystem type
122 'fsopts': fsopts, # Filesystem mount options
123 'disk_name': disk_name, # physical disk name holding partition
124 'device': None, # kpartx device node for partition
125 'mount': None, # Mount object
126 'subvol': subvol, # Subvolume name
127 'boot': boot, # Bootable flag
128 'mounted': False # Mount flag
131 # We still need partition for "/" or non-subvolume
132 if mountpoint == "/" or not fsopts or fsopts.find("subvol=") == -1:
133 # Don't need subvolume for "/" because it will be set as default subvolume
134 if fsopts and fsopts.find("subvol=") != -1:
135 opts = fsopts.split(",")
137 if opt.strip().startswith("subvol="):
140 fsopts = ",".join(opts)
142 part = { 'ks_pnum' : ks_pnum, # Partition number in the KS file
143 'size': size, # In sectors
144 'mountpoint': mountpoint, # Mount relative to chroot
145 'fstype': fstype, # Filesystem type
146 'fsopts': fsopts, # Filesystem mount options
147 'label': label, # Partition label
148 'disk_name': disk_name, # physical disk name holding partition
149 'device': None, # kpartx device node for partition
150 'mount': None, # Mount object
151 'num': None, # Partition number
152 'boot': boot, # Bootable flag
153 'align': align, # Partition alignment
154 'part_type' : part_type, # Partition type
155 'uuid': None, # Partition UUID (no-GPT use)
156 'partuuid': None } # Partition UUID (GPT-only)
158 self.__add_partition(part)
160 def layout_partitions(self, ptable_format = "msdos"):
161 """ Layout the partitions, meaning calculate the position of every
162 partition on the disk. The 'ptable_format' parameter defines the
163 partition table format, and may be either "msdos" or "gpt". """
165 msger.debug("Assigning %s partitions to disks" % ptable_format)
167 if ptable_format not in ('msdos', 'gpt'):
168 raise MountError("Unknown partition table format '%s', supported " \
169 "formats are: 'msdos' and 'gpt'" % ptable_format)
171 if self._partitions_layed_out:
174 self._partitions_layed_out = True
176 # Go through partitions in the order they are added in .ks file
177 for n in range(len(self.partitions)):
178 p = self.partitions[n]
180 if not self.disks.has_key(p['disk_name']):
181 raise MountError("No disk %s for partition %s" \
182 % (p['disk_name'], p['mountpoint']))
184 if p['part_type'] and ptable_format != 'gpt':
185 # The --part-type can also be implemented for MBR partitions,
186 # in which case it would map to the 1-byte "partition type"
187 # filed at offset 3 of the partition entry.
188 raise MountError("setting custom partition type is only " \
189 "implemented for GPT partitions")
191 # Get the disk where the partition is located
192 d = self.disks[p['disk_name']]
194 d['ptable_format'] = ptable_format
196 if d['numpart'] == 1:
197 if ptable_format == "msdos":
198 overhead = MBR_OVERHEAD
200 overhead = GPT_OVERHEAD
202 # Skip one sector required for the partitioning scheme overhead
203 d['offset'] += overhead
204 # Steal few sectors from the first partition to offset for the
205 # partitioning overhead
206 p['size'] -= overhead
209 # If not first partition and we do have alignment set we need
210 # to align the partition.
211 # FIXME: This leaves a empty spaces to the disk. To fill the
212 # gaps we could enlarge the previous partition?
214 # Calc how much the alignment is off.
215 align_sectors = d['offset'] % (p['align'] * 1024 / self.sector_size)
216 # We need to move forward to the next alignment point
217 align_sectors = (p['align'] * 1024 / self.sector_size) - align_sectors
219 msger.debug("Realignment for %s%s with %s sectors, original"
220 " offset %s, target alignment is %sK." %
221 (p['disk_name'], d['numpart'], align_sectors,
222 d['offset'], p['align']))
224 # increase the offset so we actually start the partition on right alignment
225 d['offset'] += align_sectors
227 p['start'] = d['offset']
228 d['offset'] += p['size']
230 p['type'] = 'primary'
231 p['num'] = d['numpart']
233 if d['ptable_format'] == "msdos":
235 # Every logical partition requires an additional sector for
236 # the EBR, so steal the last sector from the end of each
237 # partition starting from the 3rd one for the EBR. This
238 # will make sure the logical partitions are aligned
243 p['type'] = 'logical'
244 p['num'] = d['numpart'] + 1
246 d['partitions'].append(n)
247 msger.debug("Assigned %s to %s%d, sectors range %d-%d size %d "
248 "sectors (%d bytes)." \
249 % (p['mountpoint'], p['disk_name'], p['num'],
250 p['start'], p['start'] + p['size'] - 1,
251 p['size'], p['size'] * self.sector_size))
253 # Once all the partitions have been laid out, we can calculate the
254 # minimum disk sizes.
255 for disk_name, d in self.disks.items():
256 d['min_size'] = d['offset']
257 if d['ptable_format'] == 'gpt':
258 # Account for the backup partition table at the end of the disk
259 d['min_size'] += GPT_OVERHEAD
261 d['min_size'] *= self.sector_size
263 def __run_parted(self, args):
264 """ Run parted with arguments specified in the 'args' list. """
266 args.insert(0, self.parted)
269 rc, out = runner.runtool(args, catch = 3)
271 msger.debug('"parted": exitcode:%d, output:%s' % (rc, out))
272 # We don't throw exception when return code is not 0, because
273 # parted always fails to reload part table with loop devices. This
274 # prevents us from distinguishing real errors based on return
278 def __create_partition(self, device, parttype, fstype, start, size):
279 """ Create a partition on an image described by the 'device' object. """
281 # Start is included to the size so we need to subtract one from the end.
282 end = start + size - 1
283 msger.debug("Added '%s' partition, sectors %d-%d, size %d sectors" %
284 (parttype, start, end, size))
286 args = ["-s", device, "unit", "s", "mkpart", parttype]
288 args.extend([fstype])
289 args.extend(["%d" % start, "%d" % end])
291 return self.__run_parted(args)
293 def __format_disks(self):
294 self.layout_partitions()
297 msger.debug("Skipping disk format, because skipformat flag is set.")
300 for dev in self.disks.keys():
302 msger.debug("Initializing partition table for %s" % \
304 self.__run_parted(["-s", d['disk'].device, "mklabel",
307 msger.debug("Creating partitions")
309 for p in self.partitions:
310 d = self.disks[p['disk_name']]
311 if d['ptable_format'] == "msdos" and p['num'] == 5:
312 # The last sector of the 3rd partition was reserved for the EBR
313 # of the first _logical_ partition. This is why the extended
314 # partition should start one sector before the first logical
316 self.__create_partition(d['disk'].device, "extended",
317 None, p['start'] - 1,
318 d['offset'] - p['start'])
320 if p['fstype'] == "swap":
321 parted_fs_type = "linux-swap"
322 elif p['fstype'] == "vfat":
323 parted_fs_type = "fat32"
324 elif p['fstype'] == "msdos":
325 parted_fs_type = "fat16"
327 # Type for ext2/ext3/ext4/btrfs
328 parted_fs_type = "ext2"
330 # Boot ROM of OMAP boards require vfat boot partition to have an
331 # even number of sectors.
332 if p['mountpoint'] == "/boot" and p['fstype'] in ["vfat", "msdos"] \
334 msger.debug("Substracting one sector from '%s' partition to " \
335 "get even number of sectors for the partition" % \
339 self.__create_partition(d['disk'].device, p['type'],
340 parted_fs_type, p['start'], p['size'])
343 if d['ptable_format'] == 'gpt':
344 flag_name = "legacy_boot"
347 msger.debug("Set '%s' flag for partition '%s' on disk '%s'" % \
348 (flag_name, p['num'], d['disk'].device))
349 cmd = ["-s", d['disk'].device,
350 "set", "%d" % p['num'],
352 exitcode, output = self.__run_parted(cmd)
355 "partition '%s' is marked with --active, "
356 "but flag '%s' can't be set: "
357 "exitcode: %s, output: %s"
358 % (p['mountpoint'], flag_name, exitcode, output))
360 # If the partition table format is "gpt", find out PARTUUIDs for all
361 # the partitions. And if users specified custom parition type UUIDs,
363 for disk_name, disk in self.disks.items():
364 if disk['ptable_format'] != 'gpt':
368 gpt_parser = GptParser(d['disk'].device, SECTOR_SIZE)
369 # Iterate over all GPT partitions on this disk
370 for entry in gpt_parser.get_partitions():
372 # Find the matching partition in the 'self.partitions' list
373 for n in d['partitions']:
374 p = self.partitions[n]
376 # Found, fetch PARTUUID (partition's unique ID)
377 p['partuuid'] = entry['part_uuid']
378 msger.debug("PARTUUID for partition %d on disk '%s' " \
379 "(mount point '%s') is '%s'" % (pnum, \
380 disk_name, p['mountpoint'], p['partuuid']))
382 entry['type_uuid'] = p['part_type']
383 msger.debug("Change type of partition %d on disk " \
384 "'%s' (mount point '%s') to '%s'" % \
385 (pnum, disk_name, p['mountpoint'],
387 gpt_parser.change_partition(entry)
391 def __map_partitions(self):
392 """Load it if dm_snapshot isn't loaded. """
393 load_module("dm_snapshot")
395 for dev in self.disks.keys():
400 msger.debug("Running kpartx on %s" % d['disk'].device )
401 rc, kpartxOutput = runner.runtool([self.kpartx, "-l", "-v", d['disk'].device])
402 kpartxOutput = kpartxOutput.splitlines()
405 raise MountError("Failed to query partition mapping for '%s'" %
408 # Strip trailing blank and mask verbose output
410 while i < len(kpartxOutput) and kpartxOutput[i][0:4] != "loop":
412 kpartxOutput = kpartxOutput[i:]
414 # Make sure kpartx reported the right count of partitions
415 if len(kpartxOutput) != d['numpart']:
416 # If this disk has more than 3 partitions, then in case of MBR
417 # paritions there is an extended parition. Different versions
418 # of kpartx behave differently WRT the extended partition -
419 # some map it, some ignore it. This is why we do the below hack
420 # - if kpartx reported one more partition and the partition
421 # table type is "msdos" and the amount of partitions is more
422 # than 3, we just assume kpartx mapped the extended parition
424 if len(kpartxOutput) == d['numpart'] + 1 \
425 and d['ptable_format'] == 'msdos' and len(kpartxOutput) > 3:
428 raise MountError("Unexpected number of partitions from " \
429 "kpartx: %d != %d" % \
430 (len(kpartxOutput), d['numpart']))
432 for i in range(len(kpartxOutput)):
433 line = kpartxOutput[i]
434 newdev = line.split()[0]
435 mapperdev = "/dev/mapper/" + newdev
436 loopdev = d['disk'].device + newdev[-1]
438 msger.debug("Dev %s: %s -> %s" % (newdev, loopdev, mapperdev))
439 pnum = d['partitions'][i]
440 self.partitions[pnum]['device'] = loopdev
442 # grub's install wants partitions to be named
443 # to match their parent device + partition num
444 # kpartx doesn't work like this, so we add compat
445 # symlinks to point to /dev/mapper
446 if os.path.lexists(loopdev):
448 os.symlink(mapperdev, loopdev)
450 msger.debug("Adding partx mapping for %s" % d['disk'].device)
451 rc = runner.show([self.kpartx, "-v", "-a", d['disk'].device])
454 # Make sure that the device maps are also removed on error case.
455 # The d['mapped'] isn't set to True if the kpartx fails so
456 # failed mapping will not be cleaned on cleanup either.
457 runner.quiet([self.kpartx, "-d", d['disk'].device])
458 raise MountError("Failed to map partitions for '%s'" %
461 if not os.path.exists(mapperdev):
462 runner.quiet([self.dmsetup, "mknodes"])
466 def __unmap_partitions(self):
467 for dev in self.disks.keys():
472 msger.debug("Removing compat symlinks")
473 for pnum in d['partitions']:
474 if self.partitions[pnum]['device'] != None:
475 os.unlink(self.partitions[pnum]['device'])
476 self.partitions[pnum]['device'] = None
478 msger.debug("Unmapping %s" % d['disk'].device)
479 rc = runner.quiet([self.kpartx, "-d", d['disk'].device])
481 raise MountError("Failed to unmap partitions for '%s'" %
486 def __calculate_mountorder(self):
487 msger.debug("Calculating mount order")
488 for p in self.partitions:
490 self.mountOrder.append(p['mountpoint'])
491 self.unmountOrder.append(p['mountpoint'])
493 self.mountOrder.sort()
494 self.unmountOrder.sort()
495 self.unmountOrder.reverse()
500 self.__unmap_partitions()
501 for dev in self.disks.keys():
509 self.__unmount_subvolumes()
510 for mp in self.unmountOrder:
514 for p1 in self.partitions:
515 if p1['mountpoint'] == mp:
519 if p['mount'] != None:
521 # Create subvolume snapshot here
522 if p['fstype'] == "btrfs" and p['mountpoint'] == "/" and not self.snapshot_created:
523 self.__create_subvolume_snapshots(p, p["mount"])
530 def __get_subvolume_id(self, rootpath, subvol):
531 if not self.btrfscmd:
532 self.btrfscmd=find_binary_path("btrfs")
533 argv = [ self.btrfscmd, "subvolume", "list", rootpath ]
535 rc, out = runner.runtool(argv)
539 raise MountError("Failed to get subvolume id from %s', return code: %d." % (rootpath, rc))
542 for line in out.splitlines():
543 if line.endswith(" path %s" % subvol):
544 subvolid = line.split()[1]
545 if not subvolid.isdigit():
546 raise MountError("Invalid subvolume id: %s" % subvolid)
547 subvolid = int(subvolid)
551 def __create_subvolume_metadata(self, p, pdisk):
552 if len(self.subvolumes) == 0:
555 argv = [ self.btrfscmd, "subvolume", "list", pdisk.mountdir ]
556 rc, out = runner.runtool(argv)
560 raise MountError("Failed to get subvolume id from %s', return code: %d." % (pdisk.mountdir, rc))
562 subvolid_items = out.splitlines()
563 subvolume_metadata = ""
564 for subvol in self.subvolumes:
565 for line in subvolid_items:
566 if line.endswith(" path %s" % subvol["subvol"]):
567 subvolid = line.split()[1]
568 if not subvolid.isdigit():
569 raise MountError("Invalid subvolume id: %s" % subvolid)
571 subvolid = int(subvolid)
572 opts = subvol["fsopts"].split(",")
574 if opt.strip().startswith("subvol="):
577 fsopts = ",".join(opts)
578 subvolume_metadata += "%d\t%s\t%s\t%s\n" % (subvolid, subvol["subvol"], subvol['mountpoint'], fsopts)
580 if subvolume_metadata:
581 fd = open("%s/.subvolume_metadata" % pdisk.mountdir, "w")
582 fd.write(subvolume_metadata)
585 def __get_subvolume_metadata(self, p, pdisk):
586 subvolume_metadata_file = "%s/.subvolume_metadata" % pdisk.mountdir
587 if not os.path.exists(subvolume_metadata_file):
590 fd = open(subvolume_metadata_file, "r")
594 for line in content.splitlines():
595 items = line.split("\t")
596 if items and len(items) == 4:
597 self.subvolumes.append({'size': 0, # In sectors
598 'mountpoint': items[2], # Mount relative to chroot
599 'fstype': "btrfs", # Filesystem type
600 'fsopts': items[3] + ",subvol=%s" % items[1], # Filesystem mount options
601 'disk_name': p['disk_name'], # physical disk name holding partition
602 'device': None, # kpartx device node for partition
603 'mount': None, # Mount object
604 'subvol': items[1], # Subvolume name
605 'boot': False, # Bootable flag
606 'mounted': False # Mount flag
609 def __create_subvolumes(self, p, pdisk):
610 """ Create all the subvolumes. """
612 for subvol in self.subvolumes:
613 argv = [ self.btrfscmd, "subvolume", "create", pdisk.mountdir + "/" + subvol["subvol"]]
615 rc = runner.show(argv)
617 raise MountError("Failed to create subvolume '%s', return code: %d." % (subvol["subvol"], rc))
619 # Set default subvolume, subvolume for "/" is default
621 for subvolume in self.subvolumes:
622 if subvolume["mountpoint"] == "/" and p['disk_name'] == subvolume['disk_name']:
627 # Get default subvolume id
628 subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"])
629 # Set default subvolume
631 rc = runner.show([ self.btrfscmd, "subvolume", "set-default", "%d" % subvolid, pdisk.mountdir])
633 raise MountError("Failed to set default subvolume id: %d', return code: %d." % (subvolid, rc))
635 self.__create_subvolume_metadata(p, pdisk)
637 def __mount_subvolumes(self, p, pdisk):
640 self.__get_subvolume_metadata(p, pdisk)
641 # Set default mount options
642 if len(self.subvolumes) != 0:
643 for subvol in self.subvolumes:
644 if subvol["mountpoint"] == p["mountpoint"] == "/":
645 opts = subvol["fsopts"].split(",")
647 if opt.strip().startswith("subvol="):
650 pdisk.fsopts = ",".join(opts)
653 if len(self.subvolumes) == 0:
654 # Return directly if no subvolumes
657 # Remount to make default subvolume mounted
658 rc = runner.show([self.umountcmd, pdisk.mountdir])
660 raise MountError("Failed to umount %s" % pdisk.mountdir)
662 rc = runner.show([self.mountcmd, "-o", pdisk.fsopts, pdisk.disk.device, pdisk.mountdir])
664 raise MountError("Failed to umount %s" % pdisk.mountdir)
666 for subvol in self.subvolumes:
667 if subvol["mountpoint"] == "/":
669 subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"])
671 msger.debug("WARNING: invalid subvolume %s" % subvol["subvol"])
673 # Replace subvolume name with subvolume ID
674 opts = subvol["fsopts"].split(",")
676 if opt.strip().startswith("subvol="):
680 opts.extend(["subvolrootid=0", "subvol=%s" % subvol["subvol"]])
681 fsopts = ",".join(opts)
682 subvol['fsopts'] = fsopts
683 mountpoint = self.mountdir + subvol['mountpoint']
685 rc = runner.show([self.mountcmd, "-o", fsopts, pdisk.disk.device, mountpoint])
687 raise MountError("Failed to mount subvolume %s to %s" % (subvol["subvol"], mountpoint))
688 subvol["mounted"] = True
690 def __unmount_subvolumes(self):
691 """ It may be called multiple times, so we need to chekc if it is still mounted. """
692 for subvol in self.subvolumes:
693 if subvol["mountpoint"] == "/":
695 if not subvol["mounted"]:
697 mountpoint = self.mountdir + subvol['mountpoint']
698 rc = runner.show([self.umountcmd, mountpoint])
700 raise MountError("Failed to unmount subvolume %s from %s" % (subvol["subvol"], mountpoint))
701 subvol["mounted"] = False
703 def __create_subvolume_snapshots(self, p, pdisk):
706 if self.snapshot_created:
709 # Remount with subvolid=0
710 rc = runner.show([self.umountcmd, pdisk.mountdir])
712 raise MountError("Failed to umount %s" % pdisk.mountdir)
714 mountopts = pdisk.fsopts + ",subvolid=0"
716 mountopts = "subvolid=0"
717 rc = runner.show([self.mountcmd, "-o", mountopts, pdisk.disk.device, pdisk.mountdir])
719 raise MountError("Failed to umount %s" % pdisk.mountdir)
721 # Create all the subvolume snapshots
722 snapshotts = time.strftime("%Y%m%d-%H%M")
723 for subvol in self.subvolumes:
724 subvolpath = pdisk.mountdir + "/" + subvol["subvol"]
725 snapshotpath = subvolpath + "_%s-1" % snapshotts
726 rc = runner.show([ self.btrfscmd, "subvolume", "snapshot", subvolpath, snapshotpath ])
728 raise MountError("Failed to create subvolume snapshot '%s' for '%s', return code: %d." % (snapshotpath, subvolpath, rc))
730 self.snapshot_created = True
733 for dev in self.disks.keys():
737 self.__format_disks()
738 self.__map_partitions()
739 self.__calculate_mountorder()
741 for mp in self.mountOrder:
743 for p1 in self.partitions:
744 if p1['mountpoint'] == mp:
749 if p['mountpoint'] == "/":
750 p['label'] = 'platform'
752 p['label'] = mp.split('/')[-1]
756 p['uuid'] = str(uuid.uuid1())
757 runner.show([self.mkswap,
764 if p['mountpoint'] == "/":
766 if p['fstype'] == "vfat" or p['fstype'] == "msdos":
767 myDiskMount = VfatDiskMount
768 elif p['fstype'] in ("ext2", "ext3", "ext4"):
769 myDiskMount = ExtDiskMount
770 elif p['fstype'] == "btrfs":
771 myDiskMount = BtrfsDiskMount
773 raise MountError("Fail to support file system " + p['fstype'])
775 if p['fstype'] == "btrfs" and not p['fsopts']:
776 p['fsopts'] = "subvolid=0"
778 pdisk = myDiskMount(RawDisk(p['size'] * self.sector_size, p['device']),
779 self.mountdir + p['mountpoint'],
785 fsopts = p['fsopts'])
786 pdisk.mount(pdisk.fsopts)
787 if p['fstype'] == "btrfs" and p['mountpoint'] == "/":
788 if not self.skipformat:
789 self.__create_subvolumes(p, pdisk)
790 self.__mount_subvolumes(p, pdisk)
792 p['uuid'] = pdisk.uuid
794 def resparse(self, size = None):
795 # Can't re-sparse a disk image - too hard