stricter-interpreter-check.diff
[platform/upstream/rpmlint.git] / Pkg.py
1 # -*- coding: utf-8 -*-
2 #############################################################################
3 # File          : Pkg.py
4 # Package       : rpmlint
5 # Author        : Frederic Lepied
6 # Created on    : Tue Sep 28 07:18:06 1999
7 # Version       : $Id: Pkg.py 1892 2011-11-23 20:21:05Z scop $
8 # Purpose       : provide an API to handle a rpm package either by accessing
9 #                 the rpm file or by accessing the files contained inside.
10 #############################################################################
11
12 import commands
13 import os
14 import re
15 import stat
16 import subprocess
17 import sys
18 import tempfile
19 import types
20 import urlparse
21
22 try:
23     import magic
24     # TODO: magic.MAGIC_COMPRESS when PkgFile gets decompress support.
25     _magic = magic.open(magic.MAGIC_NONE)
26     _magic.load()
27 except:
28     _magic = None
29 import rpm
30
31 import Filter
32
33 # Python 2/3 compatibility/convenience wrapper for printing to stderr with
34 # less concerns of UnicodeErrors than plain sys.stderr.write.
35 if sys.version_info[0] > 2:
36     # Blows up with Python < 3 without the exec() hack
37     exec('def warn(s): print (s, file=sys.stderr)')
38 else:
39     def warn(s): print >> sys.stderr, s
40
41
42 # utilities
43
44 # 64: RPMSENSE_PREREQ is 0 with recent rpm versions, we want 64 here in order
45 # to do the right thing with packages built with older rpm versions
46 PREREQ_FLAG = (rpm.RPMSENSE_PREREQ or 64) | \
47               rpm.RPMSENSE_SCRIPT_PRE | \
48               rpm.RPMSENSE_SCRIPT_POST | \
49               rpm.RPMSENSE_SCRIPT_PREUN | \
50               rpm.RPMSENSE_SCRIPT_POSTUN
51
52 var_regex = re.compile('^(.*)\${?(\w+)}?(.*)$')
53
54 def shell_var_value(var, script):
55     assign_regex = re.compile('\\b' + re.escape(var) + '\s*=\s*(.+)\s*(#.*)*$',
56                               re.MULTILINE)
57     res = assign_regex.search(script)
58     if res:
59         res2 = var_regex.search(res.group(1))
60         if res2:
61             if res2.group(2) == var: # infinite loop
62                 return None
63         return substitute_shell_vars(res.group(1), script)
64     else:
65         return None
66
67 def substitute_shell_vars(val, script):
68     res = var_regex.search(val)
69     if res:
70         value = shell_var_value(res.group(2), script)
71         if not value:
72             value = ''
73         return res.group(1) + value + \
74             substitute_shell_vars(res.group(3), script)
75     else:
76         return val
77
78 def getstatusoutput(cmd, stdoutonly = False):
79     '''A version of commands.getstatusoutput() which can take cmd as a
80        sequence, thus making it potentially more secure.'''
81     if stdoutonly:
82         proc = subprocess.Popen(cmd, stdin=subprocess.PIPE,
83                                 stdout=subprocess.PIPE, close_fds=True)
84     else:
85         proc = subprocess.Popen(cmd, stdin=subprocess.PIPE,
86                                 stdout=subprocess.PIPE,
87                                 stderr=subprocess.STDOUT, close_fds=True)
88     proc.stdin.close()
89     text = proc.stdout.read()
90     sts = proc.wait()
91     if sts is None:
92         sts = 0
93     if text.endswith('\n'):
94         text = text[:-1]
95     return sts, text
96
97 bz2_regex = re.compile('\.t?bz2?$')
98 xz_regex = re.compile('\.(t[xl]z|xz|lzma)$')
99
100 def catcmd(fname):
101     """Get a 'cat' command that handles possibly compressed files."""
102     cat = 'gzip -dcf'
103     if bz2_regex.search(fname):
104         cat = 'bzip2 -dcf'
105     elif xz_regex.search(fname):
106         cat = 'xz -dc'
107     return cat
108
109 def is_utf8(fname):
110     (sts, text) = getstatusoutput(catcmd(fname).split() + [fname])
111     return not sts and is_utf8_str(text)
112
113 def is_utf8_str(s):
114     try:
115         s.decode('UTF-8')
116     except:
117         return False
118     return True
119
120 def to_utf8(string):
121     if string is None:
122         return ''
123     elif isinstance(string, unicode):
124         return string
125     try:
126         x = unicode(string, 'ascii')
127         return string
128     except UnicodeError:
129         encodings = ['utf-8', 'iso-8859-1', 'iso-8859-15', 'iso-8859-2']
130         for enc in encodings:
131             try:
132                 x = unicode(string, enc)
133             except UnicodeError:
134                 pass
135             else:
136                 if x.encode(enc) == string:
137                     return x.encode('utf-8')
138     newstring = ''
139     for char in string:
140         if ord(char) > 127:
141             newstring = newstring + '?'
142         else:
143             newstring = newstring + char
144     return newstring
145
146 def readlines(path):
147     fobj = open(path, "r")
148     try:
149         return fobj.readlines()
150     finally:
151         fobj.close()
152
153 def mktemp():
154     tmpfd, tmpname = tempfile.mkstemp(prefix = 'rpmlint.')
155     tmpfile = os.fdopen(tmpfd, 'w')
156     return tmpfile, tmpname
157
158 slash_regex = re.compile('/+')
159 slashdot_regex = re.compile('/(\.(/|$))+')
160 slashend_regex = re.compile('([^/])/+$')
161
162 def safe_normpath(path):
163     """Like os.path.normpath but normalizes less aggressively thus being
164     potentially safer for paths containing symlinks."""
165     ret = slash_regex.sub('/', path)
166     ret = slashdot_regex.sub('\\2', ret)
167     ret = slashend_regex.sub('\\1', ret)
168     return ret
169
170 def get_default_valid_rpmgroups(filename = None):
171     """Get default rpm groups from filename, or try to look them up from
172     the rpm package (if installed) if no filename is given"""
173     groups = []
174     if not filename:
175         try:
176             p = InstalledPkg('rpm')
177         except:
178             pass
179         else:
180             groupsfiles = [x for x in p.files() if x.endswith('/GROUPS')]
181             if groupsfiles:
182                 filename = groupsfiles[0]
183     if filename and os.path.exists(filename):
184         fobj = open(filename)
185         try:
186             groups = fobj.read().strip().splitlines()
187         finally:
188             fobj.close()
189         if 'Development/Debug' not in groups:
190             groups.append('Development/Debug')
191         if 'Unspecified' not in groups: # auto-added by rpm >= 4.6.0
192             groups.append('Unspecified')
193         groups.sort()
194     return groups
195
196 # from yum 3.2.27, rpmUtils.miscutils, with rpmlint modifications
197 def compareEVR((e1, v1, r1), (e2, v2, r2)):
198     # return 1: a is newer than b
199     # 0: a and b are the same version
200     # -1: b is newer than a
201     # rpmlint mod: don't stringify None epochs to 'None' strings
202     if e1 is not None:
203         e1 = str(e1)
204     v1 = str(v1)
205     r1 = str(r1)
206     if e2 is not None:
207         e2 = str(e2)
208     v2 = str(v2)
209     r2 = str(r2)
210     rc = rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
211     return rc
212
213 # from yum 3.2.27, rpmUtils.miscutils, with rpmlint modifications
214 def rangeCompare(reqtuple, provtuple):
215     """returns true if provtuple satisfies reqtuple"""
216     (reqn, reqf, (reqe, reqv, reqr)) = reqtuple
217     (n, f, (e, v, r)) = provtuple
218     if reqn != n:
219         return 0
220
221     # unversioned satisfies everything
222     if not f or not reqf:
223         return 1
224
225     # and you thought we were done having fun
226     # if the requested release is left out then we have
227     # to remove release from the package prco to make sure the match
228     # is a success - ie: if the request is EQ foo 1:3.0.0 and we have
229     # foo 1:3.0.0-15 then we have to drop the 15 so we can match
230     if reqr is None:
231         r = None
232     # rpmlint mod: don't mess with provided Epoch, doing so breaks e.g.
233     # "Requires: foo < 1.0" should not be satisfied by "Provides: foo = 1:0.5"
234     #if reqe is None:
235     #    e = None
236     if reqv is None: # just for the record if ver is None then we're going to segfault
237         v = None
238
239     # if we just require foo-version, then foo-version-* will match
240     if r is None:
241         reqr = None
242
243     rc = compareEVR((e, v, r), (reqe, reqv, reqr))
244
245     # does not match unless
246     if rc >= 1:
247         if reqf in ['GT', 'GE', 4, 12]:
248             return 1
249         if reqf in ['EQ', 8]:
250             if f in ['LE', 10, 'LT', 2]:
251                 return 1
252         if reqf in ['LE', 'LT', 'EQ', 10, 2, 8]:
253             if f in ['LE', 'LT', 10, 2]:
254                 return 1
255
256     if rc == 0:
257         if reqf in ['GT', 4]:
258             if f in ['GT', 'GE', 4, 12]:
259                 return 1
260         if reqf in ['GE', 12]:
261             if f in ['GT', 'GE', 'EQ', 'LE', 4, 12, 8, 10]:
262                 return 1
263         if reqf in ['EQ', 8]:
264             if f in ['EQ', 'GE', 'LE', 8, 12, 10]:
265                 return 1
266         if reqf in ['LE', 10]:
267             if f in ['EQ', 'LE', 'LT', 'GE', 8, 10, 2, 12]:
268                 return 1
269         if reqf in ['LT', 2]:
270             if f in ['LE', 'LT', 10, 2]:
271                 return 1
272     if rc <= -1:
273         if reqf in ['GT', 'GE', 'EQ', 4, 12, 8]:
274             if f in ['GT', 'GE', 4, 12]:
275                 return 1
276         if reqf in ['LE', 'LT', 10, 2]:
277             return 1
278 #                if rc >= 1:
279 #                    if reqf in ['GT', 'GE', 4, 12]:
280 #                        return 1
281 #                if rc == 0:
282 #                    if reqf in ['GE', 'LE', 'EQ', 8, 10, 12]:
283 #                        return 1
284 #                if rc <= -1:
285 #                    if reqf in ['LT', 'LE', 2, 10]:
286 #                        return 1
287
288     return 0
289
290 # from yum 3.2.23, rpmUtils.miscutils, with rpmlint modifications
291 def formatRequire(name, flags, evr):
292     s = name
293
294     if flags:
295         if flags & (rpm.RPMSENSE_LESS | rpm.RPMSENSE_GREATER |
296                     rpm.RPMSENSE_EQUAL):
297             s = s + " "
298             if flags & rpm.RPMSENSE_LESS:
299                 s = s + "<"
300             if flags & rpm.RPMSENSE_GREATER:
301                 s = s + ">"
302             if flags & rpm.RPMSENSE_EQUAL:
303                 s = s + "="
304             s = "%s %s" % (s, versionToString(evr))
305     return s
306
307 def versionToString(evr):
308     if not isinstance(evr, tuple) and not isinstance(evr, list):
309         # assume string
310         return evr
311     ret = ""
312     if evr[0] is not None and evr[0] != "":
313         ret += str(evr[0]) + ":"
314     if evr[1] is not None:
315         ret += evr[1]
316         if evr[2] is not None and evr[2] != "":
317             ret += "-" + evr[2]
318     return ret
319
320 # from yum 3.2.23, rpmUtils.miscutils, with some rpmlint modifications
321 def stringToVersion(verstring):
322     if verstring in (None, ''):
323         return (None, None, None)
324     epoch = None
325     i = verstring.find(':')
326     if i != -1:
327         try:
328             epoch = str(long(verstring[:i]))
329         except ValueError:
330             # garbage in epoch, ignore it
331             pass
332     i += 1
333     j = verstring.find('-', i)
334     if j != -1:
335         if verstring[i:j] == '':
336             version = None
337         else:
338             version = verstring[i:j]
339         release = verstring[j+1:]
340     else:
341         if verstring[i:] == '':
342             version = None
343         else:
344             version = verstring[i:]
345         release = None
346     return (epoch, version, release)
347
348 def parse_deps(line):
349     '''Parse provides/requires/conflicts/obsoletes line to list of
350        (name, flags, (epoch, version, release)) tuples.'''
351
352     prcos = []
353     tokens = re.split('[\s,]+', line.strip())
354
355     # Drop line continuation backslash in multiline macro definition (for
356     # spec file parsing), e.g.
357     # [...] \
358     # Obsoletes: foo-%1 <= 1.0.0 \
359     # [...] \
360     # (yes, this is an ugly hack and we probably have other problems with
361     #  multiline macro definitions elsewhere...)
362     if tokens[-1] == '\\':
363         del tokens[-1]
364
365     prco = []
366     while tokens:
367         token = tokens.pop(0)
368         if not token:
369             # skip empty tokens
370             continue
371
372         plen = len(prco)
373
374         if plen == 0:
375             prco.append(token)
376
377         elif plen == 1:
378             flags = 0
379             if token[0] in ("=", "<", "<=", ">", ">="):
380                 # versioned, flags
381                 if "=" in token:
382                     flags |= rpm.RPMSENSE_EQUAL
383                 if "<" in token:
384                     flags |= rpm.RPMSENSE_LESS
385                 if ">" in token:
386                     flags |= rpm.RPMSENSE_GREATER
387                 prco.append(flags)
388             else:
389                 # no flags following name, treat as unversioned, add and reset
390                 prco.extend((flags, (None, None, None)))
391                 prcos.append(tuple(prco))
392                 prco = [token]
393
394         elif plen == 2:
395             # last token of versioned one, add and reset
396             prco.append(stringToVersion(token))
397             prcos.append(tuple(prco))
398             prco = []
399
400     plen = len(prco)
401     if plen:
402         if plen == 1:
403             prco.extend((0, (None, None, None)))
404         elif plen == 2:
405             prco.append((None, None, None))
406         prcos.append(tuple(prco))
407
408     return prcos
409
410
411 # classes representing package
412
413 class Pkg:
414
415     _magic_from_compressed_re = re.compile('\([^)]+\s+compressed\s+data\\b')
416
417     def __init__(self, filename, dirname, header = None, is_source = False):
418         self.filename = filename
419         self.extracted = False
420         self.dirname = dirname
421         self.current_linenum = None
422         self._config_files = None
423         self._doc_files = None
424         self._noreplace_files = None
425         self._ghost_files = None
426         self._missingok_files = None
427         self._files = None
428         self._requires = None
429         self._suggests = None
430         self._supplements = None
431         self._enhances = None
432         self._recommends = None
433         self._req_names = -1
434
435         if header:
436             self.header = header
437             self.is_source = is_source
438         else:
439             # Create a package object from the file name
440             ts = rpm.TransactionSet()
441             # Don't check signatures here...
442             ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
443             fd = os.open(filename, os.O_RDONLY)
444             try:
445                 self.header = ts.hdrFromFdno(fd)
446             finally:
447                 os.close(fd)
448             self.is_source = not self.header[rpm.RPMTAG_SOURCERPM]
449
450         self.name = self.header[rpm.RPMTAG_NAME]
451         if self.isNoSource():
452             self.arch = 'nosrc'
453         elif self.isSource():
454             self.arch = 'src'
455         else:
456             self.arch = self.header[rpm.RPMTAG_ARCH]
457
458     # Return true if the package is a source package
459     def isSource(self):
460         return self.is_source
461
462     # Return true if the package is a nosource package.
463     # NoSource files are ghosts in source packages.
464     def isNoSource(self):
465         return self.is_source and self.ghostFiles()
466
467     # access the tags like an array
468     def __getitem__(self, key):
469         try:
470             val = self.header[key]
471         except:
472             val = []
473         if val == []:
474             return None
475         else:
476             return val
477
478     # return the name of the directory where the package is extracted
479     def dirName(self):
480         if not self.extracted:
481             self._extract()
482         return self.dirname
483
484     # extract rpm contents
485     def _extract(self):
486         s = os.stat(self.dirname)
487         if not stat.S_ISDIR(s[stat.ST_MODE]):
488             warn('Unable to access dir %s' % self.dirname)
489             return None
490         else:
491             self.dirname = tempfile.mkdtemp(
492                 prefix = 'rpmlint.%s.' % os.path.basename(self.filename),
493                 dir = self.dirname)
494             # TODO: better shell escaping or sequence based command invocation
495             command_str = \
496                 'rpm2cpio "%s" | (cd "%s"; cpio -id); chmod -R +rX "%s"' % \
497                 (self.filename, self.dirname, self.dirname)
498             cmd = commands.getstatusoutput(command_str)
499             self.extracted = True
500             return cmd
501
502     def checkSignature(self):
503         return getstatusoutput(('env', 'LC_ALL=C', 'rpm', '-K', self.filename))
504
505     # remove the extracted files from the package
506     def cleanup(self):
507         if self.extracted and self.dirname:
508             getstatusoutput(('rm', '-rf', self.dirname))
509
510     def grep(self, regex, filename):
511         """Grep regex from a file, return matching line numbers."""
512         ret = []
513         lineno = 0
514         in_file = None
515         try:
516             try:
517                 in_file = open(self.dirName() + '/' + filename)
518                 for line in in_file:
519                     lineno += 1
520                     if regex.search(line):
521                         ret.append(str(lineno))
522                         break
523             except Exception, e:
524                 Filter.printWarning(self, 'read-error', filename, e)
525         finally:
526             if in_file:
527                 in_file.close()
528         return ret
529
530     def langtag(self, tag, lang):
531         """Get value of tag in the given language."""
532         # LANGUAGE trumps other env vars per GNU gettext docs, see also #166
533         orig = os.environ.get('LANGUAGE')
534         os.environ['LANGUAGE'] = lang
535         ret = self[tag]
536         if orig is not None:
537             os.environ['LANGUAGE'] = orig
538         return ret
539
540     # return the associative array indexed on file names with
541     # the values as: (file perm, file owner, file group, file link to)
542     def files(self):
543         if self._files is not None:
544             return self._files
545
546         self._gatherFilesInfo()
547         return self._files
548
549     # return the list of config files
550     def configFiles(self):
551         if self._config_files is not None:
552             return self._config_files
553
554         self._config_files = [x.name for x in self.files().values()
555                               if x.is_config]
556         return self._config_files
557
558     # return the list of noreplace files
559     def noreplaceFiles(self):
560         if self._noreplace_files is not None:
561             return self._noreplace_files
562
563         self._noreplace_files = [x.name for x in self.files().values()
564                                  if x.is_noreplace]
565         return self._noreplace_files
566
567     # return the list of documentation files
568     def docFiles(self):
569         if self._doc_files is not None:
570             return self._doc_files
571
572         self._doc_files = [x.name for x in self.files().values() if x.is_doc]
573         return self._doc_files
574
575     # return the list of ghost files
576     def ghostFiles(self):
577         if self._ghost_files is not None:
578             return self._ghost_files
579
580         self._ghost_files = [x.name for x in self.files().values()
581                              if x.is_ghost]
582         return self._ghost_files
583
584     def missingOkFiles(self):
585         if self._missingok_files is not None:
586             return self._missingok_files
587
588         self._missingok_files = [x.name for x in self.files().values()
589                                  if x.is_missingok]
590         return self._missingok_files
591
592     # extract information about the files
593     def _gatherFilesInfo(self):
594
595         self._files = {}
596         flags = self.header[rpm.RPMTAG_FILEFLAGS]
597         modes = self.header[rpm.RPMTAG_FILEMODES]
598         users = self.header[rpm.RPMTAG_FILEUSERNAME]
599         groups = self.header[rpm.RPMTAG_FILEGROUPNAME]
600         links = self.header[rpm.RPMTAG_FILELINKTOS]
601         sizes = self.header[rpm.RPMTAG_FILESIZES]
602         md5s = self.header[rpm.RPMTAG_FILEMD5S]
603         mtimes = self.header[rpm.RPMTAG_FILEMTIMES]
604         rdevs = self.header[rpm.RPMTAG_FILERDEVS]
605         langs = self.header[rpm.RPMTAG_FILELANGS]
606         inodes = self.header[rpm.RPMTAG_FILEINODES]
607         requires = self.header[rpm.RPMTAG_FILEREQUIRE]
608         provides = self.header[rpm.RPMTAG_FILEPROVIDE]
609         files = self.header[rpm.RPMTAG_FILENAMES]
610         magics = self.header[rpm.RPMTAG_FILECLASS]
611         try: # rpm >= 4.7.0
612             filecaps = self.header[rpm.RPMTAG_FILECAPS]
613         except:
614             filecaps = None
615
616         # rpm-python < 4.6 does not return a list for this (or FILEDEVICES,
617         # FWIW) for packages containing exactly one file
618         if not isinstance(inodes, types.ListType):
619             inodes = [inodes]
620
621         if files:
622             for idx in range(0, len(files)):
623                 pkgfile = PkgFile(files[idx])
624                 # Do not use os.path.join here, pkgfile.name can start with a
625                 # / which would result in self.dirName being ignored
626                 pkgfile.path = os.path.normpath(
627                     self.dirName() + '/' + pkgfile.name)
628                 pkgfile.flags = flags[idx]
629                 pkgfile.mode = modes[idx]
630                 pkgfile.user = users[idx]
631                 pkgfile.group = groups[idx]
632                 pkgfile.linkto = links[idx] and safe_normpath(links[idx])
633                 pkgfile.size = sizes[idx]
634                 pkgfile.md5 = md5s[idx]
635                 pkgfile.mtime = mtimes[idx]
636                 pkgfile.rdev = rdevs[idx]
637                 pkgfile.inode = inodes[idx]
638                 pkgfile.requires = parse_deps(requires[idx])
639                 pkgfile.provides = parse_deps(provides[idx])
640                 pkgfile.lang = langs[idx]
641                 pkgfile.magic = magics[idx]
642                 if not pkgfile.magic and _magic:
643                     pkgfile.magic = _magic.file(pkgfile.path)
644                 if pkgfile.magic is None:
645                     pkgfile.magic = ''
646                 elif Pkg._magic_from_compressed_re.search(pkgfile.magic):
647                     # Discard magic from inside compressed files ("file -z")
648                     # until PkgFile gets decompression support.  We may get
649                     # such magic strings from package headers already now;
650                     # for example Fedora's rpmbuild as of F-11's 4.7.1 is
651                     # patched so it generates them.
652                     pkgfile.magic = ''
653                 if filecaps:
654                     pkgfile.filecaps = filecaps[idx]
655                 self._files[pkgfile.name] = pkgfile
656
657     def readlink(self, pkgfile):
658         """Resolve symlinks for the given PkgFile, return the dereferenced
659            PkgFile if it is found in this package, None if not."""
660         result = pkgfile
661         while result and result.linkto:
662             linkpath = urlparse.urljoin(result.name, result.linkto)
663             linkpath = safe_normpath(linkpath)
664             result = self.files().get(linkpath)
665         return result
666
667     # API to access dependency information
668     def obsoletes(self):
669         """Get package Obsoletes as list of
670            (name, flags, (epoch, version, release)) tuples."""
671         self._gatherDepInfo()
672         return self._obsoletes
673
674     def requires(self):
675         """Get package Requires as list of
676            (name, flags, (epoch, version, release)) tuples."""
677         self._gatherDepInfo()
678         return self._requires
679
680     def recommends(self):
681         self._gatherDepInfo()
682         return self._recommends
683
684     def suggests(self):
685         self._gatherDepInfo()
686         return self._suggests
687
688     def supplements(self):
689         self._gatherDepInfo()
690         return self._supplements
691
692     def enhances(self):
693         self._gatherDepInfo()
694         return self._enhances
695
696     def prereq(self):
697         """Get package PreReqs as list of
698            (name, flags, (epoch, version, release)) tuples."""
699         self._gatherDepInfo()
700         return self._prereq
701
702     def req_names(self):
703         if self._req_names == -1:
704             self._req_names = [x[0] for x in self.requires() + self.prereq()]
705         return self._req_names
706
707     def check_versioned_dep(self, name, version):
708         # try to match name%_isa as well (e.g. "foo(x86-64)", "foo(x86-32)")
709         name_re = re.compile('^%s(\(\w+-\d+\))?$' % re.escape(name))
710         for d in self.requires() + self.prereq():
711             if name_re.match(d[0]):
712                 if d[1] & rpm.RPMSENSE_EQUAL != rpm.RPMSENSE_EQUAL \
713                         or d[2][1] != version:
714                     return False
715                 return True
716         return False
717
718     def conflicts(self):
719         """Get package Conflicts as list of
720            (name, flags, (epoch, version, release)) tuples."""
721         self._gatherDepInfo()
722         return self._conflicts
723
724     def provides(self):
725         """Get package Provides as list of
726            (name, flags, (epoch, version, release)) tuples."""
727         self._gatherDepInfo()
728         return self._provides
729
730     # internal function to gather dependency info used by the above ones
731     def _gather_aux(self, header, list, nametag, flagstag, versiontag,
732                     prereq = None, strong_only = False, weak_only = False):
733         names = header[nametag]
734         flags = header[flagstag]
735         versions = header[versiontag]
736
737         if versions:
738             for loop in range(len(versions)):
739                 evr = stringToVersion(versions[loop])
740                 if prereq is not None and flags[loop] & PREREQ_FLAG:
741                     prereq.append((names[loop], flags[loop] & (~PREREQ_FLAG),
742                                    evr))
743                 elif strong_only and flags[loop] & rpm.RPMSENSE_STRONG:
744                     list.append((names[loop], versions[loop], flags[loop] & (~rpm.RPMSENSE_STRONG)))
745                 elif  weak_only and not (flags[loop] & rpm.RPMSENSE_STRONG):
746                     list.append((names[loop], versions[loop], flags[loop]))
747                 elif not (weak_only or strong_only):
748                     list.append((names[loop], flags[loop], evr))
749
750     def _gatherDepInfo(self):
751         if self._requires is None:
752             self._requires = []
753             self._prereq = []
754             self._provides = []
755             self._conflicts = []
756             self._obsoletes = []
757             self._suggests = []
758             self._supplements = []
759             self._enhances = []
760             self._recommends = []
761
762             self._gather_aux(self.header, self._requires,
763                              rpm.RPMTAG_REQUIRENAME,
764                              rpm.RPMTAG_REQUIREFLAGS,
765                              rpm.RPMTAG_REQUIREVERSION,
766                              self._prereq)
767             self._gather_aux(self.header, self._conflicts,
768                              rpm.RPMTAG_CONFLICTNAME,
769                              rpm.RPMTAG_CONFLICTFLAGS,
770                              rpm.RPMTAG_CONFLICTVERSION)
771             self._gather_aux(self.header, self._provides,
772                              rpm.RPMTAG_PROVIDENAME,
773                              rpm.RPMTAG_PROVIDEFLAGS,
774                              rpm.RPMTAG_PROVIDEVERSION)
775             self._gather_aux(self.header, self._obsoletes,
776                              rpm.RPMTAG_OBSOLETENAME,
777                              rpm.RPMTAG_OBSOLETEFLAGS,
778                              rpm.RPMTAG_OBSOLETEVERSION)
779             try:
780                 self._gather_aux(self.header, self._recommends,
781                                  rpm.RPMTAG_SUGGESTSNAME,
782                                  rpm.RPMTAG_SUGGESTSFLAGS,
783                                  rpm.RPMTAG_SUGGESTSVERSION,
784                                  strong_only=True)
785                 self._gather_aux(self.header, self._suggests,
786                                  rpm.RPMTAG_SUGGESTSNAME,
787                                  rpm.RPMTAG_SUGGESTSFLAGS,
788                                  rpm.RPMTAG_SUGGESTSVERSION,
789                                  weak_only=True)
790                 self._gather_aux(self.header, self._supplements,
791                                  rpm.RPMTAG_ENHANCESNAME,
792                                  rpm.RPMTAG_ENHANCESFLAGS,
793                                  rpm.RPMTAG_ENHANCESVERSION,
794                                  strong_only=True)
795                 self._gather_aux(self.header, self._enhances,
796                                  rpm.RPMTAG_ENHANCESNAME,
797                                  rpm.RPMTAG_ENHANCESFLAGS,
798                                  rpm.RPMTAG_ENHANCESVERSION,
799                                  weak_only=True)
800             except:
801                 pass
802
803
804     def scriptprog(self, which):
805         """Get the specified script interpreter as a string.
806            Depending on rpm-python version, the string may or may not include
807            interpreter arguments, if any."""
808         prog = self[which]
809         if prog is None:
810             prog = ""
811         elif not isinstance(prog, basestring):
812             # http://rpm.org/ticket/847#comment:2
813             prog = " ".join(prog)
814         return prog
815
816
817 def getInstalledPkgs(name):
818     """Get list of installed package objects by name."""
819
820     pkgs = []
821     ts = rpm.TransactionSet()
822     if re.search('[?*]|\[.+\]', name):
823         mi = ts.dbMatch()
824         mi.pattern("name", rpm.RPMMIRE_GLOB, name)
825     else:
826         mi = ts.dbMatch("name", name)
827
828     for hdr in mi:
829         pkgs.append(InstalledPkg(name, hdr))
830
831     return pkgs
832
833 # Class to provide an API to an installed package
834 class InstalledPkg(Pkg):
835     def __init__(self, name, hdr = None):
836         if not hdr:
837             ts = rpm.TransactionSet()
838             mi = ts.dbMatch('name', name)
839             if not mi:
840                 raise KeyError(name)
841             try:
842                 hdr = mi.next()
843             except StopIteration:
844                 raise KeyError(name)
845
846         Pkg.__init__(self, name, '/', hdr)
847
848         self.extracted = True
849         # create a fake filename to satisfy some checks on the filename
850         self.filename = '%s-%s-%s.%s.rpm' % \
851             (self.name, self[rpm.RPMTAG_VERSION], self[rpm.RPMTAG_RELEASE],
852              self[rpm.RPMTAG_ARCH])
853
854     def cleanup(self):
855         pass
856
857     def checkSignature(self):
858         return (0, 'fake: pgp md5 OK')
859
860 # Class to provide an API to a "fake" package, eg. for specfile-only checks
861 class FakePkg:
862     def __init__(self, name):
863         self.name = name
864         self.arch = None
865         self.current_linenum = None
866
867     def cleanup(self):
868         pass
869
870 # Class for files in packages
871 class PkgFile(object):
872
873     def __init__(self, name):
874         self.name = name
875         # Real path to the file (taking extract dir into account)
876         self.path = name
877         self.flags = 0
878         self.mode = 0
879         self.user = None
880         self.group = None
881         self.linkto = ''
882         self.size = None
883         self.md5 = None
884         self.mtime = 0
885         self.rdev = ''
886         self.inode = 0
887         self.requires = []
888         self.provides = []
889         self.lang = ''
890         self.magic = ''
891         self.filecaps = None
892
893     # TODO: decompression support
894
895     is_config    = property(lambda self: self.flags & rpm.RPMFILE_CONFIG)
896     is_doc       = property(lambda self: self.flags & rpm.RPMFILE_DOC)
897     is_noreplace = property(lambda self: self.flags & rpm.RPMFILE_NOREPLACE)
898     is_ghost     = property(lambda self: self.flags & rpm.RPMFILE_GHOST)
899     is_missingok = property(lambda self: self.flags & rpm.RPMFILE_MISSINGOK)
900
901
902 if __name__ == '__main__':
903     for p in sys.argv[1:]:
904         pkg = Pkg(sys.argv[1], tempfile.gettempdir())
905         print ('Requires: %s' % pkg.requires())
906         print ('Prereq: %s' % pkg.prereq())
907         print ('Conflicts: %s' % pkg.conflicts())
908         print ('Provides: %s' % pkg.provides())
909         print ('Obsoletes: %s' % pkg.obsoletes())
910         pkg.cleanup()
911
912 # Pkg.py ends here
913
914 # Local variables:
915 # indent-tabs-mode: nil
916 # py-indent-offset: 4
917 # End:
918 # ex: ts=4 sw=4 et