Skip to content

Commit a32c2ff

Browse files
committed
Fix history saving when directory writability is unreliable
1 parent b140bfc commit a32c2ff

2 files changed

Lines changed: 52 additions & 25 deletions

File tree

lib/irb/history.rb

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,6 @@ def load_history
6464
def save_history
6565
return unless History.save_history?
6666
return unless (history_file = History.history_file)
67-
unless ensure_history_file_writable(history_file)
68-
warn <<~WARN
69-
Can't write history to #{History.history_file.inspect} due to insufficient permissions.
70-
Please verify the value of `IRB.conf[:HISTORY_FILE]`. Ensure the folder exists and that both the folder and file (if it exists) are writable.
71-
WARN
72-
return
73-
end
7467

7568
history = self.class::HISTORY.to_a
7669

@@ -80,36 +73,43 @@ def save_history
8073
append_history = true
8174
end
8275

83-
File.open(history_file, (append_history ? "a" : "w"), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f|
84-
hist = history.map { |l| l.scrub.split("\n").join("\\\n") }
76+
begin
77+
ensure_history_file_permissions(history_file)
8578

86-
unless append_history || History.infinite?
87-
# Check size before slicing because array.last(huge_number) raises RangeError.
88-
hist = hist.last(History.save_history) if hist.size > History.save_history
89-
end
79+
File.open(history_file, (append_history ? "a" : "w"), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f|
80+
hist = history.map { |l| l.scrub.split("\n").join("\\\n") }
81+
82+
unless append_history || History.infinite?
83+
# Check size before slicing because array.last(huge_number) raises RangeError.
84+
hist = hist.last(History.save_history) if hist.size > History.save_history
85+
end
9086

91-
f.puts(hist)
87+
f.puts(hist)
88+
end
89+
rescue Errno::ENOENT
90+
warn <<~WARN
91+
Can't write history to #{History.history_file.inspect} because the folder does not exist.
92+
Please verify the value of `IRB.conf[:HISTORY_FILE]`. Ensure the folder exists.
93+
WARN
94+
rescue Errno::EACCES, Errno::EPERM
95+
warn <<~WARN
96+
Can't write history to #{History.history_file.inspect} due to insufficient permissions.
97+
Please verify the value of `IRB.conf[:HISTORY_FILE]`. Ensure the folder exists and that both the folder and file (if it exists) are writable.
98+
WARN
9299
end
93100
end
94101

95102
private
96103

97-
# Returns boolean whether writing to +history_file+ will be possible.
98104
# Permissions of already existing +history_file+ are changed to
99105
# owner-only-readable if necessary [BUG #7694].
100-
def ensure_history_file_writable(history_file)
106+
def ensure_history_file_permissions(history_file)
101107
history_file = Pathname.new(history_file)
102108

103-
return false unless history_file.dirname.writable?
104-
return true unless history_file.exist?
109+
return unless history_file.exist?
105110

106-
begin
107-
if history_file.stat.mode & 0o66 != 0
108-
history_file.chmod 0o600
109-
end
110-
true
111-
rescue Errno::EPERM # no permissions
112-
false
111+
if history_file.stat.mode & 0o66 != 0
112+
history_file.chmod 0o600
113113
end
114114
end
115115
end

test/irb/test_history.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,33 @@ def test_history_concurrent_use_not_present
169169
assert_equal(%w"line0 line1 line2", File.read(history_file).split)
170170
end
171171

172+
def test_history_save_uses_actual_write_result_not_directory_writable_predicate
173+
IRB.conf[:SAVE_HISTORY] = 1
174+
io = TestInputMethodWithRelineHistory.new
175+
io.class::HISTORY.clear
176+
io.class::HISTORY << "line1"
177+
178+
history_file = IRB.rc_file("_history")
179+
FileUtils.rm_f(history_file)
180+
181+
original_writable = Pathname.instance_method(:writable?)
182+
Pathname.class_eval do
183+
define_method(:writable?) { false }
184+
end
185+
186+
assert_warn("") do
187+
io.save_history
188+
end
189+
190+
assert_equal("line1\n", File.read(history_file))
191+
ensure
192+
if original_writable
193+
Pathname.class_eval do
194+
define_method(:writable?, original_writable)
195+
end
196+
end
197+
end
198+
172199
def test_history_different_encodings
173200
IRB.conf[:SAVE_HISTORY] = 2
174201
IRB.conf[:LC_MESSAGES] = IRB::Locale.new("en_US.ASCII")

0 commit comments

Comments
 (0)