Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Improved Stat() and Mode() handling for added files #1413

Closed
wants to merge 9 commits into from
19 changes: 16 additions & 3 deletions commands/cli/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"runtime"
"strings"
"syscall"

cmds "github.com/ipfs/go-ipfs/commands"
files "github.com/ipfs/go-ipfs/commands/files"
Expand Down Expand Up @@ -47,7 +48,11 @@ func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *c
}
req.SetArguments(stringArgs)

file := files.NewSliceFile("", fileArgs)
file, err := files.NewSliceFile("", nil, fileArgs)
if err != nil {
return req, cmd, path, err
}

req.SetFiles(file)

err = cmd.CheckArguments(req)
Expand Down Expand Up @@ -335,8 +340,16 @@ func appendStdinAsString(args []string, stdin *os.File) ([]string, *os.File, err
func appendFile(args []files.File, inputs []string, argDef *cmds.Argument, recursive bool) ([]files.File, []string, error) {
path := inputs[0]

file, err := os.Open(path)
file, err := os.OpenFile(path, (os.O_RDONLY | syscall.O_NOFOLLOW | syscall.O_NONBLOCK), 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets just call stat before we call open. It might introduce a slight race, but its better than breaking our cross platformability with importing syscall.

if err != nil {
errno, err2 := files.Errno(err)
if err2 == nil && errno == syscall.ELOOP {
arg, err := files.NewSymlink(path)
if err != nil {
return nil, nil, err
}
return append(args, arg), inputs[1:], nil
}
return nil, nil, err
}

Expand Down Expand Up @@ -367,7 +380,7 @@ func appendFile(args []files.File, inputs []string, argDef *cmds.Argument, recur
}

func appendStdinAsFile(args []files.File, stdin *os.File) ([]files.File, *os.File) {
arg := files.NewReaderFile("", stdin, nil)
arg := files.NewReaderFile("", stdin, files.NewDummyRegularFileInfo("stdin"))
return append(args, arg), nil
}

Expand Down
10 changes: 4 additions & 6 deletions commands/files/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,16 @@ type File interface {
// and false if the File is a normal file (and therefor supports calling `Read` and `Close`)
IsDirectory() bool

// Stat returns an os.FileInfo structure describing the file. If
// there is an error it will be of type *PathError.
Stat() (fi os.FileInfo, err error)

// NextFile returns the next child file available (if the File is a directory).
// It will return (nil, io.EOF) if no more files are available.
// If the file is a regular file (not a directory), NextFile will return a non-nil error.
NextFile() (File, error)
}

type StatFile interface {
File

Stat() os.FileInfo
}

type PeekFile interface {
SizeFile

Expand Down
5 changes: 4 additions & 1 deletion commands/files/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ func TestSliceFiles(t *testing.T) {
}
buf := make([]byte, 20)

sf := NewSliceFile(name, files)
sf, err := NewSliceFile(name, nil, files)
if err != nil {
t.Error("Failed to create a new SliceFile")
}

if !sf.IsDirectory() {
t.Error("SliceFile should always be a directory")
Expand Down
55 changes: 55 additions & 0 deletions commands/files/fileinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package files

import (
"os"
"time"
)

type fileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}

func NewDummyRegularFileInfo(name string) (fi os.FileInfo) {
return &fileInfo{
name: name,
size: 0,
mode: os.ModePerm,
modTime: time.Time{},
}
}

func NewDummyDirectoryFileInfo(name string) (fi os.FileInfo) {
return &fileInfo{
name: name,
size: 0,
mode: os.ModePerm | os.ModeDir,
modTime: time.Time{},
}
}

func (fi *fileInfo) Name() string {
return fi.name
}

func (fi *fileInfo) Size() int64 {
return fi.size
}

func (fi *fileInfo) Mode() os.FileMode {
return fi.mode
}

func (fi *fileInfo) ModTime() time.Time {
return fi.modTime
}

func (fi *fileInfo) IsDir() bool {
return (fi.mode & os.ModeDir) != 0
}

func (fi *fileInfo) Sys() interface{} {
return nil
}
58 changes: 58 additions & 0 deletions commands/files/multipartfile.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package files

import (
"fmt"
"mime"
"mime/multipart"
"net/http"
"net/url"
"os"
"strconv"
"time"
)

const (
Expand All @@ -22,6 +26,7 @@ type MultipartFile struct {
Part *multipart.Part
Reader *multipart.Reader
Mediatype string
stat os.FileInfo
}

func NewFileFromPart(part *multipart.Part) (File, error) {
Expand All @@ -47,6 +52,55 @@ func NewFileFromPart(part *multipart.Part) (File, error) {
f.Reader = multipart.NewReader(part, boundary)
}

name := f.FileName()
fileInfoString := part.Header.Get("File-Info")
if fileInfoString == "" {
if f.IsDirectory() {
f.stat = NewDummyDirectoryFileInfo(name)
} else {
f.stat = NewDummyRegularFileInfo(name)
}
} else {
version, params, err := mime.ParseMediaType(fileInfoString)
if version != "ipfs/v1" {
return nil, fmt.Errorf(
"unrecognized File-Info version: %s (%s)", version, fileInfoString)
}
sizeString, ok := params["size"]
if !ok {
return nil, fmt.Errorf(
"File-Info missing \"size\" parameter: %s", fileInfoString)
}
size, err := strconv.ParseInt(sizeString, 0, 64)
if err != nil {
return nil, err
}
modeString, ok := params["mode"]
if !ok {
return nil, fmt.Errorf(
"File-Info missing \"mode\" parameter: %s", fileInfoString)
}
mode, err := strconv.ParseUint(modeString, 0, 32)
if err != nil {
return nil, err
}
modTimeString, ok := params["mod-time"]
if !ok {
return nil, fmt.Errorf(
"File-Info missing \"mod-time\" parameter: %s", fileInfoString)
}
modTime, err := time.Parse(time.RFC3339Nano, modTimeString)
if err != nil {
return nil, err
}
f.stat = &fileInfo{
name: name,
size: size,
mode: os.FileMode(mode),
modTime: modTime,
}
}

return f, nil
}

Expand Down Expand Up @@ -89,3 +143,7 @@ func (f *MultipartFile) Close() error {
}
return f.Part.Close()
}

func (f *MultipartFile) Stat() (fi os.FileInfo, err error) {
return f.stat, nil
}
23 changes: 21 additions & 2 deletions commands/files/readerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package files
import (
"errors"
"io"
"io/ioutil"
"os"
"strings"
)

// ReaderFile is a implementation of File created from an `io.Reader`.
Expand All @@ -18,6 +20,20 @@ func NewReaderFile(filename string, reader io.ReadCloser, stat os.FileInfo) *Rea
return &ReaderFile{filename, reader, stat}
}

func NewSymlink(path string) (File, error) {
stat, err := os.Lstat(path)
if err != nil {
return nil, err
}
target, err := os.Readlink(path)
if err != nil {
return nil, err
}
reader := strings.NewReader(target)
readCloser := ioutil.NopCloser(reader)
return &ReaderFile{path, readCloser, stat}, nil
}

func (f *ReaderFile) IsDirectory() bool {
return false
}
Expand All @@ -31,15 +47,18 @@ func (f *ReaderFile) FileName() string {
}

func (f *ReaderFile) Read(p []byte) (int, error) {
if f.reader == nil {
return 0, io.EOF
}
return f.reader.Read(p)
}

func (f *ReaderFile) Close() error {
return f.reader.Close()
}

func (f *ReaderFile) Stat() os.FileInfo {
return f.stat
func (f *ReaderFile) Stat() (fi os.FileInfo, err error) {
return f.stat, nil
}

func (f *ReaderFile) Size() (int64, error) {
Expand Down
25 changes: 22 additions & 3 deletions commands/files/serialfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,12 @@ func (f *serialFile) NextFile() (File, error) {

// open the next file
filePath := fp.Join(f.path, stat.Name())
file, err := os.Open(filePath)
file, err := os.OpenFile(filePath, (os.O_RDONLY | syscall.O_NOFOLLOW | syscall.O_NONBLOCK), 0)
if err != nil {
errno, err2 := Errno(err)
if err2 == nil && errno == syscall.ELOOP {
return NewSymlink(filePath)
}
return nil, err
}
f.current = file
Expand Down Expand Up @@ -115,8 +119,8 @@ func (f *serialFile) Close() error {
return nil
}

func (f *serialFile) Stat() os.FileInfo {
return f.stat
func (f *serialFile) Stat() (fi os.FileInfo, err error) {
return f.stat, nil
}

func (f *serialFile) Size() (int64, error) {
Expand Down Expand Up @@ -148,3 +152,18 @@ func size(stat os.FileInfo, filename string) (int64, error) {
}
return output, nil
}

func Errno(err error) (syscall.Errno, error) {
if err == nil {
return 0, err
}
err2, ok := err.(*os.PathError)
if !ok {
return 0, err
}
errno, ok := err2.Err.(syscall.Errno)
if !ok {
return 0, err
}
return errno, nil
}
20 changes: 18 additions & 2 deletions commands/files/slicefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package files
import (
"errors"
"io"
"os"
)

// SliceFile implements File, and provides simple directory handling.
Expand All @@ -11,11 +12,22 @@ import (
type SliceFile struct {
filename string
files []File
stat os.FileInfo
n int
}

func NewSliceFile(filename string, files []File) *SliceFile {
return &SliceFile{filename, files, 0}
func NewSliceFile(filename string, file *os.File, files []File) (f *SliceFile, err error) {
var stat os.FileInfo
if file == nil {
stat = NewDummyDirectoryFileInfo(filename)
} else {
stat, err = file.Stat()
if err != nil {
return nil, err
}
}

return &SliceFile{filename, files, stat, 0}, nil
}

func (f *SliceFile) IsDirectory() bool {
Expand Down Expand Up @@ -43,6 +55,10 @@ func (f *SliceFile) Close() error {
return ErrNotReader
}

func (f *SliceFile) Stat() (fi os.FileInfo, err error) {
return f.stat, nil
}

func (f *SliceFile) Peek(n int) File {
return f.files[n]
}
Expand Down
5 changes: 5 additions & 0 deletions commands/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ func getResponse(httpRes *http.Response, req cmds.Request) (cmds.Response, error

if err == io.EOF {
close(outChan)
errorString := httpRes.Trailer.Get("Error")
if errorString != "" {
err = fmt.Errorf(errorString)
res.SetError(err, cmds.ErrNormal)
}
return
}
outChan <- v
Expand Down
Loading