diff options
Diffstat (limited to 'scripts/lib/mic/utils/partitionedfs.py')
| -rw-r--r-- | scripts/lib/mic/utils/partitionedfs.py | 790 |
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 @@ | |||
| 1 | #!/usr/bin/python -tt | ||
| 2 | # | ||
| 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 | ||
| 7 | # | ||
| 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 | ||
| 11 | # | ||
| 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 | ||
| 15 | # for more details. | ||
| 16 | # | ||
| 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. | ||
| 20 | |||
| 21 | import os | ||
| 22 | |||
| 23 | from mic import msger | ||
| 24 | from mic.utils import runner | ||
| 25 | from mic.utils.errors import MountError | ||
| 26 | from mic.utils.fs_related import * | ||
| 27 | from mic.utils.gpt_parser import GptParser | ||
| 28 | |||
| 29 | # Overhead of the MBR partitioning scheme (just one sector) | ||
| 30 | MBR_OVERHEAD = 1 | ||
| 31 | # Overhead of the GPT partitioning scheme | ||
| 32 | GPT_OVERHEAD = 34 | ||
| 33 | |||
| 34 | # Size of a sector in bytes | ||
| 35 | SECTOR_SIZE = 512 | ||
| 36 | |||
| 37 | class PartitionedMount(Mount): | ||
| 38 | def __init__(self, mountdir, skipformat = False): | ||
| 39 | Mount.__init__(self, mountdir) | ||
| 40 | self.disks = {} | ||
| 41 | self.partitions = [] | ||
| 42 | self.subvolumes = [] | ||
| 43 | self.mapped = False | ||
| 44 | self.mountOrder = [] | ||
| 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.btrfscmd=None | ||
| 50 | self.mountcmd = find_binary_path("mount") | ||
| 51 | self.umountcmd = find_binary_path("umount") | ||
| 52 | self.skipformat = skipformat | ||
| 53 | self.snapshot_created = self.skipformat | ||
| 54 | # Size of a sector used in calculations | ||
| 55 | self.sector_size = SECTOR_SIZE | ||
| 56 | self._partitions_layed_out = False | ||
| 57 | |||
| 58 | def __add_disk(self, disk_name): | ||
| 59 | """ Add a disk 'disk_name' to the internal list of disks. Note, | ||
| 60 | 'disk_name' is the name of the disk in the target system | ||
| 61 | (e.g., sdb). """ | ||
| 62 | |||
| 63 | if disk_name in self.disks: | ||
| 64 | # We already have this disk | ||
| 65 | return | ||
| 66 | |||
| 67 | assert not self._partitions_layed_out | ||
| 68 | |||
| 69 | self.disks[disk_name] = \ | ||
| 70 | { 'disk': None, # Disk object | ||
| 71 | 'mapped': False, # True if kpartx mapping exists | ||
| 72 | 'numpart': 0, # Number of allocate partitions | ||
| 73 | 'partitions': [], # Indexes to self.partitions | ||
| 74 | 'offset': 0, # Offset of next partition (in sectors) | ||
| 75 | # Minimum required disk size to fit all partitions (in bytes) | ||
| 76 | 'min_size': 0, | ||
| 77 | 'ptable_format': "msdos" } # Partition table format | ||
| 78 | |||
| 79 | def add_disk(self, disk_name, disk_obj): | ||
| 80 | """ Add a disk object which have to be partitioned. More than one disk | ||
| 81 | can be added. In case of multiple disks, disk partitions have to be | ||
| 82 | added for each disk separately with 'add_partition()". """ | ||
| 83 | |||
| 84 | self.__add_disk(disk_name) | ||
| 85 | self.disks[disk_name]['disk'] = disk_obj | ||
| 86 | |||
| 87 | def __add_partition(self, part): | ||
| 88 | """ This is a helper function for 'add_partition()' which adds a | ||
| 89 | partition to the internal list of partitions. """ | ||
| 90 | |||
| 91 | assert not self._partitions_layed_out | ||
| 92 | |||
| 93 | self.partitions.append(part) | ||
| 94 | self.__add_disk(part['disk_name']) | ||
| 95 | |||
| 96 | def add_partition(self, size, disk_name, mountpoint, fstype = None, | ||
| 97 | label=None, fsopts = None, boot = False, align = None, | ||
| 98 | part_type = None): | ||
| 99 | """ Add the next partition. Prtitions have to be added in the | ||
| 100 | first-to-last order. """ | ||
| 101 | |||
| 102 | ks_pnum = len(self.partitions) | ||
| 103 | |||
| 104 | # Converting MB to sectors for parted | ||
| 105 | size = size * 1024 * 1024 / self.sector_size | ||
| 106 | |||
| 107 | # We need to handle subvolumes for btrfs | ||
| 108 | if fstype == "btrfs" and fsopts and fsopts.find("subvol=") != -1: | ||
| 109 | self.btrfscmd=find_binary_path("btrfs") | ||
| 110 | subvol = None | ||
| 111 | opts = fsopts.split(",") | ||
| 112 | for opt in opts: | ||
| 113 | if opt.find("subvol=") != -1: | ||
| 114 | subvol = opt.replace("subvol=", "").strip() | ||
| 115 | break | ||
| 116 | if not subvol: | ||
| 117 | raise MountError("No subvolume: %s" % fsopts) | ||
| 118 | self.subvolumes.append({'size': size, # In sectors | ||
| 119 | 'mountpoint': mountpoint, # Mount relative to chroot | ||
| 120 | 'fstype': fstype, # Filesystem type | ||
| 121 | 'fsopts': fsopts, # Filesystem mount options | ||
| 122 | 'disk_name': disk_name, # physical disk name holding partition | ||
| 123 | 'device': None, # kpartx device node for partition | ||
| 124 | 'mount': None, # Mount object | ||
| 125 | 'subvol': subvol, # Subvolume name | ||
| 126 | 'boot': boot, # Bootable flag | ||
| 127 | 'mounted': False # Mount flag | ||
| 128 | }) | ||
| 129 | |||
| 130 | # We still need partition for "/" or non-subvolume | ||
| 131 | if mountpoint == "/" or not fsopts or fsopts.find("subvol=") == -1: | ||
| 132 | # Don't need subvolume for "/" because it will be set as default subvolume | ||
| 133 | if fsopts and fsopts.find("subvol=") != -1: | ||
| 134 | opts = fsopts.split(",") | ||
| 135 | for opt in opts: | ||
| 136 | if opt.strip().startswith("subvol="): | ||
| 137 | opts.remove(opt) | ||
| 138 | break | ||
| 139 | fsopts = ",".join(opts) | ||
| 140 | |||
| 141 | part = { 'ks_pnum' : ks_pnum, # Partition number in the KS file | ||
| 142 | 'size': size, # In sectors | ||
| 143 | 'mountpoint': mountpoint, # Mount relative to chroot | ||
| 144 | 'fstype': fstype, # Filesystem type | ||
| 145 | 'fsopts': fsopts, # Filesystem mount options | ||
| 146 | 'label': label, # Partition label | ||
| 147 | 'disk_name': disk_name, # physical disk name holding partition | ||
| 148 | 'device': None, # kpartx device node for partition | ||
| 149 | 'mount': None, # Mount object | ||
| 150 | 'num': None, # Partition number | ||
| 151 | 'boot': boot, # Bootable flag | ||
| 152 | 'align': align, # Partition alignment | ||
| 153 | 'part_type' : part_type, # Partition type | ||
| 154 | 'partuuid': None } # Partition UUID (GPT-only) | ||
| 155 | |||
| 156 | self.__add_partition(part) | ||
| 157 | |||
| 158 | def layout_partitions(self, ptable_format = "msdos"): | ||
| 159 | """ Layout the partitions, meaning calculate the position of every | ||
| 160 | partition on the disk. The 'ptable_format' parameter defines the | ||
| 161 | partition table format, and may be either "msdos" or "gpt". """ | ||
| 162 | |||
| 163 | msger.debug("Assigning %s partitions to disks" % ptable_format) | ||
| 164 | |||
| 165 | if ptable_format not in ('msdos', 'gpt'): | ||
| 166 | raise MountError("Unknown partition table format '%s', supported " \ | ||
| 167 | "formats are: 'msdos' and 'gpt'" % ptable_format) | ||
| 168 | |||
| 169 | if self._partitions_layed_out: | ||
| 170 | return | ||
| 171 | |||
| 172 | self._partitions_layed_out = True | ||
| 173 | |||
| 174 | # Go through partitions in the order they are added in .ks file | ||
| 175 | for n in range(len(self.partitions)): | ||
| 176 | p = self.partitions[n] | ||
| 177 | |||
| 178 | if not self.disks.has_key(p['disk_name']): | ||
| 179 | raise MountError("No disk %s for partition %s" \ | ||
| 180 | % (p['disk_name'], p['mountpoint'])) | ||
| 181 | |||
| 182 | if p['part_type'] and ptable_format != 'gpt': | ||
| 183 | # The --part-type can also be implemented for MBR partitions, | ||
| 184 | # in which case it would map to the 1-byte "partition type" | ||
| 185 | # filed at offset 3 of the partition entry. | ||
| 186 | raise MountError("setting custom partition type is only " \ | ||
| 187 | "imlemented for GPT partitions") | ||
| 188 | |||
| 189 | # Get the disk where the partition is located | ||
| 190 | d = self.disks[p['disk_name']] | ||
| 191 | d['numpart'] += 1 | ||
| 192 | d['ptable_format'] = ptable_format | ||
| 193 | |||
| 194 | if d['numpart'] == 1: | ||
| 195 | if ptable_format == "msdos": | ||
| 196 | overhead = MBR_OVERHEAD | ||
| 197 | else: | ||
| 198 | overhead = GPT_OVERHEAD | ||
| 199 | |||
| 200 | # Skip one sector required for the partitioning scheme overhead | ||
| 201 | d['offset'] += overhead | ||
| 202 | # Steal few sectors from the first partition to offset for the | ||
| 203 | # partitioning overhead | ||
| 204 | p['size'] -= overhead | ||
| 205 | |||
| 206 | if p['align']: | ||
| 207 | # If not first partition and we do have alignment set we need | ||
| 208 | # to align the partition. | ||
| 209 | # FIXME: This leaves a empty spaces to the disk. To fill the | ||
| 210 | # gaps we could enlargea the previous partition? | ||
| 211 | |||
| 212 | # Calc how much the alignment is off. | ||
| 213 | align_sectors = d['offset'] % (p['align'] * 1024 / self.sector_size) | ||
| 214 | # We need to move forward to the next alignment point | ||
| 215 | align_sectors = (p['align'] * 1024 / self.sector_size) - align_sectors | ||
| 216 | |||
| 217 | msger.debug("Realignment for %s%s with %s sectors, original" | ||
| 218 | " offset %s, target alignment is %sK." % | ||
| 219 | (p['disk_name'], d['numpart'], align_sectors, | ||
| 220 | d['offset'], p['align'])) | ||
| 221 | |||
| 222 | # increase the offset so we actually start the partition on right alignment | ||
| 223 | d['offset'] += align_sectors | ||
| 224 | |||
| 225 | p['start'] = d['offset'] | ||
| 226 | d['offset'] += p['size'] | ||
| 227 | |||
| 228 | p['type'] = 'primary' | ||
| 229 | p['num'] = d['numpart'] | ||
| 230 | |||
| 231 | if d['ptable_format'] == "msdos": | ||
| 232 | if d['numpart'] > 2: | ||
| 233 | # Every logical partition requires an additional sector for | ||
| 234 | # the EBR, so steal the last sector from the end of each | ||
| 235 | # partition starting from the 3rd one for the EBR. This | ||
| 236 | # will make sure the logical partitions are aligned | ||
| 237 | # correctly. | ||
| 238 | p['size'] -= 1 | ||
| 239 | |||
| 240 | if d['numpart'] > 3: | ||
| 241 | p['type'] = 'logical' | ||
| 242 | p['num'] = d['numpart'] + 1 | ||
| 243 | |||
| 244 | d['partitions'].append(n) | ||
| 245 | msger.debug("Assigned %s to %s%d, sectors range %d-%d size %d " | ||
| 246 | "sectors (%d bytes)." \ | ||
| 247 | % (p['mountpoint'], p['disk_name'], p['num'], | ||
| 248 | p['start'], p['start'] + p['size'] - 1, | ||
| 249 | p['size'], p['size'] * self.sector_size)) | ||
| 250 | |||
| 251 | # Once all the partitions have been layed out, we can calculate the | ||
| 252 | # minumim disk sizes. | ||
| 253 | for disk_name, d in self.disks.items(): | ||
| 254 | d['min_size'] = d['offset'] | ||
| 255 | if d['ptable_format'] == 'gpt': | ||
| 256 | # Account for the backup partition table at the end of the disk | ||
| 257 | d['min_size'] += GPT_OVERHEAD | ||
| 258 | |||
| 259 | d['min_size'] *= self.sector_size | ||
| 260 | |||
| 261 | def __run_parted(self, args): | ||
| 262 | """ Run parted with arguments specified in the 'args' list. """ | ||
| 263 | |||
| 264 | args.insert(0, self.parted) | ||
| 265 | msger.debug(args) | ||
| 266 | |||
| 267 | rc, out = runner.runtool(args, catch = 3) | ||
| 268 | out = out.strip() | ||
| 269 | if out: | ||
| 270 | msger.debug('"parted" output: %s' % out) | ||
| 271 | |||
| 272 | if rc != 0: | ||
| 273 | # We don't throw exception when return code is not 0, because | ||
| 274 | # parted always fails to reload part table with loop devices. This | ||
| 275 | # prevents us from distinguishing real errors based on return | ||
| 276 | # code. | ||
| 277 | msger.debug("WARNING: parted returned '%s' instead of 0" % rc) | ||
| 278 | |||
| 279 | def __create_partition(self, device, parttype, fstype, start, size): | ||
| 280 | """ Create a partition on an image described by the 'device' object. """ | ||
| 281 | |||
| 282 | # Start is included to the size so we need to substract one from the end. | ||
| 283 | end = start + size - 1 | ||
| 284 | msger.debug("Added '%s' partition, sectors %d-%d, size %d sectors" % | ||
| 285 | (parttype, start, end, size)) | ||
| 286 | |||
| 287 | args = ["-s", device, "unit", "s", "mkpart", parttype] | ||
| 288 | if fstype: | ||
| 289 | args.extend([fstype]) | ||
| 290 | args.extend(["%d" % start, "%d" % end]) | ||
| 291 | |||
| 292 | return self.__run_parted(args) | ||
| 293 | |||
| 294 | def __format_disks(self): | ||
| 295 | self.layout_partitions() | ||
| 296 | |||
| 297 | if self.skipformat: | ||
| 298 | msger.debug("Skipping disk format, because skipformat flag is set.") | ||
| 299 | return | ||
| 300 | |||
| 301 | for dev in self.disks.keys(): | ||
| 302 | d = self.disks[dev] | ||
| 303 | msger.debug("Initializing partition table for %s" % \ | ||
| 304 | (d['disk'].device)) | ||
| 305 | self.__run_parted(["-s", d['disk'].device, "mklabel", | ||
| 306 | d['ptable_format']]) | ||
| 307 | |||
| 308 | msger.debug("Creating partitions") | ||
| 309 | |||
| 310 | for p in self.partitions: | ||
| 311 | d = self.disks[p['disk_name']] | ||
| 312 | if d['ptable_format'] == "msdos" and p['num'] == 5: | ||
| 313 | # The last sector of the 3rd partition was reserved for the EBR | ||
| 314 | # of the first _logical_ partition. This is why the extended | ||
| 315 | # partition should start one sector before the first logical | ||
| 316 | # partition. | ||
| 317 | self.__create_partition(d['disk'].device, "extended", | ||
| 318 | None, p['start'] - 1, | ||
| 319 | d['offset'] - p['start']) | ||
| 320 | |||
| 321 | if p['fstype'] == "swap": | ||
| 322 | parted_fs_type = "linux-swap" | ||
| 323 | elif p['fstype'] == "vfat": | ||
| 324 | parted_fs_type = "fat32" | ||
| 325 | elif p['fstype'] == "msdos": | ||
| 326 | parted_fs_type = "fat16" | ||
| 327 | else: | ||
| 328 | # Type for ext2/ext3/ext4/btrfs | ||
| 329 | parted_fs_type = "ext2" | ||
| 330 | |||
| 331 | # Boot ROM of OMAP boards require vfat boot partition to have an | ||
| 332 | # even number of sectors. | ||
| 333 | if p['mountpoint'] == "/boot" and p['fstype'] in ["vfat", "msdos"] \ | ||
| 334 | and p['size'] % 2: | ||
| 335 | msger.debug("Substracting one sector from '%s' partition to " \ | ||
| 336 | "get even number of sectors for the partition" % \ | ||
| 337 | p['mountpoint']) | ||
| 338 | p['size'] -= 1 | ||
| 339 | |||
| 340 | self.__create_partition(d['disk'].device, p['type'], | ||
| 341 | parted_fs_type, p['start'], p['size']) | ||
| 342 | |||
| 343 | if p['boot']: | ||
| 344 | if d['ptable_format'] == 'gpt': | ||
| 345 | flag_name = "legacy_boot" | ||
| 346 | else: | ||
| 347 | flag_name = "boot" | ||
| 348 | msger.debug("Set '%s' flag for partition '%s' on disk '%s'" % \ | ||
| 349 | (flag_name, p['num'], d['disk'].device)) | ||
| 350 | self.__run_parted(["-s", d['disk'].device, "set", | ||
| 351 | "%d" % p['num'], flag_name, "on"]) | ||
| 352 | |||
| 353 | # If the partition table format is "gpt", find out PARTUUIDs for all | ||
| 354 | # the partitions. And if users specified custom parition type UUIDs, | ||
| 355 | # set them. | ||
| 356 | for disk_name, disk in self.disks.items(): | ||
| 357 | if disk['ptable_format'] != 'gpt': | ||
| 358 | continue | ||
| 359 | |||
| 360 | pnum = 0 | ||
| 361 | gpt_parser = GptParser(d['disk'].device, SECTOR_SIZE) | ||
| 362 | # Iterate over all GPT partitions on this disk | ||
| 363 | for entry in gpt_parser.get_partitions(): | ||
| 364 | pnum += 1 | ||
| 365 | # Find the matching partition in the 'self.partitions' list | ||
| 366 | for n in d['partitions']: | ||
| 367 | p = self.partitions[n] | ||
| 368 | if p['num'] == pnum: | ||
| 369 | # Found, fetch PARTUUID (partition's unique ID) | ||
| 370 | p['partuuid'] = entry['part_uuid'] | ||
| 371 | msger.debug("PARTUUID for partition %d on disk '%s' " \ | ||
| 372 | "(mount point '%s') is '%s'" % (pnum, \ | ||
| 373 | disk_name, p['mountpoint'], p['partuuid'])) | ||
| 374 | if p['part_type']: | ||
| 375 | entry['type_uuid'] = p['part_type'] | ||
| 376 | msger.debug("Change type of partition %d on disk " \ | ||
| 377 | "'%s' (mount point '%s') to '%s'" % \ | ||
| 378 | (pnum, disk_name, p['mountpoint'], | ||
| 379 | p['part_type'])) | ||
| 380 | gpt_parser.change_partition(entry) | ||
| 381 | |||
| 382 | del gpt_parser | ||
| 383 | |||
| 384 | def __map_partitions(self): | ||
| 385 | """Load it if dm_snapshot isn't loaded. """ | ||
| 386 | load_module("dm_snapshot") | ||
| 387 | |||
| 388 | for dev in self.disks.keys(): | ||
| 389 | d = self.disks[dev] | ||
| 390 | if d['mapped']: | ||
| 391 | continue | ||
| 392 | |||
| 393 | msger.debug("Running kpartx on %s" % d['disk'].device ) | ||
| 394 | rc, kpartxOutput = runner.runtool([self.kpartx, "-l", "-v", d['disk'].device]) | ||
| 395 | kpartxOutput = kpartxOutput.splitlines() | ||
| 396 | |||
| 397 | if rc != 0: | ||
| 398 | raise MountError("Failed to query partition mapping for '%s'" % | ||
| 399 | d['disk'].device) | ||
| 400 | |||
| 401 | # Strip trailing blank and mask verbose output | ||
| 402 | i = 0 | ||
| 403 | while i < len(kpartxOutput) and kpartxOutput[i][0:4] != "loop": | ||
| 404 | i = i + 1 | ||
| 405 | kpartxOutput = kpartxOutput[i:] | ||
| 406 | |||
| 407 | # Make sure kpartx reported the right count of partitions | ||
| 408 | if len(kpartxOutput) != d['numpart']: | ||
| 409 | # If this disk has more than 3 partitions, then in case of MBR | ||
| 410 | # paritions there is an extended parition. Different versions | ||
| 411 | # of kpartx behave differently WRT the extended partition - | ||
| 412 | # some map it, some ignore it. This is why we do the below hack | ||
| 413 | # - if kpartx reported one more partition and the partition | ||
| 414 | # table type is "msdos" and the amount of partitions is more | ||
| 415 | # than 3, we just assume kpartx mapped the extended parition | ||
| 416 | # and we remove it. | ||
| 417 | if len(kpartxOutput) == d['numpart'] + 1 \ | ||
| 418 | and d['ptable_format'] == 'msdos' and len(kpartxOutput) > 3: | ||
| 419 | kpartxOutput.pop(3) | ||
| 420 | else: | ||
| 421 | raise MountError("Unexpected number of partitions from " \ | ||
| 422 | "kpartx: %d != %d" % \ | ||
| 423 | (len(kpartxOutput), d['numpart'])) | ||
| 424 | |||
| 425 | for i in range(len(kpartxOutput)): | ||
| 426 | line = kpartxOutput[i] | ||
| 427 | newdev = line.split()[0] | ||
| 428 | mapperdev = "/dev/mapper/" + newdev | ||
| 429 | loopdev = d['disk'].device + newdev[-1] | ||
| 430 | |||
| 431 | msger.debug("Dev %s: %s -> %s" % (newdev, loopdev, mapperdev)) | ||
| 432 | pnum = d['partitions'][i] | ||
| 433 | self.partitions[pnum]['device'] = loopdev | ||
| 434 | |||
| 435 | # grub's install wants partitions to be named | ||
| 436 | # to match their parent device + partition num | ||
| 437 | # kpartx doesn't work like this, so we add compat | ||
| 438 | # symlinks to point to /dev/mapper | ||
| 439 | if os.path.lexists(loopdev): | ||
| 440 | os.unlink(loopdev) | ||
| 441 | os.symlink(mapperdev, loopdev) | ||
| 442 | |||
| 443 | msger.debug("Adding partx mapping for %s" % d['disk'].device) | ||
| 444 | rc = runner.show([self.kpartx, "-v", "-a", d['disk'].device]) | ||
| 445 | |||
| 446 | if rc != 0: | ||
| 447 | # Make sure that the device maps are also removed on error case. | ||
| 448 | # The d['mapped'] isn't set to True if the kpartx fails so | ||
| 449 | # failed mapping will not be cleaned on cleanup either. | ||
| 450 | runner.quiet([self.kpartx, "-d", d['disk'].device]) | ||
| 451 | raise MountError("Failed to map partitions for '%s'" % | ||
| 452 | d['disk'].device) | ||
| 453 | |||
| 454 | # FIXME: there is a bit delay for multipath device setup, | ||
| 455 | # wait 10ms for the setup | ||
| 456 | import time | ||
| 457 | time.sleep(10) | ||
| 458 | d['mapped'] = True | ||
| 459 | |||
| 460 | def __unmap_partitions(self): | ||
| 461 | for dev in self.disks.keys(): | ||
| 462 | d = self.disks[dev] | ||
| 463 | if not d['mapped']: | ||
| 464 | continue | ||
| 465 | |||
| 466 | msger.debug("Removing compat symlinks") | ||
| 467 | for pnum in d['partitions']: | ||
| 468 | if self.partitions[pnum]['device'] != None: | ||
| 469 | os.unlink(self.partitions[pnum]['device']) | ||
| 470 | self.partitions[pnum]['device'] = None | ||
| 471 | |||
| 472 | msger.debug("Unmapping %s" % d['disk'].device) | ||
| 473 | rc = runner.quiet([self.kpartx, "-d", d['disk'].device]) | ||
| 474 | if rc != 0: | ||
| 475 | raise MountError("Failed to unmap partitions for '%s'" % | ||
| 476 | d['disk'].device) | ||
| 477 | |||
| 478 | d['mapped'] = False | ||
| 479 | |||
| 480 | def __calculate_mountorder(self): | ||
| 481 | msger.debug("Calculating mount order") | ||
| 482 | for p in self.partitions: | ||
| 483 | if p['mountpoint']: | ||
| 484 | self.mountOrder.append(p['mountpoint']) | ||
| 485 | self.unmountOrder.append(p['mountpoint']) | ||
| 486 | |||
| 487 | self.mountOrder.sort() | ||
| 488 | self.unmountOrder.sort() | ||
| 489 | self.unmountOrder.reverse() | ||
| 490 | |||
| 491 | def cleanup(self): | ||
| 492 | Mount.cleanup(self) | ||
| 493 | if self.disks: | ||
| 494 | self.__unmap_partitions() | ||
| 495 | for dev in self.disks.keys(): | ||
| 496 | d = self.disks[dev] | ||
| 497 | try: | ||
| 498 | d['disk'].cleanup() | ||
| 499 | except: | ||
| 500 | pass | ||
| 501 | |||
| 502 | def unmount(self): | ||
| 503 | self.__unmount_subvolumes() | ||
| 504 | for mp in self.unmountOrder: | ||
| 505 | if mp == 'swap': | ||
| 506 | continue | ||
| 507 | p = None | ||
| 508 | for p1 in self.partitions: | ||
| 509 | if p1['mountpoint'] == mp: | ||
| 510 | p = p1 | ||
| 511 | break | ||
| 512 | |||
| 513 | if p['mount'] != None: | ||
| 514 | try: | ||
| 515 | # Create subvolume snapshot here | ||
| 516 | if p['fstype'] == "btrfs" and p['mountpoint'] == "/" and not self.snapshot_created: | ||
| 517 | self.__create_subvolume_snapshots(p, p["mount"]) | ||
| 518 | p['mount'].cleanup() | ||
| 519 | except: | ||
| 520 | pass | ||
| 521 | p['mount'] = None | ||
| 522 | |||
| 523 | # Only for btrfs | ||
| 524 | def __get_subvolume_id(self, rootpath, subvol): | ||
| 525 | if not self.btrfscmd: | ||
| 526 | self.btrfscmd=find_binary_path("btrfs") | ||
| 527 | argv = [ self.btrfscmd, "subvolume", "list", rootpath ] | ||
| 528 | |||
| 529 | rc, out = runner.runtool(argv) | ||
| 530 | msger.debug(out) | ||
| 531 | |||
| 532 | if rc != 0: | ||
| 533 | raise MountError("Failed to get subvolume id from %s', return code: %d." % (rootpath, rc)) | ||
| 534 | |||
| 535 | subvolid = -1 | ||
| 536 | for line in out.splitlines(): | ||
| 537 | if line.endswith(" path %s" % subvol): | ||
| 538 | subvolid = line.split()[1] | ||
| 539 | if not subvolid.isdigit(): | ||
| 540 | raise MountError("Invalid subvolume id: %s" % subvolid) | ||
| 541 | subvolid = int(subvolid) | ||
| 542 | break | ||
| 543 | return subvolid | ||
| 544 | |||
| 545 | def __create_subvolume_metadata(self, p, pdisk): | ||
| 546 | if len(self.subvolumes) == 0: | ||
| 547 | return | ||
| 548 | |||
| 549 | argv = [ self.btrfscmd, "subvolume", "list", pdisk.mountdir ] | ||
| 550 | rc, out = runner.runtool(argv) | ||
| 551 | msger.debug(out) | ||
| 552 | |||
| 553 | if rc != 0: | ||
| 554 | raise MountError("Failed to get subvolume id from %s', return code: %d." % (pdisk.mountdir, rc)) | ||
| 555 | |||
| 556 | subvolid_items = out.splitlines() | ||
| 557 | subvolume_metadata = "" | ||
| 558 | for subvol in self.subvolumes: | ||
| 559 | for line in subvolid_items: | ||
| 560 | if line.endswith(" path %s" % subvol["subvol"]): | ||
| 561 | subvolid = line.split()[1] | ||
| 562 | if not subvolid.isdigit(): | ||
| 563 | raise MountError("Invalid subvolume id: %s" % subvolid) | ||
| 564 | |||
| 565 | subvolid = int(subvolid) | ||
| 566 | opts = subvol["fsopts"].split(",") | ||
| 567 | for opt in opts: | ||
| 568 | if opt.strip().startswith("subvol="): | ||
| 569 | opts.remove(opt) | ||
| 570 | break | ||
| 571 | fsopts = ",".join(opts) | ||
| 572 | subvolume_metadata += "%d\t%s\t%s\t%s\n" % (subvolid, subvol["subvol"], subvol['mountpoint'], fsopts) | ||
| 573 | |||
| 574 | if subvolume_metadata: | ||
| 575 | fd = open("%s/.subvolume_metadata" % pdisk.mountdir, "w") | ||
| 576 | fd.write(subvolume_metadata) | ||
| 577 | fd.close() | ||
| 578 | |||
| 579 | def __get_subvolume_metadata(self, p, pdisk): | ||
| 580 | subvolume_metadata_file = "%s/.subvolume_metadata" % pdisk.mountdir | ||
| 581 | if not os.path.exists(subvolume_metadata_file): | ||
| 582 | return | ||
| 583 | |||
| 584 | fd = open(subvolume_metadata_file, "r") | ||
| 585 | content = fd.read() | ||
| 586 | fd.close() | ||
| 587 | |||
| 588 | for line in content.splitlines(): | ||
| 589 | items = line.split("\t") | ||
| 590 | if items and len(items) == 4: | ||
| 591 | self.subvolumes.append({'size': 0, # In sectors | ||
| 592 | 'mountpoint': items[2], # Mount relative to chroot | ||
| 593 | 'fstype': "btrfs", # Filesystem type | ||
| 594 | 'fsopts': items[3] + ",subvol=%s" % items[1], # Filesystem mount options | ||
| 595 | 'disk_name': p['disk_name'], # physical disk name holding partition | ||
| 596 | 'device': None, # kpartx device node for partition | ||
| 597 | 'mount': None, # Mount object | ||
| 598 | 'subvol': items[1], # Subvolume name | ||
| 599 | 'boot': False, # Bootable flag | ||
| 600 | 'mounted': False # Mount flag | ||
| 601 | }) | ||
| 602 | |||
| 603 | def __create_subvolumes(self, p, pdisk): | ||
| 604 | """ Create all the subvolumes. """ | ||
| 605 | |||
| 606 | for subvol in self.subvolumes: | ||
| 607 | argv = [ self.btrfscmd, "subvolume", "create", pdisk.mountdir + "/" + subvol["subvol"]] | ||
| 608 | |||
| 609 | rc = runner.show(argv) | ||
| 610 | if rc != 0: | ||
| 611 | raise MountError("Failed to create subvolume '%s', return code: %d." % (subvol["subvol"], rc)) | ||
| 612 | |||
| 613 | # Set default subvolume, subvolume for "/" is default | ||
| 614 | subvol = None | ||
| 615 | for subvolume in self.subvolumes: | ||
| 616 | if subvolume["mountpoint"] == "/" and p['disk_name'] == subvolume['disk_name']: | ||
| 617 | subvol = subvolume | ||
| 618 | break | ||
| 619 | |||
| 620 | if subvol: | ||
| 621 | # Get default subvolume id | ||
| 622 | subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"]) | ||
| 623 | # Set default subvolume | ||
| 624 | if subvolid != -1: | ||
| 625 | rc = runner.show([ self.btrfscmd, "subvolume", "set-default", "%d" % subvolid, pdisk.mountdir]) | ||
| 626 | if rc != 0: | ||
| 627 | raise MountError("Failed to set default subvolume id: %d', return code: %d." % (subvolid, rc)) | ||
| 628 | |||
| 629 | self.__create_subvolume_metadata(p, pdisk) | ||
| 630 | |||
| 631 | def __mount_subvolumes(self, p, pdisk): | ||
| 632 | if self.skipformat: | ||
| 633 | # Get subvolume info | ||
| 634 | self.__get_subvolume_metadata(p, pdisk) | ||
| 635 | # Set default mount options | ||
| 636 | if len(self.subvolumes) != 0: | ||
| 637 | for subvol in self.subvolumes: | ||
| 638 | if subvol["mountpoint"] == p["mountpoint"] == "/": | ||
| 639 | opts = subvol["fsopts"].split(",") | ||
| 640 | for opt in opts: | ||
| 641 | if opt.strip().startswith("subvol="): | ||
| 642 | opts.remove(opt) | ||
| 643 | break | ||
| 644 | pdisk.fsopts = ",".join(opts) | ||
| 645 | break | ||
| 646 | |||
| 647 | if len(self.subvolumes) == 0: | ||
| 648 | # Return directly if no subvolumes | ||
| 649 | return | ||
| 650 | |||
| 651 | # Remount to make default subvolume mounted | ||
| 652 | rc = runner.show([self.umountcmd, pdisk.mountdir]) | ||
| 653 | if rc != 0: | ||
| 654 | raise MountError("Failed to umount %s" % pdisk.mountdir) | ||
| 655 | |||
| 656 | rc = runner.show([self.mountcmd, "-o", pdisk.fsopts, pdisk.disk.device, pdisk.mountdir]) | ||
| 657 | if rc != 0: | ||
| 658 | raise MountError("Failed to umount %s" % pdisk.mountdir) | ||
| 659 | |||
| 660 | for subvol in self.subvolumes: | ||
| 661 | if subvol["mountpoint"] == "/": | ||
| 662 | continue | ||
| 663 | subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"]) | ||
| 664 | if subvolid == -1: | ||
| 665 | msger.debug("WARNING: invalid subvolume %s" % subvol["subvol"]) | ||
| 666 | continue | ||
| 667 | # Replace subvolume name with subvolume ID | ||
| 668 | opts = subvol["fsopts"].split(",") | ||
| 669 | for opt in opts: | ||
| 670 | if opt.strip().startswith("subvol="): | ||
| 671 | opts.remove(opt) | ||
| 672 | break | ||
| 673 | |||
| 674 | opts.extend(["subvolrootid=0", "subvol=%s" % subvol["subvol"]]) | ||
| 675 | fsopts = ",".join(opts) | ||
| 676 | subvol['fsopts'] = fsopts | ||
| 677 | mountpoint = self.mountdir + subvol['mountpoint'] | ||
| 678 | makedirs(mountpoint) | ||
| 679 | rc = runner.show([self.mountcmd, "-o", fsopts, pdisk.disk.device, mountpoint]) | ||
| 680 | if rc != 0: | ||
| 681 | raise MountError("Failed to mount subvolume %s to %s" % (subvol["subvol"], mountpoint)) | ||
| 682 | subvol["mounted"] = True | ||
| 683 | |||
| 684 | def __unmount_subvolumes(self): | ||
| 685 | """ It may be called multiple times, so we need to chekc if it is still mounted. """ | ||
| 686 | for subvol in self.subvolumes: | ||
| 687 | if subvol["mountpoint"] == "/": | ||
| 688 | continue | ||
| 689 | if not subvol["mounted"]: | ||
| 690 | continue | ||
| 691 | mountpoint = self.mountdir + subvol['mountpoint'] | ||
| 692 | rc = runner.show([self.umountcmd, mountpoint]) | ||
| 693 | if rc != 0: | ||
| 694 | raise MountError("Failed to unmount subvolume %s from %s" % (subvol["subvol"], mountpoint)) | ||
| 695 | subvol["mounted"] = False | ||
| 696 | |||
| 697 | def __create_subvolume_snapshots(self, p, pdisk): | ||
| 698 | import time | ||
| 699 | |||
| 700 | if self.snapshot_created: | ||
| 701 | return | ||
| 702 | |||
| 703 | # Remount with subvolid=0 | ||
| 704 | rc = runner.show([self.umountcmd, pdisk.mountdir]) | ||
| 705 | if rc != 0: | ||
| 706 | raise MountError("Failed to umount %s" % pdisk.mountdir) | ||
| 707 | if pdisk.fsopts: | ||
| 708 | mountopts = pdisk.fsopts + ",subvolid=0" | ||
| 709 | else: | ||
| 710 | mountopts = "subvolid=0" | ||
| 711 | rc = runner.show([self.mountcmd, "-o", mountopts, pdisk.disk.device, pdisk.mountdir]) | ||
| 712 | if rc != 0: | ||
| 713 | raise MountError("Failed to umount %s" % pdisk.mountdir) | ||
| 714 | |||
| 715 | # Create all the subvolume snapshots | ||
| 716 | snapshotts = time.strftime("%Y%m%d-%H%M") | ||
| 717 | for subvol in self.subvolumes: | ||
| 718 | subvolpath = pdisk.mountdir + "/" + subvol["subvol"] | ||
| 719 | snapshotpath = subvolpath + "_%s-1" % snapshotts | ||
| 720 | rc = runner.show([ self.btrfscmd, "subvolume", "snapshot", subvolpath, snapshotpath ]) | ||
| 721 | if rc != 0: | ||
| 722 | raise MountError("Failed to create subvolume snapshot '%s' for '%s', return code: %d." % (snapshotpath, subvolpath, rc)) | ||
| 723 | |||
| 724 | self.snapshot_created = True | ||
| 725 | |||
| 726 | def mount(self): | ||
| 727 | for dev in self.disks.keys(): | ||
| 728 | d = self.disks[dev] | ||
| 729 | d['disk'].create() | ||
| 730 | |||
| 731 | self.__format_disks() | ||
| 732 | self.__map_partitions() | ||
| 733 | self.__calculate_mountorder() | ||
| 734 | |||
| 735 | for mp in self.mountOrder: | ||
| 736 | p = None | ||
| 737 | for p1 in self.partitions: | ||
| 738 | if p1['mountpoint'] == mp: | ||
| 739 | p = p1 | ||
| 740 | break | ||
| 741 | |||
| 742 | if not p['label']: | ||
| 743 | if p['mountpoint'] == "/": | ||
| 744 | p['label'] = 'platform' | ||
| 745 | else: | ||
| 746 | p['label'] = mp.split('/')[-1] | ||
| 747 | |||
| 748 | if mp == 'swap': | ||
| 749 | import uuid | ||
| 750 | p['uuid'] = str(uuid.uuid1()) | ||
| 751 | runner.show([self.mkswap, | ||
| 752 | '-L', p['label'], | ||
| 753 | '-U', p['uuid'], | ||
| 754 | p['device']]) | ||
| 755 | continue | ||
| 756 | |||
| 757 | rmmountdir = False | ||
| 758 | if p['mountpoint'] == "/": | ||
| 759 | rmmountdir = True | ||
| 760 | if p['fstype'] == "vfat" or p['fstype'] == "msdos": | ||
| 761 | myDiskMount = VfatDiskMount | ||
| 762 | elif p['fstype'] in ("ext2", "ext3", "ext4"): | ||
| 763 | myDiskMount = ExtDiskMount | ||
| 764 | elif p['fstype'] == "btrfs": | ||
| 765 | myDiskMount = BtrfsDiskMount | ||
| 766 | else: | ||
| 767 | raise MountError("Fail to support file system " + p['fstype']) | ||
| 768 | |||
| 769 | if p['fstype'] == "btrfs" and not p['fsopts']: | ||
| 770 | p['fsopts'] = "subvolid=0" | ||
| 771 | |||
| 772 | pdisk = myDiskMount(RawDisk(p['size'] * self.sector_size, p['device']), | ||
| 773 | self.mountdir + p['mountpoint'], | ||
| 774 | p['fstype'], | ||
| 775 | 4096, | ||
| 776 | p['label'], | ||
| 777 | rmmountdir, | ||
| 778 | self.skipformat, | ||
| 779 | fsopts = p['fsopts']) | ||
| 780 | pdisk.mount(pdisk.fsopts) | ||
| 781 | if p['fstype'] == "btrfs" and p['mountpoint'] == "/": | ||
| 782 | if not self.skipformat: | ||
| 783 | self.__create_subvolumes(p, pdisk) | ||
| 784 | self.__mount_subvolumes(p, pdisk) | ||
| 785 | p['mount'] = pdisk | ||
| 786 | p['uuid'] = pdisk.uuid | ||
| 787 | |||
| 788 | def resparse(self, size = None): | ||
| 789 | # Can't re-sparse a disk image - too hard | ||
| 790 | pass | ||
