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

Leverage fileapi for opening files on windows #13178

Merged
9 changes: 9 additions & 0 deletions spec/std/file_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,15 @@ describe "File" do
end
end

it "deletes an open file" do
with_tempfile("delete-file.txt") do |filename|
file = File.open filename, "w"
File.exists?(file.path).should be_true
file.delete
File.exists?(file.path).should be_false
end
end

it "deletes? a file" do
with_tempfile("delete-file.txt") do |filename|
File.open(filename, "w") { }
Expand Down
80 changes: 77 additions & 3 deletions src/crystal/system/win32/file.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,85 @@ module Crystal::System::File
end

def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {LibC::Int, Errno}
flags |= LibC::O_BINARY | LibC::O_NOINHERIT
Copy link
Contributor

@HertzDevil HertzDevil Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should keep these mandatory flags

Copy link
Member Author

@Blacksmoke16 Blacksmoke16 Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensuring O_BINARY is added should address your below comment.

EDIT: But to make the code cleaner, might just hardcode/inline the _setmode call.

O_NOINHERIT seems to be controlled via the bInheritHandle property of https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa379560(v=vs.85) which is the 4th argument to CreateFileW. However, the documentation also says:

If this parameter is NULL, the handle returned by CreateFile cannot be inherited by any child processes the application may create and the file or device associated with the returned handle gets a default security descriptor.

So given we'd always be applying O_NOINHERIT, I think we'll be fine as it is.

access, disposition, attributes, share = self.posix_to_open_opts flags, perm

fd = LibC._wopen(System.to_wstr(filename), flags, perm)
handle = LibC.CreateFileW(
System.to_wstr(filename),
access,
share, # UNIX semantics
Blacksmoke16 marked this conversation as resolved.
Show resolved Hide resolved
nil,
disposition,
attributes,
LibC::HANDLE.null
)

if handle == LibC::INVALID_HANDLE_VALUE
# Map ERROR_FILE_EXISTS to Errno::EEXIST to avoid changing semantics of other systems
if WinError.value.error_file_exists?
Errno.value = Errno::EEXIST
end

return {-1, Errno.value}
Blacksmoke16 marked this conversation as resolved.
Show resolved Hide resolved
end

fd = LibC._open_osfhandle handle, flags

if fd == -1
return {-1, Errno.value}
end

if flags & LibC::O_BINARY > 0
Blacksmoke16 marked this conversation as resolved.
Show resolved Hide resolved
LibC._setmode fd, LibC::O_BINARY
elsif flags & LibC::O_TEXT > 0
LibC._setmode fd, LibC::O_TEXT
end

{fd, Errno::NONE}
end

private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions)
disposition = 0

Blacksmoke16 marked this conversation as resolved.
Show resolved Hide resolved
access = if flags & LibC::O_WRONLY > 0
LibC::GENERIC_WRITE
elsif flags & LibC::O_RDWR > 0
LibC::GENERIC_READ | LibC::GENERIC_WRITE
else
LibC::GENERIC_READ
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O_RDONLY's value is actually 0, so was failing the > 0 or != 0 check, so I just made it the default given that was 0 anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows only says:

To specify the file access mode, you must specify either _O_RDONLY, _O_RDWR, or _O_WRONLY. There's no default value for the access mode.

So this check is fine on Windows, but to be precise, these flags are not bit flags at all. POSIX defines also O_ACCMODE, to be used like this:

access =
  case flags & LibC::O_ACCMODE # unavailable on MSVC
  when LibC::O_RDONLY
  when LibC::O_WRONLY
  when LibC::O_RDWR
  when LibC::O_SEARCH # unavailable on most platforms
  when LibC::O_EXEC   # unavailable on most platforms
  else
    raise "not allowed"
  end

Blacksmoke16 marked this conversation as resolved.
Show resolved Hide resolved
end

if flags & LibC::O_APPEND > 0
access |= LibC::FILE_APPEND_DATA
end

