BaseLiveUSB/__init__.py | 250 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+)
New commits: commit 83a3172620d00478ee5fc44a6d09b9ec6a4767c3 Author: alsadi alsadi@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@redhat.com, Muayyad Saleh Alsadi alsadi@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(' ', '_') +
liveusb-creator@lists.stg.fedorahosted.org