aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DEPENDENCIES2
-rw-r--r--Makefile5
-rw-r--r--TODO1
-rw-r--r--examples/plugins/locks83
-rw-r--r--src/plugins/locks.py193
5 files changed, 281 insertions, 3 deletions
diff --git a/DEPENDENCIES b/DEPENDENCIES
index 737bf3d..08a58d3 100644
--- a/DEPENDENCIES
+++ b/DEPENDENCIES
@@ -16,6 +16,8 @@ OPTIONAL RUNTIME DEPENDENCIES:
python-sysv-ipc: for ropty example
iputils: for ping support
inotify-tools: for inotify support
+ alarm: for limiting the time of a file search in locks
+ findutils: for file search in locks
BUILD DEPENDENCIES:
diff --git a/Makefile b/Makefile
index 317093b..a221ab6 100644
--- a/Makefile
+++ b/Makefile
@@ -32,13 +32,14 @@ PLUGINS = chase clock cpuinfo cpuonline cpu df discstats ipaddress \
kmsg leapsec linereader loadavg lunar mem moc network \
pacman snmp snmp6 softirqs solar uname uptime users \
vmstat weather xdisplay xkb alsa dentrystate inodestate \
- files hdparm tzclock ropty ping inotify random swaps
+ files hdparm tzclock ropty ping inotify random swaps \
+ locks
PLUGIN_EXAMPLES = chase clock cpu cpuinfo cpuonline df discstats \
ipaddress kmsg loadavg lunar mem moc network \
pacman uname uptime users xdisplay xkb alsa \
dentrystate inodestate files tzclock ropty ping \
- inotify random swaps
+ inotify random swaps locks
TRICK_EXAMPLES = localutcclock anytzclock
diff --git a/TODO b/TODO
index 6a293c1..67f950b 100644
--- a/TODO
+++ b/TODO
@@ -43,7 +43,6 @@ List of plugins to implement:
/proc/net/udp
/proc/net/unix
/proc/sysvipc
- /proc/locks
Demo plugins:
diff --git a/examples/plugins/locks b/examples/plugins/locks
new file mode 100644
index 0000000..b24f870
--- /dev/null
+++ b/examples/plugins/locks
@@ -0,0 +1,83 @@
+# -*- python -*-
+
+# A xpybar configuration example testing the features of plugins.locks
+
+import time
+import threading
+
+from plugins.locks import Locks
+
+
+OUTPUT, HEIGHT, YPOS, TOP = 0, 12, 24, True
+
+
+locks = Locks().locks
+headers = ('Id', 'Syscall', 'Level', 'Shared', 'Pid', 'Device', 'Inode', 'Range', 'Mountpoint', 'Pathname')
+def convert(lock):
+ rc = []
+ rc.append(str(lock.lock_id))
+ rc.append({'FLOCK' : 'flock', 'POSIX' : 'lockf'}[lock.lock_type])
+ rc.append('mandatory' if lock.mandatory else 'advisory')
+ rc.append('yes' if lock.shared else 'no')
+ rc.append(str(lock.pid))
+ rc.append('%i:%i' % (lock.major, lock.minor))
+ rc.append(str(lock.inode))
+ rc.append('%i..%s' % (lock.start, 'eof' if lock.end is None else str(lock.end)))
+ rc += ['...'] * 2
+ return rc
+text_locks = [convert(lock) for lock in locks]
+
+HEIGHT *= len(text_locks)
+
+
+def locate_files():
+ dev_cache = {}
+ for i, lock in enumerate(locks):
+ if (lock.major, lock.minor) in dev_cache:
+ mountpoint, device = dev_cache[(lock.major, lock.minor)]
+ text_locks[i][-2] = mountpoint
+ text_locks[i][5] = '%s(%s)' % (text_locks[i][5], device)
+ else:
+ device = lock.find_device()
+ mountpoint = device[1] if device is not None else '(not found)'
+ device = device[2] if device is not None else None
+ dev_cache[(lock.major, lock.minor)] = (mountpoint, device)
+ text_locks[i][-2] = mountpoint
+ if device is not None:
+ text_locks[i][5] = '%s(%s)' % (text_locks[i][5], device)
+ if not mountpoint.startswith('/'):
+ text_locks[i][-1] = '(not found)'
+ try:
+ with open('/proc/%i/comm' % lock.pid, 'rb') as file:
+ cmd = file.read()
+ cmd = cmd.decode('utf-8', 'replace')[:-1]
+ text_locks[i][4] = '%s(%s)' % (text_locks[i][4], cmd)
+ except:
+ pass
+ bar.invalidate()
+ for i, lock in enumerate(locks):
+ mountpoint = text_locks[i][-2]
+ if not mountpoint.startswith('/'):
+ continue
+ pathnames = lock.find_pathname(mountpoint)
+ if len(pathnames) == 0:
+ text_locks[i][-1] = '(not found)'
+ else:
+ text_locks[i][-1] = pathnames[0]
+ bar.invalidate()
+
+
+start_ = start
+def start():
+ start_()
+ async(locate_files)
+
+
+semaphore = threading.Semaphore()
+def redraw():
+ semaphore.acquire(blocking = True)
+ text = '\n'.join(' │ '.join('%s: %s' % (h, v) for (h, v) in zip(headers, lock)) for lock in text_locks)
+ bar.clear()
+ bar.draw_coloured_text(0, 10, 0, 2, text)
+ semaphore.release()
+
diff --git a/src/plugins/locks.py b/src/plugins/locks.py
new file mode 100644
index 0000000..c22474c
--- /dev/null
+++ b/src/plugins/locks.py
@@ -0,0 +1,193 @@
+# -*- python -*-
+'''
+xpybar – xmobar replacement written in python
+Copyright © 2014, 2015 Mattias Andrée (maandree@member.fsf.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 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+'''
+
+
+class Locks:
+ '''
+ File lock statistics
+
+ @variable locks:list<Locks.Lock> List of file-locks
+ '''
+
+ def __init__(self):
+ '''
+ Constructor
+ '''
+ def convert(fields):
+ fields[0] = int(fields[0])
+ fields[4] = int(fields[4])
+ fields[5] = int(fields[5], 16)
+ fields[6] = int(fields[6], 16)
+ fields[7] = int(fields[7])
+ fields[8] = int(fields[8])
+ fields[9] = int(fields[9]) if not fields[9] == 'EOF' else None
+ return fields
+ with open('/proc/locks', 'rb') as file:
+ data = file.read()
+ data = data.decode('utf-8', 'replace').rstrip('\n').replace(':', ' ').replace(' ', ' ')
+ while ' ' in data:
+ data = data.replace(' ', ' ')
+ self.locks = [Locks.Lock(*convert(line.split(' '))) for line in data.split('\n')]
+
+
+ @staticmethod
+ def find_device(major, minor):
+ '''
+ Find a device from its major and minor number
+
+ This method will not work if a mount's mountpoint or a
+ device's pathname includes spaces or line feeds.
+
+ @param major:int The major number of the device, in decimal form
+ @param minor:int The minor number of the device, in decimal form
+ @return (root:str, mountpoint:str, device:str)? The root of the mount within the filesystem,
+ the mountpoint relative to the process's root,
+ and the mounted device (pathname or RAM filesystem),
+ `None` if not found
+ '''
+ with open('/proc/self/mountinfo', 'rb') as file:
+ data = file.read()
+ data = data.decode('utf-8', 'strict').rstrip('\n').split('\n')
+ devnum = '%i:%i' % (major, minor)
+ for line in data:
+ line = line.split(' ')
+ if not line[2] == devnum:
+ continue
+ return (line[3], line[4], line[-2])
+ return None
+
+
+ @staticmethod
+ def find_pathname(mountpoint, inode, alarm = 1):
+ '''
+ Find the pathname of a file based on its mountpoint and inode number
+
+ @param mountpoint:str The mountpoint of the file
+ @param inode:int The inode number of the file
+ @param alarm:int|str? The time limit of the search, `None` to search forever
+ @return :list<str> All found pathnames of the file
+ '''
+ from subprocess import Popen, PIPE
+ command = ['find', mountpoint, '-inum', str(inode), '-mount', '-print0']
+ if alarm is not None:
+ command = ['alarm', str(inode)] + command
+ found = Popen(command, stdin = PIPE, stdout = PIPE, stderr = PIPE).communicate()[0]
+ found = found.decode('utf-8', 'replace').rstrip('\n')
+ found = [pathname for pathname in found.split('\0') if not pathname == '']
+ return found
+
+
+ @staticmethod
+ def find(major, minor, inode, alarm = 1):
+ '''
+ Find the pathname of a file based on its device's major and minor number and its inode number
+
+ @param major:int The major number of the device, in decimal form
+ @param minor:int The minor number of the device, in decimal form
+ @param inode:int The inode number of the file
+ @param alarm:int|str? The time limit of the search, `None` to search forever
+ @return :list<str> All found pathnames of the file
+ '''
+ device = Locks.find_device()
+ if device is None:
+ return []
+ return Locks.find_pathname(device[1], inode, alarm)
+
+
+ class Lock:
+ '''
+ Information about a file-lock
+
+ @variable lock_id:int The unique ID of the lock
+ @variable lock_type:str Either 'FLOCK' or 'POSIX', indicating what lock system has been used,
+ 'FLOCK' indicates the 'flock' system call, and 'POSIX' indicates the
+ 'lockf' system call.
+ @variable mandatory:bool Whether the lock is mandatory rather than advisory
+ @variable shared:bool Whether the process has locked the file for reading rather than writing
+ @variable pid:int The ID of the process that has locked the file
+ @variable major:int The major number of the device on which the locked file is stored
+ @variable minor:int The minor number of the device on which the locked file is stored
+ @variable inode:int The inode number of the locked file
+ @variable start:int The index of the first bytes of the locked area
+ @variable end:int? The index of the last bytes of the locked area, `None` for end-of-file
+ '''
+ def __init__(self, lock_id, lock_type, lock_level, sharedness, pid, major, minor, inode, start, end):
+ '''
+ Constructor
+
+ @param lock_id:int The unique ID of the lock
+ @param lock_type:str Either 'FLOCK' or 'POSIX', indicating what lock system has been used,
+ 'FLOCK' indicates the 'flock' system call, and 'POSIX' indicates the
+ 'lockf' system call.
+ @param lock_level:str Either 'MANDATORY' or 'ADVISORY' indicating the level of the lock
+ @param sharedness:str Either 'READ' or 'WRITE' indicating the sharedness of the lock
+ @param pid:int The ID of the process that has locked the file
+ @param major:int The major number of the device on which the locked file is stored
+ @param minor:int The minor number of the device on which the locked file is stored
+ @param inode:int The inode number of the locked file
+ @param start:int The index of the first bytes of the locked area
+ @param end:int? The index of the last bytes of the locked area, `None` for end-of-file
+ '''
+ self.lock_id = lock_id
+ self.lock_type = lock_type
+ self.mandatory = lock_level == 'MANDATORY'
+ self.shared = sharedness == 'READ'
+ self.pid = pid
+ self.major = major
+ self.minor = minor
+ self.inode = inode
+ self.start = start
+ self.end = end
+
+
+ def find_device(self):
+ '''
+ Find the device that the locked file is located on
+
+ This method will not work if a mount's mountpoint or a
+ device's pathname includes spaces or line feeds.
+
+ @return (root:str, mountpoint:str, device:str)? The root of the mount within the filesystem,
+ the mountpoint relative to the process's root,
+ and the mounted device (pathname or RAM
+ filesystem), `None` if not found
+ '''
+ return Locks.find_device(self.major, self.minor)
+
+
+ def find_pathname(self, mountpoint, alarm = 1):
+ '''
+ Find the pathname of the locked file based on its mountpoint
+
+ @param mountpoint:str The mountpoint of the file
+ @param alarm:int|str? The time limit of the search, `None` to search forever
+ @return :list<str> All found pathnames of the file
+ '''
+ return Locks.find_pathname(mountpoint, self.inode, alarm)
+
+
+ def find(self, alarm = 1):
+ '''
+ Find the pathname of the locked file
+
+ @param alarm:int|str? The time limit of the search, `None` to search forever
+ @return :list<str> All found pathnames of the file
+ '''
+ return Locks.find(self.major, self.minor, self.inode, alarm)
+