#!/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: * TDECmdLineArgs stuff. """ import sys import os from python_tqt.qt 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, 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. 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 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 = 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 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()