def check_spec(self, pkg, spec_file, spec_lines=None): """SCL spec file checks""" spec = '\n'.join(Pkg.readlines(spec_file)) if global_scl_definition.search(spec): self.check_metapackage(pkg, spec) elif scl_package_definition.search(spec): self.check_scl_spec(pkg, spec) elif scl_use.search(spec): self.output.add_info('E', pkg, 'undeclared-scl')
def check_spec(self, pkg, spec_file, spec_lines=None): '''SCL spec file checks''' spec = '\n'.join(Pkg.readlines(spec_file)) if global_scl_definition.search(spec): self.check_metapackage(pkg, spec) elif scl_package_definition.search(spec): self.check_scl_spec(pkg, spec) elif scl_use.search(spec): printError(pkg, 'undeclared-scl')
def check_spec(self, pkg, spec_file, spec_lines=None): 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 = [] patches_auto_applied = False 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 patch_fuzz_override = False indent_spaces = 0 indent_tabs = 0 section = {} # None == main package current_package = None package_noarch = {} if self._spec_file: if not Pkg.is_utf8(self._spec_file): self.output.add_info('E', pkg, 'non-utf8-spec-file', self._spec_name or self._spec_file) # gather info from spec lines pkg.current_linenum = 0 nbsp = UNICODE_NBSP for line in spec_lines: pkg.current_linenum += 1 char = line.find(nbsp) if char != -1: self.output.add_info( 'W', 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 not is_lib_pkg and lib_package_regex.search(line): is_lib_pkg = True continue if (current_section in Pkg.RPM_SCRIPTLETS + ('prep', 'build') and contains_buildroot(line)): self.output.add_info('W', pkg, 'rpm-buildroot-usage', '%' + current_section, line[:-1].strip()) if make_check_regex.search(line) and current_section not in \ ('check', 'changelog', 'package', 'description'): self.output.add_info('W', 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 setup_regex.match(line): 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): self.output.add_info('W', pkg, 'setup-not-quiet') else: self.output.add_info('W', pkg, 'setup-not-quiet') if current_section != 'prep': self.output.add_info('W', pkg, 'setup-not-in-prep') elif autopatch_regex.search(line): patches_auto_applied = True if current_section != 'prep': self.output.add_info('W', pkg, '%autopatch-not-in-prep') else: res = autosetup_regex.search(line) if res: if not autosetup_n_regex.search(res.group(1)): patches_auto_applied = True if current_section != 'prep': self.output.add_info('W', pkg, '%autosetup-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) else: res = applied_patch_i_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 self.output.add_info('E', 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 self.output.add_info('W', 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: self.output.add_info('E', 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 self.hardcoded_lib_path_exceptions_regex.search( res.group(1).lstrip())): self.output.add_info('E', 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: self.output.add_info('W', pkg, 'obsolete-tag', res.group(1)) res = buildroot_regex.search(line) if res: buildroot = True if res.group(1).startswith('/'): self.output.add_info( 'W', pkg, 'hardcoded-path-in-buildroot-tag', res.group(1)) res = buildarch_regex.search(line) if res: if res.group(1) != 'noarch': self.output.add_info( 'E', pkg, 'buildarch-instead-of-exclusivearch-tag', res.group(1)) else: package_noarch[current_package] = True res = packager_regex.search(line) if res: self.output.add_info('W', pkg, 'hardcoded-packager-tag', res.group(1)) res = prefix_regex.search(line) if res: if not res.group(1).startswith('%'): self.output.add_info('W', pkg, 'hardcoded-prefix-tag', res.group(1)) res = prereq_regex.search(line) if res: self.output.add_info('E', pkg, 'prereq-use', res.group(2)) res = buildprereq_regex.search(line) if res: self.output.add_info('E', pkg, 'buildprereq-use', res.group(1)) if scriptlet_requires_regex.search(line): self.output.add_info( 'E', 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): self.output.add_info( 'W', 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): if not prov.startswith('/'): self.output.add_info( 'W', pkg, 'unversioned-explicit-provides', prov) if compop_regex.search(prov): self.output.add_info( 'W', 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): if not obs.startswith('/'): self.output.add_info( 'W', pkg, 'unversioned-explicit-obsoletes', obs) if compop_regex.search(obs): self.output.add_info( 'W', 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): self.output.add_info( 'W', pkg, 'comparison-operator-in-deptoken', conf) if current_section == 'changelog': for match in macro_regex.findall(line): res = re.match('%+', match) if len(res.group(0)) % 2: self.output.add_info('W', 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 not patch_fuzz_override: patch_fuzz_override = \ patch_fuzz_override_regex.search(line) is not None if current_section == 'files': # 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)' self.output.add_info('W', 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: self.output.add_info('W', 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 self.valid_groups and \ line.lower().startswith('group:'): group = line[6:].strip() if group not in self.valid_groups: self.output.add_info('W', 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 macro_regex.findall(line[hashPos + 1:]): res = re.match('%+', match) if len(res.group(0)) % 2: self.output.add_info('W', 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]): self.output.add_info('W', pkg, 'no-cleaning-of-buildroot', '%' + sect) if not buildroot: self.output.add_info('W', pkg, 'no-buildroot-tag') for sec in ('prep', 'build', 'install', 'clean'): if not section.get(sec): self.output.add_info('W', 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: self.output.add_info('W', pkg, 'more-than-one-%%%s-section' % sec) if is_lib_pkg and not mklibname: self.output.add_info('E', pkg, 'lib-package-without-%mklibname') if depscript_override and not depgen_disabled: self.output.add_info('W', pkg, 'depscript-without-disabling-depgen') if patch_fuzz_override: self.output.add_info('W', pkg, 'patch-fuzz-is-changed') if indent_spaces and indent_tabs: pkg.current_linenum = max(indent_spaces, indent_tabs) self.output.add_info( 'W', pkg, 'mixed-use-of-spaces-and-tabs', '(spaces: line %d, tab: line %d)' % (indent_spaces, indent_tabs)) pkg.current_linenum = None # process gathered info if not patches_auto_applied: for pnum, pfile in patches.items(): if pnum in applied_patches_ifarch: self.output.add_info('W', pkg, '%ifarch-applied-patch', 'Patch%d:' % pnum, pfile) if pnum not in applied_patches: self.output.add_info('W', 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( ('rpm', '-q', '--qf=', '-D', '_sourcedir %s' % pkg.dirName(), '--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 self.output.add_info('E', 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 rpm.addMacro('_sourcedir', pkg.dirName()) try: ts = rpm.TransactionSet() spec_obj = ts.parseSpec(self._spec_file) except (ValueError, rpm.error): # errors logged above already pass rpm.delMacro('_sourcedir') 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().get(url.split('/')[-1]) if pkgfile: if clen is not None and pkgfile.size != clen: self.output.add_info( 'W', 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: self.output.add_info( 'W', pkg, 'file-md5-mismatch', '%s = %s, %s = %s' % (pkgfile.name, pkgfile.md5, url, cmd5)) elif srctype == 'Source' and tarball_regex.search(url): self.output.add_info('W', pkg, 'invalid-url', '%s:' % tag, url)
def check_binary(self, pkg): 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 & 0o500 != 0o500: self.output.add_info('E', pkg, 'init-script-non-executable', fname) if '.' in basename: self.output.add_info('E', 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: self.output.add_info('E', pkg, 'init-script-without-chkconfig-postin', fname) elif not chkconfig_regex.search(postin): self.output.add_info('E', pkg, 'postin-without-chkconfig', fname) preun = pkg[rpm.RPMTAG_PREUN] or \ pkg.scriptprog(rpm.RPMTAG_PREUNPROG) if not preun: self.output.add_info('E', pkg, 'init-script-without-chkconfig-preun', fname) elif not chkconfig_regex.search(preun): self.output.add_info('E', 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 = [x for x in Pkg.readlines(pkgfile.path)] except Exception as e: self.output.add_info('W', 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: self.output.add_info('E', pkg, 'redundant-lsb-keyword', kw) for kw in RECOMMENDED_LSB_KEYWORDS: if kw not in lsb_tags: self.output.add_info('W', 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 self.output.add_info('E', 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: self.output.add_info('E', 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 self.use_deflevels: if res.group(1) == '-': self.output.add_info('W', pkg, 'no-default-runlevel', fname) elif res.group(1) != '-': self.output.add_info('W', pkg, 'service-default-enabled', fname) res = subsys_regex.search(line) if res: subsys_regex_found = True name = res.group(1) if self.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] == '$': self.output.add_info('W', pkg, 'incoherent-subsys', fname, name) else: self.output.add_info('E', pkg, 'incoherent-subsys', fname, name) if 'Default-Start' in lsb_tags: if ''.join(lsb_tags['Default-Start']): self.output.add_info('W', pkg, 'service-default-enabled', fname) if not status_found: self.output.add_info('E', pkg, 'no-status-entry', fname) if not reload_found: self.output.add_info('W', pkg, 'no-reload-entry', fname) if not chkconfig_content_found: self.output.add_info('E', pkg, 'no-chkconfig-line', fname) if not subsys_regex_found and self.use_subsys: self.output.add_info('E', pkg, 'subsys-not-used', fname) elif subsys_regex_found and not self.use_subsys: self.output.add_info('E', 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: self.output.add_info('W', pkg, 'incoherent-init-script-name', initscript_list[0], str(goodnames))