Skip to content

embed: implement openFile.ReadAt #59489

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

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/embed/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ func (f FS) readDir(dir string) []file {

// Open opens the named file for reading and returns it as an fs.File.
//
// The returned file implements io.Seeker when the file is not a directory.
// The returned file implements io.Seeker and io.ReaderAt when the file is not a directory.
func (f FS) Open(name string) (fs.File, error) {
file := f.lookup(name)
if file == nil {
Expand Down Expand Up @@ -346,7 +346,8 @@ type openFile struct {
}

var (
_ io.Seeker = (*openFile)(nil)
_ io.Seeker = (*openFile)(nil)
_ io.ReaderAt = (*openFile)(nil)
)

func (f *openFile) Close() error { return nil }
Expand Down Expand Up @@ -380,6 +381,17 @@ func (f *openFile) Seek(offset int64, whence int) (int64, error) {
return offset, nil
}

func (f *openFile) ReadAt(b []byte, offset int64) (int, error) {
if offset < 0 || offset > int64(len(f.f.data)) {
return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid}
}
n := copy(b, f.f.data[offset:])
if n < len(b) {
return n, io.EOF
}
return n, nil
}

// An openDir is a directory open for reading.
type openDir struct {
f *file // the directory file itself
Expand Down
57 changes: 57 additions & 0 deletions src/embed/internal/embedtest/embed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package embedtest

import (
"embed"
"io"
"reflect"
"testing"
"testing/fstest"
Expand Down Expand Up @@ -176,3 +177,59 @@ func TestAliases(t *testing.T) {
check(helloBytes)
check(helloString)
}

func TestOffset(t *testing.T) {
file, err := testDirAll.Open("testdata/hello.txt")
if err != nil {
t.Fatal("Open:", err)
}

const want = "hello, world\n"

// Read the entire file.
got := make([]byte, len(want))
n, err := file.Read(got)
if err != nil {
t.Fatal("Read:", err)
}
if n != len(want) {
t.Fatal("Read:", n)
}
if string(got) != want {
t.Fatalf("Read: %q", got)
}

// Try to read one byte; confirm we're at the EOF.
var buf [1]byte
n, err = file.Read(buf[:])
if err != io.EOF {
t.Fatal("Read:", err)
}
if n != 0 {
t.Fatal("Read:", n)
}

// Use seek to get the offset at the EOF.
seeker := file.(io.Seeker)
off, err := seeker.Seek(0, io.SeekCurrent)
if err != nil {
t.Fatal("Seek:", err)
}
if off != int64(len(want)) {
t.Fatal("Seek:", off)
}

// Use ReadAt to read the entire file, ignoring the offset.
at := file.(io.ReaderAt)
got = make([]byte, len(want))
n, err = at.ReadAt(got, 0)
if err != nil {
t.Fatal("ReadAt:", err)
}
if n != len(want) {
t.Fatal("ReadAt:", n)
}
if string(got) != want {
t.Fatalf("ReadAt: %q", got)
}
}