12 from pbs import dmsetup
13 from pbs import blockdev
17 class DiskError(Exception):
23 def __init__(self, source):
24 self._cleanup_jobs = []
28 def _add_cleanup(self, job, *args):
29 self._cleanup_jobs.append((job, args))
31 def _losetup(self, fname):
32 loop = losetup.find_unused_loop_device()
34 self._add_cleanup(loop.unmount)
37 def _dir_to_disk(self):
38 raise NotImplementedError
41 while len(self._devices):
42 device = self._devices.pop()
45 while len(self._cleanup_jobs):
46 job, args = self._cleanup_jobs.pop()
50 sourcedev = self.source
51 mode = os.stat(self.source).st_mode
52 if stat.S_ISDIR(mode):
53 return self._losetup(self._dir_to_disk())
54 elif stat.S_ISREG(mode):
55 sourcedev = self._losetup(self.source)
56 elif not stat.S_ISBLK(mode):
57 raise ValueError("Value for self.source is invalid")
59 # Take a snapshot and return it to the user
60 size = blockdev('--getsize', sourcedev)
61 cowfd, cow = tempfile.mkstemp()
62 self._add_cleanup(os.unlink, cow)
63 # Create 1G cow sparse file
64 dd('if=/dev/null', 'of=%s' % cow, 'bs=1k', 'seek=%d' % (1024 * 1024))
65 cowdev = self._losetup(cow)
67 snapshot = uuid.uuid4().hex
68 tablefd, table = tempfile.mkstemp()
70 os.write(tablefd, "0 %d snapshot %s %s n 8" % \
71 (int(size), sourcedev, cowdev))
72 dmsetup('create', snapshot, table)
73 self._add_cleanup(dmsetup, 'remove', snapshot)
77 new_device = DiskDevice("/dev/mapper/%s" % snapshot)
78 self._devices.append(new_device)
81 def destroy_device(self, device):
82 self._devices.remove(device)
86 class DiskDevice(object):
88 def __init__(self, device, bootable=True):
90 self.bootable = bootable
92 self.g = guestfs.GuestFS()
96 self.g.add_drive_opts(device, readonly=0)
98 roots = self.g.inspect_os()
100 raise DiskError("No operating system found")
102 raise DiskError("Multiple operating systems found")
105 self.ostype = self.g.inspect_get_type(self.root)
106 self.distro = self.g.inspect_get_distro(self.root)
111 # Close the guestfs handler
116 mps = self.g.inspect_get_mountpoints(self.root)
118 # Sort the keys to mount the fs in a correct order.
119 # / should be mounted befor /boot, etc
121 if len(a[0]) > len(b[0]):
123 elif len(a[0]) == len(b[0]):
130 self.g.mount(dev, mp)
131 except RuntimeError as msg:
132 print "%s (ignored)" % msg
138 dev = self.g.part_to_dev(self.root)
139 parttype = self.g.part_get_parttype(dev)
140 if parttype != 'msdos':
141 raise DiskError("You have a %s partition table. "
142 "Only msdos partitions are supported" % parttype)
144 last_partition = self.g.part_list(dev)[-1]
146 if last_partition['part_num'] > 4:
147 raise DiskError("This disk contains logical partitions. "
148 "Only primary partitions are supported.")
150 part_dev = "%s%d" % (dev, last_partition['part_num'])
151 fs_type = self.g.vfs_type(part_dev)
152 if not re.match("ext[234]", fs_type):
153 print "Warning, don't know how to resize %s partitions" % vfs_type
156 self.g.e2fsck_f(part_dev)
157 self.g.resize2fs_M(part_dev)
158 output = self.g.tune2fs_l(part_dev)
159 block_size = int(filter(lambda x: x[0] == 'Block size', output)[0][1])
160 block_cnt = int(filter(lambda x: x[0] == 'Block count', output)[0][1])
162 sector_size = self.g.blockdev_getss(dev)
164 start = last_partition['part_start'] / sector_size
165 end = start + (block_size * block_cnt) / sector_size - 1
167 return (end + 1) * sector_size
171 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :