From 998f01608cabc09de106b4fbbd038bc3123fca08 Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Sat, 1 Jan 2022 07:51:31 -0800 Subject: [PATCH] os: implement file.Seek, add smoke test --- src/os/file.go | 12 +++++++-- src/os/file_other.go | 5 ++++ src/os/file_unix.go | 6 +++++ src/os/file_windows.go | 6 +++++ src/os/filesystem.go | 3 +++ src/os/os_test.go | 52 ++++++++++++++++++++++++++++++++++++- src/syscall/syscall_libc.go | 12 +++++++-- 7 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/os/file.go b/src/os/file.go index 7b4585bc..f1314d81 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -176,9 +176,17 @@ func (f *File) Readdirnames(n int) (names []string, err error) { return nil, &PathError{"readdirnames", f.name, ErrNotImplemented} } -// Seek is a stub, not yet implemented +// Seek sets the offset for the next Read or Write on file to offset, interpreted +// according to whence: 0 means relative to the origin of the file, 1 means +// relative to the current offset, and 2 means relative to the end. +// It returns the new offset and an error, if any. +// The behavior of Seek on a file opened with O_APPEND is not specified. +// +// If f is a directory, the behavior of Seek varies by operating +// system; you can seek to the beginning of the directory on Unix-like +// operating systems, but not on Windows. func (f *File) Seek(offset int64, whence int) (ret int64, err error) { - return 0, &PathError{"seek", f.name, ErrNotImplemented} + return f.handle.Seek(offset, whence) } // Stat is a stub, not yet implemented diff --git a/src/os/file_other.go b/src/os/file_other.go index 7e5646f5..d29fb56a 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -50,6 +50,11 @@ func (f stdioFileHandle) Close() error { return ErrUnsupported } +// Seek wraps syscall.Seek. +func (f stdioFileHandle) Seek(offset int64, whence int) (int64, error) { + return -1, ErrUnsupported +} + //go:linkname putchar runtime.putchar func putchar(c byte) diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 4ce72d7f..5d9ab5d2 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -64,3 +64,9 @@ func (f unixFileHandle) ReadAt(b []byte, offset int64) (n int, err error) { } return } + +// Seek wraps syscall.Seek. +func (f unixFileHandle) Seek(offset int64, whence int) (int64, error) { + newoffset, err := syscall.Seek(syscallFd(f), offset, whence) + return newoffset, handleSyscallError(err) +} diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 47e9f535..43d6bbe6 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -66,6 +66,12 @@ func (f unixFileHandle) ReadAt(b []byte, offset int64) (n int, err error) { return -1, ErrNotImplemented } +// Seek wraps syscall.Seek. +func (f unixFileHandle) Seek(offset int64, whence int) (int64, error) { + newoffset, err := syscall.Seek(syscallFd(f), offset, whence) + return newoffset, handleSyscallError(err) +} + // isWindowsNulName reports whether name is os.DevNull ('NUL') on Windows. // True is returned if name is 'NUL' whatever the case. func isWindowsNulName(name string) bool { diff --git a/src/os/filesystem.go b/src/os/filesystem.go index 86e78621..f51f6648 100644 --- a/src/os/filesystem.go +++ b/src/os/filesystem.go @@ -49,6 +49,9 @@ type FileHandle interface { // ReadAt reads up to len(b) bytes from the file starting at the given absolute offset ReadAt(b []byte, offset int64) (n int, err error) + // Seek resets the file pointer relative to start, current position, or end + Seek(offset int64, whence int) (newoffset int64, err error) + // Write writes up to len(b) bytes to the file. Write(b []byte) (n int, err error) diff --git a/src/os/os_test.go b/src/os/os_test.go index 1dedf871..deef509a 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -6,9 +6,11 @@ package os_test import ( "io" + "io/ioutil" . "os" "runtime" "strings" + "syscall" "testing" ) @@ -20,7 +22,7 @@ func localTmp() string { func newFile(testName string, t *testing.T) (f *File) { f, err := CreateTemp("", testName) if err != nil { - t.Fatalf("TempFile %s: %s", testName, err) + t.Fatalf("newFile %s: CreateTemp fails with %s", testName, err) } return } @@ -86,6 +88,54 @@ func checkMode(t *testing.T, path string, mode FileMode) { } } +func TestSeek(t *testing.T) { + f := newFile("TestSeek", t) + if f == nil { + t.Fatalf("f is nil") + return // TODO: remove + } + defer Remove(f.Name()) + defer f.Close() + + const data = "hello, world\n" + io.WriteString(f, data) + + type test struct { + in int64 + whence int + out int64 + } + var tests = []test{ + {0, io.SeekCurrent, int64(len(data))}, + {0, io.SeekStart, 0}, + {5, io.SeekStart, 5}, + {0, io.SeekEnd, int64(len(data))}, + {0, io.SeekStart, 0}, + {-1, io.SeekEnd, int64(len(data)) - 1}, + {1 << 33, io.SeekStart, 1 << 33}, + {1 << 33, io.SeekEnd, 1<<33 + int64(len(data))}, + + // Issue 21681, Windows 4G-1, etc: + {1<<32 - 1, io.SeekStart, 1<<32 - 1}, + {0, io.SeekCurrent, 1<<32 - 1}, + {2<<32 - 1, io.SeekStart, 2<<32 - 1}, + {0, io.SeekCurrent, 2<<32 - 1}, + } + for i, tt := range tests { + off, err := f.Seek(tt.in, tt.whence) + if off != tt.out || err != nil { + if e, ok := err.(*PathError); ok && e.Err == syscall.EINVAL && tt.out > 1<<32 && runtime.GOOS == "linux" { + mounts, _ := ioutil.ReadFile("/proc/mounts") + if strings.Contains(string(mounts), "reiserfs") { + // Reiserfs rejects the big seeks. + t.Skipf("skipping test known to fail on reiserfs; https://golang.org/issue/91") + } + } + t.Errorf("#%d: Seek(%v, %v) = %v, %v want %v, nil", i, tt.in, tt.whence, off, err, tt.out) + } + } +} + func TestReadAt(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Pread for Windows") diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index af8a2810..88e920aa 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -46,8 +46,12 @@ func Pread(fd int, p []byte, offset int64) (n int, err error) { return } -func Seek(fd int, offset int64, whence int) (off int64, err error) { - return 0, ENOSYS // TODO +func Seek(fd int, offset int64, whence int) (newoffset int64, err error) { + newoffset = libc_lseek(int32(fd), offset, whence) + if newoffset < 0 { + err = getErrno() + } + return } func Open(path string, flag int, mode uint32) (fd int, err error) { @@ -248,6 +252,10 @@ func libc_read(fd int32, buf *byte, count uint) int //export pread func libc_pread(fd int32, buf *byte, count uint, offset int64) int +// ssize_t lseek(int fd, off_t offset, int whence); +//export lseek +func libc_lseek(fd int32, offset int64, whence int) int64 + // int open(const char *pathname, int flags, mode_t mode); //export open func libc_open(pathname *byte, flags int32, mode uint32) int32