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