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