Skip to content

Commit

Permalink
Merge pull request #1 from FrostyX/develop
Browse files Browse the repository at this point in the history
Version 0.1
  • Loading branch information
FrostyX committed Jan 6, 2014
2 parents c5695bb + 06e1bcc commit 275482d
Show file tree
Hide file tree
Showing 11 changed files with 270 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dist
build
eggs
parts
bin
#bin
var
sdist
develop-eggs
Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
tracer
======
# tracer

Tracer finds outdated running packages in your system

How does he do it? He simply finds all packages you have modified since you boot up. Then he traces their files in the jungle of your memory, ... senses them, and finally, finds them. In the end you will get list of packages what have been running while you updated or removed them.

## Requirements
- Supported linux distribution - There are currently supported [Fedora](http://fedoraproject.org/) and [Gentoo](http://www.gentoo.org/)
- Python interpreter
- Python [psutil](https://code.google.com/p/psutil/) module. Available [here](https://admin.fedoraproject.org/pkgdb/acls/name/python-psutil?_csrf_token=09739007ef8cd539e0b5903b86bd3ef76d614a4b) and [here](https://packages.gentoo.org/package/dev-python/psutil). Please use testing version on gentoo.

## Usage
Clone tracer from GitHub

git clone [email protected]:FrostyX/tracer.git

And simply run it

sudo ./tracer/bin/tracer.py

_Yeah, you really have to run it as root._


## Feedback
Please report any bugs or feature requests right [here](https://github.com/FrostyX/tracer/issues) on GitHub. Pull requests are also welcome. If you rather want a talk or something, you can find me on `#gentoo.cs` or `fedora-cs` `@freenode` or you can [mail me](mailto:[email protected]).
70 changes: 70 additions & 0 deletions bin/tracer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Tracer finds outdated running packages in your system
# Copyright 2013 Jakub Kadlčík

# Enable importing modules from parent directory (tracer's root directory)
import os
parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.sys.path.insert(0, parentdir)

# System modules
import re
import psutil
import platform

# Tracer modules
from packageManagers.yum import Yum
from packageManagers.portage import Portage
import resources.memory as memory

# Returns instance of package manager according to installed linux distribution
def package_manager():
def e(): raise OSError("Unknown or unsupported linux distribution")

distro = platform.linux_distribution(full_distribution_name=False)[0]
return {
'gentoo': Portage(),
'fedora': Yum(),
}.get(distro, e)

PACKAGE_MANAGER = package_manager()

# Returns list of packages what tracer should care about
def modified_packages():
# Else branch is only for dev and testing purposes
# Use: if True or if not True
if True:
packages = PACKAGE_MANAGER.packages_newer_than(psutil.BOOT_TIME)
else:
# Lets say these packages were updated
packages = [
{'name': 'xterm'},
{'name': 'ark'},
{'name': 'kactivities'},
]
return packages

# Returns list of packages which have some files loaded in memory
def trace_running():
"""
Returns list of package names which owns outdated files loaded in memory
@TODO This function should be hardly optimized
"""

files_in_memory = memory.files_in_memory()
packages = modified_packages()

modified = []
for package in packages:
for file in PACKAGE_MANAGER.package_files(package['name']):
# Doesnt matter what is after dot cause in package files there is version number after it
regex = re.compile('^' + re.escape(file) + "(\.*|$)")
if memory.is_in_memory(regex, files_in_memory):
modified.append(package['name'])
break
return modified

# More times a package is updated the more times it is contained in a package list.
for package in set(trace_running()):
print package
Empty file added packageManagers/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions packageManagers/ipackageManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#-*- coding: utf-8 -*-
"""This class should be inherited by any other package manager
Copyright 2013 Jakub Kadlčík"""

class IPackageManager:
def packages_newer_than(self, unix_time):
"""Returns list of packages which were modified between unix_time and present"""
raise NotImplementedError

def package_files(self, pkg_name):
"""Returns list of files provided by package"""
raise NotImplementedError
33 changes: 33 additions & 0 deletions packageManagers/portage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#-*- coding: utf-8 -*-
"""Module to work with portage package manager class
Copyright 2013 Jakub Kadlčík"""

from ipackageManager import IPackageManager
import commands
import time

class Portage(IPackageManager):

def packages_newer_than(self, unix_time):
"""
Returns list of packages which were modified between unix_time and present
Requires root permissions.
"""
newer = []
packages = commands.getoutput('sudo qlop -lC').split('\n')
for package in packages:
package = package.split(" >>> ")

# There actually should be %e instead of %d
modified = time.mktime(time.strptime(package[0], "%a %b %d %H:%M:%S %Y"))
if modified >= unix_time:
pkg_name = package[1]
pkg_name = pkg_name[:pkg_name.index('.')] # Cut from first . to end
pkg_name = pkg_name[:pkg_name.rindex('-')] # Cut from last - to end
newer.append({"name":pkg_name})
return newer

def package_files(self, pkg_name):
"""Returns list of files provided by package"""
return commands.getoutput('equery -q f ' + pkg_name).split('\n')

67 changes: 67 additions & 0 deletions packageManagers/yum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#-*- coding: utf-8 -*-
"""Module to work with yum package manager class
Copyright 2013 Jakub Kadlčík"""

from os import listdir
from ipackageManager import IPackageManager
import sqlite3
import commands

class Yum(IPackageManager):

def packages_newer_than(self, unix_time):
"""
Returns list of packages which were modified between unix_time and present
Requires root permissions.
"""

sql = """
SELECT *
FROM trans_data_pkgs JOIN pkgtups ON trans_data_pkgs.pkgtupid=pkgtups.pkgtupid
WHERE trans_data_pkgs.tid = ?
ORDER BY pkgtups.pkgtupid
"""

packages = []
sqlite = self._database_file()
conn = sqlite3.connect(sqlite)
conn.row_factory = sqlite3.Row
c = conn.cursor()

for t in self._transactions_newer_than(unix_time):
c.execute(sql, [t['tid']])
packages.extend(c.fetchall())
return packages

def package_files(self, pkg_name):
"""Returns list of files provided by package"""
# http://docs.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch04s02s03.html
return commands.getoutput('rpm -ql ' + pkg_name).split('\n')

def _transactions_newer_than(self, unix_time):
"""
Returns list of transactions which ran between unix_time and present.
Requires root permissions.
"""

sql = """
SELECT trans_beg.tid, trans_beg.timestamp AS beg, trans_end.timestamp AS end
FROM trans_beg JOIN trans_end ON trans_beg.tid=trans_end.tid
WHERE beg > ?
ORDER BY trans_beg.tid
"""

sqlite = self._database_file()
conn = sqlite3.connect(sqlite)
conn.row_factory = sqlite3.Row
c = conn.cursor()
c.execute(sql, [unix_time])
return c.fetchall()

def _database_file(self):
"""
Returns path to yum history database file
Requires root permissions.
"""
history_path = '/var/lib/yum/history/'
return history_path + listdir(history_path)[-1]
1 change: 1 addition & 0 deletions pydoc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.html
13 changes: 13 additions & 0 deletions pydoc/make
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

# Pydoc has to be run from project root directory!
pwd="$(pwd)"
cd "$(dirname "$0")" # Change directory to script location
cd ../

for file in $(pydoc -w ./ | awk '{print $2}');
do
mv "$file" pydoc;
done

cd $pwd
Empty file added resources/__init__.py
Empty file.
50 changes: 50 additions & 0 deletions resources/memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#-*- coding: utf-8 -*-
"""Module to work with files in memory
Copyright 2013 Jakub Kadlčík"""

import psutil
import re

def process_files(pid):
"""
Returns list of files which are used by process with given pid
"""
# By default work only with applications
paths = ['\/usr/bin', '\/usr/sbin']

# Work with libs too
paths.append('\/usr/lib')

# Make a regex that matches if any of our regexes match.
combined = "(" + ")|(".join(paths) + ")"

files = []
p = psutil.Process(pid)
for mmap in p.get_memory_maps():
if re.match(combined, mmap.path):
files.append(mmap.path)

return files

def is_in_memory(regex_file, memory):
"""
Predicates if file is loaded in memory
@TODO This function should be hardly optimized
"""
for file in memory:
if regex_file.match(file):
return True
return False

def files_in_memory():
"""
Returns list of all files loaded in memory
"""
files = []
for pid in psutil.get_pid_list():
try:
files += process_files(pid)
except psutil._error.NoSuchProcess:
pass

return set(files)

0 comments on commit 275482d

Please sign in to comment.