• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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