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