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;