Imported Upstream version 1.4 upstream/1.4
authorAnas Nashif <anas.nashif@intel.com>
Wed, 21 Nov 2012 23:28:00 +0000 (15:28 -0800)
committerAnas Nashif <anas.nashif@intel.com>
Wed, 21 Nov 2012 23:28:00 +0000 (15:28 -0800)
46 files changed:
AUTHORS [new file with mode: 0644]
AbstractCheck.py [new file with mode: 0644]
BinariesCheck.py [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
Config.py [new file with mode: 0644]
ConfigCheck.py [new file with mode: 0644]
DistributionCheck.py [new file with mode: 0644]
DocFilesCheck.py [new file with mode: 0644]
FHSCheck.py [new file with mode: 0644]
FilesCheck.py [new file with mode: 0644]
Filter.py [new file with mode: 0644]
I18NCheck.py [new file with mode: 0644]
INSTALL [new file with mode: 0644]
InitScriptCheck.py [new file with mode: 0644]
LSBCheck.py [new file with mode: 0644]
Makefile [new file with mode: 0644]
MenuCheck.py [new file with mode: 0644]
MenuXDGCheck.py [new file with mode: 0644]
NamingPolicyCheck.py [new file with mode: 0644]
PamCheck.py [new file with mode: 0644]
Pkg.py [new file with mode: 0644]
PostCheck.py [new file with mode: 0644]
README [new file with mode: 0644]
README.devel [new file with mode: 0644]
RpmFileCheck.py [new file with mode: 0644]
SignatureCheck.py [new file with mode: 0644]
SourceCheck.py [new file with mode: 0644]
SpecCheck.py [new file with mode: 0644]
TagsCheck.py [new file with mode: 0644]
ZipCheck.py [new file with mode: 0644]
__isocodes__.py [new file with mode: 0644]
__version__.py [new file with mode: 0644]
config [new file with mode: 0644]
rpmdiff [new file with mode: 0755]
rpmlint [new file with mode: 0755]
rpmlint.1 [new file with mode: 0644]
rpmlint.bash-completion [new file with mode: 0644]
test.sh [new file with mode: 0755]
test/PamCheck-0.1-1.i586.rpm [new file with mode: 0644]
test/SpecCheck.spec [new file with mode: 0644]
test/test.PamCheck.py [new file with mode: 0644]
test/test.Pkg.py [new file with mode: 0644]
test/test.SpecCheck.py [new file with mode: 0644]
tools/Testing.py [new file with mode: 0644]
tools/generate-isocodes.py [new file with mode: 0755]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..0f8ae9c
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,14 @@
+Chmouel Boudjnah
+Christian Belisle
+David Baudens <baudens at mandriva.com>
+Frederic Crozat <fcrozat at mandriva.com>
+Frédéric Lepied <flepied at mandriva.com>
+Guillaume Rousse <guillomovitch at mandriva.org>
+Gwenole Beauchesne <gbeauchesne at mandriva.com>
+Michael Scherer <misc at mandriva.org>
+Nicolas Planel <nplanel at mandriva.com>
+Pablo Saratxaga <pablo at mandriva.com>
+Pixel <pixel at mandriva.com>
+Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+Thierry Vignaud <tvignaud at mandriva.com>
+Ville Skyttä <ville.skytta at iki.fi>
diff --git a/AbstractCheck.py b/AbstractCheck.py
new file mode 100644 (file)
index 0000000..91c5879
--- /dev/null
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : AbstractCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Tue Sep 28 00:22:38 1999
+# Version       : $Id: AbstractCheck.py 1891 2011-11-23 20:03:00Z scop $
+# Purpose       : Abstract class to hold all the derived classes.
+#############################################################################
+
+import re
+import socket
+import urllib2
+
+from Filter import addDetails, printInfo, printWarning
+import Config
+
+# Note: do not add any capturing parentheses here
+macro_regex = re.compile('%+[{(]?\w+[)}]?')
+
+class _HeadRequest(urllib2.Request):
+    def get_method(self):
+        return "HEAD"
+class _HeadRedirectHandler(urllib2.HTTPRedirectHandler):
+    def redirect_request(*args):
+        res = urllib2.HTTPRedirectHandler.redirect_request(*args)
+        if res:
+            res = _HeadRequest(res.get_full_url())
+        return res
+
+class AbstractCheck:
+    known_checks = {}
+
+    def __init__(self, name):
+        if not AbstractCheck.known_checks.get(name):
+            AbstractCheck.known_checks[name] = self
+        self.name = name
+        self.verbose = False
+        self.network_enabled = Config.getOption("NetworkEnabled", False)
+        self.network_timeout = Config.getOption("NetworkTimeout", 10)
+
+    def check(self, pkg):
+        raise NotImplementedError('check must be implemented in subclass')
+
+    def check_url(self, pkg, tag, url):
+        """Check that URL points to something that seems to exist.
+           Return info() of the response if available."""
+        if not self.network_enabled:
+            if self.verbose:
+                printInfo(pkg, 'network-checks-disabled', url)
+            return
+
+        if self.verbose:
+            printInfo(pkg, 'checking-url', url,
+                      '(timeout %s seconds)' % self.network_timeout)
+
+        # Could use timeout kwarg to urlopen, but that's python >= 2.6 only
+        socket.setdefaulttimeout(self.network_timeout)
+        res = None
+        try:
+            opener = urllib2.build_opener(_HeadRedirectHandler())
+            opener.addheaders = [('User-Agent',
+                                  'rpmlint/%s' % Config.__version__)]
+            res = opener.open(_HeadRequest(url))
+        except Exception, e:
+            errstr = str(e) or repr(e) or type(e)
+            printWarning(pkg, 'invalid-url', '%s:' % tag, url, errstr)
+        info = None
+        if res:
+            info = res.info()
+            res.close()
+        return info
+
+class AbstractFilesCheck(AbstractCheck):
+    def __init__(self, name, file_regexp):
+        self.__files_re = re.compile(file_regexp)
+        AbstractCheck.__init__(self, name)
+    def check(self, pkg):
+        if pkg.isSource():
+            return
+        ghosts = pkg.ghostFiles()
+        for filename in (x for x in pkg.files() if x not in ghosts):
+            if self.__files_re.match(filename):
+                self.check_file(pkg, filename)
+
+
+    def check_file(self, pkg, filename):
+        """Virtual method called for each file that match the regexp passed
+        to the constructor.
+        """
+        raise NotImplementedError('check must be implemented in subclass')
+
+addDetails(
+'invalid-url',
+'''The value should be a valid, public HTTP, HTTPS, or FTP URL.''',
+
+'network-checks-disabled',
+'''Checks requiring network access have not been enabled in configuration,
+see the NetworkEnabled option.''',
+)
+
+# AbstractCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/BinariesCheck.py b/BinariesCheck.py
new file mode 100644 (file)
index 0000000..0355557
--- /dev/null
@@ -0,0 +1,568 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : BinariesCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Tue Sep 28 07:01:42 1999
+# Version       : $Id: BinariesCheck.py 1893 2011-11-23 20:24:32Z scop $
+# Purpose       : check binary files in a binary rpm package.
+#############################################################################
+
+import re
+import stat
+
+import rpm
+
+from Filter import addDetails, printError, printWarning
+import AbstractCheck
+import Config
+import Pkg
+
+
+DEFAULT_SYSTEM_LIB_PATHS = (
+    '/lib', '/usr/lib', '/usr/X11R6/lib',
+    '/lib64', '/usr/lib64', '/usr/X11R6/lib64')
+
+class BinaryInfo:
+
+    needed_regex = re.compile('\s+\(NEEDED\).*\[(\S+)\]')
+    rpath_regex = re.compile('\s+\(RPATH\).*\[(\S+)\]')
+    soname_regex = re.compile('\s+\(SONAME\).*\[(\S+)\]')
+    comment_regex = re.compile('^\s+\[\s*\d+\]\s+\.comment\s+')
+    pic_regex = re.compile('^\s+\[\s*\d+\]\s+\.rela?\.(data|text)')
+    #   GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
+    stack_regex = re.compile('^\s+GNU_STACK\s+(?:(?:\S+\s+){5}(\S+)\s+)?')
+    stack_exec_regex = re.compile('^..E$')
+    undef_regex = re.compile('^undefined symbol:\s+(\S+)')
+    unused_regex = re.compile('^\s+(\S+)')
+    exit_call_regex = re.compile('\s+FUNC\s+.*?\s+(_?exit(?:@\S+)?)(?:\s|$)')
+    fork_call_regex = re.compile('\s+FUNC\s+.*?\s+(fork(?:@\S+)?)(?:\s|$)')
+
+    def __init__(self, pkg, path, file, is_ar, is_shlib):
+        self.readelf_error = False
+        self.needed = []
+        self.rpath = []
+        self.undef = []
+        self.unused = []
+        self.comment = False
+        self.soname = False
+        self.non_pic = True
+        self.stack = False
+        self.exec_stack = False
+        self.exit_calls = []
+        fork_called = False
+        self.tail = ''
+
+        is_debug = path.endswith('.debug')
+
+        cmd = ['env', 'LC_ALL=C', 'readelf', '-W', '-S', '-l', '-d', '-s']
+        cmd.append(path)
+        res = Pkg.getstatusoutput(cmd)
+        if not res[0]:
+            for l in res[1].splitlines():
+
+                r = BinaryInfo.needed_regex.search(l)
+                if r:
+                    self.needed.append(r.group(1))
+                    continue
+
+                r = BinaryInfo.rpath_regex.search(l)
+                if r:
+                    for p in r.group(1).split(':'):
+                        self.rpath.append(p)
+                    continue
+
+                if BinaryInfo.comment_regex.search(l):
+                    self.comment = True
+                    continue
+
+                if BinaryInfo.pic_regex.search(l):
+                    self.non_pic = False
+                    continue
+
+                r = BinaryInfo.soname_regex.search(l)
+                if r:
+                    self.soname = r.group(1)
+                    continue
+
+                r = BinaryInfo.stack_regex.search(l)
+                if r:
+                    self.stack = True
+                    flags = r.group(1)
+                    if flags and BinaryInfo.stack_exec_regex.search(flags):
+                        self.exec_stack = True
+                    continue
+
+                if is_shlib:
+                    r = BinaryInfo.exit_call_regex.search(l)
+                    if r:
+                        self.exit_calls.append(r.group(1))
+                        continue
+                    r = BinaryInfo.fork_call_regex.search(l)
+                    if r:
+                        fork_called = True
+                        continue
+
+            if self.non_pic:
+                self.non_pic = 'TEXTREL' in res[1]
+
+            # Ignore all exit() calls if fork() is being called.
+            # Does not have any context at all but without this kludge, the
+            # number of false positives would probably be intolerable.
+            if fork_called:
+                self.exit_calls = []
+
+        else:
+            self.readelf_error = True
+            printWarning(pkg, 'binaryinfo-readelf-failed',
+                         file, re.sub('\n.*', '', res[1]))
+
+        fobj = None
+        try:
+            fobj = open(path)
+            fobj.seek(-12, 2) # 2 == os.SEEK_END, for python 2.4 compat (#172)
+            self.tail = fobj.read()
+        except Exception, e:
+            printWarning(pkg, 'binaryinfo-tail-failed %s: %s' % (file, e))
+        if fobj:
+            fobj.close()
+
+        # Undefined symbol and unused direct dependency checks make sense only
+        # for installed packages.
+        # skip debuginfo: https://bugzilla.redhat.com/190599
+        if not is_ar and not is_debug and isinstance(pkg, Pkg.InstalledPkg):
+            # We could do this with objdump, but it's _much_ simpler with ldd.
+            res = Pkg.getstatusoutput(
+                ('env', 'LC_ALL=C', 'ldd', '-d', '-r', path))
+            if not res[0]:
+                for l in res[1].splitlines():
+                    undef = BinaryInfo.undef_regex.search(l)
+                    if undef:
+                        self.undef.append(undef.group(1))
+                if self.undef:
+                    cmd = self.undef[:]
+                    cmd.insert(0, 'c++filt')
+                    try:
+                        res = Pkg.getstatusoutput(cmd)
+                        if not res[0]:
+                            self.undef = res[1].splitlines()
+                    except:
+                        pass
+            else:
+                printWarning(pkg, 'ldd-failed', file)
+            res = Pkg.getstatusoutput(
+                ('env', 'LC_ALL=C', 'ldd', '-r', '-u', path))
+            if res[0]:
+                # Either ldd doesn't grok -u (added in glibc 2.3.4) or we have
+                # unused direct dependencies
+                in_unused = False
+                for l in res[1].splitlines():
+                    if not l.rstrip():
+                        pass
+                    elif l.startswith('Unused direct dependencies'):
+                        in_unused = True
+                    elif in_unused:
+                        unused = BinaryInfo.unused_regex.search(l)
+                        if unused:
+                            self.unused.append(unused.group(1))
+                        else:
+                            in_unused = False
+
+path_regex = re.compile('(.*/)([^/]+)')
+numeric_dir_regex = re.compile('/usr(?:/share)/man/man./(.*)\.[0-9](?:\.gz|\.bz2)')
+versioned_dir_regex = re.compile('[^.][0-9]')
+ldso_soname_regex = re.compile('^ld(-linux(-(ia|x86_)64))?\.so')
+so_regex = re.compile('/lib(64)?/[^/]+\.so(\.[0-9]+)*$')
+validso_regex = re.compile('(\.so\.\d+(\.\d+)*|\d\.so)$')
+sparc_regex = re.compile('SPARC32PLUS|SPARC V9|UltraSPARC')
+system_lib_paths = Config.getOption('SystemLibPaths', DEFAULT_SYSTEM_LIB_PATHS)
+pie_exec_re = Config.getOption('PieExecutables')
+if pie_exec_re:
+    pie_exec_re = re.compile(pie_exec_re)
+usr_lib_regex = re.compile('^/usr/lib(64)?/')
+bin_regex = re.compile('^(/usr(/X11R6)?)?/s?bin/')
+soversion_regex = re.compile('.*?([0-9][.0-9]*)\\.so|.*\\.so\\.([0-9][.0-9]*).*')
+reference_regex = re.compile('\.la$|^/usr/lib(64)?/pkgconfig/')
+usr_lib_exception_regex = re.compile(Config.getOption('UsrLibBinaryException', '^/usr/lib(64)?/(perl|python|ruby|menu|pkgconfig|ocaml|lib[^/]+\.(so|l?a)$|bonobo/servers/)'))
+srcname_regex = re.compile('(.*?)-[0-9]')
+invalid_dir_ref_regex = re.compile('/(home|tmp)(\W|$)')
+ocaml_mixed_regex = re.compile('^Caml1999X0\d\d$')
+
+def dir_base(path):
+    res = path_regex.search(path)
+    if res:
+        return res.group(1), res.group(2)
+    else:
+        return '', path
+
+class BinariesCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, 'BinariesCheck')
+
+    def check(self, pkg):
+        # Check only binary package
+        if pkg.isSource():
+            return
+
+        files = pkg.files()
+        exec_files = []
+        has_lib = False
+        version = None
+        binary = False
+        binary_in_usr_lib = False
+        has_usr_lib_file = False
+
+        multi_pkg = False
+        res = srcname_regex.search(pkg[rpm.RPMTAG_SOURCERPM] or '')
+        if res:
+            multi_pkg = (pkg.name != res.group(1))
+
+        for fname, pkgfile in files.items():
+
+            if not stat.S_ISDIR(pkgfile.mode) and usr_lib_regex.search(fname):
+                has_usr_lib_file = True
+                if not binary_in_usr_lib and \
+                        usr_lib_exception_regex.search(fname):
+                    # Fake that we have binaries there to avoid
+                    # only-non-binary-in-usr-lib false positives
+                    binary_in_usr_lib = True
+
+            is_elf = 'ELF' in pkgfile.magic
+            is_ar = 'current ar archive' in pkgfile.magic
+            is_ocaml_native = 'Objective caml native' in pkgfile.magic
+            is_binary = is_elf or is_ar or is_ocaml_native
+
+            if not is_binary:
+                if reference_regex.search(fname):
+                    lines = pkg.grep(invalid_dir_ref_regex, fname)
+                    if lines:
+                        printError(pkg, 'invalid-directory-reference', fname,
+                                   '(line %s)' % ", ".join(lines))
+                continue
+
+            # binary files only from here on
+
+            binary = True
+
+            if has_usr_lib_file and not binary_in_usr_lib and \
+                    usr_lib_regex.search(fname):
+                binary_in_usr_lib = True
+
+            if pkg.arch == 'noarch':
+                printError(
+                    pkg,
+                    'arch-independent-package-contains-binary-or-object',
+                    fname)
+                continue
+
+            # arch dependent packages only from here on
+
+            # in /usr/share ?
+            if fname.startswith('/usr/share/'):
+                printError(pkg, 'arch-dependent-file-in-usr-share', fname)
+
+            # in /etc ?
+            if fname.startswith('/etc/'):
+                printError(pkg, 'binary-in-etc', fname)
+
+            if pkg.arch == 'sparc' and sparc_regex.search(pkgfile.magic):
+                printError(pkg, 'non-sparc32-binary', fname)
+
+            if is_ocaml_native or fname.endswith('.o') or \
+                    fname.endswith('.static'):
+                continue
+
+            # stripped ?
+            if 'not stripped' in pkgfile.magic:
+                printWarning(pkg, 'unstripped-binary-or-object', fname)
+
+            # inspect binary file
+            is_shlib = so_regex.search(fname)
+            bin_info = BinaryInfo(pkg, pkgfile.path, fname, is_ar, is_shlib)
+
+            if is_shlib:
+                has_lib = True
+
+            # shared libs
+            if is_shlib and not bin_info.readelf_error:
+
+                # so name in library
+                if not bin_info.soname:
+                    printWarning(pkg, 'no-soname', fname)
+                else:
+                    if not validso_regex.search(bin_info.soname):
+                        printError(pkg, 'invalid-soname', fname,
+                                   bin_info.soname)
+                    else:
+                        (directory, base) = dir_base(fname)
+                        try:
+                            symlink = directory + bin_info.soname
+                            link = files[symlink].linkto
+                            if link not in (fname, base, ''):
+                                printError(pkg, 'invalid-ldconfig-symlink',
+                                           fname, link)
+                        except KeyError:
+                            if base.startswith("lib") or base.startswith("ld-"):
+                                printError(pkg, 'no-ldconfig-symlink', fname)
+
+                    res = soversion_regex.search(bin_info.soname)
+                    if res:
+                        soversion = res.group(1) or res.group(2)
+                        if version is None:
+                            version = soversion
+                        elif version != soversion:
+                            version = -1
+
+                if bin_info.non_pic:
+                    printError(pkg, 'shlib-with-non-pic-code', fname)
+
+                # It could be useful to check these for others than shared
+                # libs only, but that has potential to generate lots of
+                # false positives and noise.
+                for s in bin_info.undef:
+                    printWarning(pkg, 'undefined-non-weak-symbol', fname, s)
+                for s in bin_info.unused:
+                    printWarning(pkg, 'unused-direct-shlib-dependency',
+                                 fname, s)
+
+                # calls exit() or _exit()?
+                for ec in bin_info.exit_calls:
+                    printWarning(pkg, 'shared-lib-calls-exit', fname, ec)
+
+            # rpath ?
+            if bin_info.rpath:
+                for p in bin_info.rpath:
+                    if p in system_lib_paths or not usr_lib_regex.search(p):
+                        printError(pkg, 'binary-or-shlib-defines-rpath',
+                                   fname, bin_info.rpath)
+                        break
+
+            is_exec = 'executable' in pkgfile.magic
+            is_shobj = 'shared object' in pkgfile.magic
+
+            if not is_exec and not is_shobj:
+                continue
+
+            if is_shobj and not is_exec and '.so' not in fname and \
+                    bin_regex.search(fname):
+                # pkgfile.magic does not contain "executable" for PIEs
+                is_exec = True
+
+            if is_exec:
+
+                if bin_regex.search(fname):
+                    exec_files.append(fname)
+
+                if ocaml_mixed_regex.search(bin_info.tail):
+                    printWarning(pkg, 'ocaml-mixed-executable', fname)
+
+                if not is_shobj and pie_exec_re and pie_exec_re.search(fname):
+                    printError(pkg, 'non-position-independent-executable',
+                               fname)
+
+            if bin_info.readelf_error:
+                continue
+
+            if not bin_info.needed and not (
+                bin_info.soname and ldso_soname_regex.search(bin_info.soname)):
+                if is_shobj:
+                    printError(pkg,
+                               'shared-lib-without-dependency-information',
+                               fname)
+                else:
+                    printError(pkg, 'statically-linked-binary', fname)
+
+            else:
+                # linked against libc ?
+                if "libc." not in fname and \
+                        (not bin_info.soname or \
+                             ("libc." not in bin_info.soname and \
+                              not ldso_soname_regex.search(bin_info.soname))):
+
+                    found_libc = False
+                    for lib in bin_info.needed:
+                        if "libc." in lib:
+                            found_libc = True
+                            break
+
+                    if not found_libc:
+                        if is_shobj:
+                            printError(pkg, 'library-not-linked-against-libc',
+                                       fname)
+                        else:
+                            printError(pkg, 'program-not-linked-against-libc',
+                                       fname)
+
+            if bin_info.stack:
+                if bin_info.exec_stack:
+                    printWarning(pkg, 'executable-stack', fname)
+            elif not bin_info.readelf_error and (
+                pkg.arch.endswith("86") or pkg.arch.startswith("pentium") or
+                pkg.arch in ("athlon", "x86_64")):
+                printError(pkg, 'missing-PT_GNU_STACK-section', fname)
+
+        if has_lib:
+            for f in exec_files:
+                printError(pkg, 'executable-in-library-package', f)
+            for f in files:
+                res = numeric_dir_regex.search(f)
+                fn = res and res.group(1) or f
+                if f not in exec_files and not so_regex.search(f) and \
+                        not versioned_dir_regex.search(fn):
+                    printError(pkg, 'non-versioned-file-in-library-package', f)
+            if version and version != -1 and version not in pkg.name:
+                printError(pkg, 'incoherent-version-in-name', version)
+
+        if not binary and not multi_pkg and pkg.arch != 'noarch':
+            printError(pkg, 'no-binary')
+
+        if has_usr_lib_file and not binary_in_usr_lib:
+            printWarning(pkg, 'only-non-binary-in-usr-lib')
+
+# Create an object to enable the auto registration of the test
+check = BinariesCheck()
+
+# Add information about checks
+addDetails(
+'arch-independent-package-contains-binary-or-object',
+'''The package contains a binary or object file but is tagged
+noarch.''',
+
+'arch-dependent-file-in-usr-share',
+'''This package installs an ELF binary in the /usr/share
+ hierarchy, which is reserved for architecture-independent files.''',
+
+'binary-in-etc',
+'''This package installs an ELF binary in /etc.  Both the
+FHS and the FSSTND forbid this.''',
+
+# 'non-sparc32-binary',
+# '',
+
+'invalid-soname',
+'''The soname of the library is neither of the form lib<libname>.so.<major> or
+lib<libname>-<major>.so.''',
+
+'invalid-ldconfig-symlink',
+'''The symbolic link references the wrong file. It should reference
+the shared library.''',
+
+'no-ldconfig-symlink',
+'''The package should not only include the shared library itself, but
+also the symbolic link which ldconfig would produce. (This is
+necessary, so that the link gets removed by rpm automatically when
+the package gets removed, even if for some reason ldconfig would not be
+run at package postinstall phase.)''',
+
+'shlib-with-non-pic-code',
+'''The listed shared libraries contain object code that was compiled
+without -fPIC. All object code in shared libraries should be
+recompiled separately from the static libraries with the -fPIC option.
+
+Another common mistake that causes this problem is linking with
+``gcc -Wl,-shared'' instead of ``gcc -shared''.''',
+
+'binary-or-shlib-defines-rpath',
+'''The binary or shared library defines `RPATH'. Usually this is a
+bad thing because it hardcodes the path to search libraries and so
+makes it difficult to move libraries around.  Most likely you will find a
+Makefile with a line like: gcc test.o -o test -Wl,--rpath.  Also, sometimes
+configure scripts provide a --disable-rpath flag to avoid this.''',
+
+'statically-linked-binary',
+'''The package installs a statically linked binary or object file.
+
+Usually this is a packaging bug. If not, contact your rpmlint distributor
+about this so that this error gets included in the exception file for rpmlint
+and will not be flagged as a packaging bug in the future (or add it to your
+local configuration if you installed rpmlint from the source tarball).''',
+
+'executable-in-library-package',
+'''The package mixes up libraries and executables. Mixing up these
+both types of files makes upgrades quite impossible.''',
+
+'non-versioned-file-in-library-package',
+'''The package contains files in non versioned directories. This makes it
+impossible to have multiple major versions of the libraries installed.
+One solution can be to change the directories which contain the files
+to subdirs of /usr/lib/<name>-<version> or /usr/share/<name>-<version>.
+Another solution can be to include a version number in the file names
+themselves.''',
+
+'incoherent-version-in-name',
+'''The package name should contain the major version of the library.''',
+
+'invalid-directory-reference',
+'This file contains a reference to /tmp or /home.',
+
+'no-binary',
+'''The package should be of the noarch architecture because it doesn't contain
+any binaries.''',
+
+# http://sources.redhat.com/ml/libc-alpha/2003-05/msg00034.html
+'undefined-non-weak-symbol',
+'''The binary contains undefined non-weak symbols.  This may indicate improper
+linkage; check that the binary has been linked as expected.''',
+
+# http://www.redhat.com/archives/fedora-maintainers/2006-June/msg00176.html
+'unused-direct-shlib-dependency',
+'''The binary contains unused direct shared library dependencies.  This may
+indicate gratuitously bloated linkage; check that the binary has been linked
+with the intended shared libraries only.''',
+
+'only-non-binary-in-usr-lib',
+'''There are only non binary files in /usr/lib so they should be in
+/usr/share.''',
+
+'binaryinfo-readelf-failed',
+'''Executing readelf on this file failed, all checks could not be run.''',
+
+'binaryinfo-tail-failed',
+'''Reading trailing bytes of this file failed, all checks could not be run.''',
+
+'ldd-failed',
+'''Executing ldd on this file failed, all checks could not be run.''',
+
+'executable-stack',
+'''The binary declares the stack as executable.  Executable stack is usually an
+error as it is only needed if the code contains GCC trampolines or similar
+constructs which uses code on the stack.  One common source for needlessly
+executable stack cases are object files built from assembler files which
+don\'t define a proper .note.GNU-stack section.''',
+
+'missing-PT_GNU_STACK-section',
+'''The binary lacks a PT_GNU_STACK section.  This forces the dynamic linker to
+make the stack executable.  Usual suspects include use of a non-GNU linker or
+an old GNU linker version.''',
+
+'shared-lib-calls-exit',
+'''This library package calls exit() or _exit(), probably in a non-fork()
+context. Doing so from a library is strongly discouraged - when a library
+function calls exit(), it prevents the calling program from handling the
+error, reporting it to the user, closing files properly, and cleaning up any
+state that the program has. It is preferred for the library to return an
+actual error code and let the calling program decide how to handle the
+situation.''',
+
+'ocaml-mixed-executable',
+'''Executables built with ocamlc -custom are deprecated.  Packagers should ask
+upstream maintainers to build these executables without the -custom option.  If
+this cannot be changed and the executable needs to be packaged in its current
+form, make sure that rpmbuild does not strip it during the build, and on setups
+that use prelink, make sure that prelink does not strip it either, usually by
+placing a blacklist file in /etc/prelink.conf.d.  For more information, see
+http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=256900#49''',
+
+'non-position-independent-executable',
+'''This executable must be position independent.  Check that it is built with
+-fPIE/-fpie in compiler flags and -pie in linker flags.''',
+)
+
+# BinariesCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..3912109
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..54fad30
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,4871 @@
+2011-12-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 1.4.
+
+2011-11-24  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Add Python 3.3 magic number.
+
+2011-11-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py, InitScriptCheck.py: Get rid of some unnecessary
+         regexps.
+       * BinariesCheck.py, DocFilesCheck.py, Pkg.py, SpecCheck.py,
+         TagsCheck.py: pylint warning cleanups.
+       * AbstractCheck.py: Fix indentation.
+
+2011-11-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Don't assume rpm changelog timestamps are always at
+         noon (#246, thanks to Paul Howarth).
+
+2011-11-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.bash-completion: Hush when trying to load
+         _rpm_installed_packages.
+
+2011-11-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile, rpmlint.bash-completion: Adjust bash completion for
+         bash-completion >= 1.90's dynamic loading.
+       * __isocodes__.py: Regenerate ISO codes list with iso-codes 3.29.
+
+2011-10-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Skip more checks/commands on files that cannot be
+         read.
+         
+         https://bugzilla.redhat.com/show_bug.cgi?id=745446
+
+2011-09-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py, InitScriptCheck.py, MenuCheck.py, Pkg.py,
+         PostCheck.py: Prepare for rpm-python possibly returning script
+         progs as arrays.
+         
+         http://rpm.org/ticket/847#comment:2
+       * PostCheck.py: Code cleanups.
+       * rpmlint.1: Force plain ASCII quotes in man page examples.
+
+2011-09-12  Michael Scherer <misc at mandriva.org>
+
+       * SpecCheck.py: refactor the noarch check into the previous commit
+         ( one less regexp )
+       * SpecCheck.py: merge patch from #97, using BuildArch with
+         something else than Noarch is likely a error and causing issues.
+
+2011-09-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * PostCheck.py: Warn about one line scriptlet commands only if the
+         interpreter is a shell.
+
+2011-08-17  Ville Skyttä <ville.skytta at iki.fi>
+
+       * PostCheck.py: Don't emit use-tmp-in-* for commented out /tmp in
+         scriptlets.
+
+2011-07-26  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmdiff: Comment spelling fix.
+       * rpmdiff: Teach rpmdiff about pretrans and posttrans.
+
+2011-07-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Drop some throwaway code for packages with <>
+         1 init script.
+       * InitScriptCheck.py: Add special init script naming treatment for
+         for *-sysvinit subpackages.
+         
+         https://bugzilla.redhat.com/723460
+
+2011-07-07  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 1.3.
+       * DocFilesCheck.py: Warn about INSTALL files included in docs.
+
+2011-06-18  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Config.py, Filter.py: Drop unnecessary global statements.
+       * Filter.py, rpmlint, rpmlint.1, rpmlint.bash-completion: Add
+         --rawout option (Ludwig Nussel).
+         
+         Allows to specify a file that gets the unfiltered result. Useful
+         to not bother the developer with unimportant stuff but still
+         retain a way for distro maintainers to evaluate all results.
+       * INSTALL: Clarify that python 3 is not yet supported.
+
+2011-06-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py, config: Add UseVarLockSubsys option for
+         forbidding use of /var/lock/subsys (Ludwig Nussel).
+
+2011-05-31  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py, test/SpecCheck.spec, test/test.SpecCheck.py:
+         Recognize patches applied via some common pipe usages.
+       * PostCheck.py: Add info message for non-empty-*.
+
+2011-05-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py, config: Check for position independent
+         executables (based on patch by Ludwig Nussel).
+       * BinariesCheck.py: Do executable checks for PIEs too.
+
+2011-05-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Warn about non-ghost files in /var/run and
+         /var/lock (based on patch from Ludwig Nussel).
+
+2011-05-11  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Filter.py: Fix setting message type for reasons with badness
+         threshold defined (Ludwig Nussel).
+
+2011-04-24  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.bash-completion: Use "declare -F" instead of "type" to
+         check if a bash function exists.
+
+2011-04-22  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 1.2.
+       * rpmlint.bash-completion: Avoid completing on installed packages
+         for some more cases that look like paths.
+       * rpmlint.bash-completion: Add *.spm to completed file extensions.
+       * rpmlint.bash-completion: Add -I/--explain argument completion.
+       * rpmlint, rpmlint.1: Allow -I/--explain to be specified multiple
+         times.
+         
+         Specifying comma separated message ids is deprecated but still
+         supported for now for backwards compatibility.
+       * rpmlint, rpmlint.1, rpmlint.bash-completion: Add --explain as
+         alias for -I.
+       * __isocodes__.py: Regenerate ISO codes list with iso-codes 3.25.
+       * SpecCheck.py: Downgrade files-attr-not-set to a warning, improve
+         its description.
+
+2011-04-14  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Run c++filt only once for all undefined
+         non-weak symbols in a file.
+       * BinariesCheck.py: Run undefined non-weak symbol names through
+         c++filt (Richard Shaw).
+         
+         https://bugzilla.redhat.com/show_bug.cgi?id=696749
+
+2011-04-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Dereference symlinks for python bytecode mtime
+         check.
+         
+         https://bugzilla.redhat.com/694090
+       * Pkg.py: Sort imports.
+       * Pkg.py: Add Pkg.readfile() for dereferencing PkgFile symlinks.
+       * rpmdiff: Update FSF address.
+
+2011-03-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Filter.py: Don't overwrite already existing details (Ludwig
+         Nussel).
+         
+         This allows the distro config to provide alternative descriptions
+         and distro specific explanations without having to patch checks.
+
+2011-03-07  Ville Skyttä <ville.skytta at iki.fi>
+
+       * INSTALL, Pkg.py: Implement is_utf8() without iconv.
+       * test/test.Pkg.py: Remove unused import.
+
+2011-03-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Check outdated or misspelled FSF addresses in text
+         files.
+         
+         https://bugzilla.redhat.com/637712
+
+2011-03-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py, config: Make man warning category configurable.
+       * config: Spelling fix.
+
+2011-02-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * AbstractCheck.py, FilesCheck.py, Makefile, Pkg.py, rpmdiff,
+         rpmlint, test.sh: Delete trailing whitespace.
+       * __isocodes__.py: Regenerate ISO codes list with isocodes 3.20.
+       * FilesCheck.py: Don't try to do python bytecode checks for
+         unreadable files (#201).
+
+2011-02-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Don't try to check Content-Length and Content-MD5
+         for invalid source URLs.
+
+2011-02-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Check for private shared object provides in both
+         lib and lib64 subdirs.
+         
+         https://bugzilla.redhat.com/675360
+
+2011-01-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 1.1.
+       * FilesCheck.py: Don't crash if a file cannot be opened when trying
+         to peek into it.
+
+2011-01-24  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Update expected Python 3.2 bytecode magic value to
+         3180.
+         
+         https://bugzilla.redhat.com/672352
+       * TagsCheck.py: Improve no-description-tag info message.
+       * TagsCheck.py: Improve changelog related info messages.
+       * Pkg.py: Treat 'Unspecified' as a valid Group the same way as
+         'Development/Debug'.
+         
+         rpmbuild >= 4.6.0 adds it automatically if not specified in
+         specfiles.
+       * TagsCheck.py: Add future and overflow checks for changelog
+         timestamps.
+         
+         http://lists.fedoraproject.org/pipermail/buildsys/2010-July/003174.html
+
+2011-01-14  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: stringToVersion cleanups, ignore dash before colon as
+         version/release separator.
+
+2010-12-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Don't emit spurious executable perm warnings for
+         docs with shebangs.
+       * FilesCheck.py: Don't read "head" of files for in each test that
+         needs to peek into them.
+         
+         Do it only once instead. istextfile() is now called peek().
+
+2010-12-11  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Check source and patch file sizes and MD5 hashes
+         against Content-Length and Content-MD5 from their URLs.
+       * AbstractCheck.py: Return urllib info() from check_url() if
+         available.
+
+2010-12-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint: Be smarter when deciding whether arguments are rpm files
+         or installed packages.
+         
+         Arguments that are existing files (not directories) but do not
+         contain slashes and do not end with .rpm, .spm or .spec are now
+         treated as installed packages.
+
+2010-12-07  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint: Make rpmlint executable again.
+       * README.devel, rpmlint, rpmlint.py, test.sh: Get rid of shell
+         script wrapper to make prefixed executables easier to set up.
+       * rpmlint.1: Improve --checkdir documentation.
+
+2010-12-02  Ville Skyttä <ville.skytta at iki.fi>
+
+       * ., Makefile: Use xz to compress tarball.
+
+2010-11-27  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: Fix traceback when stringifying epoch-only versions
+         (Roman Rakus).
+         
+         https://bugzilla.redhat.com/657593
+
+2010-11-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Fix TypeError when outputting
+         incoherent-version-dependency-on messages (#192).
+       * Filter.py: Allow info messages even if badness scoring is used
+         (Ludwig Nussel).
+
+2010-11-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: Make file capabilities available in PkgFile (rpm >=
+         4.7.0, Ludwig Nussel).
+
+2010-11-01  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 1.0.
+
+2010-10-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Add note about file capabilities to setuid-binary
+         info message.
+         
+         https://bugzilla.redhat.com/646455
+
+2010-10-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Turn some comments into docstrings.
+       * FilesCheck.py: Add support for PEP 3147 dirs when finding Python
+         sources (David Malcolm).
+         
+         https://bugzilla.redhat.com/637956
+
+2010-10-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.bash-completion: Offer only long rpmdiff completion
+         options.
+       * rpmlint.bash-completion: Add basic -c/--check completion.
+       * rpmlint.1: Document format of values for --check.
+         
+         https://bugzilla.redhat.com/639823
+
+2010-08-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 0.99.
+       * FilesCheck.py: Drop duplicate -mandoc arg to groff.
+       * FilesCheck.py: Add Python 3.2 magic number handling.
+       * FilesCheck.py: Add Python 2.7 magic number
+         (https://bugzilla.redhat.com/623607, Nils Philippsen)
+
+2010-07-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.1, rpmlint.py: Choose built in config based on prefix
+         used when invoked (Tom Callaway).
+         
+         https://bugzilla.redhat.com/show_bug.cgi?id=537430#c8
+
+2010-06-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * I18NCheck.py: Use rpm.RPMTAG_HEADERI18NTABLE instead of assuming
+         it's 100.
+       * Makefile: Release 0.98.
+
+2010-06-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * I18NCheck.py, Makefile, __isocodes__.py,
+         tools/generate-isocodes.py: Extend list of valid language country
+         codes from the iso-codes project.
+         
+         http://alioth.debian.org/projects/pkg-isocodes/
+         https://bugzilla.redhat.com/show_bug.cgi?id=599516
+
+2010-06-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py, test/test.Pkg.py: Fix self-obsoletion bug with Provides
+         containing Epoch.
+       * Pkg.py: Don't stringify None Epoch to 'None' string in
+         compareEVR().
+       * Pkg.py: Sync rangeCompare() with yum 3.2.27.
+
+2010-06-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Reduce some spell check noise.
+       * rpmlint: Fix handling of arguments containing spaces.
+
+2010-06-03  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Revert accidental bit in previous commit.
+       * SpecCheck.py: Try to avoid messages about ./configure in
+         comments.
+         
+         https://bugzilla.redhat.com/599427
+
+2010-06-02  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Simplify code, remove misleading comment.
+       * TagsCheck.py: Suppress spell check errors for words containing
+         digits.
+         
+         This is a workaround for enchant's digit tokenizing behavior:
+         http://github.com/rfk/pyenchant/issues/issue/3
+
+2010-05-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * DocFilesCheck.py: Bypass doc file check earlier if package has no
+         doc files.
+         
+         As a side effect, works around https://bugzilla.redhat.com/593553
+         with rpm 4.8.0 and gpg-pubkeys.
+
+2010-05-18  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 0.97.
+       * FilesCheck.py: Do man page warning check only in UTF-8 mode for
+         now.
+       * SpecCheck.py, test/SpecCheck.spec: Avoid some false
+         macro-in-comment positives.
+
+2010-05-17  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Add manual page warning check,
+         https://bugzilla.redhat.com/589432
+       * Pkg.py: Split "cat" command into catcmd().
+
+2010-04-27  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Output no-ldconfig-symlink only for files
+         starting with "lib" or "ld-".
+         
+         ldconfig (as of glibc 2.11.90) itself handles only files starting
+         with these prefixes. https://bugzilla.redhat.com/459452
+
+2010-04-26  Ville Skyttä <ville.skytta at iki.fi>
+
+       * test/test.PamCheck.py: Clean up after ourselves.
+
+2010-04-21  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 0.96.
+
+2010-04-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * ConfigCheck.py: Get rid of app-defaults-must-not-be-conffile
+         check, it's covered by non-etc-or-var-file-marked-as-conffile.
+       * BinariesCheck.py, ConfigCheck.py: Replace some trivial regexps
+         with string matches.
+       * ConfigCheck.py: Get rid of file-in-usr-marked-as-conffile check,
+         it's covered by non-etc-or-var-file-marked-as-conffile.
+
+2010-04-16  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Warn about shared object provides in private dirs,
+         https://bugzilla.redhat.com/487974
+       * Pkg.py: Record file provides.
+       * Pkg.py: Fix check_versioned_dep().
+       * test/test.DocFilesCheck.py: Remove obsolete test case.
+       * DocFilesCheck.py, Pkg.py: Rename pkgfile.deps to .requires, parse
+         it to (name, flags, (e, v, r)).
+       * Pkg.py, SpecCheck.py, test/SpecCheck.spec, test/test.Pkg.py,
+         test/test.SpecCheck.py: Move dep token parsing to Pkg, add some
+         tests.
+
+2010-04-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Drop no longer used epoch_regex.
+       * Pkg.py, TagsCheck.py: Store dependencies, provides etc as (name,
+         flags, (epoch, version, release)) internally.
+       * TagsCheck.py: More improvements to dependency formatting in
+         messages.
+       * TagsCheck.py: Improve no-epoch-in-* message formatting.
+       * TagsCheck.py: Check for unexpanded macros in
+         requires/provides/obsoletes/conflicts names in addition to
+         versions.
+
+2010-04-01  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.py: Drop dead code.
+       * rpmlint.py: Do not use unnecessary getopt backwards compatibility
+         things.
+
+2010-03-31  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Simplify some regexps.
+       * SpecCheck.py: Make ifarch etc regexps stricter.
+       * SpecCheck.py: Anchor BuildRoot regex at start of line (Mads
+         Kiilerich).
+       * SpecCheck.py: Don't check for buildroot usage in comments
+         (http://bugzilla.redhat.com/578390)
+
+2010-03-30  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py, InitScriptCheck.py, MenuCheck.py, PostCheck.py,
+         SourceCheck.py, SpecCheck.py, TagsCheck.py: Code cleanups, avoid
+         deeply nested blocks etc.
+
+2010-03-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * AbstractCheck.py, test/test.PamCheck.py: Clean up unused
+         variables and imports.
+       * BinariesCheck.py: Code cleanups, avoid deeply nested blocks etc.
+       * FilesCheck.py: Simplify code.
+       * FilesCheck.py: Move setuid/setgid bit check to same block with
+         rest of normal file checks.
+       * FilesCheck.py: Fix bad reuse of "pkgfile" var.
+       * Pkg.py: Don't bother checking match iterator boolean values.
+         
+         Fixes installed package globbing with rpm 4.8:
+         http://rpm.org/ticket/153 No need to raise KeyError for no
+         matches at this level either.
+       * FilesCheck.py: Check that executables in standard binary dirs
+         have man pages (https://bugzilla.redhat.com/572090, based on
+         patch by Radek Novacek).
+       * FilesCheck.py: Move text file checks to the block of other normal
+         file checks.
+
+2010-03-18  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Check for same named executables in standard bin
+         dirs (https://bugzilla.redhat.com/572097, based on patch from
+         Radek Novacek).
+       * FilesCheck.py: Treat /usr/games as a "bin" dir.
+
+2010-03-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Add dictionary install tip in
+         enchant-dictionary-not-found info message.
+
+2010-03-14  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Report all occurrences of egrep/fgrep on a line.
+       * SpecCheck.py: Check for uses of deprecated egrep and fgrep
+         (https://bugzilla.redhat.com/571386).
+       * SpecCheck.py: Recognize scriptlet section markers.
+
+2010-03-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py, test/SpecCheck.spec, test/test.SpecCheck.py: Test
+         for macros in shell comments (#162,
+         https://bugzilla.redhat.com/571375)
+
+2010-03-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Fix non-coherent-filename for source packages
+         (regression in r1729).
+
+2010-03-03  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 0.95.
+       * AbstractCheck.py: Skip ghost files in files checks,
+         https://bugzilla.redhat.com/570086
+
+2010-02-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Filter.py: Work around Python's ASCII non-TTY stdout and our
+         non-ASCII output.
+       * TagsCheck.py: Omit spell check warnings for capitalized words
+         that do not start a sentence.
+         
+         https://bugzilla.redhat.com/567285
+
+2010-02-21  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py, Config.py, DistributionCheck.py, FilesCheck.py,
+         I18NCheck.py, InitScriptCheck.py, MenuCheck.py, MenuXDGCheck.py,
+         NamingPolicyCheck.py, PamCheck.py, Pkg.py, PostCheck.py,
+         RpmFileCheck.py, SignatureCheck.py, SpecCheck.py, TagsCheck.py,
+         ZipCheck.py: Wrap bunch of long lines, tune comments and info
+         messages.
+       * INSTALL: Require rpm-python >= 4.4.2.2 for expandMacro() in
+         r1729.
+
+2010-02-09  Michael Scherer <misc at mandriva.org>
+
+       * AbstractCheck.py: do not execute check if the network test are
+         marked as disabled, and simplify the logic
+       * TagsCheck.py: - do not expand the format of the filename format,
+         (patch from Per Oyvind Karlsten )
+
+2010-01-31  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 0.94.
+       * SpecCheck.py: Output the latter occurrence of spaces/tabs as
+         mixed-use-of-spaces-and-tabs' context line number.
+       * TagsCheck.py: Sync opensource.org list of licenses with upstream
+         (fixes #58).
+       * SpecCheck.py, rpmlint.1, rpmlint.py: Add ability to read spec
+         file content from stdin (#63).
+       * SpecCheck.py: Do not try to pass non-file spec content to rpm for
+         parsing.
+       * TagsCheck.py: Fix unexpanded macros check with non-string tag
+         values.
+         
+         https://admin.fedoraproject.org/updates/F12/FEDORA-2010-1105
+
+2010-01-27  Ville Skyttä <ville.skytta at iki.fi>
+
+       * config: Improve accuracy of config value type documentation.
+       * FilesCheck.py: LSB user/group status comment update.
+
+2010-01-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Fix iterating spec sources with rpm >= 4.8.0.
+         
+         http://rpm.org/wiki/Releases/4.8.0#Specclass
+       * Makefile: Release 0.93.
+       * Pkg.py: Treat all failures opening the (installed) 'rpm' package
+         for finding default groups as non-fatal.
+       * TagsCheck.py: Load default valid groups only if ValidGroups is
+         not set (or is None).
+       * README, config, rpmlint.1: Move list of available config options
+         to "config", improve documentation.
+
+2010-01-21  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Ignore filenames contained in package when spell
+         checking.
+       * AbstractCheck.py: Use our own User-Agent in urllib2 requests.
+       * Makefile: Fix install target when no *.pyc are generated.
+       * ., Config.py, Makefile, rpmlint.py: Move version number to
+         __version__.py, make it available as Config.__version__.
+       * TagsCheck.py: Include Enchant suggestions in spelling-error
+         output.
+       * AbstractCheck.py: Try harder to get something to print about
+         inaccessible URLs.
+
+2010-01-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * MenuXDGCheck.py: Include errors from desktop-file-validate in
+         output (#85).
+         
+         Based on patch from dmueller at suse.de.
+       * DistributionCheck.py, README, SourceCheck.py, config, rpmlint.1:
+         Add arbitrary manpage/infopage/source compression support (#142).
+         
+         Based on patch from pkarlsen at mandriva.com.
+         
+         The UseBzip2, UseLzma, and UseXz config options have been
+         replaced by the new CompressExtension option (which takes a
+         filename extension without the leading dot), the
+         *page-not-compressed-with-* message ids have been replaced by
+         *page-not-compressed, and the source-or-patch-not-*zipped message
+         ids have been replaced by source-or-patch-not-compressed.
+       * AbstractCheck.py: Do HTTP redirects with HEAD too, urllib2
+         appears to reset them to GET.
+       * SpecCheck.py, TagsCheck.py: Python 2.4 compatibility fixes.
+       * AbstractCheck.py, SpecCheck.py, TagsCheck.py, ZipCheck.py: Avoid
+         embedding anything other than reason in reason id for -i to work.
+       * SpecCheck.py: Demand only tarball sources to be URLs (#170).
+       * SpecCheck.py: Check that SourceX are URLs (#170).
+       * AbstractCheck.py, README, SpecCheck.py, TagsCheck.py: Check that
+         *URL, SourceX and PatchX are not broken links (#165).
+         
+         The new config option NetworkEnabled needs to be True for this
+         check to happen (default is False). See also the new
+         NetworkTimeout option.
+       * TagsCheck.py: Require at least one dot in URL net locations.
+       * AbstractCheck.py, rpmlint.py: Pass verbosity flag to check
+         classes.
+
+2010-01-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py, README: Add svg to default SkipDocsRegexp.
+       * FilesCheck.py, TagsCheck.py: Check for unexpanded macros in many
+         more tags.
+         
+         Various misspelled-macro, percent-in-*, and macro-in-* messages
+         are now reported as unexpanded-macro, and their formatting has
+         been improved.
+       * AbstractCheck.py, FilesCheck.py, SpecCheck.py: Report all
+         unexpanded macros in files and changelogs, not just first of
+         line.
+       * TagsCheck.py: Rename spelling-error-in-* to spelling-error,
+         improve formatting.
+       * TagsCheck.py: Check DistURL and BugURL validity.
+       * TagsCheck.py: Check URLs using urlparse, remove Mandrake
+         reference from info message.
+
+2010-01-16  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Recognize BugURL tag.
+
+2010-01-09  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Do not suppress bunch of filename checks for files
+         in "bad" dirs.
+       * FilesCheck.py: Make backup file check look for patch backup
+         files.
+
+2009-12-02  Michael Scherer <misc at mandriva.org>
+
+       * rpmlint.py: - make -I work again when using addCheckDir, spotted
+         by incubusss
+
+2009-11-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: Fix default groups lookup when the rpm package is not
+         installed.
+
+2009-11-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py, README, config: Add check for version magic in
+         byte-compiled Python files (David Malcolm, modified by yours
+         truly).
+         https://www.zarb.org/pipermail/rpmlint-discuss/2009-November/000787.html
+       * Filter.py, Pkg.py, SignatureCheck.py, rpmdiff, rpmlint.py: Use
+         alternative Python 2/3 'print' compatibility hack; drop rlprint,
+         rename rlwarn to warn.
+       * FilesCheck.py: Drop unused safe_normpath import.
+       * SignatureCheck.py: Fix signature check with rpm versions that
+         output e.g. '(MD5) ' at start of unknown key name.
+       * SignatureCheck.py: Include more info in 'Error checking
+         signature' output.
+
+2009-11-02  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: 0.92.
+       * Makefile: Add --reparagraph to svn2cl options.
+       * Makefile, tools/compile.py: Add option to generate *.pyc too, use
+         py_compile directly instead of a script of our own, remove
+         "print" check from the all target.
+       * TagsCheck.py: Warn only once per misspelled word per tag.
+       * TagsCheck.py: Avoid unnecessary try-except in BAD_WORDS lookup.
+
+2009-10-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Trivial code cleanup, works around pychecker bug.
+       * BinariesCheck.py, DistributionCheck.py, FilesCheck.py,
+         InitScriptCheck.py, MenuCheck.py, PostCheck.py, SpecCheck.py:
+         Replace bunch of trivial regexps with simple string searches.
+       * FilesCheck.py: Output differing python mtimes as formatted
+         datetimes.
+       * TagsCheck.py: Cache and reuse instantiated enchant checkers.
+       * TagsCheck.py: Don't flag package name 'components' as spelling
+         errors with enchant.
+
+2009-10-27  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Make python regex match /usr/lib64 too.
+       * FilesCheck.py: Add check for python byte compiled/source mtime
+         checks (David Malcolm).
+         https://www.zarb.org/pipermail/rpmlint-discuss/2009-October/000775.html
+       * Pkg.py: Avoid import loop introduced in previous change.
+       * Filter.py, Pkg.py, SignatureCheck.py, rpmdiff, rpmlint.py:
+         Introduce Pkg.rlprint and Pkg.rlwarn print-like functions, use
+         them instead of direct writes to sys.std{out,err} (#181).
+
+2009-10-18  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmdiff: Python 3 compatibility tweaks.
+       * rpmdiff: Output usage errors to stderr.
+
+2009-10-16  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Filter.py: Sort diagnostics using key instead of cmp.
+
+2009-10-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmdiff: Don't output an empty line if no differences are found,
+         https://bugzilla.redhat.com/528535
+       * README, TagsCheck.py: Add UseEnchant config option.
+
+2009-10-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmdiff: Fix all Provides/Obsoletes/Conflicts being marked as
+         Requires.
+
+2009-10-02  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.bash-completion: Quote one more $cur for "compgen -W" to
+         avoid globbing.
+       * rpmlint.bash-completion: Reindent.
+       * rpmlint.bash-completion: Quote $cur for "compgen -W" to avoid
+         globbing.
+       * rpmlint.1, rpmlint.bash-completion, rpmlint.py: Add -o/--option
+         option for overriding config options.
+       * rpmlint.bash-completion: Don't offer short option completions
+         where long ones exist.
+       * rpmlint.bash-completion: Add (non)completion of -I and -c/--check
+         arguments.
+       * rpmlint.bash-completion: Cleanups.
+       * rpmlint.bash-completion: Remove no longer existing -p/--profile
+         option completion.
+       * rpmlint.py: Option processing and documentation
+         cleanups/improvements.
+
+2009-10-01  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.py: Clean up help message printing.
+
+2009-09-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Yell only once per missing enchant dictionary.
+       * INSTALL: Note enchant dependency.
+       * Pkg.py, TagsCheck.py: Add summary and description spell checking
+         using enchant (#166, most of the work by Debarshi Ray).
+
+2009-09-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: 0.91
+
+2009-09-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * README, rpmlint.1, rpmlint.py: Honor $XDG_CONFIG_HOME when
+         finding default user config file.
+
+2009-09-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py, Config.py, PostCheck.py, SpecCheck.py,
+         TagsCheck.py: Code cleanups.
+
+2009-09-02  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Report character index where nbsp was found.
+       * Pkg.py: Don't treat '[' without a following ']' and something in
+         between as a glob in getInstalledPkgs().
+
+2009-08-31  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.py: Process installed packages matched by a wildcard in
+         locale's alphabetic order.
+
+2009-08-30  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: Try to identify and bypass magic info originating from
+         inside compressed files for now,
+         https://bugzilla.redhat.com/519694
+
+2009-08-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Include test suite files in dist tarballs.
+       * Makefile: Rename test target to check per GNU conventions.
+       * test.sh: Really make it executable.
+       * test.sh: Make executable, trivial cleanups.
+
+2009-08-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * INSTALL: Note gzip, bzip2 and xz dependencies.
+       * Pkg.py: Decompress lzma files with xz in is_utf8.
+       * README.devel: Remove flepied's email address, no need to bother
+         him with rpmlint development nowadays.
+       * README.devel: Note that the run examples apply to unpacked
+         tarballs too.
+
+2009-08-17  Ville Skyttä <ville.skytta at iki.fi>
+
+       * README, TagsCheck.py: Make max line length configurable (config
+         option MaxLineLength, default still 79).
+
+2009-08-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.bash-completion: Don't rely on bash_completion setting
+         $filenames, just use -o filenames instead.
+
+2009-08-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmdiff: Print friendlier error message than a traceback when
+         loading a package fails, https://bugzilla.redhat.com/516492
+
+2009-08-03  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Downgrade no-buildroot-tag and
+         no-cleaning-of-buildroot to warnings; they are not needed in some
+         rpm configurations/versions, https://bugzilla.redhat.com/515185
+       * SpecCheck.py: Improve rpm-buildroot-usage info message.
+
+2009-07-28  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.bash-completion: Simplify bash completion code a bit.
+
+2009-07-26  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py, TagsCheck.py: Match "foo%_isa" when checking base package
+         dependencies on foo, https://bugzilla.redhat.com/513811
+
+2009-07-22  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.py: Do -a/--all though globbing.
+       * rpmlint.py: Cleanups, NFC.
+
+2009-07-21  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Fix crash if subsys name in an init script
+         ends up being empty.
+       * AbstractCheck.py, FilesCheck.py, SpecCheck.py, TagsCheck.py:
+         Output complete found macro candidate in misspelled macro related
+         messages.
+       * TagsCheck.py: Check for unexpanded macros in summary,
+         description, group, buildhost, and license.
+       * FilesCheck.py: Further improve misspelled-macro info message.
+       * AbstractCheck.py, FilesCheck.py, SpecCheck.py: Move regex that
+         matches macros to AbstractCheck, use it, improve misspelled-macro
+         info message.
+       * FilesCheck.py: s/mispell/misspell/ :þ
+
+2009-07-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.1: Rephrase CAVEATS more.
+       * rpmlint.1: Rephrase CAVEATS a bit.
+
+2009-06-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: 0.90.
+       * rpmdiff: Add missing magic coding comment
+         (https://bugzilla.redhat.com/508683, Michal Nowak).
+
+2009-06-21  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: 0.89
+       * TagsCheck.py: Fix incorrect info in self-obsoletion message.
+       * FilesCheck.py: Output interpreter in non-executable-script
+         warning, remove quotes from wrong-script-interpreter one.
+       * Pkg.py: Remove stale comments.
+       * TagsCheck.py: Code cleanup, NFC.
+       * TagsCheck.py: improve percent-in-* message details.
+       * TagsCheck.py: Remove obsolete-on-name check (superseded by
+         self-obsoletion).
+       * TagsCheck.py: Add check for self-obsoletion cases,
+         https://bugzilla.redhat.com/461610
+       * Pkg.py: Borrow various utilities from yum's rpmUtils.miscutils.
+       * Makefile, rpmUtils: Revert previous commit.
+       * Makefile, rpmUtils, rpmUtils/miscutils.py,
+         rpmUtils/transaction.py: Borrow
+         rpmUtils/{miscutils,transaction}.py from yum.
+       * rpmdiff: Improve dependency formatting.
+       * rpmdiff: Take Epoch into account in self-provides filtering.
+       * Pkg.py: Improve legacy prereq tracking with new rpm versions.
+
+2009-06-18  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmdiff: Add enough space padding to output.
+
+2009-06-17  Ville Skyttä <ville.skytta at iki.fi>
+
+       * DistributionCheck.py, FilesCheck.py, Pkg.py, README: Add xz
+         support, improve *page-not-compressed-with-* info messages.
+       * BinariesCheck.py: Avoid a couple of unnecesary regex matches.
+
+2009-06-16  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py, Config.py, DistributionCheck.py, FilesCheck.py,
+         Filter.py, InitScriptCheck.py, MenuCheck.py, MenuXDGCheck.py,
+         Pkg.py, PostCheck.py, README, SourceCheck.py, SpecCheck.py,
+         TagsCheck.py, ZipCheck.py, rpmdiff, rpmlint.py: Code cleanups.
+
+2009-06-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * README, config, rpmlint.1, rpmlint.py: Move default user config
+         file to ~/.config/rpmlint per freedesktop.org base dir spec.
+       * config: Comment spelling fix.
+
+2009-05-31  Ville Skyttä <ville.skytta at iki.fi>
+
+       * DistributionCheck.py:
+         s/compressed-wth-lzma/compressed-with-lzma/, thanks to Mark Hall.
+
+2009-05-30  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmdiff: Improve usage message.
+       * rpmdiff: Always append /usr/share/rpmlint to load path.
+       * rpmlint.bash-completion: Add rpmdiff bash completion.
+       * rpmdiff: Sync with koji version by Florian Festi and Mike Bonnet
+         (https://fedorahosted.org/koji/log/hub/rpmdiff)
+
+2009-05-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Check for comparison operators in dependency tokens
+         (#174).
+
+2009-04-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Hack around a subsys parsing deficiency,
+         https://bugzilla.redhat.com/498107
+       * InitScriptCheck.py: Print expected init script names in
+         incoherent-init-script-name output.
+
+2009-04-21  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Don't output missing-PT_GNU_STACK-section
+         messages if readelf failed.
+
+2009-04-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Add check for debuginfo packages without sources.
+
+2009-03-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Allow more common files to be non-readable
+         (https://bugzilla.redhat.com/226412).
+       * FilesCheck.py: Revert relative symlink target normalization back
+         to more aggressive.
+       * BinariesCheck.py, Config.py, ConfigCheck.py, FHSCheck.py,
+         FilesCheck.py, I18NCheck.py, MenuCheck.py, Pkg.py, PostCheck.py,
+         TagsCheck.py, rpmdiff: Code cleanups.
+       * FilesCheck.py: Do not check existence of relative link targets on
+         the filesystem for consistency with absolute link target checks,
+         improve dangling-*symlink info messages.
+       * FilesCheck.py, Pkg.py: Be a bit stricter when normalizing link
+         targets, do it earlier.
+       * FilesCheck.py, Pkg.py: Improve link target normalization in
+         symlink checks.
+
+2009-03-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: 0.87
+       * rpmlint.py: Avoid importing SpecCheck (and TagsCheck, FilesCheck
+         etc by recursion) too early, causing config settings for them
+         being ignored.
+       * rpmlint.bash-completion: Speed up path based completion.
+       * Makefile: 0.86
+       * Makefile: Make "verify" target more useful.
+       * SpecCheck.py: More spaces vs tabs improvements
+         (https://bugzilla.redhat.com/488146#c6, Mattias Ellert)
+
+2009-03-17  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: Don't treat %verifyscript dependencies as 'pre' ones.
+       * Pkg.py: Drop obsolete backwards compatibility cruft.
+       * Pkg.py, PostCheck.py: Add %verifyscript support.
+
+2009-03-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Improve buildconfig_rpath_regex a bit more.
+       * FilesCheck.py: Try to catch more rpaths in *.pc and *-config.
+       * FilesCheck.py: Add check for rpaths in *.pc and *-config files
+         (https://bugzilla.redhat.com/334414, based on patch from Milos
+         Jakubicek)
+
+2009-03-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Improve mixed tabs/spaces indentation check
+         (Mattias Ellert, https://bugzilla.redhat.com/488146).
+
+2009-02-28  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Work around spurious "No such file or dir" errors
+         from rpm when querying specfiles,
+         https://bugzilla.redhat.com/487855
+       * BinariesCheck.py, INSTALL, Pkg.py: Use file magic info from rpm
+         header and amend it with python-magic, include magic info in
+         PkgFile.
+       * Pkg.py: Fix pkgfile.path for files in uninstalled (extracted)
+         binary packages.
+
+2009-02-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Fix false positives caused by noarch subpackages,
+         include (sub)package name in libdir-macro-in-noarch-package
+         message (#175).
+       * test/SpecCheck.spec, test/noarch-sub.spec,
+         test/test.SpecCheck.py: Include noarch subpackage test in
+         SpecCheck test.
+       * Pkg.py: Ensure current_linenum is always defined in fake pkgs.
+
+2009-02-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * test/noarch-sub.spec: Test specfile for noarch subpackages.
+
+2009-02-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Dict iteration improvement.
+       * FilesCheck.py: Strip quotes from scriptlets before examining them
+         (#169).
+
+2009-02-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.py: Walk given dirs recursively.
+       * Pkg.py: Improve temp dir creation and temp file/dir naming.
+       * rpmlint.py: Run package cleanup handlers only once.
+
+2009-02-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * DocFilesCheck.py, INSTALL, Pkg.py, PostCheck.py, rpmlint.py: Drop
+         support for rpm < 4.4.
+
+2009-02-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Fix undefined variable.
+       * BinariesCheck.py: Make only-non-binary-in-usr-lib false positives
+         less likely, https://bugzilla.redhat.com/483199
+       * BinariesCheck.py: Downgrade only-non-binary-in-usr-lib to a
+         warning, https://bugzilla.redhat.com/483199
+
+2009-02-01  Ville Skyttä <ville.skytta at iki.fi>
+
+       * AbstractCheck.py, BinariesCheck.py, MenuCheck.py: Cleanups.
+       * BinariesCheck.py, DocFilesCheck.py, FilesCheck.py, I18NCheck.py,
+         InitScriptCheck.py, MenuCheck.py, Pkg.py, SourceCheck.py,
+         SpecCheck.py, ZipCheck.py, rpmdiff: Represent files inside
+         packages as PkgFile objects.
+       * rpmdiff: Make rpmdiff usable on installed packages.
+       * Pkg.py: Always raise KeyError (instead of sometimes
+         StopIteration) from InstalledPkg if not found by name.
+       * DocFilesCheck.py, test/test.DocFilesCheck.py: Move testing code
+         out of DocFilesCheck.py.
+       * BinariesCheck.py, DocFilesCheck.py, Pkg.py: Move file
+         dependencies to central files dict in Pkg.
+       * BinariesCheck.py, MenuCheck.py, MenuXDGCheck.py, Pkg.py,
+         rpmlint.py: Unused variable, import cleanup.
+       * FilesCheck.py: Don't reuse variable 'link' in hardlink check,
+         breaks in symlink check.
+       * BinariesCheck.py, FilesCheck.py, Pkg.py: Add cross directory hard
+         link check (#171, Lubomir Rintel)
+       * Pkg.py: Comment/TODO update.
+
+2009-01-30  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Python 2.4 compat fix for no os.SEEK_END (#172,
+         Lubomir Rintel)
+       * Filter.py: Move Config.info check outside of printDescriptions
+         (#171, lkundrak at v3.sk)
+       * BinariesCheck.py, ConfigCheck.py, DistributionCheck.py,
+         DocFilesCheck.py, FHSCheck.py, FilesCheck.py, Filter.py,
+         I18NCheck.py, InitScriptCheck.py, LSBCheck.py, MenuCheck.py,
+         MenuXDGCheck.py, NamingPolicyCheck.py, PamCheck.py, PostCheck.py,
+         RpmFileCheck.py, SignatureCheck.py, SourceCheck.py, SpecCheck.py,
+         TagsCheck.py, ZipCheck.py: Make sure details are available for -i
+         also for early loaded check modules.
+       * README, rpmlint.1: Add note about best check coverage.
+       * SpecCheck.py: Query specfiles using rpm to find syntax and other
+         errors, https://bugzilla.redhat.com/483196
+
+2009-01-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: Docstring update.
+       * I18NCheck.py, TagsCheck.py: Use generators where appropriate.
+       * SpecCheck.py: Compile section regexps only once.
+
+2009-01-28  Ville Skyttä <ville.skytta at iki.fi>
+
+       * INSTALL: Fix indentation.
+
+2009-01-28  Michael Scherer <misc at mandriva.org>
+
+       * INSTALL: - update the requirement in INSTALL
+       * Pkg.py: - fix python 2.6 deprecation warning
+
+2009-01-27  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Cleanup fallout fixes.
+       * BinariesCheck.py, ConfigCheck.py, DistributionCheck.py,
+         DocFilesCheck.py, FHSCheck.py, FilesCheck.py, I18NCheck.py,
+         InitScriptCheck.py, LSBCheck.py, MenuCheck.py, MenuXDGCheck.py,
+         NamingPolicyCheck.py, PamCheck.py, PostCheck.py, RpmFileCheck.py,
+         SignatureCheck.py, SourceCheck.py, SpecCheck.py, TagsCheck.py,
+         ZipCheck.py, rpmlint.py: Import fixes.
+       * InitScriptCheck.py: Downgrade missing-mandatory-lsb-keyword error
+         to missing-lsb-keyword warning, add some recommended keywords
+         (#71).
+       * AbstractCheck.py, DistributionCheck.py, FHSCheck.py,
+         FilesCheck.py, InitScriptCheck.py, MenuCheck.py,
+         NamingPolicyCheck.py, Pkg.py, PostCheck.py, SourceCheck.py,
+         SpecCheck.py, TagsCheck.py, ZipCheck.py, rpmdiff: Dict access
+         cleanups.
+       * BinariesCheck.py, I18NCheck.py, Pkg.py: Combine file langs into
+         regular Pkg files list.
+
+2009-01-26  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Treat *-headers as devel packages.
+       * BinariesCheck.py, Config.py, DocFilesCheck.py, FHSCheck.py,
+         FilesCheck.py, I18NCheck.py, MenuCheck.py, NamingPolicyCheck.py,
+         Pkg.py, PostCheck.py, TagsCheck.py, rpmdiff: PEP 8, pylint,
+         pychecker cleanups.
+       * AbstractCheck.py, BinariesCheck.py, Config.py, ConfigCheck.py,
+         DistributionCheck.py, DocFilesCheck.py, FHSCheck.py,
+         FilesCheck.py, Filter.py, I18NCheck.py, InitScriptCheck.py,
+         LSBCheck.py, MenuCheck.py, MenuXDGCheck.py, NamingPolicyCheck.py,
+         PamCheck.py, Pkg.py, PostCheck.py, RpmFileCheck.py,
+         SignatureCheck.py, SourceCheck.py, SpecCheck.py, TagsCheck.py,
+         ZipCheck.py, rpmlint.py, tools/Testing.py, tools/compile.py:
+         Import cleanup (PEP 8).
+       * Pkg.py, rpmlint.1: Allow PACKAGE argument to be a glob(7)
+         pattern.
+       * rpmlint.1: Document FILE and PACKAGE arguments.
+       * rpmlint.py: Do not run SpecCheck on *.spec if the check is not
+         enabled.
+       * Config.py: Strip .py[co]? from check names to add.
+       * AbstractCheck.py, Config.py, rpmlint.py: Do not reset list of
+         default checks to run if Config.addCheck() is used in config
+         files.
+       * rpmlint.1: Mention that -c may be given multiple times.
+       * rpmlint.py: Improve usage message.
+
+2009-01-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.py: Fix exit status (to 1) when no arguments given.
+       * FilesCheck.py, Filter.py, I18NCheck.py, PostCheck.py, rpmlint.py:
+         pychecker fixes.
+       * AbstractCheck.py, BinariesCheck.py, Config.py, ConfigCheck.py,
+         DistributionCheck.py, DocFilesCheck.py, FHSCheck.py,
+         FilesCheck.py, Filter.py, I18NCheck.py, InitScriptCheck.py,
+         LSBCheck.py, MenuCheck.py, MenuXDGCheck.py, NamingPolicyCheck.py,
+         PamCheck.py, Pkg.py, PostCheck.py, RpmFileCheck.py,
+         SignatureCheck.py, SourceCheck.py, SpecCheck.py, TagsCheck.py,
+         ZipCheck.py, rpmlint.py, test/test.PamCheck.py,
+         test/test.SpecCheck.py, tools/Testing.py, tools/compile.py: Add
+         utf-8 magic comments.
+       * InitScriptCheck.py: Use os.path.basename instead of a regexp.
+       * FilesCheck.py, MenuCheck.py, TagsCheck.py: Drop unused regexps.
+       * FilesCheck.py, SpecCheck.py: *_regex naming consistency fixes.
+       * FilesCheck.py: Recognize bzr internal files.
+       * TagsCheck.py: Add Enhances, Recommends, Suggests, Supplements to
+         tag_regex.
+       * TagsCheck.py: Use os.path.basename instead of
+         filename.split("/").
+       * TagsCheck.py: Sync license list with OSI.
+       * RpmFileCheck.py, ZipCheck.py: Fix spelling of my surname.
+
+2009-01-21  Ville Skyttä <ville.skytta at iki.fi>
+
+       * ZipCheck.py: Use pkg.files() instead of pkg.getFilesInfo(),
+         improve error message.
+       * Pkg.py, SpecCheck.py, TagsCheck.py, Util.py: Move default rpm
+         groups loading to Pkg, call it only once.
+       * rpmlint.1, rpmlint.py: Do not load/initialize checks more than
+         once.
+       * Pkg.py: Simplify file_regex.
+
+2009-01-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * README.devel: s/nbsp/ /
+       * README.devel, rpmlint.1, rpmlint.py: Remove the -p option, use
+         something like "python -O -u -m cProfile -s cumulative rpmlint.py
+         ..." for profiling instead.
+       * BinariesCheck.py: Speed up reading trailing bytes from binaries.
+       * rpmlint.py: Fix -a with rpm 4.6
+         (https://bugzilla.redhat.com/480664, Panu Matilainen).
+
+2009-01-14  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.bash-completion: Complete on installed packages, dirs and
+         *.rpm, *.spec only.
+       * rpmlint.bash-completion: Add *Emacs, vim and friends mode and
+         indentation settings.
+       * SpecCheck.py: Avoid duplicate non-standard-group warnings when
+         checking source packages (#167).
+       * SpecCheck.py: Match Group: case insensitively.
+       * Filter.py, Makefile, Testing.py, compile.py, test.sh, tools,
+         tools/Testing.py, tools/compile.py: Move compile.py and
+         Testing.py to tools/, do not require installation of Testing.py.
+       * SpecCheck.py, test/SpecCheck.spec, test/test.SpecCheck.py:
+         Improve applied patch detection (#59, dmueller at suse.de), add
+         test case.
+       * Testing.py: Add getTestedSpecPackage().
+       * test.sh: Fix exit code on success.
+
+2009-01-12  Michael Scherer <misc at mandriva.org>
+
+       * SpecCheck.py, TagsCheck.py, Util.py: add patch from akurtakov, to
+         check the Group tag in spec files, close ticket #167
+
+2009-01-03  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Mention symlinks(8) in symlink-should-be-relative
+         info message.
+
+2008-11-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Add name-repeated-in-summary check.
+
+2008-11-09  Ville Skyttä <ville.skytta at iki.fi>
+
+       * AbstractCheck.py, DistributionCheck.py, FilesCheck.py,
+         InitScriptCheck.py, MenuCheck.py, MenuXDGCheck.py,
+         NamingPolicyCheck.py, PamCheck.py, SpecCheck.py, TagsCheck.py,
+         rpmlint.py: Trim trailing whitespace.
+
+2008-10-30  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py, FilesCheck.py, Filter.py, MenuCheck.py: pylint
+         whitespace cleanups.
+       * BinariesCheck.py: Include filename in binaryinfo-readelf-failed
+         and binaryinfo-tail-failed messages.
+
+2008-10-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py, Config.py, ConfigCheck.py,
+         DistributionCheck.py, DocFilesCheck.py, FHSCheck.py,
+         FilesCheck.py, Filter.py, I18NCheck.py, InitScriptCheck.py,
+         LSBCheck.py, MenuCheck.py, MenuXDGCheck.py, NamingPolicyCheck.py,
+         PamCheck.py, Pkg.py, PostCheck.py, SignatureCheck.py,
+         SourceCheck.py, SpecCheck.py, TagsCheck.py, ZipCheck.py, rpmdiff,
+         rpmlint.py: pylint whitespace cleanups.
+       * rpmlint.py: Improve non-package-related error message formatting.
+       * SpecCheck.py: Warn about %setup outside of %prep (#164, akurtakov
+         at gmail.com).
+       * ZipCheck.py: Fix traceback when zip file does not exist (for
+         example L10n jars in firefox with %_install_langs).
+       * BinariesCheck.py, FilesCheck.py, InitScriptCheck.py,
+         MenuCheck.py, Pkg.py, SpecCheck.py, TagsCheck.py: Use string
+         methods instead of deprecated string functions.
+       * AbstractCheck.py: pylint cleanups.
+       * Filter.py, Pkg.py: Drop no longer needed Python < 2.3
+         workarounds.
+       * INSTALL, Pkg.py, PostCheck.py, README, Testing.py, rpmdiff,
+         rpmlint.1, rpmlint.py: Use tempfile.gettempdir() instead of
+         hardcoding /tmp, Python >= 2.3 is now required.
+       * README, rpmlint.1, rpmlint.py: Load all /etc/rpmlint/*config as
+         config files.
+
+2008-10-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 0.85.
+       * README, TagsCheck.py: Allow EVR in changelog without release
+         extension, the extension is often a macro or otherwise dynamic.
+         Also do not automatically add '$' to ReleaseExtension when
+         compiling it as a regex - existing configurations should be
+         reviewed and adjusted if necessary.
+       * INSTALL, Makefile, rpmlint.1, rpmlint.bash-completion,
+         rpmlint.py: Drop defunct policy loading code and options (#163).
+       * FilesCheck.py: Improve file-not-utf8 info message.
+
+2008-10-17  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Remove broken redundant-prefix-tag check (#160).
+       * README: Fix indentation.
+
+2008-10-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Make obsolete-not-provided info message more
+         accurate.
+
+2008-10-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Make invalid-version description more generic
+         (#161).
+
+2008-09-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Check for executables built with ocaml -custom,
+         https://bugzilla.redhat.com/461434
+
+2008-09-07  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Fix lib package detection (most likely broken since
+         section parsing was implemented).
+       * SpecCheck.py: Do tag related checks only within package sections.
+       * SpecCheck.py: Fix line number off by one in
+         configure-without-libdir-spec, https://bugzilla.redhat.com/461421
+
+2008-09-02  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Rename useless-explicit-provides to
+         useless-provides; "explicit" just adds potential confusion as all
+         Provides are treated the same way.
+       * TagsCheck.py: Variable naming improvements, TODO for
+         https://bugzilla.redhat.com/460872
+
+2008-08-21  Michael Scherer <misc at mandriva.org>
+
+       * SpecCheck.py: - if we have only one spec file wrongly named,
+         rpmlint will trigger "no-spec-file", instead of the proper error,
+         and will not analyse the specfile
+
+2008-08-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Allow interpreters to be in arbitrary deep subdirs
+         of /usr/libexec, /usr/lib(64)/*, /usr/share/*.
+       * TagsCheck.py: Recognize more library packages for the
+         explicit-lib-dependency check.
+
+2008-08-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Don't recompute nbsp for each line in spec.
+
+2008-07-30  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Try to detect and warn about shared libraries
+         that call exit() or _exit(), https://bugzilla.redhat.com/450011
+       * SpecCheck.py: Kludge for dependency token parsing within
+         multiline macro definitions, https://bugzilla.redhat.com/456843
+       * SpecCheck.py: Fix false non-break-space positives for some UTF-8
+         specfiles, https://bugzilla.redhat.com/455371
+       * SpecCheck.py, rpmlint.py: Fix enabling of UTF-8 checks for
+         specfile-only checks in UTF-8 mode.
+       * BinariesCheck.py: Reduce duplicate regex searches.
+
+2008-07-30  Michael Scherer <misc at mandriva.org>
+
+       * Pkg.py: - lzma compressed file support
+
+2008-07-26  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 0.84.
+       * FilesCheck.py: Accept interpreters also in /usr/games,
+         /usr/libexec, /usr/lib/*, /usr/lib64/*,
+         https://bugzilla.redhat.com/355861
+
+2008-07-14  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: rpm.org 4.5+ compatibility fix (Panu Matilainen)
+
+2008-07-09  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.1, rpmlint.py: Drop warnings-only exit status 65 (#157).
+       * BinariesCheck.py: Fix syntax error introduced in previous commit
+         (#159), apply #158 also for pentium* and athlon.
+
+2008-07-01  Michael Scherer <misc at mandriva.org>
+
+       * BinariesCheck.py: do not complain for PT_GNU_STACK on non x86
+         section, patch from dmueller, close bug #158
+
+2008-06-29  Michael Scherer <misc at mandriva.org>
+
+       * SpecCheck.py: tabnnany fix
+
+2008-06-21  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Change *.so regex for symlinks to match files also
+         in lib(64) subdirs, and lib*.so only to improve no-dependency-on
+         and dangling-*symlink checks.
+
+2008-06-15  Michael Scherer <misc at mandriva.org>
+
+       * SpecCheck.py: as PreReq is deprecated on almost every rpm version
+         out there ( ie > 4.2 ), we should count this as error to force
+         people to fix their packages ( mandriva patch from thierry
+         vignaud )
+       * Pkg.py: - add patch for rpm5 found in mandriva package, from
+         peroyvind
+
+2008-05-27  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Release 0.83.
+       * FilesCheck.py: Downgrade non-standard-uid/gid errors to warnings,
+         https://bugzilla.redhat.com/430206
+
+2008-05-22  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Make GPL and LGPL versioned in default valid
+         license list (#144, peroyvind at mandriva.org).
+
+2008-05-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Allow foo-devel require foo/foo-libs/libfoo (#154,
+         mostly from dan at danny.cz).
+
+2008-04-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Config.py, Filter.py, README, rpmlint.1, rpmlint.py: Add badness
+         scoring support (#70, vast majority of work by dmueller at
+         suse.de).
+       * rpmlint.py: Always print stats.
+       * rpmlint.py: Cosmetic cleanups.
+       * rpmlint.py: Output basic statistics before exiting.
+       * rpmlint.1, rpmlint.py: Exit with non-zero status if
+         errors/warnings printed (#128).
+       * Filter.py: Count number of printed (== non-filtered) messages per
+         category.
+       * rpmlint.py: Output usage errors to stderr, improve messages.
+       * PostCheck.py: Avoid percent-in-%foo warnings for commented out
+         lines (#149).
+       * FilesCheck.py: Don't warn about hidden files in /etc/skel (#151,
+         dmueller at suse.de).
+       * BinariesCheck.py: Fix section header regexps (#152, dmueller at
+         suse.de).
+       * SpecCheck.py: Tighten ifarch and endif regexps.
+       * SpecCheck.py: Check for attributes in %files (#153, most of the
+         work by dan at danny.cz).
+
+2008-04-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: Comment typo fix.
+
+2008-03-24  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Improve non-break-space info message.
+       * FHSCheck.py, FilesCheck.py, Filter.py, TagsCheck.py: Use
+         textwrap.fill() to pretty print all info messages, not just a few
+         special cases.
+       * rpmlint.py: Minor -I output tweak.
+       * SpecCheck.py: Check for %{_libdir} and %{_lib} in noarch
+         packages' %files, https://bugzilla.redhat.com/237204
+
+2008-03-21  Michael Scherer <misc at mandriva.org>
+
+       * SpecCheck.py: add a check for non-break space, asked on mandriva
+         bugzilla 39094
+
+2008-03-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * ZipCheck.py: Do not issue errors for jars without manifests;
+         META-INF/* are optional in them:
+         http://java.sun.com/j2se/1.4/docs/guide/jar/jar.html
+
+2008-03-01  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py, README: Add SkipDocsRegexp config parameter for
+         skipping end of line and UTF-8 tests for doc files, include RTF,
+         HTML, and OCaml files in it by default,
+         https://bugzilla.redhat.com/434694
+       * FilesCheck.py: Issue end-of-line warnings for Mac style end of
+         line delimiters.
+
+2008-02-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * ZipCheck.py: s/MANIFEST/MANIFEST.MF/ in relevant info messages.
+
+2008-02-26  Michael Scherer <misc at mandriva.org>
+
+       * SpecCheck.py: - check the main specfile ,not the first one, see
+         Mandriva bug 38157
+
+2008-02-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Downgrade configure-without-libdir-spec into a
+         warning and improve info message; not all configure scripts
+         support or need %{_libdir}, https://bugzilla.redhat.com/433783
+       * BinariesCheck.py: Excempt ocaml packages from
+         only-non-binary-in-usr-lib check,
+         https://bugzilla.redhat.com/433783
+
+2008-02-21  Ville Skyttä <ville.skytta at iki.fi>
+
+       * NamingPolicyCheck.py: Spelling fix.
+       * NamingPolicyCheck.py: Improve info message.
+       * NamingPolicyCheck.py: Allow "base" packages to satisfy checks in
+         addition to subpackages.
+       * NamingPolicyCheck.py: Check lib64 dirs in addition to lib.
+
+2008-02-09  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Do changelog version checks for source rpms too.
+
+2008-02-01  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Improve executable-stack explanation.
+
+2008-01-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Check for presence of PT_GNU_STACK and
+         executable stack (https://bugzilla.redhat.com/428096, messages
+         from lintian).
+       * BinariesCheck.py: Code cleanups.
+
+2008-01-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Apply license check improvements from Fedora per
+         Mandriva's request, more info:
+         http://fedoraproject.org/wiki/Packaging/LicensingGuidelines
+
+2007-12-23  Michael Scherer <misc at mandriva.org>
+
+       * FilesCheck.py: add lzma to the list of compression extension
+       * DistributionCheck.py: - check manpages compressed with lzma,
+         asked by lzma lover dvalin :) ( and blino@mandriva )
+
+2007-12-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Improve kernel module depmod regex.
+       * Makefile: 0.82
+       * Pkg.py: Don't lose version flags of prereq dependencies (part of
+         #123, dmueller at suse.de)
+
+2007-11-30  Ville Skyttä <ville.skytta at iki.fi>
+
+       * PostCheck.py: Avoid false positives for percent-in-* checks,
+         macro names are at least 3 chars long (#132, anonymous)
+
+2007-11-27  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py, TagsCheck.py: Fix UTF-8 line length calculations,
+         https://bugzilla.redhat.com/399871
+
+2007-11-17  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.1: ...
+       * rpmlint.1: Note that rpmlint does not have a full featured spec
+         file parser.
+
+2007-11-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * I18NCheck.py: Fix webapp detection (#122, dmueller at suse.de)
+
+2007-10-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Don't think that a service is enabled by
+         default if it contains LSB Default-Start: with empty value.
+
+2007-09-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Include both actual and expected filenames in
+         non-coherent-filename message.
+
+2007-09-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Skip end-of-line char and UTF-8 checks for RTF and
+         HTML files.
+
+2007-09-03  Ville Skyttä <ville.skytta at iki.fi>
+
+       * I18NCheck.py: subfile-not-in-%lang fix (pixel at mandriva.com)
+       * Makefile: 0.81
+
+2007-08-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py, INSTALL: Use readelf instead of objdump for
+         binary checks (#83, dmueller at suse.de).
+       * TagsCheck.py: Unbreak info messages for checks added in previous
+         revision.
+       * BinariesCheck.py: Use generator objects when iterating over files
+         (#83, dmueller at suse.de)
+       * TagsCheck.py: Check for percent chars in dependency (#75,
+         dmueller at suse.de), obsoletes, provides and conflicts versions.
+
+2007-08-24  Michael Scherer <misc at mandriva.org>
+
+       * I18NCheck.py: - patch from pixel, for directory marked as %lang
+         when the files or subdirectory are not
+       * test/test.PamCheck.py: fix test
+
+2007-08-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.py: Check spec files too when processing directories.
+
+2007-08-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py, PostCheck.py: Do not warn about ghost file non-creation
+         for missingok files (#79, dmueller at suse.de)
+       * BinariesCheck.py: Drop unused dynsyms stuff (#83, dmueller at
+         suse.de)
+       * Pkg.py: grep() speedup (#80, dmueller at suse.de)
+
+2007-08-11  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Symlinking a *.so to another *.so does not make it
+         a devel symlink (#78, dmueller at suse.de)
+       * DistributionCheck.py: Improve man page dir regex (#76)
+
+2007-07-31  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Comment typo fix.
+       * rpmlint.py: 2007
+
+2007-07-24  Michael Scherer <misc at mandriva.org>
+
+       * PostCheck.py: - lua is a valid shell, fix #74, patch from
+         dmueller
+
+2007-07-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * I18NCheck.py: Add "bn_IN" (#72, sankarshan.mukhopadhyay at
+         gmail.com), "my", and "si" to list of valid locale subdirs.
+
+2007-06-24  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: LSB comment blocks have keywords, not tags.
+
+2007-06-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: Tolerate nonexistent current dir,
+         https://bugzilla.redhat.com/244835
+
+2007-06-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Avoid outputting useless line numbers (alcapcom at
+         gmail.com).
+
+2007-06-02  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Ignore filename case when looking for include
+         files.
+       * FilesCheck.py: Improve OCaml devel file handling,
+         https://bugzilla.redhat.com/241471
+
+2007-05-31  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Add filename and info for missing mandatory
+         LSB tag check (#67, dmueller at suse.de)
+       * SpecCheck.py: Don't choke on trailing commas in deptokens() (#65,
+         dmueller at suse.de)
+
+2007-05-26  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Treat *.cxx, *.cpp, *.hxx and *.hpp as include
+         files.
+
+2007-05-14  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Filter.py, Pkg.py, SpecCheck.py, rpmlint.1, rpmlint.py: Add
+         direct specfile checking, error line numbers (#60, alcapcom at
+         gmail.com). Also modify error output format for editor
+         parseability (mostly useful for specfile checks), and include
+         package architectures in output.
+         
+         NOTE: locally installed filters should be reviewed against the
+         new output format and ported to it if needed.
+       * BinariesCheck.py, FilesCheck.py, Pkg.py, TagsCheck.py: Carry
+         package arch around in pkg object.
+
+2007-05-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Even more fallout from previous change.
+       * Pkg.py: More fallout from previous change.
+       * InitScriptCheck.py: Fix fallout from previous change.
+       * FilesCheck.py, InitScriptCheck.py, Pkg.py, PostCheck.py,
+         SpecCheck.py, TagsCheck.py: Better I/O error and temporary file
+         handling.
+       * FilesCheck.py: Check doc text files for UTF-8.
+
+2007-05-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.py: Remove unused/deprecated loadFile().
+       * SpecCheck.py: Better file I/O error handling.
+       * SpecCheck.py: Improve patch related regexps.
+       * PostCheck.py: Check %pretrans and %posttrans if supported by
+         installed rpm.
+       * PostCheck.py: Improve percent-in-* check,
+         https://bugzilla.redhat.com/239611
+
+2007-05-07  Ville Skyttä <ville.skytta at iki.fi>
+
+       * MenuXDGCheck.py: Fix path to failing .desktop file in output
+         (#61, dmueller at suse.de)
+       * InitScriptCheck.py: Accept LSB keywords starting with X- (#62,
+         dmueller at suse.de)
+
+2007-04-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: 0.80
+       * BinariesCheck.py: Improve valid *.so regexp.
+
+2007-04-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Don't require soname to start with 'lib' as
+         long as *.so that do not start with 'lib' are checked.
+
+2007-04-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Fail gracefully on unreadable files, eg.
+         dangling symlinks.
+
+2007-03-26  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Check %description contents for things that look
+         like tags.
+
+2007-03-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Check LSB Default-Start too when checking if
+         service is enabled by default, bugzilla.redhat.com/233795
+
+2007-03-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * AbstractCheck.py: Autoregister checks only once (by name) so
+         checks (and configs) can import each other without resulting in
+         multiple instances of a particular check being run.
+       * FilesCheck.py: Check for UTF-8 encoded filenames.
+       * INSTALL, Pkg.py, README: Implement is_utf8_str without iconv;
+         Python >= 2.2 is now required.
+
+2007-03-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Change obsolete-not-provided into a warning and
+         improve description.
+
+2007-03-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Allow hardcoded /lib in /lib/firmware.
+       * FilesCheck.py: Fix devel to base package dependency check broken
+         in [1317]
+
+2007-03-07  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Allow tabs and be as relaxed as chkconfig
+         itself with whitespace in chkconfig regex.
+
+2007-03-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Fix variable scoping/indentation issue introduced
+         in [1319].
+
+2007-03-02  Michael Scherer <misc at mandriva.org>
+
+       * FilesCheck.py: oops, fix typo spotted by guillomovitch
+       * FilesCheck.py: - check cron file, see
+         http://qa.mandriva.com/show_bug.cgi?id=23951
+
+2007-02-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Treat *-static as devel packages.
+       * TagsCheck.py: Use devel package regexp from FilesCheck.
+
+2007-02-17  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Fix LSB multiline description parsing (#52).
+       * InitScriptCheck.py: unknow-lsb-tag -> unknown-lsb-tag
+       * DocFilesCheck.py: Fix for rpm-python < 4.3.2 (#53).
+
+2007-02-16  Ville Skyttä <ville.skytta at iki.fi>
+
+       * MenuCheck.py, TagsCheck.py: Improve capitalization checks,
+         https://bugzilla.redhat.com/228645
+
+2007-02-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Don't warn about missing -q to %setup if there's a
+         -T without -a or -b, https://bugzilla.redhat.com/227389
+
+2007-02-02  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: 0.79.
+       * Makefile: Fix test target.
+
+2007-02-01  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Treat Ocaml *.cmx and *.cmxa as arch dependent
+         binaries, https://bugzilla.redhat.com/226879
+
+2007-01-28  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Check for unused direct shared lib deps,
+         inspired by
+         http://www.redhat.com/archives/fedora-maintainers/2006-June/msg00176.html
+
+2007-01-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Avoid spewing both spurious-executable and
+         script-without-shebang warnings for one file.
+
+2007-01-24  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Check for usual spurious executable bits,
+         https://bugzilla.redhat.com/222585
+
+2007-01-02  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Don't do end-of-line checks on RTF files,
+         https://bugzilla.redhat.com/220061
+       * MenuXDGCheck.py: Use Pkg.getstatusoutput instead of
+         subprocess.call for Python < 2.4 compat,
+         http://bugzilla.redhat.com/221116
+
+2006-12-18  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Treat PDF files always as binary,
+         https://bugzilla.redhat.com/220061
+       * FilesCheck.py: Use floating point division in istextfile().
+
+2006-12-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Check for > 1 %changelog sections,
+         http://bugzilla.redhat.com/219068
+
+2006-12-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Spelling fixes (Bernard Johnson,
+         https://bugzilla.redhat.com/218250)
+
+2006-12-03  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Improve PreReq and broken context marked dep syntax
+         regexps and descriptions.
+
+2006-11-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * README: Don't hardcode InvalidRequires default, fix up
+         indentation.
+       * README: InvalidRequires is a list of regexps, not strings.
+
+2006-11-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Add Creative Commons licenses,
+         https://bugzilla.redhat.com/211417
+
+2006-11-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * PostCheck.py: Fine tune forbidden-selinux-command info message.
+       * PostCheck.py: Check for forbidden SELinux related commands (Steve
+         Grubb, https://bugzilla.redhat.com/214605)
+       * PostCheck.py: Drop some duplicate code.
+
+2006-10-28  Ville Skyttä <ville.skytta at iki.fi>
+
+       * I18NCheck.py: Allow 3-letter language codes in locale subdirs
+         (Dave Lehman, https://bugzilla.redhat.com/212491).
+
+2006-10-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Skip non-PIC check if objdump failed.
+
+2006-10-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Don't run ldd on ar archives,
+         https://bugzilla.redhat.com/210110
+
+2006-10-11  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: Try to avoid infinite recursion in shell variable
+         expansion https://bugzilla.redhat.com/210261
+       * Pkg.py: Add start boundary to shell variable assignment regexp
+         https://bugzilla.redhat.com/210261
+       * InitScriptCheck.py: Fix regression introduced in [1242]: shell
+         variable expansion needs the whole file to be passed to it, not
+         just the current line being examined
+         https://bugzilla.redhat.com/210261
+
+2006-10-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Don't crash in istextfile() if reading fails, eg.
+         https://bugzilla.redhat.com/209876
+       * BinariesCheck.py, I18NCheck.py, TagsCheck.py: Don't assume all
+         packages have a source rpm or i18n table, eg. gpg-pubkeys don't;
+         https://bugzilla.redhat.com/209889
+       * rpmlint.py: Only try to check rpm files when given a directory.
+       * rpmlint.py: Don't run main() when importing rpmlint.py, makes eg.
+         pychecker happier (#48).
+
+2006-09-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * MenuXDGCheck.py, PamCheck.py, Testing.py: Add editor indentation
+         settings.
+
+2006-09-23  Michael Scherer <misc at mandriva.org>
+
+       * Makefile: tag 0.78 in Makefile
+       * SpecCheck.py: - some refactoring
+
+2006-09-22  Michael Scherer <misc at mandriva.org>
+
+       * test/test.PamCheck.py: - fix test
+
+2006-09-14  Ville Skyttä <ville.skytta at iki.fi>
+
+       * INSTALL: Python >= 2.0 required for popen4.
+       * BinariesCheck.py, MenuCheck.py, Pkg.py, PostCheck.py: Use
+         sequence based command invocation more; avoids shell
+         escaping/whitespace issues eg. like
+         https://bugzilla.redhat.com/206383
+       * I18NCheck.py: sw is valid for Swahili, don't treat is as
+         misspelled Swedish.
+
+2006-09-09  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Take escaping into account when finding buildroot
+         strings, fixes #47.
+       * SpecCheck.py: Say which section lacks cleanup in
+         no-cleaning-of-buildroot.
+
+2006-09-03  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Recognize GIT and Mercurial "SCM internal" files.
+       * FilesCheck.py, InitScriptCheck.py, PamCheck.py: More literal dot
+         in regexp escaping.
+       * DistributionCheck.py: Drop unused regexps.
+       * SpecCheck.py: Check for unversioned Provides/Obsoletes in
+         specfiles.
+
+2006-08-30  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Distinguish between sourced and executed scripts
+         (Guillaume Rousse, #17).
+       * FilesCheck.py: Add comment about *.pm shebang (initially from
+         #17)
+       * FilesCheck.py: s/shellbang/shebang/ (part of #17)
+
+2006-08-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Escape literal dots in regexps.
+       * FilesCheck.py: Don't demand executable scripts in /etc/profile.d;
+         cf. https://bugzilla.redhat.com/35714
+
+2006-08-26  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Allow init scripts with package name + 'd'.
+       * PamCheck.py: Use Pkg.grep, output line numbers; fixes #38.
+       * BinariesCheck.py: Improve invalid dir reference regexp, compile
+         it only once.
+       * BinariesCheck.py, MenuCheck.py, Pkg.py: Make Pkg.grep more
+         robust, change to instance method, return matching line numbers.
+
+2006-08-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Tone down invalid-license message a bit.
+       * TagsCheck.py: Add no-url-tag description (duh).
+
+2006-08-24  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Improve broken scriptlet dep regex.
+       * SpecCheck.py: Check broken scriptlet dep syntax only in the
+         "package" section (Pascal Terjan, #42).
+
+2006-07-31  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Check for world writable files and dirs.
+
+2006-07-25  Ville Skyttä <ville.skytta at iki.fi>
+
+       * PostCheck.py, SpecCheck.py: Add some TODO comments.
+
+2006-07-14  Michael Scherer <misc at mandriva.org>
+
+       * PostCheck.py: - add Xdg menu directory to %updates-menus
+         detection
+       * MenuXDGCheck.py: - really fix the call to desktop-file-validate
+       * MenuXDGCheck.py: - fix the check
+
+2006-07-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Catch macroized %{__make} etc in make check regexp,
+         and ignore all of it in %description too.
+       * TagsCheck.py: Use zlib/libpng, not zlib for that license to
+         follow the OSI list (bugzilla.redhat.com/198616).
+
+2006-07-11  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.py: Improve error messages, fix checking dirs containing
+         packages.
+
+2006-07-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Config.py, README: Add removeFilter() for removing filters from
+         the config, useful eg. in per-user config files for restoring
+         filters added in system config files.
+
+2006-07-09  Michael Scherer <misc at mandriva.org>
+
+       * SpecCheck.py: do not complaint if %changelog contains a line like
+         : - move the make test to %%check bug reported by vincent danen
+
+2006-07-07  Michael Scherer <misc at mandriva.org>
+
+       * MenuXDGCheck.py: add non-utf8-desktopfile check, for [30]
+       * ., InitScriptCheck.py: r1045@crovax: misc | 2006-07-07 00:37:01
+         +0200 Lsb tags checking
+       * ., InitScriptCheck.py: r1044@crovax: misc | 2006-07-06 23:48:30
+         +0200 - check the script line by line, needed to check Lsb init
+         tags
+
+2006-07-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Allow EVR also in the first line of the changelog
+         text, based on a patch from Thomas Vander Stichele (#23).
+
+2006-07-06  Michael Scherer <misc at mandriva.org>
+
+       * ., I18NCheck.py, MenuXDGCheck.py, SourceCheck.py, SpecCheck.py:
+         merge
+       * ., TagsCheck.py: r1034@crovax: misc | 2006-07-06 16:41:03 +0200 -
+         replace regexp by simple search
+       * ., DistributionCheck.py: r1033@crovax: misc | 2006-07-06 16:37:03
+         +0200 - remove regexp, use simple search
+       * ., MenuCheck.py: r1032@crovax: misc | 2006-07-06 16:33:22 +0200 -
+         remove uneeded import
+       * ., FilesCheck.py: r1031@crovax: misc | 2006-07-06 16:32:38 +0200
+         - do not use regexp when we can use a single search
+       * .:
+       * ., FHSCheck.py: r1019@mallarme: misc | 2006-07-06 01:23:02 +0200
+         - do not hardcode standard subdir in the detailed message, better
+         to take them from the check itself
+       * ., FHSCheck.py: r1018@mallarme: misc | 2006-07-06 01:14:07 +0200
+         - do not use regexp when we can avoid them, in order to use less
+         memory and be faster.
+       * ., Config.py, MenuXDGCheck.py: r1017@mallarme: misc | 2006-07-06
+         01:02:24 +0200 - start MenuXDGCheck, for now, it just run
+         desktop-file-validate on every file, maybe i should restrict more
+       * ., Pkg.py: r1016@mallarme: misc | 2006-07-06 00:06:39 +0200 -
+         pychecker fix, do not mask builtins
+       * ., FilesCheck.py, MenuCheck.py: r1015@mallarme: misc | 2006-07-06
+         00:00:31 +0200 - pychecker fix
+       * ., test.sh: r1014@mallarme: misc | 2006-07-05 23:57:46 +0200 -
+         improve test.sh, exit when a test fail, and try to launch rpmlint
+         ( basic syntax checking )
+       * ., FilesCheck.py: r1013@mallarme: misc | 2006-07-05 23:20:12
+         +0200 - pychecker fix, erase idx as this is also used in
+         Filter.py
+       * ., InitScriptCheck.py: r1012@mallarme: misc | 2006-07-05 23:17:56
+         +0200 - better variable name, fix pychecker warning
+
+2006-07-05  Michael Scherer <misc at mandriva.org>
+
+       * NamingPolicyCheck.py: - revert [910], fix bug #18
+       * PamCheck.py: - fix bug #33, by removing comment from pam files
+       * Filter.py, Makefile, Testing.py, test, test.sh,
+         test/PamCheck-0.1-1.i586.rpm, test/test.PamCheck.py: - start of
+         testing infrastructure
+
+2006-06-30  Ville Skyttä <ville.skytta at iki.fi>
+
+       * PostCheck.py: Improve home regex.
+       * PostCheck.py: Improve dangerous command regexp.
+
+2006-06-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Add line numbers to mixed-use-of-spaces-and-tabs
+         warning (#28).
+       * TagsCheck.py: Don't fail if the GROUPS file is not found (eg. rpm
+         installed with --excludedocs).
+       * TagsCheck.py: Improve incoherent-version-in-changelog
+         description.
+       * SpecCheck.py: Check that %prep, %build and %install exist, even
+         if empty: https://bugzilla.redhat.com/192422
+
+2006-06-28  Michael Scherer <misc at mandriva.org>
+
+       * Makefile: preparing for release
+
+2006-06-28  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.py: Fix error message output when loading a system config
+         file fails.
+
+2006-06-28  Michael Scherer <misc at mandriva.org>
+
+       * rpmlint.py: - give error message about config file loading
+         failure, and give the correct file that failed.
+       * PostCheck.py, TagsCheck.py, rpmlint.py: - pychecker fix
+
+2006-06-28  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Use default standard users/groups from LSB.
+         Distributors may want to customize StandardUsers/StandardGroups.
+       * Config.py, setuplist.py: Remove dead code.
+       * FilesCheck.py: Don't hardcode lists of standard users/groups in
+         error messages, use configured ones instead.
+       * README: Typo fixes.
+
+2006-06-26  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Remove single-percent-in-changelog; it's already
+         covered by macro-in-%changelog (#27).
+
+2006-06-22  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py, README: Always prefer relative symlinks by default
+         (#25), the new configuration option UseRelativeSymlinks can be
+         set to 0 to restore the old behaviour. Also add a tiny
+         description for symlink-should-be-relative.
+       * FilesCheck.py: Sort list of standard dirs, add /, /media,
+         /selinux, /srv, /sys, and /usr/local/share*.
+       * SpecCheck.py: Add check for mixed use of spaces and tabs in
+         specfiles (#19).
+
+2006-06-22  Michael Scherer <misc at mandriva.org>
+
+       * AbstractCheck.py, BinariesCheck.py, Config.py, DocFilesCheck.py,
+         FHSCheck.py, I18NCheck.py, MenuCheck.py, NamingPolicyCheck.py,
+         PostCheck.py, ZipCheck.py: - remove pychecker errors: - unused
+         variable - variable that shadow builtins - unused expression (
+         usually used to trigger a exception ) - do not raise string as
+         exception
+       * PamCheck.py, RpmFileCheck.py, SpecCheck.py: - fix pychecker
+         warning, notably a missing regexp in commit [1201]
+
+2006-06-21  Michael Scherer <misc at mandriva.org>
+
+       * MenuCheck.py: add non-xdg-migrated-menu, patch from frederic
+         crozat, closes #21
+       * SpecCheck.py: add a check for macro in %changelog
+
+2006-06-20  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py: Adapt rpm >= 4.2 and source package detection to rpm
+         4.4.x (x=6?) versions which no longer have RPMTAG_SOURCEPACKAGE.
+
+2006-06-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * DocFilesCheck.py: Revert previous change; the order of filenames
+         and their deps needs to be in sync.
+       * SpecCheck.py: Warn if __find_(provides|requires) is defined
+         without disabling the internal dependency generator.
+       * DistributionCheck.py, DocFilesCheck.py, FilesCheck.py,
+         I18NCheck.py, InitScriptCheck.py, LSBCheck.py, MenuCheck.py,
+         NamingPolicyCheck.py, Pkg.py, SpecCheck.py, TagsCheck.py:
+         Cleanups.
+       * BinariesCheck.py: Include filename in ldd failure warning.
+
+2006-06-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Make name of file system base package a bit less
+         hardcoded, allow multiple.
+
+2006-06-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Improve subsys regexp, fixes
+         bugzilla.redhat.com/194466.
+
+2006-06-06  Ville Skyttä <ville.skytta at iki.fi>
+
+       * PostCheck.py: Fix fallout from [1183].
+
+2006-06-05  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Pkg.py, rpmlint.py: Check all matching installed packages, not
+         only the first (for multiarch systems and/or multi-version
+         packages).
+       * rpmlint.py: Fix setting ExtractDir in config files.
+       * rpmlint.py: 2006.
+       * rpmlint.py: Use execfile() for loading conf files, fixes #20.
+       * SpecCheck.py: Really bypass checks on section markers.
+       * SpecCheck.py: Catch redundant prefix specified as %_prefix
+         (without curlies).
+       * SpecCheck.py: Add check for macros in %changelog.
+       * SpecCheck.py: Don't bother running checks against section marker
+         lines.
+       * SpecCheck.py: Tighten section marker regexp.
+       * PostCheck.py: Make sure that the correct interpreter is used for
+         scriptlet syntax checks.
+       * FilesCheck.py: Base doc file decisions on whether a file is
+         marked as doc, not a regexp heuristic.
+
+2006-06-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Do not hardcode lib*-devel no-documentation
+         warning suppression. It would work only for specific package
+         naming schemes, and even in the cases where it works, it can be
+         argued that it's of dubious usefulness. Configurable filters
+         according to distro policies are a better way to handle cases
+         like this.
+
+2006-05-19  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Check that debuginfo packages contain files.
+
+2006-05-16  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmdiff: Print what's wrong if getopt fails.
+       * rpmdiff: Add -h/--help.
+       * rpmdiff: Add option to ignore file timestamps, thanks to Matt
+         Domsch.
+
+2006-05-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Tolerate macroized "rm" in buildroot cleaning
+         check.
+       * SpecCheck.py: Wrap info message lines at < 80 chars.
+       * SpecCheck.py: Fix build root regexp.
+
+2006-05-13  Michael Scherer <misc at mandriva.org>
+
+       * SpecCheck.py: add make-check-outside-check-section, close #14.
+       * SpecCheck.py: add rpm-buildroot-usage, close ticket #15.
+       * SpecCheck.py: - add no-cleaning-of-buildroot check, to be sure
+         that $BUILDROOT is cleaned, as this will leave old files, and
+         break in funny way when files are removed from package, etc.
+
+2006-05-13  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Check for undefined non-weak symbols (shared
+         libs in installed pkgs only).
+
+2006-05-12  Michael Scherer <misc at mandriva.org>
+
+       * SpecCheck.py: add setup-not-quiet, to check if %setup -q is used
+       * SpecCheck.py: - use a variable to hold the current esection, as
+         it will help to detect if some macros are misused, and as it
+         unify the handling of the section
+       * SpecCheck.py: avoid using useless temporary variables if not
+         needed
+       * SpecCheck.py: - style fix, i cannot really stand foo=bar as this
+         is less readable than foo = bar, but a real tool should be used
+         to do that.
+
+2006-05-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Drop unused la-file-with-invalid-dir-reference
+         info message.
+       * TagsCheck.py: Add Development/Debug into default groups, many
+         distros use it in -debug(info) packages but do not have it in
+         GROUPS. While at it, sort the list and close the GROUPS file
+         explicitly.
+       * TagsCheck.py: OPL is ambiguous, do not use it.
+       * TagsCheck.py: Bring list of licenses up to date with OSI, add a
+         couple of other common (mainly non-software) licenses.
+
+2006-05-04  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Make *.so regexp stricter.
+       * BinariesCheck.py: Update ld.so filename regexp for x86_64.
+
+2006-05-03  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py, FilesCheck.py: Don't tell people to bother
+         flepied about distro config issues.
+
+2006-04-28  Michael Scherer <misc at mandriva.org>
+
+       * AbstractCheck.py, PamCheck.py: - create a new class
+         AbstractFilesCheck to factorise the code that filter and extract
+         files based on a regexp. this class is now used for PamCheck.py,
+         and other similar check will be converted soon.
+       * RpmFileCheck.py: - fix error caused by unterminated string.
+
+2006-04-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Allow bundling depmod arguments in kernel module
+         post(un)install scriptlets.
+
+2006-04-14  Michael Scherer <misc at mandriva.org>
+
+       * RpmFileCheck.py: add svn keywords and copyright notice to
+         RpmFileCheck.py
+       * FilesCheck.py: remove setuid-gid-binary error, as we already have
+         setuid-binary and setgid-binary, and remove Fred email address
+         from the detailed explication, as this is no longer used, and not
+         generic enough.
+       * FilesCheck.py, I18NCheck.py: remove all reference to
+         /etc/httpd/webapps.d, fix bug #8
+
+2006-04-14  Ville Skyttä <ville.skytta at iki.fi>
+
+       * RpmFileCheck.py: Make info message wrap with < 80 char lines.
+
+2006-04-13  Michael Scherer <misc at mandriva.org>
+
+       * Config.py, README, RpmFileCheck.py: add RpmFileCheck, to test the
+         rpm file itself, for the moment, only check the length of the
+         filename, close ticket #9
+
+2006-04-10  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.1: Bump month.
+       * rpmlint.1: Mention /usr/share/rpmlint/config.
+       * rpmlint.1: Mention AUTHORS.
+       * FilesCheck.py: Treat *-debug(info) as devel packages.
+       * Makefile: Produce prettier and more concise ChangeLog.
+       * Makefile: Fix AUTHORS and ChangeLog up to date checks.
+       * TagsCheck.py: List valid groups in non-standard-group info.
+       * TagsCheck.py: Don't hardcode valid license list in
+         invalid-license info.
+       * TagsCheck.py: Avoid empty strings in list from
+         get_default_valid_rpmgroups().
+
+2006-04-10  Michael Scherer <misc at mandriva.org>
+
+       * Makefile: for some reason, svn messages do not have space at the
+         same place for french locales and C locales :
+         
+         [misc@takara rpmlint] $ svn info . | grep URL URL :
+         svn+ssh://rpmlint.zarb.org/home/projects/rpmlint/svn/trunk
+         [misc@takara rpmlint] $ LC_ALL=C svn info . | grep URL URL:
+         svn+ssh://rpmlint.zarb.org/home/projects/rpmlint/svn/trunk
+         
+         (note 'URL:' vs 'URL :')
+         
+         so, running make tag do not work on my computer, using sed
+         instead of cut did the trick.
+
+2006-04-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Clean up AUTHORS too.
+       * ., AUTHORS, Makefile, authors.xsl: Generate AUTHORS from
+         authors.xml.
+       * Makefile: More target dependency fixes and cleanups.
+       * Makefile: Fix localcopy/tar dependencies.
+       * Makefile: Don't require trailing slash for ETCDIR.
+
+2006-04-05  Michael Scherer <misc at mandriva.org>
+
+       * check-install.py: Not used by rpmlint, broken on latest rpm
+         python binding
+       * Config.py, PamCheck.py: add use-old-pam-stack check
+       * README: update default values for configuration option
+       * Config.py: automatically transform filter regexp using () into
+         regexp without named group, to prevent overflow, as it seems to
+         be limited to 100 for this version of python. It may also reduce
+         memory usage.
+       * FilesCheck.py, README: add a option StandardUsers and
+         StandardGroups, for uid checking
+       * MenuCheck.py: place DEFAULT_LAUNCHERS here, as it was removed by
+         last Config.py cleaning
+
+2006-04-04  Michael Scherer <misc at mandriva.org>
+
+       * Config.py: remove all exceptions, they should be placed in
+         another file ( it's up to each packager to provides it )
+       * Makefile: use do not override ETCDIR in Makefile, as it misplaces
+         bash completion, and make package relocation difficult.
+       * Makefile: shebang line of ./compile.py was broken on my computer
+         : [misc@n1 rpmlint-0.76] $ make ./compile.py
+         "/usr/share/rpmlint/" [A-Z]*.py Unknown option: - usage:
+         /usr/bin/python [option] ... [-c cmd | -m mod | file | -] [arg]
+         ... Try `python -h' for more information. make: *** [all] Erreur
+         2
+       * rpmlint.py: add another configuration file for packager
+         /usr/share/rpmlint/config is used by packager, to set package
+         wide policy, etc, and should not be modified by users
+         /etc/rpmlint/config is used to set a configuration for a specific
+         host, and should be marked as %config(noreplace) in rpm
+       * Makefile: tar target requires dir target, as it use the directory
+         created before
+       * TagsCheck.py: do not trigger description-use-invalid-word and
+         summary-use-invalid-word if ForbiddenWords is empty ( default
+         value )
+       * TagsCheck.py: use rpm package default group, taken from file
+         GROUPS
+
+2006-04-03  Michael Scherer <misc at mandriva.org>
+
+       * TagsCheck.py: do not hardcode "mandrake" forbidden word in code
+       * TagsCheck.py: oops, seems i forget to remove last occurence of
+         DEFAULT_PACKAGER
+
+2006-04-02  Michael Scherer <misc at mandriva.org>
+
+       * DistributionCheck.py, TagsCheck.py: use default empty value for
+         Distribution and Vendor
+       * TagsCheck.py: remove mandriva domain from check details
+       * TagsCheck.py: no default value for release extension
+       * TagsCheck.py: remove mandriva default value for
+         valid_buildhost_regex
+       * TagsCheck.py: remove useless constant DEFAULT_PACKAGER, as we now
+         have default empty value for getOption
+       * TagsCheck.py: do not hardcode mandrake forbidden word in url
+       * Config.py: add default empty value for getOption
+       * TagsCheck.py: do not hardcode mandriva email address in code
+
+2006-04-01  Michael Scherer <misc at mandriva.org>
+
+       * TagsCheck.py: change requires-on-release from error to warning,
+         close ticket #5
+
+2006-04-01  Ville Skyttä <ville.skytta at iki.fi>
+
+       * AbstractCheck.py, BinariesCheck.py, Config.py, ConfigCheck.py,
+         DistributionCheck.py, DocFilesCheck.py, FHSCheck.py,
+         FilesCheck.py, Filter.py, I18NCheck.py, InitScriptCheck.py,
+         LSBCheck.py, MenuCheck.py, NamingPolicyCheck.py, Pkg.py,
+         PostCheck.py, SignatureCheck.py, SourceCheck.py, SpecCheck.py,
+         TagsCheck.py, ZipCheck.py, check-install.py, compile.py, rpmdiff,
+         rpmlint, rpmlint.py: Add indentation settings for vi* and *Emacs,
+         use -tt, untabify.
+
+2006-03-31  Michael Scherer <misc at mandriva.org>
+
+       * DistributionCheck.py, README: - Do not use mandriva defaut value
+         for DistributionCheck - Do not warn for invalid distribution and
+         vendor if the option not set
+       * FilesCheck.py, README: Remove unused CrossCompilation option, the
+         code was commented since it was commited
+
+2006-03-30  Michael Scherer <misc at mandriva.org>
+
+       * TagsCheck.py: add explanation for requires-on-release
+
+2006-03-29  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Drop removed rpmlint.spec from FILES.
+
+2006-03-29  Michael Scherer <misc at mandriva.org>
+
+       * AbstractCheck.py: - raise not implemented when
+         AbstractCheck.check is not implemented instead of silently
+         passing
+       * Makefile, rpmlint.spec: - remove spec and associated Makefile
+         target\n- rename rename localdist to dist
+
+2006-03-28  Michael Scherer <misc at mandriva.org>
+
+       * Config.py: - do not report
+         shared-lib-without-dependency-information and
+         library-not-linked-against-libc for glibc on x86_64, reported and
+         patched by Dirk Mueller, close ticket #2
+
+2006-03-27  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Makefile: Try to detect if a tag already exists.
+       * Makefile: Set root:root ownership inside tarball.
+       * Makefile: Drop superfluous dependency on changelog from tar.
+       * Makefile: Comment out rpm target for now.
+       * Makefile: Adapt (cvs)tag target for svn.
+       * Makefile: Adapt export target to svn.
+
+2006-03-24  Ville Skyttä <ville.skytta at iki.fi>
+
+       * rpmlint.spec: Fix querying the specfile on setups without the
+         %mkrel macro.
+       * .cvsignore: No longer needed.
+
+2006-03-24  Michael Scherer <misc at mandriva.org>
+
+       * authors.xml: add my new uid to authors.xml
+
+2006-03-24  Ville Skyttä <ville.skytta at iki.fi>
+
+       * README.CVS, README.devel: Update anon checkout instructions.
+
+2006-03-23  Ville Skyttä <ville.skytta at iki.fi>
+
+       * ., ChangeLog, Makefile, authors.xml: Generate ChangeLog with
+         svn2cl, remove it from svn.
+       * Makefile, rpmlint.1, rpmlint.spec: Add man page.
+       * rpmlint.spec: Update project URL.
+       * README: Update contact info.
+
+2006-03-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Don't use -T to objdump for *.debug files.
+       * BinariesCheck.py: Take lib64 archs into account in reference and
+         usr_lib_exception regexps.
+
+2006-03-08  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Sync code and info messages for "*-too-long"
+         checks.
+
+2006-03-02  Ville Skyttä <ville.skytta at iki.fi>
+
+       * FilesCheck.py: Treat *-config as "build config" files only if
+         they are in .../bin/
+
+2006-03-02  Michael Scherer <misc at mandriva.org>
+
+       * I18NCheck.py: add Khmer language code ( thanks Dirk Mueller from
+         suse for the patch )
+
+2006-02-17  Ville Skyttä <ville.skytta at iki.fi>
+
+       * TagsCheck.py: Take Epoch always into account when checking -devel
+         deps to main package.
+
+2006-02-12  Ville Skyttä <ville.skytta at iki.fi>
+
+       * SpecCheck.py: Improve (Build)PreReq messages and descriptions.
+       * FilesCheck.py: Don't warn about shebang-less executable *.la in
+         lib paths; libtool does them that way.
+       * FilesCheck.py: Don't print an error if a script included in docs
+         is not executable.
+       * FilesCheck.py: Avoid using potentially stale "line" and "res"
+         values in text file check.
+
+2006-02-10  Guillaume Rousse <guillomovitch at mandriva.org>
+
+       * Makefile: install bash completion file
+       * FilesCheck.py: let perl module keep shellbang for their own use
+
+2006-02-06  Michael Scherer <misc at mandriva.org>
+
+       * ChangeLog: Generated by cvs2cl the 06_Feb
+       * rpmlint.spec: - release 0.75-1mdk
+       * Makefile: - Remove spurious spaces after setting the locales
+       * Makefile: - force C locales in the shell subcommand, as it will
+         otherwise includes lowercase filenames in the check for print
+         statement in Makefile
+       * ChangeLog: Generated by cvs2cl the 06_f�v
+       * README: Add MetaPackageRegexp option in README
+
+2006-02-06  Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+
+       * FilesCheck.py: Fix english spelling in new warning
+
+2006-02-06  Michael Scherer <misc at mandriva.org>
+
+       * ChangeLog: Generated by cvs2cl the 06_f�v
+       * FilesCheck.py: Add file-in-meta-package check, to detect if a
+         meta package contains files ( rgs request )
+
+2006-02-02  Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+
+       * SpecCheck.py: Patch by Pascal Terjan to warn against the faulty
+         Requires(pre,post) syntax in spec files.
+
+2006-01-31  Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+
+       * TagsCheck.py: Add Development/PHP to the list of default allowed
+         rpm groups
+
+2006-01-30  Michael Scherer <misc at mandriva.org>
+
+       * TagsCheck.py: - add the Mozart Licence ( bug #16416 ), thanks to
+         guillaume rousse.
+
+2006-01-15  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py, README: Add option for warning services being
+         on by default after chkconfig --add.
+       * Pkg.py: Remove accidentally included debug outputs.
+       * Config.py, FilesCheck.py, Pkg.py, README, SpecCheck.py,
+         TagsCheck.py: Add optional UTF-8 checks for docs, specfiles and
+         header field values.
+       * FilesCheck.py: Recognize docs in /usr/X11R6 too.
+
+2006-01-09  Michael Scherer <misc at mandriva.org>
+
+       * Pkg.py: Fix backtrace when there is a missing tag ( bug #20518 on
+         mdv bugzilla )
+
+2005-12-20  Frédéric Lepied <flepied at mandriva.com>
+
+       * I18NCheck.py: fixed typo making the nso and oc ISO codes not
+         reported as valid (Dirk Mueller).
+
+2005-12-14  Ville Skyttä <ville.skytta at iki.fi>
+
+       * Config.py, DocFilesCheck.py: New check for dependencies caused by
+         %doc files (Enrico Scholz).
+       * BinariesCheck.py: Clean up failed objdump output.
+
+2005-12-09  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 09_Dec
+
+2005-12-09  Ville Skyttä <ville.skytta at iki.fi>
+
+       * BinariesCheck.py: Skip some checks and output better warning
+         message if objdump fails.
+       * BinariesCheck.py, Pkg.py: Ensure English output for commands
+         whose output is parsed.
+
+2005-12-06  Michael Scherer <misc at mandriva.org>
+
+       * rpmlint.spec: - use mkrel macro
+
+2005-11-27  Ville Skyttä <ville.skytta at iki.fi>
+
+       * InitScriptCheck.py: Report incoherent subsys starting with "$" as
+         a warning instead of an error.
+       * AbstractCheck.py, BinariesCheck.py, Config.py, ConfigCheck.py,
+         DistributionCheck.py, FHSCheck.py, FilesCheck.py, Filter.py,
+         I18NCheck.py, InitScriptCheck.py, LSBCheck.py, MenuCheck.py,
+         NamingPolicyCheck.py, Pkg.py, PostCheck.py, SignatureCheck.py,
+         SourceCheck.py, SpecCheck.py, TagsCheck.py, check-install.py,
+         compile.py, rpmdiff, rpmlint, rpmlint.py: Fix indentation,
+         untabify, trim trailing whitespace.
+
+2005-11-24  Ville Skyttä <ville.skytta at iki.fi>
+
+       * ConfigCheck.py: Recognize app-defaults in /usr/share/X11/.
+
+2005-11-22  Ville Skyttä <ville.skytta at iki.fi>
+
+       * COPYING: Update FSF's address, copy current GPL from gnu.org
+         as-is.
+       * FilesCheck.py: Don't warn about dangling symlinks whose target is
+         a file-based dependency.
+       * InitScriptCheck.py: Don't blame the package if our shell
+         expansion for subsys filename fails.
+       * FilesCheck.py: Only non-empty perl *.bs files are unneeded (see
+         DynaLoader.pm), and empty ones are already caught by the empty
+         file check.
+       * FilesCheck.py: Improve accuracy of doc, info, and games path
+         regexps.
+       * rpmlint.py: Improve error message when invoked on non-rpm files.
+       * AUTHORS, README.CVS: Update URLs and addresses.
+       * BinariesCheck.py, DistributionCheck.py, FHSCheck.py,
+         FilesCheck.py, InitScriptCheck.py, MenuCheck.py, SourceCheck.py,
+         SpecCheck.py, TagsCheck.py: Improve and fix spelling of error
+         descriptions.
+
+2005-09-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * Makefile: add a check to avoid releasing with print statements.
+       * Pkg.py: use stderr for error messages.
+       * ChangeLog: Generated by cvs2cl the 11_Sep
+       * FilesCheck.py: added noarch-python-in-64bit-path
+       * MenuCheck.py: added menu-in-wrong-dir
+       * rpmdiff: output details on dependencies
+
+2005-08-17  Thierry Vignaud <tvignaud at mandriva.com>
+
+       * Config.py: add filter rules for harddrake & drakxtools
+
+2005-08-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: check Mandrivalinux in bad words
+       * ChangeLog: Generated by cvs2cl the 10_Aug
+       * rpmlint.spec: real 0.71-1mdk
+       * SpecCheck.py: added prereq-use
+
+2005-08-10  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: added "ang" (anglo-saxon or old-english) to rpmlint
+       * I18NCheck.py: added 'rw'
+
+2005-08-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: 0.71-1mdk
+       * ChangeLog: Generated by cvs2cl the 10_Aug
+       * Pkg.py: handle the new Requires(pre) and co syntax. fixed broken
+         code that was extracting info from headers multiple times.
+       * SpecCheck.py: allow to do a symlink to a configure file without
+         having the configure-without-libdir-spec error. (reported by Hans
+         de Goede)
+       * I18NCheck.py: fixed uninitialized variable (reported by Dan
+         Kegel).
+       * Config.py: added exception for uucp (Bruno Cornec)
+       * InitScriptCheck.py: make the "incoherent subsys" check work
+         properly with trailing " or '. (Ville Skytta)
+       * I18NCheck.py: typo (Ville Skytta)
+       * FilesCheck.py: Brings the verbose message about non-config files
+         in /etc up to date wrt. executables. (Ville Skytta)
+       * ChangeLog: Generated by cvs2cl the 10_Aug
+
+2005-07-06  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: new languages to be recognized as valid: 'se' (was
+         wrongly assumed to be an error for 'sv'), 'yo', 'pa_IN'
+
+2005-07-01  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: added the CECILL licence (requested by S�bastion
+         Savarin).
+
+2005-06-21  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: kernel-uml
+
+2005-06-18  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 18_Jun
+       * rpmlint.spec: 0.70-1mdk
+       * ChangeLog: Generated by cvs2cl the 18_Jun
+       * Pkg.py: use RPMSENSE_SCRIPT_PRE if available to detect prereq.
+
+2005-06-17  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: fix script detection (Guillaume Rousse, bug
+         #15152).
+       * TagsCheck.py: added LaTeX Project Public License (Guillaume
+         Rousse, bug #15928).
+       * PostCheck.py: adds userdel and groupdel to the list of
+         "dangerous" commands. (Ville Skytt�)
+       * BinariesCheck.py, Config.py, FilesCheck.py, README: mandriva
+       * TagsCheck.py: check that the url doesn't contain anymore
+         mandrake.
+       * rpmlint.spec: fixed url
+       * ChangeLog: Generated by cvs2cl the 17_Jun
+
+2005-05-30  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 30_May
+
+2005-05-23  Pixel <pixel at mandriva.com>
+
+       * Config.py: whitelist ash statically-linked-binary /sbin/bsh
+
+2005-04-15  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 15_Apr
+       * rpmlint.spec: 0.69-1mdk
+       * ChangeLog: Generated by cvs2cl the 15_Apr
+       * Config.py, DistributionCheck.py, InitScriptCheck.py, LSBCheck.py,
+         MenuCheck.py, NamingPolicyCheck.py, PostCheck.py, README,
+         TagsCheck.py, check-install.py, rpmdiff, rpmlint.py,
+         rpmlint.spec: Mandriva
+
+2005-04-09  Guillaume Rousse <guillomovitch at mandriva.org>
+
+       * FilesCheck.py: /etc/cron.d is a configuration directory, not a
+         script directory
+
+2005-03-23  Guillaume Rousse <guillomovitch at mandriva.org>
+
+       * FilesCheck.py: intepreters can also live in /sbin or /usr/sbin
+
+2005-03-19  Frédéric Lepied <flepied at mandriva.com>
+
+       * PostCheck.py, README: add non-empty shell check (Ville Skytt�)
+
+2005-03-14  Frédéric Lepied <flepied at mandriva.com>
+
+       * Makefile: don't install compile.pyo (Ville Skytt�).
+
+2005-03-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 10_Mar
+       * rpmlint.spec: fix %prefix
+       * ChangeLog: Generated by cvs2cl the 10_Mar
+       * rpmlint.spec: 0.68-1mdk
+       * ChangeLog: Generated by cvs2cl the 10_Mar
+
+2005-03-04  Thierry Vignaud <tvignaud at mandriva.com>
+
+       * Config.py: add exception for dmraid
+
+2005-03-04  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added recognition of Quechua (qu) language
+
+2005-02-23  Pablo Saratxaga <pablo at mandriva.com>
+
+       * ChangeLog: converted to UTF-8
+
+2005-02-16  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 16_Feb
+       * FilesCheck.py: simplify site_perl regexp (Ville Skytt�).
+
+2005-02-14  Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+
+       * Pkg.py: Fix version checking when the epoch is not defined
+         (Michael Scherer)
+
+2005-02-11  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: removed Prefix tag
+       * ChangeLog: Generated by cvs2cl the 11_Feb
+       * rpmlint.spec: 0.67-1mdk
+       * ChangeLog: Generated by cvs2cl the 11_Feb
+       * SpecCheck.py: added hardcoded-packager-tag, hardcoded-prefix-tag
+         and redundant-prefix-tag checks (Guillaume Rousse, bug #12725).
+       * Config.py: added wrong-script-interpreter, non-executable-script,
+         script-without-shellbang, wrong-script-end-of-line-encoding and
+         wrong-file-end-of-line-encoding for 10.2 policy.
+       * FilesCheck.py: added wrong-script-interpreter,
+         non-executable-script, script-without-shellbang,
+         wrong-script-end-of-line-encoding and
+         wrong-file-end-of-line-encoding. (Guillaume Rousse, bug #12725).
+       * TagsCheck.py: added the 'Graphical desktop/Xfce' group (bug
+         #13141).
+       * rpmdiff: filter the provides on name-version-release for the
+         package itself.
+
+2005-02-08  Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+
+       * Pkg.py: Make check_versioned_dep ignore epoch when comparing
+         versions (patch by Michael Scherer)
+
+2005-02-08  Thierry Vignaud <tvignaud at mandriva.com>
+
+       * Config.py: do not complain about explicit dependancy on liblua5
+         (else b/c of buggy lua, lua users accepted either lua4 or lua5
+         thus resulting in linkinkg issues at runtime)
+       * Config.py: update drakconf rule
+
+2005-02-07  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: added Design Sciences License (S�bastien Savarin)
+         fixed Lucent Public License
+
+2005-01-30  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: add exceptions for dkms packages
+
+2005-01-25  Gwenole Beauchesne <gbeauchesne at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 25_Jan
+       * rpmlint.spec: 0.66-1mdk
+       * Config.py: exceptions for %multiarch policy
+       * ChangeLog: Generated by cvs2cl the 25_Jan
+       * ChangeLog: Generated by cvs2cl the 25_Jan
+
+2005-01-24  Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+
+       * FilesCheck.py: Add a new warning for perl modules installed under
+         site_perl instead of vendor_perl
+       * FilesCheck.py: Perl modules go under vendor_perl, not site_perl
+
+2005-01-21  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 21_Jan
+       * rpmlint.spec: oops put the right version
+       * ChangeLog: Generated by cvs2cl the 21_Jan
+       * rpmlint.spec: 0.65-1mdk
+       * ChangeLog: Generated by cvs2cl the 21_Jan
+
+2005-01-19  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: recognition of some more languages (bug #12216)
+
+2005-01-14  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added language codes (nr, nso, tn, ts) of South
+         Africa that have efforts on creating localizations for them
+         (source: http://www.translate.org.za/ )
+
+2005-01-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: don't report non-conffile-in-etc on executable.
+       * ChangeLog: Generated by cvs2cl the 10_Jan
+
+2005-01-05  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added dir-or-file-in-var-local to mdk10.2 policy
+       * TagsCheck.py: - E:V-R should be consistent in package and
+         changelog regardless if use_epoch is set or not (Ville Skytt�). -
+         Spelling fixes (Ville Skytt�).
+       * Makefile: - Include rpmlint.bash-completion in dist tarball. -
+         Don't install compile.py.
+       * I18NCheck.py: - "se" -> "sv" in I18NCheck (Ville Skytt�).
+       * FilesCheck.py: - Flag installing files to /var/local as an error
+         (Ville Skytt�). - Improved perl temp file regexp (Ville Skytt�).
+         - Extended CVS internal file regexp to cover Subversion and GNU
+         Arch (Ville Skytt�).
+       * Config.py: added missing-menu-command to policy
+       * MenuCheck.py: added missing-menu-command (Michael Scherer)
+
+2004-12-30  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 30_Dec
+       * FilesCheck.py: removed unused variables
+       * rpmlint.spec: 0.64-1mdk
+       * ChangeLog: Generated by cvs2cl the 30_Dec
+       * rpmlint.py: fix double import
+       * FilesCheck.py: Check that pkg-config files and config script are
+         in devel packages (Guillaume Rousse, bug #12662).
+       * FilesCheck.py: added htaccess-file check (Guillaume Rousse, bug
+         #12661).
+       * Config.py: first pass to update load_policy.
+       * FilesCheck.py: added executable-marked-as-config-file check.
+       * TagsCheck.py: added requires-on-release check
+       * Makefile: clean the build
+
+2004-12-19  Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+
+       * TagsCheck.py: The Lucent Public Licence (Plan9) is
+         opensource.org-approved.
+
+2004-12-18  Guillaume Rousse <guillomovitch at mandriva.org>
+
+       * rpmlint.bash-completion: initial import
+       * FilesCheck.py: typo
+
+2004-12-06  Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+
+       * SpecCheck.py: Clarify the use-of-RPM_SOURCE_DIR message
+         explanation
+
+2004-12-05  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: lookup .cmi files as devel files too (Guillaume
+         Rousse) [bug #12186].
+       * ChangeLog: Generated by cvs2cl the 05_Dec
+       * rpmlint.spec: 0.63-1mdk
+       * ChangeLog: Generated by cvs2cl the 05_Dec
+       * FilesCheck.py: add /usr/lib/menu to STANDARD_DIRS (Michael)
+       * ChangeLog: Generated by cvs2cl the 05_Dec
+       * Config.py: added exceptions for kernel-source.* on
+         devel-file-in-non-devel-package reports.
+       * FilesCheck.py: added dir-or-file-in-usr-local (Michael Scherer).
+       * BinariesCheck.py: allow soname in the form libfoo-X.Y.Z.so too
+         (Guillaume Rousse) [bug #12522].
+       * NamingPolicyCheck.py: make exception to the
+         python/perl/ruby/ocaml naming policy when the package contains
+         executable (Guillaume Rousse) [bug #12521].
+       * I18NCheck.py: Don't tag .mo in webapps (Guillaume Rousse) [bug
+         #12186]
+       * TagsCheck.py: added summary-ended-with-dot (Guillaume Rousse)
+         [bug #12520]
+
+2004-12-04  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: cleanup exceptions (Michael Scherer)
+       * rpmlint.py: exit on C-c (Michael Scherer)
+       * ChangeLog: Generated by cvs2cl the 05_Dec
+
+2004-11-28  Guillaume Rousse <guillomovitch at mandriva.org>
+
+       * NamingPolicyCheck.py: ocaml naming policy
+
+2004-11-23  Frédéric Lepied <flepied at mandriva.com>
+
+       * PostCheck.py: doc for postin-without-ghost-file-creation (Pascal
+         Terjan)
+
+2004-09-22  Michael Scherer <misc at mandriva.org>
+
+       * Config.py: - some code factorisation - fix addCheck ( was not
+         useable since it was a tuple instead of a list )
+
+2004-08-30  Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+
+       * FilesCheck.py: Minor nit in the regexp that checks that perl
+         module rpms should come without the source tarball MANIFEST.
+
+2004-08-27  Frederic Crozat <fcrozat at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 27_Aug
+       * rpmlint.spec: Fix missing capitalization
+       * MenuCheck.py: Fix missing capitalization
+
+2004-08-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 25_Aug
+       * rpmlint.spec: 0.61-1mdk
+       * ChangeLog: Generated by cvs2cl the 25_Aug
+
+2004-08-19  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added recognition of "pa" (Punjabi) language code
+
+2004-08-17  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 17_Aug
+
+2004-08-17  Frederic Crozat <fcrozat at mandriva.com>
+
+       * MenuCheck.py: Fix menu capitalization
+
+2004-08-03  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: added some more language codes
+
+2004-08-03  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 03_Aug
+       * rpmlint.spec: 0.60-1mdk
+       * ChangeLog: Generated by cvs2cl the 03_Aug
+       * TagsCheck.py: added obsolete-on-name
+
+2004-07-29  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exceptions for perl and dyalog on
+         devel-file-in-non-devel-package
+
+2004-07-27  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 27_Jul
+
+2004-07-13  Rafael Garcia-Suarez <rgarciasuarez at mandriva.com>
+
+       * FilesCheck.py: Add a warning for MANIFEST* files in perl modules
+
+2004-07-08  Frédéric Lepied <flepied at mandriva.com>
+
+       * BinariesCheck.py: add ruby exceptions like perl and python.
+       * ChangeLog: Generated by cvs2cl the 08_jui
+
+2004-07-05  Michael Scherer <misc at mandriva.org>
+
+       * TagsCheck.py: - added useless-explicit-provides ( check if there
+         is 2 times the same provides )
+
+2004-05-17  Michael Scherer <misc at mandriva.org>
+
+       * rpmlint.py: - fix -I, rpmlint didn't work f run without the
+         option.
+
+2004-05-07  Michael Scherer <misc at mandriva.org>
+
+       * rpmlint.py: added option -I, to print description of the error
+         passed on commandline
+
+2004-05-03  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added Furlan language code (fur)
+
+2004-04-30  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 30_Apr
+       * rpmlint.spec: 0.59-1mdk
+       * ChangeLog: Generated by cvs2cl the 30_Apr
+
+2004-04-30  Michael Scherer <misc at mandriva.org>
+
+       * TagsCheck.py: check if a package requires a interpreter in
+         /usr/local/bin/
+
+2004-04-19  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 19_Apr
+       * Config.py, DistributionCheck.py, InitScriptCheck.py, LSBCheck.py,
+         NamingPolicyCheck.py, PostCheck.py, README, TagsCheck.py,
+         check-install.py: Mandrakelinux (Robert Vojta)
+
+2004-03-22  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: some more languages recognized
+
+2004-03-19  Frédéric Lepied <flepied at mandriva.com>
+
+       * InitScriptCheck.py: chack that the initscript is executable
+         (Michael Scherer)
+
+2004-03-12  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 12_Mar
+       * rpmlint.spec: 0.58-1mdk
+       * ChangeLog: Generated by cvs2cl the 12_Mar
+       * TagsCheck.py: The Mandrake word is forbidden alone.
+
+2004-03-09  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: consolehelper is in usermode-consoleonly
+       * MenuCheck.py: fixed missing comma
+
+2004-03-01  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: fixed missing comma (Michael Scherer)
+
+2004-02-20  David Baudens <baudens at mandriva.com>
+
+       * MenuCheck.py: Add mission "More applications/Other" section
+
+2004-02-12  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 12_Feb
+       * rpmlint.spec: 0.57.1-1mdk
+       * PostCheck.py: removed buggy trigger code
+       * ChangeLog: Generated by cvs2cl the 12_Feb
+       * rpmlint.spec: 0.57-1mdk
+       * ChangeLog: Generated by cvs2cl the 12_Feb
+       * FilesCheck.py: no-dependancy-on => no-dependency-on fixed perl
+         check (Michael Scherer)
+       * Config.py: dependancy => dependency
+       * TagsCheck.py: added Education as a valid group
+       * PostCheck.py: makes postcheck not to whine about ghost files that
+         are created by %triggerin scripts in addition to %pre and %post.
+         (Ville Skytt�)
+         
+         Additionally, somewhat improved documentation on
+         one-line-commands, using "should" instead of "must" since AFAICS
+         %post -p <command> cannot be used if <command> needs parameters.
+         (Ville Skytt�)
+
+2004-02-11  David Baudens <baudens at mandriva.com>
+
+       * MenuCheck.py: Add missing entries
+
+2004-02-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 10_Feb
+       * rpmlint.spec: 0.56-1mdk
+       * ChangeLog: Generated by cvs2cl the 10_Feb
+
+2004-02-10  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: changed uz@Latn locale
+
+2004-02-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * MenuCheck.py: added missing ',' (Michael Scherer)
+
+2004-02-09  Frédéric Lepied <flepied at mandriva.com>
+
+       * NamingPolicyCheck.py: Better error message. Fixed bug when no
+         files are present. (Michael Scherer)
+       * rpmlint.py: more robust processing when scanning a directory
+         (Michael Scherer)
+         
+         force to have / in directory names to allow to have directory
+         with the same name as an installed package (Michael Scherer)
+
+2004-01-28  Frédéric Lepied <flepied at mandriva.com>
+
+       * MenuCheck.py: removed duplicated Physics entry
+
+2004-01-27  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 27_Jan
+       * rpmlint.spec: 0.55-1mdk
+       * MenuCheck.py: final menu structure
+
+2004-01-23  David Baudens <baudens at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 23_Jan
+       * ChangeLog, MenuCheck.py, rpmlint.spec: Replace old menu structure
+         by new menu structure
+
+2004-01-19  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 19_Jan
+       * rpmlint.spec: 0.54-1mdk
+       * ChangeLog: Generated by cvs2cl the 19_Jan
+       * rpmlint.py: fixed main loop for argument testing on files and
+         directories
+       * Pkg.py: in __getitem__ return None instead of [] (change in rpm
+         4.2.2)
+
+2004-01-15  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.py: updated directory support (Michael Scherer)
+
+2004-01-14  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.py: Allow to pass a directory as an argument (Michael
+         Scherer)
+
+2004-01-13  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added new language codes
+
+2003-12-31  Frédéric Lepied <flepied at mandriva.com>
+
+       * PostCheck.py: allow the [[:space:]] construction (bug #6466)
+         (Luca Berra)
+       * Config.py: added exceptions for postfix (Luca Berra)
+       * BinariesCheck.py: in only-non-binary-in-usr-lib don't report
+         directories (Michael Scherer)
+
+2003-12-26  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: mispelled-macro check (Michael Scherer)
+
+2003-12-22  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 22_Dec
+       * rpmlint.spec: 0.53-1mdk
+       * TagsCheck.py: added *@mandrake.org as a correct packager tag.
+       * ChangeLog: Generated by cvs2cl the 22_Dec
+       * ZipCheck.py: handle exception while reading zip file
+       * FilesCheck.py: log-files-without-logrotate (Michael Scherer)
+       * Config.py, README, ZipCheck.py: new check ZipCheck (Ville Skytt�)
+       * SpecCheck.py: check lib packages only they start by lib (Pixel)
+       * FilesCheck.py, InitScriptCheck.py, LSBCheck.py, MenuCheck.py,
+         TagsCheck.py: spelling fixes (Ville Skytt�)
+       * README: KernelModuleRPMsOK defaults to 1
+       * README, config: added KernelModuleRPMsOK
+       * FilesCheck.py: non-standard-executable-perm was never run (Ville
+         Skytt�)
+       * FilesCheck.py: added checks for correct depmod calls in
+         scriptlets (Eric Sandeen)
+
+2003-12-11  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exception for busybox
+
+2003-11-20  Nicolas Planel <nplanel at mandriva.com>
+
+       * Config.py, rpmlint.spec: add policycoreutils exception
+
+2003-11-19  Nicolas Planel <nplanel at mandriva.com>
+
+       * Config.py, rpmlint.spec: added statically-linked-binary exception
+         for udev
+
+2003-10-01  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exceptions for kernel-source
+       * InitScriptCheck.py: allow multiple spaces before chkconfig (Eric
+         Sandeen)
+
+2003-09-05  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 05_Sep
+       * rpmlint.spec: 0.52-1mdk
+       * ChangeLog: Generated by cvs2cl the 05_Sep
+       * TagsCheck.py: added explicit-lib-dependency check
+       * Config.py: added exceptions for explicit-lib-dependency and
+         invalid-build-requires do not report errors on debug packages
+
+2003-09-04  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: added invalid-build-requires
+
+2003-08-05  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: don't depend on rpm-devel anymore
+       * ChangeLog: Generated by cvs2cl the 05_Aug
+       * rpmlint.spec: 0.51.1-1mdk
+       * ChangeLog: Generated by cvs2cl the 05_Aug
+       * TagsCheck.py: don't check devel-dependency on source package
+       * rpmlint.py: 2003
+       * NamingPolicyCheck.py: corrected info reports
+
+2003-08-05  Gwenole Beauchesne <gbeauchesne at mandriva.com>
+
+       * SpecCheck.py: Add /usr/lib/hotplug to hardcoded-library-path
+         exceptions
+
+2003-08-04  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 04_Aug
+       * rpmlint.spec: 0.51-1mdk
+       * ChangeLog: Generated by cvs2cl the 04_Aug
+       * TagsCheck.py: added devel-dependency check
+       * Config.py: escape ++ in addFilter correct load_policy
+       * Config.py: corrected load_policy
+       * Config.py: added devel-dependancy wrong warnings
+       * Config.py: added exceptios for ppp (Guillaume Rousse)
+
+2003-07-30  Pablo Saratxaga <pablo at mandriva.com>
+
+       * TagsCheck.py: fixed English typo
+
+2003-07-22  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 22_Jul
+       * SpecCheck.py: allow the following form for a patch instruction:
+         %patch -P 1 (request from Stephan Kulow)
+       * NamingPolicyCheck.py: first version from Michael Scherer
+       * Pkg.py: in shell_var_value escape the var name to avoid a
+         backtrace (Ville Skytt�)
+       * Config.py: don't warn on -debug packages (Ville Skytt�)
+       * InitScriptCheck.py: added init-script-name-with-dot check
+         (Michael Scherer)
+
+2003-06-30  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added 'mn' to list of languages
+
+2003-06-29  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added some more languages
+
+2003-05-09  Gwenole Beauchesne <gbeauchesne at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 09_May
+       * rpmlint.spec: 0.50-1mdk
+       * ChangeLog: Generated by cvs2cl the 09_mai
+       * SpecCheck.py: Make %ifarch-applied-patch a warning
+
+2003-05-09  Frédéric Lepied <flepied at mandriva.com>
+
+       * Makefile: install rpmdiff in /usr/bin
+       * rpmdiff: load Pkg from /usr/share/rpmlint to be able to be
+         installed anywhere
+
+2003-05-08  Gwenole Beauchesne <gbeauchesne at mandriva.com>
+
+       * SpecCheck.py: Hanle %ifnarch in ifarch_regex too. Aka. don't
+         suddenly think about rpmlint prior to taking a bath.
+       * ChangeLog: Generated by cvs2cl the 08_mai
+       * SpecCheck.py: check for hardcoded-library-path exceptions only on
+         the actual suspected hardcoded library path
+       * SpecCheck.py: add %ifarch-applied-patch check
+       * SpecCheck.py: Add hardcoded-library-path exceptions
+
+2003-05-07  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: changed Serbian lang codes to match what is now
+         used in Gnome and KDE
+
+2003-05-05  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: corrected epoch tests (Ville Skytt�)
+       * FilesCheck.py: allow empty __init__.py (St�fane Fermigier)
+       * TagsCheck.py: added Zope Public License
+       * Config.py: added exceptions for hidden-file-or-dir check (Michael
+         Scherer)
+       * FilesCheck.py: added hidden-file-or-dir check (Michael Scherer)
+
+2003-04-30  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added an exception for gconf schemas
+
+2003-04-29  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 29_Apr
+       * rpmlint.spec: 0.49-1mdk
+       * ChangeLog: Generated by cvs2cl the 29_Apr
+       * Config.py: added an exception for tpctl
+
+2003-04-23  Frédéric Lepied <flepied at mandriva.com>
+
+       * Pkg.py, SignatureCheck.py, rpmlint.py: rpm 4.2 support (Ville
+         Skytt�)
+
+2003-03-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * README: Spelling fixes, new options: UseEpoch, ValidSrcPerms
+         (Ville Skytt�).
+       * TagsCheck.py: Handle nosrc packages properly, add required Epoch
+         functionality (Ville Skytt�).
+       * SourceCheck.py: Made valid source permissions configurable (Ville
+         Skytt�).
+       * Pkg.py: Added isNoSource()
+
+2003-02-19  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Fixed Maori ('mi', was wrongly coded as 'ma'),
+         Added various Indic languages that have Gnome/KDE translations,
+         Added Xhosa (xh), changed Ganda code lug -> lg (we standardize on
+         two letter codes)
+
+2003-01-31  Frédéric Lepied <flepied at mandriva.com>
+
+       * Pkg.py: added support for rpm 4.2
+
+2003-01-17  Gwenole Beauchesne <gbeauchesne at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 17_Jan
+       * rpmlint.spec: 0.48-2mdk
+       * ChangeLog: Generated by cvs2cl the 17_jan
+       * FilesCheck.py: Errour out about outside-libdir-files only if it
+         concerns a library package. This is heuristically determined on
+         the package name as '^(lib|.+-libs)'.
+       * BinariesCheck.py: Add lib64 paths
+
+2003-01-16  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 17_Jan
+       * rpmlint.spec: 0.48-1mdk
+       * ChangeLog: Generated by cvs2cl the 16_Jan
+       * rpmlint.py: added a way to load an alternative config file.
+       * SpecCheck.py: added lib-package-without-%mklibname
+       * FilesCheck.py: added outside-libdir-files
+
+2003-01-09  Chmouel Boudjnah
+
+       * Config.py: Add modutils rules.
+
+2003-01-07  Chmouel Boudjnah
+
+       * Config.py: fix pcmcia-cs regexp.
+
+2002-12-19  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: exception for wvdial
+
+2002-12-06  Frédéric Lepied <flepied at mandriva.com>
+
+       * SpecCheck.py: don't parse changelog section to find errors and
+         correct source_dir_regex.
+
+2002-11-19  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added 'en_US' as valid locale name
+
+2002-11-07  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exceptions for extipl, ocamltk and drakconf
+
+2002-10-14  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added "lug" (Luganda) language as a valid code for
+         translations
+
+2002-08-21  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added recognition of some more language codes
+         (Gnome includes some translations in those languages now)
+
+2002-08-08  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 08_Aug
+       * rpmlint.spec: 0.47-1mdk
+       * ChangeLog: Generated by cvs2cl the 08_Aug
+       * setuplist.py: split old and new users/groups.
+       * PostCheck.py: check rpm-helper prereq.
+       * MenuCheck.py: add default values from Config.
+       * InitScriptCheck.py: allow to add/del service with rpm-helper
+         scripts.
+       * FilesCheck.py: use default values from Config.
+       * Config.py: added handling of default values.
+       * BinariesCheck.py: added /usr/lib/bonobo to no binary in /usr/lib
+         exceptions
+
+2002-07-23  Gwenole Beauchesne <gbeauchesne at mandriva.com>
+
+       * FilesCheck.py: Add lib64 directories
+       * FHSCheck.py: Add lib64 as standard subdir in /usr (that's the
+         /lib<qual> part of FHS)
+
+2002-07-12  Frédéric Lepied <flepied at mandriva.com>
+
+       * setuplist.py: Sync with version 2.2.0-28mdk of setup package
+
+2002-07-11  Frédéric Lepied <flepied at mandriva.com>
+
+       * PostCheck.py: added perl to dangerous command check trigger
+         scripts too
+       * BinariesCheck.py: corrected wrong loop for /usr/lib check
+
+2002-06-19  Pablo Saratxaga <pablo at mandriva.com>
+
+       * ChangeLog, I18NCheck.py: Added 'zh_HK' recognition
+
+2002-06-14  Frédéric Lepied <flepied at mandriva.com>
+
+       * PostCheck.py: corrected prereq test
+
+2002-06-07  Gwenole Beauchesne <gbeauchesne at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 07_jun
+       * SpecCheck.py: Also check for %{?_prefix}?/lib references
+
+2002-06-04  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 04_Jun
+       * Makefile: remove cvs commit from cvstag target
+       * ChangeLog: Generated by cvs2cl the 04_Jun
+       * rpmlint.spec: 0.46-1mdk
+       * ChangeLog: Generated by cvs2cl the 04_Jun
+       * Config.py: added exceptions for no-binary
+       * README: added UsrLibBinaryException
+       * BinariesCheck.py: added UsrLibBinaryException option and
+         exception no-binary for multiple packages.
+       * Pkg.py: make all extracted files accessible.
+       * BinariesCheck.py: added no-binary and only-non-binary-in-usr-lib
+
+2002-06-03  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 03_Jun
+       * rpmlint.spec: 0.45-1mdk
+       * Config.py: added new check from Gwenole in mdk9.0 policy
+       * ChangeLog: Generated by cvs2cl the 03_Jun
+
+2002-06-01  Gwenole Beauchesne <gbeauchesne at mandriva.com>
+
+       * SpecCheck.py: - Add configure-without-libdir-spec check - Fix
+         typos in previous hardcoded-library-path description
+       * Config.py: Revert last change
+       * Config.py: Add hardcoded-library-path to mdk9.0 policy
+       * SpecCheck.py: Add hardcoded-library-path check
+
+2002-05-29  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 29_May
+       * rpmlint.spec: 0.44-1mdk
+       * ChangeLog: Generated by cvs2cl the 29_May
+       * Config.py: added
+         non-root-user-log-file|non-root-group-log-file|non-ghost-file for
+         mdk9.0 policy.
+       * FilesCheck.py: added non-ghost-file check
+
+2002-05-14  Frédéric Lepied <flepied at mandriva.com>
+
+       * Makefile: added AUTHORS
+       * AUTHORS: first version
+
+2002-05-02  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: added non-root-user-log-file and
+         non-root-group-log-file.
+
+2002-05-01  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 01_May
+       * TagsCheck.py: remove debug trace
+       * rpmlint.spec: 0.43-1mdk
+       * ChangeLog: Generated by cvs2cl the 01_May
+       * Config.py: added no-prereq-on for mdk9.0 policy
+       * TagsCheck.py: change non-coherent-filename to check all the
+         filename
+       * PostCheck.py: added no-prereq-on
+
+2002-04-24  Frédéric Lepied <flepied at mandriva.com>
+
+       * BinariesCheck.py: corrected bad report on libgimp1.2_1
+       * INSTALL: described policy
+       * Config.py: added load_policy
+       * Makefile: retrieve the version and release in a more generic way.
+         
+         put the version and policy at install time.
+       * TagsCheck.py: check that the architecture is coherent with the
+         file name.
+       * rpmlint.py: added a --policy option.
+         
+         the version is set at install time.
+       * Pkg.py: create a fake filename to satisfy some checks on the
+         filename for InstalledPkg objects.
+
+2002-04-17  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: build one regex from all the exceptions
+         
+         added exception for avifile-samples
+
+2002-03-08  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: 0.42-2mdk
+       * Makefile: install rpmdiff in LIBDIR
+
+2002-03-04  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 04_Mar
+       * Makefile: added rpmdiff
+       * rpmlint.py, rpmlint.spec: 0.42
+       * ChangeLog: Generated by cvs2cl the 03_Mar
+       * README: added PerlVersionTrick
+       * BinariesCheck.py: handle new file attributes
+       * FilesCheck.py: allow perl and python dependencies to be on
+         perl-base and python-base.
+         
+         manage Mandrake perl versionning.
+       * Pkg.py: added md5, mtime and rdev to file attributes
+
+2002-02-26  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added 'mt' to recognized locales
+
+2002-02-23  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmdiff: first version
+
+2002-02-20  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exceptions for shorewall and DansGuardian
+
+2002-02-13  Frédéric Lepied <flepied at mandriva.com>
+
+       * PostCheck.py: add descriptions for spurious-bracket-in-.
+
+2002-02-11  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exception for logrotate entry for hylafax.
+
+2002-02-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 09_Feb
+       * setuplist.py: Sync with version 2.2.0-23mdk of setup package
+       * rpmlint.py, rpmlint.spec: 0.41
+       * Makefile: pychecker.sh => pychecker
+
+2002-02-07  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 07_Feb
+       * Pkg.py: added check_versioned_dep
+       * FilesCheck.py: check dependency on the right version of the
+         interpreter for python and perl modules.
+       * Config.py: exceptions for perl, python and nut.
+
+2002-02-01  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exception for fetchmail
+
+2002-01-29  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: added W3C Licence
+       * Config.py: add exception for no-%clean-section'
+
+2002-01-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * SpecCheck.py: report missing %clean section.
+
+2002-01-14  Frédéric Lepied <flepied at mandriva.com>
+
+       * setuplist.py: Sync with version 2.2.0-18mdk of setup package
+
+2002-01-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 10_Jan
+       * rpmlint.py, rpmlint.spec: 0.40-1mdk
+       * ChangeLog: Generated by cvs2cl the 10_Jan
+       * Config.py, MenuCheck.py, README: icons for menu are now png
+       * TagsCheck.py: added libsafe.so as an invalid Requires.
+       * Makefile: compile.py takes an extra argument now.
+
+2002-01-07  Chmouel Boudjnah
+
+       * setuplist.py: Sync with version 2.2.0-18mdk of setup package
+       * Config.py: Add exceptions for wine
+
+2002-01-03  Frédéric Lepied <flepied at mandriva.com>
+
+       * compile.py: pass the destination directory to avoid change when
+         the byte compilation is checked.
+
+2001-12-11  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exception for kdebase.
+
+2001-12-04  Chmouel Boudjnah
+
+       * setuplist.py: Sync with version 2.2.0-16mdk of setup package
+
+2001-11-30  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 30_Nov
+       * rpmlint.spec: 0.39-2mdk
+       * ChangeLog: Generated by cvs2cl the 30_Nov
+       * BinariesCheck.py: search references to home or tmp in
+         /usr/lib/pkgconfig/ files.
+       * FilesCheck.py: .nosearch files are allowed to have a zero length.
+       * Config.py: added an exception for use-of-RPM_SOURCE_DIR in the
+         kernel package.
+
+2001-11-28  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: exceptions for ;getty
+
+2001-11-27  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: group "Development/KDE and QT" renamed
+         "Development/KDE and Qt"
+
+2001-11-27  Chmouel Boudjnah
+
+       * Config.py: Add some exceptions for zero-lenght files in setup
+         packages.
+       * setuplist.py: Sync with version of setup package
+       * Config.py: mandrake_consmap doen't have a status and reload entry
+         which is normal.
+
+2001-11-26  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: exception for libsane
+
+2001-11-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exceptions for zapping.
+       * ChangeLog: Generated by cvs2cl the 25_nov
+       * rpmlint.py, rpmlint.spec: 0.39-1mdk
+       * ChangeLog: Generated by cvs2cl the 25_nov
+       * LSBCheck.py, PostCheck.py, check-install.py: Linux-Mandrake =>
+         Mandrake Linux
+       * TagsCheck.py: corrected regexp to check devel provides.
+       * InitScriptCheck.py: added incoherent-init-script-name check.
+         
+         expand shell variable in incoherent-subsys check.
+       * Pkg.py: added substitute_shell_vars and shell_var_value
+         functions.
+
+2001-11-23  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: use list imported from the setup package for users
+         and groups (from setuplist.py).
+       * README: added InvalidRequires to the list of options.
+       * TagsCheck.py: added the new check invalid-dependency.
+
+2001-11-22  Frédéric Lepied <flepied at mandriva.com>
+
+       * setuplist.py: setup 2.2.0-13mdk
+
+2001-11-21  Chmouel Boudjnah
+
+       * ChangeLog: Generated by cvs2cl the 21_Nov
+       * PostCheck.py: Don't print error about percent if post-script has
+         a %%.
+
+2001-11-19  Frédéric Lepied <flepied at mandriva.com>
+
+       * SpecCheck.py: check also %_sourcedir.
+
+2001-11-16  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 16_Nov
+
+2001-11-14  Frédéric Lepied <flepied at mandriva.com>
+
+       * AbstractCheck.py, BinariesCheck.py, ConfigCheck.py,
+         DistributionCheck.py, FHSCheck.py, FilesCheck.py, I18NCheck.py,
+         InitScriptCheck.py, LSBCheck.py, MenuCheck.py, Pkg.py,
+         PostCheck.py, README, SignatureCheck.py, SourceCheck.py,
+         SpecCheck.py, TagsCheck.py, check-install.py, rpmlint.py:
+         corrected warnings reported by pychecker
+       * Makefile: added a verify target to use pychecker.
+       * SpecCheck.py: Check that the BuildRoot tag doesn't contain a
+         hardcoded path
+
+2001-11-13  Frédéric Lepied <flepied at mandriva.com>
+
+       * BinariesCheck.py: check if .la files contain tmp or home
+         references.
+
+2001-11-13  Chmouel Boudjnah
+
+       * Config.py: Fix regexp with emacs.*el
+       * Config.py: Don't do dependences on locale-el on all emacs and
+         xemacs el package.
+
+2001-11-09  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exceptions for iptable and old menu files.
+       * PostCheck.py: check that RPM_BUILD_ROOT or RPM_BUILD_DIR isn't
+         used
+
+2001-10-30  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 30_Oct
+       * rpmlint.py, rpmlint.spec: 0.38-1mdk
+       * ChangeLog: Generated by cvs2cl the 30_Oct
+       * Config.py: added incoherent-version-in-name exceptions.
+
+2001-10-29  Frédéric Lepied <flepied at mandriva.com>
+
+       * README, TagsCheck.py: added mandrake.org as a valid build host.
+       * BinariesCheck.py: check that major version is present in package
+         name.
+
+2001-10-27  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: check that regular files haven't a zero size.
+       * Pkg.py: add the size to the record about a file
+
+2001-10-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: exceptions for Mesa libification
+       * I18NCheck.py: only check binary packages
+
+2001-10-24  Frédéric Lepied <flepied at mandriva.com>
+
+       * SpecCheck.py: don't allow space before tag name
+
+2001-10-23  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: added mandrakeexpert url as a valid Packager field.
+
+2001-10-19  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: allow space after the release in a changelog entry.
+
+2001-10-18  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: updated list from opensource.org and added non
+         opensource ones.
+
+2001-10-17  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: report a warning if no url tag is defined.
+       * Config.py: exception for fetchmail-daemon.
+
+2001-10-17  Pixel <pixel at mandriva.com>
+
+       * Config.py: - add ocaml-lablgtk and camlp4 in devel packages - fix
+         the setuid filter for /usr/bin/sperl5.6.1
+
+2001-10-16  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 16_Oct
+       * README: added descriptions for ForbiddenWords and ValidBuildHost.
+       * SourceCheck.py: correct boolean expression for strange-permission
+       * ChangeLog: Generated by cvs2cl the 16_Oct
+       * rpmlint.py, rpmlint.spec: 0.37-1mdk
+       * ChangeLog: Generated by cvs2cl the 16_Oct
+       * SourceCheck.py: allow 0755 as a valid mode for source.
+       * Config.py: various exceptions
+       * ChangeLog: Generated by cvs2cl the 16_Oct
+       * TagsCheck.py: added invalid-word check in description and
+         summary. added invalid-buildhost check.
+       * FilesCheck.py: added .cvsignore to the list of cvs-internal-file.
+       * BinariesCheck.py: check for new style of pic sections.
+
+2001-10-11  Chmouel Boudjnah
+
+       * ChangeLog: Generated by cvs2cl the 11_Oct
+
+2001-10-10  Chmouel Boudjnah
+
+       * FilesCheck.py: Check if kernel modules are in the kernel package.
+
+2001-10-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * PostCheck.py: track command with full path too.
+
+2001-10-09  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: exceptions for hylafax
+       * Config.py: exceptions for mkinitrd automake gettext chromium
+         webmin methane apache-suexec
+
+2001-10-05  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: added squid group and user.
+       * BinariesCheck.py: Warn for man pages without version in library
+         packages.
+
+2001-10-02  Chmouel Boudjnah
+
+       * DistributionCheck.py: More explicit path regexp check for info
+         files.
+
+2001-09-29  Chmouel Boudjnah
+
+       * Config.py: execptions for shadow-utils package.
+
+2001-09-28  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 28_Sep
+       * rpmlint.spec: 0.36-1mdk
+       * ChangeLog: Generated by cvs2cl the 28_Sep
+       * MenuCheck.py: check if a menu file is executable.
+       * PostCheck.py: check if /tmp or /var/tmp is used.
+         
+         check if update-menus is called without a menu file.
+       * Config.py: don't make exception if no_exception is set.
+       * rpmlint.py: added -n/--noexception option to display all the
+         errors/warnings without exceptions from Config.
+       * TagsCheck.py: added the https address as a valid one.
+
+2001-09-28  Chmouel Boudjnah
+
+       * Config.py: ipsec.secrets is normal to be not readable.
+       * Config.py: mandrake_consmap like mandrake_(fistime|everytime)
+       * Config.py: Add exeptions for traceoute6 and ping6 setuid.
+       * Config.py: Add nfs-utils execptions.
+
+2001-09-14  Frédéric Lepied <flepied at mandriva.com>
+
+       * PostCheck.py: ghost-files-without-postun =>
+         ghost-files-without-postin
+
+2001-09-13  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exceptions for portsentry.
+
+2001-09-04  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: added /etc/logrotate.d entry check.
+
+2001-08-24  Chmouel Boudjnah
+
+       * Config.py: Add execpt for initscripts.
+
+2001-08-23  Chmouel Boudjnah
+
+       * Config.py: iputils setuid ping6/tracroute6, safe as they drop it
+         VERY early.
+
+2001-08-21  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 21_Aug
+       * rpmlint.spec: 0.35-1mdk
+       * rpmlint.py: 0.35
+       * ChangeLog: Generated by cvs2cl the 21_Aug
+       * BinariesCheck.py: Make libraries not linked against libc errors
+         and not warnings. (Bill Nottingham)
+         
+         libc doesn't need to be linked against libc, and the dynamic
+         linker doesn't need dependeny information. (Bill Nottingham)
+         
+         Fix some of the library checks to be more correct. (Bill
+         Nottingham)
+       * TagsCheck.py: added a check on obsoleted packages not provided.
+       * Pkg.py: factorize code for obsoletes/provides/requires/prereq.
+
+2001-08-20  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: check non readable files.
+       * PostCheck.py: check ~/ instead of ~ to allow awk scripts not to
+         give false reports.
+       * MenuCheck.py: added a check for / in menu titles.
+
+2001-08-13  Chmouel Boudjnah
+
+       * FilesCheck.py: Add wine groups.
+
+2001-08-11  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: Added 'bs' as a valid language code name
+
+2001-08-04  Chmouel Boudjnah
+
+       * Config.py: Add a filter for reiserfsprogs
+         dangling-relative-symlink /sbin/fsck.reiserfs ../bin/true
+
+2001-07-18  Frederic Crozat <fcrozat at mandriva.com>
+
+       * MenuCheck.py: Add missing menu entries
+
+2001-07-15  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 15_Jul
+       * Config.py: added exceptions for egcs.
+       * rpmlint.spec: 0.34-1mdk
+       * rpmlint.py: added -a option to check all the installed packages.
+         
+         bumped the version to 0.34.
+       * TagsCheck.py: added missing descriptions.
+         
+         corrected the -devel warning to handle the libbzip2_1-devel case.
+       * BinariesCheck.py, DistributionCheck.py, FilesCheck.py,
+         InitScriptCheck.py, MenuCheck.py, SignatureCheck.py,
+         SourceCheck.py, SpecCheck.py: added missing descriptions.
+       * Pkg.py: authorize to pass an rpm header to the InstalledPkg
+         constructor.
+       * Filter.py: don't print the description if the error/warning is
+         filtered.
+
+2001-07-11  Frederic Crozat <fcrozat at mandriva.com>
+
+       * Config.py: userhelper (from usermode) is authorized to be setuid
+
+2001-07-06  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: added two more language codes
+
+2001-07-06  Christian Belisle
+
+       * rpmlint.spec: 0.33-2mdk
+       * ChangeLog: Generated by cvs2cl the 06_Jul
+       * rpmlint.spec: Version 0.33-2mdk, Added descriptions
+       * ChangeLog: Generated by cvs2cl the 06_Jul
+       * TagsCheck.py: Added descriptions
+
+2001-07-05  Christian Belisle
+
+       * TagsCheck.py: Added descriptions.
+       * TagsCheck.py: Added entries for descriptions.
+       * SpecCheck.py: Added descriptions.
+
+2001-07-04  Pablo Saratxaga <pablo at mandriva.com>
+
+       * ChangeLog, I18NCheck.py: updated I18NCheck.py file
+
+2001-07-04  Frédéric Lepied <flepied at mandriva.com>
+
+       * I18NCheck.py: added nn as a valid subdir of /usr/share/local.
+
+2001-07-03  Christian Belisle
+
+       * SpecCheck.py: Added entries for the descriptions
+       * MenuCheck.py: Added entries to put descriptions.
+       * SourceCheck.py: Added descriptions.
+       * SignatureCheck.py: Added a description.
+       * LSBCheck.py: Added descriptions
+
+2001-07-02  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 02_Jul
+       * rpmlint.spec: 0.33-1mdk
+       * rpmlint.py: 0.33
+       * ChangeLog: Generated by cvs2cl the 02_Jul
+       * Config.py: added library policy exceptions
+       * BinariesCheck.py: removed debug trace
+
+2001-06-27  Christian Belisle
+
+       * InitScriptCheck.py: Added descriptions.
+       * FilesCheck.py: Added descriptions.
+
+2001-06-26  Christian Belisle
+
+       * ConfigCheck.py, DistributionCheck.py, FHSCheck.py: Added
+         descriptions.
+
+2001-06-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * BinariesCheck.py: new check for files which can cause upgrade
+         problems in the library packages.
+       * TagsCheck.py: try to check alpha/beta/pre version use.
+
+2001-06-20  Frédéric Lepied <flepied at mandriva.com>
+
+       * Filter.py: print description only if they aren't empty.
+       * TagsCheck.py: added a check for invalid version.
+       * SpecCheck.py: added a check for obsolete tags.
+       * PostCheck.py: described one-line-command-in warnings.
+
+2001-06-19  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: added named user and group to the exception list.
+
+2001-06-19  Christian Belisle
+
+       * FHSCheck.py, I18NCheck.py, InitScriptCheck.py, LSBCheck.py,
+         rpmlint.spec: Added few descriptions
+       * ChangeLog: Generated by cvs2cl the 19_Jun
+       * DistributionCheck.py: Added few descriptions
+
+2001-06-18  Christian Belisle
+
+       * DistributionCheck.py: Added few descriptions
+       * ConfigCheck.py: Added few descriptions
+       * FilesCheck.py: Added few descriptions
+
+2001-06-15  Chmouel Boudjnah
+
+       * Config.py: Add more filesystem exclude.
+
+2001-06-14  Chmouel Boudjnah
+
+       * Config.py: /var/run/usb as 700 is normal.
+
+2001-06-13  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 13_Jun
+       * rpmlint.spec: 0.32-1mdk
+       * README: changed Linux-Mandrake => Mandrake Linux as default
+         Distribution tag.
+       * ChangeLog: Generated by cvs2cl the 13_Jun
+       * rpmlint.py: corrected copyright statement
+       * DistributionCheck.py: changed Linux-Mandrake => Mandrake Linux as
+         default Distribution tag.
+       * MenuCheck.py: added new Office sub menus.
+
+2001-06-12  Chmouel Boudjnah
+
+       * ChangeLog: Generated by cvs2cl the 12_Jun
+       * FilesCheck.py: Add /etc/profile.d/.
+
+2001-06-06  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.py: If the file given on the command line doesn't exist,
+         try to use the name as an installed package to check.
+       * TagsCheck.py: add error desccriptions only when -i is given on
+         the command line.
+       * FilesCheck.py: added /usr/X11R6/man subdirs to the list of
+         STANDARD_DIRS.
+         
+         warn for .so file only if they are in a lib dir.
+         
+         warn for source files in a non devel package only if they are not
+         a doc file.
+       * BinariesCheck.py: corrected soname regexp.
+         
+         document errors.
+       * SignatureCheck.py: use checkSignature from the Pkg class to avoid
+         calling rpm directly to support the installed packages.
+       * Pkg.py: created InstalledPkg class to access already installed
+         packages.
+
+2001-05-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: added an example of details use.
+       * rpmlint.py: added -i/--info command line option to print details
+         of warings/errors.
+       * Filter.py: added functions to print/store details of
+         warnings/errors.
+       * Config.py: added info global variable.
+
+2001-05-22  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: corrected description-line-too-long check.
+
+2001-05-20  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: add the rpm user and group per request of Jeff
+         Johnson for the future version of rpm.
+
+2001-05-18  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 18_May
+       * rpmlint.py, rpmlint.spec: 0.31
+       * Makefile: added rules to build test and release rpms.
+       * Config.py: exceptions for XFree86
+       * Config.py: added various exceptions
+       * ChangeLog: Generated by cvs2cl the 18_May
+       * PostCheck.py: check that a script isn't a oneliner.
+       * PostCheck.py: check postin and prein instead of postun and preun
+         for ghost files creation.
+       * MenuCheck.py: don't check NO_XALF in menu command
+       * check-install.py: factorized checks
+       * ChangeLog: Generated by cvs2cl the 18_May
+
+2001-04-01  Chmouel Boudjnah
+
+       * ChangeLog: Generated by cvs2cl the 01_Apr
+       * FilesCheck.py: Add rpcuser
+
+2001-03-15  Chmouel Boudjnah
+
+       * Config.py: Expections for ldconfig.
+       * Config.py: Some more exeptions for initscripts.
+       * Config.py: Add some Execptions for initscripts.
+
+2001-02-28  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: check length of summary and description lines
+
+2001-02-21  Chmouel Boudjnah
+
+       * Config.py: netkit-base and iputils is the same for Filter.
+
+2001-02-16  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: 0.30-1mdk
+       * ChangeLog: Generated by cvs2cl the 16_Feb
+       * rpmlint.py: 0.30
+       * Config.py: exception for autoconf and libclanlib0-gl.
+       * InitScriptCheck.py: check if runlevels are set
+       * LSBCheck.py: also check source packages.
+       * MenuCheck.py: added support to check launchers.
+       * Pkg.py: added req_names to retrieve the list of packages names
+         (requires+prereq).
+       * TagsCheck.py: changed Window Maker to WindowMaker
+
+2001-02-13  Frédéric Lepied <flepied at mandriva.com>
+
+       * I18NCheck.py: check subdirs of /sur/share/man.
+
+2001-02-02  Frédéric Lepied <flepied at mandriva.com>
+
+       * PostCheck.py: check that the postun creates the ghost files
+       * PostCheck.py: added install to dangerous commands
+       * LSBCheck.py: first version
+
+2001-01-23  Chmouel Boudjnah
+
+       * ChangeLog: Generated by cvs2cl the 23_Jan
+       * TagsCheck.py: Add https as valid url.
+
+2000-12-13  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: used list of licenses from
+         www.opensource.org/licenses
+
+2000-12-07  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: check the full license before splitting in it
+         multiple parts.
+       * rpmlint.py, rpmlint.spec: 0.29
+
+2000-12-07  Chmouel Boudjnah
+
+       * ChangeLog: Generated by cvs2cl the 07_Dec
+       * PostCheck.py: Add /sbin/sash as VALID_SHELLS.
+
+2000-12-06  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: added exceptions for dev.
+       * FilesCheck.py: check dangling-symlink in the file index too to
+         avoid missing special files that aren't created when extracted as
+         a user.
+       * FilesCheck.py: removed trace.
+       * Config.py: cleaned header.
+       * README: added description of DanglingSymlinkExceptions.
+       * FilesCheck.py: added a generic way to avoid dangling-symlink
+         warnings.
+       * TagsCheck.py: for devel packages, check dependency on lib package
+         only when a .so file is present.
+
+2000-11-29  Chmouel Boudjnah
+
+       * Config.py: addFilter W: dev86-devel no-provides dev8-devel on
+         this warning.
+       * Config.py: add some execptions for pam (0750 dir for /etc/default
+         is normal as weel to have gpasswd and chage as suid).
+       * Config.py: the dangling symlink in dev are not dangled they are
+         relatives !!!
+       * Config.py: Don't check info-file-with-install-info for bash since
+         it's by default in the dir file.
+
+2000-11-24  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: 0.28-1mdk
+       * ChangeLog: Generated by cvs2cl the 24_Nov
+       * rpmlint.py: 0.28
+       * TagsCheck.py: check -devel package naming scheme only on binary
+         packages.
+       * SourceCheck.py: only check compression on tar or diff files.
+       * Config.py: various exceptions added.
+       * TagsCheck.py: report a warning if a -devel package comes with no
+         major in its name. added python licence and public domain. check
+         syntax of url tag.
+       * BinariesCheck.py: report the file location on objdump errors. new
+         error: executable in library package.
+
+2000-11-23  Frédéric Lepied <flepied at mandriva.com>
+
+       * I18NCheck.py: fuzzy check on packages without dependency on
+         locales
+       * FilesCheck.py: check if a package provides sources.
+       * PostCheck.py: force a separator before dangerous command.
+
+2000-11-13  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: 0.27-1mdk
+       * ChangeLog: Generated by cvs2cl the 13_Nov
+       * rpmlint.py: 0.27
+       * FilesCheck.py: don't warn if a games is setgid games.
+       * README: RpmGamesGroup added to the list of available options.
+       * Config.py: added axception for xman.
+       * BinariesCheck.py: check ldconfig symlinks.
+
+2000-11-11  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: don't check no-version-in-changelog for source rpm.
+
+2000-11-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: 0.26
+       * ChangeLog: Generated by cvs2cl the 10_Nov
+       * rpmlint.py: 0.26
+       * Config.py: added various exceptions.
+       * TagsCheck.py: allow multiple licenses.
+         
+         don't report anymore the package-provides-itself warning because
+         it's the default in rpm 4.
+         
+         try to not report incoherent-version-in-changelog for
+         sub-packages.
+       * MenuCheck.py: correct the non-transparent-xpm check.
+       * FilesCheck.py: don't report buggy length-symlink anymore.
+
+2000-10-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: don't check if package provides itself because rpm
+         4.0 always does it.
+
+2000-10-17  Chmouel Boudjnah
+
+       * Config.py: Fix exception for glibc.
+
+2000-10-16  Frédéric Lepied <flepied at mandriva.com>
+
+       * check-install.py: first version.
+       * Pkg.py: added the possibility to create a Pkg object directly
+         from an rpm header.
+
+2000-10-12  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: corrected changelog
+       * rpmlint.spec: corrected changelog
+       * rpmlint.spec: 0.25-1mdk
+       * ChangeLog: Generated by cvs2cl the 12_Oct
+       * rpmlint.py: 0.25
+       * Config.py: added exception for sympa, rpm and bcast.
+       * TagsCheck.py: check that devel package depends on the base
+         package with the same version. check that summary begins with a
+         capital letter.
+       * PostCheck.py: check dangerous commands. check reference to ~ or
+         $HOME.
+       * SourceCheck.py: cleanup.
+       * MenuCheck.py: check that titles and longtitles begin by a capital
+         letter. check that no version is included in title and longtitle.
+       * FilesCheck.py: check package owning system dirs.
+
+2000-10-11  Frédéric Lepied <flepied at mandriva.com>
+
+       * SpecCheck.py: check name of spec file.
+       * README: added description of SpecCheck.
+       * Config.py: added SpecCheck to DEFAULT_CHECKS.
+       * SpecCheck.py: check use of $RPM_SOURCE_DIR.
+       * SpecCheck.py: first version
+
+2000-10-10  Chmouel Boudjnah
+
+       * MenuCheck.py: /lib/cpp errors to /dev/null for new cpp.
+
+2000-10-02  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.py, rpmlint.spec: 0.24
+       * ChangeLog: Generated by cvs2cl the 02_Oct
+       * FilesCheck.py: added apache and postgres to standard groups.
+       * TagsCheck.py: spell check a la Debian.
+
+2000-09-29  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.py, rpmlint.spec: 0.23
+       * ChangeLog: Generated by cvs2cl the 29_Sep
+       * MenuCheck.py: added Applications/Accessibility. check that menu
+         file are readable by everyone.
+       * Config.py: * removed exception for /home. * added exceptions for
+         vixie-cron.
+
+2000-09-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: check cvs internal files.
+
+2000-09-12  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: 0.22
+       * ChangeLog: Generated by cvs2cl the 12_Sep
+       * rpmlint.py: 0.22
+       * PostCheck.py: print a warning on empty script.
+       * FilesCheck.py: added postgres and apache to default users.
+       * Config.py: added libwmf and doxygen as dev packages.
+         
+         info/dir exception for info-install package.
+       * README, TagsCheck.py: added bugs@linux-mandrake.com as a valid
+         packager address.
+
+2000-09-06  Pixel <pixel at mandriva.com>
+
+       * ChangeLog: *** empty log message ***
+       * I18NCheck.py: check *.mo for file-not-in-%lang, not only in
+         /usr/share/locale
+
+2000-09-05  Frédéric Lepied <flepied at mandriva.com>
+
+       * MenuCheck.py, TagsCheck.py: replaced Networking/ICQ group with
+         Networking/Instant messaging.
+
+2000-08-31  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: 0.21
+       * ChangeLog: Generated by cvs2cl the 31_Aug
+       * rpmlint.py: 0.21
+       * TagsCheck.py: check packager field compliance to a regexp.
+       * README: added description of the Packager option.
+       * Config.py: added exception for libwmf.
+       * ChangeLog: Generated by cvs2cl the 31_Aug
+       * README: removed XpmIconPath.
+       * Config.py: imported default exceptions.
+       * config: move standard exceptions to Config.py.
+       * TagsCheck.py: added Apache License, PHP Licence and BSD-Style.
+       * MenuCheck.py: check hardcoded path in icon field and large, mini,
+         normal icon files.
+
+2000-08-28  Chmouel Boudjnah
+
+       * ChangeLog: Generated by cvs2cl the 28_Aug
+       * PostCheck.py: Fix typo in check of /usr/bin/perl.
+       * ChangeLog: Generated by cvs2cl the 28_Aug
+       * PostCheck.py: Check perl script like we do for bash script.
+
+2000-08-28  Pablo Saratxaga <pablo at mandriva.com>
+
+       * I18NCheck.py: updated locales list
+
+2000-08-26  Chmouel Boudjnah
+
+       * ChangeLog: Generated by cvs2cl the 26_Aug
+       * FilesCheck.py: Only check perl_temp_file in a /perl/ directory.
+
+2000-08-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.py, rpmlint.spec: 0.20
+       * ChangeLog: Generated by cvs2cl the 25_Aug
+       * Config.py: added InitScriptCheck.
+       * InitScriptCheck.py: first version.
+       * config: added exceptions for InitScriptCheck.
+       * README: added InitScriptCheck description.
+       * PostCheck.py: check where a script is present that the shell is
+         valid.
+       * FilesCheck.py: moved /etc/rc.d/init.d checks to InitScriptCheck.
+       * ConfigCheck.py: report warnings for app-defaults only in
+         /usr/X11R6/lib/X11/app-defaults.
+       * BinariesCheck.py: report the rpath warning if directory isn't a
+         sub-directory of /usr/lib/.
+
+2000-08-18  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: I18NCheck is back.
+       * ChangeLog, rpmlint.spec: 0.19
+       * rpmlint.py: 0.19
+       * README: added SystemLibPaths mention.
+       * BinariesCheck.py: check rpath only on system lib paths (ie /lib,
+         /usr/lib and /usr/X11R6/lib). This can be configured with the
+         SystemLibPaths option.
+       * Pkg.py: added fileLang to retrieve the lang associated to a file.
+
+2000-08-17  Frédéric Lepied <flepied at mandriva.com>
+
+       * I18NCheck.py: warn if .mo is not registered in %lang.
+       * MenuCheck.py: protected kdesu check.
+       * FilesCheck.py: check perl temporary files.
+
+2000-08-16  Frédéric Lepied <flepied at mandriva.com>
+
+       * README, rpmlint.py: added ExtractDir option usable in the config
+         file.
+       * PostCheck.py: check ] in if statement. report warning for a
+         percent.
+
+2000-08-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: Generated by cvs2cl the 10_Aug
+       * rpmlint.spec: 0.18-1mdk
+       * TagsCheck.py: check for valid licence.
+       * README: added ValidLicenses.
+       * rpmlint.py: 0.18
+       * ChangeLog: Generated by cvs2cl the 10_Aug
+       * ConfigCheck.py: check files without no-replace flag.
+       * Pkg.py: added noreplaceFiles()
+       * MenuCheck.py: allow depency on kdesu to point directly to
+         /usr/bin/kdesu.
+
+2000-08-08  Frédéric Lepied <flepied at mandriva.com>
+
+       * FHSCheck.py: allow ftp and www in var (from upcoming FHS 2.2).
+       * rpmlint.py: 0.17
+       * ChangeLog: Generated by cvs2cl the 08_Aug
+       * rpmlint.spec: 0.17-1mdk
+       * FilesCheck.py: corrected check for install_info to avoid
+         backtrace on empty postun or preun.
+       * rpmlint.spec: * 0.17
+
+2000-08-07  Frédéric Lepied <flepied at mandriva.com>
+
+       * ScriptCheck.py: * replaced by PostCheck.py
+
+2000-08-03  Chmouel Boudjnah
+
+       * config: Add few filters for util-linux
+
+2000-08-01  Chmouel Boudjnah
+
+       * config: Correct pam setuid-binary execptions.
+       * config: mount and umount are suid binary.
+
+2000-07-31  Chmouel Boudjnah
+
+       * ChangeLog: Generated by cvs2cl the 31_Jul
+
+2000-07-31  Frédéric Lepied <flepied at mandriva.com>
+
+       * Config.py: * (DEFAULT_CHECKS): removed ScriptCheck.
+       * PostCheck.py: * merged ScriptCheck.py and corrected shell script
+         check.
+       * FilesCheck.py: * allow install-info call in preun. * check
+         chkconfig calls for package that comes with an
+         /etc/rc.d/init.d/script.
+
+2000-07-29  Chmouel Boudjnah
+
+       * ChangeLog: Generated by cvs2cl the 29_Jul
+       * MenuCheck.py: If we use kdesu check in it present in Requires:
+         Prereq:
+       * MenuCheck.py: Fix again kdesu (i hate python indentation :-()
+       * MenuCheck.py: Get kdesu check to work :-\
+       * ChangeLog: Generated by cvs2cl the 29_Jul
+       * Makefile: Add a changelog rules to be used with cvs2cl.
+       * Config.py, ScriptCheck.py: check syntax of (post|pre)(un)?install
+         script, currently only bash is supported.
+
+2000-07-28  Chmouel Boudjnah
+
+       * FilesCheck.py: install-info can't be RPMTAG_POSTUNPROG or
+         RPMTAG_POSTPROG
+
+2000-07-27  Chmouel Boudjnah
+
+       * config: Add e2fsprogs changes.
+
+2000-07-25  Pixel <pixel at mandriva.com>
+
+       * ChangeLog: no_comment
+       * config: add exception for some devel packages, updated the filter
+         for setuid perl's
+
+2000-07-21  Chmouel Boudjnah
+
+       * config: Add some glibc exceptions.
+
+2000-07-20  Chmouel Boudjnah
+
+       * FilesCheck.py: if there is info files check to see if we have a
+         install-info in the %post(un)?
+
+2000-07-19  Chmouel Boudjnah
+
+       * config: filter me the "shadow-utils dangling-symlink /usr/bin/sg
+         newgrp"
+       * config: static binaries for ldconfig is permit :-)
+       * config: Add some exeptions for kernel packages.
+       * config: /bin/ping as suid binary is correct.
+
+2000-07-19  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog, rpmlint.spec: * 0.16
+       * rpmlint.py: * 0.16
+       * TagsCheck.py: * (DEFAULT_VALID_GROUPS): sawmill => sawfish.
+       * Config.py: * added FHSCheck by default.
+       * config: * added exception for wall.
+       * FHSCheck.py: * corrected check to not match substrings.
+       * FilesCheck.py: * added check dandling symlinks. * check the
+         presence of /usr(/share)/info/dir
+
+2000-07-19  Chmouel Boudjnah
+
+       * config: Exception for pam package.
+
+2000-07-19  Frédéric Lepied <flepied at mandriva.com>
+
+       * README.CVS: * give command line example.
+
+2000-07-19  Chmouel Boudjnah
+
+       * MenuCheck.py: If the menu_command contain a kdesu -c "", check
+         instead for the command instead of kdesu.
+       * FilesCheck.py: By default {doc,man,info} in /usr/share, product
+         an error when the package use /usr/.
+       * config: su is suid and it normal !!
+
+2000-07-05  Frédéric Lepied <flepied at mandriva.com>
+
+       * README: * added description for PostCheck.
+       * Config.py: * added PostCheck
+       * PostCheck.py: * first version.
+
+2000-07-01  Chmouel Boudjnah
+
+       * config: Remove lftp .So warning (should be fixed in rpmlint), add
+         expeption for slocate.
+
+2000-06-30  Chmouel Boudjnah
+
+       * config: Add sudo exception.
+
+2000-06-27  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog, rpmlint.spec: * 0.15
+       * rpmlint.py: * 0.15
+       * README: * added XpmIconPath.
+       * MenuCheck.py: * check non transparent pixmaps in icon path.
+       * config: * added man for sgid exception. * corrected the regex for
+         /var/catman and /usr/man.
+       * BinariesCheck.py: * added a check for soname.
+       * TagsCheck.py: * removed trace.
+       * TagsCheck.py: * added a warning for packages that provide
+         themselves (for Pixel).
+       * Pkg.py: * (_gatherDepInfo) corrected the conflicts and provides
+         acquisition.
+
+2000-06-16  Chmouel Boudjnah
+
+       * config: add W: lftp shared-lib-without-dependency-information
+         /usr/lib/lftp/.*.so
+
+2000-06-15  Frédéric Lepied <flepied at mandriva.com>
+
+       * Pkg.py: * added a builtin grep.
+       * config: * removed stange-needs filters.
+       * README: * added ExtraMenuNeeds
+       * MenuCheck.py: * added a list of valid needs.
+
+2000-06-15  Chmouel Boudjnah
+
+       * config: add execption for "I: iceconf strange-needs icewm
+         /usr/lib/menu/iceconf"
+
+2000-04-17  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog, rpmlint.py, rpmlint.spec: * 0.14
+       * FilesCheck.py, MenuCheck.py: * corrected check of %post, %postun
+         to avoid comments.
+       * MenuCheck.py: * check old menu entries for KDE and GNOME. * allow
+         entries for sections.
+       * config: * added exceptions for urpmi, sash, octave, ghc,
+         procmail, rsh.
+
+2000-04-12  Frédéric Lepied <flepied at mandriva.com>
+
+       * Pkg.py: * (Pkg._extract): extract in dir
+         <tmppath>/<pkgname>.<pid>
+
+2000-04-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog, rpmlint.py, rpmlint.spec: * 0.13
+       * config: * added exception for XFree86 4.0 .a modules.
+       * ChangeLog: * 0.13
+       * MenuCheck.py: * use POSTINPROG if no POSTIN. * use POSTUNPROG if
+         no POSTUN.
+       * FilesCheck.py: * check ldconfig in %post and %postun. * added
+         urpmi to default group list.
+
+2000-04-07  Chmouel Boudjnah
+
+       * rpmlint.spec: Use %{_tmppath}
+       * ChangeLog: "Seethechangelog"
+       * MenuCheck.py: Add check on icons, if no icon print a warning, if
+         icon specified (with a long path, not with relative path) is not
+         here print a Error.
+       * FilesCheck.py: package -source are also devel package.
+
+2000-04-05  Chmouel Boudjnah
+
+       * config: traceroute need to be setuid-binary
+       * config: Add exception rules for initscripts.
+       * ChangeLog: "Seethechangelog"
+       * MenuCheck.py: Move Applications/Terminals to Terminals
+
+2000-04-04  Chmouel Boudjnah
+
+       * ChangeLog: *** empty log message ***
+       * MenuCheck.py: Add Session/Windowmanagers in List of good
+         window-managers.
+
+2000-04-03  Chmouel Boudjnah
+
+       * config: Add hackkernel* like kernel* for the
+         devel-file-in-non-devel-package stuff.
+
+2000-03-31  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog, rpmlint.py: * 0.12
+       * rpmlint.spec: * 1.12
+       * MenuCheck.py: * check update-menus in %post and %postun if a menu
+         is present.
+       * config: * avoid I: strange-needs kde for package beginning by k.
+       * MenuCheck.py: * corrected default menu (thanks to DindinX).
+
+2000-03-30  Chmouel Boudjnah
+
+       * config: add /mnt/disk like /mnt/(floppy|cdrom) and clean up
+         regex.
+
+2000-03-29  Frédéric Lepied <flepied at mandriva.com>
+
+       * MenuCheck.py: * check that the command is present in the menu.
+       * BinariesCheck.py: * check for non sparc32 binaries in sparc rpms.
+
+2000-03-27  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog, rpmlint.py, rpmlint.spec: * 0.11
+       * README: * added MenuCheck.
+       * MenuCheck.py: * valid sections are now a configuration variable.
+       * config: * added an exception for MenuCheck.
+
+2000-03-23  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: * commented out non-empty-dir-listed.
+       * Config.py: * (DEFAULT_CHECKS): added MenuCheck.
+
+2000-03-23  Chmouel Boudjnah
+
+       * config: don't check devel-file-in-non-devel-package for
+         alsa-source
+       * config: it's allow to have a tmpdir as 700 in etcskel and
+         rootfile.
+       * config: Don't check for config files on /root/*.
+
+2000-03-20  Frédéric Lepied <flepied at mandriva.com>
+
+       * MenuCheck.py: * first version.
+
+2000-03-14  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: * corrected Group header.
+       * config: * added exceptions for devel-file-in-non-devel-package
+         and dir-or-file-in-home.
+       * rpmlint.spec: * 1.10.
+       * ChangeLog: * 1.10
+       * rpmlint.py: * 0.10.
+
+2000-03-13  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: * corrected new group list.
+       * FilesCheck.py: * check files on /home.
+
+2000-03-09  Chmouel Boudjnah
+
+       * FilesCheck.py: Correct cdwriters to cdwriter Add x10 group.
+
+2000-03-06  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: * changed the list of standard groups.
+       * FilesCheck.py: * (FilesCheck.check): added a check for .h and .a
+         files and symbolic link .so in non devel packages.
+
+2000-02-28  Frédéric Lepied <flepied at mandriva.com>
+
+       * Pkg.py: * real correction for rpm 3.0.4 (I hope).
+       * rpmlint.py: * 0.9.2.
+       * ChangeLog, rpmlint.spec: * 0.9.2
+       * Pkg.py: * corrected rpm 3.0.4 support.
+
+2000-02-23  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: * 0.9.1.
+       * rpmlint.spec: * 0.9.1-1mdk.
+       * Makefile: * added README.CVS.
+       * rpmlint.py: * 0.9.1 * changed copyright year.
+       * Pkg.py: * added support for the rpm 3.0.4 way to store file
+         names.
+       * README.CVS: * first version.
+
+2000-02-10  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: * 0.9.
+       * ChangeLog: * forgot comment about SignatureCheck.
+       * SignatureCheck.py: * added gpg to correct signatures.
+       * ChangeLog: * 0.9.
+       * rpmlint.py: * 0.9.
+       * README: * added description of new options: ValidGroups,
+         ReleaseExtension and UseVersionInChangelog.
+       * config: * added commented examples for ReleaseExtension and
+         ValidGroups.
+       * TagsCheck.py: * (check): check release extension. * (check):
+         added configuration option for version on changelog and release
+         extension.
+       * DistributionCheck.py: * first version.
+       * Makefile: * install .py files too.
+       * Config.py: * change MandrakeCheck to DistributionCheck.
+       * MandrakeCheck.py: * renamed in DistributionCheck.
+
+2000-01-24  Frédéric Lepied <flepied at mandriva.com>
+
+       * FilesCheck.py: * added a check on non executable in bin
+         directories.
+
+1999-12-30  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: * 0.8-1mdk.
+       * ChangeLog, rpmlint.py: * 0.8.
+       * README: * change email. * added a line about i18n checks.
+       * config: * added exception for sympa, postgresql-test and
+         filesystem.
+       * MandrakeCheck.py: * change default distribution to
+         Linux-Mandrake.
+       * TagsCheck.py: * (TagsCheck.check): added check on version in the
+         first line of the changelog.
+       * FilesCheck.py: * change severity of reports.
+       * BinariesCheck.py: * binaries not stripped is dowgraded to
+         warning.
+
+1999-12-14  Frédéric Lepied <flepied at mandriva.com>
+
+       * I18NCheck.py: * added a check on dirs containing LC_MESSAGES
+         catalogs.
+
+1999-11-30  Frédéric Lepied <flepied at mandriva.com>
+
+       * I18NCheck.py: * correct the locales subdir regex to capture only
+         correct names.
+
+1999-11-25  Frédéric Lepied <flepied at mandriva.com>
+
+       * I18NCheck.py: * initial release.
+
+1999-11-18  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: * real v 0.7
+       * MandrakeCheck.py: * don't warn about info/dir not compressed
+
+1999-11-16  Frédéric Lepied <flepied at mandriva.com>
+
+       * README: * updated to reflect the change to addFilter. * corrected
+         the description of the checks.
+
+1999-11-15  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog, rpmlint.spec: * 0.7
+       * Filter.py: * test a string against the filters.
+       * Pkg.py: * (cleanup): change access rights before removing the
+         package to prevent very bad packages from making rpmlint abort.
+       * TagsCheck.py: * peform all the check on source package too.
+       * config: * added /var/catman exceptions.
+       * Config.py: * filters are regexp now.
+       * rpmlint.py: * 0.7 * output through Filter.
+
+1999-10-27  Frédéric Lepied <flepied at mandriva.com>
+
+       * SourceCheck.py: * oops: added Filter import.
+       * TagsCheck.py: * (TagsCheck.check): verify the name tag and the
+         file name coherence.
+       * Config.py: * (DEFAULT_CHECKS): added SourceCheck.
+       * SourceCheck.py: * first version.
+
+1999-10-23  Frédéric Lepied <flepied at mandriva.com>
+
+       * ., .cvsignore: * added .flog and .bz2.
+       * rpmlint.spec: * 0.6.1.
+       * ChangeLog: * 0.6.1
+       * Makefile: * (all): use compile.py to byte compile files.
+       * compile.py: * first version.
+       * rpmlint.py: * 0.6.1
+       * rpmlint.spec: * 0.6.
+       * ChangeLog: * O.6.
+       * BinariesCheck.py, ConfigCheck.py, FHSCheck.py, MandrakeCheck.py,
+         SignatureCheck.py: * output via Filter.
+       * README: * added description of addFilter.
+       * Config.py: * don't use FHS check by default because rpm doesn't
+         put the doc files under /usr/share/doc => too much reports.
+       * rpmlint.py: * version 0.6.
+       * config: * added an example of addFilter.
+       * TagsCheck.py: * output via Filter. * (TagsCheck.check): checks if
+         the summary is on multiple lines.
+       * FilesCheck.py: * output via Filter. * added documentation checks.
+       * Filter.py: * first version.
+       * Config.py: * (addFilter isFiltered): new funtions for output
+         filters.
+
+1999-10-16  Frédéric Lepied <flepied at mandriva.com>
+
+       * ChangeLog: * 0.5.
+       * rpmlint.spec: * 0.5.
+       * rpmlint.py: * (version): 0.5. * uses Config to get the list of
+         checks.
+       * README: * added description of config files and options.
+       * MandrakeCheck.py: * uses Config options for vendor, ditribution
+         and compression.
+       * Makefile: * install the config file in /etc/rpmlint
+       * AllChecks.py: no more needed
+       * Config.py, FHSCheck.py, config: * first version.
+
+1999-10-12  Frédéric Lepied <flepied at mandriva.com>
+
+       * TagsCheck.py: * (TagsCheck.check): corrected the message for
+         non-standard-group to display the package name.
+       * Pkg.py: * (Pkg._gatherFilesInfo): gather ghost files.
+       * FilesCheck.py: * (FilesCheck.check): avoid reporting
+         non-conffile-in-etc for a ghost file. * (FilesCheck.check): added
+         a check of non standard users and groups.
+
+1999-10-11  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: * corrected typo.
+
+1999-10-08  Chmouel Boudjnah
+
+       * rpmlint.spec: *** empty log message ***
+
+1999-10-07  Frédéric Lepied <flepied at mandriva.com>
+
+       * rpmlint.spec: * (Requires): added cpio. * 0.4.
+       * rpmlint.py: * (version): 0.4.
+       * TagsCheck.py: * added a check for valid group name.
+       * README: * pgp check impemented.
+       * FilesCheck.py: * (FilesCheck.check): check only binary package.
+       * ConfigCheck.py: * (ConfigCheck.check): check only binary package.
+       * ChangeLog: * 0.4.
+       * BinariesCheck.py: * corrected error message when there is a
+         problem with objdump.
+       * AllChecks.py: * added SignatureCheck.
+       * SignatureCheck.py: * first version.
+
+1999-10-06  Frédéric Lepied <flepied at mandriva.com>
+
+       * PermissionsCheck.py: removed
+       * rpmlint.spec: * 0.3-1. * added version of needed dependencies.
+       * rpmlint.py: * changed the exception handling to have all the
+         traceback.
+       * rpmlint: * launch python with unbuffered output.
+       * README: * added name of check on the implemented part. * added
+         FileCheck to the implemented part.
+       * Pkg.py: * added comments. * extract all file listing in one place
+         (_gatherFilesInfo).
+       * Makefile: * install only .pyo files. * new target ndist to make a
+         tar ball without the version number in the directory name.
+       * INSTALL: * added cpio to the list of dependencies.
+       * ChangeLog: * 0.3.
+       * AllChecks.py: * added FilesCheck.
+       * ., .cvsignore, FilesCheck.py: * first version.
+       * rpmlint.spec: * added header. * 0.2-1.
+       * README: * changed configuration check comment from planed to
+         implemented.
+       * Pkg.py: (configFiles): new method to return the list of
+         configuration files.
+       * Makefile: * added ChangeLog to distribution files. * bzip2 in a
+         separate command to make the dist target run on system without
+         the y option of tar.
+       * AllChecks.py: * added ConfigCheck.
+       * ChangeLog, ConfigCheck.py: * first version.
+       * rpmlint.py: * 0.2
+
+1999-10-01  Chmouel Boudjnah
+
+       * AbstractCheck.py, AllChecks.py, BinariesCheck.py, COPYING,
+         INSTALL, Makefile, MandrakeCheck.py, PermissionsCheck.py, Pkg.py,
+         README, TagsCheck.py, rpmlint, rpmlint.py, rpmlint.spec: Initial
+         revision
+
+1999-10-01  
+
+       * .: New repository initialized by cvs2svn.
+
diff --git a/Config.py b/Config.py
new file mode 100644 (file)
index 0000000..29b124c
--- /dev/null
+++ b/Config.py
@@ -0,0 +1,161 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : Config.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Fri Oct 15 20:04:25 1999
+# Version       : $Id: Config.py 1871 2011-06-18 09:40:52Z scop $
+# Purpose       : handle configuration options. To be used from config files.
+#############################################################################
+
+import locale
+import os.path
+import re
+
+try:
+    from __version__ import __version__
+except ImportError:
+    __version__ = 'devel'
+
+DEFAULT_CHECKS = ("DistributionCheck",
+                  "TagsCheck",
+                  "BinariesCheck",
+                  "ConfigCheck",
+                  "FilesCheck",
+                  "DocFilesCheck",
+                  "FHSCheck",
+                  "SignatureCheck",
+                  "I18NCheck",
+                  "MenuCheck",
+                  "PostCheck",
+                  "InitScriptCheck",
+                  "SourceCheck",
+                  "SpecCheck",
+                  "NamingPolicyCheck",
+                  "ZipCheck",
+                  "PamCheck",
+                  "RpmFileCheck",
+                  "MenuXDGCheck",
+                  )
+
+USEUTF8_DEFAULT = False
+try:
+    if locale.getpreferredencoding() == 'UTF-8':
+        USEUTF8_DEFAULT = True
+except:
+    try:
+        if re.match('utf', locale.getdefaultlocale()[1], re.I):
+            USEUTF8_DEFAULT = True
+    except:
+        pass
+
+info = False
+no_exception = False
+
+# handle the list of checks to load
+_checks = []
+_checks.extend(DEFAULT_CHECKS)
+
+def addCheck(check):
+    check = re.sub('\.py[co]?$', '', check)
+    if check not in _checks:
+        _checks.append(check)
+
+def allChecks():
+    if _checks == []:
+        defaultChecks()
+    return _checks
+
+def defaultChecks():
+    resetChecks()
+    _checks.extend(DEFAULT_CHECKS)
+
+def resetChecks():
+    global _checks
+
+    _checks = []
+
+# handle the list of directories to look for checks
+
+_dirs = ["/usr/share/rpmlint"]
+
+def addCheckDir(dir):
+    d = os.path.expanduser(dir)
+    if d not in _dirs:
+        _dirs.insert(0, d)
+
+def checkDirs():
+    return _dirs
+
+# handle options
+
+_options = {}
+
+def setOption(name, value):
+    _options[name] = value
+
+def getOption(name, default = ""):
+    try:
+        return _options[name]
+    except:
+        return default
+
+# List of filters
+_filters = []
+_filters_re = None
+
+def addFilter(s):
+    global _filters_re
+
+    _filters.append(s)
+    _filters_re = None
+
+def removeFilter(s):
+    global _filters_re
+
+    try:
+        _filters.remove(s)
+    except:
+        pass
+    else:
+        _filters_re = None
+
+_scoring = {}
+
+def setBadness(s, score):
+    _scoring[s] = score
+
+def badness(s):
+    return _scoring.get(s, 0)
+
+_non_named_group_re = re.compile('[^\\](\()[^:]')
+def isFiltered(s):
+    global _filters_re
+
+    if _filters_re == None:
+        # no filter
+        if len(_filters) == 0:
+            return False
+        _filters_re = '(?:' + _filters[0] + ')'
+
+        for idx in range(1, len(_filters)):
+            # to prevent named group overflow that happen when there is too
+            # many () in a single regexp: AssertionError: sorry, but this
+            # version only supports 100 named groups
+            if '(' in _filters[idx]:
+                _non_named_group_re.subn('(:?', _filters[idx])
+            _filters_re = _filters_re + '|(?:' + _filters[idx] +')'
+        _filters_re = re.compile(_filters_re)
+
+    if not no_exception:
+        if _filters_re.search(s):
+            return True
+    return False
+
+# Config.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/ConfigCheck.py b/ConfigCheck.py
new file mode 100644 (file)
index 0000000..42296d6
--- /dev/null
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : ConfigCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Sun Oct  3 21:48:20 1999
+# Version       : $Id: ConfigCheck.py 1774 2010-04-20 20:07:10Z scop $
+# Purpose       :
+#############################################################################
+
+from Filter import addDetails, printError, printWarning
+import AbstractCheck
+
+
+class ConfigCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, "ConfigCheck")
+
+    def check(self, pkg):
+        # Check only binary package
+        if pkg.isSource():
+            return
+
+        config_files = pkg.configFiles()
+        noreplace_files = pkg.noreplaceFiles()
+
+        for c in config_files:
+            if c.startswith("/var/lib/games/"):
+                printError(pkg, "score-file-must-not-be-conffile", c)
+            elif not c.startswith("/etc/") and not c.startswith("/var/"):
+                printWarning(pkg, "non-etc-or-var-file-marked-as-conffile", c)
+
+            if c not in noreplace_files:
+                printWarning(pkg, "conffile-without-noreplace-flag", c)
+
+# Create an object to enable the auto registration of the test
+check = ConfigCheck()
+
+# Add information about checks
+addDetails(
+'score-file-must-not-be-conffile',
+"""A file in /var/lib/games/ is a configuration file. Store your conf
+files in /etc instead.""",
+
+'non-etc-or-var-file-marked-as-conffile',
+"""A file not in /etc or /var is marked as being a configuration file.
+Please put your conf files in /etc or /var.""",
+
+'conffile-without-noreplace-flag',
+"""A configuration file is stored in your package without the noreplace flag.
+A way to resolve this is to put the following in your SPEC file:
+
+%config(noreplace) /etc/your_config_file_here
+""",
+
+)
+
+# ConfigCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/DistributionCheck.py b/DistributionCheck.py
new file mode 100644 (file)
index 0000000..05f8f29
--- /dev/null
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : DistributionCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Tue Sep 28 00:05:33 1999
+# Version       : $Id: DistributionCheck.py 1732 2010-02-21 11:28:42Z scop $
+# Purpose       : check the Distribution specificities in a binary rpm package.
+#############################################################################
+
+import re
+
+import rpm
+
+from Filter import addDetails, printWarning
+import AbstractCheck
+import Config
+
+
+man_regex = re.compile("/man(?:\d[px]?|n)/")
+info_regex = re.compile("(/usr/share|/usr)/info/")
+vendor = Config.getOption("Vendor")
+distribution = Config.getOption("Distribution")
+compress_ext = Config.getOption("CompressExtension", "bz2")
+
+class DistributionCheck(AbstractCheck.AbstractCheck):
+
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, "DistributionCheck")
+
+    def check(self, pkg):
+        # Check only binary package
+        if pkg.isSource():
+            return
+
+        if vendor and pkg[rpm.RPMTAG_VENDOR] != vendor:
+            printWarning(pkg, "invalid-vendor", pkg[rpm.RPMTAG_VENDOR])
+
+        if distribution and pkg[rpm.RPMTAG_DISTRIBUTION] != distribution:
+            printWarning(pkg, "invalid-distribution",
+                         pkg[rpm.RPMTAG_DISTRIBUTION])
+
+        if compress_ext:
+            for fname in pkg.files():
+                if man_regex.search(fname):
+                    if not fname.endswith(compress_ext):
+                        printWarning(pkg, 'manpage-not-compressed',
+                                     compress_ext, fname)
+                elif info_regex.search(fname) and \
+                        not fname.endswith("/info/dir"):
+                    if not fname.endswith(compress_ext):
+                        printWarning(pkg, 'infopage-not-compressed',
+                                     compress_ext, fname)
+
+
+# Create an object to enable the auto registration of the test
+check = DistributionCheck()
+
+addDetails(
+'invalid-vendor',
+'''In the "%s" distribution, vendor should be "%s".''' % (distribution, vendor),
+
+'invalid-distribution',
+'The distribution value should be "' + distribution + '".',
+
+'manpage-not-compressed',
+'''This manual page is not compressed with the %s compression method
+(does not have the %s extension). If the compression does not happen
+automatically when the package is rebuilt, make sure that you have the
+appropriate rpm helper and/or config packages for your target distribution
+installed and try rebuilding again; if it still does not happen automatically,
+you can compress this file in the %%install section of the spec file.''' \
+% (compress_ext, compress_ext),
+
+'infopage-not-compressed',
+'''This info page is not compressed with the %s compression method
+(does not have the %s extension). If the compression does not happen
+automatically when the package is rebuilt, make sure that you have the
+appropriate rpm helper and/or config packages for your target distribution
+installed and try rebuilding again; if it still does not happen automatically,
+you can compress this file in the %%install section of the spec file.''' \
+% (compress_ext, compress_ext),
+)
+
+# DistributionCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/DocFilesCheck.py b/DocFilesCheck.py
new file mode 100644 (file)
index 0000000..4463579
--- /dev/null
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2005 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+import rpm
+
+from Filter import addDetails, printWarning
+import AbstractCheck
+
+
+class DocFilesCheck(AbstractCheck.AbstractCheck):
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, 'DocFilesCheck')
+
+    def __checkRequirements(self, pkg):
+
+        doc_files = pkg.docFiles()
+        files = pkg.files()
+
+        reqs = {}
+        for fname, pkgfile in files.items():
+            reqs[fname] = [x[0] for x in pkgfile.requires]
+
+        core_reqs = {}  # dependencies of non-doc files
+        doc_reqs  = {}  # dependencies of doc files
+
+        for dep in pkg.header.dsFromHeader():
+            # skip deps which were found by find-requires
+            if dep.Flags() & rpm.RPMSENSE_FIND_REQUIRES != 0:
+                continue
+            core_reqs[dep.N()] = []
+
+        # register things which are provided by the package
+        for i in pkg.header[rpm.RPMTAG_PROVIDES] + files.keys():
+            core_reqs[i] = []
+
+        for i in files:
+            if not reqs[i]:
+                continue # skip empty dependencies
+            if i in doc_files:
+                target = doc_reqs
+            else:
+                target = core_reqs
+
+            for r in reqs[i]:
+                if r not in target:
+                    target[r] = []
+                target[r].append(i)
+
+        # go through the calculated requirements of the %doc files
+        for (dep, req_files) in doc_reqs.items():
+            if dep not in core_reqs:
+                for f in req_files:
+                    printWarning(pkg, "doc-file-dependency", f, dep)
+
+    def __checkUnwantedFiles(self, pkg):
+
+        for docfile in pkg.docFiles():
+            if docfile.endswith("/INSTALL"):
+                printWarning(pkg, "install-file-in-docs", docfile)
+
+    def check(self, pkg):
+
+        if pkg.isSource() or not pkg.docFiles():
+            return
+
+        self.__checkRequirements(pkg)
+        self.__checkUnwantedFiles(pkg)
+
+
+check = DocFilesCheck()
+
+addDetails(
+'doc-file-dependency',
+'''An included file marked as %doc creates a possible additional dependency in
+the package.  Usually, this is not wanted and may be caused by eg. example
+scripts with executable bits set included in the package's documentation.''',
+
+'install-file-in-docs',
+'''A file whose name suggests that it contains installation instructions is
+included in the package.  Such instructions are often not relevant for already
+installed packages; if this is the case for this file and it does not contain
+any information that is of interest after the package has been built and
+installed, do not include the file in the binary package.''',
+)
+
+# DocFilesCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/FHSCheck.py b/FHSCheck.py
new file mode 100644 (file)
index 0000000..56bc65b
--- /dev/null
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : FHSCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Fri Oct 15 17:40:32 1999
+# Version       : $Id: FHSCheck.py 1578 2009-03-23 18:47:03Z scop $
+# Purpose       : check FHS conformity
+#############################################################################
+
+import re
+
+from Filter import addDetails, printWarning
+import AbstractCheck
+
+
+class FHSCheck(AbstractCheck.AbstractCheck):
+    usr_regex = re.compile("^/usr/([^/]+)/")
+    usr_subdir = ('X11R6', 'X386', 'bin', 'games', 'include', 'lib', 'lib64',
+                  'local', 'sbin', 'share', 'src', 'spool', 'tmp')
+    var_regex = re.compile("^/var/([^/]+)/")
+    var_fsstnd = ('adm', 'catman', 'local', 'named', 'nis', 'preserve')
+    var_subdir = ('account', 'lib', 'cache', 'crash', 'games', 'lock', 'log',
+                  'opt', 'run', 'spool', 'state', 'tmp', 'yp', 'www', 'ftp')
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, "FHSCheck")
+
+    def check(self, pkg):
+        # Check only binary package
+        if pkg.isSource():
+            return
+
+        var_list = []
+        usr_list = []
+
+        for fname in pkg.files():
+            s = FHSCheck.usr_regex.search(fname)
+            if s:
+                d = s.group(1)
+                if d not in FHSCheck.usr_subdir and d not in usr_list:
+                    printWarning(pkg, "non-standard-dir-in-usr", d)
+                    usr_list.append(d)
+            else:
+                s = FHSCheck.var_regex.search(fname)
+                if s:
+                    d = s.group(1)
+                    if d in var_list:
+                        continue
+                    if d in FHSCheck.var_fsstnd:
+                        printWarning(pkg, "FSSTND-dir-in-var", fname)
+                        var_list.append(d)
+                    elif d not in FHSCheck.var_subdir:
+                        printWarning(pkg, "non-standard-dir-in-var", d)
+                        var_list.append(d)
+
+# Create an object to enable the auto registration of the test
+check = FHSCheck()
+
+addDetails(
+'non-standard-dir-in-usr',
+"""Your package is creating a non-standard subdirectory in /usr. The standard
+directories are:
+%s.""" % ", ".join(FHSCheck.usr_subdir),
+
+'FSSTND-dir-in-var',
+"""Your package is creating an illegal directory in /var. The FSSTND (illegal)
+ones are:
+%s.""" % ", ".join(FHSCheck.var_fsstnd),
+
+'non-standard-dir-in-var',
+"""Your package is creating a non-standard subdirectory in /var. The standard
+directories are:
+%s.""" % ", ".join(FHSCheck.var_subdir),
+)
+
+# FHSCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/FilesCheck.py b/FilesCheck.py
new file mode 100644 (file)
index 0000000..eb314e0
--- /dev/null
@@ -0,0 +1,1341 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : FilesCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Mon Oct  4 19:32:49 1999
+# Version       : $Id: FilesCheck.py 1894 2011-11-24 21:56:18Z scop $
+# Purpose       : test various aspects on files: locations, owner, groups,
+#                 permission, setuid, setgid...
+#############################################################################
+
+from datetime import datetime
+import commands
+import os
+import re
+import stat
+import string
+
+import rpm
+
+from Filter import addDetails, printError, printWarning
+from Pkg import catcmd, is_utf8, is_utf8_str
+import AbstractCheck
+import Config
+
+
+# must be kept in sync with the filesystem package
+STANDARD_DIRS = (
+    '/',
+    '/bin',
+    '/boot',
+    '/etc',
+    '/etc/X11',
+    '/etc/opt',
+    '/etc/profile.d',
+    '/etc/skel',
+    '/etc/xinetd.d',
+    '/home',
+    '/lib',
+    '/lib/modules',
+    '/lib64',
+    '/media',
+    '/mnt',
+    '/mnt/cdrom',
+    '/mnt/disk',
+    '/mnt/floppy',
+    '/opt',
+    '/proc',
+    '/root',
+    '/sbin',
+    '/selinux',
+    '/srv',
+    '/sys',
+    '/tmp',
+    '/usr',
+    '/usr/X11R6',
+    '/usr/X11R6/bin',
+    '/usr/X11R6/doc',
+    '/usr/X11R6/include',
+    '/usr/X11R6/lib',
+    '/usr/X11R6/lib64',
+    '/usr/X11R6/man',
+    '/usr/X11R6/man/man1',
+    '/usr/X11R6/man/man2',
+    '/usr/X11R6/man/man3',
+    '/usr/X11R6/man/man4',
+    '/usr/X11R6/man/man5',
+    '/usr/X11R6/man/man6',
+    '/usr/X11R6/man/man7',
+    '/usr/X11R6/man/man8',
+    '/usr/X11R6/man/man9',
+    '/usr/X11R6/man/mann',
+    '/usr/bin',
+    '/usr/bin/X11',
+    '/usr/etc',
+    '/usr/games',
+    '/usr/include',
+    '/usr/lib',
+    '/usr/lib/X11',
+    '/usr/lib/games',
+    '/usr/lib/gcc-lib',
+    '/usr/lib/menu',
+    '/usr/lib64',
+    '/usr/lib64/gcc-lib',
+    '/usr/local',
+    '/usr/local/bin',
+    '/usr/local/doc',
+    '/usr/local/etc',
+    '/usr/local/games',
+    '/usr/local/info',
+    '/usr/local/lib',
+    '/usr/local/lib64',
+    '/usr/local/man',
+    '/usr/local/man/man1',
+    '/usr/local/man/man2',
+    '/usr/local/man/man3',
+    '/usr/local/man/man4',
+    '/usr/local/man/man5',
+    '/usr/local/man/man6',
+    '/usr/local/man/man7',
+    '/usr/local/man/man8',
+    '/usr/local/man/man9',
+    '/usr/local/man/mann',
+    '/usr/local/sbin',
+    '/usr/local/share',
+    '/usr/local/share/man',
+    '/usr/local/share/man/man1',
+    '/usr/local/share/man/man2',
+    '/usr/local/share/man/man3',
+    '/usr/local/share/man/man4',
+    '/usr/local/share/man/man5',
+    '/usr/local/share/man/man6',
+    '/usr/local/share/man/man7',
+    '/usr/local/share/man/man8',
+    '/usr/local/share/man/man9',
+    '/usr/local/share/man/mann',
+    '/usr/local/src',
+    '/usr/sbin',
+    '/usr/share',
+    '/usr/share/dict',
+    '/usr/share/doc',
+    '/usr/share/icons',
+    '/usr/share/info',
+    '/usr/share/man',
+    '/usr/share/man/man1',
+    '/usr/share/man/man2',
+    '/usr/share/man/man3',
+    '/usr/share/man/man4',
+    '/usr/share/man/man5',
+    '/usr/share/man/man6',
+    '/usr/share/man/man7',
+    '/usr/share/man/man8',
+    '/usr/share/man/man9',
+    '/usr/share/man/mann',
+    '/usr/share/misc',
+    '/usr/src',
+    '/usr/tmp',
+    '/var',
+    '/var/cache',
+    '/var/db',
+    '/var/lib',
+    '/var/lib/games',
+    '/var/lib/misc',
+    '/var/lib/rpm',
+    '/var/local',
+    '/var/lock',
+    '/var/lock/subsys',
+    '/var/log',
+    '/var/mail',
+    '/var/nis',
+    '/var/opt',
+    '/var/preserve',
+    '/var/run',
+    '/var/spool',
+    '/var/spool/mail',
+    '/var/tmp',
+    )
+
+DEFAULT_GAMES_GROUPS = 'Games'
+
+DEFAULT_DANGLING_EXCEPTIONS = (['consolehelper$', 'usermode-consoleonly'],
+                               )
+
+# Standard users and groups from LSB Core 4.0.0: 21.2 User & Group Names
+DEFAULT_STANDARD_USERS  = ('root', 'bin', 'daemon', 'adm', 'lp', 'sync',
+                           'shutdown', 'halt', 'mail', 'news', 'uucp',
+                           'operator', 'man', 'nobody',)
+DEFAULT_STANDARD_GROUPS = ('root', 'bin', 'daemon', 'adm', 'lp', 'sync',
+                           'shutdown', 'halt', 'mail', 'news', 'uucp',
+                           'man', 'nobody',)
+
+tmp_regex = re.compile('^/tmp/|^(/var|/usr)/tmp/')
+sub_bin_regex = re.compile('^(/usr)?/s?bin/\S+/')
+backup_regex = re.compile('(~|\#[^/]+\#|\.orig|\.rej)$')
+compr_regex = re.compile('\.(gz|z|Z|zip|bz2|lzma|xz)$')
+absolute_regex = re.compile('^/([^/]+)')
+absolute2_regex = re.compile('^/?([^/]+)')
+points_regex = re.compile('^\.\./(.*)')
+doc_regex = re.compile('^/usr(/share|/X11R6)?/(doc|man|info)/')
+bin_regex = re.compile('^/(?:usr/(?:s?bin|games)|s?bin)/(.*)')
+includefile_regex = re.compile('\.(c|h)(pp|xx)?$', re.IGNORECASE)
+develfile_regex = re.compile('\.(a|cmxa?|mli?)$')
+buildconfigfile_regex = re.compile('(\.pc|/bin/.+-config)$')
+# room for improvement with catching more -R, but also for false positives...
+buildconfig_rpath_regex = re.compile('(?:-rpath|Wl,-R)\\b')
+sofile_regex = re.compile('/lib(64)?/(.+/)?lib[^/]+\.so$')
+devel_regex = re.compile('(.*)-(debug(info)?|devel|headers|source|static)$')
+debuginfo_package_regex = re.compile('-debug(info)?$')
+lib_regex = re.compile('lib(64)?/lib[^/]*\.so\..*')
+ldconfig_regex = re.compile('^[^#]*ldconfig', re.MULTILINE)
+depmod_regex = re.compile('^[^#]*depmod', re.MULTILINE)
+install_info_regex = re.compile('^[^#]*install-info', re.MULTILINE)
+perl_temp_file_regex = re.compile('.*perl.*/(\.packlist|perllocal\.pod)$')
+scm_regex = re.compile('/CVS/[^/]+$|/\.(bzr|cvs|git|hg)ignore$|/\.hgtags$|/\.(bzr|git|hg|svn)/|/(\.arch-ids|{arch})/')
+games_path_regex = re.compile('^/usr(/lib(64)?)?/games/')
+games_group_regex = re.compile(Config.getOption('RpmGamesGroups', DEFAULT_GAMES_GROUPS))
+dangling_exceptions = Config.getOption('DanglingSymlinkExceptions', DEFAULT_DANGLING_EXCEPTIONS)
+logrotate_regex = re.compile('^/etc/logrotate\.d/(.*)')
+module_rpms_ok = Config.getOption('KernelModuleRPMsOK', True)
+kernel_modules_regex = re.compile('^/lib/modules/(2\.[23456]\.[0-9]+[^/]*?)/')
+kernel_package_regex = re.compile('^kernel(22)?(-)?(smp|enterprise|bigmem|secure|BOOT|i686-up-4GB|p3-smp-64GB)?')
+normal_zero_length_regex = re.compile('^/etc/security/console\.apps/|/\.nosearch$|/__init__\.py$')
+perl_regex = re.compile('^/usr/lib/perl5/(?:vendor_perl/)?([0-9]+\.[0-9]+)\.([0-9]+)/')
+python_regex = re.compile('^/usr/lib(?:64)?/python([.0-9]+)/')
+python_bytecode_regex_pep3147 = re.compile('^(.*)/__pycache__/(.*)\.(.*)(\.py[oc])$')
+python_bytecode_regex = re.compile('^(.*)(\.py[oc])$')
+python_default_version = Config.getOption('PythonDefaultVersion', None)
+perl_version_trick = Config.getOption('PerlVersionTrick', True)
+log_regex = re.compile('^/var/log/[^/]+$')
+lib_path_regex = re.compile('^(/usr(/X11R6)?)?/lib(64)?')
+lib_package_regex = re.compile('^(lib|.+-libs)')
+hidden_file_regex = re.compile('/\.[^/]*$')
+manifest_perl_regex = re.compile('^/usr/share/doc/perl-.*/MANIFEST(\.SKIP)?$')
+shebang_regex = re.compile('^#!\s*(\S+)')
+interpreter_regex = re.compile('^/(usr/)?(s?bin|games|libexec(/.+)?|(lib(64)?|share)/.+)/[^/]+$')
+script_regex = re.compile('^/((usr/)?s?bin|etc/(rc\.d/init\.d|X11/xinit\.d|cron\.(hourly|daily|monthly|weekly)))/')
+sourced_script_regex = re.compile('^/etc/(bash_completion\.d|profile\.d)/')
+use_utf8 = Config.getOption('UseUTF8', Config.USEUTF8_DEFAULT)
+skipdocs_regex = re.compile(Config.getOption('SkipDocsRegexp', '\.(?:rtf|x?html?|svg|ml[ily]?)$'), re.IGNORECASE)
+meta_package_regex = re.compile(Config.getOption('MetaPackageRegexp', '^(bundle|task)-'))
+filesys_packages = ['filesystem'] # TODO: make configurable?
+quotes_regex = re.compile('[\'"]+')
+
+for idx in range(0, len(dangling_exceptions)):
+    dangling_exceptions[idx][0] = re.compile(dangling_exceptions[idx][0])
+del idx
+
+use_relative_symlinks = Config.getOption("UseRelativeSymlinks", True)
+
+standard_groups = Config.getOption('StandardGroups', DEFAULT_STANDARD_GROUPS)
+standard_users = Config.getOption('StandardUsers', DEFAULT_STANDARD_USERS)
+
+non_readable_regexs = (re.compile('^/var/log/'),
+                       re.compile('^/etc/(g?shadow-?|securetty)$'))
+
+man_base_regex = re.compile(r'^/usr(?:/share)?/man/man[^/]+/(.+)\.[1-9n]')
+man_warn_regex = re.compile(r'^([^:]+:)\d+:\s*')
+man_nowarn_regex = re.compile(
+    # From Lintian: ignore common undefined macros from pod2man << Perl 5.10
+    r'\`(Tr|IX)\' not defined|'
+    # .so entries won't resolve as we're dealing with stdin
+    r'No such file or directory|'
+    # TODO, better handling for these (see e.g. Lintian)
+    r'(can\'t break|cannot adjust) line')
+man_warn_category = Config.getOption('ManWarningCategory', 'mac')
+
+fsf_license_regex = re.compile('(GNU((\s+(Library|Lesser|Affero))?(\s+General)?\s+Public|\s+Free\s+Documentation)\s+Licen[cs]e|(GP|FD)L)', re.IGNORECASE)
+fsf_wrong_address_regex = re.compile('(675\s+Mass\s+Ave|59\s+Temple\s+Place|Franklin\s+Steet|02139|02111-1307)', re.IGNORECASE)
+
+# loosely inspired from Python Cookbook
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/173220
+text_characters = "".join(map(chr, range(32, 127)) + list("\n\r\t\b"))
+_null_trans = string.maketrans("", "")
+
+def peek(filename, pkg, length=1024):
+    """Peek into a file, return a chunk from its beginning and a flag if it
+       seems to be a text file."""
+    fobj = None
+    chunk = None
+    try:
+        fobj = open(filename, 'rb')
+        chunk = fobj.read(length)
+        fobj.close()
+    except Exception, e: # eg. https://bugzilla.redhat.com/209876
+        printWarning(pkg, 'read-error', e)
+        if fobj:
+            fobj.close()
+        return (chunk, False)
+
+    if "\0" in chunk:
+        return (chunk, False)
+
+    if not chunk:  # Empty files are considered text
+        return (chunk, True)
+
+    # PDF's are binary but often detected as text by the algorithm below
+    if filename.lower().endswith('.pdf') and chunk.startswith('%PDF-'):
+        return (chunk, False)
+
+    # Get the non-text characters (maps a character to itself then
+    # use the 'remove' option to get rid of the text characters.)
+    t = chunk.translate(_null_trans, text_characters)
+
+    # If more than 30% non-text characters, then consider it a binary file
+    istext = float(len(t))/len(chunk) <= 0.30
+    return (chunk, istext)
+
+# See Python/import.c (in the trunk and py3k branches) for a full list of
+# the values here.
+_python_magic_values = {
+    '2.2': 60717,
+    '2.3': 62011,
+    '2.4': 62061,
+    '2.5': 62131,
+    '2.6': 62161,
+    '2.7': 62211,
+    '3.0': 3130,
+    '3.1': 3150,
+    '3.2': 3180,
+    '3.3': 3190,
+    }
+
+def get_expected_pyc_magic(path):
+    """.pyc/.pyo files embed a 4-byte magic value identifying which version of
+    the python bytecode ABI they are for. Given a path to a .pyc/.pyo file,
+    return a (magic ABI value, python version) tuple.  For example,
+    '/usr/lib/python3.1/foo.pyc' should return (3151, '3.1').
+    The first value will be None if the python version was not resolved
+    from the given pathname and the PythonDefaultVersion configuration
+    variable is not set, or if we don't know the magic ABI value for the
+    python version (no matter from which source the version came from).
+    The second value will be None if a python version could not be resolved
+    from the given pathname."""
+
+    ver_from_path = None
+    m = python_regex.search(path)
+    if m:
+        ver_from_path = m.group(1)
+
+    expected_version = ver_from_path or python_default_version
+    expected_magic_value = _python_magic_values.get(expected_version)
+
+    if not expected_magic_value:
+        return (None, ver_from_path)
+
+    # In Python 2, if Py_UnicodeFlag is set, Python's import code uses a value
+    # one higher, but this is off by default. In Python 3.0 and 3.1 (but no
+    # longer in 3.2), it always uses the value one higher:
+    if expected_version[:3] in ('3.0', '3.1'):
+        expected_magic_value += 1
+
+    return (expected_magic_value, ver_from_path)
+
+def py_demarshal_long(b):
+    """Counterpart to Python's PyMarshal_ReadLongFromFile, operating on the
+    bytes in a string."""
+    return (ord(b[0])
+            + (ord(b[1]) << 8)
+            + (ord(b[2]) << 16)
+            + (ord(b[3]) << 24))
+
+def python_bytecode_to_script(path):
+    """Given a python bytecode path, give the path of the .py file
+    (or None if not python bytecode)."""
+
+    res = python_bytecode_regex_pep3147.search(path)
+    if res:
+        return res.group(1) + '/' + res.group(2) + '.py'
+
+    res = python_bytecode_regex.search(path)
+    if res:
+        return res.group(1) + '.py'
+
+    return None
+
+class FilesCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, 'FilesCheck')
+
+    def check(self, pkg):
+
+        files = pkg.files()
+
+        if use_utf8:
+            for filename in files:
+                if not is_utf8_str(filename):
+                    printError(pkg, 'filename-not-utf8', filename)
+
+        # Rest of the checks are for binary packages only
+        if pkg.isSource():
+            return
+
+        # Check if the package is a development package
+        devel_pkg = devel_regex.search(pkg.name)
+
+        config_files = pkg.configFiles()
+        ghost_files = pkg.ghostFiles()
+        doc_files = pkg.docFiles()
+        req_names = pkg.req_names()
+        lib_package = lib_package_regex.search(pkg.name)
+        is_kernel_package = kernel_package_regex.search(pkg.name)
+        debuginfo_package = debuginfo_package_regex.search(pkg.name)
+
+        # report these errors only once
+        perl_dep_error = False
+        python_dep_error = False
+        lib_file = False
+        non_lib_file = None
+        log_file = None
+        logrotate_file = False
+        debuginfo_srcs = False
+        debuginfo_debugs = False
+
+        if not doc_files:
+            printWarning(pkg, 'no-documentation')
+
+        if files:
+            if meta_package_regex.search(pkg.name):
+                printWarning(pkg, 'file-in-meta-package')
+        elif debuginfo_package:
+            printError(pkg, 'empty-debuginfo-package')
+
+        # Unique (rdev, inode) combinations
+        hardlinks = {}
+
+        # All executable files from standard bin dirs (basename => [paths])
+        # Hack: basenames with empty paths links are symlinks (not subject
+        # to duplicate binary check, but yes for man page existence check)
+        bindir_exes = {}
+
+        # All man page "base" names (without section etc extensions)
+        man_basenames = set()
+
+        for f, pkgfile in files.items():
+            mode = pkgfile.mode
+            user = pkgfile.user
+            group = pkgfile.group
+            link = pkgfile.linkto
+            size = pkgfile.size
+            rdev = pkgfile.rdev
+            inode = pkgfile.inode
+            is_doc = f in doc_files
+            nonexec_file = False
+
+            for match in AbstractCheck.macro_regex.findall(f):
+                printWarning(pkg, 'unexpanded-macro', f, match)
+            if standard_users and user not in standard_users:
+                printWarning(pkg, 'non-standard-uid', f, user)
+            if standard_groups and group not in standard_groups:
+                printWarning(pkg, 'non-standard-gid', f, group)
+
+            if not module_rpms_ok and kernel_modules_regex.search(f) and not \
+                    is_kernel_package:
+                printError(pkg, "kernel-modules-not-in-kernel-packages", f)
+
+            if tmp_regex.search(f):
+                printError(pkg, 'dir-or-file-in-tmp', f)
+            elif f.startswith('/mnt/'):
+                printError(pkg, 'dir-or-file-in-mnt', f)
+            elif f.startswith('/opt/'):
+                printError(pkg, 'dir-or-file-in-opt', f)
+            elif f.startswith('/usr/local/'):
+                printError(pkg, 'dir-or-file-in-usr-local', f)
+            elif f.startswith('/var/local/'):
+                printError(pkg, 'dir-or-file-in-var-local', f)
+            elif f.startswith('/var/run/'):
+                if f not in ghost_files:
+                    printWarning(pkg, 'non-ghost-in-var-run', f)
+            elif f.startswith('/var/lock/'):
+                if f not in ghost_files:
+                    printWarning(pkg, 'non-ghost-in-var-lock', f)
+            elif sub_bin_regex.search(f):
+                printError(pkg, 'subdir-in-bin', f)
+            elif f.startswith('/home/'):
+                printError(pkg, 'dir-or-file-in-home', f)
+            elif '/site_perl/' in f:
+                printWarning(pkg, 'siteperl-in-perl-module', f)
+
+            if backup_regex.search(f):
+                printError(pkg, 'backup-file-in-package', f)
+            elif scm_regex.search(f):
+                printError(pkg, 'version-control-internal-file', f)
+            elif f.endswith('/.htaccess'):
+                printError(pkg, 'htaccess-file', f)
+            elif hidden_file_regex.search(f) and not f.startswith("/etc/skel/"):
+                printWarning(pkg, 'hidden-file-or-dir', f)
+            elif manifest_perl_regex.search(f):
+                printWarning(pkg, 'manifest-in-perl-module', f)
+            elif f == '/usr/info/dir' or f == '/usr/share/info/dir':
+                printError(pkg, 'info-dir-file', f)
+
+            res = logrotate_regex.search(f)
+            if res:
+                logrotate_file = True
+                if res.group(1) != pkg.name:
+                    printError(pkg, 'incoherent-logrotate-file', f)
+
+            if link != '':
+                ext = compr_regex.search(link)
+                if ext:
+                    if not re.compile('\.' + ext.group(1) + '$').search(f):
+                        printError(pkg, 'compressed-symlink-with-wrong-ext',
+                                   f, link)
+
+            perm = mode & 07777
+
+            if log_regex.search(f):
+                log_file = f
+
+            # Hardlink check
+            hardlink = hardlinks.get((rdev, inode))
+            if hardlink and os.path.dirname(hardlink) != os.path.dirname(f):
+                printWarning(pkg, 'cross-directory-hard-link', f, hardlink)
+            hardlinks[(rdev, inode)] = f
+
+            # normal file check
+            if stat.S_ISREG(mode):
+
+                # set[ug]id bit check
+                if stat.S_ISGID & mode or stat.S_ISUID & mode:
+                    if stat.S_ISUID & mode:
+                        printError(pkg, 'setuid-binary', f, user, oct(perm))
+                    if stat.S_ISGID & mode:
+                        if not (group == 'games' and
+                                (games_path_regex.search(f) or
+                                 games_group_regex.search(
+                                    pkg[rpm.RPMTAG_GROUP]))):
+                            printError(pkg, 'setgid-binary', f, group,
+                                       oct(perm))
+                    if mode & 0777 != 0755:
+                        printError(pkg, 'non-standard-executable-perm', f,
+                                   oct(perm))
+
+                # Prefetch scriptlets, strip quotes from them (#169)
+                postin = pkg[rpm.RPMTAG_POSTIN] or \
+                    pkg.scriptprog(rpm.RPMTAG_POSTINPROG)
+                if postin:
+                    postin = quotes_regex.sub('', postin)
+                postun = pkg[rpm.RPMTAG_POSTUN] or \
+                    pkg.scriptprog(rpm.RPMTAG_POSTUNPROG)
+                if postun:
+                    postun = quotes_regex.sub('', postun)
+
+                if not devel_pkg:
+                    if lib_path_regex.search(f):
+                        lib_file = True
+                    elif not is_doc:
+                        non_lib_file = f
+
+                if log_regex.search(f):
+                    nonexec_file = True
+                    if user != 'root':
+                        printError(pkg, 'non-root-user-log-file', f, user)
+                    if group != 'root':
+                        printError(pkg, 'non-root-group-log-file', f, group)
+                    if f not in ghost_files:
+                        printError(pkg, 'non-ghost-file', f)
+
+                chunk = None
+                istext = False
+                if os.access(pkgfile.path, os.R_OK):
+                    (chunk, istext) = peek(pkgfile.path, pkg)
+
+                interpreter = None
+                if chunk:
+                    res = shebang_regex.search(chunk)
+                    if res:
+                        interpreter = res.group(1)
+
+                if doc_regex.search(f):
+                    if not interpreter:
+                        nonexec_file = True
+                    if not is_doc:
+                        printError(pkg, 'not-listed-as-documentation', f)
+
+                # check ldconfig call in %post and %postun
+                if lib_regex.search(f):
+                    if not postin:
+                        printError(pkg, 'library-without-ldconfig-postin', f)
+                    else:
+                        if not ldconfig_regex.search(postin):
+                            printError(pkg, 'postin-without-ldconfig', f)
+
+                    if not postun:
+                        printError(pkg, 'library-without-ldconfig-postun', f)
+                    else:
+                        if not ldconfig_regex.search(postun):
+                            printError(pkg, 'postun-without-ldconfig', f)
+
+                # check depmod call in %post and %postun
+                res = not is_kernel_package and kernel_modules_regex.search(f)
+                if res:
+                    kernel_version = res.group(1)
+                    kernel_version_regex = re.compile(
+                        '\\bdepmod\s+-a.*F\s+/boot/System\.map-' +
+                        re.escape(kernel_version) + '\\b.*\\b' +
+                        re.escape(kernel_version) + '\\b',
+                        re.MULTILINE | re.DOTALL)
+
+                    if not postin or not depmod_regex.search(postin):
+                        printError(pkg, 'module-without-depmod-postin', f)
+                    # check that we run depmod on the right kernel
+                    elif not kernel_version_regex.search(postin):
+                        printError(pkg, 'postin-with-wrong-depmod', f)
+
+                    if not postun or not depmod_regex.search(postun):
+                        printError(pkg, 'module-without-depmod-postun', f)
+                    # check that we run depmod on the right kernel
+                    elif not kernel_version_regex.search(postun):
+                        printError(pkg, 'postun-with-wrong-depmod', f)
+
+                # check install-info call in %post and %postun
+                if f.startswith('/usr/share/info/'):
+                    if not postin:
+                        printError(pkg,
+                                   'info-files-without-install-info-postin', f)
+                    elif not install_info_regex.search(postin):
+                        printError(pkg, 'postin-without-install-info', f)
+
+                    preun = pkg[rpm.RPMTAG_PREUN] or \
+                        pkg.scriptprog(rpm.RPMTAG_PREUNPROG)
+                    if not postun and not preun:
+                        printError(pkg,
+                                   'info-files-without-install-info-postun', f)
+                    elif (not postun or
+                          not install_info_regex.search(postun)) and \
+                          (not preun or not install_info_regex.search(preun)):
+                        printError(pkg, 'postin-without-install-info', f)
+
+                # check perl temp file
+                if perl_temp_file_regex.search(f):
+                    printWarning(pkg, 'perl-temp-file', f)
+
+                is_buildconfig = buildconfigfile_regex.search(f) and True
+
+                # check rpaths in buildconfig files
+                if is_buildconfig:
+                    ln = pkg.grep(buildconfig_rpath_regex, f)
+                    if ln:
+                        printError(pkg, 'rpath-in-buildconfig', f, 'lines', ln)
+
+                res = bin_regex.search(f)
+                if res:
+                    if mode & 0111 == 0:
+                        printWarning(pkg, 'non-executable-in-bin', f, oct(perm))
+                    else:
+                        exe = res.group(1)
+                        if "/" not in exe:
+                            bindir_exes.setdefault(exe, []).append(f)
+
+                if not devel_pkg and not is_doc and \
+                       (includefile_regex.search(f) or \
+                        develfile_regex.search(f) or is_buildconfig):
+                    printWarning(pkg, 'devel-file-in-non-devel-package', f)
+                if mode & 0444 != 0444 and perm & 07000 == 0:
+                    ok_nonreadable = False
+                    for regex in non_readable_regexs:
+                        if regex.search(f):
+                            ok_nonreadable = True
+                            break
+                    if not ok_nonreadable:
+                        printError(pkg, 'non-readable', f, oct(perm))
+                if size == 0 and not normal_zero_length_regex.search(f) and \
+                        f not in ghost_files:
+                    printError(pkg, 'zero-length', f)
+
+                if mode & 0002 != 0:
+                    printError(pkg, 'world-writable', f, oct(perm))
+
+                if not perl_dep_error:
+                    res = perl_regex.search(f)
+                    if res:
+                        if perl_version_trick:
+                            vers = res.group(1) + '.' + res.group(2)
+                        else:
+                            vers = res.group(1) + res.group(2)
+                        if not (pkg.check_versioned_dep('perl-base', vers) or
+                                pkg.check_versioned_dep('perl', vers)):
+                            printError(pkg, 'no-dependency-on',
+                                       'perl-base', vers)
+                            perl_dep_error = True
+
+                if not python_dep_error:
+                    res = python_regex.search(f)
+                    if res and not (pkg.check_versioned_dep('python-base',
+                                                            res.group(1)) or
+                                    pkg.check_versioned_dep('python',
+                                                            res.group(1))):
+                        printError(pkg, 'no-dependency-on', 'python-base',
+                                   res.group(1))
+                        python_dep_error = True
+
+                source_file = python_bytecode_to_script(f)
+                if source_file:
+                    if source_file in files:
+                        if chunk:
+                            # Verify that the magic ABI value embedded in the
+                            # .pyc header is correct
+                            found_magic = py_demarshal_long(chunk[:4]) & 0xffff
+                            exp_magic, exp_version = get_expected_pyc_magic(f)
+                            if exp_magic and found_magic != exp_magic:
+                                found_version = 'unknown'
+                                for (pv, pm) in _python_magic_values.items():
+                                    if pm == found_magic:
+                                        found_version = pv
+                                        break
+                                # If expected version was from the file path,
+                                # issue # an error, otherwise a warning.
+                                msg = (pkg, 'python-bytecode-wrong-magic-value',
+                                       f, "expected %d (%s), found %d (%s)" %
+                                       (exp_magic,
+                                        exp_version or python_default_version,
+                                        found_magic, found_version))
+                                if exp_version is not None:
+                                    printError(*msg)
+                                else:
+                                    printWarning(*msg)
+
+                            # Verify that the timestamp embedded in the .pyc
+                            # header matches the mtime of the .py file:
+                            pyc_timestamp = py_demarshal_long(chunk[4:8])
+                            # If it's a symlink, check target file mtime.
+                            srcfile = pkg.readlink(files[source_file])
+                            if not srcfile:
+                                printWarning(
+                                    pkg, 'python-bytecode-without-source', f)
+                            elif pyc_timestamp != srcfile.mtime:
+                                cts = datetime.fromtimestamp(
+                                    pyc_timestamp).isoformat()
+                                sts = datetime.fromtimestamp(
+                                    srcfile.mtime).isoformat()
+                                printError(pkg,
+                                           'python-bytecode-inconsistent-mtime',
+                                           f, cts, srcfile.name, sts)
+                    else:
+                        printWarning(pkg, 'python-bytecode-without-source', f)
+
+                # normal executable check
+                if mode & stat.S_IXUSR and perm != 0755:
+                    printError(pkg, 'non-standard-executable-perm',
+                               f, oct(perm))
+                if mode & 0111 != 0:
+                    if f in config_files:
+                        printError(pkg, 'executable-marked-as-config-file', f)
+                    if not nonexec_file:
+                        # doc_regex and log_regex checked earlier, no match,
+                        # check rest of usual cases here.  Sourced scripts have
+                        # their own check, so disregard them here.
+                        nonexec_file = f.endswith('.pc') or \
+                                       compr_regex.search(f) or \
+                                       includefile_regex.search(f) or \
+                                       develfile_regex.search(f) or \
+                                       logrotate_regex.search(f)
+                    if nonexec_file:
+                        printWarning(pkg, 'spurious-executable-perm', f)
+                elif f.startswith('/etc/') and f not in config_files and \
+                        f not in ghost_files:
+                    printWarning(pkg, 'non-conffile-in-etc', f)
+
+                if pkg.arch == 'noarch' and f.startswith('/usr/lib64/python'):
+                    printError(pkg, 'noarch-python-in-64bit-path', f)
+
+                if debuginfo_package:
+                    if f.endswith('.debug'):
+                        debuginfo_debugs = True
+                    else:
+                        debuginfo_srcs = True
+
+                res = man_base_regex.search(f)
+                if res:
+                    man_basenames.add(res.group(1))
+                    if use_utf8 and chunk:
+                        # TODO: better shell escaping or seq based invocation
+                        cmd = commands.getstatusoutput(
+                            'env LC_ALL=C %s "%s" | gtbl | '
+                            'env LC_ALL=en_US.UTF-8 groff -mtty-char -Tutf8 '
+                            '-P-c -mandoc -w%s >/dev/null' %
+                            (catcmd(f), pkgfile.path, man_warn_category))
+                        for line in cmd[1].split("\n"):
+                            res = man_warn_regex.search(line)
+                            if not res or man_nowarn_regex.search(line):
+                                continue
+                            printWarning(pkg, "manual-page-warning", f,
+                                         line[res.end(1):])
+
+                # text file checks
+                if istext:
+                    # ignore perl module shebang -- TODO: disputed...
+                    if f.endswith('.pm'):
+                        interpreter = None
+                    # sourced scripts should not be executable
+                    if sourced_script_regex.search(f):
+                        if interpreter:
+                            printError(pkg,
+                                       'sourced-script-with-shebang', f,
+                                       interpreter)
+                        if mode & 0111 != 0:
+                            printError(pkg, 'executable-sourced-script',
+                                       f, oct(perm))
+                    # ...but executed ones should
+                    elif interpreter or mode & 0111 != 0 or \
+                            script_regex.search(f):
+                        if interpreter:
+                            if not interpreter_regex.search(interpreter):
+                                printError(pkg, 'wrong-script-interpreter',
+                                           f, interpreter)
+                        elif not nonexec_file and not \
+                                (lib_path_regex.search(f) and
+                                 f.endswith('.la')):
+                            printError(pkg, 'script-without-shebang', f)
+
+                        if mode & 0111 == 0 and not is_doc:
+                            printError(pkg, 'non-executable-script', f,
+                                       oct(perm), interpreter)
+                        if '\r' in chunk:
+                            printError(
+                                pkg, 'wrong-script-end-of-line-encoding', f)
+                    elif is_doc and not skipdocs_regex.search(f):
+                        if '\r' in chunk:
+                            printWarning(
+                                pkg, 'wrong-file-end-of-line-encoding', f)
+                        # We check only doc text files for UTF-8-ness;
+                        # checking everything may be slow and can generate
+                        # lots of unwanted noise.
+                        if use_utf8 and not is_utf8(pkgfile.path):
+                            printWarning(pkg, 'file-not-utf8', f)
+                    if fsf_license_regex.search(chunk) and \
+                            fsf_wrong_address_regex.search(chunk):
+                        printError(pkg, 'incorrect-fsf-address', f)
+
+                elif is_doc and chunk and compr_regex.search(f):
+                    ff = compr_regex.sub('', f)
+                    if not skipdocs_regex.search(ff):
+                        # compressed docs, eg. info and man files etc
+                        if use_utf8 and not is_utf8(pkgfile.path):
+                            printWarning(pkg, 'file-not-utf8', f)
+
+            # normal dir check
+            elif stat.S_ISDIR(mode):
+                if mode & 01002 == 2: # world writable without sticky bit
+                    printError(pkg, 'world-writable', f, oct(perm))
+                if perm != 0755:
+                    printError(pkg, 'non-standard-dir-perm', f, oct(perm))
+                if pkg.name not in filesys_packages and f in STANDARD_DIRS:
+                    printError(pkg, 'standard-dir-owned-by-package', f)
+                if hidden_file_regex.search(f):
+                    printWarning(pkg, 'hidden-file-or-dir', f)
+
+
+            # symbolic link check
+            elif stat.S_ISLNK(mode):
+
+                is_so = sofile_regex.search(f)
+                if not devel_pkg and is_so and not link.endswith('.so'):
+                    printWarning(pkg, 'devel-file-in-non-devel-package', f)
+
+                res = man_base_regex.search(f)
+                if res:
+                    man_basenames.add(res.group(1))
+                else:
+                    res = bin_regex.search(f)
+                    if res:
+                        exe = res.group(1)
+                        if "/" not in exe:
+                            bindir_exes.setdefault(exe, [])
+
+                # absolute link
+                r = absolute_regex.search(link)
+                if r:
+                    if not is_so and link not in files and \
+                            link not in req_names:
+                        is_exception = False
+                        for e in dangling_exceptions:
+                            if e[0].search(link):
+                                is_exception = e[1]
+                                break
+                        if is_exception:
+                            if is_exception not in req_names:
+                                printWarning(pkg, 'no-dependency-on',
+                                             is_exception)
+                        else:
+                            printWarning(pkg, 'dangling-symlink', f, link)
+                    linktop = r.group(1)
+                    r = absolute_regex.search(f)
+                    if r:
+                        filetop = r.group(1)
+                        if filetop == linktop or use_relative_symlinks:
+                            printWarning(pkg, 'symlink-should-be-relative',
+                                         f, link)
+                # relative link
+                else:
+                    if not is_so:
+                        abslink = '%s/%s' % (os.path.dirname(f), link)
+                        abslink = os.path.normpath(abslink)
+                        if abslink not in files and abslink not in req_names:
+                            is_exception = False
+                            for e in dangling_exceptions:
+                                if e[0].search(link):
+                                    is_exception = e[1]
+                                    break
+                            if is_exception:
+                                if is_exception not in req_names:
+                                    printWarning(pkg, 'no-dependency-on',
+                                                 is_exception)
+                            else:
+                                printWarning(pkg, 'dangling-relative-symlink',
+                                             f, link)
+                    pathcomponents = f.split('/')[1:]
+                    r = points_regex.search(link)
+                    lastpop = None
+                    mylink = None
+
+                    while r:
+                        mylink = r.group(1)
+                        if len(pathcomponents) == 0:
+                            printError(pkg, 'symlink-has-too-many-up-segments',
+                                       f, link)
+                            break
+                        else:
+                            lastpop = pathcomponents[0]
+                            pathcomponents = pathcomponents[1:]
+                            r = points_regex.search(mylink)
+
+                    if mylink and lastpop:
+                        r = absolute2_regex.search(mylink)
+                        linktop = r.group(1)
+
+                        # does the link go up and then down into the same
+                        # directory?
+                        #if linktop == lastpop:
+                        #    printWarning(pkg, 'lengthy-symlink', f, link)
+
+                        # have we reached the root directory?
+                        if len(pathcomponents) == 0 and linktop != lastpop \
+                                and not use_relative_symlinks:
+                            # relative link into other toplevel directory
+                            printWarning(pkg, 'symlink-should-be-absolute', f,
+                                         link)
+                        # check additional segments for mistakes like
+                        # `foo/../bar/'
+                        for linksegment in mylink.split('/'):
+                            if linksegment == '..':
+                                printError(
+                                    pkg,
+                                    'symlink-contains-up-and-down-segments',
+                                    f, link)
+
+            if f.startswith('/etc/cron.d/'):
+                if stat.S_ISLNK(mode):
+                    printError(pkg, 'symlink-crontab-file', f)
+
+                if mode & 0111:
+                    printError(pkg, 'executable-crontab-file', f)
+
+                if stat.S_IWGRP & mode or stat.S_IWOTH & mode:
+                    printError(pkg, 'non-owner-writeable-only-crontab-file', f)
+
+        if log_file and not logrotate_file:
+            printWarning(pkg, 'log-files-without-logrotate', log_file)
+
+        if lib_package and lib_file and non_lib_file:
+            printError(pkg, 'outside-libdir-files', non_lib_file)
+
+        if debuginfo_package and debuginfo_debugs and not debuginfo_srcs:
+            printError(pkg, 'debuginfo-without-sources')
+
+        for exe, paths in bindir_exes.items():
+            if len(paths) > 1:
+                printWarning(pkg, "duplicate-executable", exe, paths)
+            if exe not in man_basenames:
+                printWarning(pkg, "no-manual-page-for-binary", exe)
+
+# Create an object to enable the auto registration of the test
+check = FilesCheck()
+
+addDetails(
+'no-documentation',
+'''The package contains no documentation (README, doc, etc).
+You have to include documentation files.''',
+
+'not-listed-as-documentation',
+'''The documentation files of this package are not listed with
+the standard %doc tag.''',
+
+'non-standard-uid',
+'''A file in this package is owned by a non standard user.
+Standard users are:
+%s.''' % ", ".join(standard_users),
+
+'non-standard-gid',
+'''A file in this package is owned by a non standard group.
+Standard groups are:
+%s.''' % ", ".join(standard_groups),
+
+'library-without-ldconfig-postin',
+'''This package contains a library and provides no %post scriptlet containing
+a call to ldconfig.''',
+
+'postin-without-ldconfig',
+'''This package contains a library and its %post scriptlet doesn't call
+ldconfig.''',
+
+'library-without-ldconfig-postun',
+'''This package contains a library and provides no %postun scriptlet containing
+a call to ldconfig.''',
+
+'postun-without-ldconfig',
+'''This package contains a library and its %postun doesn't call ldconfig.''',
+
+'info-files-without-install-info-postin',
+'''This package contains info files and provides no %post scriptlet containing
+a call to install-info.''',
+
+'postin-without-install-info',
+'''This package contains info files and its %post doesn't call install-info.''',
+
+'info-files-without-install-info-postun',
+'''This package contains info files and provides no %postun scriptlet containing
+a call to install-info.''',
+
+'postun-without-install-info',
+'''This package contains info files and its %postun doesn't call
+install-info.''',
+
+'perl-temp-file',
+'''You have a perl temporary file in your package. Usually, this
+file is beginning with a dot (.) and contain "perl" in its name.''',
+
+'dir-or-file-in-tmp',
+'''A file in the package is located in /tmp. It's not permitted
+for packages to install files in this directory.''',
+
+'dir-or-file-in-mnt',
+'''A file in the package is located in /mnt. It's not permitted
+for packages to install files in this directory.''',
+
+'dir-or-file-in-opt',
+'''A file in the package is located in /opt. It's not permitted
+for packages to install files in this directory.''',
+
+'dir-or-file-in-usr-local',
+'''A file in the package is located in /usr/local. It's not permitted
+for packages to install files in this directory.''',
+
+'dir-or-file-in-var-local',
+'''A file in the package is located in /var/local. It's not permitted
+for packages to install files in this directory.''',
+
+'non-ghost-in-var-run',
+'''A file or directory in the package is located in /var/run. Files installed
+in this directory should be marked as %ghost and created at runtime to work
+properly in tmpfs /var/run setups.''',
+
+'non-ghost-in-var-lock',
+'''A file or directory in the package is located in /var/lock. Files installed
+in this directory should be marked as %ghost and created at runtime to work
+properly in tmpfs /var/lock setups.''',
+
+'subdir-in-bin',
+'''The package contains a subdirectory in /usr/bin. It's not permitted to
+create a subdir there. Create it in /usr/lib/ instead.''',
+
+'backup-file-in-package',
+'''You have a file whose name looks like one for backup files, usually created
+by an editor or resulting from applying unclean (fuzzy, or ones with line
+offsets) patches.''',
+
+'dir-or-file-in-home',
+'''A file in the package is located in /home. It's not permitted
+for packages to install files in this directory.''',
+
+'version-control-internal-file',
+'''You have included file(s) internally used by a version control system
+in the package. Move these files out of the package and rebuild it.''',
+
+'htaccess-file',
+'''You have individual apache configuration .htaccess file(s) in your package.
+Replace them by a central configuration file in /etc/, according to the web
+application packaging policy for your distribution.''',
+
+'info-dir-file',
+'''You have /usr/info/dir or /usr/share/info/dir in your package. It will cause
+conflicts with other packages and thus is not allowed. Please remove it and
+rebuild your package.''',
+
+'non-conffile-in-etc',
+'''A non-executable file in your package is being installed in /etc, but is not
+a configuration file. All non-executable files in /etc should be configuration
+files. Mark the file as %config in the spec file.''',
+
+'compressed-symlink-with-wrong-ext',
+'''The symlink points to a compressed file but doesn't use the same
+extension.''',
+
+'setuid-binary',
+'''The file is setuid; this may be dangerous, especially if this
+file is setuid root. Sometimes file capabilities can be used instead of
+setuid bits.''',
+
+'setgid-binary',
+'''The file is setgid. Usually this is a packaging bug. If this is a game,
+then, you should use the proper rpm group, or location.''',
+
+'non-standard-executable-perm',
+'''A standard executable should have permission set to 0755. If you get this
+message, it means that you have a wrong executable permissions in some files
+included in your package.''',
+
+'non-executable-in-bin',
+'''A file is being installed in /usr/bin, but is not an executable. Be sure
+that the file is an executable or that it has executable permissions.''',
+
+'devel-file-in-non-devel-package',
+'''A development file (usually source code) is located in a non-devel
+package. If you want to include source code in your package, be sure to
+create a development package.''',
+
+'non-standard-dir-perm',
+'''A standard directory should have permission set to 0755. If you get this
+message, it means that you have wrong directory permissions in some dirs
+included in your package.''',
+
+'spurious-executable-perm',
+'''The file is installed with executable permissions, but was identified as one
+that probably should not be executable.  Verify if the executable bits are
+desired, and remove if not.''',
+
+'world-writable',
+'''A file or directory in the package is installed with world writable
+permissions, which is most likely a security issue.''',
+
+'standard-dir-owned-by-package',
+'''This package owns a directory that is part of the standard hierarchy, which
+can lead to default directory permissions or ownerships being changed to
+something non-standard.''',
+
+'no-dependency-on',
+'''
+''',
+
+'cross-directory-hard-link',
+'''File is hard linked across directories.  This can cause problems in
+installations where the directories are located on different devices.''',
+
+'dangling-symlink',
+'''The target of the symbolic link does not exist within this package or its
+file based dependencies.  Verify spelling of the link target and that the
+target is included in a package in this package's dependency chain.''',
+
+'symlink-should-be-relative',
+'''Absolute symlinks are problematic eg. when working with chroot environments.
+symlinks(8) is a tool that can be useful for creating/dealing with relative
+symlinks at package build time.''',
+
+'dangling-relative-symlink',
+'''The target of the symbolic link does not exist within this package or its
+file based dependencies.  Verify spelling of the link target and that the
+target is included in a package in this package's dependency chain.''',
+
+'symlink-has-too-many-up-segments',
+'''
+''',
+
+'symlink-should-be-absolute',
+'''
+''',
+
+'symlink-contains-up-and-down-segments',
+'''
+''',
+
+'non-readable',
+'''The file can't be read by everybody. If this is expected (for security
+reasons), contact your rpmlint distributor to get it added to the list of
+exceptions for your distro (or add it to your local configuration if you
+installed rpmlint from the source tarball).''',
+
+'incoherent-logrotate-file',
+'''Your logrotate file should be named /etc/logrotate.d/<package name>.''',
+
+'non-root-user-log-file',
+'''If you need log files owned by a non-root user, just create a subdir in
+/var/log and put your log files in it.''',
+
+'non-root-group-log-file',
+'''If you need log files owned by a non-root group, just create a subdir in
+/var/log and put your log files in it.''',
+
+'non-ghost-file',
+'''File should be tagged %ghost.''',
+
+'outside-libdir-files',
+'''This library package must not contain non library files to allow 64
+and 32 bits versions of the package to coexist.''',
+
+'hidden-file-or-dir',
+'''The file or directory is hidden. You should see if this is normal,
+and delete it from the package if not.''',
+
+'module-without-depmod-postin',
+'''This package contains a kernel module but provides no call to depmod in the
+%post scriptlet.''',
+
+'postin-with-wrong-depmod',
+'''This package contains a kernel module but its %post scriptlet calls depmod
+for the wrong kernel.''',
+
+'module-without-depmod-postun',
+'''This package contains a kernel module but provides no call to depmod in the
+%postun scriptlet.''',
+
+'postun-with-wrong-depmod',
+'''This package contains a kernel module but its %postun scriptlet calls depmod
+for the wrong kernel.''',
+
+'log-files-without-logrotate',
+'''This package contains files in /var/log/ without adding logrotate
+configuration for them.''',
+
+'unexpanded-macro',
+'''This package contains a file whose path contains something that looks like
+an unexpanded macro; this is often the sign of a misspelling. Please check your
+specfile.''',
+
+'manifest-in-perl-module',
+'''This perl module package contains a MANIFEST or a MANIFEST.SKIP file
+in the documentation directory.''',
+
+'siteperl-in-perl-module',
+'''This perl module package installs files under the subdirectory site_perl,
+while they must appear under vendor_perl.''',
+
+'executable-marked-as-config-file',
+'''Executables must not be marked as config files because that may
+prevent upgrades from working correctly. If you need to be able to
+customize an executable, make it for example read a config file in
+/etc/sysconfig.''',
+
+'sourced-script-with-shebang',
+'''This text file contains a shebang, but is meant to be sourced, not
+executed.''',
+
+'executable-sourced-script',
+'''This text file has executable bit set, but is meant to be sourced, not
+executed.''',
+
+'wrong-script-interpreter',
+'''This script uses an incorrect interpreter.''',
+
+'non-executable-script',
+'''This text file contains a shebang or is located in a path dedicated for
+executables, but lacks the executable bits and cannot thus be executed.  If
+the file is meant to be an executable script, add the executable bits,
+otherwise remove the shebang or move the file elsewhere.''',
+
+'script-without-shebang',
+'''This text file has executable bits set or is located in a path dedicated
+for executables, but lacks a shebang and cannot thus be executed.  If the file
+is meant to be an executable script, add the shebang, otherwise remove the
+executable bits or move the file elsewhere.''',
+
+'wrong-script-end-of-line-encoding',
+'''This script has wrong end-of-line encoding, usually caused by creation or
+modification on a non-Unix system. It will prevent its execution.''',
+
+'wrong-file-end-of-line-encoding',
+'''This file has wrong end-of-line encoding, usually caused by creation or
+modification on a non-Unix system. It could prevent it from being displayed
+correctly in some circumstances.''',
+
+'file-not-utf8',
+'''The character encoding of this file is not UTF-8.  Consider converting it
+in the specfile's %prep section for example using iconv(1).''',
+
+'filename-not-utf8',
+'''The character encoding of the name of this file is not UTF-8.
+Rename it.''',
+
+'file-in-meta-package',
+'''This package seems to be a meta-package (an empty package used to require
+other packages), but it is not empty. You should remove or rename it, see the
+option MetaPackageRegexp.''',
+
+'empty-debuginfo-package',
+'''This debuginfo package contains no files.  This is often a sign of binaries
+being unexpectedly stripped too early during the build, rpmbuild not being able
+to strip the binaries, the package actually being a noarch one but erratically
+packaged as arch dependent, or something else.  Verify what the case is, and
+if there's no way to produce useful debuginfo out of it, disable creation of
+the debuginfo package.''',
+
+'debuginfo-without-sources',
+'''This debuginfo package appears to contain debug symbols but no source files.
+This is often a sign of binaries being unexpectedly stripped too early during
+the build, or being compiled without compiler debug flags (which again often
+is a sign of distro's default compiler flags ignored which might have security
+consequences), or other compiler flags which result in rpmbuild's debuginfo
+extraction not working as expected.  Verify that the binaries are not
+unexpectedly stripped and that the intended compiler flags are used.''',
+
+'read-error',
+'''This file could not be read.  A reason for this could be that the info about
+it in the rpm header indicates that it is supposed to be a readable normal file
+but it actually is not in the filesystem.  Because of this, some checks will
+be skipped.''',
+
+'executable-crontab-file',
+'''This crontab file has executable bit set, which is refused by newer version
+of cron''',
+
+'non-owner-writeable-only-crontab-file',
+'''This crontab file is writeable by other users as its owner, which is refused
+by newer version of cron and insecure''',
+
+'symlink-crontab-file',
+'''This crontab file is a symbolic link, which is insecure and refused by newer
+version of cron''',
+
+'rpath-in-buildconfig',
+'''This build configuration file contains rpaths which will be introduced into
+dependent packages.''',
+
+'python-bytecode-wrong-magic-value',
+'''The "magic" ABI version embedded in this python bytecode file isn't equal
+to that of the corresponding runtime, which will force the interpreter to
+recompile the .py source every time, ignoring the saved bytecode.''',
+
+'python-bytecode-inconsistent-mtime',
+'''The timestamp embedded in this python bytecode file isn't equal to the mtime
+of the original source file, which will force the interpreter to recompile the
+.py source every time, ignoring the saved bytecode.''',
+
+'python-bytecode-without-source',
+'''This python bytecode file (.pyo/.pyc) is not accompanied by its original
+source file (.py)''',
+
+'duplicate-executable',
+'''This executable file exists in more than one standard binary directories.
+It can cause problems when dirs in $PATH are reordered.''',
+
+'no-manual-page-for-binary',
+'''Each executable in standard binary directories should have a man page.''',
+
+'manual-page-warning',
+'''This man page may contain problems that can cause it not to be formatted
+as intended.''',
+
+'incorrect-fsf-address',
+'''The Free Software Foundation address in this file seems to be outdated or
+misspelled.  Ask upstream to update the address, or if this is a license file,
+possibly the entire file with a new copy available from the FSF.''',
+)
+
+# FilesCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/Filter.py b/Filter.py
new file mode 100644 (file)
index 0000000..0900fe7
--- /dev/null
+++ b/Filter.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : Filter.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Sat Oct 23 15:52:27 1999
+# Version       : $Id: Filter.py 1871 2011-06-18 09:40:52Z scop $
+# Purpose       : filter the output of rpmlint to allow exceptions.
+#############################################################################
+
+import locale
+import sys
+import textwrap
+
+import Config
+try:
+    import Testing
+except ImportError:
+    Testing = None
+
+
+_rawout = None
+_diagnostic = list()
+_badness_score = 0
+printed_messages = { "I": 0, "W": 0, "E": 0 }
+
+if sys.stdout.isatty():
+    def __print(s):
+        print(s)
+else:
+    def __print(s):
+        print(s.encode(locale.getpreferredencoding(), "replace"))
+
+def printInfo(pkg, reason, *details):
+    _print("I", pkg, reason, details)
+
+def printWarning(pkg, reason, *details):
+    _print("W", pkg, reason, details)
+
+def printError(pkg, reason, *details):
+    _print("E", pkg, reason, details)
+
+def _print(msgtype, pkg, reason, details):
+    global _badness_score
+
+    threshold = badnessThreshold()
+
+    badness = 0
+    if threshold >= 0:
+        badness = Config.badness(reason)
+        # anything with badness is an error
+        if badness:
+            msgtype = 'E'
+        # errors without badness become warnings
+        elif msgtype == 'E':
+            msgtype = 'W'
+
+    ln = ""
+    if pkg.current_linenum is not None:
+        ln = "%s:" % pkg.current_linenum
+    arch = ""
+    if pkg.arch is not None:
+        arch = ".%s" % pkg.arch
+    s = "%s%s:%s %s: %s" % (pkg.name, arch, ln, msgtype, reason)
+    if badness:
+        s = s + " (Badness: %d)" % badness
+    for d in details:
+        s = s + " %s" % d
+    if Testing and Testing.isTest():
+        Testing.addOutput(s)
+    else:
+        if _rawout:
+            print >>_rawout, s.encode(locale.getpreferredencoding(), "replace")
+        if not Config.isFiltered(s):
+            printed_messages[msgtype] += 1
+            _badness_score += badness
+            if threshold >= 0:
+                _diagnostic.append(s + "\n")
+            else:
+                __print(s)
+                if Config.info:
+                    printDescriptions(reason)
+            return True
+
+    return False
+
+def printDescriptions(reason):
+    try:
+        d = _details[reason]
+        if d and d != '' and d != "\n":
+            __print(textwrap.fill(d, 78))
+            __print("")
+    except KeyError:
+        pass
+
+def _diag_sortkey(x):
+    xs = x.split()
+    return (xs[2], xs[1])
+
+def printAllReasons():
+    threshold = badnessThreshold()
+    if threshold < 0:
+        return False
+
+    global _diagnostic
+    _diagnostic.sort(key = _diag_sortkey, reverse = True)
+    last_reason = ''
+    for diag in _diagnostic:
+        if Config.info:
+            reason = diag.split()[2]
+            if reason != last_reason:
+                if len(last_reason):
+                    printDescriptions(last_reason)
+                last_reason = reason
+        __print(diag)
+    if Config.info and len(last_reason):
+        printDescriptions(last_reason)
+    _diagnostic = list()
+    return _badness_score > threshold
+
+
+_details = {}
+
+def addDetails(*details):
+    for idx in range(len(details)/2):
+        if not details[idx*2] in _details:
+            _details[details[idx*2]] = details[idx*2+1]
+
+def badnessScore():
+    return _badness_score
+
+def badnessThreshold():
+    return Config.getOption("BadnessThreshold", -1)
+
+def setRawOut(file):
+    global _rawout
+    if _rawout:
+        _rawout.close()
+    _rawout = open(file, "w")
+
+# Filter.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/I18NCheck.py b/I18NCheck.py
new file mode 100644 (file)
index 0000000..f94064b
--- /dev/null
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : I18NCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Mon Nov 22 20:02:56 1999
+# Version       : $Id: I18NCheck.py 1798 2010-06-23 19:47:26Z scop $
+# Purpose       : checks i18n bugs.
+#############################################################################
+
+import re
+
+import rpm
+
+from Filter import addDetails, printError, printWarning
+from __isocodes__ import COUNTRIES, LANGUAGES
+import AbstractCheck
+
+
+# Associative array of invalid value => correct value
+INCORRECT_LOCALES = {
+    'in': 'id',
+    'in_ID': 'id_ID',
+    'iw': 'he',
+    'iw_IL': 'he_IL',
+    'gr': 'el',
+    'gr_GR': 'el_GR',
+    'cz': 'cs',
+    'cz_CZ': 'cs_CZ',
+    'lug': 'lg', # 'lug' is valid, but we standardize on 2 letter codes
+    'en_UK': 'en_GB'}
+
+package_regex = re.compile('-(' + '|'.join(LANGUAGES) + ')$')
+locale_regex = re.compile('^(/usr/share/locale/([^/]+))/')
+correct_subdir_regex = re.compile('^(([a-z][a-z]([a-z])?(_[A-Z][A-Z])?)([.@].*$)?)$')
+lc_messages_regex = re.compile('/usr/share/locale/([^/]+)/LC_MESSAGES/.*(mo|po)$')
+man_regex = re.compile('/usr(?:/share)?/man/([^/]+)/man[0-9n][^/]*/[^/]+$')
+
+# list of exceptions
+#
+# note: ISO-8859-9E is non standard, ISO-8859-{6,8} are of limited use
+# as locales (since all modern handling of bidi is based on utf-8 anyway),
+# so they should be removed once UTF-8 is deployed)
+EXCEPTION_DIRS = ('C', 'POSIX', 'CP1251', 'CP1255', 'CP1256',
+'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4', 'ISO-8859-5',
+'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9', 'ISO-8859-9E',
+'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15',
+'KOI8-R', 'KOI8-U', 'UTF-8', 'default')
+
+def is_valid_lang(lang):
+    # TODO: @Foo and charset handling
+    lang = re.sub("[@.].*$", "", lang)
+
+    if lang in LANGUAGES:
+        return True
+
+    ix = lang.find("_")
+    if ix == -1:
+        return False
+
+    # TODO: don't accept all lang_COUNTRY combinations
+
+    country = lang[ix+1:]
+    if country not in COUNTRIES:
+        return False
+
+    lang = lang[0:ix]
+    if lang not in LANGUAGES:
+        return False
+
+    return True
+
+class I18NCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, 'I18NCheck')
+
+    def check(self, pkg):
+
+        if pkg.isSource():
+            return
+
+        files = pkg.files().keys()
+        files.sort()
+        locales = []                      # list of locales for this packages
+        webapp = False
+
+        i18n_tags = pkg[rpm.RPMTAG_HEADERI18NTABLE] or ()
+
+        for i in i18n_tags:
+            try:
+                correct = INCORRECT_LOCALES[i]
+                printError(pkg, 'incorrect-i18n-tag-' + correct, i)
+            except KeyError:
+                pass
+
+        # as some webapps have their files under /var/www/html, and
+        # others in /usr/share or /usr/lib, the only reliable way
+        # sofar to detect them is to look for an apache configuration file
+        for f in files:
+            if f.startswith('/etc/apache2/') or \
+                    f.startswith('/etc/httpd/conf.d/'):
+                webapp = True
+
+        for f in files:
+            res = locale_regex.search(f)
+            if res:
+                locale = res.group(2)
+                # checks the same locale only once
+                if locale not in locales:
+                    locales.append(locale)
+                    res2 = correct_subdir_regex.search(locale)
+                    if not res2:
+                        if locale not in EXCEPTION_DIRS:
+                            printError(pkg, 'incorrect-locale-subdir', f)
+                    else:
+                        locale_name = res2.group(2)
+                        try:
+                            correct = INCORRECT_LOCALES[locale_name]
+                            printError(pkg, 'incorrect-locale-' + correct, f)
+                        except KeyError:
+                            pass
+            res = lc_messages_regex.search(f)
+            subdir = None
+            if res:
+                subdir = res.group(1)
+                if not is_valid_lang(subdir):
+                    printError(pkg, 'invalid-lc-messages-dir', f)
+            else:
+                res = man_regex.search(f)
+                if res:
+                    subdir = res.group(1)
+                    if is_valid_lang(subdir):
+                        subdir = None
+                    else:
+                        printError(pkg, 'invalid-locale-man-dir', f)
+
+            if f.endswith('.mo') or subdir:
+                if pkg.files()[f].lang == '' and not webapp:
+                    printWarning(pkg, 'file-not-in-%lang', f)
+
+        main_dir, main_lang = ("", "")
+        for f in files:
+            lang = pkg.files()[f].lang
+            if main_lang and lang == "" and is_prefix(main_dir + '/', f):
+                printError(pkg, 'subfile-not-in-%lang', f)
+            if main_lang != lang:
+                main_dir, main_lang = f, lang
+
+        name = pkg.name
+        res = package_regex.search(name)
+        if res:
+            locales = 'locales-' + res.group(1)
+            if locales != name:
+                if locales not in (x[0] for x in pkg.requires()):
+                    printError(pkg, 'no-dependency-on', locales)
+
+def is_prefix(p, s):
+    return len(p) <= len(s) and p == s[:len(p)]
+
+# Create an object to enable the auto registration of the test
+check = I18NCheck()
+
+addDetails(
+# Need to add a function to list all the locales
+'incorrect-i18n-tag-',
+"""
+""",
+
+'incorrect-locale-subdir',
+"""
+""",
+
+'incorrect-locale-',
+"""
+""",
+
+'invalid-lc-messages-dir',
+"""
+""",
+
+'invalid-locale-man-dir',
+"""
+""",
+
+'file-not-in-lang',
+"""
+""",
+
+'no-dependency-on',
+"""
+""",
+
+'subfile-not-in-%lang',
+""" If /foo/bar is not tagged %lang(XX) whereas /foo is, the package won't be
+installable if XX is not in %_install_langs.""",
+
+)
+
+# I18NCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..bc607a3
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,16 @@
+You need the following utilities to run rpmlint:
+
+       o python 2.x (x >= 4)
+       o rpm 4.4.2.2 or newer and its python bindings
+       o libmagic and its python bindings (optional)
+       o readelf
+       o cpio
+       o desktop-file-validate
+       o gzip, bzip2 and xz
+       o enchant and its python bindings (optional)
+
+and the following programs to install it:
+
+       o python 2.x (x >= 4)
+       o rpm python bindings 4.4 or newer
+       o sed
diff --git a/InitScriptCheck.py b/InitScriptCheck.py
new file mode 100644 (file)
index 0000000..c77adbc
--- /dev/null
@@ -0,0 +1,286 @@
+# -*- coding: utf-8 -*-
+#---------------------------------------------------------------
+# Project         : Mandriva Linux
+# Module          : rpmlint
+# File            : InitScriptCheck.py
+# Version         : $Id: InitScriptCheck.py 1893 2011-11-23 20:24:32Z scop $
+# Author          : Frederic Lepied
+# Created On      : Fri Aug 25 09:26:37 2000
+# Purpose         : check init scripts (files in /etc/rc.d/init.d)
+#---------------------------------------------------------------
+
+import os
+import re
+
+import rpm
+
+from Filter import addDetails, printError, printWarning
+import AbstractCheck
+import Config
+import Pkg
+
+
+chkconfig_content_regex = re.compile('^\s*#\s*chkconfig:\s*([-0-9]+)\s+[-0-9]+\s+[-0-9]+')
+subsys_regex = re.compile('/var/lock/subsys/([^/"\'\n\s;&|]+)', re.MULTILINE)
+chkconfig_regex = re.compile('^[^#]*(chkconfig|add-service|del-service)', re.MULTILINE)
+status_regex = re.compile('^[^#]*status', re.MULTILINE)
+reload_regex = re.compile('^[^#]*reload', re.MULTILINE)
+use_deflevels = Config.getOption('UseDefaultRunlevels', True)
+lsb_tags_regex = re.compile('^# ([\w-]+):\s*(.*?)\s*$')
+lsb_cont_regex = re.compile('^#(?:\t|  )(.*?)\s*$')
+use_subsys = Config.getOption('UseVarLockSubsys', True)
+
+LSB_KEYWORDS = ('Provides', 'Required-Start', 'Required-Stop', 'Should-Start',
+                'Should-Stop', 'Default-Start', 'Default-Stop',
+                'Short-Description', 'Description')
+RECOMMENDED_LSB_KEYWORDS = ('Provides', 'Required-Start', 'Required-Stop',
+                            'Default-Stop', 'Short-Description')
+
+class InitScriptCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, 'InitScriptCheck')
+
+    def check(self, pkg):
+        # Check only binary package
+        if pkg.isSource():
+            return
+
+        initscript_list = []
+        for fname, pkgfile in pkg.files().items():
+
+            if not fname.startswith('/etc/init.d/') and \
+                    not fname.startswith('/etc/rc.d/init.d/'):
+                continue
+
+            basename = os.path.basename(fname)
+            initscript_list.append(basename)
+            if pkgfile.mode & 0500 != 0500:
+                printError(pkg, 'init-script-non-executable', fname)
+
+            if "." in basename:
+                printError(pkg, 'init-script-name-with-dot', fname)
+
+            # check chkconfig call in %post and %preun
+            postin = pkg[rpm.RPMTAG_POSTIN] or \
+                pkg.scriptprog(rpm.RPMTAG_POSTINPROG)
+            if not postin:
+                printError(pkg, 'init-script-without-chkconfig-postin', fname)
+            elif not chkconfig_regex.search(postin):
+                printError(pkg, 'postin-without-chkconfig', fname)
+
+            preun = pkg[rpm.RPMTAG_PREUN] or \
+                pkg.scriptprog(rpm.RPMTAG_PREUNPROG)
+            if not preun:
+                printError(pkg, 'init-script-without-chkconfig-preun', fname)
+            elif not chkconfig_regex.search(preun):
+                printError(pkg, 'preun-without-chkconfig', fname)
+
+            status_found = False
+            reload_found = False
+            chkconfig_content_found = False
+            subsys_regex_found = False
+            in_lsb_tag = False
+            in_lsb_description = False
+            lastline = ''
+            lsb_tags = {}
+            # check common error in file content
+            content = None
+            try:
+                content = Pkg.readlines(pkgfile.path)
+            except Exception, e:
+                printWarning(pkg, 'read-error', e)
+                continue
+            content_str = "".join(content)
+            for line in content:
+                line = line[:-1] # chomp
+                # TODO check if there is only one line like this
+                if line.startswith('### BEGIN INIT INFO'):
+                    in_lsb_tag = True
+                    continue
+                if line.endswith('### END INIT INFO'):
+                    in_lsb_tag = False
+                    for kw, vals in lsb_tags.items():
+                        if len(vals) != 1:
+                            printError(pkg, 'redundant-lsb-keyword', kw)
+
+                    for kw in RECOMMENDED_LSB_KEYWORDS:
+                        if kw not in lsb_tags:
+                            printWarning(pkg, 'missing-lsb-keyword',
+                                         "%s in %s" % (kw, fname))
+                if in_lsb_tag:
+                    # TODO maybe we do not have to handle this ?
+                    if lastline.endswith('\\'):
+                        line = lastline + line
+                    else:
+                        res = lsb_tags_regex.search(line)
+                        if not res:
+                            cres = lsb_cont_regex.search(line)
+                            if not (in_lsb_description and cres):
+                                in_lsb_description = False
+                                printError(pkg,
+                                           'malformed-line-in-lsb-comment-block',
+                                           line)
+                            else:
+                                lsb_tags["Description"][-1] += \
+                                    " " + cres.group(1)
+                        else:
+                            tag = res.group(1)
+                            if not tag.startswith('X-') and \
+                                    tag not in LSB_KEYWORDS:
+                                printError(pkg, 'unknown-lsb-keyword', line)
+                            else:
+                                in_lsb_description = (tag == 'Description')
+                                if tag not in lsb_tags:
+                                    lsb_tags[tag] = []
+                                lsb_tags[tag].append(res.group(2))
+                    lastline = line
+
+                if not status_found and status_regex.search(line):
+                    status_found = True
+
+                if not reload_found and reload_regex.search(line):
+                    reload_found = True
+
+                res = chkconfig_content_regex.search(line)
+                if res:
+                    chkconfig_content_found = True
+                    if use_deflevels:
+                        if res.group(1) == '-':
+                            printWarning(pkg, 'no-default-runlevel', fname)
+                    elif res.group(1) != '-':
+                        printWarning(pkg, 'service-default-enabled', fname)
+
+                res = subsys_regex.search(line)
+                if res:
+                    subsys_regex_found = True
+                    name = res.group(1)
+                    if use_subsys and name != basename:
+                        error = True
+                        if name[0] == '$':
+                            value = Pkg.substitute_shell_vars(name, content_str)
+                            if value == basename:
+                                error = False
+                        else:
+                            i = name.find('}')
+                            if i != -1:
+                                name = name[0:i]
+                                error = name != basename
+                        if error and len(name):
+                            if name[0] == '$':
+                                printWarning(pkg, 'incoherent-subsys', fname,
+                                             name)
+                            else:
+                                printError(pkg, 'incoherent-subsys', fname,
+                                           name)
+
+            if "Default-Start" in lsb_tags:
+                if "".join(lsb_tags["Default-Start"]):
+                    printWarning(pkg, 'service-default-enabled', fname)
+
+            if not status_found:
+                printError(pkg, 'no-status-entry', fname)
+            if not reload_found:
+                printWarning(pkg, 'no-reload-entry', fname)
+            if not chkconfig_content_found:
+                printError(pkg, 'no-chkconfig-line', fname)
+            if not subsys_regex_found and use_subsys:
+                printError(pkg, 'subsys-not-used', fname)
+            elif subsys_regex_found and not use_subsys:
+                printError(pkg, 'subsys-unsupported', fname)
+
+        if len(initscript_list) == 1:
+            pkgname = re.sub("-sysvinit$", "", pkg.name.lower())
+            goodnames = (pkgname, pkgname + 'd')
+            if initscript_list[0] not in goodnames:
+                printWarning(pkg, 'incoherent-init-script-name',
+                             initscript_list[0], str(goodnames))
+
+
+# Create an object to enable the auto registration of the test
+check = InitScriptCheck()
+
+addDetails(
+'init-script-without-chkconfig-postin',
+'''The package contains an init script but doesn't contain a %post with
+a call to chkconfig.''',
+
+'postin-without-chkconfig',
+'''The package contains an init script but doesn't call chkconfig in its
+%post script.''',
+
+'init-script-without-chkconfig-preun',
+'''The package contains an init script but doesn't contain a %preun with
+a call to chkconfig.''',
+
+'preun-without-chkconfig',
+'''The package contains an init script but doesn't call chkconfig in its
+%preun script.''',
+
+'missing-lsb-keyword',
+'''The package contains an init script that does not contain one of the LSB
+init script comment block convention keywords that are recommendable for all
+init scripts.  If there is nothing to add to a keyword's value, include the
+keyword in the script with an empty value.  Note that as of version 3.2, the
+LSB specification does not mandate presence of any keywords.''',
+
+'no-status-entry',
+'''In your init script (/etc/rc.d/init.d/your_file), you don't
+have a 'status' entry, which is necessary for good functionality.''',
+
+'no-reload-entry',
+'''In your init script (/etc/rc.d/init.d/your_file), you don't
+have a 'reload' entry, which is necessary for good functionality.''',
+
+'no-chkconfig-line',
+'''The init script doesn't contain a chkconfig line to specify the runlevels
+at which to start and stop it.''',
+
+'no-default-runlevel',
+'''The default runlevel isn't specified in the init script.''',
+
+'service-default-enabled',
+'''The service is enabled by default after "chkconfig --add"; for security
+reasons, most services should not be. Use "-" as the default runlevel in the
+init script's "chkconfig:" line and/or remove the "Default-Start:" LSB keyword
+to fix this if appropriate for this service.''',
+
+'subsys-unsupported',
+'''The init script uses /var/lock/subsys which is not supported by
+this distribution.''',
+
+'subsys-not-used',
+'''While your daemon is running, you have to put a lock file in
+/var/lock/subsys/. To see an example, look at this directory on your
+machine and examine the corresponding init scripts.''',
+
+'incoherent-subsys',
+'''The filename of your lock file in /var/lock/subsys/ is incoherent
+with your actual init script name. For example, if your script name
+is httpd, you have to use 'httpd' as the filename in your subsys directory.
+It is also possible that rpmlint gets this wrong, especially if the init
+script contains nontrivial shell variables and/or assignments.  These
+cases usually manifest themselves when rpmlint reports that the subsys name
+starts a with '$'; in these cases a warning instead of an error is reported
+and you should check the script manually.''',
+
+'incoherent-init-script-name',
+'''The init script name should be the same as the package name in lower case,
+or one with 'd' appended if it invokes a process by that name.''',
+
+'init-script-name-with-dot',
+'''The init script name should not contain a dot in its name. Some versions
+of chkconfig don't work as expected with init script names like that.''',
+
+'init-script-non-executable',
+'''The init script should have at least the execution bit set for root
+in order for it to run at boot time.''',
+)
+
+# InitScriptCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/LSBCheck.py b/LSBCheck.py
new file mode 100644 (file)
index 0000000..d88a8e0
--- /dev/null
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+#---------------------------------------------------------------
+# Project         : Mandriva Linux
+# Module          : rpmlint
+# File            : LSBCheck.py
+# Version         : $Id: LSBCheck.py 1532 2009-01-30 22:01:50Z scop $
+# Author          : Frederic Lepied
+# Created On      : Tue Jan 30 14:44:37 2001
+# Purpose         : LSB non compliance checks
+#---------------------------------------------------------------
+
+import re
+
+import rpm
+
+from Filter import addDetails, printError
+import AbstractCheck
+
+
+version_regex = re.compile('^[a-zA-Z0-9.+]+$')
+name_regex = re.compile('^[a-z0-9.+-]+$')
+
+class LSBCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, "LSBCheck")
+
+    def check(self, pkg):
+
+        name = pkg.name
+        if name and not name_regex.search(name):
+            printError(pkg, 'non-lsb-compliant-package-name', name)
+
+        version = pkg[rpm.RPMTAG_VERSION]
+        if version and not version_regex.search(version):
+            printError(pkg, 'non-lsb-compliant-version', version)
+
+        release = pkg[rpm.RPMTAG_RELEASE]
+        if release and not version_regex.search(release):
+            printError(pkg, 'non-lsb-compliant-release', release)
+
+# Create an object to enable the auto registration of the test
+check = LSBCheck()
+
+addDetails(
+'non-lsb-compliant-package-name',
+"""Your package name contains an illegal character. Use only
+alphanumeric symbols in your package name.""",
+
+'non-lsb-compliant-version',
+"""Your version number contains an illegal character. Use only
+lowercase letters and/or numbers.""",
+
+'non-lsb-compliant-release',
+"""Your version number contains an illegal character. Use only
+lowercase letters and/or numbers.""",
+
+)
+
+# LSBCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..c55482e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,110 @@
+#############################################################################
+# File         : Makefile
+# Package      : rpmlint
+# Author       : Frederic Lepied
+# Created on   : Mon Sep 30 13:20:18 1999
+# Version      : $Id: Makefile 1895 2011-12-04 16:23:10Z scop $
+# Purpose      : rules to manage the files.
+#############################################################################
+
+BINDIR=/usr/bin
+LIBDIR=/usr/share/rpmlint
+ETCDIR=/etc
+MANDIR=/usr/share/man
+
+FILES = rpmlint *.py INSTALL README README.devel COPYING tools/*.py \
+       Makefile config rpmdiff rpmlint.bash-completion rpmlint.1 \
+       test.sh test/*.rpm test/*.spec test/*.py
+GENERATED = AUTHORS ChangeLog __version__.py
+
+PACKAGE = rpmlint
+PYTHON = python
+
+# update this variable to create a new release
+VERSION := 1.4
+TAG := $(shell echo "V$(VERSION)" | tr -- '-.' '__')
+SVNBASE = $(shell svn info . | grep URL | sed -e 's/[^:]*:\s*//' -e 's,/\(trunk\|tags/.\+\)$$,,')
+
+# for the [A-Z]* part
+LC_ALL:=C
+export LC_ALL
+
+all: __version__.py __isocodes__.py
+       if [ "x${COMPILE_PYC}" = "x1" ] ; then \
+               $(PYTHON) -m py_compile [A-Z]*.py __*__.py ; \
+       fi
+       $(PYTHON) -O -m py_compile [A-Z]*.py __*__.py
+
+clean:
+       rm -f *~ *.pyc *.pyo $(GENERATED)
+
+install: all
+       mkdir -p $(DESTDIR)$(LIBDIR) $(DESTDIR)$(BINDIR) $(DESTDIR)$(ETCDIR)/$(PACKAGE) $(DESTDIR)$(MANDIR)/man1
+       -cp -p *.pyc $(DESTDIR)$(LIBDIR)
+       cp -p *.py *.pyo $(DESTDIR)$(LIBDIR)
+       cp -p rpmlint rpmdiff $(DESTDIR)$(BINDIR)
+       cp -p config $(DESTDIR)$(ETCDIR)/$(PACKAGE)
+       compdir=`pkg-config --variable=completionsdir bash-completion 2>/dev/null` ; \
+       if [ "x$$compdir" = "x" ] ; then \
+               mkdir -p $(DESTDIR)$(ETCDIR)/bash_completion.d ; \
+               cp -p rpmlint.bash-completion $(DESTDIR)$(ETCDIR)/bash_completion.d/rpmlint ; \
+       else \
+               mkdir -p $(DESTDIR)$$compdir ; \
+               cp -p rpmlint.bash-completion $(DESTDIR)$$compdir/rpmlint ; \
+               ln -s rpmlint $(DESTDIR)$$compdir/rpmdiff ; \
+       fi
+       cp -p rpmlint.1 $(DESTDIR)$(MANDIR)/man1/rpmlint.1
+
+verify:
+       pychecker --limit=100 [A-Z]*.py __*__.py
+
+.PHONY: check
+
+check:
+       ./test.sh
+
+version:
+       @echo "$(VERSION)"
+
+
+dist: cleandist localcopy tar
+
+cleandist:
+       rm -rf $(PACKAGE)-$(VERSION) $(PACKAGE)-$(VERSION).tar.xz
+
+localcopy: $(FILES) $(GENERATED)
+       mkdir $(PACKAGE)-$(VERSION)
+       cp -p --parents $(FILES) $(GENERATED) $(PACKAGE)-$(VERSION)
+
+tar: localcopy
+       tar cv --owner=root --group=root -f $(PACKAGE)-$(VERSION).tar $(PACKAGE)-$(VERSION)
+       xz -9evf $(PACKAGE)-$(VERSION).tar
+       rm -rf $(PACKAGE)-$(VERSION)
+
+export:
+       svn export $(SVNBASE)/tags/$(TAG) $(PACKAGE)-$(VERSION)
+
+tag:
+       @if svn list $(SVNBASE)/tags/$(TAG) &>/dev/null ; then \
+           echo "ERROR: tag \"$(TAG)\" probably already exists" ; \
+           exit 1 ; \
+       else \
+           echo 'svn copy -m "Tag $(TAG)." . $(SVNBASE)/tags/$(TAG)' ; \
+           svn copy -m "Tag $(TAG)." . $(SVNBASE)/tags/$(TAG) ; \
+       fi
+
+AUTHORS: authors.xml authors.xsl
+       xsltproc authors.xsl authors.xml | sort -u > $@
+
+ChangeLog: $(FILES) authors.xml
+       svn2cl --authors=authors.xml --group-by-day --reparagraph \
+               --strip-prefix=trunk
+
+__version__.py: Makefile
+       echo "# Automatically generated, do not edit" > $@
+       echo "__version__ = '$(VERSION)'" >> $@
+
+__isocodes__.py:
+       tools/generate-isocodes.py > $@
+
+# Makefile ends here
diff --git a/MenuCheck.py b/MenuCheck.py
new file mode 100644 (file)
index 0000000..0dd865a
--- /dev/null
@@ -0,0 +1,460 @@
+# -*- coding: utf-8 -*-
+#---------------------------------------------------------------
+# Project         : Mandriva Linux
+# Module          : rpmlint
+# File            : MenuCheck.py
+# Version         : $Id: MenuCheck.py 1885 2011-09-13 18:15:29Z scop $
+# Author          : Frederic Lepied
+# Created On      : Mon Mar 20 07:43:37 2000
+#---------------------------------------------------------------
+
+import re
+import stat
+
+import rpm
+
+from Filter import addDetails, printError, printInfo, printWarning
+import AbstractCheck
+import Config
+import Pkg
+
+
+DEFAULT_VALID_SECTIONS = (
+    'Office/Accessories',
+    'Office/Address Books',
+    'Office/Communications/Fax',
+    'Office/Communications/PDA',
+    'Office/Communications/Phone',
+    'Office/Communications/Other',
+    'Office/Drawing',
+    'Office/Graphs',
+    'Office/Presentations',
+    'Office/Publishing',
+    'Office/Spreadsheets',
+    'Office/Tasks Management',
+    'Office/Time Management',
+    'Office/Wordprocessors',
+    'Office/Other',
+    'Internet/Chat',
+    'Internet/File Transfer',
+    'Internet/Instant Messaging',
+    'Internet/Mail',
+    'Internet/News',
+    'Internet/Remote Access',
+    'Internet/Video Conference',
+    'Internet/Web Browsers',
+    'Internet/Web Editors',
+    'Internet/Other',
+    'Multimedia/Graphics',
+    'Multimedia/Sound',
+    'Multimedia/Video',
+    'Multimedia/Other',
+    'System/Archiving/Backup',
+    'System/Archiving/CD Burning',
+    'System/Archiving/Compression',
+    'System/Archiving/Other',
+    'System/Configuration/Boot and Init',
+    'System/Configuration/GNOME',
+    'System/Configuration/Hardware',
+    'System/Configuration/KDE',
+    'System/Configuration/Networking',
+    'System/Configuration/Packaging',
+    'System/Configuration/Printing',
+    'System/Configuration/Users',
+    'System/Configuration/Other',
+    'System/File Tools',
+    'System/Monitoring',
+    'System/Session/Windowmanagers',
+    'System/Terminals',
+    'System/Text Tools',
+    'System/Other',
+    'More Applications/Accessibility',
+    'More Applications/Communications',
+    'More Applications/Databases',
+    'More Applications/Development/Code Generators',
+    'More Applications/Development/Development Environments',
+    'More Applications/Development/Interpreters',
+    'More Applications/Development/Tools',
+    'More Applications/Development/Other',
+    'More Applications/Documentation',
+    'More Applications/Editors',
+    'More Applications/Education/Economy',
+    'More Applications/Education/Geography',
+    'More Applications/Education/History',
+    'More Applications/Education/Languages',
+    'More Applications/Education/Literature',
+    'More Applications/Education/Sciences',
+    'More Applications/Education/Sports',
+    'More Applications/Education/Other',
+    'More Applications/Emulators',
+    'More Applications/Finances',
+    'More Applications/Games/Adventure',
+    'More Applications/Games/Arcade',
+    'More Applications/Games/Boards',
+    'More Applications/Games/Cards',
+    'More Applications/Games/Puzzles',
+    'More Applications/Games/Sports',
+    'More Applications/Games/Strategy',
+    'More Applications/Games/Toys',
+    'More Applications/Games/Other',
+    'More Applications/Sciences/Artificial Intelligence',
+    'More Applications/Sciences/Astronomy',
+    'More Applications/Sciences/Biology',
+    'More Applications/Sciences/Chemistry',
+    'More Applications/Sciences/Computer Science',
+    'More Applications/Sciences/Data visualization',
+    'More Applications/Sciences/Electricity',
+    'More Applications/Sciences/Geosciences',
+    'More Applications/Sciences/Image Processing',
+    'More Applications/Sciences/Mathematics',
+    'More Applications/Sciences/Numerical Analysis',
+    'More Applications/Sciences/Parallel Computing',
+    'More Applications/Sciences/Physics',
+    'More Applications/Sciences/Robotics',
+    'More Applications/Sciences/Other',
+    'More Applications/Other',
+    )
+
+DEFAULT_EXTRA_MENU_NEEDS = (
+    'gnome',
+    'icewm',
+    'kde',
+    'wmaker',
+    )
+
+DEFAULT_ICON_PATH = (('/usr/share/icons/', 'normal'),
+                     ('/usr/share/icons/mini/', 'mini'),
+                     ('/usr/share/icons/large/', 'large'))
+
+DEFAULT_LAUNCHERS = (['(?:/usr/bin/)?kdesu', ('/usr/bin/kdesu', 'kdesu')],
+                     ['(?:/usr/bin/)?launch_x11_clanapp', ('/usr/bin/launch_x11_clanapp', 'clanlib', 'libclanlib0')],
+                     ['(?:/usr/bin/)?soundwrapper', None],
+                    )
+
+menu_file_regex = re.compile('^/usr/lib/menu/([^/]+)$')
+old_menu_file_regex = re.compile('^/usr/share/(gnome/apps|applnk)/([^/]+)$')
+package_regex = re.compile('\?package\((.*)\):')
+needs_regex = re.compile('needs=(\"([^\"]+)\"|([^ \t\"]+))')
+section_regex = re.compile('section=(\"([^\"]+)\"|([^ \t\"]+))')
+title_regex = re.compile('[\"\s]title=(\"([^\"]+)\"|([^ \t\"]+))')
+longtitle_regex = re.compile('longtitle=(\"([^\"]+)\"|([^ \t\"]+))')
+command_regex = re.compile('command=(?:\"([^\"]+)\"|([^ \t\"]+))')
+icon_regex = re.compile('icon=\"?([^\" ]+)')
+valid_sections = Config.getOption('ValidMenuSections', DEFAULT_VALID_SECTIONS)
+update_menus_regex = re.compile('^[^#]*update-menus', re.MULTILINE)
+standard_needs = Config.getOption('ExtraMenuNeeds', DEFAULT_EXTRA_MENU_NEEDS)
+icon_paths = Config.getOption('IconPath', DEFAULT_ICON_PATH)
+xpm_ext_regex = re.compile('/usr/share/icons/(mini/|large/).*\.xpm$')
+icon_ext_regex = re.compile(Config.getOption('IconFilename', '.*\.png$'))
+version_regex = re.compile('([0-9.][0-9.]+)($|\s)')
+launchers = Config.getOption('MenuLaunchers', DEFAULT_LAUNCHERS)
+xdg_migrated_regex = re.compile('xdg=\"?([^\" ]+)')
+
+# compile regexps
+for l in launchers:
+    l[0] = re.compile(l[0])
+del l
+
+class MenuCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, 'MenuCheck')
+
+    def check(self, pkg):
+        # Check only binary package
+        if pkg.isSource():
+            return
+
+        files = pkg.files()
+        menus = []
+
+        for fname, pkgfile in files.items():
+            # Check menu files
+            res = menu_file_regex.search(fname)
+            mode = pkgfile.mode
+            if res:
+                basename = res.group(1)
+                if not stat.S_ISREG(mode):
+                    printError(pkg, 'non-file-in-menu-dir', fname)
+                else:
+                    if basename != pkg.name:
+                        printWarning(pkg, 'non-coherent-menu-filename', fname)
+                    if mode & 0444 != 0444:
+                        printError(pkg, 'non-readable-menu-file', fname)
+                    if mode & 0111 != 0:
+                        printError(pkg, 'executable-menu-file', fname)
+                    menus.append(fname)
+            else:
+                # Check old menus from KDE and GNOME
+                res = old_menu_file_regex.search(fname)
+                if res:
+                    if stat.S_ISREG(mode):
+                        printError(pkg, 'old-menu-entry', fname)
+                else:
+                    # Check non transparent xpm files
+                    res = xpm_ext_regex.search(fname)
+                    if res:
+                        if stat.S_ISREG(mode) and not pkg.grep('None",', fname):
+                            printWarning(pkg, 'non-transparent-xpm', fname)
+                if fname.startswith('/usr/lib64/menu'):
+                    printError(pkg, 'menu-in-wrong-dir', fname)
+
+        if menus:
+            postin = pkg[rpm.RPMTAG_POSTIN] or \
+                pkg.scriptprog(rpm.RPMTAG_POSTINPROG)
+            if not postin:
+                printError(pkg, 'menu-without-postin')
+            elif not update_menus_regex.search(postin):
+                printError(pkg, 'postin-without-update-menus')
+
+            postun = pkg[rpm.RPMTAG_POSTUN] or \
+                pkg.scriptprog(rpm.RPMTAG_POSTUNPROG)
+            if not postun:
+                printError(pkg, 'menu-without-postun')
+            elif not update_menus_regex.search(postun):
+                printError(pkg, 'postun-without-update-menus')
+
+            directory = pkg.dirName()
+            for f in menus:
+                # remove comments and handle cpp continuation lines
+                cmd = Pkg.getstatusoutput(('/lib/cpp', directory + f), True)[1]
+
+                for line in cmd.splitlines():
+                    if not line.startswith('?'):
+                        continue
+                    res = package_regex.search(line)
+                    if res:
+                        package = res.group(1)
+                        if package != pkg.name:
+                            printWarning(pkg,
+                                         'incoherent-package-value-in-menu',
+                                         package, f)
+                    else:
+                        printInfo(pkg, 'unable-to-parse-menu-entry', line)
+
+                    command = True
+                    res = command_regex.search(line)
+                    if res:
+                        command_line = (res.group(1) or res.group(2)).split()
+                        command = command_line[0]
+                        for launcher in launchers:
+                            if not launcher[0].search(command):
+                                continue
+                            found = False
+                            if launcher[1]:
+                                if ('/bin/' + command_line[0] in files or
+                                    '/usr/bin/' + command_line[0] in files
+                                    or '/usr/X11R6/bin/' + command_line[0]
+                                    in files):
+                                    found = True
+                                else:
+                                    for l in launcher[1]:
+                                        if l in pkg.req_names():
+                                            found = True
+                                            break
+                                if not found:
+                                    printError(pkg,
+                                               'use-of-launcher-in-menu-but-no-requires-on',
+                                               launcher[1][0])
+                            command = command_line[1]
+                            break
+
+                        if command[0] == '/':
+                            if command not in files:
+                                printWarning(pkg, 'menu-command-not-in-package',
+                                             command)
+                        elif not ('/bin/' + command in files or
+                                  '/usr/bin/' + command in files or
+                                  '/usr/X11R6/bin/' + command in files):
+                            printWarning(pkg, 'menu-command-not-in-package',
+                                         command)
+                    else:
+                        printWarning(pkg, 'missing-menu-command')
+                        command = False
+
+                    res = longtitle_regex.search(line)
+                    if res:
+                        grp = res.groups()
+                        title = grp[1] or grp[2]
+                        if title[0] != title[0].upper():
+                            printWarning(pkg, 'menu-longtitle-not-capitalized',
+                                         title)
+                        res = version_regex.search(title)
+                        if res:
+                            printWarning(pkg, 'version-in-menu-longtitle',
+                                         title)
+                    else:
+                        printError(pkg, 'no-longtitle-in-menu', f)
+                        title = None
+
+                    res = title_regex.search(line)
+                    if res:
+                        grp = res.groups()
+                        title = grp[1] or grp[2]
+                        if title[0] != title[0].upper():
+                            printWarning(pkg, 'menu-title-not-capitalized',
+                                         title)
+                        res = version_regex.search(title)
+                        if res:
+                            printWarning(pkg, 'version-in-menu-title', title)
+                        if '/' in title:
+                            printError(pkg, 'invalid-title', title)
+                    else:
+                        printError(pkg, 'no-title-in-menu', f)
+                        title = None
+
+                    res = needs_regex.search(line)
+                    if res:
+                        grp = res.groups()
+                        needs = (grp[1] or grp[2]).lower()
+                        if needs in ('x11', 'text' ,'wm'):
+                            res = section_regex.search(line)
+                            if res:
+                                grp = res.groups()
+                                section = grp[1] or grp[2]
+                                # don't warn entries for sections
+                                if command and section not in valid_sections:
+                                    printError(pkg, 'invalid-menu-section',
+                                               section, f)
+                            else:
+                                printInfo(pkg, 'unable-to-parse-menu-section',
+                                          line)
+                        elif needs not in standard_needs:
+                            printInfo(pkg, 'strange-needs', needs, f)
+                    else:
+                        printInfo(pkg, 'unable-to-parse-menu-needs', line)
+
+                    res = icon_regex.search(line)
+                    if res:
+                        icon = res.group(1)
+                        if not icon_ext_regex.search(icon):
+                            printWarning(pkg, 'invalid-menu-icon-type', icon)
+                        if icon[0] == '/' and needs == 'x11':
+                            printWarning(pkg, 'hardcoded-path-in-menu-icon',
+                                         icon)
+                        else:
+                            for path in icon_paths:
+                                if (path[0] + icon) not in files:
+                                    printError(pkg,
+                                               path[1] + '-icon-not-in-package',
+                                               icon, f)
+                    else:
+                        printWarning(pkg, 'no-icon-in-menu', title)
+
+                    res = xdg_migrated_regex.search(line)
+                    if res:
+                        if not res.group(1).lower() == "true":
+                            printError(pkg, 'non-xdg-migrated-menu')
+                    else:
+                        printError(pkg, 'non-xdg-migrated-menu')
+
+
+# Create an object to enable the auto registration of the test
+check = MenuCheck()
+
+addDetails(
+'non-file-in-menu-dir',
+'''/usr/lib/menu must not contain anything else than normal files.''',
+
+'non-coherent-menu-filename',
+'''The menu file name should be /usr/lib/menu/<package>.''',
+
+'non-readable-menu-file',
+'''The menu file isn't readable. Check the permissions.''',
+
+'old-menu-entry',
+'''
+''',
+
+'non-transparent-xpm',
+'''xpm icon should be transparent for use in menus.''',
+
+'menu-without-postin',
+'''A menu file exists in the package but no %post scriptlet is present to call
+update-menus.''',
+
+'postin-without-update-menus',
+'''A menu file exists in the package but its %post scriptlet doesn't call
+update-menus.''',
+
+'menu-without-postun',
+'''A menu file exists in the package but no %postun scriptlet is present to call
+update-menus.''',
+
+'postun-without-update-menus',
+'''A menu file exists in the package but its %postun scriptlet doesn't call
+update-menus.''',
+
+'incoherent-package-value-in-menu',
+'''The package field of the menu entry isn't the same as the package name.''',
+
+'use-of-launcher-in-menu-but-no-requires-on',
+'''The menu command uses a launcher but there is no dependency in the package
+that contains it.''',
+
+'menu-command-not-in-package',
+'''The command used in the menu isn't included in the package.''',
+
+'menu-longtitle-not-capitalized',
+'''The longtitle field of the menu doesn't start with a capital letter.''',
+
+'version-in-menu-longtitle',
+'''The longtitle filed of the menu entry contains a version. This is bad
+because it will be prone to error when the version of the package changes.''',
+
+'no-longtitle-in-menu',
+'''The longtitle field isn't present in the menu entry.''',
+
+'menu-title-not-capitalized',
+'''The title field of the menu entry doesn't start with a capital letter.''',
+
+'version-in-menu-title',
+'''The title filed of the menu entry contains a version. This is bad
+because it will be prone to error when the version of the package changes.''',
+
+'no-title-in-menu',
+'''The title field isn't present in the menu entry.''',
+
+'invalid-menu-section',
+'''The section field of the menu entry isn't standard.''',
+
+'unable-to-parse-menu-section',
+'''rpmlint wasn't able to parse the menu section. Please report.''',
+
+'hardcoded-path-in-menu-icon',
+'''The path of the icon is hardcoded in the menu entry. This prevent multiple
+sizes of the icon from being found.''',
+
+'normal-icon-not-in-package',
+'''The normal icon isn't present in the package.''',
+
+'mini-icon-not-in-package',
+'''The mini icon isn't present in the package.''',
+
+'large-icon-not-in-package',
+'''The large icon isn't present in the package.''',
+
+'no-icon-in-menu',
+'''The menu entry doesn't contain an icon field.''',
+
+'invalid-title',
+'''The menu title contains invalid characters like /.''',
+
+'missing-menu-command',
+'''The menu file doesn't contain a command.''',
+
+'menu-in-wrong-directory',
+'''The menu files must be under /usr/lib/menu.''',
+
+'non-xdg-migrated-menu',
+'''The menu file has not been migrated to new XDG menu system.''',
+
+)
+
+# MenuCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/MenuXDGCheck.py b/MenuXDGCheck.py
new file mode 100644 (file)
index 0000000..7fb9e5b
--- /dev/null
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+# Version         : $Id$
+
+#
+# check xdg file format violation
+#
+# http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
+#
+
+from Filter import addDetails, printError
+from Pkg import getstatusoutput, is_utf8
+import AbstractCheck
+
+
+class MenuXDGCheck(AbstractCheck.AbstractFilesCheck):
+    def __init__(self):
+        # desktop file need to be in $XDG_DATA_DIRS
+        # $ echo $XDG_DATA_DIRS/applications
+        # /var/lib/menu-xdg:/usr/share
+        AbstractCheck.AbstractFilesCheck.__init__(
+            self, "MenuXDGCheck", "/usr/share/applications/.*\.desktop$")
+
+    def check_file(self, pkg, filename):
+        f = pkg.dirName() + filename
+        st = getstatusoutput(('desktop-file-validate', f), True)
+        if st[0]:
+            error_printed = False
+            for line in st[1].splitlines():
+                if 'error: ' in line:
+                    printError(pkg, 'invalid-desktopfile', filename,
+                               line.split('error: ')[1])
+                    error_printed = True
+            if not error_printed:
+                printError(pkg, 'invalid-desktopfile', filename)
+        if not is_utf8(f):
+            printError(pkg, 'non-utf8-desktopfile', filename)
+
+
+check = MenuXDGCheck()
+
+addDetails(
+'invalid-desktopfile',
+'''.desktop file is not valid, check with desktop-file-validate''',
+
+'non-utf8-desktopfile',
+'''.desktop file is not encoded in UTF-8''',
+)
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/NamingPolicyCheck.py b/NamingPolicyCheck.py
new file mode 100644 (file)
index 0000000..8f22483
--- /dev/null
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+#---------------------------------------------------------------
+# Project         : Mandriva Linux
+# Module          : rpmlint
+# File            : NamingPolicyCheck.py
+# Version         : $Id: NamingPolicyCheck.py 1732 2010-02-21 11:28:42Z scop $
+# Author          : Michael Scherer
+# Created On      : Mon May 19 11:25:37 2003
+# Purpose         : Check package names according to their content.
+#---------------------------------------------------------------
+
+import re
+
+from Filter import addDetails, printWarning
+import AbstractCheck
+
+
+# could be added.
+#
+# zope
+# abiword2
+# alsaplayer-plugin-input
+# emacs
+# gstreamer
+# nautilus
+# vlc-plugin
+# XFree
+# xine
+
+simple_naming_policy_re = re.compile('\^[a-zA-Z1-9-_]*$')
+
+class NamingPolicyNotAppliedException(Exception):
+    pass
+
+class NamingPolicyCheck(AbstractCheck.AbstractCheck):
+    checks_ = []
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, "NamingPolicyCheck")
+
+    def add_check(self, pkg_name, name_re, file_re):
+        c = {}
+        c['pkg_name'] = pkg_name
+        c['name_re'] = re.compile(name_re)
+        c['file_re'] = re.compile(file_re)
+        self.checks_.append(c)
+
+        if simple_naming_policy_re.search(name_re):
+            details = "Its name should begin with " + name_re[1:]
+        else:
+            details = "Its name should match the regular expression " + name_re
+
+        addDetails(pkg_name + '-naming-policy-not-applied',
+                   "This package doesn't respect the naming policy for %s "
+                   "packages.\n%s." % (pkg_name, details))
+
+    def check(self, pkg):
+        if pkg.isSource():
+            return
+        files = pkg.files()
+        if not files:
+            return
+        try:
+            # check for files then
+            for c in self.checks_:
+                for f in files:
+                    if c['file_re'].search(f) and \
+                            not c['name_re'].search(pkg.name):
+                        raise NamingPolicyNotAppliedException
+        except NamingPolicyNotAppliedException:
+            printWarning(pkg, c['pkg_name'] + '-naming-policy-not-applied', f)
+
+check = NamingPolicyCheck()
+
+#
+# these are the check currently implemented.
+#
+# first argument is the name of the check, printed by the warning.
+#   ex : xmms.
+#
+# secund argument is the regular expression of the naming policy.
+#   ex: xmms plugin should be named xmms-name_of_plugin.
+#
+# third is the path of the file that should contains a package to be related to
+# the naming scheme.
+#   ex: xmms plugin are put under /usr/lib/xmms/
+#
+# the module is far from being perfect since you need to check this file for
+# the naming file.
+# if somone as a elegant solution, I will be happy to implement and test it.
+
+
+check.add_check('xmms', '^xmms(-|$)', '^/usr/lib(64)?/xmms/')
+check.add_check('python', '^python(-|$)', '^/usr/lib(64)?/python[1-9](-[1-9])?')
+check.add_check('perl5', '^perl(-|$)', '^/usr/lib(64)?/perl5/vendor_perl')
+check.add_check('apache2', '^apache2-mod_', '^/usr/lib(64)?/apache2-')
+check.add_check('fortune', '^fortune(-|$)', '^/usr/share/games/fortunes/')
+check.add_check('php', '^php(-|$)', '/usr/lib(64)?/php/extensions/')
+check.add_check('ruby', '^ruby(-|$)', '/usr/lib(64)?/ruby/[1-9](-[1-9])?/')
+check.add_check('ocaml', '^ocaml(-|$)', '/usr/lib(64)?/ocaml/')
+
+# these exception should be added
+# apache2 => apache2-devel
+#            apache2-modules
+# ruby => apache2-mod_ruby
+#         ruby
+
+# NamingPolicyCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/PamCheck.py b/PamCheck.py
new file mode 100644 (file)
index 0000000..43cd35f
--- /dev/null
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+#---------------------------------------------------------------
+# Project         : Mandriva Linux
+# Module          : rpmlint
+# File            : PamCheck.py
+# Version         : $Id: /local/personal/PamCheck.py 4977 2006-01-31T09:29:13.664059Z misc  $
+# Author          : Michael Scherer
+# Created On      : 031/01/2006
+# Purpose         : Apply pam policy
+#---------------------------------------------------------------
+
+import re
+
+from Filter import addDetails, printError
+import AbstractCheck
+
+
+pam_stack_re = re.compile('^\s*[^#].*pam_stack\.so\s*service')
+
+class PamCheck(AbstractCheck.AbstractFilesCheck):
+    def __init__(self):
+        AbstractCheck.AbstractFilesCheck.__init__(self, "PamCheck",
+                                                  "/etc/pam\.d/.*")
+
+    def check_file(self, pkg, filename):
+        lines = pkg.grep(pam_stack_re, filename)
+        if lines:
+            printError(pkg, 'use-old-pam-stack', filename,
+                       '(line %s)' % ", ".join(lines))
+
+check = PamCheck()
+
+addDetails(
+'use-old-pam-stack',
+'''Update pam file to use include instead of pam_stack.''',
+)
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/Pkg.py b/Pkg.py
new file mode 100644 (file)
index 0000000..14118aa
--- /dev/null
+++ b/Pkg.py
@@ -0,0 +1,865 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : Pkg.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Tue Sep 28 07:18:06 1999
+# Version       : $Id: Pkg.py 1892 2011-11-23 20:21:05Z scop $
+# Purpose       : provide an API to handle a rpm package either by accessing
+#                 the rpm file or by accessing the files contained inside.
+#############################################################################
+
+import commands
+import os
+import re
+import stat
+import subprocess
+import sys
+import tempfile
+import types
+import urlparse
+
+try:
+    import magic
+    # TODO: magic.MAGIC_COMPRESS when PkgFile gets decompress support.
+    _magic = magic.open(magic.MAGIC_NONE)
+    _magic.load()
+except:
+    _magic = None
+import rpm
+
+import Filter
+
+# Python 2/3 compatibility/convenience wrapper for printing to stderr with
+# less concerns of UnicodeErrors than plain sys.stderr.write.
+if sys.version_info[0] > 2:
+    # Blows up with Python < 3 without the exec() hack
+    exec('def warn(s): print (s, file=sys.stderr)')
+else:
+    def warn(s): print >> sys.stderr, s
+
+
+# utilities
+
+# 64: RPMSENSE_PREREQ is 0 with recent rpm versions, we want 64 here in order
+# to do the right thing with packages built with older rpm versions
+PREREQ_FLAG = (rpm.RPMSENSE_PREREQ or 64) | \
+              rpm.RPMSENSE_SCRIPT_PRE | \
+              rpm.RPMSENSE_SCRIPT_POST | \
+              rpm.RPMSENSE_SCRIPT_PREUN | \
+              rpm.RPMSENSE_SCRIPT_POSTUN
+
+var_regex = re.compile('^(.*)\${?(\w+)}?(.*)$')
+
+def shell_var_value(var, script):
+    assign_regex = re.compile('\\b' + re.escape(var) + '\s*=\s*(.+)\s*(#.*)*$',
+                              re.MULTILINE)
+    res = assign_regex.search(script)
+    if res:
+        res2 = var_regex.search(res.group(1))
+        if res2:
+            if res2.group(2) == var: # infinite loop
+                return None
+        return substitute_shell_vars(res.group(1), script)
+    else:
+        return None
+
+def substitute_shell_vars(val, script):
+    res = var_regex.search(val)
+    if res:
+        value = shell_var_value(res.group(2), script)
+        if not value:
+            value = ''
+        return res.group(1) + value + \
+            substitute_shell_vars(res.group(3), script)
+    else:
+        return val
+
+def getstatusoutput(cmd, stdoutonly = False):
+    '''A version of commands.getstatusoutput() which can take cmd as a
+       sequence, thus making it potentially more secure.'''
+    if stdoutonly:
+        proc = subprocess.Popen(cmd, stdin=subprocess.PIPE,
+                                stdout=subprocess.PIPE, close_fds=True)
+    else:
+        proc = subprocess.Popen(cmd, stdin=subprocess.PIPE,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.STDOUT, close_fds=True)
+    proc.stdin.close()
+    text = proc.stdout.read()
+    sts = proc.wait()
+    if sts is None:
+        sts = 0
+    if text.endswith('\n'):
+        text = text[:-1]
+    return sts, text
+
+bz2_regex = re.compile('\.t?bz2?$')
+xz_regex = re.compile('\.(t[xl]z|xz|lzma)$')
+
+def catcmd(fname):
+    """Get a 'cat' command that handles possibly compressed files."""
+    cat = 'gzip -dcf'
+    if bz2_regex.search(fname):
+        cat = 'bzip2 -dcf'
+    elif xz_regex.search(fname):
+        cat = 'xz -dc'
+    return cat
+
+def is_utf8(fname):
+    (sts, text) = getstatusoutput(catcmd(fname).split() + [fname])
+    return not sts and is_utf8_str(text)
+
+def is_utf8_str(s):
+    try:
+        s.decode('UTF-8')
+    except:
+        return False
+    return True
+
+def to_utf8(string):
+    if string is None:
+        return ''
+    elif isinstance(string, unicode):
+        return string
+    try:
+        x = unicode(string, 'ascii')
+        return string
+    except UnicodeError:
+        encodings = ['utf-8', 'iso-8859-1', 'iso-8859-15', 'iso-8859-2']
+        for enc in encodings:
+            try:
+                x = unicode(string, enc)
+            except UnicodeError:
+                pass
+            else:
+                if x.encode(enc) == string:
+                    return x.encode('utf-8')
+    newstring = ''
+    for char in string:
+        if ord(char) > 127:
+            newstring = newstring + '?'
+        else:
+            newstring = newstring + char
+    return newstring
+
+def readlines(path):
+    fobj = open(path, "r")
+    try:
+        return fobj.readlines()
+    finally:
+        fobj.close()
+
+def mktemp():
+    tmpfd, tmpname = tempfile.mkstemp(prefix = 'rpmlint.')
+    tmpfile = os.fdopen(tmpfd, 'w')
+    return tmpfile, tmpname
+
+slash_regex = re.compile('/+')
+slashdot_regex = re.compile('/(\.(/|$))+')
+slashend_regex = re.compile('([^/])/+$')
+
+def safe_normpath(path):
+    """Like os.path.normpath but normalizes less aggressively thus being
+    potentially safer for paths containing symlinks."""
+    ret = slash_regex.sub('/', path)
+    ret = slashdot_regex.sub('\\2', ret)
+    ret = slashend_regex.sub('\\1', ret)
+    return ret
+
+def get_default_valid_rpmgroups(filename = None):
+    """Get default rpm groups from filename, or try to look them up from
+    the rpm package (if installed) if no filename is given"""
+    groups = []
+    if not filename:
+        try:
+            p = InstalledPkg('rpm')
+        except:
+            pass
+        else:
+            groupsfiles = [x for x in p.files() if x.endswith('/GROUPS')]
+            if groupsfiles:
+                filename = groupsfiles[0]
+    if filename and os.path.exists(filename):
+        fobj = open(filename)
+        try:
+            groups = fobj.read().strip().splitlines()
+        finally:
+            fobj.close()
+        if 'Development/Debug' not in groups:
+            groups.append('Development/Debug')
+        if 'Unspecified' not in groups: # auto-added by rpm >= 4.6.0
+            groups.append('Unspecified')
+        groups.sort()
+    return groups
+
+# from yum 3.2.27, rpmUtils.miscutils, with rpmlint modifications
+def compareEVR((e1, v1, r1), (e2, v2, r2)):
+    # return 1: a is newer than b
+    # 0: a and b are the same version
+    # -1: b is newer than a
+    # rpmlint mod: don't stringify None epochs to 'None' strings
+    if e1 is not None:
+        e1 = str(e1)
+    v1 = str(v1)
+    r1 = str(r1)
+    if e2 is not None:
+        e2 = str(e2)
+    v2 = str(v2)
+    r2 = str(r2)
+    rc = rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
+    return rc
+
+# from yum 3.2.27, rpmUtils.miscutils, with rpmlint modifications
+def rangeCompare(reqtuple, provtuple):
+    """returns true if provtuple satisfies reqtuple"""
+    (reqn, reqf, (reqe, reqv, reqr)) = reqtuple
+    (n, f, (e, v, r)) = provtuple
+    if reqn != n:
+        return 0
+
+    # unversioned satisfies everything
+    if not f or not reqf:
+        return 1
+
+    # and you thought we were done having fun
+    # if the requested release is left out then we have
+    # to remove release from the package prco to make sure the match
+    # is a success - ie: if the request is EQ foo 1:3.0.0 and we have
+    # foo 1:3.0.0-15 then we have to drop the 15 so we can match
+    if reqr is None:
+        r = None
+    # rpmlint mod: don't mess with provided Epoch, doing so breaks e.g.
+    # "Requires: foo < 1.0" should not be satisfied by "Provides: foo = 1:0.5"
+    #if reqe is None:
+    #    e = None
+    if reqv is None: # just for the record if ver is None then we're going to segfault
+        v = None
+
+    # if we just require foo-version, then foo-version-* will match
+    if r is None:
+        reqr = None
+
+    rc = compareEVR((e, v, r), (reqe, reqv, reqr))
+
+    # does not match unless
+    if rc >= 1:
+        if reqf in ['GT', 'GE', 4, 12]:
+            return 1
+        if reqf in ['EQ', 8]:
+            if f in ['LE', 10, 'LT', 2]:
+                return 1
+        if reqf in ['LE', 'LT', 'EQ', 10, 2, 8]:
+            if f in ['LE', 'LT', 10, 2]:
+                return 1
+
+    if rc == 0:
+        if reqf in ['GT', 4]:
+            if f in ['GT', 'GE', 4, 12]:
+                return 1
+        if reqf in ['GE', 12]:
+            if f in ['GT', 'GE', 'EQ', 'LE', 4, 12, 8, 10]:
+                return 1
+        if reqf in ['EQ', 8]:
+            if f in ['EQ', 'GE', 'LE', 8, 12, 10]:
+                return 1
+        if reqf in ['LE', 10]:
+            if f in ['EQ', 'LE', 'LT', 'GE', 8, 10, 2, 12]:
+                return 1
+        if reqf in ['LT', 2]:
+            if f in ['LE', 'LT', 10, 2]:
+                return 1
+    if rc <= -1:
+        if reqf in ['GT', 'GE', 'EQ', 4, 12, 8]:
+            if f in ['GT', 'GE', 4, 12]:
+                return 1
+        if reqf in ['LE', 'LT', 10, 2]:
+            return 1
+#                if rc >= 1:
+#                    if reqf in ['GT', 'GE', 4, 12]:
+#                        return 1
+#                if rc == 0:
+#                    if reqf in ['GE', 'LE', 'EQ', 8, 10, 12]:
+#                        return 1
+#                if rc <= -1:
+#                    if reqf in ['LT', 'LE', 2, 10]:
+#                        return 1
+
+    return 0
+
+# from yum 3.2.23, rpmUtils.miscutils, with rpmlint modifications
+def formatRequire(name, flags, evr):
+    s = name
+
+    if flags:
+        if flags & (rpm.RPMSENSE_LESS | rpm.RPMSENSE_GREATER |
+                    rpm.RPMSENSE_EQUAL):
+            s = s + " "
+            if flags & rpm.RPMSENSE_LESS:
+                s = s + "<"
+            if flags & rpm.RPMSENSE_GREATER:
+                s = s + ">"
+            if flags & rpm.RPMSENSE_EQUAL:
+                s = s + "="
+            s = "%s %s" % (s, versionToString(evr))
+    return s
+
+def versionToString(evr):
+    if not isinstance(evr, tuple) and not isinstance(evr, list):
+        # assume string
+        return evr
+    ret = ""
+    if evr[0] is not None and evr[0] != "":
+        ret += str(evr[0]) + ":"
+    if evr[1] is not None:
+        ret += evr[1]
+        if evr[2] is not None and evr[2] != "":
+            ret += "-" + evr[2]
+    return ret
+
+# from yum 3.2.23, rpmUtils.miscutils, with some rpmlint modifications
+def stringToVersion(verstring):
+    if verstring in (None, ''):
+        return (None, None, None)
+    epoch = None
+    i = verstring.find(':')
+    if i != -1:
+        try:
+            epoch = str(long(verstring[:i]))
+        except ValueError:
+            # garbage in epoch, ignore it
+            pass
+    i += 1
+    j = verstring.find('-', i)
+    if j != -1:
+        if verstring[i:j] == '':
+            version = None
+        else:
+            version = verstring[i:j]
+        release = verstring[j+1:]
+    else:
+        if verstring[i:] == '':
+            version = None
+        else:
+            version = verstring[i:]
+        release = None
+    return (epoch, version, release)
+
+def parse_deps(line):
+    '''Parse provides/requires/conflicts/obsoletes line to list of
+       (name, flags, (epoch, version, release)) tuples.'''
+
+    prcos = []
+    tokens = re.split('[\s,]+', line.strip())
+
+    # Drop line continuation backslash in multiline macro definition (for
+    # spec file parsing), e.g.
+    # [...] \
+    # Obsoletes: foo-%1 <= 1.0.0 \
+    # [...] \
+    # (yes, this is an ugly hack and we probably have other problems with
+    #  multiline macro definitions elsewhere...)
+    if tokens[-1] == '\\':
+        del tokens[-1]
+
+    prco = []
+    while tokens:
+        token = tokens.pop(0)
+        if not token:
+            # skip empty tokens
+            continue
+
+        plen = len(prco)
+
+        if plen == 0:
+            prco.append(token)
+
+        elif plen == 1:
+            flags = 0
+            if token[0] in ("=", "<", "<=", ">", ">="):
+                # versioned, flags
+                if "=" in token:
+                    flags |= rpm.RPMSENSE_EQUAL
+                if "<" in token:
+                    flags |= rpm.RPMSENSE_LESS
+                if ">" in token:
+                    flags |= rpm.RPMSENSE_GREATER
+                prco.append(flags)
+            else:
+                # no flags following name, treat as unversioned, add and reset
+                prco.extend((flags, (None, None, None)))
+                prcos.append(tuple(prco))
+                prco = [token]
+
+        elif plen == 2:
+            # last token of versioned one, add and reset
+            prco.append(stringToVersion(token))
+            prcos.append(tuple(prco))
+            prco = []
+
+    plen = len(prco)
+    if plen:
+        if plen == 1:
+            prco.extend((0, (None, None, None)))
+        elif plen == 2:
+            prco.append((None, None, None))
+        prcos.append(tuple(prco))
+
+    return prcos
+
+
+# classes representing package
+
+class Pkg:
+
+    _magic_from_compressed_re = re.compile('\([^)]+\s+compressed\s+data\\b')
+
+    def __init__(self, filename, dirname, header = None, is_source = False):
+        self.filename = filename
+        self.extracted = False
+        self.dirname = dirname
+        self.current_linenum = None
+        self._config_files = None
+        self._doc_files = None
+        self._noreplace_files = None
+        self._ghost_files = None
+        self._missingok_files = None
+        self._files = None
+        self._requires = None
+        self._req_names = -1
+
+        if header:
+            self.header = header
+            self.is_source = is_source
+        else:
+            # Create a package object from the file name
+            ts = rpm.TransactionSet()
+            # Don't check signatures here...
+            ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
+            fd = os.open(filename, os.O_RDONLY)
+            try:
+                self.header = ts.hdrFromFdno(fd)
+            finally:
+                os.close(fd)
+            self.is_source = not self.header[rpm.RPMTAG_SOURCERPM]
+
+        self.name = self.header[rpm.RPMTAG_NAME]
+        if self.isNoSource():
+            self.arch = 'nosrc'
+        elif self.isSource():
+            self.arch = 'src'
+        else:
+            self.arch = self.header[rpm.RPMTAG_ARCH]
+
+    # Return true if the package is a source package
+    def isSource(self):
+        return self.is_source
+
+    # Return true if the package is a nosource package.
+    # NoSource files are ghosts in source packages.
+    def isNoSource(self):
+        return self.is_source and self.ghostFiles()
+
+    # access the tags like an array
+    def __getitem__(self, key):
+        try:
+            val = self.header[key]
+        except:
+            val = []
+        if val == []:
+            return None
+        else:
+            return val
+
+    # return the name of the directory where the package is extracted
+    def dirName(self):
+        if not self.extracted:
+            self._extract()
+        return self.dirname
+
+    # extract rpm contents
+    def _extract(self):
+        s = os.stat(self.dirname)
+        if not stat.S_ISDIR(s[stat.ST_MODE]):
+            warn('Unable to access dir %s' % self.dirname)
+            return None
+        else:
+            self.dirname = tempfile.mkdtemp(
+                prefix = 'rpmlint.%s.' % os.path.basename(self.filename),
+                dir = self.dirname)
+            # TODO: better shell escaping or sequence based command invocation
+            command_str = \
+                'rpm2cpio "%s" | (cd "%s"; cpio -id); chmod -R +rX "%s"' % \
+                (self.filename, self.dirname, self.dirname)
+            cmd = commands.getstatusoutput(command_str)
+            self.extracted = True
+            return cmd
+
+    def checkSignature(self):
+        return getstatusoutput(('env', 'LC_ALL=C', 'rpm', '-K', self.filename))
+
+    # remove the extracted files from the package
+    def cleanup(self):
+        if self.extracted and self.dirname:
+            getstatusoutput(('rm', '-rf', self.dirname))
+
+    def grep(self, regex, filename):
+        """Grep regex from a file, return matching line numbers."""
+        ret = []
+        lineno = 0
+        in_file = None
+        try:
+            try:
+                in_file = open(self.dirName() + '/' + filename)
+                for line in in_file:
+                    lineno += 1
+                    if regex.search(line):
+                        ret.append(str(lineno))
+                        break
+            except Exception, e:
+                Filter.printWarning(self, 'read-error', filename, e)
+        finally:
+            if in_file:
+                in_file.close()
+        return ret
+
+    def langtag(self, tag, lang):
+        """Get value of tag in the given language."""
+        # LANGUAGE trumps other env vars per GNU gettext docs, see also #166
+        orig = os.environ.get('LANGUAGE')
+        os.environ['LANGUAGE'] = lang
+        ret = self[tag]
+        if orig is not None:
+            os.environ['LANGUAGE'] = orig
+        return ret
+
+    # return the associative array indexed on file names with
+    # the values as: (file perm, file owner, file group, file link to)
+    def files(self):
+        if self._files is not None:
+            return self._files
+
+        self._gatherFilesInfo()
+        return self._files
+
+    # return the list of config files
+    def configFiles(self):
+        if self._config_files is not None:
+            return self._config_files
+
+        self._config_files = [x.name for x in self.files().values()
+                              if x.is_config]
+        return self._config_files
+
+    # return the list of noreplace files
+    def noreplaceFiles(self):
+        if self._noreplace_files is not None:
+            return self._noreplace_files
+
+        self._noreplace_files = [x.name for x in self.files().values()
+                                 if x.is_noreplace]
+        return self._noreplace_files
+
+    # return the list of documentation files
+    def docFiles(self):
+        if self._doc_files is not None:
+            return self._doc_files
+
+        self._doc_files = [x.name for x in self.files().values() if x.is_doc]
+        return self._doc_files
+
+    # return the list of ghost files
+    def ghostFiles(self):
+        if self._ghost_files is not None:
+            return self._ghost_files
+
+        self._ghost_files = [x.name for x in self.files().values()
+                             if x.is_ghost]
+        return self._ghost_files
+
+    def missingOkFiles(self):
+        if self._missingok_files is not None:
+            return self._missingok_files
+
+        self._missingok_files = [x.name for x in self.files().values()
+                                 if x.is_missingok]
+        return self._missingok_files
+
+    # extract information about the files
+    def _gatherFilesInfo(self):
+
+        self._files = {}
+        flags = self.header[rpm.RPMTAG_FILEFLAGS]
+        modes = self.header[rpm.RPMTAG_FILEMODES]
+        users = self.header[rpm.RPMTAG_FILEUSERNAME]
+        groups = self.header[rpm.RPMTAG_FILEGROUPNAME]
+        links = self.header[rpm.RPMTAG_FILELINKTOS]
+        sizes = self.header[rpm.RPMTAG_FILESIZES]
+        md5s = self.header[rpm.RPMTAG_FILEMD5S]
+        mtimes = self.header[rpm.RPMTAG_FILEMTIMES]
+        rdevs = self.header[rpm.RPMTAG_FILERDEVS]
+        langs = self.header[rpm.RPMTAG_FILELANGS]
+        inodes = self.header[rpm.RPMTAG_FILEINODES]
+        requires = self.header[rpm.RPMTAG_FILEREQUIRE]
+        provides = self.header[rpm.RPMTAG_FILEPROVIDE]
+        files = self.header[rpm.RPMTAG_FILENAMES]
+        magics = self.header[rpm.RPMTAG_FILECLASS]
+        try: # rpm >= 4.7.0
+            filecaps = self.header[rpm.RPMTAG_FILECAPS]
+        except:
+            filecaps = None
+
+        # rpm-python < 4.6 does not return a list for this (or FILEDEVICES,
+        # FWIW) for packages containing exactly one file
+        if not isinstance(inodes, types.ListType):
+            inodes = [inodes]
+
+        if files:
+            for idx in range(0, len(files)):
+                pkgfile = PkgFile(files[idx])
+                # Do not use os.path.join here, pkgfile.name can start with a
+                # / which would result in self.dirName being ignored
+                pkgfile.path = os.path.normpath(
+                    self.dirName() + '/' + pkgfile.name)
+                pkgfile.flags = flags[idx]
+                pkgfile.mode = modes[idx]
+                pkgfile.user = users[idx]
+                pkgfile.group = groups[idx]
+                pkgfile.linkto = links[idx] and safe_normpath(links[idx])
+                pkgfile.size = sizes[idx]
+                pkgfile.md5 = md5s[idx]
+                pkgfile.mtime = mtimes[idx]
+                pkgfile.rdev = rdevs[idx]
+                pkgfile.inode = inodes[idx]
+                pkgfile.requires = parse_deps(requires[idx])
+                pkgfile.provides = parse_deps(provides[idx])
+                pkgfile.lang = langs[idx]
+                pkgfile.magic = magics[idx]
+                if not pkgfile.magic and _magic:
+                    pkgfile.magic = _magic.file(pkgfile.path)
+                if pkgfile.magic is None:
+                    pkgfile.magic = ''
+                elif Pkg._magic_from_compressed_re.search(pkgfile.magic):
+                    # Discard magic from inside compressed files ("file -z")
+                    # until PkgFile gets decompression support.  We may get
+                    # such magic strings from package headers already now;
+                    # for example Fedora's rpmbuild as of F-11's 4.7.1 is
+                    # patched so it generates them.
+                    pkgfile.magic = ''
+                if filecaps:
+                    pkgfile.filecaps = filecaps[idx]
+                self._files[pkgfile.name] = pkgfile
+
+    def readlink(self, pkgfile):
+        """Resolve symlinks for the given PkgFile, return the dereferenced
+           PkgFile if it is found in this package, None if not."""
+        result = pkgfile
+        while result and result.linkto:
+            linkpath = urlparse.urljoin(result.name, result.linkto)
+            linkpath = safe_normpath(linkpath)
+            result = self.files().get(linkpath)
+        return result
+
+    # API to access dependency information
+    def obsoletes(self):
+        """Get package Obsoletes as list of
+           (name, flags, (epoch, version, release)) tuples."""
+        self._gatherDepInfo()
+        return self._obsoletes
+
+    def requires(self):
+        """Get package Requires as list of
+           (name, flags, (epoch, version, release)) tuples."""
+        self._gatherDepInfo()
+        return self._requires
+
+    def prereq(self):
+        """Get package PreReqs as list of
+           (name, flags, (epoch, version, release)) tuples."""
+        self._gatherDepInfo()
+        return self._prereq
+
+    def req_names(self):
+        if self._req_names == -1:
+            self._req_names = [x[0] for x in self.requires() + self.prereq()]
+        return self._req_names
+
+    def check_versioned_dep(self, name, version):
+        # try to match name%_isa as well (e.g. "foo(x86-64)", "foo(x86-32)")
+        name_re = re.compile('^%s(\(\w+-\d+\))?$' % re.escape(name))
+        for d in self.requires() + self.prereq():
+            if name_re.match(d[0]):
+                if d[1] & rpm.RPMSENSE_EQUAL != rpm.RPMSENSE_EQUAL \
+                        or d[2][1] != version:
+                    return False
+                return True
+        return False
+
+    def conflicts(self):
+        """Get package Conflicts as list of
+           (name, flags, (epoch, version, release)) tuples."""
+        self._gatherDepInfo()
+        return self._conflicts
+
+    def provides(self):
+        """Get package Provides as list of
+           (name, flags, (epoch, version, release)) tuples."""
+        self._gatherDepInfo()
+        return self._provides
+
+    # internal function to gather dependency info used by the above ones
+    def _gather_aux(self, header, list, nametag, flagstag, versiontag,
+                    prereq = None):
+        names = header[nametag]
+        flags = header[flagstag]
+        versions = header[versiontag]
+
+        if versions:
+            for loop in range(len(versions)):
+                evr = stringToVersion(versions[loop])
+                if prereq is not None and flags[loop] & PREREQ_FLAG:
+                    prereq.append((names[loop], flags[loop] & (~PREREQ_FLAG),
+                                   evr))
+                else:
+                    list.append((names[loop], flags[loop], evr))
+
+    def _gatherDepInfo(self):
+        if self._requires is None:
+            self._requires = []
+            self._prereq = []
+            self._provides = []
+            self._conflicts = []
+            self._obsoletes = []
+
+            self._gather_aux(self.header, self._requires,
+                             rpm.RPMTAG_REQUIRENAME,
+                             rpm.RPMTAG_REQUIREFLAGS,
+                             rpm.RPMTAG_REQUIREVERSION,
+                             self._prereq)
+            self._gather_aux(self.header, self._conflicts,
+                             rpm.RPMTAG_CONFLICTNAME,
+                             rpm.RPMTAG_CONFLICTFLAGS,
+                             rpm.RPMTAG_CONFLICTVERSION)
+            self._gather_aux(self.header, self._provides,
+                             rpm.RPMTAG_PROVIDENAME,
+                             rpm.RPMTAG_PROVIDEFLAGS,
+                             rpm.RPMTAG_PROVIDEVERSION)
+            self._gather_aux(self.header, self._obsoletes,
+                             rpm.RPMTAG_OBSOLETENAME,
+                             rpm.RPMTAG_OBSOLETEFLAGS,
+                             rpm.RPMTAG_OBSOLETEVERSION)
+
+    def scriptprog(self, which):
+        """Get the specified script interpreter as a string.
+           Depending on rpm-python version, the string may or may not include
+           interpreter arguments, if any."""
+        prog = self[which]
+        if prog is None:
+            prog = ""
+        elif not isinstance(prog, basestring):
+            # http://rpm.org/ticket/847#comment:2
+            prog = " ".join(prog)
+        return prog
+
+def getInstalledPkgs(name):
+    """Get list of installed package objects by name."""
+
+    pkgs = []
+    ts = rpm.TransactionSet()
+    if re.search('[?*]|\[.+\]', name):
+        mi = ts.dbMatch()
+        mi.pattern("name", rpm.RPMMIRE_GLOB, name)
+    else:
+        mi = ts.dbMatch("name", name)
+
+    for hdr in mi:
+        pkgs.append(InstalledPkg(name, hdr))
+
+    return pkgs
+
+# Class to provide an API to an installed package
+class InstalledPkg(Pkg):
+    def __init__(self, name, hdr = None):
+        if not hdr:
+            ts = rpm.TransactionSet()
+            mi = ts.dbMatch('name', name)
+            if not mi:
+                raise KeyError(name)
+            try:
+                hdr = mi.next()
+            except StopIteration:
+                raise KeyError(name)
+
+        Pkg.__init__(self, name, '/', hdr)
+
+        self.extracted = True
+        # create a fake filename to satisfy some checks on the filename
+        self.filename = '%s-%s-%s.%s.rpm' % \
+            (self.name, self[rpm.RPMTAG_VERSION], self[rpm.RPMTAG_RELEASE],
+             self[rpm.RPMTAG_ARCH])
+
+    def cleanup(self):
+        pass
+
+    def checkSignature(self):
+        return (0, 'fake: pgp md5 OK')
+
+# Class to provide an API to a "fake" package, eg. for specfile-only checks
+class FakePkg:
+    def __init__(self, name):
+        self.name = name
+        self.arch = None
+        self.current_linenum = None
+
+    def cleanup(self):
+        pass
+
+# Class for files in packages
+class PkgFile(object):
+
+    def __init__(self, name):
+        self.name = name
+        # Real path to the file (taking extract dir into account)
+        self.path = name
+        self.flags = 0
+        self.mode = 0
+        self.user = None
+        self.group = None
+        self.linkto = ''
+        self.size = None
+        self.md5 = None
+        self.mtime = 0
+        self.rdev = ''
+        self.inode = 0
+        self.requires = []
+        self.provides = []
+        self.lang = ''
+        self.magic = ''
+        self.filecaps = None
+
+    # TODO: decompression support
+
+    is_config    = property(lambda self: self.flags & rpm.RPMFILE_CONFIG)
+    is_doc       = property(lambda self: self.flags & rpm.RPMFILE_DOC)
+    is_noreplace = property(lambda self: self.flags & rpm.RPMFILE_NOREPLACE)
+    is_ghost     = property(lambda self: self.flags & rpm.RPMFILE_GHOST)
+    is_missingok = property(lambda self: self.flags & rpm.RPMFILE_MISSINGOK)
+
+
+if __name__ == '__main__':
+    for p in sys.argv[1:]:
+        pkg = Pkg(sys.argv[1], tempfile.gettempdir())
+        print ('Requires: %s' % pkg.requires())
+        print ('Prereq: %s' % pkg.prereq())
+        print ('Conflicts: %s' % pkg.conflicts())
+        print ('Provides: %s' % pkg.provides())
+        print ('Obsoletes: %s' % pkg.obsoletes())
+        pkg.cleanup()
+
+# Pkg.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/PostCheck.py b/PostCheck.py
new file mode 100644 (file)
index 0000000..c72bbe1
--- /dev/null
@@ -0,0 +1,257 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# Project         : Mandriva Linux
+# Module          : rpmlint
+# File            : PostCheck.py
+# Version         : $Id: PostCheck.py 1885 2011-09-13 18:15:29Z scop $
+# Author          : Frederic Lepied
+# Created On      : Wed Jul  5 13:30:17 2000
+# Purpose         : Check post/pre scripts
+#############################################################################
+
+import os
+import re
+import types
+
+import rpm
+
+from Filter import addDetails, printError, printWarning
+import AbstractCheck
+import Config
+import Pkg
+
+
+DEFAULT_VALID_SHELLS = ('<lua>',
+                        '/bin/sh',
+                        '/bin/bash',
+                        '/sbin/sash',
+                        '/usr/bin/perl',
+                        '/sbin/ldconfig',
+                        )
+
+DEFAULT_EMPTY_SHELLS = ('/sbin/ldconfig',
+                        )
+
+valid_shells = Config.getOption('ValidShells', DEFAULT_VALID_SHELLS)
+empty_shells = Config.getOption('ValidEmptyShells', DEFAULT_EMPTY_SHELLS)
+# shells that grok the -n switch for debugging
+syntaxcheck_shells = ('/bin/sh', '/bin/bash')
+
+percent_regex = re.compile('^[^#]*%{?\w{3,}', re.MULTILINE)
+bracket_regex = re.compile('^[^#]*if.*[^ :\]]\]', re.MULTILINE)
+home_regex = re.compile('[^a-zA-Z]+~/|\${?HOME(\W|$)', re.MULTILINE)
+dangerous_command_regex = re.compile("(^|[;\|`]|&&|$\()\s*(?:\S*/s?bin/)?(cp|mv|ln|tar|rpm|chmod|chown|rm|cpio|install|perl|userdel|groupdel)\s", re.MULTILINE)
+selinux_regex = re.compile("(^|[;\|`]|&&|$\()\s*(?:\S*/s?bin/)?(chcon|runcon)\s", re.MULTILINE)
+single_command_regex = re.compile("^[ \n]*([^ \n]+)[ \n]*$")
+tmp_regex = re.compile('^[^#]*\s(/var)?/tmp', re.MULTILINE)
+menu_regex = re.compile('^/usr/lib/menu/|^/etc/menu-methods/|^/usr/share/applications/')
+bogus_var_regex = re.compile('(\${?RPM_BUILD_(ROOT|DIR)}?)')
+
+prereq_assoc = (
+#    ['chkconfig', ('chkconfig', '/sbin/chkconfig')],
+    ['chkfontpath', ('chkfontpath', '/usr/sbin/chkfontpath')],
+    ['rpm-helper', ('rpm-helper',)],
+    )
+
+for p in prereq_assoc:
+    p[0] = re.compile('^[^#]+' + p[0], re.MULTILINE)
+
+# pychecker fix
+del p
+
+script_tags = [
+    (rpm.RPMTAG_PREIN,          rpm.RPMTAG_PREINPROG,         '%pre'),
+    (rpm.RPMTAG_POSTIN,         rpm.RPMTAG_POSTINPROG,        '%post'),
+    (rpm.RPMTAG_PREUN,          rpm.RPMTAG_PREUNPROG,         '%preun'),
+    (rpm.RPMTAG_POSTUN,         rpm.RPMTAG_POSTUNPROG,        '%postun'),
+    (rpm.RPMTAG_TRIGGERSCRIPTS, rpm.RPMTAG_TRIGGERSCRIPTPROG, '%trigger'),
+    (rpm.RPMTAG_PRETRANS,       rpm.RPMTAG_PRETRANSPROG,      '%pretrans'),
+    (rpm.RPMTAG_POSTTRANS,      rpm.RPMTAG_POSTTRANSPROG,     '%posttrans'),
+    (rpm.RPMTAG_VERIFYSCRIPT,   rpm.RPMTAG_VERIFYSCRIPTPROG,  '%verifyscript'),
+    ]
+
+def incorrect_shell_script(prog, shellscript):
+    if not shellscript:
+        return False
+    # TODO: test that "prog" is available/executable
+    tmpfile, tmpname = Pkg.mktemp()
+    try:
+        tmpfile.write(shellscript)
+        tmpfile.close()
+        ret = Pkg.getstatusoutput((prog, '-n', tmpname))
+    finally:
+        tmpfile.close()
+        os.remove(tmpname)
+    return ret[0]
+
+def incorrect_perl_script(prog, perlscript):
+    if not perlscript:
+        return False
+    # TODO: test that "prog" is available/executable
+    tmpfile, tmpname = Pkg.mktemp()
+    try:
+        tmpfile.write(perlscript)
+        tmpfile.close()
+        ret = Pkg.getstatusoutput((prog, '-wc', tmpname))
+    finally:
+        tmpfile.close()
+        os.remove(tmpname)
+    return ret[0]
+
+class PostCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, 'PostCheck')
+
+    def check(self, pkg):
+        # Check only binary package
+        if pkg.isSource():
+            return
+
+        prereq = [x[0] for x in pkg.prereq()]
+        files = pkg.files()
+
+        for tag in script_tags:
+            script = pkg[tag[0]]
+
+            if not isinstance(script, types.ListType):
+                prog = pkg.scriptprog(tag[1])
+                if prog:
+                    prog = prog.split()[0]
+                self.check_aux(pkg, files, prog, script, tag[2], prereq)
+            else:
+                prog = pkg[tag[1]]
+                for idx in range(0, len(prog)):
+                    self.check_aux(
+                        pkg, files, prog[idx], script[idx], tag[2], prereq)
+
+        ghost_files = pkg.ghostFiles()
+        if ghost_files:
+            postin = pkg[rpm.RPMTAG_POSTIN]
+            prein = pkg[rpm.RPMTAG_PREIN]
+            if not postin and not prein:
+                printWarning(pkg, 'ghost-files-without-postin')
+            else:
+                for f in ghost_files:
+                    if (not postin or f not in postin) and \
+                       (not prein or f not in prein) and \
+                       f not in pkg.missingOkFiles():
+                        printWarning(pkg,
+                                     'postin-without-ghost-file-creation', f)
+
+    def check_aux(self, pkg, files, prog, script, tag, prereq):
+        if script:
+            if prog:
+                if prog not in valid_shells:
+                    printError(pkg, 'invalid-shell-in-' + tag, prog)
+                if prog in empty_shells:
+                    printError(pkg, 'non-empty-' + tag, prog)
+            if prog in syntaxcheck_shells or prog == '/usr/bin/perl':
+                if percent_regex.search(script):
+                    printWarning(pkg, 'percent-in-' + tag)
+                if bracket_regex.search(script):
+                    printWarning(pkg, 'spurious-bracket-in-' + tag)
+                res = dangerous_command_regex.search(script)
+                if res:
+                    printWarning(pkg, 'dangerous-command-in-' + tag,
+                                 res.group(2))
+                res = selinux_regex.search(script)
+                if res:
+                    printError(pkg, 'forbidden-selinux-command-in-' + tag,
+                               res.group(2))
+
+                if 'update-menus' in script:
+                    menu_error = True
+                    for f in files:
+                        if menu_regex.search(f):
+                            menu_error = False
+                            break
+                    if menu_error:
+                        printError(pkg, 'update-menus-without-menu-file-in-' +
+                                   tag)
+                if tmp_regex.search(script):
+                    printError(pkg, 'use-tmp-in-' + tag)
+                for c in prereq_assoc:
+                    if c[0].search(script):
+                        found = False
+                        for p in c[1]:
+                            if p in prereq or p in files:
+                                found = True
+                                break
+                        if not found:
+                            printError(pkg, 'no-prereq-on', c[1][0])
+
+            if prog in syntaxcheck_shells:
+                if incorrect_shell_script(prog, script):
+                    printError(pkg, 'shell-syntax-error-in-' + tag)
+                if home_regex.search(script):
+                    printError(pkg, 'use-of-home-in-' + tag)
+                res = bogus_var_regex.search(script)
+                if res:
+                    printWarning(pkg, 'bogus-variable-use-in-' + tag,
+                                 res.group(1))
+
+            if prog == '/usr/bin/perl':
+                if incorrect_perl_script(prog, script):
+                    printError(pkg, 'perl-syntax-error-in-' + tag)
+            elif prog.endswith('sh'):
+                res = single_command_regex.search(script)
+                if res:
+                    printWarning(pkg, 'one-line-command-in-' + tag,
+                                 res.group(1))
+
+        elif prog not in empty_shells and prog in valid_shells:
+            printWarning(pkg, 'empty-' + tag)
+
+# Create an object to enable the auto registration of the test
+check = PostCheck()
+
+# Add information about checks
+addDetails(
+'postin-without-ghost-file-creation',
+'''A file tagged as ghost is not created during %prein nor during %postin.''',
+)
+for scriptlet in (
+    '%pre', '%post', '%preun', '%postun', '%pretrans', '%posttrans',
+    '%trigger', '%triggerin', '%triggerprein', '%triggerun', '%triggerpostun',
+    '%verifyscript'):
+    addDetails(
+'one-line-command-in-%s' % scriptlet,
+'''You should use %s -p <command> instead of using:
+
+%s
+<command>
+
+It will avoid the fork of a shell interpreter to execute your command as
+well as allows rpm to automatically mark the dependency on your command
+for the excecution of the scriptlet.''' % (scriptlet, scriptlet),
+
+'percent-in-%s' % scriptlet,
+'''The %s scriptlet contains a "%%" in a context which might indicate it being
+fallout from an rpm macro/variable which was not expanded during build.
+Investigate whether this is the case and fix if appropriate.''' % scriptlet,
+
+'spurious-bracket-in-%s' % scriptlet,
+'''The %s scriptlet contains an "if []" construct without a space before
+the "]".''' % scriptlet,
+
+'forbidden-selinux-command-in-%s' % scriptlet,
+'''A command which requires intimate knowledge about a specific SELinux
+policy type was found in the scriptlet. These types are subject to change
+on a policy version upgrade. Use the restorecon command which queries the
+currently loaded policy for the correct type instead.''',
+
+'non-empty-%s' % scriptlet,
+'''Scriptlets for the interpreter mentioned in the message should be empty.
+One common case where they are unintentionally not is when the specfile
+contains comments after the scriptlet and before the next section. Review
+and clean up the scriptlet contents if appropriate.''',
+)
+
+# PostCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..6b8e352
--- /dev/null
+++ b/README
@@ -0,0 +1,67 @@
+rpmlint is a tool for checking common errors in rpm packages.  rpmlint
+can be used to test individual packages before uploading or to check
+an entire distribution.  By default all applicable checks are
+performed but specific checks can be performed by using command line
+parameters.
+
+rpmlint can check binary rpms (files and installed ones), source rpms,
+and plain specfiles, but all checks do not apply to all argument
+types.  For best check coverage, run rpmlint on source rpms instead of
+plain specfiles, and installed binary rpms instead of uninstalled
+binary rpm files.
+
+The idea for rpmlint is from the lintian tool of the Debian project.
+
+Comments and new checks welcome. See the project home page at
+http://rpmlint.zarb.org/ for mailing list information, bug tracking
+system and other project resources.
+
+Implemented checks:
+
+       o Tag checks (TagsCheck).
+       o Distribution specific checks (MandrakeCheck).
+       o Binary checks (BinaryCheck).
+       o Configuration file checks (ConfigCheck).
+       o Location, permission, group and owner checks (FileCheck).
+       o suid warnings (FileCheck).
+       o Signature checks (SignatureCheck).
+       o FHS checks (FHSCheck).
+       o Source specific checks (SourceCheck).
+       o i18n checks (I18NCheck).
+       o Menu system checks (MenuCheck).
+       o %post; %pre, %postun and %preun script checks (PostCheck).
+       o /etc/rc.d/init.d checks (InitScriptCheck).
+       o Spec file checks (SpecCheck).
+       o Zip/Jar file checks (ZipCheck).
+       o Pam configuration file checks (PamCheck).
+       o Rpm file checks (RpmFileCheck).
+
+If you want to change configuration options or the list of checks, use
+the global configuration files /etc/rpmlint/*config or the user
+configuration file $XDG_CONFIG_HOME/rpmlint (~/.config/rpmlint if
+$XDG_CONFIG_HOME is empty or not set).
+
+Configuration files are Python source files and should begin with the
+following line:
+
+from Config import *
+
+to load configuration functions.
+
+Configuration functions:
+
+resetChecks() resets the list of checks.
+
+addCheck(check) adds the check to the list of checks to try.
+
+addCheckDir(path) adds a path to look for checks.
+
+setOption(name, value) sets the value of the configuration option.
+See below for the list of available options.
+
+addFilter(regexp) adds a filter to remove the output of a check, and
+removeFilter(regexp) removes one (for use eg. in per-user configuration
+files to remove filters added in system config files).
+
+See the file "config" shipped with rpmlint for examples, available
+options and their default values.
diff --git a/README.devel b/README.devel
new file mode 100644 (file)
index 0000000..81657d1
--- /dev/null
@@ -0,0 +1,15 @@
+The latest development version can be retrieved from anonymous Subversion:
+
+$ svn checkout http://rpmlint.zarb.org/svn/trunk/
+
+To run rpmlint from the working tree or in place from an unpacked tarball:
+
+$ ./rpmlint -C . <rpms>
+
+...and to run only a single check, for example to test only FHSCheck:
+
+$ ./rpmlint -C . -c FHSCheck <rpms>
+
+For profiling, use something like:
+
+$ python -O -u -m cProfile -s cumulative rpmlint -C . [...]
diff --git a/RpmFileCheck.py b/RpmFileCheck.py
new file mode 100644 (file)
index 0000000..d4ffb19
--- /dev/null
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# check the rpm file for various errors.
+# $Id: RpmFileCheck.py 1732 2010-02-21 11:28:42Z scop $
+
+# Copyright (C) 2006 Michael Scherer <misc@zarb.org>
+#                    Ville Skyttä <ville.skytta@iki.fi>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+import os
+
+from Filter import addDetails, printWarning
+import AbstractCheck
+
+
+class RpmFileCheck(AbstractCheck.AbstractCheck):
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, "RpmFileCheck")
+
+    def check(self, pkg):
+        # http://en.wikipedia.org/wiki/Joliet_(file_system)
+        rpmfile_name = os.path.basename(pkg.filename)
+        if len(rpmfile_name) > 64:
+            printWarning(pkg, 'filename-too-long-for-joliet', rpmfile_name)
+
+check = RpmFileCheck()
+
+addDetails(
+'filename-too-long-for-joliet',
+'''This filename is too long to fit on a joliet filesystem (limit is 64 unicode
+chars).''',
+)
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/SignatureCheck.py b/SignatureCheck.py
new file mode 100644 (file)
index 0000000..fd09812
--- /dev/null
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : SignatureCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Thu Oct  7 17:06:14 1999
+# Version       : $Id: SignatureCheck.py 1732 2010-02-21 11:28:42Z scop $
+# Purpose       : check the presence of a PGP signature.
+#############################################################################
+
+import re
+
+from Filter import addDetails, printError
+import AbstractCheck
+import Pkg
+
+
+class SignatureCheck(AbstractCheck.AbstractCheck):
+    pgp_regex = re.compile("pgp|gpg", re.IGNORECASE)
+    unknown_key_regex = re.compile("\(MISSING KEYS:(?:\([^)]+\))?\s+([^\)]+)\)")
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, "SignatureCheck")
+
+    def check(self, pkg):
+        res = pkg.checkSignature()
+        if not res or res[0] != 0:
+            if res and res[1]:
+                kres = SignatureCheck.unknown_key_regex.search(res[1])
+            else:
+                kres = None
+            if kres:
+                printError(pkg, "unknown-key", kres.group(1))
+            else:
+                Pkg.warn("Error checking signature of %s: %s" %
+                         (pkg.filename, res[1]))
+        else:
+            if not SignatureCheck.pgp_regex.search(res[1]):
+                printError(pkg, "no-signature")
+
+# Create an object to enable the auto registration of the test
+check = SignatureCheck()
+
+addDetails(
+'no-signature',
+'''You have to include your pgp or gpg signature in your package.
+For more information on signatures, please refer to www.gnupg.org.''',
+
+'unknown-key',
+'''The package was signed, but with an unknown key.
+See the rpm --import option for more information.''',
+)
+
+# SignatureCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/SourceCheck.py b/SourceCheck.py
new file mode 100644 (file)
index 0000000..b93d5ba
--- /dev/null
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : SourceCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Wed Oct 27 21:17:03 1999
+# Version       : $Id: SourceCheck.py 1754 2010-03-30 20:34:48Z scop $
+# Purpose       : verify source package correctness.
+#############################################################################
+
+import re
+
+from Filter import addDetails, printError, printWarning
+import AbstractCheck
+import Config
+
+
+DEFAULT_VALID_SRC_PERMS = (0644, 0755)
+
+source_regex = re.compile('\\.(tar|patch|tgz|diff)$')
+compress_ext = Config.getOption("CompressExtension", "bz2")
+valid_src_perms = Config.getOption("ValidSrcPerms", DEFAULT_VALID_SRC_PERMS)
+
+class SourceCheck(AbstractCheck.AbstractCheck):
+
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, 'SourceCheck')
+
+    def check(self, pkg):
+        # Check only source package
+        if not pkg.isSource():
+            return
+
+        # process file list
+        spec_file = None
+        for fname, pkgfile in pkg.files().items():
+            if fname.endswith('.spec'):
+                if spec_file:
+                    printError(pkg, 'multiple-specfiles', spec_file, fname)
+                else:
+                    spec_file = fname
+            elif source_regex.search(fname) and compress_ext and \
+                    not fname.endswith(compress_ext):
+                printWarning(pkg, 'source-or-patch-not-compressed',
+                             compress_ext, fname)
+            perm = pkgfile.mode & 07777
+            if perm not in valid_src_perms:
+                printWarning(pkg, 'strange-permission', fname, oct(perm))
+
+check = SourceCheck()
+
+addDetails(
+'multiple-specfiles',
+'''Your package contains multiple spec files. To build a
+correct package, you need to have only one spec file containing
+all your RPM information.''',
+
+'source-or-patch-not-compressed',
+'''A source archive or file in your package is not compressed using the %s
+compression method (doesn't have the %s extension).''' %
+(compress_ext, compress_ext),
+
+'strange-permission',
+'''A file that you listed to include in your package has strange
+permissions. Usually, a file should have 0644 permissions.''',
+
+)
+
+# SourceCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/SpecCheck.py b/SpecCheck.py
new file mode 100644 (file)
index 0000000..97e1da6
--- /dev/null
@@ -0,0 +1,812 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : SpecCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Thu Oct  7 17:06:14 1999
+# Version       : $Id: SpecCheck.py 1892 2011-11-23 20:21:05Z scop $
+# Purpose       : check the spec file of a source rpm.
+#############################################################################
+
+import re
+try:
+    from urlparse import urlparse
+except ImportError: # Python 3
+    from urllib.parse import urlparse
+
+import rpm
+
+from Filter import addDetails, printError, printWarning
+from TagsCheck import VALID_GROUPS
+import AbstractCheck
+import Config
+import Pkg
+
+
+# Don't check for hardcoded library paths in biarch packages
+DEFAULT_BIARCH_PACKAGES = '^(gcc|glibc)'
+
+# Don't check for hardcoded library paths in packages which can have
+# their noarch files in /usr/lib/<package>/*, or packages that can't
+# be installed on biarch systems
+DEFAULT_HARDCODED_LIB_PATH_EXCEPTIONS = '/lib/(modules|cpp|perl5|rpm|hotplug|firmware)($|[\s/,])'
+patch_regex = re.compile("^Patch(\d*)\s*:\s*(\S+)", re.IGNORECASE)
+applied_patch_regex = re.compile("^%patch(\d*)")
+applied_patch_p_regex = re.compile("\s-P\s+(\d+)\\b")
+applied_patch_pipe_regex = re.compile(r'\s%\{PATCH(\d+)\}\s*\|\s*(%\{?__)?patch\b')
+source_dir_regex = re.compile("^[^#]*(\$RPM_SOURCE_DIR|%{?_sourcedir}?)")
+obsolete_tags_regex = re.compile("^(Copyright|Serial)\s*:\s*(\S+)")
+buildroot_regex = re.compile('^BuildRoot\s*:\s*(\S+)', re.IGNORECASE)
+prefix_regex = re.compile('^Prefix\s*:\s*(\S+)', re.IGNORECASE)
+packager_regex = re.compile('^Packager\s*:\s*(\S+)', re.IGNORECASE)
+buildarch_regex = re.compile('^BuildArch(itectures)?\s*:\s*(.+?)\s*$', re.IGNORECASE)
+make_check_regex = re.compile('(^|\s|%{?__)make}?\s+(check|test)')
+rm_regex = re.compile('(^|\s)((.*/)?rm|%{?__rm}?) ')
+rpm_buildroot_regex = re.compile('^[^#]*(?:(\\\*)\${?RPM_BUILD_ROOT}?|(%+){?buildroot}?)')
+configure_libdir_spec_regex = re.compile('ln |\./configure[^#]*--libdir=(\S+)[^#]*')
+lib_package_regex = re.compile('^%package.*\Wlib')
+ifarch_regex = re.compile('^\s*%ifn?arch\s')
+if_regex = re.compile('^\s*%if\s')
+endif_regex = re.compile('^\s*%endif\\b')
+biarch_package_regex = re.compile(DEFAULT_BIARCH_PACKAGES)
+hardcoded_lib_path_exceptions_regex = re.compile(Config.getOption('HardcodedLibPathExceptions', DEFAULT_HARDCODED_LIB_PATH_EXCEPTIONS))
+prereq_regex = re.compile('^PreReq(\(.*\))?:\s*(.+?)\s*$', re.IGNORECASE)
+buildprereq_regex = re.compile('^BuildPreReq:\s*(.+?)\s*$', re.IGNORECASE)
+use_utf8 = Config.getOption('UseUTF8', Config.USEUTF8_DEFAULT)
+libdir_regex = re.compile('%{?_lib(?:dir)?\}?\\b')
+comment_or_empty_regex = re.compile('^\s*(#|$)')
+defattr_regex = re.compile('^\s*%defattr\\b')
+attr_regex = re.compile('^\s*%attr\\b')
+section_regexs = dict(
+    ([x, re.compile('^%' + x + '(?:\s|$)')]
+     for x in ('build', 'changelog', 'check', 'clean', 'description', 'files',
+               'install', 'package', 'prep', 'pre', 'post', 'preun', 'postun',
+               'trigger', 'triggerin', 'triggerun', 'triggerprein',
+               'triggerpostun', 'pretrans', 'posttrans')))
+deprecated_grep_regex = re.compile(r'\b[ef]grep\b')
+
+# Only check for /lib, /usr/lib, /usr/X11R6/lib
+# TODO: better handling of X libraries and modules.
+hardcoded_library_paths = '(/lib|/usr/lib|/usr/X11R6/lib/(?!([^/]+/)+)[^/]*\\.([oa]|la|so[0-9.]*))'
+hardcoded_library_path_regex = re.compile('^[^#]*((^|\s+|\.\./\.\.|\${?RPM_BUILD_ROOT}?|%{?buildroot}?|%{?_prefix}?)' + hardcoded_library_paths + '(?=[\s;/])([^\s,;]*))')
+
+# Requires(pre,post) is broken in some rpm versions, see
+# https://bugzilla.redhat.com/118780 and bugs linked to that one.
+scriptlet_requires_regex = re.compile('^(PreReq|Requires)\([^\)]*,', re.IGNORECASE)
+
+depscript_override_regex = re.compile('(^|\s)%(define|global)\s+__find_(requires|provides)\s')
+depgen_disable_regex = re.compile('(^|\s)%(define|global)\s+_use_internal_dependency_generator\s+0')
+
+# See https://bugzilla.redhat.com/488146 for details
+indent_spaces_regex = re.compile('( \t|(^|\t)([^\t]{8})*[^\t]{4}[^\t]?([^\t][^\t.!?]|[^\t]?[.!?] )  )')
+
+requires_regex = re.compile('^(?:Build)?(?:Pre)?Req(?:uires)?(?:\([^\)]+\))?:\s*(.*)', re.IGNORECASE)
+provides_regex = re.compile('^Provides(?:\([^\)]+\))?:\s*(.*)', re.IGNORECASE)
+obsoletes_regex = re.compile('^Obsoletes:\s*(.*)', re.IGNORECASE)
+conflicts_regex = re.compile('^(?:Build)?Conflicts:\s*(.*)', re.IGNORECASE)
+
+compop_regex = re.compile('[<>=]')
+
+setup_q_regex = re.compile(' -[A-Za-z]*q')
+setup_t_regex = re.compile(' -[A-Za-z]*T')
+setup_ab_regex = re.compile(' -[A-Za-z]*[ab]')
+
+filelist_regex = re.compile('\s+-f\s+\S+')
+pkgname_regex = re.compile('\s+(?:-n\s+)?(\S+)')
+tarball_regex = re.compile('\.(?:t(?:ar|[glx]z|bz2?)|zip)\\b', re.IGNORECASE)
+
+
+def unversioned(deps):
+    '''Yield unversioned dependency names from the given list.'''
+    for dep in deps:
+        if not dep[1]:
+            yield dep[0]
+
+def contains_buildroot(line):
+    '''Check if the given line contains use of rpm buildroot.'''
+    res = rpm_buildroot_regex.search(line)
+    if res and \
+           (not res.group(1) or len(res.group(1)) % 2 == 0) and \
+           (not res.group(2) or len(res.group(2)) % 2 != 0):
+        return True
+    return False
+
+
+class SpecCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, "SpecCheck")
+        self._spec_file = None
+
+    def check(self, pkg):
+        if not pkg.isSource():
+            return
+
+        wrong_spec = False
+
+        # lookup spec file
+        for fname, pkgfile in pkg.files().items():
+            if fname.endswith('.spec'):
+                self._spec_file = pkgfile.path
+                if fname == pkg.name + ".spec":
+                    wrong_spec = False
+                    break
+                else:
+                    wrong_spec = True
+        if not self._spec_file:
+            printError(pkg, "no-spec-file")
+        else:
+            if wrong_spec:
+                printError(pkg, "invalid-spec-name")
+
+            # check content of spec file
+            self.check_spec(pkg, self._spec_file)
+
+    def check_spec(self, pkg, spec_file, spec_lines=[]):
+        self._spec_file = spec_file
+        spec_only = isinstance(pkg, Pkg.FakePkg)
+        if not spec_lines:
+            spec_lines = Pkg.readlines(spec_file)
+        patches = {}
+        applied_patches = []
+        applied_patches_ifarch = []
+        source_dir = False
+        buildroot = False
+        configure_linenum = None
+        configure_cmdline = ""
+        mklibname = False
+        is_lib_pkg = False
+        if_depth = 0
+        ifarch_depth = -1
+        current_section = 'package'
+        buildroot_clean = {'clean': False, 'install' : False}
+        depscript_override = False
+        depgen_disabled = False
+        indent_spaces = 0
+        indent_tabs = 0
+        files_has_defattr = False
+        section = {}
+        # None == main package
+        current_package = None
+        package_noarch = {}
+
+        is_utf8 = False
+        if self._spec_file and use_utf8:
+            if Pkg.is_utf8(self._spec_file):
+                is_utf8 = True
+            else:
+                printError(pkg, "non-utf8-spec-file", self._spec_file)
+
+        # gather info from spec lines
+
+        pkg.current_linenum = 0
+
+        nbsp = chr(0xA0)
+        if is_utf8:
+            nbsp = unichr(0xA0)
+
+        for line in spec_lines:
+
+            pkg.current_linenum += 1
+
+            if is_utf8:
+                line = unicode(line, "utf-8", "replace")
+
+            char = line.find(nbsp)
+            if char != -1:
+                printWarning(pkg, "non-break-space", "line %s, char %d" %
+                             (pkg.current_linenum, char))
+
+            section_marker = False
+            for sec, regex in section_regexs.items():
+                res = regex.search(line)
+                if res:
+                    current_section = sec
+                    section_marker = True
+                    section[sec] = section.get(sec, 0) + 1
+                    if sec in ('package', 'files'):
+                        rest = filelist_regex.sub('', line[res.end()-1:])
+                        res = pkgname_regex.search(rest)
+                        if res:
+                            current_package = res.group(1)
+                        else:
+                            current_package = None
+                    break
+
+            if section_marker:
+
+                if current_section == 'files':
+                    files_has_defattr = False
+
+                if not is_lib_pkg and lib_package_regex.search(line):
+                    is_lib_pkg = True
+
+                continue
+
+            if current_section in ('prep', 'build') and \
+                    contains_buildroot(line):
+                printWarning(pkg, 'rpm-buildroot-usage', '%' + current_section,
+                             line[:-1].strip())
+
+            if make_check_regex.search(line) and current_section not in \
+                    ('check', 'changelog', 'package', 'description'):
+                printWarning(pkg, 'make-check-outside-check-section', line[:-1])
+
+            if current_section in buildroot_clean and \
+                    not buildroot_clean[current_section] and \
+                    contains_buildroot(line) and rm_regex.search(line):
+                buildroot_clean[current_section] = True
+
+            if ifarch_regex.search(line):
+                if_depth = if_depth + 1
+                ifarch_depth = if_depth
+
+            if if_regex.search(line):
+                if_depth = if_depth + 1
+
+            if line.startswith('%setup'):
+                if not setup_q_regex.search(line):
+                    # Don't warn if there's a -T without -a or -b
+                    if setup_t_regex.search(line):
+                        if setup_ab_regex.search(line):
+                            printWarning(pkg, 'setup-not-quiet')
+                    else:
+                        printWarning(pkg, 'setup-not-quiet')
+                if current_section != 'prep':
+                    printWarning(pkg, 'setup-not-in-prep')
+
+            if endif_regex.search(line):
+                if ifarch_depth == if_depth:
+                    ifarch_depth = -1
+                if_depth = if_depth - 1
+
+            res = applied_patch_regex.search(line)
+            if res:
+                pnum = res.group(1) or 0
+                for tmp in applied_patch_p_regex.findall(line) or [pnum]:
+                    pnum = int(tmp)
+                    applied_patches.append(pnum)
+                    if ifarch_depth > 0:
+                        applied_patches_ifarch.append(pnum)
+            else:
+                res = applied_patch_pipe_regex.search(line)
+                if res:
+                    pnum = int(res.group(1))
+                    applied_patches.append(pnum)
+                    if ifarch_depth > 0:
+                        applied_patches_ifarch.append(pnum)
+            if not res and not source_dir:
+                res = source_dir_regex.search(line)
+                if res:
+                    source_dir = True
+                    printError(pkg, "use-of-RPM_SOURCE_DIR")
+
+            if configure_linenum:
+                if configure_cmdline[-1] == "\\":
+                    configure_cmdline = configure_cmdline[:-1] + line.strip()
+                else:
+                    res = configure_libdir_spec_regex.search(configure_cmdline)
+                    if not res:
+                        # Hack to get the correct (start of ./configure) line
+                        # number displayed:
+                        real_linenum = pkg.current_linenum
+                        pkg.current_linenum = configure_linenum
+                        printWarning(pkg, "configure-without-libdir-spec")
+                        pkg.current_linenum = real_linenum
+                    elif res.group(1):
+                        res = re.match(hardcoded_library_paths, res.group(1))
+                        if res:
+                            printError(pkg, "hardcoded-library-path",
+                                       res.group(1), "in configure options")
+                    configure_linenum = None
+
+            hashPos = line.find("#")
+
+            if current_section != 'changelog':
+                cfgPos = line.find('./configure')
+                if cfgPos != -1 and (hashPos == -1 or hashPos > cfgPos):
+                    # store line where it started
+                    configure_linenum = pkg.current_linenum
+                    configure_cmdline = line.strip()
+
+            res = hardcoded_library_path_regex.search(line)
+            if current_section != 'changelog' and res and not \
+                    (biarch_package_regex.match(pkg.name) or
+                     hardcoded_lib_path_exceptions_regex.search(
+                            res.group(1).lstrip())):
+                printError(pkg, "hardcoded-library-path", "in",
+                           res.group(1).lstrip())
+
+            if '%mklibname' in line:
+                mklibname = True
+
+            if current_section == 'package':
+
+                # Would be cleaner to get sources and patches from the
+                # specfile parsed in Python (see below), but we want to
+                # catch %ifarch'd etc ones as well, and also catch these when
+                # the specfile is not parseable.
+
+                res = patch_regex.search(line)
+                if res:
+                    pnum = int(res.group(1) or 0)
+                    patches[pnum] = res.group(2)
+
+                res = obsolete_tags_regex.search(line)
+                if res:
+                    printWarning(pkg, "obsolete-tag", res.group(1))
+
+                res = buildroot_regex.search(line)
+                if res:
+                    buildroot = True
+                    if res.group(1).startswith('/'):
+                        printWarning(pkg, 'hardcoded-path-in-buildroot-tag',
+                                     res.group(1))
+
+                res = buildarch_regex.search(line)
+                if res:
+                    if res.group(2) != "noarch":
+                        printError(pkg, 'buildarch-instead-of-exclusivearch-tag', res.group(2))
+                    else:
+                        package_noarch[current_package] = True
+
+                res = packager_regex.search(line)
+                if res:
+                    printWarning(pkg, 'hardcoded-packager-tag', res.group(1))
+
+                res = prefix_regex.search(line)
+                if res:
+                    if not res.group(1).startswith('%'):
+                        printWarning(pkg, 'hardcoded-prefix-tag', res.group(1))
+
+                res = prereq_regex.search(line)
+                if res:
+                    printError(pkg, 'prereq-use', res.group(2))
+
+                res = buildprereq_regex.search(line)
+                if res:
+                    printError(pkg, 'buildprereq-use', res.group(1))
+
+                if scriptlet_requires_regex.search(line):
+                    printError(pkg, 'broken-syntax-in-scriptlet-requires',
+                               line.strip())
+
+                res = requires_regex.search(line)
+                if res:
+                    reqs = Pkg.parse_deps(res.group(1))
+                    for req in unversioned(reqs):
+                        if compop_regex.search(req):
+                            printWarning(pkg, 'comparison-operator-in-deptoken',
+                                         req)
+
+                res = provides_regex.search(line)
+                if res:
+                    provs = Pkg.parse_deps(res.group(1))
+                    for prov in unversioned(provs):
+                        printWarning(pkg, 'unversioned-explicit-provides', prov)
+                        if compop_regex.search(prov):
+                            printWarning(pkg, 'comparison-operator-in-deptoken',
+                                         prov)
+
+                res = obsoletes_regex.search(line)
+                if res:
+                    obses = Pkg.parse_deps(res.group(1))
+                    for obs in unversioned(obses):
+                        printWarning(pkg, 'unversioned-explicit-obsoletes', obs)
+                        if compop_regex.search(obs):
+                            printWarning(pkg, 'comparison-operator-in-deptoken',
+                                         obs)
+
+                res = conflicts_regex.search(line)
+                if res:
+                    confs = Pkg.parse_deps(res.group(1))
+                    for conf in unversioned(confs):
+                        if compop_regex.search(conf):
+                            printWarning(pkg, 'comparison-operator-in-deptoken',
+                                         conf)
+
+            if current_section == 'changelog':
+                for match in AbstractCheck.macro_regex.findall(line):
+                    res = re.match('%+', match)
+                    if len(res.group(0)) % 2:
+                        printWarning(pkg, 'macro-in-%changelog', match)
+            else:
+                if not depscript_override:
+                    depscript_override = \
+                        depscript_override_regex.search(line) is not None
+                if not depgen_disabled:
+                    depgen_disabled = \
+                        depgen_disable_regex.search(line) is not None
+
+            if current_section == 'files':
+
+                if not comment_or_empty_regex.search(line) and not \
+                   (ifarch_regex.search(line) or if_regex.search(line) or
+                    endif_regex.search(line)):
+                    if defattr_regex.search(line):
+                        files_has_defattr = True
+                    elif not (files_has_defattr or attr_regex.search(line)):
+                        printWarning(pkg, 'files-attr-not-set')
+
+                # TODO: check scriptlets for these too?
+                if package_noarch.get(current_package) or \
+                        (current_package not in package_noarch and
+                         package_noarch.get(None)):
+                    res = libdir_regex.search(line)
+                    if res:
+                        pkgname = current_package
+                        if pkgname is None:
+                            pkgname = '(main package)'
+                        printWarning(pkg, 'libdir-macro-in-noarch-package',
+                                     pkgname, line.rstrip())
+
+            if not indent_tabs and '\t' in line:
+                indent_tabs = pkg.current_linenum
+            if not indent_spaces and indent_spaces_regex.search(line):
+                indent_spaces = pkg.current_linenum
+
+            # Check if egrep or fgrep is used
+            if current_section not in \
+                    ('package', 'changelog', 'description', 'files'):
+                greps = deprecated_grep_regex.findall(line)
+                if greps:
+                    printWarning(pkg, "deprecated-grep", greps)
+
+            # If not checking spec file only, we're checking one inside a
+            # SRPM -> skip this check to avoid duplicate warnings (#167)
+            if spec_only and VALID_GROUPS and \
+                   line.lower().startswith("group:"):
+                group = line[6:].strip()
+                if group not in VALID_GROUPS:
+                    printWarning(pkg, 'non-standard-group', group)
+
+            # Test if there are macros in comments
+            if hashPos != -1 and \
+                    (hashPos == 0 or line[hashPos-1] in (" ", "\t")):
+                for match in AbstractCheck.macro_regex.findall(
+                    line[hashPos+1:]):
+                    res = re.match('%+', match)
+                    if len(res.group(0)) % 2:
+                        printWarning(pkg, 'macro-in-comment', match)
+
+        # Last line read is not useful after this point
+        pkg.current_linenum = None
+
+        for sect in (x for x in buildroot_clean if not buildroot_clean[x]):
+            printWarning(pkg, 'no-cleaning-of-buildroot', '%' + sect)
+
+        if not buildroot:
+            printWarning(pkg, 'no-buildroot-tag')
+
+        for sec in ('prep', 'build', 'install', 'clean'):
+            if not section.get(sec):
+                printWarning(pkg, 'no-%%%s-section' % sec)
+        for sec in ('changelog',):
+            # prep, build, install, clean, check prevented by rpmbuild 4.4
+            if section.get(sec, 0) > 1:
+                printWarning(pkg, 'more-than-one-%%%s-section' % sec)
+
+        if is_lib_pkg and not mklibname:
+            printError(pkg, 'lib-package-without-%mklibname')
+
+        if depscript_override and not depgen_disabled:
+            printWarning(pkg, 'depscript-without-disabling-depgen')
+
+        if indent_spaces and indent_tabs:
+            pkg.current_linenum = max(indent_spaces, indent_tabs)
+            printWarning(pkg, 'mixed-use-of-spaces-and-tabs',
+                         '(spaces: line %d, tab: line %d)' %
+                         (indent_spaces, indent_tabs))
+            pkg.current_linenum = None
+
+        # process gathered info
+        for pnum, pfile in patches.items():
+            if pnum in applied_patches_ifarch:
+                printWarning(pkg, "%ifarch-applied-patch", "Patch%d:" % pnum,
+                             pfile)
+            if pnum not in applied_patches:
+                printWarning(pkg, "patch-not-applied", "Patch%d:" % pnum,
+                             pfile)
+
+        # Rest of the checks require a real spec file
+        if not self._spec_file:
+            return
+
+        # We'd like to parse the specfile only once using python bindings,
+        # but it seems errors from rpmlib get logged to stderr and we can't
+        # capture and print them nicely, so we do it once each way :P
+
+        out = Pkg.getstatusoutput(('env', 'LC_ALL=C', 'rpm', '-q',
+                                   '--qf=', '--specfile', self._spec_file))
+        parse_error = False
+        for line in out[1].splitlines():
+            # No such file or dir hack: https://bugzilla.redhat.com/487855
+            if 'No such file or directory' not in line:
+                parse_error = True
+                printError(pkg, 'specfile-error', line)
+
+        if not parse_error:
+            # grab sources and patches from parsed spec object to get
+            # them with macros expanded for URL checking
+
+            spec_obj = None
+            try:
+                ts = rpm.TransactionSet()
+                spec_obj = ts.parseSpec(self._spec_file)
+            except:
+                # errors logged above already
+                pass
+            if spec_obj:
+                try:
+                    # rpm < 4.8.0
+                    sources = spec_obj.sources()
+                except TypeError:
+                    # rpm >= 4.8.0
+                    sources = spec_obj.sources
+                for src in sources:
+                    (url, num, flags) = src
+                    (scheme, netloc) = urlparse(url)[0:2]
+                    if flags & 1: # rpmspec.h, rpm.org ticket #123
+                        srctype = "Source"
+                    else:
+                        srctype = "Patch"
+                    tag = '%s%s' % (srctype, num)
+                    if scheme and netloc:
+                        info = self.check_url(pkg, tag, url)
+                        if not info or not hasattr(pkg, 'files'):
+                            continue
+                        clen = info.get("Content-Length")
+                        if clen is not None:
+                            clen = int(clen)
+                        cmd5 = info.get("Content-MD5")
+                        if cmd5 is not None:
+                            cmd5 = cmd5.lower()
+                        if clen is not None or cmd5 is not None:
+                            # Not using path from urlparse results to match how
+                            # rpm itself parses the basename.
+                            pkgfile = pkg.files()[url.split("/")[-1]]
+                            if pkgfile:
+                                if clen is not None and pkgfile.size != clen:
+                                    printWarning(pkg, 'file-size-mismatch',
+                                                 '%s = %s, %s = %s' %
+                                                 (pkgfile.name, pkgfile.size,
+                                                  url, clen))
+                                # pkgfile.md5 could be some other digest than
+                                # MD5, treat as MD5 only if it's 32 chars long
+                                if cmd5 and len(pkgfile.md5) == 32 \
+                                        and pkgfile.md5 != cmd5:
+                                    printWarning(pkg, 'file-md5-mismatch',
+                                                 '%s = %s, %s = %s' %
+                                                 (pkgfile.name, pkgfile.md5,
+                                                  url, cmd5))
+                    elif srctype == "Source" and tarball_regex.search(url):
+                        printWarning(pkg, 'invalid-url', '%s:' % tag, url)
+
+# Create an object to enable the auto registration of the test
+check = SpecCheck()
+
+# Add information about checks
+addDetails(
+'no-spec-file',
+'''No spec file was specified in your RPM building. Please specify a valid
+SPEC file to build a valid RPM package.''',
+
+'invalid-spec-name',
+'''Your spec filename must end with '.spec'. If it's not the case, rename your
+file and rebuild your package.''',
+
+'non-utf8-spec-file',
+'''The character encoding of the spec file is not UTF-8.  Convert it for
+example using iconv(1).''',
+
+'use-of-RPM_SOURCE_DIR',
+'''You use $RPM_SOURCE_DIR or %{_sourcedir} in your spec file. If you have to
+use a directory for building, use $RPM_BUILD_ROOT instead.''',
+
+'patch-not-applied',
+'''A patch is included in your package but was not applied. Refer to the patches
+documentation to see what's wrong.''',
+
+'obsolete-tag',
+'''The following tags are obsolete: Copyright and Serial. They must
+be replaced by License and Epoch respectively.''',
+
+'deprecated-grep',
+'''Direct use of grep as egrep or fgrep is deprecated in GNU grep and
+historical in POSIX, use grep -E and grep -F instead.''',
+
+'no-buildroot-tag',
+'''The BuildRoot tag isn't used in your spec. It must be used in order to
+allow building the package as non root on some systems. For some rpm versions
+(e.g. rpm.org >= 4.6) the BuildRoot tag is not necessary in specfiles and is
+ignored by rpmbuild; if your package is only going to be built with such rpm
+versions you can ignore this warning.''',
+
+'hardcoded-path-in-buildroot-tag',
+'''A path is hardcoded in your Buildroot tag. It should be replaced
+by something like %{_tmppath}/%name-root.''',
+
+'hardcoded-packager-tag',
+'''The Packager tag is hardcoded in your spec file. It should be removed, so
+as to use rebuilder's own defaults.''',
+
+'buildarch-instead-of-exclusivearch-tag',
+'''Use ExclusiveArch instead of BuildArch (or BuildArchitectures)
+to restrict build on some specific architectures.
+Only use BuildArch with noarch''',
+
+'hardcoded-prefix-tag',
+'''The Prefix tag is hardcoded in your spec file. It should be removed, so as
+to allow package relocation.''',
+
+'hardcoded-library-path',
+'''A library path is hardcoded to one of the following paths: /lib,
+/usr/lib. It should be replaced by something like /%{_lib} or %{_libdir}.''',
+
+'configure-without-libdir-spec',
+'''A configure script is run without specifying the libdir. configure
+options must be augmented with something like --libdir=%{_libdir} whenever
+the script supports it.''',
+
+'no-%prep-section',
+'''The spec file does not contain a %prep section.  Even if some packages don't
+directly need it, section markers may be overridden in rpm's configuration
+to provide additional "under the hood" functionality.  Add the section, even
+if empty.''',
+
+'no-%build-section',
+'''The spec file does not contain a %build section.  Even if some packages
+don't directly need it, section markers may be overridden in rpm's
+configuration to provide additional "under the hood" functionality, such as
+injection of automatic -debuginfo subpackages.  Add the section, even if
+empty.''',
+
+'no-%install-section',
+'''The spec file does not contain an %install section.  Even if some packages
+don't directly need it, section markers may be overridden in rpm's
+configuration to provide additional "under the hood" functionality.  Add the
+section, even if empty.''',
+
+'no-%clean-section',
+'''The spec file doesn't contain a %clean section to remove the files installed
+by the %install section.''',
+
+'more-than-one-%changelog-section',
+'''The spec file unnecessarily contains more than one %changelog section;
+remove the extra ones.''',
+
+'lib-package-without-%mklibname',
+'''The package name must be built using %mklibname to allow lib64 and lib32
+coexistence.''',
+
+'%ifarch-applied-patch',
+'''A patch is applied inside an %ifarch block. Patches must be applied
+on all architectures and may contain necessary configure and/or code
+patch to be effective only on a given arch.''',
+
+'prereq-use',
+'''The use of PreReq is deprecated. In the majority of cases, a plain Requires
+is enough and the right thing to do. Sometimes Requires(pre), Requires(post),
+Requires(preun) and/or Requires(postun) can also be used instead of PreReq.''',
+
+'buildprereq-use',
+'''The use of BuildPreReq is deprecated, build dependencies are always required
+before a package can be built.  Use plain BuildRequires instead.''',
+
+'broken-syntax-in-scriptlet-requires',
+'''Comma separated context marked dependencies are silently broken in some
+versions of rpm.  One way to work around it is to split them into several ones,
+eg. replace "Requires(post,preun): foo" with "Requires(post): foo" and
+"Requires(preun): foo".''',
+
+'setup-not-in-prep',
+'''The %setup macro should only be used within the %prep section because it may
+not expand to anything outside of it and can break the build in unpredictable
+ways.''',
+
+'setup-not-quiet',
+'''Use the -q option to the %setup macro to avoid useless build output from
+unpacking the sources.''',
+
+'no-cleaning-of-buildroot',
+'''You should clean $RPM_BUILD_ROOT in the %clean section and in the beginning
+of the %install section. Use "rm -rf $RPM_BUILD_ROOT". Some rpm configurations
+do this automatically; if your package is only going to be built in such
+configurations, you can ignore this warning for the section(s) where your rpm
+takes care of it.''',
+
+'rpm-buildroot-usage',
+'''$RPM_BUILD_ROOT should not be touched during %build or %prep stage, as it
+may break short circuit builds.''',
+
+'make-check-outside-check-section',
+'''Make check or other automated regression test should be run in %check, as
+they can be disabled with a rpm macro for short circuiting purposes.''',
+
+'macro-in-%changelog',
+'''Macros are expanded in %changelog too, which can in unfortunate cases lead
+to the package not building at all, or other subtle unexpected conditions that
+affect the build.  Even when that doesn\'t happen, the expansion results in
+possibly "rewriting history" on subsequent package revisions and generally
+odd entries eg. in source rpms, which is rarely wanted.  Avoid use of macros
+in %changelog altogether, or use two '%'s to escape them, like '%%foo'.''',
+
+'depscript-without-disabling-depgen',
+'''In some common rpm configurations/versions, defining __find_provides and/or
+__find_requires has no effect if rpm's internal dependency generator has not
+been disabled for the build.  %define _use_internal_dependency_generator to 0
+to disable it in the specfile, or don't define __find_provides/requires.''',
+
+'mixed-use-of-spaces-and-tabs',
+'''The specfile mixes use of spaces and tabs for indentation, which is a
+cosmetic annoyance.  Use either spaces or tabs for indentation, not both.''',
+
+'unversioned-explicit-provides',
+'''The specfile contains an unversioned Provides: token, which will match all
+older, equal, and newer versions of the provided thing.  This may cause
+update problems and will make versioned dependencies, obsoletions and conflicts
+on the provided thing useless -- make the Provides versioned if possible.''',
+
+'unversioned-explicit-obsoletes',
+'''The specfile contains an unversioned Obsoletes: token, which will match all
+older, equal and newer versions of the obsoleted thing.  This may cause update
+problems, restrict future package/provides naming, and may match something it
+was originally not inteded to match -- make the Obsoletes versioned if
+possible.''',
+
+'libdir-macro-in-noarch-package',
+'''The %{_libdir} or %{_lib} macro was found in a noarch package in a section
+that gets included in binary packages.  This is most likely an error because
+these macros are expanded on the build host and their values vary between
+architectures, probably resulting in a package that does not work properly
+on all architectures at runtime. Investigate whether the package is really
+architecture independent or if some other dir/macro should be instead.''',
+
+'non-break-space',
+'''The spec file contains a non-break space, which looks like a regular space
+in some editors but can lead to obscure errors. It should be replaced by a
+regular space.''',
+
+'files-attr-not-set',
+'''A file or a directory entry in a %files section does not have attributes
+set which may result in unexpected file permissions and thus security issues
+in the resulting binary package depending on the build environment and rpmbuild
+version (typically < 4.4).  Add default attributes using %defattr before it in
+the %files section, or use per entry %attr's.''',
+
+'non-standard-group',
+'''The value of the Group tag in the package is not valid.  Valid groups are:
+"%s".''' % '", "'.join(VALID_GROUPS),
+
+'specfile-error',
+'''This error occurred when rpmlint used rpm to query the specfile.  The error
+is output by rpm and the message should contain more information.''',
+
+'comparison-operator-in-deptoken',
+'''This dependency token contains a comparison operator (<, > or =).  This is
+usually not intended and may be caused by missing whitespace between the token's
+name, the comparison operator and the version string.''',
+
+'macro-in-comment',
+'''There is a unescaped macro after a shell style comment in the specfile.
+Macros are expanded everywhere, so check if it can cause a problem in this
+case and escape the macro with another leading % if appropriate.''',
+
+'file-size-mismatch',
+'''The size of the file in the package does not match the size indicated by
+peeking at its URL.  Verify that the file in the package has the intended
+contents.''',
+
+'file-md5-mismatch',
+'''The MD5 hash of the file in the package does not match the MD5 hash
+indicated by peeking at its URL.  Verify that the file in the package has the
+intended contents.''',
+)
+
+# SpecCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/TagsCheck.py b/TagsCheck.py
new file mode 100644 (file)
index 0000000..1194660
--- /dev/null
@@ -0,0 +1,1138 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : TagsCheck.py
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Tue Sep 28 00:03:24 1999
+# Version       : $Id: TagsCheck.py 1892 2011-11-23 20:21:05Z scop $
+# Purpose       : Check a package to see if some rpm tags are present
+#############################################################################
+
+import calendar
+import os
+import re
+import time
+try:
+    from urlparse import urlparse
+except ImportError: # Python 3
+    from urllib.parse import urlparse
+
+import rpm
+
+from Filter import addDetails, printError, printInfo, printWarning
+import AbstractCheck
+import Config
+import FilesCheck
+import Pkg
+
+_use_enchant = Config.getOption("UseEnchant", None)
+if _use_enchant or _use_enchant is None:
+    try:
+        import enchant
+        import enchant.checker
+    except ImportError:
+        enchant = None
+else:
+    enchant = None
+del _use_enchant
+
+DEFAULT_VALID_LICENSES = (
+    # OSI approved licenses, http://www.opensource.org/licenses/ (unversioned,
+    # trailing "license" dropped based on fuzzy logic, and in well-known cases,
+    # the abbreviation used instead of the full name, but list kept sorted by
+    # the full name).  Updated 2010-02-01.
+    'Academic Free License',
+    'Adaptive Public License',
+    'AGPLv3', # Affero GNU Public License
+    'AGPLv3+', # Affero GNU Public License
+    'Apache License',
+    'Apache Software License',
+    'Apple Public Source License',
+    'Artistic',
+    'Attribution Assurance License',
+    'BSD',
+    'Boost Software License',
+    'Computer Associates Trusted Open Source License',
+    'CDDL', # Common Development and Distribution License
+    'Common Public Attribution License',
+    'CUA Office Public License',
+    'EU DataGrid Software License',
+    'Eclipse Public License',
+    'Educational Community License',
+    'Eiffel Forum License',
+    'Entessa Public License',
+    'European Union Public License',
+    'Fair License',
+    'Frameworx License',
+    'GPLv1',
+    'GPLv1+',
+    'GPLv2',
+    'GPLv2+',
+    'GPLv3',
+    'GPLv3+',
+    'LGPLv2',
+    'LGPLv2+',
+    'LGPLv3',
+    'LGPLv3+',
+    'Historical Permission Notice and Disclaimer',
+    'IBM Public License',
+    'IPA Font License',
+    'ISC License',
+    'Lucent Public License',
+    'Microsoft Public License',
+    'Microsoft Reciprocal License',
+    'MirOS License',
+    'MIT',
+    'Motosoto License',
+    'MPL', # Mozilla Public License
+    'Multics License',
+    'NASA Open Source Agreement',
+    'Naumen Public License',
+    'Nethack General Public License',
+    'Nokia Open Source License',
+    'Non-profit Open Software License',
+    'NTP License',
+    'OCLC Research Public License',
+    'OFL', # Open Font License
+    'Open Group Test Suite License',
+    'Open Software License',
+    'PHP License',
+    'Python license', # CNRI Python License
+    'Python Software Foundation License',
+    'QPL', # Qt Public License
+    'RealNetworks Public Source License',
+    'Reciprocal Public License',
+    'Ricoh Source Code Public License',
+    'Simple Public License',
+    'Sleepycat License',
+    'Sun Public License',
+    'Sybase Open Watcom Public License',
+    'University of Illinois/NCSA Open Source License',
+    'Vovida Software License',
+    'W3C License',
+    'wxWindows Library License',
+    'X.Net License',
+    'Zope Public License',
+    'zlib/libpng License',
+    # Creative commons licenses, http://creativecommons.org/licenses/:
+    'Creative Commons Attribution',
+    'Creative Commons Attribution-NoDerivs',
+    'Creative Commons Attribution-NonCommercial-NoDerivs',
+    'Creative Commons Attribution-NonCommercial',
+    'Creative Commons Attribution-NonCommercial-ShareAlike',
+    'Creative Commons Attribution-ShareAlike',
+    # Others:
+    'Design Public License', # ???
+    'GFDL', # GNU Free Documentation License
+    'LaTeX Project Public License',
+    'OpenContent License',
+    'Open Publication License',
+    'Public Domain',
+    'Ruby License',
+    'SIL Open Font License',
+    # Non open source licences:
+    'Charityware',
+    'Commercial',
+    'Distributable',
+    'Freeware',
+    'Non-distributable',
+    'Proprietary',
+    'Shareware',
+    )
+
+BAD_WORDS = {
+    'alot': 'a lot',
+    'accesnt': 'accent',
+    'accelleration': 'acceleration',
+    'accessable': 'accessible',
+    'accomodate': 'accommodate',
+    'acess': 'access',
+    'acording': 'according',
+    'additionaly': 'additionally',
+    'adress': 'address',
+    'adresses': 'addresses',
+    'adviced': 'advised',
+    'albumns': 'albums',
+    'alegorical': 'allegorical',
+    'algorith': 'algorithm',
+    'allpication': 'application',
+    'altough': 'although',
+    'alows': 'allows',
+    'amoung': 'among',
+    'amout': 'amount',
+    'analysator': 'analyzer',
+    'ang': 'and',
+    'appropiate': 'appropriate',
+    'arraival': 'arrival',
+    'artifical': 'artificial',
+    'artillary': 'artillery',
+    'attemps': 'attempts',
+    'automatize': 'automate',
+    'automatized': 'automated',
+    'automatizes': 'automates',
+    'auxilliary': 'auxiliary',
+    'availavility': 'availability',
+    'availble': 'available',
+    'avaliable': 'available',
+    'availiable': 'available',
+    'backgroud': 'background',
+    'baloons': 'balloons',
+    'becomming': 'becoming',
+    'becuase': 'because',
+    'cariage': 'carriage',
+    'challanges': 'challenges',
+    'changable': 'changeable',
+    'charachters': 'characters',
+    'charcter': 'character',
+    'choosen': 'chosen',
+    'colorfull': 'colorful',
+    'comand': 'command',
+    'commerical': 'commercial',
+    'comminucation': 'communication',
+    'commoditiy': 'commodity',
+    'compability': 'compatibility',
+    'compatability': 'compatibility',
+    'compatable': 'compatible',
+    'compatibiliy': 'compatibility',
+    'compatibilty': 'compatibility',
+    'compleatly': 'completely',
+    'complient': 'compliant',
+    'compres': 'compress',
+    'containes': 'contains',
+    'containts': 'contains',
+    'contence': 'contents',
+    'continous': 'continuous',
+    'contraints': 'constraints',
+    'convertor': 'converter',
+    'convinient': 'convenient',
+    'cryptocraphic': 'cryptographic',
+    'deamon': 'daemon',
+    'debians': 'Debian\'s',
+    'decompres': 'decompress',
+    'definate': 'definite',
+    'definately': 'definitely',
+    'dependancies': 'dependencies',
+    'dependancy': 'dependency',
+    'dependant': 'dependent',
+    'developement': 'development',
+    'developped': 'developed',
+    'deveolpment': 'development',
+    'devided': 'divided',
+    'dictionnary': 'dictionary',
+    'diplay': 'display',
+    'disapeared': 'disappeared',
+    'dissapears': 'disappears',
+    'documentaion': 'documentation',
+    'docuentation': 'documentation',
+    'documantation': 'documentation',
+    'dont': 'don\'t',
+    'easilly': 'easily',
+    'ecspecially': 'especially',
+    'edditable': 'editable',
+    'editting': 'editing',
+    'eletronic': 'electronic',
+    'enchanced': 'enhanced',
+    'encorporating': 'incorporating',
+    'enlightnment': 'enlightenment',
+    'enterily': 'entirely',
+    'enviroiment': 'environment',
+    'environement': 'environment',
+    'excellant': 'excellent',
+    'exlcude': 'exclude',
+    'exprimental': 'experimental',
+    'extention': 'extension',
+    'failuer': 'failure',
+    'familar': 'familiar',
+    'fatser': 'faster',
+    'fetaures': 'features',
+    'forse': 'force',
+    'fortan': 'fortran',
+    'framwork': 'framework',
+    'fuction': 'function',
+    'fuctions': 'functions',
+    'functionnality': 'functionality',
+    'functonality': 'functionality',
+    'functionaly': 'functionally',
+    'futhermore': 'furthermore',
+    'generiously': 'generously',
+    'grahical': 'graphical',
+    'grahpical': 'graphical',
+    'grapic': 'graphic',
+    'guage': 'gauge',
+    'halfs': 'halves',
+    'heirarchically': 'hierarchically',
+    'helpfull': 'helpful',
+    'hierachy': 'hierarchy',
+    'hierarchie': 'hierarchy',
+    'howver': 'however',
+    'implemantation': 'implementation',
+    'incomming': 'incoming',
+    'incompatabilities': 'incompatibilities',
+    'indended': 'intended',
+    'indendation': 'indentation',
+    'independant': 'independent',
+    'informatiom': 'information',
+    'initalize': 'initialize',
+    'inofficial': 'unofficial',
+    'integreated': 'integrated',
+    'integrety': 'integrity',
+    'integrey': 'integrity',
+    'intendet': 'intended',
+    'interchangable': 'interchangeable',
+    'intermittant': 'intermittent',
+    'jave': 'java',
+    'langage': 'language',
+    'langauage': 'language',
+    'langugage': 'language',
+    'lauch': 'launch',
+    'lesstiff': 'lesstif',
+    'libaries': 'libraries',
+    'licenceing': 'licencing',
+    'loggin': 'login',
+    'logile': 'logfile',
+    'loggging': 'logging',
+    'mandrivalinux': 'Mandriva Linux',
+    'maintainance': 'maintenance',
+    'maintainence': 'maintenance',
+    'makeing': 'making',
+    'managable': 'manageable',
+    'manoeuvering': 'maneuvering',
+    'ment': 'meant',
+    'modulues': 'modules',
+    'monochromo': 'monochrome',
+    'multidimensionnal': 'multidimensional',
+    'navagating': 'navigating',
+    'nead': 'need',
+    'neccesary': 'necessary',
+    'neccessary': 'necessary',
+    'necesary': 'necessary',
+    'nescessary': 'necessary',
+    'noticable': 'noticeable',
+    'optionnal': 'optional',
+    'orientied': 'oriented',
+    'pacakge': 'package',
+    'pachage': 'package',
+    'packacge': 'package',
+    'packege': 'package',
+    'packge': 'package',
+    'pakage': 'package',
+    'particularily': 'particularly',
+    'persistant': 'persistent',
+    'plattform': 'platform',
+    'ploting': 'plotting',
+    'posible': 'possible',
+    'powerfull': 'powerful',
+    'prefered': 'preferred',
+    'prefferably': 'preferably',
+    'prepaired': 'prepared',
+    'princliple': 'principle',
+    'priorty': 'priority',
+    'proccesors': 'processors',
+    'proces': 'process',
+    'processsing': 'processing',
+    'processessing': 'processing',
+    'progams': 'programs',
+    'programers': 'programmers',
+    'programm': 'program',
+    'programms': 'programs',
+    'promps': 'prompts',
+    'pronnounced': 'pronounced',
+    'prononciation': 'pronunciation',
+    'pronouce': 'pronounce',
+    'protcol': 'protocol',
+    'protocoll': 'protocol',
+    'recieve': 'receive',
+    'recieved': 'received',
+    'redircet': 'redirect',
+    'regulamentations': 'regulations',
+    'remoote': 'remote',
+    'repectively': 'respectively',
+    'replacments': 'replacements',
+    'requiere': 'require',
+    'runnning': 'running',
+    'safly': 'safely',
+    'savable': 'saveable',
+    'searchs': 'searches',
+    'separatly': 'separately',
+    'seperate': 'separate',
+    'seperately': 'separately',
+    'seperatly': 'separately',
+    'serveral': 'several',
+    'setts': 'sets',
+    'similiar': 'similar',
+    'simliar': 'similar',
+    'speach': 'speech',
+    'standart': 'standard',
+    'staically': 'statically',
+    'staticly': 'statically',
+    'succesful': 'successful',
+    'succesfully': 'successfully',
+    'suplied': 'supplied',
+    'suport': 'support',
+    'suppport': 'support',
+    'supportin': 'supporting',
+    'synchonized': 'synchronized',
+    'syncronize': 'synchronize',
+    'syncronizing': 'synchronizing',
+    'syncronus': 'synchronous',
+    'syste': 'system',
+    'sythesis': 'synthesis',
+    'taht': 'that',
+    'throught': 'through',
+    'useable': 'usable',
+    'usefull': 'useful',
+    'usera': 'users',
+    'usetnet': 'Usenet',
+    'utilites': 'utilities',
+    'utillities': 'utilities',
+    'utilties': 'utilities',
+    'utiltity': 'utility',
+    'utitlty': 'utility',
+    'variantions': 'variations',
+    'varient': 'variant',
+    'verson': 'version',
+    'vicefersa': 'vice-versa',
+    'yur': 'your',
+    'wheter': 'whether',
+    'wierd': 'weird',
+    'xwindows': 'X'
+    }
+
+DEFAULT_INVALID_REQUIRES = ('^is$', '^not$', '^owned$', '^by$', '^any$', '^package$', '^libsafe\.so\.')
+
+VALID_GROUPS = Config.getOption('ValidGroups', None)
+if VALID_GROUPS is None: # get defaults from rpm package only if it's not set
+    VALID_GROUPS = Pkg.get_default_valid_rpmgroups()
+VALID_LICENSES = Config.getOption('ValidLicenses', DEFAULT_VALID_LICENSES)
+INVALID_REQUIRES = map(re.compile, Config.getOption('InvalidRequires', DEFAULT_INVALID_REQUIRES))
+packager_regex = re.compile(Config.getOption('Packager'))
+changelog_version_regex = re.compile('[^>]([^ >]+)\s*$')
+changelog_text_version_regex = re.compile('^\s*-\s*((\d+:)?[\w\.]+-[\w\.]+)')
+release_ext = Config.getOption('ReleaseExtension')
+extension_regex = release_ext and re.compile(release_ext)
+use_version_in_changelog = Config.getOption('UseVersionInChangelog', True)
+devel_number_regex = re.compile('(.*?)([0-9.]+)(_[0-9.]+)?-devel')
+lib_devel_number_regex = re.compile('^lib(.*?)([0-9.]+)(_[0-9.]+)?-devel')
+invalid_url_regex = re.compile(Config.getOption('InvalidURL'), re.IGNORECASE)
+lib_package_regex = re.compile('(?:^(?:compat-)?lib.*?(\.so.*)?|libs?[\d-]*)$', re.IGNORECASE)
+leading_space_regex = re.compile('^\s+')
+license_regex = re.compile('\(([^)]+)\)|\s(?:and|or)\s')
+invalid_version_regex = re.compile('([0-9](?:rc|alpha|beta|pre).*)', re.IGNORECASE)
+# () are here for grouping purpose in the regexp
+forbidden_words_regex = re.compile('(' + Config.getOption('ForbiddenWords') + ')', re.IGNORECASE)
+valid_buildhost_regex = re.compile(Config.getOption('ValidBuildHost'))
+use_epoch = Config.getOption('UseEpoch', False)
+use_utf8 = Config.getOption('UseUTF8', Config.USEUTF8_DEFAULT)
+max_line_len = Config.getOption('MaxLineLength', 79)
+tag_regex = re.compile('^((?:Auto(?:Req|Prov|ReqProv)|Build(?:Arch(?:itectures)?|Root)|(?:Build)?Conflicts|(?:Build)?(?:Pre)?Requires|Copyright|(?:CVS|SVN)Id|Dist(?:ribution|Tag|URL)|DocDir|(?:Build)?Enhances|Epoch|Exclu(?:de|sive)(?:Arch|OS)|Group|Icon|License|Name|No(?:Patch|Source)|Obsoletes|Packager|Patch\d*|Prefix(?:es)?|Provides|(?:Build)?Recommends|Release|RHNPlatform|Serial|Source\d*|(?:Build)?Suggests|Summary|(?:Build)?Supplements|(?:Bug)?URL|Vendor|Version)(?:\([^)]+\))?:)\s*\S', re.IGNORECASE)
+punct = '.,:;!?'
+sentence_break_regex = re.compile(r'(^|[.:;!?])\s*$')
+so_dep_regex = re.compile(r'\.so(\.[0-9a-zA-z]+)*(\([^)]*\))*$')
+# we assume that no rpm packages existed before rpm itself existed...
+oldest_changelog_timestamp = calendar.timegm(time.strptime("1995-01-01", "%Y-%m-%d"))
+
+private_so_paths = set()
+for path in ('%perl_archlib', '%perl_vendorarch', '%perl_sitearch',
+             '%python_sitearch', '%ruby_sitearch', '%php_extdir'):
+    epath = rpm.expandMacro(path)
+    if epath != path:
+        private_so_paths.add(epath)
+        private_so_paths.add(re.sub(r'/lib64(?=/|$)', '/lib', epath))
+        private_so_paths.add(re.sub(r'/lib(?=/|$)', '/lib64', epath))
+
+_enchant_checkers = {}
+def spell_check(pkg, str, fmt, lang, ignored):
+
+    dict_found = True
+    warned = set()
+    if enchant:
+        if lang == 'C':
+            lang = 'en_US'
+
+        checker = _enchant_checkers.get(lang)
+        if not checker and lang not in _enchant_checkers:
+            try:
+                checker = enchant.checker.SpellChecker(
+                    lang, filters = [ enchant.tokenize.EmailFilter,
+                                      enchant.tokenize.URLFilter,
+                                      enchant.tokenize.WikiWordFilter ])
+            except enchant.DictNotFoundError:
+                printInfo(pkg, 'enchant-dictionary-not-found', lang)
+                pass
+            _enchant_checkers[lang] = checker
+
+        if checker:
+            # squeeze whitespace to ease leading context check
+            checker.set_text(re.sub(r'\s+', ' ', str))
+            uppername = pkg.name.upper()
+            upperparts = uppername.split('-')
+            if lang.startswith('en'):
+                ups = [x + "'S" for x in upperparts]
+                upperparts.extend(ups)
+            for err in checker:
+
+                # Skip already warned and ignored words
+                if err.word in warned or err.word in ignored:
+                    continue
+
+                # Skip all capitalized words that do not start a sentence
+                if err.word[0].isupper() and not \
+                        sentence_break_regex.search(checker.leading_context(3)):
+                    continue
+
+                upperword = err.word.upper()
+
+                # Skip all uppercase words
+                if err.word == upperword:
+                    continue
+
+                # Skip errors containing package name or equal to a
+                # "component" of it, case insensitively
+                if uppername in upperword or upperword in upperparts:
+                    continue
+
+                # Work around enchant's digit tokenizing behavior:
+                # http://github.com/rfk/pyenchant/issues/issue/3
+                if checker.leading_context(1).isdigit() or \
+                        checker.trailing_context(1).isdigit():
+                    continue
+
+                # Warn and suggest
+                sug = ', '.join(checker.suggest()[:3])
+                if sug:
+                    sug = '-> %s' % sug
+                printWarning(pkg, 'spelling-error', fmt % lang, err.word, sug)
+                warned.add(err.word)
+
+        else:
+            dict_found = False
+
+    if not enchant or not dict_found:
+        for seq in str.split():
+            for word in re.split('[^a-z]+', seq.lower()):
+                if len(word) == 0:
+                    continue
+                correct = BAD_WORDS.get(word)
+                if not correct:
+                    continue
+                if word[0] == '\'':
+                    word = word[1:]
+                if word[-1] == '\'':
+                    word = word[:-1]
+                if word in warned or word in ignored:
+                    continue
+                printWarning(pkg, 'spelling-error', fmt % lang, word, '->',
+                             correct)
+                warned.add(word)
+
+
+class TagsCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, 'TagsCheck')
+
+    def _unexpanded_macros(self, pkg, tagname, value, is_url=False):
+        if not value:
+            return
+        for match in AbstractCheck.macro_regex.findall(str(value)):
+            # Do not warn about %XX URL escapes
+            if is_url and re.match('^%[0-9A-F][0-9A-F]$', match, re.I):
+                continue
+            printWarning(pkg, 'unexpanded-macro', tagname, match)
+
+    def check(self, pkg):
+
+        packager = pkg[rpm.RPMTAG_PACKAGER]
+        self._unexpanded_macros(pkg, 'Packager', packager)
+        if not packager:
+            printError(pkg, 'no-packager-tag')
+        elif Config.getOption('Packager') and \
+                not packager_regex.search(packager):
+            printWarning(pkg, 'invalid-packager', packager)
+
+        version = pkg[rpm.RPMTAG_VERSION]
+        self._unexpanded_macros(pkg, 'Version', version)
+        if not version:
+            printError(pkg, 'no-version-tag')
+        else:
+            res = invalid_version_regex.search(version)
+            if res:
+                printError(pkg, 'invalid-version', version)
+
+        release = pkg[rpm.RPMTAG_RELEASE]
+        self._unexpanded_macros(pkg, 'Release', release)
+        if not release:
+            printError(pkg, 'no-release-tag')
+        elif release_ext and not extension_regex.search(release):
+            printWarning(pkg, 'not-standard-release-extension', release)
+
+        epoch = pkg[rpm.RPMTAG_EPOCH]
+        if epoch is None:
+            if use_epoch:
+                printError(pkg, 'no-epoch-tag')
+        else:
+            if epoch > 99:
+                printWarning(pkg, 'unreasonable-epoch', epoch)
+            epoch = str(epoch)
+
+        if use_epoch:
+            for o in (x for x in pkg.obsoletes() if x[1] and x[2][0] is None):
+                printWarning(pkg, 'no-epoch-in-obsoletes',
+                             apply(Pkg.formatRequire, o))
+            for c in (x for x in pkg.conflicts() if x[1] and x[2][0] is None):
+                printWarning(pkg, 'no-epoch-in-conflicts',
+                             apply(Pkg.formatRequire, c))
+            for p in (x for x in pkg.provides() if x[1] and x[2][0] is None):
+                printWarning(pkg, 'no-epoch-in-provides',
+                             apply(Pkg.formatRequire, p))
+
+        name = pkg.name
+        deps = pkg.requires() + pkg.prereq()
+        devel_depend = False
+        is_devel = FilesCheck.devel_regex.search(name)
+        is_source = pkg.isSource()
+        for d in deps:
+            value = apply(Pkg.formatRequire, d)
+            if use_epoch and d[1] and d[2][0] is None \
+                    and d[0][0:7] != 'rpmlib(':
+                printWarning(pkg, 'no-epoch-in-dependency', value)
+            for r in INVALID_REQUIRES:
+                if r.search(d[0]):
+                    printError(pkg, 'invalid-dependency', d[0])
+
+            if d[0].startswith('/usr/local/'):
+                printError(pkg, 'invalid-dependency', d[0])
+
+            if not devel_depend and not is_devel and not is_source and \
+                    FilesCheck.devel_regex.search(d[0]):
+                printError(pkg, 'devel-dependency', d[0])
+                devel_depend = True
+            if is_source and lib_devel_number_regex.search(d[0]):
+                printError(pkg, 'invalid-build-requires', d[0])
+            if not is_source and not is_devel:
+                res = lib_package_regex.search(d[0])
+                if res and not res.group(1) and not d[1]:
+                    printError(pkg, 'explicit-lib-dependency', d[0])
+            if d[1] == rpm.RPMSENSE_EQUAL and d[2][2] is not None:
+                printWarning(pkg, 'requires-on-release', value)
+            self._unexpanded_macros(pkg, 'dependency %s' % (value,), value)
+
+        self._unexpanded_macros(pkg, 'Name', name)
+        if not name:
+            printError(pkg, 'no-name-tag')
+        else:
+            if is_devel and not is_source:
+                base = is_devel.group(1)
+                dep = None
+                has_so = False
+                for fname in pkg.files():
+                    if fname.endswith('.so'):
+                        has_so = True
+                        break
+                if has_so:
+                    base_or_libs = base + '/' + base + '-libs/lib' + base
+                    # try to match *%_isa as well (e.g. "(x86-64)", "(x86-32)")
+                    base_or_libs_re = re.compile(
+                        '^(lib)?%s(-libs)?(\(\w+-\d+\))?$' % re.escape(base))
+                    for d in deps:
+                        if base_or_libs_re.match(d[0]):
+                            dep = d
+                            break
+                    if not dep:
+                        printWarning(pkg, 'no-dependency-on', base_or_libs)
+                    elif version:
+                        exp = (epoch, version, None)
+                        sexp = Pkg.versionToString(exp)
+                        if not dep[1]:
+                            printWarning(pkg, 'no-version-dependency-on',
+                                         base_or_libs, sexp)
+                        elif dep[2][:2] != exp[:2]:
+                            printWarning(pkg,
+                                         'incoherent-version-dependency-on',
+                                         base_or_libs,
+                                         Pkg.versionToString((dep[2][0],
+                                                              dep[2][1], None)),
+                                         sexp)
+                    res = devel_number_regex.search(name)
+                    if not res:
+                        printWarning(pkg, 'no-major-in-name', name)
+                    else:
+                        if res.group(3):
+                            prov = res.group(1) + res.group(2) + '-devel'
+                        else:
+                            prov = res.group(1) + '-devel'
+
+                        if prov not in (x[0] for x in pkg.provides()):
+                            printWarning(pkg, 'no-provides', prov)
+
+        # List of words to ignore in spell check
+        ignored_words = set()
+        for pf in pkg.files():
+            ignored_words.update(pf.split('/'))
+        ignored_words.update((x[0] for x in pkg.provides()))
+        ignored_words.update((x[0] for x in pkg.requires()))
+        ignored_words.update((x[0] for x in pkg.conflicts()))
+        ignored_words.update((x[0] for x in pkg.obsoletes()))
+
+        summary = pkg[rpm.RPMTAG_SUMMARY]
+        if not summary:
+            printError(pkg, 'no-summary-tag')
+        else:
+            if not pkg[rpm.RPMTAG_HEADERI18NTABLE]:
+                self._unexpanded_macros(pkg, 'Summary', summary)
+            else:
+                for lang in pkg[rpm.RPMTAG_HEADERI18NTABLE]:
+                    self.check_summary(pkg, lang, ignored_words)
+
+        description = pkg[rpm.RPMTAG_DESCRIPTION]
+        if not description:
+            printError(pkg, 'no-description-tag')
+        else:
+            if not pkg[rpm.RPMTAG_HEADERI18NTABLE]:
+                self._unexpanded_macros(pkg, '%description', description)
+            else:
+                for lang in pkg[rpm.RPMTAG_HEADERI18NTABLE]:
+                    self.check_description(pkg, lang, ignored_words)
+
+        group = pkg[rpm.RPMTAG_GROUP]
+        self._unexpanded_macros(pkg, 'Group', group)
+        if not group:
+            printError(pkg, 'no-group-tag')
+        elif VALID_GROUPS and group not in VALID_GROUPS:
+            printWarning(pkg, 'non-standard-group', group)
+
+        buildhost = pkg[rpm.RPMTAG_BUILDHOST]
+        self._unexpanded_macros(pkg, 'BuildHost', buildhost)
+        if not buildhost:
+            printError(pkg, 'no-buildhost-tag')
+        elif Config.getOption('ValidBuildHost') and \
+                not valid_buildhost_regex.search(buildhost):
+            printWarning(pkg, 'invalid-buildhost', buildhost)
+
+        changelog = pkg[rpm.RPMTAG_CHANGELOGNAME]
+        if not changelog:
+            printError(pkg, 'no-changelogname-tag')
+        else:
+            clt = pkg[rpm.RPMTAG_CHANGELOGTEXT]
+            if use_version_in_changelog:
+                ret = changelog_version_regex.search(changelog[0])
+                if not ret and clt:
+                    # we also allow the version specified as the first
+                    # thing on the first line of the text
+                    ret = changelog_text_version_regex.search(clt[0])
+                if not ret:
+                    printWarning(pkg, 'no-version-in-last-changelog')
+                elif version and release:
+                    srpm = pkg[rpm.RPMTAG_SOURCERPM] or ''
+                    # only check when source name correspond to name
+                    if srpm[0:-8] == '%s-%s-%s' % (name, version, release):
+                        expected = [version + '-' + release]
+                        if epoch is not None: # regardless of use_epoch
+                            expected[0] = str(epoch) + ':' + expected[0]
+                        # Allow EVR in changelog without release extension,
+                        # the extension is often a macro or otherwise dynamic.
+                        if release_ext:
+                            expected.append(
+                                extension_regex.sub('', expected[0]))
+                        if ret.group(1) not in expected:
+                            if len(expected) == 1:
+                                expected = expected[0]
+                            printWarning(pkg, 'incoherent-version-in-changelog',
+                                         ret.group(1), expected)
+
+            if clt:
+                changelog = changelog + clt
+            if use_utf8 and not Pkg.is_utf8_str(' '.join(changelog)):
+                printError(pkg, 'tag-not-utf8', '%changelog')
+
+            clt = pkg[rpm.RPMTAG_CHANGELOGTIME][0]
+            if clt:
+                clt -= clt % (24*3600) # roll back to 00:00:00, see #246
+                if clt < oldest_changelog_timestamp:
+                    printWarning(pkg, 'changelog-time-overflow',
+                                 time.strftime("%Y-%m-%d", time.gmtime(clt)))
+                elif clt > time.time():
+                    printError(pkg, 'changelog-time-in-future',
+                                 time.strftime("%Y-%m-%d", time.gmtime(clt)))
+
+#         for provide_name in (x[0] for x in pkg.provides()):
+#             if name == provide_name:
+#                 printWarning(pkg, 'package-provides-itself')
+#                 break
+
+        def split_license(license):
+            return (x.strip() for x in
+                    (l for l in license_regex.split(license) if l))
+
+        rpm_license = pkg[rpm.RPMTAG_LICENSE]
+        if not rpm_license:
+            printError(pkg, 'no-license')
+        else:
+            valid_license = True
+            if rpm_license not in VALID_LICENSES:
+                for l1 in split_license(rpm_license):
+                    if l1 in VALID_LICENSES:
+                        continue
+                    for l2 in split_license(l1):
+                        if l2 not in VALID_LICENSES:
+                            printWarning(pkg, 'invalid-license', l2)
+                            valid_license = False
+            if not valid_license:
+                self._unexpanded_macros(pkg, 'License', rpm_license)
+
+        for tag in ('URL', 'DistURL', 'BugURL'):
+            if hasattr(rpm, 'RPMTAG_%s' % tag.upper()):
+                url = pkg[getattr(rpm, 'RPMTAG_%s' % tag.upper())]
+                self._unexpanded_macros(pkg, tag, url, is_url = True)
+                if url:
+                    (scheme, netloc) = urlparse(url)[0:2]
+                    if not scheme or not netloc or "." not in netloc or \
+                            scheme not in ('http', 'https', 'ftp') or \
+                            (Config.getOption('InvalidURL') and \
+                             invalid_url_regex.search(url)):
+                        printWarning(pkg, 'invalid-url', tag, url)
+                    else:
+                        self.check_url(pkg, tag, url)
+                elif tag == 'URL':
+                    printWarning(pkg, 'no-url-tag')
+
+        obs_names = [x[0] for x in pkg.obsoletes()]
+        prov_names = [x[0] for x in pkg.provides()]
+
+        for o in (x for x in obs_names if x not in prov_names):
+            printWarning(pkg, 'obsolete-not-provided', o)
+        for o in pkg.obsoletes():
+            value = apply(Pkg.formatRequire, o)
+            self._unexpanded_macros(pkg, 'Obsoletes %s' % (value,), value)
+
+        # TODO: should take versions, <, <=, =, >=, > into account here
+        #       https://bugzilla.redhat.com/460872
+        useless_provides = []
+        for p in prov_names:
+            if prov_names.count(p) != 1 and p not in useless_provides:
+                useless_provides.append(p)
+        for p in useless_provides:
+            printError(pkg, 'useless-provides', p)
+
+        for p in pkg.provides():
+            value = apply(Pkg.formatRequire, p)
+            self._unexpanded_macros(pkg, 'Provides %s' % (value,), value)
+
+        for c in pkg.conflicts():
+            value = apply(Pkg.formatRequire, c)
+            self._unexpanded_macros(pkg, 'Conflicts %s' % (value,), value)
+
+        obss = pkg.obsoletes()
+        if obss:
+            provs = pkg.provides()
+            for prov in provs:
+                for obs in obss:
+                    if Pkg.rangeCompare(obs, prov):
+                        printWarning(pkg, 'self-obsoletion', '%s obsoletes %s' %
+                                     (apply(Pkg.formatRequire, obs),
+                                      apply(Pkg.formatRequire, prov)))
+
+        expfmt = rpm.expandMacro("%{_build_name_fmt}")
+        if pkg.isSource():
+            # _build_name_fmt often (always?) ends up not outputting src/nosrc
+            # as arch for source packages, do it ourselves
+            expfmt = re.sub(r'(?i)%\{?ARCH\b\}?', pkg.arch, expfmt)
+        expected = pkg.header.sprintf(expfmt).split("/")[-1]
+        basename = os.path.basename(pkg.filename)
+        if basename != expected:
+            printWarning(pkg, 'non-coherent-filename', basename, expected)
+
+        for tag in ('Distribution', 'DistTag', 'ExcludeArch', 'ExcludeOS',
+                    'Vendor'):
+            if hasattr(rpm, 'RPMTAG_%s' % tag.upper()):
+                self._unexpanded_macros(
+                    pkg, tag, pkg[getattr(rpm, 'RPMTAG_%s' % tag.upper())])
+
+        for path in private_so_paths:
+            for fname, pkgfile in pkg.files().items():
+                if fname.startswith(path):
+                    for prov in pkgfile.provides:
+                        if so_dep_regex.search(prov[0]):
+                            printWarning(pkg, "private-shared-object-provides",
+                                         fname, apply(Pkg.formatRequire, prov))
+
+
+    def check_description(self, pkg, lang, ignored_words):
+        description = pkg.langtag(rpm.RPMTAG_DESCRIPTION, lang)
+        self._unexpanded_macros(pkg, '%%description -l %s' % lang, description)
+        utf8desc = description
+        if use_utf8:
+            utf8desc = Pkg.to_utf8(description).decode('utf-8')
+        spell_check(pkg, utf8desc, '%%description -l %s', lang, ignored_words)
+        for l in utf8desc.splitlines():
+            if len(l) > max_line_len:
+                printError(pkg, 'description-line-too-long', lang, l)
+            res = forbidden_words_regex.search(l)
+            if res and Config.getOption('ForbiddenWords'):
+                printWarning(pkg, 'description-use-invalid-word', lang,
+                             res.group(1))
+            res = tag_regex.search(l)
+            if res:
+                printWarning(pkg, 'tag-in-description', lang, res.group(1))
+        if use_utf8 and not Pkg.is_utf8_str(description):
+            printError(pkg, 'tag-not-utf8', '%description', lang)
+
+    def check_summary(self, pkg, lang, ignored_words):
+        summary = pkg.langtag(rpm.RPMTAG_SUMMARY, lang)
+        self._unexpanded_macros(pkg, 'Summary(%s)' % lang, summary)
+        utf8summary = summary
+        if use_utf8:
+            utf8summary = Pkg.to_utf8(summary).decode('utf-8')
+        spell_check(pkg, utf8summary, 'Summary(%s)', lang, ignored_words)
+        if '\n' in summary:
+            printError(pkg, 'summary-on-multiple-lines', lang)
+        if summary[0] != summary[0].upper():
+            printWarning(pkg, 'summary-not-capitalized', lang, summary)
+        if summary[-1] == '.':
+            printWarning(pkg, 'summary-ended-with-dot', lang, summary)
+        if len(utf8summary) > max_line_len:
+            printError(pkg, 'summary-too-long', lang, summary)
+        if leading_space_regex.search(summary):
+            printError(pkg, 'summary-has-leading-spaces', lang, summary)
+        res = forbidden_words_regex.search(summary)
+        if res and Config.getOption('ForbiddenWords'):
+            printWarning(pkg, 'summary-use-invalid-word', lang, res.group(1))
+        if pkg.name:
+            sepchars = '[\s' + punct + ']'
+            res = re.search('(?:^|\s)(%s)(?:%s|$)' %
+                            (re.escape(pkg.name), sepchars),
+                            summary, re.IGNORECASE | re.UNICODE)
+            if res:
+                printWarning(pkg, 'name-repeated-in-summary', lang,
+                             res.group(1))
+        if use_utf8 and not Pkg.is_utf8_str(summary):
+            printError(pkg, 'tag-not-utf8', 'Summary', lang)
+
+
+# Create an object to enable the auto registration of the test
+check = TagsCheck()
+
+# Add information about checks
+addDetails(
+'summary-too-long',
+'The "Summary:" must not exceed %d characters.' % max_line_len,
+
+'invalid-version',
+'''The version string must not contain the pre, alpha, beta or rc suffixes
+because when the final version will be out, you will have to use an Epoch tag
+to make the package upgradable. Instead put it in the release tag, prefixed
+with something you have control over.''',
+
+'spelling-error',
+'''The value of this tag appears to be misspelled. Please double-check.''',
+
+'no-packager-tag',
+'''There is no Packager tag in your package. You have to specify a packager
+using the Packager tag. Ex: Packager: John Doe <john.doe@example.com>.''',
+
+'invalid-packager',
+'''The packager email must end with an email compatible with the Packager
+option of rpmlint. Please change it and rebuild your package.''',
+
+'no-version-tag',
+'''There is no Version tag in your package. You have to specify a version using
+the Version tag.''',
+
+'no-release-tag',
+'''There is no Release tag in your package. You have to specify a release using
+the Release tag.''',
+
+'not-standard-release-extension',
+'Your release tag must match the regular expression ' + release_ext + '.',
+
+'no-name-tag',
+'''There is no Name tag in your package. You have to specify a name using the
+Name tag.''',
+
+'non-coherent-filename',
+'''The file which contains the package should be named
+<NAME>-<VERSION>-<RELEASE>.<ARCH>.rpm.''',
+
+'no-dependency-on',
+'''
+''',
+
+'incoherent-version-dependency-on',
+'''
+''',
+
+'no-version-dependency-on',
+'''
+''',
+
+'no-major-in-name',
+'''The major number of the library isn't included in the package's name.
+''',
+
+'no-provides',
+'''Your library package doesn't provide the -devel name without the major
+version included.''',
+
+'no-summary-tag',
+'''There is no Summary tag in your package. You have to describe your package
+using this tag. To insert it, just insert a tag 'Summary'.''',
+
+'summary-on-multiple-lines',
+'''Your summary must fit on one line. Please make it shorter and rebuild the
+package.''',
+
+'summary-not-capitalized',
+'''Summary doesn't begin with a capital letter.''',
+
+'summary-ended-with-dot',
+'''Summary ends with a dot.''',
+
+'summary-has-leading-spaces',
+'''Summary begins with whitespace which will waste space when displayed.''',
+
+'no-description-tag',
+'''The description of the package is empty or missing. To add it, insert a
+%description section in your spec file, add a textual description of the
+package after it, and rebuild the package.''',
+
+'description-line-too-long',
+'''Your description lines must not exceed %d characters. If a line is exceeding
+this number, cut it to fit in two lines.''' % max_line_len,
+
+'tag-in-description',
+'''Something that looks like a tag was found in the package's description.
+This may indicate a problem where the tag was not actually parsed as a tag
+but just textual description content, thus being a no-op.  Verify if this is
+the case, and move the tag to a place in the specfile where %description
+won't fool the specfile parser, and rebuild the package.''',
+
+'no-group-tag',
+'''There is no Group tag in your package. You have to specify a valid group
+in your spec file using the Group tag.''',
+
+'non-standard-group',
+'''The value of the Group tag in the package is not valid.  Valid groups are:
+"%s".''' % '", "'.join(VALID_GROUPS),
+
+'no-changelogname-tag',
+'''There is no %changelog tag in your spec file. To insert it, just insert a
+'%changelog' in your spec file and rebuild it.''',
+
+'no-version-in-last-changelog',
+'''The latest changelog entry doesn't contain a version. Please insert the
+version that is coherent with the version of the package and rebuild it.''',
+
+'incoherent-version-in-changelog',
+'''The latest entry in %changelog contains a version identifier that is not
+coherent with the epoch:version-release tuple of the package.''',
+
+'changelog-time-overflow',
+'''The timestamp of the latest entry in %changelog is suspiciously far away in
+the past; it is possible that it is actually so much in the future that it
+has overflowed rpm's timestamp representation.''',
+
+'changelog-time-in-future',
+'''The timestamp of the latest entry in %changelog is in the future.''',
+
+'no-license',
+'''There is no License tag in your spec file. You have to specify one license
+for your program (eg. GPL). To insert this tag, just insert a 'License' in
+your specfile.''',
+
+'invalid-license',
+'''The value of the License tag was not recognized.  Known values are:
+"%s".''' % '", "'.join(VALID_LICENSES),
+
+'obsolete-not-provided',
+'''If a package is obsoleted by a compatible replacement, the obsoleted package
+should also be provided in order to not cause unnecessary dependency breakage.
+If the obsoleting package is not a compatible replacement for the old one,
+leave out the Provides.''',
+
+'invalid-dependency',
+'''An invalid dependency has been detected. It usually means that the build of
+the package was buggy.''',
+
+'no-epoch-tag',
+'''There is no Epoch tag in your package. You have to specify an epoch using the
+Epoch tag.''',
+
+'unreasonable-epoch',
+'''The value of your Epoch tag is unreasonably large (> 99).''',
+
+'no-epoch-in-obsoletes',
+'''Your package contains a versioned Obsoletes entry without an Epoch.''',
+
+'no-epoch-in-conflicts',
+'''Your package contains a versioned Conflicts entry without an Epoch.''',
+
+'no-epoch-in-provides',
+'''Your package contains a versioned Provides entry without an Epoch.''',
+
+'no-epoch-in-dependency',
+'''Your package contains a versioned dependency without an Epoch.''',
+
+'devel-dependency',
+'''Your package has a dependency on a devel package but it's not a devel
+package itself.''',
+
+'invalid-build-requires',
+'''Your source package contains a dependency not compliant with the lib64
+naming. This BuildRequires dependency will not be resolved on lib64 platforms
+(eg. amd64).''',
+
+'explicit-lib-dependency',
+'''You must let rpm find the library dependencies by itself. Do not put unneeded
+explicit Requires: tags.''',
+
+'useless-provides',
+'''This package provides 2 times the same capacity. It should only provide it
+once.''',
+
+'tag-not-utf8',
+'''The character encoding of the value of this tag is not UTF-8.''',
+
+'requires-on-release',
+'''This rpm requires a specific release of another package.''',
+
+'no-url-tag',
+'''The URL tag is missing.''',
+
+'name-repeated-in-summary',
+'''The name of the package is repeated in its summary.  This is often redundant
+information and looks silly in various programs' output.  Make the summary
+brief and to the point without including redundant information in it.''',
+
+'enchant-dictionary-not-found',
+'''A dictionary for the Enchant spell checking library is not available for
+the language given in the info message.  Spell checking will proceed with
+rpmlint's built-in implementation for localized tags in this language.
+For better spell checking results in this language, install the appropriate
+dictionary that Enchant will use for this language, often for example
+hunspell-* or aspell-*.''',
+
+'self-obsoletion',
+'''The package obsoletes itself.  This is known to cause errors in various
+tools and should thus be avoided, usually by using appropriately versioned
+Obsoletes and/or Provides and avoiding unversioned ones.''',
+
+'unexpanded-macro',
+'''This tag contains something that looks like an unexpanded macro; this is
+often the sign of a misspelling. Please check your specfile.''',
+
+'private-shared-object-provides',
+'''A shared object soname provides is provided by a file in a path from which
+other packages should not directly load shared objects from.  Such shared
+objects should thus not be depended on and they should not result in provides
+in the containing package.  Get rid of the provides if appropriate, for example
+by filtering it out during build.  Note that in some cases this may require
+disabling rpmbuild's internal dependency generator.''',
+)
+
+# TagsCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/ZipCheck.py b/ZipCheck.py
new file mode 100644 (file)
index 0000000..ce8f2a3
--- /dev/null
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+#------------------------------------------------------------------------------
+# File          : ZipCheck.py
+# Package       : rpmlint
+# Author        : Ville Skyttä
+# Created on    : Thu Oct 30 00:14:45 EET 2003
+# Version       : $Id: ZipCheck.py 1732 2010-02-21 11:28:42Z scop $
+# Purpose       : Verify Zip/Jar file correctness
+#------------------------------------------------------------------------------
+
+import os
+import re
+import stat
+import sys
+import zipfile
+
+from Filter import addDetails, printError, printWarning
+import AbstractCheck
+import Config
+
+
+zip_regex = re.compile('\.(zip|[ewj]ar)$')
+jar_regex = re.compile('\.[ewj]ar$')
+classpath_regex = re.compile('^\s*Class-Path\s*:', re.M | re.I)
+
+want_indexed_jars = Config.getOption('UseIndexedJars', True)
+
+class ZipCheck(AbstractCheck.AbstractCheck):
+
+    def __init__(self):
+        AbstractCheck.AbstractCheck.__init__(self, "ZipCheck")
+
+    def check(self, pkg):
+        for fname, pkgfile in pkg.files().items():
+            path = pkgfile.path
+            if zip_regex.search(fname) and os.path.exists(path) and \
+                   stat.S_ISREG(os.lstat(path)[stat.ST_MODE]) and \
+                   zipfile.is_zipfile(path):
+                z = None
+                try:
+                    z = zipfile.ZipFile(path, 'r')
+                    badcrc = z.testzip()
+                    if badcrc:
+                        printError(pkg, 'bad-crc-in-zip', badcrc, fname)
+                    compressed = False
+                    for zinfo in z.infolist():
+                        if zinfo.compress_type != zipfile.ZIP_STORED:
+                            compressed = True
+                            break
+                    if not compressed:
+                        printWarning(pkg, 'uncompressed-zip', fname)
+
+                    # additional jar checks
+                    if jar_regex.search(fname):
+                        try:
+                            mf = z.read('META-INF/MANIFEST.MF')
+                            if classpath_regex.search(mf):
+                                printWarning(pkg,
+                                             'class-path-in-manifest', fname)
+                        except KeyError:
+                            # META-INF/* are optional:
+                            # http://java.sun.com/j2se/1.4/docs/guide/jar/jar.html
+                            pass
+                        try:
+                            zinfo = z.getinfo('META-INF/INDEX.LIST')
+                            if not want_indexed_jars:
+                                printWarning(pkg, 'jar-indexed', fname)
+                        except KeyError:
+                            if want_indexed_jars:
+                                printWarning(pkg, 'jar-not-indexed', fname)
+                            pass
+                except:
+                    printWarning(pkg, 'unable-to-read-zip', '%s: %s' %
+                                 (fname, sys.exc_info()[1]))
+
+                z and z.close()
+
+
+check = ZipCheck()
+
+addDetails(
+'bad-crc-in-zip',
+'''The reported file in the zip fails the CRC check. Usually this is a
+sign of a corrupt zip file.''',
+
+'uncompressed-zip',
+'''The zip file is not compressed.''',
+
+'class-path-in-manifest',
+'''The META-INF/MANIFEST.MF file in the jar contains a hardcoded Class-Path.
+These entries do not work with older Java versions and even if they do work,
+they are inflexible and usually cause nasty surprises.''',
+
+'jar-indexed',
+'''The jar file is indexed, ie. it contains the META-INF/INDEX.LIST file.
+These files are known to cause problems with some older Java versions.''',
+
+'jar-not-indexed',
+'''The jar file is not indexed, ie. it does not contain the META-INF/INDEX.LIST
+file.  Indexed jars speed up the class searching process of classloaders
+in some situations.''',
+)
+
+# ZipCheck.py ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/__isocodes__.py b/__isocodes__.py
new file mode 100644 (file)
index 0000000..b3459a1
--- /dev/null
@@ -0,0 +1,7965 @@
+# Generated with tools/generate-isocodes.py
+
+LANGUAGES = set((
+       "aa",
+       "aaa",
+       "aab",
+       "aac",
+       "aad",
+       "aae",
+       "aaf",
+       "aag",
+       "aah",
+       "aai",
+       "aak",
+       "aal",
+       "aam",
+       "aan",
+       "aao",
+       "aap",
+       "aaq",
+       "aas",
+       "aat",
+       "aau",
+       "aaw",
+       "aax",
+       "aaz",
+       "ab",
+       "aba",
+       "abb",
+       "abc",
+       "abd",
+       "abe",
+       "abf",
+       "abg",
+       "abh",
+       "abi",
+       "abj",
+       "abl",
+       "abm",
+       "abn",
+       "abo",
+       "abp",
+       "abq",
+       "abr",
+       "abs",
+       "abt",
+       "abu",
+       "abv",
+       "abw",
+       "abx",
+       "aby",
+       "abz",
+       "aca",
+       "acb",
+       "acd",
+       "ace",
+       "acf",
+       "ach",
+       "aci",
+       "ack",
+       "acl",
+       "acm",
+       "acn",
+       "acp",
+       "acq",
+       "acr",
+       "acs",
+       "act",
+       "acu",
+       "acv",
+       "acw",
+       "acx",
+       "acy",
+       "acz",
+       "ada",
+       "adb",
+       "add",
+       "ade",
+       "adf",
+       "adg",
+       "adh",
+       "adi",
+       "adj",
+       "adl",
+       "adn",
+       "ado",
+       "adp",
+       "adq",
+       "adr",
+       "ads",
+       "adt",
+       "adu",
+       "adw",
+       "adx",
+       "ady",
+       "adz",
+       "ae",
+       "aea",
+       "aeb",
+       "aec",
+       "aed",
+       "aee",
+       "aek",
+       "ael",
+       "aem",
+       "aen",
+       "aeq",
+       "aer",
+       "aes",
+       "aeu",
+       "aew",
+       "aey",
+       "aez",
+       "af",
+       "afb",
+       "afd",
+       "afe",
+       "afg",
+       "afh",
+       "afi",
+       "afk",
+       "afn",
+       "afo",
+       "afp",
+       "afs",
+       "aft",
+       "afu",
+       "afz",
+       "aga",
+       "agb",
+       "agc",
+       "agd",
+       "age",
+       "agf",
+       "agg",
+       "agh",
+       "agi",
+       "agj",
+       "agk",
+       "agl",
+       "agm",
+       "agn",
+       "ago",
+       "agq",
+       "agr",
+       "ags",
+       "agt",
+       "agu",
+       "agv",
+       "agw",
+       "agx",
+       "agy",
+       "agz",
+       "aha",
+       "ahb",
+       "ahg",
+       "ahh",
+       "ahi",
+       "ahk",
+       "ahl",
+       "ahm",
+       "ahn",
+       "aho",
+       "ahp",
+       "ahr",
+       "ahs",
+       "aht",
+       "aia",
+       "aib",
+       "aic",
+       "aid",
+       "aie",
+       "aif",
+       "aig",
+       "aih",
+       "aii",
+       "aij",
+       "aik",
+       "ail",
+       "aim",
+       "ain",
+       "aio",
+       "aip",
+       "aiq",
+       "air",
+       "ais",
+       "ait",
+       "aiw",
+       "aix",
+       "aiy",
+       "aja",
+       "ajg",
+       "aji",
+       "ajp",
+       "ajt",
+       "aju",
+       "ajw",
+       "ajz",
+       "ak",
+       "akb",
+       "akc",
+       "akd",
+       "ake",
+       "akf",
+       "akg",
+       "akh",
+       "aki",
+       "akj",
+       "akk",
+       "akl",
+       "akm",
+       "ako",
+       "akp",
+       "akq",
+       "akr",
+       "aks",
+       "akt",
+       "aku",
+       "akv",
+       "akw",
+       "akx",
+       "aky",
+       "akz",
+       "ala",
+       "alc",
+       "ald",
+       "ale",
+       "alf",
+       "alh",
+       "ali",
+       "alj",
+       "alk",
+       "all",
+       "alm",
+       "aln",
+       "alo",
+       "alp",
+       "alq",
+       "alr",
+       "als",
+       "alt",
+       "alu",
+       "alw",
+       "alx",
+       "aly",
+       "alz",
+       "am",
+       "ama",
+       "amb",
+       "amc",
+       "ame",
+       "amf",
+       "amg",
+       "ami",
+       "amj",
+       "amk",
+       "aml",
+       "amm",
+       "amn",
+       "amo",
+       "amp",
+       "amq",
+       "amr",
+       "ams",
+       "amt",
+       "amu",
+       "amv",
+       "amw",
+       "amx",
+       "amy",
+       "amz",
+       "an",
+       "ana",
+       "anb",
+       "anc",
+       "and",
+       "ane",
+       "anf",
+       "ang",
+       "anh",
+       "ani",
+       "anj",
+       "ank",
+       "anl",
+       "anm",
+       "ann",
+       "ano",
+       "anp",
+       "anq",
+       "anr",
+       "ans",
+       "ant",
+       "anu",
+       "anv",
+       "anw",
+       "anx",
+       "any",
+       "anz",
+       "aoa",
+       "aob",
+       "aoc",
+       "aod",
+       "aoe",
+       "aof",
+       "aog",
+       "aoh",
+       "aoi",
+       "aoj",
+       "aok",
+       "aol",
+       "aom",
+       "aon",
+       "aor",
+       "aos",
+       "aot",
+       "aox",
+       "aoz",
+       "apb",
+       "apc",
+       "apd",
+       "ape",
+       "apf",
+       "apg",
+       "aph",
+       "api",
+       "apj",
+       "apk",
+       "apl",
+       "apm",
+       "apn",
+       "apo",
+       "app",
+       "apq",
+       "apr",
+       "aps",
+       "apt",
+       "apu",
+       "apv",
+       "apw",
+       "apx",
+       "apy",
+       "apz",
+       "aqc",
+       "aqd",
+       "aqg",
+       "aqm",
+       "aqn",
+       "aqp",
+       "aqr",
+       "aqz",
+       "ar",
+       "arb",
+       "arc",
+       "ard",
+       "are",
+       "arh",
+       "ari",
+       "arj",
+       "ark",
+       "arl",
+       "arn",
+       "aro",
+       "arp",
+       "arq",
+       "arr",
+       "ars",
+       "aru",
+       "arv",
+       "arw",
+       "arx",
+       "ary",
+       "arz",
+       "as",
+       "asa",
+       "asb",
+       "asc",
+       "asd",
+       "ase",
+       "asf",
+       "asg",
+       "ash",
+       "asi",
+       "asj",
+       "ask",
+       "asl",
+       "asn",
+       "aso",
+       "asp",
+       "asq",
+       "asr",
+       "ass",
+       "ast",
+       "asu",
+       "asv",
+       "asw",
+       "asx",
+       "asy",
+       "asz",
+       "ata",
+       "atb",
+       "atc",
+       "atd",
+       "ate",
+       "atg",
+       "ati",
+       "atj",
+       "atk",
+       "atl",
+       "atm",
+       "atn",
+       "ato",
+       "atp",
+       "atq",
+       "atr",
+       "ats",
+       "att",
+       "atu",
+       "atv",
+       "atw",
+       "atx",
+       "aty",
+       "atz",
+       "aua",
+       "aub",
+       "auc",
+       "aud",
+       "aue",
+       "aug",
+       "auh",
+       "aui",
+       "auj",
+       "auk",
+       "aul",
+       "aum",
+       "aun",
+       "auo",
+       "aup",
+       "auq",
+       "aur",
+       "aut",
+       "auu",
+       "auw",
+       "aux",
+       "auy",
+       "auz",
+       "av",
+       "avb",
+       "avd",
+       "avi",
+       "avk",
+       "avl",
+       "avn",
+       "avo",
+       "avs",
+       "avt",
+       "avu",
+       "avv",
+       "awa",
+       "awb",
+       "awc",
+       "awe",
+       "awh",
+       "awi",
+       "awk",
+       "awm",
+       "awn",
+       "awo",
+       "awr",
+       "aws",
+       "awt",
+       "awu",
+       "awv",
+       "aww",
+       "awx",
+       "awy",
+       "axb",
+       "axg",
+       "axk",
+       "axm",
+       "axx",
+       "ay",
+       "aya",
+       "ayb",
+       "ayc",
+       "ayd",
+       "aye",
+       "ayg",
+       "ayh",
+       "ayi",
+       "ayk",
+       "ayl",
+       "ayn",
+       "ayo",
+       "ayp",
+       "ayq",
+       "ayr",
+       "ays",
+       "ayt",
+       "ayu",
+       "ayy",
+       "ayz",
+       "az",
+       "aza",
+       "azb",
+       "azg",
+       "azj",
+       "azm",
+       "azo",
+       "azt",
+       "azz",
+       "ba",
+       "baa",
+       "bab",
+       "bac",
+       "bae",
+       "baf",
+       "bag",
+       "bah",
+       "baj",
+       "bal",
+       "ban",
+       "bao",
+       "bap",
+       "bar",
+       "bas",
+       "bau",
+       "bav",
+       "baw",
+       "bax",
+       "bay",
+       "baz",
+       "bba",
+       "bbb",
+       "bbc",
+       "bbd",
+       "bbe",
+       "bbf",
+       "bbg",
+       "bbh",
+       "bbi",
+       "bbj",
+       "bbk",
+       "bbl",
+       "bbm",
+       "bbn",
+       "bbo",
+       "bbp",
+       "bbq",
+       "bbr",
+       "bbs",
+       "bbt",
+       "bbu",
+       "bbv",
+       "bbw",
+       "bbx",
+       "bby",
+       "bbz",
+       "bca",
+       "bcb",
+       "bcc",
+       "bcd",
+       "bce",
+       "bcf",
+       "bcg",
+       "bch",
+       "bci",
+       "bcj",
+       "bck",
+       "bcl",
+       "bcm",
+       "bcn",
+       "bco",
+       "bcp",
+       "bcq",
+       "bcr",
+       "bcs",
+       "bct",
+       "bcu",
+       "bcv",
+       "bcw",
+       "bcy",
+       "bcz",
+       "bda",
+       "bdb",
+       "bdc",
+       "bdd",
+       "bde",
+       "bdf",
+       "bdg",
+       "bdh",
+       "bdi",
+       "bdj",
+       "bdk",
+       "bdl",
+       "bdm",
+       "bdn",
+       "bdo",
+       "bdp",
+       "bdq",
+       "bdr",
+       "bds",
+       "bdt",
+       "bdu",
+       "bdv",
+       "bdw",
+       "bdx",
+       "bdy",
+       "bdz",
+       "be",
+       "bea",
+       "beb",
+       "bec",
+       "bed",
+       "bee",
+       "bef",
+       "beg",
+       "beh",
+       "bei",
+       "bej",
+       "bek",
+       "bem",
+       "beo",
+       "bep",
+       "beq",
+       "bes",
+       "bet",
+       "beu",
+       "bev",
+       "bew",
+       "bex",
+       "bey",
+       "bez",
+       "bfa",
+       "bfb",
+       "bfc",
+       "bfd",
+       "bfe",
+       "bff",
+       "bfg",
+       "bfh",
+       "bfi",
+       "bfj",
+       "bfk",
+       "bfl",
+       "bfm",
+       "bfn",
+       "bfo",
+       "bfp",
+       "bfq",
+       "bfr",
+       "bfs",
+       "bft",
+       "bfu",
+       "bfw",
+       "bfx",
+       "bfy",
+       "bfz",
+       "bg",
+       "bga",
+       "bgb",
+       "bgc",
+       "bgd",
+       "bge",
+       "bgf",
+       "bgg",
+       "bgi",
+       "bgj",
+       "bgk",
+       "bgl",
+       "bgm",
+       "bgn",
+       "bgo",
+       "bgp",
+       "bgq",
+       "bgr",
+       "bgs",
+       "bgt",
+       "bgu",
+       "bgv",
+       "bgw",
+       "bgx",
+       "bgy",
+       "bgz",
+       "bh",
+       "bha",
+       "bhb",
+       "bhc",
+       "bhd",
+       "bhe",
+       "bhf",
+       "bhg",
+       "bhh",
+       "bhi",
+       "bhj",
+       "bhl",
+       "bhm",
+       "bhn",
+       "bho",
+       "bhp",
+       "bhq",
+       "bhr",
+       "bhs",
+       "bht",
+       "bhu",
+       "bhv",
+       "bhw",
+       "bhx",
+       "bhy",
+       "bhz",
+       "bi",
+       "bia",
+       "bib",
+       "bic",
+       "bid",
+       "bie",
+       "bif",
+       "big",
+       "bij",
+       "bik",
+       "bil",
+       "bim",
+       "bin",
+       "bio",
+       "bip",
+       "biq",
+       "bir",
+       "bit",
+       "biu",
+       "biv",
+       "biw",
+       "bix",
+       "biy",
+       "biz",
+       "bja",
+       "bjb",
+       "bjc",
+       "bjd",
+       "bje",
+       "bjf",
+       "bjg",
+       "bjh",
+       "bji",
+       "bjj",
+       "bjk",
+       "bjl",
+       "bjm",
+       "bjn",
+       "bjo",
+       "bjr",
+       "bjs",
+       "bjt",
+       "bju",
+       "bjv",
+       "bjw",
+       "bjx",
+       "bjy",
+       "bjz",
+       "bka",
+       "bkc",
+       "bkd",
+       "bkf",
+       "bkg",
+       "bkh",
+       "bki",
+       "bkj",
+       "bkk",
+       "bkl",
+       "bkm",
+       "bkn",
+       "bko",
+       "bkp",
+       "bkq",
+       "bkr",
+       "bks",
+       "bkt",
+       "bku",
+       "bkv",
+       "bkw",
+       "bkx",
+       "bky",
+       "bkz",
+       "bla",
+       "blb",
+       "blc",
+       "bld",
+       "ble",
+       "blf",
+       "blg",
+       "blh",
+       "bli",
+       "blj",
+       "blk",
+       "bll",
+       "blm",
+       "bln",
+       "blo",
+       "blp",
+       "blq",
+       "blr",
+       "bls",
+       "blt",
+       "blv",
+       "blw",
+       "blx",
+       "bly",
+       "blz",
+       "bm",
+       "bma",
+       "bmb",
+       "bmc",
+       "bmd",
+       "bme",
+       "bmf",
+       "bmg",
+       "bmh",
+       "bmi",
+       "bmj",
+       "bmk",
+       "bml",
+       "bmm",
+       "bmn",
+       "bmo",
+       "bmp",
+       "bmq",
+       "bmr",
+       "bms",
+       "bmt",
+       "bmu",
+       "bmv",
+       "bmw",
+       "bmx",
+       "bmy",
+       "bmz",
+       "bn",
+       "bna",
+       "bnb",
+       "bnc",
+       "bnd",
+       "bne",
+       "bnf",
+       "bng",
+       "bni",
+       "bnj",
+       "bnk",
+       "bnl",
+       "bnm",
+       "bnn",
+       "bno",
+       "bnp",
+       "bnq",
+       "bnr",
+       "bns",
+       "bnu",
+       "bnv",
+       "bnw",
+       "bnx",
+       "bny",
+       "bnz",
+       "bo",
+       "boa",
+       "bob",
+       "boe",
+       "bof",
+       "bog",
+       "boh",
+       "boi",
+       "boj",
+       "bok",
+       "bol",
+       "bom",
+       "bon",
+       "boo",
+       "bop",
+       "boq",
+       "bor",
+       "bot",
+       "bou",
+       "bov",
+       "bow",
+       "box",
+       "boy",
+       "boz",
+       "bpa",
+       "bpb",
+       "bpd",
+       "bpg",
+       "bph",
+       "bpi",
+       "bpj",
+       "bpk",
+       "bpl",
+       "bpm",
+       "bpn",
+       "bpo",
+       "bpp",
+       "bpq",
+       "bpr",
+       "bps",
+       "bpt",
+       "bpu",
+       "bpv",
+       "bpw",
+       "bpx",
+       "bpy",
+       "bpz",
+       "bqa",
+       "bqb",
+       "bqc",
+       "bqd",
+       "bqf",
+       "bqg",
+       "bqh",
+       "bqi",
+       "bqj",
+       "bqk",
+       "bql",
+       "bqm",
+       "bqn",
+       "bqo",
+       "bqp",
+       "bqq",
+       "bqr",
+       "bqs",
+       "bqt",
+       "bqu",
+       "bqv",
+       "bqw",
+       "bqx",
+       "bqy",
+       "bqz",
+       "br",
+       "bra",
+       "brb",
+       "brc",
+       "brd",
+       "brf",
+       "brg",
+       "brh",
+       "bri",
+       "brj",
+       "brk",
+       "brl",
+       "brm",
+       "brn",
+       "bro",
+       "brp",
+       "brq",
+       "brr",
+       "brs",
+       "brt",
+       "bru",
+       "brv",
+       "brw",
+       "brx",
+       "bry",
+       "brz",
+       "bs",
+       "bsa",
+       "bsb",
+       "bsc",
+       "bse",
+       "bsf",
+       "bsg",
+       "bsh",
+       "bsi",
+       "bsj",
+       "bsk",
+       "bsl",
+       "bsm",
+       "bsn",
+       "bso",
+       "bsp",
+       "bsq",
+       "bsr",
+       "bss",
+       "bst",
+       "bsu",
+       "bsv",
+       "bsw",
+       "bsx",
+       "bsy",
+       "bta",
+       "btc",
+       "btd",
+       "bte",
+       "btf",
+       "btg",
+       "bth",
+       "bti",
+       "btj",
+       "btl",
+       "btm",
+       "btn",
+       "bto",
+       "btp",
+       "btq",
+       "btr",
+       "bts",
+       "btt",
+       "btu",
+       "btv",
+       "btw",
+       "btx",
+       "bty",
+       "btz",
+       "bua",
+       "bub",
+       "buc",
+       "bud",
+       "bue",
+       "buf",
+       "bug",
+       "buh",
+       "bui",
+       "buj",
+       "buk",
+       "bum",
+       "bun",
+       "buo",
+       "bup",
+       "buq",
+       "bus",
+       "but",
+       "buu",
+       "buv",
+       "buw",
+       "bux",
+       "buy",
+       "buz",
+       "bva",
+       "bvb",
+       "bvc",
+       "bvd",
+       "bve",
+       "bvf",
+       "bvg",
+       "bvh",
+       "bvi",
+       "bvj",
+       "bvk",
+       "bvl",
+       "bvm",
+       "bvn",
+       "bvo",
+       "bvq",
+       "bvr",
+       "bvt",
+       "bvu",
+       "bvv",
+       "bvw",
+       "bvx",
+       "bvy",
+       "bvz",
+       "bwa",
+       "bwb",
+       "bwc",
+       "bwd",
+       "bwe",
+       "bwf",
+       "bwg",
+       "bwh",
+       "bwi",
+       "bwj",
+       "bwk",
+       "bwl",
+       "bwm",
+       "bwn",
+       "bwo",
+       "bwp",
+       "bwq",
+       "bwr",
+       "bws",
+       "bwt",
+       "bwu",
+       "bww",
+       "bwx",
+       "bwy",
+       "bwz",
+       "bxa",
+       "bxb",
+       "bxc",
+       "bxd",
+       "bxe",
+       "bxf",
+       "bxg",
+       "bxh",
+       "bxi",
+       "bxj",
+       "bxk",
+       "bxl",
+       "bxm",
+       "bxn",
+       "bxo",
+       "bxp",
+       "bxq",
+       "bxr",
+       "bxs",
+       "bxu",
+       "bxv",
+       "bxw",
+       "bxx",
+       "bxz",
+       "bya",
+       "byb",
+       "byc",
+       "byd",
+       "bye",
+       "byf",
+       "byg",
+       "byh",
+       "byi",
+       "byj",
+       "byk",
+       "byl",
+       "bym",
+       "byn",
+       "byo",
+       "byp",
+       "byq",
+       "byr",
+       "bys",
+       "byt",
+       "byv",
+       "byw",
+       "byx",
+       "byy",
+       "byz",
+       "bza",
+       "bzb",
+       "bzc",
+       "bzd",
+       "bze",
+       "bzf",
+       "bzg",
+       "bzh",
+       "bzi",
+       "bzj",
+       "bzk",
+       "bzl",
+       "bzm",
+       "bzn",
+       "bzo",
+       "bzp",
+       "bzq",
+       "bzr",
+       "bzs",
+       "bzt",
+       "bzu",
+       "bzv",
+       "bzw",
+       "bzx",
+       "bzy",
+       "bzz",
+       "ca",
+       "caa",
+       "cab",
+       "cac",
+       "cad",
+       "cae",
+       "caf",
+       "cag",
+       "cah",
+       "caj",
+       "cak",
+       "cal",
+       "cam",
+       "can",
+       "cao",
+       "cap",
+       "caq",
+       "car",
+       "cas",
+       "cav",
+       "caw",
+       "cax",
+       "cay",
+       "caz",
+       "cbb",
+       "cbc",
+       "cbd",
+       "cbe",
+       "cbg",
+       "cbh",
+       "cbi",
+       "cbj",
+       "cbk",
+       "cbl",
+       "cbn",
+       "cbo",
+       "cbr",
+       "cbs",
+       "cbt",
+       "cbu",
+       "cbv",
+       "cbw",
+       "cby",
+       "cca",
+       "ccc",
+       "ccd",
+       "cce",
+       "ccg",
+       "cch",
+       "ccj",
+       "ccl",
+       "ccm",
+       "cco",
+       "ccp",
+       "ccq",
+       "ccr",
+       "cda",
+       "cde",
+       "cdf",
+       "cdg",
+       "cdh",
+       "cdi",
+       "cdj",
+       "cdm",
+       "cdn",
+       "cdo",
+       "cdr",
+       "cds",
+       "cdy",
+       "cdz",
+       "ce",
+       "cea",
+       "ceb",
+       "ceg",
+       "cen",
+       "cet",
+       "cfa",
+       "cfd",
+       "cfg",
+       "cfm",
+       "cga",
+       "cgc",
+       "cgg",
+       "cgk",
+       "ch",
+       "chb",
+       "chc",
+       "chd",
+       "chf",
+       "chg",
+       "chh",
+       "chj",
+       "chk",
+       "chl",
+       "chm",
+       "chn",
+       "cho",
+       "chp",
+       "chq",
+       "chr",
+       "cht",
+       "chw",
+       "chx",
+       "chy",
+       "chz",
+       "cia",
+       "cib",
+       "cic",
+       "cid",
+       "cie",
+       "cih",
+       "cik",
+       "cim",
+       "cin",
+       "cip",
+       "cir",
+       "ciw",
+       "ciy",
+       "cja",
+       "cje",
+       "cjh",
+       "cji",
+       "cjk",
+       "cjm",
+       "cjn",
+       "cjo",
+       "cjp",
+       "cjs",
+       "cjv",
+       "cjy",
+       "cka",
+       "ckb",
+       "ckh",
+       "ckl",
+       "cko",
+       "ckq",
+       "ckr",
+       "cks",
+       "ckt",
+       "cku",
+       "ckv",
+       "ckx",
+       "cky",
+       "ckz",
+       "cla",
+       "clc",
+       "cld",
+       "cle",
+       "clh",
+       "cli",
+       "clk",
+       "cll",
+       "clm",
+       "clo",
+       "clu",
+       "clw",
+       "cly",
+       "cma",
+       "cme",
+       "cmg",
+       "cmi",
+       "cml",
+       "cmm",
+       "cmn",
+       "cmo",
+       "cmr",
+       "cms",
+       "cmt",
+       "cna",
+       "cnb",
+       "cnc",
+       "cng",
+       "cnh",
+       "cni",
+       "cnk",
+       "cnl",
+       "cno",
+       "cns",
+       "cnt",
+       "cnu",
+       "cnw",
+       "cnx",
+       "co",
+       "coa",
+       "cob",
+       "coc",
+       "cod",
+       "coe",
+       "cof",
+       "cog",
+       "coh",
+       "coj",
+       "cok",
+       "col",
+       "com",
+       "con",
+       "coo",
+       "cop",
+       "coq",
+       "cot",
+       "cou",
+       "cov",
+       "cow",
+       "cox",
+       "coy",
+       "coz",
+       "cpa",
+       "cpb",
+       "cpc",
+       "cpg",
+       "cpi",
+       "cpn",
+       "cps",
+       "cpu",
+       "cpx",
+       "cpy",
+       "cqd",
+       "cqu",
+       "cr",
+       "cra",
+       "crb",
+       "crc",
+       "crd",
+       "crf",
+       "crg",
+       "crh",
+       "cri",
+       "crj",
+       "crk",
+       "crl",
+       "crm",
+       "crn",
+       "cro",
+       "crq",
+       "crr",
+       "crs",
+       "crt",
+       "crv",
+       "crw",
+       "crx",
+       "cry",
+       "crz",
+       "cs",
+       "csa",
+       "csb",
+       "csc",
+       "csd",
+       "cse",
+       "csf",
+       "csg",
+       "csh",
+       "csi",
+       "csk",
+       "csl",
+       "csm",
+       "csn",
+       "cso",
+       "csq",
+       "csr",
+       "css",
+       "cst",
+       "csw",
+       "csy",
+       "csz",
+       "cta",
+       "ctc",
+       "ctd",
+       "cte",
+       "ctg",
+       "ctl",
+       "ctm",
+       "ctn",
+       "cto",
+       "ctp",
+       "cts",
+       "ctt",
+       "ctu",
+       "ctz",
+       "cu",
+       "cua",
+       "cub",
+       "cuc",
+       "cug",
+       "cuh",
+       "cui",
+       "cuj",
+       "cuk",
+       "cul",
+       "cum",
+       "cuo",
+       "cup",
+       "cuq",
+       "cur",
+       "cut",
+       "cuu",
+       "cuv",
+       "cuw",
+       "cux",
+       "cv",
+       "cvg",
+       "cvn",
+       "cwa",
+       "cwb",
+       "cwd",
+       "cwe",
+       "cwg",
+       "cwt",
+       "cy",
+       "cya",
+       "cyb",
+       "cyo",
+       "czh",
+       "czk",
+       "czn",
+       "czo",
+       "czt",
+       "da",
+       "daa",
+       "dac",
+       "dad",
+       "dae",
+       "daf",
+       "dag",
+       "dah",
+       "dai",
+       "daj",
+       "dak",
+       "dal",
+       "dam",
+       "dao",
+       "dap",
+       "daq",
+       "dar",
+       "das",
+       "dau",
+       "dav",
+       "daw",
+       "dax",
+       "daz",
+       "dba",
+       "dbb",
+       "dbd",
+       "dbe",
+       "dbf",
+       "dbg",
+       "dbi",
+       "dbj",
+       "dbl",
+       "dbm",
+       "dbn",
+       "dbo",
+       "dbp",
+       "dbq",
+       "dbr",
+       "dbu",
+       "dbv",
+       "dby",
+       "dcc",
+       "dcr",
+       "ddd",
+       "dde",
+       "ddg",
+       "ddi",
+       "ddj",
+       "ddn",
+       "ddo",
+       "dds",
+       "ddw",
+       "de",
+       "dec",
+       "ded",
+       "dee",
+       "def",
+       "deg",
+       "deh",
+       "dei",
+       "dek",
+       "del",
+       "dem",
+       "den",
+       "dep",
+       "deq",
+       "der",
+       "des",
+       "dev",
+       "dez",
+       "dga",
+       "dgb",
+       "dgc",
+       "dgd",
+       "dge",
+       "dgg",
+       "dgh",
+       "dgi",
+       "dgk",
+       "dgn",
+       "dgo",
+       "dgr",
+       "dgs",
+       "dgu",
+       "dgx",
+       "dgz",
+       "dhd",
+       "dhg",
+       "dhi",
+       "dhl",
+       "dhm",
+       "dhn",
+       "dho",
+       "dhr",
+       "dhs",
+       "dhu",
+       "dhv",
+       "dhw",
+       "dia",
+       "dib",
+       "dic",
+       "did",
+       "dif",
+       "dig",
+       "dih",
+       "dii",
+       "dij",
+       "dik",
+       "dil",
+       "dim",
+       "din",
+       "dio",
+       "dip",
+       "diq",
+       "dir",
+       "dis",
+       "dit",
+       "diu",
+       "diw",
+       "dix",
+       "diy",
+       "diz",
+       "djb",
+       "djc",
+       "djd",
+       "dje",
+       "djf",
+       "dji",
+       "djj",
+       "djk",
+       "djl",
+       "djm",
+       "djn",
+       "djo",
+       "djr",
+       "dju",
+       "djw",
+       "dka",
+       "dkk",
+       "dkr",
+       "dks",
+       "dkx",
+       "dlg",
+       "dlm",
+       "dln",
+       "dma",
+       "dmb",
+       "dmc",
+       "dme",
+       "dmg",
+       "dmk",
+       "dml",
+       "dmm",
+       "dmo",
+       "dmr",
+       "dms",
+       "dmu",
+       "dmv",
+       "dmx",
+       "dmy",
+       "dna",
+       "dnd",
+       "dne",
+       "dng",
+       "dni",
+       "dnk",
+       "dnn",
+       "dnr",
+       "dnt",
+       "dnu",
+       "dnw",
+       "dny",
+       "doa",
+       "dob",
+       "doc",
+       "doe",
+       "dof",
+       "doh",
+       "doi",
+       "dok",
+       "dol",
+       "don",
+       "doo",
+       "dop",
+       "doq",
+       "dor",
+       "dos",
+       "dot",
+       "dov",
+       "dow",
+       "dox",
+       "doy",
+       "doz",
+       "dpp",
+       "drb",
+       "drc",
+       "drd",
+       "dre",
+       "drg",
+       "dri",
+       "drl",
+       "drn",
+       "dro",
+       "drq",
+       "drr",
+       "drs",
+       "drt",
+       "dru",
+       "dry",
+       "dsb",
+       "dse",
+       "dsh",
+       "dsi",
+       "dsl",
+       "dsn",
+       "dso",
+       "dsq",
+       "dta",
+       "dtb",
+       "dtd",
+       "dti",
+       "dtk",
+       "dtm",
+       "dtp",
+       "dtr",
+       "dts",
+       "dtt",
+       "dtu",
+       "dua",
+       "dub",
+       "duc",
+       "dud",
+       "due",
+       "duf",
+       "dug",
+       "duh",
+       "dui",
+       "duj",
+       "duk",
+       "dul",
+       "dum",
+       "dun",
+       "duo",
+       "dup",
+       "duq",
+       "dur",
+       "dus",
+       "duu",
+       "duv",
+       "duw",
+       "dux",
+       "duy",
+       "duz",
+       "dv",
+       "dva",
+       "dwa",
+       "dwl",
+       "dwr",
+       "dws",
+       "dww",
+       "dya",
+       "dyb",
+       "dyd",
+       "dyg",
+       "dyi",
+       "dym",
+       "dyn",
+       "dyo",
+       "dyu",
+       "dyy",
+       "dz",
+       "dza",
+       "dzd",
+       "dzg",
+       "dzl",
+       "dzn",
+       "ebg",
+       "ebk",
+       "ebo",
+       "ebr",
+       "ebu",
+       "ecr",
+       "ecs",
+       "ecy",
+       "ee",
+       "eee",
+       "efa",
+       "efe",
+       "efi",
+       "ega",
+       "egl",
+       "ego",
+       "egy",
+       "ehu",
+       "eip",
+       "eit",
+       "eiv",
+       "eja",
+       "eka",
+       "eke",
+       "ekg",
+       "eki",
+       "ekk",
+       "ekl",
+       "ekm",
+       "eko",
+       "ekp",
+       "ekr",
+       "eky",
+       "el",
+       "ele",
+       "elh",
+       "eli",
+       "elk",
+       "elm",
+       "elo",
+       "elp",
+       "elu",
+       "elx",
+       "ema",
+       "emb",
+       "eme",
+       "emg",
+       "emi",
+       "emk",
+       "emm",
+       "emn",
+       "emo",
+       "emp",
+       "ems",
+       "emu",
+       "emw",
+       "emx",
+       "emy",
+       "en",
+       "ena",
+       "enb",
+       "enc",
+       "end",
+       "enf",
+       "enh",
+       "enm",
+       "enn",
+       "eno",
+       "enq",
+       "enr",
+       "enu",
+       "env",
+       "enw",
+       "eo",
+       "eot",
+       "epi",
+       "era",
+       "erg",
+       "erh",
+       "eri",
+       "erk",
+       "ero",
+       "err",
+       "ers",
+       "ert",
+       "erw",
+       "es",
+       "ese",
+       "esh",
+       "esi",
+       "esk",
+       "esl",
+       "esm",
+       "esn",
+       "eso",
+       "esq",
+       "ess",
+       "esu",
+       "et",
+       "etb",
+       "etc",
+       "eth",
+       "etn",
+       "eto",
+       "etr",
+       "ets",
+       "ett",
+       "etu",
+       "etx",
+       "etz",
+       "eu",
+       "eve",
+       "evh",
+       "evn",
+       "ewo",
+       "ext",
+       "eya",
+       "eyo",
+       "eze",
+       "fa",
+       "faa",
+       "fab",
+       "fad",
+       "faf",
+       "fag",
+       "fah",
+       "fai",
+       "faj",
+       "fak",
+       "fal",
+       "fam",
+       "fan",
+       "fap",
+       "far",
+       "fat",
+       "fau",
+       "fax",
+       "fay",
+       "faz",
+       "fbl",
+       "fcs",
+       "fer",
+       "ff",
+       "ffi",
+       "ffm",
+       "fgr",
+       "fi",
+       "fia",
+       "fie",
+       "fil",
+       "fip",
+       "fir",
+       "fit",
+       "fiw",
+       "fj",
+       "fkv",
+       "fla",
+       "flh",
+       "fli",
+       "fll",
+       "fln",
+       "flr",
+       "fly",
+       "fmp",
+       "fmu",
+       "fng",
+       "fni",
+       "fo",
+       "fod",
+       "foi",
+       "fom",
+       "fon",
+       "for",
+       "fos",
+       "fpe",
+       "fqs",
+       "fr",
+       "frc",
+       "frd",
+       "frk",
+       "frm",
+       "fro",
+       "frp",
+       "frq",
+       "frr",
+       "frs",
+       "frt",
+       "fse",
+       "fsl",
+       "fss",
+       "fub",
+       "fuc",
+       "fud",
+       "fue",
+       "fuf",
+       "fuh",
+       "fui",
+       "fuj",
+       "fum",
+       "fun",
+       "fuq",
+       "fur",
+       "fut",
+       "fuu",
+       "fuv",
+       "fuy",
+       "fvr",
+       "fwa",
+       "fwe",
+       "fy",
+       "ga",
+       "gaa",
+       "gab",
+       "gac",
+       "gad",
+       "gae",
+       "gaf",
+       "gag",
+       "gah",
+       "gai",
+       "gaj",
+       "gak",
+       "gal",
+       "gam",
+       "gan",
+       "gao",
+       "gap",
+       "gaq",
+       "gar",
+       "gas",
+       "gat",
+       "gau",
+       "gaw",
+       "gax",
+       "gay",
+       "gaz",
+       "gba",
+       "gbb",
+       "gbc",
+       "gbd",
+       "gbe",
+       "gbf",
+       "gbg",
+       "gbh",
+       "gbi",
+       "gbj",
+       "gbk",
+       "gbl",
+       "gbm",
+       "gbn",
+       "gbo",
+       "gbp",
+       "gbq",
+       "gbr",
+       "gbs",
+       "gbu",
+       "gbv",
+       "gbx",
+       "gby",
+       "gbz",
+       "gcc",
+       "gcd",
+       "gce",
+       "gcf",
+       "gcl",
+       "gcn",
+       "gcr",
+       "gct",
+       "gd",
+       "gda",
+       "gdb",
+       "gdc",
+       "gdd",
+       "gde",
+       "gdf",
+       "gdg",
+       "gdh",
+       "gdi",
+       "gdj",
+       "gdk",
+       "gdl",
+       "gdm",
+       "gdn",
+       "gdo",
+       "gdq",
+       "gdr",
+       "gdu",
+       "gdx",
+       "gea",
+       "geb",
+       "gec",
+       "ged",
+       "geg",
+       "geh",
+       "gei",
+       "gej",
+       "gek",
+       "gel",
+       "geq",
+       "ges",
+       "gew",
+       "gex",
+       "gey",
+       "gez",
+       "gfk",
+       "gft",
+       "gga",
+       "ggb",
+       "ggd",
+       "gge",
+       "ggg",
+       "ggk",
+       "ggl",
+       "ggn",
+       "ggo",
+       "ggr",
+       "ggt",
+       "ggu",
+       "ggw",
+       "gha",
+       "ghc",
+       "ghe",
+       "ghh",
+       "ghk",
+       "ghl",
+       "ghn",
+       "gho",
+       "ghr",
+       "ghs",
+       "ght",
+       "gia",
+       "gib",
+       "gic",
+       "gid",
+       "gig",
+       "gil",
+       "gim",
+       "gin",
+       "gio",
+       "gip",
+       "giq",
+       "gir",
+       "gis",
+       "git",
+       "giw",
+       "gix",
+       "giy",
+       "giz",
+       "gji",
+       "gjk",
+       "gjn",
+       "gju",
+       "gka",
+       "gke",
+       "gkn",
+       "gkp",
+       "gl",
+       "glc",
+       "gld",
+       "glh",
+       "gli",
+       "glj",
+       "glk",
+       "glo",
+       "glr",
+       "glu",
+       "glw",
+       "gly",
+       "gma",
+       "gmb",
+       "gmd",
+       "gmh",
+       "gml",
+       "gmm",
+       "gmn",
+       "gmu",
+       "gmv",
+       "gmx",
+       "gmy",
+       "gn",
+       "gna",
+       "gnb",
+       "gnc",
+       "gnd",
+       "gne",
+       "gng",
+       "gnh",
+       "gni",
+       "gnk",
+       "gnl",
+       "gnm",
+       "gnn",
+       "gno",
+       "gnq",
+       "gnr",
+       "gnt",
+       "gnu",
+       "gnw",
+       "gnz",
+       "goa",
+       "gob",
+       "goc",
+       "god",
+       "goe",
+       "gof",
+       "gog",
+       "goh",
+       "goi",
+       "goj",
+       "gok",
+       "gol",
+       "gom",
+       "gon",
+       "goo",
+       "gop",
+       "goq",
+       "gor",
+       "gos",
+       "got",
+       "gou",
+       "gow",
+       "gox",
+       "goy",
+       "goz",
+       "gpa",
+       "gpn",
+       "gqa",
+       "gqi",
+       "gqn",
+       "gqr",
+       "gra",
+       "grb",
+       "grc",
+       "grd",
+       "grg",
+       "grh",
+       "gri",
+       "grj",
+       "grm",
+       "gro",
+       "grq",
+       "grr",
+       "grs",
+       "grt",
+       "gru",
+       "grv",
+       "grw",
+       "grx",
+       "gry",
+       "grz",
+       "gse",
+       "gsg",
+       "gsl",
+       "gsm",
+       "gsn",
+       "gso",
+       "gsp",
+       "gss",
+       "gsw",
+       "gta",
+       "gti",
+       "gu",
+       "gua",
+       "gub",
+       "guc",
+       "gud",
+       "gue",
+       "guf",
+       "gug",
+       "guh",
+       "gui",
+       "guk",
+       "gul",
+       "gum",
+       "gun",
+       "guo",
+       "gup",
+       "guq",
+       "gur",
+       "gus",
+       "gut",
+       "guu",
+       "guv",
+       "guw",
+       "gux",
+       "guz",
+       "gv",
+       "gva",
+       "gvc",
+       "gve",
+       "gvf",
+       "gvj",
+       "gvl",
+       "gvm",
+       "gvn",
+       "gvo",
+       "gvp",
+       "gvr",
+       "gvs",
+       "gvy",
+       "gwa",
+       "gwb",
+       "gwc",
+       "gwd",
+       "gwe",
+       "gwf",
+       "gwg",
+       "gwi",
+       "gwj",
+       "gwn",
+       "gwr",
+       "gwt",
+       "gwu",
+       "gww",
+       "gwx",
+       "gxx",
+       "gya",
+       "gyb",
+       "gyd",
+       "gye",
+       "gyf",
+       "gyg",
+       "gyi",
+       "gyl",
+       "gym",
+       "gyn",
+       "gyr",
+       "gyy",
+       "gza",
+       "gzi",
+       "gzn",
+       "ha",
+       "haa",
+       "hab",
+       "hac",
+       "had",
+       "hae",
+       "haf",
+       "hag",
+       "hah",
+       "hai",
+       "haj",
+       "hak",
+       "hal",
+       "ham",
+       "han",
+       "hao",
+       "hap",
+       "haq",
+       "har",
+       "has",
+       "hav",
+       "haw",
+       "hax",
+       "hay",
+       "haz",
+       "hba",
+       "hbb",
+       "hbn",
+       "hbo",
+       "hbu",
+       "hca",
+       "hch",
+       "hdn",
+       "hds",
+       "hdy",
+       "he",
+       "hea",
+       "hed",
+       "heg",
+       "heh",
+       "hei",
+       "hem",
+       "hgm",
+       "hgw",
+       "hhi",
+       "hhr",
+       "hhy",
+       "hi",
+       "hia",
+       "hib",
+       "hid",
+       "hif",
+       "hig",
+       "hih",
+       "hii",
+       "hij",
+       "hik",
+       "hil",
+       "hio",
+       "hir",
+       "hit",
+       "hiw",
+       "hix",
+       "hji",
+       "hka",
+       "hke",
+       "hkk",
+       "hks",
+       "hla",
+       "hlb",
+       "hld",
+       "hle",
+       "hlt",
+       "hlu",
+       "hma",
+       "hmb",
+       "hmc",
+       "hmd",
+       "hme",
+       "hmf",
+       "hmg",
+       "hmh",
+       "hmi",
+       "hmj",
+       "hmk",
+       "hml",
+       "hmm",
+       "hmn",
+       "hmp",
+       "hmq",
+       "hmr",
+       "hms",
+       "hmt",
+       "hmu",
+       "hmv",
+       "hmw",
+       "hmy",
+       "hmz",
+       "hna",
+       "hnd",
+       "hne",
+       "hnh",
+       "hni",
+       "hnj",
+       "hnn",
+       "hno",
+       "hns",
+       "hnu",
+       "ho",
+       "hoa",
+       "hob",
+       "hoc",
+       "hod",
+       "hoe",
+       "hoh",
+       "hoi",
+       "hoj",
+       "hol",
+       "hom",
+       "hoo",
+       "hop",
+       "hor",
+       "hos",
+       "hot",
+       "hov",
+       "how",
+       "hoy",
+       "hoz",
+       "hpo",
+       "hps",
+       "hr",
+       "hra",
+       "hre",
+       "hrk",
+       "hrm",
+       "hro",
+       "hrr",
+       "hrt",
+       "hru",
+       "hrx",
+       "hrz",
+       "hsb",
+       "hsh",
+       "hsl",
+       "hsn",
+       "hss",
+       "ht",
+       "hti",
+       "hto",
+       "hts",
+       "htu",
+       "htx",
+       "hu",
+       "hub",
+       "huc",
+       "hud",
+       "hue",
+       "huf",
+       "hug",
+       "huh",
+       "hui",
+       "huj",
+       "huk",
+       "hul",
+       "hum",
+       "huo",
+       "hup",
+       "huq",
+       "hur",
+       "hus",
+       "hut",
+       "huu",
+       "huv",
+       "huw",
+       "hux",
+       "huy",
+       "huz",
+       "hvc",
+       "hve",
+       "hvk",
+       "hvn",
+       "hvv",
+       "hwa",
+       "hwc",
+       "hwo",
+       "hy",
+       "hya",
+       "hz",
+       "ia",
+       "iai",
+       "ian",
+       "iap",
+       "iar",
+       "iba",
+       "ibb",
+       "ibd",
+       "ibe",
+       "ibg",
+       "ibi",
+       "ibl",
+       "ibm",
+       "ibn",
+       "ibr",
+       "ibu",
+       "iby",
+       "ica",
+       "ich",
+       "icl",
+       "icr",
+       "id",
+       "ida",
+       "idb",
+       "idc",
+       "idd",
+       "ide",
+       "idi",
+       "idr",
+       "ids",
+       "idt",
+       "idu",
+       "ie",
+       "ifa",
+       "ifb",
+       "ife",
+       "iff",
+       "ifk",
+       "ifm",
+       "ifu",
+       "ify",
+       "ig",
+       "igb",
+       "ige",
+       "igg",
+       "igl",
+       "igm",
+       "ign",
+       "igo",
+       "igs",
+       "igw",
+       "ihb",
+       "ihi",
+       "ihp",
+       "ii",
+       "ijc",
+       "ije",
+       "ijj",
+       "ijn",
+       "ijs",
+       "ik",
+       "ike",
+       "iki",
+       "ikk",
+       "ikl",
+       "iko",
+       "ikp",
+       "ikt",
+       "ikv",
+       "ikw",
+       "ikx",
+       "ikz",
+       "ila",
+       "ilb",
+       "ilg",
+       "ili",
+       "ilk",
+       "ill",
+       "ilo",
+       "ils",
+       "ilu",
+       "ilv",
+       "ilw",
+       "ima",
+       "ime",
+       "imi",
+       "iml",
+       "imn",
+       "imo",
+       "imr",
+       "ims",
+       "imy",
+       "inb",
+       "ing",
+       "inh",
+       "inj",
+       "inl",
+       "inm",
+       "inn",
+       "ino",
+       "inp",
+       "ins",
+       "int",
+       "inz",
+       "io",
+       "ior",
+       "iou",
+       "iow",
+       "ipi",
+       "ipo",
+       "iqu",
+       "ire",
+       "irh",
+       "iri",
+       "irk",
+       "irn",
+       "irr",
+       "iru",
+       "irx",
+       "iry",
+       "is",
+       "isa",
+       "isc",
+       "isd",
+       "ise",
+       "isg",
+       "ish",
+       "isi",
+       "isk",
+       "ism",
+       "isn",
+       "iso",
+       "isr",
+       "ist",
+       "isu",
+       "it",
+       "itb",
+       "ite",
+       "iti",
+       "itk",
+       "itl",
+       "itm",
+       "ito",
+       "itr",
+       "its",
+       "itt",
+       "itv",
+       "itw",
+       "itx",
+       "ity",
+       "itz",
+       "iu",
+       "ium",
+       "ivb",
+       "ivv",
+       "iwk",
+       "iwm",
+       "iwo",
+       "iws",
+       "ixc",
+       "ixl",
+       "iya",
+       "iyo",
+       "iyx",
+       "izh",
+       "izi",
+       "izr",
+       "ja",
+       "jaa",
+       "jab",
+       "jac",
+       "jad",
+       "jae",
+       "jaf",
+       "jah",
+       "jaj",
+       "jak",
+       "jal",
+       "jam",
+       "jao",
+       "jaq",
+       "jar",
+       "jas",
+       "jat",
+       "jau",
+       "jax",
+       "jay",
+       "jaz",
+       "jbe",
+       "jbj",
+       "jbn",
+       "jbo",
+       "jbr",
+       "jbt",
+       "jbu",
+       "jcs",
+       "jct",
+       "jda",
+       "jdg",
+       "jdt",
+       "jeb",
+       "jee",
+       "jeg",
+       "jeh",
+       "jei",
+       "jek",
+       "jel",
+       "jen",
+       "jer",
+       "jet",
+       "jeu",
+       "jgb",
+       "jge",
+       "jgo",
+       "jhi",
+       "jhs",
+       "jia",
+       "jib",
+       "jic",
+       "jid",
+       "jie",
+       "jig",
+       "jih",
+       "jii",
+       "jil",
+       "jim",
+       "jio",
+       "jiq",
+       "jit",
+       "jiu",
+       "jiv",
+       "jiy",
+       "jko",
+       "jku",
+       "jle",
+       "jls",
+       "jma",
+       "jmb",
+       "jmc",
+       "jmd",
+       "jmi",
+       "jml",
+       "jmn",
+       "jmr",
+       "jms",
+       "jmx",
+       "jna",
+       "jnd",
+       "jng",
+       "jni",
+       "jnj",
+       "jnl",
+       "jns",
+       "job",
+       "jod",
+       "jor",
+       "jos",
+       "jow",
+       "jpa",
+       "jpr",
+       "jqr",
+       "jra",
+       "jrb",
+       "jrr",
+       "jrt",
+       "jru",
+       "jsl",
+       "jua",
+       "jub",
+       "juc",
+       "jud",
+       "juh",
+       "juk",
+       "jul",
+       "jum",
+       "jun",
+       "juo",
+       "jup",
+       "jur",
+       "jus",
+       "jut",
+       "juu",
+       "juw",
+       "juy",
+       "jv",
+       "jvd",
+       "jvn",
+       "jwi",
+       "jya",
+       "jye",
+       "jyy",
+       "ka",
+       "kaa",
+       "kab",
+       "kac",
+       "kad",
+       "kae",
+       "kaf",
+       "kag",
+       "kah",
+       "kai",
+       "kaj",
+       "kak",
+       "kam",
+       "kao",
+       "kap",
+       "kaq",
+       "kav",
+       "kaw",
+       "kax",
+       "kay",
+       "kba",
+       "kbb",
+       "kbc",
+       "kbd",
+       "kbe",
+       "kbf",
+       "kbg",
+       "kbh",
+       "kbi",
+       "kbj",
+       "kbk",
+       "kbl",
+       "kbm",
+       "kbn",
+       "kbo",
+       "kbp",
+       "kbq",
+       "kbr",
+       "kbs",
+       "kbt",
+       "kbu",
+       "kbv",
+       "kbw",
+       "kbx",
+       "kby",
+       "kbz",
+       "kca",
+       "kcb",
+       "kcc",
+       "kcd",
+       "kce",
+       "kcf",
+       "kcg",
+       "kch",
+       "kci",
+       "kcj",
+       "kck",
+       "kcl",
+       "kcm",
+       "kcn",
+       "kco",
+       "kcp",
+       "kcq",
+       "kcr",
+       "kcs",
+       "kct",
+       "kcu",
+       "kcv",
+       "kcw",
+       "kcx",
+       "kcy",
+       "kcz",
+       "kda",
+       "kdc",
+       "kdd",
+       "kde",
+       "kdf",
+       "kdg",
+       "kdh",
+       "kdi",
+       "kdj",
+       "kdk",
+       "kdl",
+       "kdm",
+       "kdn",
+       "kdp",
+       "kdq",
+       "kdr",
+       "kdt",
+       "kdu",
+       "kdv",
+       "kdw",
+       "kdx",
+       "kdy",
+       "kdz",
+       "kea",
+       "keb",
+       "kec",
+       "ked",
+       "kee",
+       "kef",
+       "keg",
+       "keh",
+       "kei",
+       "kej",
+       "kek",
+       "kel",
+       "kem",
+       "ken",
+       "keo",
+       "kep",
+       "keq",
+       "ker",
+       "kes",
+       "ket",
+       "keu",
+       "kev",
+       "kew",
+       "kex",
+       "key",
+       "kez",
+       "kfa",
+       "kfb",
+       "kfc",
+       "kfd",
+       "kfe",
+       "kff",
+       "kfg",
+       "kfh",
+       "kfi",
+       "kfj",
+       "kfk",
+       "kfl",
+       "kfm",
+       "kfn",
+       "kfo",
+       "kfp",
+       "kfq",
+       "kfr",
+       "kfs",
+       "kft",
+       "kfu",
+       "kfv",
+       "kfw",
+       "kfx",
+       "kfy",
+       "kfz",
+       "kg",
+       "kga",
+       "kgb",
+       "kgc",
+       "kgd",
+       "kge",
+       "kgf",
+       "kgg",
+       "kgh",
+       "kgi",
+       "kgj",
+       "kgk",
+       "kgl",
+       "kgm",
+       "kgn",
+       "kgo",
+       "kgp",
+       "kgq",
+       "kgr",
+       "kgs",
+       "kgt",
+       "kgu",
+       "kgv",
+       "kgw",
+       "kgx",
+       "kgy",
+       "kha",
+       "khb",
+       "khc",
+       "khd",
+       "khe",
+       "khf",
+       "khg",
+       "khh",
+       "khj",
+       "khk",
+       "khl",
+       "khn",
+       "kho",
+       "khp",
+       "khq",
+       "khr",
+       "khs",
+       "kht",
+       "khu",
+       "khv",
+       "khw",
+       "khx",
+       "khy",
+       "khz",
+       "ki",
+       "kia",
+       "kib",
+       "kic",
+       "kid",
+       "kie",
+       "kif",
+       "kig",
+       "kih",
+       "kii",
+       "kij",
+       "kil",
+       "kim",
+       "kio",
+       "kip",
+       "kiq",
+       "kis",
+       "kit",
+       "kiu",
+       "kiv",
+       "kiw",
+       "kix",
+       "kiy",
+       "kiz",
+       "kj",
+       "kja",
+       "kjb",
+       "kjc",
+       "kjd",
+       "kje",
+       "kjf",
+       "kjg",
+       "kjh",
+       "kji",
+       "kjj",
+       "kjk",
+       "kjl",
+       "kjm",
+       "kjn",
+       "kjo",
+       "kjp",
+       "kjq",
+       "kjr",
+       "kjs",
+       "kjt",
+       "kju",
+       "kjx",
+       "kjy",
+       "kjz",
+       "kk",
+       "kka",
+       "kkb",
+       "kkc",
+       "kkd",
+       "kke",
+       "kkf",
+       "kkg",
+       "kkh",
+       "kki",
+       "kkj",
+       "kkk",
+       "kkl",
+       "kkm",
+       "kkn",
+       "kko",
+       "kkp",
+       "kkq",
+       "kkr",
+       "kks",
+       "kkt",
+       "kku",
+       "kkv",
+       "kkw",
+       "kkx",
+       "kky",
+       "kkz",
+       "kl",
+       "kla",
+       "klb",
+       "klc",
+       "kld",
+       "kle",
+       "klf",
+       "klg",
+       "klh",
+       "kli",
+       "klj",
+       "klk",
+       "kll",
+       "klm",
+       "kln",
+       "klo",
+       "klp",
+       "klq",
+       "klr",
+       "kls",
+       "klt",
+       "klu",
+       "klv",
+       "klw",
+       "klx",
+       "kly",
+       "klz",
+       "km",
+       "kma",
+       "kmb",
+       "kmc",
+       "kmd",
+       "kme",
+       "kmf",
+       "kmg",
+       "kmh",
+       "kmi",
+       "kmj",
+       "kmk",
+       "kml",
+       "kmm",
+       "kmn",
+       "kmo",
+       "kmp",
+       "kmq",
+       "kmr",
+       "kms",
+       "kmt",
+       "kmu",
+       "kmv",
+       "kmw",
+       "kmx",
+       "kmy",
+       "kmz",
+       "kn",
+       "kna",
+       "knb",
+       "knc",
+       "knd",
+       "kne",
+       "knf",
+       "kng",
+       "kni",
+       "knj",
+       "knk",
+       "knl",
+       "knm",
+       "knn",
+       "kno",
+       "knp",
+       "knq",
+       "knr",
+       "kns",
+       "knt",
+       "knu",
+       "knv",
+       "knw",
+       "knx",
+       "kny",
+       "knz",
+       "ko",
+       "koa",
+       "koc",
+       "kod",
+       "koe",
+       "kof",
+       "kog",
+       "koh",
+       "koi",
+       "koj",
+       "kok",
+       "kol",
+       "koo",
+       "kop",
+       "koq",
+       "kos",
+       "kot",
+       "kou",
+       "kov",
+       "kow",
+       "kox",
+       "koy",
+       "koz",
+       "kpa",
+       "kpb",
+       "kpc",
+       "kpd",
+       "kpe",
+       "kpf",
+       "kpg",
+       "kph",
+       "kpi",
+       "kpj",
+       "kpk",
+       "kpl",
+       "kpm",
+       "kpn",
+       "kpo",
+       "kpp",
+       "kpq",
+       "kpr",
+       "kps",
+       "kpt",
+       "kpu",
+       "kpv",
+       "kpw",
+       "kpx",
+       "kpy",
+       "kpz",
+       "kqa",
+       "kqb",
+       "kqc",
+       "kqd",
+       "kqe",
+       "kqf",
+       "kqg",
+       "kqh",
+       "kqi",
+       "kqj",
+       "kqk",
+       "kql",
+       "kqm",
+       "kqn",
+       "kqo",
+       "kqp",
+       "kqq",
+       "kqr",
+       "kqs",
+       "kqt",
+       "kqu",
+       "kqv",
+       "kqw",
+       "kqx",
+       "kqy",
+       "kqz",
+       "kr",
+       "kra",
+       "krb",
+       "krc",
+       "krd",
+       "kre",
+       "krf",
+       "krh",
+       "kri",
+       "krj",
+       "krk",
+       "krl",
+       "krm",
+       "krn",
+       "krp",
+       "krr",
+       "krs",
+       "krt",
+       "kru",
+       "krv",
+       "krw",
+       "krx",
+       "kry",
+       "krz",
+       "ks",
+       "ksa",
+       "ksb",
+       "ksc",
+       "ksd",
+       "kse",
+       "ksf",
+       "ksg",
+       "ksh",
+       "ksi",
+       "ksj",
+       "ksk",
+       "ksl",
+       "ksm",
+       "ksn",
+       "kso",
+       "ksp",
+       "ksq",
+       "ksr",
+       "kss",
+       "kst",
+       "ksu",
+       "ksv",
+       "ksw",
+       "ksx",
+       "ksy",
+       "ksz",
+       "kta",
+       "ktb",
+       "ktc",
+       "ktd",
+       "kte",
+       "ktf",
+       "ktg",
+       "kth",
+       "kti",
+       "ktj",
+       "ktk",
+       "ktl",
+       "ktm",
+       "ktn",
+       "kto",
+       "ktp",
+       "ktq",
+       "ktr",
+       "kts",
+       "ktt",
+       "ktu",
+       "ktv",
+       "ktw",
+       "ktx",
+       "kty",
+       "ktz",
+       "ku",
+       "kub",
+       "kuc",
+       "kud",
+       "kue",
+       "kuf",
+       "kug",
+       "kuh",
+       "kui",
+       "kuj",
+       "kuk",
+       "kul",
+       "kum",
+       "kun",
+       "kuo",
+       "kup",
+       "kuq",
+       "kus",
+       "kut",
+       "kuu",
+       "kuv",
+       "kuw",
+       "kux",
+       "kuy",
+       "kuz",
+       "kv",
+       "kva",
+       "kvb",
+       "kvc",
+       "kvd",
+       "kve",
+       "kvf",
+       "kvg",
+       "kvh",
+       "kvi",
+       "kvj",
+       "kvk",
+       "kvl",
+       "kvm",
+       "kvn",
+       "kvo",
+       "kvp",
+       "kvq",
+       "kvr",
+       "kvs",
+       "kvt",
+       "kvu",
+       "kvv",
+       "kvw",
+       "kvx",
+       "kvy",
+       "kvz",
+       "kw",
+       "kwa",
+       "kwb",
+       "kwc",
+       "kwd",
+       "kwe",
+       "kwf",
+       "kwg",
+       "kwh",
+       "kwi",
+       "kwj",
+       "kwk",
+       "kwl",
+       "kwm",
+       "kwn",
+       "kwo",
+       "kwp",
+       "kwq",
+       "kwr",
+       "kws",
+       "kwt",
+       "kwu",
+       "kwv",
+       "kww",
+       "kwx",
+       "kwy",
+       "kwz",
+       "kxa",
+       "kxb",
+       "kxc",
+       "kxd",
+       "kxe",
+       "kxf",
+       "kxh",
+       "kxi",
+       "kxj",
+       "kxk",
+       "kxl",
+       "kxm",
+       "kxn",
+       "kxo",
+       "kxp",
+       "kxq",
+       "kxr",
+       "kxs",
+       "kxt",
+       "kxu",
+       "kxv",
+       "kxw",
+       "kxx",
+       "kxy",
+       "kxz",
+       "ky",
+       "kya",
+       "kyb",
+       "kyc",
+       "kyd",
+       "kye",
+       "kyf",
+       "kyg",
+       "kyh",
+       "kyi",
+       "kyj",
+       "kyk",
+       "kyl",
+       "kym",
+       "kyn",
+       "kyo",
+       "kyp",
+       "kyq",
+       "kyr",
+       "kys",
+       "kyt",
+       "kyu",
+       "kyv",
+       "kyw",
+       "kyx",
+       "kyy",
+       "kyz",
+       "kza",
+       "kzb",
+       "kzc",
+       "kzd",
+       "kze",
+       "kzf",
+       "kzg",
+       "kzh",
+       "kzi",
+       "kzj",
+       "kzk",
+       "kzl",
+       "kzm",
+       "kzn",
+       "kzo",
+       "kzp",
+       "kzq",
+       "kzr",
+       "kzs",
+       "kzt",
+       "kzu",
+       "kzv",
+       "kzw",
+       "kzx",
+       "kzy",
+       "kzz",
+       "la",
+       "laa",
+       "lab",
+       "lac",
+       "lad",
+       "lae",
+       "laf",
+       "lag",
+       "lah",
+       "lai",
+       "laj",
+       "lak",
+       "lal",
+       "lam",
+       "lan",
+       "lap",
+       "laq",
+       "lar",
+       "las",
+       "lau",
+       "law",
+       "lax",
+       "lay",
+       "laz",
+       "lb",
+       "lba",
+       "lbb",
+       "lbc",
+       "lbe",
+       "lbf",
+       "lbg",
+       "lbi",
+       "lbj",
+       "lbk",
+       "lbl",
+       "lbm",
+       "lbn",
+       "lbo",
+       "lbq",
+       "lbr",
+       "lbs",
+       "lbt",
+       "lbu",
+       "lbv",
+       "lbw",
+       "lbx",
+       "lby",
+       "lbz",
+       "lcc",
+       "lcd",
+       "lce",
+       "lcf",
+       "lch",
+       "lcl",
+       "lcm",
+       "lcp",
+       "lcq",
+       "lcs",
+       "ldb",
+       "ldd",
+       "ldg",
+       "ldh",
+       "ldi",
+       "ldj",
+       "ldk",
+       "ldl",
+       "ldm",
+       "ldn",
+       "ldo",
+       "ldp",
+       "ldq",
+       "lea",
+       "leb",
+       "lec",
+       "led",
+       "lee",
+       "lef",
+       "leg",
+       "leh",
+       "lei",
+       "lej",
+       "lek",
+       "lel",
+       "lem",
+       "len",
+       "leo",
+       "lep",
+       "leq",
+       "ler",
+       "les",
+       "let",
+       "leu",
+       "lev",
+       "lew",
+       "lex",
+       "ley",
+       "lez",
+       "lfa",
+       "lfn",
+       "lg",
+       "lga",
+       "lgb",
+       "lgg",
+       "lgh",
+       "lgi",
+       "lgk",
+       "lgl",
+       "lgm",
+       "lgn",
+       "lgq",
+       "lgr",
+       "lgt",
+       "lgu",
+       "lgz",
+       "lha",
+       "lhh",
+       "lhi",
+       "lhl",
+       "lhm",
+       "lhn",
+       "lhp",
+       "lhs",
+       "lht",
+       "lhu",
+       "li",
+       "lia",
+       "lib",
+       "lic",
+       "lid",
+       "lie",
+       "lif",
+       "lig",
+       "lih",
+       "lii",
+       "lij",
+       "lik",
+       "lil",
+       "lio",
+       "lip",
+       "liq",
+       "lir",
+       "lis",
+       "liu",
+       "liv",
+       "liw",
+       "lix",
+       "liy",
+       "liz",
+       "lje",
+       "lji",
+       "ljl",
+       "ljp",
+       "lka",
+       "lkb",
+       "lkc",
+       "lkd",
+       "lke",
+       "lkh",
+       "lki",
+       "lkj",
+       "lkl",
+       "lkn",
+       "lko",
+       "lkr",
+       "lks",
+       "lkt",
+       "lky",
+       "lla",
+       "llb",
+       "llc",
+       "lld",
+       "lle",
+       "llf",
+       "llg",
+       "llh",
+       "lli",
+       "llk",
+       "lll",
+       "llm",
+       "lln",
+       "llo",
+       "llp",
+       "llq",
+       "lls",
+       "llu",
+       "llx",
+       "lma",
+       "lmb",
+       "lmc",
+       "lmd",
+       "lme",
+       "lmf",
+       "lmg",
+       "lmh",
+       "lmi",
+       "lmj",
+       "lmk",
+       "lml",
+       "lmm",
+       "lmn",
+       "lmo",
+       "lmp",
+       "lmq",
+       "lmr",
+       "lmu",
+       "lmv",
+       "lmw",
+       "lmx",
+       "lmy",
+       "lmz",
+       "ln",
+       "lna",
+       "lnb",
+       "lnd",
+       "lng",
+       "lnh",
+       "lni",
+       "lnj",
+       "lnl",
+       "lnm",
+       "lnn",
+       "lno",
+       "lns",
+       "lnu",
+       "lnz",
+       "lo",
+       "loa",
+       "lob",
+       "loc",
+       "loe",
+       "lof",
+       "log",
+       "loh",
+       "loi",
+       "loj",
+       "lok",
+       "lol",
+       "lom",
+       "lon",
+       "loo",
+       "lop",
+       "loq",
+       "lor",
+       "los",
+       "lot",
+       "lou",
+       "lov",
+       "low",
+       "lox",
+       "loy",
+       "loz",
+       "lpa",
+       "lpe",
+       "lpn",
+       "lpo",
+       "lpx",
+       "lra",
+       "lrc",
+       "lre",
+       "lrg",
+       "lri",
+       "lrk",
+       "lrl",
+       "lrm",
+       "lrn",
+       "lro",
+       "lrr",
+       "lrt",
+       "lrv",
+       "lrz",
+       "lsa",
+       "lsd",
+       "lse",
+       "lsg",
+       "lsh",
+       "lsi",
+       "lsl",
+       "lsm",
+       "lso",
+       "lsp",
+       "lsr",
+       "lss",
+       "lst",
+       "lsy",
+       "lt",
+       "ltc",
+       "ltg",
+       "lti",
+       "ltn",
+       "lto",
+       "lts",
+       "ltu",
+       "lu",
+       "lua",
+       "luc",
+       "lud",
+       "lue",
+       "luf",
+       "lui",
+       "luj",
+       "luk",
+       "lul",
+       "lum",
+       "lun",
+       "luo",
+       "lup",
+       "luq",
+       "lur",
+       "lus",
+       "lut",
+       "luu",
+       "luv",
+       "luw",
+       "luy",
+       "luz",
+       "lv",
+       "lva",
+       "lvk",
+       "lvs",
+       "lvu",
+       "lwa",
+       "lwe",
+       "lwg",
+       "lwh",
+       "lwl",
+       "lwm",
+       "lwo",
+       "lwt",
+       "lww",
+       "lya",
+       "lyg",
+       "lyn",
+       "lzh",
+       "lzl",
+       "lzn",
+       "lzz",
+       "maa",
+       "mab",
+       "mad",
+       "mae",
+       "maf",
+       "mag",
+       "mai",
+       "maj",
+       "mak",
+       "mam",
+       "man",
+       "maq",
+       "mas",
+       "mat",
+       "mau",
+       "mav",
+       "maw",
+       "max",
+       "maz",
+       "mba",
+       "mbb",
+       "mbc",
+       "mbd",
+       "mbe",
+       "mbf",
+       "mbh",
+       "mbi",
+       "mbj",
+       "mbk",
+       "mbl",
+       "mbm",
+       "mbn",
+       "mbo",
+       "mbp",
+       "mbq",
+       "mbr",
+       "mbs",
+       "mbt",
+       "mbu",
+       "mbv",
+       "mbw",
+       "mbx",
+       "mby",
+       "mbz",
+       "mca",
+       "mcb",
+       "mcc",
+       "mcd",
+       "mce",
+       "mcf",
+       "mcg",
+       "mch",
+       "mci",
+       "mcj",
+       "mck",
+       "mcl",
+       "mcm",
+       "mcn",
+       "mco",
+       "mcp",
+       "mcq",
+       "mcr",
+       "mcs",
+       "mct",
+       "mcu",
+       "mcv",
+       "mcw",
+       "mcx",
+       "mcy",
+       "mcz",
+       "mda",
+       "mdb",
+       "mdc",
+       "mdd",
+       "mde",
+       "mdf",
+       "mdg",
+       "mdh",
+       "mdi",
+       "mdj",
+       "mdk",
+       "mdl",
+       "mdm",
+       "mdn",
+       "mdp",
+       "mdq",
+       "mdr",
+       "mds",
+       "mdt",
+       "mdu",
+       "mdv",
+       "mdw",
+       "mdx",
+       "mdy",
+       "mdz",
+       "mea",
+       "meb",
+       "mec",
+       "med",
+       "mee",
+       "mef",
+       "meg",
+       "meh",
+       "mei",
+       "mej",
+       "mek",
+       "mel",
+       "mem",
+       "men",
+       "meo",
+       "mep",
+       "meq",
+       "mer",
+       "mes",
+       "met",
+       "meu",
+       "mev",
+       "mew",
+       "mey",
+       "mez",
+       "mfa",
+       "mfb",
+       "mfc",
+       "mfd",
+       "mfe",
+       "mff",
+       "mfg",
+       "mfh",
+       "mfi",
+       "mfj",
+       "mfk",
+       "mfl",
+       "mfm",
+       "mfn",
+       "mfo",
+       "mfp",
+       "mfq",
+       "mfr",
+       "mfs",
+       "mft",
+       "mfu",
+       "mfv",
+       "mfw",
+       "mfx",
+       "mfy",
+       "mfz",
+       "mg",
+       "mga",
+       "mgb",
+       "mgc",
+       "mgd",
+       "mge",
+       "mgf",
+       "mgg",
+       "mgh",
+       "mgi",
+       "mgj",
+       "mgk",
+       "mgl",
+       "mgm",
+       "mgn",
+       "mgo",
+       "mgp",
+       "mgq",
+       "mgr",
+       "mgs",
+       "mgt",
+       "mgu",
+       "mgv",
+       "mgw",
+       "mgx",
+       "mgy",
+       "mgz",
+       "mh",
+       "mha",
+       "mhb",
+       "mhc",
+       "mhd",
+       "mhe",
+       "mhf",
+       "mhg",
+       "mhh",
+       "mhi",
+       "mhj",
+       "mhk",
+       "mhl",
+       "mhm",
+       "mhn",
+       "mho",
+       "mhp",
+       "mhq",
+       "mhr",
+       "mhs",
+       "mht",
+       "mhu",
+       "mhw",
+       "mhx",
+       "mhy",
+       "mhz",
+       "mi",
+       "mia",
+       "mib",
+       "mic",
+       "mid",
+       "mie",
+       "mif",
+       "mig",
+       "mih",
+       "mii",
+       "mij",
+       "mik",
+       "mil",
+       "mim",
+       "min",
+       "mio",
+       "mip",
+       "miq",
+       "mir",
+       "mis",
+       "mit",
+       "miu",
+       "miw",
+       "mix",
+       "miy",
+       "miz",
+       "mjc",
+       "mjd",
+       "mje",
+       "mjg",
+       "mjh",
+       "mji",
+       "mjj",
+       "mjk",
+       "mjl",
+       "mjm",
+       "mjn",
+       "mjo",
+       "mjp",
+       "mjq",
+       "mjr",
+       "mjs",
+       "mjt",
+       "mju",
+       "mjv",
+       "mjw",
+       "mjx",
+       "mjy",
+       "mjz",
+       "mk",
+       "mka",
+       "mkb",
+       "mkc",
+       "mke",
+       "mkf",
+       "mkg",
+       "mki",
+       "mkj",
+       "mkk",
+       "mkl",
+       "mkm",
+       "mkn",
+       "mko",
+       "mkp",
+       "mkq",
+       "mkr",
+       "mks",
+       "mkt",
+       "mku",
+       "mkv",
+       "mkw",
+       "mkx",
+       "mky",
+       "mkz",
+       "ml",
+       "mla",
+       "mlb",
+       "mlc",
+       "mld",
+       "mle",
+       "mlf",
+       "mlh",
+       "mli",
+       "mlj",
+       "mlk",
+       "mll",
+       "mlm",
+       "mln",
+       "mlo",
+       "mlp",
+       "mlq",
+       "mlr",
+       "mls",
+       "mlu",
+       "mlv",
+       "mlw",
+       "mlx",
+       "mlz",
+       "mma",
+       "mmb",
+       "mmc",
+       "mmd",
+       "mme",
+       "mmf",
+       "mmg",
+       "mmh",
+       "mmi",
+       "mmj",
+       "mmk",
+       "mml",
+       "mmm",
+       "mmn",
+       "mmo",
+       "mmp",
+       "mmq",
+       "mmr",
+       "mmt",
+       "mmu",
+       "mmv",
+       "mmw",
+       "mmx",
+       "mmy",
+       "mmz",
+       "mn",
+       "mna",
+       "mnb",
+       "mnc",
+       "mnd",
+       "mne",
+       "mnf",
+       "mng",
+       "mnh",
+       "mni",
+       "mnj",
+       "mnk",
+       "mnl",
+       "mnm",
+       "mnn",
+       "mnp",
+       "mnq",
+       "mnr",
+       "mns",
+       "mnt",
+       "mnu",
+       "mnv",
+       "mnw",
+       "mnx",
+       "mny",
+       "mnz",
+       "mo",
+       "moa",
+       "moc",
+       "mod",
+       "moe",
+       "mog",
+       "moh",
+       "moi",
+       "moj",
+       "mok",
+       "mom",
+       "moo",
+       "mop",
+       "moq",
+       "mor",
+       "mos",
+       "mot",
+       "mou",
+       "mov",
+       "mow",
+       "mox",
+       "moy",
+       "moz",
+       "mpa",
+       "mpb",
+       "mpc",
+       "mpd",
+       "mpe",
+       "mpg",
+       "mph",
+       "mpi",
+       "mpj",
+       "mpk",
+       "mpl",
+       "mpm",
+       "mpn",
+       "mpo",
+       "mpp",
+       "mpq",
+       "mpr",
+       "mps",
+       "mpt",
+       "mpu",
+       "mpv",
+       "mpw",
+       "mpx",
+       "mpy",
+       "mpz",
+       "mqa",
+       "mqb",
+       "mqc",
+       "mqe",
+       "mqf",
+       "mqg",
+       "mqh",
+       "mqi",
+       "mqj",
+       "mqk",
+       "mql",
+       "mqm",
+       "mqn",
+       "mqo",
+       "mqp",
+       "mqq",
+       "mqr",
+       "mqs",
+       "mqt",
+       "mqu",
+       "mqv",
+       "mqw",
+       "mqx",
+       "mqy",
+       "mqz",
+       "mr",
+       "mra",
+       "mrb",
+       "mrc",
+       "mrd",
+       "mre",
+       "mrf",
+       "mrg",
+       "mrh",
+       "mrj",
+       "mrk",
+       "mrl",
+       "mrm",
+       "mrn",
+       "mro",
+       "mrp",
+       "mrq",
+       "mrr",
+       "mrs",
+       "mrt",
+       "mru",
+       "mrv",
+       "mrw",
+       "mrx",
+       "mry",
+       "mrz",
+       "ms",
+       "msb",
+       "msc",
+       "msd",
+       "mse",
+       "msf",
+       "msg",
+       "msh",
+       "msi",
+       "msj",
+       "msk",
+       "msl",
+       "msm",
+       "msn",
+       "mso",
+       "msp",
+       "msq",
+       "msr",
+       "mss",
+       "msu",
+       "msv",
+       "msw",
+       "msx",
+       "msy",
+       "msz",
+       "mt",
+       "mta",
+       "mtb",
+       "mtc",
+       "mtd",
+       "mte",
+       "mtf",
+       "mtg",
+       "mth",
+       "mti",
+       "mtj",
+       "mtk",
+       "mtl",
+       "mtm",
+       "mtn",
+       "mto",
+       "mtp",
+       "mtq",
+       "mtr",
+       "mts",
+       "mtt",
+       "mtu",
+       "mtv",
+       "mtw",
+       "mtx",
+       "mty",
+       "mua",
+       "mub",
+       "muc",
+       "mud",
+       "mue",
+       "mug",
+       "muh",
+       "mui",
+       "muj",
+       "muk",
+       "mul",
+       "mum",
+       "muo",
+       "mup",
+       "muq",
+       "mur",
+       "mus",
+       "mut",
+       "muu",
+       "muv",
+       "mux",
+       "muy",
+       "muz",
+       "mva",
+       "mvb",
+       "mvd",
+       "mve",
+       "mvf",
+       "mvg",
+       "mvh",
+       "mvi",
+       "mvk",
+       "mvl",
+       "mvm",
+       "mvn",
+       "mvo",
+       "mvp",
+       "mvq",
+       "mvr",
+       "mvs",
+       "mvt",
+       "mvu",
+       "mvv",
+       "mvw",
+       "mvx",
+       "mvy",
+       "mvz",
+       "mwa",
+       "mwb",
+       "mwc",
+       "mwd",
+       "mwe",
+       "mwf",
+       "mwg",
+       "mwh",
+       "mwi",
+       "mwj",
+       "mwk",
+       "mwl",
+       "mwm",
+       "mwn",
+       "mwo",
+       "mwp",
+       "mwq",
+       "mwr",
+       "mws",
+       "mwt",
+       "mwu",
+       "mwv",
+       "mww",
+       "mwx",
+       "mwy",
+       "mwz",
+       "mxa",
+       "mxb",
+       "mxc",
+       "mxd",
+       "mxe",
+       "mxf",
+       "mxg",
+       "mxh",
+       "mxi",
+       "mxj",
+       "mxk",
+       "mxl",
+       "mxm",
+       "mxn",
+       "mxo",
+       "mxp",
+       "mxq",
+       "mxr",
+       "mxs",
+       "mxt",
+       "mxu",
+       "mxv",
+       "mxw",
+       "mxx",
+       "mxy",
+       "mxz",
+       "my",
+       "myb",
+       "myc",
+       "myd",
+       "mye",
+       "myf",
+       "myg",
+       "myh",
+       "myi",
+       "myj",
+       "myk",
+       "myl",
+       "mym",
+       "myo",
+       "myp",
+       "myq",
+       "myr",
+       "mys",
+       "myu",
+       "myv",
+       "myw",
+       "myx",
+       "myy",
+       "myz",
+       "mza",
+       "mzb",
+       "mzc",
+       "mzd",
+       "mze",
+       "mzg",
+       "mzh",
+       "mzi",
+       "mzj",
+       "mzk",
+       "mzl",
+       "mzm",
+       "mzn",
+       "mzo",
+       "mzp",
+       "mzq",
+       "mzr",
+       "mzs",
+       "mzt",
+       "mzu",
+       "mzv",
+       "mzw",
+       "mzx",
+       "mzy",
+       "mzz",
+       "na",
+       "naa",
+       "nab",
+       "nac",
+       "nad",
+       "nae",
+       "naf",
+       "nag",
+       "naj",
+       "nak",
+       "nal",
+       "nam",
+       "nan",
+       "nao",
+       "nap",
+       "naq",
+       "nar",
+       "nas",
+       "nat",
+       "naw",
+       "nax",
+       "nay",
+       "naz",
+       "nb",
+       "nba",
+       "nbb",
+       "nbc",
+       "nbd",
+       "nbe",
+       "nbg",
+       "nbh",
+       "nbi",
+       "nbj",
+       "nbk",
+       "nbm",
+       "nbn",
+       "nbo",
+       "nbp",
+       "nbq",
+       "nbr",
+       "nbs",
+       "nbt",
+       "nbu",
+       "nbv",
+       "nbw",
+       "nbx",
+       "nby",
+       "nca",
+       "ncb",
+       "ncc",
+       "ncd",
+       "nce",
+       "ncf",
+       "ncg",
+       "nch",
+       "nci",
+       "ncj",
+       "nck",
+       "ncl",
+       "ncm",
+       "ncn",
+       "nco",
+       "ncp",
+       "ncr",
+       "ncs",
+       "nct",
+       "ncu",
+       "ncx",
+       "ncz",
+       "nd",
+       "nda",
+       "ndb",
+       "ndc",
+       "ndd",
+       "ndf",
+       "ndg",
+       "ndh",
+       "ndi",
+       "ndj",
+       "ndk",
+       "ndl",
+       "ndm",
+       "ndn",
+       "ndp",
+       "ndq",
+       "ndr",
+       "nds",
+       "ndt",
+       "ndu",
+       "ndv",
+       "ndw",
+       "ndx",
+       "ndy",
+       "ndz",
+       "ne",
+       "nea",
+       "neb",
+       "nec",
+       "ned",
+       "nee",
+       "nef",
+       "neg",
+       "neh",
+       "nei",
+       "nej",
+       "nek",
+       "nem",
+       "nen",
+       "neo",
+       "neq",
+       "ner",
+       "nes",
+       "net",
+       "nev",
+       "new",
+       "nex",
+       "ney",
+       "nez",
+       "nfa",
+       "nfd",
+       "nfl",
+       "nfr",
+       "nfu",
+       "ng",
+       "nga",
+       "ngb",
+       "ngc",
+       "ngd",
+       "nge",
+       "ngg",
+       "ngh",
+       "ngi",
+       "ngj",
+       "ngk",
+       "ngl",
+       "ngm",
+       "ngn",
+       "ngo",
+       "ngp",
+       "ngq",
+       "ngr",
+       "ngs",
+       "ngt",
+       "ngu",
+       "ngv",
+       "ngw",
+       "ngx",
+       "ngy",
+       "ngz",
+       "nha",
+       "nhb",
+       "nhc",
+       "nhd",
+       "nhe",
+       "nhf",
+       "nhg",
+       "nhh",
+       "nhi",
+       "nhk",
+       "nhm",
+       "nhn",
+       "nho",
+       "nhp",
+       "nhq",
+       "nhr",
+       "nht",
+       "nhu",
+       "nhv",
+       "nhw",
+       "nhx",
+       "nhy",
+       "nhz",
+       "nia",
+       "nib",
+       "nid",
+       "nie",
+       "nif",
+       "nig",
+       "nih",
+       "nii",
+       "nij",
+       "nik",
+       "nil",
+       "nim",
+       "nin",
+       "nio",
+       "niq",
+       "nir",
+       "nis",
+       "nit",
+       "niu",
+       "niv",
+       "niw",
+       "nix",
+       "niy",
+       "niz",
+       "nja",
+       "njb",
+       "njd",
+       "njh",
+       "nji",
+       "njj",
+       "njl",
+       "njm",
+       "njn",
+       "njo",
+       "njr",
+       "njs",
+       "njt",
+       "nju",
+       "njx",
+       "njy",
+       "nka",
+       "nkb",
+       "nkc",
+       "nkd",
+       "nke",
+       "nkf",
+       "nkg",
+       "nkh",
+       "nki",
+       "nkj",
+       "nkk",
+       "nkm",
+       "nkn",
+       "nko",
+       "nkp",
+       "nkq",
+       "nkr",
+       "nks",
+       "nkt",
+       "nku",
+       "nkv",
+       "nkw",
+       "nkx",
+       "nkz",
+       "nl",
+       "nla",
+       "nlc",
+       "nle",
+       "nlg",
+       "nli",
+       "nlj",
+       "nlk",
+       "nll",
+       "nln",
+       "nlo",
+       "nlr",
+       "nlu",
+       "nlv",
+       "nlx",
+       "nly",
+       "nlz",
+       "nma",
+       "nmb",
+       "nmc",
+       "nmd",
+       "nme",
+       "nmf",
+       "nmg",
+       "nmh",
+       "nmi",
+       "nmj",
+       "nmk",
+       "nml",
+       "nmm",
+       "nmn",
+       "nmo",
+       "nmp",
+       "nmq",
+       "nmr",
+       "nms",
+       "nmt",
+       "nmu",
+       "nmv",
+       "nmw",
+       "nmx",
+       "nmy",
+       "nmz",
+       "nn",
+       "nna",
+       "nnb",
+       "nnc",
+       "nnd",
+       "nne",
+       "nnf",
+       "nng",
+       "nnh",
+       "nni",
+       "nnj",
+       "nnk",
+       "nnl",
+       "nnm",
+       "nnn",
+       "nnp",
+       "nnq",
+       "nnr",
+       "nns",
+       "nnt",
+       "nnu",
+       "nnv",
+       "nnw",
+       "nnx",
+       "nny",
+       "nnz",
+       "no",
+       "noa",
+       "noc",
+       "nod",
+       "noe",
+       "nof",
+       "nog",
+       "noh",
+       "noi",
+       "noj",
+       "nok",
+       "nom",
+       "non",
+       "nop",
+       "noq",
+       "nos",
+       "not",
+       "nou",
+       "nov",
+       "now",
+       "noy",
+       "noz",
+       "npa",
+       "npb",
+       "nph",
+       "npl",
+       "npn",
+       "npo",
+       "nps",
+       "npu",
+       "npy",
+       "nqg",
+       "nqk",
+       "nqm",
+       "nqn",
+       "nqo",
+       "nr",
+       "nra",
+       "nrb",
+       "nrc",
+       "nre",
+       "nrg",
+       "nri",
+       "nrl",
+       "nrm",
+       "nrn",
+       "nrp",
+       "nrr",
+       "nrt",
+       "nru",
+       "nrx",
+       "nrz",
+       "nsa",
+       "nsc",
+       "nsd",
+       "nse",
+       "nsg",
+       "nsh",
+       "nsi",
+       "nsk",
+       "nsl",
+       "nsm",
+       "nsn",
+       "nso",
+       "nsp",
+       "nsq",
+       "nsr",
+       "nss",
+       "nst",
+       "nsu",
+       "nsv",
+       "nsw",
+       "nsx",
+       "nsy",
+       "nsz",
+       "nte",
+       "nti",
+       "ntj",
+       "ntk",
+       "ntm",
+       "nto",
+       "ntp",
+       "ntr",
+       "nts",
+       "ntu",
+       "ntw",
+       "nty",
+       "ntz",
+       "nua",
+       "nuc",
+       "nud",
+       "nue",
+       "nuf",
+       "nug",
+       "nuh",
+       "nui",
+       "nuj",
+       "nuk",
+       "nul",
+       "num",
+       "nun",
+       "nuo",
+       "nup",
+       "nuq",
+       "nur",
+       "nus",
+       "nut",
+       "nuu",
+       "nuv",
+       "nuw",
+       "nux",
+       "nuy",
+       "nuz",
+       "nv",
+       "nvh",
+       "nvm",
+       "nwa",
+       "nwb",
+       "nwc",
+       "nwe",
+       "nwi",
+       "nwm",
+       "nwr",
+       "nwx",
+       "nwy",
+       "nxa",
+       "nxd",
+       "nxe",
+       "nxg",
+       "nxi",
+       "nxl",
+       "nxm",
+       "nxn",
+       "nxq",
+       "nxr",
+       "nxu",
+       "nxx",
+       "ny",
+       "nyb",
+       "nyc",
+       "nyd",
+       "nye",
+       "nyf",
+       "nyg",
+       "nyh",
+       "nyi",
+       "nyj",
+       "nyk",
+       "nyl",
+       "nym",
+       "nyn",
+       "nyo",
+       "nyp",
+       "nyq",
+       "nyr",
+       "nys",
+       "nyt",
+       "nyu",
+       "nyv",
+       "nyw",
+       "nyx",
+       "nyy",
+       "nza",
+       "nzb",
+       "nzi",
+       "nzk",
+       "nzm",
+       "nzs",
+       "nzu",
+       "nzy",
+       "nzz",
+       "oaa",
+       "oac",
+       "oar",
+       "oav",
+       "obi",
+       "obk",
+       "obl",
+       "obm",
+       "obo",
+       "obr",
+       "obt",
+       "obu",
+       "oc",
+       "oca",
+       "och",
+       "oco",
+       "ocu",
+       "oda",
+       "odk",
+       "odt",
+       "odu",
+       "ofo",
+       "ofs",
+       "ofu",
+       "ogb",
+       "ogc",
+       "oge",
+       "ogg",
+       "ogo",
+       "ogu",
+       "oht",
+       "ohu",
+       "oia",
+       "oin",
+       "oj",
+       "ojb",
+       "ojc",
+       "ojg",
+       "ojp",
+       "ojs",
+       "ojv",
+       "ojw",
+       "oka",
+       "okb",
+       "okd",
+       "oke",
+       "okh",
+       "oki",
+       "okj",
+       "okk",
+       "okl",
+       "okm",
+       "okn",
+       "oko",
+       "okr",
+       "oks",
+       "oku",
+       "okv",
+       "okx",
+       "ola",
+       "old",
+       "ole",
+       "olm",
+       "olo",
+       "olr",
+       "om",
+       "oma",
+       "omb",
+       "omc",
+       "ome",
+       "omg",
+       "omi",
+       "omk",
+       "oml",
+       "omn",
+       "omo",
+       "omp",
+       "omr",
+       "omt",
+       "omu",
+       "omw",
+       "omx",
+       "ona",
+       "onb",
+       "one",
+       "ong",
+       "oni",
+       "onj",
+       "onk",
+       "onn",
+       "ono",
+       "onp",
+       "onr",
+       "ons",
+       "ont",
+       "onu",
+       "onw",
+       "onx",
+       "ood",
+       "oog",
+       "oon",
+       "oor",
+       "oos",
+       "opa",
+       "opk",
+       "opm",
+       "opo",
+       "opt",
+       "opy",
+       "or",
+       "ora",
+       "orc",
+       "ore",
+       "org",
+       "orh",
+       "orn",
+       "oro",
+       "orr",
+       "ors",
+       "ort",
+       "oru",
+       "orv",
+       "orw",
+       "orx",
+       "orz",
+       "os",
+       "osa",
+       "osc",
+       "osi",
+       "oso",
+       "osp",
+       "ost",
+       "osu",
+       "osx",
+       "ota",
+       "otb",
+       "otd",
+       "ote",
+       "oti",
+       "otk",
+       "otl",
+       "otm",
+       "otn",
+       "otq",
+       "otr",
+       "ots",
+       "ott",
+       "otu",
+       "otw",
+       "otx",
+       "oty",
+       "otz",
+       "oua",
+       "oub",
+       "oue",
+       "oui",
+       "oum",
+       "oun",
+       "owi",
+       "owl",
+       "oyb",
+       "oyd",
+       "oym",
+       "oyy",
+       "ozm",
+       "pa",
+       "pab",
+       "pac",
+       "pad",
+       "pae",
+       "paf",
+       "pag",
+       "pah",
+       "pai",
+       "pak",
+       "pal",
+       "pam",
+       "pao",
+       "pap",
+       "paq",
+       "par",
+       "pas",
+       "pat",
+       "pau",
+       "pav",
+       "paw",
+       "pax",
+       "pay",
+       "paz",
+       "pbb",
+       "pbc",
+       "pbe",
+       "pbf",
+       "pbg",
+       "pbh",
+       "pbi",
+       "pbl",
+       "pbn",
+       "pbo",
+       "pbp",
+       "pbr",
+       "pbs",
+       "pbt",
+       "pbu",
+       "pbv",
+       "pby",
+       "pbz",
+       "pca",
+       "pcb",
+       "pcc",
+       "pcd",
+       "pce",
+       "pcf",
+       "pcg",
+       "pch",
+       "pci",
+       "pcj",
+       "pck",
+       "pcl",
+       "pcm",
+       "pcn",
+       "pcp",
+       "pcr",
+       "pcw",
+       "pda",
+       "pdc",
+       "pdi",
+       "pdn",
+       "pdo",
+       "pdt",
+       "pdu",
+       "pea",
+       "peb",
+       "ped",
+       "pee",
+       "pef",
+       "peg",
+       "peh",
+       "pei",
+       "pej",
+       "pek",
+       "pel",
+       "pem",
+       "peo",
+       "pep",
+       "peq",
+       "pes",
+       "pev",
+       "pex",
+       "pey",
+       "pez",
+       "pfa",
+       "pfe",
+       "pfl",
+       "pga",
+       "pgg",
+       "pgi",
+       "pgk",
+       "pgl",
+       "pgn",
+       "pgs",
+       "pgu",
+       "pgy",
+       "pha",
+       "phd",
+       "phg",
+       "phh",
+       "phk",
+       "phl",
+       "phm",
+       "phn",
+       "pho",
+       "phq",
+       "phr",
+       "pht",
+       "phu",
+       "phv",
+       "phw",
+       "pi",
+       "pia",
+       "pib",
+       "pic",
+       "pid",
+       "pie",
+       "pif",
+       "pig",
+       "pih",
+       "pii",
+       "pij",
+       "pil",
+       "pim",
+       "pin",
+       "pio",
+       "pip",
+       "pir",
+       "pis",
+       "pit",
+       "piu",
+       "piv",
+       "piw",
+       "pix",
+       "piy",
+       "piz",
+       "pjt",
+       "pka",
+       "pkb",
+       "pkc",
+       "pkg",
+       "pkh",
+       "pkn",
+       "pko",
+       "pkp",
+       "pkr",
+       "pks",
+       "pkt",
+       "pku",
+       "pl",
+       "pla",
+       "plb",
+       "plc",
+       "pld",
+       "ple",
+       "plg",
+       "plh",
+       "plj",
+       "plk",
+       "pll",
+       "pln",
+       "plo",
+       "plp",
+       "plq",
+       "plr",
+       "pls",
+       "plt",
+       "plu",
+       "plv",
+       "plw",
+       "ply",
+       "plz",
+       "pma",
+       "pmb",
+       "pmc",
+       "pme",
+       "pmf",
+       "pmh",
+       "pmi",
+       "pmj",
+       "pmk",
+       "pml",
+       "pmm",
+       "pmn",
+       "pmo",
+       "pmq",
+       "pmr",
+       "pms",
+       "pmt",
+       "pmu",
+       "pmw",
+       "pmx",
+       "pmy",
+       "pmz",
+       "pna",
+       "pnb",
+       "pnc",
+       "pne",
+       "png",
+       "pnh",
+       "pni",
+       "pnm",
+       "pnn",
+       "pno",
+       "pnp",
+       "pnq",
+       "pnr",
+       "pns",
+       "pnt",
+       "pnu",
+       "pnv",
+       "pnw",
+       "pnx",
+       "pny",
+       "pnz",
+       "poc",
+       "pod",
+       "poe",
+       "pof",
+       "pog",
+       "poh",
+       "poi",
+       "pok",
+       "pom",
+       "pon",
+       "poo",
+       "pop",
+       "poq",
+       "pos",
+       "pot",
+       "pov",
+       "pow",
+       "pox",
+       "poy",
+       "ppa",
+       "ppe",
+       "ppi",
+       "ppk",
+       "ppl",
+       "ppm",
+       "ppn",
+       "ppo",
+       "ppp",
+       "ppq",
+       "ppr",
+       "pps",
+       "ppt",
+       "ppu",
+       "pqa",
+       "pqm",
+       "prb",
+       "prc",
+       "prd",
+       "pre",
+       "prf",
+       "prg",
+       "prh",
+       "pri",
+       "prk",
+       "prl",
+       "prm",
+       "prn",
+       "pro",
+       "prp",
+       "prq",
+       "prr",
+       "prs",
+       "prt",
+       "pru",
+       "prw",
+       "prx",
+       "pry",
+       "prz",
+       "ps",
+       "psa",
+       "psc",
+       "psd",
+       "pse",
+       "psg",
+       "psh",
+       "psi",
+       "psl",
+       "psm",
+       "psn",
+       "pso",
+       "psp",
+       "psq",
+       "psr",
+       "pss",
+       "pst",
+       "psu",
+       "psw",
+       "psy",
+       "pt",
+       "pta",
+       "pth",
+       "pti",
+       "ptn",
+       "pto",
+       "ptp",
+       "ptr",
+       "ptt",
+       "ptu",
+       "ptv",
+       "ptw",
+       "pty",
+       "pua",
+       "pub",
+       "puc",
+       "pud",
+       "pue",
+       "puf",
+       "pug",
+       "pui",
+       "puj",
+       "puk",
+       "pum",
+       "puo",
+       "pup",
+       "puq",
+       "pur",
+       "put",
+       "puu",
+       "puw",
+       "pux",
+       "puy",
+       "puz",
+       "pwa",
+       "pwb",
+       "pwg",
+       "pwm",
+       "pwn",
+       "pwo",
+       "pwr",
+       "pww",
+       "pxm",
+       "pye",
+       "pym",
+       "pyn",
+       "pys",
+       "pyu",
+       "pyx",
+       "pyy",
+       "pzn",
+       "qu",
+       "qua",
+       "qub",
+       "quc",
+       "qud",
+       "quf",
+       "qug",
+       "quh",
+       "qui",
+       "quk",
+       "qul",
+       "qum",
+       "qun",
+       "qup",
+       "quq",
+       "qur",
+       "qus",
+       "quv",
+       "quw",
+       "qux",
+       "quy",
+       "quz",
+       "qva",
+       "qvc",
+       "qve",
+       "qvh",
+       "qvi",
+       "qvj",
+       "qvl",
+       "qvm",
+       "qvn",
+       "qvo",
+       "qvp",
+       "qvs",
+       "qvw",
+       "qvy",
+       "qvz",
+       "qwa",
+       "qwc",
+       "qwh",
+       "qwm",
+       "qws",
+       "qwt",
+       "qxa",
+       "qxc",
+       "qxh",
+       "qxl",
+       "qxn",
+       "qxo",
+       "qxp",
+       "qxq",
+       "qxr",
+       "qxs",
+       "qxt",
+       "qxu",
+       "qxw",
+       "qya",
+       "qyp",
+       "raa",
+       "rab",
+       "rac",
+       "rad",
+       "raf",
+       "rag",
+       "rah",
+       "rai",
+       "raj",
+       "rak",
+       "ral",
+       "ram",
+       "ran",
+       "rao",
+       "rap",
+       "raq",
+       "rar",
+       "ras",
+       "rat",
+       "rau",
+       "rav",
+       "raw",
+       "rax",
+       "ray",
+       "raz",
+       "rbb",
+       "rbk",
+       "rbl",
+       "rcf",
+       "rdb",
+       "rea",
+       "reb",
+       "ree",
+       "reg",
+       "rei",
+       "rej",
+       "rel",
+       "rem",
+       "ren",
+       "rer",
+       "res",
+       "ret",
+       "rey",
+       "rga",
+       "rge",
+       "rgk",
+       "rgn",
+       "rgr",
+       "rgs",
+       "rgu",
+       "rhg",
+       "rhp",
+       "ria",
+       "rie",
+       "rif",
+       "ril",
+       "rim",
+       "rin",
+       "rir",
+       "rit",
+       "riu",
+       "rjg",
+       "rji",
+       "rjs",
+       "rka",
+       "rkb",
+       "rkh",
+       "rki",
+       "rkm",
+       "rkt",
+       "rm",
+       "rma",
+       "rmb",
+       "rmc",
+       "rmd",
+       "rme",
+       "rmf",
+       "rmg",
+       "rmh",
+       "rmi",
+       "rmk",
+       "rml",
+       "rmm",
+       "rmn",
+       "rmo",
+       "rmp",
+       "rmq",
+       "rms",
+       "rmt",
+       "rmu",
+       "rmv",
+       "rmw",
+       "rmx",
+       "rmy",
+       "rmz",
+       "rn",
+       "rna",
+       "rnd",
+       "rng",
+       "rnl",
+       "rnn",
+       "rnp",
+       "rnw",
+       "ro",
+       "rob",
+       "roc",
+       "rod",
+       "roe",
+       "rof",
+       "rog",
+       "rol",
+       "rom",
+       "roo",
+       "rop",
+       "ror",
+       "rou",
+       "row",
+       "rpn",
+       "rpt",
+       "rri",
+       "rro",
+       "rsb",
+       "rsi",
+       "rsl",
+       "rth",
+       "rtm",
+       "rtw",
+       "ru",
+       "rub",
+       "ruc",
+       "rue",
+       "ruf",
+       "rug",
+       "ruh",
+       "rui",
+       "ruk",
+       "ruo",
+       "rup",
+       "ruq",
+       "rut",
+       "ruu",
+       "ruy",
+       "ruz",
+       "rw",
+       "rwa",
+       "rwk",
+       "rwm",
+       "rwo",
+       "rwr",
+       "ryn",
+       "rys",
+       "ryu",
+       "sa",
+       "saa",
+       "sab",
+       "sac",
+       "sad",
+       "sae",
+       "saf",
+       "sah",
+       "saj",
+       "sak",
+       "sam",
+       "sao",
+       "sap",
+       "saq",
+       "sar",
+       "sas",
+       "sat",
+       "sau",
+       "sav",
+       "saw",
+       "sax",
+       "say",
+       "saz",
+       "sba",
+       "sbb",
+       "sbc",
+       "sbd",
+       "sbe",
+       "sbf",
+       "sbg",
+       "sbh",
+       "sbi",
+       "sbj",
+       "sbk",
+       "sbl",
+       "sbm",
+       "sbn",
+       "sbo",
+       "sbp",
+       "sbq",
+       "sbr",
+       "sbs",
+       "sbt",
+       "sbu",
+       "sbv",
+       "sbw",
+       "sbx",
+       "sby",
+       "sbz",
+       "sc",
+       "sca",
+       "scb",
+       "sce",
+       "scf",
+       "scg",
+       "sch",
+       "sci",
+       "sck",
+       "scl",
+       "scn",
+       "sco",
+       "scp",
+       "scq",
+       "scs",
+       "scu",
+       "scv",
+       "scw",
+       "scx",
+       "sd",
+       "sda",
+       "sdb",
+       "sdc",
+       "sde",
+       "sdf",
+       "sdg",
+       "sdh",
+       "sdj",
+       "sdk",
+       "sdl",
+       "sdm",
+       "sdn",
+       "sdo",
+       "sdp",
+       "sdr",
+       "sds",
+       "sdt",
+       "sdu",
+       "sdx",
+       "sdz",
+       "se",
+       "sea",
+       "seb",
+       "sec",
+       "sed",
+       "see",
+       "sef",
+       "seg",
+       "seh",
+       "sei",
+       "sej",
+       "sek",
+       "sel",
+       "sen",
+       "seo",
+       "sep",
+       "seq",
+       "ser",
+       "ses",
+       "set",
+       "seu",
+       "sev",
+       "sew",
+       "sey",
+       "sez",
+       "sfb",
+       "sfm",
+       "sfs",
+       "sfw",
+       "sg",
+       "sga",
+       "sgb",
+       "sgc",
+       "sgd",
+       "sge",
+       "sgg",
+       "sgh",
+       "sgi",
+       "sgk",
+       "sgm",
+       "sgo",
+       "sgp",
+       "sgr",
+       "sgs",
+       "sgt",
+       "sgu",
+       "sgw",
+       "sgx",
+       "sgy",
+       "sgz",
+       "sh",
+       "sha",
+       "shb",
+       "shc",
+       "shd",
+       "she",
+       "shg",
+       "shh",
+       "shi",
+       "shj",
+       "shk",
+       "shl",
+       "shm",
+       "shn",
+       "sho",
+       "shp",
+       "shq",
+       "shr",
+       "shs",
+       "sht",
+       "shu",
+       "shv",
+       "shw",
+       "shx",
+       "shy",
+       "shz",
+       "si",
+       "sia",
+       "sib",
+       "sid",
+       "sie",
+       "sif",
+       "sig",
+       "sih",
+       "sii",
+       "sij",
+       "sik",
+       "sil",
+       "sim",
+       "sip",
+       "siq",
+       "sir",
+       "sis",
+       "siu",
+       "siv",
+       "siw",
+       "six",
+       "siy",
+       "siz",
+       "sja",
+       "sjb",
+       "sjd",
+       "sje",
+       "sjg",
+       "sjk",
+       "sjl",
+       "sjm",
+       "sjn",
+       "sjo",
+       "sjp",
+       "sjr",
+       "sjs",
+       "sjt",
+       "sju",
+       "sjw",
+       "sk",
+       "ska",
+       "skb",
+       "skc",
+       "skd",
+       "ske",
+       "skf",
+       "skg",
+       "skh",
+       "ski",
+       "skj",
+       "skk",
+       "skm",
+       "skn",
+       "sko",
+       "skp",
+       "skq",
+       "skr",
+       "sks",
+       "skt",
+       "sku",
+       "skv",
+       "skw",
+       "skx",
+       "sky",
+       "skz",
+       "sl",
+       "slc",
+       "sld",
+       "sle",
+       "slf",
+       "slg",
+       "slh",
+       "sli",
+       "slj",
+       "sll",
+       "slm",
+       "sln",
+       "slp",
+       "slq",
+       "slr",
+       "sls",
+       "slt",
+       "slu",
+       "slw",
+       "slx",
+       "sly",
+       "slz",
+       "sm",
+       "sma",
+       "smb",
+       "smc",
+       "smd",
+       "smf",
+       "smg",
+       "smh",
+       "smj",
+       "smk",
+       "sml",
+       "smm",
+       "smn",
+       "smp",
+       "smq",
+       "smr",
+       "sms",
+       "smt",
+       "smu",
+       "smv",
+       "smw",
+       "smx",
+       "smy",
+       "smz",
+       "sn",
+       "snb",
+       "snc",
+       "sne",
+       "snf",
+       "sng",
+       "snh",
+       "sni",
+       "snj",
+       "snk",
+       "snl",
+       "snm",
+       "snn",
+       "sno",
+       "snp",
+       "snq",
+       "snr",
+       "sns",
+       "snu",
+       "snv",
+       "snw",
+       "snx",
+       "sny",
+       "snz",
+       "so",
+       "soa",
+       "sob",
+       "soc",
+       "sod",
+       "soe",
+       "sog",
+       "soh",
+       "soi",
+       "soj",
+       "sok",
+       "sol",
+       "soo",
+       "sop",
+       "soq",
+       "sor",
+       "sos",
+       "sou",
+       "sov",
+       "sow",
+       "sox",
+       "soy",
+       "soz",
+       "spb",
+       "spc",
+       "spd",
+       "spe",
+       "spg",
+       "spi",
+       "spk",
+       "spl",
+       "spm",
+       "spo",
+       "spp",
+       "spq",
+       "spr",
+       "sps",
+       "spt",
+       "spu",
+       "spx",
+       "spy",
+       "sq",
+       "sqa",
+       "sqh",
+       "sqm",
+       "sqn",
+       "sqo",
+       "sqq",
+       "sqr",
+       "sqs",
+       "sqt",
+       "squ",
+       "sr",
+       "sra",
+       "srb",
+       "src",
+       "sre",
+       "srf",
+       "srg",
+       "srh",
+       "sri",
+       "srk",
+       "srl",
+       "srm",
+       "srn",
+       "sro",
+       "srq",
+       "srr",
+       "srs",
+       "srt",
+       "sru",
+       "srv",
+       "srw",
+       "srx",
+       "sry",
+       "srz",
+       "ss",
+       "ssb",
+       "ssc",
+       "ssd",
+       "sse",
+       "ssf",
+       "ssg",
+       "ssh",
+       "ssi",
+       "ssj",
+       "ssk",
+       "ssl",
+       "ssm",
+       "ssn",
+       "sso",
+       "ssp",
+       "ssq",
+       "ssr",
+       "sss",
+       "sst",
+       "ssu",
+       "ssv",
+       "ssx",
+       "ssy",
+       "ssz",
+       "st",
+       "sta",
+       "stb",
+       "std",
+       "ste",
+       "stf",
+       "stg",
+       "sth",
+       "sti",
+       "stj",
+       "stk",
+       "stl",
+       "stm",
+       "stn",
+       "sto",
+       "stp",
+       "stq",
+       "str",
+       "sts",
+       "stt",
+       "stu",
+       "stv",
+       "stw",
+       "su",
+       "sua",
+       "sub",
+       "suc",
+       "sue",
+       "sug",
+       "sui",
+       "suj",
+       "suk",
+       "suq",
+       "sur",
+       "sus",
+       "sut",
+       "suv",
+       "suw",
+       "sux",
+       "suy",
+       "suz",
+       "sv",
+       "sva",
+       "svb",
+       "svc",
+       "sve",
+       "svk",
+       "svr",
+       "svs",
+       "svx",
+       "sw",
+       "swb",
+       "swc",
+       "swf",
+       "swg",
+       "swh",
+       "swi",
+       "swj",
+       "swk",
+       "swl",
+       "swm",
+       "swn",
+       "swo",
+       "swp",
+       "swq",
+       "swr",
+       "sws",
+       "swt",
+       "swu",
+       "swv",
+       "sww",
+       "swx",
+       "swy",
+       "sxb",
+       "sxc",
+       "sxe",
+       "sxg",
+       "sxk",
+       "sxl",
+       "sxm",
+       "sxn",
+       "sxo",
+       "sxr",
+       "sxs",
+       "sxu",
+       "sxw",
+       "sya",
+       "syb",
+       "syc",
+       "syi",
+       "syk",
+       "syl",
+       "sym",
+       "syn",
+       "syo",
+       "syr",
+       "sys",
+       "syw",
+       "syy",
+       "sza",
+       "szb",
+       "szc",
+       "szd",
+       "sze",
+       "szg",
+       "szl",
+       "szn",
+       "szp",
+       "szv",
+       "szw",
+       "ta",
+       "taa",
+       "tab",
+       "tac",
+       "tad",
+       "tae",
+       "taf",
+       "tag",
+       "taj",
+       "tak",
+       "tal",
+       "tan",
+       "tao",
+       "tap",
+       "taq",
+       "tar",
+       "tas",
+       "tau",
+       "tav",
+       "taw",
+       "tax",
+       "tay",
+       "taz",
+       "tba",
+       "tbb",
+       "tbc",
+       "tbd",
+       "tbe",
+       "tbf",
+       "tbg",
+       "tbh",
+       "tbi",
+       "tbj",
+       "tbk",
+       "tbl",
+       "tbm",
+       "tbn",
+       "tbo",
+       "tbp",
+       "tbr",
+       "tbs",
+       "tbt",
+       "tbu",
+       "tbv",
+       "tbw",
+       "tbx",
+       "tby",
+       "tbz",
+       "tca",
+       "tcb",
+       "tcc",
+       "tcd",
+       "tce",
+       "tcf",
+       "tcg",
+       "tch",
+       "tci",
+       "tck",
+       "tcl",
+       "tcm",
+       "tcn",
+       "tco",
+       "tcp",
+       "tcq",
+       "tcs",
+       "tct",
+       "tcu",
+       "tcw",
+       "tcx",
+       "tcy",
+       "tcz",
+       "tda",
+       "tdb",
+       "tdc",
+       "tdd",
+       "tde",
+       "tdf",
+       "tdg",
+       "tdh",
+       "tdi",
+       "tdj",
+       "tdk",
+       "tdl",
+       "tdn",
+       "tdo",
+       "tdq",
+       "tdr",
+       "tds",
+       "tdt",
+       "tdu",
+       "tdv",
+       "tdx",
+       "tdy",
+       "te",
+       "tea",
+       "teb",
+       "tec",
+       "ted",
+       "tee",
+       "tef",
+       "teg",
+       "teh",
+       "tei",
+       "tek",
+       "tem",
+       "ten",
+       "teo",
+       "tep",
+       "teq",
+       "ter",
+       "tes",
+       "tet",
+       "teu",
+       "tev",
+       "tew",
+       "tex",
+       "tey",
+       "tfi",
+       "tfn",
+       "tfo",
+       "tfr",
+       "tft",
+       "tg",
+       "tga",
+       "tgb",
+       "tgc",
+       "tgd",
+       "tge",
+       "tgf",
+       "tgg",
+       "tgh",
+       "tgi",
+       "tgn",
+       "tgo",
+       "tgp",
+       "tgq",
+       "tgr",
+       "tgs",
+       "tgt",
+       "tgu",
+       "tgv",
+       "tgw",
+       "tgx",
+       "tgy",
+       "th",
+       "thc",
+       "thd",
+       "the",
+       "thf",
+       "thh",
+       "thi",
+       "thk",
+       "thl",
+       "thm",
+       "thn",
+       "thp",
+       "thq",
+       "thr",
+       "ths",
+       "tht",
+       "thu",
+       "thv",
+       "thw",
+       "thx",
+       "thy",
+       "thz",
+       "ti",
+       "tia",
+       "tic",
+       "tid",
+       "tif",
+       "tig",
+       "tih",
+       "tii",
+       "tij",
+       "tik",
+       "til",
+       "tim",
+       "tin",
+       "tio",
+       "tip",
+       "tiq",
+       "tis",
+       "tit",
+       "tiu",
+       "tiv",
+       "tiw",
+       "tix",
+       "tiy",
+       "tiz",
+       "tja",
+       "tjg",
+       "tji",
+       "tjm",
+       "tjn",
+       "tjo",
+       "tjs",
+       "tju",
+       "tk",
+       "tka",
+       "tkb",
+       "tkd",
+       "tke",
+       "tkf",
+       "tkg",
+       "tkl",
+       "tkm",
+       "tkn",
+       "tkp",
+       "tkq",
+       "tkr",
+       "tks",
+       "tkt",
+       "tku",
+       "tkw",
+       "tkx",
+       "tkz",
+       "tl",
+       "tla",
+       "tlb",
+       "tlc",
+       "tld",
+       "tlf",
+       "tlg",
+       "tlh",
+       "tli",
+       "tlj",
+       "tlk",
+       "tll",
+       "tlm",
+       "tln",
+       "tlo",
+       "tlp",
+       "tlq",
+       "tlr",
+       "tls",
+       "tlt",
+       "tlu",
+       "tlv",
+       "tlw",
+       "tlx",
+       "tly",
+       "tma",
+       "tmb",
+       "tmc",
+       "tmd",
+       "tme",
+       "tmf",
+       "tmg",
+       "tmh",
+       "tmi",
+       "tmj",
+       "tmk",
+       "tml",
+       "tmm",
+       "tmn",
+       "tmo",
+       "tmp",
+       "tmq",
+       "tmr",
+       "tms",
+       "tmt",
+       "tmu",
+       "tmv",
+       "tmw",
+       "tmy",
+       "tmz",
+       "tn",
+       "tna",
+       "tnb",
+       "tnc",
+       "tnd",
+       "tne",
+       "tng",
+       "tnh",
+       "tni",
+       "tnk",
+       "tnl",
+       "tnm",
+       "tnn",
+       "tno",
+       "tnp",
+       "tnq",
+       "tnr",
+       "tns",
+       "tnt",
+       "tnu",
+       "tnv",
+       "tnw",
+       "tnx",
+       "tny",
+       "tnz",
+       "to",
+       "tob",
+       "toc",
+       "tod",
+       "toe",
+       "tof",
+       "tog",
+       "toh",
+       "toi",
+       "toj",
+       "tol",
+       "tom",
+       "too",
+       "top",
+       "toq",
+       "tor",
+       "tos",
+       "tou",
+       "tov",
+       "tow",
+       "tox",
+       "toy",
+       "toz",
+       "tpa",
+       "tpc",
+       "tpe",
+       "tpf",
+       "tpg",
+       "tpi",
+       "tpj",
+       "tpk",
+       "tpl",
+       "tpm",
+       "tpn",
+       "tpo",
+       "tpp",
+       "tpq",
+       "tpr",
+       "tpt",
+       "tpu",
+       "tpv",
+       "tpw",
+       "tpx",
+       "tpy",
+       "tpz",
+       "tqb",
+       "tql",
+       "tqm",
+       "tqn",
+       "tqo",
+       "tqp",
+       "tqq",
+       "tqr",
+       "tqt",
+       "tqu",
+       "tqw",
+       "tr",
+       "tra",
+       "trb",
+       "trc",
+       "trd",
+       "tre",
+       "trf",
+       "trg",
+       "trh",
+       "tri",
+       "trj",
+       "trl",
+       "trm",
+       "trn",
+       "tro",
+       "trp",
+       "trq",
+       "trr",
+       "trs",
+       "trt",
+       "tru",
+       "trv",
+       "trw",
+       "trx",
+       "try",
+       "trz",
+       "ts",
+       "tsa",
+       "tsb",
+       "tsc",
+       "tsd",
+       "tse",
+       "tsf",
+       "tsg",
+       "tsh",
+       "tsi",
+       "tsj",
+       "tsk",
+       "tsl",
+       "tsm",
+       "tsp",
+       "tsq",
+       "tsr",
+       "tss",
+       "tst",
+       "tsu",
+       "tsv",
+       "tsw",
+       "tsx",
+       "tsy",
+       "tsz",
+       "tt",
+       "tta",
+       "ttb",
+       "ttc",
+       "ttd",
+       "tte",
+       "ttf",
+       "ttg",
+       "tth",
+       "tti",
+       "ttj",
+       "ttk",
+       "ttl",
+       "ttm",
+       "ttn",
+       "tto",
+       "ttp",
+       "ttq",
+       "ttr",
+       "tts",
+       "ttt",
+       "ttu",
+       "ttv",
+       "ttw",
+       "tty",
+       "ttz",
+       "tua",
+       "tub",
+       "tuc",
+       "tud",
+       "tue",
+       "tuf",
+       "tug",
+       "tuh",
+       "tui",
+       "tuj",
+       "tul",
+       "tum",
+       "tun",
+       "tuo",
+       "tuq",
+       "tus",
+       "tuu",
+       "tuv",
+       "tux",
+       "tuy",
+       "tuz",
+       "tva",
+       "tvd",
+       "tve",
+       "tvk",
+       "tvl",
+       "tvm",
+       "tvn",
+       "tvo",
+       "tvs",
+       "tvt",
+       "tvw",
+       "tvy",
+       "tw",
+       "twa",
+       "twb",
+       "twc",
+       "twd",
+       "twe",
+       "twf",
+       "twg",
+       "twh",
+       "twl",
+       "twm",
+       "twn",
+       "two",
+       "twp",
+       "twq",
+       "twr",
+       "twt",
+       "twu",
+       "tww",
+       "twx",
+       "twy",
+       "txa",
+       "txb",
+       "txc",
+       "txe",
+       "txg",
+       "txh",
+       "txi",
+       "txm",
+       "txn",
+       "txo",
+       "txq",
+       "txr",
+       "txs",
+       "txt",
+       "txu",
+       "txx",
+       "txy",
+       "ty",
+       "tya",
+       "tye",
+       "tyh",
+       "tyi",
+       "tyj",
+       "tyl",
+       "tyn",
+       "typ",
+       "tyr",
+       "tys",
+       "tyt",
+       "tyu",
+       "tyv",
+       "tyx",
+       "tyz",
+       "tza",
+       "tzh",
+       "tzj",
+       "tzm",
+       "tzn",
+       "tzo",
+       "tzx",
+       "uam",
+       "uan",
+       "uar",
+       "uba",
+       "ubi",
+       "ubl",
+       "ubr",
+       "ubu",
+       "uby",
+       "uda",
+       "ude",
+       "udg",
+       "udi",
+       "udj",
+       "udl",
+       "udm",
+       "udu",
+       "ues",
+       "ufi",
+       "ug",
+       "uga",
+       "ugb",
+       "uge",
+       "ugn",
+       "ugo",
+       "ugy",
+       "uha",
+       "uhn",
+       "uis",
+       "uiv",
+       "uji",
+       "uk",
+       "uka",
+       "ukg",
+       "ukh",
+       "ukl",
+       "ukp",
+       "ukq",
+       "uks",
+       "uku",
+       "ukw",
+       "ula",
+       "ulb",
+       "ulc",
+       "ulf",
+       "uli",
+       "ulk",
+       "ull",
+       "ulm",
+       "uln",
+       "ulu",
+       "ulw",
+       "uma",
+       "umb",
+       "umc",
+       "umd",
+       "umg",
+       "umi",
+       "umm",
+       "umn",
+       "umo",
+       "ump",
+       "umr",
+       "ums",
+       "umu",
+       "una",
+       "und",
+       "une",
+       "ung",
+       "unk",
+       "unm",
+       "unp",
+       "unr",
+       "unx",
+       "unz",
+       "uok",
+       "upi",
+       "upv",
+       "ur",
+       "ura",
+       "urb",
+       "urc",
+       "ure",
+       "urf",
+       "urg",
+       "urh",
+       "uri",
+       "urk",
+       "url",
+       "urm",
+       "urn",
+       "uro",
+       "urp",
+       "urr",
+       "urt",
+       "uru",
+       "urv",
+       "urw",
+       "urx",
+       "ury",
+       "urz",
+       "usa",
+       "ush",
+       "usi",
+       "usk",
+       "usp",
+       "usu",
+       "uta",
+       "ute",
+       "utp",
+       "utr",
+       "utu",
+       "uum",
+       "uun",
+       "uur",
+       "uuu",
+       "uve",
+       "uvh",
+       "uvl",
+       "uwa",
+       "uya",
+       "uz",
+       "uzn",
+       "uzs",
+       "vaa",
+       "vae",
+       "vaf",
+       "vag",
+       "vah",
+       "vai",
+       "vaj",
+       "val",
+       "vam",
+       "van",
+       "vao",
+       "vap",
+       "var",
+       "vas",
+       "vau",
+       "vav",
+       "vay",
+       "vbb",
+       "vbk",
+       "ve",
+       "vec",
+       "ved",
+       "vel",
+       "vem",
+       "veo",
+       "vep",
+       "ver",
+       "vgr",
+       "vgt",
+       "vi",
+       "vic",
+       "vid",
+       "vif",
+       "vig",
+       "vil",
+       "vin",
+       "vis",
+       "vit",
+       "viv",
+       "vka",
+       "vki",
+       "vkj",
+       "vkk",
+       "vkl",
+       "vkm",
+       "vko",
+       "vkp",
+       "vkt",
+       "vku",
+       "vlp",
+       "vls",
+       "vma",
+       "vmb",
+       "vmc",
+       "vmd",
+       "vme",
+       "vmf",
+       "vmg",
+       "vmh",
+       "vmi",
+       "vmj",
+       "vmk",
+       "vml",
+       "vmm",
+       "vmp",
+       "vmq",
+       "vmr",
+       "vms",
+       "vmu",
+       "vmv",
+       "vmw",
+       "vmx",
+       "vmy",
+       "vmz",
+       "vnk",
+       "vnm",
+       "vnp",
+       "vo",
+       "vor",
+       "vot",
+       "vra",
+       "vro",
+       "vrs",
+       "vrt",
+       "vsi",
+       "vsl",
+       "vsv",
+       "vto",
+       "vum",
+       "vun",
+       "vut",
+       "vwa",
+       "wa",
+       "waa",
+       "wab",
+       "wac",
+       "wad",
+       "wae",
+       "waf",
+       "wag",
+       "wah",
+       "wai",
+       "waj",
+       "wal",
+       "wam",
+       "wan",
+       "wao",
+       "wap",
+       "waq",
+       "war",
+       "was",
+       "wat",
+       "wau",
+       "wav",
+       "waw",
+       "wax",
+       "way",
+       "waz",
+       "wba",
+       "wbb",
+       "wbe",
+       "wbf",
+       "wbh",
+       "wbi",
+       "wbj",
+       "wbk",
+       "wbl",
+       "wbm",
+       "wbp",
+       "wbq",
+       "wbr",
+       "wbt",
+       "wbv",
+       "wbw",
+       "wca",
+       "wci",
+       "wdd",
+       "wdg",
+       "wdj",
+       "wdu",
+       "wea",
+       "wec",
+       "wed",
+       "weh",
+       "wei",
+       "wem",
+       "weo",
+       "wep",
+       "wer",
+       "wes",
+       "wet",
+       "weu",
+       "wew",
+       "wfg",
+       "wga",
+       "wgb",
+       "wgg",
+       "wgi",
+       "wgo",
+       "wgy",
+       "wha",
+       "whg",
+       "whk",
+       "whu",
+       "wib",
+       "wic",
+       "wie",
+       "wif",
+       "wig",
+       "wih",
+       "wii",
+       "wij",
+       "wik",
+       "wil",
+       "wim",
+       "win",
+       "wir",
+       "wit",
+       "wiu",
+       "wiv",
+       "wiw",
+       "wiy",
+       "wja",
+       "wji",
+       "wka",
+       "wkb",
+       "wkd",
+       "wkl",
+       "wku",
+       "wkw",
+       "wla",
+       "wlc",
+       "wle",
+       "wlg",
+       "wli",
+       "wlk",
+       "wll",
+       "wlm",
+       "wlo",
+       "wlr",
+       "wls",
+       "wlu",
+       "wlv",
+       "wlw",
+       "wlx",
+       "wly",
+       "wma",
+       "wmb",
+       "wmc",
+       "wmd",
+       "wme",
+       "wmh",
+       "wmi",
+       "wmm",
+       "wmn",
+       "wmo",
+       "wms",
+       "wmt",
+       "wmw",
+       "wmx",
+       "wnb",
+       "wnc",
+       "wnd",
+       "wne",
+       "wng",
+       "wni",
+       "wnk",
+       "wnm",
+       "wno",
+       "wnp",
+       "wnu",
+       "wo",
+       "woa",
+       "wob",
+       "woc",
+       "wod",
+       "woe",
+       "wof",
+       "wog",
+       "woi",
+       "wok",
+       "wom",
+       "won",
+       "woo",
+       "wor",
+       "wos",
+       "wow",
+       "woy",
+       "wpc",
+       "wra",
+       "wrb",
+       "wrd",
+       "wrg",
+       "wrh",
+       "wri",
+       "wrl",
+       "wrm",
+       "wrn",
+       "wrp",
+       "wrr",
+       "wrs",
+       "wru",
+       "wrv",
+       "wrw",
+       "wrx",
+       "wry",
+       "wrz",
+       "wsa",
+       "wsi",
+       "wsk",
+       "wsr",
+       "wss",
+       "wsu",
+       "wsv",
+       "wtf",
+       "wti",
+       "wtk",
+       "wtm",
+       "wtw",
+       "wua",
+       "wub",
+       "wud",
+       "wuh",
+       "wul",
+       "wum",
+       "wun",
+       "wur",
+       "wut",
+       "wuu",
+       "wuv",
+       "wux",
+       "wuy",
+       "wwa",
+       "wwo",
+       "wwr",
+       "www",
+       "wxa",
+       "wya",
+       "wyb",
+       "wym",
+       "wyr",
+       "wyy",
+       "xaa",
+       "xab",
+       "xac",
+       "xad",
+       "xae",
+       "xag",
+       "xai",
+       "xal",
+       "xam",
+       "xan",
+       "xao",
+       "xap",
+       "xaq",
+       "xar",
+       "xas",
+       "xat",
+       "xau",
+       "xav",
+       "xaw",
+       "xay",
+       "xba",
+       "xbb",
+       "xbc",
+       "xbi",
+       "xbm",
+       "xbn",
+       "xbo",
+       "xbr",
+       "xbw",
+       "xbx",
+       "xcb",
+       "xcc",
+       "xce",
+       "xcg",
+       "xch",
+       "xcl",
+       "xcm",
+       "xcn",
+       "xco",
+       "xcr",
+       "xct",
+       "xcu",
+       "xcv",
+       "xcw",
+       "xcy",
+       "xdc",
+       "xdm",
+       "xdy",
+       "xeb",
+       "xed",
+       "xeg",
+       "xel",
+       "xem",
+       "xep",
+       "xer",
+       "xes",
+       "xet",
+       "xeu",
+       "xfa",
+       "xga",
+       "xgf",
+       "xgl",
+       "xgr",
+       "xh",
+       "xha",
+       "xhc",
+       "xhd",
+       "xhe",
+       "xhr",
+       "xht",
+       "xhu",
+       "xhv",
+       "xia",
+       "xib",
+       "xii",
+       "xil",
+       "xin",
+       "xip",
+       "xir",
+       "xiv",
+       "xiy",
+       "xka",
+       "xkb",
+       "xkc",
+       "xkd",
+       "xke",
+       "xkf",
+       "xkg",
+       "xkh",
+       "xki",
+       "xkj",
+       "xkk",
+       "xkl",
+       "xkn",
+       "xko",
+       "xkp",
+       "xkq",
+       "xkr",
+       "xks",
+       "xkt",
+       "xku",
+       "xkv",
+       "xkw",
+       "xkx",
+       "xky",
+       "xkz",
+       "xla",
+       "xlb",
+       "xlc",
+       "xld",
+       "xle",
+       "xlg",
+       "xli",
+       "xln",
+       "xlo",
+       "xlp",
+       "xls",
+       "xlu",
+       "xly",
+       "xma",
+       "xmb",
+       "xmc",
+       "xmd",
+       "xme",
+       "xmf",
+       "xmg",
+       "xmh",
+       "xmj",
+       "xmk",
+       "xml",
+       "xmm",
+       "xmn",
+       "xmo",
+       "xmp",
+       "xmq",
+       "xmr",
+       "xms",
+       "xmt",
+       "xmu",
+       "xmv",
+       "xmw",
+       "xmx",
+       "xmy",
+       "xmz",
+       "xna",
+       "xnb",
+       "xng",
+       "xnh",
+       "xnn",
+       "xno",
+       "xnr",
+       "xns",
+       "xnt",
+       "xoc",
+       "xod",
+       "xog",
+       "xoi",
+       "xok",
+       "xom",
+       "xon",
+       "xoo",
+       "xop",
+       "xor",
+       "xow",
+       "xpc",
+       "xpe",
+       "xpg",
+       "xpi",
+       "xpk",
+       "xpm",
+       "xpn",
+       "xpo",
+       "xpp",
+       "xpq",
+       "xpr",
+       "xps",
+       "xpu",
+       "xpy",
+       "xqa",
+       "xqt",
+       "xra",
+       "xrb",
+       "xre",
+       "xri",
+       "xrm",
+       "xrn",
+       "xrr",
+       "xrt",
+       "xru",
+       "xrw",
+       "xsa",
+       "xsb",
+       "xsc",
+       "xsd",
+       "xse",
+       "xsh",
+       "xsi",
+       "xsj",
+       "xsl",
+       "xsm",
+       "xsn",
+       "xso",
+       "xsp",
+       "xsq",
+       "xsr",
+       "xss",
+       "xsu",
+       "xsv",
+       "xsy",
+       "xta",
+       "xtb",
+       "xtc",
+       "xtd",
+       "xte",
+       "xtg",
+       "xti",
+       "xtj",
+       "xtl",
+       "xtm",
+       "xtn",
+       "xto",
+       "xtp",
+       "xtq",
+       "xtr",
+       "xts",
+       "xtt",
+       "xtu",
+       "xtw",
+       "xty",
+       "xtz",
+       "xua",
+       "xub",
+       "xug",
+       "xuj",
+       "xum",
+       "xuo",
+       "xup",
+       "xur",
+       "xut",
+       "xuu",
+       "xve",
+       "xvi",
+       "xvn",
+       "xvo",
+       "xvs",
+       "xwa",
+       "xwc",
+       "xwe",
+       "xwg",
+       "xwl",
+       "xwo",
+       "xwr",
+       "xxb",
+       "xxk",
+       "xxr",
+       "xxt",
+       "xyl",
+       "xzh",
+       "xzm",
+       "xzp",
+       "yaa",
+       "yab",
+       "yac",
+       "yad",
+       "yae",
+       "yaf",
+       "yag",
+       "yah",
+       "yai",
+       "yaj",
+       "yak",
+       "yal",
+       "yam",
+       "yan",
+       "yao",
+       "yap",
+       "yaq",
+       "yar",
+       "yas",
+       "yat",
+       "yau",
+       "yav",
+       "yaw",
+       "yax",
+       "yay",
+       "yaz",
+       "yba",
+       "ybb",
+       "ybd",
+       "ybe",
+       "ybh",
+       "ybi",
+       "ybj",
+       "ybk",
+       "ybl",
+       "ybm",
+       "ybn",
+       "ybo",
+       "ybx",
+       "yby",
+       "ych",
+       "ycl",
+       "ycn",
+       "ycp",
+       "ydd",
+       "yde",
+       "ydg",
+       "ydk",
+       "yds",
+       "yea",
+       "yec",
+       "yee",
+       "yei",
+       "yej",
+       "yel",
+       "yen",
+       "yer",
+       "yes",
+       "yet",
+       "yeu",
+       "yev",
+       "yey",
+       "ygl",
+       "ygm",
+       "ygp",
+       "ygr",
+       "ygw",
+       "yha",
+       "yhd",
+       "yhl",
+       "yi",
+       "yia",
+       "yif",
+       "yig",
+       "yih",
+       "yii",
+       "yij",
+       "yik",
+       "yil",
+       "yim",
+       "yin",
+       "yip",
+       "yiq",
+       "yir",
+       "yis",
+       "yit",
+       "yiu",
+       "yiv",
+       "yix",
+       "yiy",
+       "yiz",
+       "yka",
+       "ykg",
+       "yki",
+       "ykk",
+       "ykl",
+       "ykm",
+       "yko",
+       "ykr",
+       "ykt",
+       "yky",
+       "yla",
+       "ylb",
+       "yle",
+       "ylg",
+       "yli",
+       "yll",
+       "ylm",
+       "yln",
+       "ylo",
+       "ylr",
+       "ylu",
+       "yly",
+       "yma",
+       "ymb",
+       "ymc",
+       "ymd",
+       "yme",
+       "ymg",
+       "ymh",
+       "ymi",
+       "ymk",
+       "yml",
+       "ymm",
+       "ymn",
+       "ymo",
+       "ymp",
+       "ymq",
+       "ymr",
+       "yms",
+       "ymt",
+       "ymx",
+       "ymz",
+       "yna",
+       "ynd",
+       "yne",
+       "yng",
+       "ynh",
+       "ynk",
+       "ynl",
+       "ynn",
+       "yno",
+       "yns",
+       "ynu",
+       "yo",
+       "yob",
+       "yog",
+       "yoi",
+       "yok",
+       "yol",
+       "yom",
+       "yon",
+       "yos",
+       "yox",
+       "yoy",
+       "ypa",
+       "ypb",
+       "ypg",
+       "yph",
+       "ypm",
+       "ypn",
+       "ypo",
+       "ypp",
+       "ypz",
+       "yra",
+       "yrb",
+       "yre",
+       "yri",
+       "yrk",
+       "yrl",
+       "yrn",
+       "yrs",
+       "yrw",
+       "ysc",
+       "ysd",
+       "ysl",
+       "ysn",
+       "yso",
+       "ysp",
+       "ysr",
+       "yss",
+       "ysy",
+       "yta",
+       "ytl",
+       "ytp",
+       "ytw",
+       "yua",
+       "yub",
+       "yuc",
+       "yud",
+       "yue",
+       "yuf",
+       "yug",
+       "yui",
+       "yuj",
+       "yuk",
+       "yul",
+       "yum",
+       "yun",
+       "yup",
+       "yuq",
+       "yur",
+       "yut",
+       "yuu",
+       "yuw",
+       "yux",
+       "yuy",
+       "yuz",
+       "yva",
+       "yvt",
+       "ywa",
+       "ywl",
+       "ywn",
+       "ywq",
+       "ywr",
+       "ywt",
+       "ywu",
+       "yww",
+       "yyu",
+       "yyz",
+       "yzg",
+       "yzk",
+       "za",
+       "zaa",
+       "zab",
+       "zac",
+       "zad",
+       "zae",
+       "zaf",
+       "zag",
+       "zah",
+       "zai",
+       "zaj",
+       "zak",
+       "zal",
+       "zam",
+       "zao",
+       "zap",
+       "zaq",
+       "zar",
+       "zas",
+       "zat",
+       "zau",
+       "zav",
+       "zaw",
+       "zax",
+       "zay",
+       "zaz",
+       "zbc",
+       "zbe",
+       "zbl",
+       "zbt",
+       "zbw",
+       "zca",
+       "zch",
+       "zdj",
+       "zea",
+       "zeg",
+       "zeh",
+       "zen",
+       "zga",
+       "zgb",
+       "zgm",
+       "zgn",
+       "zgr",
+       "zh",
+       "zhb",
+       "zhd",
+       "zhi",
+       "zhn",
+       "zhw",
+       "zia",
+       "zib",
+       "zik",
+       "zil",
+       "zim",
+       "zin",
+       "zir",
+       "ziw",
+       "ziz",
+       "zka",
+       "zkb",
+       "zkg",
+       "zkh",
+       "zkk",
+       "zko",
+       "zkp",
+       "zkr",
+       "zkt",
+       "zku",
+       "zkv",
+       "zkz",
+       "zlj",
+       "zlm",
+       "zln",
+       "zlq",
+       "zma",
+       "zmb",
+       "zmc",
+       "zmd",
+       "zme",
+       "zmf",
+       "zmg",
+       "zmh",
+       "zmi",
+       "zmj",
+       "zmk",
+       "zml",
+       "zmm",
+       "zmn",
+       "zmo",
+       "zmp",
+       "zmq",
+       "zmr",
+       "zms",
+       "zmt",
+       "zmu",
+       "zmv",
+       "zmw",
+       "zmx",
+       "zmy",
+       "zmz",
+       "zna",
+       "zne",
+       "zng",
+       "znk",
+       "zns",
+       "zoc",
+       "zoh",
+       "zom",
+       "zoo",
+       "zoq",
+       "zor",
+       "zos",
+       "zpa",
+       "zpb",
+       "zpc",
+       "zpd",
+       "zpe",
+       "zpf",
+       "zpg",
+       "zph",
+       "zpi",
+       "zpj",
+       "zpk",
+       "zpl",
+       "zpm",
+       "zpn",
+       "zpo",
+       "zpp",
+       "zpq",
+       "zpr",
+       "zps",
+       "zpt",
+       "zpu",
+       "zpv",
+       "zpw",
+       "zpx",
+       "zpy",
+       "zpz",
+       "zqe",
+       "zra",
+       "zrg",
+       "zrn",
+       "zro",
+       "zrp",
+       "zrs",
+       "zsa",
+       "zsk",
+       "zsl",
+       "zsm",
+       "zsr",
+       "zsu",
+       "zte",
+       "ztg",
+       "ztl",
+       "ztm",
+       "ztn",
+       "ztp",
+       "ztq",
+       "zts",
+       "ztt",
+       "ztu",
+       "ztx",
+       "zty",
+       "zu",
+       "zua",
+       "zuh",
+       "zum",
+       "zun",
+       "zuy",
+       "zwa",
+       "zxx",
+       "zyb",
+       "zyg",
+       "zyj",
+       "zyn",
+       "zyp",
+       "zza",
+       "zzj",
+))
+
+COUNTRIES = set((
+       "AD",
+       "AE",
+       "AF",
+       "AG",
+       "AI",
+       "AL",
+       "AM",
+       "AO",
+       "AQ",
+       "AR",
+       "AS",
+       "AT",
+       "AU",
+       "AW",
+       "AX",
+       "AZ",
+       "BA",
+       "BB",
+       "BD",
+       "BE",
+       "BF",
+       "BG",
+       "BH",
+       "BI",
+       "BJ",
+       "BL",
+       "BM",
+       "BN",
+       "BO",
+       "BQ",
+       "BR",
+       "BS",
+       "BT",
+       "BV",
+       "BW",
+       "BY",
+       "BZ",
+       "CA",
+       "CC",
+       "CD",
+       "CF",
+       "CG",
+       "CH",
+       "CI",
+       "CK",
+       "CL",
+       "CM",
+       "CN",
+       "CO",
+       "CR",
+       "CU",
+       "CV",
+       "CW",
+       "CX",
+       "CY",
+       "CZ",
+       "DE",
+       "DJ",
+       "DK",
+       "DM",
+       "DO",
+       "DZ",
+       "EC",
+       "EE",
+       "EG",
+       "EH",
+       "ER",
+       "ES",
+       "ET",
+       "FI",
+       "FJ",
+       "FK",
+       "FM",
+       "FO",
+       "FR",
+       "GA",
+       "GB",
+       "GD",
+       "GE",
+       "GF",
+       "GG",
+       "GH",
+       "GI",
+       "GL",
+       "GM",
+       "GN",
+       "GP",
+       "GQ",
+       "GR",
+       "GS",
+       "GT",
+       "GU",
+       "GW",
+       "GY",
+       "HK",
+       "HM",
+       "HN",
+       "HR",
+       "HT",
+       "HU",
+       "ID",
+       "IE",
+       "IL",
+       "IM",
+       "IN",
+       "IO",
+       "IQ",
+       "IR",
+       "IS",
+       "IT",
+       "JE",
+       "JM",
+       "JO",
+       "JP",
+       "KE",
+       "KG",
+       "KH",
+       "KI",
+       "KM",
+       "KN",
+       "KP",
+       "KR",
+       "KW",
+       "KY",
+       "KZ",
+       "LA",
+       "LB",
+       "LC",
+       "LI",
+       "LK",
+       "LR",
+       "LS",
+       "LT",
+       "LU",
+       "LV",
+       "LY",
+       "MA",
+       "MC",
+       "MD",
+       "ME",
+       "MF",
+       "MG",
+       "MH",
+       "MK",
+       "ML",
+       "MM",
+       "MN",
+       "MO",
+       "MP",
+       "MQ",
+       "MR",
+       "MS",
+       "MT",
+       "MU",
+       "MV",
+       "MW",
+       "MX",
+       "MY",
+       "MZ",
+       "NA",
+       "NC",
+       "NE",
+       "NF",
+       "NG",
+       "NI",
+       "NL",
+       "NO",
+       "NP",
+       "NR",
+       "NU",
+       "NZ",
+       "OM",
+       "PA",
+       "PE",
+       "PF",
+       "PG",
+       "PH",
+       "PK",
+       "PL",
+       "PM",
+       "PN",
+       "PR",
+       "PS",
+       "PT",
+       "PW",
+       "PY",
+       "QA",
+       "RE",
+       "RO",
+       "RS",
+       "RU",
+       "RW",
+       "SA",
+       "SB",
+       "SC",
+       "SD",
+       "SE",
+       "SG",
+       "SH",
+       "SI",
+       "SJ",
+       "SK",
+       "SL",
+       "SM",
+       "SN",
+       "SO",
+       "SR",
+       "SS",
+       "ST",
+       "SV",
+       "SX",
+       "SY",
+       "SZ",
+       "TC",
+       "TD",
+       "TF",
+       "TG",
+       "TH",
+       "TJ",
+       "TK",
+       "TL",
+       "TM",
+       "TN",
+       "TO",
+       "TR",
+       "TT",
+       "TV",
+       "TW",
+       "TZ",
+       "UA",
+       "UG",
+       "UM",
+       "US",
+       "UY",
+       "UZ",
+       "VA",
+       "VC",
+       "VE",
+       "VG",
+       "VI",
+       "VN",
+       "VU",
+       "WF",
+       "WS",
+       "YE",
+       "YT",
+       "ZA",
+       "ZM",
+       "ZW",
+))
diff --git a/__version__.py b/__version__.py
new file mode 100644 (file)
index 0000000..92d92ba
--- /dev/null
@@ -0,0 +1,2 @@
+# Automatically generated, do not edit
+__version__ = '1.4'
diff --git a/config b/config
new file mode 100644 (file)
index 0000000..5631260
--- /dev/null
+++ b/config
@@ -0,0 +1,214 @@
+# -*- python -*-
+
+# Example configuration file for rpmlint.
+
+# This line is mandatory to access the configuration functions
+from Config import *
+
+# Additional paths to look for checks.
+# ------------------------------------
+
+#addCheckDir("~/mandrake/rpmlint")
+
+# Configure the checks if you don't want the default ones.
+# --------------------------------------------------------
+
+#addCheck("FHSCheck")
+#addCheck("BinariesCheck")
+
+# Configuration options used by the checks shipped with rpmlint.
+# The values in the commented out setOption() calls represent default
+# or typical example values for the option.
+# -------------------------------------------------------------------
+
+# Type: integer, default: -1 (less than 0 means disabled)
+#setOption("BadnessThreshold", -1)
+
+# When checking that various files that should be compressed are
+# indeed compressed, look for this filename extension (no dot here).
+# Type: string, default: "bz2"
+#setOption("CompressExtension", "bz2")
+
+# Exception list for dangling symlink checks.  The first in each pair
+# is a regexp, and the second the package in which the target of the
+# dangling symlink is shipped.
+# Type: tuple of lists, default: (['consolehelper$', 'usermode-consoleonly'])
+#setOption("DanglingSymlinkExceptions", ())
+
+# Value for the Distribution tag.
+# Type: string, default: "" (the empty string disables checking)
+#setOption("Distribution", "")
+
+# Base directory where to extract uninstalled packages while checking.
+# Type: string, default: tempfile.gettempdir()
+#setOption("ExtractDir", "/tmp")
+
+# Standard "needs" values for non-XDG legacy menu items.
+# Type: tuple of strings, default: ('gnome', 'icewm', 'kde', 'wmaker')
+#setOption("ExtraMenuNeeds", ('gnome', 'icewm', 'kde', 'wmaker'))
+
+# Words that must not exist in various tag values.
+# Type: regexp, default: '' ('' effectively disables this check)
+#setOption("ForbiddenWords", '')
+
+# Exceptions for hardcoded library paths.
+# Type: regexp, default: see DEFAULT_HARDCODED_LIB_PATH_EXCEPTIONS in SpecCheck
+#setOption("HardcodedLibPathExceptions", '/lib/modules/')
+
+# Accepted non-XDG legacy icon filenames.
+# Type: regexp, default: '.*\.png$'
+#setOption("IconFilename", '.*\.png$')
+
+# Paths in which non-XDG legacy icons should be installed.  The first
+# item in the tuples is a filesystem path, and the second the icon type.
+# Type: tuple of string tuples, default: see DEFAULT_ICON_PATH in MenuCheck
+#setOption("IconPath", ())
+
+# Disallowed dependencies.
+# Type: tuple of regexps, default: see DEFAULT_INVALID_REQUIRES in TagsCheck
+#setOption("InvalidRequires", ())
+
+# Strings to disallow in various URL tags.
+# Type: regexp, default: '' ('' effectively disables this check)
+#setOption("InvalidURL", '')
+
+# Whether to allow packaging kernel modules in non-kernel packages.
+# Type: boolean, default: True
+#setOption("KernelModuleRPMsOK", True)
+
+# Maximum line length for summaries and descriptions.
+# Type: integer, default: 79
+#setOption("MaxLineLength", 79)
+
+# Type: tuple of string,tuple lists, default: see DEFAULT_LAUNCHERS in MenuCheck
+#setOption("MenuLaunchers", ())
+
+# Names of packages to treat as "meta" ones.
+# Type: regexp, default: '^(bundle|task)-'
+#setOption("MetaPackageRegexp", '^(bundle|task)-')
+
+# Whether to enable checks that require networking.
+# Type: boolean, default: False
+#setOption("NetworkEnabled", False)
+
+# Timeout for network operations in seconds.
+# Type: integer, default: 10
+#setOption("NetworkTimeout", 10)
+
+# Value for the Packager tag.
+# Type: regexp, default: '' ('' effectively disables this check)
+#setOption("Packager", '')
+
+# Type: boolean, default: True
+#setOption("PerlVersionTrick", True)
+
+# Assumed default version of Python if one cannot be determined from files.
+# Type: string, default: None
+#setOption("PythonDefaultVersion", None)
+
+# Expected suffix in Release tags.
+# Type: regexp, default: '' ('' effectively disables this check)
+#setOption("ReleaseExtension", '')
+
+# Group tag for games.
+# Type: regexp, default: 'Games'
+#setOption("RpmGamesGroup", 'Games')
+
+# Doc files to which end of line and UTF-8 checks should not be applied.
+# Type: regexp, default: \.(?:rtf|x?html?|svg|ml[ily]?)$'
+#setOption("SkipDocsRegexp", '\.(?:rtf|x?html?|svg|ml[ily]?)$')
+
+# Standard OS groups.
+# Type: tuple of strings, default: see DEFAULT_STANDARD_GROUPS in FilesCheck
+#setOption("StandardGroups", ())
+
+# Standard OS users.
+# Type: tuple of strings, see DEFAULT_STANDARD_USERS in FilesCheck
+#setOption("StandardUsers", ())
+
+# List of directories considered to be system default library search paths.
+# Type: tuple of strings, default: see DEFAULT_SYSTEM_LIB_PATHS in BinariesCheck
+#setOption("SystemLibPaths", ('/lib', '/lib64', '/usr/lib', '/usr/lib64'))
+
+# Executables that must be compiled as position independent.
+# Type: regex, default: None
+#setOption("PieExecutables", '^/bin/(ping6?|su)$')
+
+# Whether to want default start/stop runlevels specified in init scripts.
+# Type: boolean, default: True
+#setOption("UseDefaultRunlevels", True)
+
+# Whether to use the Enchant spell checker (if available) for spell checking.
+# Type: boolean, default: True
+#setOption("UseEnchant", True)
+
+# Whether an explicit Epoch should always be specified.
+# Type: boolean, default: False
+#setOption("UseEpoch", False)
+
+# Whether jars should be indexed.
+# Type: boolean, default: True
+#setOption("UseIndexedJars", True)
+
+# Whether symlinks between directories should be relative.
+# Type: boolean, default: True
+#setOption("UseRelativeSymlinks", True)
+
+# Whether the UTF-8 character encoding should be used where applicable.
+# Type: boolean, default: autodetected from environment
+#setOption("UseUTF8", True)
+
+# Whether %changelog entries should contain a version.
+# Type: boolean, default: True
+#setOption("UseVersionInChangelog", True)
+
+# Whether init scripts must use /var/lock/subsys
+# Type: boolean, default: True
+#setOption("UseVarLockSubsys", True)
+
+# Architecture dependent paths in which packages are allowed to install files
+# even if they are all non-binary.
+# Type: regexp, default: see BinariesCheck
+#setOption("UsrLibBinaryException", '^/usr/lib(64)?/(perl|python|ruby)')
+
+# Value for the BuildHost tag.
+# Type: regexp, default '' ('' effectively disables this check)
+#setOption("ValidBuildHost", '')
+
+# Interpreters whose scriptlets are allowed to be empty.
+# Type: tuple of strings, default: ('/sbin/ldconfig',)
+#setOption("ValidEmptyShells", ('/sbin/ldconfig',))
+
+# Values for the Group tag.
+# Type: list of strings, default: extracted from GROUPS file shipped with rpm
+#setOption("ValidGroups", [])
+
+# Values for the License tag.
+# Type: tuple of strings, default: see DEFAULT_VALID_LICENSES in TagsCheck
+#setOption("ValidLicenses", ())
+
+# Values for non-XDG legacy menu item sections.
+# Type: tuple of strings, default: see DEFAULT_VALID_SECTIONS in MenuCheck
+#setOption("ValidMenuSections", ())
+
+# Package scriptlet interpreters.
+# Type: tuple of strings, default: see DEFAULT_VALID_SHELLS in PostCheck
+#setOption("ValidShells", ('/bin/sh', '/bin/bash'))
+
+# Permissions for files in source packages.
+# Type: tuple of modes, default: (0644, 0755)
+#setOption("ValidSrcPerms", (0644, 0755))
+
+# Value for the Vendor tag.
+# Type: string, default: "" ("" effectively disables this check)
+#setOption("Vendor", "")
+
+# Man page warning category, passed to groff -w while checking man pages.
+# See the groff(1) or troff(1) man pages for available categories.
+# Type: string, default: 'mac'
+#SetOption("ManWarningCategory", 'mac')
+
+# Output filters.
+# ---------------
+
+#addFilter("E: .* no-signature")
diff --git a/rpmdiff b/rpmdiff
new file mode 100755 (executable)
index 0000000..00c6787
--- /dev/null
+++ b/rpmdiff
@@ -0,0 +1,294 @@
+#!/usr/bin/python -tt
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006 Mandriva; 2009 Red Hat, Inc.; 2009 Ville Skyttä
+# Authors: Frederic Lepied, Florian Festi
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Library General Public License as published by
+# the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public License
+# along with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import getopt
+import itertools
+import os
+import site
+import stat
+import sys
+import tempfile
+
+import rpm
+
+if os.path.isdir("/usr/share/rpmlint"):
+    site.addsitedir("/usr/share/rpmlint")
+import Pkg
+
+
+class Rpmdiff:
+
+    # constants
+
+    TAGS = ( rpm.RPMTAG_NAME, rpm.RPMTAG_SUMMARY,
+             rpm.RPMTAG_DESCRIPTION, rpm.RPMTAG_GROUP,
+             rpm.RPMTAG_LICENSE, rpm.RPMTAG_URL,
+             rpm.RPMTAG_PREIN, rpm.RPMTAG_POSTIN,
+             rpm.RPMTAG_PREUN, rpm.RPMTAG_POSTUN,
+             rpm.RPMTAG_PRETRANS, rpm.RPMTAG_POSTTRANS)
+
+    PRCO = ( 'REQUIRES', 'PROVIDES', 'CONFLICTS', 'OBSOLETES')
+
+    #{fname : (size, mode, mtime, flags, dev, inode,
+    #          nlink, state, vflags, user, group, digest)}
+    __FILEIDX = [ ['S', 0],
+                  ['M', 1],
+                  ['5', 11],
+                  ['D', 4],
+                  ['N', 6],
+                  ['L', 7],
+                  ['V', 8],
+                  ['U', 9],
+                  ['G', 10],
+                  ['F', 3],
+                  ['T', 2] ]
+
+    DEPFORMAT = '%-12s%s %s %s %s'
+    FORMAT = '%-12s%s'
+
+    ADDED   = 'added'
+    REMOVED = 'removed'
+
+    # code starts here
+
+    def __init__(self, old, new, ignore=None):
+        self.result = []
+        self.ignore = ignore
+        if self.ignore is None:
+            self.ignore = []
+
+        FILEIDX = self.__FILEIDX
+        for tag in self.ignore:
+            for entry in FILEIDX:
+                if tag == entry[0]:
+                    entry[1] = None
+                    break
+
+        try:
+            old = self.__load_pkg(old).header
+            new = self.__load_pkg(new).header
+        except KeyError, e:
+            Pkg.warn(str(e))
+            sys.exit(2)
+
+        # Compare single tags
+        for tag in self.TAGS:
+            old_tag = old[tag]
+            new_tag = new[tag]
+            if old_tag != new_tag:
+                tagname = rpm.tagnames[tag]
+                if old_tag == None:
+                    self.__add(self.FORMAT, (self.ADDED, tagname))
+                elif new_tag == None:
+                    self.__add(self.FORMAT, (self.REMOVED, tagname))
+                else:
+                    self.__add(self.FORMAT, ('S.5.....', tagname))
+
+        # compare Provides, Requires, ...
+        for  tag in self.PRCO:
+            self.__comparePRCOs(old, new, tag)
+
+        # compare the files
+
+        old_files_dict = self.__fileIteratorToDict(old.fiFromHeader())
+        new_files_dict = self.__fileIteratorToDict(new.fiFromHeader())
+        files = list(set(itertools.chain(old_files_dict.iterkeys(),
+                                         new_files_dict.iterkeys())))
+        files.sort()
+
+        for f in files:
+            diff = False
+
+            old_file = old_files_dict.get(f)
+            new_file = new_files_dict.get(f)
+
+            if not old_file:
+                self.__add(self.FORMAT, (self.ADDED, f))
+            elif not new_file:
+                self.__add(self.FORMAT, (self.REMOVED, f))
+            else:
+                format = ''
+                for entry in FILEIDX:
+                    if entry[1] != None and \
+                            old_file[entry[1]] != new_file[entry[1]]:
+                        format = format + entry[0]
+                        diff = True
+                    else:
+                        format = format + '.'
+                if diff:
+                    self.__add(self.FORMAT, (format, f))
+
+    # return a report of the differences
+    def textdiff(self):
+        return '\n'.join((format % data for format, data in self.result))
+
+    # do the two rpms differ
+    def differs(self):
+        return bool(self.result)
+
+    # add one differing item
+    def __add(self, format, data):
+        self.result.append((format, data))
+
+    # load a package from a file or from the installed ones
+    def __load_pkg(self, name, tmpdir = tempfile.gettempdir()):
+        try:
+            st = os.stat(name)
+            if stat.S_ISREG(st[stat.ST_MODE]):
+                return Pkg.Pkg(name, tmpdir)
+        except (OSError, TypeError):
+            pass
+        inst = Pkg.getInstalledPkgs(name)
+        if not inst:
+            raise KeyError("No installed packages by name %s" % name)
+        if len(inst) > 1:
+            raise KeyError("More than one installed packages by name %s" % name)
+        return inst[0]
+
+    # output the right string according to RPMSENSE_* const
+    def sense2str(self, sense):
+        s = ""
+        for tag, char in ((rpm.RPMSENSE_LESS, "<"),
+                          (rpm.RPMSENSE_GREATER, ">"),
+                          (rpm.RPMSENSE_EQUAL, "=")):
+            if sense & tag:
+                s += char
+        return s
+
+    # output the right requires string according to RPMSENSE_* const
+    def req2str(self, req):
+        s = "REQUIRES"
+        # we want to use 64 even with rpm versions that define RPMSENSE_PREREQ
+        # as 0 to get sane results when comparing packages built with an old
+        # (64) version and a new (0) one
+        if req & (rpm.RPMSENSE_PREREQ or 64):
+            s = "PREREQ"
+
+        ss = []
+        if req & rpm.RPMSENSE_SCRIPT_PRE:
+            ss.append("pre")
+        elif req & rpm.RPMSENSE_SCRIPT_POST:
+            ss.append("post")
+        elif req & rpm.RPMSENSE_SCRIPT_PREUN:
+            ss.append("preun")
+        elif req & rpm.RPMSENSE_SCRIPT_POSTUN:
+            ss.append("postun")
+        elif req & getattr(rpm, "RPMSENSE_PRETRANS", 1 << 7): # rpm >= 4.9.0
+            ss.append("pretrans")
+        elif req & getattr(rpm, "RPMSENSE_POSTTRANS", 1 << 5): # rpm >= 4.9.0
+            ss.append("posttrans")
+        if ss:
+            s += "(%s)" % ",".join(ss)
+
+        return s
+
+    # compare Provides, Requires, Conflicts, Obsoletes
+    def __comparePRCOs(self, old, new, name):
+        oldflags = old[name[:-1]+'FLAGS']
+        newflags = new[name[:-1]+'FLAGS']
+        # fix buggy rpm binding not returning list for single entries
+        if not isinstance(oldflags, list): oldflags = [ oldflags ]
+        if not isinstance(newflags, list): newflags = [ newflags ]
+
+        o = zip(old[name], oldflags, old[name[:-1]+'VERSION'])
+        n = zip(new[name], newflags, new[name[:-1]+'VERSION'])
+
+        # filter self provides, TODO: self %name(%_isa) as well
+        if name == 'PROVIDES':
+            oldE = old['epoch'] is not None and str(old['epoch'])+":" or ""
+            oldNV = (old['name'], rpm.RPMSENSE_EQUAL,
+                     "%s%s-%s" % (oldE, old['version'], old['release']))
+            newE = new['epoch'] is not None and str(new['epoch'])+":" or ""
+            newNV = (new['name'], rpm.RPMSENSE_EQUAL,
+                     "%s%s-%s" % (newE, new['version'], new['release']))
+            o = [entry for entry in o if entry != oldNV]
+            n = [entry for entry in n if entry != newNV]
+
+        for oldentry in o:
+            if not oldentry in n:
+                namestr = name
+                if namestr == 'REQUIRES':
+                    namestr = self.req2str(oldentry[1])
+                self.__add(self.DEPFORMAT,
+                           (self.REMOVED, namestr, oldentry[0],
+                            self.sense2str(oldentry[1]), oldentry[2]))
+        for newentry in n:
+            if not newentry in o:
+                namestr = name
+                if namestr == 'REQUIRES':
+                    namestr = self.req2str(newentry[1])
+                self.__add(self.DEPFORMAT,
+                           (self.ADDED, namestr, newentry[0],
+                            self.sense2str(newentry[1]), newentry[2]))
+
+    def __fileIteratorToDict(self, fi):
+        result = {}
+        for filedata in fi:
+            result[filedata[0]] = filedata[1:]
+        return result
+
+def _usage(exit=1):
+    print ('''Usage: %s [<options>] <old package> <new package>
+Options:
+  -h, --help     Output this message and exit
+  -i, --ignore   File property to ignore when calculating differences (may be
+                 used multiple times); valid values are: S (size), M (mode),
+                 5 (checksum), D (device), N (inode), L (number of links),
+                 V (vflags), U (user), G (group), F (digest), T (time)''' \
+        % sys.argv[0])
+    sys.exit(exit)
+
+def main():
+
+    ignore_tags = []
+    try:
+        opts, args = getopt.getopt(sys.argv[1:],
+                                   "hti:", ["help", "ignore-times", "ignore="])
+    except getopt.GetoptError, e:
+        Pkg.warn("Error: %s" % e)
+        _usage()
+
+    for option, argument in opts:
+        if option in ("-h", "--help"):
+            _usage(0)
+        if option in ("-t", "--ignore-times"):
+            # deprecated; --ignore=T should be used instead
+            ignore_tags.append("T")
+        if option in ("-i", "--ignore"):
+            ignore_tags.append(argument)
+
+    if len(args) != 2:
+        _usage()
+
+    d = Rpmdiff(args[0], args[1], ignore=ignore_tags)
+    textdiff = d.textdiff()
+    if textdiff:
+        print (textdiff)
+    sys.exit(int(d.differs()))
+
+if __name__ == '__main__':
+    main()
+
+# rpmdiff ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/rpmlint b/rpmlint
new file mode 100755 (executable)
index 0000000..4838014
--- /dev/null
+++ b/rpmlint
@@ -0,0 +1,387 @@
+#!/usr/bin/python -ttOu
+# -*- coding: utf-8 -*-
+#############################################################################
+# File          : rpmlint
+# Package       : rpmlint
+# Author        : Frederic Lepied
+# Created on    : Mon Sep 27 19:20:18 1999
+# Version       : $Id: rpmlint 1870 2011-06-18 09:19:24Z scop $
+# Purpose       : main entry point: process options, load the checks and run
+#                 the checks.
+#############################################################################
+
+import getopt
+import glob
+import imp
+import locale
+import os
+import re
+import stat
+import sys
+import tempfile
+
+# 1 instead of 0 here because we want the script dir to be looked up first,
+# e.g. for in-place from tarball or SCM checkout
+sys.path.insert(1, '/usr/share/rpmlint')
+
+# Do not import anything that initializes its global variables from
+# Config at load time here (or anything that imports such a thing),
+# that results in those variables initialized before config files are
+# loaded which is too early - settings from config files won't take
+# place for those variables.
+
+from Filter import badnessScore, badnessThreshold, printAllReasons, \
+     printDescriptions, printInfo, printed_messages, setRawOut
+import AbstractCheck
+import Config
+import Pkg
+
+
+_default_user_conf = '%s/rpmlint' % \
+    (os.environ.get('XDG_CONFIG_HOME') or '~/.config')
+
+# Print usage information
+def usage(name):
+    print ('''usage: %s [<options>] <rpm files|installed packages|specfiles|dirs>
+  options:
+\t[-i|--info]
+\t[-I|--explain <messageid>]
+\t[-c|--check <check>]
+\t[-a|--all]
+\t[-C|--checkdir <checkdir>]
+\t[-h|--help]
+\t[-v|--verbose]
+\t[-E|--extractdir <dir>]
+\t[-V|--version]
+\t[-n|--noexception]
+\t[   --rawout <file>]
+\t[-f|--file <user config file to use instead of %s]
+\t[-o|--option <key value>]''' \
+        % (name, _default_user_conf))
+
+# Print version information
+def printVersion():
+    print ('rpmlint version %s Copyright (C) 1999-2007 Frederic Lepied, Mandriva' % Config.__version__)
+
+def loadCheck(name):
+    '''Load a (check) module by its name, unless it is already loaded.'''
+    # Avoid loading more than once (initialization costs)
+    loaded = sys.modules.get(name)
+    if loaded:
+        return loaded
+    (fobj, pathname, description) = imp.find_module(name)
+    try:
+        imp.load_module(name, fobj, pathname, description)
+    finally:
+        fobj.close()
+
+#############################################################################
+# main program
+#############################################################################
+def main():
+
+    locale.setlocale(locale.LC_COLLATE, '')
+
+    # Add check dirs to the front of load path
+    sys.path[0:0] = Config.checkDirs()
+
+    # Load all checks
+    for c in Config.allChecks():
+        loadCheck(c)
+
+    packages_checked = 0
+    specfiles_checked = 0
+    do_spec_check = 'SpecCheck' in Config.allChecks()
+    if do_spec_check:
+        # See comments in "top level import section" for why this isn't
+        # imported earlier.
+        import SpecCheck
+
+    try:
+        # Loop over all file names given in arguments
+        dirs = []
+        for arg in args:
+            pkgs = []
+            isfile = False
+            try:
+                if arg == "-":
+                    arg = "(standard input)"
+                    # Short-circuit stdin spec file check
+                    if do_spec_check:
+                        stdin = sys.stdin.readlines()
+                        if not stdin:
+                            continue
+                        pkg = Pkg.FakePkg(arg)
+                        check = SpecCheck.SpecCheck()
+                        check.verbose = verbose
+                        check.check_spec(pkg, None, spec_lines=stdin)
+                        pkg.cleanup()
+                        specfiles_checked += 1
+                    continue
+
+                try:
+                    st = os.stat(arg)
+                    isfile = True
+                    if stat.S_ISREG(st[stat.ST_MODE]):
+                        if arg.endswith(".spec"):
+                            if do_spec_check:
+                                # Short-circuit spec file checks
+                                pkg = Pkg.FakePkg(arg)
+                                check = SpecCheck.SpecCheck()
+                                check.verbose = verbose
+                                check.check_spec(pkg, arg)
+                                pkg.cleanup()
+                                specfiles_checked += 1
+                        elif "/" in arg or arg.endswith(".rpm") or \
+                                arg.endswith(".spm"):
+                            pkgs.append(Pkg.Pkg(arg, extract_dir))
+                        else:
+                            raise OSError
+
+                    elif stat.S_ISDIR(st[stat.ST_MODE]):
+                        dirs.append(arg)
+                        continue
+                    else:
+                        raise OSError
+                except OSError:
+                    ipkgs = Pkg.getInstalledPkgs(arg)
+                    if not ipkgs:
+                        Pkg.warn(
+                            '(none): E: no installed packages by name %s' % arg)
+                    else:
+                        ipkgs.sort(key = lambda x: locale.strxfrm(
+                                x.header.sprintf("%{NAME}.%{ARCH}")))
+                        pkgs.extend(ipkgs)
+            except KeyboardInterrupt:
+                if isfile:
+                    arg = os.path.abspath(arg)
+                Pkg.warn(
+                    '(none): E: interrupted, exiting while reading %s' % arg)
+                sys.exit(2)
+            except Exception, e:
+                if isfile:
+                    arg = os.path.abspath(arg)
+                Pkg.warn('(none): E: error while reading %s: %s' % (arg, e))
+                pkgs = []
+                continue
+
+            for pkg in pkgs:
+                runChecks(pkg)
+                packages_checked += 1
+
+        for dname in dirs:
+            try:
+                for path, dirs, files in os.walk(dname):
+                    for fname in files:
+                        fname = os.path.abspath(os.path.join(path, fname))
+                        try:
+                            if fname.endswith('.rpm') or \
+                               fname.endswith('.spm'):
+                                pkg = Pkg.Pkg(fname, extract_dir)
+                                runChecks(pkg)
+                                packages_checked += 1
+
+                            elif do_spec_check and fname.endswith('.spec'):
+                                pkg = Pkg.FakePkg(fname)
+                                check = SpecCheck.SpecCheck()
+                                check.verbose = verbose
+                                check.check_spec(pkg, fname)
+                                pkg.cleanup()
+                                specfiles_checked += 1
+
+                        except KeyboardInterrupt:
+                            Pkg.warn('(none): E: interrupted, exiting while ' +
+                                     'reading %s' % fname)
+                            sys.exit(2)
+                        except Exception, e:
+                            Pkg.warn(
+                                '(none): E: while reading %s: %s' % (fname, e))
+                            continue
+            except Exception, e:
+                Pkg.warn(
+                    '(none): E: error while reading dir %s: %s' % (dname, e))
+                continue
+
+        if printAllReasons():
+            Pkg.warn('(none): E: badness %d exceeds threshold %d, aborting.' %
+                     (badnessScore(), badnessThreshold()))
+            sys.exit(66)
+
+    finally:
+        print "%d packages and %d specfiles checked; %d errors, %d warnings." \
+              % (packages_checked, specfiles_checked,
+                 printed_messages["E"], printed_messages["W"])
+
+    if printed_messages["E"] > 0:
+        sys.exit(64)
+    sys.exit(0)
+
+def runChecks(pkg):
+
+    try:
+        if verbose:
+            printInfo(pkg, 'checking')
+
+        for name in Config.allChecks():
+            check = AbstractCheck.AbstractCheck.known_checks.get(name)
+            if check:
+                check.verbose = verbose
+                check.check(pkg)
+            else:
+                Pkg.warn('(none): W: unknown check %s, skipping' % name)
+    finally:
+        pkg.cleanup()
+
+#############################################################################
+#
+#############################################################################
+
+sys.argv[0] = os.path.basename(sys.argv[0])
+
+# parse options
+try:
+    (opt, args) = getopt.getopt(sys.argv[1:],
+                              'iI:c:C:hVvanE:f:o:',
+                              ['info',
+                               'explain=',
+                               'check=',
+                               'checkdir=',
+                               'help',
+                               'version',
+                               'verbose',
+                               'all',
+                               'noexception',
+                               'extractdir=',
+                               'file=',
+                               'option=',
+                               'rawout=',
+                               ])
+except getopt.GetoptError, e:
+    Pkg.warn("%s: %s" % (sys.argv[0], e))
+    usage(sys.argv[0])
+    sys.exit(1)
+
+# process options
+checkdir = '/usr/share/rpmlint'
+checks = []
+verbose = False
+extract_dir = None
+conf_file = _default_user_conf
+if not os.path.exists(os.path.expanduser(conf_file)):
+    # deprecated backwards compatibility with < 0.88
+    conf_file = '~/.rpmlintrc'
+info_error = set()
+
+# load global config files
+configs = glob.glob('/etc/rpmlint/*config')
+configs.sort()
+
+# Was rpmlint invoked as a prefixed variant?
+m = re.match(r"(?P<prefix>[\w-]+)-rpmlint(\.py)?", sys.argv[0])
+if m:
+    # Okay, we're a prefixed variant. Look for the variant config.
+    # If we find it, use it. If not, fallback to the default.
+    prefix = m.group('prefix')
+    if os.path.isfile('/usr/share/rpmlint/config.%s' % prefix):
+        configs.insert(0, '/usr/share/rpmlint/config.%s' % prefix)
+    else:
+        configs.insert(0, '/usr/share/rpmlint/config')
+else:
+    configs.insert(0, '/usr/share/rpmlint/config')
+
+for f in configs:
+    try:
+        execfile(f)
+    except IOError:
+        pass
+    except Exception, E:
+        Pkg.warn('(none): W: error loading %s, skipping: %s' % (f, E))
+# pychecker fix
+del f
+
+config_overrides = {}
+
+# process command line options
+for o in opt:
+    if o[0] in ('-c', '--check'):
+        checks.append(o[1])
+    elif o[0] in ('-i', '--info'):
+        Config.info = True
+    elif o[0] in ('-I', '--explain'):
+        # split by comma for deprecated backwards compatibility with < 1.2
+        info_error.update(o[1].split(','))
+    elif o[0] in ('-h', '--help'):
+        usage(sys.argv[0])
+        sys.exit(0)
+    elif o[0] in ('-C', '--checkdir'):
+        Config.addCheckDir(o[1])
+    elif o[0] in ('-v', '--verbose'):
+        verbose = True
+    elif o[0] in ('-V', '--version'):
+        printVersion()
+        sys.exit(0)
+    elif o[0] in ('-E', '--extractdir'):
+        extract_dir = o[1]
+        Config.setOption('ExtractDir', extract_dir)
+    elif o[0] in ('-n', '--noexception'):
+        Config.no_exception = True
+    elif o[0] in ('-a', '--all'):
+        if '*' not in args:
+            args.append('*')
+    elif o[0] in ('-f', '--file'):
+        conf_file = o[1]
+    elif o[0] in ('-o', '--option'):
+        kv = o[1].split(None, 1)
+        if len(kv) == 1:
+            config_overrides[kv[0]] = None
+        else:
+            config_overrides[kv[0]] = eval(kv[1])
+    elif o[0] in ('--rawout',):
+        setRawOut(o[1])
+
+# load user config file
+try:
+    execfile(os.path.expanduser(conf_file))
+except IOError:
+    pass
+except Exception,E:
+    Pkg.warn('(none): W: error loading %s, skipping: %s' % (conf_file, E))
+
+# apply config overrides
+for key, value in config_overrides.items():
+    Config.setOption(key, value)
+
+if not extract_dir:
+    extract_dir = Config.getOption('ExtractDir', tempfile.gettempdir())
+
+if info_error:
+    Config.info = True
+    sys.path[0:0] = Config.checkDirs()
+    for c in checks:
+        Config.addCheck(c)
+    for c in Config.allChecks():
+        loadCheck(c)
+    for e in sorted(info_error):
+        print "%s:" % e
+        printDescriptions(e)
+    sys.exit(0)
+
+# if no argument print usage
+if not args:
+    usage(sys.argv[0])
+    sys.exit(1)
+
+if __name__ == '__main__':
+    if checks:
+        Config.resetChecks()
+        for check in checks:
+            Config.addCheck(check)
+    main()
+
+# rpmlint ends here
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/rpmlint.1 b/rpmlint.1
new file mode 100644 (file)
index 0000000..7e7794c
--- /dev/null
+++ b/rpmlint.1
@@ -0,0 +1,115 @@
+.TH RPMLINT "1" "April 2011" "rpmlint" "User Commands"
+.SH NAME
+rpmlint \- check common problems in rpm packages
+.SH SYNOPSIS
+\fBrpmlint\fR [\fIOPTION\fR]... [\fIFILE\fR|\fIPACKAGE\fR]...
+.SH DESCRIPTION
+\fBrpmlint\fR is a tool for checking common errors in rpm packages.
+It can be used to test individual packages and spec files before
+uploading or to check an entire distribution.  By default all
+applicable checks are processed but specific checks can be performed
+by using command line parameters.
+
+\fIFILE\fR can be a rpm package file, a spec file, or a directory.  In
+case of a directory, it is recursively searched for rpm and spec files
+to check.  The special value \fB\-\fR results in standard input being
+read and treated as (single) spec file content.
+\fIPACKAGE\fR is the name of an installed package or a
+.BR glob (7)
+pattern to match installed packages, unless a file by that name exists.
+.TP
+\fB\-i\fR, \fB\-\-info\fR
+Display explanations for reported messages.
+.TP
+\fB-I\fR, \fB\-\-explain\fR=\fImessageid\fR
+Display explanations for the specified message identifiers and exit.
+This option may be given multiple times.
+.TP
+\fB\-c\fR, \fB\-\-check\fR=\fIcheck\fR
+Run only the specified check.  This option may be given multiple times
+to specify multiple checks to run.  \fIcheck\fR is the name of the Python
+module (as it would be given to Python's import statement) containing the
+check.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+Check all installed packages.
+.TP
+\fB\-C\fR, \fB\-\-checkdir\fR=\fIdir\fR
+Insert \fIdir\fR to the front of the list of paths to load checks
+from, unless it is already in the list.  The default list of check
+dirs typically contains only /usr/share/rpmlint.  Directories in the
+check dirs list are also inserted to the front of the list of paths to
+load Python modules from when the check process begins.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display summary of command line options and exit.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Operate in verbose mode.
+.TP
+\fB\-E\fR, \fB\-\-extractdir\fR=\fIdir\fR
+Base directory for extracted temporary files, default is what Python's
+tempfile.gettempdir() returns.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-n\fR, \fB\-\-noexception\fR
+Ignore output filters.
+.TP
+\fB\-\-rawout\fR=\fIfile\fR
+Write unfiltered output to \fIfile\fR.
+.TP
+\fB\-f\fR, \fB\-\-file\fR=\fIconffile\fR
+Load user configuration from the specified file, default is
+$XDG_CONFIG_HOME/rpmlint (~/.config/rpmlint if $XDG_CONFIG_HOME is
+empty or not set).
+.TP
+\fB\-o\fR, \fB\-\-option\fR=\fIvalue\fR
+Override a configuration option.  \fIvalue\fR is a whitespace separated string,
+first word of which is the option name to set, and the Python eval() return
+value for the rest is set as the value for the option.  Passing only an option
+name is treated as if None was passed as its value.  See the file "config"
+shipped with rpmlint for the list of configuration options and their types.
+For example:
+ \-o "NetworkEnabled True"
+ \-o "Distribution \(aqMy favorite distro\(aq"
+ \-o "MaxLineLength 80"
+ \-o "ValidShells (\(aq/bin/sh\(aq, \(aq/bin/bash\(aq)"
+.SH CAVEATS
+All checks do not apply to all argument types.  For best check
+coverage, run rpmlint on all source and binary packages your build
+produces.  The set of checks rpmlint runs on source packages is a
+superset of the one for plain specfiles, the set of checks run for
+installed binary packages is a superset of the one for uninstalled
+binary package files, and the source and binary package check sets are
+quite different.
+.SH FILES
+.TP
+\fB/usr/share/rpmlint/config\fR, \fB/usr/share/rpmlint/config.*\fR
+Built-in configuration.  When invoked as \fIsomeprefix\fR-rpmlint,
+/usr/share/rpmlint/config.\fIsomeprefix\fR is used if it exists,
+otherwise /usr/share/rpmlint/config.
+.TP
+\fB/etc/rpmlint/*config\fR
+System wide configuration.
+.TP
+\fB$XDG_CONFIG_HOME/rpmlint\fR or \fB~/.config/rpmlint\fR
+User configuration.
+.SH EXIT CODES
+.IP 0
+No errors.
+.IP 1
+Unspecified error.
+.IP 2
+Interrupted.
+.IP 64
+One or more error message printed.
+.IP 66
+Badness threshold exceeded.
+.SH AUTHOR
+Originally written by Frédéric Lepied, see the file AUTHORS for (probably
+incomplete) list of additional contributors.
+.SH COPYRIGHT
+This program is licensed under the GNU General Public License, see the
+file COPYING included in the distribution archive.
diff --git a/rpmlint.bash-completion b/rpmlint.bash-completion
new file mode 100644 (file)
index 0000000..189582f
--- /dev/null
@@ -0,0 +1,94 @@
+# bash-completion add-on for rpmlint
+# http://bash-completion.alioth.debian.org/
+
+_rpmlint_installed_packages()
+{
+    if declare -F _rpm_installed_packages &>/dev/null ; then
+        _rpm_installed_packages
+    elif declare -F _xfunc &>/dev/null ; then
+        # bash-completion 1.90+ dynamic loading
+        _xfunc rpm _rpm_installed_packages
+    fi
+}
+
+_rpmlint()
+{
+    COMPREPLY=()
+    local cur=$2 # for _rpm_installed_packages, _filedir
+
+    case $3 in
+        -C|--checkdir|-E|--extractdir)
+            _filedir -d
+            return 0
+            ;;
+        -f|--file|--rawout)
+            _filedir
+            return 0
+            ;;
+        -c|--check)
+            # should take --checkdir, python path, inheritance into account...
+            COMPREPLY=( $( compgen -W \
+                "$( command ls /usr/share/rpmlint/*Check.py* 2>/dev/null |
+                    sed -e '/^AbstractCheck/d' \
+                        -e 's|.*/\([^/.]*\)\.py.*|\1|' )" -- "$cur" ) )
+            return 0
+            ;;
+        -I|--explain)
+            # should take --checkdir into account...
+            COMPREPLY=( $( compgen -W "$( sed -e '1,/^addDetails/d' \
+                -ne "s/^'\([^'[:space:]]\{1,\}\)',\$/\1/p" \
+                /usr/share/rpmlint/*Check.py 2>/dev/null )" -- "$cur" ) ) #'
+            return 0
+            ;;
+        -o|--option)
+            # argument required but no completions available
+            return 0
+            ;;
+    esac
+
+    if [[ "$cur" == -* ]]; then
+        COMPREPLY=( $( compgen -W '--info --explain --check --all --checkdir
+                        --help --verbose --extractdir --version --noexception
+                        --file --option' -- "$cur" ) )
+    else
+        # Installed packages completion is potentially slow, do it only if $cur
+        # does not look like a path.
+        [[ $cur != */* && $cur != [.~]* ]] && _rpmlint_installed_packages
+        _filedir @([rs]pm|spec)
+    fi
+}
+complete -F _rpmlint -o filenames rpmlint
+
+_rpmdiff()
+{
+    COMPREPLY=()
+    local cur=$2 # for _rpm_installed_packages, _filedir
+
+    case $3 in
+        -i|--ignore)
+            COMPREPLY=( $( compgen -W 'S M 5 D N L V U G F T' -- "$cur" ) )
+            return 0
+            ;;
+        -h|--help)
+            return 0
+            ;;
+    esac
+
+    if [[ "$cur" == -* ]]; then
+        COMPREPLY=( $( compgen -W '--help --ignore' -- "$cur" ) )
+    else
+        # Installed packages completion is potentially slow, do it only if $cur
+        # does not look like a path.
+        [[ $cur != */* && $cur != [.~]* ]] && _rpmlint_installed_packages
+        _filedir [rs]pm
+    fi
+}
+complete -F _rpmdiff -o filenames rpmdiff
+
+# Local variables:
+# mode: shell-script
+# sh-basic-offset: 4
+# sh-indent-comment: t
+# indent-tabs-mode: nil
+# End:
+# ex: ts=4 sw=4 et filetype=sh
diff --git a/test.sh b/test.sh
new file mode 100755 (executable)
index 0000000..0c0d202
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+export PYTHONPATH=$(pwd)/tools:$(pwd)
+export TESTPATH="$(pwd)/test/"
+
+for i in $TESTPATH/test.*.py; do
+    python $i
+    RET=$?
+    if [ $RET -ne 0 ]; then
+        exit $RET
+    fi
+done
+
+echo "Check that rpmlint executes with no unexpected errors"
+python ./rpmlint -C $(pwd) test/*.rpm test/*.spec >/dev/null
+rc=$?
+test $rc -eq 0 -o $rc -eq 64
diff --git a/test/PamCheck-0.1-1.i586.rpm b/test/PamCheck-0.1-1.i586.rpm
new file mode 100644 (file)
index 0000000..2606df9
Binary files /dev/null and b/test/PamCheck-0.1-1.i586.rpm differ
diff --git a/test/SpecCheck.spec b/test/SpecCheck.spec
new file mode 100644 (file)
index 0000000..14f4e6f
--- /dev/null
@@ -0,0 +1,62 @@
+Name:           SpecCheck
+Version:        0
+Release:        0
+Summary:        None here
+
+Group:          Undefined
+License:        GPLv2
+URL:            http://rpmlint.zarb.org/#%{name}
+Source0:        Source0.tar.gz
+Patch:          Patch.patch
+Patch1:         Patch1.patch
+Patch2:         Patch2.patch
+Patch3:         Patch3.patch
+Patch4:         Patch4.patch
+Patch5:         Patch5.patch
+BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+Provides:       unversioned-provides, versioned-provides = 1.0
+Obsoletes:      versioned-obsoletes < 2.0
+Obsoletes:      unversioned-obsoletes
+
+%description
+SpecCheck test.
+
+%package        noarch-sub
+Summary:        Noarch subpackage
+Group:          Undefined
+BuildArch:      noarch
+
+%description    noarch-sub
+Noarch subpackage test.
+
+
+%prep
+%setup -q
+%patch1
+%patch
+%patch -P 2 -P 4
+sed -e s/foo/bar/ %{PATCH5} | %{__patch} -p1
+
+
+%build
+# %configure
+# %%%
+
+
+%install
+rm -rf $RPM_BUILD_ROOT
+
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+
+%files
+%defattr(-,root,root,-)
+%{_libdir}/foo
+
+%files noarch-sub
+%defattr(-,root,root,-)
+
+
+%changelog
diff --git a/test/test.PamCheck.py b/test/test.PamCheck.py
new file mode 100644 (file)
index 0000000..08d1cff
--- /dev/null
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+import unittest
+import Testing
+import PamCheck
+# FIXME harcode
+
+class TestPamCheck(unittest.TestCase):
+    def setUp(self):
+        self.pkg = Testing.getTestedPackage('PamCheck')
+        Testing.startTest()
+    def tearDown(self):
+        self.pkg.cleanup()
+    def testcheck(self):
+        PamCheck.check.check(self.pkg)
+        self.assertEqual( Testing.getOutput(), ['PamCheck.i586: E: use-old-pam-stack /etc/pam.d/PamCheck (line 1)'])
+
+# enjoy \o/
+if __name__ == '__main__':
+    unittest.main()
+
+#print Testing.getOutput()
diff --git a/test/test.Pkg.py b/test/test.Pkg.py
new file mode 100644 (file)
index 0000000..6d061d4
--- /dev/null
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+import rpm
+import unittest
+import Testing
+import Pkg
+
+class TestPkg(unittest.TestCase):
+    def setUp(self):
+        Testing.startTest()
+
+    def test_parse_deps(self):
+        for (arg, exp) in (
+            ("a, b < 1.0 c = 5:2.0-3 d",
+             [("a", 0, (None, None, None)),
+              ("b", rpm.RPMSENSE_LESS, (None, "1.0", None)),
+              ("c", rpm.RPMSENSE_EQUAL, ("5", "2.0", "3")),
+              ("d", 0, (None, None, None))]),
+            ):
+            self.assertEqual(Pkg.parse_deps(arg), exp)
+
+    def test_rangeCompare(self):
+        for (req, prov) in (
+            (("foo", rpm.RPMSENSE_LESS, (None, "1.0", None)),
+             ("foo", rpm.RPMSENSE_EQUAL, ("1", "0.5", None))),
+            ):
+            self.assertFalse(Pkg.rangeCompare(req, prov))
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/test.SpecCheck.py b/test/test.SpecCheck.py
new file mode 100644 (file)
index 0000000..fadf970
--- /dev/null
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+import re
+import unittest
+import Testing
+import SpecCheck
+
+class TestSpecCheck(unittest.TestCase):
+    def setUp(self):
+        self.pkg = Testing.getTestedSpecPackage('SpecCheck')
+        Testing.startTest()
+    def testcheck(self):
+        SpecCheck.check.check_spec(self.pkg, self.pkg.name)
+        out = "\n".join(Testing.getOutput())
+        self.assertTrue("patch-not-applied Patch3" in out)
+        self.assertFalse(re.search("patch-not-applied Patch\\b", out))
+        self.assertFalse(re.search("patch-not-applied Patch[01245]", out))
+        self.assertTrue("libdir-macro-in-noarch-package" not in out)
+        self.assertTrue(len(re.findall("macro-in-comment", out)) == 1)
+        self.assertTrue("unversioned-explicit-provides unversioned-provides"
+                        in out)
+        self.assertTrue("unversioned-explicit-provides versioned-provides"
+                        not in out)
+        self.assertTrue("unversioned-explicit-obsoletes unversioned-obsoletes"
+                        in out)
+        self.assertTrue("unversioned-explicit-obsoletes versioned-obsoletes"
+                        not in out)
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tools/Testing.py b/tools/Testing.py
new file mode 100644 (file)
index 0000000..f111597
--- /dev/null
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import glob
+import os
+import tempfile
+
+import Pkg
+
+
+currently_testing = 0
+output = []
+
+def isTest():
+    return currently_testing
+
+def startTest():
+    global currently_testing
+    global output
+    output = []
+    currently_testing = 1
+
+def addOutput(s):
+    global output
+    output.append(s)
+
+def getOutput():
+    global output
+    return output
+
+def getTestedPackage(name):
+    pkg_path = glob.glob(os.environ['TESTPATH'] + '/' + name + '-*.rpm')[0]
+    return Pkg.Pkg(pkg_path, tempfile.gettempdir())
+
+def getTestedSpecPackage(name):
+    pkg_path = glob.glob(os.environ['TESTPATH'] + '/' + name + '.spec')[0]
+    return Pkg.FakePkg(pkg_path)
+
+# Local variables:
+# indent-tabs-mode: nil
+# py-indent-offset: 4
+# End:
+# ex: ts=4 sw=4 et
diff --git a/tools/generate-isocodes.py b/tools/generate-isocodes.py
new file mode 100755 (executable)
index 0000000..97b5422
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+
+# Generate ISO codes for use with e.g. locale subdir checks
+# http://alioth.debian.org/projects/pkg-isocodes/
+
+import sys
+from xml.etree.ElementTree import ElementTree
+
+
+langs = set()
+countries = set()
+
+# 2-letter country codes
+tree = ElementTree(file = "/usr/share/xml/iso-codes/iso_3166.xml")
+for entry in tree.findall("iso_3166_entry"):
+    countries.add(entry.get("alpha_2_code"))
+
+# 2-letter codes
+tree = ElementTree(file = "/usr/share/xml/iso-codes/iso_639.xml")
+for entry in tree.findall("iso_639_entry"):
+    langs.add(entry.get("iso_639_1_code"))
+
+# Remaining 2-letter codes plus 3-letter ones for which we have no 2-letter one
+tree = ElementTree(file = "/usr/share/xml/iso-codes/iso_639_3.xml")
+for entry in tree.findall("iso_639_3_entry"):
+    code = entry.get("part1_code")
+    if code:
+        langs.add(code)
+    if not code:
+        langs.add(entry.get("id"))
+
+print "# Generated with %s" % sys.argv[0]
+print ""
+print "LANGUAGES = set(("
+for code in sorted(langs):
+    if code:
+        print "\t\"%s\"," % code
+print "))"
+print ""
+print "COUNTRIES = set(("
+for code in sorted(countries):
+    if code:
+        print "\t\"%s\"," % code
+print "))"