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