[ANNOUNCE] rmkernel.py v0.2 -- a simple tool for deleting Linux kernels

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



As Steven Rostedt pointed out when announcing his clean-boot.pl tool, one can end up with quite a large number of installed kernels if building and installing from a GIT playground. Attached is a Python script that eases the burden of managing this problem by providing a convenient mechanism for deleting kernels.

Usage: rmkernel.py [-i] [-v] <kernel> ....
       rmkernel.py -l [<kernel> ...]
       rmkernel.py -g

where <kernel> can be a glob expression (n.b. lists of expressions accepted) describing which kernels to delete, the -i option selects interactive mode where confirmation is requested for each deletion and -v lists each kernel that has been deleted. This script uses grubby to update grub (or lilo etc.) for the removal of the kernel. Additionally, the script checks to see if the kernel was installed via rpm and if it was uses rpm to do the removal. It will not delete the currently running kernel and will silently skip it if it is in the list of kernels to be deleted.

The -l option lists installed kernels that match the <kernel> glob expression(s) or all installed kernels if no globs are provided. The currently running kernel will be marked with "******" after its name.

The -g selects "GUI mode" (using PyGTK) and presents a list of installed kernels which can be selected and deleted with the provided buttons. The currently running kernel is not included in the list but is displayed at the top of the window for information only.

Peter
--
Peter Williams                                   [email protected]

"Learning, n. The kind of ignorance distinguishing the studious."
 -- Ambrose Bierce
#!/usr/bin/env python

### Copyright (C) 2005 Peter Williams <[email protected]>

### 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; version 2 of the License only.

### 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.

### A copy of the GNU General Public License is available at
### <http://www.gnu.org/licenses/gpl.txt>; if not, write to the Free Software
### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

## Version 0.2

import os, os.path, signal, popen2, fcntl, select, re, pygtk, gtk

def make_non_blocking(fd):
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    try:
        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY)
    except AttributeError:
        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.FNDELAY)

# Take advice from "Python Cookbook" on avoiding potential dead locks
# reading stdout and stderr from sub process
def run_cmd(cmd, input=None):
    savedsh = signal.getsignal(signal.SIGPIPE)
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
    sub = popen2.Popen3(cmd, True)
    if not input is None:
        sub.tochild.write(input)
    sub.tochild.close()
    outfd = sub.fromchild.fileno()
    errfd = sub.childerr.fileno()
    make_non_blocking(outfd)
    make_non_blocking(errfd)
    outd, errd = [], []
    outeof = erreof = False
    while True:
        to_check = [outfd] * (not outeof) + [errfd] * (not erreof)
        ready = select.select(to_check, [], [])
        if outfd in ready[0]:
            outchunk = sub.fromchild.read()
            if outchunk == '':
                outeof = True
            else:
                outd.append(outchunk)
        if errfd in ready[0]:
            outchunk = sub.childerr.read()
            if outchunk == '':
                erreof = True
            else:
                errd.append(outchunk)
        if outeof and erreof:
            break
        try:
            select.select([], [], [], 0.05)
        except select.error, data:
            if data[0] is errno.EINTR:
                pass
            else:
                return [ data[0], "", data[1] ]
    res = sub.wait()
    signal.signal(signal.SIGPIPE, savedsh)
    return [ res, ''.join(outd), ''.join(errd) ]

_kernel_re = re.compile("^(\d+)\.(\d+)\.(\d+)(.*)$")

def cmp_kernels(k1, k2):
    m1 = _kernel_re.match(k1)
    m2 = _kernel_re.match(k2)
    for i in range(3):
        res = cmp(int(m1.group(i+1)), int(m2.group(i+1)))
        if res != 0:
            return res
    if len(m1.groups()) == 4:
        if len(m2.groups()) == 4:
            return cmp(m1.group(4), m2.group(4))
        else:
            return -1
    elif len(m2.groups()) == 4:
        return 1
    return 0

