Skip to content

Commit

Permalink
fix: be explicit about key limitations
Browse files Browse the repository at this point in the history
Only allow keys of the form `/[0-9A-Z+-_=]`. That is, upper-case alphanumeric
keys in the root namespace (plus some special characters).

Why? We don't encode keys before writing them to the filesystem. This change
ensures that:

1. Case sensitivity doesn't matter because we only allow upper-case keys.
2. Path separators and special characters doesn't matter.

For context, go-ipfs only uses flatfs for storing blocks. Every block CID is
encoded as uppercase alphanumeric text (specifically, uppercase base32).

We could be less restrictive, but this is safer and easier to understand.
Unfortunately, we _can't_ allow mixed case (Windows) and can't allow lowercase
because we're already using uppercase keys.

fixes #23
  • Loading branch information
Stebalien committed Feb 14, 2020
1 parent 5717e84 commit 4ca877d
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 39 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
`go-ds-flatfs` is used by `go-ipfs` to store raw block contents on disk. It supports several sharding functions (prefix, suffix, next-to-last/*).

It is _not_ a general-purpose datastore and has several important restrictions.
See the restrictions section for details.

## Lead Maintainer

[Jakub Sztandera](https://github.com/kubuxu)
Expand All @@ -33,13 +36,21 @@
import "github.com/ipfs/go-ds-flatfs"
```

`go-ds-flatfs` uses [`Gx`](https://github.com/whyrusleeping/gx) and [`Gx-go`](https://github.com/whyrusleeping/gx-go) to handle dependendencies. Run `make deps` to download and rewrite the imports to their fixed dependencies.

## Usage

Check the [GoDoc module documentation](https://godoc.org/github.com/ipfs/go-ds-flatfs) for an overview of this module's
functionality.

### Restrictions

FlatFS keys are severely restricted. Only keys that match `/[0-9A-Z+-_=]\+` are
allowed. That is, keys may only contain upper-case alpha-numeric characters,
'-', '+', '_', and '='. This is because values are written directly to the
filesystem without encoding.

Importantly, this means namespaced keys (e.g., /FOO/BAR), are _not_ allowed.
Attempts to write to such keys will result in an error.

### DiskUsage and Accuracy

This datastore implements the [`PersistentDatastore`](https://godoc.org/github.com/ipfs/go-datastore#PersistentDatastore) interface. It offers a `DiskUsage()` method which strives to find a balance between accuracy and performance. This implies:
Expand Down
4 changes: 2 additions & 2 deletions convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package flatfs_test

import (
"bytes"
"encoding/hex"
"encoding/base32"
"io/ioutil"
"math/rand"
"os"
Expand Down Expand Up @@ -205,7 +205,7 @@ func populateDatastore(t *testing.T, dir string) ([]datastore.Key, [][]byte) {
r.Read(blk)
blocks = append(blocks, blk)

key := "x" + hex.EncodeToString(blk[:8])
key := "X" + base32.StdEncoding.EncodeToString(blk[:8])
keys = append(keys, datastore.NewKey(key))
err := ds.Put(keys[i], blocks[i])
if err != nil {
Expand Down
31 changes: 30 additions & 1 deletion flatfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ var putMaxRetries = 6
// concurrent Put and a Delete operation, we cannot guarantee which one
// will win.
func (fs *Datastore) Put(key datastore.Key, value []byte) error {
if !keyIsValid(key) {
return fmt.Errorf("key not supported by flatfs: '%q'", key)
}

fs.shutdownLock.RLock()
defer fs.shutdownLock.RUnlock()
if fs.shutdown {
Expand Down Expand Up @@ -580,6 +584,11 @@ func (fs *Datastore) putMany(data map[datastore.Key][]byte) error {
}

func (fs *Datastore) Get(key datastore.Key) (value []byte, err error) {
// Can't exist in datastore.
if !keyIsValid(key) {
return nil, datastore.ErrNotFound
}

_, path := fs.encode(key)
data, err := ioutil.ReadFile(path)
if err != nil {
Expand All @@ -593,6 +602,11 @@ func (fs *Datastore) Get(key datastore.Key) (value []byte, err error) {
}

func (fs *Datastore) Has(key datastore.Key) (exists bool, err error) {
// Can't exist in datastore.
if !keyIsValid(key) {
return false, nil
}

_, path := fs.encode(key)
switch _, err := os.Stat(path); {
case err == nil:
Expand All @@ -605,6 +619,11 @@ func (fs *Datastore) Has(key datastore.Key) (exists bool, err error) {
}

func (fs *Datastore) GetSize(key datastore.Key) (size int, err error) {
// Can't exist in datastore.
if !keyIsValid(key) {
return -1, datastore.ErrNotFound
}

_, path := fs.encode(key)
switch s, err := os.Stat(path); {
case err == nil:
Expand All @@ -620,6 +639,11 @@ func (fs *Datastore) GetSize(key datastore.Key) (size int, err error) {
// the Put() explanation about the handling of concurrent write
// operations to the same key.
func (fs *Datastore) Delete(key datastore.Key) error {
// Can't exist in datastore.
if !keyIsValid(key) {
return nil
}

fs.shutdownLock.RLock()
defer fs.shutdownLock.RUnlock()
if fs.shutdown {
Expand Down Expand Up @@ -1113,12 +1137,17 @@ func (fs *Datastore) Batch() (datastore.Batch, error) {
}

func (bt *flatfsBatch) Put(key datastore.Key, val []byte) error {
if !keyIsValid(key) {
return fmt.Errorf("key not supported by flatfs: '%q'", key)
}
bt.puts[key] = val
return nil
}

func (bt *flatfsBatch) Delete(key datastore.Key) error {
bt.deletes[key] = struct{}{}
if keyIsValid(key) {
bt.deletes[key] = struct{}{}
} // otherwise, delete is a no-op anyways.
return nil
}

Expand Down
Loading

0 comments on commit 4ca877d

Please sign in to comment.