os: implement os.(*File).WriteAt (#3697)
os: implement os.(*File).WriteAt Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
Этот коммит содержится в:
родитель
1d5c5ca2ef
коммит
602f35a5ca
8 изменённых файлов: 152 добавлений и 17 удалений
|
@ -6,4 +6,5 @@ package os
|
||||||
|
|
||||||
// Export for testing.
|
// Export for testing.
|
||||||
|
|
||||||
|
var ErrWriteAtInAppendMode = errWriteAtInAppendMode
|
||||||
var ErrPatternHasSeparator = errPatternHasSeparator
|
var ErrPatternHasSeparator = errPatternHasSeparator
|
||||||
|
|
|
@ -90,7 +90,9 @@ func OpenFile(name string, flag int, perm FileMode) (*File, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &PathError{Op: "open", Path: name, Err: err}
|
return nil, &PathError{Op: "open", Path: name, Err: err}
|
||||||
}
|
}
|
||||||
return NewFile(handle, name), nil
|
f := NewFile(handle, name)
|
||||||
|
f.appendMode = (flag & O_APPEND) != 0
|
||||||
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens the file named for reading.
|
// Open opens the file named for reading.
|
||||||
|
@ -170,13 +172,34 @@ func (f *File) WriteString(s string) (n int, err error) {
|
||||||
return f.Write([]byte(s))
|
return f.Write([]byte(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
|
var errWriteAtInAppendMode = errors.New("os: invalid use of WriteAt on file opened with O_APPEND")
|
||||||
if f.handle == nil {
|
|
||||||
err = ErrClosed
|
// WriteAt writes len(b) bytes to the File starting at byte offset off.
|
||||||
} else {
|
// It returns the number of bytes written and an error, if any.
|
||||||
err = ErrNotImplemented
|
// 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, offset int64) (n int, err error) {
|
||||||
|
switch {
|
||||||
|
case offset < 0:
|
||||||
|
return 0, &PathError{Op: "writeat", Path: f.name, Err: errNegativeOffset}
|
||||||
|
case f.handle == nil:
|
||||||
|
return 0, &PathError{Op: "writeat", Path: f.name, Err: ErrClosed}
|
||||||
|
case f.appendMode:
|
||||||
|
// Go does not wrap this error but it would be more consistent
|
||||||
|
// if it did.
|
||||||
|
return 0, errWriteAtInAppendMode
|
||||||
|
}
|
||||||
|
for len(b) > 0 {
|
||||||
|
m, e := f.handle.WriteAt(b, offset)
|
||||||
|
if e != nil {
|
||||||
|
err = &PathError{Op: "writeat", Path: f.name, Err: e}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n += m
|
||||||
|
b = b[m:]
|
||||||
|
offset += int64(m)
|
||||||
}
|
}
|
||||||
err = &PathError{Op: "writeat", Path: f.name, Err: err}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ type stdioFileHandle uint8
|
||||||
type file struct {
|
type file struct {
|
||||||
handle FileHandle
|
handle FileHandle
|
||||||
name string
|
name string
|
||||||
|
appendMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) close() error {
|
func (f *file) close() error {
|
||||||
|
@ -38,7 +39,7 @@ func (f *file) close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFile(fd uintptr, name string) *File {
|
func NewFile(fd uintptr, name string) *File {
|
||||||
return &File{&file{stdioFileHandle(fd), name}}
|
return &File{&file{handle: stdioFileHandle(fd), name: name}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read reads up to len(b) bytes from machine.Serial.
|
// Read reads up to len(b) bytes from machine.Serial.
|
||||||
|
@ -67,6 +68,10 @@ func (f stdioFileHandle) ReadAt(b []byte, off int64) (n int, err error) {
|
||||||
return 0, ErrNotImplemented
|
return 0, ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f stdioFileHandle) WriteAt(b []byte, off int64) (n int, err error) {
|
||||||
|
return 0, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
// Write writes len(b) bytes to the output. It returns the number of bytes
|
// Write writes len(b) bytes to the output. It returns the number of bytes
|
||||||
// written or an error if this file is not stdout or stderr.
|
// written or an error if this file is not stdout or stderr.
|
||||||
func (f stdioFileHandle) Write(b []byte) (n int, err error) {
|
func (f stdioFileHandle) Write(b []byte) (n int, err error) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ type file struct {
|
||||||
handle FileHandle
|
handle FileHandle
|
||||||
name string
|
name string
|
||||||
dirinfo *dirInfo // nil unless directory being read
|
dirinfo *dirInfo // nil unless directory being read
|
||||||
|
appendMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) close() (err error) {
|
func (f *file) close() (err error) {
|
||||||
|
@ -51,7 +52,7 @@ func (f *file) close() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFile(fd uintptr, name string) *File {
|
func NewFile(fd uintptr, name string) *File {
|
||||||
return &File{&file{unixFileHandle(fd), name, nil}}
|
return &File{&file{handle: unixFileHandle(fd), name: name}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Pipe() (r *File, w *File, err error) {
|
func Pipe() (r *File, w *File, err error) {
|
||||||
|
@ -124,6 +125,18 @@ func (f unixFileHandle) ReadAt(b []byte, offset int64) (n int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// WriteAt returns a non-nil error when n != len(b).
|
||||||
|
//
|
||||||
|
// If file was opened with the O_APPEND flag, WriteAt returns an error.
|
||||||
|
//
|
||||||
|
// TODO: move to file_anyos once WriteAt is implemented for windows.
|
||||||
|
func (f unixFileHandle) WriteAt(b []byte, offset int64) (int, error) {
|
||||||
|
n, err := syscall.Pwrite(syscallFd(f), b, offset)
|
||||||
|
return n, handleSyscallError(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Seek wraps syscall.Seek.
|
// Seek wraps syscall.Seek.
|
||||||
func (f unixFileHandle) Seek(offset int64, whence int) (int64, error) {
|
func (f unixFileHandle) Seek(offset int64, whence int) (int64, error) {
|
||||||
newoffset, err := syscall.Seek(syscallFd(f), offset, whence)
|
newoffset, err := syscall.Seek(syscallFd(f), offset, whence)
|
||||||
|
|
|
@ -37,6 +37,7 @@ func rename(oldname, newname string) error {
|
||||||
type file struct {
|
type file struct {
|
||||||
handle FileHandle
|
handle FileHandle
|
||||||
name string
|
name string
|
||||||
|
appendMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) close() error {
|
func (f *file) close() error {
|
||||||
|
@ -44,7 +45,7 @@ func (f *file) close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFile(fd uintptr, name string) *File {
|
func NewFile(fd uintptr, name string) *File {
|
||||||
return &File{&file{unixFileHandle(fd), name}}
|
return &File{&file{handle: unixFileHandle(fd), name: name}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Pipe() (r *File, w *File, err error) {
|
func Pipe() (r *File, w *File, err error) {
|
||||||
|
@ -84,6 +85,17 @@ func (f unixFileHandle) ReadAt(b []byte, offset int64) (n int, err error) {
|
||||||
return -1, ErrNotImplemented
|
return -1, ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// WriteAt returns a non-nil error when n != len(b).
|
||||||
|
//
|
||||||
|
// If file was opened with the O_APPEND flag, WriteAt returns an error.
|
||||||
|
//
|
||||||
|
// TODO: move to file_anyos once WriteAt is implemented for windows.
|
||||||
|
func (f unixFileHandle) WriteAt(b []byte, offset int64) (n int, err error) {
|
||||||
|
return -1, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
// Seek wraps syscall.Seek.
|
// Seek wraps syscall.Seek.
|
||||||
func (f unixFileHandle) Seek(offset int64, whence int) (int64, error) {
|
func (f unixFileHandle) Seek(offset int64, whence int) (int64, error) {
|
||||||
newoffset, err := syscall.Seek(syscallFd(f), offset, whence)
|
newoffset, err := syscall.Seek(syscallFd(f), offset, whence)
|
||||||
|
|
|
@ -58,6 +58,9 @@ type FileHandle interface {
|
||||||
// Write writes up to len(b) bytes to the file.
|
// Write writes up to len(b) bytes to the file.
|
||||||
Write(b []byte) (n int, err error)
|
Write(b []byte) (n int, err error)
|
||||||
|
|
||||||
|
// WriteAt writes b to the file at the given absolute offset
|
||||||
|
WriteAt(b []byte, offset int64) (n int, err error)
|
||||||
|
|
||||||
// Close closes the file, making it unusable for further writes.
|
// Close closes the file, making it unusable for further writes.
|
||||||
Close() (err error)
|
Close() (err error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package os_test
|
package os_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
. "os"
|
. "os"
|
||||||
|
@ -249,3 +250,66 @@ func TestReadAtEOF(t *testing.T) {
|
||||||
t.Fatalf("ReadAt failed: %s", err)
|
t.Fatalf("ReadAt failed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteAt(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Log("TODO: implement Pwrite for Windows")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f := newFile("TestWriteAt", t)
|
||||||
|
defer Remove(f.Name())
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
const data = "hello, world\n"
|
||||||
|
io.WriteString(f, data)
|
||||||
|
|
||||||
|
n, err := f.WriteAt([]byte("WORLD"), 7)
|
||||||
|
if err != nil || n != 5 {
|
||||||
|
t.Fatalf("WriteAt 7: %d, %v", n, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := os.ReadFile(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadFile %s: %v", f.Name(), err)
|
||||||
|
}
|
||||||
|
if string(b) != "hello, WORLD\n" {
|
||||||
|
t.Fatalf("after write: have %q want %q", string(b), "hello, WORLD\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that WriteAt doesn't allow negative offset.
|
||||||
|
func TestWriteAtNegativeOffset(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Log("TODO: implement Pwrite for Windows")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f := newFile("TestWriteAtNegativeOffset", t)
|
||||||
|
defer Remove(f.Name())
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
n, err := f.WriteAt([]byte("WORLD"), -10)
|
||||||
|
|
||||||
|
const wantsub = "negative offset"
|
||||||
|
if !strings.Contains(fmt.Sprint(err), wantsub) || n != 0 {
|
||||||
|
t.Errorf("WriteAt(-10) = %v, %v; want 0, ...%q...", n, err, wantsub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that WriteAt doesn't work in append mode.
|
||||||
|
func TestWriteAtInAppendMode(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Log("TODO: implement Pwrite for Windows")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer chtmpdir(t)()
|
||||||
|
f, err := OpenFile("write_at_in_append_mode.txt", O_APPEND|O_CREATE|O_WRONLY, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,15 @@ func Pread(fd int, p []byte, offset int64) (n int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Pwrite(fd int, p []byte, offset int64) (n int, err error) {
|
||||||
|
buf, count := splitSlice(p)
|
||||||
|
n = libc_pwrite(int32(fd), buf, uint(count), offset)
|
||||||
|
if n < 0 {
|
||||||
|
err = getErrno()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func Seek(fd int, offset int64, whence int) (newoffset int64, err error) {
|
func Seek(fd int, offset int64, whence int) (newoffset int64, err error) {
|
||||||
newoffset = libc_lseek(int32(fd), offset, whence)
|
newoffset = libc_lseek(int32(fd), offset, whence)
|
||||||
if newoffset < 0 {
|
if newoffset < 0 {
|
||||||
|
@ -342,6 +351,11 @@ func libc_read(fd int32, buf *byte, count uint) int
|
||||||
//export pread
|
//export pread
|
||||||
func libc_pread(fd int32, buf *byte, count uint, offset int64) int
|
func libc_pread(fd int32, buf *byte, count uint, offset int64) int
|
||||||
|
|
||||||
|
// ssize_t pwrite(int fd, void *buf, size_t count, off_t offset);
|
||||||
|
//
|
||||||
|
//export pwrite
|
||||||
|
func libc_pwrite(fd int32, buf *byte, count uint, offset int64) int
|
||||||
|
|
||||||
// ssize_t lseek(int fd, off_t offset, int whence);
|
// ssize_t lseek(int fd, off_t offset, int whence);
|
||||||
//
|
//
|
||||||
//export lseek
|
//export lseek
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче