作者:voidExceptio
项目:bazi
func TestWriteSaveAndReadLarge(t *testing.T) {
const chunkSize = 4096
const fanout = 2
chunkStore := &mock.InMemory{}
// just enough to span multiple chunks
greeting := bytes.Repeat(GREETING, chunkSize/len(GREETING)+1)
var saved *blobs.Manifest
{
blob, err := blobs.Open(chunkStore, &blobs.Manifest{
Type: "footype",
ChunkSize: chunkSize,
Fanout: fanout,
})
if err != nil {
t.Fatalf("cannot open blob: %v", err)
}
n, err := blob.WriteAt(greeting, 0)
if err != nil {
t.Fatalf("unexpected write error: %v", err)
}
if g, e := n, len(greeting); g != e {
t.Errorf("unexpected write length: %v != %v", g, e)
}
if g, e := blob.Size(), uint64(len(greeting)); g != e {
t.Errorf("unexpected manifest size: %v != %v", g, e)
}
saved, err = blob.Save()
if err != nil {
t.Fatalf("unexpected error from Save: %v", err)
}
}
t.Logf("saved manifest: %+v", saved)
b, err := blobs.Open(chunkStore, saved)
if err != nil {
t.Fatalf("cannot open saved blob: %v", err)
}
// do +1 to trigger us seeing EOF too
buf := make([]byte, len(greeting)+1)
n, err := b.ReadAt(buf, 0)
if err != io.EOF {
t.Errorf("expected read EOF: %v", err)
}
if g, e := n, len(greeting); g != e {
t.Errorf("unexpected read length: %v != %v", g, e)
}
buf = buf[:n]
if !bytes.Equal(greeting, buf) {
t.Errorf("unexpected read data: %q", buf)
}
}
作者:jgluc
项目:bazi
func (d fuseDir) Lookup(name string, intr fusefs.Intr) (fusefs.Node, fuse.Error) {
de, err := d.reader.Lookup(name)
if err != nil {
if os.IsNotExist(err) {
return nil, fuse.ENOENT
}
return nil, fmt.Errorf("snap lookup error: %v", err)
}
switch {
case de.Type.File != nil:
manifest := de.Type.File.Manifest.ToBlob("file")
blob, err := blobs.Open(d.chunkStore, manifest)
if err != nil {
return nil, fmt.Errorf("snap file blob open error: %v", err)
}
child := fuseFile{
rat: blob,
de: de,
}
return child, nil
case de.Type.Dir != nil:
child, err := Open(d.chunkStore, de.Type.Dir)
if err != nil {
return nil, fmt.Errorf("snap dir FUSE serving error: %v", err)
}
return child, nil
default:
return nil, fmt.Errorf("unknown entry in tree, %v", de.Type.GetValue())
}
}
作者:voidExceptio
项目:bazi
func TestWriteAndSaveLarge(t *testing.T) {
const chunkSize = 4096
const fanout = 64
chunkStore := &mock.InMemory{}
blob, err := blobs.Open(chunkStore, &blobs.Manifest{
Type: "footype",
ChunkSize: chunkSize,
Fanout: fanout,
})
if err != nil {
t.Fatalf("cannot open blob: %v", err)
}
n, err := blob.WriteAt(bytes.Join([][]byte{
bytes.Repeat([]byte{'x'}, chunkSize),
bytes.Repeat([]byte{'y'}, chunkSize),
}, []byte{}), 0)
if err != nil {
t.Fatalf("unexpected write error: %v", err)
}
if g, e := n, 2*chunkSize; g != e {
t.Errorf("unexpected write length: %v != %v", g, e)
}
saved, err := blob.Save()
if err != nil {
t.Fatalf("unexpected error from Save: %v", err)
}
if g, e := saved.Root.String(), "9f3f6815c7680f98e00fe9ab5edc85ba3f4ceb657b9562c35b5a865d970ea3270bab8c7aa3162cbaaa966ad84330f34a22aa9539b4c416f858c35c0775482665"; g != e {
t.Errorf("unexpected key: %q != %q", g, e)
}
if g, e := saved.Size, uint64(chunkSize+chunkSize); g != e {
t.Errorf("unexpected size: %v != %v", g, e)
}
}
作者:voidExceptio
项目:bazi
func setup_dir(t testing.TB, chunkStore chunks.Store, dirents []*wire.Dirent) *wire.Dirent {
blob, err := blobs.Open(
chunkStore,
blobs.EmptyManifest("dir"),
)
if err != nil {
t.Fatalf("unexpected blob open error: %v", err)
}
w := snap.NewWriter(blob)
for _, de := range dirents {
err := w.Add(de)
if err != nil {
t.Fatalf("unexpected add error: %v", err)
}
}
manifest, err := blob.Save()
if err != nil {
t.Fatalf("unexpected save error: %v", err)
}
var de wire.Dirent
de.Type.Dir = &wire.Dir{
Manifest: wirecas.FromBlob(manifest),
}
return &de
}
作者:voidExceptio
项目:bazi
func TestWriteSaveLoopAndRead(t *testing.T) {
const chunkSize = 4096
const fanout = 2
chunkStore := &mock.InMemory{}
blob, err := blobs.Open(chunkStore, &blobs.Manifest{
Type: "footype",
ChunkSize: chunkSize,
Fanout: fanout,
})
if err != nil {
t.Fatalf("cannot open blob: %v", err)
}
// not exactly sure where this magic number comes from :(
greeting := bytes.Repeat(GREETING, 40330)
var prev *cas.Key
for i := 0; i <= 2; i++ {
n, err := blob.WriteAt(greeting, 0)
if err != nil {
t.Fatalf("unexpected write error: %v", err)
}
if g, e := n, len(greeting); g != e {
t.Errorf("unexpected write length: %v != %v", g, e)
}
if g, e := blob.Size(), uint64(len(greeting)); g != e {
t.Errorf("unexpected manifest size: %v != %v", g, e)
}
saved, err := blob.Save()
if err != nil {
t.Fatalf("unexpected error from Save: %v", err)
}
t.Logf("saved %v size=%d", saved.Root, saved.Size)
if prev != nil {
if g, e := saved.Root, *prev; g != e {
t.Errorf("unexpected key: %q != %q", g, e)
}
}
tmp := saved.Root
prev = &tmp
}
// do +1 to trigger us seeing EOF too
buf := make([]byte, len(greeting)+1)
n, err := blob.ReadAt(buf, 0)
if err != io.EOF {
t.Errorf("expected read EOF: %v", err)
}
if g, e := n, len(greeting); g != e {
t.Errorf("unexpected read length: %v != %v", g, e)
}
buf = buf[:n]
if !bytes.Equal(greeting, buf) {
// assumes len > 100, which we know is true
t.Errorf("unexpected read data %q..%q", buf[:100], buf[len(buf)-100:])
}
}
作者:voidExceptio
项目:bazi
func emptyBlob(t testing.TB, chunkStore chunks.Store) *blobs.Blob {
blob, err := blobs.Open(
chunkStore,
blobs.EmptyManifest("footype"),
)
if err != nil {
t.Fatalf("cannot open blob: %v", err)
}
return blob
}
作者:voidExceptio
项目:bazi
func TestOpenNoType(t *testing.T) {
_, err := blobs.Open(mock.NeverUsed{}, &blobs.Manifest{
// no Type
ChunkSize: blobs.MinChunkSize,
Fanout: 2,
})
if g, e := err, blobs.MissingType; g != e {
t.Fatalf("bad error: %v != %v", g, e)
}
}
作者:som-snyt
项目:bazi
// Serve this snapshot with FUSE, with this object store.
func Open(chunkStore chunks.Store, de *wire.Dirent) (fusefs.Node, error) {
switch {
case de.File != nil:
manifest, err := de.File.Manifest.ToBlob("file")
if err != nil {
return nil, err
}
blob, err := blobs.Open(chunkStore, manifest)
if err != nil {
return nil, fmt.Errorf("snap file blob open error: %v", err)
}
child := fuseFile{
rat: blob,
de: de,
}
return child, nil
case de.Dir != nil:
manifest, err := de.Dir.Manifest.ToBlob("dir")
if err != nil {
return nil, err
}
blob, err := blobs.Open(chunkStore, manifest)
if err != nil {
return nil, err
}
r, err := NewReader(blob, de.Dir.Align)
if err != nil {
return nil, err
}
child := fuseDir{
chunkStore: chunkStore,
reader: r,
}
return child, nil
default:
return nil, fmt.Errorf("unknown entry in tree, %v", de)
}
}
作者:voidExceptio
项目:bazi
func TestWriteTruncateZero(t *testing.T) {
const chunkSize = 4096
const fanout = 64
blob, err := blobs.Open(&mock.InMemory{}, &blobs.Manifest{
Type: "footype",
ChunkSize: chunkSize,
Fanout: fanout,
})
if err != nil {
t.Fatalf("cannot open blob: %v", err)
}
n, err := blob.WriteAt(bytes.Join([][]byte{
bytes.Repeat([]byte{'x'}, chunkSize),
bytes.Repeat([]byte{'y'}, chunkSize),
}, []byte{}), 0)
if err != nil {
t.Fatalf("unexpected write error: %v", err)
}
if g, e := n, 2*chunkSize; g != e {
t.Errorf("unexpected write length: %v != %v", g, e)
}
_, err = blob.Save()
if err != nil {
t.Fatalf("unexpected error from Save: %v", err)
}
err = blob.Truncate(0)
if err != nil {
t.Fatalf("unexpected Truncate error: %v", err)
}
if g, e := blob.Size(), uint64(0); g != e {
t.Errorf("unexpected manifest size: %v != %v", g, e)
}
saved, err := blob.Save()
if err != nil {
t.Errorf("unexpected error from Save: %v", err)
}
if g, e := saved.Root, cas.Empty; g != e {
t.Errorf("unexpected key: %v != %v", g, e)
}
if g, e := saved.Size, uint64(0); g != e {
t.Errorf("unexpected size: %v != %v", g, e)
}
}
作者:jgluc
项目:bazi
// Serve this snapshot with FUSE, with this object store.
func Open(chunkStore chunks.Store, dir *wire.Dir) (fusefs.Node, error) {
manifest := dir.Manifest.ToBlob("dir")
blob, err := blobs.Open(chunkStore, manifest)
if err != nil {
return nil, err
}
r, err := NewReader(blob, dir.Align)
if err != nil {
return nil, err
}
node := fuseDir{
chunkStore: chunkStore,
reader: r,
}
return node, nil
}
作者:voidExceptio
项目:bazi
func setup_greeting(t testing.TB, chunkStore chunks.Store) *blobs.Manifest {
blob, err := blobs.Open(
chunkStore,
blobs.EmptyManifest("file"),
)
if err != nil {
t.Fatalf("unexpected blob open error: %v", err)
}
_, err = blob.WriteAt([]byte(GREETING), 0)
if err != nil {
t.Fatalf("unexpected write error: %v", err)
}
manifest, err := blob.Save()
if err != nil {
t.Fatalf("unexpected save error: %v", err)
}
return manifest
}
作者:jgluc
项目:bazi
func (d *dir) Create(req *fuse.CreateRequest, resp *fuse.CreateResponse, intr fs.Intr) (fs.Node, fs.Handle, fuse.Error) {
d.mu.Lock()
defer d.mu.Unlock()
// TODO check for duplicate name
switch req.Mode & os.ModeType {
case 0:
var child node
err := d.fs.db.Update(func(tx *bolt.Tx) error {
bucket := d.fs.bucket(tx).Bucket(bucketInode)
if bucket == nil {
return errors.New("inode bucket is missing")
}
inode, err := inodes.Allocate(bucket)
if err != nil {
return err
}
manifest := blobs.EmptyManifest("file")
blob, err := blobs.Open(d.fs.chunkStore, manifest)
if err != nil {
return fmt.Errorf("blob open problem: %v", err)
}
child = &file{
inode: inode,
name: req.Name,
parent: d,
blob: blob,
}
d.active[req.Name] = child
return d.saveInternal(tx, req.Name, child)
// TODO clean up active on error
})
if err != nil {
return nil, nil, err
}
return child, child, nil
default:
return nil, nil, fuse.EPERM
}
}
作者:voidExceptio
项目:bazi
func TestSparseRead(t *testing.T) {
const chunkSize = 4096
blob, err := blobs.Open(
&mock.InMemory{},
&blobs.Manifest{
Type: "footype",
Size: 100,
ChunkSize: chunkSize,
Fanout: 2,
},
)
buf := make([]byte, 10)
n, err := blob.ReadAt(buf, 3)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
if g, e := n, 10; g != e {
t.Errorf("expected to read 0 bytes: %v != %v", g, e)
}
}
作者:jgluc
项目:bazi
func (d *dir) reviveNode(de *wire.Dirent, name string) (node, error) {
switch {
case de.Type.Dir != nil:
return d.reviveDir(de, name)
case de.Type.File != nil:
manifest := de.Type.File.Manifest.ToBlob("file")
blob, err := blobs.Open(d.fs.chunkStore, manifest)
if err != nil {
return nil, err
}
child := &file{
inode: de.Inode,
name: name,
parent: d,
blob: blob,
}
return child, nil
}
return nil, fmt.Errorf("dirent unknown type: %v", de.GetValue())
}
作者:voidExceptio
项目:bazi
func TestWriteSaveAndRead(t *testing.T) {
chunkStore := &mock.InMemory{}
var saved *blobs.Manifest
{
blob := emptyBlob(t, chunkStore)
n, err := blob.WriteAt(GREETING, 0)
if err != nil {
t.Fatalf("unexpected write error: %v", err)
}
if g, e := n, len(GREETING); g != e {
t.Errorf("unexpected write length: %v != %v", g, e)
}
if g, e := blob.Size(), uint64(len(GREETING)); g != e {
t.Errorf("unexpected manifest size: %v != %v", g, e)
}
saved, err = blob.Save()
if err != nil {
t.Fatalf("unexpected error from Save: %v", err)
}
}
b, err := blobs.Open(chunkStore, saved)
if err != nil {
t.Fatalf("cannot open saved blob: %v", err)
}
// do +1 to trigger us seeing EOF too
buf := make([]byte, len(GREETING)+1)
n, err := b.ReadAt(buf, 0)
if err != io.EOF {
t.Errorf("expected read EOF: %v", err)
}
if g, e := n, len(GREETING); g != e {
t.Errorf("unexpected read length: %v != %v", g, e)
}
buf = buf[:n]
if !bytes.Equal(GREETING, buf) {
t.Errorf("unexpected read data: %q", buf)
}
}
作者:voidExceptio
项目:bazi
func TestWriteSparse(t *testing.T) {
const chunkSize = 4096
chunkStore := &mock.InMemory{}
blob, err := blobs.Open(chunkStore, &blobs.Manifest{
Type: "footype",
ChunkSize: chunkSize,
Fanout: 2,
})
if err != nil {
t.Fatalf("cannot open blob: %v", err)
}
// note: gap after end of first chunk
n, err := blob.WriteAt([]byte{'x'}, chunkSize+3)
if err != nil {
t.Fatalf("unexpected write error: %v", err)
}
if g, e := n, 1; g != e {
t.Errorf("unexpected write length: %v != %v", g, e)
}
if g, e := blob.Size(), uint64(chunkSize)+3+1; g != e {
t.Errorf("unexpected manifest size: %v != %v", g, e)
}
// read exactly a chunksize to access only the hole
buf := make([]byte, 1)
n, err = blob.ReadAt(buf, 0)
if err != nil {
t.Fatalf("unexpected read error: %v", err)
}
if g, e := n, len(buf); g != e {
t.Errorf("unexpected read length: %v != %v", g, e)
}
buf = buf[:n]
if !bytes.Equal([]byte{0}, buf) {
t.Errorf("unexpected read data: %q", buf)
}
}
作者:voidExceptio
项目:bazi
func TestWriteTruncateGrow(t *testing.T) {
const chunkSize = 4096
const fanout = 64
chunkStore := &mock.InMemory{}
blob, err := blobs.Open(chunkStore, &blobs.Manifest{
Type: "footype",
ChunkSize: chunkSize,
Fanout: fanout,
})
if err != nil {
t.Fatalf("cannot open blob: %v", err)
}
n, err := blob.WriteAt(GREETING, 0)
if err != nil {
t.Fatalf("unexpected write error: %v", err)
}
if g, e := n, len(GREETING); g != e {
t.Errorf("unexpected write length: %v != %v", g, e)
}
if g, e := blob.Size(), uint64(len(GREETING)); g != e {
t.Errorf("unexpected manifest size: %v != %v", g, e)
}
_, err = blob.Save()
if err != nil {
t.Fatalf("unexpected error from Save: %v", err)
}
// grow enough to need a new chunk
const newSize = chunkSize + 3
err = blob.Truncate(newSize)
if err != nil {
t.Fatalf("unexpected Truncate error: %v", err)
}
if g, e := blob.Size(), uint64(newSize); g != e {
t.Errorf("unexpected manifest size: %v != %v", g, e)
}
// do +1 to trigger us seeing EOF too
buf := make([]byte, newSize+1)
n, err = blob.ReadAt(buf, 0)
if err != io.EOF {
t.Errorf("expected read EOF: %v", err)
}
if g, e := n, newSize; g != e {
t.Errorf("unexpected read length: %v != %v", g, e)
}
buf = buf[:n]
want := bytes.Join([][]byte{
GREETING,
make([]byte, newSize-len(GREETING)),
}, []byte{})
if g, e := buf, want; !bytes.Equal(g, e) {
t.Errorf("unexpected read data: %q != %q", g, e)
}
saved, err := blob.Save()
if err != nil {
t.Fatalf("unexpected error from Save: %v", err)
}
if g, e := saved.Size, uint64(newSize); g != e {
t.Errorf("unexpected size: %v != %v", g, e)
}
{
blob, err := blobs.Open(chunkStore, saved)
if err != nil {
t.Fatalf("cannot open saved blob: %v", err)
}
buf := make([]byte, newSize+1)
n, err = blob.ReadAt(buf, 0)
if err != io.EOF {
t.Errorf("expected read EOF: %v", err)
}
if g, e := n, newSize; g != e {
t.Errorf("unexpected read length: %v != %v", g, e)
}
buf = buf[:n]
want := bytes.Join([][]byte{
GREETING,
make([]byte, newSize-len(GREETING)),
}, []byte{})
if g, e := buf, want; !bytes.Equal(g, e) {
t.Errorf("unexpected read data: %q != %q", g, e)
}
}
}
作者:jgluc
项目:bazi
// snapshot records a snapshot of the directory and stores it in wde
func (d *dir) snapshot(tx *bolt.Tx, out *wiresnap.Dir, intr fs.Intr) error {
// NOT HOLDING THE LOCK, accessing database snapshot ONLY
// TODO move bucket lookup to caller?
bucket := d.fs.bucket(tx).Bucket(bucketDir)
if bucket == nil {
return errors.New("dir bucket missing")
}
manifest := blobs.EmptyManifest("dir")
blob, err := blobs.Open(d.fs.chunkStore, manifest)
if err != nil {
return err
}
w := snap.NewWriter(blob)
c := bucket.Cursor()
prefix := pathToKey(d.inode, "")
for k, v := c.Seek(prefix); k != nil; k, v = c.Next() {
if !bytes.HasPrefix(k, prefix) {
// past the end of the directory
break
}
name := string(k[len(prefix):])
de, err := d.unmarshalDirent(v)
if err != nil {
return err
}
sde := wiresnap.Dirent{
Name: name,
}
switch {
case de.Type.File != nil:
// TODO d.reviveNode would do blobs.Open and that's a bit
// too much work; rework the apis
sde.Type.File = &wiresnap.File{
Manifest: de.Type.File.Manifest,
}
case de.Type.Dir != nil:
child, err := d.reviveDir(de, name)
if err != nil {
return err
}
sde.Type.Dir = &wiresnap.Dir{}
err = child.snapshot(tx, sde.Type.Dir, intr)
if err != nil {
return err
}
default:
return errors.New("TODO")
}
err = w.Add(&sde)
if err != nil {
return err
}
}
manifest, err = blob.Save()
if err != nil {
return err
}
out.Manifest = wirecas.FromBlob(manifest)
out.Align = w.Align()
return nil
}
作者:voidExceptio
项目:bazi
func TestWriteTruncateShrink(t *testing.T) {
const chunkSize = 4096
const fanout = 64
chunkStore := &mock.InMemory{}
blob, err := blobs.Open(chunkStore, &blobs.Manifest{
Type: "footype",
ChunkSize: chunkSize,
Fanout: fanout,
})
if err != nil {
t.Fatalf("cannot open blob: %v", err)
}
n, err := blob.WriteAt(bytes.Join([][]byte{
bytes.Repeat([]byte{'x'}, chunkSize),
bytes.Repeat([]byte{'y'}, chunkSize),
}, []byte{}), 0)
if err != nil {
t.Fatalf("unexpected write error: %v", err)
}
if g, e := n, 2*chunkSize; g != e {
t.Errorf("unexpected write length: %v != %v", g, e)
}
_, err = blob.Save()
if err != nil {
t.Fatalf("unexpected error from Save: %v", err)
}
// shrink enough to need less depth in tree
const newSize = 5
err = blob.Truncate(newSize)
if err != nil {
t.Fatalf("unexpected Truncate error: %v", err)
}
if g, e := blob.Size(), uint64(newSize); g != e {
t.Errorf("unexpected manifest size: %v != %v", g, e)
}
// do +1 to trigger us seeing EOF too
buf := make([]byte, newSize+1)
n, err = blob.ReadAt(buf, 0)
if err != io.EOF {
t.Errorf("expected read EOF: %v", err)
}
if g, e := n, newSize; g != e {
t.Errorf("unexpected read length: %v != %v", g, e)
}
buf = buf[:n]
if g, e := buf, []byte("xxxxx"); !bytes.Equal(g, e) {
t.Errorf("unexpected read data: %q != %q", g, e)
}
saved, err := blob.Save()
if err != nil {
t.Fatalf("unexpected error from Save: %v", err)
}
if g, e := saved.Size, uint64(newSize); g != e {
t.Errorf("unexpected size: %v != %v", g, e)
}
{
blob, err := blobs.Open(chunkStore, saved)
if err != nil {
t.Fatalf("cannot open saved blob: %v", err)
}
buf := make([]byte, newSize+1)
n, err = blob.ReadAt(buf, 0)
if err != io.EOF {
t.Errorf("expected read EOF: %v", err)
}
if g, e := n, newSize; g != e {
t.Errorf("unexpected read length: %v != %v", g, e)
}
buf = buf[:n]
if g, e := buf, []byte("xxxxx"); !bytes.Equal(g, e) {
t.Errorf("unexpected read data: %q != %q", g, e)
}
}
}
作者:som-snyt
项目:bazi
func testCompareBoth(t *testing.T, saveEvery int) {
f, err := ioutil.TempFile("", "baziltest-")
if err != nil {
t.Fatalf("tempfile error: %v")
}
defer f.Close()
blob, err := blobs.Open(&mock.InMemory{},
&blobs.Manifest{
Type: "footype",
ChunkSize: blobs.MinChunkSize,
Fanout: 2,
},
)
if err != nil {
t.Fatalf("cannot open blob: %v", err)
}
if seed == 0 {
seed = uint64(entropy.Seed())
}
t.Logf("Seed is %d", seed)
qconf := quick.Config{
Rand: rand.New(rand.NewSource(int64(seed))),
}
count := 0
got := func(isWrite bool, off int64, size int, writeSeed int64) (num int, read []byte, err error) {
if off < 0 {
off = -off
}
off = off % (10 * 1024 * 1024)
if size < 0 {
size = -size
}
size = size % (10 * 1024)
if isWrite {
count++
if saveEvery > 0 && count%saveEvery == 0 {
_, err := blob.Save()
if err != nil {
return 0, nil, err
}
}
p := make([]byte, size)
NewRandReader(writeSeed).Read(p)
t.Logf("write %[email protected]%d", len(p), off)
n, err := blob.WriteAt(p, off)
return n, nil, err
} else {
p := make([]byte, size)
t.Logf("read %[email protected]%d", len(p), off)
n, err := blob.ReadAt(p, off)
// http://golang.org/pkg/io/#ReaderAt says "If the n = len(p)
// bytes returned by ReadAt are at the end of the input
// source, ReadAt may return either err == EOF or err ==
// nil." Unify the result
if n == len(p) && err == io.EOF {
err = nil
}
return n, p, err
}
}
exp := func(isWrite bool, off int64, size int, writeSeed int64) (num int, read []byte, err error) {
if off < 0 {
off = -off
}
off = off % (10 * 1024 * 1024)
if size < 0 {
size = -size
}
size = size % (10 * 1024)
if isWrite {
p := make([]byte, size)
NewRandReader(writeSeed).Read(p)
n, err := f.WriteAt(p, off)
return n, nil, err
} else {
p := make([]byte, size)
n, err := f.ReadAt(p, off)
// http://golang.org/pkg/io/#ReaderAt says "If the n = len(p)
// bytes returned by ReadAt are at the end of the input
// source, ReadAt may return either err == EOF or err ==
// nil." Unify the result
if n == len(p) && err == io.EOF {
err = nil
}
return n, p, err
}
}
//.........这里部分代码省略.........