#!/usr/bin/env python #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++# # # # LiloSetup - GTK frontend to setup lilo, from a LiveCD or a standard system. # # # # Copyright Pierrick Le Brun . # # # # This program is free software; you can redistribute it and/or # # modify it under the terms of the GNU General Public License # # as published by the Free Software Foundation; either version 2 # # of the License, or (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # # #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++# # version = '0.3' # TODO Code cleanup and re-organization (external function library?) # TODO Add GUI option for choosing graphic file (or not) # TODO Add GUI option for Addappend and Append lines # TODO Add GUI option for selecting resolution (or leave on automatic) # TODO Move lilosetup.conf stub to an external file import shutil import subprocess import os import sys import commands import glob import gtk # Internationalization import locale import gettext import gtk.glade locale.setlocale(locale.LC_ALL, "") gettext.bindtextdomain("lilosetup", "/usr/share/locale") gettext.textdomain("lilosetup") gettext.install("lilosetup", "/usr/share/locale", unicode=1) gtk.glade.bindtextdomain("lilosetup", "/usr/share/locale") gtk.glade.textdomain("lilosetup") ##### INITIALISATION ##### ### Set global variables ### # This will help us monitor if a label is already in use already_there = [] # This will help us monitor whether the configuration file is succesfully created or not config_creation = [] # This will help us to ensure at least one partition has been configured partition_set = [] # Initialize the temporary mountpoint lists temp_mount = [] temp_dir = [] # Initialize key mountpoints value temp_chroot_mnt = '' chroot_mnt = '' try : root_device_uuid = commands.getoutput('blkid /dev/root').split()[1].split('"')[1] except IndexError : root_device_uuid = 'none' ### Set global functions ### # Info window skeleton: def info_dialog(message, parent = None): """ Display an information message. """ dialog = gtk.MessageDialog(parent = parent, type = gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK, flags = gtk.DIALOG_MODAL) dialog.set_markup(message) global result_info result_info = dialog.run() dialog.destroy() # Warning window skeleton: def warning_dialog(message, parent = None): """ Display a warning message. """ dialog = gtk.MessageDialog(parent = parent, type = gtk.MESSAGE_WARNING, flags = gtk.DIALOG_MODAL) dialog.add_buttons(gtk.STOCK_YES, gtk.RESPONSE_YES) dialog.add_buttons(gtk.STOCK_NO, gtk.RESPONSE_NO) dialog.set_default_response(gtk.RESPONSE_NO) dialog.set_markup(message) global result_warning result_warning = dialog.run() dialog.destroy() # Error window skeleton: def error_dialog(message, parent = None): """ Display an error message. """ dialog = gtk.MessageDialog(parent = parent, type = gtk.MESSAGE_ERROR, buttons = gtk.BUTTONS_CLOSE, flags = gtk.DIALOG_MODAL) dialog.set_markup(message) global result_error result_error = dialog.run() dialog.destroy() # Output of bash commands : def run_bash(cmd): """ Take a bash command and return the output. """ p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) out = p.stdout.read().strip() return out #This is the stdout from the shell command # We may need to mount a partition temporarily global check_and_mount def check_and_mount(partition): """ Checks if a partition is mounted and mounts if necessary. """ mount_info=commands.getoutput("mount").splitlines() global partition_mountpoint while mount_info: if partition in mount_info[0]: # The partition is mounted partition_mountpoint = mount_info[0].split()[2] break mount_info.remove(mount_info[0]) if mount_info == [] : # The partition is not mounted, we need to do it. # Except if it is a swap or an extended partition # Then we create the temporary mountpoint temporary_montpoint = work_dir + partition.replace('dev', 'mnt') partition_mountpoint = temporary_montpoint try : os.makedirs(temporary_montpoint) temp_dir.append(temporary_montpoint)# allows clean deleting of temporary directories later except OSError : pass # Then we mount the partition on it subprocess.call("mount " + partition + " " + temporary_montpoint + " 2>/dev/null", shell=True) temp_mount.append(temporary_montpoint) # allows clean unmounting of temporary mounting later # We may need to unmount a partition if necessary global check_and_unmount def check_and_unmount(partition): """ Check if a partition is unmounted and unmounts if necessary (only if it was mounted by LiloSetup). """ mount_info=commands.getoutput("mount").splitlines() while mount_info: if partition in mount_info[0] : # The partition is mounted, we need to unmount it and remove the temporary mountpoint. # First we unmount the device but only if if was mounted by LiloSetup temporary_montpoint = work_dir + partition.replace('dev', 'mnt') subprocess.call("umount " + temporary_montpoint + " 2>/dev/null", shell=True) try : temp_mount.remove(temporary_montpoint) except ValueError : pass # Not there since it wasn't mounted by LiloSetup # Then we remove the temporary mountpoint try: os.removedirs(temporary_montpoint) except OSError: pass # The directory doesn't exist since it wasn't created by LiloSetup try : temp_dir.remove(temporary_montpoint) except ValueError : pass # Not there since it wasn't created by LiloSetup mount_info.remove(mount_info[0]) # We need to know if a partition holds a boot system or not (basic substitute for os-prober) global check_if_bootable def check_if_bootable(partition): """ Check if a partition is bootable and if so, retrieve basic info from it. """ partition_device = '' file_system = '' operating_system = '' kernel_check = [] win_boot_flag = [] check_and_mount(partition) # check for a Linux kernel kernel_check = glob.glob(partition_mountpoint + "/boot/vmlinuz*") win_boot_flag = commands.getoutput('fdisk -l | grep "\*" | grep ' + partition ) # Check for Linux partition if kernel_check != [] : # This is probably a Linux bootable partititon, let's check it out a bit more if os.path.isdir(partition_mountpoint + '/proc') is True and os.path.isdir(partition_mountpoint + '/sys') is True: # Define the partition device partition_device = partition # Get the file system string_output = 'blkid ' + partition + ' | grep -m 1 TYPE' file_system = commands.getoutput(string_output).split()[-1].split('"')[1] # Define the operating system. try : version_file_path = (partition_mountpoint + "/etc/lsb-release") version_file = open(version_file_path, "r") version_file_lines = version_file.read().splitlines() for line in version_file_lines : if "DISTRIB_ID" in line : operating_system = line.split("=")[1] version_file.close() break except : try : version_file_path = glob.glob(partition_mountpoint + "/etc/*release*")[0] version_file = open(version_file_path, "r") operating_system = version_file.read().split()[0] version_file.close() except : try : version_file_path = glob.glob(partition_mountpoint + "/etc/*version*")[0] version_file = open(version_file_path, "r") operating_system = version_file.read().split()[0] version_file.close() except: version_file.close() operating_system = "Unknown" return partition_device, file_system, operating_system # else check for Windows boot partitions elif partition in win_boot_flag : # This could be a Windows bootable system, let's check it out a bit more win_path =["/Windows/System32", "/WINDOWS/system32", "/WINDOWS/SYSTEM32", "/windows/system32"] win_file =["/IO.SYS", "/io.sys", "/MSDOS.SYS", "/msdos.sys", "/KERNEL.SYS", "/kernel.sys", "/COMMAND.COM", "/command.com", "/CONFIG.SYS", "/config.sys", "/autoexec.bat", "/AUTOEXEC.BAT", "bootmgr"] for i in win_path : winboot_dir_path = os.path.isdir(partition_mountpoint + i) if winboot_dir_path is True : for i in win_file : winboot_file_path = os.path.isfile(partition_mountpoint + i) if winboot_file_path is True : # Define the partition device partition_device = partition # Get the file system string_output = 'blkid ' + partition + ' | grep -m 1 TYPE' file_system = commands.getoutput(string_output).split()[-1].split('"')[1] # Define the operating system. operating_system = "Windows" return partition_device, file_system, operating_system break check_and_unmount(partition) # Purge any LiloSeup customization & reverts to initial defaults def lilosetup_undo(): """ Unmount temp mountpoints & cleanup some temp files """ global temp_mount, temp_dir # First we unmount the various temporary mountpoints while temp_mount : subprocess.call("umount " + temp_mount[0] + " 2>/dev/null", shell=True) temp_mount.remove(temp_mount[0]) # Then we remove the temporary mountpoints created by LiloSetup while temp_dir : try: os.removedirs(temp_dir[0]) except OSError: pass temp_dir.remove(temp_dir[0]) # Last we unmount & remove the temporary mountpoint for the temporary chrooted partition if temp_chroot_mnt: subprocess.call("umount " + temp_chroot_mnt + " 2>/dev/null", shell=True) try: os.removedirs(temp_chroot_mnt) except OSError: pass # Removal of the temporary configuration file try: os.remove(config_location) except OSError: pass # Clean up all temporary files and directories when exiting def lilosetup_quit(): """ Unmount temp mountpoints, cleanup all temp files & close LiloSetup window. """ if partition_set == [] : pass else: lilosetup_undo() # Removal of the configuration stub file try: os.remove(stub_location) except OSError: pass # Removal of the temporary work directory try : # We are using os.rmdir instead of os.rmtree in case some partitions would still be mounted on the workdirectory os.rmdir(work_dir) except OSError: pass # Quit LiloSetup GUI gtk.main_quit() ### Set work directory ### work_dir = "/tmp/lilosetup" try : # We are using os.rmdir instead of os.rmtree in case some partitions would still # be mounted on a work directory created by a previous use of LiloSetup os.rmdir(work_dir) except OSError: pass try : os.mkdir(work_dir) except OSError: pass ### Set configuration file stub ### config_location = work_dir + "/lilosetup.conf" try : os.remove(config_location) except OSError: pass # Retrieve the boot device with the MBR that will host LILO # Preferably the first hard drive having a partition with a boot flag, boot_partition = commands.getoutput('fdisk -l | grep "^/dev" | grep "*" -m 1 | cut -f1 -d " "').rstrip('0123456789') # Else, just the first hard drive if boot_partition == "" : boot_partition = commands.getoutput('fdisk -l | grep "^/dev" -m 1 | cut -f1 -d " "').rstrip('0123456789') stub_location = work_dir + "/lilosetup.stub" # Purge eventual stub remnant from previous use of LiloSetip try: os.remove(stub_location) except OSError: pass # New stub with following defaults that reflect Slackel' customisations for Lilo stub = open(stub_location, "w") stub.write(_("# LILO configuration file\n\ # Generated by LiloSetup\n")) stub.write("#\n") stub.write(_("# Start LILO global section\n\ # Append any additional kernel parameters:\n")) stub.write('append = "vt.default_utf8=1 "\n') stub.write("boot = " + boot_partition + "\n") stub.write("lba32\n") stub.write("compact\n") stub.write("\n") stub.write(_("# Boot BMP Image.\n\ # Bitmap in BMP format: 640x480x8\n")) stub.write("bitmap = /boot/slackel.bmp\n") stub.write(_("# Menu colors (foreground, background, shadow, highlighted\n\ # foreground, highlighted background, highlighted shadow):\n")) stub.write("bmp-colors = 255,20,255,20,255,20\n") stub.write(_('# Location of the option table: location x, location y, number of\n\ # columns, lines per column (max 15), "spill" this is how many\n\ # entries must be in the first column before the next begins to\n\ # be used. We do not specify it here, as there is just one column.\n')) stub.write("bmp-table = 60,6,1,16\n") stub.write(_("# Timer location x, timer location y, foreground color,\n\ # background color, shadow color.\n")) stub.write("bmp-timer = 65,29,0,255\n") stub.write("\n") stub.write(_("# Standard menu.\n\ # Or, you can comment out the bitmap menu above and \n\ # use a boot message with the standard menu:\n")) stub.write("# message = /boot/boot_message.txt\n") stub.write("\n") stub.write(_("# Wait until the timeout to boot (if commented out, boot the\n\ # first entry immediately):\n")) stub.write("prompt\n") stub.write(_("# Timeout before the first entry boots.\n\ # This is given in tenths of a second, so 600 for every minute:\n")) stub.write("timeout = 50\n") stub.write(_("# Override dangerous defaults that rewrite the partition table:\n")) stub.write("change-rules\n") stub.write("reset\n") stub.write("\n") stub.write(_("# Normal VGA console\n")) stub.write("# vga = normal\n") stub.write(_("# VESA framebuffer console @ 1600x1200x16m\n")) stub.write("# vga=799\n") stub.write(_("# VESA framebuffer console @ 1600x1200x64k\n")) stub.write("# vga=798\n") stub.write(_("# VESA framebuffer console @ 1600x1200x32k\n")) stub.write("# vga=797\n") stub.write(_("# VESA framebuffer console @ 1600x1200x256\n")) stub.write("# vga=796\n") stub.write(_("# VESA framebuffer console @ 1280x1024x16m\n")) stub.write("# vga=795\n") stub.write(_("# VESA framebuffer console @ 1280x1024x64k\n")) stub.write("# vga=794\n") stub.write(_("# VESA framebuffer console @ 1280x1024x32k\n")) stub.write("# vga=793\n") stub.write(_("# VESA framebuffer console @ 1280x1024x256\n")) stub.write("# vga=775\n") stub.write(_("# VESA framebuffer console @ 1024x768x64k\n")) stub.write("# vga=791\n") stub.write(_("# VESA framebuffer console @ 1024x768x32k\n")) stub.write("# vga=790\n") stub.write(_("# VESA framebuffer console @ 1024x768x256\n")) stub.write("# vga=773\n") stub.write(_("# VESA framebuffer console @ 800x600x16m\n")) stub.write("# vga=789\n") stub.write(_("# VESA framebuffer console @ 800x600x64k\n")) stub.write("# vga=788\n") stub.write(_("# VESA framebuffer console @ 800x600x32k\n")) stub.write("# vga=787\n") stub.write(_("# VESA framebuffer console @ 800x600x256\n")) stub.write("# vga=771\n") stub.write(_("# VESA framebuffer console @ 640x480x64k\n")) stub.write("# vga=785\n") stub.write(_("# VESA framebuffer console @ 640x480x32k\n")) stub.write("# vga=784\n") stub.write(_("# VESA framebuffer console @ 640x480x256\n")) stub.write("# vga=769\n") stub.write(_("# End LILO global section\n")) stub.write("#\n") stub.write(_("# LiloSetup can be executed from a LiveCD. This means that lilo\n\ # could be issued from a 'chrooted' Linux partition, which would\n\ # happen to be the first Linux partition listed below.\n\ # Therefore the following paths are relevant only when viewed\n\ # from that 'chrooted' partition's perspective. Please take this\n\ # constraint into consideration if you must modify this file\n\ # or else LiloSetup will fail.\n")) stub.write("#\n") stub.write(_('# If later on you want to use this configuration file directly\n\ # with lilo in a command line, use the following syntax:\n\ # "lilo -v -C /etc/lilosetup/conf" instead of the traditional\n\ # "lilo -v" command. You must of course issue that command from\n\ # the operating system holding /etc/lilosetup.conf & ensure that\n\ # all partitions referenced in it are mounted on the appropriate\n\ # mountpoints.\n')) stub.close() # Set the appropriate framebuffer os.putenv("stubfile", stub_location) # Sets the variable 'stubfile' in bash environment def failsafe_fb(): """ Fall back on a failsafe framebuffer option in lilosetup configuration file """ FBLINE= """ cat $stubfile | grep "vga = normal" -m 1 -n | cut -f1 -d : """ fbline = run_bash(FBLINE) os.putenv("editline", fbline) set_framebuffer = "sed $editline's/# //' -i $stubfile" subprocess.call(set_framebuffer, shell=True) # Check if framebuffer is available USEDFB = """ fbset | grep -w mode | cut -f2 -d " " | cut -f1 -d "-" | sed 's/"//' """ if run_bash(USEDFB): os.putenv("setfb", run_bash(USEDFB)) # Ensure the adequate framebuffer resolution is in the stub FBLINE= """ cat $stubfile | grep "@ $setfb" -m 1 -n | cut -f1 -d : """ fbline = run_bash(FBLINE) if fbline != '' : fblineplus = int(fbline) + 1 os.putenv("editline", repr(fblineplus)) set_framebuffer = "sed $editline's/# //' -i $stubfile" subprocess.call(set_framebuffer, shell=True) # If no fitting resolution is available from the stub, then failsafe to vga-normal else : failsafe_fb() # If framebuffer is not available, then failsafe to vga-normal else : failsafe_fb() ##### GUI CONSTRUCTION ##### class LiloSetup: """ Main application class. """ def __init__(self): """ Initialize the application. """ builder = gtk.Builder() if os.path.exists("lilosetup.glade") : builder.add_from_file("lilosetup.glade") elif os.path.exists("/usr/share/lilosetup/lilosetup.glade") : builder.add_from_file("/usr/share/lilosetup/lilosetup.glade") elif os.path.exists("../share/lilosetup/lilosetup.glade") : builder.add_from_file("../share/lilosetup/lilosetup.glade") # Get a handle on the glade file widgets we want to interact with self.Window = builder.get_object("lilosetup_main") self.AboutDialog = builder.get_object("about_dialog") self.BootPartitionTreeview = builder.get_object("boot_partition_treeview") self.BootPartitionListStore = builder.get_object("boot_partition_list_store") self.EditButton = builder.get_object("edit_button") self.UndoButton = builder.get_object("undo_button") self.ExecuteButton = builder.get_object("execute_button") self.UpButton = builder.get_object("up_button") self.DownButton = builder.get_object("down_button") self.LabelCellRendererCombo = builder.get_object("label_cellrenderercombo") self.PartitionTreeViewColumn = builder.get_object("partition_treeviewcolumn") self.FileSystemTreeViewColumn = builder.get_object("filesystem_treeviewcolumn") self.OsTreeViewColumn = builder.get_object("os_treeviewcolumn") self.LabelTreeViewColumn = builder.get_object("label_treeviewcolumn") self.LabelContextHelp = builder.get_object("label_context_help") # Connect signals builder.connect_signals(self) # Set the column names self.PartitionTreeViewColumn.set_title(_('Partition')) self.FileSystemTreeViewColumn.set_title(_('File system')) self.OsTreeViewColumn.set_title(_('Operating system')) self.LabelTreeViewColumn.set_title(_('Boot menu label')) # Initialize the contextual help box global context_intro context_intro = _("LiloSetup will install a new LILO bootloader on your computer. \n\ \n\ A bootloader is required to load the main operating system of a computer and will initially display \ a boot menu if several operating systems are available on the same computer.") self.LabelContextHelp.set_markup(context_intro) ### Initialize the partition list ### global setup_partition_list def setup_partition_list(): """ Generate the initial partition list. """ # We need to get the file system of each device whether mounted or not # Start on a clean slate self.BootPartitionListStore.clear() # Initialize the different variables boot_partition_feedline_list = [] partition_device = '' file_system = '' operating_system = '' boot_label = _('Set...') boot_partition_feedline = [] # Get a list of all partitions blkid_list = commands.getoutput('blkid -c /dev/null | grep -iv recovery | grep -v ' + root_device_uuid + ' | grep -v swap').splitlines() for i in blkid_list : partition = i.split(':')[0] try : partition_device, file_system, operating_system = check_if_bootable(partition) boot_partition_feedline = [partition_device, file_system, operating_system, boot_label] boot_partition_feedline_list.append(boot_partition_feedline) except TypeError : pass # this is not a bootable partition # Add the main partition of the os we are in blkid_global_output = commands.getoutput('blkid') if root_device_uuid != 'none' : # In which case we could otherwise be in a LiveCD root_device = commands.getoutput('blkid | grep ' + root_device_uuid).split(':')[0] for line in blkid_global_output.splitlines() : if root_device_uuid in line : # Check that it is a 'non-loop' device and that we are indeed in a non-live environment if root_device != "/dev/root" : # Yes, we are in a non-live environment, we need to add that partititon to lilosetup view break # we break this process so that we can go on with adding it else : # we are in a LiveCD , no need to add root pass # so we do nothing & check the next partition if root_device != "/dev/root" : # Keep that extra check to ensure there will be no double entries # Get the file system string_output = commands.getoutput('blkid | grep ' + root_device_uuid + ' | grep -m 1 TYPE') file_system = string_output.split()[-1].split('"')[1] # Get the operating system. try : version_file_path = glob.glob("/etc/*version*")[0] version_file = open(version_file_path) operating_system = version_file.read().split()[0] version_file.close() except : operating_system = "Unknown" # Put it all together boot_partition_feedline = [root_device, file_system, operating_system, boot_label] boot_partition_feedline_list.append(boot_partition_feedline) else: # We probably are in a LiveCD environment. pass # Insert Menu Label editable combobox # We get this gui handle here to ensure it is reset to its default in the event of a configuration undo self.BootLabelListStore = builder.get_object("boot_label_list_store") self.LabelCellRendererCombo.set_property("model", self.BootLabelListStore) self.LabelCellRendererCombo.set_property('text-column', 0) self.LabelCellRendererCombo.set_property('editable', True) self.LabelCellRendererCombo.set_property('cell_background', '#CCCCCC') self.LabelTreeViewColumn.set_attributes(self.LabelCellRendererCombo, text = 3) # Sort the list for the partitions who do not follow the hard drive order boot_partition_feedline_list.sort() # Populate GUI partition list view rows for line in boot_partition_feedline_list: line.append('gtk-edit') self.BootPartitionListStore.append(line) # Show partition list setup_partition_list() global create_configuration def create_configuration(): """ Populate lilosetup.conf & mounts needed partition to lilo's chrooted partition. """ shutil.copy(stub_location,config_location) # Re-initialize key variables # Temporary mountpoint list global temp_mount, temp_chroot_mnt, temp_dir, partition_set, chroot_mnt temp_mount = [] # Configuration file config_creation = [] # This will help ensure that at least one partition has been configured partition_set = [] # This is for retrieving all the partition rows values BootPartitionsValues = [] x = 0 while x <= 20 : try : treeiter = self.BootPartitionListStore.get_iter(x) x += 1 BootPartitionsValues.append(self.BootPartitionListStore.get(treeiter, 0, 1, 2, 3)) except ValueError : break for set in BootPartitionsValues: windows_sys_labels = ['Microsoft', 'Windows'] # We skip the partitions that have not been configured by the user if _('Set...') in set[3] : pass # If Windows partition: elif set[2] in windows_sys_labels : # Append to lilosetup.conf stub = open(config_location, "a") stub.write("#\n") stub.write(_("# Windows bootable partition config begins\n")) stub.write("other = " + set[0] + "\n") stub.write("label = " + set[3] + "\n") stub.write(_("# Windows bootable partition config ends\n")) stub.close() else: # Applies to Linux partitions # Let's determines Lilo's chrooted Linux partition directory, only happens one. am_i_first = 'cat ' + config_location + ' | grep -i uuid' already_done = commands.getoutput(am_i_first).splitlines() if already_done == []: # This is the first Linux partition, the one we will chroot in to launch lilo! chroot_dev = set[0] mount_inconf = '' # Defines how the partitions 'appears' mounted from the 'chrooted' partition's viewpoint other_mnt = '' # we need this blank for the first Linux partition # Check if lilo's chroot directory is mounted CHROOT_MNT="mount | grep -v \/mnt\/live | grep " + chroot_dev + " | awk -F' ' '{print $3 }'" chroot_mnt = commands.getoutput(CHROOT_MNT) # chrooted partition mountpoint if chroot_mnt == '' : # Either it is not mounted or else it is used as the current root filesystem (/) but linked to /dev/root check_if_root = commands.getoutput("ls -l /dev/root | grep " + chroot_dev.replace("/dev/", "")) if check_if_root in chroot_dev : # The link leads to the 'chrooted' device, it is the current file system. chroot_mnt = "/" else : # It is not mounted, let's create a temporary mountpoint ourselves temp_chroot_mnt = work_dir + chroot_dev.replace('dev', 'mnt') try: os.makedirs(temp_chroot_mnt) except OSError : pass # Mount the 'chrooted' partition chroot_mnt_command = "mount " + chroot_dev + " " + temp_chroot_mnt + " 2>/dev/null" subprocess.call(chroot_mnt_command, shell=True) chroot_mnt = temp_chroot_mnt if chroot_mnt != "/" : # This is necessary only if we execute lilo from a 'real' chrooted partition subprocess.call("mount --bind /dev " + chroot_mnt + "/dev 2>/dev/null", shell=True) temp_mount.append(chroot_mnt + "/dev") # allows unmounting temporary mountpoints later subprocess.call("mount -t proc proc " + chroot_mnt + "/proc 2>/dev/null", shell=True) temp_mount.append(chroot_mnt + "/proc") # allows unmounting temporary mountpoints later else : # This applies to all subsequent partitions # How this partition is --or should be-- mounted on a the current partition if chroot_mnt == "/" : mount_info=commands.getoutput("mount").splitlines() while mount_info: if set[0] in mount_info[0]: other_mnt = mount_info[0].split()[2] mount_info.remove(mount_info[0]) if other_mnt == '': # We need to create a temporary mountpoint ourself & mount it: temp_other_mnt = work_dir + set[0].replace('dev', 'mnt') try : os.makedirs(temp_other_mnt) temp_dir.append(temp_other_mnt) except OSError : pass other_mnt = temp_other_mnt # all needed temporary mountpoints on chrooted partitions exist, we can now mount them mnt_command = "mount " + set[0] + " " + other_mnt + " 2>/dev/null" subprocess.call(mnt_command, shell=True) temp_mount.append(other_mnt) # allows cleanup temporary mountpoints later # Else, how this partition should (and will) be mounted on a 'real' chrooted partition else : other_mnt = '' # reinitialization # We create a fork, chroot in the child process & pipe the mount info to the parent process # If lilosetup is executed from a 32bit environment it can't chroot to a 64bit environment. if os.path.isdir('/lib64') is not True and os.path.isdir(chroot_mnt + '/lib64') is True: pass else : r, w = os.pipe() # these are file descriptors, not file objects pid = os.fork() if pid: chroot_mnt # Parent process os.close(w) # use os.close() to close a file descriptor r = os.fdopen(r) # turn r into a file object other_mnt = r.read() os.waitpid(pid, 0) # make sure the child process gets cleaned up else: # Child process os.close(r) w = os.fdopen(w, 'w') os.chroot(chroot_mnt) # We use fake mounting to avoid dealing with eventual fstab UUID schemes subprocess.call("mount -af 2>/dev/null", shell=True) # Retrieve the mount info mount_info=commands.getoutput("mount").splitlines() while mount_info: if set[0] in mount_info[0]: other_mnt = mount_info[0].split()[2] elif set[0].replace('hd', 'sd') in mount_info[0]: other_mnt = mount_info[0].split()[2] elif set[0].replace('sd', 'hd') in mount_info[0]: other_mnt = mount_info[0].split()[2] mount_info.remove(mount_info[0]) w.write(other_mnt) w.close() # Exit child process sys.exit(0) if other_mnt == '': # we need to create a temporary mountpoint ourselves temp_other_mnt = work_dir + set[0].replace('dev', 'mnt') try : os.makedirs(chroot_mnt + temp_other_mnt) temp_dir.append(chroot_mnt + temp_other_mnt) except OSError : pass other_mnt = temp_other_mnt # all needed temporary mountpoints on chrooted partitions exist, we can now mount them mnt_command = "mount " + set[0] + " " + chroot_mnt + other_mnt + " 2>/dev/null" subprocess.call(mnt_command, shell=True) temp_mount.append(chroot_mnt +other_mnt) # allows cleanup temporary mountpoints later mount_inconf = other_mnt # defines how the partition 'appears' mounted in lilosetup.conf # Confirm that the partition is configured partition_set.append("OK") # Append to lilosetup.conf stub = open(config_location, "a") # There maybe a few kernels in the same partition # Some of them may have an initrd, which we assume have exactly the same suffix. if chroot_mnt != "/": vmlist = sorted(glob.glob(chroot_mnt + other_mnt + "/boot/vmlinuz*")) initlist = sorted(glob.glob(chroot_mnt + other_mnt + "/boot/initr*")) if chroot_mnt == "/": vmlist = sorted(glob.glob(other_mnt + "/boot/vmlinuz*")) initlist = sorted(glob.glob(other_mnt + "/boot/initr*")) # Remove directories for i in vmlist : if os.path.isdir(i) : vmlist.remove(i) for i in initlist : if os.path.isdir(i) : initlist.remove(i) # Remove symbolic links for i in vmlist : if os.path.islink(i) : vmlist.remove(i) for i in initlist : if os.path.islink(i) : initlist.remove(i) # There could be a few kernels in the same partition: it = 0 y = 1 while it < len(vmlist) : stub.write("#\n") stub.write(_("# Linux bootable partition config begins\n")) try : vmlinuz_file_path = vmlist[it].split("boot")[1] stub.write("image = " + mount_inconf + "/boot" + vmlinuz_file_path + "\n") vmlinuz_suffix = vmlinuz_file_path.split('/')[-1].replace("vmlinuz", "") except: error_dialog(_("One of your partitions does not seem to hold a valid kernel file. Please verify and correct LiloSetup configuration file manually.\n")) # We'll use uuid to avoid libata/non-libata confusion # Find the uuid linked to the partition linked_partition = set[0].split('/')[-1] uuid_line = commands.getoutput('ls -l /dev/disk/by-uuid | grep ' + linked_partition).split('->')[0].split()[-1] if 'Fedora' in set[2]: stub.write('append = "root=UUID=' + uuid_line +' "\n') else: stub.write("root = /dev/disk/by-uuid/" + uuid_line +"\n") if len(vmlist) == 1 : # There is only one kernel so only one entry for this partition stub.write("label = " + set[3] + "\n") else: # There is more than one kernel in the same partition, we need to create multiple entries # We need to ensure that the label is not too long new_label = set[3] + vmlinuz_suffix if len(new_label) >= 14 : corrected_label = set[3] + '-' + str(y) stub.write("label = " + corrected_label +"\n") else: stub.write("label = " + new_label +"\n") # Add the initrd if suffix is matching kernel for i in initlist: if vmlinuz_suffix in i: initrd_file_path = i.split("boot")[1] stub.write("initrd = " + mount_inconf + "/boot" + initrd_file_path + "\n") break # If only one kernel & one initrd, we can assume they match even if their suffix do not. if it == 0 and vmlinuz_suffix not in i: if len(initlist) == 1 and len(vmlist) == 1: initrd_file_path = initlist[0].split("boot")[1] stub.write("initrd = " + mount_inconf + "/boot" + initrd_file_path + "\n") stub.write("read-only\n") stub.write(_("# Linux bootable partition config ends\n")) y += 1 it += 1 stub.close() ### Callback signals waiting in a constant loop: ### ### WINDOWS MAIN SIGNALS ### # Contextual help: def on_about_button_enter_notify_event(self, widget, data=None): self.LabelContextHelp.set_text(_("About Lilo Setup.")) def on_about_button_leave_notify_event(self, widget, data=None): global context_intro self.LabelContextHelp.set_markup(context_intro) def on_context_eventbox_enter_notify_event(self, widget, data=None): self.LabelContextHelp.set_text(_("It is necessary to (re)configure the \ bootloader each time you install a new operating system on your computer. \n\ LiloSetup can also be useful in the process of a disaster recovery in which case you \ may have to launch it from a LiveCD if you have lost all other means to boot into your system.\n\ Lilosetup can indifferently be executed from a Linux LiveCD environment or from a regular \ installed Linux system. In both cases, it will setup a new LILO bootloader on the MBR of \ your first hard drive.")) def on_context_eventbox_leave_notify_event(self, widget, data=None): global context_intro self.LabelContextHelp.set_markup(context_intro) def on_boot_partition_treeview_enter_notify_event(self, widget, data=None): self.LabelContextHelp.set_markup(_("Here you must define a Boot menu label for each \ of the operating system that will be displayed on your bootloader menu.\n\ Any partition for which you do not set a Boot menu label will not be configured and will \ not be displayed on the bootloader menu.\n\ If a few kernels are available within one partition, the label you have chosen for that \ partition will be appended numerically to create multiple menu entries for each of these kernels.\n\ Any of these settings can be edited manually in lilosetup configuration file.")) def on_boot_partition_treeview_leave_notify_event(self, widget, data=None): global context_intro self.LabelContextHelp.set_markup(context_intro) def on_up_eventbox_enter_notify_event(self, widget, data=None): self.LabelContextHelp.set_markup(_("Use this arrow if you want to move the \ selected Operating System up to a higher rank.\n\ The partition with the highest rank will be displayed on the first line of the bootloader menu.\n\ Any of these settings can be edited manually in lilosetup configuration file.")) def on_up_eventbox_leave_notify_event(self, widget, data=None): global context_intro self.LabelContextHelp.set_markup(context_intro) def on_down_eventbox_enter_notify_event(self, widget, data=None): self.LabelContextHelp.set_markup(_("Use this arrow if you want to move the \ selected Operating System down to a lower rank.\n\ The partition with the lowest rank will be displayed on the last line of the bootloader menu.\n\ Any of these settings can be edited manually in lilosetup configuration file.")) def on_down_eventbox_leave_notify_event(self, widget, data=None): global context_intro self.LabelContextHelp.set_markup(context_intro) def on_undo_eventbox_enter_notify_event(self, widget, data=None): global context_intro self.LabelContextHelp.set_markup(_("This will undo all settings (even manual modifications).")) def on_undo_eventbox_leave_notify_event(self, widget, data=None): self.LabelContextHelp.set_markup(context_intro) def on_edit_eventbox_enter_notify_event(self, widget, data=None): self.LabelContextHelp.set_markup(_("Experienced users have the possibility to \ manually edit LiloSetup configuration file (settings are identical to lilo.conf). \n\ Please do not temper with this file unless you know what you are doing and you have \ read its commented instructions regarding chrooted paths.")) def on_edit_eventbox_leave_notify_event(self, widget, data=None): global context_intro self.LabelContextHelp.set_markup(context_intro) def on_button_quit_enter_notify_event(self, widget, data=None): self.LabelContextHelp.set_markup(_("Exit Lilo Setup program.")) def on_button_quit_leave_notify_event(self, widget, data=None): global context_intro self.LabelContextHelp.set_markup(context_intro) def on_execute_eventbox_enter_notify_event(self, widget, data=None): self.LabelContextHelp.set_markup(_("Once you have defined your settings, \ click on this button to create your new LILO's bootloader.")) def on_execute_eventbox_leave_notify_event(self, widget, data=None): global context_intro self.LabelContextHelp.set_markup(context_intro) # What to do when the exit X on the main window upper right is clicked def gtk_main_quit(self, widget, data=None): lilosetup_quit() # What to do when the quit button is is clicked def on_main_window_destroy(self, widget, data=None): lilosetup_quit() # What to do when the about button is is clicked def on_about_button_clicked(self, widget, data=None): """ Open the about dialog. """ self.AboutDialog.show() def on_about_dialog_close(self, widget, data=None): """ Close the about dialog. """ self.AboutDialog.hide() return True # What to do when a combo line is edited in the Label column def on_label_cellrenderercombo_edited(self, widget, row_number, new_text): # Retrieve the selected label row iter bootlabelchoice = self.BootPartitionTreeview.get_selection() self.BootPartitionListStore, iter = bootlabelchoice.get_selected() # We ensure there are no identical labels if any(new_text in item for item in already_there) : for item in already_there[:] : # The user could change his mind and leave a label unchanged after starting edition, in which case we shouldn't warn him if (row_number,new_text) in already_there : break else : error_dialog(_("You have used the same label for different Operating Systems. Please verify and correct.\n")) # Remove the item from the list if it already set before setting back to default try : already_there.remove((row_number,new_text)) except ValueError : pass # Reset the default value of the label self.BootPartitionListStore.set_value(iter, 3, _("Set...")) self.BootPartitionListStore.set_value(iter, 4, 'gtk-edit') # Re-enables buttons if there are other valid entries if already_there != [] : self.UndoButton.set_sensitive(True) self.EditButton.set_sensitive(True) self.ExecuteButton.set_sensitive(True) self.UpButton.set_sensitive(True) self.DownButton.set_sensitive(True) break # We need to ensure that the labels do not contain empty space elif ' ' in new_text : error_dialog(_("\nAn Operating System label should not contain any space. \n\nPlease verify and correct.\n")) # We ensure that the label is less than 15 characters long elif len(new_text) > 15 : error_dialog(_("\nAn Operating System label should not hold more than 15 characters. \n\nPlease verify and correct.\n")) else: # Allow for successive editing of the same partition for item in already_there[:] : if row_number in item : already_there.remove(item) if new_text != _("Set..."): already_there.append((row_number,new_text)) # Set the new partition row value on the fourth column (3) self.BootPartitionListStore.set_value(iter, 3, new_text) if new_text != _("Set..."): self.BootPartitionListStore.set_value(iter, 4, 'gtk-yes') else: self.BootPartitionListStore.set_value(iter, 4, 'gtk-edit') # Re-enables buttons if there are other valid entries if already_there != [] : self.UndoButton.set_sensitive(True) self.EditButton.set_sensitive(True) self.ExecuteButton.set_sensitive(True) self.UpButton.set_sensitive(True) self.DownButton.set_sensitive(True) def on_label_cellrenderercombo_editing_started(self, widget, path, data): self.UndoButton.set_sensitive(False) self.EditButton.set_sensitive(False) self.ExecuteButton.set_sensitive(False) self.UpButton.set_sensitive(False) self.DownButton.set_sensitive(False) def on_label_cellrenderercombo_editing_canceled(self, data): self.UndoButton.set_sensitive(False) self.EditButton.set_sensitive(True) self.ExecuteButton.set_sensitive(True) self.UpButton.set_sensitive(True) self.DownButton.set_sensitive(True) def on_up_button_clicked(self, widget, data=None): """ Move the row items upward. """ # Obtain selection sel = self.BootPartitionTreeview.get_selection() # Get selected path ( model, rows ) = sel.get_selected_rows() if rows == [] : return # Get new path for each selected row and swap items. for path1 in rows: # Move path2 upward path2 = ( path1[0] - 1, ) # If path2 is negative, we're trying to move first path up. if path2[0] < 0: return # Obtain iters and swap items. iter1 = model.get_iter( path1 ) iter2 = model.get_iter( path2 ) model.swap( iter1, iter2 ) def on_down_button_clicked(self, widget, data=None): """ Move the row items downward. """ # Obtain selection sel = self.BootPartitionTreeview.get_selection() # Get selected path ( model, rows ) = sel.get_selected_rows() if rows == [] : return # Get new path for each selected row and swap items. for path1 in rows: # Move path2 downward path2 = ( path1[0] + 1, ) # Obtain iters and swap items. # If the second iter is invalid, we're trying to move the last item down. iter1 = model.get_iter( path1 ) try : iter2 = model.get_iter( path2 ) except ValueError: return model.swap( iter1, iter2 ) def on_undo_button_clicked(self, widget, data=None): """ Delete lilosetup.conf & unmount temporary mountpoints """ setup_partition_list() self.EditButton.set_sensitive(False) self.UndoButton.set_sensitive(False) self.ExecuteButton.set_sensitive(False) self.UpButton.set_sensitive(False) self.DownButton.set_sensitive(False) self.BootPartitionTreeview.get_selection().unselect_all() self.BootPartitionTreeview.set_sensitive(True) global already_there already_there = [] if partition_set == [] : pass else: lilosetup_undo() def on_edit_button_clicked(self, widget, data=None): """ Opens the edit lilosetup.conf dialog. """ # Check if the configuration file has already beeen created if os.path.isfile(config_location) == False : create_configuration() if 'failure' not in config_creation: try : if os.path.isfile("/usr/bin/mousepad") : subprocess.call('/usr/bin/mousepad ' + config_location + ' 2>/dev/null', shell=True) elif os.path.isfile("/usr/bin/leafpad") : subprocess.call('/usr/bin/leafpad ' + config_location + ' 2>/dev/null', shell=True) elif os.path.isfile("/usr/bin/gedit") : subprocess.call('/usr/bin/gedit ' + config_location + ' 2>/dev/null', shell=True) elif os.path.isfile("/usr/bin/kwrite") : subprocess.call('/usr/bin/kwrite ' + config_location + ' 2>/dev/null', shell=True) elif os.path.isfile("/usr/bin/geany") : subprocess.call('/usr/bin/geany ' + config_location + ' 2>/dev/null', shell=True) else : subprocess.call('xdg-open ' + config_location + ' 2>/dev/null', shell=True) # Ensure changes are taken into account subprocess.call('sync', shell=True) subprocess.call('rm -f ' + config_location + '~ 2>/dev/null', shell=True) self.UpButton.set_sensitive(False) self.DownButton.set_sensitive(False) self.ExecuteButton.set_sensitive(True) self.BootPartitionTreeview.get_selection().unselect_all() self.BootPartitionTreeview.set_sensitive(False) except : self.EditButton.set_sensitive(False) error_dialog(_("Sorry, LiloSetup is unable to find a suitable text editor in your system. You will not be able to manually modify LiloSetup configuration.\n")) def on_execute_button_clicked(self, widget, data=None): # Check if the configuration file has already beeen created if os.path.isfile(config_location) == False : create_configuration() if 'failure' not in config_creation: # Check if at least one Linux partition has been configured: if partition_set == [] : error_dialog(_("Your configuration is not complete. Please, select at least one Linux booting partition and define its Boot menu label.\n")) self.UndoButton.clicked() else: warning_dialog(_("You are about to install a new LILO bootloader. Are you sure you want to continue?")) if result_warning == gtk.RESPONSE_YES: # If previous lilosetup.conf file exist, save it as lilosetup.old if os.path.isfile(chroot_mnt + "/etc/lilosetup.conf") == True : os.rename(chroot_mnt + "/etc/lilosetup.conf", chroot_mnt +'/etc/lilosetup.old') shutil.copy(config_location, chroot_mnt + "/etc/lilosetup.conf") # Copy /boot/slackel graphics to chroot_mnt if needed if os.path.isfile(chroot_mnt + "/boot/slackel.bmp") == False : if os.path.isfile("/boot/slackel.bmp") == True : shutil.copy("/boot/slackel.bmp", chroot_mnt + "/boot/slackel.bmp") elif os.path.isfile("/mnt/live/memory/images/01-core.lzm/boot/slackel.bmp") == True : shutil.copy("/mnt/live/memory/images/01-core.lzm/boot/slackel.bmp", chroot_mnt + "/boot/slackel.bmp") # Execute Lilo lilo_command = "lilo -v -r " + chroot_mnt + " -C /etc/lilosetup.conf > /var/log/lilosetup.log" output = commands.getstatusoutput(lilo_command) if 0 in output : info_dialog(_("The installation of your new LILO bootloader was succesful. You can now exit LiloSetup and reboot your computer.\n")) self.ExecuteButton.set_sensitive(False) else: error_dialog(_("The installation of your new LILO bootloader failed. Please verify /var/log/lilosetup.log, modify your settings and try again.\n")) self.ExecuteButton.set_sensitive(False) if result_warning == gtk.RESPONSE_NO: pass if __name__ == '__main__': # Checks for root privileges if os.getuid() != 0: error_dialog(_("Sorry! \n\nRoot privileges are required to run LiloSetup. ")) sys.exit(1) # Executes the main program LiloSetup() gtk.main()