from image_creator.util import get_command
from image_creator.util import FatalError
from image_creator.util import try_fail_repeat
+from image_creator.util import free_space
from image_creator.gpt import GPTPartitionTable
from image_creator.bundle_volume import BundleVolume
import re
import sys
import guestfs
+import shutil
from sendfile import sendfile
blockdev = get_command('blockdev')
+TMP_CANDIDATES = ['/var/tmp', os.path.expanduser('~'), '/mnt']
+
+
class Disk(object):
"""This class represents a hard disk hosting an Operating System
the Linux kernel.
"""
- def __init__(self, source, output):
+ def __init__(self, source, output, tmp=None):
"""Create a new Disk instance out of a source media. The source
media can be an image file, a block device or a directory.
"""
self.source = source
self.out = output
self.meta = {}
+ self.tmp = tempfile.mkdtemp(prefix='.snf_image_creator.',
+ dir=self._get_tmp_dir(tmp))
+
+ self._add_cleanup(shutil.rmtree, self.tmp)
+
+ def _get_tmp_dir(self, default=None):
+ if default is not None:
+ return default
+
+ space = map(free_space, TMP_CANDIDATES)
+
+ max_idx = 0
+ max_val = space[0]
+ for i, val in zip(range(len(space)), space):
+ if val > max_val:
+ max_val = val
+ max_idx = i
+
+ # Return the candidate path with more available space
+ return TMP_CANDIDATES[max_idx]
def _add_cleanup(self, job, *args):
self._cleanup_jobs.append((job, args))
def _dir_to_disk(self):
if self.source == '/':
bundle = BundleVolume(self.out, self.meta)
- image = '/var/tmp/%s.diskdump' % uuid.uuid4().hex
+ image = '%s/%s.diskdump' % (self.tmp, uuid.uuid4().hex)
def check_unlink(path):
if os.path.exists(path):
instance.
"""
- self.out.output("Examining source media `%s'..." % self.source, False)
+ self.out.output("Examining source media `%s' ..." % self.source, False)
sourcedev = self.source
mode = os.stat(self.source).st_mode
if stat.S_ISDIR(mode):
# Take a snapshot and return it to the user
self.out.output("Snapshotting media source...", False)
size = blockdev('--getsz', sourcedev)
- cowfd, cow = tempfile.mkstemp()
+ cowfd, cow = tempfile.mkstemp(dir=self.tmp)
os.close(cowfd)
self._add_cleanup(os.unlink, cow)
# Create cow sparse file
mount = self.g.mount_ro if readonly else self.g.mount
msg = " read-only" if readonly else ""
- self.out.output("Mounting the media%s..." % msg, False)
+ self.out.output("Mounting the media%s ..." % msg, False)
mps = self.g.inspect_get_mountpoints(self.root)
# Sort the keys to mount the fs in a correct order.
raise FatalError(msg)
is_extended = lambda p: \
- self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) == 5
+ self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) \
+ in (0x5, 0xf)
is_logical = lambda p: \
- self.meta['PARTITION_TABLE'] != 'msdos' and p['part_num'] > 4
+ self.meta['PARTITION_TABLE'] == 'msdos' and p['part_num'] > 4
partitions = self.g.part_list(self.guestfs_device)
last_partition = partitions[-1]
if is_logical(last_partition):
# The disk contains extended and logical partitions....
- extended = [p for p in partitions if is_extended(p)][0]
+ extended = filter(is_extended, partitions)[0]
last_primary = [p for p in partitions if p['part_num'] <= 4][-1]
# check if extended is the last primary partition
self.meta['PARTITION_TABLE'] == 'msdos' and p['part_num'] > 4
is_extended = lambda p: \
self.meta['PARTITION_TABLE'] == 'msdos' and \
- self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) == 5
+ self.g.part_get_mbr_id(self.guestfs_device, p['part_num']) \
+ in (0x5, 0xf)
part_add = lambda ptype, start, stop: \
self.g.part_add(self.guestfs_device, ptype, start, stop)
MB = 2 ** 20
- self.out.output("Shrinking image (this may take a while)...", False)
+ self.out.output("Shrinking image (this may take a while) ...", False)
sector_size = self.g.blockdev_getss(self.guestfs_device)
self.g.resize2fs_M(part_dev)
out = self.g.tune2fs_l(part_dev)
- block_size = int(
- filter(lambda x: x[0] == 'Block size', out)[0][1])
- block_cnt = int(
- filter(lambda x: x[0] == 'Block count', out)[0][1])
+ block_size = int(filter(lambda x: x[0] == 'Block size', out)[0][1])
+ block_cnt = int(filter(lambda x: x[0] == 'Block count', out)[0][1])
start = last_part['part_start'] / sector_size
end = start + (block_size * block_cnt) / sector_size - 1
'num': partition['part_num'],
'start': partition['part_start'] / sector_size,
'end': partition['part_end'] / sector_size,
- 'id': part_get_(partition['part_num']),
+ 'id': part_get_id(partition['part_num']),
'bootable': part_get_bootable(partition['part_num'])
})
logical[-1]['end'] = end # new end after resize
# Recreate the extended partition
- extended = [p for p in partitions if self._is_extended(p)][0]
+ extended = filter(is_extended, partitions)[0]
part_del(extended['part_num'])
- part_add('e', extended['part_start'], end)
+ part_add('e', extended['part_start'] / sector_size, end)
# Create all the logical partitions back
for l in logical:
progressbar.next()
while left > 0:
length = min(left, blocksize)
- _, sent = sendfile(dst.fileno(), src.fileno(), offset,
- length)
+ sent = sendfile(dst.fileno(), src.fileno(), offset, length)
+
+ # Workaround for python-sendfile API change. In
+ # python-sendfile 1.2.x (py-sendfile) the returning value
+ # of sendfile is a tuple, where in version 2.x (pysendfile)
+ # it is just a sigle integer.
+ if isinstance(sent, tuple):
+ sent = sent[1]
+
offset += sent
left -= sent
progressbar.goto((size - left) // MB)