diff options
Diffstat (limited to 'mountconfig/fuser.py')
-rw-r--r-- | mountconfig/fuser.py | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/mountconfig/fuser.py b/mountconfig/fuser.py new file mode 100644 index 0000000..d898b37 --- /dev/null +++ b/mountconfig/fuser.py @@ -0,0 +1,299 @@ +#!/usr/bin/python +########################################################################### +# fuser.py - description # +# ------------------------------ # +# begin : Wed Jun 15 2005 # +# copyright : (C) 2005-2006 by Sebastian Kuegler # +# email : [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; either version 2 of the License, or # +# (at your option) any later version. # +# # +########################################################################### +""" +TODO: +- Fix running standalone: + * KCmdLineArgs stuff. +""" + +import sys +import os +from qt import * +from kdeui import * +#import kdedesigner +from fuser_ui import * +from SimpleCommandRunner import * + +standalone = __name__ == "__main__" + +class FileProcess(QListViewItem): + """ A FileProcess is simply one line from lsof, one filedescriptor that's in use + by a process represented as a listviewitem in the lsof processtable. """ + + # Available signals. + signals = { + "TERM":15, + "KILL":9 } + + # Column names mapping. + cols = { + "pname":0, + "pid":1, + "powner":2, + "pfile":3 } + + def __init__(self,parent,pid,isparent=False): + QListViewItem.__init__(self,parent) + self.setPid(pid) + self.isparent = isparent + self.pfile = "" + self.pix = None + + def setPid(self,pid): + self.pid = pid + + def setName(self,pname): + self.pname = pname + + def setOwner(self,powner): + self.powner = powner + + def setFile(self,pfile): + self.pfile = pfile + + def setPixmaps(self,pix): + """ Eats a dict with pixmaps. """ + self.pix = pix + + def sendSignal(self,signal): + """ Parses a signal string representation or a signal number and sends it to + the process.""" + if not self.isparent: + print "Item is not a process, only a filedescriptor." + return + try: + signal_int = int(signal) + except ValueError: + try: + signal_int = self.signals[signal] + except IndexError: + print "No known signal received ", signal + return False + try: + rc = os.kill(int(self.pid),signal_int) # TODO: Catch OSError + except OSError, message: + print "OSError: Couldn't %s %s %s" % (signal,self.pname,self.pid) + print message + if not rc: + print "Successfully sent signal ", signal_int, " to process ", self.pid + return True + print "Signal %i didn't succeed" % signal_int + return False + + def fillColumns(self): + """ Writes strings into columns once an entry is completed. """ + if self.isparent: + self.setText(self.cols["pid"],self.pid) + self.setText(self.cols["pname"],self.pname) + self.setText(self.cols["powner"],self.powner) + self.setPixmap(0,self.pix["exec"]) + self.setPixmap(1,self.pix["pid"]) + self.setPixmap(2,self.pix["owner"]) + else: + self.setText(self.cols["pfile"],self.pfile) + self.setPixmap(3,self.pix["file"]) + +######################################################################################################## +class FUser(FUserUI): + """ done() / result() return 0 on successful umount and 1 if cancelled. """ + + def __init__(self,device,parentdialog=None,lsof_bin='/usr/sbin/lsof',kapp=None): + FUserUI.__init__(self,parentdialog,name = None,modal = 0,fl = 0) + self.device = device + self.fileprocesses = [] + self.lsof_bin = '/usr/sbin/lsof' + self.setLsof(lsof_bin) + self.setApp(kapp) + + self.processlist.clear() + self.processhidden = False + # We're having processes blocking umounting, show that. + self.umountbutton.setEnabled(False) + + self.explanationlabel.setText( + unicode(i18n("""The volume %s is in use and can not be disabled.<br> + <br> + The processes that are blocking %s are listed below. These processes must be closed + before %s can be disabled. + Killing a process may cause data loss! Make sure all work is saved before killing an + application. + """)) % (self.device,self.device,self.device)) + + self.connect(self.cancelbutton,SIGNAL("clicked()"),self.slotCancelButtonClicked) + self.connect(self.killbutton,SIGNAL("clicked()"),self.slotKillButtonClicked) + self.connect(self.killallbutton,SIGNAL("clicked()"),self.slotKillallButtonClicked) + self.connect(self.refreshbutton,SIGNAL("clicked()"),self.refreshProcesslist) + self.connect(self.processlist,SIGNAL("selectionChanged()"),self.slotSelectionChanged) + self.connect(self.umountbutton,SIGNAL("clicked()"),self.slotUmountButtonClicked) + + # TODO: Make optionsbutton resize dialog if processframe is hidden, hide Optionsbutton until then. + self.optionsbutton.hide() + self.readPixmaps() + self.warningimage.setPixmap(MainBarIcon("messagebox_warning")) + + # Delayed initialisation. + QTimer.singleShot(0,self.isMounted) + QTimer.singleShot(0,self.refreshProcesslist) + + def setApp(self,app): + """ We need a reference to the (K|Q)Application for certain things, e.g. setting + the MouseCursor. """ + self.app = app + + def setLsof(self,path): + """ Where's the lsof binary? """ + if os.path.isfile(path): + self.lsof_bin = path + else: + print path, " is not a valid binary, keeping %s", self.lsof_bin + + def readPixmaps(self): + self.pix = { + "exec": UserIcon("exec"), + "owner": UserIcon("user"), + "pid": UserIcon("tux"), + "file": UserIcon("file")} + + def refreshProcesslist(self): + """ Read lsof output and add the processdescriptors to the listview. """ + kapp = self.app + + kapp.setOverrideCursor(QCursor(Qt.BusyCursor)) + + self.processlist.clear() + rc, output = SimpleCommandRunner().run([self.lsof_bin,'-FpcLn',self.device],True) + procs = output.split() + + self.processes = [] + self.realprocesses = [] + for line in procs: + line = str(line) + type = line[0] + info = line[1:] + + if type is "p": + pid = info + parentproc = FileProcess(self.processlist,pid,True) + self.processes.append(parentproc) + self.realprocesses.append(parentproc) + parentproc.setPixmaps(self.pix) + files = 0 + + if type == "c": + pname = info + parentproc.setName(pname) + + if type == "L": + powner = info + parentproc.setOwner(powner) + + if type == "n": + pfile = info + childproc = FileProcess(parentproc,pid) + self.processes.append(childproc) + childproc.setPixmaps(self.pix) + childproc.setFile(pfile) + childproc.setOwner(powner) + childproc.setName(pname) + if files == 0: + parentproc.fillColumns() + files += 1 + childproc.fillColumns() + + kapp.restoreOverrideCursor() + + # Enable / disable buttons which are (in)appropriate. + self.killallbutton.setEnabled(len(self.realprocesses)!=0) + self.killbutton.setEnabled(len(self.realprocesses)!=0) + self.umountbutton.setEnabled(len(self.realprocesses)==0) + if self.processlist.selectedItem() == None: + self.killbutton.setEnabled(False) + + def isMounted(self): + rc,output = SimpleCommandRunner().run(["/bin/mount"],False) + mounts = [] + for line in output.split('\n'): + try: + mounts.append(line.split()[0]) + except IndexError: + pass + ismounted = self.device in mounts + self.umountbutton.setEnabled(ismounted) + return ismounted + + def slotCancelButtonClicked(self): + self.done(1) + + def slotKillButtonClicked(self): + try: + self.processlist.selectedItem().sendSignal("KILL") + self.refreshProcesslist() + except AttributeError: + print "No killable item selected." + + def slotKillallButtonClicked(self): + for process in self.realprocesses: + process.sendSignal("KILL") + self.refreshProcesslist() + + def slotOptionsButtonCLicked(self): + self.processhidden = not self.processhidden + self.processframe.setHidden(self.processhidden) + + def slotSelectionChanged(self): + """ Check if item is a process or a file, disable killbutton for children. """ + selected = self.processlist.selectedItem() + if not selected.isparent: + self.killbutton.setEnabled(False) + else: + self.killbutton.setEnabled(True) + + def slotUmountButtonClicked(self): + SimpleCommandRunner + rc, output = SimpleCommandRunner().run(['/bin/umount',self.device]) + if rc == 0: + print "%s successfully unmounted." % self.device + # Close dialog and return 0 - sucessfully umounted. + self.done(0) + else: + print "Unmounting %s failed: %s" % (self.device,output[:-1]) + self.isMounted() + +################################################################################################ +if standalone: + device = "/dev/hda1" + print 'Device is ', device + + cmd_args = KCmdLineArgs.init(sys.argv, "FUser", + "A graphical frontend to fuser, but without using it :-)", "0.2") + + # ---------------------------------------------------------------------------- + # FIXME: All the arg-parsing stuff does not work yet since I don't understand KCmdLineArgs. + options = [("device <device>", "Device to umount")] + KCmdLineArgs.addCmdLineOptions(options) + args = KCmdLineArgs.parsedArgs() + # print args.count() + # ---------------------------------------------------------------------------- + + kapp = KApplication() + KGlobal.iconLoader().addAppDir("guidance") + fuserapp = FUser(device) + + fuserapp.setApp(kapp) + kapp.setMainWidget(fuserapp) + fuserapp.show() + kapp.exec_loop() |