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
38 real = os.readlink(ref)
39 if not real.startswith('/'):
40 return os.path.realpath(os.path.join(ref, real))
44 class PartitionedMount(Mount):
45 def __init__(self, mountdir, skipformat = False):
46 Mount.__init__(self, mountdir)
52 self.unmount_order = []
53 self.parted = find_binary_path("parted")
54 self.kpartx = find_binary_path("kpartx")
55 self.mkswap = find_binary_path("mkswap")
56 self.dmsetup = find_binary_path("dmsetup")
58 self.mountcmd = find_binary_path("mount")
59 self.umountcmd = find_binary_path("umount")
60 self.skipformat = skipformat
61 self.snapshot_created = self.skipformat
62 # Size of a sector used in calculations
63 self.sector_size = SECTOR_SIZE
64 self._partitions_layed_out = False
66 def __add_disk(self, disk_name):
67 """ Add a disk 'disk_name' to the internal list of disks. Note,
68 'disk_name' is the name of the disk in the target system
71 if disk_name in self.disks:
72 # We already have this disk
75 assert not self._partitions_layed_out
77 self.disks[disk_name] = \
78 { 'disk': None, # Disk object
79 'mapped': False, # True if kpartx mapping exists
80 'numpart': 0, # Number of allocate partitions
81 'partitions': [], # Indexes to self.partitions
82 'offset': 0, # Offset of next partition (in sectors)
83 # Minimum required disk size to fit all partitions (in bytes)
85 'ptable_format': "msdos" } # Partition table format
87 def add_disk(self, disk_name, disk_obj):
88 """ Add a disk object which have to be partitioned. More than one disk
89 can be added. In case of multiple disks, disk partitions have to be
90 added for each disk separately with 'add_partition()". """
92 self.__add_disk(disk_name)
93 self.disks[disk_name]['disk'] = disk_obj
95 def __add_partition(self, part):
96 """ This is a helper function for 'add_partition()' which adds a
97 partition to the internal list of partitions. """
99 assert not self._partitions_layed_out
101 self.partitions.append(part)
102 self.__add_disk(part['disk_name'])
104 def add_partition(self, size, disk_name, mountpoint, fstype = None,
105 label=None, fsopts = None, boot = False, align = None,
107 """ Add the next partition. Partitions have to be added in the
108 first-to-last order. """
110 ks_pnum = len(self.partitions)
112 # Converting MB to sectors for parted
113 size = size * 1024 * 1024 / self.sector_size
115 # We need to handle subvolumes for btrfs
116 if fstype == "btrfs" and fsopts and fsopts.find("subvol=") != -1:
117 self.btrfscmd = find_binary_path("btrfs")
119 opts = fsopts.split(",")
121 if opt.find("subvol=") != -1:
122 subvol = opt.replace("subvol=", "").strip()
125 raise MountError("No subvolume: %s" % fsopts)
126 self.subvolumes.append({'size': size, # In sectors
127 'mountpoint': mountpoint, # Mount relative to chroot
128 'fstype': fstype, # Filesystem type
129 'fsopts': fsopts, # Filesystem mount options
130 'disk_name': disk_name, # physical disk name holding partition
131 'device': None, # kpartx device node for partition
132 'mapper_device': None, # mapper device node
133 'mpath_device': None, # multipath device of device mapper
134 'mount': None, # Mount object
135 'subvol': subvol, # Subvolume name
136 'boot': boot, # Bootable flag
137 'mounted': False # Mount flag
140 # We still need partition for "/" or non-subvolume
141 if mountpoint == "/" or not fsopts or fsopts.find("subvol=") == -1:
142 # Don't need subvolume for "/" because it will be set as default subvolume
143 if fsopts and fsopts.find("subvol=") != -1:
144 opts = fsopts.split(",")
146 if opt.strip().startswith("subvol="):
149 fsopts = ",".join(opts)
151 part = { 'ks_pnum' : ks_pnum, # Partition number in the KS file
152 'size': size, # In sectors
153 'mountpoint': mountpoint, # Mount relative to chroot
154 'fstype': fstype, # Filesystem type
155 'fsopts': fsopts, # Filesystem mount options
156 'label': label, # Partition label
157 'disk_name': disk_name, # physical disk name holding partition
158 'device': None, # kpartx device node for partition
159 'mapper_device': None, # mapper device node
160 'mpath_device': None, # multipath device of device mapper
161 'mount': None, # Mount object
162 'num': None, # Partition number
163 'boot': boot, # Bootable flag
164 'align': align, # Partition alignment
165 'part_type' : part_type, # Partition type
166 'uuid': None, # Partition UUID (no-GPT use)
167 'partuuid': None } # Partition UUID (GPT-only)
169 self.__add_partition(part)
171 def layout_partitions(self, ptable_format = "msdos"):
172 """ Layout the partitions, meaning calculate the position of every
173 partition on the disk. The 'ptable_format' parameter defines the
174 partition table format, and may be either "msdos" or "gpt". """
176 msger.debug("Assigning %s partitions to disks" % ptable_format)
178 if ptable_format not in ('msdos', 'gpt'):
179 raise MountError("Unknown partition table format '%s', supported " \
180 "formats are: 'msdos' and 'gpt'" % ptable_format)
182 if self._partitions_layed_out:
185 self._partitions_layed_out = True
187 # Go through partitions in the order they are added in .ks file
188 for n in range(len(self.partitions)):
189 p = self.partitions[n]
191 if p['disk_name'] not in self.disks:
192 raise MountError("No disk %s for partition %s" \
193 % (p['disk_name'], p['mountpoint']))
195 if p['part_type'] and ptable_format != 'gpt':
196 # The --part-type can also be implemented for MBR partitions,
197 # in which case it would map to the 1-byte "partition type"
198 # filed at offset 3 of the partition entry.
199 raise MountError("setting custom partition type is only " \
200 "implemented for GPT partitions")
202 # Get the disk where the partition is located
203 d = self.disks[p['disk_name']]
205 d['ptable_format'] = ptable_format
207 if d['numpart'] == 1:
208 if ptable_format == "msdos":
209 overhead = MBR_OVERHEAD
211 overhead = GPT_OVERHEAD
213 # Skip one sector required for the partitioning scheme overhead
214 d['offset'] += overhead
215 # Steal few sectors from the first partition to offset for the
216 # partitioning overhead
217 p['size'] -= overhead
220 # If not first partition and we do have alignment set we need
221 # to align the partition.
222 # FIXME: This leaves a empty spaces to the disk. To fill the
223 # gaps we could enlarge the previous partition?
225 # Calc how much the alignment is off.
226 align_sectors = d['offset'] % (p['align'] * 1024 / self.sector_size)
227 # We need to move forward to the next alignment point
228 align_sectors = (p['align'] * 1024 / self.sector_size) - align_sectors
230 msger.debug("Realignment for %s%s with %s sectors, original"
231 " offset %s, target alignment is %sK." %
232 (p['disk_name'], d['numpart'], align_sectors,
233 d['offset'], p['align']))
235 # increase the offset so we actually start the partition on right alignment
236 d['offset'] += align_sectors
238 p['start'] = d['offset']
239 d['offset'] += p['size']
241 p['type'] = 'primary'
242 p['num'] = d['numpart']
244 if d['ptable_format'] == "msdos":
246 # Every logical partition requires an additional sector for
247 # the EBR, so steal the last sector from the end of each
248 # partition starting from the 3rd one for the EBR. This
249 # will make sure the logical partitions are aligned
254 p['type'] = 'logical'
255 p['num'] = d['numpart'] + 1
257 d['partitions'].append(n)
258 msger.debug("Assigned %s to %s%d, sectors range %d-%d size %d "
259 "sectors (%d bytes)." \
260 % (p['mountpoint'], p['disk_name'], p['num'],
261 p['start'], p['start'] + p['size'] - 1,
262 p['size'], p['size'] * self.sector_size))
264 # Once all the partitions have been laid out, we can calculate the
265 # minimum disk sizes.
266 for disk_name, d in list(self.disks.items()):
267 d['min_size'] = d['offset']
268 if d['ptable_format'] == 'gpt':
269 # Account for the backup partition table at the end of the disk
270 d['min_size'] += GPT_OVERHEAD
272 d['min_size'] *= self.sector_size
274 def __run_parted(self, args):
275 """ Run parted with arguments specified in the 'args' list. """
277 args.insert(0, self.parted)
280 rc, out = runner.runtool(args, catch = 3)
282 msger.debug("'parted': exitcode: %d, output: %s" % (rc, out))
283 # We don't throw exception when return code is not 0, because
284 # parted always fails to reload part table with loop devices. This
285 # prevents us from distinguishing real errors based on return
289 def __create_partition(self, device, parttype, fstype, start, size):
290 """ Create a partition on an image described by the 'device' object. """
292 # Start is included to the size so we need to subtract one from the end.
293 end = start + size - 1
294 msger.debug("Added '%s' partition, sectors %d-%d, size %d sectors" %
295 (parttype, start, end, size))
297 args = ["-s", device, "unit", "s", "mkpart", parttype]
299 args.extend([fstype])
300 args.extend(["%d" % start, "%d" % end])
302 return self.__run_parted(args)
304 def __format_disks(self):
305 self.layout_partitions()
308 msger.debug("Skipping disk format, because skipformat flag is set.")
311 for dev in list(self.disks.keys()):
313 msger.debug("Initializing partition table for %s" % \
315 self.__run_parted(["-s", d['disk'].device, "mklabel",
318 msger.debug("Creating partitions")
320 for p in self.partitions:
321 d = self.disks[p['disk_name']]
322 if d['ptable_format'] == "msdos" and p['num'] == 5:
323 # The last sector of the 3rd partition was reserved for the EBR
324 # of the first _logical_ partition. This is why the extended
325 # partition should start one sector before the first logical
327 self.__create_partition(d['disk'].device, "extended",
328 None, p['start'] - 1,
329 d['offset'] - p['start'])
331 if p['fstype'] == "swap":
332 parted_fs_type = "linux-swap"
333 elif p['fstype'] == "vfat":
334 parted_fs_type = "fat32"
335 elif p['fstype'] == "msdos":
336 parted_fs_type = "fat16"
338 # Type for ext2/ext3/ext4/btrfs
339 parted_fs_type = "ext2"
341 # Boot ROM of OMAP boards require vfat boot partition to have an
342 # even number of sectors.
343 if p['mountpoint'] == "/boot" and p['fstype'] in ["vfat", "msdos"] \
345 msger.debug("Substracting one sector from '%s' partition to " \
346 "get even number of sectors for the partition" % \
350 self.__create_partition(d['disk'].device, p['type'],
351 parted_fs_type, p['start'], p['size'])
354 if d['ptable_format'] == 'gpt':
355 flag_name = "legacy_boot"
358 msger.debug("Set '%s' flag for partition '%s' on disk '%s'" % \
359 (flag_name, p['num'], d['disk'].device))
360 cmd = ["-s", d['disk'].device,
361 "set", "%d" % p['num'],
363 exitcode, output = self.__run_parted(cmd)
366 "partition '%s' is marked with --active, "
367 "but flag '%s' can't be set: "
368 "exitcode: %s, output: %s"
369 % (p['mountpoint'], flag_name, exitcode, output))
371 # If the partition table format is "gpt", find out PARTUUIDs for all
372 # the partitions. And if users specified custom parition type UUIDs,
374 for disk_name, disk in list(self.disks.items()):
375 if disk['ptable_format'] != 'gpt':
379 gpt_parser = GptParser(disk['disk'].device, SECTOR_SIZE)
380 # Iterate over all GPT partitions on this disk
381 for entry in gpt_parser.get_partitions():
383 # Find the matching partition in the 'self.partitions' list
384 for n in disk['partitions']:
385 p = self.partitions[n]
387 # Found, fetch PARTUUID (partition's unique ID)
388 p['partuuid'] = entry['part_uuid']
389 msger.debug("PARTUUID for partition %d on disk '%s' " \
390 "(mount point '%s') is '%s'" % (pnum, \
391 disk_name, p['mountpoint'], p['partuuid']))
393 entry['type_uuid'] = p['part_type']
394 msger.debug("Change type of partition %d on disk " \
395 "'%s' (mount point '%s') to '%s'" % \
396 (pnum, disk_name, p['mountpoint'],
398 gpt_parser.change_partition(entry)
402 def __map_partitions(self):
403 """Load it if dm_snapshot isn't loaded. """
404 load_module("dm_snapshot")
406 for dev in list(self.disks.keys()):
411 msger.debug("Running kpartx on %s" % d['disk'].device )
412 rc, kpartx_output = runner.runtool([self.kpartx, "-l", "-v", d['disk'].device])
413 kpartx_output = kpartx_output.splitlines()
416 raise MountError("Failed to query partition mapping for '%s'" %
419 # Strip trailing blank and mask verbose output
421 while i < len(kpartx_output) and kpartx_output[i][0:4] != "loop":
423 kpartx_output = kpartx_output[i:]
425 # Make sure kpartx reported the right count of partitions
426 if len(kpartx_output) != d['numpart']:
427 # If this disk has more than 3 partitions, then in case of MBR
428 # paritions there is an extended parition. Different versions
429 # of kpartx behave differently WRT the extended partition -
430 # some map it, some ignore it. This is why we do the below hack
431 # - if kpartx reported one more partition and the partition
432 # table type is "msdos" and the amount of partitions is more
433 # than 3, we just assume kpartx mapped the extended parition
435 if len(kpartx_output) == d['numpart'] + 1 \
436 and d['ptable_format'] == 'msdos' and len(kpartx_output) > 3:
439 raise MountError("Unexpected number of partitions from " \
440 "kpartx: %d != %d" % \
441 (len(kpartx_output), d['numpart']))
443 for i in range(len(kpartx_output)):
444 line = kpartx_output[i]
445 newdev = line.split()[0]
446 mapperdev = "/dev/mapper/" + newdev
447 loopdev = d['disk'].device + newdev[-1]
449 msger.debug("Dev %s: %s -> %s" % (newdev, loopdev, mapperdev))
450 pnum = d['partitions'][i]
451 self.partitions[pnum]['device'] = loopdev
452 self.partitions[pnum]['mapper_device'] = mapperdev
454 # grub's install wants partitions to be named
455 # to match their parent device + partition num
456 # kpartx doesn't work like this, so we add compat
457 # symlinks to point to /dev/mapper
458 if os.path.lexists(loopdev):
460 os.symlink(mapperdev, loopdev)
462 msger.debug("Adding partx mapping for %s" % d['disk'].device)
463 rc = runner.show([self.kpartx, "-v", "-sa", d['disk'].device])
466 # Make sure that the device maps are also removed on error case.
467 # The d['mapped'] isn't set to True if the kpartx fails so
468 # failed mapping will not be cleaned on cleanup either.
469 runner.quiet([self.kpartx, "-sd", d['disk'].device])
470 raise MountError("Failed to map partitions for '%s'" %
473 for p in self.partitions:
474 if p['mapper_device'] and os.path.islink(p['mapper_device']):
475 p['mpath_device'] = resolve_ref(p['mapper_device'])
477 p['mpath_device'] = ''
479 # FIXME: need a better way to fix the latency
483 if not os.path.exists(mapperdev):
484 # load mapper device if not updated
485 runner.quiet([self.dmsetup, "mknodes"])
486 # still not updated, roll back
487 if not os.path.exists(mapperdev):
488 runner.quiet([self.kpartx, "-sd", d['disk'].device])
489 raise MountError("Failed to load mapper devices for '%s'" %
494 def __unmap_partitions(self):
495 for dev in list(self.disks.keys()):
500 msger.debug("Removing compat symlinks")
501 for pnum in d['partitions']:
502 if self.partitions[pnum]['device'] != None:
503 os.unlink(self.partitions[pnum]['device'])
504 self.partitions[pnum]['device'] = None
506 msger.debug("Unmapping %s" % d['disk'].device)
507 rc = runner.quiet([self.kpartx, "-sd", d['disk'].device])
509 raise MountError("Failed to unmap partitions for '%s'" %
514 def __calculate_mountorder(self):
515 msger.debug("Calculating mount order")
516 for p in self.partitions:
518 self.mount_order.append(p['mountpoint'])
519 self.unmount_order.append(p['mountpoint'])
521 self.mount_order.sort()
522 self.unmount_order.sort()
523 self.unmount_order.reverse()
528 self.__unmap_partitions()
529 for dev in list(self.disks.keys()):
537 self.__unmount_subvolumes()
538 for mp in self.unmount_order:
542 for p1 in self.partitions:
543 if p1['mountpoint'] == mp:
547 if p['mount'] != None:
549 # Create subvolume snapshot here
550 if p['fstype'] == "btrfs" and p['mountpoint'] == "/" and \
551 not self.snapshot_created:
552 self.__create_subvolume_snapshots(p, p["mount"])
559 def __get_subvolume_id(self, rootpath, subvol):
560 if not self.btrfscmd:
561 self.btrfscmd = find_binary_path("btrfs")
562 argv = [ self.btrfscmd, "subvolume", "list", rootpath ]
564 rc, out = runner.runtool(argv)
568 raise MountError("Failed to get subvolume id from %s',"
569 "return code: %d." % (rootpath, rc))
572 for line in out.splitlines():
573 if line.endswith(" path %s" % subvol):
574 subvolid = line.split()[1]
575 if not subvolid.isdigit():
576 raise MountError("Invalid subvolume id: %s" % subvolid)
577 subvolid = int(subvolid)
581 def __create_subvolume_metadata(self, p, pdisk):
582 if len(self.subvolumes) == 0:
585 argv = [ self.btrfscmd, "subvolume", "list", pdisk.mountdir ]
586 rc, out = runner.runtool(argv)
590 raise MountError("Failed to get subvolume id from %s', return code: %d." % (pdisk.mountdir, rc))
592 subvolid_items = out.splitlines()
593 subvolume_metadata = ""
594 for subvol in self.subvolumes:
595 for line in subvolid_items:
596 if line.endswith(" path %s" % subvol["subvol"]):
597 subvolid = line.split()[1]
598 if not subvolid.isdigit():
599 raise MountError("Invalid subvolume id: %s" % subvolid)
601 subvolid = int(subvolid)
602 opts = subvol["fsopts"].split(",")
604 if opt.strip().startswith("subvol="):
607 fsopts = ",".join(opts)
608 subvolume_metadata += "%d\t%s\t%s\t%s\n" % (subvolid, subvol["subvol"],
609 subvol['mountpoint'], fsopts)
611 if subvolume_metadata:
612 fd = open("%s/.subvolume_metadata" % pdisk.mountdir, "w")
613 fd.write(subvolume_metadata)
616 def __get_subvolume_metadata(self, p, pdisk):
617 subvolume_metadata_file = "%s/.subvolume_metadata" % pdisk.mountdir
618 if not os.path.exists(subvolume_metadata_file):
621 fd = open(subvolume_metadata_file, "r")
625 for line in content.splitlines():
626 items = line.split("\t")
627 if items and len(items) == 4:
628 self.subvolumes.append({'size': 0, # In sectors
629 'mountpoint': items[2], # Mount relative to chroot
630 'fstype': "btrfs", # Filesystem type
631 'fsopts': items[3] + ",subvol=%s" % items[1], # Filesystem mount options
632 'disk_name': p['disk_name'], # physical disk name holding partition
633 'device': None, # kpartx device node for partition
634 'mount': None, # Mount object
635 'subvol': items[1], # Subvolume name
636 'boot': False, # Bootable flag
637 'mounted': False # Mount flag
640 def __create_subvolumes(self, p, pdisk):
641 """ Create all the subvolumes. """
643 for subvol in self.subvolumes:
644 argv = [ self.btrfscmd, "subvolume", "create", pdisk.mountdir + "/" + subvol["subvol"]]
646 rc = runner.show(argv)
648 raise MountError("Failed to create subvolume '%s', return code: %d." % (subvol["subvol"], rc))
650 # Set default subvolume, subvolume for "/" is default
652 for subvolume in self.subvolumes:
653 if subvolume["mountpoint"] == "/" and p['disk_name'] == subvolume['disk_name']:
658 # Get default subvolume id
659 subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"])
660 # Set default subvolume
662 rc = runner.show([ self.btrfscmd, "subvolume", "set-default", "%d" %
663 subvolid, pdisk.mountdir])
665 raise MountError("Failed to set default subvolume id: %d', return code: %d."
668 self.__create_subvolume_metadata(p, pdisk)
670 def __mount_subvolumes(self, p, pdisk):
673 self.__get_subvolume_metadata(p, pdisk)
674 # Set default mount options
675 if len(self.subvolumes) != 0:
676 for subvol in self.subvolumes:
677 if subvol["mountpoint"] == p["mountpoint"] == "/":
678 opts = subvol["fsopts"].split(",")
680 if opt.strip().startswith("subvol="):
683 pdisk.fsopts = ",".join(opts)
686 if len(self.subvolumes) == 0:
687 # Return directly if no subvolumes
690 # Remount to make default subvolume mounted
691 rc = runner.show([self.umountcmd, pdisk.mountdir])
693 raise MountError("Failed to umount %s" % pdisk.mountdir)
695 rc = runner.show([self.mountcmd, "-o", pdisk.fsopts, pdisk.disk.device, pdisk.mountdir])
697 raise MountError("Failed to umount %s" % pdisk.mountdir)
699 for subvol in self.subvolumes:
700 if subvol["mountpoint"] == "/":
702 subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"])
704 msger.debug("WARNING: invalid subvolume %s" % subvol["subvol"])
706 # Replace subvolume name with subvolume ID
707 opts = subvol["fsopts"].split(",")
709 if opt.strip().startswith("subvol="):
713 opts.extend(["subvolrootid=0", "subvol=%s" % subvol["subvol"]])
714 fsopts = ",".join(opts)
715 subvol['fsopts'] = fsopts
716 mountpoint = self.mountdir + subvol['mountpoint']
718 rc = runner.show([self.mountcmd, "-o", fsopts, pdisk.disk.device, mountpoint])
720 raise MountError("Failed to mount subvolume %s to %s" % (subvol["subvol"], mountpoint))
721 subvol["mounted"] = True
723 def __unmount_subvolumes(self):
724 """ It may be called multiple times, so we need to chekc if it is still mounted. """
725 for subvol in self.subvolumes:
726 if subvol["mountpoint"] == "/":
728 if not subvol["mounted"]:
730 mountpoint = self.mountdir + subvol['mountpoint']
731 rc = runner.show([self.umountcmd, mountpoint])
733 raise MountError("Failed to unmount subvolume %s from %s" % (subvol["subvol"], mountpoint))
734 subvol["mounted"] = False
736 def __create_subvolume_snapshots(self, p, pdisk):
739 if self.snapshot_created:
742 # Remount with subvolid=0
743 rc = runner.show([self.umountcmd, pdisk.mountdir])
745 raise MountError("Failed to umount %s" % pdisk.mountdir)
747 mountopts = pdisk.fsopts + ",subvolid=0"
749 mountopts = "subvolid=0"
750 rc = runner.show([self.mountcmd, "-o", mountopts, pdisk.disk.device, pdisk.mountdir])
752 raise MountError("Failed to umount %s" % pdisk.mountdir)
754 # Create all the subvolume snapshots
755 snapshotts = time.strftime("%Y%m%d-%H%M")
756 for subvol in self.subvolumes:
757 subvolpath = pdisk.mountdir + "/" + subvol["subvol"]
758 snapshotpath = subvolpath + "_%s-1" % snapshotts
759 rc = runner.show([ self.btrfscmd, "subvolume", "snapshot", subvolpath, snapshotpath ])
761 raise MountError("Failed to create subvolume snapshot '%s' for '%s', return code:"
762 "%d." % (snapshotpath, subvolpath, rc))
764 self.snapshot_created = True
767 for dev in list(self.disks.keys()):
771 self.__format_disks()
772 self.__map_partitions()
773 self.__calculate_mountorder()
775 for mp in self.mount_order:
777 for p1 in self.partitions:
778 if p1['mountpoint'] == mp:
783 if p['mountpoint'] == "/":
784 p['label'] = 'platform'
786 p['label'] = mp.split('/')[-1]
790 p['uuid'] = str(uuid.uuid1())
791 runner.show([self.mkswap,
798 if p['mountpoint'] == "/":
800 if p['fstype'] == "vfat" or p['fstype'] == "msdos":
801 my_disk_mount = VfatDiskMount
802 elif p['fstype'] in ("ext2", "ext3", "ext4"):
803 my_disk_mount = ExtDiskMount
804 elif p['fstype'] == "btrfs":
805 my_disk_mount = BtrfsDiskMount
807 raise MountError("Fail to support file system " + p['fstype'])
809 if p['fstype'] == "btrfs" and not p['fsopts']:
810 p['fsopts'] = "subvolid=0"
812 pdisk = my_disk_mount(RawDisk(p['size'] * self.sector_size, p['device']),
813 self.mountdir + p['mountpoint'],
819 fsopts = p['fsopts'])
820 pdisk.mount(pdisk.fsopts)
821 if p['fstype'] == "btrfs" and p['mountpoint'] == "/":
822 if not self.skipformat:
823 self.__create_subvolumes(p, pdisk)
824 self.__mount_subvolumes(p, pdisk)
826 p['uuid'] = pdisk.uuid
828 def resparse(self, size = None):
829 # Can't re-sparse a disk image - too hard