#!/usr/bin/python
###########################################################################
# fuser.py - description #
# ------------------------------ #
# begin : Wed Jun 15 2005 #
# copyright : (C) 2005-2006 by Sebastian Kuegler #
# email : sebas@vizZzion.org #
# #
###########################################################################
# #
# 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 tdeui 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.
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 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()