1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-07-01 02:30:39 +08:00

Merge pull request #4026 from ipfs/kevina/files-raw-leaves

Add full support for CidV1 in Files API and Dag Modifier
This commit is contained in:
Jeromy Johnson
2017-10-19 23:55:58 +01:00
committed by GitHub
19 changed files with 983 additions and 463 deletions

View File

@ -18,8 +18,10 @@ import (
ft "github.com/ipfs/go-ipfs/unixfs"
uio "github.com/ipfs/go-ipfs/unixfs/io"
cid "gx/ipfs/QmNp85zy9RLrQ5oQD4hPyS39ezrrXpcaa7R4Y9kxdWQLLQ/go-cid"
node "gx/ipfs/QmPN7cwmpcc4DWXb4KTB9dNAJgjuPY69h3npsMfhRrQL9c/go-ipld-format"
logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log"
mh "gx/ipfs/QmU9a9NV9RdPNwZQDYd5uKsm6N6LJLSvLbywDDYFbaaC6P/go-multihash"
)
var log = logging.Logger("cmds/files")
@ -54,9 +56,13 @@ operations.
"stat": FilesStatCmd,
"rm": FilesRmCmd,
"flush": FilesFlushCmd,
"chcid": FilesChcidCmd,
},
}
var cidVersionOption = cmds.IntOption("cid-version", "cid-ver", "Cid version to use. (experimental)")
var hashOption = cmds.StringOption("hash", "Hash function to use. Will set Cid version to 1 if used. (experimental)")
var formatError = errors.New("Format was set by multiple options. Only one format option is allowed")
var FilesStatCmd = &cmds.Command{
@ -162,38 +168,46 @@ func statNode(ds dag.DAGService, fsn mfs.FSNode) (*Object, error) {
c := nd.Cid()
pbnd, ok := nd.(*dag.ProtoNode)
if !ok {
return nil, dag.ErrNotProtobuf
}
d, err := ft.FromBytes(pbnd.Data())
if err != nil {
return nil, err
}
cumulsize, err := nd.Size()
if err != nil {
return nil, err
}
var ndtype string
switch fsn.Type() {
case mfs.TDir:
ndtype = "directory"
case mfs.TFile:
ndtype = "file"
default:
return nil, fmt.Errorf("Unrecognized node type: %s", fsn.Type())
}
switch n := nd.(type) {
case *dag.ProtoNode:
d, err := ft.FromBytes(n.Data())
if err != nil {
return nil, err
}
return &Object{
Hash: c.String(),
Blocks: len(nd.Links()),
Size: d.GetFilesize(),
CumulativeSize: cumulsize,
Type: ndtype,
}, nil
var ndtype string
switch fsn.Type() {
case mfs.TDir:
ndtype = "directory"
case mfs.TFile:
ndtype = "file"
default:
return nil, fmt.Errorf("unrecognized node type: %s", fsn.Type())
}
return &Object{
Hash: c.String(),
Blocks: len(nd.Links()),
Size: d.GetFilesize(),
CumulativeSize: cumulsize,
Type: ndtype,
}, nil
case *dag.RawNode:
return &Object{
Hash: c.String(),
Blocks: 0,
Size: cumulsize,
CumulativeSize: cumulsize,
Type: "file",
}, nil
default:
return nil, fmt.Errorf("not unixfs node (proto or raw)")
}
}
var FilesCpCmd = &cmds.Command{
@ -562,6 +576,13 @@ a beginning offset to write to. The entire length of the input will be written.
If the '--create' option is specified, the file will be created if it does not
exist. Nonexistant intermediate directories will not be created.
Newly created files will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.
Newly created leaves will be in the legacy format (Protobuf) if the
CID version is 0, or raw is the CID version is non-zero. Use of the
--raw-leaves option will override this behavior.
If the '--flush' option is set to false, changes will not be propogated to the
merkledag root. This can make operations much faster when doing a large number
of writes to a deeper directory structure.
@ -587,6 +608,9 @@ stat' on the file or any of its ancestors.
cmds.BoolOption("create", "e", "Create the file if it does not exist."),
cmds.BoolOption("truncate", "t", "Truncate the file to size zero before writing."),
cmds.IntOption("count", "n", "Maximum number of bytes to read."),
cmds.BoolOption("raw-leaves", "Use raw blocks for newly created leaf nodes. (experimental)"),
cidVersionOption,
hashOption,
},
Run: func(req cmds.Request, res cmds.Response) {
path, err := checkPath(req.Arguments()[0])
@ -598,6 +622,13 @@ stat' on the file or any of its ancestors.
create, _, _ := req.Option("create").Bool()
trunc, _, _ := req.Option("truncate").Bool()
flush, _, _ := req.Option("flush").Bool()
rawLeaves, rawLeavesDef, _ := req.Option("raw-leaves").Bool()
prefix, err := getPrefix(req)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
nd, err := req.InvocContext().GetNode()
if err != nil {
@ -615,11 +646,14 @@ stat' on the file or any of its ancestors.
return
}
fi, err := getFileHandle(nd.FilesRoot, path, create)
fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
if rawLeavesDef {
fi.RawLeaves = rawLeaves
}
wfd, err := fi.Open(mfs.OpenWriteOnly, flush)
if err != nil {
@ -685,6 +719,9 @@ var FilesMkdirCmd = &cmds.Command{
ShortDescription: `
Create the directory if it does not already exist.
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.
NOTE: All paths must be absolute.
Examples:
@ -699,6 +736,8 @@ Examples:
},
Options: []cmds.Option{
cmds.BoolOption("parents", "p", "No error if existing, make parent directories as needed."),
cidVersionOption,
hashOption,
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.InvocContext().GetNode()
@ -716,7 +755,18 @@ Examples:
flush, _, _ := req.Option("flush").Bool()
err = mfs.Mkdir(n.FilesRoot, dirtomake, dashp, flush)
prefix, err := getPrefix(req)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
root := n.FilesRoot
err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
Mkparents: dashp,
Flush: flush,
Prefix: prefix,
})
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
@ -756,6 +806,72 @@ are run with the '--flush=false'.
},
}
var FilesChcidCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Change the cid version or hash function of the root node of a given path.",
ShortDescription: `
Change the cid version or hash function of the root node of a given path.
`,
},
Arguments: []cmds.Argument{
cmds.StringArg("path", false, false, "Path to change. Default: '/'."),
},
Options: []cmds.Option{
cidVersionOption,
hashOption,
},
Run: func(req cmds.Request, res cmds.Response) {
nd, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
path := "/"
if len(req.Arguments()) > 0 {
path = req.Arguments()[0]
}
flush, _, _ := req.Option("flush").Bool()
prefix, err := getPrefix(req)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
err = updatePath(nd.FilesRoot, path, prefix, flush)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
},
}
func updatePath(rt *mfs.Root, pth string, prefix *cid.Prefix, flush bool) error {
if prefix == nil {
return nil
}
nd, err := mfs.Lookup(rt, pth)
if err != nil {
return err
}
switch n := nd.(type) {
case *mfs.Directory:
n.SetPrefix(prefix)
default:
return fmt.Errorf("can only update directories")
}
if flush {
nd.Flush()
}
return nil
}
var FilesRmCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Remove a file.",
@ -860,8 +976,36 @@ Remove files or directories.
},
}
func getFileHandle(r *mfs.Root, path string, create bool) (*mfs.File, error) {
func getPrefix(req cmds.Request) (*cid.Prefix, error) {
cidVer, cidVerSet, _ := req.Option("cid-version").Int()
hashFunStr, hashFunSet, _ := req.Option("hash").String()
if !cidVerSet && !hashFunSet {
return nil, nil
}
if hashFunSet && cidVer == 0 {
cidVer = 1
}
prefix, err := dag.PrefixForCidVersion(cidVer)
if err != nil {
return nil, err
}
if hashFunSet {
hashFunCode, ok := mh.Names[strings.ToLower(hashFunStr)]
if !ok {
return nil, fmt.Errorf("unrecognized hash function: %s", strings.ToLower(hashFunStr))
}
prefix.MhType = hashFunCode
prefix.MhLength = -1
}
return &prefix, nil
}
func getFileHandle(r *mfs.Root, path string, create bool, prefix *cid.Prefix) (*mfs.File, error) {
target, err := mfs.Lookup(r, path)
switch err {
case nil:
@ -887,8 +1031,12 @@ func getFileHandle(r *mfs.Root, path string, create bool) (*mfs.File, error) {
if !ok {
return nil, fmt.Errorf("%s was not a directory", dirname)
}
if prefix == nil {
prefix = pdir.GetPrefix()
}
nd := dag.NodeWithData(ft.FilePBData(nil, 0))
nd.SetPrefix(prefix)
err = pdir.AddChild(fname, nd)
if err != nil {
return nil, err

View File

@ -119,7 +119,6 @@ func (adder *Adder) mfsRoot() (*mfs.Root, error) {
rnode := unixfs.EmptyDirNode()
rnode.SetPrefix(adder.Prefix)
mr, err := mfs.NewRoot(adder.ctx, adder.dagService, rnode, nil)
mr.Prefix = adder.Prefix
if err != nil {
return nil, err
}
@ -398,7 +397,12 @@ func (adder *Adder) addNode(node node.Node, path string) error {
}
dir := gopath.Dir(path)
if dir != "." {
if err := mfs.Mkdir(mr, dir, true, false); err != nil {
opts := mfs.MkdirOpts{
Mkparents: true,
Flush: false,
Prefix: adder.Prefix,
}
if err := mfs.Mkdir(mr, dir, opts); err != nil {
return err
}
}
@ -496,7 +500,11 @@ func (adder *Adder) addDir(dir files.File) error {
if err != nil {
return err
}
err = mfs.Mkdir(mr, dir.FileName(), true, false)
err = mfs.Mkdir(mr, dir.FileName(), mfs.MkdirOpts{
Mkparents: true,
Flush: false,
Prefix: adder.Prefix,
})
if err != nil {
return err
}

View File

@ -120,8 +120,8 @@ func (db *DagBuilderHelper) NewUnixfsNode() *UnixfsNode {
return n
}
// NewUnixfsBlock creates a new Unixfs node to represent a raw data block
func (db *DagBuilderHelper) NewUnixfsBlock() *UnixfsNode {
// newUnixfsBlock creates a new Unixfs node to represent a raw data block
func (db *DagBuilderHelper) newUnixfsBlock() *UnixfsNode {
n := &UnixfsNode{
node: new(dag.ProtoNode),
ufmt: &ft.FSNode{Type: ft.TRaw},
@ -181,7 +181,7 @@ func (db *DagBuilderHelper) GetNextDataNode() (*UnixfsNode, error) {
}, nil
}
} else {
blk := db.NewUnixfsBlock()
blk := db.newUnixfsBlock()
blk.SetData(data)
return blk, nil
}

View File

@ -19,10 +19,23 @@ import (
u "gx/ipfs/QmSU6eubNdhXjFBJBSksTp8kv8YRub8mGAPv8tVJHmL2EU/go-ipfs-util"
)
func buildTestDag(ds merkledag.DAGService, spl chunk.Splitter) (*merkledag.ProtoNode, error) {
type UseRawLeaves bool
const (
ProtoBufLeaves UseRawLeaves = false
RawLeaves UseRawLeaves = true
)
func runBothSubtests(t *testing.T, tfunc func(*testing.T, UseRawLeaves)) {
t.Run("leaves=ProtoBuf", func(t *testing.T) { tfunc(t, ProtoBufLeaves) })
t.Run("leaves=Raw", func(t *testing.T) { tfunc(t, RawLeaves) })
}
func buildTestDag(ds merkledag.DAGService, spl chunk.Splitter, rawLeaves UseRawLeaves) (*merkledag.ProtoNode, error) {
dbp := h.DagBuilderParams{
Dagserv: ds,
Maxlinks: h.DefaultLinksPerBlock,
Dagserv: ds,
Maxlinks: h.DefaultLinksPerBlock,
RawLeaves: bool(rawLeaves),
}
nd, err := TrickleLayout(dbp.New(spl))
@ -35,22 +48,31 @@ func buildTestDag(ds merkledag.DAGService, spl chunk.Splitter) (*merkledag.Proto
return nil, merkledag.ErrNotProtobuf
}
return pbnd, VerifyTrickleDagStructure(pbnd, ds, dbp.Maxlinks, layerRepeat)
return pbnd, VerifyTrickleDagStructure(pbnd, VerifyParams{
Getter: ds,
Direct: dbp.Maxlinks,
LayerRepeat: layerRepeat,
RawLeaves: bool(rawLeaves),
})
}
//Test where calls to read are smaller than the chunk size
func TestSizeBasedSplit(t *testing.T) {
runBothSubtests(t, testSizeBasedSplit)
}
func testSizeBasedSplit(t *testing.T, rawLeaves UseRawLeaves) {
if testing.Short() {
t.SkipNow()
}
bs := chunk.SizeSplitterGen(512)
testFileConsistency(t, bs, 32*512)
testFileConsistency(t, bs, 32*512, rawLeaves)
bs = chunk.SizeSplitterGen(4096)
testFileConsistency(t, bs, 32*4096)
testFileConsistency(t, bs, 32*4096, rawLeaves)
// Uneven offset
testFileConsistency(t, bs, 31*4095)
testFileConsistency(t, bs, 31*4095, rawLeaves)
}
func dup(b []byte) []byte {
@ -59,13 +81,13 @@ func dup(b []byte) []byte {
return o
}
func testFileConsistency(t *testing.T, bs chunk.SplitterGen, nbytes int) {
func testFileConsistency(t *testing.T, bs chunk.SplitterGen, nbytes int, rawLeaves UseRawLeaves) {
should := make([]byte, nbytes)
u.NewTimeSeededRand().Read(should)
read := bytes.NewReader(should)
ds := mdtest.Mock()
nd, err := buildTestDag(ds, bs(read))
nd, err := buildTestDag(ds, bs(read), rawLeaves)
if err != nil {
t.Fatal(err)
}
@ -87,12 +109,16 @@ func testFileConsistency(t *testing.T, bs chunk.SplitterGen, nbytes int) {
}
func TestBuilderConsistency(t *testing.T) {
runBothSubtests(t, testBuilderConsistency)
}
func testBuilderConsistency(t *testing.T, rawLeaves UseRawLeaves) {
nbytes := 100000
buf := new(bytes.Buffer)
io.CopyN(buf, u.NewTimeSeededRand(), int64(nbytes))
should := dup(buf.Bytes())
dagserv := mdtest.Mock()
nd, err := buildTestDag(dagserv, chunk.DefaultSplitter(buf))
nd, err := buildTestDag(dagserv, chunk.DefaultSplitter(buf), rawLeaves)
if err != nil {
t.Fatal(err)
}
@ -125,6 +151,10 @@ func arrComp(a, b []byte) error {
}
func TestIndirectBlocks(t *testing.T) {
runBothSubtests(t, testIndirectBlocks)
}
func testIndirectBlocks(t *testing.T, rawLeaves UseRawLeaves) {
splitter := chunk.SizeSplitterGen(512)
nbytes := 1024 * 1024
buf := make([]byte, nbytes)
@ -133,7 +163,7 @@ func TestIndirectBlocks(t *testing.T) {
read := bytes.NewReader(buf)
ds := mdtest.Mock()
dag, err := buildTestDag(ds, splitter(read))
dag, err := buildTestDag(ds, splitter(read), rawLeaves)
if err != nil {
t.Fatal(err)
}
@ -154,13 +184,17 @@ func TestIndirectBlocks(t *testing.T) {
}
func TestSeekingBasic(t *testing.T) {
runBothSubtests(t, testSeekingBasic)
}
func testSeekingBasic(t *testing.T, rawLeaves UseRawLeaves) {
nbytes := int64(10 * 1024)
should := make([]byte, nbytes)
u.NewTimeSeededRand().Read(should)
read := bytes.NewReader(should)
ds := mdtest.Mock()
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 512))
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 512), rawLeaves)
if err != nil {
t.Fatal(err)
}
@ -191,13 +225,17 @@ func TestSeekingBasic(t *testing.T) {
}
func TestSeekToBegin(t *testing.T) {
runBothSubtests(t, testSeekToBegin)
}
func testSeekToBegin(t *testing.T, rawLeaves UseRawLeaves) {
nbytes := int64(10 * 1024)
should := make([]byte, nbytes)
u.NewTimeSeededRand().Read(should)
read := bytes.NewReader(should)
ds := mdtest.Mock()
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500))
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500), rawLeaves)
if err != nil {
t.Fatal(err)
}
@ -235,13 +273,17 @@ func TestSeekToBegin(t *testing.T) {
}
func TestSeekToAlmostBegin(t *testing.T) {
runBothSubtests(t, testSeekToAlmostBegin)
}
func testSeekToAlmostBegin(t *testing.T, rawLeaves UseRawLeaves) {
nbytes := int64(10 * 1024)
should := make([]byte, nbytes)
u.NewTimeSeededRand().Read(should)
read := bytes.NewReader(should)
ds := mdtest.Mock()
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500))
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500), rawLeaves)
if err != nil {
t.Fatal(err)
}
@ -279,13 +321,17 @@ func TestSeekToAlmostBegin(t *testing.T) {
}
func TestSeekEnd(t *testing.T) {
runBothSubtests(t, testSeekEnd)
}
func testSeekEnd(t *testing.T, rawLeaves UseRawLeaves) {
nbytes := int64(50 * 1024)
should := make([]byte, nbytes)
u.NewTimeSeededRand().Read(should)
read := bytes.NewReader(should)
ds := mdtest.Mock()
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500))
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500), rawLeaves)
if err != nil {
t.Fatal(err)
}
@ -305,13 +351,17 @@ func TestSeekEnd(t *testing.T) {
}
func TestSeekEndSingleBlockFile(t *testing.T) {
runBothSubtests(t, testSeekEndSingleBlockFile)
}
func testSeekEndSingleBlockFile(t *testing.T, rawLeaves UseRawLeaves) {
nbytes := int64(100)
should := make([]byte, nbytes)
u.NewTimeSeededRand().Read(should)
read := bytes.NewReader(should)
ds := mdtest.Mock()
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 5000))
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 5000), rawLeaves)
if err != nil {
t.Fatal(err)
}
@ -331,13 +381,17 @@ func TestSeekEndSingleBlockFile(t *testing.T) {
}
func TestSeekingStress(t *testing.T) {
runBothSubtests(t, testSeekingStress)
}
func testSeekingStress(t *testing.T, rawLeaves UseRawLeaves) {
nbytes := int64(1024 * 1024)
should := make([]byte, nbytes)
u.NewTimeSeededRand().Read(should)
read := bytes.NewReader(should)
ds := mdtest.Mock()
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 1000))
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 1000), rawLeaves)
if err != nil {
t.Fatal(err)
}
@ -376,13 +430,17 @@ func TestSeekingStress(t *testing.T) {
}
func TestSeekingConsistency(t *testing.T) {
runBothSubtests(t, testSeekingConsistency)
}
func testSeekingConsistency(t *testing.T, rawLeaves UseRawLeaves) {
nbytes := int64(128 * 1024)
should := make([]byte, nbytes)
u.NewTimeSeededRand().Read(should)
read := bytes.NewReader(should)
ds := mdtest.Mock()
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500))
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500), rawLeaves)
if err != nil {
t.Fatal(err)
}
@ -419,6 +477,10 @@ func TestSeekingConsistency(t *testing.T) {
}
func TestAppend(t *testing.T) {
runBothSubtests(t, testAppend)
}
func testAppend(t *testing.T, rawLeaves UseRawLeaves) {
nbytes := int64(128 * 1024)
should := make([]byte, nbytes)
u.NewTimeSeededRand().Read(should)
@ -426,14 +488,15 @@ func TestAppend(t *testing.T) {
// Reader for half the bytes
read := bytes.NewReader(should[:nbytes/2])
ds := mdtest.Mock()
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500))
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500), rawLeaves)
if err != nil {
t.Fatal(err)
}
dbp := &h.DagBuilderParams{
Dagserv: ds,
Maxlinks: h.DefaultLinksPerBlock,
Dagserv: ds,
Maxlinks: h.DefaultLinksPerBlock,
RawLeaves: bool(rawLeaves),
}
r := bytes.NewReader(should[nbytes/2:])
@ -444,7 +507,12 @@ func TestAppend(t *testing.T) {
t.Fatal(err)
}
err = VerifyTrickleDagStructure(nnode, ds, dbp.Maxlinks, layerRepeat)
err = VerifyTrickleDagStructure(nnode, VerifyParams{
Getter: ds,
Direct: dbp.Maxlinks,
LayerRepeat: layerRepeat,
RawLeaves: bool(rawLeaves),
})
if err != nil {
t.Fatal(err)
}
@ -467,6 +535,10 @@ func TestAppend(t *testing.T) {
// This test appends one byte at a time to an empty file
func TestMultipleAppends(t *testing.T) {
runBothSubtests(t, testMultipleAppends)
}
func testMultipleAppends(t *testing.T, rawLeaves UseRawLeaves) {
ds := mdtest.Mock()
// TODO: fix small size appends and make this number bigger
@ -475,14 +547,15 @@ func TestMultipleAppends(t *testing.T) {
u.NewTimeSeededRand().Read(should)
read := bytes.NewReader(nil)
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500))
nd, err := buildTestDag(ds, chunk.NewSizeSplitter(read, 500), rawLeaves)
if err != nil {
t.Fatal(err)
}
dbp := &h.DagBuilderParams{
Dagserv: ds,
Maxlinks: 4,
Dagserv: ds,
Maxlinks: 4,
RawLeaves: bool(rawLeaves),
}
spl := chunk.SizeSplitterGen(500)
@ -495,7 +568,12 @@ func TestMultipleAppends(t *testing.T) {
t.Fatal(err)
}
err = VerifyTrickleDagStructure(nnode, ds, dbp.Maxlinks, layerRepeat)
err = VerifyTrickleDagStructure(nnode, VerifyParams{
Getter: ds,
Direct: dbp.Maxlinks,
LayerRepeat: layerRepeat,
RawLeaves: bool(rawLeaves),
})
if err != nil {
t.Fatal(err)
}

View File

@ -9,6 +9,7 @@ import (
dag "github.com/ipfs/go-ipfs/merkledag"
ft "github.com/ipfs/go-ipfs/unixfs"
cid "gx/ipfs/QmNp85zy9RLrQ5oQD4hPyS39ezrrXpcaa7R4Y9kxdWQLLQ/go-cid"
node "gx/ipfs/QmPN7cwmpcc4DWXb4KTB9dNAJgjuPY69h3npsMfhRrQL9c/go-ipld-format"
)
@ -234,36 +235,78 @@ func trickleDepthInfo(node *h.UnixfsNode, maxlinks int) (int, int) {
return ((n - maxlinks) / layerRepeat) + 1, (n - maxlinks) % layerRepeat
}
// VerifyParams is used by VerifyTrickleDagStructure
type VerifyParams struct {
Getter node.NodeGetter
Direct int
LayerRepeat int
Prefix *cid.Prefix
RawLeaves bool
}
// VerifyTrickleDagStructure checks that the given dag matches exactly the trickle dag datastructure
// layout
func VerifyTrickleDagStructure(nd node.Node, ds dag.DAGService, direct int, layerRepeat int) error {
pbnd, ok := nd.(*dag.ProtoNode)
if !ok {
return dag.ErrNotProtobuf
}
return verifyTDagRec(pbnd, -1, direct, layerRepeat, ds)
func VerifyTrickleDagStructure(nd node.Node, p VerifyParams) error {
return verifyTDagRec(nd, -1, p)
}
// Recursive call for verifying the structure of a trickledag
func verifyTDagRec(nd *dag.ProtoNode, depth, direct, layerRepeat int, ds dag.DAGService) error {
func verifyTDagRec(n node.Node, depth int, p VerifyParams) error {
codec := cid.DagProtobuf
if depth == 0 {
// zero depth dag is raw data block
if len(nd.Links()) > 0 {
if len(n.Links()) > 0 {
return errors.New("expected direct block")
}
// zero depth dag is raw data block
switch nd := n.(type) {
case *dag.ProtoNode:
pbn, err := ft.FromBytes(nd.Data())
if err != nil {
return err
}
pbn, err := ft.FromBytes(nd.Data())
if err != nil {
return err
}
if pbn.GetType() != ft.TRaw {
return errors.New("Expected raw block")
}
if pbn.GetType() != ft.TRaw {
return errors.New("Expected raw block")
if p.RawLeaves {
return errors.New("expected raw leaf, got a protobuf node")
}
case *dag.RawNode:
if !p.RawLeaves {
return errors.New("expected protobuf node as leaf")
}
codec = cid.Raw
default:
return errors.New("expected ProtoNode or RawNode")
}
}
// verify prefix
if p.Prefix != nil {
prefix := n.Cid().Prefix()
expect := *p.Prefix // make a copy
expect.Codec = uint64(codec)
if codec == cid.Raw && expect.Version == 0 {
expect.Version = 1
}
if expect.MhLength == -1 {
expect.MhLength = prefix.MhLength
}
if prefix != expect {
return fmt.Errorf("unexpected cid prefix: expected: %v; got %v", expect, prefix)
}
}
if depth == 0 {
return nil
}
nd, ok := n.(*dag.ProtoNode)
if !ok {
return errors.New("expected ProtoNode")
}
// Verify this is a branch node
pbn, err := ft.FromBytes(nd.Data())
if err != nil {
@ -279,29 +322,24 @@ func verifyTDagRec(nd *dag.ProtoNode, depth, direct, layerRepeat int, ds dag.DAG
}
for i := 0; i < len(nd.Links()); i++ {
childi, err := nd.Links()[i].GetNode(context.TODO(), ds)
child, err := nd.Links()[i].GetNode(context.TODO(), p.Getter)
if err != nil {
return err
}
childpb, ok := childi.(*dag.ProtoNode)
if !ok {
return fmt.Errorf("cannot operate on non-protobuf nodes")
}
if i < direct {
if i < p.Direct {
// Direct blocks
err := verifyTDagRec(childpb, 0, direct, layerRepeat, ds)
err := verifyTDagRec(child, 0, p)
if err != nil {
return err
}
} else {
// Recursive trickle dags
rdepth := ((i - direct) / layerRepeat) + 1
rdepth := ((i - p.Direct) / p.LayerRepeat) + 1
if rdepth >= depth && depth > 0 {
return errors.New("Child dag was too deep!")
}
err := verifyTDagRec(childpb, rdepth, direct, layerRepeat, ds)
err := verifyTDagRec(child, rdepth, p)
if err != nil {
return err
}

View File

@ -42,6 +42,12 @@ var v1CidPrefix = cid.Prefix{
Version: 1,
}
// V0CidPrefix returns a prefix for CIDv0
func V0CidPrefix() cid.Prefix { return v0CidPrefix }
// V1CidPrefix returns a prefix for CIDv1 with the default settings
func V1CidPrefix() cid.Prefix { return v1CidPrefix }
// PrefixForCidVersion returns the Protobuf prefix for a given CID version
func PrefixForCidVersion(version int) (cid.Prefix, error) {
switch version {

View File

@ -58,6 +58,11 @@ func NewDirectory(ctx context.Context, name string, node node.Node, parent child
}, nil
}
// GetPrefix gets the CID prefix of the root node
func (d *Directory) GetPrefix() *cid.Prefix {
return d.dirbuilder.GetPrefix()
}
// SetPrefix sets the CID prefix
func (d *Directory) SetPrefix(prefix *cid.Prefix) {
d.dirbuilder.SetPrefix(prefix)
@ -299,6 +304,7 @@ func (d *Directory) Mkdir(name string) (*Directory, error) {
}
ndir := ft.EmptyDirNode()
ndir.SetPrefix(d.GetPrefix())
_, err = d.dserv.Add(ndir)
if err != nil {

View File

@ -23,16 +23,23 @@ type File struct {
dserv dag.DAGService
node node.Node
nodelk sync.Mutex
RawLeaves bool
}
// NewFile returns a NewFile object with the given parameters
// NewFile returns a NewFile object with the given parameters. If the
// Cid version is non-zero RawLeaves will be enabled.
func NewFile(name string, node node.Node, parent childCloser, dserv dag.DAGService) (*File, error) {
return &File{
fi := &File{
dserv: dserv,
parent: parent,
name: name,
node: node,
}, nil
}
if node.Cid().Prefix().Version > 0 {
fi.RawLeaves = true
}
return fi, nil
}
const (
@ -79,6 +86,7 @@ func (fi *File) Open(flags int, sync bool) (FileDescriptor, error) {
if err != nil {
return nil, err
}
dmod.RawLeaves = fi.RawLeaves
return &fileDescriptor{
inode: fi,

View File

@ -735,7 +735,7 @@ func TestMfsHugeDir(t *testing.T) {
_, rt := setupRoot(ctx, t)
for i := 0; i < 10000; i++ {
err := Mkdir(rt, fmt.Sprintf("/dir%d", i), false, false)
err := Mkdir(rt, fmt.Sprintf("/dir%d", i), MkdirOpts{Mkparents: false, Flush: false})
if err != nil {
t.Fatal(err)
}
@ -747,7 +747,7 @@ func TestMkdirP(t *testing.T) {
defer cancel()
_, rt := setupRoot(ctx, t)
err := Mkdir(rt, "/a/b/c/d/e/f", true, true)
err := Mkdir(rt, "/a/b/c/d/e/f", MkdirOpts{Mkparents: true, Flush: true})
if err != nil {
t.Fatal(err)
}

View File

@ -9,6 +9,7 @@ import (
path "github.com/ipfs/go-ipfs/path"
cid "gx/ipfs/QmNp85zy9RLrQ5oQD4hPyS39ezrrXpcaa7R4Y9kxdWQLLQ/go-cid"
node "gx/ipfs/QmPN7cwmpcc4DWXb4KTB9dNAJgjuPY69h3npsMfhRrQL9c/go-ipld-format"
)
@ -97,9 +98,16 @@ func PutNode(r *Root, path string, nd node.Node) error {
return pdir.AddChild(filename, nd)
}
// MkdirOpts is used by Mkdir
type MkdirOpts struct {
Mkparents bool
Flush bool
Prefix *cid.Prefix
}
// Mkdir creates a directory at 'path' under the directory 'd', creating
// intermediary directories as needed if 'mkparents' is set to true
func Mkdir(r *Root, pth string, mkparents bool, flush bool) error {
func Mkdir(r *Root, pth string, opts MkdirOpts) error {
if pth == "" {
return fmt.Errorf("no path given to Mkdir")
}
@ -115,7 +123,7 @@ func Mkdir(r *Root, pth string, mkparents bool, flush bool) error {
if len(parts) == 0 {
// this will only happen on 'mkdir /'
if mkparents {
if opts.Mkparents {
return nil
}
return fmt.Errorf("cannot create directory '/': Already exists")
@ -124,12 +132,14 @@ func Mkdir(r *Root, pth string, mkparents bool, flush bool) error {
cur := r.GetValue().(*Directory)
for i, d := range parts[:len(parts)-1] {
fsn, err := cur.Child(d)
if err == os.ErrNotExist && mkparents {
if err == os.ErrNotExist && opts.Mkparents {
mkd, err := cur.Mkdir(d)
if err != nil {
return err
}
mkd.SetPrefix(r.Prefix)
if opts.Prefix != nil {
mkd.SetPrefix(opts.Prefix)
}
fsn = mkd
} else if err != nil {
return err
@ -144,13 +154,15 @@ func Mkdir(r *Root, pth string, mkparents bool, flush bool) error {
final, err := cur.Mkdir(parts[len(parts)-1])
if err != nil {
if !mkparents || err != os.ErrExist || final == nil {
if !opts.Mkparents || err != os.ErrExist || final == nil {
return err
}
}
final.SetPrefix(r.Prefix)
if opts.Prefix != nil {
final.SetPrefix(opts.Prefix)
}
if flush {
if opts.Flush {
err := final.Flush()
if err != nil {
return err

View File

@ -61,9 +61,6 @@ type Root struct {
dserv dag.DAGService
Type string
// Prefix to use for any children created
Prefix *cid.Prefix
}
type PubFunc func(context.Context, *cid.Cid) error

View File

@ -46,11 +46,15 @@ verify_dir_contents() {
}
test_sharding() {
test_expect_success "make a directory" '
ipfs files mkdir /foo
local EXTRA ARGS
EXTRA=$1
ARGS=$2 # only applied to the initial directory
test_expect_success "make a directory $EXTRA" '
ipfs files mkdir $ARGS /foo
'
test_expect_success "can make 100 files in a directory" '
test_expect_success "can make 100 files in a directory $EXTRA" '
printf "" > list_exp_raw
for i in `seq 100`
do
@ -59,144 +63,164 @@ test_sharding() {
done
'
test_expect_success "listing works" '
test_expect_success "listing works $EXTRA" '
ipfs files ls /foo |sort > list_out &&
sort list_exp_raw > list_exp &&
test_cmp list_exp list_out
'
test_expect_success "can read a file from sharded directory" '
test_expect_success "can read a file from sharded directory $EXTRA" '
ipfs files read /foo/file65 > file_out &&
echo "65" > file_exp &&
test_cmp file_out file_exp
'
test_expect_success "can pin a file from sharded directory" '
test_expect_success "can pin a file from sharded directory $EXTRA" '
ipfs files stat --hash /foo/file42 > pin_file_hash &&
ipfs pin add < pin_file_hash > pin_hash
'
test_expect_success "can unpin a file from sharded directory" '
test_expect_success "can unpin a file from sharded directory $EXTRA" '
read -r _ HASH _ < pin_hash &&
ipfs pin rm $HASH
'
test_expect_success "output object was really sharded" '
test_expect_success "output object was really sharded and has correct hash $EXTRA" '
ipfs files stat --hash /foo > expected_foo_hash &&
echo QmPkwLJTYZRGPJ8Lazr9qPdrLmswPtUjaDbEpmR9jEh1se > actual_foo_hash &&
echo $SHARD_HASH > actual_foo_hash &&
test_cmp expected_foo_hash actual_foo_hash
'
test_expect_success "clean up $EXTRA" '
ipfs files rm -r /foo
'
}
test_files_api() {
ROOT_HASH=$1
local EXTRA ARGS RAW_LEAVES
EXTRA=$1
ARGS=$2
RAW_LEAVES=$3
test_expect_success "can mkdir in root" '
ipfs files mkdir /cats
test_expect_success "can mkdir in root $EXTRA" '
ipfs files mkdir $ARGS /cats
'
test_expect_success "'files ls' lists root by default" '
test_expect_success "'files ls' lists root by default $EXTRA" '
ipfs files ls >actual &&
echo "cats" >expected &&
test_cmp expected actual
'
test_expect_success "directory was created" '
test_expect_success "directory was created $EXTRA" '
verify_path_exists /cats
'
test_expect_success "directory is empty" '
test_expect_success "directory is empty $EXTRA" '
verify_dir_contents /cats
'
# we do verification of stat formatting now as we depend on it
test_expect_success "stat works" '
test_expect_success "stat works $EXTRA" '
ipfs files stat / >stat
'
test_expect_success "hash is first line of stat" '
test_expect_success "hash is first line of stat $EXTRA" '
ipfs ls $(head -1 stat) | grep "cats"
'
test_expect_success "stat --hash gives only hash" '
test_expect_success "stat --hash gives only hash $EXTRA" '
ipfs files stat --hash / >actual &&
head -n1 stat >expected &&
test_cmp expected actual
'
test_expect_success "stat with multiple format options should fail" '
test_expect_success "stat with multiple format options should fail $EXTRA" '
test_must_fail ipfs files stat --hash --size /
'
test_expect_success "compare hash option with format" '
test_expect_success "compare hash option with format $EXTRA" '
ipfs files stat --hash / >expected &&
ipfs files stat --format='"'"'<hash>'"'"' / >actual &&
test_cmp expected actual
'
test_expect_success "compare size option with format" '
test_expect_success "compare size option with format $EXTRA" '
ipfs files stat --size / >expected &&
ipfs files stat --format='"'"'<cumulsize>'"'"' / >actual &&
test_cmp expected actual
'
test_expect_success "check root hash" '
test_expect_success "check root hash $EXTRA" '
ipfs files stat --hash / > roothash
'
test_expect_success "cannot mkdir /" '
test_expect_code 1 ipfs files mkdir /
test_expect_success "cannot mkdir / $EXTRA" '
test_expect_code 1 ipfs files mkdir $ARGS /
'
test_expect_success "check root hash was not changed" '
test_expect_success "check root hash was not changed $EXTRA" '
ipfs files stat --hash / > roothashafter &&
test_cmp roothash roothashafter
'
test_expect_success "can put files into directory" '
test_expect_success "can put files into directory $EXTRA" '
ipfs files cp /ipfs/$FILE1 /cats/file1
'
test_expect_success "file shows up in directory" '
test_expect_success "file shows up in directory $EXTRA" '
verify_dir_contents /cats file1
'
test_expect_success "file has correct hash and size in directory" '
test_expect_success "file has correct hash and size in directory $EXTRA" '
echo "file1 $FILE1 4" > ls_l_expected &&
ipfs files ls -l /cats > ls_l_actual &&
test_cmp ls_l_expected ls_l_actual
'
test_expect_success "can read file" '
test_expect_success "can stat file $EXTRA" '
ipfs files stat /cats/file1 > file1stat_orig
'
test_expect_success "stat output looks good" '
grep -v CumulativeSize: file1stat_orig > file1stat_actual &&
echo "$FILE1" > file1stat_expect &&
echo "Size: 4" >> file1stat_expect &&
echo "ChildBlocks: 0" >> file1stat_expect &&
echo "Type: file" >> file1stat_expect &&
test_cmp file1stat_expect file1stat_actual
'
test_expect_success "can read file $EXTRA" '
ipfs files read /cats/file1 > file1out
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
echo foo > expected &&
test_cmp expected file1out
'
test_expect_success "can put another file into root" '
test_expect_success "can put another file into root $EXTRA" '
ipfs files cp /ipfs/$FILE2 /file2
'
test_expect_success "file shows up in root" '
test_expect_success "file shows up in root $EXTRA" '
verify_dir_contents / file2 cats
'
test_expect_success "can read file" '
test_expect_success "can read file $EXTRA" '
ipfs files read /file2 > file2out
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
echo bar > expected &&
test_cmp expected file2out
'
test_expect_success "can make deep directory" '
ipfs files mkdir -p /cats/this/is/a/dir
test_expect_success "can make deep directory $EXTRA" '
ipfs files mkdir $ARGS -p /cats/this/is/a/dir
'
test_expect_success "directory was created correctly" '
test_expect_success "directory was created correctly $EXTRA" '
verify_path_exists /cats/this/is/a/dir &&
verify_dir_contents /cats this file1 &&
verify_dir_contents /cats/this is &&
@ -205,362 +229,435 @@ test_files_api() {
verify_dir_contents /cats/this/is/a/dir
'
test_expect_success "can copy file into new dir" '
test_expect_success "can copy file into new dir $EXTRA" '
ipfs files cp /ipfs/$FILE3 /cats/this/is/a/dir/file3
'
test_expect_success "can read file" '
test_expect_success "can read file $EXTRA" '
ipfs files read /cats/this/is/a/dir/file3 > output
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
echo baz > expected &&
test_cmp expected output
'
test_expect_success "file shows up in dir" '
test_expect_success "file shows up in dir $EXTRA" '
verify_dir_contents /cats/this/is/a/dir file3
'
test_expect_success "can remove file" '
test_expect_success "can remove file $EXTRA" '
ipfs files rm /cats/this/is/a/dir/file3
'
test_expect_success "file no longer appears" '
test_expect_success "file no longer appears $EXTRA" '
verify_dir_contents /cats/this/is/a/dir
'
test_expect_success "can remove dir" '
test_expect_success "can remove dir $EXTRA" '
ipfs files rm -r /cats/this/is/a/dir
'
test_expect_success "dir no longer appears" '
test_expect_success "dir no longer appears $EXTRA" '
verify_dir_contents /cats/this/is/a
'
test_expect_success "can remove file from root" '
test_expect_success "can remove file from root $EXTRA" '
ipfs files rm /file2
'
test_expect_success "file no longer appears" '
test_expect_success "file no longer appears $EXTRA" '
verify_dir_contents / cats
'
test_expect_success "check root hash" '
test_expect_success "check root hash $EXTRA" '
ipfs files stat --hash / > roothash
'
test_expect_success "cannot remove root" '
test_expect_success "cannot remove root $EXTRA" '
test_expect_code 1 ipfs files rm -r /
'
test_expect_success "check root hash was not changed" '
test_expect_success "check root hash was not changed $EXTRA" '
ipfs files stat --hash / > roothashafter &&
test_cmp roothash roothashafter
'
# test read options
test_expect_success "read from offset works" '
test_expect_success "read from offset works $EXTRA" '
ipfs files read -o 1 /cats/file1 > output
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
echo oo > expected &&
test_cmp expected output
'
test_expect_success "read with size works" '
test_expect_success "read with size works $EXTRA" '
ipfs files read -n 2 /cats/file1 > output
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
printf fo > expected &&
test_cmp expected output
'
test_expect_success "cannot read from negative offset" '
test_expect_success "cannot read from negative offset $EXTRA" '
test_expect_code 1 ipfs files read --offset -3 /cats/file1
'
test_expect_success "read from offset 0 works" '
test_expect_success "read from offset 0 works $EXTRA" '
ipfs files read --offset 0 /cats/file1 > output
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
echo foo > expected &&
test_cmp expected output
'
test_expect_success "read last byte works" '
test_expect_success "read last byte works $EXTRA" '
ipfs files read --offset 2 /cats/file1 > output
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
echo o > expected &&
test_cmp expected output
'
test_expect_success "offset past end of file fails" '
test_expect_success "offset past end of file fails $EXTRA" '
test_expect_code 1 ipfs files read --offset 5 /cats/file1
'
test_expect_success "cannot read negative count bytes" '
test_expect_success "cannot read negative count bytes $EXTRA" '
test_expect_code 1 ipfs read --count -1 /cats/file1
'
test_expect_success "reading zero bytes prints nothing" '
test_expect_success "reading zero bytes prints nothing $EXTRA" '
ipfs files read --count 0 /cats/file1 > output
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
printf "" > expected &&
test_cmp expected output
'
test_expect_success "count > len(file) prints entire file" '
test_expect_success "count > len(file) prints entire file $EXTRA" '
ipfs files read --count 200 /cats/file1 > output
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
echo foo > expected &&
test_cmp expected output
'
# test write
test_expect_success "can write file" '
test_expect_success "can write file $EXTRA" '
echo "ipfs rocks" > tmpfile &&
cat tmpfile | ipfs files write --create /cats/ipfs
cat tmpfile | ipfs files write $ARGS $RAW_LEAVES --create /cats/ipfs
'
test_expect_success "file was created" '
test_expect_success "file was created $EXTRA" '
verify_dir_contents /cats ipfs file1 this
'
test_expect_success "can read file we just wrote" '
test_expect_success "can read file we just wrote $EXTRA" '
ipfs files read /cats/ipfs > output
'
test_expect_success "can write to offset" '
echo "is super cool" | ipfs files write -o 5 /cats/ipfs
test_expect_success "can write to offset $EXTRA" '
echo "is super cool" | ipfs files write $ARGS $RAW_LEAVES -o 5 /cats/ipfs
'
test_expect_success "file looks correct" '
test_expect_success "file looks correct $EXTRA" '
echo "ipfs is super cool" > expected &&
ipfs files read /cats/ipfs > output &&
test_cmp expected output
'
test_expect_success "cant write to negative offset" '
test_expect_success "file hash correct $EXTRA" '
echo $FILE_HASH > filehash_expected &&
ipfs files stat --hash /cats/ipfs > filehash &&
test_expect_code 1 ipfs files write --offset -1 /cats/ipfs < output
test_cmp filehash_expected filehash
'
test_expect_success "verify file was not changed" '
test_expect_success "cant write to negative offset $EXTRA" '
test_expect_code 1 ipfs files write $ARGS $RAW_LEAVES --offset -1 /cats/ipfs < output
'
test_expect_success "verify file was not changed $EXTRA" '
ipfs files stat --hash /cats/ipfs > afterhash &&
test_cmp filehash afterhash
'
test_expect_success "write new file for testing" '
echo foobar | ipfs files write --create /fun
test_expect_success "write new file for testing $EXTRA" '
echo foobar | ipfs files write $ARGS $RAW_LEAVES --create /fun
'
test_expect_success "write to offset past end works" '
echo blah | ipfs files write --offset 50 /fun
test_expect_success "write to offset past end works $EXTRA" '
echo blah | ipfs files write $ARGS $RAW_LEAVES --offset 50 /fun
'
test_expect_success "can read file" '
test_expect_success "can read file $EXTRA" '
ipfs files read /fun > sparse_output
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
echo foobar > sparse_expected &&
echo blah | dd of=sparse_expected bs=50 seek=1 &&
test_cmp sparse_expected sparse_output
'
test_expect_success "cleanup" '
test_expect_success "cleanup $EXTRA" '
ipfs files rm /fun
'
test_expect_success "cannot write to directory" '
test_expect_success "cannot write to directory $EXTRA" '
ipfs files stat --hash /cats > dirhash &&
test_expect_code 1 ipfs files write /cats < output
test_expect_code 1 ipfs files write $ARGS $RAW_LEAVES /cats < output
'
test_expect_success "verify dir was not changed" '
test_expect_success "verify dir was not changed $EXTRA" '
ipfs files stat --hash /cats > afterdirhash &&
test_cmp dirhash afterdirhash
'
test_expect_success "cannot write to nonexistant path" '
test_expect_code 1 ipfs files write /cats/bar/ < output
test_expect_success "cannot write to nonexistant path $EXTRA" '
test_expect_code 1 ipfs files write $ARGS $RAW_LEAVES /cats/bar/ < output
'
test_expect_success "no new paths were created" '
test_expect_success "no new paths were created $EXTRA" '
verify_dir_contents /cats file1 ipfs this
'
test_expect_success "write 'no-flush' succeeds" '
echo "testing" | ipfs files write -f=false -e /cats/walrus
test_expect_success "write 'no-flush' succeeds $EXTRA" '
echo "testing" | ipfs files write $ARGS $RAW_LEAVES -f=false -e /cats/walrus
'
test_expect_success "root hash not bubbled up yet" '
test_expect_success "root hash not bubbled up yet $EXTRA" '
test -z "$ONLINE" ||
(ipfs refs local > refsout &&
test_expect_code 1 grep $ROOT_HASH refsout)
'
test_expect_success "changes bubbled up to root on inspection" '
test_expect_success "changes bubbled up to root on inspection $EXTRA" '
ipfs files stat --hash / > root_hash
'
test_expect_success "root hash looks good" '
test_expect_success "root hash looks good $EXTRA" '
export EXP_ROOT_HASH="$ROOT_HASH" &&
echo $EXP_ROOT_HASH > root_hash_exp &&
test_cmp root_hash_exp root_hash
'
test_expect_success "flush root succeeds" '
test_expect_success "/cats hash looks good $EXTRA" '
export EXP_CATS_HASH="$CATS_HASH" &&
echo $EXP_CATS_HASH > cats_hash_exp &&
ipfs files stat --hash /cats > cats_hash
test_cmp cats_hash_exp cats_hash
'
test_expect_success "flush root succeeds $EXTRA" '
ipfs files flush /
'
# test mv
test_expect_success "can mv dir" '
test_expect_success "can mv dir $EXTRA" '
ipfs files mv /cats/this/is /cats/
'
test_expect_success "mv worked" '
test_expect_success "mv worked $EXTRA" '
verify_dir_contents /cats file1 ipfs this is walrus &&
verify_dir_contents /cats/this
'
test_expect_success "cleanup, remove 'cats'" '
test_expect_success "cleanup, remove 'cats' $EXTRA" '
ipfs files rm -r /cats
'
test_expect_success "cleanup looks good" '
test_expect_success "cleanup looks good $EXTRA" '
verify_dir_contents /
'
# test truncating
test_expect_success "create a new file" '
echo "some content" | ipfs files write --create /cats
test_expect_success "create a new file $EXTRA" '
echo "some content" | ipfs files write $ARGS $RAW_LEAVES --create /cats
'
test_expect_success "truncate and write over that file" '
echo "fish" | ipfs files write --truncate /cats
test_expect_success "truncate and write over that file $EXTRA" '
echo "fish" | ipfs files write $ARGS $RAW_LEAVES --truncate /cats
'
test_expect_success "output looks good" '
test_expect_success "output looks good $EXTRA" '
ipfs files read /cats > file_out &&
echo "fish" > file_exp &&
test_cmp file_out file_exp
'
test_expect_success "cleanup" '
test_expect_success "file hash correct $EXTRA" '
echo $TRUNC_HASH > filehash_expected &&
ipfs files stat --hash /cats > filehash &&
test_cmp filehash_expected filehash
'
test_expect_success "cleanup $EXTRA" '
ipfs files rm /cats
'
# test flush flags
test_expect_success "mkdir --flush works" '
ipfs files mkdir --flush --parents /flushed/deep
test_expect_success "mkdir --flush works $EXTRA" '
ipfs files mkdir $ARGS --flush --parents /flushed/deep
'
test_expect_success "mkdir --flush works a second time" '
ipfs files mkdir --flush --parents /flushed/deep
test_expect_success "mkdir --flush works a second time $EXTRA" '
ipfs files mkdir $ARGS --flush --parents /flushed/deep
'
test_expect_success "dir looks right" '
test_expect_success "dir looks right $EXTRA" '
verify_dir_contents / flushed
'
test_expect_success "child dir looks right" '
test_expect_success "child dir looks right $EXTRA" '
verify_dir_contents /flushed deep
'
test_expect_success "cleanup" '
test_expect_success "cleanup $EXTRA" '
ipfs files rm -r /flushed
'
test_expect_success "child dir looks right" '
test_expect_success "child dir looks right $EXTRA" '
verify_dir_contents /
'
# test for https://github.com/ipfs/go-ipfs/issues/2654
test_expect_success "create and remove dir" '
ipfs files mkdir /test_dir &&
test_expect_success "create and remove dir $EXTRA" '
ipfs files mkdir $ARGS /test_dir &&
ipfs files rm -r "/test_dir"
'
test_expect_success "create test file" '
echo "content" | ipfs files write -e "/test_file"
test_expect_success "create test file $EXTRA" '
echo "content" | ipfs files write $ARGS $RAW_LEAVES -e "/test_file"
'
test_expect_success "copy test file onto test dir" '
test_expect_success "copy test file onto test dir $EXTRA" '
ipfs files cp "/test_file" "/test_dir"
'
test_expect_success "test /test_dir" '
test_expect_success "test /test_dir $EXTRA" '
ipfs files stat "/test_dir" | grep -q "^Type: file"
'
test_expect_success "clean up /test_dir and /test_file" '
test_expect_success "clean up /test_dir and /test_file $EXTRA" '
ipfs files rm -r /test_dir &&
ipfs files rm -r /test_file
'
test_expect_success "make a directory and a file" '
ipfs files mkdir /adir &&
echo "blah" | ipfs files write --create /foobar
test_expect_success "make a directory and a file $EXTRA" '
ipfs files mkdir $ARGS /adir &&
echo "blah" | ipfs files write $ARGS $RAW_LEAVES --create /foobar
'
test_expect_success "copy a file into a directory" '
test_expect_success "copy a file into a directory $EXTRA" '
ipfs files cp /foobar /adir/
'
test_expect_success "file made it into directory" '
test_expect_success "file made it into directory $EXTRA" '
ipfs files ls /adir | grep foobar
'
test_expect_success "clean up" '
test_expect_success "clean up $EXTRA" '
ipfs files rm -r /foobar &&
ipfs files rm -r /adir
'
test_expect_success "root mfs entry is empty" '
test_expect_success "root mfs entry is empty $EXTRA" '
verify_dir_contents /
'
test_expect_success "repo gc" '
test_expect_success "repo gc $EXTRA" '
ipfs repo gc
'
}
# test offline and online
test_expect_success "can create some files for testing" '
create_files
'
test_files_api QmcwKfTMCT7AaeiD92hWjnZn9b6eh9NxnhfSzN5x2vnDpt
test_expect_success "can create some files for testing with raw-leaves" '
create_files --raw-leaves
'
test_files_api QmTpKiKcAj4sbeesN6vrs5w3QeVmd4QmGpxRL81hHut4dZ
tests_for_files_api() {
local EXTRA
EXTRA=$1
test_expect_success "can create some files for testing ($EXTRA)" '
create_files
'
ROOT_HASH=QmcwKfTMCT7AaeiD92hWjnZn9b6eh9NxnhfSzN5x2vnDpt
CATS_HASH=Qma88m8ErTGkZHbBWGqy1C7VmEmX8wwNDWNpGyCaNmEgwC
FILE_HASH=QmQdQt9qooenjeaNhiKHF3hBvmNteB4MQBtgu3jxgf9c7i
TRUNC_HASH=QmdaQZbLwK5ykweGdCVovNnvBom7QhikovDUVqTPHQG4L8
test_files_api "($EXTRA)"
test_expect_success "can create some files for testing with raw-leaves ($EXTRA)" '
create_files --raw-leaves
'
if [ "$EXTRA" = "offline" ]; then
ROOT_HASH=QmTpKiKcAj4sbeesN6vrs5w3QeVmd4QmGpxRL81hHut4dZ
CATS_HASH=QmPhPkmtUGGi8ySPHoPu1qbfryLJKKq1GYxpgLyyCruvGe
test_files_api "($EXTRA, partial raw-leaves)"
fi
ROOT_HASH=QmW3dMSU6VNd1mEdpk9S3ZYRuR1YwwoXjGaZhkyK6ru9YU
CATS_HASH=QmPqWDEg7NoWRX8Y4vvYjZtmdg5umbfsTQ9zwNr12JoLmt
FILE_HASH=QmRCgHeoKxCqK2Es6M6nPUDVWz19yNQPnsXGsXeuTkSKpN
TRUNC_HASH=QmRFJEKWF5A5FyFYZgNhusLw2UziW9zBKYr4huyHjzcB6o
test_files_api "($EXTRA, raw-leaves)" '' --raw-leaves
ROOT_HASH=QmageRWxC7wWjPv5p36NeAgBAiFdBHaNfxAehBSwzNech2
CATS_HASH=zdj7WkEzPLNAr5TYJSQC8CFcBjLvWFfGdx6kaBrJXnBguwWeX
FILE_HASH=zdj7WYHvf5sBRgSBjYnq64QFr449CCbgupXfBvoYL3aHC1DzJ
TRUNC_HASH=zdj7WYLYbka6Ydg8gZUJRLKnFBVehCADhQKBsFbNiMxZSB5Gj
if [ "$EXTRA" = "offline" ]; then
test_files_api "($EXTRA, cidv1)" --cid-version=1
fi
test_expect_success "can update root hash to cidv1" '
ipfs files chcid --cid-version=1 / &&
echo zdj7WbTaiJT1fgatdet9Ei9iDB5hdCxkbVyhyh8YTUnXMiwYi > hash_expect &&
ipfs files stat --hash / > hash_actual &&
test_cmp hash_expect hash_actual
'
ROOT_HASH=zdj7Whmtnx23bR7c7E1Yn3zWYWjnvT4tpzWYGaBMyqcopDWrx
test_files_api "($EXTRA, cidv1 root)"
if [ "$EXTRA" = "offline" ]; then
test_expect_success "can update root hash to blake2b-256" '
ipfs files chcid --hash=blake2b-256 / &&
echo zDMZof1kvswQMT8txrmnb3JGBuna6qXCTry6hSifrkZEd6VmHbBm > hash_expect &&
ipfs files stat --hash / > hash_actual &&
test_cmp hash_expect hash_actual
'
ROOT_HASH=zDMZof1kxEsAwSgCZsGQRVcHCMtHLjkUQoiZUbZ87erpPQJGUeW8
CATS_HASH=zDMZof1kuAhr3zBkxq48V7o9HJZCTVyu1Wd9wnZtVcPJLW8xnGft
FILE_HASH=zDMZof1kxbB9CvxgRioBzESbGnZUxtSCsZ18H1EUkxDdWt1DYEkK
TRUNC_HASH=zDMZof1kxXqKdVsVo231qVdN3hCTF5a34UuQZpzmm5K7CbRJ4u2S
test_files_api "($EXTRA, blake2b-256 root)"
fi
test_expect_success "can update root hash back to cidv0" '
ipfs files chcid / --cid-version=0 &&
echo QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn > hash_expect &&
ipfs files stat --hash / > hash_actual &&
test_cmp hash_expect hash_actual
'
}
tests_for_files_api "online"
test_launch_ipfs_daemon --offline
ONLINE=1 # set online flag so tests can easily tell
test_expect_success "can create some files for testing" '
create_files
'
test_files_api QmcwKfTMCT7AaeiD92hWjnZn9b6eh9NxnhfSzN5x2vnDpt
test_expect_success "can create some files for testing with raw-leaves" '
create_files --raw-leaves
'
test_files_api QmTpKiKcAj4sbeesN6vrs5w3QeVmd4QmGpxRL81hHut4dZ
tests_for_files_api "offline"
test_kill_ipfs_daemon --offline
@ -569,7 +666,13 @@ test_expect_success "enable sharding in config" '
'
test_launch_ipfs_daemon --offline
test_sharding
SHARD_HASH=QmPkwLJTYZRGPJ8Lazr9qPdrLmswPtUjaDbEpmR9jEh1se
test_sharding "(cidv0)"
SHARD_HASH=zdj7WZXr6vG2Ne7ZLHGEKrGyF3pHBfAViEnmH9CoyvjrFQM8E
test_sharding "(cidv1 root)" "--cid-version=1"
test_kill_ipfs_daemon
test_done

View File

@ -75,8 +75,8 @@ test_add_large_dir_v1() {
'
}
# this hash implies both the directory and the leaf entries are CIDv1
SHARDEDV1="zdj7WX91spg4DsnNpvoBLjyjXUGgcTTWavygBbSifpmJdgPUA"
# this hash implies the directory is CIDv1 and leaf entries are CIDv1 and raw
SHARDEDV1="zdj7WY8aNcxF49q1ZpFXfchNmbswnUxiVDVjmrHb53xRM8W4C"
test_add_large_dir_v1 "$SHARDEDV1"
test_launch_ipfs_daemon

View File

@ -121,6 +121,7 @@ func NewHamtFromDag(dserv dag.DAGService, nd node.Node) (*HamtShard, error) {
ds.children = make([]child, len(pbnd.Links()))
ds.bitfield = new(big.Int).SetBytes(pbd.GetData())
ds.hashFunc = pbd.GetHashType()
ds.prefix = &ds.nd.Prefix
return ds, nil
}
@ -130,6 +131,11 @@ func (ds *HamtShard) SetPrefix(prefix *cid.Prefix) {
ds.prefix = prefix
}
// Prefix gets the CID Prefix, may be nil if unset
func (ds *HamtShard) Prefix() *cid.Prefix {
return ds.prefix
}
// Node serializes the HAMT structure into a merkledag node with unixfs formatting
func (ds *HamtShard) Node() (node.Node, error) {
out := new(dag.ProtoNode)
@ -500,6 +506,7 @@ func (ds *HamtShard) modifyValue(ctx context.Context, hv *hashBits, key string,
if err != nil {
return err
}
ns.prefix = ds.prefix
chhv := &hashBits{
b: hash([]byte(child.key)),
consumed: hv.consumed,

View File

@ -17,7 +17,7 @@ import (
func TestBasicRead(t *testing.T) {
dserv := testu.GetDAGServ()
inbuf, node := testu.GetRandomNode(t, dserv, 1024)
inbuf, node := testu.GetRandomNode(t, dserv, 1024, testu.UseProtoBufLeaves)
ctx, closer := context.WithCancel(context.Background())
defer closer()
@ -44,7 +44,7 @@ func TestSeekAndRead(t *testing.T) {
inbuf[i] = byte(i)
}
node := testu.GetNode(t, dserv, inbuf)
node := testu.GetNode(t, dserv, inbuf, testu.UseProtoBufLeaves)
ctx, closer := context.WithCancel(context.Background())
defer closer()
@ -84,7 +84,7 @@ func TestRelativeSeek(t *testing.T) {
}
inbuf[1023] = 1 // force the reader to be 1024 bytes
node := testu.GetNode(t, dserv, inbuf)
node := testu.GetNode(t, dserv, inbuf, testu.UseProtoBufLeaves)
reader, err := NewDagReader(ctx, node, dserv)
if err != nil {
@ -160,7 +160,7 @@ func TestBadPBData(t *testing.T) {
func TestMetadataNode(t *testing.T) {
dserv := testu.GetDAGServ()
rdata, rnode := testu.GetRandomNode(t, dserv, 512)
rdata, rnode := testu.GetRandomNode(t, dserv, 512, testu.UseProtoBufLeaves)
_, err := dserv.Add(rnode)
if err != nil {
t.Fatal(err)
@ -203,7 +203,7 @@ func TestMetadataNode(t *testing.T) {
func TestWriteTo(t *testing.T) {
dserv := testu.GetDAGServ()
inbuf, node := testu.GetRandomNode(t, dserv, 1024)
inbuf, node := testu.GetRandomNode(t, dserv, 1024, testu.UseProtoBufLeaves)
ctx, closer := context.WithCancel(context.Background())
defer closer()
@ -225,7 +225,7 @@ func TestWriteTo(t *testing.T) {
func TestReaderSzie(t *testing.T) {
dserv := testu.GetDAGServ()
size := int64(1024)
_, node := testu.GetRandomNode(t, dserv, size)
_, node := testu.GetRandomNode(t, dserv, size, testu.UseProtoBufLeaves)
ctx, closer := context.WithCancel(context.Background())
defer closer()

View File

@ -115,6 +115,7 @@ func (d *Directory) switchToSharding(ctx context.Context) error {
if err != nil {
return err
}
s.SetPrefix(&d.dirnode.Prefix)
d.shard = s
for _, lnk := range d.dirnode.Links() {
@ -192,3 +193,12 @@ func (d *Directory) GetNode() (node.Node, error) {
return d.shard.Node()
}
// GetPrefix returns the CID Prefix used
func (d *Directory) GetPrefix() *cid.Prefix {
if d.shard == nil {
return &d.dirnode.Prefix
}
return d.shard.Prefix()
}

View File

@ -40,11 +40,18 @@ type DagModifier struct {
curWrOff uint64
wrBuf *bytes.Buffer
Prefix cid.Prefix
RawLeaves bool
read uio.DagReader
}
var ErrNotUnixfs = fmt.Errorf("dagmodifier only supports unixfs nodes (proto or raw)")
// NewDagModifier returns a new DagModifier, the Cid prefix for newly
// created nodes will be inherted from the passed in node. If the Cid
// version if not 0 raw leaves will also be enabled. The Prefix and
// RawLeaves options can be overridden by changing them after the call.
func NewDagModifier(ctx context.Context, from node.Node, serv mdag.DAGService, spl chunk.SplitterGen) (*DagModifier, error) {
switch from.(type) {
case *mdag.ProtoNode, *mdag.RawNode:
@ -53,11 +60,20 @@ func NewDagModifier(ctx context.Context, from node.Node, serv mdag.DAGService, s
return nil, ErrNotUnixfs
}
prefix := from.Cid().Prefix()
prefix.Codec = cid.DagProtobuf
rawLeaves := false
if prefix.Version > 0 {
rawLeaves = true
}
return &DagModifier{
curNode: from.Copy(),
dagserv: serv,
splitter: spl,
ctx: ctx,
curNode: from.Copy(),
dagserv: serv,
splitter: spl,
ctx: ctx,
Prefix: prefix,
RawLeaves: rawLeaves,
}, nil
}
@ -113,17 +129,7 @@ func (dm *DagModifier) expandSparse(size int64) error {
return err
}
_, err = dm.dagserv.Add(nnode)
if err != nil {
return err
}
pbnnode, ok := nnode.(*mdag.ProtoNode)
if !ok {
return mdag.ErrNotProtobuf
}
dm.curNode = pbnnode
return nil
return err
}
// Write continues writing to the dag at the current offset
@ -149,26 +155,28 @@ func (dm *DagModifier) Write(b []byte) (int, error) {
return n, nil
}
var ErrNoRawYet = fmt.Errorf("currently only fully support protonodes in the dagmodifier")
// Size returns the Filesize of the node
func (dm *DagModifier) Size() (int64, error) {
switch nd := dm.curNode.(type) {
fileSize, err := fileSize(dm.curNode)
if err != nil {
return 0, err
}
if dm.wrBuf != nil && int64(dm.wrBuf.Len())+int64(dm.writeStart) > int64(fileSize) {
return int64(dm.wrBuf.Len()) + int64(dm.writeStart), nil
}
return int64(fileSize), nil
}
func fileSize(n node.Node) (uint64, error) {
switch nd := n.(type) {
case *mdag.ProtoNode:
pbn, err := ft.FromBytes(nd.Data())
f, err := ft.FromBytes(nd.Data())
if err != nil {
return 0, err
}
if dm.wrBuf != nil && uint64(dm.wrBuf.Len())+dm.writeStart > pbn.GetFilesize() {
return int64(dm.wrBuf.Len()) + int64(dm.writeStart), nil
}
return int64(pbn.GetFilesize()), nil
return f.GetFilesize(), nil
case *mdag.RawNode:
if dm.wrBuf != nil {
return 0, ErrNoRawYet
}
sz, err := nd.Size()
return int64(sz), err
return uint64(len(nd.RawData())), nil
default:
return 0, ErrNotUnixfs
}
@ -196,36 +204,22 @@ func (dm *DagModifier) Sync() error {
return err
}
nd, err := dm.dagserv.Get(dm.ctx, thisc)
dm.curNode, err = dm.dagserv.Get(dm.ctx, thisc)
if err != nil {
return err
}
pbnd, ok := nd.(*mdag.ProtoNode)
if !ok {
return mdag.ErrNotProtobuf
}
dm.curNode = pbnd
// need to write past end of current dag
if !done {
nd, err := dm.appendData(dm.curNode, dm.splitter(dm.wrBuf))
dm.curNode, err = dm.appendData(dm.curNode, dm.splitter(dm.wrBuf))
if err != nil {
return err
}
_, err = dm.dagserv.Add(nd)
_, err = dm.dagserv.Add(dm.curNode)
if err != nil {
return err
}
pbnode, ok := nd.(*mdag.ProtoNode)
if !ok {
return mdag.ErrNotProtobuf
}
dm.curNode = pbnode
}
dm.writeStart += uint64(buflen)
@ -238,9 +232,82 @@ func (dm *DagModifier) Sync() error {
// returns the new key of the passed in node and whether or not all the data in the reader
// has been consumed.
func (dm *DagModifier) modifyDag(n node.Node, offset uint64, data io.Reader) (*cid.Cid, bool, error) {
// If we've reached a leaf node.
if len(n.Links()) == 0 {
switch nd0 := n.(type) {
case *mdag.ProtoNode:
f, err := ft.FromBytes(nd0.Data())
if err != nil {
return nil, false, err
}
n, err := data.Read(f.Data[offset:])
if err != nil && err != io.EOF {
return nil, false, err
}
// Update newly written node..
b, err := proto.Marshal(f)
if err != nil {
return nil, false, err
}
nd := new(mdag.ProtoNode)
nd.SetData(b)
nd.SetPrefix(&nd0.Prefix)
k, err := dm.dagserv.Add(nd)
if err != nil {
return nil, false, err
}
// Hey look! we're done!
var done bool
if n < len(f.Data[offset:]) {
done = true
}
return k, done, nil
case *mdag.RawNode:
origData := nd0.RawData()
bytes := make([]byte, len(origData))
// copy orig data up to offset
copy(bytes, origData[:offset])
// copy in new data
n, err := data.Read(bytes[offset:])
if err != nil && err != io.EOF {
return nil, false, err
}
// copy remaining data
offsetPlusN := int(offset) + n
if offsetPlusN < len(origData) {
copy(bytes[offsetPlusN:], origData[offsetPlusN:])
}
nd, err := mdag.NewRawNodeWPrefix(bytes, nd0.Cid().Prefix())
if err != nil {
return nil, false, err
}
k, err := dm.dagserv.Add(nd)
if err != nil {
return nil, false, err
}
// Hey look! we're done!
var done bool
if n < len(bytes[offset:]) {
done = true
}
return k, done, nil
}
}
node, ok := n.(*mdag.ProtoNode)
if !ok {
return nil, false, ErrNoRawYet
return nil, false, ErrNotUnixfs
}
f, err := ft.FromBytes(node.Data())
@ -248,35 +315,6 @@ func (dm *DagModifier) modifyDag(n node.Node, offset uint64, data io.Reader) (*c
return nil, false, err
}
// If we've reached a leaf node.
if len(node.Links()) == 0 {
n, err := data.Read(f.Data[offset:])
if err != nil && err != io.EOF {
return nil, false, err
}
// Update newly written node..
b, err := proto.Marshal(f)
if err != nil {
return nil, false, err
}
nd := new(mdag.ProtoNode)
nd.SetData(b)
k, err := dm.dagserv.Add(nd)
if err != nil {
return nil, false, err
}
// Hey look! we're done!
var done bool
if n < len(f.Data[offset:]) {
done = true
}
return k, done, nil
}
var cur uint64
var done bool
for i, bs := range f.GetBlocksizes() {
@ -287,12 +325,7 @@ func (dm *DagModifier) modifyDag(n node.Node, offset uint64, data io.Reader) (*c
return nil, false, err
}
childpb, ok := child.(*mdag.ProtoNode)
if !ok {
return nil, false, mdag.ErrNotProtobuf
}
k, sdone, err := dm.modifyDag(childpb, offset-cur, data)
k, sdone, err := dm.modifyDag(child, offset-cur, data)
if err != nil {
return nil, false, err
}
@ -323,14 +356,14 @@ func (dm *DagModifier) modifyDag(n node.Node, offset uint64, data io.Reader) (*c
// appendData appends the blocks from the given chan to the end of this dag
func (dm *DagModifier) appendData(nd node.Node, spl chunk.Splitter) (node.Node, error) {
switch nd := nd.(type) {
case *mdag.ProtoNode:
case *mdag.ProtoNode, *mdag.RawNode:
dbp := &help.DagBuilderParams{
Dagserv: dm.dagserv,
Maxlinks: help.DefaultLinksPerBlock,
Dagserv: dm.dagserv,
Maxlinks: help.DefaultLinksPerBlock,
Prefix: &dm.Prefix,
RawLeaves: dm.RawLeaves,
}
return trickle.TrickleAppend(dm.ctx, nd, dbp.New(spl))
case *mdag.RawNode:
return nil, fmt.Errorf("appending to raw node types not yet supported")
default:
return nil, ErrNotUnixfs
}
@ -478,26 +511,30 @@ func (dm *DagModifier) Truncate(size int64) error {
}
// dagTruncate truncates the given node to 'size' and returns the modified Node
func dagTruncate(ctx context.Context, n node.Node, size uint64, ds mdag.DAGService) (*mdag.ProtoNode, error) {
nd, ok := n.(*mdag.ProtoNode)
if !ok {
return nil, ErrNoRawYet
func dagTruncate(ctx context.Context, n node.Node, size uint64, ds mdag.DAGService) (node.Node, error) {
if len(n.Links()) == 0 {
switch nd := n.(type) {
case *mdag.ProtoNode:
// TODO: this can likely be done without marshaling and remarshaling
pbn, err := ft.FromBytes(nd.Data())
if err != nil {
return nil, err
}
nd.SetData(ft.WrapData(pbn.Data[:size]))
return nd, nil
case *mdag.RawNode:
return mdag.NewRawNodeWPrefix(nd.RawData()[:size], nd.Cid().Prefix())
}
}
if len(nd.Links()) == 0 {
// TODO: this can likely be done without marshaling and remarshaling
pbn, err := ft.FromBytes(nd.Data())
if err != nil {
return nil, err
}
nd.SetData(ft.WrapData(pbn.Data[:size]))
return nd, nil
nd, ok := n.(*mdag.ProtoNode)
if !ok {
return nil, ErrNotUnixfs
}
var cur uint64
end := 0
var modified *mdag.ProtoNode
var modified node.Node
ndata := new(ft.FSNode)
for i, lnk := range nd.Links() {
child, err := lnk.GetNode(ctx, ds)
@ -505,19 +542,14 @@ func dagTruncate(ctx context.Context, n node.Node, size uint64, ds mdag.DAGServi
return nil, err
}
childpb, ok := child.(*mdag.ProtoNode)
if !ok {
return nil, err
}
childsize, err := ft.DataSize(childpb.Data())
childsize, err := fileSize(child)
if err != nil {
return nil, err
}
// found the child we want to cut
if size < cur+childsize {
nchild, err := dagTruncate(ctx, childpb, size-cur, ds)
nchild, err := dagTruncate(ctx, child, size-cur, ds)
if err != nil {
return nil, err
}

View File

@ -9,15 +9,14 @@ import (
h "github.com/ipfs/go-ipfs/importer/helpers"
trickle "github.com/ipfs/go-ipfs/importer/trickle"
mdag "github.com/ipfs/go-ipfs/merkledag"
ft "github.com/ipfs/go-ipfs/unixfs"
uio "github.com/ipfs/go-ipfs/unixfs/io"
testu "github.com/ipfs/go-ipfs/unixfs/test"
u "gx/ipfs/QmSU6eubNdhXjFBJBSksTp8kv8YRub8mGAPv8tVJHmL2EU/go-ipfs-util"
)
func testModWrite(t *testing.T, beg, size uint64, orig []byte, dm *DagModifier) []byte {
func testModWrite(t *testing.T, beg, size uint64, orig []byte, dm *DagModifier, opts testu.NodeOpts) []byte {
newdata := make([]byte, size)
r := u.NewTimeSeededRand()
r.Read(newdata)
@ -36,12 +35,24 @@ func testModWrite(t *testing.T, beg, size uint64, orig []byte, dm *DagModifier)
t.Fatalf("Mod length not correct! %d != %d", nmod, size)
}
verifyNode(t, orig, dm, opts)
return orig
}
func verifyNode(t *testing.T, orig []byte, dm *DagModifier, opts testu.NodeOpts) {
nd, err := dm.GetNode()
if err != nil {
t.Fatal(err)
}
err = trickle.VerifyTrickleDagStructure(nd, dm.dagserv, h.DefaultLinksPerBlock, 4)
err = trickle.VerifyTrickleDagStructure(nd, trickle.VerifyParams{
Getter: dm.dagserv,
Direct: h.DefaultLinksPerBlock,
LayerRepeat: 4,
Prefix: &opts.Prefix,
RawLeaves: opts.RawLeavesUsed,
})
if err != nil {
t.Fatal(err)
}
@ -60,12 +71,21 @@ func testModWrite(t *testing.T, beg, size uint64, orig []byte, dm *DagModifier)
if err != nil {
t.Fatal(err)
}
return orig
}
func runAllSubtests(t *testing.T, tfunc func(*testing.T, testu.NodeOpts)) {
t.Run("opts=ProtoBufLeaves", func(t *testing.T) { tfunc(t, testu.UseProtoBufLeaves) })
t.Run("opts=RawLeaves", func(t *testing.T) { tfunc(t, testu.UseRawLeaves) })
t.Run("opts=CidV1", func(t *testing.T) { tfunc(t, testu.UseCidV1) })
t.Run("opts=Blake2b256", func(t *testing.T) { tfunc(t, testu.UseBlake2b256) })
}
func TestDagModifierBasic(t *testing.T) {
runAllSubtests(t, testDagModifierBasic)
}
func testDagModifierBasic(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
b, n := testu.GetRandomNode(t, dserv, 50000)
b, n := testu.GetRandomNode(t, dserv, 50000, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -73,32 +93,35 @@ func TestDagModifierBasic(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
// Within zero block
beg := uint64(15)
length := uint64(60)
t.Log("Testing mod within zero block")
b = testModWrite(t, beg, length, b, dagmod)
b = testModWrite(t, beg, length, b, dagmod, opts)
// Within bounds of existing file
beg = 1000
length = 4000
t.Log("Testing mod within bounds of existing multiblock file.")
b = testModWrite(t, beg, length, b, dagmod)
b = testModWrite(t, beg, length, b, dagmod, opts)
// Extend bounds
beg = 49500
length = 4000
t.Log("Testing mod that extends file.")
b = testModWrite(t, beg, length, b, dagmod)
b = testModWrite(t, beg, length, b, dagmod, opts)
// "Append"
beg = uint64(len(b))
length = 3000
t.Log("Testing pure append")
_ = testModWrite(t, beg, length, b, dagmod)
_ = testModWrite(t, beg, length, b, dagmod, opts)
// Verify reported length
node, err := dagmod.GetNode()
@ -106,7 +129,7 @@ func TestDagModifierBasic(t *testing.T) {
t.Fatal(err)
}
size, err := ft.DataSize(node.(*mdag.ProtoNode).Data())
size, err := fileSize(node)
if err != nil {
t.Fatal(err)
}
@ -118,8 +141,11 @@ func TestDagModifierBasic(t *testing.T) {
}
func TestMultiWrite(t *testing.T) {
runAllSubtests(t, testMultiWrite)
}
func testMultiWrite(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -128,6 +154,9 @@ func TestMultiWrite(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
data := make([]byte, 4000)
u.NewTimeSeededRand().Read(data)
@ -150,29 +179,16 @@ func TestMultiWrite(t *testing.T) {
t.Fatal("Size was reported incorrectly")
}
}
nd, err := dagmod.GetNode()
if err != nil {
t.Fatal(err)
}
read, err := uio.NewDagReader(context.Background(), nd, dserv)
if err != nil {
t.Fatal(err)
}
rbuf, err := ioutil.ReadAll(read)
if err != nil {
t.Fatal(err)
}
err = testu.ArrComp(rbuf, data)
if err != nil {
t.Fatal(err)
}
verifyNode(t, data, dagmod, opts)
}
func TestMultiWriteAndFlush(t *testing.T) {
runAllSubtests(t, testMultiWriteAndFlush)
}
func testMultiWriteAndFlush(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -181,6 +197,9 @@ func TestMultiWriteAndFlush(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
data := make([]byte, 20)
u.NewTimeSeededRand().Read(data)
@ -198,29 +217,16 @@ func TestMultiWriteAndFlush(t *testing.T) {
t.Fatal(err)
}
}
nd, err := dagmod.GetNode()
if err != nil {
t.Fatal(err)
}
read, err := uio.NewDagReader(context.Background(), nd, dserv)
if err != nil {
t.Fatal(err)
}
rbuf, err := ioutil.ReadAll(read)
if err != nil {
t.Fatal(err)
}
err = testu.ArrComp(rbuf, data)
if err != nil {
t.Fatal(err)
}
verifyNode(t, data, dagmod, opts)
}
func TestWriteNewFile(t *testing.T) {
runAllSubtests(t, testWriteNewFile)
}
func testWriteNewFile(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -229,6 +235,9 @@ func TestWriteNewFile(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
towrite := make([]byte, 2000)
u.NewTimeSeededRand().Read(towrite)
@ -241,29 +250,15 @@ func TestWriteNewFile(t *testing.T) {
t.Fatal("Wrote wrong amount")
}
nd, err := dagmod.GetNode()
if err != nil {
t.Fatal(err)
}
read, err := uio.NewDagReader(ctx, nd, dserv)
if err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadAll(read)
if err != nil {
t.Fatal(err)
}
if err := testu.ArrComp(data, towrite); err != nil {
t.Fatal(err)
}
verifyNode(t, towrite, dagmod, opts)
}
func TestMultiWriteCoal(t *testing.T) {
runAllSubtests(t, testMultiWriteCoal)
}
func testMultiWriteCoal(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -272,6 +267,9 @@ func TestMultiWriteCoal(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
data := make([]byte, 1000)
u.NewTimeSeededRand().Read(data)
@ -287,29 +285,16 @@ func TestMultiWriteCoal(t *testing.T) {
}
}
nd, err := dagmod.GetNode()
if err != nil {
t.Fatal(err)
}
read, err := uio.NewDagReader(context.Background(), nd, dserv)
if err != nil {
t.Fatal(err)
}
rbuf, err := ioutil.ReadAll(read)
if err != nil {
t.Fatal(err)
}
err = testu.ArrComp(rbuf, data)
if err != nil {
t.Fatal(err)
}
verifyNode(t, data, dagmod, opts)
}
func TestLargeWriteChunks(t *testing.T) {
runAllSubtests(t, testLargeWriteChunks)
}
func testLargeWriteChunks(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -318,6 +303,9 @@ func TestLargeWriteChunks(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
wrsize := 1000
datasize := 10000000
@ -343,12 +331,14 @@ func TestLargeWriteChunks(t *testing.T) {
if err = testu.ArrComp(out, data); err != nil {
t.Fatal(err)
}
}
func TestDagTruncate(t *testing.T) {
runAllSubtests(t, testDagTruncate)
}
func testDagTruncate(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
b, n := testu.GetRandomNode(t, dserv, 50000)
b, n := testu.GetRandomNode(t, dserv, 50000, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -356,6 +346,9 @@ func TestDagTruncate(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
err = dagmod.Truncate(12345)
if err != nil {
@ -414,8 +407,11 @@ func TestDagTruncate(t *testing.T) {
}
func TestSparseWrite(t *testing.T) {
runAllSubtests(t, testSparseWrite)
}
func testSparseWrite(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -423,6 +419,9 @@ func TestSparseWrite(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
buf := make([]byte, 5000)
u.NewTimeSeededRand().Read(buf[2500:])
@ -452,8 +451,11 @@ func TestSparseWrite(t *testing.T) {
}
func TestSeekPastEndWrite(t *testing.T) {
runAllSubtests(t, testSeekPastEndWrite)
}
func testSeekPastEndWrite(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -461,6 +463,9 @@ func TestSeekPastEndWrite(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
buf := make([]byte, 5000)
u.NewTimeSeededRand().Read(buf[2500:])
@ -499,8 +504,11 @@ func TestSeekPastEndWrite(t *testing.T) {
}
func TestRelativeSeek(t *testing.T) {
runAllSubtests(t, testRelativeSeek)
}
func testRelativeSeek(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -508,6 +516,9 @@ func TestRelativeSeek(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
for i := 0; i < 64; i++ {
dagmod.Write([]byte{byte(i)})
@ -529,8 +540,11 @@ func TestRelativeSeek(t *testing.T) {
}
func TestInvalidSeek(t *testing.T) {
runAllSubtests(t, testInvalidSeek)
}
func testInvalidSeek(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -538,6 +552,10 @@ func TestInvalidSeek(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
_, err = dagmod.Seek(10, -10)
if err != ErrUnrecognizedWhence {
@ -546,9 +564,12 @@ func TestInvalidSeek(t *testing.T) {
}
func TestEndSeek(t *testing.T) {
runAllSubtests(t, testEndSeek)
}
func testEndSeek(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -556,6 +577,9 @@ func TestEndSeek(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
_, err = dagmod.Write(make([]byte, 100))
if err != nil {
@ -588,9 +612,12 @@ func TestEndSeek(t *testing.T) {
}
func TestReadAndSeek(t *testing.T) {
runAllSubtests(t, testReadAndSeek)
}
func testReadAndSeek(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -598,6 +625,9 @@ func TestReadAndSeek(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
writeBuf := []byte{0, 1, 2, 3, 4, 5, 6, 7}
dagmod.Write(writeBuf)
@ -656,9 +686,12 @@ func TestReadAndSeek(t *testing.T) {
}
func TestCtxRead(t *testing.T) {
runAllSubtests(t, testCtxRead)
}
func testCtxRead(t *testing.T, opts testu.NodeOpts) {
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(t, dserv)
n := testu.GetEmptyNode(t, dserv, opts)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -666,6 +699,9 @@ func TestCtxRead(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if opts.ForceRawLeaves {
dagmod.RawLeaves = true
}
_, err = dagmod.Write([]byte{0, 1, 2, 3, 4, 5, 6, 7})
if err != nil {
@ -689,7 +725,7 @@ func TestCtxRead(t *testing.T) {
func BenchmarkDagmodWrite(b *testing.B) {
b.StopTimer()
dserv := testu.GetDAGServ()
n := testu.GetEmptyNode(b, dserv)
n := testu.GetEmptyNode(b, dserv, testu.UseProtoBufLeaves)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@ -8,14 +8,17 @@ import (
"io/ioutil"
"testing"
imp "github.com/ipfs/go-ipfs/importer"
"github.com/ipfs/go-ipfs/importer/chunk"
h "github.com/ipfs/go-ipfs/importer/helpers"
trickle "github.com/ipfs/go-ipfs/importer/trickle"
mdag "github.com/ipfs/go-ipfs/merkledag"
mdagmock "github.com/ipfs/go-ipfs/merkledag/test"
ft "github.com/ipfs/go-ipfs/unixfs"
cid "gx/ipfs/QmNp85zy9RLrQ5oQD4hPyS39ezrrXpcaa7R4Y9kxdWQLLQ/go-cid"
node "gx/ipfs/QmPN7cwmpcc4DWXb4KTB9dNAJgjuPY69h3npsMfhRrQL9c/go-ipld-format"
u "gx/ipfs/QmSU6eubNdhXjFBJBSksTp8kv8YRub8mGAPv8tVJHmL2EU/go-ipfs-util"
mh "gx/ipfs/QmU9a9NV9RdPNwZQDYd5uKsm6N6LJLSvLbywDDYFbaaC6P/go-multihash"
)
func SizeSplitterGen(size int64) chunk.SplitterGen {
@ -28,9 +31,37 @@ func GetDAGServ() mdag.DAGService {
return mdagmock.Mock()
}
func GetNode(t testing.TB, dserv mdag.DAGService, data []byte) node.Node {
// NodeOpts is used by GetNode, GetEmptyNode and GetRandomNode
type NodeOpts struct {
Prefix cid.Prefix
// ForceRawLeaves if true will force the use of raw leaves
ForceRawLeaves bool
// RawLeavesUsed is true if raw leaves or either implicitly or explicitly enabled
RawLeavesUsed bool
}
var UseProtoBufLeaves = NodeOpts{Prefix: mdag.V0CidPrefix()}
var UseRawLeaves = NodeOpts{Prefix: mdag.V0CidPrefix(), ForceRawLeaves: true, RawLeavesUsed: true}
var UseCidV1 = NodeOpts{Prefix: mdag.V1CidPrefix(), RawLeavesUsed: true}
var UseBlake2b256 NodeOpts
func init() {
UseBlake2b256 = UseCidV1
UseBlake2b256.Prefix.MhType = mh.Names["blake2b-256"]
UseBlake2b256.Prefix.MhLength = -1
}
func GetNode(t testing.TB, dserv mdag.DAGService, data []byte, opts NodeOpts) node.Node {
in := bytes.NewReader(data)
node, err := imp.BuildTrickleDagFromReader(dserv, SizeSplitterGen(500)(in))
dbp := h.DagBuilderParams{
Dagserv: dserv,
Maxlinks: h.DefaultLinksPerBlock,
Prefix: &opts.Prefix,
RawLeaves: opts.RawLeavesUsed,
}
node, err := trickle.TrickleLayout(dbp.New(SizeSplitterGen(500)(in)))
if err != nil {
t.Fatal(err)
}
@ -38,18 +69,18 @@ func GetNode(t testing.TB, dserv mdag.DAGService, data []byte) node.Node {
return node
}
func GetEmptyNode(t testing.TB, dserv mdag.DAGService) node.Node {
return GetNode(t, dserv, []byte{})
func GetEmptyNode(t testing.TB, dserv mdag.DAGService, opts NodeOpts) node.Node {
return GetNode(t, dserv, []byte{}, opts)
}
func GetRandomNode(t testing.TB, dserv mdag.DAGService, size int64) ([]byte, node.Node) {
func GetRandomNode(t testing.TB, dserv mdag.DAGService, size int64, opts NodeOpts) ([]byte, node.Node) {
in := io.LimitReader(u.NewTimeSeededRand(), size)
buf, err := ioutil.ReadAll(in)
if err != nil {
t.Fatal(err)
}
node := GetNode(t, dserv, buf)
node := GetNode(t, dserv, buf, opts)
return buf, node
}