Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
2115afa
openSUSE: strict policy to ERR on patch-macro-old-format
danigm Sep 18, 2024
cfbde86
opensuse: explain pull requests more precisely
wfrisch Sep 18, 2024
9bdca14
test: Normalize mock_pkgconfig
danigm Sep 25, 2024
ea83634
CI: Disable failing CI images
danigm Mar 25, 2025
ec62fac
SpecCheck: Report rpm warnings on spec files
danigm Mar 26, 2025
50c9f51
Fixup fallback regexp for buildroot detection
dirkmueller Apr 4, 2025
fe4c71e
Avoid unnecessary backslashes
dirkmueller Apr 4, 2025
adac36a
Tests: Accept long GPG key ids (for RPM 6)
hroncok Apr 23, 2025
95fbf02
SpecCheck: Add shared-dir-glob-in-files warning
scfc Apr 23, 2025
ea1442f
When Declarative BuildSystem is used, assume patches are auto-applied
hroncok May 5, 2025
f133507
test: Fix spellchecking
danigm May 8, 2025
24096ee
SpecCheck: Add new warning for setup.py install usage
danigm May 8, 2025
cb2ae8c
SpecCheck: warn also for py3_install macro usage
danigm May 8, 2025
9a8e335
SUIDPermissionsCheck: Check for permctl instead of chkstat
scabrero Mar 10, 2025
51d74a4
BuildRootAndDateCheck: More specific buildroot check
danigm Jun 3, 2025
d809842
LogrotateCheck: consider configuration files in /usr/etc; add badness
mgerstner Jun 6, 2025
202e9ba
BinariesCheck: also consider /usr/etc
mgerstner Jun 6, 2025
51a56de
AlternativesCheck: Fix .conf files regex
danigm Jun 9, 2025
1acddab
ZipCheck: fix utf8 decoding erros in jarfile manifest
danigm Jul 8, 2025
88d2e5e
Run only once the validate_filters per call
danigm Jul 22, 2025
b71efa3
AlternativesCheck: detect update-alternatives on Fedora
xdelaruelle Aug 4, 2025
a7b8a23
Release 2.8.0
danigm Aug 6, 2025
2bb5f3d
Simplify code by using `sorted()`
vil02 Aug 12, 2025
bbdfdab
Resolve `consider-using-sys-exit`
vil02 Aug 14, 2025
40ae1fe
Use list comprehensions
vil02 Aug 13, 2025
d5ce4b6
Resolve `no-else-return`
vil02 Aug 18, 2025
24feca4
Resolve `no-else-raise`
vil02 Aug 19, 2025
0f677a3
Simplify `AbstractPkg._gather_aux` to resolve `consider-using-enumerate`
vil02 Aug 20, 2025
0ee39a9
Resolve `consider-merging-isinstance`
vil02 Aug 25, 2025
c6ef0df
Resolve `consider-using-in`
vil02 Aug 27, 2025
fed0ffa
Resolve `redefined-outer-name`
vil02 Aug 28, 2025
fb65ce8
Resolve `use-implicit-booleaness-not-len`
vil02 Sep 3, 2025
b65381c
Resolve `superfluous-parens`
vil02 Sep 4, 2025
299ab73
Synced /var/spool/mail entry with Factory filesystem package.
sudomibo Sep 5, 2025
3d3b63b
Fix typo
Oct 2, 2025
08a1d37
Permit repeating --rpmlintrc
Oct 2, 2025
d2d8a1e
cli: Remove double check of rpmlintrc file
danigm Oct 6, 2025
1a948b4
Add checks for atomic update compatibility
mtravitzky Aug 18, 2025
e8c1dd9
test: Add some tests for AtomicUpdateCheck
danigm Oct 30, 2025
ba48405
Drop dir-or-file-outside-snapshot badness to zero for now (bsc#1253008)
FilippoBonazziSUSE Nov 3, 2025
0db5cc1
Format and modify test/spec/bogus-date.spec
wanggai-kylin Nov 6, 2025
5007251
opensuse.toml: block filters for sudoers-, sysctl- whitelisting errors
mgerstner Nov 20, 2025
8b38513
Remove post-without-tmpfile-creation warning
danigm Aug 8, 2025
fce61dd
MenuXDGCheck: Fix error string
danigm Dec 15, 2025
f8b3187
Fix lint
danigm Dec 15, 2025
9ec3231
Fix merge
danigm Dec 17, 2025
326752d
scoring: raise badness for logrotate-user-writable-log-dir (bsc#1245961)
mgerstner Sep 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions .packit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ jobs:
- fedora-rawhide-aarch64
- mageia-cauldron-x86_64
- mageia-cauldron-aarch64
- opensuse-tumbleweed-x86_64
- opensuse-tumbleweed-aarch64
# opensuse images are failing because dnf-plugins-core package is not working
#- opensuse-tumbleweed-x86_64
#- opensuse-tumbleweed-aarch64
trigger: pull_request
- job: copr_build
trigger: commit
Expand All @@ -25,8 +26,9 @@ jobs:
- fedora-rawhide-aarch64
- mageia-cauldron-x86_64
- mageia-cauldron-aarch64
- opensuse-tumbleweed-x86_64
- opensuse-tumbleweed-aarch64
# opensuse images are failing because dnf-plugins-core package is not working
#- opensuse-tumbleweed-x86_64
#- opensuse-tumbleweed-aarch64
branch: main
project: rpm-software-management-rpmlint-mainline
list_on_homepage: True
Expand All @@ -35,8 +37,9 @@ jobs:
trigger: commit
metadata:
targets:
- opensuse-tumbleweed-x86_64
- opensuse-tumbleweed-aarch64
# opensuse images are failing because dnf-plugins-core package is not working
#- opensuse-tumbleweed-x86_64
#- opensuse-tumbleweed-aarch64
branch: opensuse
project: rpm-software-management-rpmlint-opensuse
list_on_homepage: True
Expand Down
2 changes: 1 addition & 1 deletion .packit/rpmlint.spec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
%{!?python3: %global python3 %{__python3}}

Name: rpmlint
Version: 2.7.0
Version: 2.8.0
Release: 0%{?dist}
Summary: Tool for checking common errors in RPM packages

Expand Down
47 changes: 41 additions & 6 deletions configs/openSUSE/opensuse.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ UseVarLockSubsys = false
UseVersionInChangelog = false
BadnessThreshold = 999

# Set to true to issue a warning for ghost entries outside snapshots
# when checking for atomic update compatibility
AtomicCheckGhosts = false

# Enabled checks for the rpmlint to be run (besides the default set)
Checks = [
"BashismsCheck",
Expand All @@ -24,13 +28,33 @@ Checks = [
"SystemdTmpfilesCheck",
"SUIDPermissionsCheck",
"WorldWritableCheck",
"AtomicUpdateCheck",
]

# List of directory prefixes that are not allowed in packages
DisallowedDirs = [
"/etc/NetworkManager/dispatcher.d",
]

# Only these directories may be used by packages compatible with
# atomic updates
AtomicAllowedDirs = [
"/etc/",
"/usr/",
"/bin/",
"/lib/",
"/lib64/",
"/sbin/",
"/boot/",
]

# List of subdirectories which are disallowed for atomic updates
# despite being within otherwise allowed directories
AtomicDisallowedSubdirs = [
"/usr/local/",
"/boot/efi/",
]

FilterErrorTitles = [
'cross-directory-hard-link',
]
Expand Down Expand Up @@ -83,6 +107,7 @@ Filters = [
'^filesystem\..*: dir-or-file-in-tmp',
'^filesystem\..*: dir-or-file-in-mnt',
'^filesystem\..*: dir-or-file-in-home',
'^filesystem\..*: dir-or-file-outside-snapshot',
'^filesystem\..*: hidden-file-or-dir /root/.gnupg',
'^filesystem\..*: hidden-file-or-dir /root/.gnupg',
'^filesystem\..*: hidden-file-or-dir /etc/skel/.config',
Expand Down Expand Up @@ -270,6 +295,14 @@ BlockedFilters = [
"polkit-untracked-privilege",
"polkit-user-privilege",
"polkit-xml-exception",
"sudoers-file-digest-mismatch",
"sudoers-file-ghost",
"sudoers-file-symlink",
"sudoers-file-unauthorized",
"sysctl-file-digest-mismatch",
"sysctl-file-ghost",
"sysctl-file-symlink",
"sysctl-file-unauthorized",
"systemd-tmpfile-ghost",
"systemd-tmpfile-symlink",
"systemd-tmpfile-parse-error",
Expand Down Expand Up @@ -318,13 +351,15 @@ paths = [
[SystemdTmpfilesWhitelist]

[Descriptions]
non-standard-uid = '''A file in this package is owned by an unregistered user id.
To register the user, please make a pull request to the rpmlint config file
configs/openSUSE/users-groups.toml in the rpmlint repository.
non-standard-uid = '''A file in this package is owned by an unregistered user
id. To register the user, please make a pull request to the rpmlint config
file configs/openSUSE/users-groups.toml in the opensuse branch of the rpmlint
repository.
'''
non-standard-gid = '''A file in this package is owned by an unregistered group id.
To register the group, please make a pull request to the rpmlint config file
configs/openSUSE/users-groups.toml in the rpmlint repository.
non-standard-gid = '''A file in this package is owned by an unregistered group
id. To register the group, please make a pull request to the rpmlint config
file configs/openSUSE/users-groups.toml in the opensuse branch of the rpmlint
repository.
'''
no-changelogname-tag = '''There is no changelog. Please insert a '%changelog' section heading in your
spec file and prepare your changes file using e.g. the 'osc vc' command.'''
6 changes: 6 additions & 0 deletions configs/openSUSE/scoring.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ deprecated-boot-script = 10000
executable-stack = 10000
binary-or-shlib-defines-rpath = 10000
patchable-function-entry-in-archive = 10000
patch-macro-old-format = 10000
pam-file-ghost = 10
pam-file-unauthorized = 10
pam-file-symlink = 10
Expand Down Expand Up @@ -98,3 +99,8 @@ missing-hash-section = 10000
zypperplugin-file-digest-mismatch = 10
zypperplugin-file-ghost = 10
zypperplugin-file-unauthorized = 10
logrotate-user-writable-log-dir = 10000

# Set to 10000 once affected packages have been updated
# for atomic update compatibility
dir-or-file-outside-snapshot = 0
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "rpmlint"
version = "2.7.0"
version = "2.8.0"
description = "Check for common errors in RPM packages"
license = {text = "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)"}
authors = [
Expand Down
4 changes: 1 addition & 3 deletions rpmlint/checks/AbstractCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ def check_binary(self, pkg):
# start with the biggest files first
filenames = sorted(filenames, key=lambda x: pkg.files[x].size, reverse=True)
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []
for filename in filenames:
futures.append(executor.submit(self.check_file, pkg, filename))
futures = [executor.submit(self.check_file, pkg, filename) for filename in filenames]
concurrent.futures.wait(futures)
for future in futures:
err = future.exception()
Expand Down
100 changes: 52 additions & 48 deletions rpmlint/checks/AlternativesCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class AlternativesCheck(AbstractCheck):
Requires(post) and Requires(postun) must depend on update-alternatives
"""
# Regex to match anything that can be in requires for update-alternatives
re_requirement = re.compile(r'^(/usr/sbin/|%{?_sbindir}?/)?update-alternatives$')
re_requirement = re.compile(r'^(/usr/s?bin/|%{?_s?bindir}?/)?update-alternatives$')
re_install = re.compile(r'--install\s+(?P<link>\S+)\s+(?P<name>\S+)\s+(\S+)\s+(\S+)')
re_slave = re.compile(r'--slave\s+(?P<link>\S+)\s+(\S+)\s+(\S+)')
command = 'update-alternatives'
Expand Down Expand Up @@ -220,52 +220,56 @@ def _check_libalternatives_filelist(self, pkg):
Checking content of all /usr/share/libalternatives/*/*.conf files
"""
for f, pkgfile in pkg.files.items():
if re.search('^/usr/share/libalternatives/.*conf$', f):
filename = Path(pkg.dirname + f)
if not filename.exists():
if pkgfile.is_ghost:
self.output.add_info('I', pkg, 'libalternatives-conf-not-found', f)
else:
self.output.add_info('E', pkg, 'libalternatives-conf-not-found', f)
continue
bin_found = False
man_found = False
with open(filename) as read_obj:
# Read all lines in the file one by one. E.g:
#
# binary=/usr/bin/jupyter-3.8
# man=jupyter-3.8.1
# group=jupyter, jupyter-migrate, jupyter-troubleshoot
#
for line_nr, line in enumerate(read_obj):
line_array = [x.strip() for x in line.split('=')]
line_nr_str = f'Line: {line_nr}'
if len(line_array) != 2: # empty values are valid
self.output.add_info('E', pkg, 'wrong-entry-format', f, line_nr_str)
if not re.search(r'^/usr/share/libalternatives/[^/]+/.*\.conf$', f):
continue

key, value = line_array
if key == 'binary':
if bin_found:
self.output.add_info('E', pkg, 'multiple-entries', f, line_nr_str)
continue
filename = Path(pkg.dirname + f)
if not filename.exists():
if pkgfile.is_ghost:
self.output.add_info('I', pkg, 'libalternatives-conf-not-found', f)
else:
self.output.add_info('E', pkg, 'libalternatives-conf-not-found', f)
continue

bin_found = False
man_found = False
with open(filename) as read_obj:
# Read all lines in the file one by one. E.g:
#
# binary=/usr/bin/jupyter-3.8
# man=jupyter-3.8.1
# group=jupyter, jupyter-migrate, jupyter-troubleshoot
#
for line_nr, line in enumerate(read_obj):
line_array = [x.strip() for x in line.split('=')]
line_nr_str = f'Line: {line_nr}'
if len(line_array) != 2: # empty values are valid
self.output.add_info('E', pkg, 'wrong-entry-format', f, line_nr_str)
continue

key, value = line_array
if key == 'binary':
if bin_found:
self.output.add_info('E', pkg, 'multiple-entries', f, line_nr_str)
continue
for path in pkg.files:
if 'bin/' in path and path.endswith(value):
bin_found = True
if not bin_found:
self.output.add_info('W', pkg, 'binary-entry-value-not-found', f, line_nr_str)
elif key == 'man':
if man_found:
self.output.add_info('E', pkg, 'double-entries', f, line_nr_str)
continue
mans = value.split(',')
for man in mans:
man_found = False
for path in pkg.files:
if 'bin/' in path and path.endswith(value):
bin_found = True
if not bin_found:
self.output.add_info('W', pkg, 'binary-entry-value-not-found', f, line_nr_str)
elif key == 'man':
if man_found:
self.output.add_info('E', pkg, 'double-entries', f, line_nr_str)
continue
mans = value.split(',')
for man in mans:
man_found = False
for path in pkg.files:
if path.startswith('/usr/share/man/') and man.strip() in path:
man_found = True
if not man_found:
self.output.add_info('W', pkg, 'man-entry-value-not-found', f, line_nr_str)
elif key != 'group' and key != 'options':
self.output.add_info('W', pkg, 'wrong-tag-found', f, line_nr_str)
if not bin_found:
self.output.add_info('W', pkg, 'wrong-or-missed-binary-entry', f)
if path.startswith('/usr/share/man/') and man.strip() in path:
man_found = True
if not man_found:
self.output.add_info('W', pkg, 'man-entry-value-not-found', f, line_nr_str)
elif key != 'group' and key != 'options':
self.output.add_info('W', pkg, 'wrong-tag-found', f, line_nr_str)
if not bin_found:
self.output.add_info('W', pkg, 'wrong-or-missed-binary-entry', f)
44 changes: 44 additions & 0 deletions rpmlint/checks/AtomicUpdateCheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from rpmlint.checks.AbstractCheck import AbstractCheck


class AtomicUpdateCheck(AbstractCheck):

"""
Requirements for atomic updates:
* All files must be stored inside the snapshot, which is in our case /etc and /usr, not /var,
/opt, /srv, /usr/local or anything else.
* (Re)starting daemons is not possible.
* Modifying files outside of /usr and /etc is not possible.
* Modifications outside the snapshot have to be done via systemd-tmpfiles and systemd services.
This check currently only implements checking for files at illegal paths.
"""

def __init__(self, config, output):
super().__init__(config, output)
self.check_ghosts = self.config.configuration['AtomicCheckGhosts']
self.allowed_dirs = self.config.configuration['AtomicAllowedDirs']
self.disallowed_subdirs = self.config.configuration['AtomicDisallowedSubdirs']

def check(self, pkg):
if pkg.is_source:
return

# Check for files stored outside the snapshot
self._check_paths(pkg, self.check_ghosts)

def _check_paths(self, pkg, check_ghosts=False):
for file in pkg.files.keys():
if file in pkg.ghost_files:
continue # Ghosts are only handled if explicitly desired
if not (self._check_single_path(file)):
self.output.add_info('E', pkg, 'dir-or-file-outside-snapshot', file)
if check_ghosts:
for ghost in pkg.ghost_files:
if not (self._check_single_path(ghost)):
self.output.add_info('W', pkg, 'ghost-outside-snapshot', ghost)

def _check_single_path(self, file):
return (
file.startswith(tuple(self.allowed_dirs)) and
not file.startswith(tuple(self.disallowed_subdirs))
)
2 changes: 1 addition & 1 deletion rpmlint/checks/BinariesCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def _check_binary_in_etc(self, pkg, bin_name):

We suppose that the package is arch dependent.
"""
if bin_name.startswith('/etc/'):
if bin_name.startswith('/etc/') or bin_name.startswith('/usr/etc/'):
self.output.add_info('E', pkg, 'binary-in-etc', bin_name)

def _check_unstripped_binary(self, bin_name, pkg, pkgfile):
Expand Down
12 changes: 9 additions & 3 deletions rpmlint/checks/BuildRootAndDateCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@ def __init__(self, config, output):
super().__init__(config, output, r'.*')
self.looksliketime = re.compile('(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])')
self.istoday = re.compile(time.strftime('%b %e %Y'))
self.prepare_regex(rpm.expandMacro('%{?buildroot}') or '^/.*/BUILDROOT/')
# in rpm 4.20 the expandMacro will return an empty string and so we run
# into the "or" case.
# return a string that is modelled after the actual path that rpm 4.20
# is using so we can actually match what the rpm 4.20 path will be and
# not just any random string in a file that references buildroot.
# https://github.com/rpm-software-management/rpm/blob/rpm-4.20.1-release/build/parsePreamble.c#L1290
self.prepare_regex(rpm.expandMacro('%{?buildroot}') or '/%{NAME}-%{VERSION}-build/BUILDROOT/')

def prepare_regex(self, buildroot):
for m in ('name', 'version', 'release', 'NAME', 'VERSION', 'RELEASE'):
buildroot = buildroot.replace('%%{%s}' % (m), r'[\w\!-\.]{1,20}')
self.build_root_re = re.compile(buildroot)
self.lookslikebuildroot = re.compile(buildroot)

def check_file(self, pkg, filename):
if filename.startswith('/usr/lib/debug') or pkg.is_source or \
Expand All @@ -35,5 +41,5 @@ def check_file(self, pkg, filename):
self.output.add_info('E', pkg, 'file-contains-date-and-time', filename)
else:
self.output.add_info('E', pkg, 'file-contains-current-date', filename)
if self.build_root_re.search(data):
if self.lookslikebuildroot.search(data):
self.output.add_info('E', pkg, 'file-contains-buildroot', filename)
1 change: 0 additions & 1 deletion rpmlint/checks/FilesCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@
'/var/opt',
'/var/preserve',
'/var/spool',
'/var/spool/mail',
'/var/tmp',
)

Expand Down
3 changes: 1 addition & 2 deletions rpmlint/checks/I18NCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ def is_valid_lang(lang):

class I18NCheck(AbstractCheck):
def check_binary(self, pkg):
files = list(pkg.files.keys())
files.sort()
files = sorted(pkg.files.keys())
locales = [] # list of locales for this packages
webapp = False

Expand Down
Loading