Branch 'liveusb-creator-gtk' - BaseLiveUSB/__init__.py
Muayyad Alsadi
alsadi at fedoraproject.org
Thu Nov 20 00:25:49 UTC 2008
BaseLiveUSB/__init__.py | 250 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 250 insertions(+)
New commits:
commit 83a3172620d00478ee5fc44a6d09b9ec6a4767c3
Author: alsadi <alsadi at pc1.localdomain>
Date: Thu Nov 20 02:20:24 2008 +0200
Add GUI independent new API in BaseLiveUSB
The new API is still broken, don't use it
diff --git a/BaseLiveUSB/__init__.py b/BaseLiveUSB/__init__.py
new file mode 100644
index 0000000..59f1669
--- /dev/null
+++ b/BaseLiveUSB/__init__.py
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © 2008 Red Hat, Inc. All rights reserved.
+# Copyright © 2008 Ojuba.org. All rights reserved.
+#
+# This copyrighted material is made available to anyone wishing to use, modify,
+# copy, or redistribute it subject to the terms and conditions of the GNU
+# General Public License v.2. This program is distributed in the hope that it
+# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
+# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details. You should have
+# received a copy of the GNU General Public License along with this program; if
+# not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
+# Floor, Boston, MA 02110-1301, USA. Any Red Hat trademarks that are
+# incorporated in the source code or documentation are not subject to the GNU
+# General Public License and may only be used or replicated with the express
+# permission of Red Hat, Inc.
+#
+# Author(s): Luke Macken <lmacken at redhat.com>, Muayyad Saleh Alsadi <alsadi at ojuba.org>
+"""Install a liveOS into a vfat/ext3 parttion from an ISO image
+aimed to privide an easy to extend API and replacing the old livecd-iso-to-disk bash script"""
+
+import os
+import os.path
+
+
+class LiveUSBError(Exception):
+ """ A generic error message that is thrown by the LiveUSBCreator """
+
+class LiveUSBCreator():
+ """Platform independent class for dealing with creating liveusb pen drives."""
+ def __init__(self):
+ self.overlay=0 # size in mb of persistent overlay (0 disabled)
+ self.home=0 # size in mb of home.img (0 disabled)
+ self.swapfile=0 # size in mb of swap.img (0 disabled)
+ self.is_home_enc=0 # should home be encrypted
+ self.keep_home=1 # should old home.img be kept if it already exists
+ self.iso_fn=None # the source iso filename
+ self.iso_mnt=None # mount point of the source iso
+ self.target_partition=None # target partition eg. /dev/sdb1
+ self.target_fs_type=None # vfat/msdos/ext[23]
+ self.target_disk=None # target whole disk eg. /dev/sdb
+ self.target_mnt=None # target partition mount point (could be a temporary directory)
+ self.__target_mnt_orig=None # original target partition mount point (if it was mounted)
+ self.__progress_cb=None
+ self.__mbr='/usr/lib/syslinux/mbr.bin' # should be moved away to bootloader class
+ # passing preexec_fn=self.__setsid to popen makes
+ # subprocesses to continue even when parent is closed
+ self.__setsid = getattr(os, 'setsid', None)
+ if not self.__setsid: self.__setsid = getattr(os, 'setpgrp', None)
+
+ # TODO: self.target_disk is documented to be mount point while it's used as the target disk (not partition) # DONE
+ # TODO: this mean that is should be renamed to remove that ambiguity # DONE
+ def target_have_liveos(self):
+ """return true if target partition already seems to have a LiveOS"""
+ if not self.target_mnt: raise LiveUSBError('TargetNotMounted')
+ d=os.path.join(self.target_mnt,'LiveOS')
+ if not os.path.isdir(d): return False
+ sq=os.path.join(d,'squashfs.img')
+ ex=os.path.join(d,'ext3fs.img')
+ if not os.path.isfile(sq) or not os.path.isfile(ex): return False
+ return True
+ def target_have_home(self):
+ """return true if target partition already seems to have a home.img"""
+ if not self.target_mnt: raise LiveUSBError('TargetNotMounted')
+ if os.path.isfile(os.path.join(self.target_mnt,'LiveOS','home.img')): return True
+ return False
+ def get_free_bytes(self, device=None):
+ """Linux-only function that return available bytes on device given mount point"""
+ #TODO: implement windows version
+ import statvfs
+ device = device or self.target_mnt
+ stat = os.statvfs(device)
+ return stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
+ def get_iso_bytes(self):
+ """return number of bytes in the source iso (works even if the iso is a device file)"""
+ f=open(self.iso_fn,'rb')
+ f.seek(0,2); r=f.tell()
+ f.close()
+ return r
+ def verify_iso_md5(self):
+ """Verify the ISO md5sum.
+ At the moment this is Linux specific, until we port checkisomd5 to Windows."""
+ self.log.info(_('Verifying ISO MD5 checksum'))
+ cmd='checkisomd5 --gauge "%s"' % self.iso_fn
+ proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, shell=True)
+ l='0'
+ p=0
+ while(l):
+ l=proc.stdout.readline()
+ try: i=int(l.strip())
+ except ValueError: pass
+ else: p=i
+ self.__partial_progress(p)
+ return proc.wait()==0
+ def validate(self):
+ """return None if all options are valid or return a reason for being invalid"""
+ if self.target_fs_type not in ('vfat','msdos','ext2','ext3'): return 'BadTargetFS'
+ if self.target_fs_type=='vfat' or self.target_fs_type=='msdos':
+ if self.overlay>2047: return 'TooBigOverlay'
+ if self.home>2047: return 'TooBigHome'
+ avail_bytes=get_free_bytes()
+ iso_bytes=get_free_bytes()
+ if avail_bytes-iso_bytes-(self.home<<20)-(self.overlay<<20)-(self.swapfile<<20)<1048576: return 'NoEnoughRoom' # leave at least one mega free space
+
+ return None
+ def run(self):
+ # TODO: add an option to create a swap file
+ # check if options are valid [overlay|home on fat<2048]
+ # check if we have persistent home then should it be reused (with same size) or removed
+ # check free space
+ # verify fs type
+ # verify iso md5sum [linuxonly]
+ # unmount target partition
+ # mount loopback and target partition on tmp dirs
+ # install boot loader [syslinux: (check mbr->reset mbr), mactel (checkGPT -> createGPTLayout)],
+ # create bootloader dirs
+ # cp boot loader config or create them
+ # if LiveOS exists mv out what we want from it, then remove it
+ # mkdir $USBMNT/LiveOS
+ # mv home.img back into LiveOS (if we want that)
+ # cp squashfs.img or ext3fs.img to target LiveOS/ (in xo it's better to be the uncompressed one)
+ # update boot config files to reflect the label and overlay
+ # some xo boot fixes
+ # create home (with/out encryption), persistent layer and swap
+
+ pass
+ def popen(self, cmd, **kwargs):
+ """ A wrapper method for running subprocesses.
+
+ This method handles logging of the command and it's output, and keeps
+ track of the pids in case we need to kill them. If something goes
+ wrong, an error log is written out and a LiveUSBError is thrown.
+
+ @param cmd: The commandline to execute. Either a string or a list.
+ @param kwargs: Extra arguments to pass to subprocess.Popen
+ """
+ self.log.debug(cmd)
+ self.output.write(cmd)
+ proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, shell=True, **kwargs)
+ self.pids=filter(lambda x: x.poll()!=None,self.pids) # remove terminated processes from _ps list
+ self.pids.append(proc.pid)
+ out, err = proc.communicate()
+ self.output.write(out + '\n' + err + '\n')
+ if proc.returncode:
+ self.write_log()
+ raise LiveUSBError("There was a problem executing the following "
+ "command: `%s`\nA more detailed error log has "
+ "been written to 'liveusb-creator.log'" % cmd)
+ return proc
+
+ def set_progress_cb(cb=None):
+ """set the funtion to be called when some progress is to be shown to the user,
+cb will be passed with a string, a progress percent or -1 if the value of the progress is unknown"""
+ self.__progress_cb=cb
+
+ def is_blank_mbr(self):
+ """Return whether the MBR is empty or not."""
+ try: fi = open(self.target_disk, 'rb')
+ except IOError: raise LiveUSBError("Could not access target device")
+ r=fi.read(2)=='\0'*2
+ fi.close()
+ return r
+
+ def reset_mbr(self):
+ """Put boot loader on the Master boot record of target device"""
+ if '/dev/loop' in self.drive:
+ self.log.warning('Cannot reset MBR on loopback device')
+ return
+ #self.popen('cat /usr/lib/syslinux/mbr.bin > %s' % self.target_disk)
+ try: fi=open(self.__mbr,"rb")
+ except IOError: raise LiveUSBError("MBR file not found")
+ fo=open(self.target_disk,"wb")
+ except IOError: raise LiveUSBError("Could not open target device for writing")
+ fo.write('\0'*512) # just to be more than sure ;-)
+ fo.seek(0)
+ fo.write(fi.read()) # TODO: do we need to check that __mbr <=512B in size ?
+ fi.close()
+ fo.close()
+ # TODO: implement checkPartActive createGPTLayout checkGPT checkSyslinuxVersion()
+ # TODO: checkFilesystem is implemented as verify_filesystem()
+ def verify_iso_md5(self):
+ """Verify the ISO md5sum.
+At the moment this is Linux specific, until we port checkisomd5 to Windows."""
+ self.log.info('Verifying ISO MD5 checksum')
+ try:
+ self.popen('checkisomd5 "%s"' % self.iso)
+ except LiveUSBError:
+ return False
+ return True
+ def verify_iso_md5_w32(self):
+ return True
+
+ # all methods below needs to be rechecked
+ def verify_filesystem(self):
+ """verify fs type and reset MBR if needed"""
+ self.log.info("Verifying filesystem...")
+ # TODO: it only reads fstype from a preset table created by some call
+ if self.fstype not in ('vfat', 'msdos', 'ext2', 'ext3'):
+ if not self.fstype:
+ raise LiveUSBError("Unknown filesystem for %s. Your device "
+ "may need to be reformatted.")
+ else:
+ raise LiveUSBError("Unsupported filesystem: %s" % self.fstype)
+ if self.drive['label']:
+ self.label = self.drive['label']
+ else:
+ self.log.info("Setting %s label to %s" % (self.drive['device'],self.label))
+ try:
+ if self.fstype in ('vfat', 'msdos'):
+ try:
+ self.popen('/sbin/dosfslabel %s %s' % (self.drive['device'], self.label))
+ except LiveUSBError:
+ # dosfslabel returns an error code even upon success
+ pass
+ else:
+ self.popen('/sbin/e2label %s %s' % (self.drive['device'],self.label))
+ except LiveUSBError, e:
+ self.log.error("Unable to change volume label: %s" % str(e))
+ self.label = None
+ # Ensure our master boot record is not empty
+ if self.blank_mbr():
+ self.log.debug('Your MBR appears to be blank')
+ self.reset_mbr()
+
+ def verify_filesystem_w32(self):
+ import win32api, win32file, pywintypes
+ self.log.info("Verifying filesystem...")
+ try:
+ vol = win32api.GetVolumeInformation(self.drive['device'])
+ except Exception, e:
+ raise LiveUSBError("Make sure your USB key is plugged in and "
+ "formatted with the FAT filesystem")
+ if vol[-1] not in ('FAT32', 'FAT'):
+ raise LiveUSBError("Unsupported filesystem: %s\nPlease backup "
+ "and format your USB key with the FAT "
+ "filesystem." % vol[-1])
+ self.fstype = 'vfat'
+ if vol[0] == '':
+ try:
+ win32file.SetVolumeLabel(self.drive['device'], self.label)
+ self.log.debug("Set %s label to %s" % (self.drive['device'], self.label))
+ except pywintypes.error, e:
+ self.log.warning("Unable to SetVolumeLabel: " + str(e))
+ self.label = None
+ else:
+ self.label = vol[0].replace(' ', '_')
+
More information about the liveusb-creator
mailing list