• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""
2Functions to handle software packages. The functions covered here aim to be
3generic, with implementations that deal with different package managers, such
4as dpkg and rpm.
5"""
6
7__author__ = 'lucasmr@br.ibm.com (Lucas Meneghel Rodrigues)'
8
9import os, re
10from autotest_lib.client.bin import os_dep, utils
11from autotest_lib.client.common_lib import error
12
13# As more package methods are implemented, this list grows up
14KNOWN_PACKAGE_MANAGERS = ['rpm', 'dpkg']
15
16
17def _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
103def _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
158def 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
176def 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
217def 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
256def 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
289def 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