What version of Go are you using (go version
)?
$ go version devel go1.22-98bacdeafe Mon Aug 14 12:51:16 2023 +0000
Does this issue reproduce with the latest release?
Yes.
What did you do?
- Use
os.Open
to open anos.File
for a relative directory. - Call
os.Chdir
to change the current working directory. - Call
ReadDir
orReaddir
on theFile
opened in step (1). - Inspect the resulting
FileInfo
s.
(https://go.dev/play/p/bLLMvUJGu_u?v=gotip)
What did you expect to see?
The FileInfo
s should correspond to the directory entries relative to the File
.
What did you see instead?
On Linux:
- ReadDir
produces the directory contents relative to the current directory instead of the opened file.
- Readdir
produces DirEntry
contents relative to the opened file, but calling the Info
method on those entries attempts to open the wrong paths (compare #52747).
It isn't clear to me how this can be fixed in general — it is possible to use fdopendir
to read the correct entries while the parent file is still open, but if the parent file is closed before the Info
method is called on the returned DirEntry
, I don't see a standard API to reliably open the correct path.
Comment From: rsc
In general Chdir breaks plenty of invariants, and we're not likely to try to work around that fact, simply because it's impossible to catch them all and just makes things more brittle. The only safe Chdirs are (1) at quiet moments when nothing else is going on and you're not going to try to reuse FS state from before the Chdir afterward, which typically reduces to (1a) at startup, and (2) during os/exec by setting cmd.Dir, which doesn't affect your process.
If there was some compelling use case where Open(dir), Chdir, ReadDir+Info, dir.Close was common, then I think the fix would be optimistic fstatat in the implementation of Readdir and the implementation of DirEntry.Info, with the latter falling back to name-based stat if the directory fd has been closed. But I'd want to see that use case before complicating this code. A racing Chdir in another goroutine would be the most common way to get that sequence, but in that case the Chdir is racing with Open and so you have bigger problems.
Leaving this open but probably we won't fix this unless there is a more compelling demonstration of this arising in actual practice.
Comment From: bcmills
A racing
Chdir
in another goroutine would be the most common way to get that sequence, but in that case theChdir
is racing withOpen
and so you have bigger problems.
Note that the Chdir
need not race with Open
: it only needs to race with the Info
method of the DirEntry
structs returned by File.ReadDir
.
But I agree that this doesn't seem to arise in real-world use. (I noticed it in the context of thinking about edge-cases while reviewing https://go.dev/cl/518195.)
Comment From: neild
This isn't just a problem with Chdir.
link
is a symlink todir
,dir
is a directory.os.Open("link")
.- Remove
link
. - Call
ReadDir
on theFile
obtained in step 2. - Call
Info
on the resultingFileInfo
.
This returns an error, because the Info
call tries to stat a file via the now-deleted symlink.