Merge pull request #15413 from reitermarkus/symlink-sudo

Use `sudo` for symlinks if necessary.
This commit is contained in:
Markus Reiter 2023-05-13 04:10:01 +02:00 committed by GitHub
commit b0dc84b117
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 41 deletions

View File

@ -82,13 +82,7 @@ module Cask
ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'"
unless target.dirname.exist?
if target.dirname.ascend.find(&:directory?).writable?
target.dirname.mkpath
else
command.run!("/bin/mkdir", args: ["-p", target.dirname], sudo: true)
end
end
Utils.gain_permissions_mkpath(target.dirname, command: command) unless target.dirname.exist?
if target.directory?
if target.writable?

View File

@ -43,7 +43,7 @@ module Cask
private
def link(force: false, **options)
def link(force: false, command: nil, **_options)
unless source.exist?
raise CaskError,
"It seems the #{self.class.link_type_english_name.downcase} " \
@ -57,26 +57,29 @@ module Cask
if force && target.symlink? &&
(target.realpath == source.realpath || target.realpath.to_s.start_with?("#{cask.caskroom_path}/"))
opoo "#{message}; overwriting."
target.delete
Utils.gain_permissions_remove(target, command: command)
else
raise CaskError, "#{message}."
end
end
ohai "Linking #{self.class.english_name} '#{source.basename}' to '#{target}'"
create_filesystem_link(**options)
create_filesystem_link(command: command)
end
def unlink(**)
def unlink(command: nil, **)
return unless target.symlink?
ohai "Unlinking #{self.class.english_name} '#{target}'"
target.delete
Utils.gain_permissions_remove(target, command: command)
end
def create_filesystem_link(command: nil, **_)
target.dirname.mkpath
command.run!("/bin/ln", args: ["-h", "-f", "-s", "--", source, target])
def create_filesystem_link(command: nil)
Utils.gain_permissions_mkpath(target.dirname, command: command)
command.run! "/bin/ln", args: ["-h", "-f", "-s", "--", source, target],
sudo: !target.dirname.writable?
add_altname_metadata(source, target.basename, command: command)
end
end

View File

@ -11,20 +11,45 @@ module Cask
#
# @api private
module Utils
def self.gain_permissions_mkpath(path, command: SystemCommand)
dir = path.ascend.find(&:directory?)
return if path == dir
if dir.writable?
path.mkpath
else
command.run!("/bin/mkdir", args: ["-p", "--", path], sudo: true)
end
end
def self.gain_permissions_remove(path, command: SystemCommand)
if path.respond_to?(:rmtree) && path.exist?
gain_permissions(path, ["-R"], command) do |p|
directory = false
permission_flags = if path.symlink?
["-h"]
elsif path.directory?
directory = true
["-R"]
elsif path.exist?
[]
else
# Nothing to remove.
return
end
gain_permissions(path, permission_flags, command) do |p|
if p.parent.writable?
if directory
p.rmtree
else
command.run("/bin/rm",
args: ["-r", "-f", "--", p],
FileUtils.rm_f p
end
else
recursive_flag = directory ? ["-R"] : []
command.run!("/bin/rm",
args: recursive_flag + ["-f", "--", p],
sudo: true)
end
end
elsif File.symlink?(path)
gain_permissions(path, ["-h"], command, &FileUtils.method(:rm_f))
end
end
def self.gain_permissions(path, command_args, command)
@ -40,13 +65,10 @@ module Cask
# dependent on whether the file argument has a trailing
# slash. This should do the right thing, but is fragile.
command.run("/usr/bin/chflags",
must_succeed: false,
args: command_args + ["--", "000", path])
command.run("/bin/chmod",
must_succeed: false,
args: command_args + ["--", "u+rwx", path])
command.run("/bin/chmod",
must_succeed: false,
args: command_args + ["-N", path])
tried_permissions = true
retry # rmtree

View File

@ -171,21 +171,12 @@ describe Cask::Artifact::App, :cask do
end
it "overwrites the existing app" do
expect(command).to receive(:run).with(
"/bin/chmod", args: [
"-R", "--", "u+rwx", target_path
], must_succeed: false
).and_call_original
expect(command).to receive(:run).with(
"/bin/chmod", args: [
"-R", "-N", target_path
], must_succeed: false
).and_call_original
expect(command).to receive(:run).with(
"/usr/bin/chflags", args: [
"-R", "--", "000", target_path
], must_succeed: false
).and_call_original
expect(command).to receive(:run).with("/usr/bin/chflags",
args: ["-R", "--", "000", target_path]).and_call_original
expect(command).to receive(:run).with("/bin/chmod",
args: ["-R", "--", "u+rwx", target_path]).and_call_original
expect(command).to receive(:run).with("/bin/chmod",
args: ["-R", "-N", target_path]).and_call_original
stdout = <<~EOS
==> Removing App '#{target_path}'

View File

@ -0,0 +1,79 @@
# frozen_string_literal: true
describe Cask::Utils do
let(:command) { NeverSudoSystemCommand }
let(:dir) { mktmpdir }
let(:path) { dir/"a/b/c" }
let(:link) { dir/"link" }
describe "::gain_permissions_mkpath" do
it "creates a directory" do
expect(path).not_to exist
described_class.gain_permissions_mkpath path, command: command
expect(path).to be_a_directory
described_class.gain_permissions_mkpath path, command: command
expect(path).to be_a_directory
end
context "when parent directory is not writable" do
it "creates a directory with `sudo`" do
FileUtils.chmod "-w", dir
expect(dir).not_to be_writable
expect(command).to receive(:run!).exactly(:once).and_wrap_original do |original, *args, **options|
FileUtils.chmod "+w", dir
original.call(*args, **options)
FileUtils.chmod "-w", dir
end
expect(path).not_to exist
described_class.gain_permissions_mkpath path, command: command
expect(path).to be_a_directory
described_class.gain_permissions_mkpath path, command: command
expect(path).to be_a_directory
expect(dir).not_to be_writable
FileUtils.chmod "+w", dir
end
end
end
describe "::gain_permissions_remove" do
it "removes the symlink, not the file it points to" do
path.dirname.mkpath
FileUtils.touch path
FileUtils.ln_s path, link
expect(path).to be_a_file
expect(link).to be_a_symlink
expect(link.realpath).to eq path
described_class.gain_permissions_remove link, command: command
expect(path).to be_a_file
expect(link).not_to exist
described_class.gain_permissions_remove path, command: command
expect(path).not_to exist
end
it "removes the symlink, not the directory it points to" do
path.mkpath
FileUtils.ln_s path, link
expect(path).to be_a_directory
expect(link).to be_a_symlink
expect(link.realpath).to eq path
described_class.gain_permissions_remove link, command: command
expect(path).to be_a_directory
expect(link).not_to exist
described_class.gain_permissions_remove path, command: command
expect(path).not_to exist
end
end
end