Go: proposal: os: Create/Open/OpenFile() set FILE_SHARE_DELETE on windows

Created on 16 May 2019  ·  194Comments  ·  Source: golang/go

On Linux & MacOS we can write this; on Windows it fails with a "sharing violation":

path := "delete-after-open"
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
if err != nil { ... }
err = os.Remove(path)            // or os.Rename(path, path+"2")
if err != nil { ... }
fd.Close()

If you develop on Windows and deploy to Linux etc, and your code relies on this _undocumented_ GOOS=windows behavior of os.Rename() & .Remove(), it is broken and perhaps vulnerable. Note that package "os" has _fifteen mentions_ of other Windows-specific behavior.

To fix this, syscall.Open() at https://golang.org/src/syscall/syscall_windows.go#L272
needs sharemode |= FILE_SHARE_DELETE

Microsoft recommends this be made the default: https://github.com/golang/go/issues/32088#issuecomment-502850674
Rust made it a default: https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html
Mingw-w64 made it a default seven years ago:
https://sourceforge.net/p/mingw-w64/code/HEAD/tree/stable/v3.x/mingw-w64-headers/include/ntdef.h#l858
Erlang made it a default over six years ago: erlang/otp@0e02f48
Python couldn't adopt it due to limitations of MSVC runtime: https://bugs.python.org/issue15244

~Therefore syscall.Open() should use file_share_delete by default, and syscall should provide both:
a) a global switch to disable it (for any existing apps that rely on its absence), and
b) a flag for use with os.OpenFile() to disable it on a specific file handle.~

Update after https://github.com/golang/go/issues/32088#issuecomment-532784947 by @rsc:
a) os.Create/Open/OpenFile() should always enable file_share_delete on Windows,
b) syscall.Open() on Windows should accept a flag which enables file_share_delete, and
c) syscall on Windows should export a constant for the new flag.

The docs for os.Remove() should also note that to reuse a filename after deleting an open file on Windows, one must do: os.Rename(path, unique_path); os.Remove(unique_path).

Win API docs
https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-deletefilea
https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea

If there is a reason not to do this, it should be documented in os.Remove() & .Rename().

cc @alexbrainman
@gopherbot add OS-Windows

FrozenDueToAge OS-Windows Proposal Proposal-FinalCommentPeriod release-blocker

Most helpful comment

I'll try to represent the Windows team perspective here... we'd rather Go just always set FILE_SHARE_DELETE, without any option. In general, to make porting to/from Windows easier, we would prefer consistent behavior with Linux, and I don't see why Windows users or software would prefer the default !FILE_SHARE_DELETE behavior enough for this to be an exception to the rule.

An interesting note, contrary to @mattn's comment: in the most recent version of Windows, we updated DeleteFile (on NTFS) to perform a "POSIX" delete, where the file is removed from the namespace immediately instead of waiting for all open handles to the file to be closed. It still respects FILE_SHARE_DELETE, but now otherwise behaves more like POSIX unlink. This functionality was added for WSL and was considered worth using by default for Windows software, too.

In other words, if if you run mattn's test program and sequence of del, dir, etc. on the latest version of Windows, you will see the file disappear from the namespace immediately after deleting the file, not after the test program exits. Just like Linux.

So even in Windows itself, with its significantly larger appcompat risk, we are making small changes to ease the porting of non-Windows software to Windows. I really encourage Go to do the same.

All 194 comments

/cc @alexbrainman

syscall.Open() at https://golang.org/src/syscall/syscall_windows.go#L272
should use sharemode := ... | FILE_SHARE_DELETE

Why should it?

Alex

FILE_SHARE_DELETE enables the code example above, which works on MacOS & Linux but fails on Windows. It's also necessary for:

fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
defer fd.Close()
_, err = fd.Write(...)
err = fd.Sync()  // file now safe to share
err = os.Rename(path, path+"2")
_, err = fd.Read(...)

FILE_SHARE_DELETE enables the code example above, which works on MacOS & Linux but fails on Windows.

Why don't we adjust MacOS & Linux code instead?

It's also necessary for:

I don't understand what you are trying to say.

Alex

@networkimprov You must call Remove after Close() on Windows.

path := "delete-after-open"
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
if err != nil { panic(err) }
fd.Close()
err = os.Remove(path)            // or os.Rename(path, path+"2")
if err != nil { panic(err) }

@alexbrainman when doing reliable file I/O (as for databases), it's standard practice to create a file with a temporary name, write it, fsync it, and rename it.

Open files can be renamed or deleted by default on Unix. It seems like an oversight that the Windows flag for this capability is not set. I doubt we'll convince the Go team to change the way it works for Linux & MacOS :-)

@mattn pls apply the fix I described and try the code I posted.

I doubt we'll convince the Go team to change the way it works for Linux & MacOS :-)

I am fine the way things are now.

Alex

Is there a rationale for omitting this common capability in Windows?

Can you provide a switch in syscall_windows.go so that we can select the Unix behavior at program start?

I'm okay that we add new API or flags. But I have objection to change current behavior. Since FILE_SHARE_DELETE is not same as Unix behavior.

#include <windows.h>
#include <stdio.h>

int
main(int argc, char* argv[]) {
  HANDLE h = CreateFile("test.txt",
      GENERIC_READ | GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  getchar();
  char buf[256] = {0};
  DWORD nread;
  printf("%d\n", ReadFile(h, buf, 256, &nread, NULL));
  printf("%s,%d\n", buf, nread);
  return 0;
}

Comple this code on Windows, and try to run as test.exe. While this app is waiting hit-a-key, open new cmd.exe, delete "test.txt" like following.

image

The file can be deleted but remaining there while the process exists. So this change will not work well for your expected.

I realize the directory entry isn't removed but the docs say

Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED.

So I don't understand your screen log.

Anyway, a switch like this would be fine:

syscall.OpenFileShareDelete = true

I’d like to mention that I’ve been bitten by this at work before.

Basically we had:

f, err := os.Open(...)
if err != nil { ... }
defer f.Close()

// lots of code

if err := os.Rename(...); err != nil { ... }

The code ran fine on our unix-based CI platforms, but exploded on Windows.

Assuming there aren’t any weird side effects, it would be nice if things just worked.

IIRC, we've made a few adjustments to GOOS=windows & plan9 behavior in the past to more closely match Unix semantics. I wouldn't mind making this be another such case if the semantics are close enough. @mattn's comment, however, suggests the behavior is not close enough so it might not be worth it.

I don't want to see some global option, though. That just seems like a debugging nightmare.

@bradfitz
It would probably not, please refer to my comment here
https://groups.google.com/forum/#!topic/golang-dev/R79TJAzsBfM
or if you prefer I can copy the content here, as there is also a possible solution, although I would not implement it as the default behavior, as this would not be consistent with how windows programs behave but rather a workaround to create a similar cross-os experience.

@guybrand writes in the golang-dev thread:

my program is writing a file called "my.data", and then calls another program and does not wait for it to end ... this program lets say uploads this file which takes 10 seconds (lets call this other program "uploader") .
...
When my program calls .Remove on Windows (FILE_SHARE_DELETE is on):
...
uploader would receive an error telling it the file is removed (probably EOF).

Assuming "uploader" opens the file before "my program" calls os.Remove(), I think you contradicted the Win API docs:

_DeleteFile marks a file for deletion on close. Therefore, the file deletion does not occur until the last handle to the file is closed. Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED._

Re "pairing an _open_osfhandle() of the CreateFile to an _fdopen", can you point to example code?

If we add FILE_SHARE_DELETE, many programmer will mistakenly use it to simulate Unix behavior. In this case, you can make a function separated by build-constraints for each OSs. And it should return os.NewFile(), use FILE_SHARE_DELETE on Windows.

make a function separated by build-constraints for each OSs... return os.NewFile(), use FILE_SHARE_DELETE on Windows

To retain the functionality of os.OpenFile() this plan appears to require reimplementing all of:

os.OpenFile()
openFileNolog()
openFile()
openDir()
newFile()
fixLongPath()
syscall.Open()
makeInheritSa()

@networkimprov

@guybrand writes in the golang-dev thread:

my program is writing a file called "my.data", and then calls another program and does not wait for it to end ... this program lets say uploads this file which takes 10 seconds (lets call this other program "uploader") .
...
When my program calls .Remove on Windows (FILE_SHARE_DELETE is on):
...
uploader would receive an error telling it the file is removed (probably EOF).

Assuming "uploader" opens the file before "my program" calls os.Remove(), haven't you contradicted the Win API docs?

DeleteFile marks a file for deletion on close. Therefore, the file deletion does not occur until the last handle to the file is closed. Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED.

Re "pairing an _open_osfhandle() of the CreateFile to an _fdopen", can you point to example code?

I dont see a contradiction with WIndows API, please point what looks like a contridiction.

As for a code sample, I Googled a bit, and found this:
http://blog.httrack.com/blog/2013/10/05/creating-deletable-and-movable-files-on-windows/

BUT
Please note - this will only give you a nix-like behavior internally, any other external program working with it will break it (as it 99% using standard windows API

@networkimprov
If you mean "this sounds like "dir my.data" would display the file, as far as I remember this was the behavior until windows .... XP or 7 (dont remember which) and changed since (perhaps the explorer just hides it somehow) - I can retest the next time I launch my windows env. (happens every few week...)

If you mean "this sounds like other processes with a handle to the file would still be able to read and write to the file", I would bet lunch the second you read write to such a file you do get an "EOF" style error - but again need to confirm to be 100% positive.

In either case, my point would be (at this point - I am taking "sides" in the "native like" vs " os-agnostic" point) - even if you implement _fdopen style solution, you would get inconsistency between your service and all other executables you collaborate with, so the use could only be in interaction with other go executables (or rare services that DO use fd's directly).

In other words - your app would be the "smartest kid in class" - that no other kid can understand.
Two examples from many I can think of:
Inputs:
My app downloads a file,the antivirus identifies its harfull and deletes/quarantines (==sort of rename) it, if I use fd - my app would still be able to do whatever it want with it (which is coll, but may end up hitting a virus...)
outputs:
my app pipes a file to another ("uploader" like) service, and deletes it, I even bothered and wrote a tester in go to see that all is working fine - and the test passes.
Now instead of my go test, I use filezilla, we transfter, dropbx API, whatever
it would fail/not behave in the same manner my test works...

Do you still think changing this to the default behavior makes sense ?

Is there a rationale for omitting this common capability in Windows?

I have never considered that question. I do not know.

The way Go files work on Windows is consistent with all other developers tools I have used in my life. It would be surprising to me, if Go files would work as you propose. I also suspect, it would break many existing programs.

Alex

I also suspect, it would break many existing programs.

@alexbrainman I also suggested a switch to enable it, instead of changing the default.

@bradfitz there is syscall.SocketDisableIPv6, that's not really different from a flag to adjust syscall.Open() behavior.

Since syscall_windows*.go states
func Open(path string, mode int, perm uint32) (fd Handle, err error) {
....
sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE)

and type_windows*.go has
FILE_SHARE_DELETE = 0x00000004

All is quite ready, the only question is how to implement the flag.
I do not like the global option as well as its not explicit, and while a single developer may remember he has set os.DeleteUponLastHandleClosed = 1, its not a good practice for long term or multiple developer.
Other options would either be setting a specific reserved number for flags, like in:
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.DELETE_WHEN_FREED, 0600)
whereas DELETE_WHEN_FREED can even be 0 for env. other than windows,

Another option would be to use the perm parameter, which is not supported for windows, this may get a little awkward
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 777)
777 is reserved, so we would either need a 1777 or -777 ro support both systems
to make is readable DELETE_WHEN_FREED | 777

last option I can think of is os.OpenDeletableFile(
Which would os.OpenFile on nix's and
turn
sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
on windows

all the above solutions are simple to implement, a little more time to test (cross os), just need a voter...

One way to support this behavior without changing the defaults or extending the API surface might be to just accept syscall.FILE_SHARE_DELETE in the flag parameter of os.OpenFile on Windows and fold it into the computed sharemode value. Since the syscall.O_* (and hence os.O_*) flags on Windows use made up values anyway, they could be engineered to avoid colliding with any Windows-specific flags that one wanted to include. Fortunately, they already avoid collisions with syscall.FILE_SHARE_DELETE.

In any event, I would strongly oppose changing the default behavior. Making FILE_SHARE_DELETE the default would put you in the same position as POSIX systems in terms of being unable to ensure race-free filesystem traversal. Having this flag be optional is why Windows doesn't need the equivalent of openat, renameat, readlinkat, etc.

I haven't really thought about what we should do here, but I want to make one thing clear:

Can you provide a switch in syscall_windows.go so that we can select the Unix behavior at program start?

We will not be doing this. That would make it impossible for a single program to use different packages that expect different behavior.

@havoc-io
Your suggestion would create error prone programs as syscall.FILE_SHARE_DELETE differs from POSIX std behavior.
Please note the authors of syscall_windows*.go were aware of the FILE_SHARE_DELETE flag (its in there) and decided using it would not be the preferred behavior.
Why?

lets take Liam's code, he
defer fd.Close()
deletes/renames
and then read/writes

so actual sort order is
open
mark as deleted
read/write (and probably cross process/cross go routine read write as well)
close

Current windows behavior alerts the developer "this is wrong", developer would probably push the delete to be defer'ed as well
If you Add FILE_SHARE_DELETE, windows would allow you to "mark as delete even though the file open" BUT another process/goroutine running concurrently that would try to access the file between the delete and the close (which is probably the longer task in this code) would fail
Result: instead of a consistent "you can do that" behavior - you would get a "sometimes - especially when there are many concurrent users it fails and I dont know why.
So your not solving the problem, just handling a specific use case on which the developer "only run that once"
My vote is for the consistent behavior of course...

How is that diff from Posix?
Once marked as deleted the file is no longer on the file table, it only exists as an fd, allowing another routine/process to create a file with the very same name.

I think we should stop looking for a "same solution" as there are differences we will not resolve within go (case sensitive filenames ? :) we will let Linux 5.2 do that...)

Altogether it looks like looking for a solution for a temporary file, if it is, windows supports GetTempFileName which can be considered as a standard solution for a file that is "used and then trashed" .

If on the other hand we wish to allow a developer to defer deletes in windows, that's possible, see my suggestions above, but the developer must take responsibility for that, and understand the consciousness - therefore must be a flag with a good name convention.

The primary use for this feature is to rename an open file after creating it:

fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
_, err = fd.Write(...)
err = fd.Sync()  // file now safe to share
err = os.Rename(path, shared_path)
// os.Close() at program exit

I don't know why you wouldn't add a flag os.O_WRNDEL (windows rename/delete; a no-op on other platforms), and document that mode's differences with Unix; that a deleted file remains in its directory, but can't be opened, until os.Close(). Also mention that moving the file to a temp dir before deleting it provides more Unix-like behavior.

@guybrand I think perhaps you're misunderstanding my proposal. I'm not advocating that FILE_SHARE_DELETE be enabled by default - in fact I'm opposing it for the reasons mentioned in the second paragraph of my comment. My proposal was for a mechanism that allows users to opt-in to FILE_SHARE_DELETE behavior (on a per-file basis) while still using the os package's file infrastructure. This proposal provides a mechanism for doing so without extending the API surface of any package.

I don't know why you wouldn't add a flag os.O_WRNDEL (windows rename/delete; a no-op on other platforms)

@networkimprov That's essentially what I'm proposing, except that there's no point in defining a new flag since a perfectly valid one already exists: syscall.FILE_SHARE_DELETE. The only implementation change would be to watch for this flag in syscall.Open on Windows and add checks to ensure that no future additions to syscall.O_*/os.O_* flags collide with this flag. The documentation for os.OpenFile could then be updated to reflect acceptance of this flag on Windows.

@havoc-io sorry for the misunderstanding, this was my takeout from:
" ... to just accept syscall.FILE_SHARE_DELETE ... into the computed sharemode..."

Adding os.O_WRNDEL matches with my second suggestion apart from not being explicit enough for the developer in terms of "what would be the behavior", perhaps os.WADRNDEL - Windows allow deferred rename/delete) .

