Skip to content
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: File.ReadFrom copy_file_range: bad file descriptor on append only file #60181

Closed
HeikoSchlittermann opened this issue May 14, 2023 · 13 comments
Assignees
Labels
NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@HeikoSchlittermann
Copy link

What version of Go are you using (go version)?

$ go version
go version go1.20.4 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

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

What did you do?

https://go.dev/play/p/c0MHjwdDm4K

Basically io.Copy(os.Stdout, ...)
And calling the program on the (Linux, Bash) commandline:

go run . </etc/hosts >>/tmp/file-test

What did you expect to see?

No error message.

What did you see instead?

2023/05/14 10:32:34 0 write /dev/stdout: copy_file_range: bad file descriptor

Additional Info

This issues seems to be bound to the type of the input. E.g. running the above examples as

cat /etc/hosts | go run . >>/tmp/file-test

it works as expected (despite the useless use of cat).
For me it seems as if Go tries to be a way too smart. Using bytes.NewBufferString("...\n") as input to io.Copy works too.

I'm not sure if this is just my own stupidity, as I didn't find a lot of similar reports.

@seankhliao seankhliao changed the title affected/package: io.Copy: can't use ">>FILE": write /dev/stdout: copy_file_range: bad file descriptor io: Copy: can't use ">>FILE": write /dev/stdout: copy_file_range: bad file descriptor May 14, 2023
@seankhliao seankhliao changed the title io: Copy: can't use ">>FILE": write /dev/stdout: copy_file_range: bad file descriptor os: File.ReadFrom copy_file_range: bad file descriptor on append only file May 14, 2023
@seankhliao seankhliao added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label May 14, 2023
@seankhliao
Copy link
Member

I think this is equivalent to:

package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.Open("in")
	if err != nil {
		log.Fatalln(err)
	}
	out, err := os.OpenFile("out", os.O_APPEND|os.O_CREATE, 0o644)
	if err != nil {
		log.Fatalln(err)
	}
	_, err = out.ReadFrom(f)
	if err != nil {
		log.Fatalln(err)
	}
}

@HeikoSchlittermann
Copy link
Author

No, I do not think that this is the same.

  • I assume, the flags os.O_* are passed directly to the open(2) syscall. There must be one flag for the requested mode: O_RDONLY, O_WRONLY, O_RDWR, at least according to the manpage of open(2) I find on a recent Debian GNU/Linux.

  • W/o passing os.O_WR*, I get write: bad filedescriptor, which is ok.

  • W/ passing os.O_WRONLY the append mode works.

Even in case this would reproduce the case I'm describing, I wouldn't compare it. In you example the author of the program can control the flags. In my example I can't. I'm just writing to os.Stdout and I can't control if the caller redirects my output using >, >>, or |.

@bcmills
Copy link
Contributor

bcmills commented May 15, 2023

(attn @ianlancetaylor; CC @tklauser @panjf2000)

@bcmills bcmills added this to the Backlog milestone May 15, 2023
@tklauser
Copy link
Member

tklauser commented May 15, 2023

2023/05/14 10:32:34 0 write /dev/stdout: copy_file_range: bad file descriptor

I think this is because the copy_file_range doesn't support destinations opened with O_APPEND, cf. https://cs.opensource.google/go/go/+/refs/tags/go1.20.4:src/os/readfrom_linux.go;l=15-19 and https://man7.org/linux/man-pages/man2/copy_file_range.2.html#ERRORS.

That check for f.appendMode in the code linked above should cause the fallback path to be taken instead of copy_file_range. However, we don't seem to set File.appendMode in os.NewFile when creating os.Stdout.

This issues seems to be bound to the type of the input. E.g. running the above examples as

cat /etc/hosts | go run . >>/tmp/file-test

I'm not sure why it doesn't fail in that case. I'd still expect O_APPEND to be set on os.Stdout. However, os.Stdin is a pipe in that case, so maybe we're not hitting the path using copy_file_range?

@gopherbot
Copy link

Change https://go.dev/cl/494915 mentions this issue: os: set File.appendMode in newFile if file is opened with O_APPEND

