Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cb46869

Browse files
committedJun 21, 2015
commands/files: Make Stat() part of the File interface
We need Stat() calls to figure out whether a file is a directory, regular file, or other object (e.g. a symlink, named pipe, etc.). This commit shifts our File interface to more closely match os.File. I'm a bit concerned that the backing Stat calls on any underlying os.File is done at wrapper-creation time and not at wrapper.Stat() time. That means we'll get stale data with a workflow like: 1. Create a new commands/files/... File (e.g. via NewSerialFile) 2. Edit the backing file (e.g. appending data to it). 3. Call f.Stat() on our wrapping file to get a FileInfo type. In that case, fi.Size() and fi.ModTime() on the returned FileInfo from (3) will reflect the values that the file had at (1), and not the values that it should have at (3). Maybe we're mostly using our File wrappers as read- or write-only entities and then throwing them away, in which case this discrepancy may not be too important. But it's still a race hole I'd like to close. License: MIT Signed-off-by: W. Trevor King <wking@tremily.us>
1 parent dc06c98 commit cb46869

File tree

9 files changed

+60
-20
lines changed

9 files changed

+60
-20
lines changed
 

‎commands/cli/parse.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *c
4747
}
4848
req.SetArguments(stringArgs)
4949

50-
file := files.NewSliceFile("", fileArgs)
50+
file, err := files.NewSliceFile("", nil, fileArgs)
51+
if err != nil {
52+
return req, cmd, path, err
53+
}
54+
5155
req.SetFiles(file)
5256

5357
err = cmd.CheckArguments(req)

‎commands/files/file.go

+4-6
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,16 @@ type File interface {
2525
// and false if the File is a normal file (and therefor supports calling `Read` and `Close`)
2626
IsDirectory() bool
2727

28+
// Stat returns an os.FileInfo structure describing the file. If
29+
// there is an error it will be of type *PathError.
30+
Stat() (fi os.FileInfo, err error)
31+
2832
// NextFile returns the next child file available (if the File is a directory).
2933
// It will return (nil, io.EOF) if no more files are available.
3034
// If the file is a regular file (not a directory), NextFile will return a non-nil error.
3135
NextFile() (File, error)
3236
}
3337

34-
type StatFile interface {
35-
File
36-
37-
Stat() os.FileInfo
38-
}
39-
4038
type PeekFile interface {
4139
SizeFile
4240

‎commands/files/file_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ func TestSliceFiles(t *testing.T) {
1717
}
1818
buf := make([]byte, 20)
1919

20-
sf := NewSliceFile(name, files)
20+
sf, err := NewSliceFile(name, nil, files)
21+
if err != nil {
22+
t.Error("Failed to create a new SliceFile")
23+
}
2124

2225
if !sf.IsDirectory() {
2326
t.Error("SliceFile should always be a directory")

‎commands/files/readerfile.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ func (f *ReaderFile) Close() error {
3838
return f.reader.Close()
3939
}
4040

41-
func (f *ReaderFile) Stat() os.FileInfo {
42-
return f.stat
41+
func (f *ReaderFile) Stat() (fi os.FileInfo, err error) {
42+
return f.stat, nil
4343
}
4444

4545
func (f *ReaderFile) Size() (int64, error) {

‎commands/files/serialfile.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ func (f *serialFile) Close() error {
115115
return nil
116116
}
117117

118-
func (f *serialFile) Stat() os.FileInfo {
119-
return f.stat
118+
func (f *serialFile) Stat() (fi os.FileInfo, err error) {
119+
return f.stat, nil
120120
}
121121

122122
func (f *serialFile) Size() (int64, error) {

‎commands/files/slicefile.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package files
33
import (
44
"errors"
55
"io"
6+
"os"
67
)
78

89
// SliceFile implements File, and provides simple directory handling.
@@ -11,11 +12,17 @@ import (
1112
type SliceFile struct {
1213
filename string
1314
files []File
15+
stat os.FileInfo
1416
n int
1517
}
1618

17-
func NewSliceFile(filename string, files []File) *SliceFile {
18-
return &SliceFile{filename, files, 0}
19+
func NewSliceFile(filename string, file *os.File, files []File) (f *SliceFile, err error) {
20+
stat, err := file.Stat()
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
return &SliceFile{filename, files, stat, 0}, nil
1926
}
2027

2128
func (f *SliceFile) IsDirectory() bool {
@@ -43,6 +50,10 @@ func (f *SliceFile) Close() error {
4350
return ErrNotReader
4451
}
4552

53+
func (f *SliceFile) Stat() (fi os.FileInfo, err error) {
54+
return f.stat, nil
55+
}
56+
4657
func (f *SliceFile) Peek(n int) File {
4758
return f.files[n]
4859
}

‎commands/http/multifilereader_test.go

+15-5
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,25 @@ import (
1212

1313
func TestOutput(t *testing.T) {
1414
text := "Some text! :)"
15+
16+
sf, err := files.NewSliceFile("boop", nil, []files.File{
17+
files.NewReaderFile("boop/a.txt", ioutil.NopCloser(strings.NewReader("bleep")), nil),
18+
files.NewReaderFile("boop/b.txt", ioutil.NopCloser(strings.NewReader("bloop")), nil),
19+
})
20+
if err != nil {
21+
t.Error("Failed to create a new SliceFile")
22+
}
23+
1524
fileset := []files.File{
1625
files.NewReaderFile("file.txt", ioutil.NopCloser(strings.NewReader(text)), nil),
17-
files.NewSliceFile("boop", []files.File{
18-
files.NewReaderFile("boop/a.txt", ioutil.NopCloser(strings.NewReader("bleep")), nil),
19-
files.NewReaderFile("boop/b.txt", ioutil.NopCloser(strings.NewReader("bloop")), nil),
20-
}),
26+
sf,
2127
files.NewReaderFile("beep.txt", ioutil.NopCloser(strings.NewReader("beep")), nil),
2228
}
23-
sf := files.NewSliceFile("", fileset)
29+
30+
sf, err := files.NewSliceFile("", nil, fileset)
31+
if err != nil {
32+
t.Error("Failed to create a new SliceFile")
33+
}
2434
buf := make([]byte, 20)
2535

2636
// testing output by reading it with the go stdlib "mime/multipart" Reader

‎core/commands/add.go

+10
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,16 @@ func addFile(n *core.IpfsNode, file files.File, out chan interface{}, progress b
250250
return addDir(n, file, out, progress, useTrickle)
251251
}
252252

253+
stat, err := file.Stat()
254+
if err != nil {
255+
return nil, err
256+
}
257+
258+
mode := stat.Mode()
259+
if !mode.IsRegular() {
260+
return nil, fmt.Errorf("`%s` is neither a directory nor a file", file.FileName())
261+
}
262+
253263
// if the progress flag was specified, wrap the file so that we can send
254264
// progress updates to the client (over the output channel)
255265
var reader io.Reader = file

‎core/coreunix/add.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ func AddR(n *core.IpfsNode, root string) (key string, err error) {
8080
// the directory, and and error if any.
8181
func AddWrapped(n *core.IpfsNode, r io.Reader, filename string) (string, *merkledag.Node, error) {
8282
file := files.NewReaderFile(filename, ioutil.NopCloser(r), nil)
83-
dir := files.NewSliceFile("", []files.File{file})
83+
dir, err := files.NewSliceFile("", nil, []files.File{file})
84+
if err != nil {
85+
return "", nil, err
86+
}
87+
8488
dagnode, err := addDir(n, dir)
8589
if err != nil {
8690
return "", nil, err

0 commit comments

Comments
 (0)
Please sign in to comment.