Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify WindowsFile #3774

Merged
merged 3 commits into from Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
164 changes: 50 additions & 114 deletions lib/fluent/plugin/file_wrapper.rb
Expand Up @@ -16,35 +16,8 @@

module Fluent
module FileWrapper
include File::Constants

def self.mode2flags(mode)
# Always need BINARY to enable SHARE_DELETE
# https://bugs.ruby-lang.org/issues/11218
# https://github.com/ruby/ruby/blob/d6684f063bc53e3cab025bd39526eca3b480b5e7/win32/win32.c#L6332-L6345
flags = BINARY | SHARE_DELETE
case mode.delete("b")
when "r"
flags |= RDONLY
when "r+"
flags |= RDWR
when "w"
flags |= WRONLY | CREAT | TRUNC
when "w+"
flags |= RDWR | CREAT | TRUNC
when "a"
flags |= WRONLY | CREAT | APPEND
when "a+"
flags |= RDWR | CREAT | APPEND
else
raise Errno::EINVAL.new("Unsupported mode by Fluent::FileWrapper: #{mode}")
end
end

def self.open(path, mode='r')
# inject File::Constants::SHARE_DELETE
# https://github.com/fluent/fluentd/pull/3585#issuecomment-1101502617
io = File.open(path, mode2flags(mode))
io = WindowsFile.new(path, mode).io
if block_given?
v = yield io
io.close
Expand All @@ -62,97 +35,35 @@ def self.stat(path)
end
end

class Win32Error < StandardError
require 'windows/error'
include Windows::Error

attr_reader :errcode, :msg

WSABASEERR = 10000

def initialize(errcode, msg = nil)
@errcode = errcode
@msg = msg
end

def format_english_message(errcode)
buf = 0.chr * 260
flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY
english_lang_id = 1033 # The result of MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)
FormatMessageA.call(flags, 0, errcode, english_lang_id, buf, buf.size, 0)
buf.force_encoding(Encoding.default_external).strip
end

def to_s
msg = super
msg << ": code: #{@errcode}, #{format_english_message(@errcode)}"
msg << " - #{@msg}" if @msg
msg
end

def inspect
"#<#{to_s}>"
end

def ==(other)
return false if other.class != Win32Error
@errcode == other.errcode && @msg == other.msg
end

def wsaerr?
@errcode >= WSABASEERR
end
end

# To open and get stat with setting FILE_SHARE_DELETE.
# Although recent Ruby's File.stat uses it, we still need this to keep
# backward compatibility of ino and delete_pending methods.
class WindowsFile
require 'windows/file'
require 'windows/error'
require 'windows/handle'
require 'windows/nio'

include Windows::Error
include File::Constants
include Windows::File
include Windows::Handle
include Windows::NIO

def initialize(path, mode='r', sharemode=FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE)
@path = path
@file_handle = INVALID_HANDLE_VALUE
@mode = mode

attr_reader :io

access, creationdisposition, _seektoend = case mode.delete('b')
when "r" ; [FILE_GENERIC_READ , OPEN_EXISTING, false]
when "r+"; [FILE_GENERIC_READ | FILE_GENERIC_WRITE, OPEN_ALWAYS , false]
when "w" ; [FILE_GENERIC_WRITE , CREATE_ALWAYS, false]
when "w+"; [FILE_GENERIC_READ | FILE_GENERIC_WRITE, CREATE_ALWAYS, false]
when "a" ; [FILE_GENERIC_WRITE , OPEN_ALWAYS , true]
when "a+"; [FILE_GENERIC_READ | FILE_GENERIC_WRITE, OPEN_ALWAYS , true]
else raise "unknown mode '#{mode}'"
end

@file_handle = CreateFile.call(@path, access, sharemode,
0, creationdisposition, FILE_ATTRIBUTE_NORMAL, 0)
if @file_handle == INVALID_HANDLE_VALUE
win32err = Win32Error.new(Win32::API.last_error, path)
errno = ServerEngine::RbWinSock.rb_w32_map_errno(win32err.errcode)
if errno == Errno::EINVAL::Errno || win32err.wsaerr?
# maybe failed to map
raise win32err
else
raise SystemCallError.new(win32err.message, errno)
end
def initialize(path, mode='r')
@path = path
@io = File.open(path, mode2flags(mode))
@file_handle = _get_osfhandle(@io.to_i)
@io.instance_variable_set(:@file_index, self.ino)
def @io.ino
@file_index
end
end

def close
CloseHandle.call(@file_handle)
@io.close
@file_handle = INVALID_HANDLE_VALUE
end

# To keep backward compatibility, we continue to use GetFileInformationByHandle()
# to get file id.
# Node that Ruby's File.stat uses GetFileInformationByHandleEx() with FileIdInfo
# and returned value is different with above one.
ashie marked this conversation as resolved.
Show resolved Hide resolved
def ino
by_handle_file_information = '\0'*(4+8+8+8+4+4+4+4+4+4) #72bytes

