Skip to content

Commit

Permalink
implement symlinks in unixfs, first draft
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Jeromy <[email protected]>
  • Loading branch information
whyrusleeping committed Aug 30, 2015
1 parent 8c65290 commit d993bc0
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 9 deletions.
50 changes: 50 additions & 0 deletions commands/files/linkfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package files

import (
"io"
"os"
"strings"
)

type Symlink struct {
name string
path string
Target string
stat os.FileInfo

reader io.Reader
}

func NewLinkFile(name, path, target string, stat os.FileInfo) File {
return &Symlink{
name: name,
path: path,
Target: target,
stat: stat,
reader: strings.NewReader(target),
}
}

func (lf *Symlink) IsDirectory() bool {
return false
}

func (lf *Symlink) NextFile() (File, error) {
return nil, io.EOF
}

func (f *Symlink) FileName() string {
return f.name
}

func (f *Symlink) Close() error {
return nil
}

func (f *Symlink) FullPath() string {
return f.path
}

func (f *Symlink) Read(b []byte) (int, error) {
return f.reader.Read(b)
}
12 changes: 12 additions & 0 deletions commands/files/multipartfile.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package files

import (
"io/ioutil"
"mime"
"mime/multipart"
"net/http"
Expand Down Expand Up @@ -30,6 +31,17 @@ func NewFileFromPart(part *multipart.Part) (File, error) {
}

contentType := part.Header.Get(contentTypeHeader)
if contentType == "symlink" {
out, err := ioutil.ReadAll(part)
if err != nil {
return nil, err
}

return &Symlink{
Target: string(out),
name: f.FileName(),
}, nil
}

var params map[string]string
var err error
Expand Down
15 changes: 15 additions & 0 deletions commands/files/serialfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,25 @@ func (f *serialFile) NextFile() (File, error) {
// open the next file
fileName := fp.Join(f.name, stat.Name())
filePath := fp.Join(f.path, stat.Name())
st, err := os.Lstat(filePath)
if err != nil {
return nil, err
}

if st.Mode()&os.ModeSymlink != 0 {
f.current = nil
target, err := os.Readlink(filePath)
if err != nil {
return nil, err
}
return NewLinkFile(fileName, filePath, target, st), nil
}

file, err := os.Open(filePath)
if err != nil {
return nil, err
}

f.current = file

// recursively call the constructor on the next file
Expand Down
21 changes: 13 additions & 8 deletions commands/http/multifilereader.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,23 @@ func (mfr *MultiFileReader) Read(buf []byte) (written int, err error) {

// handle starting a new file part
if !mfr.closed {
if file.IsDirectory() {

var contentType string
if s, ok := file.(*files.Symlink); ok {
mfr.currentFile = s

// TODO(why): this is a hack. pick a real contentType
contentType = "symlink"
} else if file.IsDirectory() {
// if file is a directory, create a multifilereader from it
// (using 'multipart/mixed')
mfr.currentFile = NewMultiFileReader(file, false)
nmfr := NewMultiFileReader(file, false)
mfr.currentFile = nmfr
contentType = fmt.Sprintf("multipart/mixed; boundary=%s", nmfr.Boundary())
} else {
// otherwise, use the file as a reader to read its contents
mfr.currentFile = file
contentType = "application/octet-stream"
}

// write the boundary and headers
Expand All @@ -83,12 +93,7 @@ func (mfr *MultiFileReader) Read(buf []byte) (written int, err error) {
header.Set("Content-Disposition", fmt.Sprintf("file; filename=\"%s\"", filename))
}

if file.IsDirectory() {
boundary := mfr.currentFile.(*MultiFileReader).Boundary()
header.Set("Content-Type", fmt.Sprintf("multipart/mixed; boundary=%s", boundary))
} else {
header.Set("Content-Type", "application/octet-stream")
}
header.Set("Content-Type", contentType)

_, err := mfr.mpWriter.CreatePart(header)
if err != nil {
Expand Down
18 changes: 18 additions & 0 deletions core/commands/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ remains to be implemented.
return nil // done
}

log.Errorf("FILE: %#v", file)
if _, err := fileAdder.addFile(file); err != nil {
return err
}
Expand Down Expand Up @@ -359,6 +360,23 @@ func (params *adder) addFile(file files.File) (*dag.Node, error) {
return params.addDir(file)
}

if s, ok := file.(*files.Symlink); ok {
log.Error("SYMLINK: ", s)
log.Error(s.Target)
log.Error(s.FileName())
dagnode := &dag.Node{
Data: ft.SymlinkData(s.Target),
}

_, err := params.node.DAG.Add(dagnode)
if err != nil {
return nil, err
}

err = params.addNode(dagnode, s.FileName())
return dagnode, err
}

// if the progress flag was specified, wrap the file so that we can send
// progress updates to the client (over the output channel)
var reader io.Reader = file
Expand Down
19 changes: 19 additions & 0 deletions fuse/readonly/readonly_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"fmt"
"io"
"os"
"syscall"
"time"

fuse "github.com/ipfs/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
fs "github.com/ipfs/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
Expand Down Expand Up @@ -58,6 +60,8 @@ func (s *Root) Lookup(ctx context.Context, name string) (fs.Node, error) {
return nil, fuse.ENOENT
}

log.Error("RESOLVE: ", name)
ctx, _ = context.WithTimeout(ctx, time.Second/2)
nd, err := s.Ipfs.Resolver.ResolvePath(ctx, path.Path(name))
if err != nil {
// todo: make this error more versatile.
Expand Down Expand Up @@ -118,6 +122,13 @@ func (s *Node) Attr(ctx context.Context, a *fuse.Attr) error {
Uid: uint32(os.Getuid()),
Gid: uint32(os.Getgid()),
}
case ftpb.Data_Symlink:
*a = fuse.Attr{
Mode: 0777 | os.ModeSymlink,
Size: uint64(len(s.cached.GetData())),
Uid: uint32(os.Getuid()),
Gid: uint32(os.Getgid()),
}

default:
return fmt.Errorf("Invalid data type - %s", s.cached.GetType())
Expand Down Expand Up @@ -155,6 +166,13 @@ func (s *Node) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
return nil, fuse.ENOENT
}

func (s *Node) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {
if s.cached.GetType() != ftpb.Data_Symlink {
return "", fuse.Errno(syscall.EINVAL)
}
return string(s.cached.GetData()), nil
}

func (s *Node) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {

k, err := s.Nd.Key()
Expand Down Expand Up @@ -204,6 +222,7 @@ type roNode interface {
fs.HandleReader
fs.Node
fs.NodeStringLookuper
fs.NodeReadlinker
}

var _ roNode = (*Node)(nil)
Expand Down
14 changes: 14 additions & 0 deletions unixfs/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ func WrapData(b []byte) []byte {
return out
}

func SymlinkData(path string) []byte {
pbdata := new(pb.Data)
typ := pb.Data_Symlink
pbdata.Data = []byte(path)
pbdata.Type = &typ

out, err := proto.Marshal(pbdata)
if err != nil {
panic(err)
}

return out
}

func UnwrapData(data []byte) ([]byte, error) {
pbdata := new(pb.Data)
err := proto.Unmarshal(data, pbdata)
Expand Down
6 changes: 6 additions & 0 deletions unixfs/io/dagreader.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (

var ErrIsDir = errors.New("this dag node is a directory")

var ErrCantReadSymlinks = errors.New("cannot currently read symlinks")

// DagReader provides a way to easily read the data contained in a dag.
type DagReader struct {
serv mdag.DAGService
Expand Down Expand Up @@ -79,6 +81,8 @@ func NewDagReader(ctx context.Context, n *mdag.Node, serv mdag.DAGService) (*Dag
return nil, err
}
return NewDagReader(ctx, child, serv)
case ftpb.Data_Symlink:
return nil, ErrCantReadSymlinks
default:
return nil, ft.ErrUnrecognizedType
}
Expand Down Expand Up @@ -130,6 +134,8 @@ func (dr *DagReader) precalcNextBuf(ctx context.Context) error {
return nil
case ftpb.Data_Metadata:
return errors.New("Shouldnt have had metadata object inside file")
case ftpb.Data_Symlink:
return errors.New("shouldnt have had symlink inside file")
default:
return ft.ErrUnrecognizedType
}
Expand Down
5 changes: 4 additions & 1 deletion unixfs/pb/unixfs.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions unixfs/pb/unixfs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ message Data {
Directory = 1;
File = 2;
Metadata = 3;
Symlink = 4;
}

required DataType Type = 1;
Expand Down

0 comments on commit d993bc0

Please sign in to comment.