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