@alexbrainman I also suggested a switch to enable it, instead of changing the default

I am not interested. Thank you.

Alex

@guybrand rename of an open file is not deferred, I checked.

Sorry, I'm asking again what is your suggestion? delete opening file? rename opening file? both?

Three of us are now suggesting a new flag, e.g.

fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE| os._WRNDEL, 0600)

fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE| syscall.FILE_SHARE_DELETE, 0600)

Three of us are now suggesting a new flag

I think that a new flag is inappropriate, especially one that isn't easily parsable by someone reading the code. Using syscall.FILE_SHARE_DELETE would be far more explicit and clear, immediately signaling to the reader that something platform-specific is happening.

This is already allowed on POSIX platforms. The are many POSIX-specific (and even platform-specific) open flags that aren't incorporated into the os package. For example, it wouldn't make sense to add something like os._DEVTONLY to support Darwin's O_EVTONLY flag, because you can just pass the flag from the syscall package directly to os.OpenFile, e.g.:

os.OpenFile(..., os.O_RDONLY | syscall.O_EVTONLY, ...)

In this case, the platform-specific flag will be passed down to the underlying open system call.

If FILE_SHARE_DELETE behavior is truly something that people need, then I think the answer is simply to make os.OpenFile able to similarly thread flags down to the underlying CreateFileW call on Windows. At least for FILE_SHARE_DELETE.

An example implementation can be found here.

@networkimprov

Three of us are now suggesting a new flag, e.g.

What do you expect the flag?

@guybrand rename of an open file is not deferred, I checked.

Right, but both need FILE_SHARE_DELETE in order to be allowed while file is open
If you are referring to my suggested terminology, we can
os.WARNDFDEL - Windows allow rename and deferred delete is a little too long, I myself would use an acronym at all WINDOWS_ALLOW_OPENNED_FILE_RENAME_OR_DEFFERED_DELETE
is better IMO.

@guybrand I really don't see the value of adding a new platform-specific flag with a complex name and opaque behavior to the os package when a perfectly good flag for this has been around for decades (FILE_SHARE_DELETE). Using syscall.FILE_SHARE_DELETE as the flag for this is far more clear and explicit. As I mentioned above, there are many POSIX-specific and platform-specific flags that aren't added to the os package but are still accepted by it.

syscall.FILE_SHARE_DELETE is windows-only; we need something that's a no-op elsewhere. Maybe it should appear in pkg os, with a WIN or WINDOWS prefix?

@mattn pls see the patch above from @havoc-io

we need something that's a no-op elsewhere.

@networkimprov The flag doesn't need to exist elsewhere. Your Windows code will just look like:

import (
    "syscall"
    "os"
)

file, err := os.OpenFile(..., os.O_RDWR | syscall.FILE_SHARE_DELETE, ...)

Yes, this code will need to be gated on the platform, but you'll probably need to be doing that anyway since the behavior with this flag won't match that of POSIX systems. Or, if you want the convenience of having the same code on all platforms, you can define your own constant (with a value of syscall.FILE_SHARE_DELETE on Windows and 0 on non-Windows systems) and pass it to os.OpenFile. This is exactly what you'd do if you wanted to bring in a platform-specific O_* flag that wasn't part of the os package (e.g. O_EVTONLY on Darwin or O_ASYNC on Linux).

When I write code I expect it to be cross platform, FILE_SHARE_DELETE does not exist in syscall_* just the windows one (anf therefore will not compile on other systems).

Can we use 0x4 const, as its :
FILE_SHARE_DELETE = 0x00000004

Apart from being ugly,
netbsd_arm
O_NDELAY = 0x4
O_NONBLOCK = 0x4

So using the 0x4 would create the code that differently in netbsd_arm.

So either we add FILE_SHARE_DELETE to all platforms, windows ones will be 0x4 others will be 0x0 and thus "discard" it, or create a specific flag for this issue.

AND anyhow we must change syscall_windows*
func open(
...
sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | isDeleteOn )
where isDeleteOn will check if mode includes the new flag

@guybrand

When I write code I expect it to be cross platform

I would caution you that just because the same os.OpenFile call compiles on multiple platforms, it doesn't mean that your code is cross-platform. There are a number of security considerations on Windows that you would need to take into account when using FILE_SHARE_DELETE, and the semantics of file lifecycles are so different between POSIX and Windows systems that it's really hard to imagine a scenario where your file handling code wouldn't look at least a little bit different between the platforms. Using FILE_SHARE_DELETE essentially puts you in a pre-POSIX.1-2008 situation, without any way to ensure race-free filesystem traversal (and without the benefit of POSIX's post-unlink file access for open files).

Anyway, if you want a flag that has a value of syscall.FILE_SHARE_DELETE on Windows and 0 on other platforms, you can still easily do that with your own constant definition that's controlled by build tags, and then your main code that calls os.OpenFile can be the same on all platforms (using that custom flag).

There are many platform-specific flags that exist in other platforms' syscall packages, but they can't all be exposed by the os package and no-op'd on platforms where they aren't supported. If you want platform-specific code and behavior, it's necessary to reach for the syscall package.

The only thing that the Go standard library should (potentially) do here is support syscall.FILE_SHARE_DELETE in syscall.Open (and consequently os.OpenFile) on Windows.

As I mentioned in above, If we add FILE_SHARE_DELETE, many programmer will mistakenly use it to simulate Unix behavior. But it is not good. For example, please think about case of creating application to watch file exists. It is supporsed to be file must not be found from another applications if an application delete a file. But FILE_SHARE_DELETE do only mark to delete in later. The file is remaining. On UNIX, it works well, but Windows doesn't. Please do NOT use FILE_SHARE_DELETE for simulating UNIX behavior.

@mattn I agree with your concerns - people may try to use the flag as a convenience to make Windows code "work the same" as POSIX systems without understanding the subtle implications. However, I think that if they have to go to the syscall package to access that flag, they might have taken the time to understand what it does.

@mattn, for Unix-like behavior, apps would need 2 extra lines of platform-neutral code:

fd, err := os.OpenFile(path, ... | syscall.FILE_SHARE_DELETE, 0600)
...
tmp_path := mkTempName()
err = os.Rename(path, tmp_path)  // works immediately; path is now available
err = os.Remove(tmp_path)

Documenting this requires one sentence in the docs for os.OpenFile().

Pls don't make me distribute a patch to pkg syscall that everyone who works on my windows code must apply! See also https://github.com/golang/go/issues/32088#issuecomment-493876119.

I think we all agree on most of the facts, just phrase it differently so I'll try to summarize, and set up course of action:

  1. Agreed : Windows and POSIX differ in the above behavior and we should not try to aim for same behavior.
  2. Agreed : the request to allow FILE_SHARE_DELETE as a flag is useful
  3. Suggestion : let change the ticket to "support FILE_SHARE_DELETE" so it would sound like a feature request rather than a bug
  4. Mostly agreed : The feature should be a developer flag at a program level, not go base package.

Action Items:

  1. change syscall_windows.go
    func open(
    ...
    sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | (mode & FILE_SHARE_DELETE) )
  2. Developer that would like to utilize this behavior would either
    file, err := os.OpenFile(..., os.O_RDWR | syscall.FILE_SHARE_DELETE, ...)
    or if they would like their program to be cross platform would create an internal flag of:

mycode_windows.go
const OPENFILEFLAG = syscall.FILE_SHARE_DELETE
mycode_others.go
const OPENFILEFLAG = 0

and then use:
file, err := os.OpenFile(..., os.O_RDWR | OPENFILEFLAG, ...)

If this is agreed, the fix is minimal (one liner), with very low risk

Let vote on this, and we can quickly fix this

Agreed : the request to allow FILE_SHARE_DELETE as a flag is useful

I don't agree with this yet since I don't understand what is the purpose of this issue?

Purpose : allow developers to rename or delete opened files.

What's missing : syscall_windows.go currently hard coded :
func Open(path string, mode int, perm uint32) (fd Handle, err error) {
...
sharemode := uint32(**FILE_SHARE_READ | FILE_SHARE_WRITE**)

Proposed change:
When developer passes a mode parameter with bitwise 4 (FILE_SHARE_DELETE) turned on, the sharemode will change accordingly by
sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | **(mode & FILE_SHARE_DELETE)** )

I'm still wondering why you don't call Close() before Rename() or Remove(). You can read code of standard library of Go. The files are closed by Close() before Rename() or Remove(). Why you must call Close() after Renamoe/Rename ?

I'm not sure what is the business case @networkimprov is considering, my takeout on this would be a temp shared file - my app and another app are using this file, I want to make sure (even if my app crashes) this file will not exist anymore after I'm done - so I create it, Remove() it and when I either close it or my app is closed - the file is remove.

but it looks like @networkimprov wants to use the Rename(), so probably a diff use case.

As mentioned in https://github.com/golang/go/issues/32088#issuecomment-494305074 we may not need to close a renamed file until program exit.

@mattn, pls don't insist that we should never use an OS feature that C and C++ developers can use.

I want to make sure (even if my app crashes) this file will not exist anymore after I'm done

@guybrand A defer statement isn't guaranteed to run in every type of crash, so if you're trying to ensure file deletion, then deferring an os.Remove operation isn't a reliable way to go about it. If you want a file that's automatically deleted, a temporary file that the system deletes is a better option. If you want a file location that's shared by two programs, it should be coordinated by locks (e.g. fcntl locks on POSIX and LockFileEx/UnlockFileEx on Windows), not file existence.

As mentioned in #32088 (comment) we may not need to close a renamed file until program exit.
@mattn, pls don't insist that we should never use an OS feature that C and C++ developers can use.

@networkimprov A rename-after-open operation is a valid use case, but if you're the program holding the file HANDLE, can't you already do that? The only purpose of FILE_SHARE_DELETE would be to allow other processes to rename the file while you're holding it open. In any case, you can use FILE_SHARE_DELETE now, you just need to manually invoke CreateFileW and pass the result off to os.NewFile. You can find an example of what this looks like here. The resulting HANDLE can just be passed to os.NewFile.

@mattn I'm starting to agree with you that this flag will be more of a footgun than a useful tool. Just to be clear, I don't actually want this flag, and the only arguments I can see for its existence are completeness and parity of control between POSIX and Windows. On the other hand, as I mentioned above, it's also relatively easy to manually invoke CreateFileW (with this flag specified) and then hand the result off to os.NewFile, so maybe there is no need to add support for it.

@havoc-io there is no os.File.Rename(). That would be a fine solution, but it's a relatively heavy lift.

Re CreateFileW + os.NewFile(), see https://github.com/golang/go/issues/32088#issuecomment-493876119.

@havoc-io

I want to make sure (even if my app crashes) this file will not exist anymore after I'm done
@guybrand A defer statement isn't guaranteed to run in every type of crash
This was exactly what I meant, When I said "even if my app crashes".

the only arguments I can see for its existence are completeness and parity of control between POSIX and Windows
I disagree this would bring "completeness and parity of control between POSIX and Windows" - IMO it would only confuse developer to think the behavior is the same when its not, I gave few examples above.

easy to manually invoke CreateFileW
That's really a tweak, easy or not,and in other words saying : "we do not want to support this in go", which is by the way an ok decision IMO, but the requester is @networkimprov not me.

