summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-01 23:52:17 +0200
committerPaul Buetow <paul@buetow.org>2026-03-01 23:52:17 +0200
commit3f833b7f82bde48ceb20f4b90407afc928ebe7b3 (patch)
tree3d169a582572aab80503573b0f03634be8939612 /lib
parent9a6526a7022c1d1172c1be9b9b3545ed6e00c9e6 (diff)
bugfix/refactor: add error handling to DNFPackageManager shell commands
system() returns false/nil on failure but the original code ignored the return value, silently swallowing dnf errors. Add a private run_dnf! helper that checks the return value and raises DNFPackageManager::CommandFailed with the dnf exit code on failure. Replace the one-liner methods with full method bodies that delegate to run_dnf!. Also fix two latent constant-resolution bugs: - Use $? instead of $CHILD_STATUS (the latter requires `require 'English'` and is always nil without it) - Use ::File instead of File in Package#initialize to avoid resolving to RCM::File once file.rb is loaded Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/dslkeywords/package.rb41
1 files changed, 34 insertions, 7 deletions
diff --git a/lib/dslkeywords/package.rb b/lib/dslkeywords/package.rb
index d528ae8..9e4324f 100644
--- a/lib/dslkeywords/package.rb
+++ b/lib/dslkeywords/package.rb
@@ -5,14 +5,40 @@ require_relative 'resource'
module RCM
class DNFPackageManager
+ # Raised when a dnf subcommand exits with a non-zero status or when
+ # the dnf binary cannot be found.
+ class CommandFailed < StandardError; end
+
def installed?(pkg) = false
- # Use system() with separate arguments to avoid shell injection —
- # backtick interpolation passes the command through a shell, which
- # allows metacharacters in pkg names to execute arbitrary commands.
- def install(pkg) = system('dnf', 'install', '-y', pkg) unless installed?(pkg)
- def update(pkg) = system('dnf', 'update', '-y', pkg)
- def remove(pkg) = system('dnf', 'remove', '-y', pkg) if installed?(pkg)
+ def install(pkg)
+ return if installed?(pkg)
+
+ run_dnf!('install', pkg)
+ end
+
+ def update(pkg)
+ run_dnf!('update', pkg)
+ end
+
+ def remove(pkg)
+ return unless installed?(pkg)
+
+ run_dnf!('remove', pkg)
+ end
+
+ private
+
+ # Execute dnf <subcommand> -y <pkg> using separate arguments (no shell
+ # interpolation). Raises CommandFailed when dnf exits non-zero or is
+ # not found ($? is nil when the binary cannot be exec'd).
+ def run_dnf!(subcommand, pkg)
+ result = system('dnf', subcommand, '-y', pkg)
+ return if result
+
+ exit_code = $?&.exitstatus || '?'
+ raise CommandFailed, "dnf #{subcommand} #{pkg} failed (exit #{exit_code})"
+ end
end
# Managing packages
@@ -23,7 +49,8 @@ module RCM
def initialize(name)
super(name)
- raise UnsupportedOS, 'OS is not supported' unless File.file?('/etc/fedora-release')
+ # Use ::File to avoid resolving to RCM::File once file.rb is loaded.
+ raise UnsupportedOS, 'OS is not supported' unless ::File.file?('/etc/fedora-release')
@manager = DNFPackageManager.new