Expand All @@ -163,6 +74,41 @@ def ino
by_handle_file_information.unpack("I11Q1")[11] # fileindex
end

def stat
raise Errno::ENOENT if delete_pending
s = File.stat(@path)
s.instance_variable_set :@ino, self.ino
def s.ino; @ino; end
s
end

private

def mode2flags(mode)
# Always inject File::Constants::SHARE_DELETE
# https://github.com/fluent/fluentd/pull/3585#issuecomment-1101502617
# To enable SHARE_DELETE, BINARY is also required.
# https://bugs.ruby-lang.org/issues/11218
# https://github.com/ruby/ruby/blob/d6684f063bc53e3cab025bd39526eca3b480b5e7/win32/win32.c#L6332-L6345
flags = BINARY | SHARE_DELETE
case mode.delete("b")
when "r"
flags |= RDONLY
when "r+"
flags |= RDWR
when "w"
flags |= WRONLY | CREAT | TRUNC
when "w+"
flags |= RDWR | CREAT | TRUNC
when "a"
flags |= WRONLY | CREAT | APPEND
when "a+"
flags |= RDWR | CREAT | APPEND
else
raise Errno::EINVAL.new("Unsupported mode by Fluent::FileWrapper: #{mode}")
end
end

# DeletePending is a Windows-specific file state that roughly means
# "this file is queued for deletion, so close any open handlers"
#
Expand All @@ -181,15 +127,5 @@ def delete_pending

return buf.unpack("QQICC")[3] != 0
end

private :delete_pending

def stat
raise Errno::ENOENT if delete_pending
s = File.stat(@path)
s.instance_variable_set :@ino, self.ino
def s.ino; @ino; end
s
end
end
end if Fluent.windows?
68 changes: 0 additions & 68 deletions test/plugin/test_file_wrapper.rb
Expand Up @@ -17,58 +17,6 @@ def teardown
FileUtils.rm_rf(TMP_DIR)
end

sub_test_case 'Win32Error' do
test 'equal' do
assert_equal(Fluent::Win32Error.new(ERROR_SHARING_VIOLATION, "message"),
Fluent::Win32Error.new(ERROR_SHARING_VIOLATION, "message"))
end

test 'different error code' do
assert_not_equal(Fluent::Win32Error.new(ERROR_FILE_NOT_FOUND),
Fluent::Win32Error.new(ERROR_SHARING_VIOLATION))
end

test 'different error message' do
assert_not_equal(Fluent::Win32Error.new(ERROR_FILE_NOT_FOUND, "message1"),
Fluent::Win32Error.new(ERROR_FILE_NOT_FOUND, "message2"))
end

test 'different class' do
assert_not_equal(Errno::EPIPE,
Fluent::Win32Error.new(ERROR_SHARING_VIOLATION))
end

test 'ERROR_SHARING_VIOLATION message' do
assert_equal(Fluent::Win32Error.new(ERROR_SHARING_VIOLATION).message,
"Fluent::Win32Error: code: 32, The process cannot access the file because it is being used by another process.")
end

test 'ERROR_SHARING_VIOLATION with a message' do
assert_equal(Fluent::Win32Error.new(ERROR_SHARING_VIOLATION, "cannot open the file").message,
"Fluent::Win32Error: code: 32, The process cannot access the file because it is being used by another process." +
" - cannot open the file")
end

test 'to_s' do
assert_equal("Fluent::Win32Error: code: 32, The process cannot access the file because it is being used by another process. - C:\file.txt",
Fluent::Win32Error.new(ERROR_SHARING_VIOLATION, "C:\file.txt").to_s)
end

test 'inspect' do
assert_equal("#<Fluent::Win32Error: code: 32, The process cannot access the file because it is being used by another process. - C:\file.txt>",
Fluent::Win32Error.new(ERROR_SHARING_VIOLATION, "C:\file.txt").inspect)
end

data('0' => [false, 0],
'9999' => [false, 9999],
'10000' => [true, 10000],
'10001' => [true, 10001])
test 'wsaerr?' do |data|
expected, code = data
assert_equal(expected, Fluent::Win32Error.new(code).wsaerr?)
end
end

sub_test_case 'WindowsFile exceptions' do
test 'nothing raised' do
begin
Expand Down Expand Up @@ -106,21 +54,5 @@ def teardown
file.close if file
end
end

test 'ERROR_SHARING_VIOLATION raised' do
begin
path = "#{TMP_DIR}/test_windows_file.txt"
file1 = file2 = nil
file1 = File.open(path, "wb")
win32err = Fluent::Win32Error.new(ERROR_SHARING_VIOLATION, path)
assert_raise(Errno::EACCES.new(win32err.message)) do
file2 = Fluent::WindowsFile.new(path, 'r', FILE_SHARE_READ)
ensure
file2.close if file2
end
ensure
file1.close if file1
end
end
end
end if Fluent.windows?