From 1b8e59388f344fdf8722bae6ad5bd37ebe2883d8 Mon Sep 17 00:00:00 2001 From: Bilal El Medkouri Date: Wed, 7 Oct 2026 10:36:32 +0200 Subject: [PATCH] Explain why pacman fails when MSYS2 is not writable An all-users installation, which is what an installer run from an elevated terminal (for instance per winget) does, grants regular users read-only access to the MSYS2 directory. pacman then fails with misleading keyring and database errors and the following native gem build fails due to missing packages. Check the write permission before running pacman at gem install and at 'ridk install' and print what to do instead. --- .../build/components/02_pacman_update.rb | 1 + .../build/components/03_dev_tools.rb | 1 + lib/ruby_installer/build/components/base.rb | 4 +++ .../build/msys2_installation.rb | 26 +++++++++++++++++++ test/ruby_installer/test_module.rb | 21 +++++++++++++++ 5 files changed, 53 insertions(+) diff --git a/lib/ruby_installer/build/components/02_pacman_update.rb b/lib/ruby_installer/build/components/02_pacman_update.rb index 87807e0d6..a650f5dfd 100644 --- a/lib/ruby_installer/build/components/02_pacman_update.rb +++ b/lib/ruby_installer/build/components/02_pacman_update.rb @@ -12,6 +12,7 @@ def description def execute(args) require "tempfile" + check_msys_writable msys.with_msys_apps_enabled do puts "Check msys2-keyring version:" diff --git a/lib/ruby_installer/build/components/03_dev_tools.rb b/lib/ruby_installer/build/components/03_dev_tools.rb index c5f63f25f..ee8e534d2 100644 --- a/lib/ruby_installer/build/components/03_dev_tools.rb +++ b/lib/ruby_installer/build/components/03_dev_tools.rb @@ -57,6 +57,7 @@ def description } def execute(args) + check_msys_writable msys.with_msys_apps_enabled do puts "Install #{description} ..." packages = PACKAGES.fetch(msys.mingwarch).map do |package| diff --git a/lib/ruby_installer/build/components/base.rb b/lib/ruby_installer/build/components/base.rb index b35cc352b..0f30bc61b 100644 --- a/lib/ruby_installer/build/components/base.rb +++ b/lib/ruby_installer/build/components/base.rb @@ -98,6 +98,10 @@ def check_hash(path, hash) end end + def check_msys_writable + raise msys.not_writable_message("ridk install #{task_index}") unless msys.writable? + end + def kill_all_msys2_processes puts 'Kill all running msys2 binaries to avoid error "size of shared memory region changed"' # See https://github.com/msys2/MSYS2-packages/issues/258 diff --git a/lib/ruby_installer/build/msys2_installation.rb b/lib/ruby_installer/build/msys2_installation.rb index 5f6775373..7a32abe90 100644 --- a/lib/ruby_installer/build/msys2_installation.rb +++ b/lib/ruby_installer/build/msys2_installation.rb @@ -346,6 +346,28 @@ def disable_msys_apps_per_ps1 @@pacman_lock.synchronize(&block) end + # Whether the current user may change the MSYS2 installation, which pacman requires. + # + # An all-users installation of RubyInstaller, which is the default when the installer runs elevated, grants regular users read-only access to it. + # pacman then fails with misleading keyring and database errors. + def writable? + probe = File.join(msys_path, "var/lib/pacman/ri-writable-#{Process.pid}") + File.write(probe, "") + File.unlink(probe) rescue nil + true + rescue Errno::EACCES, Errno::EPERM + false + rescue SystemCallError + true + end + + def not_writable_message(command) + "MSYS2 in #{msys_path} is not writable for the current user, so that pacman can not install or update packages.\n" \ + "This is the case, if Ruby was installed for all users, for instance by running the installer in an elevated terminal.\n" \ + "Run the following command from an administrator terminal or re-install Ruby for the current user only:\n" \ + " #{command}" + end + def install_packages(packages, verbose: false) return if packages.empty? @@ -361,6 +383,10 @@ def install_packages(packages, verbose: false) # Install required packages if installs.any? + unless writable? + raise CommandError, not_writable_message("ridk exec pacman -S --needed #{installs.join(" ")}") + end + Gem.ui.say("Installing required msys2 packages: #{installs.join(" ")}") if verbose args = ["pacman", "-S", "--needed", "--noconfirm", *installs] diff --git a/test/ruby_installer/test_module.rb b/test/ruby_installer/test_module.rb index db8f13fcf..3614dcacb 100644 --- a/test/ruby_installer/test_module.rb +++ b/test/ruby_installer/test_module.rb @@ -90,6 +90,27 @@ def test_enable_msys_apps_with_msys_installed assert_nil ENV['MSYSTEM'] end + def test_msys2_writable + require "tmpdir" + require "fileutils" + + Dir.mktmpdir do |dir| + pacman_dir = File.join(dir, "var/lib/pacman") + FileUtils.mkdir_p(pacman_dir) + msys = RubyInstaller::Runtime::Msys2Installation.new(msys_path: dir) + assert_predicate msys, :writable? + + assert system("icacls", pacman_dir, "/deny", "*S-1-1-0:(W)", out: File::NULL) + begin + refute_predicate msys, :writable? + assert_match(/not writable.*ridk install 2/m, msys.not_writable_message("ridk install 2")) + ensure + assert system("icacls", pacman_dir, "/remove:d", "*S-1-1-0", out: File::NULL) + end + assert_empty Dir.children(pacman_dir) + end + end + def test_enable_msys_apps_without_msys_installed skip unless File.directory?(msys_path) simulate_no_msysdir do