Skip to content
Merged
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
17 changes: 16 additions & 1 deletion lib/puppet/util/selinux.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ module Puppet::Util::SELinux
S_IFDIR = 0o040000
S_IFLNK = 0o120000

@@mounts_cache = nil
@@mounts_mtime = nil

def self.selinux_support?
return false unless defined?(Selinux)
if Selinux.is_selinux_enabled == 1
Expand Down Expand Up @@ -259,6 +262,16 @@ def file_mode(file, resource_ensure)

# Internal helper function to read and parse /proc/mounts
def read_mounts
current_mtime = begin
File.mtime('/proc/mounts')
rescue Errno::ENOENT, Errno::ENOTDIR
nil
end

if @@mounts_cache && current_mtime && @@mounts_mtime == current_mtime
return @@mounts_cache
end

mounts = ''.dup
begin
if File.method_defined? "read_nonblock"
Expand Down Expand Up @@ -292,7 +305,9 @@ def read_mounts

mntpoint[params[1]] = params[2]
end
mntpoint

@@mounts_mtime = current_mtime
@@mounts_cache = mntpoint
end

# Internal helper function to return which type of filesystem a given file
Expand Down
50 changes: 48 additions & 2 deletions spec/unit/util/selinux_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,20 @@
allow(File).to receive(:new).and_call_original()
allow(File).to receive(:new).with("/proc/mounts").and_return(fh)
times_fh_called = 0
expect(fh).to receive(:read_nonblock) do
allow(fh).to receive(:read_nonblock) do
times_fh_called += 1
raise EOFError if times_fh_called > 1

"rootfs / rootfs rw 0 0\n/dev/root / ext3 rw,relatime,errors=continue,user_xattr,acl,data=ordered 0 0\n/dev /dev tmpfs rw,relatime,mode=755 0 0\n/proc /proc proc rw,relatime 0 0\n/sys /sys sysfs rw,relatime 0 0\n192.168.1.1:/var/export /mnt/nfs nfs rw,relatime,vers=3,rsize=32768,wsize=32768,namlen=255,hard,nointr,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=192.168.1.1,mountvers=3,mountproto=udp,addr=192.168.1.1 0 0\n"
end.twice()
end

Puppet::Util::SELinux.class_variable_set(:@@mounts_cache, nil)
Puppet::Util::SELinux.class_variable_set(:@@mounts_mtime, nil)
end

it "should parse the contents of /proc/mounts" do
expect(File).to receive(:mtime).with('/proc/mounts').once.and_return(Time.at(1000))

result = read_mounts
expect(result).to eq({
'/' => 'ext3',
Expand All @@ -57,6 +62,47 @@
'/proc' => 'proc',
'/dev' => 'tmpfs' })
end

it "should memoize the result of read_mounts on subsequent calls" do
expect(File).to receive(:mtime).with('/proc/mounts').twice.and_return(Time.at(1000))

first_result = read_mounts

expect(File).not_to receive(:new).with("/proc/mounts")

second_result = read_mounts
expect(second_result).to eq(first_result)
end

it "should re-read /proc/mounts if the mtime changes mid-run" do
# Simulate an update to the file modification time across sequential reads
expect(File).to receive(:mtime).with('/proc/mounts').twice.and_return(Time.at(1000), Time.at(2000))

fh = double('fh', :close => nil)
allow(File).to receive(:new).with("/proc/mounts").and_return(fh)

# Mock alternating file content and EOF responses across reads
times_fh_called = 0
allow(fh).to receive(:read_nonblock) do
times_fh_called += 1

raise EOFError if times_fh_called.even?

if times_fh_called == 1
"/dev /dev tmpfs rw,relatime,mode=755 0 0\n"
else
"/dev /dev tmpfs rw,relatime,mode=755 0 0\n/dev/foo /mnt/newfs ext3 rw 0 0\n"
end
end

# Initial read should populate the baseline cache map
result1 = read_mounts
expect(result1).to eq({ '/dev' => 'tmpfs' })

# An mtime mismatch must invalidate the cache and return updated mount data
result2 = read_mounts
expect(result2).to eq({ '/dev' => 'tmpfs', '/mnt/newfs' => 'ext3' })
end
end

describe "filesystem detection" do
Expand Down