Advertisement
xyzxyzxyz

Depfinder

Oct 31st, 2016
73
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.48 KB | None | 0 0
  1. #!/usr/bin/python3
  2.  
  3. # Copyright (c) 2013-2016 Lena Svensson
  4. # (reversed, insert dot and at): com gmail tehnegal
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18.  
  19.  
  20. ########################################################################
  21. ########################################################################
  22. ##                                                                    ##
  23. ##  This script searches for dependencies on systems using pacman     ##
  24. ##  package manager.                                                  ##
  25. ##                                                                    ##
  26. ##  Warning! First time this script is run it creates a cache, which  ##
  27. ##  might take a terribly long time. If you have an old machine with  ##
  28. ##  only one cpu core, expect 30 minutes... If you have 2 or more     ##
  29. ##  cpu's, it starts several subprocecss, which doesn't stop by       ##
  30. ##  pressing ctrl + C.                                                ##
  31. ##                                                                    ##
  32. ########################################################################
  33. ########################################################################
  34.  
  35.  
  36. import os
  37. import re
  38. import sys
  39. import getopt
  40. import subprocess
  41. from multiprocessing import Pool, cpu_count
  42.  
  43. opts,args = getopt.getopt(sys.argv[1:], 'dhlrs')
  44.  
  45. LIST = 1   
  46. DETAILED = 0
  47. SUPER_DETAILED = 0
  48. REVERSED = 0
  49.    
  50. for opt in opts:
  51.     for o in opt:
  52.         if o == '-h':
  53.             usage()
  54.             sys.exit()
  55.         elif o == '-d':
  56.             DETAILED = 1
  57.             LIST = 0
  58.         elif o == '-l':
  59.             LIST = 1
  60.         elif o == '-r':
  61.             REVERSED = 1
  62.             LIST = 0
  63.         elif o == '-s':
  64.             SUPER_DETAILED = 1
  65.             LIST = 0
  66.            
  67.            
  68. def usage():
  69.     print("Usage: depfinder [option] [pkg]")
  70.     print("Options: ")     
  71.     print("\t -d see a detailed list of dependencies")
  72.     print("\t -l see a list of dependencies")
  73.     print("\t -s see a super detailed list of dependencies")
  74.     print("\t -r reversed dependency search, find pkgs that depend on a pkg")  
  75.     print("")
  76.     print("Without option: defaults to '-l'")
  77.  
  78.    
  79. try:           
  80.     qpkg = sys.argv[-1]
  81. except IndexError:
  82.     print('This script needs at least one agrument, the package to be examined.')
  83.     usage()
  84.     sys.exit(1)
  85.    
  86. if len(sys.argv) <= 1:
  87.     usage()
  88.     sys.exit(1)
  89.  
  90.  
  91. def create_cache(name_ver_rel):
  92.     """
  93.     Create a cache for the binaries of all installed packages.
  94.     Without it, this script would take terribly long time.
  95.     """    
  96.     print("Creating cache for " + name_ver_rel)
  97.    
  98.     cachefile_ = os.path.join(cachedir, name_ver_rel)
  99.     with  open(cachefile_, 'w+') as cachefile:
  100.         pacman_file = os.path.join(pacmandb, name_ver_rel, 'files')
  101.         with open(pacman_file) as f:   
  102.             files = f.read().splitlines()      
  103.             files_to_check = []    
  104.             for line in files: 
  105.                 if line == '%FILES%':
  106.                     continue
  107.                 elif line == '%BACKUP%':
  108.                     break
  109.                 elif line[-1:] == '/':
  110.                     continue
  111.                
  112.                 # No need to check for binaries in these directories
  113.                 elif line[:11] == 'usr/include':
  114.                     continue
  115.                 elif    line[:13] == 'usr/share/doc':
  116.                     continue
  117.                 elif    line[:13] == 'usr/share/man':
  118.                     continue
  119.                 elif    line[:14] == 'usr/share/i18n':
  120.                     continue   
  121.                 elif    line[:15] == 'usr/share/fonts':
  122.                     continue
  123.                 elif    line[:16] == 'usr/share/locale':
  124.                     continue
  125.                 elif    line[:15] == 'usr/share/icons':
  126.                     continue
  127.                 elif    line[:17] == 'usr/share/pixmaps':
  128.                     continue   
  129.                 elif    line[:7] == 'usr/src':
  130.                     continue
  131.                 elif    line[:3] == 'etc':
  132.                     continue
  133.                 elif    line[:3] == 'var':
  134.                     continue   
  135.                 else:
  136.                     files_to_check.append(line)
  137.                
  138.         for file_ in files_to_check:               
  139.             if not re.search("lib/ld-[0-9]\.[0-9].*\.so", file_):
  140.                 file_ = '/' + file_
  141.                 if os.path.isfile(file_):
  142.                     output = subprocess.check_output(['file', file_])
  143.                     output = str(output)               
  144.                     if re.search('ELF', output):
  145.                         if re.search('dynamically', output):           
  146.                             if not re.search('Windows', output):
  147.                                 if not re.search('statically', output):
  148.                                     if not re.search('kernel', output):
  149.                                         cachefile.write(file_ + '\n')
  150.    
  151.    
  152. def find_new_pkgs():
  153.     '''
  154.     Returns a list of new packages installed in the system,
  155.     that should be added to the cache.
  156.     '''
  157.     if not os.path.isdir(cachedir):
  158.         os.makedirs(cachedir)
  159.    
  160.     # Find out what's already in the cache
  161.     cached = subprocess.check_output(['ls', cachedir]).splitlines()
  162.    
  163.     # Remove cachefiles from removed packages
  164.     for cached_pkg in cached:
  165.         cached_pkg = cached_pkg.decode('ascii')
  166.         cached_pkg_file = os.path.join(cachedir, cached_pkg)
  167.         db_dir = os.path.join(pacmandb, cached_pkg)    
  168.         if not os.path.isdir(db_dir):
  169.             os.remove(cached_pkg_file)
  170.            
  171.     # Find installed packages that's not in cache  
  172.     new_pkgs = []  
  173.     for inst_pkg in installed:
  174.         cachefile = os.path.join(cachedir, inst_pkg)       
  175.         if not os.path.isfile(cachefile):
  176.             new_pkgs.append(inst_pkg)
  177.    
  178.     return new_pkgs
  179.    
  180.    
  181. def create_dbs():
  182.     '''
  183.     Create a database of owners of all files in filesystem.
  184.     Create another database of packages names and versions.
  185.     '''
  186.     global pkgowners_db, installed_db
  187.  
  188.     pkgowners_db = {}
  189.     installed_db = {}
  190.    
  191.     for name_ver_rel in installed:             
  192.         remove = re.search('.*-', name_ver_rel)
  193.         rel = name_ver_rel[remove.end():]
  194.         remove = re.search('-' + rel + '$', name_ver_rel)
  195.         name_ver = name = name_ver_rel[:remove.start()] + name_ver_rel[remove.end():]
  196.        
  197.         remove = re.search('.*-', name_ver)
  198.         ver = name_ver[remove.end():]
  199.        
  200.         remove = re.search('-' + ver, name_ver)
  201.         name = name_ver[:remove.start()] + name_ver[remove.end():]     
  202.         ver_rel = ver + '-' + rel
  203.        
  204.         # Add it to the db of names and versions of all packages
  205.         installed_db[name] = ver_rel
  206.        
  207.         pacman_file = os.path.join(pacmandb, name_ver_rel, 'files')
  208.         with open(pacman_file) as f:   
  209.             files = f.read().splitlines()
  210.             for line in files:
  211.                 if line == '%FILES%':
  212.                     continue
  213.                 elif len(line) == 0:
  214.                     break
  215.                 elif line[-1] == '/':
  216.                     continue
  217.                 else:
  218.                     pkgowners_db[line] = name
  219.    
  220.  
  221. def installed_check():
  222.     '''
  223.     Check if the queried package is installed
  224.     '''
  225.     if not qpkg in installed_db:
  226.         print("Package {} is not installed and can't be examined.".format(qpkg))
  227.         sys.exit(1)
  228.  
  229.  
  230. def bin_check():
  231.     '''
  232.     Check if the queried package has dynamically linked binaries
  233.     '''
  234.     if len(qbinaries) == 0:
  235.         print("This package doesn't have any dynamically linked binaries,")
  236.         print("so no dependencies can be found by this script.")
  237.         exit()
  238.  
  239. def lib_check():
  240.     '''
  241.     Check if the queried package has shared libraries
  242.     '''
  243.     for line in qbinaries:
  244.         if re.search('\.so', line):
  245.             return True
  246.         else:
  247.             continue
  248.  
  249. def find_libs():
  250.     '''
  251.     Find all libraries the package links to
  252.     '''        
  253.     for binary in qbinaries:   
  254.         if SUPER_DETAILED:
  255.             print("")
  256.             print(binary)
  257.        
  258.         (dirname, filename) = os.path.split(binary)    
  259.         try:
  260.             output = subprocess.check_output(['scanelf', '-BF', "%n", binary]).decode('ascii').split(',')                              
  261.         except subprocess.CalledProcessError as e:
  262.             problems.append(binary)
  263.             continue
  264.                    
  265.         for lib in output:
  266.             # Remove the ' /path/to/binary' after last lib
  267.             if re.search(' /*', lib):
  268.                 remove = re.search(' /*', lib)
  269.                 lib = lib[:remove.start()]
  270.            
  271.             # In case nothing was found and lib is now empty
  272.             if not lib:
  273.                 continue
  274.            
  275.             if lib not in libraries:
  276.                 libraries.append(lib)
  277.        
  278.             if SUPER_DETAILED:
  279.                 lib1 = 'usr/lib/' + lib
  280.                 lib2 = 'lib/' + lib
  281.                 found = 0
  282.                 for lib_test in lib1, lib2:
  283.                     if lib_test in pkgowners_db:
  284.                         if not pkgowners_db[lib_test] == qpkg:
  285.                             owner = pkgowners_db[lib_test]
  286.                             # Freetype-infinality should be shown as freetype
  287.                             if owner == 'freetype-infinality':
  288.                                 owner = 'freetype'
  289.                             print('    /{} \t{}'.format(lib_test, owner))
  290.                             found = 1              
  291.                        
  292.                 if not found:          
  293.                     # Check if it's qpkg's own library
  294.                     file_test = '/var/lib/pacman/local/' +  qname_ver_rel + '/files'                   
  295.                     try:
  296.                         output = subprocess.check_output(['grep', lib, file_test]).decode('ascii').splitlines()                                            
  297.                         for i in output:
  298.                             owner = pkgowners_db[i]
  299.                             # Freetype-infinality should be shown as freetype
  300.                             if owner == 'freetype-infinality':
  301.                                 owner == 'freetype'
  302.                             print('    /{} \t{}'.format(i, owner))
  303.                             found = 1
  304.                     except subprocess.CalledProcessError as e: 
  305.                             problems.append(e)                         
  306.                                
  307.                 # If still not found, add it to missing
  308.                 if not found:          
  309.                         missing.append(lib)
  310.                        
  311.  
  312. def output():
  313.     owners = []
  314.     # Test if the library is found in /lib or /usr/lib
  315.     for lib in libraries:          
  316.         lib1 = 'usr/lib/' + lib
  317.         lib2 = 'lib/' + lib
  318.         found = 0      
  319.         for lib_test in lib1, lib2:
  320.             if lib_test in pkgowners_db:               
  321.                 pkg = pkgowners_db[lib_test]               
  322.                 # Freetype-infinality should be shown as freetype
  323.                 if pkg == 'freetype-infinality':
  324.                     pkg = 'freetype'       
  325.                
  326.                 if DETAILED:
  327.                     owners.append('(' + pkg + ') /' + lib_test)
  328.                 else:
  329.                     if not pkgowners_db[lib_test] == qpkg:
  330.                         owners.append(pkg)
  331.                 found = pkg
  332.         if not found:          
  333.             # Check if the package itself has that library, otherwise add it to missing
  334.             file_test = '/var/lib/pacman/local/' +  qname_ver_rel + '/files'
  335.             try:
  336.                 output = subprocess.check_output(['grep', lib, file_test]).decode('ascii').splitlines()
  337.             except subprocess.CalledProcessError as e: 
  338.                 missing.append(lib)
  339.                 continue
  340.                            
  341.             for i in output:
  342.                 pkg = pkgowners_db[i]
  343.                 if DETAILED:   
  344.                     # Freetype-infinality should be shown as freetype
  345.                     if pkg == 'freetype-infinality':
  346.                         pkg == 'freetype'
  347.                
  348.                     owners.append('(' + pkg + ') /' + i)
  349.                 found = pkg
  350.        
  351.         if not found:
  352.             missing.append(lib)
  353.  
  354.     if DETAILED:
  355.         owners.sort()
  356.         for i in owners:
  357.             print(i)
  358.     elif LIST:
  359.         unique = []    
  360.         for i in owners:
  361.             if i not in unique:
  362.                 unique.append(i)
  363.         unique.sort()
  364.         for i in unique:
  365.             print(i, end=' ')
  366.         print("")
  367.    
  368.  
  369. def rev_depsearch(name_ver_rel):
  370.     '''
  371.     Find out which packages depend on the queried package
  372.     '''
  373.     if name_ver_rel == qname_ver_rel:
  374.         # Don't check if qpkg depends on itself...
  375.         return
  376.    
  377.     # Extract the name of the package
  378.     rel = name_ver_rel.split('-')[-1]
  379.     ver = name_ver_rel.split('-')[-2]
  380.     ver_rel = '-' + ver + '-' + rel
  381.     remove = re.search(ver_rel, name_ver_rel)
  382.     name = name_ver_rel[:remove.start()]
  383.    
  384.     # Find all binaries belonging to the package to be examined
  385.     cachefile = os.path.join(cachedir, name_ver_rel)
  386.     with open(cachefile) as f:
  387.         binaries = f.read().splitlines()   
  388.        
  389.     # Find out what the binaries link to
  390.     for b in binaries:
  391.         (dirname, filename) = os.path.split(b)
  392.         try:
  393.             output = subprocess.check_output(['scanelf', '-BF', "%n", b]).decode('ascii').split(',')   
  394.         except subprocess.CalledProcessError as e:
  395.             # It's a later problem...
  396.             problems.append(e)
  397.             continue   
  398.                        
  399.         for lib in output:
  400.             # Remove the ' /path/to/binary' after last lib
  401.             if re.search(' /*', lib):
  402.                 remove = re.search(' /*', lib)
  403.                 lib = lib[:remove.start()]
  404.                                    
  405.             lib1 = 'usr/lib/' + lib
  406.             lib2 = 'lib/' + lib
  407.             found = 0
  408.                        
  409.             # Test if the lib is found in /lib or /usr/lib
  410.             for lib_test in lib1, lib2:    
  411.                 if lib_test in pkgowners_db:
  412.                     # See if qpkg owns it
  413.                     if pkgowners_db[lib_test] == qpkg:
  414.                         # Freetype-infinality should be shown as freetype
  415.                         if name == 'freetype-infinality':
  416.                             name = 'freetype'                      
  417.                         print(name)
  418.                         found = 1
  419.                        
  420.                         # If a package anyway depends on qpkg, don't show it
  421.                         # in 'some dependencies not found'-list.
  422.                         if name in missing:
  423.                             missing.remove(name)       
  424.                        
  425.                         # Go on to next package
  426.                         return
  427.  
  428.  
  429. HOME = os.getenv("HOME")
  430. cachedir = HOME + '/.cache/depfinder'
  431. pacmandb = '/var/lib/pacman/local'
  432. installed_ = subprocess.check_output(['ls', pacmandb]).splitlines()
  433. installed = []
  434.  
  435. for name_ver_rel in installed_:    
  436.     name_ver_rel = name_ver_rel.decode('ascii')
  437.     if name_ver_rel != 'ALPM_DB_VERSION':
  438.         installed.append(name_ver_rel)
  439.  
  440. # Update cache  (Uses all cpu cores.)
  441. new_pkgs = find_new_pkgs()
  442. cpus = cpu_count() + 1
  443. pool = Pool(cpus)
  444. pool.map(create_cache, new_pkgs)
  445.  
  446. create_dbs()
  447. installed_check()
  448.  
  449. qname_ver_rel = qpkg + '-' + installed_db[qpkg]
  450. cachefile = os.path.join(cachedir, qname_ver_rel)
  451. with open(cachefile, 'r') as cachefile:
  452.     qbinaries = cachefile.read().splitlines()
  453.    
  454. bin_check()
  455.  
  456. if REVERSED:
  457.     if not lib_check():
  458.         print("This package doesn't have any shared libraries")
  459.         sys.exit(0)
  460.  
  461. libraries = []
  462. problems = []
  463. missing = []
  464.  
  465. if REVERSED:
  466.     print("Searching for packages that depend on {}... ".format(qpkg))
  467.     poolb = Pool(cpus)
  468.     poolb.map(rev_depsearch, installed)
  469. else:
  470.     # SUPER_DETAILED prints directly from find_libs
  471.     find_libs()
  472.    
  473. if LIST or DETAILED:
  474.     output()
  475.  
  476. if missing:
  477.     print("")
  478.     print("Some dependencies not found.")
  479.     for m in missing:
  480.         print(m)
  481.  
  482. if problems:
  483.     print("")
  484.     for p in problems:
  485.         print(p)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement