Skip to content

Commit 2b43a98

Browse files
committed
Basic Filestore implementation.
License: MIT Signed-off-by: Kevin Atkinson <[email protected]>
1 parent a9df187 commit 2b43a98

28 files changed

+2729
-40
lines changed

blocks/blockstore/blockstore.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
var log = logging.Logger("blockstore")
2222

2323
// BlockPrefix namespaces blockstore datastores
24-
var BlockPrefix = ds.NewKey("blocks")
24+
const DefaultPrefix = "/blocks"
25+
26+
var blockPrefix = ds.NewKey(DefaultPrefix)
2527

2628
var ValueTypeMismatch = errors.New("the retrieved value is not a Block")
2729
var ErrHashMismatch = errors.New("block in storage has different hash than requested")
@@ -71,20 +73,23 @@ type gcBlockstore struct {
7173
}
7274

7375
func NewBlockstore(d ds.Batching) *blockstore {
76+
return NewBlockstoreWPrefix(d, DefaultPrefix)
77+
}
78+
79+
func NewBlockstoreWPrefix(d ds.Batching, prefix string) *blockstore {
7480
var dsb ds.Batching
75-
dd := dsns.Wrap(d, BlockPrefix)
81+
prefixKey := ds.NewKey(prefix)
82+
dd := dsns.Wrap(d, prefixKey)
7683
dsb = dd
7784
return &blockstore{
7885
datastore: dsb,
86+
prefix: prefixKey,
7987
}
8088
}
8189

8290
type blockstore struct {
8391
datastore ds.Batching
84-
85-
lk sync.RWMutex
86-
gcreq int32
87-
gcreqlk sync.Mutex
92+
prefix ds.Key
8893

8994
rehash bool
9095
}
@@ -175,7 +180,7 @@ func (bs *blockstore) AllKeysChan(ctx context.Context) (<-chan *cid.Cid, error)
175180
// KeysOnly, because that would be _a lot_ of data.
176181
q := dsq.Query{KeysOnly: true}
177182
// datastore/namespace does *NOT* fix up Query.Prefix
178-
q.Prefix = BlockPrefix.String()
183+
q.Prefix = bs.prefix.String()
179184
res, err := bs.datastore.Query(q)
180185
if err != nil {
181186
return nil, err

blocks/blockstore/blockstore_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ func TestAllKeysRespectsContext(t *testing.T) {
170170
default:
171171
}
172172

173-
e := dsq.Entry{Key: BlockPrefix.ChildString("foo").String()}
173+
e := dsq.Entry{Key: blockPrefix.ChildString("foo").String()}
174174
resultChan <- dsq.Result{Entry: e} // let it go.
175175
close(resultChan)
176176
<-done // should be done now.
@@ -190,7 +190,7 @@ func TestValueTypeMismatch(t *testing.T) {
190190
block := blocks.NewBlock([]byte("some data"))
191191

192192
datastore := ds.NewMapDatastore()
193-
k := BlockPrefix.Child(dshelp.CidToDsKey(block.Cid()))
193+
k := blockPrefix.Child(dshelp.CidToDsKey(block.Cid()))
194194
datastore.Put(k, "data that isn't a block!")
195195

196196
blockstore := NewBlockstore(ds_sync.MutexWrap(datastore))

core/builder.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
pin "github.com/ipfs/go-ipfs/pin"
1717
repo "github.com/ipfs/go-ipfs/repo"
1818
cfg "github.com/ipfs/go-ipfs/repo/config"
19+
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
1920

2021
context "context"
2122
retry "gx/ipfs/QmPF5kxTYFkzhaY5LmkExood7aTTZBHWQC6cjdDQBuGrjp/retry-datastore"
@@ -26,6 +27,9 @@ import (
2627
ds "gx/ipfs/QmbzuUusHqaLLoNTDEVLcSF6vZDHZDLPC7p4bztRvvkXxU/go-datastore"
2728
dsync "gx/ipfs/QmbzuUusHqaLLoNTDEVLcSF6vZDHZDLPC7p4bztRvvkXxU/go-datastore/sync"
2829
ci "gx/ipfs/QmfWDLQjGjVe4fr5CoztYW2DYYjRysMJrFe1RCsXLPTf46/go-libp2p-crypto"
30+
31+
"github.com/ipfs/go-ipfs/filestore"
32+
"github.com/ipfs/go-ipfs/filestore/support"
2933
)
3034

3135
type BuildCfg struct {
@@ -184,7 +188,14 @@ func setupNode(ctx context.Context, n *IpfsNode, cfg *BuildCfg) error {
184188
return err
185189
}
186190

187-
n.Blockstore = bstore.NewGCBlockstore(cbs, bstore.NewGCLocker())
191+
var mbs bstore.Blockstore = cbs
192+
193+
if n.Repo.DirectMount(fsrepo.FilestoreMount) != nil {
194+
fs := bstore.NewBlockstoreWPrefix(n.Repo.Datastore(), fsrepo.FilestoreMount)
195+
mbs = filestore_support.NewMultiBlockstore(cbs, fs)
196+
}
197+
198+
n.Blockstore = bstore.NewGCBlockstore(mbs, bstore.NewGCLocker())
188199

189200
rcfg, err := n.Repo.Config()
190201
if err != nil {
@@ -206,9 +217,13 @@ func setupNode(ctx context.Context, n *IpfsNode, cfg *BuildCfg) error {
206217

207218
n.Blocks = bserv.New(n.Blockstore, n.Exchange)
208219
n.DAG = dag.NewDAGService(n.Blocks)
220+
if fs, ok := n.Repo.DirectMount(fsrepo.FilestoreMount).(*filestore.Datastore); ok {
221+
n.DAG = filestore_support.NewDAGService(fs, n.DAG)
222+
}
209223

210224
internalDag := dag.NewDAGService(bserv.New(n.Blockstore, offline.Exchange(n.Blockstore)))
211225
n.Pinning, err = pin.LoadPinner(n.Repo.Datastore(), n.DAG, internalDag)
226+
212227
if err != nil {
213228
// TODO: we should move towards only running 'NewPinner' explicity on
214229
// node init instead of implicitly here as a result of the pinner keys

core/commands/add.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package commands
22

33
import (
4+
"errors"
45
"fmt"
56
"io"
67

78
"github.com/ipfs/go-ipfs/core/coreunix"
9+
"github.com/ipfs/go-ipfs/filestore"
10+
"github.com/ipfs/go-ipfs/filestore/support"
11+
"github.com/ipfs/go-ipfs/repo/fsrepo"
812
"gx/ipfs/QmeWjRodbcZFKe5tMN7poEx3izym6osrLSnTLf9UjJZBbs/pb"
913

10-
blockservice "github.com/ipfs/go-ipfs/blockservice"
14+
//bs "github.com/ipfs/go-ipfs/blocks/blockstore"
15+
bserv "github.com/ipfs/go-ipfs/blockservice"
1116
cmds "github.com/ipfs/go-ipfs/commands"
1217
files "github.com/ipfs/go-ipfs/commands/files"
1318
core "github.com/ipfs/go-ipfs/core"
@@ -33,6 +38,7 @@ const (
3338
chunkerOptionName = "chunker"
3439
pinOptionName = "pin"
3540
rawLeavesOptionName = "raw-leaves"
41+
noCopyName = "no-copy"
3642
)
3743

3844
var AddCmd = &cmds.Command{
@@ -80,6 +86,7 @@ You can now refer to the added file in a gateway, like so:
8086
cmds.StringOption(chunkerOptionName, "s", "Chunking algorithm to use."),
8187
cmds.BoolOption(pinOptionName, "Pin this object when adding.").Default(true),
8288
cmds.BoolOption(rawLeavesOptionName, "Use raw blocks for leaf nodes. (experimental)"),
89+
cmds.BoolOption(noCopyName, "Don't copy file contents. (experimental)"),
8390
},
8491
PreRun: func(req cmds.Request) error {
8592
if quiet, _, _ := req.Option(quietOptionName).Bool(); quiet {
@@ -138,6 +145,7 @@ You can now refer to the added file in a gateway, like so:
138145
chunker, _, _ := req.Option(chunkerOptionName).String()
139146
dopin, _, _ := req.Option(pinOptionName).Bool()
140147
rawblks, _, _ := req.Option(rawLeavesOptionName).Bool()
148+
nocopy, _, _ := req.Option(noCopyName).Bool()
141149

142150
if hash {
143151
nilnode, err := core.NewNode(n.Context(), &core.BuildCfg{
@@ -152,18 +160,33 @@ You can now refer to the added file in a gateway, like so:
152160
n = nilnode
153161
}
154162

155-
dserv := n.DAG
163+
exchange := n.Exchange
156164
local, _, _ := req.Option("local").Bool()
157165
if local {
158-
offlineexch := offline.Exchange(n.Blockstore)
159-
bserv := blockservice.New(n.Blockstore, offlineexch)
160-
dserv = dag.NewDAGService(bserv)
166+
exchange = offline.Exchange(n.Blockstore)
161167
}
162168

163169
outChan := make(chan interface{}, 8)
164170
res.SetOutput((<-chan interface{})(outChan))
165171

166-
fileAdder, err := coreunix.NewAdder(req.Context(), n.Pinning, n.Blockstore, dserv)
172+
var fileAdder *coreunix.Adder
173+
if nocopy {
174+
fs, ok := n.Repo.DirectMount(fsrepo.FilestoreMount).(*filestore.Datastore)
175+
if !ok {
176+
res.SetError(errors.New("filestore not enabled"), cmds.ErrNormal)
177+
return
178+
}
179+
blockstore := filestore_support.NewBlockstore(n.Blockstore, fs)
180+
blockService := bserv.NewWriteThrough(blockstore, exchange)
181+
dagService := dag.NewDAGService(blockService)
182+
fileAdder, err = coreunix.NewAdder(req.Context(), n.Pinning, blockstore, dagService)
183+
} else if exchange != n.Exchange {
184+
blockService := bserv.New(n.Blockstore, exchange)
185+
dagService := dag.NewDAGService(blockService)
186+
fileAdder, err = coreunix.NewAdder(req.Context(), n.Pinning, n.Blockstore, dagService)
187+
} else {
188+
fileAdder, err = coreunix.NewAdder(req.Context(), n.Pinning, n.Blockstore, n.DAG)
189+
}
167190
if err != nil {
168191
res.SetError(err, cmds.ErrNormal)
169192
return

core/commands/block.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ multihash.
3636
},
3737

3838
Subcommands: map[string]*cmds.Command{
39-
"stat": blockStatCmd,
40-
"get": blockGetCmd,
41-
"put": blockPutCmd,
42-
"rm": blockRmCmd,
39+
"stat": blockStatCmd,
40+
"get": blockGetCmd,
41+
"put": blockPutCmd,
42+
"rm": blockRmCmd,
4343
},
4444
}
4545

@@ -285,3 +285,4 @@ It takes a list of base58 encoded multihashs to remove.
285285
},
286286
Type: util.RemovedBlock{},
287287
}
288+

core/core.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,11 @@ type IpfsNode struct {
9595
PrivateKey ic.PrivKey // the local node's private Key
9696

9797
// Services
98-
Peerstore pstore.Peerstore // storage for other Peer instances
99-
Blockstore bstore.GCBlockstore // the block store (lower level)
100-
Blocks bserv.BlockService // the block service, get/add blocks.
101-
DAG merkledag.DAGService // the merkle dag service, get/add objects.
102-
Resolver *path.Resolver // the path resolution system
98+
Peerstore pstore.Peerstore // storage for other Peer instances
99+
Blockstore bstore.GCBlockstore // the block store (lower level)
100+
Blocks bserv.BlockService // the block service, get/add blocks.
101+
DAG merkledag.DAGService // the merkle dag service, get/add objects.
102+
Resolver *path.Resolver // the path resolution system
103103
Reporter metrics.Reporter
104104
Discovery discovery.Service
105105
FilesRoot *mfs.Root
@@ -120,7 +120,7 @@ type IpfsNode struct {
120120
proc goprocess.Process
121121
ctx context.Context
122122

123-
mode mode
123+
mode mode
124124
localModeSet bool
125125
}
126126

filestore/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Notes on the Filestore
2+
3+
The filestore is a work-in-progress datastore that stores the unixfs
4+
data component of blocks in files on the filesystem instead of in the
5+
block itself. The main use of the datastore is to add content to IPFS
6+
without duplicating the content in the IPFS datastore.
7+
8+
The filestore is developed on Debian (GNU/Linux). It has has limited
9+
testing on Windows and should work on MacOS X and other Unix like
10+
systems.
11+
12+
## Adding Files
13+
14+
To add a file to IPFS without copying, use `add --no-copy` or to add a
15+
directory use `add --no-copy`. (Throughout this document all
16+
command are assumed to start with `ipfs` so `filestore add` really
17+
mains `ipfs filestore add`). For example to add the file `hello.txt`
18+
use:
19+
```
20+
ipfs filestore add "`pwd`"/hello.txt
21+
```
22+
23+
Paths stored in the filestore must be absolute.
24+
25+
By default, the contents of the file are always verified by
26+
recomputing the hash. The setting `Filestore.Verify` can be used to
27+
change this to never recompute the hash (not recommended) or to only
28+
recompute the hash when the modification-time has changed.
29+
30+
Adding files to the filestore will generally be faster than adding
31+
blocks normally as less data is copied around. Retrieving blocks from
32+
the filestore takes about the same time when the hash is not
33+
recomputed, when it is, retrieval is slower.
34+
35+
## About filestore entries
36+
37+
Each entry in the filestore is uniquely refereed to by combining the
38+
(1) the hash of the block, (2) the path to the file, and (3) the
39+
offset within the file, using the following syntax:
40+
```
41+
<HASH>/<FILEPATH>//<OFFSET>
42+
```
43+
for example:
44+
```
45+
QmVr26fY1tKyspEJBniVhqxQeEjhF78XerGiqWAwraVLQH//somedir/hello.txt//0
46+
```
47+
48+
In the case that there is only one entry for a hash the entry is
49+
stored using just the hash. If there is more than one entry for a
50+
hash (for example if adding two files with identical content) than one
51+
entry will be stored using just the hash and the others will be stored
52+
using the full key. If the backing file changes or becomes
53+
inaccessible for the default entry (the one with just the hash) the
54+
other entries are tried until a valid entry is found. Once a valid
55+
entry is found that entry will become the default.
56+
57+
When listing the contents of the filestore entries that are stored
58+
using just the hash are displayed as
59+
```
60+
<HASH> /<FILEPATH>//<OFFSET>
61+
```
62+
with a space between the <HASH> amd <FILEPATH>.
63+
64+
It is always possible to refer to a specific entry in the filestore
65+
using the full key regardless to how it is stored.
66+
67+
## Controlling when blocks are verified.
68+
69+
The config variable `Filestore.Verify` can be used to customize when
70+
blocks from the filestore are verified. The default value `Always`
71+
will always verify blocks. A value of `IfChanged. will verify a
72+
block if the modification time of the backing file has changed. This
73+
value works well in most cases, but can miss some changes, espacally
74+
if the filesystem only tracks file modification times with a
75+
resolution of one second (HFS+, used by OS X) or less (FAT32). A
76+
value of `Never`, never checks blocks.

0 commit comments

Comments
 (0)