@havoc-io there is no os.File.Rename(). That would be a fine solution, but it's a relatively heavy lift.
Re CreateFileW + os.NewFile(), see #32088 (comment).

@networkimprov Can't you just use os.Rename(file.Name(), <target>)? That's the beauty of Go not having FILE_SHARE_DELETE on by default - you know that file.Name() will still point to something valid since you're holding the file HANDLE (something you can't guarantee on POSIX, even with renameat). Regarding CreateFileW + os.NewFile, most of the stack described in your comment would become irrelevant, and makeInheritSa is almost certainly something you don't want (it's only there to simulate POSIX's bad default file descriptor inheritance behavior - it's not the default). The only thing you'd be missing out on would be the internal fixLongPath path function, which probably won't be necessary for the use cases you've described.

@guybrand Just to clarify what I mean...

completeness: API surface completeness (i.e. the ability to use FILE_SHARE_DELETE if you want to use it for some reason)
parity of control: The ability to specify flags to CreateFileW via os.OpenFile's flag parameter. For example, I can route syscall.O_NOFOLLOW through os.OpenFile on POSIX, but I can't route syscall.FILE_FLAG_OPEN_REPARSE_POINT through os.OpenFile on Windows. Though again, I think that total parity of control here is a pipe dream since the os package is modeled around POSIX APIs. I'm quite happy reaching for syscall.CreateFileW when it's necessary.

@mattn thanks for the links. The Go CL was to patch the syscall.Open() _default_. The discussion gives no general reason to disallow a _flag_ for Open(). It notes that a third-party package can return an os.File lacking the flag, but that's only a problem if the caller tries to rename/delete that file -- a rare corner case.

The Python thread notes that you should rename an open file before deleting it, as I wrote above.

Erlang has had file_share_delete as the _default_ for over SIX years.

Reflecting on the Erlang experience and Python thread linked above, it's apparent that:

  1. File_share_delete as default works well in a cross-platform environment; the only caveat is that user code should rename a file to a unique name immediately before removing it (a trivial change) if the original filename can be reused.
  2. Most Windows programs can't use file_share_delete simply because the MSVC runtime only allows it in combination with O_TEMPORARY.

Therefore I'd conclude that syscall.Open() should use file_share_delete by default, and syscall should provide a global flag to disable it where necessary (e.g. debugging).

If problems appear with that approach during the 1.14 cycle, the explicit flag method can be applied instead.

Therefore I'd conclude that syscall.Open() should use file_share_delete by default, and syscall should provide a global flag to disable it where necessary (e.g. debugging).

@networkimprov Aside from breaking a huge number of existing programs in subtle and not-so-subtle ways, the fact of the matter is that introducing this flag now would be a security issue for programs relying on the absence of FILE_SHARE_DELETE. In particular, it would open up a number of time-of-check-to-time-of-use vulnerabilities for programs relying on the immutability of files and directories that they're holding open.

Edit: For further reading on why changing the default is a bad idea and almost certain to break existing programs, please see Hyrum's Law.

The most common case of Go on Windows is development, for deployment to Linux or other Unix.

If your code depends on any GOOS=windows behavior, it is broken and possibly vulnerable on Linux. So we already have a possible security issue.

os.Rename() & .Remove() do not document differences on Windows, so no one would guess they exist. Documenting them now will not fix existing code. Fixing the incompatibility and publishing a blog post on it would help far more.

Note that Erlang had been around for a while before it adopted file_share_delete as a default.

The most common case of Go on Windows is development, for deployment to Linux or other Unix.

If your code depends on any GOOS=windows behavior, it is broken and possibly vulnerable on Linux. So we already have a possible security issue.

@networkimprov

By that logic, the file opening infrastructure on Windows should also be modified to make file descriptors inheritable by default across process creation, in order to match POSIX's default behavior. Existing programs can be blamed for relying on the runtime/standard library's previous default behavior, and a template CVE can be included in the release notes for users to file for their programs.

But of course that's not what's done, and instead Go's runtime and standard library bend over backwards to work around POSIX's default and badly designed behavior, trying to mirror what Windows does.

The situation is the same with shared file access. Windows arguably got the design right, and POSIX.1-2008 had to add a number of functions to work around its design limitations.

Also, as @mattn has mentioned several times above, including FILE_SHARE_DELETE when opening files does NOT make Windows behave like POSIX — there will still be significant behavioral differences that will become far more apparent with this flag than differing os.Remove and os.Rename behavior.

I'd argue that if your code universally relies on the behavior of any platform, it's broken and possibly vulnerable on other platforms (and that includes relying on POSIX behavior on Windows). If you're not taking the time to understand and address platform variations in your code, then you're not doing cross-platform development.

In any event, @ianlancetaylor already vetoed the idea of using a global flag to control turning this on, so I seriously doubt that you're going to get one to turn it off after changing the default.

In my opinion, the only non-breaking (and non-security-compromising) option here is to add support for syscall.FILE_SHARE_DELETE in syscall.Open's flag argument, or just force users who want FILE_SHARE_DELETE functionality to use CreateFileW + os.NewFile. Yes, it requires a bit of work from developers who want this behavior, but Win32 and POSIX are fundamentally different beasts and it's amazing that the runtime and standard library do as good a job as they do of paving over their differences. There are many more significant differences between these platforms that manifest themselves in the standard library, e.g. completely different permissions models, the fact that os.File.Chmod doesn't work on Windows, os.Chown doesn't work on Windows, the fact that the internal poller supports different types of files on different platforms, etc. Go's runtime and standard library do the best they can (and do it well), but they're not a panacea for cross-platform development woes. At some point, developers have to handle these differences on their own. Adding a breaking change to modify (notice that I didn't say correct) an obscure behavioral edge case doesn't make any sense to me.

I wanted to share one more use case for this.

Currently when Docker log rotate logic works on way that Docker engine will rename active log to name .1, create new one with old name.
Any client which is actively following log with command docker logs --follow <container>
will notice that from file system events:
https://github.com/moby/moby/blob/916eabd459fe707b5c4a86377d12e2ad1871b353/daemon/logger/loggerutils/logfile.go#L552-L593

There is also this logic which makes Windows noticing those events immediately:
https://github.com/moby/moby/blob/916eabd459fe707b5c4a86377d12e2ad1871b353/daemon/logger/loggerutils/logfile.go#L670-L676

All this works nicely on Linux but on Windows log rotate fails to error The process cannot access the file because it is being used by another process. if there is any clients following log (issue: https://github.com/moby/moby/issues/39274 )

As it is possible to have n number of those client who are following log it would be very tricky to first collect all those open file handles and close them before file rotate so I really would like to see nice to have support for syscall.FILE_SHARE_DELETE flag.

Hi! Quick background: I work on the Chrome CI infrastructure. We are porting our low level worker code to Go. We run hundred of thousands of tests invocation on Windows per day.

I've read all comments and I hope the following is a clear and correct summary of each individual's opinion.

Our use case is building Go executables on Windows to run on Windows, unlike @networkimprov 's surprising characterization. I'll ignore this "assume POSIX behavior on Windows" use case in this comment since it's just plain incorrect assumption and dragged @mattn into highlighting the differences, which IMHO is a moot point, more below on that.

As part of our migration, we need FILE_SHARE_DELETE, in part due to log file rotation, similar to #39274. It happens it is also a problem for issue #25965.

I agree with @ianlancetaylor changing the whole toolchain behavior, as proposed in https://codereview.appspot.com/8203043/ is not a good idea. One immediate problem that comes to mind this proposal when using a file handle as a lock file. We use this behavior. And what @havoc-io said. So let's ignore this too.

To restate, I'm going to ignore these proposals in the rest of this comment:

  • Trying to behave exactly like POSIX, that ain't going to work
  • Change globally behavior as opt-out (or no opt-out!), that breaks users

This leaves us with a per-call flag with a name that is clearly Windows-specific. This is exactly what @guybrand proposes.

With all due respect, I don't understand @mattn objection. What @guybrand propose is reasonable. Yes Windows behaves differently with regards to file sharing. It's like file deletion rights, it's very different on Windows. It already diverges is many ways. Having an objection for a Windows-specific flag on the stance that "Developers could misunderstand its behavior" is not a fact, it's an opinion. You assume developers don't know what they are doing or won't read the documentation on MSDN. This is a fair objection for the general case 😎, but not a strong one on the basis of blocking a legitimate use case as an optional flag.

Ironically, this means that until this issue is resolved, to fix #25965 I'll have to change 'go run' to call CreateFileW directly 🙃 because we also need FILE_FLAG_DELETE_ON_CLOSE.

Thanks!

I just want to know how may case exists to know whether we should implement new function(golang.org/x/sys?) for open/create a file with new attributes, Or change original behavior. Currently, as far as I can know from above comments:

  1. creating deletable(rename-able) file for logging.
  2. creating file for temporary file that will be deleted on close.

For 1, it can be provided from new function to create/open a file with the attributes. And we can set the file using logger.SetOutput(file).

For 2, also can create/open with new function.

BTW, I'm guessing #25965 is not related on this issue. Probably, it is limitation of Windows.

(In any case, we can not delete the directory where the file opened with this attribute exists)

@mattn Can you explain the trade off between:

  • adding a completely new function in golang.org/x/sys
  • supporting a new flag (already defined!) to OpenFile(). [1]

I don't understand why we should favor the first instead of the second.

[1] I just realized I assumed here changing the behavior of os.OpenFile() but the issue calls out Open().

First of all, syscall package is already locked down. So I'm looking for a way to fix this without changing the syscall package.

BTW, I'm guessing #25965 is not related on this issue. Probably, it is limitation of Windows.

(In any case, we can not delete the directory where the file opened with this attribute exists)

@mattn that is not completely true. FILE_SHARE_DELETE flag will allow you move those files to another folder so you can remove original one. Here is simple PowerShell example of that:

# Create folder and open new file with FILE_SHARE_DELETE flag
New-Item -Type Directory -Path C:\folder1
$file = [System.IO.File]::Open("C:\folder1\test.txt", "Create", "ReadWrite", "Delete")

# Create temp folder and move all open files to there
New-Item -Type Directory -Path C:\folder1-removing
Get-ChildItem -Path C:\folder1 -Recurse | Move-Item -Destination C:\folder1-removing\

# Remove original folder
Remove-Item -Path C:\folder1

# Trigger removal for files on temp folder (NOTE! Those will exist on disk until they are closed).
Get-ChildItem -Path C:\folder1-removing -File -Recurse | Remove-Item -Force

# Close file and remove temp folder
$file.Close()
Remove-Item -Path C:\folder1-removing

First of all, syscall package is already locked down. So I'm looking for a way to fix this without changing the syscall package.

@mattn If it's only an issue of the API being frozen, then as I mentioned/demo'd above, syscall.Open could just be adjusted to understand syscall.FILE_SHARE_DELETE, solving the issue without changing the API.

If it's an implementation freeze, then the change could be made to golang.org/x/sys/windows's Open function and then vendored into the internal/syscall package. The file_windows.go code is already using functions from internal/syscall. The only inconsistency then would be that syscall.Open wouldn't understand this flag, but most people looking to access low-level Windows API functionality are using golang.org/x/sys/windows anyway, and probably using CreateFile instead of Open.

The proposal that initiated the syscall package "lock-down" states:
_The core repository will not depend on the go.sys packages_

So changing x/sys wouldn't help callers of os.OpenFile(). But internal/syscall could add Open(), and os.OpenFile() would call it.

@maruel, the Docker project, and presumably many others, assumed Unix behavior for os.Rename() & .Remove() of opened files because package "os" doesn't mention Windows-specific behavior for them, yet has __fifteen mentions__ of other Windows behavior.

The core repository will not depend on the go.sys packages
But internal/syscall could add Open(), and os.OpenFile() would call it.

@networkimprov Okay, I was under the mistaken impression that internal/syscall was a vendored subset of golang.org/x/sys, but either way I think that that's the right strategy (adding internal/syscall/windows.Open). Of course, this would be under the assumption that syscall.Open's behavior is immutable due to the freeze. Ideally it could be modified directly, potentially with corresponding changes in golang.org/x/sys/windows.Open.

Although the syscall package is frozen, we can change it to fix bugs or address serious deficiencies. In particular we could change it to better support the os package.

But if people are calling syscall.Open directly, then they should be using the x/sys/windows package, not the syscall package.

But if people are calling syscall.Open directly, then they should be using the x/sys/windows package, not the syscall package.

I don't think anyone is looking to call syscall.Open directly - it's only the target of discussion because it underlies os.OpenFile (so that adding support for FILE_SHARE_DELETE in syscall.Open adds support for using the flag with os.OpenFile).

Thanks. It's fine to change the syscall package to support changes to the os package.

Thanks @ianlancetaylor and @havoc-io . A more formal proposal from what I noted above:

  • Modify syscall.Open() with the explicit intent of allowing os.OpenFile() to accept FILE_SHARE_DELETE (and eventually FILE_FLAG_DELETE_ON_CLOSE as a potential follow up)
  • Update os.OpenFile() documentation to describe the new Windows-only flag.
  • Update os.OpenFile(), os.Rename() and os.Remove() documentation to clarify the difference in behavior between POSIX and Windows.

The third point is to address @networkimprov concern. I understand the challenge there, and while it shouldn't be the language's responsibility to describe how the OS works, I'm starting to agree with @mattn that these cases are subtle enough to warrant more documentation. I think the doc for Rename and Remove can be improved right away, no need to wait for any behavior change.

I'll willing to contribute on this if there's approval.

This leaves us with a per-call flag with a name that is clearly Windows-specific.

Do you propose new os.Open flag? Then the flag should be supported by all OSes - that is a whole point of os package - be OS independent. So what would semantics of that flag would be? And you would need some new tests that verify the flag works as documented.

I don't see why you cannot write whatever code you need using syscall package. I don't think your code will coexist well with other Windows programs. And maybe it is OK in special circumstances. But I don't want people using this functionality lightly - so it should not be part of standard library.

Alex

@alexbrainman
please see this

@alexbrainman
please see this

I did look. I still don't see how all your proposed changes will work on other OSes? What tests will you have to implement your proposed change?

Alex

quoting this

whereas DELETE_WHEN_FREED can even be 0 for env. other than windows

So - nothing to test on other OSes, the call for :
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.DELETE_WHEN_FREED, 0600)
in windows would be translating the const to 4
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|4, 0600)
While others (0)
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|0, 0600)

