You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
11 KiB
300 lines
11 KiB
#!/usr/bin/python3
|
|
###########################################################################
|
|
# 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:
|
|
* TDECmdLineArgs stuff.
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
from PyTQt.tqt import *
|
|
from tdeui import *
|
|
#import kdedesigner
|
|
from fuser_ui import *
|
|
from SimpleCommandRunner import *
|
|
|
|
standalone = __name__ == "__main__"
|
|
|
|
class FileProcess(TQListViewItem):
|
|
""" 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):
|
|
TQListViewItem.__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 as 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(
|
|
str(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,TQ_SIGNAL("clicked()"),self.slotCancelButtonClicked)
|
|
self.connect(self.killbutton,TQ_SIGNAL("clicked()"),self.slotKillButtonClicked)
|
|
self.connect(self.killallbutton,TQ_SIGNAL("clicked()"),self.slotKillallButtonClicked)
|
|
self.connect(self.refreshbutton,TQ_SIGNAL("clicked()"),self.refreshProcesslist)
|
|
self.connect(self.processlist,TQ_SIGNAL("selectionChanged()"),self.slotSelectionChanged)
|
|
self.connect(self.umountbutton,TQ_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.
|
|
TQTimer.singleShot(0,self.isMounted)
|
|
TQTimer.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("application-x-executable"),
|
|
"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.setOverrideCursorT(TQCursor(TQt.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 == "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 = TDECmdLineArgs.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 TDECmdLineArgs.
|
|
options = [("device <device>", "Device to umount")]
|
|
TDECmdLineArgs.addCmdLineOptions(options)
|
|
args = TDECmdLineArgs.parsedArgs()
|
|
# print args.count()
|
|
# ----------------------------------------------------------------------------
|
|
|
|
kapp = TDEApplication()
|
|
TDEGlobal.iconLoader().addAppDir("guidance")
|
|
fuserapp = FUser(device)
|
|
|
|
fuserapp.setApp(kapp)
|
|
kapp.setMainWidget(fuserapp)
|
|
fuserapp.show()
|
|
kapp.exec_loop()
|