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)