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.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 @@
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
28
29# Overhead of the MBR partitioning scheme (just one sector)
30MBR_OVERHEAD = 1
31# Overhead of the GPT partitioning scheme
32GPT_OVERHEAD = 34
33
34# Size of a sector in bytes
35SECTOR_SIZE = 512
36
37class 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