nothing to test on other OSes

I don't see how we could add new os package const or variable that is only usable on Windows.

c:\>go doc os
package os // import "os"

Package os provides a platform-independent interface to operating system
functionality. 
...
The os interface is intended to be uniform across all operating systems.
Features not generally available appear in the system-specific package
syscall.

Alex

Alex the answer to your objection was first stated here https://github.com/golang/go/issues/32088#issuecomment-494157514

A test is trivial

_, err := os.OpenFile(path, os.O_CREATE | syscall.FILE_SHARE_DELETE, 0);
err = os.Rename(path, path+"2")

We're at the point where someone could submit a patch; I suspect @ianlancetaylor would accept it.

I investigated setting FILE_SHARE_DELETE as part of #32188, but found that setting it did not empirically reduce the rate of errors from os.Rename and io.ReadFile calls; a retry loop was still needed anyway.

I would be very interested to see a test that demonstrates a significant observable difference from setting this flag, because I don't really understand its implications myself.

I have experimented FILE_SHARE_DELETE a few years ago, and it did behave
differently.
I can probably relaunch my windows env. and look it up, if it would help,
but it would be ~Go 1.3 or so.

On Mon, 10 Jun 2019 at 17:44, Bryan C. Mills notifications@github.com
wrote:

I investigated setting FILE_SHARE_DELETE as part of #32188
https://github.com/golang/go/issues/32188, but found that setting it
did not empirically reduce the rate of errors from os.Rename and
io.ReadFile calls; a retry loop was still needed anyway.

I would be very interested to see a test that demonstrates a significant
observable difference from setting this flag, because I don't really
understand its implications myself.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/32088?email_source=notifications&email_token=ABNEY4VVIVJ2IEWQPWCWBZTPZZSETA5CNFSM4HNPNYIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXKCG4Y#issuecomment-500441971,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABNEY4STTW7U526HPO6TUHDPZZSETANCNFSM4HNPNYIA
.

@bcmills, reading #32188 I gather that you were seeing errors from os.Rename() on a file that _was not open_ during the rename? I imagine one could outrun the NTFS journal, which I believe is written for all directory change ops, and thereby induce an error; but I've no idea what error it would be.

The request here to enable FILE_SHARE_DELETE in syscall.Open() is to allow os.Rename() to work on opened files. I have tested that flag with 1.12 on Win7, it works; and fails without it.

I have not specifically tested os.Rename() on windows "under load", but have not seen any unexpected errors from os.Rename() on opened files since enabling f.s.d. I'll report it if I do.

I gather that you were seeing errors from os.Rename() on a file that was not open during the rename?

Yes.

I've no idea what error it would be.

Empirically, the errors I'm seeing (from MoveFileEx, ReplaceFile, and/or are CreateFile) are ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION, and ERROR_FILE_NOT_FOUND.

The request here to enable FILE_SHARE_DELETE in syscall.Open() is to allow os.Rename() to work on opened files. I have tested that flag with 1.12 on Win7, it works; and fails without it.

Right; the related problems I'm trying to solve are to allow concurrent calls to os.Rename to succeed, and to allow calls to io.ReadFile concurrent with os.Rename to succeed. Getting a single os.Rename to work with existing handles open may be a step in the right direction, but I don't think it's sufficient for the kind of use-cases we for which we employ the POSIX rename.

I would be very interested to see a test that demonstrates a significant observable difference from setting this flag, because I don't really understand its implications myself.

@bcmills PowerShell example as I write that much faster than Go

New-Item -Type Directory -Path C:\folder1
for($i=0; $i -le 1000; $i++)
{
    $temp = [System.IO.File]::Open("C:\folder1\test.txt", "Create", "ReadWrite", "Delete")
    Set-Variable -Name "file$i" -Value $temp -Force
    $TempName = "C:\folder1\test.txt." + (New-Guid).Guid
    Rename-Item -Path C:\folder1\test.txt -NewName $TempName
    Remove-Item -Path $TempName -Force
}

After that finished there will be thousand test.txt.??? files under C:\folder1 but when you close PowerShell those files will be removed.

So what FILE_SHARE_DELETE flag actually does it allow you rename/move/remove open files but you still need make sure that destination file names are unique because they will exist on disk as long as handles are open.

"employ the POSIX rename" is impossible in windows, and perhaps that's the reason for the errors your getting.

POSIX :

create file "filename"
read/write from "filename"
delete "filename"

create file "filename"
...
delete "filename"

would work, as each create file would have a diff fd.

Windows:
create file "filename"
read/write from "filename"
delete "filename"

create file "filename" - boom, sharing violation

Should be very easy to reconstruct...

What can FILE_SHARE_DELETE do then?
allow deffered delete while the file is open.

That's it.

On Mon, 10 Jun 2019 at 19:32, Olli Janatuinen notifications@github.com
wrote:

I would be very interested to see a test that demonstrates a significant
observable difference from setting this flag, because I don't really
understand its implications myself.

@bcmills https://github.com/bcmills PowerShell example as I write that
much faster than Go

New-Item -Type Directory -Path C:folder1for($i=0; $i -le 1000; $i++)
{
$temp = [System.IO.File]::Open("C:folder1test.txt", "Create", "ReadWrite", "Delete")
Set-Variable -Name "file$i" -Value $temp -Force
$TempName = "C:folder1test.txt." + (New-Guid).Guid
Rename-Item -Path C:folder1test.txt -NewName $TempName
Remove-Item -Path $TempName -Force
}

After that finished there will be thousand test.txt.??? files under
C:folder1 but when you close PowerShell those files will be removed.

So what FILE_SHARE_DELETE flag actually does it allow you
rename/move/remove open files but you still need make sure that destination
file names are unique because they will exist on disk as long as handles
are open.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/32088?email_source=notifications&email_token=ABNEY4WSTWIUVNVHLYMBFY3PZZ62DA5CNFSM4HNPNYIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXKMMDY#issuecomment-500483599,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABNEY4VS5XF3EXB6QPBWVQ3PZZ62DANCNFSM4HNPNYIA
.

allow calls to io.ReadFile concurrent with os.Rename to succeed

@bcmills I think that one should always work with the f.s.d. flag set, assuming it's not running concurrently with other threads attempting the same sequence on unrelated files. And it should usually fail without the flag.

That aspect of the test script correctly found a bug in Windows syscall.Open(). But no one else commenting in this thread wants it fixed :-( so yeah, patch the script.

TLDR:
minor safe change to syscal_windows.go and works as expected.

explained:
So, here's what I've done, works as expected, and no risk (IMO)

  1. tweak syscal_windows.go
func Open(path string, mode int, perm uint32) (fd Handle, err error) {

sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | (mode & FILE_SHARE_DELETE))

the addition is : "| (mode & FILE_SHARE_DELETE)"
if the developer will not send mode & 4 set (why should he - this was never working before...) - nothing has changed, so a code like

os.OpenFile(filename,os.O_RDWR|os.O_CREATE, 0600)

will behave EXACTLY as it does before the change, only developers that will

os.OpenFile(filename,os.O_RDWR|os.O_CREATE  | 4 , 0600)

will have "feel" the change

  1. ran the code
package main

import (
    "fmt"
    "os"
)

func main() {
    filename := "./myfile"
    if f ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{
        fmt.Printf("Open file %s error : %s" , filename, err.Error())
    } else if _,err:= f.WriteString("bla bla") ;err!=nil{
        fmt.Printf("writing to %s returned error : %s" , filename, err.Error())
    } else if err := os.Remove(filename);err!=nil{
        fmt.Printf("removing %s returned error : %s" , filename, err.Error())
    }
}

and it worked

  1. ran the code without the |4 and it failed
  2. ran the code with a small addition:
package main

import (
    "fmt"
    "os"
)

func main() {
    filename := "./myfile"
    if f ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{
        fmt.Printf("Open file %s error : %s" , filename, err.Error())
    } else if _,err:= f.WriteString("bla bla") ;err!=nil{
        fmt.Printf("writing to %s returned error : %s" , filename, err.Error())
    } else if err := os.Remove(filename);err!=nil{
        fmt.Printf("removing %s returned error : %s" , filename, err.Error())
    } else if secondF ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{
        fmt.Printf("reOpen file %s error : %s" , filename, err.Error())
    } else {
        secondF.Close()
    }
}

and of course it failed on the secondF
But thats expected behavior in windows - no need to "load test" - there is no concurrency option on deferred .Remove, so I think we can safely add this half a line to syscal_windows, without compromising any existing code, and allowing only developers that really want to use this mode to be able to defer deletes.

all we need to do (in bold):
syscal_windows.go:
sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | (mode & FILE_SHARE_DELETE))

I investigated setting FILE_SHARE_DELETE as part of #32188, but found that setting it did not empirically reduce the rate of errors from os.Rename and io.ReadFile calls; a retry loop was still needed anyway.

I would be very interested to see a test that demonstrates a significant observable difference from setting this flag, because I don't really understand its implications myself.

@bcmills now you can find unit/regression tests for this from https://github.com/olljanat/go/commit/3828f1a5d0ebb69b4c459d5243799ded36ac1ee8 currently it fails to error The process cannot access the file because it is being used by another process. on Windows and starts working after FILE_SHARE_DELETE flag is included to the code.

Note! that on Windows you still need that step which moves log example.log.1 file to random/unique name because other why renaming example.log to example.log.1 would fail to Access is denied. error even when FILE_SHARE_DELETE flag is enabled. That is platform specific thing which developer need to take care of.

/cc @jhowardmsft @jterry75 @jstarks @ddebroy FYI; you might be interested in this one as well.

@kevpar - FYI since you are working around this as well

If any folks joining the thread can voice reasons (e.g. existing code is broken) to make this the default, vs available via Windows-only flag in os.OpenFile(), do pipe up!

@networkimprov IMO it should be handled with Windows-only flag because developer is needed to handle also some other Windows specific things anyway like on my log rotate example.

However as I can see that couple of Microsoft employees was invited to discussion so it is interesting to see if they thing this differently.

Have anyone btw started to develop new solution to this one? Any volunteers?

@olljanat
If its a fix-as-a-flag, then the fix is a one liner, please read:
https://github.com/golang/go/issues/32088#issuecomment-500562223

I'll try to represent the Windows team perspective here... we'd rather Go just always set FILE_SHARE_DELETE, without any option. In general, to make porting to/from Windows easier, we would prefer consistent behavior with Linux, and I don't see why Windows users or software would prefer the default !FILE_SHARE_DELETE behavior enough for this to be an exception to the rule.

An interesting note, contrary to @mattn's comment: in the most recent version of Windows, we updated DeleteFile (on NTFS) to perform a "POSIX" delete, where the file is removed from the namespace immediately instead of waiting for all open handles to the file to be closed. It still respects FILE_SHARE_DELETE, but now otherwise behaves more like POSIX unlink. This functionality was added for WSL and was considered worth using by default for Windows software, too.

In other words, if if you run mattn's test program and sequence of del, dir, etc. on the latest version of Windows, you will see the file disappear from the namespace immediately after deleting the file, not after the test program exits. Just like Linux.

So even in Windows itself, with its significantly larger appcompat risk, we are making small changes to ease the porting of non-Windows software to Windows. I really encourage Go to do the same.

@jstarks, thank you for vindicating me. I've taken a ton of heat in this thread for that position.

I would suggest a global option to disable f.s.d. in case any Windows deployments rely on the current undocumented behavior.

@ianlancetaylor do you concur?

As the default behavior of CreateFile is that can not delete the opening file, I think that we should keep the default behavior. Someone may want the original behavior. For example, there may be programmers who is checking that their application is working using way not possible to delete the file.

Behavior of Python is the same as the current behavior of Go. And I've never heard that Python have plan to changes the behavior that can not delete open files.

I have not taken the time to develop a serious opinion on this issue, but I do not think that a global option is a good choice. We should either keep the current behavior (and perhaps add a flag to select the other behavior) or we should change the behavior (and perhaps add a flag to select the original, current, behavior).

With respect to whether to change the default behavior or not, I think we have to ask whether it is more likely that changing the behavior will fix a Go program written to run across Unix and Windows systems or whether it is more likely that changing the behavior will break a Go program written to run on Windows systems. I don't know the answer to that.

As @mattn suggests it is also worth asking what other languages do.

If it becomes the default, and any Windows deployments depend on it, I imagine they'd want both an os.OpenFile() flag, and a global option to switch back for transitional purposes.

There's a global option to disable IPv6, last I checked.

One way to discover impact is change it on tip (and announce on blog?) and see what's reported.

Python and Erlang are both mentioned in the issue text:
Erlang made it a default over six years ago: erlang/otp@0e02f48
Python wanted to but couldn't, due to limitations of MSVC runtime: https://bugs.python.org/issue15244

