Skip to content
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
81 changes: 78 additions & 3 deletions src/crystal/system/win32/file.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,86 @@ 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

@HertzDevil HertzDevil Mar 14, 2023

Copy link
Copy Markdown
Contributor

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

@Blacksmoke16 Blacksmoke16 Mar 14, 2023

Copy link
Copy Markdown
Member Author

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
Comment thread
Blacksmoke16 marked this conversation as resolved.
Outdated
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}
Comment thread
Blacksmoke16 marked this conversation as resolved.
Outdated
end

fd = LibC._open_osfhandle handle, flags

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

if flags & LibC::O_BINARY > 0
Comment thread
Blacksmoke16 marked this conversation as resolved.
Outdated
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)
access = disposition = attributes = 0

case flags & (LibC::O_RDONLY | LibC::O_WRONLY | LibC::O_RDWR)
when LibC::O_RDONLY then access = LibC::GENERIC_READ
when LibC::O_WRONLY then access = LibC::GENERIC_WRITE
when LibC::O_RDWR then access = LibC::GENERIC_READ | LibC::GENERIC_WRITE
end
Comment thread
Blacksmoke16 marked this conversation as resolved.
Outdated

if flags & LibC::O_APPEND > 0
access |= LibC::FILE_APPEND_DATA
attributes &= ~LibC::FILE_FLAG_BACKUP_SEMANTICS
Comment thread
Blacksmoke16 marked this conversation as resolved.
Outdated
end

share = LibC::DEFAULT_SHARE_MODE

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
Comment thread
Blacksmoke16 marked this conversation as resolved.
Outdated

attributes |= LibC::FILE_ATTRIBUTE_NORMAL
Comment thread
Blacksmoke16 marked this conversation as resolved.
Outdated
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, share}
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
Comment thread
Blacksmoke16 marked this conversation as resolved.
Outdated

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