@cryptotaxi247 / kubo / commits / 38fab9101

address comments from CR

License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>

Jeromy committed Sep 30, 2015 at 17:12 UTC 38fab91013d710db8a9cc4f26cf5a750f90dac29
6 files changed +472 -106
core/commands/files/files.go
+219 -68
@@ -16,6 +16,7 @@ import (
16 path "github.com/ipfs/go-ipfs/path"
17 ft "github.com/ipfs/go-ipfs/unixfs"
18
19 + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
20 logging "github.com/ipfs/go-ipfs/vendor/QmQg1J6vikuXF9oDvm4wpdeAUvvkVEKW1EYDw9HhTMnP2b/go-log"
21 )
22
@@ -55,44 +56,75 @@ var FilesStatCmd = &cmds.Command{
56 return
57 }
58
58 - path := req.Arguments()[0]
59 - fsn, err := mfs.Lookup(node.FilesRoot, path)
59 + path, err := checkPath(req.Arguments()[0])
60 if err != nil {
61 res.SetError(err, cmds.ErrNormal)
62 return
63 }
64
65 - nd, err := fsn.GetNode()
65 + fsn, err := mfs.Lookup(node.FilesRoot, path)
66 if err != nil {
67 res.SetError(err, cmds.ErrNormal)
68 return
69 }
70
71 - k, err := nd.Key()
71 + o, err := statNode(fsn)
72 if err != nil {
73 res.SetError(err, cmds.ErrNormal)
74 return
75 }
76
77 - res.SetOutput(&Object{
78 - Hash: k.B58String(),
79 - })
77 + res.SetOutput(o)
78 },
79 Marshalers: cmds.MarshalerMap{
80 cmds.Text: func(res cmds.Response) (io.Reader, error) {
81 out := res.Output().(*Object)
84 - return strings.NewReader(out.Hash), nil
82 + buf := new(bytes.Buffer)
83 + fmt.Fprintln(buf, out.Hash)
84 + fmt.Fprintf(buf, "Size: %d\n", out.Size)
85 + fmt.Fprintf(buf, "CumulativeSize: %d\n", out.CumulativeSize)
86 + fmt.Fprintf(buf, "ChildBlocks: %d\n", out.Blocks)
87 + return buf, nil
88 },
89 },
90 Type: Object{},
91 }
92
93 +func statNode(fsn mfs.FSNode) (*Object, error) {
94 + nd, err := fsn.GetNode()
95 + if err != nil {
96 + return nil, err
97 + }
98 +
99 + k, err := nd.Key()
100 + if err != nil {
101 + return nil, err
102 + }
103 +
104 + d, err := ft.FromBytes(nd.Data)
105 + if err != nil {
106 + return nil, err
107 + }
108 +
109 + cumulsize, err := nd.Size()
110 + if err != nil {
111 + return nil, err
112 + }
113 +
114 + return &Object{
115 + Hash: k.B58String(),
116 + Blocks: len(nd.Links),
117 + Size: d.GetFilesize(),
118 + CumulativeSize: cumulsize,
119 + }, nil
120 +}
121 +
122 var FilesCpCmd = &cmds.Command{
123 Helptext: cmds.HelpText{
124 Tagline: "copy files into mfs",
125 },
126 Arguments: []cmds.Argument{
95 - cmds.StringArg("src", true, false, "source object to copy"),
127 + cmds.StringArg("source", true, false, "source object to copy"),
128 cmds.StringArg("dest", true, false, "destination to copy object to"),
129 },
130 Run: func(req cmds.Request, res cmds.Response) {
@@ -102,39 +134,21 @@ var FilesCpCmd = &cmds.Command{
134 return
135 }
136
105 - src := req.Arguments()[0]
106 - dst := req.Arguments()[1]
107 -
108 - var nd *dag.Node
109 - switch {
110 - case strings.HasPrefix(src, "/ipfs/"):
111 - p, err := path.ParsePath(src)
112 - if err != nil {
113 - res.SetError(err, cmds.ErrNormal)
114 - return
115 - }
116 -
117 - obj, err := core.Resolve(req.Context(), node, p)
118 - if err != nil {
119 - res.SetError(err, cmds.ErrNormal)
120 - return
121 - }
122 -
123 - nd = obj
124 - default:
125 - fsn, err := mfs.Lookup(node.FilesRoot, src)
126 - if err != nil {
127 - res.SetError(err, cmds.ErrNormal)
128 - return
129 - }
130 -
131 - obj, err := fsn.GetNode()
132 - if err != nil {
133 - res.SetError(err, cmds.ErrNormal)
134 - return
135 - }
137 + src, err := checkPath(req.Arguments()[0])
138 + if err != nil {
139 + res.SetError(err, cmds.ErrNormal)
140 + return
141 + }
142 + dst, err := checkPath(req.Arguments()[1])
143 + if err != nil {
144 + res.SetError(err, cmds.ErrNormal)
145 + return
146 + }
147
137 - nd = obj
148 + nd, err := getNodeFromPath(req.Context(), node, src)
149 + if err != nil {
150 + res.SetError(err, cmds.ErrNormal)
151 + return
152 }
153
154 err = mfs.PutNode(node.FilesRoot, dst, nd)
@@ -145,8 +159,30 @@ var FilesCpCmd = &cmds.Command{
159 },
160 }
161
162 +func getNodeFromPath(ctx context.Context, node *core.IpfsNode, p string) (*dag.Node, error) {
163 + switch {
164 + case strings.HasPrefix(p, "/ipfs/"):
165 + np, err := path.ParsePath(p)
166 + if err != nil {
167 + return nil, err
168 + }
169 +
170 + return core.Resolve(ctx, node, np)
171 + default:
172 + fsn, err := mfs.Lookup(node.FilesRoot, p)
173 + if err != nil {
174 + return nil, err
175 + }
176 +
177 + return fsn.GetNode()
178 + }
179 +}
180 +
181 type Object struct {
149 - Hash string
182 + Hash string
183 + Size uint64
184 + CumulativeSize uint64
185 + Blocks int
186 }
187
188 type FilesLsOutput struct {
@@ -181,7 +217,12 @@ Examples:
217 cmds.BoolOption("l", "use long listing format"),
218 },
219 Run: func(req cmds.Request, res cmds.Response) {
184 - path := req.Arguments()[0]
220 + path, err := checkPath(req.Arguments()[0])
221 + if err != nil {
222 + res.SetError(err, cmds.ErrNormal)
223 + return
224 + }
225 +
226 nd, err := req.InvocContext().GetNode()
227 if err != nil {
228 res.SetError(err, cmds.ErrNormal)
@@ -243,7 +284,7 @@ Examples:
284
285 $ ipfs files read /test/hello
286 hello
246 - `,
287 + `,
288 },
289
290 Arguments: []cmds.Argument{
@@ -260,7 +301,12 @@ Examples:
301 return
302 }
303
263 - path := req.Arguments()[0]
304 + path, err := checkPath(req.Arguments()[0])
305 + if err != nil {
306 + res.SetError(err, cmds.ErrNormal)
307 + return
308 + }
309 +
310 fsn, err := mfs.Lookup(n.FilesRoot, path)
311 if err != nil {
312 res.SetError(err, cmds.ErrNormal)
@@ -273,7 +319,26 @@ Examples:
319 return
320 }
321
276 - offset, _, _ := req.Option("offset").Int()
322 + offset, _, err := req.Option("offset").Int()
323 + if err != nil {
324 + res.SetError(err, cmds.ErrNormal)
325 + return
326 + }
327 + if offset < 0 {
328 + res.SetError(fmt.Errorf("cannot specify negative offset"), cmds.ErrNormal)
329 + return
330 + }
331 +
332 + filen, err := fi.Size()
333 + if err != nil {
334 + res.SetError(err, cmds.ErrNormal)
335 + return
336 + }
337 +
338 + if int64(offset) > filen {
339 + res.SetError(fmt.Errorf("offset was past end of file (%d > %d)", offset, filen), cmds.ErrNormal)
340 + return
341 + }
342
343 _, err = fi.Seek(int64(offset), os.SEEK_SET)
344 if err != nil {
@@ -282,7 +347,15 @@ Examples:
347 }
348 var r io.Reader = fi
349 count, found, err := req.Option("count").Int()
285 - if err == nil && found {
350 + if err != nil {
351 + res.SetError(err, cmds.ErrNormal)
352 + return
353 + }
354 + if found {
355 + if count < 0 {
356 + res.SetError(fmt.Errorf("cannot specify negative 'count'"), cmds.ErrNormal)
357 + return
358 + }
359 r = io.LimitReader(fi, int64(count))
360 }
361
@@ -300,7 +373,7 @@ Example:
373
374 $ ipfs files mv /myfs/a/b/c /myfs/foo/newc
375
303 - `,
376 +`,
377 },
378
379 Arguments: []cmds.Argument{
@@ -314,8 +387,16 @@ Example:
387 return
388 }
389
317 - src := req.Arguments()[0]
318 - dst := req.Arguments()[1]
390 + src, err := checkPath(req.Arguments()[0])
391 + if err != nil {
392 + res.SetError(err, cmds.ErrNormal)
393 + return
394 + }
395 + dst, err := checkPath(req.Arguments()[1])
396 + if err != nil {
397 + res.SetError(err, cmds.ErrNormal)
398 + return
399 + }
400
401 err = mfs.Mv(n.FilesRoot, src, dst)
402 if err != nil {
@@ -332,14 +413,14 @@ var FilesWriteCmd = &cmds.Command{
413 Write data to a file in a given filesystem. This command allows you to specify
414 a beginning offset to write to. The entire length of the input will be written.
415
335 -If the '--create' option is specified, the file will be create if it does not
416 +If the '--create' option is specified, the file will be created if it does not
417 exist. Nonexistant intermediate directories will not be created.
418
419 Example:
420
340 - echo "hello world" | ipfs files write --create /myfs/a/b/file
341 - echo "hello world" | ipfs files write --truncate /myfs/a/b/file
342 - `,
421 + echo "hello world" | ipfs files write --create /myfs/a/b/file
422 + echo "hello world" | ipfs files write --truncate /myfs/a/b/file
423 +`,
424 },
425 Arguments: []cmds.Argument{
426 cmds.StringArg("path", true, false, "path to write to"),
@@ -347,11 +428,17 @@ Example:
428 },
429 Options: []cmds.Option{
430 cmds.IntOption("o", "offset", "offset to write to"),
350 - cmds.BoolOption("n", "create", "create the file if it does not exist"),
431 + cmds.BoolOption("e", "create", "create the file if it does not exist"),
432 cmds.BoolOption("t", "truncate", "truncate the file before writing"),
433 + cmds.IntOption("n", "count", "maximum number of bytes to read"),
434 },
435 Run: func(req cmds.Request, res cmds.Response) {
354 - path := req.Arguments()[0]
436 + path, err := checkPath(req.Arguments()[0])
437 + if err != nil {
438 + res.SetError(err, cmds.ErrNormal)
439 + return
440 + }
441 +
442 create, _, _ := req.Option("create").Bool()
443 trunc, _, _ := req.Option("truncate").Bool()
444
@@ -375,7 +462,25 @@ Example:
462 }
463 }
464
378 - offset, _, _ := req.Option("offset").Int()
465 + offset, _, err := req.Option("offset").Int()
466 + if err != nil {
467 + res.SetError(err, cmds.ErrNormal)
468 + return
469 + }
470 + if offset < 0 {
471 + res.SetError(fmt.Errorf("cannot have negative write offset"), cmds.ErrNormal)
472 + return
473 + }
474 +
475 + count, countfound, err := req.Option("count").Int()
476 + if err != nil {
477 + res.SetError(err, cmds.ErrNormal)
478 + return
479 + }
480 + if countfound && count < 0 {
481 + res.SetError(fmt.Errorf("cannot have negative byte count"), cmds.ErrNormal)
482 + return
483 + }
484
485 _, err = fi.Seek(int64(offset), os.SEEK_SET)
486 if err != nil {
@@ -390,6 +495,11 @@ Example:
495 return
496 }
497
498 + var r io.Reader = input
499 + if countfound {
500 + r = io.LimitReader(r, int64(count))
501 + }
502 +
503 n, err := io.Copy(fi, input)
504 if err != nil {
505 res.SetError(err, cmds.ErrNormal)
@@ -411,7 +521,7 @@ Note: all paths must be absolute.
521 Examples:
522
523 $ ipfs mfs mkdir /test/newdir
414 - $ ipfs mfs mkdir -p /test/does/not/exist/yet
524 + $ ipfs mfs mkdir -p /test/does/not/exist/yet
525 `,
526 },
527
@@ -429,10 +539,9 @@ Examples:
539 }
540
541 dashp, _, _ := req.Option("parents").Bool()
432 - dirtomake := req.Arguments()[0]
433 -
434 - if dirtomake[0] != '/' {
435 - res.SetError(errors.New("paths must be absolute"), cmds.ErrNormal)
542 + dirtomake, err := checkPath(req.Arguments()[0])
543 + if err != nil {
544 + res.SetError(err, cmds.ErrNormal)
545 return
546 }
547
@@ -446,8 +555,17 @@ Examples:
555
556 var FilesRmCmd = &cmds.Command{
557 Helptext: cmds.HelpText{
449 - Tagline: "remove a file",
450 - ShortDescription: ``,
558 + Tagline: "remove a file",
559 + ShortDescription: `
560 +remove files or directories
561 +
562 + $ ipfs files rm /foo
563 + $ ipfs files ls /bar
564 + cat
565 + dog
566 + fish
567 + $ ipfs files rm -r /bar
568 +`,
569 },
570
571 Arguments: []cmds.Argument{
@@ -463,7 +581,22 @@ var FilesRmCmd = &cmds.Command{
581 return
582 }
583
466 - path := req.Arguments()[0]
584 + path, err := checkPath(req.Arguments()[0])
585 + if err != nil {
586 + res.SetError(err, cmds.ErrNormal)
587 + return
588 + }
589 +
590 + if path == "/" {
591 + res.SetError(fmt.Errorf("cannot delete root"), cmds.ErrNormal)
592 + return
593 + }
594 +
595 + // 'rm a/b/c/' will fail unless we trim the slash at the end
596 + if path[len(path)-1] == '/' {
597 + path = path[:len(path)-1]
598 + }
599 +
600 dir, name := gopath.Split(path)
601 parent, err := mfs.Lookup(nd.FilesRoot, dir)
602 if err != nil {
@@ -546,11 +679,29 @@ func getFileHandle(r *mfs.Root, path string, create bool) (*mfs.File, error) {
679 return nil, err
680 }
681
549 - // can unsafely cast, if it fails, that means programmer error
550 - return fsn.(*mfs.File), nil
682 + fi, ok := fsn.(*mfs.File)
683 + if !ok {
684 + return nil, errors.New("expected *mfs.File, didnt get it. This is likely a race condition")
685 + }
686 + return fi, nil
687
688 default:
553 - log.Error("GFH default")
689 return nil, err
690 }
691 }
692 +
693 +func checkPath(p string) (string, error) {
694 + if len(p) == 0 {
695 + return "", fmt.Errorf("paths must not be empty")
696 + }
697 +
698 + if p[0] != '/' {
699 + return "", fmt.Errorf("paths must start with a leading slash")
700 + }
701 +
702 + cleaned := gopath.Clean(p)
703 + if p[len(p)-1] == '/' && p != "/" {
704 + cleaned += "/"
705 + }
706 + return cleaned, nil
707 +}
core/core.go
+3 -3
@@ -57,7 +57,7 @@ import (
57 pin "github.com/ipfs/go-ipfs/pin"
58 repo "github.com/ipfs/go-ipfs/repo"
59 config "github.com/ipfs/go-ipfs/repo/config"
60 - unixfs "github.com/ipfs/go-ipfs/unixfs"
60 + uio "github.com/ipfs/go-ipfs/unixfs/io"
61 u "github.com/ipfs/go-ipfs/util"
62 )
63
@@ -472,7 +472,7 @@ func (n *IpfsNode) loadBootstrapPeers() ([]peer.PeerInfo, error) {
472 }
473
474 func (n *IpfsNode) loadFilesRoot() error {
475 - dsk := ds.NewKey("/filesroot")
475 + dsk := ds.NewKey("/local/filesroot")
476 pf := func(ctx context.Context, k key.Key) error {
477 return n.Repo.Datastore().Put(dsk, []byte(k))
478 }
@@ -482,7 +482,7 @@ func (n *IpfsNode) loadFilesRoot() error {
482
483 switch {
484 case err == ds.ErrNotFound || val == nil:
485 - nd = &merkledag.Node{Data: unixfs.FolderPBData()}
485 + nd = uio.NewEmptyDirectory()
486 _, err := n.DAG.Add(nd)
487 if err != nil {
488 return fmt.Errorf("failure writing to dagstore: %s", err)
mfs/ops.go
+57 -25
@@ -14,11 +14,6 @@ import (
14 func Mv(r *Root, src, dst string) error {
15 srcDir, srcFname := gopath.Split(src)
16
17 - srcObj, err := Lookup(r, src)
18 - if err != nil {
19 - return err
20 - }
21 -
17 var dstDirStr string
18 var filename string
19 if dst[len(dst)-1] == '/' {
@@ -28,28 +23,46 @@ func Mv(r *Root, src, dst string) error {
23 dstDirStr, filename = gopath.Split(dst)
24 }
25
31 - dstDiri, err := Lookup(r, dstDirStr)
26 + // get parent directories of both src and dest first
27 + dstDir, err := lookupDir(r, dstDirStr)
28 if err != nil {
29 return err
30 }
31
36 - dstDir := dstDiri.(*Directory)
37 - nd, err := srcObj.GetNode()
32 + srcDirObj, err := lookupDir(r, srcDir)
33 if err != nil {
34 return err
35 }
36
42 - err = dstDir.AddChild(filename, nd)
37 + srcObj, err := srcDirObj.Child(srcFname)
38 if err != nil {
39 return err
40 }
41
47 - srcDirObji, err := Lookup(r, srcDir)
42 + nd, err := srcObj.GetNode()
43 + if err != nil {
44 + return err
45 + }
46 +
47 + fsn, err := dstDir.Child(filename)
48 + if err == nil {
49 + switch n := fsn.(type) {
50 + case *File:
51 + _ = dstDir.Unlink(filename)
52 + case *Directory:
53 + dstDir = n
54 + default:
55 + return fmt.Errorf("unexpected type at path: %s", dst)
56 + }
57 + } else if err != os.ErrNotExist {
58 + return err
59 + }
60 +
61 + err = dstDir.AddChild(filename, nd)
62 if err != nil {
63 return err
64 }
65
52 - srcDirObj := srcDirObji.(*Directory)
66 err = srcDirObj.Unlink(srcFname)
67 if err != nil {
68 return err
@@ -58,18 +71,27 @@ func Mv(r *Root, src, dst string) error {
71 return nil
72 }
73
74 +func lookupDir(r *Root, path string) (*Directory, error) {
75 + di, err := Lookup(r, path)
76 + if err != nil {
77 + return nil, err
78 + }
79 +
80 + d, ok := di.(*Directory)
81 + if !ok {
82 + return nil, fmt.Errorf("%s is not a directory", path)
83 + }
84 +
85 + return d, nil
86 +}
87 +
88 // PutNode inserts 'nd' at 'path' in the given mfs
89 func PutNode(r *Root, path string, nd *dag.Node) error {
90 dirp, filename := gopath.Split(path)
91
65 - parent, err := Lookup(r, dirp)
92 + pdir, err := lookupDir(r, dirp)
93 if err != nil {
67 - return fmt.Errorf("lookup '%s' failed: %s", dirp, err)
68 - }
69 -
70 - pdir, ok := parent.(*Directory)
71 - if !ok {
72 - return fmt.Errorf("%s did not point to directory", dirp)
94 + return err
95 }
96
97 return pdir.AddChild(filename, nd)
@@ -83,17 +105,27 @@ func Mkdir(r *Root, path string, parents bool) error {
105 parts = parts[1:]
106 }
107
108 + // allow 'mkdir /a/b/c/' to create c
109 + if parts[len(parts)-1] == "" {
110 + parts = parts[:len(parts)-1]
111 + }
112 +
113 + if len(parts) == 0 {
114 + // this will only happen on 'mkdir /'
115 + return fmt.Errorf("cannot mkdir '%s'", path)
116 + }
117 +
118 cur := r.GetValue().(*Directory)
119 for i, d := range parts[:len(parts)-1] {
120 fsn, err := cur.Child(d)
89 - if err != nil {
90 - if err == os.ErrNotExist && parents {
91 - mkd, err := cur.Mkdir(d)
92 - if err != nil {
93 - return err
94 - }
95 - fsn = mkd
121 + if err == os.ErrNotExist && parents {
122 + mkd, err := cur.Mkdir(d)
123 + if err != nil {
124 + return err
125 }
126 + fsn = mkd
127 + } else if err != nil {
128 + return err
129 }
130
131 next, ok := fsn.(*Directory)
test/sharness/t0250-files-api.sh
+130 -6
@@ -59,6 +59,19 @@ test_files_api() {
59 verify_dir_contents /cats
60 '
61
62 + test_expect_success "check root hash" '
63 + ipfs files stat / | head -n1 > roothash
64 + '
65 +
66 + test_expect_success "cannot mkdir /" '
67 + test_expect_code 1 ipfs files mkdir /
68 + '
69 +
70 + test_expect_success "check root hash was not changed" '
71 + ipfs files stat / | head -n1 > roothashafter &&
72 + test_cmp roothash roothashafter
73 + '
74 +
75 test_expect_success "can put files into directory" '
76 ipfs files cp /ipfs/$FILE1 /cats/file1
77 '
@@ -73,7 +86,7 @@ test_files_api() {
86
87 test_expect_success "output looks good" '
88 echo foo > expected &&
76 - test_cmp file1out expected
89 + test_cmp expected file1out
90 '
91
92 test_expect_success "can put another file into root" '
@@ -90,7 +103,7 @@ test_files_api() {
103
104 test_expect_success "output looks good" '
105 echo bar > expected &&
93 - test_cmp file2out expected
106 + test_cmp expected file2out
107 '
108
109 test_expect_success "can make deep directory" '
@@ -116,7 +129,7 @@ test_files_api() {
129
130 test_expect_success "output looks good" '
131 echo baz > expected &&
119 - test_cmp output expected
132 + test_cmp expected output
133 '
134
135 test_expect_success "file shows up in dir" '
@@ -147,6 +160,19 @@ test_files_api() {
160 verify_dir_contents / cats
161 '
162
163 + test_expect_success "check root hash" '
164 + ipfs files stat / | head -n1 > roothash
165 + '
166 +
167 + test_expect_success "cannot remove root" '
168 + test_expect_code 1 ipfs files rm -r /
169 + '
170 +
171 + test_expect_success "check root hash was not changed" '
172 + ipfs files stat / | head -n1 > roothashafter &&
173 + test_cmp roothash roothashafter
174 + '
175 +
176 # test read options
177
178 test_expect_success "read from offset works" '
@@ -155,7 +181,7 @@ test_files_api() {
181
182 test_expect_success "output looks good" '
183 echo oo > expected &&
158 - test_cmp output expected
184 + test_cmp expected output
185 '
186
187 test_expect_success "read with size works" '
@@ -164,7 +190,55 @@ test_files_api() {
190
191 test_expect_success "output looks good" '
192 printf fo > expected &&
167 - test_cmp output expected
193 + test_cmp expected output
194 + '
195 +
196 + test_expect_success "cannot read from negative offset" '
197 + test_expect_code 1 ipfs files read --offset -3 /cats/file1
198 + '
199 +
200 + test_expect_success "read from offset 0 works" '
201 + ipfs files read --offset 0 /cats/file1 > output
202 + '
203 +
204 + test_expect_success "output looks good" '
205 + echo foo > expected &&
206 + test_cmp expected output
207 + '
208 +
209 + test_expect_success "read last byte works" '
210 + ipfs files read --offset 2 /cats/file1 > output
211 + '
212 +
213 + test_expect_success "output looks good" '
214 + echo o > expected &&
215 + test_cmp expected output
216 + '
217 +
218 + test_expect_success "offset past end of file fails" '
219 + test_expect_code 1 ipfs files read --offset 5 /cats/file1
220 + '
221 +
222 + test_expect_success "cannot read negative count bytes" '
223 + test_expect_code 1 ipfs read --count -1 /cats/file1
224 + '
225 +
226 + test_expect_success "reading zero bytes prints nothing" '
227 + ipfs files read --count 0 /cats/file1 > output
228 + '
229 +
230 + test_expect_success "output looks good" '
231 + printf "" > expected &&
232 + test_cmp expected output
233 + '
234 +
235 + test_expect_success "count > len(file) prints entire file" '
236 + ipfs files read --count 200 /cats/file1 > output
237 + '
238 +
239 + test_expect_success "output looks good" '
240 + echo foo > expected &&
241 + test_cmp expected output
242 '
243
244 # test write
@@ -189,7 +263,57 @@ test_files_api() {
263 test_expect_success "file looks correct" '
264 echo "ipfs is super cool" > expected &&
265 ipfs files read /cats/ipfs > output &&
192 - test_cmp output expected
266 + test_cmp expected output
267 + '
268 +
269 + test_expect_success "cant write to negative offset" '
270 + ipfs files stat /cats/ipfs | head -n1 > filehash &&
271 + test_expect_code 1 ipfs files write --offset -1 /cats/ipfs < output
272 + '
273 +
274 + test_expect_success "verify file was not changed" '
275 + ipfs files stat /cats/ipfs | head -n1 > afterhash &&
276 + test_cmp filehash afterhash
277 + '
278 +
279 + test_expect_success "write new file for testing" '
280 + echo foobar | ipfs files write --create /fun
281 + '
282 +
283 + test_expect_success "write to offset past end works" '
284 + echo blah | ipfs files write --offset 50 /fun
285 + '
286 +
287 + test_expect_success "can read file" '
288 + ipfs files read /fun > sparse_output
289 + '
290 +
291 + test_expect_success "output looks good" '
292 + echo foobar > sparse_expected &&
293 + echo blah | dd of=sparse_expected bs=50 seek=1 &&
294 + test_cmp sparse_expected sparse_output
295 + '
296 +
297 + test_expect_success "cleanup" '
298 + ipfs files rm /fun
299 + '
300 +
301 + test_expect_success "cannot write to directory" '
302 + ipfs files stat /cats | head -n1 > dirhash &&
303 + test_expect_code 1 ipfs files write /cats < output
304 + '
305 +
306 + test_expect_success "verify dir was not changed" '
307 + ipfs files stat /cats | head -n1 > afterdirhash &&
308 + test_cmp dirhash afterdirhash
309 + '
310 +
311 + test_expect_success "cannot write to nonexistant path" '
312 + test_expect_code 1 ipfs files write /cats/bar/ < output
313 + '
314 +
315 + test_expect_success "no new paths were created" '
316 + verify_dir_contents /cats file1 ipfs this
317 '
318
319 # test mv
unixfs/mod/dagmodifier.go
+16 -4
@@ -368,19 +368,31 @@ func (dm *DagModifier) Seek(offset int64, whence int) (int64, error) {
368 return 0, err
369 }
370
371 + fisize, err := dm.Size()
372 + if err != nil {
373 + return 0, err
374 + }
375 +
376 + var newoffset uint64
377 switch whence {
378 case os.SEEK_CUR:
373 - dm.curWrOff += uint64(offset)
374 - dm.writeStart = dm.curWrOff
379 + newoffset = dm.curWrOff + uint64(offset)
380 case os.SEEK_SET:
376 - dm.curWrOff = uint64(offset)
377 - dm.writeStart = uint64(offset)
381 + newoffset = uint64(offset)
382 case os.SEEK_END:
383 return 0, ErrSeekEndNotImpl
384 default:
385 return 0, ErrUnrecognizedWhence
386 }
387
388 + if offset > fisize {
389 + if err := dm.expandSparse(offset - fisize); err != nil {
390 + return 0, err
391 + }
392 + }
393 + dm.curWrOff = newoffset
394 + dm.writeStart = newoffset
395 +
396 if dm.read != nil {
397 _, err = dm.read.Seek(offset, whence)
398 if err != nil {
unixfs/mod/dagmodifier_test.go
+47
@@ -487,6 +487,53 @@ func TestSparseWrite(t *testing.T) {
487 }
488 }
489
490 +func TestSeekPastEndWrite(t *testing.T) {
491 + dserv := getMockDagServ(t)
492 + _, n := getNode(t, dserv, 0)
493 + ctx, cancel := context.WithCancel(context.Background())
494 + defer cancel()
495 +
496 + dagmod, err := NewDagModifier(ctx, n, dserv, sizeSplitterGen(512))
497 + if err != nil {
498 + t.Fatal(err)
499 + }
500 +
501 + buf := make([]byte, 5000)
502 + u.NewTimeSeededRand().Read(buf[2500:])
503 +
504 + nseek, err := dagmod.Seek(2500, os.SEEK_SET)
505 + if err != nil {
506 + t.Fatal(err)
507 + }
508 +
509 + if nseek != 2500 {
510 + t.Fatal("failed to seek")
511 + }
512 +
513 + wrote, err := dagmod.Write(buf[2500:])
514 + if err != nil {
515 + t.Fatal(err)
516 + }
517 +
518 + if wrote != 2500 {
519 + t.Fatal("incorrect write amount")
520 + }
521 +
522 + _, err = dagmod.Seek(0, os.SEEK_SET)
523 + if err != nil {
524 + t.Fatal(err)
525 + }
526 +
527 + out, err := ioutil.ReadAll(dagmod)
528 + if err != nil {
529 + t.Fatal(err)
530 + }
531 +
532 + if err = arrComp(out, buf); err != nil {
533 + t.Fatal(err)
534 + }
535 +}
536 +
537 func BenchmarkDagmodWrite(b *testing.B) {
538 b.StopTimer()
539 dserv := getMockDagServ(b)