I'll try to represent the Windows team perspective here... we'd rather Go just always set FILE_SHARE_DELETE, without any option. In general, to make porting to/from Windows easier, we would prefer consistent behavior with Linux, and I don't see why Windows users or software would prefer the default !FILE_SHARE_DELETE behavior enough for this to be an exception to the rule.

in the most recent version of Windows, we updated DeleteFile (on NTFS) to perform a "POSIX" delete, where the file is removed from the namespace immediately instead of waiting for all open handles to the file to be closed. It still respects FILE_SHARE_DELETE, but now otherwise behaves more like POSIX unlink. This functionality was added for WSL and was considered worth using by default for Windows software, too.

@jstarks that is very interesting detail. Is that something which we can expect Microsoft backporting to older OS versions too? I think especially of Windows Server 2019 which is latest, just released LTSC version which will be supported for long time still.

As if Microsoft does that then I would prefer to enable FILE_SHARE_DELETE by default on Go too.

Java, C#, and all other major languages I am aware of take the default OS behavior. In my opinion, the default behavior should be left to the OS implementer. If Windows really want to take the POSIX approaching regarding opening files, they should take that risk and do so as they did for DeleteFile.

I do think the Go library should make it possible to create/open share delete files. I can give one practical example in dealing with the file mode. When porting Hadoop to Windows, we have to make extra efforts to create a special method in JNI to get the shared delete file handle or stream.

https://github.com/apache/hadoop/search?q=getShareDeleteFileInputStream&unscoped_q=getShareDeleteFileInputStream

@jstarks

When saying

in the most recent version of Windows, we updated DeleteFile (on NTFS) to perform a "POSIX" delete, where the file is removed from the namespace immediately

do you mean that the open file stays only as a fd , and the code below :

package main

import (
    "fmt"
    "os"
)

func main() {
    filename := "./myfile"
    if f ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{
        fmt.Printf("Open file %s error : %s" , filename, err.Error())
    } else if _,err:= f.WriteString("bla bla") ;err!=nil{
        fmt.Printf("writing to %s returned error : %s" , filename, err.Error())
    } else if err := os.Remove(filename);err!=nil{
        fmt.Printf("removing %s returned error : %s" , filename, err.Error())
    } else if secondF ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{
        fmt.Printf("reOpen file %s error : %s" , filename, err.Error())
    } else {
        secondF.Close()
    }
}

