diff options
author | Anas Nashif <anas.nashif@intel.com> | 2012-11-21 15:28:00 -0800 |
---|---|---|
committer | Anas Nashif <anas.nashif@intel.com> | 2012-11-21 15:28:00 -0800 |
commit | ec6268183d43997c7fe124ca40a877edb0d7555b (patch) | |
tree | eb871af5b189b33b9bdbb18fc7fd77d40b0e8c8f | |
download | rpmlint-ec6268183d43997c7fe124ca40a877edb0d7555b.tar.gz rpmlint-ec6268183d43997c7fe124ca40a877edb0d7555b.tar.bz2 rpmlint-ec6268183d43997c7fe124ca40a877edb0d7555b.zip |
Imported Upstream version 1.4upstream/1.4
-rw-r--r-- | AUTHORS | 14 | ||||
-rw-r--r-- | AbstractCheck.py | 108 | ||||
-rw-r--r-- | BinariesCheck.py | 568 | ||||
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | ChangeLog | 4871 | ||||
-rw-r--r-- | Config.py | 161 | ||||
-rw-r--r-- | ConfigCheck.py | 65 | ||||
-rw-r--r-- | DistributionCheck.py | 92 | ||||
-rw-r--r-- | DocFilesCheck.py | 105 | ||||
-rw-r--r-- | FHSCheck.py | 83 | ||||
-rw-r--r-- | FilesCheck.py | 1341 | ||||
-rw-r--r-- | Filter.py | 147 | ||||
-rw-r--r-- | I18NCheck.py | 206 | ||||
-rw-r--r-- | INSTALL | 16 | ||||
-rw-r--r-- | InitScriptCheck.py | 286 | ||||
-rw-r--r-- | LSBCheck.py | 66 | ||||
-rw-r--r-- | Makefile | 110 | ||||
-rw-r--r-- | MenuCheck.py | 460 | ||||
-rw-r--r-- | MenuXDGCheck.py | 53 | ||||
-rw-r--r-- | NamingPolicyCheck.py | 114 | ||||
-rw-r--r-- | PamCheck.py | 42 | ||||
-rw-r--r-- | Pkg.py | 865 | ||||
-rw-r--r-- | PostCheck.py | 257 | ||||
-rw-r--r-- | README | 67 | ||||
-rw-r--r-- | README.devel | 15 | ||||
-rw-r--r-- | RpmFileCheck.py | 50 | ||||
-rw-r--r-- | SignatureCheck.py | 60 | ||||
-rw-r--r-- | SourceCheck.py | 76 | ||||
-rw-r--r-- | SpecCheck.py | 812 | ||||
-rw-r--r-- | TagsCheck.py | 1138 | ||||
-rw-r--r-- | ZipCheck.py | 110 | ||||
-rw-r--r-- | __isocodes__.py | 7965 | ||||
-rw-r--r-- | __version__.py | 2 | ||||
-rw-r--r-- | config | 214 | ||||
-rwxr-xr-x | rpmdiff | 294 | ||||
-rwxr-xr-x | rpmlint | 387 | ||||
-rw-r--r-- | rpmlint.1 | 115 | ||||
-rw-r--r-- | rpmlint.bash-completion | 94 | ||||
-rwxr-xr-x | test.sh | 17 | ||||
-rw-r--r-- | test/PamCheck-0.1-1.i586.rpm | bin | 0 -> 2100 bytes | |||
-rw-r--r-- | test/SpecCheck.spec | 62 | ||||
-rw-r--r-- | test/test.PamCheck.py | 22 | ||||
-rw-r--r-- | test/test.Pkg.py | 30 | ||||
-rw-r--r-- | test/test.SpecCheck.py | 30 | ||||
-rw-r--r-- | tools/Testing.py | 42 | ||||
-rwxr-xr-x | tools/generate-isocodes.py | 44 |
46 files changed, 22016 insertions, 0 deletions
@@ -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 index 0000000..91c5879 --- /dev/null +++ b/AbstractCheck.py @@ -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 index 0000000..0355557 --- /dev/null +++ b/BinariesCheck.py @@ -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 @@ -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. + + 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.) + +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. + + 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. + + 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 + + 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 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 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 index 0000000..42296d6 --- /dev/null +++ b/ConfigCheck.py @@ -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 index 0000000..05f8f29 --- /dev/null +++ b/DistributionCheck.py @@ -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 index 0000000..4463579 --- /dev/null +++ b/DocFilesCheck.py @@ -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 index 0000000..56bc65b --- /dev/null +++ b/FHSCheck.py @@ -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 index 0000000..eb314e0 --- /dev/null +++ b/FilesCheck.py @@ -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 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 index 0000000..f94064b --- /dev/null +++ b/I18NCheck.py @@ -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 @@ -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 index 0000000..c77adbc --- /dev/null +++ b/InitScriptCheck.py @@ -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 index 0000000..d88a8e0 --- /dev/null +++ b/LSBCheck.py @@ -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 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 index 0000000..0dd865a --- /dev/null +++ b/MenuCheck.py @@ -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 index 0000000..7fb9e5b --- /dev/null +++ b/MenuXDGCheck.py @@ -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 index 0000000..8f22483 --- /dev/null +++ b/NamingPolicyCheck.py @@ -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 index 0000000..43cd35f --- /dev/null +++ b/PamCheck.py @@ -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 @@ -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 index 0000000..c72bbe1 --- /dev/null +++ b/PostCheck.py @@ -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 @@ -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 index 0000000..81657d1 --- /dev/null +++ b/README.devel @@ -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 index 0000000..d4ffb19 --- /dev/null +++ b/RpmFileCheck.py @@ -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 index 0000000..fd09812 --- /dev/null +++ b/SignatureCheck.py @@ -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 index 0000000..b93d5ba --- /dev/null +++ b/SourceCheck.py @@ -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 index 0000000..97e1da6 --- /dev/null +++ b/SpecCheck.py @@ -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 index 0000000..1194660 --- /dev/null +++ b/TagsCheck.py @@ -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 index 0000000..ce8f2a3 --- /dev/null +++ b/ZipCheck.py @@ -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 index 0000000..b3459a1 --- /dev/null +++ b/__isocodes__.py @@ -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 index 0000000..92d92ba --- /dev/null +++ b/__version__.py @@ -0,0 +1,2 @@ +# Automatically generated, do not edit +__version__ = '1.4' @@ -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") @@ -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 @@ -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 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 index 0000000..189582f --- /dev/null +++ b/rpmlint.bash-completion @@ -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 @@ -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 Binary files differnew file mode 100644 index 0000000..2606df9 --- /dev/null +++ b/test/PamCheck-0.1-1.i586.rpm diff --git a/test/SpecCheck.spec b/test/SpecCheck.spec new file mode 100644 index 0000000..14f4e6f --- /dev/null +++ b/test/SpecCheck.spec @@ -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 index 0000000..08d1cff --- /dev/null +++ b/test/test.PamCheck.py @@ -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 index 0000000..6d061d4 --- /dev/null +++ b/test/test.Pkg.py @@ -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 index 0000000..fadf970 --- /dev/null +++ b/test/test.SpecCheck.py @@ -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 index 0000000..f111597 --- /dev/null +++ b/tools/Testing.py @@ -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 index 0000000..97b5422 --- /dev/null +++ b/tools/generate-isocodes.py @@ -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 "))" |