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