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