fsrefstore.go 4.93 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
package filestore

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/ipfs/go-ipfs/blocks"
	"github.com/ipfs/go-ipfs/blocks/blockstore"
	pb "github.com/ipfs/go-ipfs/filestore/pb"
	dshelp "github.com/ipfs/go-ipfs/thirdparty/ds-help"
	posinfo "github.com/ipfs/go-ipfs/thirdparty/posinfo"

	ds "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore"
	dsns "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore/namespace"
	dsq "gx/ipfs/QmRWDav6mzWseLWeYfVd5fvUKiVe9xNH29YfMF438fG364/go-datastore/query"
	proto "gx/ipfs/QmT6n4mspWYEya864BhCUJEgyxiRfmiSY9ruQwTUNpRKaM/protobuf/proto"
	cid "gx/ipfs/QmV5gPoRsjN1Gid3LMdNZTyfCtP2DsvqEbMAmz82RmmiGk/go-cid"
)

var FilestorePrefix = ds.NewKey("filestore")

type FileManager struct {
	ds   ds.Batching
	root string
}

30 31 32 33 34 35 36 37 38
type CorruptReferenceCode int

const (
	OtherErr    CorruptReferenceCode = 0
	FileError   CorruptReferenceCode = 1
	FileMissing CorruptReferenceCode = 2
	FileChanged CorruptReferenceCode = 3
)

39
type CorruptReferenceError struct {
40 41
	Code CorruptReferenceCode
	Err  error
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
}

func (c CorruptReferenceError) Error() string {
	return c.Err.Error()
}

func NewFileManager(ds ds.Batching, root string) *FileManager {
	return &FileManager{dsns.Wrap(ds, FilestorePrefix), root}
}

func (f *FileManager) AllKeysChan(ctx context.Context) (<-chan *cid.Cid, error) {
	q := dsq.Query{KeysOnly: true}
	q.Prefix = FilestorePrefix.String()

	res, err := f.ds.Query(q)
	if err != nil {
		return nil, err
	}

61
	out := make(chan *cid.Cid, dsq.KeysOnlyBufSize)
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
	go func() {
		defer close(out)
		for {
			v, ok := res.NextSync()
			if !ok {
				return
			}

			k := ds.RawKey(v.Key)
			c, err := dshelp.DsKeyToCid(k)
			if err != nil {
				log.Error("decoding cid from filestore: %s", err)
				continue
			}

			select {
			case out <- c:
			case <-ctx.Done():
				return
			}
		}
	}()

	return out, nil
}

func (f *FileManager) DeleteBlock(c *cid.Cid) error {
	err := f.ds.Delete(dshelp.CidToDsKey(c))
	if err == ds.ErrNotFound {
		return blockstore.ErrNotFound
	}
	return err
}

func (f *FileManager) Get(c *cid.Cid) (blocks.Block, error) {
Kevin Atkinson's avatar
Kevin Atkinson committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110
	dobj, err := f.getDataObj(c)
	if err != nil {
		return nil, err
	}

	out, err := f.readDataObj(c, dobj)
	if err != nil {
		return nil, err
	}

	return blocks.NewBlockWithCid(out, c)
}

func (f *FileManager) getDataObj(c *cid.Cid) (*pb.DataObj, error) {
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
	o, err := f.ds.Get(dshelp.CidToDsKey(c))
	switch err {
	case ds.ErrNotFound:
		return nil, blockstore.ErrNotFound
	default:
		return nil, err
	case nil:
		//
	}

	data, ok := o.([]byte)
	if !ok {
		return nil, fmt.Errorf("stored filestore dataobj was not a []byte")
	}

	var dobj pb.DataObj
	if err := proto.Unmarshal(data, &dobj); err != nil {
		return nil, err
	}

Kevin Atkinson's avatar
Kevin Atkinson committed
131
	return &dobj, nil
132 133
}

134 135
// reads and verifies the block
func (f *FileManager) readDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
Jeromy's avatar
Jeromy committed
136 137
	p := filepath.FromSlash(d.GetFilePath())
	abspath := filepath.Join(f.root, p)
138 139

	fi, err := os.Open(abspath)
140 141 142 143
	if os.IsNotExist(err) {
		return nil, &CorruptReferenceError{FileMissing, err}
	} else if err != nil {
		return nil, &CorruptReferenceError{FileError, err}
144 145 146 147 148
	}
	defer fi.Close()

	_, err = fi.Seek(int64(d.GetOffset()), os.SEEK_SET)
	if err != nil {
149
		return nil, &CorruptReferenceError{FileError, err}
150 151 152 153
	}

	outbuf := make([]byte, d.GetSize_())
	_, err = io.ReadFull(fi, outbuf)
154 155 156 157
	if err == io.EOF || err == io.ErrUnexpectedEOF {
		return nil, &CorruptReferenceError{FileChanged, err}
	} else if err != nil {
		return nil, &CorruptReferenceError{FileError, err}
158 159
	}

160 161 162 163 164 165
	outcid, err := c.Prefix().Sum(outbuf)
	if err != nil {
		return nil, err
	}

	if !c.Equals(outcid) {
166 167
		return nil, &CorruptReferenceError{FileChanged,
			fmt.Errorf("data in file did not match. %s offset %d", d.GetFilePath(), d.GetOffset())}
168 169
	}

170 171 172 173 174 175 176 177 178 179
	return outbuf, nil
}

func (f *FileManager) Has(c *cid.Cid) (bool, error) {
	// NOTE: interesting thing to consider. Has doesnt validate the data.
	// So the data on disk could be invalid, and we could think we have it.
	dsk := dshelp.CidToDsKey(c)
	return f.ds.Has(dsk)
}

180 181 182 183
type putter interface {
	Put(ds.Key, interface{}) error
}

184
func (f *FileManager) Put(b *posinfo.FilestoreNode) error {
185 186 187 188
	return f.putTo(b, f.ds)
}

func (f *FileManager) putTo(b *posinfo.FilestoreNode, to putter) error {
189 190 191 192 193 194 195 196 197 198 199
	var dobj pb.DataObj

	if !filepath.HasPrefix(b.PosInfo.FullPath, f.root) {
		return fmt.Errorf("cannot add filestore references outside ipfs root")
	}

	p, err := filepath.Rel(f.root, b.PosInfo.FullPath)
	if err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
200
	dobj.FilePath = proto.String(filepath.ToSlash(p))
201 202 203 204 205 206 207 208
	dobj.Offset = proto.Uint64(b.PosInfo.Offset)
	dobj.Size_ = proto.Uint64(uint64(len(b.RawData())))

	data, err := proto.Marshal(&dobj)
	if err != nil {
		return err
	}

209
	return to.Put(dshelp.CidToDsKey(b.Cid()), data)
210 211 212
}

func (f *FileManager) PutMany(bs []*posinfo.FilestoreNode) error {
213 214 215 216 217
	batch, err := f.ds.Batch()
	if err != nil {
		return err
	}

218
	for _, b := range bs {
219
		if err := f.putTo(b, batch); err != nil {
220 221 222
			return err
		}
	}
223 224

	return batch.Commit()
225
}