Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 8 additions & 2 deletions lib/tmpdir.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ def self.tmpdir
case
when !stat.directory?
warn "#{name} is not a directory: #{dir}"
when stat.world_writable? && !stat.sticky?
warn "#{name} is world-writable: #{dir}"
when !File.writable?(dir)
# We call File.writable?, not stat.writable?, because you can't tell if a dir is actually
# writable just from stat; OS mechanisms other than user/group/world bits can affect this.
#
# However, File.writable? can be a false negative, so fall back to stat.writable?.
if stat.writable?
warn "#{name}: File.writable? reports not writable but file mode bits suggest writable, using anyway: #{dir}"
break dir
end
warn "#{name} is not writable: #{dir}"
when stat.world_writable? && !stat.sticky?
warn "#{name} is world-writable: #{dir}"
else
break dir
end
Expand Down
70 changes: 70 additions & 0 deletions test/test_tmpdir.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,76 @@ def test_tmpdir_not_empty_parent
end
end

def test_writable_fallback_to_stat
omit "no meaning on this platform" if /mswin|mingw/ =~ RUBY_PLATFORM
Dir.mktmpdir do |tmpdir|
envs = %w[TMPDIR TMP TEMP]
oldenv = envs.each_with_object({}) {|v, h| h[v] = ENV.delete(v)}
begin
ENV[envs[0]] = tmpdir

# Stub File.writable? to return false for our tmpdir
# This simulates container environments where access(2) returns false
# even though the directory is actually writable
original_writable = File.method(:writable?)
File.define_singleton_method(:writable?) do |path|
if path == tmpdir
false
else
original_writable.call(path)
end
end

# Should fall back to stat.writable? and succeed with a warning
assert_equal(tmpdir, assert_warn(/File\.writable\? reports not writable but file mode bits suggest writable/) { Dir.tmpdir })

ensure
# Restore original File.writable?
File.define_singleton_method(:writable?, original_writable) if original_writable
ENV.update(oldenv)
end
end
end

def test_tmpdir_rejects_world_writable_non_sticky_before_writability
omit "no meaning on this platform" if /mswin|mingw/ =~ RUBY_PLATFORM
Dir.mktmpdir do |tmpdir|
envs = %w[TMPDIR TMP TEMP]
oldenv = envs.each_with_object({}) {|v, h| h[v] = ENV.delete(v)}
begin
ENV[envs[0]] = tmpdir
File.chmod(0777, tmpdir)

assert_not_equal(tmpdir, assert_warn(/is world-writable/) { Dir.tmpdir })
ensure
File.chmod(0755, tmpdir)
ENV.update(oldenv)
end
end
end

def test_writable_fallback_both_fail
omit "no meaning on this platform" if /mswin|mingw/ =~ RUBY_PLATFORM
omit "root can write to any directory" if Process.euid == 0
Dir.mktmpdir do |tmpdir|
envs = %w[TMPDIR TMP TEMP]
oldenv = envs.each_with_object({}) {|v, h| h[v] = ENV.delete(v)}
begin
ENV[envs[0]] = tmpdir

# Make directory not writable (both File.writable? and stat.writable? will return false)
File.chmod(0555, tmpdir)

# Should reject the directory with "not writable" warning
assert_not_equal(tmpdir, assert_warn(/is not writable/) { Dir.tmpdir })

ensure
File.chmod(0755, tmpdir)
ENV.update(oldenv)
end
end
end

def test_no_homedir
bug7547 = '[ruby-core:50793]'
home, ENV["HOME"] = ENV["HOME"], nil
Expand Down