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