Home | History | Annotate | Download | only in bin
      1 """
      2 Functions to handle software packages. The functions covered here aim to be
      3 generic, with implementations that deal with different package managers, such
      4 as dpkg and rpm.
      5 """
      6 
      7 __author__ = 'lucasmr (at] br.ibm.com (Lucas Meneghel Rodrigues)'
      8 
      9 import os, re
     10 from autotest_lib.client.bin import os_dep, utils
     11 from autotest_lib.client.common_lib import error
     12 
     13 # As more package methods are implemented, this list grows up
     14 KNOWN_PACKAGE_MANAGERS = ['rpm', 'dpkg']
     15 
     16 
     17 def _rpm_info(rpm_package):
     18     """\
     19     Private function that returns a dictionary with information about an
     20     RPM package file
     21     - type: Package management program that handles the file
     22     - system_support: If the package management program is installed on the
     23     system or not
     24     - source: If it is a source (True) our binary (False) package
     25     - version: The package version (or name), that is used to check against the
     26     package manager if the package is installed
     27     - arch: The architecture for which a binary package was built
     28     - installed: Whether the package is installed (True) on the system or not
     29     (False)
     30     """
     31     # We will make good use of what the file command has to tell us about the
     32     # package :)
     33     file_result = utils.system_output('file ' + rpm_package)
     34     package_info = {}
     35     package_info['type'] = 'rpm'
     36     try:
     37         os_dep.command('rpm')
     38         # Build the command strings that will be used to get package info
     39         # s_cmd - Command to determine if package is a source package
     40         # a_cmd - Command to determine package architecture
     41         # v_cmd - Command to determine package version
     42         # i_cmd - Command to determiine if package is installed
     43         s_cmd = 'rpm -qp --qf %{SOURCE} ' + rpm_package + ' 2>/dev/null'
     44         a_cmd = 'rpm -qp --qf %{ARCH} ' + rpm_package + ' 2>/dev/null'
     45         v_cmd = 'rpm -qp ' + rpm_package + ' 2>/dev/null'
     46         i_cmd = 'rpm -q ' + utils.system_output(v_cmd) + ' 2>&1 >/dev/null'
     47 
     48         package_info['system_support'] = True
     49         # Checking whether this is a source or src package
     50         source = utils.system_output(s_cmd)
     51         if source == '(none)':
     52             package_info['source'] = False
     53         else:
     54             package_info['source'] = True
     55         package_info['version'] = utils.system_output(v_cmd)
     56         package_info['arch'] = utils.system_output(a_cmd)
     57         # Checking if package is installed
     58         try:
     59             utils.system(i_cmd)
     60             package_info['installed'] = True
     61         except:
     62             package_info['installed'] = False
     63 
     64     except:
     65         package_info['system_support'] = False
     66         package_info['installed'] = False
     67         # File gives a wealth of information about rpm packages.
     68         # However, we can't trust all this info, as incorrectly
     69         # packaged rpms can report some wrong values.
     70         # It's better than nothing though :)
     71         if len(file_result.split(' ')) == 6:
     72             # Figure if package is a source package
     73             if file_result.split(' ')[3] == 'src':
     74                 package_info['source'] = True
     75             elif file_result.split(' ')[3] == 'bin':
     76                 package_info['source'] = False
     77             else:
     78                 package_info['source'] = False
     79             # Get architecture
     80             package_info['arch'] = file_result.split(' ')[4]
     81             # Get version
     82             package_info['version'] = file_result.split(' ')[5]
     83         elif len(file_result.split(' ')) == 5:
     84             # Figure if package is a source package
     85             if file_result.split(' ')[3] == 'src':
     86                 package_info['source'] = True
     87             elif file_result.split(' ')[3] == 'bin':
     88                 package_info['source'] = False
     89             else:
     90                 package_info['source'] = False
     91             # When the arch param is missing on file, we assume noarch
     92             package_info['arch'] = 'noarch'
     93             # Get version
     94             package_info['version'] = file_result.split(' ')[4]
     95         else:
     96             # If everything else fails...
     97             package_info['source'] =  False
     98             package_info['arch'] = 'Not Available'
     99             package_info['version'] = 'Not Available'
    100     return package_info
    101 
    102 
    103 def _dpkg_info(dpkg_package):
    104     """\
    105     Private function that returns a dictionary with information about a
    106     dpkg package file
    107     - type: Package management program that handles the file
    108     - system_support: If the package management program is installed on the
    109     system or not
    110     - source: If it is a source (True) our binary (False) package
    111     - version: The package version (or name), that is used to check against the
    112     package manager if the package is installed
    113     - arch: The architecture for which a binary package was built
    114     - installed: Whether the package is installed (True) on the system or not
    115     (False)
    116     """
    117     # We will make good use of what the file command has to tell us about the
    118     # package :)
    119     file_result = utils.system_output('file ' + dpkg_package)
    120     package_info = {}
    121     package_info['type'] = 'dpkg'
    122     # There's no single debian source package as is the case
    123     # with RPM
    124     package_info['source'] = False
    125     try:
    126         os_dep.command('dpkg')
    127         # Build the command strings that will be used to get package info
    128         # a_cmd - Command to determine package architecture
    129         # v_cmd - Command to determine package version
    130         # i_cmd - Command to determiine if package is installed
    131         a_cmd = 'dpkg -f ' + dpkg_package + ' Architecture 2>/dev/null'
    132         v_cmd = 'dpkg -f ' + dpkg_package + ' Package 2>/dev/null'
    133         i_cmd = 'dpkg -s ' + utils.system_output(v_cmd) + ' 2>/dev/null'
    134 
    135         package_info['system_support'] = True
    136         package_info['version'] = utils.system_output(v_cmd)
    137         package_info['arch'] = utils.system_output(a_cmd)
    138         # Checking if package is installed
    139         package_status = utils.system_output(i_cmd, ignore_status=True)
    140         not_inst_pattern = re.compile('not-installed', re.IGNORECASE)
    141         dpkg_not_installed = re.search(not_inst_pattern, package_status)
    142         if dpkg_not_installed:
    143             package_info['installed'] = False
    144         else:
    145             package_info['installed'] = True
    146 
    147     except:
    148         package_info['system_support'] = False
    149         package_info['installed'] = False
    150         # The output of file is not as generous for dpkg files as
    151         # it is with rpm files
    152         package_info['arch'] = 'Not Available'
    153         package_info['version'] = 'Not Available'
    154 
    155     return package_info
    156 
    157 
    158 def list_all():
    159     """Returns a list with the names of all currently installed packages."""
    160     support_info = os_support()
    161     installed_packages = []
    162 
    163     if support_info['rpm']:
    164         installed_packages += utils.system_output('rpm -qa').splitlines()
    165 
    166     if support_info['dpkg']:
    167         raw_list = utils.system_output('dpkg -l').splitlines()[5:]
    168         for line in raw_list:
    169             parts = line.split()
    170             if parts[0] == "ii":  # only grab "installed" packages
    171                 installed_packages.append("%s-%s" % (parts[1], parts[2]))
    172 
    173     return installed_packages
    174 
    175 
    176 def info(package):
    177     """\
    178     Returns a dictionary with package information about a given package file:
    179     - type: Package management program that handles the file
    180     - system_support: If the package management program is installed on the
    181     system or not
    182     - source: If it is a source (True) our binary (False) package
    183     - version: The package version (or name), that is used to check against the
    184     package manager if the package is installed
    185     - arch: The architecture for which a binary package was built
    186     - installed: Whether the package is installed (True) on the system or not
    187     (False)
    188 
    189     Implemented package types:
    190     - 'dpkg' - dpkg (debian, ubuntu) package files
    191     - 'rpm' - rpm (red hat, suse) package files
    192     Raises an exception if the package type is not one of the implemented
    193     package types.
    194     """
    195     if not os.path.isfile(package):
    196         raise ValueError('invalid file %s to verify' % package)
    197     # Use file and libmagic to determine the actual package file type.
    198     file_result = utils.system_output('file ' + package)
    199     for package_manager in KNOWN_PACKAGE_MANAGERS:
    200         if package_manager == 'rpm':
    201             package_pattern = re.compile('RPM', re.IGNORECASE)
    202         elif package_manager == 'dpkg':
    203             package_pattern = re.compile('Debian', re.IGNORECASE)
    204 
    205         result = re.search(package_pattern, file_result)
    206 
    207         if result and package_manager == 'rpm':
    208             return _rpm_info(package)
    209         elif result and package_manager == 'dpkg':
    210             return _dpkg_info(package)
    211 
    212     # If it's not one of the implemented package manager methods, there's
    213     # not much that can be done, hence we throw an exception.
    214     raise error.PackageError('Unknown package type %s' % file_result)
    215 
    216 
    217 def install(package, nodeps = False):
    218     """\
    219     Tries to install a package file. If the package is already installed,
    220     it prints a message to the user and ends gracefully. If nodeps is set to
    221     true, it will ignore package dependencies.
    222     """
    223     my_package_info = info(package)
    224     type = my_package_info['type']
    225     system_support = my_package_info['system_support']
    226     source = my_package_info['source']
    227     installed = my_package_info['installed']
    228 
    229     if not system_support:
    230         e_msg = ('Client does not have package manager %s to handle %s install'
    231                  % (type, package))
    232         raise error.PackageError(e_msg)
    233 
    234     opt_args = ''
    235     if type == 'rpm':
    236         if nodeps:
    237             opt_args = opt_args + '--nodeps'
    238         install_command = 'rpm %s -U %s' % (opt_args, package)
    239     if type == 'dpkg':
    240         if nodeps:
    241             opt_args = opt_args + '--force-depends'
    242         install_command = 'dpkg %s -i %s' % (opt_args, package)
    243 
    244     # RPM source packages can be installed along with the binary versions
    245     # with this check
    246     if installed and not source:
    247         return 'Package %s is already installed' % package
    248 
    249     # At this point, the most likely thing to go wrong is that there are
    250     # unmet dependencies for the package. We won't cover this case, at
    251     # least for now.
    252     utils.system(install_command)
    253     return 'Package %s was installed successfuly' % package
    254 
    255 
    256 def convert(package, destination_format):
    257     """\
    258     Convert packages with the 'alien' utility. If alien is not installed, it
    259     throws a NotImplementedError exception.
    260     returns: filename of the package generated.
    261     """
    262     try:
    263         os_dep.command('alien')
    264     except:
    265         e_msg = 'Cannot convert to %s, alien not installed' % destination_format
    266         raise error.TestError(e_msg)
    267 
    268     # alien supports converting to many formats, but its interesting to map
    269     # convertions only for the implemented package types.
    270     if destination_format == 'dpkg':
    271         deb_pattern = re.compile('[A-Za-z0-9_.-]*[.][d][e][b]')
    272         conv_output = utils.system_output('alien --to-deb %s 2>/dev/null'
    273                                           % package)
    274         converted_package = re.findall(deb_pattern, conv_output)[0]
    275     elif destination_format == 'rpm':
    276         rpm_pattern = re.compile('[A-Za-z0-9_.-]*[.][r][p][m]')
    277         conv_output = utils.system_output('alien --to-rpm %s 2>/dev/null'
    278                                           % package)
    279         converted_package = re.findall(rpm_pattern, conv_output)[0]
    280     else:
    281         e_msg = 'Convertion to format %s not implemented' % destination_format
    282         raise NotImplementedError(e_msg)
    283 
    284     print 'Package %s successfuly converted to %s' % \
    285             (os.path.basename(package), os.path.basename(converted_package))
    286     return os.path.abspath(converted_package)
    287 
    288 
    289 def os_support():
    290     """\
    291     Returns a dictionary with host os package support info:
    292     - rpm: True if system supports rpm packages, False otherwise
    293     - dpkg: True if system supports dpkg packages, False otherwise
    294     - conversion: True if the system can convert packages (alien installed),
    295     or False otherwise
    296     """
    297     support_info = {}
    298     for package_manager in KNOWN_PACKAGE_MANAGERS:
    299         try:
    300             os_dep.command(package_manager)
    301             support_info[package_manager] = True
    302         except:
    303             support_info[package_manager] = False
    304 
    305     try:
    306         os_dep.command('alien')
    307         support_info['conversion'] = True
    308     except:
    309         support_info['conversion'] = False
    310 
    311     return support_info
    312