def file_is_known_to_rpm(file):
    res, so, se = run_cmd("rpm -qf " + file)
    return res == 0

def remove_rpm_kernel(kernel):
    kfile = kernel_file(kernel)
    run_cmd("rpm -e --nodeps `rpm -qf " + kfile + "`")

def modules_dir(kernel):
    return "/lib/modules/" + kernel

def kernel_file(kernel):
    return "/boot/vmlinuz-" + kernel

def system_map_file(kernel):
    return "/boot/System.map-" + kernel

def initrd_file(kernel):
    return "/boot/initrd-" + kernel + ".img"

def remove_kernel_modules(kernel):
    kmdir = modules_dir(kernel)
    if os.path.isdir(kmdir):
        os.system("/bin/rm -r " + kmdir)

def _remove_files(file):
    if os.path.exists(file):
        os.remove(file)
    if os.path.exists(file + ".old"):
        os.remove(file + ".old")

def remove_kernel_file(kernel):
    _remove_files(kernel_file(kernel))

def remove_system_map_file(kernel):
    _remove_files(system_map_file(kernel))

def remove_initrd_file(kernel):
    _remove_files(initrd_file(kernel))

def remove_kernel_from_grub(kernel):
    kfile = kernel_file(kernel)
    if run_cmd("/sbin/grubby --info=" + kfile)[0] == 0:
        run_cmd("/sbin/grubby --remove-kernel=" + kfile)

def remove_kernel(kernel, verbose=False):
    if verbose:
        print "Removing", kernel,
    if file_is_known_to_rpm(kernel_file(kernel)):
        remove_rpm_kernel(kernel)
        if verbose:
            print "done via rpm."
    else:
        remove_kernel_file(kernel)
        remove_system_map_file(kernel)
        remove_initrd_file(kernel)
        remove_kernel_modules(kernel)
        remove_kernel_from_grub(kernel)
        if verbose:
            print "done."

def find_kernels():
    kernels = os.listdir("/lib/modules")
    kre = re.compile("^vmlinuz-(.*?)(.old)?$")
    for f in os.listdir("/boot"):
        m = kre.match(f)
        if m:
            k = m.group(1)
            if k not in kernels:
                kernels.append(m.group(1))
    return kernels

def get_current_kernel():
    res, so, se = run_cmd("uname -r")
    return so.strip()

