aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/lib/mic/utils/partitionedfs.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/mic/utils/partitionedfs.py')
-rw-r--r--scripts/lib/mic/utils/partitionedfs.py790
1 files changed, 790 insertions, 0 deletions
diff --git a/scripts/lib/mic/utils/partitionedfs.py b/scripts/lib/mic/utils/partitionedfs.py
new file mode 100644
index 0000000000..04758440e1
--- /dev/null
+++ b/scripts/lib/mic/utils/partitionedfs.py
@@ -0,0 +1,790 @@
+#!/usr/bin/python -tt
+#
+# Copyright (c) 2009, 2010, 2011 Intel, Inc.
+# Copyright (c) 2007, 2008 Red Hat, Inc.
+# Copyright (c) 2008 Daniel P. Berrange
+# Copyright (c) 2008 David P. Huff
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; version 2 of the License
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc., 59
+# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+import os
+
+from mic import msger
+from mic.utils import runner
+from mic.utils.errors import MountError
+from mic.utils.fs_related import *
+from mic.utils.gpt_parser import GptParser
+
+# Overhead of the MBR partitioning scheme (just one sector)
+MBR_OVERHEAD = 1
+# Overhead of the GPT partitioning scheme
+GPT_OVERHEAD = 34
+
+# Size of a sector in bytes
+SECTOR_SIZE = 512
+
+class PartitionedMount(Mount):
+ def __init__(self, mountdir, skipformat = False):
+ Mount.__init__(self, mountdir)
+ self.disks = {}
+ self.partitions = []
+ self.subvolumes = []
+ self.mapped = False
+ self.mountOrder = []
+ self.unmountOrder = []
+ self.parted = find_binary_path("parted")
+ self.kpartx = find_binary_path("kpartx")
+ self.mkswap = find_binary_path("mkswap")
+ self.btrfscmd=None
+ self.mountcmd = find_binary_path("mount")
+ self.umountcmd = find_binary_path("umount")
+ self.skipformat = skipformat
+ self.snapshot_created = self.skipformat
+ # Size of a sector used in calculations
+ self.sector_size = SECTOR_SIZE
+ self._partitions_layed_out = False
+
+ def __add_disk(self, disk_name):
+ """ Add a disk 'disk_name' to the internal list of disks. Note,
+ 'disk_name' is the name of the disk in the target system
+ (e.g., sdb). """
+
+ if disk_name in self.disks:
+ # We already have this disk
+ return
+
+ assert not self._partitions_layed_out
+
+ self.disks[disk_name] = \
+ { 'disk': None, # Disk object
+ 'mapped': False, # True if kpartx mapping exists
+ 'numpart': 0, # Number of allocate partitions
+ 'partitions': [], # Indexes to self.partitions
+ 'offset': 0, # Offset of next partition (in sectors)
+ # Minimum required disk size to fit all partitions (in bytes)
+ 'min_size': 0,
+ 'ptable_format': "msdos" } # Partition table format
+
+ def add_disk(self, disk_name, disk_obj):
+ """ Add a disk object which have to be partitioned. More than one disk
+ can be added. In case of multiple disks, disk partitions have to be
+ added for each disk separately with 'add_partition()". """
+
+ self.__add_disk(disk_name)
+ self.disks[disk_name]['disk'] = disk_obj
+
+ def __add_partition(self, part):
+ """ This is a helper function for 'add_partition()' which adds a
+ partition to the internal list of partitions. """
+
+ assert not self._partitions_layed_out
+
+ self.partitions.append(part)
+ self.__add_disk(part['disk_name'])
+
+ def add_partition(self, size, disk_name, mountpoint, fstype = None,
+ label=None, fsopts = None, boot = False, align = None,
+ part_type = None):
+ """ Add the next partition. Prtitions have to be added in the
+ first-to-last order. """
+
+ ks_pnum = len(self.partitions)
+
+ # Converting MB to sectors for parted
+ size = size * 1024 * 1024 / self.sector_size
+
+ # We need to handle subvolumes for btrfs
+ if fstype == "btrfs" and fsopts and fsopts.find("subvol=") != -1:
+ self.btrfscmd=find_binary_path("btrfs")
+ subvol = None
+ opts = fsopts.split(",")
+ for opt in opts:
+ if opt.find("subvol=") != -1:
+ subvol = opt.replace("subvol=", "").strip()
+ break
+ if not subvol:
+ raise MountError("No subvolume: %s" % fsopts)
+ self.subvolumes.append({'size': size, # In sectors
+ 'mountpoint': mountpoint, # Mount relative to chroot
+ 'fstype': fstype, # Filesystem type
+ 'fsopts': fsopts, # Filesystem mount options
+ 'disk_name': disk_name, # physical disk name holding partition
+ 'device': None, # kpartx device node for partition
+ 'mount': None, # Mount object
+ 'subvol': subvol, # Subvolume name
+ 'boot': boot, # Bootable flag
+ 'mounted': False # Mount flag
+ })
+
+ # We still need partition for "/" or non-subvolume
+ if mountpoint == "/" or not fsopts or fsopts.find("subvol=") == -1:
+ # Don't need subvolume for "/" because it will be set as default subvolume
+ if fsopts and fsopts.find("subvol=") != -1:
+ opts = fsopts.split(",")
+ for opt in opts:
+ if opt.strip().startswith("subvol="):
+ opts.remove(opt)
+ break
+ fsopts = ",".join(opts)
+
+ part = { 'ks_pnum' : ks_pnum, # Partition number in the KS file
+ 'size': size, # In sectors
+ 'mountpoint': mountpoint, # Mount relative to chroot
+ 'fstype': fstype, # Filesystem type
+ 'fsopts': fsopts, # Filesystem mount options
+ 'label': label, # Partition label
+ 'disk_name': disk_name, # physical disk name holding partition
+ 'device': None, # kpartx device node for partition
+ 'mount': None, # Mount object
+ 'num': None, # Partition number
+ 'boot': boot, # Bootable flag
+ 'align': align, # Partition alignment
+ 'part_type' : part_type, # Partition type
+ 'partuuid': None } # Partition UUID (GPT-only)
+
+ self.__add_partition(part)
+
+ def layout_partitions(self, ptable_format = "msdos"):
+ """ Layout the partitions, meaning calculate the position of every
+ partition on the disk. The 'ptable_format' parameter defines the
+ partition table format, and may be either "msdos" or "gpt". """
+
+ msger.debug("Assigning %s partitions to disks" % ptable_format)
+
+ if ptable_format not in ('msdos', 'gpt'):
+ raise MountError("Unknown partition table format '%s', supported " \
+ "formats are: 'msdos' and 'gpt'" % ptable_format)
+
+ if self._partitions_layed_out:
+ return
+
+ self._partitions_layed_out = True
+
+ # Go through partitions in the order they are added in .ks file
+ for n in range(len(self.partitions)):
+ p = self.partitions[n]
+
+ if not self.disks.has_key(p['disk_name']):
+ raise MountError("No disk %s for partition %s" \
+ % (p['disk_name'], p['mountpoint']))
+
+ if p['part_type'] and ptable_format != 'gpt':
+ # The --part-type can also be implemented for MBR partitions,
+ # in which case it would map to the 1-byte "partition type"
+ # filed at offset 3 of the partition entry.
+ raise MountError("setting custom partition type is only " \
+ "imlemented for GPT partitions")
+
+ # Get the disk where the partition is located
+ d = self.disks[p['disk_name']]
+ d['numpart'] += 1
+ d['ptable_format'] = ptable_format
+
+ if d['numpart'] == 1:
+ if ptable_format == "msdos":
+ overhead = MBR_OVERHEAD
+ else:
+ overhead = GPT_OVERHEAD
+
+ # Skip one sector required for the partitioning scheme overhead
+ d['offset'] += overhead
+ # Steal few sectors from the first partition to offset for the
+ # partitioning overhead
+ p['size'] -= overhead
+
+ if p['align']:
+ # If not first partition and we do have alignment set we need
+ # to align the partition.
+ # FIXME: This leaves a empty spaces to the disk. To fill the
+ # gaps we could enlargea the previous partition?
+
+ # Calc how much the alignment is off.
+ align_sectors = d['offset'] % (p['align'] * 1024 / self.sector_size)
+ # We need to move forward to the next alignment point
+ align_sectors = (p['align'] * 1024 / self.sector_size) - align_sectors
+
+ msger.debug("Realignment for %s%s with %s sectors, original"
+ " offset %s, target alignment is %sK." %
+ (p['disk_name'], d['numpart'], align_sectors,
+ d['offset'], p['align']))
+
+ # increase the offset so we actually start the partition on right alignment
+ d['offset'] += align_sectors
+
+ p['start'] = d['offset']
+ d['offset'] += p['size']
+
+ p['type'] = 'primary'
+ p['num'] = d['numpart']
+
+ if d['ptable_format'] == "msdos":
+ if d['numpart'] > 2:
+ # Every logical partition requires an additional sector for
+ # the EBR, so steal the last sector from the end of each
+ # partition starting from the 3rd one for the EBR. This
+ # will make sure the logical partitions are aligned
+ # correctly.
+ p['size'] -= 1
+
+ if d['numpart'] > 3:
+ p['type'] = 'logical'
+ p['num'] = d['numpart'] + 1
+
+ d['partitions'].append(n)
+ msger.debug("Assigned %s to %s%d, sectors range %d-%d size %d "
+ "sectors (%d bytes)." \
+ % (p['mountpoint'], p['disk_name'], p['num'],
+ p['start'], p['start'] + p['size'] - 1,
+ p['size'], p['size'] * self.sector_size))
+
+ # Once all the partitions have been layed out, we can calculate the
+ # minumim disk sizes.
+ for disk_name, d in self.disks.items():
+ d['min_size'] = d['offset']
+ if d['ptable_format'] == 'gpt':
+ # Account for the backup partition table at the end of the disk
+ d['min_size'] += GPT_OVERHEAD
+
+ d['min_size'] *= self.sector_size
+
+ def __run_parted(self, args):
+ """ Run parted with arguments specified in the 'args' list. """
+
+ args.insert(0, self.parted)
+ msger.debug(args)
+
+ rc, out = runner.runtool(args, catch = 3)
+ out = out.strip()
+ if out:
+ msger.debug('"parted" output: %s' % out)
+
+ if rc != 0:
+ # We don't throw exception when return code is not 0, because
+ # parted always fails to reload part table with loop devices. This
+ # prevents us from distinguishing real errors based on return
+ # code.
+ msger.debug("WARNING: parted returned '%s' instead of 0" % rc)
+
+ def __create_partition(self, device, parttype, fstype, start, size):
+ """ Create a partition on an image described by the 'device' object. """
+
+ # Start is included to the size so we need to substract one from the end.
+ end = start + size - 1
+ msger.debug("Added '%s' partition, sectors %d-%d, size %d sectors" %
+ (parttype, start, end, size))
+
+ args = ["-s", device, "unit", "s", "mkpart", parttype]
+ if fstype:
+ args.extend([fstype])
+ args.extend(["%d" % start, "%d" % end])
+
+ return self.__run_parted(args)
+
+ def __format_disks(self):
+ self.layout_partitions()
+
+ if self.skipformat:
+ msger.debug("Skipping disk format, because skipformat flag is set.")
+ return
+
+ for dev in self.disks.keys():
+ d = self.disks[dev]
+ msger.debug("Initializing partition table for %s" % \
+ (d['disk'].device))
+ self.__run_parted(["-s", d['disk'].device, "mklabel",
+ d['ptable_format']])
+
+ msger.debug("Creating partitions")
+
+ for p in self.partitions:
+ d = self.disks[p['disk_name']]
+ if d['ptable_format'] == "msdos" and p['num'] == 5:
+ # The last sector of the 3rd partition was reserved for the EBR
+ # of the first _logical_ partition. This is why the extended
+ # partition should start one sector before the first logical
+ # partition.
+ self.__create_partition(d['disk'].device, "extended",
+ None, p['start'] - 1,
+ d['offset'] - p['start'])
+
+ if p['fstype'] == "swap":
+ parted_fs_type = "linux-swap"
+ elif p['fstype'] == "vfat":
+ parted_fs_type = "fat32"
+ elif p['fstype'] == "msdos":
+ parted_fs_type = "fat16"
+ else:
+ # Type for ext2/ext3/ext4/btrfs
+ parted_fs_type = "ext2"
+
+ # Boot ROM of OMAP boards require vfat boot partition to have an
+ # even number of sectors.
+ if p['mountpoint'] == "/boot" and p['fstype'] in ["vfat", "msdos"] \
+ and p['size'] % 2:
+ msger.debug("Substracting one sector from '%s' partition to " \
+ "get even number of sectors for the partition" % \
+ p['mountpoint'])
+ p['size'] -= 1
+
+ self.__create_partition(d['disk'].device, p['type'],
+ parted_fs_type, p['start'], p['size'])
+
+ if p['boot']:
+ if d['ptable_format'] == 'gpt':
+ flag_name = "legacy_boot"
+ else:
+ flag_name = "boot"
+ msger.debug("Set '%s' flag for partition '%s' on disk '%s'" % \
+ (flag_name, p['num'], d['disk'].device))
+ self.__run_parted(["-s", d['disk'].device, "set",
+ "%d" % p['num'], flag_name, "on"])
+
+ # If the partition table format is "gpt", find out PARTUUIDs for all
+ # the partitions. And if users specified custom parition type UUIDs,
+ # set them.
+ for disk_name, disk in self.disks.items():
+ if disk['ptable_format'] != 'gpt':
+ continue
+
+ pnum = 0
+ gpt_parser = GptParser(d['disk'].device, SECTOR_SIZE)
+ # Iterate over all GPT partitions on this disk
+ for entry in gpt_parser.get_partitions():
+ pnum += 1
+ # Find the matching partition in the 'self.partitions' list
+ for n in d['partitions']:
+ p = self.partitions[n]
+ if p['num'] == pnum:
+ # Found, fetch PARTUUID (partition's unique ID)
+ p['partuuid'] = entry['part_uuid']
+ msger.debug("PARTUUID for partition %d on disk '%s' " \
+ "(mount point '%s') is '%s'" % (pnum, \
+ disk_name, p['mountpoint'], p['partuuid']))
+ if p['part_type']:
+ entry['type_uuid'] = p['part_type']
+ msger.debug("Change type of partition %d on disk " \
+ "'%s' (mount point '%s') to '%s'" % \
+ (pnum, disk_name, p['mountpoint'],
+ p['part_type']))
+ gpt_parser.change_partition(entry)
+
+ del gpt_parser
+
+ def __map_partitions(self):
+ """Load it if dm_snapshot isn't loaded. """
+ load_module("dm_snapshot")
+
+ for dev in self.disks.keys():
+ d = self.disks[dev]
+ if d['mapped']:
+ continue
+
+ msger.debug("Running kpartx on %s" % d['disk'].device )
+ rc, kpartxOutput = runner.runtool([self.kpartx, "-l", "-v", d['disk'].device])
+ kpartxOutput = kpartxOutput.splitlines()
+
+ if rc != 0:
+ raise MountError("Failed to query partition mapping for '%s'" %
+ d['disk'].device)
+
+ # Strip trailing blank and mask verbose output
+ i = 0
+ while i < len(kpartxOutput) and kpartxOutput[i][0:4] != "loop":
+ i = i + 1
+ kpartxOutput = kpartxOutput[i:]
+
+ # Make sure kpartx reported the right count of partitions
+ if len(kpartxOutput) != d['numpart']:
+ # If this disk has more than 3 partitions, then in case of MBR
+ # paritions there is an extended parition. Different versions
+ # of kpartx behave differently WRT the extended partition -
+ # some map it, some ignore it. This is why we do the below hack
+ # - if kpartx reported one more partition and the partition
+ # table type is "msdos" and the amount of partitions is more
+ # than 3, we just assume kpartx mapped the extended parition
+ # and we remove it.
+ if len(kpartxOutput) == d['numpart'] + 1 \
+ and d['ptable_format'] == 'msdos' and len(kpartxOutput) > 3:
+ kpartxOutput.pop(3)
+ else:
+ raise MountError("Unexpected number of partitions from " \
+ "kpartx: %d != %d" % \
+ (len(kpartxOutput), d['numpart']))
+
+ for i in range(len(kpartxOutput)):
+ line = kpartxOutput[i]
+ newdev = line.split()[0]
+ mapperdev = "/dev/mapper/" + newdev
+ loopdev = d['disk'].device + newdev[-1]
+
+ msger.debug("Dev %s: %s -> %s" % (newdev, loopdev, mapperdev))
+ pnum = d['partitions'][i]
+ self.partitions[pnum]['device'] = loopdev
+
+ # grub's install wants partitions to be named
+ # to match their parent device + partition num
+ # kpartx doesn't work like this, so we add compat
+ # symlinks to point to /dev/mapper
+ if os.path.lexists(loopdev):
+ os.unlink(loopdev)
+ os.symlink(mapperdev, loopdev)
+
+ msger.debug("Adding partx mapping for %s" % d['disk'].device)
+ rc = runner.show([self.kpartx, "-v", "-a", d['disk'].device])
+
+ if rc != 0:
+ # Make sure that the device maps are also removed on error case.
+ # The d['mapped'] isn't set to True if the kpartx fails so
+ # failed mapping will not be cleaned on cleanup either.
+ runner.quiet([self.kpartx, "-d", d['disk'].device])
+ raise MountError("Failed to map partitions for '%s'" %
+ d['disk'].device)
+
+ # FIXME: there is a bit delay for multipath device setup,
+ # wait 10ms for the setup
+ import time
+ time.sleep(10)
+ d['mapped'] = True
+
+ def __unmap_partitions(self):
+ for dev in self.disks.keys():
+ d = self.disks[dev]
+ if not d['mapped']:
+ continue
+
+ msger.debug("Removing compat symlinks")
+ for pnum in d['partitions']:
+ if self.partitions[pnum]['device'] != None:
+ os.unlink(self.partitions[pnum]['device'])
+ self.partitions[pnum]['device'] = None
+
+ msger.debug("Unmapping %s" % d['disk'].device)
+ rc = runner.quiet([self.kpartx, "-d", d['disk'].device])
+ if rc != 0:
+ raise MountError("Failed to unmap partitions for '%s'" %
+ d['disk'].device)
+
+ d['mapped'] = False
+
+ def __calculate_mountorder(self):
+ msger.debug("Calculating mount order")
+ for p in self.partitions:
+ if p['mountpoint']:
+ self.mountOrder.append(p['mountpoint'])
+ self.unmountOrder.append(p['mountpoint'])
+
+ self.mountOrder.sort()
+ self.unmountOrder.sort()
+ self.unmountOrder.reverse()
+
+ def cleanup(self):
+ Mount.cleanup(self)
+ if self.disks:
+ self.__unmap_partitions()
+ for dev in self.disks.keys():
+ d = self.disks[dev]
+ try:
+ d['disk'].cleanup()
+ except:
+ pass
+
+ def unmount(self):
+ self.__unmount_subvolumes()
+ for mp in self.unmountOrder:
+ if mp == 'swap':
+ continue
+ p = None
+ for p1 in self.partitions:
+ if p1['mountpoint'] == mp:
+ p = p1
+ break
+
+ if p['mount'] != None:
+ try:
+ # Create subvolume snapshot here
+ if p['fstype'] == "btrfs" and p['mountpoint'] == "/" and not self.snapshot_created:
+ self.__create_subvolume_snapshots(p, p["mount"])
+ p['mount'].cleanup()
+ except:
+ pass
+ p['mount'] = None
+
+ # Only for btrfs
+ def __get_subvolume_id(self, rootpath, subvol):
+ if not self.btrfscmd:
+ self.btrfscmd=find_binary_path("btrfs")
+ argv = [ self.btrfscmd, "subvolume", "list", rootpath ]
+
+ rc, out = runner.runtool(argv)
+ msger.debug(out)
+
+ if rc != 0:
+ raise MountError("Failed to get subvolume id from %s', return code: %d." % (rootpath, rc))
+
+ subvolid = -1
+ for line in out.splitlines():
+ if line.endswith(" path %s" % subvol):
+ subvolid = line.split()[1]
+ if not subvolid.isdigit():
+ raise MountError("Invalid subvolume id: %s" % subvolid)
+ subvolid = int(subvolid)
+ break
+ return subvolid
+
+ def __create_subvolume_metadata(self, p, pdisk):
+ if len(self.subvolumes) == 0:
+ return
+
+ argv = [ self.btrfscmd, "subvolume", "list", pdisk.mountdir ]
+ rc, out = runner.runtool(argv)
+ msger.debug(out)
+
+ if rc != 0:
+ raise MountError("Failed to get subvolume id from %s', return code: %d." % (pdisk.mountdir, rc))
+
+ subvolid_items = out.splitlines()
+ subvolume_metadata = ""
+ for subvol in self.subvolumes:
+ for line in subvolid_items:
+ if line.endswith(" path %s" % subvol["subvol"]):
+ subvolid = line.split()[1]
+ if not subvolid.isdigit():
+ raise MountError("Invalid subvolume id: %s" % subvolid)
+
+ subvolid = int(subvolid)
+ opts = subvol["fsopts"].split(",")
+ for opt in opts:
+ if opt.strip().startswith("subvol="):
+ opts.remove(opt)
+ break
+ fsopts = ",".join(opts)
+ subvolume_metadata += "%d\t%s\t%s\t%s\n" % (subvolid, subvol["subvol"], subvol['mountpoint'], fsopts)
+
+ if subvolume_metadata:
+ fd = open("%s/.subvolume_metadata" % pdisk.mountdir, "w")
+ fd.write(subvolume_metadata)
+ fd.close()
+
+ def __get_subvolume_metadata(self, p, pdisk):
+ subvolume_metadata_file = "%s/.subvolume_metadata" % pdisk.mountdir
+ if not os.path.exists(subvolume_metadata_file):
+ return
+
+ fd = open(subvolume_metadata_file, "r")
+ content = fd.read()
+ fd.close()
+
+ for line in content.splitlines():
+ items = line.split("\t")
+ if items and len(items) == 4:
+ self.subvolumes.append({'size': 0, # In sectors
+ 'mountpoint': items[2], # Mount relative to chroot
+ 'fstype': "btrfs", # Filesystem type
+ 'fsopts': items[3] + ",subvol=%s" % items[1], # Filesystem mount options
+ 'disk_name': p['disk_name'], # physical disk name holding partition
+ 'device': None, # kpartx device node for partition
+ 'mount': None, # Mount object
+ 'subvol': items[1], # Subvolume name
+ 'boot': False, # Bootable flag
+ 'mounted': False # Mount flag
+ })
+
+ def __create_subvolumes(self, p, pdisk):
+ """ Create all the subvolumes. """
+
+ for subvol in self.subvolumes:
+ argv = [ self.btrfscmd, "subvolume", "create", pdisk.mountdir + "/" + subvol["subvol"]]
+
+ rc = runner.show(argv)
+ if rc != 0:
+ raise MountError("Failed to create subvolume '%s', return code: %d." % (subvol["subvol"], rc))
+
+ # Set default subvolume, subvolume for "/" is default
+ subvol = None
+ for subvolume in self.subvolumes:
+ if subvolume["mountpoint"] == "/" and p['disk_name'] == subvolume['disk_name']:
+ subvol = subvolume
+ break
+
+ if subvol:
+ # Get default subvolume id
+ subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"])
+ # Set default subvolume
+ if subvolid != -1:
+ rc = runner.show([ self.btrfscmd, "subvolume", "set-default", "%d" % subvolid, pdisk.mountdir])
+ if rc != 0:
+ raise MountError("Failed to set default subvolume id: %d', return code: %d." % (subvolid, rc))
+
+ self.__create_subvolume_metadata(p, pdisk)
+
+ def __mount_subvolumes(self, p, pdisk):
+ if self.skipformat:
+ # Get subvolume info
+ self.__get_subvolume_metadata(p, pdisk)
+ # Set default mount options
+ if len(self.subvolumes) != 0:
+ for subvol in self.subvolumes:
+ if subvol["mountpoint"] == p["mountpoint"] == "/":
+ opts = subvol["fsopts"].split(",")
+ for opt in opts:
+ if opt.strip().startswith("subvol="):
+ opts.remove(opt)
+ break
+ pdisk.fsopts = ",".join(opts)
+ break
+
+ if len(self.subvolumes) == 0:
+ # Return directly if no subvolumes
+ return
+
+ # Remount to make default subvolume mounted
+ rc = runner.show([self.umountcmd, pdisk.mountdir])
+ if rc != 0:
+ raise MountError("Failed to umount %s" % pdisk.mountdir)
+
+ rc = runner.show([self.mountcmd, "-o", pdisk.fsopts, pdisk.disk.device, pdisk.mountdir])
+ if rc != 0:
+ raise MountError("Failed to umount %s" % pdisk.mountdir)
+
+ for subvol in self.subvolumes:
+ if subvol["mountpoint"] == "/":
+ continue
+ subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"])
+ if subvolid == -1:
+ msger.debug("WARNING: invalid subvolume %s" % subvol["subvol"])
+ continue
+ # Replace subvolume name with subvolume ID
+ opts = subvol["fsopts"].split(",")
+ for opt in opts:
+ if opt.strip().startswith("subvol="):
+ opts.remove(opt)
+ break
+
+ opts.extend(["subvolrootid=0", "subvol=%s" % subvol["subvol"]])
+ fsopts = ",".join(opts)
+ subvol['fsopts'] = fsopts
+ mountpoint = self.mountdir + subvol['mountpoint']
+ makedirs(mountpoint)
+ rc = runner.show([self.mountcmd, "-o", fsopts, pdisk.disk.device, mountpoint])
+ if rc != 0:
+ raise MountError("Failed to mount subvolume %s to %s" % (subvol["subvol"], mountpoint))
+ subvol["mounted"] = True
+
+ def __unmount_subvolumes(self):
+ """ It may be called multiple times, so we need to chekc if it is still mounted. """
+ for subvol in self.subvolumes:
+ if subvol["mountpoint"] == "/":
+ continue
+ if not subvol["mounted"]:
+ continue
+ mountpoint = self.mountdir + subvol['mountpoint']
+ rc = runner.show([self.umountcmd, mountpoint])
+ if rc != 0:
+ raise MountError("Failed to unmount subvolume %s from %s" % (subvol["subvol"], mountpoint))
+ subvol["mounted"] = False
+
+ def __create_subvolume_snapshots(self, p, pdisk):
+ import time
+
+ if self.snapshot_created:
+ return
+
+ # Remount with subvolid=0
+ rc = runner.show([self.umountcmd, pdisk.mountdir])
+ if rc != 0:
+ raise MountError("Failed to umount %s" % pdisk.mountdir)
+ if pdisk.fsopts:
+ mountopts = pdisk.fsopts + ",subvolid=0"
+ else:
+ mountopts = "subvolid=0"
+ rc = runner.show([self.mountcmd, "-o", mountopts, pdisk.disk.device, pdisk.mountdir])
+ if rc != 0:
+ raise MountError("Failed to umount %s" % pdisk.mountdir)
+
+ # Create all the subvolume snapshots
+ snapshotts = time.strftime("%Y%m%d-%H%M")
+ for subvol in self.subvolumes:
+ subvolpath = pdisk.mountdir + "/" + subvol["subvol"]
+ snapshotpath = subvolpath + "_%s-1" % snapshotts
+ rc = runner.show([ self.btrfscmd, "subvolume", "snapshot", subvolpath, snapshotpath ])
+ if rc != 0:
+ raise MountError("Failed to create subvolume snapshot '%s' for '%s', return code: %d." % (snapshotpath, subvolpath, rc))
+
+ self.snapshot_created = True
+
+ def mount(self):
+ for dev in self.disks.keys():
+ d = self.disks[dev]
+ d['disk'].create()
+
+ self.__format_disks()
+ self.__map_partitions()
+ self.__calculate_mountorder()
+
+ for mp in self.mountOrder:
+ p = None
+ for p1 in self.partitions:
+ if p1['mountpoint'] == mp:
+ p = p1
+ break
+
+ if not p['label']:
+ if p['mountpoint'] == "/":
+ p['label'] = 'platform'
+ else:
+ p['label'] = mp.split('/')[-1]
+
+ if mp == 'swap':
+ import uuid
+ p['uuid'] = str(uuid.uuid1())
+ runner.show([self.mkswap,
+ '-L', p['label'],
+ '-U', p['uuid'],
+ p['device']])
+ continue
+
+ rmmountdir = False
+ if p['mountpoint'] == "/":
+ rmmountdir = True
+ if p['fstype'] == "vfat" or p['fstype'] == "msdos":
+ myDiskMount = VfatDiskMount
+ elif p['fstype'] in ("ext2", "ext3", "ext4"):
+ myDiskMount = ExtDiskMount
+ elif p['fstype'] == "btrfs":
+ myDiskMount = BtrfsDiskMount
+ else:
+ raise MountError("Fail to support file system " + p['fstype'])
+
+ if p['fstype'] == "btrfs" and not p['fsopts']:
+ p['fsopts'] = "subvolid=0"
+
+ pdisk = myDiskMount(RawDisk(p['size'] * self.sector_size, p['device']),
+ self.mountdir + p['mountpoint'],
+ p['fstype'],
+ 4096,
+ p['label'],
+ rmmountdir,
+ self.skipformat,
+ fsopts = p['fsopts'])
+ pdisk.mount(pdisk.fsopts)
+ if p['fstype'] == "btrfs" and p['mountpoint'] == "/":
+ if not self.skipformat:
+ self.__create_subvolumes(p, pdisk)
+ self.__mount_subvolumes(p, pdisk)
+ p['mount'] = pdisk
+ p['uuid'] = pdisk.uuid
+
+ def resparse(self, size = None):
+ # Can't re-sparse a disk image - too hard
+ pass