will not fail at

} else if secondF ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{

after applying the suggested fix ?

If so, that's actually cool !

With respect to whether to change the default behavior or not, I think we have to ask whether it is more likely that changing the behavior will fix a Go program written to run across Unix and Windows systems or whether it is more likely that changing the behavior will break a Go program written to run on Windows systems. I don't know the answer to that.

There is no problem to be fixed running Unix program on Windows. Windows handles deletion of opened files differently from Unix. It is similar to file name case sensitivity: Unix is case sensitive, and Windows is case insensitive. Nothing we can do about both differences.

And, while John assures that Microsoft are trying to change things, I would wait until the change is here. And a lot of Windows users still use Windows 7 and all other Windows version. John would you update Windows 7 too?

I think we should let Microsoft handle the change. If we fix Go users, there will still be non-Go developers. So Microsoft would have to deal with this regardless.

Also, FILE_SHARE_DELETE has its own fiddles. For example, reading https://bugs.python.org/issue15244

Actually, it is not quite the same semantics as Unix.

After you delete the the file you cannot create a file of the same name or delete the directory which contains it until the handle has been closed.

However, one can work around that by moving the file somewhere else (like the root directory) before deleting it.

There are, probably, many others like that that we don't know about. We have not used FILE_SHARE_DELETE at all. Do you, propose, we change Go libs to use FILE_SHARE_DELETE by default, and then wait for users to complain about their broken programs?

As @mattn suggests it is also worth asking what other languages do.

I only checked Mingw C - it behaves just like Go. Once file is opened with fopen, it cannot be deleted until fclose is called. I would be surprised if any of Microsoft development tools (C, C++, C#, VB and others) are different.

And I don't see how we could change default behavior like that. Some program might have file opened specifically, so it cannot be deleted by other program. That is valid approach on Windows in my view. We have to support it going forward.

Alex

Added 7 years ago, I believe:
https://sourceforge.net/p/mingw-w64/code/HEAD/tree/stable/v3.x/mingw-w64-headers/include/ntdef.h#l858

So far, no one has posted any cases of Go apps that depend on the current undocumented Windows behavior. But we've heard at least 4 cases of Go apps that failed on Windows due to it.

As stated above, there's a simple fix for filename reuse after os.Remove(), which we should document:
os.Rename(path, unique_path); os.Remove(unique_path)

So far, no one has posted any cases of Go apps that depend on the current undocumented Windows behavior.

These people do not even know that it is possible to report Go bugs.

You should not expect these users monitor go issue list for changes in the hope something will affect them. They have life to live. It is upon us to not to break tools they use.

Alex

Alex, it is clear you are unpersuaded. Then I see two possible paths forward so that people can write portable code in Go:

  1. a new opt-in flag to Open that means FILE_SHARE_DELETE for Windows and is ignored for the other OSes. We would then encourage anyone writing code expecting POSIX semantics to pass this flag everywhere; or,
  2. a new Go package posix (in some repo somewhere) with one function, Open, that wraps CreateFile with FILE_SHARE_DELETE but otherwise behaves as os.Open. On non-Windows platforms, it would just call os.Open directly. We would then encourage anyone writing code expecting POSIX semantics to use posix.Open instead of os.Open everywhere.

Both of these approaches require Linux developers to take additional action to preserve POSIX behavior on Windows, but at least these are better than writing one-off CreateFile wrappers (like we just did for containerd).

Alex, it is clear you are unpersuaded.

I am not persuaded. But you don't need to persuade me, you need to persuade Go Team. They will be making the decision.

  1. a new opt-in flag to Open that means FILE_SHARE_DELETE for Windows and is ignored for the other OSes. We would then encourage anyone writing code expecting POSIX semantics to pass this flag everywhere; or,

I don't like this option either.

First of all, os package is supposed to provide facility available for any OS. Anything OS specific should go into syscall, or golang.org/x/sys/windows, or whatever other package we like.

Second, I am worried that I will end up with FILE_SHARE_DELETE file type in my program, even if I don't want to. Imagine, if I import external package that decides to use FILE_SHARE_DELETE file mode. How would I know, that some of my code uses FILE_SHARE_DELETE? I would have to grep package source code. Or package documentation would have to warn its users. I suppose, this could also happen, if some package author just use syscall.CreateFile with FILE_SHARE_DELETE directly. But, I think, if FILE_SHARE_DELETE is blessed for os package, this will be more common. I suspect, Linux users, who know nothing about Windows, would use this flag by default, to make their Linux program port "easier" to Windows.

Third, we would have to document the FILE_SHARE_DELETE flag. We cannot just say POSIX. We would have to describe what the flag does. In simple words. Also remember:

Actually, it is not quite the same semantics as Unix.

After you delete the the file you cannot create a file of the same name or delete the directory which contains it until the handle has been closed.

However, one can work around that by moving the file somewhere else (like the root directory) before deleting it.

from https://github.com/golang/go/issues/32088#issuecomment-504321027. We would have to document that too. And all the other functionality that FILE_SHARE_DELETE flag brings.

Fourth, we would need add new tests for FILE_SHARE_DELETE. What would these tests be? What happens, if we forget to test some functionality, only to discover later that the functionality cannot be achieved using FILE_SHARE_DELETE flag? We cannot remove FILE_SHARE_DELETE flag, if we don't like it. Once it is in, it stays. And we have to support it in future.

2. a new Go package posix (in some repo somewhere) with one function, Open, that wraps CreateFile with FILE_SHARE_DELETE but otherwise behaves as os.Open

I don't see how that is possible. But, if it is possible, you should be able to create the package yourself. For example, as github.com/jstarks/posix. No? We could add the package under golang.org/x/sys/windows/posix or something, but I don't see much difference.

Alex

os package is supposed to provide facility available for any OS...
we would have to document the FILE_SHARE_DELETE flag. We cannot just say POSIX...
we would need add new tests for FILE_SHARE_DELETE. What would these tests be?

All these were addressed above.

if I import external package that decides to use FILE_SHARE_DELETE file mode. How would I know

That is another argument for making it the default, and providing a global flag to disable it.

I posted to golang-nuts to seek affected Windows apps; I'll ping it every few days to keep it visible. If that doesn't surface much, making it the default on tip for 1.14 should clarify things.
https://groups.google.com/d/topic/golang-nuts/8BiP_mPoCd4/discussion

That is another argument for making it the default, and providing a global flag to disable it.

Btw. What that flag should do on Linux? Afaiu we actually also miss functionality to open files in Linux on way that they cannot be deleted by other processes? Based on this https://gavv.github.io/articles/file-locks/#open-file-description-locks-fcntl it should be possible implement it on modern versions of Linux.

The flag (e.g. var OpenWithout_FILE_SHARE_DELETE = false) would be defined in syscall_windows.go, as it affects the behavior of syscall.Open().

A flag for something Linux-specific would have a different name, and be defined in syscall_linux.go. Offhand I don't know how to prevent deletion of an open file on Linux other than via permissions on its directory. The link you provided describes "advisory" locking.

@jstarks before we consider introducing FILE_SHARE_DELETE flag, we also need to decide what to do about Go inability to delete running executable file. I don't think FILE_SHARE_DELETE flag can help us here. Can it? If we cannot implement deletion of executable file that is still running, then we cannot claim any POSIX compatibility. And FILE_SHARE_DELETE flag looks like half solution.

Even more, sometimes we fail to delete executable file of a process that even finished. See, for example, #25965, #19491 and #32188 for details. Basically we call WaitForSingleObject on process handle until it signals. But that does not guarantee that executable file can be deleted. We had to add 5 millisecond wait before attempting to delete the file. And even that does not always work - https://github.com/golang/go/issues/25965#issuecomment-482037476

Also, unrelated to this issue, but, given that I have your attention, maybe you can help us revive windows-arm port. See #32135 for details. Basically @jordanrh1 ported Go onto windows-arm, but we don't have windows-arm builder anymore. So we don't even know if port is still working or broken. If you could help us get builder running, maybe we could try and continue to support windows-am port. Otherwise port might get removed - https://github.com/golang/go/wiki/PortingPolicy I don't know, if someone is interested in running Go programs on Windows 10 Iot, but we already have this support. It would be sad to loose it just because we don't have working builder.

Thank you.

Alex

Note this suggested solution for deleting temporary executables: https://github.com/golang/go/issues/25965#issuecomment-495636291

No results from golang-nuts posts; started a new thread:
https://groups.google.com/d/topic/golang-nuts/aRvSo3iKvJY/discussion

At least one not-so-hypothetical concern that I think ought to be investigated would be the effect on file locking and programs that rely on it.

Opening every file handle with FILE_SHARE_DELETE would effectively weaken the semantics of LockFileEx to that of POSIX advisory locks, because any file with FILE_SHARE_DELETE specified can be removed, even if a lock is held on a region of that file.1

Of course this would only apply to LockFileEx calls made with the descriptor returned from os.File.Fd, but that's exactly what's done in the most popular Go file locking library (which currently has 33 known importers, including Docker, gVisor, and Kubernetes). It's also done in Terraform, and LockFileEx is used in loads of other Go code.

I still think this change seems like an ill-advised endeavor in the best case (and a CVE-factory in the worst case). I really don't see how the murky benefits outweigh the clear code breakage that's going to occur.

I also think that most golang-nuts users aren't going to fully grok the implications of this change. With the permission of the Go team, a post on golang-dev might be more effective in generating discussion (especially since this is a core change). Actually, it technically affects the Go toolchain, which also uses LockFileEx in the aforementioned manner.


1 Yes, I know that some of the Windows lock enforcement would still be stronger than on POSIX, but if you can nuke the file, then using a lock file for resource reservation goes out the window.

I posted to golang-dev when I opened the issue.
https://groups.google.com/d/topic/golang-dev/R79TJAzsBfM/discussion

Do any of the libraries you listed _depend on_ the GOOS=windows behavior? You've repeatedly claimed that this change would break existing code, but never provided any evidence.

Kindly stop making sensational unsupportable statements. It clarifies nothing, and I find it offensive.

I posted to golang-dev when I opened the issue. https://groups.google.com/d/topic/golang-dev/R79TJAzsBfM/discussion

It seems like everyone there shot down the idea for their own reasons (all of them similar to the ones mentioned previously in this thread), but if the idea is now being floated to apply this change in Go 1.14, a follow-up to that thread might be useful (with a reminder link to this discussion).

Do any of the libraries you listed depend on the GOOS=windows behavior? You've repeatedly claimed that this change would break existing code, but never provided any evidence.

That would require a full security assessment (of a lot of code) to say for certain. I think I've certainly provided enough evidence that people are relying on behavior that would change visibly with the introduction of this flag. And that's just publicly available code on GitHub.

Kindly stop making sensational unsupportable statements. It clarifies nothing, and I find it offensive.

If links and code aren't support enough, then I don't know what would be in your book. You're not going to find code that stops compiling because of this change, but you will certainly find code that behaves differently with it, and a lot of people would consider that code "broken" because of those changes in behavior.

Don't take my objection personally, I think everyone here (including you) is just trying to add their input to improve the language. If you're going to champion a core change, you should expect healthy resistance.

As I explained in above, deleting file with FILE_SHARE_DELETE does not have same feature of UNIX's one. For example, please try this steps.

main.c

#include <windows.h>
#include <stdio.h>

int
main() {
  // ensure delete the file
  DeleteFile("test.txt");

  // create test.txt with FILE_SHARE_DELETE
  HANDLE h = CreateFile("test.txt",
      GENERIC_READ | GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  puts("waiting 10sec, please run watch.exe on another cmd.exe");
  Sleep(10000);
  if (DeleteFile("test.txt")) {
    puts("deleted");
  }
  getchar();
  return 0;
}

watch.c

#include <stdio.h>
#include <shlwapi.h>

int
main() {
  // waiting test.txt will be removed.
  while (PathFileExists("test.txt")) {
    puts("watching...");
    Sleep(1000);
  }
  // now test.txt should not exists. So this is possible to create new file
  system("notepad test.txt");
  return 0;
}

Makefile

all : main.exe watch.exe

watch.exe : watch.c
    gcc -o watch.exe watch.c -lshlwapi

main.exe : main.c
    gcc -o main.exe main.c

At the first, please run main.exe and next run watch.exe. On UNIX, unlink(2) remove the file immediately. So another processes that is watching the file exists can be create new file with same filename. But on Windows, notepad.exe show an error dialog "access violation" since Windows does not delete the file immediately. I can't tell you what will be happend with this. Security issue? Lost important files? Sorry, I don't know.

@havoc-io Windows won't delete an open file until it is closed, and only holds locks for open files. So use of locking doesn't imply dependence on failure of os.Remove(). Pls provide examples that depend specifically on that failure.

I'll ping the golang-dev thread next week. Two ppl replied to it; one hadn't read this issue, and neither provided any examples.

Sensational, unsupportable claims are not "healthy resistance".

@havoc-io Windows won't delete an open file until it is closed, and only holds locks for open files. So use of locking doesn't imply dependence on failure of os.Remove(). Pls provide examples that depend specifically on that failure.

@networkimprov You're conflating two different types of locking.

Lock files (as locked with LockFileEx) are typically used to guard a resource. One example might be to ensure that there's only a single instance of a daemon running. If a Windows Go daemon opens a file and then executes LockFileEx on the file and successfully acquires an exclusive lock, it might rightfully assume that the lock file won't be removed from under it. But, if FILE_SHARE_DELETE is enabled by default, then all existing code making that assumption is no longer correct. If some additional agent were to remove that lock file (e.g. a user inadvertently deleting a directory), the original daemon might still be running when a new one decides to start.

Lock files can also be used to pipeline commands if the LOCKFILE_FAIL_IMMEDIATELY flag is omitted, causing LockFileEx to wait its turn for lock acquisition. If the lock file is inadvertently removed, it could cause multiple commands to execute at once that should have been pipelined.

Sensational, unsupportable claims are not "healthy resistance".

Nobody is making either sensationalist or unsupportable claims in this thread. Every single person who has disagreed with your proposal, myself included, has provided sound arguments supported by code. As have the people who have supported your proposal.

Pls review the WinAPI docs; Windows won't delete an open file until it is closed, and only holds LockFileEx() locks for open files, so a lock can't persist when the file is closed, regardless of delete. An attempt to open a locked (ie open) file after delete would see an error, so no second lock could be acquired. Threads waiting on a lock have the file open, so wouldn't be disrupted by delete.

Unless I misread the docs, your last two scenarios are unsupported. And this was sensational and unsupported: "this change seems like ... a CVE-factory in the worst case. I really don't see how the murky benefits outweigh the clear code breakage that's going to occur."

@networkimprov The problem is that resource-restricting lock files are usually based on file path. The underlying file object may not be deleted until the file is closed, but if removed via DeleteFile, it won't be accessible at the previous path on the filesystem1, and that will allow other lock files to be created at that path, negating the purpose of the lock file.

I don't think it was a sensationalist comment - it seems pretty clear that something like this could cause a security issue if code is relying on certain OS invariants that are no longer true. And the benefits here do seem pretty murky (as several people have commented: this does not align Windows behavior with POSIX) and the breakage does seem pretty clear (it at least breaks certain locking behaviors).

1 At least that seems to be the case when testing and based on this comment.

Re "in the most recent version of Windows, we updated DeleteFile (on NTFS) to perform a 'POSIX' delete, where the file is removed from the namespace immediately" from https://github.com/golang/go/issues/32088#issuecomment-502850674. AFAIK that version of Windows is not released. What testing do you refer to?

Re benefits, I think you should review the entire thread.

AFAIK that version of Windows is not released.

@networkimprov I believe it's already the case on Windows 10. I seem to be able to remove a file locked with LockFileEx if FILE_SHARE_DELETE is specified to CreateFileW when opening the file.

DeleteFileW() wouldn't give an error in that scenario. How do you know it was removed immediately from the filesystem, vs removed on file/app close? Did you try to open/re-create the file after removing it?

Actually, it technically affects the Go toolchain, which _also_ uses LockFileEx in the aforementioned manner.

Within the go command we very much want POSIX semantics, and should not rely in any way on the absence of FILE_SHARE_DELETE. In fact, I added the cmd/go/internal/robustio package specifically to _work around_ the ways that Windows file-locking deviates from POSIX.

@ianlancetaylor I've posted repeatedly to golang-nuts & -dev seeking comment from any project that relies on the current (undocumented) Windows syscall.Open() behavior. Not a single use case has emerged.

Since several use cases which need FILE_SHARE_DELETE have appeared above (and since Microsoft specifically requested we use it), let's set this flag by default in pre-release 1.14, and re-evaluate if any use cases for the original behavior surface during the cycle.

As suggested earlier in this thread, someone who needs an open() call without this flag could roll their own, using CreateFileW() & os.NewFile().

Can we at least expose a knob for this?

This is super useful for implementing log rotation on windows.

This is super useful for implementing log rotation on windows.

I don't make sure but I guess that it won't work since FILE_SHARE_DELETE does not allow to create new file with original file name if handle of the file is not closed.

@Random-Liu are you asking for a way to enable f.s.d. on Open(), or disable it?

@mattn It works for me. After os.Rename the old log file, I can create a new log file with the orignal name, and inform the daemon to reopen the log file.

@networkimprov I want a way to enable FILE_SHARE_DELETE. I'm currently using syscall.CreateFile directly.

My windows version:

Major  Minor  Build  Revision
-----  -----  -----  --------
10     0      17763  0

@Random-Liu But I guess we can't use this for simulating UNIX behavior.

https://github.com/golang/go/issues/32088#issuecomment-510345908

External application can not access original filename.

mattn, there is no need to keep repeating that point. It's already been stated that we must document the need (on Windows) to rename a file before deleting it when the filename must be reusable before the handle is closed.

If it is renaming the file before closing by application itself, I feel that there is no pros for users who want to do logrotate.

@ianlancetaylor retry re: https://github.com/golang/go/issues/32088#issuecomment-526074116

I've marked this issue as a release blocker.

@bcmills could I interest you in posting a CL?

This line: https://golang.org/src/syscall/syscall_windows.go#L291
Just needs: sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)

Edit: A test to verify it would create a file, then attempt to rename it before closing it.

We need this soon so there's time for any issues to surface before freeze...

Also os.Remove() docs should mention that on Windows, an open file should be renamed before being removed if the filename needs to be available for reuse before the file is closed. Windows doesn't complete a delete on an open file until it's closed.

Looks like the latest version of Windows fulfills https://github.com/golang/go/issues/32088#issuecomment-502850674. See https://github.com/papertrail/go-tail/pull/10#issuecomment-529460973 for details. We're now able to reuse a deleted file's name, even if there are still open handles to the old file, provided that those handles were opened with the FILE_SHARE_DELETE sharing mode. No need to rename before deleting. Perhaps it finally makes sense to set FILE_SHARE_DELETE by default in Go?

That's the intent; we just need someone to submit a CL. (I would, but I can't sign a CLA.)
My previous two comments describe the necessary changes...

@jstarks @jordanrh1 @thaJeztah

Question:
How do we obtain a consistent behavior?
If the program runs on "latest of Windows" it would allow creating a file with a similar name to a "pending delete" file, older version will break on error.

This may actually say : "passed tests / qa but does not work on most targets" now, and
"usually works, and sometime not" in the future (a developer 2 years away from now getting a "file already exit" error will not be able to reproduce).

We need to figure out how to identify 'old' versions and either change the default behavior for those or improve the error if "there is a pending delete" - I'm not sure how can we do that.
And if we cant, how do we deal with inconsistency .

@guybrand as stated above, we'll document that on Windows "an open file should be renamed before being removed if the filename needs to be available for reuse before the file is closed."

This works for old & new Windows versions as well as POSIX.

That sounds like a different approach: "Always rename before reuse, even on new Windows or POSIX"
I like it - it endorses a common practice that will succeed everywhere.

Change https://golang.org/cl/194957 mentions this issue: syscall: allow FILE_SHARE_DELETE on syscall.Open on Windows

The golang-nuts thread is here, and had no responses. That seems to indicate that no one who reads golang-nuts and knows about this behavior is relying on it.

The golang-dev threads are here and here, and the latter (the more recent thread) also garnered no objections.

@alexbrainman -2'd the CL to implement it based on an apparent lack of decision, so I've relabeled this issue as NeedsDecision so that we can get a clear direction.

Personally, to me the decision seems clear: given that the folks from Microsoft on this thread are in support of changing the default behavior to use FILE_SHARE_DELETE (https://github.com/golang/go/issues/32088#issuecomment-502850674), and that no one on golang-nuts seems to care one way or the other, I think we should do it.

The objections I have seen on this thread seem to fall into two categories:

  1. The concern of @alexbrainman (https://github.com/golang/go/issues/32088#issuecomment-504321027) and @mattn (https://github.com/golang/go/issues/32088#issuecomment-494417146) seems to be that since the behavior still won't match POSIX, users expecting POSIX semantics may be confused. (But it is already the case that Open on Windows does not provide POSIX semantics, so to me that concern seems independent of the proposed change.)

  2. @havoc-io's concern (https://github.com/golang/go/issues/32088#issuecomment-510314947) seems to be that FILE_SHARE_DELETE will diminish the invariants provided by file-locking packages when running on Windows. But that argument seems a bit confusing to me, because the concrete package held as an example (github.com/gofrs/flock) explicitly notes that “the locking behaviors are not guaranteed to be the same on each platform”, and from a quick audit none of the concrete examples seem to rely on the Windows-specific interaction between locking and file deletion.

The comment in https://github.com/golang/go/issues/32088#issuecomment-498690757 does intrigue me, though:

I agree with @ianlancetaylor changing the whole toolchain behavior, as proposed in https://codereview.appspot.com/8203043/ is not a good idea. One immediate problem that comes to mind this proposal when using a file handle as a lock file. We use this behavior.

@maruel, could you give some more detail on how your program relies on the Windows-specific interaction between file-locking and file deletion or renaming?

We in Chrome infra use file presence as a locking mechanism. I'd say to not worry too much about this use case. In practice from what I understand O_CREATE + FILE_FLAG_DELETE_ON_CLOSE semantics are sufficient for this need and this can be replaced with a mutex in the Global\\ namespace, which we already do in the python code base.

This solution is not mentioned:

path := "delete-after-open"
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
if err != nil { ... }
// defer statements are executed in LIFO
defer os.Remove(path)            // or defer os.Rename(path, path+"2")
if err != nil { ... }
defer fd.Close()

It has two drawbacks:

  • Close() is not right after Open()
  • Recovering error code is more complicated

@iwdgo This solution is for single process usage. Most of the discussion above is about concurrent access from multiple processes. That's what makes the flag important.

To supplement @bcmills re-cap of the issue, we looked at three other cross-platform development tools:

Mingw-w64 made it a default seven years ago:
https://sourceforge.net/p/mingw-w64/code/HEAD/tree/stable/v3.x/mingw-w64-headers/include/ntdef.h#l858

Erlang made it a default over six years ago: erlang/otp@0e02f48

Python couldn't adopt it due to limitations of MSVC runtime at the time they considered it: https://bugs.python.org/issue15244

A second golang-nuts thread also saw no objections:
https://groups.google.com/d/topic/golang-nuts/aRvSo3iKvJY/discussion

Do you want to make FILE_SHARE_DELETE in default for Open() ? Our discussiion should not be that.

  1. The concern of @alexbrainman (#32088 (comment)) ... seems to be that since the behavior still won't match POSIX ...

My main concern is about breaking people's programs. Similar to what @minux said at https://codereview.appspot.com/8203043/#msg10

another reason: people using windows accept that not being able to rename or
remove an open file is the norm and has already get used to that fact.

We can't silently change the Go 1.0 program behavior here, perhaps people
actually depend on it.

What happened to Go 1.0 compatibility promise here? Where in https://golang.org/doc/go1compat does it say, that it is OK to change open file behavior if you announce your intention at go-nuts?

Maybe we can change this behavior once we introduce Go 2? Can we use modules to protect existing code from breaking? Am I wrong to assume that is what modules were designed for?

Personally, to me the decision seems clear: given that the folks from Microsoft on this thread are in support of changing the default behavior to use FILE_SHARE_DELETE ...

If Microsoft is authority in this subject for you, then you should look at what Microsoft development tools do.

I used this program https://play.golang.org/p/4ZPmV6Df3SD similar to what @mattn posted at https://github.com/golang/go/issues/32088#issuecomment-493753714 I built it with recent C compiler

Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27030.1 for x64
Copyright (C) Microsoft Corporation. All rights reserved.

And, when I run the program, I could not delete C:\Users\Alex\AppData\Local\Temp\a.txt file until the program existed.

Same with similar C# program https://play.golang.org/p/SuM2iWYpZir I used recent

Microsoft (R) Visual C# Compiler version 2.10.0.0 (b9fb1610)
Copyright (C) Microsoft Corporation. All rights reserved.

Similarly this C program https://play.golang.org/p/6HgxePzEW_W built with recent Mingw

gcc (x86_64-win32-seh-rev0, Built by MinGW-W64 project) 7.3.0
Copyright (C) 2017 Free Software Foundation, Inc.

have the same effect. So neither of those tools have FILE_SHARE_DELETE flag set by default.

And personally I don't see any benefits of that change. That includes my Go contributions. We still won't be able to delete executable files while process is running. We will still have problems deleting directories if any process have their current directories set there.

Alex

Do you want to make FILE_SHARE_DELETE in default for Open() ?

The intention is to make os.Open use FILE_SHARE_DELETE by default. This https://golang.org/cl/194957 is the plan.

Alex

My main concern is about breaking people's programs

So far, no one can point to a single example of a program that would break because it relies on getting an error when trying to rename/remove a file opened with Go. But there are documented cases where the current behavior breaks Go apps on Windows.

What happened to Go 1.0 compatibility promise here?

The Go docs for os.Rename() & .Remove() are silent about this Windows feature, but have 15 mentions of other Windows-specific behavior. BTW, Go 2 has shipped: https://blog.golang.org/go2-here-we-come

gcc (x86_64-win32-seh-rev0, Built by MinGW-W64 project) 7.3.0

Whose C-lib are you using with gcc -- perhaps Microsoft's? Mingw-w64 is at v6.0 and it includes FILE_SHARE_DELETE in FILE_SHARE_VALID_FLAGS.

As documented above re Python, in the past the MSVC runtime disallowed certain CreateFile() flag combinations for unknown reasons. I don't know if that's still the case, but it may the reason MS hasn't changed their C-lib fopen().

I don't see any benefits of that change.

The benefits have been explained repeatedly above, by several people; especially allowing os.Rename() on an open file.

Marking as a proposal because the discussion is not converging easily.

To try to summarize the issue and discussion so far:

  • On Unix systems, if one process opens a file, a second process can remove that file. The original process can keep using the file (it has no name anymore), including reads and writes, which succeed. The file content is not removed from disk until the last open reference goes away.

  • On Windows systems, by default, if one process opens a file, a second process _cannot_ remove that file. A file can only be removed when no other process has it open. The FILE_SHARE_DELETE flag changes this behavior: if all the open references to the file were opened with FILE_SHARE_DELETE, then another process can call DeleteFile, and it will succeed instead of failing.

The Windows behavior with FILE_SHARE_DELETE appears to be still subtly different from Unix. The MSDN page says:

The DeleteFile function fails if an application attempts to delete a file that has other handles open for normal I/O or as a memory-mapped file (FILE_SHARE_DELETE must have been specified when other handles were opened).

The DeleteFile function marks a file for deletion on close. Therefore, the file deletion does not occur until the last handle to the file is closed. Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED.

That is, the file _name_ is not removed from the file system until the open references go away. This is different from Unix, where the name disappears before the open references are closed. So if you were removing the file in preparation for recreating a file of the same name, that works in Unix but not in Windows, not even with FILE_SHARE_DELETE.

The original suggestion was to change syscall.Open to set this flag always. Given that syscall is supposed to be close to the kernel interface, adding that flag seems out-of-place. But we could instead consider whether it should be set during os.Open / os.OpenFile.

@alexbrainman has made the argument that Unix and Windows are different, they would remain different even if we set this flag, and so we shouldn't confuse the matter by making this change behind users' backs.

@jstarks from Microsoft says that Microsoft would prefer us to set FILE_SHARE_DELETE automatically and that the "most recent version" of Windows (unclear which, or if it is released) changes DeleteFile to have the Unix semantics of the name disappearing on return from DeleteFile. So if we did this, on that latest Windows, we really would get the same behavior across Unix and Windows.

@networkimprov argues that we should make the change and that there are no actual examples of programs that would break.

@alexbrainman still seems unconvinced.

@mattn also has suggested we should not set the flag automatically, in part because (at least until everyone uses the latest Windows) it will still be different from Unix.

Overall, package os is trying to give a portable API, and it seems like - especially with the Windows change - setting the flag automatically would help with that. There are other flags we set automatically in os.Open to help make behavior less surprising for users, in particular O_CLOEXEC (close-on-exec). Any program that breaks on Windows because of setting this flag in os.Open would also necessarily break on Unix, right?

Especially since at least one engineer at Microsoft says we should be setting it, it seems like it would be OK to set in package os. People who don't want the flag set could still fall back to package syscall.

@alexbrainman and @mattn, suppose we set the flag in os.Open. We know that some programs would work better. Do you have specific examples of real (or likely) programs that would break? Would those programs not be broken on Unix already? Thanks.

@rsc, thanks for your input. Enabling this in package "os" works for me. If that's adopted, a syscall.Open() flag to enable FILE_SHARE_DELETE should be provided for completeness.

To clarify your summary, process boundaries are not a factor; effects are the same within a single process. And the flag also affects file rename; with the flag it completes immediately unlike file delete. (And that allows a workaround for filename availability immediately after delete.)

Thanks @networkimprov. Yes, I understand that process boundaries are not strictly relevant; it just makes the situations easier to describe.

I've changed the title and revised the action plan in the issue text:

a) os.Create/Open/OpenFile() should always enable file_share_delete on Windows,
b) syscall.Open() on Windows should accept a flag which enables file_share_delete, and
c) syscall on Windows should export a constant for the new flag.

The docs for os.Remove() should also note that to reuse a filename after deleting an open file on Windows, one must do: os.Rename(path, unique_path); os.Remove(unique_path).

The possibility that there is a user who implements exclusive processing using current Go's os.Open behavior cannot be denied.

But there are documented cases where the current behavior breaks Go apps on Windows.

What are these? What are the issue numbers? And what do you mean by the word break? When did they break?

Whose C-lib are you using with gcc -- perhaps Microsoft's? Mingw-w64 is at v6.0 and it includes FILE_SHARE_DELETE in FILE_SHARE_VALID_FLAGS.

I do not know. I just downloaded x86_64-7.3.0-release-win32-seh-rt_v5-rev0.7z file from the Internet. You can google for it. It is of version 7.3.

I don't see any benefits of that change.

The benefits have been explained repeatedly above, ...

You left the word personally out of your quote. I talk about my own needs. And that includes my Go project contributions.

To try to summarize the issue and discussion so far:

You did not say that Microsoft developer tools on Windows do not provide this flag by default. See my comment https://github.com/golang/go/issues/32088#issuecomment-531713562

You also ignored my comment about Go 1 compatibility

What happened to Go 1.0 compatibility promise here? Where in https://golang.org/doc/go1compat does it say, that it is OK to change open file behavior if you announce your intention at go-nuts?

How does this change fit into Go 1.0 compatibility promise?

Overall, package os is trying to give a portable API, and it seems like - especially with the Windows change - setting the flag automatically would help with that.

I agree this change aims at people porting Unix software to Windows.

Unfortunately at the expense of Windows users. This change will be surprising to Windows users and developers, because no other programs or tools on Windows work this way. Maybe in the future, but not yet.

And even for people porting Unix software to Windows, this change is doing pretty bad job IMHO. See We still won't be able to delete executable files while process is running. We will still have problems deleting directories if any process have their current directories set there. part from my comment https://github.com/golang/go/issues/32088#issuecomment-531713562

There are other flags we set automatically in os.Open to help make behavior less surprising for users, in particular O_CLOEXEC (close-on-exec). Any program that breaks on Windows because of setting this flag in os.Open would also necessarily break on Unix, right?

I do not know what you mean here. O_CLOEXEC on Windows means to prevent file handles escape into child process. And O_CLOEXEC is always set in os.Open on Windows. So no file opened with os.Open can be accessed by child process on Windows. So what? It sounds reasonable.

Would those programs not be broken on Unix already?

I don't understand your question.

Do you have specific examples of real (or likely) programs that would break?

I don't know, if I have such programs. It is impossible to say. I don't remember all programs I have written. I don't remember what they do.

But I could imagine a Windows service opens predefined file, lets say c:\mysvc.txt, and keeps it open. Imagine another program that checks the service is alive, and restarts the service, if necessary. This another program can just try and delete c:\mysvc.txt, and, if the file is gone, it can safely assume that service process is dead.

Also, I can imagine, some programs might want to prevent users deleting or moving their files - https://stackoverflow.com/questions/11318663/prevent-a-user-from-deleting-moving-or-renaming-a-file

Alex

Here's a data point re deployment of Go on Windows:
Win8 shipped 7 years ago.
This Go bug affecting Win8+ was reported just 5 months ago: #31528

@rsc any program that expects to get an error when trying to rename/remove a file opened with Go is broken on Unix. For instance, Alex's c:\mysvc.txt scenario.

If one develops on Windows and deploys to Linux/etc (or vice versa), this could be a source of bugs.

Our use case is we have a logger which logs to a file.
Log file supports rotation (e.g. next write if log file is > maxSize then rotate).
We also have readers which will open these files and keep them open while they are being read.

Without even the ability to set FILE_SHARE_DELETE we cannot perform a rotation (rename and/or delete) while there are active readers.

@mattn:

The possibility that there is a user who implements exclusive processing using current Go's os.Open behavior cannot be denied.

That may be, but such code is incorrect on Unix anyway. Package os is meant to be a portable interface. Also we are more interested in _actual_ programs than in possibilities.

@alexbrainman:

You did not say that Microsoft developer tools on Windows do not provide this flag by default. See my comment #32088 (comment)

Yes but that is C; we are talking about Go. We are not mimicking the C APIs in general. We are trying to provide a mostly-portable abstraction in package os. The point about O_CLOEXEC was that on Unix systems os.Open sets O_CLOEXEC even though it is not set by default in the corresponding C call. We _do_ try to provide useful defaults that behave roughly the same across all systems.

As far as Go 1 compatibility, changing a corner-case behavior on one OS to align better with other OSes seems OK. We are not promising to preserve all cross-system incompatibilities for all time.

Windows users who insist on the Window-specific behavior always have the option of using package syscall, which would make clear that the behavior being reached for really applies only to Windows.

Again, do you have concrete examples of real programs that would break?

@cpuguy83, thanks for the real-world use case of a program that would be helped.

@rsc As you say, I have not yet figure out the breaking effects of FILE_SHARE_DELETE. Similarly, no confirmation has been found that logrotate can be implemented by this change.

However, once Go has include this change that can delete the file, the user will expect this behavior even though Go can not implement logrotate on Windows by this change. So we have to think carefully about this change. Basically, Windows cannot delete files that are not flagged with FILE_SHARE_DELETE. Users may think that this change will allow Go to delete any files. In particular, files created by os.NewFile with handle can manipulate methods of os.File struct even though this flag is not set. The programmer should know that the file can be deleted on Windows or not by themselfs, I think. I prefer a mechanism that allows FILE_SHARE_DELETE to be passed by flag for os.OpenFile instead of the default. I think the default behavior should be followed to the OS's one.

Yes but that is C; we are talking about Go.

And C# too. I did not investigate, but I am pretty sure any other development tool on Windows behaves this way.

As far as Go 1 compatibility, changing a corner-case behavior on one OS to align better with other OSes seems OK.

It is corner-case to non Windows users. For Windows users this behavior is standard.

Again, do you have concrete examples of real programs that would break?

Consider my service example above concrete.

@cpuguy83, thanks for the real-world use case of a program that would be helped.

@cpuguy83 can you say more about your problem. I would like to see how this change will solve your problem, so I would like to see some real code. If you can provide some small program, that is broken now, but will be working once this issue is resolved, that would be appreciated. It does not have to build, but at least it should provide enough for everyone to judge. Thank you.

Basically, Windows cannot delete files that are not flagged with FILE_SHARE_DELETE. Users may think that this change will allow Go to delete any files.

I agree this is a good point. Only files opened by Go programs could be deleted. But all other files opened by non Go programs will still remain undeletable. That will make os.Remove unpredictable - sometimes it will work, and sometimes it will not.

Alex

I have code calling a patched syscall.Open() which renames open files. That's how you implement log rotation; it works.

// several processes or goroutines do this
   fd, err := os.OpenFile("log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, ...)
   _, err = fd.Write(log_entry) // one or more times
   err = fd.Close()

// log rotator process does this
   err := os.Rename("log", "log_old") // active writers not disturbed
   fmt.Println(err) // "sharing violation" without FILE_SHARE_DELETE

But you didn't need anyone to tell you that :-/

Naturally an os.File from os.NewFile() could have many different properties than those from os.Create/Open/OpenFile(), that's a feature, not a surprise.

You claim that the Microsoft C fopen() behavior is the Windows default behavior; that's not true. There is no CreateFile() default sharing mode; it's not FILE_SHARE_READ | FILE_SHARE_WRITE.

You assert that deployed Windows programs commonly assume that __programs by other vendors__ always open files in a specific way. If that were the case, Microsoft would not have asked us to enable file_share_delete by default.

@networkimprov

But you didn't need anyone to tell you that :-/

I want to know that the external process can remove&rename the file correctly with any cases.

What I want to know is: after include that change, the log file is output from Go, the logrotate executed by the external process works fine, and the it works as production ready without any errors.

@alexbrainman

Unfortunately the code is complicated, but this is actual real-world code: https://github.com/moby/moby/blob/master/daemon/logger/loggerutils/logfile.go

The proposed mitigation is currently to just fork os.OpenFile and syscall.Open: https://github.com/moby/moby/pull/39974/files

To be clear, just having the option to pass through FILE_SHARE_DELETE from OpenFile or even to syscall.Open would be enough to handle our case.

Unfortunately the code is complicated, but this is actual real-world code: https://github.com/moby/moby/blob/master/daemon/logger/loggerutils/logfile.go

The proposed mitigation is currently to just fork os.OpenFile and syscall.Open: https://github.com/moby/moby/pull/39974/files

@cpuguy83 thanks for the pointer.

Unfortunately my brief look at it does not provide enough info to judge if this current issue is the solution. I would have to start with repro (probably this https://github.com/moby/moby/issues/39274#issuecomment-497966709 ) and take it from there. I am not sure when I will have time to spend on this. If you have better suggestion then my plan, let me know.

Alex

Below is a working demo of standard-practice log rotation, with 100 goroutines each logging 50 lines at 0.1-2sec intervals every time they open the log file, and another goroutine rotating the log at 1min intervals. I ran this for several hours yesterday on a Win7 laptop with zero errors.

It's built with a patch to syscall.Open() that enables FILE_SHARE_DELETE; it fails otherwise. One could concoct a more complex logging scheme without this flag, but there are many other uses for it; my own code renames open files for different reasons.

@rsc, I believe this completes the case for your suggestion, if there were still any doubts. (And as noted above, I posted repeatedly in golang-dev and golang-nuts asking for projects that rely on the current behavior, with zero results.)

package main

import (
   "fmt"
   "os"
   "math/rand"
   "syscall"
   "time"
)

const kLogFile = "winrotate"

func main() {
   syscall.Open_FileShareDelete = true
   rand.Seed(time.Now().UnixNano())

   for a := 0; a < 100; a++ {
      go runLog()
   }

   fmt.Println("rotating to "+ kLogFile +"N.txt")
   for a := 0; true; a++ {
      if a >= 5 {
         a = 0
      }
      time.Sleep(60 * time.Second)
      err := os.Rename(kLogFile +".txt", kLogFile + string('0'+a) +".txt")
      if err != nil {
         fmt.Println(err)
         os.Exit(1)
      }
   }
}

const kLineVal = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func runLog() {
   aLine := make([]byte, 102)
   for {
      aFd, err := os.OpenFile(kLogFile +".txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
      if err != nil {
         fmt.Println(err)
         return
      }
      for _, aV := range kLineVal {
         for a := range aLine {
            aLine[a] = byte(aV)
         }
         aEnd := aV - ('z' - 100) // limit line len to 100
         copy(aLine[aEnd:], []byte{'\r','\n'})
         _, err = aFd.Write(aLine[:aEnd + 2])
         if err != nil {
            fmt.Println(err)
            return
         }
         time.Sleep(time.Duration(100 + rand.Intn(1900)) * time.Millisecond)
      }
      err = aFd.Close()
      if err != nil {
         fmt.Println(err)
         return
      }
   }
}

Patch to syscall_windows.go:

diff --git a/src/syscall/syscall_windows.go b/src/syscall/syscall_windows.go
index de05840..e1455d5 100644
--- a/src/syscall/syscall_windows.go
+++ b/src/syscall/syscall_windows.go
@@ -245,6 +245,8 @@ func makeInheritSa() *SecurityAttributes {
    return &sa
 }

+var Open_FileShareDelete = false
+
 func Open(path string, mode int, perm uint32) (fd Handle, err error) {
    if len(path) == 0 {
        return InvalidHandle, ERROR_FILE_NOT_FOUND
@@ -270,6 +272,9 @@ func Open(path string, mode int, perm uint32) (fd Handle, err error) {
        access |= FILE_APPEND_DATA
    }
    sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE)
+   if Open_FileShareDelete {
+       sharemode |= FILE_SHARE_DELETE
+   }
    var sa *SecurityAttributes
    if mode&O_CLOEXEC == 0 {
        sa = makeInheritSa()

@networkimprov The file is not renamed by external process. If renaming the file is doing in same process, there are some other ways to implement it.

https://github.com/mattn/go-sizedwriter/blob/master/_example/example.go#L14

Below is a working demo of standard-practice log rotation

Thank you for your example, @networkimprov . I can see now what you are trying to do.

The only issue with your code is that, if some external process will open files you are writing to without FILE_SHARE_DELETE, then you would have the same problem as you have now regardless of changes we make in Go.

@cpuguy83 I won't be looking at your issue (as I promised here https://github.com/golang/go/issues/32088#issuecomment-536233195 ), because @networkimprov example give me a picture of what he is trying to do.

Alex

Alex, one can loop the rename attempt if outside access is allowed, or restrict the active log file if not.

Mattn, the rotate rename is usually done by a separate process.

It seems like there is no clear consensus here. The developers with Unix backgrounds seem in favor of this, but two of our most active Windows developers (@alexbranman and @mattn) are not. The change would also only truly unify the latest version of Windows that has the new Unix-like behavior for FILE_SHARE_DELETE.

It may be worth revisiting this topic in a few years, to see how widely available the new flag behavior is and whether other languages have converged to a common behavior. If someone wants to file a new issue in, say, two years or so to rethink this, that would be fine.

But for now, given the lack of consensus, this seems like a likely decline.

Leaving open for a week for final comments.

Is there contention on just making the option available?

If we're looking for more voices to chime in, I'd definitely be in favor of doing _something_. Right now, the only solution is to literally copy a bunch of code out of Go's standard library, change one line, and then maintain that fork forever. Every implementation of a log monitor or live "tail" eventually seems to do this.

For an example, see https://github.com/elastic/beats/blob/master/libbeat/common/file/file_windows.go#L85-L103

If I followed correctly, this proposal actually has two parts:

  1. Allow the use of the flag FILE_SHARE_DELETE when opening a file -
    implementation should be easy and safe, developer would have to explicitly
    request this mode when opening a new file
  2. Turn this flag on by default - can be risky, and well supported only
    when using last windows 10 build.

If all would agree on 1, perhaps we can just allow the flag when developer
explicitly request it for now, and revisit 2 in a couple of years.

On Wed, 2 Oct 2019, 19:32 Mark Dascher, notifications@github.com wrote:

If we're looking for more voices to chime in, I'd definitely be in favor
of doing something. Right now, the only solution is to literally copy a
bunch of code out of Go's standard library, change one line, and then
maintain that fork forever. Every implementation of a log monitor or live
"tail" eventually seems to do this.

For an example, see
https://github.com/elastic/beats/blob/master/libbeat/common/file/file_windows.go#L85-L103


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/32088?email_source=notifications&email_token=ABNEY4VFQLSYVI66ENT6G4LQMTLJ7A5CNFSM4HNPNYIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAFRRIQ#issuecomment-537598114,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABNEY4U5L3WFZYNIUOSEY2TQMTLJ7ANCNFSM4HNPNYIA
.

Definitely in favour of _at least_ have 1. (Allow the use of the flag FILE_SHARE_DELETE when opening a file)

Just to revisit my previous proposal and follow-up with example implementation:

The syscall.FILE_SHARE_DELETE flag would fit nicely in the os.OpenFile's flag parameter and would provide a trivial mechanism to enable this behavior on-demand. It doesn't collide with any other os.O_* flags on Windows (and this can be enforced/engineered) and it provides an idiomatic way of specifying system-specific file opening behavior (insofar as os's POSIX-oriented interface can be considered idiomatic on Windows). This is the same route that's already used to pass in system-specific file opening behavior on POSIX platforms (e.g. syscall.O_DIRECTORY on Linux).

Even if the decision is made not to turn it on by default (which I think is the right call), I do agree that the flag has its use cases (including those pointed out by @networkimprov) and that it would be useful to be able to turn it on without having to resort to CreateFileW calls and copying boilerplate code.

Please consider alternative [1] above which allows developers the option to set FILE_SHARE_DELETE when necessary and prevent forked chunks of Go standard library code.

__Rust__ also sets this flag by default, and allows you to _switch off_ any of the FILE_SHARE_* options.
https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html

@rsc could I ask which of the technical arguments made against the proposal were not refuted?

@networkimprov after reading the whole proposal and discussion I think you need to close this and re-open a new issue explicitly about adding new windows specific flag to ossyscall package. This would ensure that the new behavior is opt-in rather than opt-out. Current proposal implies changing existing code, which worries people.

@rsc could I ask which of the technical arguments made against the proposal were not refuted?

I'm sorry, but that's not how the proposal process works. It is not enough to "refute" other people's arguments. "The goal of the proposal process is to reach general consensus about the outcome in a timely manner." There is no clear consensus about the path forward here. Technical arguments have been made, and they did not convince key Go developers who have made significant contributions to the Windows port (namely, @alexbrainman and @mattn). In addition to the lack of clear consensus, there is no clear sign of urgency to do something today: Go worked fine on Windows for nearly 10 years before this issue was filed. As I understand it, leaving everything alone means Go will continue to work as well as it always has.

I filed #34681 to provide an easier way to open files with FILE_SHARE_DELETE in the (still likely) event that this one is declined. It seemed more helpful to start a new thread limited to that idea than to continue this one, which has gotten very long.

@rsc, before you decline this, let's hear what Alex thinks of your O_ALLOW_DELETE proposal.

Early in this discussion, he was against a new os.OpenFile() flag that sets file_share_delete, for the same reasons he disagreed with your os.Create/Open/OpenFile() suggestion. He is concerned that other programs assume that no one ever opens files that way, because MSVC fopen() cannot do so. Those (still unspecified) programs will therefore break Go programs which set the flag.

Alex, one can loop the rename attempt if outside access is allowed, ...

If you are prepared to loop the rename, you don't need to change anything in Go repo code. Your program will work as good as it works now.

Right now, the only solution is to literally copy a bunch of code out of Go's standard library, change one line, and then maintain that fork forever.

I think copying code into separate package is fine. While investigating https://github.com/moby/moby/pull/39974 I got the same idea. I don't think there is much code there to maintain. In fact I implemented just that

https://github.com/alexbrainman/goissue34681

Feel free to copy or use as is. Maybe, given, there is so much interest in this functionality, you actually create proper package that can be shared and used by others. This way you can keep it up to date and fix bugs.

@rsc, before you decline this, let's hear what Alex thinks of your O_ALLOW_DELETE proposal.

Please, try

https://github.com/alexbrainman/goissue34681

first. If you not satisfied for some reason, we could discuss adding new functionality to Go repo.

Thank you.

Alex

I'm really disappointed by this response.

Copying this large swath of code, which is largely not even understood (as
in why is this doing what it's doing) just so we can add a single option
that the system itself supports, just that Go, mostly likely I assume not
even intentionally, doesn't provide a means to pass the option down even to
syscall.Open seems like a rediculous situation.

Why do I have to rewrite syscall.Open to pass this option in?

On Sat, Oct 5, 2019 at 18:43 Alex Brainman notifications@github.com wrote:

Alex, one can loop the rename attempt if outside access is allowed, ...

If you are prepared to loop the rename, you don't need to change anything
in Go repo code. Your program will work as good as it works now.

Right now, the only solution is to literally copy a bunch of code out of
Go's standard library, change one line, and then maintain that fork forever.

I think copying code into separate package is fine. While investigating
moby/moby#39974 https://github.com/moby/moby/pull/39974 I got the same
idea. I don't think there is much code there to maintain. In fact I
implemented just that

https://github.com/alexbrainman/goissue34681

Feel free to copy or use as is. Maybe, given, there is so much interest in
this functionality, you actually create proper package that can be shared
and used by others. This way you can keep it up to date and fix bugs.

@rsc https://github.com/rsc, before you decline this, let's hear what
Alex thinks of your O_ALLOW_DELETE proposal.

Please, try

https://github.com/alexbrainman/goissue34681

first. If you not satisfied for some reason, we could discuss adding new
functionality to Go repo.

Thank you.

Alex


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/32088?email_source=notifications&email_token=AAGDCZXHULQEMHAPTO6ZUJTQNFGE7A5CNFSM4HNPNYIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAN7QIQ#issuecomment-538703906,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGDCZWYUNMCTAGO567AV73QNFGE7ANCNFSM4HNPNYIA
.

>

  • Brian Goff

I'm really disappointed by this response.

I am sorry you feel that way. But did you actually tried to use my package?

Alex

@rsc, since Alex is also opposed to an os.OpenFile() flag, should we do nothing?

How about putting this feature behind a build flag?

As to whether "Go worked fine on Windows for nearly 10 years," it definitely did not, in the event you needed to rename an open file. (It was also broken on Windows 8/10 laptops for the past 7 years.)

I have my own fork of os.OpenFile and syscall.Open already in moby/moby.

Why are we being so dismissive here?

On Tue, Oct 8, 2019 at 01:47 Alex Brainman notifications@github.com wrote:

I'm really disappointed by this response.

I am sorry you feel that way. But did you actually tried to use my package?

Alex


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/32088?email_source=notifications&email_token=AAGDCZRV72GY4IJQJVJWMYTQNRCIHA5CNFSM4HNPNYIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEATNATA#issuecomment-539414604,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGDCZRNYSMIE6BX77XJVWDQNRCIHANCNFSM4HNPNYIA
.

>

  • Brian Goff

I'd suggest this specific proposal be declined. We're not going to introduce a bunch of TOCTOU security bugs for Windows users who relied on the existing behavior of os.OpenFile.

The larger question here is, "how can we expose to Go users the large variety of interesting flags for Windows' CreateFile and NtCreateFile functions?" I expect we'll be able to address those in different proposals, but certainly not by turning them all on by default as this here suggests.

@zx2c4 did you read the whole thread? We weren't able to identify a single case of a Go user who relies on the existing, undocumented behavior, despite repeated attempts.

It's been a week since https://github.com/golang/go/issues/32088#issuecomment-537590826, and there is still very much no clear consensus, which means we should decline this.

Declined.

I've posted a summary of options with pros & cons in https://github.com/golang/go/issues/34681#issuecomment-565853605.

Was this page helpful?
0 / 5 - 0 ratings