@cryptotaxi247 / kubo / commits / d993bc04d

implement symlinks in unixfs, first draft

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

Jeromy committed Aug 12, 2015 at 12:17 UTC d993bc04d6b21bd76a5a9f0a7924b48e0e09b275
10 files changed +152 -9
commands/files/linkfile.go new
+50
@@ -0,0 +1,50 @@
1 +package files
2 +
3 +import (
4 + "io"
5 + "os"
6 + "strings"
7 +)
8 +
9 +type Symlink struct {
10 + name string
11 + path string
12 + Target string
13 + stat os.FileInfo
14 +
15 + reader io.Reader
16 +}
17 +
18 +func NewLinkFile(name, path, target string, stat os.FileInfo) File {
19 + return &Symlink{
20 + name: name,
21 + path: path,
22 + Target: target,
23 + stat: stat,
24 + reader: strings.NewReader(target),
25 + }
26 +}
27 +
28 +func (lf *Symlink) IsDirectory() bool {
29 + return false
30 +}
31 +
32 +func (lf *Symlink) NextFile() (File, error) {
33 + return nil, io.EOF
34 +}
35 +
36 +func (f *Symlink) FileName() string {
37 + return f.name
38 +}
39 +
40 +func (f *Symlink) Close() error {
41 + return nil
42 +}
43 +
44 +func (f *Symlink) FullPath() string {
45 + return f.path
46 +}
47 +
48 +func (f *Symlink) Read(b []byte) (int, error) {
49 + return f.reader.Read(b)
50 +}
commands/files/multipartfile.go
+12
@@ -1,6 +1,7 @@
1 package files
2
3 import (
4 + "io/ioutil"
5 "mime"
6 "mime/multipart"
7 "net/http"
@@ -30,6 +31,17 @@ func NewFileFromPart(part *multipart.Part) (File, error) {
31 }
32
33 contentType := part.Header.Get(contentTypeHeader)
34 + if contentType == "symlink" {
35 + out, err := ioutil.ReadAll(part)
36 + if err != nil {
37 + return nil, err
38 + }
39 +
40 + return &Symlink{
41 + Target: string(out),
42 + name: f.FileName(),
43 + }, nil
44 + }
45
46 var params map[string]string
47 var err error
commands/files/serialfile.go
+15
@@ -84,10 +84,25 @@ func (f *serialFile) NextFile() (File, error) {
84 // open the next file
85 fileName := fp.Join(f.name, stat.Name())
86 filePath := fp.Join(f.path, stat.Name())
87 + st, err := os.Lstat(filePath)
88 + if err != nil {
89 + return nil, err
90 + }
91 +
92 + if st.Mode()&os.ModeSymlink != 0 {
93 + f.current = nil
94 + target, err := os.Readlink(filePath)
95 + if err != nil {
96 + return nil, err
97 + }
98 + return NewLinkFile(fileName, filePath, target, st), nil
99 + }
100 +
101 file, err := os.Open(filePath)
102 if err != nil {
103 return nil, err
104 }
105 +
106 f.current = file
107
108 // recursively call the constructor on the next file
commands/http/multifilereader.go
+13 -8
@@ -64,13 +64,23 @@ func (mfr *MultiFileReader) Read(buf []byte) (written int, err error) {
64
65 // handle starting a new file part
66 if !mfr.closed {
67 - if file.IsDirectory() {
67 +
68 + var contentType string
69 + if s, ok := file.(*files.Symlink); ok {
70 + mfr.currentFile = s
71 +
72 + // TODO(why): this is a hack. pick a real contentType
73 + contentType = "symlink"
74 + } else if file.IsDirectory() {
75 // if file is a directory, create a multifilereader from it
76 // (using 'multipart/mixed')
70 - mfr.currentFile = NewMultiFileReader(file, false)
77 + nmfr := NewMultiFileReader(file, false)
78 + mfr.currentFile = nmfr
79 + contentType = fmt.Sprintf("multipart/mixed; boundary=%s", nmfr.Boundary())
80 } else {
81 // otherwise, use the file as a reader to read its contents
82 mfr.currentFile = file
83 + contentType = "application/octet-stream"
84 }
85
86 // write the boundary and headers
@@ -83,12 +93,7 @@ func (mfr *MultiFileReader) Read(buf []byte) (written int, err error) {
93 header.Set("Content-Disposition", fmt.Sprintf("file; filename=\"%s\"", filename))
94 }
95
86 - if file.IsDirectory() {
87 - boundary := mfr.currentFile.(*MultiFileReader).Boundary()
88 - header.Set("Content-Type", fmt.Sprintf("multipart/mixed; boundary=%s", boundary))
89 - } else {
90 - header.Set("Content-Type", "application/octet-stream")
91 - }
96 + header.Set("Content-Type", contentType)
97
98 _, err := mfr.mpWriter.CreatePart(header)
99 if err != nil {
core/commands/add.go
+18
@@ -143,6 +143,7 @@ remains to be implemented.
143 return nil // done
144 }
145
146 + log.Errorf("FILE: %#v", file)
147 if _, err := fileAdder.addFile(file); err != nil {
148 return err
149 }
@@ -359,6 +360,23 @@ func (params *adder) addFile(file files.File) (*dag.Node, error) {
360 return params.addDir(file)
361 }
362
363 + if s, ok := file.(*files.Symlink); ok {
364 + log.Error("SYMLINK: ", s)
365 + log.Error(s.Target)
366 + log.Error(s.FileName())
367 + dagnode := &dag.Node{
368 + Data: ft.SymlinkData(s.Target),
369 + }
370 +
371 + _, err := params.node.DAG.Add(dagnode)
372 + if err != nil {
373 + return nil, err
374 + }
375 +
376 + err = params.addNode(dagnode, s.FileName())
377 + return dagnode, err
378 + }
379 +
380 // if the progress flag was specified, wrap the file so that we can send
381 // progress updates to the client (over the output channel)
382 var reader io.Reader = file
fuse/readonly/readonly_unix.go
+19
@@ -7,6 +7,8 @@ import (
7 "fmt"
8 "io"
9 "os"
10 + "syscall"
11 + "time"
12
13 fuse "github.com/ipfs/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
14 fs "github.com/ipfs/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
@@ -58,6 +60,8 @@ func (s *Root) Lookup(ctx context.Context, name string) (fs.Node, error) {
60 return nil, fuse.ENOENT
61 }
62
63 + log.Error("RESOLVE: ", name)
64 + ctx, _ = context.WithTimeout(ctx, time.Second/2)
65 nd, err := s.Ipfs.Resolver.ResolvePath(ctx, path.Path(name))
66 if err != nil {
67 // todo: make this error more versatile.
@@ -118,6 +122,13 @@ func (s *Node) Attr(ctx context.Context, a *fuse.Attr) error {
122 Uid: uint32(os.Getuid()),
123 Gid: uint32(os.Getgid()),
124 }
125 + case ftpb.Data_Symlink:
126 + *a = fuse.Attr{
127 + Mode: 0777 | os.ModeSymlink,
128 + Size: uint64(len(s.cached.GetData())),
129 + Uid: uint32(os.Getuid()),
130 + Gid: uint32(os.Getgid()),
131 + }
132
133 default:
134 return fmt.Errorf("Invalid data type - %s", s.cached.GetType())
@@ -155,6 +166,13 @@ func (s *Node) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
166 return nil, fuse.ENOENT
167 }
168
169 +func (s *Node) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {
170 + if s.cached.GetType() != ftpb.Data_Symlink {
171 + return "", fuse.Errno(syscall.EINVAL)
172 + }
173 + return string(s.cached.GetData()), nil
174 +}
175 +
176 func (s *Node) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
177
178 k, err := s.Nd.Key()
@@ -204,6 +222,7 @@ type roNode interface {
222 fs.HandleReader
223 fs.Node
224 fs.NodeStringLookuper
225 + fs.NodeReadlinker
226 }
227
228 var _ roNode = (*Node)(nil)
unixfs/format.go
+14
@@ -77,6 +77,20 @@ func WrapData(b []byte) []byte {
77 return out
78 }
79
80 +func SymlinkData(path string) []byte {
81 + pbdata := new(pb.Data)
82 + typ := pb.Data_Symlink
83 + pbdata.Data = []byte(path)
84 + pbdata.Type = &typ
85 +
86 + out, err := proto.Marshal(pbdata)
87 + if err != nil {
88 + panic(err)
89 + }
90 +
91 + return out
92 +}
93 +
94 func UnwrapData(data []byte) ([]byte, error) {
95 pbdata := new(pb.Data)
96 err := proto.Unmarshal(data, pbdata)
unixfs/io/dagreader.go
+6
@@ -17,6 +17,8 @@ import (
17
18 var ErrIsDir = errors.New("this dag node is a directory")
19
20 +var ErrCantReadSymlinks = errors.New("cannot currently read symlinks")
21 +
22 // DagReader provides a way to easily read the data contained in a dag.
23 type DagReader struct {
24 serv mdag.DAGService
@@ -79,6 +81,8 @@ func NewDagReader(ctx context.Context, n *mdag.Node, serv mdag.DAGService) (*Dag
81 return nil, err
82 }
83 return NewDagReader(ctx, child, serv)
84 + case ftpb.Data_Symlink:
85 + return nil, ErrCantReadSymlinks
86 default:
87 return nil, ft.ErrUnrecognizedType
88 }
@@ -130,6 +134,8 @@ func (dr *DagReader) precalcNextBuf(ctx context.Context) error {
134 return nil
135 case ftpb.Data_Metadata:
136 return errors.New("Shouldnt have had metadata object inside file")
137 + case ftpb.Data_Symlink:
138 + return errors.New("shouldnt have had symlink inside file")
139 default:
140 return ft.ErrUnrecognizedType
141 }
unixfs/pb/unixfs.pb.go
+4 -1
@@ -14,7 +14,7 @@ It has these top-level messages:
14 */
15 package unixfs_pb
16
17 -import proto "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/gogo/protobuf/proto"
17 +import proto "github.com/gogo/protobuf/proto"
18 import math "math"
19
20 // Reference imports to suppress errors if they are not otherwise used.
@@ -28,6 +28,7 @@ const (
28 Data_Directory Data_DataType = 1
29 Data_File Data_DataType = 2
30 Data_Metadata Data_DataType = 3
31 + Data_Symlink Data_DataType = 4
32 )
33
34 var Data_DataType_name = map[int32]string{
@@ -35,12 +36,14 @@ var Data_DataType_name = map[int32]string{
36 1: "Directory",
37 2: "File",
38 3: "Metadata",
39 + 4: "Symlink",
40 }
41 var Data_DataType_value = map[string]int32{
42 "Raw": 0,
43 "Directory": 1,
44 "File": 2,
45 "Metadata": 3,
46 + "Symlink": 4,
47 }
48
49 func (x Data_DataType) Enum() *Data_DataType {
unixfs/pb/unixfs.proto
+1
@@ -6,6 +6,7 @@ message Data {
6 Directory = 1;
7 File = 2;
8 Metadata = 3;
9 + Symlink = 4;
10 }
11
12 required DataType Type = 1;