Skip to content
Open
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
8 changes: 8 additions & 0 deletions lib/tmpdir.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ def self.tmpdir
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, in some container environments (e.g. Kubernetes with read-only root filesystem
# and emptyDir volumes), File.writable? may return false even though the directory is
# actually writable via mounted volumes. Fall back to stat.writable? in that case.
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}"
Expand Down
53 changes: 53 additions & 0 deletions test/test_tmpdir.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,59 @@ 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_writable_fallback_both_fail
omit "no meaning on this platform" if /mswin|mingw/ =~ RUBY_PLATFORM
omit "root can write to any directory" if Process.uid == 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