Updated chroot's cleanup function
[tools/mic.git] / mic / utils / partitionedfs.py
1 #
2 # partitionedfs.py: partitioned files system class, extends fs.py
3 #
4 # Copyright 2007-2008, Red Hat  Inc.
5 # Copyright 2008, Daniel P. Berrange
6 # Copyright 2008,  David P. Huff
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; version 2 of the License.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU Library General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
21 import os
22 import os.path
23 import glob
24 import shutil
25 import subprocess
26 import logging
27 import time
28
29 from mic.utils.errors import *
30 from mic.utils.fs_related import *
31
32 class PartitionedMount(Mount):
33     def __init__(self, disks, mountdir, skipformat = False):
34         Mount.__init__(self, mountdir)
35         self.disks = {}
36         for name in disks.keys():
37             self.disks[name] = { 'disk': disks[name],  # Disk object
38                                  'mapped': False, # True if kpartx mapping exists
39                                  'numpart': 0, # Number of allocate partitions
40                                  'partitions': [], # indexes to self.partitions
41                                  # Partitions with part num higher than 3 will 
42                                  # be put inside extended partition.
43                                  'extended': 0, # Size of extended partition
44                                  # Sector 0 is used by the MBR and can't be used
45                                  # as the start, so setting offset to 1.
46                                  'offset': 1 } # Offset of next partition (in sectors)
47
48         self.partitions = []
49         self.subvolumes = []
50         self.mapped = False
51         self.mountOrder = []
52         self.unmountOrder = []
53         self.parted=find_binary_path("parted")
54         self.kpartx=find_binary_path("kpartx")
55         self.mkswap=find_binary_path("mkswap")
56         self.btrfscmd=None
57         self.mountcmd=find_binary_path("mount")
58         self.umountcmd=find_binary_path("umount")
59         self.skipformat = skipformat
60         self.snapshot_created = self.skipformat
61         # Size of a sector used in calculations
62         self.sector_size = 512
63
64     def add_partition(self, size, disk, mountpoint, fstype = None, fsopts = None, boot = False):
65         # Converting M to s for parted
66         size = size * 1024 * 1024 / self.sector_size
67
68         """ We need to handle subvolumes for btrfs """
69         if fstype == "btrfs" and fsopts and fsopts.find("subvol=") != -1:
70             self.btrfscmd=find_binary_path("btrfs")
71             subvol = None
72             opts = fsopts.split(",")
73             for opt in opts:
74                 if opt.find("subvol=") != -1:
75                     subvol = opt.replace("subvol=", "").strip()
76                     break
77             if not subvol:
78                 raise MountError("No subvolume: %s" % fsopts)
79             self.subvolumes.append({'size': size, # In sectors
80                                     'mountpoint': mountpoint, # Mount relative to chroot
81                                     'fstype': fstype, # Filesystem type
82                                     'fsopts': fsopts, # Filesystem mount options
83                                     'disk': disk, # physical disk name holding partition
84                                     'device': None, # kpartx device node for partition
85                                     'mount': None, # Mount object
86                                     'subvol': subvol, # Subvolume name
87                                     'boot': boot, # Bootable flag
88                                     'mounted': False # Mount flag
89                                    })
90
91         """ We still need partition for "/" or non-subvolume """
92         if mountpoint == "/" or not fsopts or fsopts.find("subvol=") == -1:
93             """ Don't need subvolume for "/" because it will be set as default subvolume """
94             if fsopts and fsopts.find("subvol=") != -1:
95                 opts = fsopts.split(",")
96                 for opt in opts:
97                     if opt.strip().startswith("subvol="):
98                         opts.remove(opt)
99                         break
100                 fsopts = ",".join(opts)
101             self.partitions.append({'size': size, # In sectors
102                                     'mountpoint': mountpoint, # Mount relative to chroot
103                                     'fstype': fstype, # Filesystem type
104                                     'fsopts': fsopts, # Filesystem mount options
105                                     'disk': disk, # physical disk name holding partition
106                                     'device': None, # kpartx device node for partition
107                                     'mount': None, # Mount object
108                                     'num': None, # Partition number
109                                     'boot': boot}) # Bootable flag
110
111     def __create_part_to_image(self,device, parttype, fstype, start, size):
112         # Start is included to the size so we need to substract one from the end.
113         end = start+size-1
114         logging.debug("Added '%s' part at %d of size %d" % (parttype,start,end))
115         part_cmd = [self.parted, "-s", device, "unit", "s", "mkpart", parttype]
116         if fstype:
117             part_cmd.extend([fstype])
118         part_cmd.extend(["%d" % start, "%d" % end])
119         logging.debug(part_cmd)
120         p1 = subprocess.Popen(part_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
121         (out,err) = p1.communicate()
122         logging.debug(out)
123         return p1
124
125     def __format_disks(self):
126         logging.debug("Assigning partitions to disks")
127         
128         mbr_sector_skipped = False
129         
130         for n in range(len(self.partitions)):
131             p = self.partitions[n]
132
133             if not self.disks.has_key(p['disk']):
134                 raise MountError("No disk %s for partition %s" % (p['disk'], p['mountpoint']))
135             
136             if not mbr_sector_skipped:
137                 # This hack is used to remove one sector from the first partition,
138                 # that is the used to the MBR.
139                 p['size'] -= 1
140                 mbr_sector_skipped = True
141
142             d = self.disks[p['disk']]
143             d['numpart'] += 1
144             if d['numpart'] > 3:
145                 # Increase allocation of extended partition to hold this partition
146                 d['extended'] += p['size']
147                 p['type'] = 'logical'
148                 p['num'] = d['numpart'] + 1
149             else:
150                 p['type'] = 'primary'
151                 p['num'] = d['numpart']
152
153             p['start'] = d['offset']
154             d['offset'] += p['size']
155             d['partitions'].append(n)
156             logging.debug("Assigned %s to %s%d at %d at size %d" % (p['mountpoint'], p['disk'], p['num'], p['start'], p['size']))
157
158         if self.skipformat:
159             logging.debug("Skipping disk format, because skipformat flag is set.")
160             return
161             
162         for dev in self.disks.keys():
163             d = self.disks[dev]
164             logging.debug("Initializing partition table for %s" % (d['disk'].device))
165             p1 = subprocess.Popen([self.parted, "-s", d['disk'].device, "mklabel", "msdos"],
166                                  stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
167             (out,err) = p1.communicate()            
168             logging.debug(out)
169             
170             if p1.returncode != 0:
171                 # NOTE: We don't throw exception when return code is not 0, because
172                 # parted always fails to reload part table with loop devices.
173                 # This prevents us from distinguishing real errors based on return code.
174                 logging.debug("WARNING: parted returned '%s' instead of 0 when creating partition-table for disk '%s'." % (p1.returncode,d['disk'].device))
175
176         logging.debug("Creating partitions")
177
178         for p in self.partitions:
179             d = self.disks[p['disk']]
180             if p['num'] == 5:
181                 self.__create_part_to_image(d['disk'].device,"extended",None,p['start'],d['extended'])
182                 
183             if p['fstype'] == "swap":
184                 parted_fs_type = "linux-swap"
185             elif p['fstype'] == "vfat":
186                 parted_fs_type = "fat32"
187             elif p['fstype'] == "msdos":
188                 parted_fs_type = "fat16"
189             else:
190                 # Type for ext2/ext3/ext4/btrfs
191                 parted_fs_type = "ext2"
192
193             # Boot ROM of OMAP boards require vfat boot partition to have an 
194             # even number of sectors.
195             if p['mountpoint'] == "/boot" and p['fstype'] in ["vfat","msdos"] and p['size'] % 2:
196                 logging.debug("Substracting one sector from '%s' partition to get even number of sectors for the partition." % (p['mountpoint']))
197                 p['size'] -= 1
198                 
199             p1 = self.__create_part_to_image(d['disk'].device,p['type'], 
200                                              parted_fs_type, p['start'], 
201                                              p['size'])
202
203             if p1.returncode != 0:
204                 # NOTE: We don't throw exception when return code is not 0, because
205                 # parted always fails to reload part table with loop devices.
206                 # This prevents us from distinguishing real errors based on return code.
207                 logging.debug("WARNING: parted returned '%s' instead of 0 when creating partition '%s' for disk '%s'." % (p1.returncode,p['mountpoint'],d['disk'].device))
208
209             if p['boot']:
210                 logging.debug("Setting boot flag for partition '%s' on disk '%s'." % (p['num'],d['disk'].device))
211                 boot_cmd = [self.parted, "-s", d['disk'].device, "set", "%d" % p['num'], "boot", "on"]
212                 logging.debug(boot_cmd)
213                 p1 = subprocess.Popen(boot_cmd,
214                                       stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
215                 (out,err) = p1.communicate()            
216                 logging.debug(out)
217
218                 if p1.returncode != 0:
219                     # NOTE: We don't throw exception when return code is not 0, because
220                     # parted always fails to reload part table with loop devices.
221                     # This prevents us from distinguishing real errors based on return code.
222                     logging.debug("WARNING: parted returned '%s' instead of 0 when adding boot flag for partition '%s' disk '%s'." % (p1.returncode,p['num'],d['disk'].device))
223
224     def __map_partitions(self):
225         """Load it if dm_snapshot isn't loaded"""
226         load_module("dm_snapshot")
227
228         dev_null = os.open("/dev/null", os.O_WRONLY)
229         for dev in self.disks.keys():
230             d = self.disks[dev]
231             if d['mapped']:
232                 continue
233
234             logging.debug("Running kpartx on %s" % d['disk'].device )
235             kpartx = subprocess.Popen([self.kpartx, "-l", "-v", d['disk'].device],
236                                       stdout=subprocess.PIPE, stderr=dev_null)
237
238             kpartxOutput = kpartx.communicate()[0].strip().split("\n")
239
240             if kpartx.returncode:
241                 os.close(dev_null)
242                 raise MountError("Failed to query partition mapping for '%s'" %
243                                  d['disk'].device)
244
245             # Strip trailing blank and mask verbose output
246             i = 0
247             while i < len(kpartxOutput) and kpartxOutput[i][0:4] != "loop":
248                i = i + 1
249             kpartxOutput = kpartxOutput[i:]
250
251             # Quick sanity check that the number of partitions matches
252             # our expectation. If it doesn't, someone broke the code
253             # further up
254             if len(kpartxOutput) != d['numpart']:
255                 os.close(dev_null)
256                 raise MountError("Unexpected number of partitions from kpartx: %d != %d" %
257                                  (len(kpartxOutput), d['numpart']))
258
259             for i in range(len(kpartxOutput)):
260                 line = kpartxOutput[i]
261                 newdev = line.split()[0]
262                 mapperdev = "/dev/mapper/" + newdev
263                 loopdev = d['disk'].device + newdev[-1]
264
265                 logging.debug("Dev %s: %s -> %s" % (newdev, loopdev, mapperdev))
266                 pnum = d['partitions'][i]
267                 self.partitions[pnum]['device'] = loopdev
268
269                 # grub's install wants partitions to be named
270                 # to match their parent device + partition num
271                 # kpartx doesn't work like this, so we add compat
272                 # symlinks to point to /dev/mapper
273                 if os.path.lexists(loopdev):
274                     os.unlink(loopdev)
275                 os.symlink(mapperdev, loopdev)
276
277             logging.debug("Adding partx mapping for %s" % d['disk'].device)
278             p1 = subprocess.Popen([self.kpartx, "-v", "-a", d['disk'].device],
279                                   stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
280             
281             (out,err) = p1.communicate()
282             logging.debug(out)
283             
284             if p1.returncode != 0:
285                 # Make sure that the device maps are also removed on error case.
286                 # The d['mapped'] isn't set to True if the kpartx fails so
287                 # failed mapping will not be cleaned on cleanup either.
288                 subprocess.call([self.kpartx, "-d", d['disk'].device],
289                                 stdout=dev_null, stderr=dev_null)
290                 os.close(dev_null)
291                 raise MountError("Failed to map partitions for '%s'" %
292                                  d['disk'].device)
293             d['mapped'] = True
294         os.close(dev_null)
295
296
297     def __unmap_partitions(self):
298         dev_null = os.open("/dev/null", os.O_WRONLY)
299         for dev in self.disks.keys():
300             d = self.disks[dev]
301             if not d['mapped']:
302                 continue
303
304             logging.debug("Removing compat symlinks")
305             for pnum in d['partitions']:
306                 if self.partitions[pnum]['device'] != None:
307                     os.unlink(self.partitions[pnum]['device'])
308                     self.partitions[pnum]['device'] = None
309
310             logging.debug("Unmapping %s" % d['disk'].device)
311             rc = subprocess.call([self.kpartx, "-d", d['disk'].device],
312                                  stdout=dev_null, stderr=dev_null)
313             if rc != 0:
314                 os.close(dev_null)
315                 raise MountError("Failed to unmap partitions for '%s'" %
316                                  d['disk'].device)
317
318             d['mapped'] = False
319             os.close(dev_null)
320
321
322     def __calculate_mountorder(self):
323         logging.debug("Calculating mount order")
324         for p in self.partitions:
325             self.mountOrder.append(p['mountpoint'])
326             self.unmountOrder.append(p['mountpoint'])
327
328         self.mountOrder.sort()
329         self.unmountOrder.sort()
330         self.unmountOrder.reverse()
331
332     def cleanup(self):
333         Mount.cleanup(self)
334         self.__unmap_partitions()
335         for dev in self.disks.keys():
336             d = self.disks[dev]
337             try:
338                 d['disk'].cleanup()
339             except:
340                 pass
341
342     def unmount(self):
343         self.__unmount_subvolumes()
344         for mp in self.unmountOrder:
345             if mp == 'swap':
346                 continue
347             p = None
348             for p1 in self.partitions:
349                 if p1['mountpoint'] == mp:
350                     p = p1
351                     break
352
353             if p['mount'] != None:
354                 try:
355                     """ Create subvolume snapshot here """
356                     if p['fstype'] == "btrfs" and p['mountpoint'] == "/" and not self.snapshot_created:
357                         self.__create_subvolume_snapshots(p, p["mount"])
358                     p['mount'].cleanup()
359                 except:
360                     pass
361                 p['mount'] = None
362
363     """ Only for btrfs """
364     def __get_subvolume_id(self, rootpath, subvol):
365         if not self.btrfscmd:
366             self.btrfscmd=find_binary_path("btrfs")
367         argv = [ self.btrfscmd, "subvolume", "list", rootpath ]
368         p1 = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
369         (out,err) = p1.communicate()
370         logging.debug(out)
371         if p1.returncode != 0:
372             raise MountError("Failed to get subvolume id from %s', return code: %d." % (rootpath, p1.returncode))
373         subvolid = -1
374         for line in out.split("\n"):
375             if line.endswith(" path %s" % subvol):
376                 subvolid = line.split(" ")[1]
377                 if not subvolid.isdigit():
378                     raise MountError("Invalid subvolume id: %s" % subvolid)
379                 subvolid = int(subvolid)
380                 break
381         return subvolid
382
383     def __create_subvolume_metadata(self, p, pdisk):
384         if len(self.subvolumes) == 0:
385             return
386         argv = [ self.btrfscmd, "subvolume", "list", pdisk.mountdir ]
387         p1 = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
388         (out,err) = p1.communicate()
389         logging.debug(out)
390         if p1.returncode != 0:
391             raise MountError("Failed to get subvolume id from %s', return code: %d." % (pdisk.mountdir, p1.returncode))
392         subvolid_items = out.split("\n")
393         subvolume_metadata = ""
394         for subvol in self.subvolumes:
395             for line in subvolid_items:
396                 if line.endswith(" path %s" % subvol["subvol"]):
397                     subvolid = line.split(" ")[1]
398                     if not subvolid.isdigit():
399                         raise MountError("Invalid subvolume id: %s" % subvolid)
400                     subvolid = int(subvolid)
401                     opts = subvol["fsopts"].split(",")
402                     for opt in opts:
403                         if opt.strip().startswith("subvol="):
404                             opts.remove(opt)
405                             break
406                     fsopts = ",".join(opts)
407                     subvolume_metadata += "%d\t%s\t%s\t%s\n" % (subvolid, subvol["subvol"], subvol['mountpoint'], fsopts)
408         if subvolume_metadata:
409             fd = open("%s/.subvolume_metadata" % pdisk.mountdir, "w")
410             fd.write(subvolume_metadata)
411             fd.close()
412
413     def __get_subvolume_metadata(self, p, pdisk):
414         subvolume_metadata_file = "%s/.subvolume_metadata" % pdisk.mountdir
415         if not os.path.exists(subvolume_metadata_file):
416             return
417         fd = open(subvolume_metadata_file, "r")
418         content = fd.read()
419         fd.close()
420         for line in content.split("\n"):
421             items = line.split("\t")
422             if items and len(items) == 4:
423                 self.subvolumes.append({'size': 0, # In sectors
424                                         'mountpoint': items[2], # Mount relative to chroot
425                                         'fstype': "btrfs", # Filesystem type
426                                         'fsopts': items[3] + ",subvol=%s" %  items[1], # Filesystem mount options
427                                         'disk': p['disk'], # physical disk name holding partition
428                                         'device': None, # kpartx device node for partition
429                                         'mount': None, # Mount object
430                                         'subvol': items[1], # Subvolume name
431                                         'boot': False, # Bootable flag
432                                         'mounted': False # Mount flag
433                                    })
434
435     def __create_subvolumes(self, p, pdisk):
436         """ Create all the subvolumes """
437         for subvol in self.subvolumes:
438             argv = [ self.btrfscmd, "subvolume", "create", pdisk.mountdir + "/" + subvol["subvol"]]
439             p1 = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
440             (out,err) = p1.communicate()
441             logging.debug(out)
442             if p1.returncode != 0:
443                 raise MountError("Failed to create subvolume '%s', return code: %d." % (subvol["subvol"], p1.returncode))
444
445         """ Set default subvolume, subvolume for "/" is default """
446         subvol = None
447         for subvolume in self.subvolumes:
448             if subvolume["mountpoint"] == "/" and p["disk"] == subvolume["disk"]:
449                 subvol = subvolume
450                 break
451         if subvol:
452             """ Get default subvolume id """
453             subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"])
454             """ Set default subvolume """
455             if subvolid != -1:
456                 argv = [ self.btrfscmd, "subvolume", "set-default", "%d" % subvolid, pdisk.mountdir]
457                 p1 = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
458                 (out,err) = p1.communicate()
459                 logging.debug(out)
460                 if p1.returncode != 0:
461                     raise MountError("Failed to set default subvolume id: %d', return code: %d." % (subvolid, p1.returncode))
462
463         self.__create_subvolume_metadata(p, pdisk)
464
465     def __mount_subvolumes(self, p, pdisk):
466         if self.skipformat:
467             """ Get subvolume info """
468             self.__get_subvolume_metadata(p, pdisk)
469             """ Set default mount options """
470             if len(self.subvolumes) != 0:
471                 for subvol in self.subvolumes:
472                     if subvol["mountpoint"] == p["mountpoint"] == "/":
473                         opts = subvol["fsopts"].split(",")
474                         for opt in opts:
475                             if opt.strip().startswith("subvol="):
476                                 opts.remove(opt)
477                                 break
478                         pdisk.fsopts = ",".join(opts)
479                         break
480             
481         if len(self.subvolumes) == 0:
482             """ Return directly if no subvolumes """
483             return
484
485         """ Remount to make default subvolume mounted """
486         rc = subprocess.call([self.umountcmd, pdisk.mountdir])
487         if rc != 0:
488             raise MountError("Failed to umount %s" % pdisk.mountdir)
489         rc = subprocess.call([self.mountcmd, "-o", pdisk.fsopts, pdisk.disk.device, pdisk.mountdir])
490         if rc != 0:
491             raise MountError("Failed to umount %s" % pdisk.mountdir)
492         for subvol in self.subvolumes:
493             if subvol["mountpoint"] == "/":
494                 continue
495             subvolid = self. __get_subvolume_id(pdisk.mountdir, subvol["subvol"])
496             if subvolid == -1:
497                 logging.debug("WARNING: invalid subvolume %s" % subvol["subvol"])
498                 continue
499             """ Replace subvolume name with subvolume ID """
500             opts = subvol["fsopts"].split(",")
501             for opt in opts:
502                 if opt.strip().startswith("subvol="):
503                     opts.remove(opt)
504                     break
505             #opts.append("subvolid=%d" % subvolid)
506             opts.extend(["subvolrootid=0", "subvol=%s" % subvol["subvol"]])
507             fsopts = ",".join(opts)
508             subvol['fsopts'] = fsopts
509             mountpoint = self.mountdir + subvol['mountpoint']
510             makedirs(mountpoint)
511             rc = subprocess.call([self.mountcmd, "-o", fsopts, pdisk.disk.device, mountpoint])
512             if rc != 0:
513                 raise MountError("Failed to mount subvolume %s to %s" % (subvol["subvol"], mountpoint))
514             subvol["mounted"] = True
515
516     def __unmount_subvolumes(self):
517         """ It may be called multiple times, so we need to chekc if it is still mounted. """
518         for subvol in self.subvolumes:
519             if subvol["mountpoint"] == "/":
520                 continue
521             if not subvol["mounted"]:
522                 continue
523             mountpoint = self.mountdir + subvol['mountpoint']
524             rc = subprocess.call([self.umountcmd, mountpoint])
525             if rc != 0:
526                 raise MountError("Failed to unmount subvolume %s from %s" % (subvol["subvol"], mountpoint))
527             subvol["mounted"] = False
528
529     def __create_subvolume_snapshots(self, p, pdisk):
530         if self.snapshot_created:
531             return
532
533         """ Remount with subvolid=0 """
534         rc = subprocess.call([self.umountcmd, pdisk.mountdir])
535         if rc != 0:
536             raise MountError("Failed to umount %s" % pdisk.mountdir)
537         if pdisk.fsopts:
538             mountopts = pdisk.fsopts + ",subvolid=0"
539         else:
540             mountopts = "subvolid=0"
541         rc = subprocess.call([self.mountcmd, "-o", mountopts, pdisk.disk.device, pdisk.mountdir])
542         if rc != 0:
543             raise MountError("Failed to umount %s" % pdisk.mountdir)
544
545         """ Create all the subvolume snapshots """
546         snapshotts = time.strftime("%Y%m%d-%H%M")
547         for subvol in self.subvolumes:
548             subvolpath = pdisk.mountdir + "/" + subvol["subvol"]
549             snapshotpath = subvolpath + "_%s-1" % snapshotts
550             argv = [ self.btrfscmd, "subvolume", "snapshot", subvolpath, snapshotpath ]
551             p1 = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
552             (out,err) = p1.communicate()
553             logging.debug(out)
554             if p1.returncode != 0:
555                 raise MountError("Failed to create subvolume snapshot '%s' for '%s', return code: %d." % (snapshotpath, subvolpath, p1.returncode))
556         self.snapshot_created = True
557   
558     def mount(self):
559         for dev in self.disks.keys():
560             d = self.disks[dev]
561             d['disk'].create()
562
563         self.__format_disks()
564         self.__map_partitions()
565         self.__calculate_mountorder()
566
567         for mp in self.mountOrder:
568             p = None
569             for p1 in self.partitions:
570                 if p1['mountpoint'] == mp:
571                     p = p1
572                     break
573
574             if mp == 'swap':
575                 subprocess.call([self.mkswap, p['device']])
576                 continue
577
578             rmmountdir = False
579             if p['mountpoint'] == "/":
580                 rmmountdir = True
581             if p['fstype'] == "vfat" or p['fstype'] == "msdos":
582                 myDiskMount = VfatDiskMount
583             elif p['fstype'] in ("ext2", "ext3", "ext4"):
584                 myDiskMount = ExtDiskMount
585             elif p['fstype'] == "btrfs":
586                 myDiskMount = BtrfsDiskMount
587             else:
588                 raise MountError("Fail to support file system " + p['fstype'])
589
590             if p['fstype'] == "btrfs" and not p['fsopts']:
591                 p['fsopts'] = "subvolid=0"
592                 
593             pdisk = myDiskMount(RawDisk(p['size'] * self.sector_size, p['device']),
594                                  self.mountdir + p['mountpoint'],
595                                  p['fstype'],
596                                  4096,
597                                  p['mountpoint'],
598                                  rmmountdir,
599                                  self.skipformat,
600                                  fsopts = p['fsopts'])
601             pdisk.mount(pdisk.fsopts)
602             if p['fstype'] == "btrfs" and p['mountpoint'] == "/":
603                 if not self.skipformat:
604                     self.__create_subvolumes(p, pdisk)
605                 self.__mount_subvolumes(p, pdisk)
606             p['mount'] = pdisk
607
608     def resparse(self, size = None):
609         # Can't re-sparse a disk image - too hard
610         pass