@gopherbot
Copy link

Change https://go.dev/cl/494916 mentions this issue: internal/poll, internal/syscall/unix, net: move and export fcntl syscall wrapper

@tklauser tklauser self-assigned this May 15, 2023
@HeikoSchlittermann
Copy link
Author

HeikoSchlittermann commented May 15, 2023

This issues seems to be bound to the type of the input. E.g. running the above examples as

cat /etc/hosts | go run . >>/tmp/file-test

I'm not sure why it doesn't fail in that case. I'd still expect O_APPEND to be set on os.Stdout. However, os.Stdin is a pipe in that case, so maybe we're not hitting the path using copy_file_range?

Wild guessing w/o reading the (Go) sources: Go tries to be smart if both input and output seem to be regular files. (e.g. >>/dev/null doesn't produce the above error, not sure, if we're not on the copy_file_range path then, or of the OS is forgiving here.)

So, IMHO Go should get even smarter (as suggested by @tklauser) by checking the mode the output is opened as, or less smarter by just avoiding the copy_file_range when operating file handles it doesn't open on its own (0, 1, 2, and maybe even more, if inheritated from a calling process.)

gopherbot pushed a commit that referenced this issue May 18, 2023
…all wrapper

This will allow to use the fcntl syscall in packages other than
internal/poll.

For #60181

Change-Id: I76703766a655f2343c61dad95faf81aad58e007f
Reviewed-on: https://go-review.googlesource.com/c/go/+/494916
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Auto-Submit: Tobias Klauser <tobias.klauser@gmail.com>
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
@Masterxilo
Copy link

This bug broke my simple program that basically just does "io.Copy(os.Stdout, someData())" whenever that program was used to append to a file: "program >> file.txt" ...

The err you get is: "write /dev/stdout: copy_file_range: bad file descriptor"

@Masterxilo
Copy link

was any regression test added to prevent regressions of this in the future?

A basic e2e test like "go run . </etc/hosts >>/tmp/file-test" should definitely be part of the ci build test suite...

@ianlancetaylor
Copy link
Contributor

We believe that this bug is fixed in the upcoming 1.21 release.

If you want to send in a test, that would be welcome. It could be in the os package, or in cmd/go/testdata/script.

@Masterxilo
Copy link

Yes I downloaded 1.21rc3 and appending stdout works there. I will see if I find time to contribute.

@gopherbot
Copy link

Change https://go.dev/cl/517755 mentions this issue: os: test that copying to append-only files doesn't fail on Linux

gopherbot pushed a commit that referenced this issue Aug 9, 2023
Before CL 494915, this test would fail on Linux in io.Copy with error
"write /dev/stdout: copy_file_range: bad file descriptor" because the
copy_file_range syscall doesn't support destination files opened with
O_APPEND, see
https://man7.org/linux/man-pages/man2/copy_file_range.2.html#ERRORS

For #60181

Change-Id: I2eb4bcac71175121821e0033eb2297a2bc4ec759
Reviewed-on: https://go-review.googlesource.com/c/go/+/517755
Auto-Submit: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
@gopherbot
Copy link

Change https://go.dev/cl/518255 mentions this issue: os: define TestIssue60181 only on Unix platforms

gopherbot pushed a commit that referenced this issue Aug 10, 2023
In CL 517755 the test was added in the unconstrained os_test.go
because it appeared to be portable, but it turned out not to be
valid on plan9.

(The build error was masked on the misc-compile TryBots by #61923.)

Although the test can also compile and run on Windows, the bug it
checks for is specific to Linux and only really needs to run there, so
I am moving it to os_unix_test.go instead of adding yet another test
file for “Unix and Windows but not Plan 9”.

Updates #60181.

Change-Id: I41fd11b288217e95652b5daa72460c0d26bde606
Reviewed-on: https://go-review.googlesource.com/c/go/+/518255
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
@dmitshur dmitshur added NeedsFix The path to resolution is known, but the work has not been done. and removed NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Apr 9, 2024
@dmitshur dmitshur modified the milestones: Backlog, Go1.21 Apr 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests

8 participants