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