def safe_open(file, mode = File::RDONLY, opts = {}, &block)
if mode.is_a?(String)
raise Errno::EPERM, "Bad mode string #{mode.inspect} for opening a file safely." if %w(w w+).include?(mode)
elsif mode.is_a?(Integer)
raise Errno::EPERM, "Bad mode string #{mode.inspect} for opening a file safely." if (File::TRUNC == (mode & File::TRUNC))
else
raise ArgumentError, "Bad mode #{mode.inspect}"
end
opts = {:uid => nil, :gid => nil, :mode => (0666 - File.umask)}.merge(opts)
fh = nil
begin
fh = File.open(file, mode, opts[:mode])
link_stat = fh.lstat
file_stat = fh.stat
if link_stat.symlink? and file_stat.uid != link_stat.uid
raise Errno::EPERM, file
end
if ( link_stat.writable? and link_stat.file? and
( File::WRONLY == (fh.fcntl(Fcntl::F_GETFL) & File::WRONLY) or
File::RDWR == (fh.fcntl(Fcntl::F_GETFL) & File::RDWR) ) )
if ((opts[:uid] and file_stat.uid != opts[:uid]) or
(opts[:gid] and file_stat.gid != opts[:gid]))
fh.chown(opts[:uid], opts[:gid])
end
if opts[:mode]
fh.chmod(opts[:mode])
end
end
rescue ArgumentError, IOError, SystemCallError => err
fh.close unless fh.nil? or fh.closed?
raise err
end
if block_given?
begin
yield fh
ensure
fh.close unless fh.nil? or fh.closed?
end
else
return fh
end
end