go 操作系统:在应该出错的目录中,Seek操作成功,

mfuanj7w  于 6个月前  发布在  Go
关注(0)|答案(7)|浏览(53)

你正在使用的Go版本是什么( go version )?

$ go version
go version go1.16.2 linux/amd64

这个问题在最新版本的发布中是否重现?

是的,在最新版本中。

你正在使用什么操作系统和处理器架构( go env )?

go env 输出

$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/tom/.cache/go-build"
GOENV="/home/tom/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/tom/go/pkg/mod"
GONOPROXY=...
GONOSUMDB=...
GOOS="linux"
GOPATH="/home/tom/go"
GOPRIVATE=...
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/tom/sdk/go1.16.2"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/tom/sdk/go1.16.2/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.16.2"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build14584737=/tmp/go-build -gno-record-gcc-switches"

你做了什么?

我正在浏览os目录,阅读源代码,并注意到Seek的EISDIR错误条件是损坏的。
Seek检查os特定的seek方法是否成功处理具有dirinfo的文件:
go/src/os/file.go
第239行到第241行:
| ife==nil&&f.dirinfo!=nil&&r!=0 { |
| e=syscall.EISDIR |
| } |
不幸的是,seek方法总是清除dirinfo( #35767#37161 ):
go/src/os/file_unix.go
第269行到第274行:
| iff.dirinfo!=nil { |
| // 释放缓存的dirinfo,所以如果我们再次访问这个文件作为目录,我们会分配一个新的one。参见#35767和#37161。 |
| f.dirinfo.close() |
| f.dirinfo=nil |
| } |
这个脚本应该在本地运行时重现错误:
https://play.golang.org/p/brgpk5je9tP
顺便说一下:我不确定这是处理目录的最佳方式,它只会在之前调用过readdir时触发,至少在Linux上,只有在seek实际成功后才会触发。

你期望看到什么?

返回一个syscall.EISDIR错误。

你看到了什么?

没有错误地返回了Seek

2lpgd968

2lpgd9682#

据我所知,这正如预期的那样工作。在目录中查找是有意义的。特别是,你可以调用 Seek(1, 0) 来获取当前偏移量,然后稍后你可以回到那个位置。在 (*File).Seek 中的 dirinfo 的测试是为了处理 Windows 的情况,其中 seek 方法不会清除 dirinfo 字段。

是否有特定原因使得 Seek 在 Unix 系统上的目录上应该失败?

ibrsph3r

ibrsph3r3#

以下是修正后的测试程序,我遗漏了 Readdirnames 调用:
https://play.golang.org/p/INPumuqPZ-V
在 Unix 系统中查找目录曾经失败过:

$ go1.14.15 run dir-seek.go
Seek failed for directory as expected: seek /home/tom: is a directory
$ go1.16.2 run dir-seek.go
Seek succeeded unexpectedly for directory with pos=9223372036854775807

我不反对保持当前的行为,但我在更改中没有看到任何表明故意允许这种情况的内容。我非常确定这是一个意外,尽管之前有明确的检查,但它仍然起作用了,因此我认为这是错误的。

xxb16uws

xxb16uws4#

同时,虽然在系统调用级别,Seek是有意义的,但我不认为它在Go级别有意义。dir_unix.go代码缓冲了每个读取的完整8KiB目录信息。如果我没记错的话,如果你试图使用Seek来记住一个位置,你最终总是会到达其中一个8KiB块的末尾,而不是在你刚刚读取的目录条目之后立即到达。

fcy6dtqo

fcy6dtqo5#

好的。我之前没有意识到这个问题在之前的版本中就已经失败了。
看起来这个问题在 https://golang.org/cl/219143 版本中发生了变化。参见 #35767#37161 中的测试用例。这些问题的测试用例涉及到寻求位置 0,这一直都是允许的。但是,正如你所说,那个 CL 改变了代码,使得在寻求非 0 偏移量时返回 EISDIR 的检查不再失败。
有趣的是,在寻求非 0 偏移量时返回 EISDIR 可以追溯到 Seek 方法首次引入的时候,即在 d94c5ab 版本中。
我不太确定我们应该在未来强制执行什么样的行为。CC @randall77

uplii1fm

uplii1fm6#

我认为我们希望 Seek 在目录上工作,用于 (0, io.SeekStart)(n, io.SeekStart)n 从之前的调用返回给 (0, io.SeekCur)(0, io.SeekCur)。因此,whence == io.SeekStart || (whence == io.SeekCur && off == 0)。对于其他情况,我们是否可以立即为 Seek 返回错误?这可能对于 <=1.13 版本的行为是这样的。
实际上,我们可以在 (*FIle).Seek 中更改测试的顺序,将 dirinfo 测试放在对 (*File).seek 的调用之前。
引用 CL 209961 中的自己的话:
附言:我讨厌 Readdirnames API。

x759pob2

x759pob27#

历史上,我们只允许 Seek 的结果为 0 的情况。也就是说,历史上我们没有接受 (n, io.SeekStart) 作为 (0, io.SeekCur) 之前调用返回的值。(尽管我上面说了)。我认为可以回到那种情况。这意味着我认为只接受 (0, io.SeekStart) 是没问题的,因为虽然还有其他方法可以达到 0,但它们是不必要的复杂。

相关问题