case flags & (LibC::O_CREAT | LibC::O_EXCL | LibC::O_TRUNC)
when 0, LibC::O_EXCL then disposition = LibC::OPEN_EXISTING
when LibC::O_CREAT then disposition = LibC::OPEN_ALWAYS
when LibC::O_CREAT | LibC::O_EXCL, LibC::O_CREAT | LibC::O_TRUNC | LibC::O_EXCL then disposition = LibC::CREATE_NEW
when LibC::O_TRUNC, LibC::O_TRUNC | LibC::O_EXCL then disposition = LibC::TRUNCATE_EXISTING
when LibC::O_CREAT | LibC::O_TRUNC then disposition = LibC::CREATE_ALWAYS
end
Blacksmoke16 marked this conversation as resolved.
Show resolved Hide resolved

attributes = LibC::FILE_ATTRIBUTE_NORMAL
unless perm.owner_write?
attributes |= LibC::FILE_ATTRIBUTE_READONLY
end

if flags & LibC::O_TEMPORARY > 0
attributes |= LibC::FILE_FLAG_DELETE_ON_CLOSE | LibC::FILE_ATTRIBUTE_TEMPORARY
access |= LibC::DELETE
end

if flags & LibC::O_SHORT_LIVED > 0
attributes |= LibC::FILE_ATTRIBUTE_TEMPORARY
end

case flags & (LibC::O_SEQUENTIAL | LibC::O_RANDOM)
when LibC::O_SEQUENTIAL then attributes |= LibC::FILE_FLAG_SEQUENTIAL_SCAN
when LibC::O_RANDOM then attributes |= LibC::FILE_FLAG_RANDOM_ACCESS
end

{fd, fd == -1 ? Errno.value : Errno::NONE}
{access, disposition, attributes, LibC::DEFAULT_SHARE_MODE}
end

NOT_FOUND_ERRORS = {
Expand Down
24 changes: 21 additions & 3 deletions src/lib_c/x86_64-windows-msvc/c/fileapi.cr
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,37 @@ lib LibC
fun SetFileAttributesW(lpFileName : LPWSTR, dwFileAttributes : DWORD) : BOOL
fun GetFileAttributesExW(lpFileName : LPWSTR, fInfoLevelId : GET_FILEEX_INFO_LEVELS, lpFileInformation : Void*) : BOOL

OPEN_EXISTING = 3
CREATE_NEW = 1
CREATE_ALWAYS = 2
OPEN_EXISTING = 3
OPEN_ALWAYS = 4
TRUNCATE_EXISTING = 5

FILE_ATTRIBUTE_NORMAL = 0x80
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
FILE_ATTRIBUTE_NORMAL = 0x80
FILE_ATTRIBUTE_TEMPORARY = 0x100

FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
FILE_FLAG_RANDOM_ACCESS = 0x10000000
FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000

FILE_SHARE_READ = 0x1
FILE_SHARE_WRITE = 0x2
FILE_SHARE_DELETE = 0x4

DEFAULT_SHARE_MODE = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE

GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000

fun CreateFileW(lpFileName : LPWSTR, dwDesiredAccess : DWORD, dwShareMode : DWORD,
lpSecurityAttributes : SECURITY_ATTRIBUTES*, dwCreationDisposition : DWORD,
dwFlagsAndAttributes : DWORD, hTemplateFile : HANDLE) : HANDLE

fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int
fun _setmode(fd : LibC::Int, mode : LibC::Int) : LibC::Int
Blacksmoke16 marked this conversation as resolved.
Show resolved Hide resolved

fun ReadFile(hFile : HANDLE, lpBuffer : Void*, nNumberOfBytesToRead : DWORD, lpNumberOfBytesRead : DWORD*, lpOverlapped : OVERLAPPED*) : BOOL

MAX_PATH = 260
Expand Down
7 changes: 5 additions & 2 deletions src/lib_c/x86_64-windows-msvc/c/winnt.cr
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ lib LibC
FILE_ATTRIBUTE_READONLY = 0x1
FILE_ATTRIBUTE_REPARSE_POINT = 0x400

FILE_READ_ATTRIBUTES = 0x80
FILE_WRITE_ATTRIBUTES = 0x0100
FILE_APPEND_DATA = 0x00000004

DELETE = 0x00010000
FILE_READ_ATTRIBUTES = 0x80
FILE_WRITE_ATTRIBUTES = 0x0100

# Memory protection constants
PAGE_READWRITE = 0x04
Expand Down