summaryrefslogtreecommitdiffstats
path: root/mountconfig/fuser.py
diff options
context:
space:
mode:
Diffstat (limited to 'mountconfig/fuser.py')
-rw-r--r--mountconfig/fuser.py299
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()