-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
os: Symlink on Windows with a to-be-created directory path silently creates a link of the wrong type #39183
Comments
Change https://golang.org/cl/234112 mentions this issue: |
Sounds right to me, alas. |
I don't see why it is mistake.
Sure. It is nice to have this information. But I don't see how you can provide information about the file / directory that has not been created yet.
I don't see what you are proposing here. Please try again.
It looks like Windows CreateSymbolicLink API succeeds without target file or directory, I don't see why we should second guess API intentions and fail /cc @mattn since he loves symlinks Alex |
os.Symlink should return normal when the target does not exist. As you say, because Windows symbolic link itself has an attribute (file or directory), I feel that it is nonsense to change it dynamically or to make an empty directory. It is also not atomic. I feel that adding new Symlink or SymlinkTo, which allows us to specify attributes in x/sys/windows, is sufficient. The current os.Symlink is returning a proper error. It's not possible to simulate all UNIX behavior on Windows perfectly. |
No, it isn't — and that's the point. |
I am proposing that:
That will eliminate false-negative (nil) returns for symlinks to directories that do not yet exist, at the cost of some false-positive (error) returns for symlinks to files that do not yet exist. However, if the user knows that the link target will eventually be a file, they can use I believe that the majority of calls to
That is correct.
The API of |
Heh, my investigation into a fix uncovered a related bug for symlinks starting with a slash or backslash (#19937). We were invoking
Lines 331 to 335 in 567556d
|
Change https://golang.org/cl/234879 mentions this issue: |
Change https://golang.org/cl/234737 mentions this issue: |
https://golang.org/cl/234879 contains the proposed fix. I think it is telling that none of the test fixes required falling back to |
The proposed change would break my app. I create symlinks to non-existent files to implement reliable storage when creating a new file. Bryan, why shouldn't your use case use x/sys/windows to do this? |
@networkimprov, if you know that you are creating symlinks to nonexistent files or directories on Windows, then it is indeed possible to use We could consider an alternate change instead: fix the relative-link bug, but change the documentation for On balance, I prefer making code that relies on Windows-specific behavior use a Windows-specific system call explicitly, and returning an error for portable programs when |
I also ship my app on MacOS & Linux; it works the same there. I never create files for those symlink targets; the symlinks are placeholders that are removed after the temp file for a new file is fsync()'d. It's not common to create any symlinks in apps that run on Windows, because it requires Admin privs on Win7. What's your scenario that needs symlinks to future directories? |
@networkimprov, for the code from which this issue was discovered, it is possible to restructure the code to avoid creating symlinks to nonexistent directories. However, discovering the current “assume file” behavior as the source of the bug was quite difficult due to the lack of error-reporting. It is the lack of error-reporting that needs to be fixed, not the inability to create dangling symlinks. |
The fact that an arbitrary 50/50 guess happens to work for your program does not make it appropriate for |
Could os.Open() correctly report the cause of the failure? "Access denied" isn't "File/Path not found". Maybe it could Stat() the filename on access-denied. Go on Windows has a variety of filesystem glitches, including: I've resorted to patching the stdlib locally. C'est la Go. |
Even if we changed |
If your Go program calls the filesystem, it must test that on every OS it supports. This problem is easy to surface, as (IIUC) any attempt to treat a symlink-to-file as a directory yields access-denied, regardless of whether the target exists. Were it my call, I'd add Sadly, even in Win10, you have to enable "Developer Mode" to create symlinks without admin privs, so they'll probably remain uncommon on Windows. cc @jstarks in case John has any thoughts... |
But that is not what symlinks created with CreateSymbolicLink API do. For example, they could point to a file on another volume / drive, and that drive might not be mounted all the time. It is obviously fine for symlinks point to non-existing file / directory. Did you read https://docs.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions ? Alex |
I don't have any creative solutions for this problem, unfortunately. We run into a similar issue when trying to determine what symlink type to create when creating symbolic links in WSL. If the target doesn't exist, or if the target type changes after creating the symlink, then we get it wrong. This breaks symlink accesses from Windows (but not from WSL since we interpret the symlinks there, ignoring the type flag). Ideally we would change Windows to not require this distinction, but so far we don't think that's practical. |
Change https://golang.org/cl/235281 mentions this issue: |
Yes. But they still need to point to the correct type of non-existing target — a file (
I don't think that document is relevant to this issue..? This issue is specifically about symbolic links, not junctions or hard-links. (I haven't checked whether an analogous bug exists for |
It is empirically easy to detect the symptom of this problem (the |
But to be honest I don't think we even need to expand the For the few programs that actually do need to create symlinks to nonexistent targets, |
Change https://golang.org/cl/235318 mentions this issue: |
To try to recap the discussion so far:
There are five possible relevant behavior cases to compare Unix and Windows behavior:
Then the question is whether os.Symlink should fail when the target does not exist or otherwise cannot be accessed. Looking again the cases:
If we change to failing on any os.Stat error, we introduce new incompatibility between Unix and Windows in case 2 and one sub-case of case 4, and we don't create any new compatibilities. If we change to failing on any non-os.IsNotExist os.Stat error, we introduce a new incompatibility between Unix and Windows in just the "target was really a file" subcase of case 4. It seems like documenting the behavior might be the cleanest, most compatible path forward. And by compatible I mean both "compatible with older Go" and "minimizing incompatibilities between the Unix and Windows ports". Thoughts? * The philosophy of Go is to avoid deviating from os behavior when possible. So while we could change os.Open to act like WSL and paper over the "incorrect link type" failure, we should not do that. That would make Go programs different from regular non-Go Windows programs, and we try to avoid that. (WSL does not - the whole point of WSL is to be something different.) It's also worth noting that os.Readlink works any time os.Symlink succeeds - the link is definitely created. It just may not be usable with the current kind of target. |
Agreed, documentation is the best solution. Russ, do you believe that someone who needs to create a non-existent directory-target symlink on Windows should resort to x/sys/windows? If I needed that, I'd probably patch my local stdlib, because I'm already patching it for the glitches listed in #39183 (comment) We could consider an os API |
Thanks for writing down all the cases. I agree that we should just document. For people using Windows who need to create a symlink to a directory that does not yet exist, I think the approach is either 1) create the directory first; 2) use x/sys/windows. I don't think we need an |
I think this analysis disregards the possibility of repeatable (or repeating) processes, which are the de-facto standard way to deal with temporary problems such as network failures or expired credentials. Consider a related case: 4(a). Target cannot be access for some temporary reason, in an idempotent program that (manually or automatically) retries failed operations until either the operation reports success or the link exists. Today, In contrast, if we propagate non- Case 4(a) demonstrates that the existing “local compatibility” leads to an incompatibility at the level of the larger program, and the “new incompatibility” of reporting an error instead introduces a new compatibility (in that the two overall programs would then produce equivalent final results). |
Bryan, how would the program you describe acquire Admin privs on Windows? Or check its privs and exit if not Admin? Check .UID from os/user.Current()? Currently, users run my app from a .cmd script that escalates its privs via PowerShell. Eventually I'll create an installer to grant the app Admin privs. |
Windows 10 allows non-Admin users to have the That said, it's not too difficult to probe for the symlink capability by checking whether |
From earlier in thread: PS: enabling Developer Mode requires Admin privs :-) |
This issue is currently labeled as early-in-cycle for Go 1.16. |
We didn't quite resolve this above, but it looks like Ian and I both believe this is a documentation issue and that we shouldn't change anything now. |
Thanks for the input everyone, given the current conviction for a documentation change, I shall punt this to Go1.17. Cheers. |
This issue is currently labeled as early-in-cycle for Go 1.17. |
Change https://golang.org/cl/314275 mentions this issue: |
…d links Also make Export create all parent directories before all files. If the files are symlinks to directories, the target directory must exist before the symlink is created on Windows. Otherwise, the symlink will be created with the wrong type (and thus broken). Fixes golang/go#46503 Updates golang/go#38772 Updates golang/go#39183 Change-Id: I17864ed66e0464e0ed1f56c55751b384b8c59484 Reviewed-on: https://go-review.googlesource.com/c/tools/+/234112 Trust: Bryan C. Mills <bcmills@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> Run-TryBot: Bryan C. Mills <bcmills@google.com> Reviewed-by: Ian Cottrell <iancottrell@google.com>
…d links Also make Export create all parent directories before all files. If the files are symlinks to directories, the target directory must exist before the symlink is created on Windows. Otherwise, the symlink will be created with the wrong type (and thus broken). Fixes golang/go#46503 Updates golang/go#38772 Updates golang/go#39183 Change-Id: I17864ed66e0464e0ed1f56c55751b384b8c59484 Reviewed-on: https://go-review.googlesource.com/c/tools/+/234112 Trust: Bryan C. Mills <bcmills@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> Run-TryBot: Bryan C. Mills <bcmills@google.com> Reviewed-by: Ian Cottrell <iancottrell@google.com>
(Discovered via debugging on #38772.)
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
CL 234737 contains a complete example.
What did you expect to see?
Either a non-nil error from the call to
os.Symlink
, or a successful call toos.Open
.What did you see instead?
Diagnosis
The Windows
CreateSymbolicLinkA
system call requires a flag to indicate whether the destination is a file or a directory. (IfSYMBOLIC_LINK_FLAG_DIRECTORY
is set, the symlink is a directory symlink; otherwise, it is a file symlink.)The current implementation of
os.Symlink
on Windows uses a call toos.Stat
to determine which kind of link to create. Unfortunately, if thatStat
call fails,Symlink
assumes that it is a file rather than reporting the error to the caller:go/src/os/file_windows.go
Lines 337 to 338 in 567556d
That assumption seems like a mistake. If a Windows user needs to create a symlink to a not-yet-existing file or directory on Windows, they should be made aware that the call is missing essential information (the destination type), and can then make an explicit choice to use
golang.org/x/sys/windows.CreateSymbolicLink
instead ofos.Syscall
if they are able to supply the missing information.I think we should change
os.Symlink
at the beginning of the Go 1.16 cycle so that it propagates theStat
error instead of implicitly assuming that the destination is a file.CC @fraenkel @alexbrainman @ianlancetaylor
The text was updated successfully, but these errors were encountered: