Commit c7f7f593 authored by LE Manh Cuong's avatar LE Manh Cuong Committed by Ian Lance Taylor

os: reject WriteAt if file opened in append mode

WriteAt use pwrite syscall on *nix or WriteFile on Windows.

On Linux/Windows, these system calls always write to end of file in
append mode, regardless of offset parameter.

It is hard (maybe impossible) to make WriteAt work portably.

Making WriteAt returns an error if file is opened in append mode, we
guarantee to get consistent behavior between platforms, also prevent
user from accidently corrupting their data.

Fixes #30716

Change-Id: If83d935a22a29eed2ff8fe53d13d0b4798aa2b81
Reviewed-on: https://go-review.googlesource.com/c/go/+/166578Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 5ee22904
...@@ -8,3 +8,4 @@ package os ...@@ -8,3 +8,4 @@ package os
var Atime = atime var Atime = atime
var LstatP = &lstat var LstatP = &lstat
var ErrWriteAtInAppendMode = errWriteAtInAppendMode
...@@ -163,13 +163,20 @@ func (f *File) Write(b []byte) (n int, err error) { ...@@ -163,13 +163,20 @@ func (f *File) Write(b []byte) (n int, err error) {
return n, err return n, err
} }
var errWriteAtInAppendMode = errors.New("os: invalid use of WriteAt on file opened with O_APPEND")
// WriteAt writes len(b) bytes to the File starting at byte offset off. // WriteAt writes len(b) bytes to the File starting at byte offset off.
// It returns the number of bytes written and an error, if any. // It returns the number of bytes written and an error, if any.
// WriteAt returns a non-nil error when n != len(b). // WriteAt returns a non-nil error when n != len(b).
//
// If file was opened with the O_APPEND flag, WriteAt returns an error.
func (f *File) WriteAt(b []byte, off int64) (n int, err error) { func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
if err := f.checkValid("write"); err != nil { if err := f.checkValid("write"); err != nil {
return 0, err return 0, err
} }
if f.appendMode {
return 0, errWriteAtInAppendMode
}
if off < 0 { if off < 0 {
return 0, &PathError{"writeat", f.name, errors.New("negative offset")} return 0, &PathError{"writeat", f.name, errors.New("negative offset")}
...@@ -286,7 +293,13 @@ func Create(name string) (*File, error) { ...@@ -286,7 +293,13 @@ func Create(name string) (*File, error) {
// If there is an error, it will be of type *PathError. // If there is an error, it will be of type *PathError.
func OpenFile(name string, flag int, perm FileMode) (*File, error) { func OpenFile(name string, flag int, perm FileMode) (*File, error) {
testlog.Open(name) testlog.Open(name)
return openFileNolog(name, flag, perm) f, err := openFileNolog(name, flag, perm)
if err != nil {
return nil, err
}
f.appendMode = flag&O_APPEND != 0
return f, nil
} }
// lstat is overridden in tests. // lstat is overridden in tests.
......
...@@ -22,9 +22,10 @@ func fixLongPath(path string) string { ...@@ -22,9 +22,10 @@ func fixLongPath(path string) string {
// can overwrite this data, which could cause the finalizer // can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor. // to close the wrong file descriptor.
type file struct { type file struct {
fd int fd int
name string name string
dirinfo *dirInfo // nil unless directory being read dirinfo *dirInfo // nil unless directory being read
appendMode bool // whether file is opened for appending
} }
// Fd returns the integer Plan 9 file descriptor referencing the open file. // Fd returns the integer Plan 9 file descriptor referencing the open file.
......
...@@ -52,6 +52,7 @@ type file struct { ...@@ -52,6 +52,7 @@ type file struct {
dirinfo *dirInfo // nil unless directory being read dirinfo *dirInfo // nil unless directory being read
nonblock bool // whether we set nonblocking mode nonblock bool // whether we set nonblocking mode
stdoutOrErr bool // whether this is stdout or stderr stdoutOrErr bool // whether this is stdout or stderr
appendMode bool // whether file is opened for appending
} }
// Fd returns the integer Unix file descriptor referencing the open file. // Fd returns the integer Unix file descriptor referencing the open file.
......
...@@ -19,9 +19,10 @@ import ( ...@@ -19,9 +19,10 @@ import (
// can overwrite this data, which could cause the finalizer // can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor. // to close the wrong file descriptor.
type file struct { type file struct {
pfd poll.FD pfd poll.FD
name string name string
dirinfo *dirInfo // nil unless directory being read dirinfo *dirInfo // nil unless directory being read
appendMode bool // whether file is opened for appending
} }
// Fd returns the Windows handle referencing the open file. // Fd returns the Windows handle referencing the open file.
......
...@@ -1646,6 +1646,21 @@ func TestWriteAtNegativeOffset(t *testing.T) { ...@@ -1646,6 +1646,21 @@ func TestWriteAtNegativeOffset(t *testing.T) {
} }
} }
// Verify that WriteAt doesn't work in append mode.
func TestWriteAtInAppendMode(t *testing.T) {
defer chtmpdir(t)()
f, err := OpenFile("write_at_in_append_mode.txt", O_APPEND|O_CREATE, 0666)
if err != nil {
t.Fatalf("OpenFile: %v", err)
}
defer f.Close()
_, err = f.WriteAt([]byte(""), 1)
if err != ErrWriteAtInAppendMode {
t.Fatalf("f.WriteAt returned %v, expected %v", err, ErrWriteAtInAppendMode)
}
}
func writeFile(t *testing.T, fname string, flag int, text string) string { func writeFile(t *testing.T, fname string, flag int, text string) string {
f, err := OpenFile(fname, flag, 0666) f, err := OpenFile(fname, flag, 0666)
if err != nil { if err != nil {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment