#!/usr/bin/python
#
# LC
#
# read an already created swirl and check if it can be run of the current
# system, which deps are missing, print on the screen current swirl
#
#
import os, string, stat
from swirl import Swirl
import utils
from FingerPrint.plugins import PluginManager
from FingerPrint.serializer import PickleSerializer
#
# compatibility with python2.4
#
try:
from hashlib import md5
except ImportError:
from md5 import md5
#
# compatibility with python2.4
#
if "any" not in dir(__builtins__):
from FingerPrint.utils import any
[docs]def readFromPickle(fileName):
"""
helper function to get a swirl from a filename
:type fileName: string
:param fileName: a relative or absolute path to the file to read
:rtype: :class:`FingerPrint.swirl.Swirl`
:return: the Swirl read from the file
"""
inputfd = open(fileName)
pickle = PickleSerializer(inputfd)
swirl = pickle.load()
inputfd.close()
serg = Sergeant(swirl)
serg.setFilename(fileName)
return serg
[docs]def getShortPath(path):
"""
Given a full path it shorten it leaving only /bin/../filename
:type path: string
:param path: a long absolute path to the file
:rtype: string
:return: the shortened path
"""
if len(path.split('/')) <= 3:
#no need to shorten
return '"' + path + '"'
returnValue = '"'
if path[0] == '/':
# absolute path name
returnValue += '/' + path.split('/')[1]
else:
returnValue += path.split('/')[0]
return returnValue + '/../' + os.path.basename(path) + '"'
# do we have a prelinker?
# this variable is use by getHash and by the syscaltracer
if os.path.exists('/usr/sbin/prelink'):
prelink = '/usr/sbin/prelink'
else:
prelink = utils.which("prelink", "/usr/sbin")
#let's skip vairous special files
specialFolders = ["/proc/","/sys/","/tmp", "/dev/",
"/etc/shadow", "/etc/passwd", "/etc/group", "/etc/nsswitch.conf",
"/etc/localtime", "/etc/hosts", "/etc/selinux", "/etc/resolv.conf",
"/etc/fstab", "/etc/inittab", "/etc/rc", "/etc/sys", "/etc/security",
"/etc/pam", "/etc/ntp", "/etc/issue", "/etc/rpc", "/etc/grub",
"/etc/krb5.conf"]
[docs]def is_special_folder(path):
"""
return true if path is to be considered special, which means it should
be skipped from archivingi, checksumming, etc.
:type path: string
:param path: an absolute path to the file
:rtype: bool
:return: True if the given path is special
"""
return any([ path.startswith(i) for i in specialFolders ])
[docs]def getHash(fileName, fileType):
"""
It return a md5 checksum of the given file name. If we are running
on a system which prelink binaries (aka RedHat based) the command
prelink must be on the PATH
:type fileName: string
:param fileName: a path to the file which we want to checksum
:type fileType: string
:param fileType: the file type (the only recognized value is EFL for
triggering the prelink on RHEL base system)
:rtype: string
:return: an hexdadeciaml representation of the md5sum checksum
"""
# let's skip weird stuff
if is_special_folder(fileName):
return ""
if not stat.S_ISREG( os.stat(fileName).st_mode ):
# probably a socket, fifo, or similar
return ""
if fileType == 'ELF' and prelink:
#let's use prelink for the md5sum
#TODO what if isPrelink fails
(temp, returncode) = utils.getOutputAsList([prelink, '-y', '--md5', fileName])
if returncode == 0:
return temp[0].split()[0]
else:
#undoing prelinking failed for some reasons
pass
try:
# ok let's do standard md5sum
fd=open(fileName)
md=md5()
md.update(fd.read())
fd.close()
return md.hexdigest()
except IOError:
#file not found
return None
[docs]class Sergeant:
"""
Given an already existent Swirl:
- it detects if it can run on this system (:meth:`check`)
- it detects what has been changed (:meth:`checkHash`)
- print this swirl on the screen (:meth:`print_swirl`)
- print this swirl as a dot file for Graphviz (:meth:`getDotFile`)
:type swirl: :class:`FingerPrint.swirl.Swirl`
:param swirl: The Swirl that we want to test
:type extraPath: list
:param extraPath: a list of string containing system path which should
be included in the search of dependencies
"""
def __init__(self, swirl, extraPath=[]):
self.swirl = swirl
self.extraPath = extraPath
self.error = []
self.missingDeps = set()
[docs] def setFilename(self, filename):
"""TODO remove this function"""
self.filename = filename
[docs] def check(self):
"""
It performs the check on the system and verifies that all the
dependencies of this Swirl can be satisfied.
:rtype: bool
:return: True if the check passes False otherwise. The list of
missing dependencies can be retrieved with :meth:`getError`
"""
returnValue = True
# this method of using rpath is not totaly correct but it's faster
# so for the moment we have to live with this
for swF in self.swirl.execedFiles:
rpath = swF.rpaths + self.extraPath + utils.getLDLibraryPath(swF.env)
for swf_dep in [swF] + self.swirl.getListSwirlFilesDependentStatic(swF):
for dep in swf_dep.staticDependencies:
if not PluginManager.getPathToLibrary(dep, rpath = rpath):
self.missingDeps.add(dep)
returnValue = False
for dynamic_dep in swF.dynamicDependencies:
if not os.path.exists(dynamic_dep.path):
self.missingDeps = self.missingDeps.union(dynamic_dep.provides)
returnValue = False
return returnValue
[docs] def checkHash(self, verbose=False):
"""
It checks if any dependency was modified since the swirl file creation
(using checksumming)
:type verbose: bool
:param verbose: if True it will generate more verbose error message
:rtype: bool
:return: True if the check passes False otherwise. The list of
modified dependencies can be retrieved with :meth:getError()
"""
self.error = []
pathCache = []
returnValue = True
for dep in self.swirl.getDependencies():
path = PluginManager.getPathToLibrary(dep)
if not path:
# error `
tmpStr = str(dep)
if verbose:
tmpStr += " unable to find its file"
self.error.append(tmpStr)
returnValue = False
continue
if path in pathCache:
#we already did this file
continue
hash = getHash(path, dep.type)
pathCache.append(path)
swirlProvider = self.swirl.getSwirlFileByProv(dep)
if not swirlProvider:
self.error.append("SwirlFile has unresolved dependency " + str(dep) \
+ " the hash can not be verified")
returnValue = False
if hash != swirlProvider.md5sum :
tmpStr = str(dep)
if verbose:
tmpStr += " wrong hash (computed " + hash + " originals " + \
swirlProvider.md5sum + ")"
self.error.append(tmpStr)
returnValue = False
return returnValue
[docs] def searchModules(self):
"""
It searches for missing dependencies using the 'module' command line.
:meth:`check` should be called before this
:rtype: string
:return: with a human readable list of module which can satisfy
missing dependencies
"""
# loop through all the modules
retDict = {}
(output, retval) = utils.getOutputAsList(["bash", "-c", "module -t avail 2>&1"])
if retval:
print "Unable to run module command, verify it\'s in the path."
return ""
for module in output :
# in the output there are some paths! remove them e.g. "/opt/module/blabla:"
if ':' in module:
continue
# remove (default)
module = module.split("(default)")[0]
(output, retval) = utils.getOutputAsList(["bash", "-c",
"module show " + module + " 2>&1"])
if retval:
#print "Unable to fetch module information: \'module show " + module + "\'"
# all module which depend on another module return 1
pass
for line in output:
if 'LD_LIBRARY_PATH' in line:
# we found another path to scan
path = line.split('LD_LIBRARY_PATH')[1]
path = [i.strip() for i in path.split(":")] #strip
PluginManager.systemPath = path # update path
for dep in self.missingDeps:
if PluginManager.getPathToLibrary(dep, False):
#we found a candidate for this missing dependency
if module not in retDict:
retDict[module] = []
retDict[module].append(dep.getName())
retStr = ""
for mod in retDict:
retStr += " " + mod + " satisfies "
num_deps = len(retDict[mod])
if num_deps == len(self.missingDeps):
retStr += "all "
retStr += "" + str(num_deps) + " dependencies:\n"
# print the deps
retStr += " " + "\n ".join(retDict[mod]) + "\n"
return retStr
[docs] def print_swirl(self, verbosity):
"""
return a string with the representation of this swirl
:type verbosity: int
:param verbosity: various verbosity level see
:meth:`FingerPrint.swirl.Swirl.printVerbose`
:rtype: string
:return: a human readable representation of this Swirl
"""
return self.swirl.printVerbose(verbosity)
[docs] def checkDependencyPath(self, fileName):
"""
it returns a list of SwirlFiles which requires the given fileName, if the
given file is not required in this swirl it returns an empty list []
:type fileName: string
:param fileName: a path to a file
:rtype: list
:return: a list of :class:`FingerPrint.swirl.SwirlFile` required by the fileName
"""
returnFilelist = []
for execSwirlFile in self.swirl.execedFiles:
for swDepFile in self.swirl.getListSwirlFilesDependentStaticAndDynamic(execSwirlFile):
if fileName in swDepFile.getPaths():
returnFilelist.append(execSwirlFile.path)
return returnFilelist
[docs] def getDotFile(self):
"""
return a dot representation of this swirl
:rtype: string
:return: a string with the dot representation of this swirl
"""
clusterExec = []
clusterSoname = set()
clusterLinker = set()
clusterPackage = []
newDependencies = []
connections = ""
for execedSwirlFile in self.swirl.execedFiles:
clusterExec.append( getShortPath( execedSwirlFile.path ) )
dependenciesDict = execedSwirlFile.getDependenciesDict()
for soname in dependenciesDict:
# get the dep which satisfy the soname
depSwf = self.swirl.getListSwirlFileProvide( [dependenciesDict[soname][0]] )[0]
newDependencies.append(depSwf)
fileName = getShortPath(depSwf.path)
# depName is soname\nversion1\nversion2\nversion3 etc.
depNameStr = '"' + soname + '"'
# swirlfile -> soname
connections += ' ' + getShortPath(execedSwirlFile.path)
connections += ' -> ' + depNameStr + ';\n'
# soname -> Filename
newConnection = ' ' + depNameStr
newConnection += ' -> ' + fileName + ';\n'
if newConnection not in connections:
connections += newConnection
# filename -> packagename
if depSwf.package :
packageName = '"' + depSwf.package.split()[0] + '"'
newConnection = ' ' + fileName
newConnection += ' -> ' + packageName + ';\n'
if newConnection not in connections:
connections += newConnection
else:
packageName = None
colorStr = self._getColor(packageName, clusterPackage)
# these are sets so no need to check for duplicate
clusterSoname.add(depNameStr + colorStr)
clusterLinker.add(fileName + colorStr )
#TODO secondary dependencies
#for swf in newDependencies:
for dynDep in execedSwirlFile.dynamicDependencies:
# create a dynamic dependencies
fileName = getShortPath(dynDep.path)
# swirlfile -> synDepPath
connections += ' ' + getShortPath(execedSwirlFile.path)
connections += ' -> ' + fileName + ';\n'
if dynDep.package :
packageName = '"' + dynDep.package.split()[0] + '"'
newConnection = ' ' + fileName
newConnection += ' -> ' + packageName + ';\n'
if newConnection not in connections:
connections += newConnection
else:
packageName = None
colorStr = self._getColor(packageName, clusterPackage)
# these are sets so no need to check for duplicate
clusterSoname.add(depNameStr + colorStr)
clusterLinker.add(fileName + colorStr )
retString = "digraph FingerPrint {\n rankdir=LR;nodesep=0.15; ranksep=0.1; fontsize=26;label =\""
retString += self.swirl.name + " " + self.swirl.getDateString()
if self.swirl.cmdLine :
retString += ' \\"' + self.swirl.cmdLine.replace('"','\\"') + '\\"'
retString += "\";\n"
retString += " labelloc=top;\n"
# execution section
retString += ' {\n'
retString += ' rank=same;\n'
retString += ' "Execution Domain" [shape=none fontsize=26];\n'
retString += ' node [shape=hexagon fontsize=16];\n'
retString += ' ' + string.join(clusterExec, ';\n ') + ";\n"
retString += " }\n"
# linker section
retString += ' subgraph cluster_linker {\n'
retString += ' label="";\n'
retString += ' "Linker Domain" [shape=none fontsize=26];\n'
retString += ' node [style=filled colorscheme=set312 fontsize=16];\n'
retString += ' {rank=same;\n'
retString += ' ' + string.join(clusterSoname, ';\n ') + ';\n'
retString += ' }\n {rank=same;\n'
retString += ' ' + string.join(clusterLinker, ';\n ') + ';\n'
retString += " }\n }\n"
# pakcage section
retString += ' {\n'
retString += ' rank=same;\n'
retString += ' "Package Domain" [shape=none style="" fontsize=26];\n'
retString += ' node [shape=box style=filled colorscheme=set312 fontsize=16];\n'
retString += ' ' + string.join(clusterPackage, ';\n ') + ';\n'
retString += ' }\n'
retString += ' "Execution Domain" -> "Linker Domain" -> "Package Domain" [style=invis];\n'
retString += connections
retString += "\n}"
return retString
def _getColor(self, packageName, clusterPackage):
"""return the color number associated with the given packageName"""
# need to get the index of the color scheme for this package
# which is also the index of the clusterPackage list
if not packageName:
#no package get gray
return " [color=\"gray\"]"
colorIndex = 0
for index, package in enumerate(clusterPackage):
if packageName in package:
# color scheme312 has 12 colors in it
colorIndex = (index % 12) + 1
break
if colorIndex == 0:
# we need have a new color
colorIndex = (len(clusterPackage) % 12) + 1
clusterPackage.append(packageName + " [color=\"%d\"]"
% colorIndex )
colorStr = " [color=\"%d\"]" % colorIndex
return colorStr
[docs] def getError(self):
"""
After running check or checkHash it return a list of the problems found
:rtype: list
:return: a lit of strings with all the problems encountered
"""
return [ i.getName() for i in self.missingDeps] + self.error
[docs] def getSwirl(self):
"""
return the current swirl
:rtype: :class:`FingerPrint.swirl.Swirl`
:return: the current swirl
"""
return self.swirl