class gui(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        self.connect("destroy", self._quit)
        self.set_title("rmkernel")
        self.tooltips = gtk.Tooltips()
        self.window = self.get_root_window()
        vbox = gtk.VBox()
        self.add(vbox)
        self.current_kernel = get_current_kernel()
        ck_label = gtk.Label(self.current_kernel + " :Current Kernel")
        ck_label.set_padding(0, 0)
        vbox.pack_start(ck_label, False, False, 0)
        self.store = gtk.ListStore(str)
        self.view = gtk.TreeView(self.store)
        text_cell = gtk.CellRendererText()
        tvcolumn = gtk.TreeViewColumn("Installed Kernels")
        tvcolumn.pack_start(text_cell)
        tvcolumn.set_attributes(text_cell, text=0)
        self.view.append_column(tvcolumn)
        sw = gtk.ScrolledWindow()
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        sw.add(self.view)
        vbox.add(sw)
        self.selection = self.view.get_selection()
        self.selection.set_mode(gtk.SELECTION_MULTIPLE)
        self.update_kernels()
        bbox = gtk.HButtonBox()
        vbox.pack_start(bbox, False, False, 0)
        qbutton = gtk.Button("Quit", gtk.STOCK_QUIT)
        bbox.add(qbutton)
        qbutton.connect("clicked", self._quit)
        self.tooltips.set_tip(qbutton, "Quit this application")
        rbutton = gtk.Button("Refresh", gtk.STOCK_REFRESH)
        bbox.add(rbutton)
        rbutton.connect("clicked", self.update_kernels)
        self.tooltips.set_tip(rbutton, "Refresh the list of installed kernels")
        dbutton = gtk.Button("Delete", gtk.STOCK_DELETE)
        bbox.add(dbutton)
        dbutton.connect("clicked", self.delete_selection)
        self.tooltips.set_tip(dbutton, "Delete/uninstall the selected kernels")
        self.selection.unselect_all()
        self.show_all()
        self.show()
    def _show_busy(self):
        self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
        while gtk.events_pending():
            gtk.main_iteration()
    def _unshow_busy(self):
        self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
    def update_kernels(self, data=None):
        self._show_busy()
        kernels = find_kernels()
        kernels.sort(cmp_kernels)
        self.store.clear()
        for k in kernels:
            if k != self.current_kernel:
                self.store.append([k])
        self._unshow_busy()
    def _get_selected_kernels_cb(self, store, path, iter, list):
        list.append(store.get_value(iter, 0))
    def delete_selection(self, data=None):
        self._show_busy()
        res = []
        self.selection.selected_foreach(self._get_selected_kernels_cb, res)
        for k in res:
            try:
                remove_kernel(k)
            except OSError, msg:
                dialog = gtk.Dialog("rmkernel", self, gtk.DIALOG_MODAL,
                    (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
                dialog.vbox.add(gtk.Label("Error: " + str(msg)))
                dialog.show_all()
                dialog.run()
                dialog.destroy()
                break
        self._unshow_busy()
        self.update_kernels()
    def _quit(self, widget):
        gtk.main_quit()

if __name__ == "__main__":
    def print_usage_and_exit(ev):
        print "Usage: rmkernel.py [-i] [-v] <kernel> ..."
        print "       rmkernel.py -l [<kernel> ...]"
        print "       rmkernel.py -g"
        sys.exit(ev)
    import getopt, sys, fnmatch
    syntax_error = False
    interactive = False
    verbose = False
    list = False
    use_gui = False
    opts_ok = True
    args_ok = True
    try:
        optlist, args = getopt.getopt(sys.argv[1:], "ivlg")
    except getopt.GetoptError:
        syntax_error = True
    if syntax_error:
        print_usage_and_exit(1)
    for o, a in optlist:
        if o == "-i":
            interactive = True
            opts_ok = opts_ok and not (list or use_gui)
            import tty, termios
            def getch():
                fd = sys.stdin.fileno()
                old_settings = termios.tcgetattr(fd)
                try:
                    tty.setraw(fd)
                    ch = sys.stdin.read(1)
                finally:
                    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
                return ch
        if o == "-v":
            opts_ok = opts_ok and not (list or use_gui)
            verbose = True
        if o == "-l":
            opts_ok = opts_ok and not (verbose or interactive or use_gui)
            list = True
        if o == "-g":
            opts_ok = opts_ok and not (verbose or interactive or list)
            args_ok = len(args) == 0
            use_gui = True
    args_ok = args_ok and ((list or use_gui) or len(args) > 0)
    if not (args_ok and opts_ok):
        print_usage_and_exit(2)
    kernels = find_kernels()
    current_kernel = get_current_kernel()
    if list:
        if len(args) == 0:
            klist = kernels
            klist.sort(cmp_kernels)
        else:
            klist = []
            for g in args:
                sublist = fnmatch.filter(kernels, g)
                sublist.sort(cmp_kernels)
                klist += sublist
        for k in klist:
            if k == current_kernel:
                print k, "******"
            else:
                print k
    elif use_gui:
        app = gui()
        gtk.main()
    else:
        for g in args:
            klist = fnmatch.filter(kernels, g)
            klist.sort(cmp_kernels)
            for k in klist:
                if k == current_kernel:
                    continue
                if not interactive:
                    remove_kernel(k, verbose)
                else:
                    print "Delete", k, "?"
                    while True:
                        response = getch()
                        print response
                        if response in ["y", "n"]:
                            break
                        else:
                            beep()
                    if response == "y":
                        remove_kernel(k, verbose)
                del kernels[kernels.index(k)]

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux