Skip to content

Commit db6ca74

Browse files
committed
Fix 2 — Writability fallback (lines 36-47)
1 parent 474386d commit db6ca74

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

lib/tmpdir.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ def self.tmpdir
3535
when !File.writable?(dir)
3636
# We call File.writable?, not stat.writable?, because you can't tell if a dir is actually
3737
# writable just from stat; OS mechanisms other than user/group/world bits can affect this.
38+
#
39+
# However, in some container environments (e.g. Kubernetes with read-only root filesystem
40+
# and emptyDir volumes), File.writable? may return false even though the directory is
41+
# actually writable via mounted volumes. Fall back to stat.writable? in that case.
42+
if stat.writable?
43+
warn "#{name}: File.writable? reports not writable but file mode bits suggest writable, using anyway: #{dir}"
44+
break dir
45+
end
3846
warn "#{name} is not writable: #{dir}"
3947
when stat.world_writable? && !stat.sticky?
4048
warn "#{name} is world-writable: #{dir}"

test/test_tmpdir.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,58 @@ def test_tmpdir_not_empty_parent
5959
end
6060
end
6161

62+
def test_writable_fallback_to_stat
63+
omit "no meaning on this platform" if /mswin|mingw/ =~ RUBY_PLATFORM
64+
Dir.mktmpdir do |tmpdir|
65+
envs = %w[TMPDIR TMP TEMP]
66+
oldenv = envs.each_with_object({}) {|v, h| h[v] = ENV.delete(v)}
67+
begin
68+
ENV[envs[0]] = tmpdir
69+
70+
# Stub File.writable? to return false for our tmpdir
71+
# This simulates container environments where access(2) returns false
72+
# even though the directory is actually writable
73+
original_writable = File.method(:writable?)
74+
File.define_singleton_method(:writable?) do |path|
75+
if path == tmpdir
76+
false
77+
else
78+
original_writable.call(path)
79+
end
80+
end
81+
82+
# Should fall back to stat.writable? and succeed with a warning
83+
assert_equal(tmpdir, assert_warn(/File\.writable\? reports not writable but file mode bits suggest writable/) { Dir.tmpdir })
84+
85+
ensure
86+
# Restore original File.writable?
87+
File.define_singleton_method(:writable?, original_writable) if original_writable
88+
ENV.update(oldenv)
89+
end
90+
end
91+
end
92+
93+
def test_writable_fallback_both_fail
94+
omit "no meaning on this platform" if /mswin|mingw/ =~ RUBY_PLATFORM
95+
Dir.mktmpdir do |tmpdir|
96+
envs = %w[TMPDIR TMP TEMP]
97+
oldenv = envs.each_with_object({}) {|v, h| h[v] = ENV.delete(v)}
98+
begin
99+
ENV[envs[0]] = tmpdir
100+
101+
# Make directory not writable (both File.writable? and stat.writable? will return false)
102+
File.chmod(0555, tmpdir)
103+
104+
# Should reject the directory with "not writable" warning
105+
assert_not_equal(tmpdir, assert_warn(/is not writable/) { Dir.tmpdir })
106+
107+
ensure
108+
File.chmod(0755, tmpdir)
109+
ENV.update(oldenv)
110+
end
111+
end
112+
end
113+
62114
def test_no_homedir
63115
bug7547 = '[ruby-core:50793]'
64116
home, ENV["HOME"] = ENV["HOME"], nil

0 commit comments

Comments
 (0)