swarm/api: FUSE read-write support (#13872)
- Moved fuse related code in a new package, swarm/fuse - Added write support - Create new files - Delete existing files - Append to files (with limitations) - More test coverage
This commit is contained in:
		
							parent
							
								
									dd37064a15
								
							
						
					
					
						commit
						1d1d988aa7
					
				
							
								
								
									
										191
									
								
								swarm/api/api.go
									
									
									
									
									
								
							
							
						
						
									
										191
									
								
								swarm/api/api.go
									
									
									
									
									
								
							| @ -25,9 +25,13 @@ import ( | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"bytes" | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/log" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/storage" | ||||
| 	"mime" | ||||
| 	"path/filepath" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| @ -59,6 +63,13 @@ func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) { | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // to be used only in TEST
 | ||||
| func (self *Api) Upload(uploadDir, index string) (hash string, err error) { | ||||
| 	fs := NewFileSystem(self) | ||||
| 	hash, err = fs.Upload(uploadDir, index) | ||||
| 	return hash, err | ||||
| } | ||||
| 
 | ||||
| // DPA reader API
 | ||||
| func (self *Api) Retrieve(key storage.Key) storage.LazySectionReader { | ||||
| 	return self.dpa.Retrieve(key) | ||||
| @ -111,7 +122,7 @@ func (self *Api) Put(content, contentType string) (storage.Key, error) { | ||||
| } | ||||
| 
 | ||||
| // Get uses iterative manifest retrieval and prefix matching
 | ||||
| // to resolve path to content using dpa retrieve
 | ||||
| // to resolve basePath to content using dpa retrieve
 | ||||
| // it returns a section reader, mimeType, status and an error
 | ||||
| func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionReader, mimeType string, status int, err error) { | ||||
| 	trie, err := loadManifest(self.dpa, key, nil) | ||||
| @ -160,3 +171,181 @@ func (self *Api) Modify(key storage.Key, path, contentHash, contentType string) | ||||
| 	} | ||||
| 	return trie.hash, nil | ||||
| } | ||||
| 
 | ||||
| func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver bool) (storage.Key, string, error) { | ||||
| 
 | ||||
| 	uri, err := Parse("bzz:/" + mhash) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 	mkey, err := self.Resolve(uri) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 
 | ||||
| 	// trim the root dir we added
 | ||||
| 	if path[:1] == "/" { | ||||
| 		path = path[1:] | ||||
| 	} | ||||
| 
 | ||||
| 	entry := &ManifestEntry{ | ||||
| 		Path:        filepath.Join(path, fname), | ||||
| 		ContentType: mime.TypeByExtension(filepath.Ext(fname)), | ||||
| 		Mode:        0700, | ||||
| 		Size:        int64(len(content)), | ||||
| 		ModTime:     time.Now(), | ||||
| 	} | ||||
| 
 | ||||
| 	mw, err := self.NewManifestWriter(mkey, nil) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 
 | ||||
| 	fkey, err := mw.AddEntry(bytes.NewReader(content), entry) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 
 | ||||
| 	newMkey, err := mw.Store() | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	return fkey, newMkey.String(), nil | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (string, error) { | ||||
| 
 | ||||
| 	uri, err := Parse("bzz:/" + mhash) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	mkey, err := self.Resolve(uri) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 
 | ||||
| 	// trim the root dir we added
 | ||||
| 	if path[:1] == "/" { | ||||
| 		path = path[1:] | ||||
| 	} | ||||
| 
 | ||||
| 	mw, err := self.NewManifestWriter(mkey, nil) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 
 | ||||
| 	err = mw.RemoveEntry(filepath.Join(path, fname)) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 
 | ||||
| 	newMkey, err := mw.Store() | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	return newMkey.String(), nil | ||||
| } | ||||
| 
 | ||||
| func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, content []byte, oldKey storage.Key, offset int64, addSize int64, nameresolver bool) (storage.Key, string, error) { | ||||
| 
 | ||||
| 	buffSize := offset + addSize | ||||
| 	if buffSize < existingSize { | ||||
| 		buffSize = existingSize | ||||
| 	} | ||||
| 
 | ||||
| 	buf := make([]byte, buffSize) | ||||
| 
 | ||||
| 	oldReader := self.Retrieve(oldKey) | ||||
| 	io.ReadAtLeast(oldReader, buf, int(offset)) | ||||
| 
 | ||||
| 	newReader := bytes.NewReader(content) | ||||
| 	io.ReadAtLeast(newReader, buf[offset:], int(addSize)) | ||||
| 
 | ||||
| 	if buffSize < existingSize { | ||||
| 		io.ReadAtLeast(oldReader, buf[addSize:], int(buffSize)) | ||||
| 	} | ||||
| 
 | ||||
| 	combinedReader := bytes.NewReader(buf) | ||||
| 	totalSize := int64(len(buf)) | ||||
| 
 | ||||
| 	// TODO(jmozah): to append using pyramid chunker when it is ready
 | ||||
| 	//oldReader := self.Retrieve(oldKey)
 | ||||
| 	//newReader := bytes.NewReader(content)
 | ||||
| 	//combinedReader := io.MultiReader(oldReader, newReader)
 | ||||
| 
 | ||||
| 	uri, err := Parse("bzz:/" + mhash) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 	mkey, err := self.Resolve(uri) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 
 | ||||
| 	// trim the root dir we added
 | ||||
| 	if path[:1] == "/" { | ||||
| 		path = path[1:] | ||||
| 	} | ||||
| 
 | ||||
| 	mw, err := self.NewManifestWriter(mkey, nil) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 
 | ||||
| 	err = mw.RemoveEntry(filepath.Join(path, fname)) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 
 | ||||
| 	entry := &ManifestEntry{ | ||||
| 		Path:        filepath.Join(path, fname), | ||||
| 		ContentType: mime.TypeByExtension(filepath.Ext(fname)), | ||||
| 		Mode:        0700, | ||||
| 		Size:        totalSize, | ||||
| 		ModTime:     time.Now(), | ||||
| 	} | ||||
| 
 | ||||
| 	fkey, err := mw.AddEntry(io.Reader(combinedReader), entry) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
| 
 | ||||
| 	newMkey, err := mw.Store() | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	return fkey, newMkey.String(), nil | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func (self *Api) BuildDirectoryTree(mhash string, nameresolver bool) (key storage.Key, manifestEntryMap map[string]*manifestTrieEntry, err error) { | ||||
| 
 | ||||
| 	uri, err := Parse("bzz:/" + mhash) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	key, err = self.Resolve(uri) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	quitC := make(chan bool) | ||||
| 	rootTrie, err := loadManifest(self.dpa, key, quitC) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, fmt.Errorf("can't load manifest %v: %v", key.String(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	manifestEntryMap = map[string]*manifestTrieEntry{} | ||||
| 	err = rootTrie.listWithPrefix(uri.Path, quitC, func(entry *manifestTrieEntry, suffix string) { | ||||
| 		manifestEntryMap[suffix] = entry | ||||
| 	}) | ||||
| 
 | ||||
| 	return key, manifestEntryMap, nil | ||||
| } | ||||
|  | ||||
| @ -68,7 +68,6 @@ func (self *FileSystem) Upload(lpath, index string) (string, error) { | ||||
| 		log.Debug(fmt.Sprintf("uploading '%s'", localpath)) | ||||
| 		err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { | ||||
| 			if (err == nil) && !info.IsDir() { | ||||
| 				//fmt.Printf("lp %s  path %s\n", localpath, path)
 | ||||
| 				if len(path) <= start { | ||||
| 					return fmt.Errorf("Path is too short") | ||||
| 				} | ||||
| @ -170,7 +169,7 @@ func (self *FileSystem) Upload(lpath, index string) (string, error) { | ||||
| 	return hs, err2 | ||||
| } | ||||
| 
 | ||||
| // Download replicates the manifest path structure on the local filesystem
 | ||||
| // Download replicates the manifest basePath structure on the local filesystem
 | ||||
| // under localpath
 | ||||
| //
 | ||||
| // DEPRECATED: Use the HTTP API instead
 | ||||
| @ -269,7 +268,7 @@ func (self *FileSystem) Download(bzzpath, localpath string) error { | ||||
| } | ||||
| 
 | ||||
| func retrieveToFile(quitC chan bool, dpa *storage.DPA, key storage.Key, path string) error { | ||||
| 	f, err := os.Create(path) // TODO: path separators
 | ||||
| 	f, err := os.Create(path) // TODO: basePath separators
 | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| @ -1,133 +0,0 @@ | ||||
| // Copyright 2017 The go-ethereum Authors
 | ||||
| // This file is part of the go-ethereum library.
 | ||||
| //
 | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU Lesser General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| // The go-ethereum library is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | ||||
| // GNU Lesser General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| // +build linux darwin freebsd
 | ||||
| 
 | ||||
| // Data structures used for Fuse filesystem, serving directories and serving files to Fuse driver.
 | ||||
| 
 | ||||
| package api | ||||
| 
 | ||||
| import ( | ||||
| 	"io" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"bazil.org/fuse" | ||||
| 	"bazil.org/fuse/fs" | ||||
| 	"github.com/ethereum/go-ethereum/log" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/storage" | ||||
| 	"golang.org/x/net/context" | ||||
| ) | ||||
| 
 | ||||
| type FS struct { | ||||
| 	root *Dir | ||||
| } | ||||
| 
 | ||||
| type Dir struct { | ||||
| 	inode       uint64 | ||||
| 	name        string | ||||
| 	path        string | ||||
| 	directories []*Dir | ||||
| 	files       []*File | ||||
| } | ||||
| 
 | ||||
| type File struct { | ||||
| 	inode    uint64 | ||||
| 	name     string | ||||
| 	path     string | ||||
| 	key      storage.Key | ||||
| 	swarmApi *Api | ||||
| 	fileSize uint64 | ||||
| 	reader   storage.LazySectionReader | ||||
| } | ||||
| 
 | ||||
| // Functions which satisfy the Fuse File System requests
 | ||||
| func (filesystem *FS) Root() (fs.Node, error) { | ||||
| 	return filesystem.root, nil | ||||
| } | ||||
| 
 | ||||
| func (directory *Dir) Attr(ctx context.Context, a *fuse.Attr) error { | ||||
| 	a.Inode = directory.inode | ||||
| 	//TODO: need to get permission as argument
 | ||||
| 	a.Mode = os.ModeDir | 0500 | ||||
| 	a.Uid = uint32(os.Getuid()) | ||||
| 	a.Gid = uint32(os.Getegid()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (directory *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { | ||||
| 	if directory.files != nil { | ||||
| 		for _, n := range directory.files { | ||||
| 			if n.name == name { | ||||
| 				return n, nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if directory.directories != nil { | ||||
| 		for _, n := range directory.directories { | ||||
| 			if n.name == name { | ||||
| 				return n, nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, fuse.ENOENT | ||||
| } | ||||
| 
 | ||||
| func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { | ||||
| 	var children []fuse.Dirent | ||||
| 	if d.files != nil { | ||||
| 		for _, file := range d.files { | ||||
| 			children = append(children, fuse.Dirent{Inode: file.inode, Type: fuse.DT_File, Name: file.name}) | ||||
| 		} | ||||
| 	} | ||||
| 	if d.directories != nil { | ||||
| 		for _, dir := range d.directories { | ||||
| 			children = append(children, fuse.Dirent{Inode: dir.inode, Type: fuse.DT_Dir, Name: dir.name}) | ||||
| 		} | ||||
| 	} | ||||
| 	return children, nil | ||||
| } | ||||
| 
 | ||||
| func (file *File) Attr(ctx context.Context, a *fuse.Attr) error { | ||||
| 	a.Inode = file.inode | ||||
| 	//TODO: need to get permission as argument
 | ||||
| 	a.Mode = 0500 | ||||
| 	a.Uid = uint32(os.Getuid()) | ||||
| 	a.Gid = uint32(os.Getegid()) | ||||
| 
 | ||||
| 	reader := file.swarmApi.Retrieve(file.key) | ||||
| 	quitC := make(chan bool) | ||||
| 	size, err := reader.Size(quitC) | ||||
| 	if err != nil { | ||||
| 		log.Warn("Couldnt file size of file %s : %v", file.path, err) | ||||
| 		a.Size = uint64(0) | ||||
| 	} | ||||
| 	a.Size = uint64(size) | ||||
| 	file.fileSize = a.Size | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| var _ = fs.HandleReader(&File{}) | ||||
| 
 | ||||
| func (file *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { | ||||
| 	buf := make([]byte, req.Size) | ||||
| 	reader := file.swarmApi.Retrieve(file.key) | ||||
| 	n, err := reader.ReadAt(buf, req.Offset) | ||||
| 	if err == io.ErrUnexpectedEOF || err == io.EOF { | ||||
| 		err = nil | ||||
| 	} | ||||
| 	resp.Data = buf[:n] | ||||
| 	return err | ||||
| } | ||||
| @ -162,7 +162,7 @@ func (m *ManifestWalker) walk(trie *manifestTrie, prefix string, walkFn WalkFn) | ||||
| 
 | ||||
| type manifestTrie struct { | ||||
| 	dpa     *storage.DPA | ||||
| 	entries [257]*manifestTrieEntry // indexed by first character of path, entries[256] is the empty path entry
 | ||||
| 	entries [257]*manifestTrieEntry // indexed by first character of basePath, entries[256] is the empty basePath entry
 | ||||
| 	hash    storage.Key             // if hash != nil, it is stored
 | ||||
| } | ||||
| 
 | ||||
| @ -340,6 +340,7 @@ func (self *manifestTrie) recalcAndStore() error { | ||||
| 			} | ||||
| 			list.Entries = append(list.Entries, entry.ManifestEntry) | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	manifest, err := json.Marshal(list) | ||||
|  | ||||
| @ -83,7 +83,7 @@ func (self *Storage) Get(bzzpath string) (*Response, error) { | ||||
| 	return &Response{mimeType, status, expsize, string(body[:size])}, err | ||||
| } | ||||
| 
 | ||||
| // Modify(rootHash, path, contentHash, contentType) takes th e manifest trie rooted in rootHash,
 | ||||
| // Modify(rootHash, basePath, contentHash, contentType) takes th e manifest trie rooted in rootHash,
 | ||||
| // and merge on  to it. creating an entry w conentType (mime)
 | ||||
| //
 | ||||
| // DEPRECATED: Use the HTTP API instead
 | ||||
|  | ||||
| @ -1,115 +0,0 @@ | ||||
| // Copyright 2016 The go-ethereum Authors
 | ||||
| // This file is part of the go-ethereum library.
 | ||||
| //
 | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU Lesser General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| // The go-ethereum library is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | ||||
| // GNU Lesser General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| package api | ||||
| 
 | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| var testUploadDir, _ = ioutil.TempDir(os.TempDir(), "fuse-source") | ||||
| var testMountDir, _ = ioutil.TempDir(os.TempDir(), "fuse-dest") | ||||
| 
 | ||||
| func testFuseFileSystem(t *testing.T, f func(*FileSystem)) { | ||||
| 	testApi(t, func(api *Api) { | ||||
| 		f(NewFileSystem(api)) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func createTestFiles(t *testing.T, files []string) { | ||||
| 	os.RemoveAll(testUploadDir) | ||||
| 	os.RemoveAll(testMountDir) | ||||
| 	defer os.MkdirAll(testMountDir, 0777) | ||||
| 
 | ||||
| 	for f := range files { | ||||
| 		actualPath := filepath.Join(testUploadDir, files[f]) | ||||
| 		filePath := filepath.Dir(actualPath) | ||||
| 
 | ||||
| 		err := os.MkdirAll(filePath, 0777) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Error creating directory '%v' : %v", filePath, err) | ||||
| 		} | ||||
| 
 | ||||
| 		_, err1 := os.OpenFile(actualPath, os.O_RDONLY|os.O_CREATE, 0666) | ||||
| 		if err1 != nil { | ||||
| 			t.Fatalf("Error creating file %v: %v", actualPath, err1) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func compareFiles(t *testing.T, files []string) { | ||||
| 	for f := range files { | ||||
| 		sourceFile := filepath.Join(testUploadDir, files[f]) | ||||
| 		destinationFile := filepath.Join(testMountDir, files[f]) | ||||
| 
 | ||||
| 		sfinfo, err := os.Stat(sourceFile) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Source file %v missing in mount: %v", files[f], err) | ||||
| 		} | ||||
| 
 | ||||
| 		dfinfo, err := os.Stat(destinationFile) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Destination file %v missing in mount: %v", files[f], err) | ||||
| 		} | ||||
| 
 | ||||
| 		if sfinfo.Size() != dfinfo.Size() { | ||||
| 			t.Fatalf("Size mismatch  source (%v) vs destination(%v)", sfinfo.Size(), dfinfo.Size()) | ||||
| 		} | ||||
| 
 | ||||
| 		if dfinfo.Mode().Perm().String() != "-r-x------" { | ||||
| 			t.Fatalf("Permission is not 0500for file: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func doHashTest(fs *FileSystem, t *testing.T, ensName string, files ...string) { | ||||
| 	createTestFiles(t, files) | ||||
| 	bzzhash, err := fs.Upload(testUploadDir, "") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Error uploading directory %v: %v", testUploadDir, err) | ||||
| 	} | ||||
| 
 | ||||
| 	swarmfs := NewSwarmFS(fs.api) | ||||
| 	defer swarmfs.Stop() | ||||
| 
 | ||||
| 	_, err = swarmfs.Mount(bzzhash, testMountDir) | ||||
| 	if isFUSEUnsupportedError(err) { | ||||
| 		t.Skip("FUSE not supported:", err) | ||||
| 	} else if err != nil { | ||||
| 		t.Fatalf("Error mounting hash %v: %v", bzzhash, err) | ||||
| 	} | ||||
| 
 | ||||
| 	compareFiles(t, files) | ||||
| 
 | ||||
| 	if _, err := swarmfs.Unmount(testMountDir); err != nil { | ||||
| 		t.Fatalf("Error unmounting path %v: %v", testMountDir, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // mounting with manifest Hash
 | ||||
| func TestFuseMountingScenarios(t *testing.T) { | ||||
| 	testFuseFileSystem(t, func(fs *FileSystem) { | ||||
| 		//doHashTest(fs,t, "test","1.txt")
 | ||||
| 		doHashTest(fs, t, "", "1.txt") | ||||
| 		doHashTest(fs, t, "", "1.txt", "11.txt", "111.txt", "two/2.txt", "two/two/2.txt", "three/3.txt") | ||||
| 		doHashTest(fs, t, "", "1/2/3/4/5/6/7/8/9/10/11/12/1.txt") | ||||
| 		doHashTest(fs, t, "", "one/one.txt", "one.txt", "once/one.txt", "one/one/one.txt") | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										155
									
								
								swarm/fuse/fuse_dir.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								swarm/fuse/fuse_dir.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,155 @@ | ||||
| // Copyright 2017 The go-ethereum Authors
 | ||||
| // This file is part of the go-ethereum library.
 | ||||
| //
 | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU Lesser General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| // The go-ethereum library is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | ||||
| // GNU Lesser General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| // +build linux darwin freebsd
 | ||||
| 
 | ||||
| package fuse | ||||
| 
 | ||||
| import ( | ||||
| 	"bazil.org/fuse" | ||||
| 	"bazil.org/fuse/fs" | ||||
| 	"golang.org/x/net/context" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"sync" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	_ fs.Node                = (*SwarmDir)(nil) | ||||
| 	_ fs.NodeRequestLookuper = (*SwarmDir)(nil) | ||||
| 	_ fs.HandleReadDirAller  = (*SwarmDir)(nil) | ||||
| 	_ fs.NodeCreater         = (*SwarmDir)(nil) | ||||
| 	_ fs.NodeRemover         = (*SwarmDir)(nil) | ||||
| 	_ fs.NodeMkdirer         = (*SwarmDir)(nil) | ||||
| ) | ||||
| 
 | ||||
| type SwarmDir struct { | ||||
| 	inode       uint64 | ||||
| 	name        string | ||||
| 	path        string | ||||
| 	directories []*SwarmDir | ||||
| 	files       []*SwarmFile | ||||
| 
 | ||||
| 	mountInfo *MountInfo | ||||
| 	lock      *sync.RWMutex | ||||
| } | ||||
| 
 | ||||
| func NewSwarmDir(fullpath string, minfo *MountInfo) *SwarmDir { | ||||
| 	newdir := &SwarmDir{ | ||||
| 		inode:       NewInode(), | ||||
| 		name:        filepath.Base(fullpath), | ||||
| 		path:        fullpath, | ||||
| 		directories: []*SwarmDir{}, | ||||
| 		files:       []*SwarmFile{}, | ||||
| 		mountInfo:   minfo, | ||||
| 		lock:        &sync.RWMutex{}, | ||||
| 	} | ||||
| 	return newdir | ||||
| } | ||||
| 
 | ||||
| func (sd *SwarmDir) Attr(ctx context.Context, a *fuse.Attr) error { | ||||
| 	a.Inode = sd.inode | ||||
| 	a.Mode = os.ModeDir | 0700 | ||||
| 	a.Uid = uint32(os.Getuid()) | ||||
| 	a.Gid = uint32(os.Getegid()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (sd *SwarmDir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) { | ||||
| 
 | ||||
| 	for _, n := range sd.files { | ||||
| 		if n.name == req.Name { | ||||
| 			return n, nil | ||||
| 		} | ||||
| 	} | ||||
| 	for _, n := range sd.directories { | ||||
| 		if n.name == req.Name { | ||||
| 			return n, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, fuse.ENOENT | ||||
| } | ||||
| 
 | ||||
| func (sd *SwarmDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { | ||||
| 	var children []fuse.Dirent | ||||
| 	for _, file := range sd.files { | ||||
| 		children = append(children, fuse.Dirent{Inode: file.inode, Type: fuse.DT_File, Name: file.name}) | ||||
| 	} | ||||
| 	for _, dir := range sd.directories { | ||||
| 		children = append(children, fuse.Dirent{Inode: dir.inode, Type: fuse.DT_Dir, Name: dir.name}) | ||||
| 	} | ||||
| 	return children, nil | ||||
| } | ||||
| 
 | ||||
| func (sd *SwarmDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { | ||||
| 
 | ||||
| 	newFile := NewSwarmFile(sd.path, req.Name, sd.mountInfo) | ||||
| 	newFile.fileSize = 0 // 0 means, file is not in swarm yet and it is just created
 | ||||
| 
 | ||||
| 	sd.lock.Lock() | ||||
| 	defer sd.lock.Unlock() | ||||
| 	sd.files = append(sd.files, newFile) | ||||
| 
 | ||||
| 	return newFile, newFile, nil | ||||
| } | ||||
| 
 | ||||
| func (sd *SwarmDir) Remove(ctx context.Context, req *fuse.RemoveRequest) error { | ||||
| 
 | ||||
| 	if req.Dir && sd.directories != nil { | ||||
| 		newDirs := []*SwarmDir{} | ||||
| 		for _, dir := range sd.directories { | ||||
| 			if dir.name == req.Name { | ||||
| 				removeDirectoryFromSwarm(dir) | ||||
| 			} else { | ||||
| 				newDirs = append(newDirs, dir) | ||||
| 			} | ||||
| 		} | ||||
| 		if len(sd.directories) > len(newDirs) { | ||||
| 			sd.lock.Lock() | ||||
| 			defer sd.lock.Unlock() | ||||
| 			sd.directories = newDirs | ||||
| 		} | ||||
| 		return nil | ||||
| 	} else if !req.Dir && sd.files != nil { | ||||
| 		newFiles := []*SwarmFile{} | ||||
| 		for _, f := range sd.files { | ||||
| 			if f.name == req.Name { | ||||
| 				removeFileFromSwarm(f) | ||||
| 			} else { | ||||
| 				newFiles = append(newFiles, f) | ||||
| 			} | ||||
| 		} | ||||
| 		if len(sd.files) > len(newFiles) { | ||||
| 			sd.lock.Lock() | ||||
| 			defer sd.lock.Unlock() | ||||
| 			sd.files = newFiles | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return fuse.ENOENT | ||||
| } | ||||
| 
 | ||||
| func (sd *SwarmDir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) { | ||||
| 
 | ||||
| 	newDir := NewSwarmDir(req.Name, sd.mountInfo) | ||||
| 
 | ||||
| 	sd.lock.Lock() | ||||
| 	defer sd.lock.Unlock() | ||||
| 	sd.directories = append(sd.directories, newDir) | ||||
| 
 | ||||
| 	return newDir, nil | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										144
									
								
								swarm/fuse/fuse_file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								swarm/fuse/fuse_file.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,144 @@ | ||||
| // Copyright 2017 The go-ethereum Authors
 | ||||
| // This file is part of the go-ethereum library.
 | ||||
| //
 | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU Lesser General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| // The go-ethereum library is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | ||||
| // GNU Lesser General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| // +build linux darwin freebsd
 | ||||
| 
 | ||||
| package fuse | ||||
| 
 | ||||
| import ( | ||||
| 	"bazil.org/fuse" | ||||
| 	"bazil.org/fuse/fs" | ||||
| 	"errors" | ||||
| 	"github.com/ethereum/go-ethereum/log" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/storage" | ||||
| 	"golang.org/x/net/context" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"sync" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	MaxAppendFileSize = 10485760 // 10Mb
 | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	errInvalidOffset           = errors.New("Invalid offset during write") | ||||
| 	errFileSizeMaxLimixReached = errors.New("File size exceeded max limit") | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	_ fs.Node         = (*SwarmFile)(nil) | ||||
| 	_ fs.HandleReader = (*SwarmFile)(nil) | ||||
| 	_ fs.HandleWriter = (*SwarmFile)(nil) | ||||
| ) | ||||
| 
 | ||||
| type SwarmFile struct { | ||||
| 	inode    uint64 | ||||
| 	name     string | ||||
| 	path     string | ||||
| 	key      storage.Key | ||||
| 	fileSize int64 | ||||
| 	reader   storage.LazySectionReader | ||||
| 
 | ||||
| 	mountInfo *MountInfo | ||||
| 	lock      *sync.RWMutex | ||||
| } | ||||
| 
 | ||||
| func NewSwarmFile(path, fname string, minfo *MountInfo) *SwarmFile { | ||||
| 	newFile := &SwarmFile{ | ||||
| 		inode:    NewInode(), | ||||
| 		name:     fname, | ||||
| 		path:     path, | ||||
| 		key:      nil, | ||||
| 		fileSize: -1, // -1 means , file already exists in swarm and you need to just get the size from swarm
 | ||||
| 		reader:   nil, | ||||
| 
 | ||||
| 		mountInfo: minfo, | ||||
| 		lock:      &sync.RWMutex{}, | ||||
| 	} | ||||
| 	return newFile | ||||
| } | ||||
| 
 | ||||
| func (file *SwarmFile) Attr(ctx context.Context, a *fuse.Attr) error { | ||||
| 
 | ||||
| 	a.Inode = file.inode | ||||
| 	//TODO: need to get permission as argument
 | ||||
| 	a.Mode = 0700 | ||||
| 	a.Uid = uint32(os.Getuid()) | ||||
| 	a.Gid = uint32(os.Getegid()) | ||||
| 
 | ||||
| 	if file.fileSize == -1 { | ||||
| 		reader := file.mountInfo.swarmApi.Retrieve(file.key) | ||||
| 		quitC := make(chan bool) | ||||
| 		size, err := reader.Size(quitC) | ||||
| 		if err != nil { | ||||
| 			log.Warn("Couldnt get size of file %s : %v", file.path, err) | ||||
| 		} | ||||
| 		file.fileSize = int64(size) | ||||
| 	} | ||||
| 	a.Size = uint64(file.fileSize) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (sf *SwarmFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { | ||||
| 
 | ||||
| 	sf.lock.RLock() | ||||
| 	defer sf.lock.RUnlock() | ||||
| 	if sf.reader == nil { | ||||
| 		sf.reader = sf.mountInfo.swarmApi.Retrieve(sf.key) | ||||
| 	} | ||||
| 	buf := make([]byte, req.Size) | ||||
| 	n, err := sf.reader.ReadAt(buf, req.Offset) | ||||
| 	if err == io.ErrUnexpectedEOF || err == io.EOF { | ||||
| 		err = nil | ||||
| 	} | ||||
| 	resp.Data = buf[:n] | ||||
| 	sf.reader = nil | ||||
| 	return err | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func (sf *SwarmFile) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { | ||||
| 
 | ||||
| 	if sf.fileSize == 0 && req.Offset == 0 { | ||||
| 
 | ||||
| 		// A new file is created
 | ||||
| 		err := addFileToSwarm(sf, req.Data, len(req.Data)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		resp.Size = len(req.Data) | ||||
| 
 | ||||
| 	} else if req.Offset <= sf.fileSize { | ||||
| 
 | ||||
| 		totalSize := sf.fileSize + int64(len(req.Data)) | ||||
| 		if totalSize > MaxAppendFileSize { | ||||
| 			log.Warn("Append file size reached (%v) : (%v)", sf.fileSize, len(req.Data)) | ||||
| 			return errFileSizeMaxLimixReached | ||||
| 		} | ||||
| 
 | ||||
| 		err := appendToExistingFileInSwarm(sf, req.Data, req.Offset, int64(len(req.Data))) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		resp.Size = int(sf.fileSize) | ||||
| 	} else { | ||||
| 		log.Warn("Invalid write request size(%v) : off(%v)", sf.fileSize, req.Offset) | ||||
| 		return errInvalidOffset | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| @ -14,30 +14,22 @@ | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| package api | ||||
| // +build linux darwin freebsd
 | ||||
| 
 | ||||
| package fuse | ||||
| 
 | ||||
| import ( | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 	"bazil.org/fuse/fs" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	Swarmfs_Version = "0.1" | ||||
| 	mountTimeout    = time.Second * 5 | ||||
| 	maxFuseMounts   = 5 | ||||
| var ( | ||||
| 	_ fs.Node = (*SwarmDir)(nil) | ||||
| ) | ||||
| 
 | ||||
| type SwarmFS struct { | ||||
| 	swarmApi     *Api | ||||
| 	activeMounts map[string]*MountInfo | ||||
| 	activeLock   *sync.RWMutex | ||||
| type SwarmRoot struct { | ||||
| 	root *SwarmDir | ||||
| } | ||||
| 
 | ||||
| func NewSwarmFS(api *Api) *SwarmFS { | ||||
| 	swarmfs := &SwarmFS{ | ||||
| 		swarmApi:     api, | ||||
| 		activeLock:   &sync.RWMutex{}, | ||||
| 		activeMounts: map[string]*MountInfo{}, | ||||
| 	} | ||||
| 	return swarmfs | ||||
| func (filesystem *SwarmRoot) Root() (fs.Node, error) { | ||||
| 	return filesystem.root, nil | ||||
| } | ||||
							
								
								
									
										64
									
								
								swarm/fuse/swarmfs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								swarm/fuse/swarmfs.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| // Copyright 2017 The go-ethereum Authors
 | ||||
| // This file is part of the go-ethereum library.
 | ||||
| //
 | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU Lesser General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| // The go-ethereum library is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | ||||
| // GNU Lesser General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| package fuse | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/ethereum/go-ethereum/swarm/api" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	Swarmfs_Version = "0.1" | ||||
| 	mountTimeout    = time.Second * 5 | ||||
| 	unmountTimeout  = time.Second * 10 | ||||
| 	maxFuseMounts   = 5 | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	swarmfs     *SwarmFS // Swarm file system singleton
 | ||||
| 	swarmfsLock sync.Once | ||||
| 
 | ||||
| 	inode     uint64 = 1 // global inode
 | ||||
| 	inodeLock sync.RWMutex | ||||
| ) | ||||
| 
 | ||||
| type SwarmFS struct { | ||||
| 	swarmApi     *api.Api | ||||
| 	activeMounts map[string]*MountInfo | ||||
| 	swarmFsLock  *sync.RWMutex | ||||
| } | ||||
| 
 | ||||
| func NewSwarmFS(api *api.Api) *SwarmFS { | ||||
| 	swarmfsLock.Do(func() { | ||||
| 		swarmfs = &SwarmFS{ | ||||
| 			swarmApi:     api, | ||||
| 			swarmFsLock:  &sync.RWMutex{}, | ||||
| 			activeMounts: map[string]*MountInfo{}, | ||||
| 		} | ||||
| 	}) | ||||
| 	return swarmfs | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Inode numbers need to be unique, they are used for caching inside fuse
 | ||||
| func NewInode() uint64 { | ||||
| 	inodeLock.Lock() | ||||
| 	defer inodeLock.Unlock() | ||||
| 	inode += 1 | ||||
| 	return inode | ||||
| } | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| // +build !linux,!darwin,!freebsd
 | ||||
| 
 | ||||
| package api | ||||
| package fuse | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| @ -29,8 +29,9 @@ func isFUSEUnsupportedError(err error) bool { | ||||
| } | ||||
| 
 | ||||
| type MountInfo struct { | ||||
| 	MountPoint   string | ||||
| 	ManifestHash string | ||||
| 	MountPoint     string | ||||
| 	StartManifest  string | ||||
| 	LatestManifest string | ||||
| } | ||||
| 
 | ||||
| func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { | ||||
							
								
								
									
										897
									
								
								swarm/fuse/swarmfs_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										897
									
								
								swarm/fuse/swarmfs_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,897 @@ | ||||
| // Copyright 2017 The go-ethereum Authors
 | ||||
| // This file is part of the go-ethereum library.
 | ||||
| //
 | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU Lesser General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| // The go-ethereum library is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | ||||
| // GNU Lesser General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| // +build linux darwin freebsd
 | ||||
| 
 | ||||
| package fuse | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/rand" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/api" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/storage" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| type fileInfo struct { | ||||
| 	perm     uint64 | ||||
| 	uid      int | ||||
| 	gid      int | ||||
| 	contents []byte | ||||
| } | ||||
| 
 | ||||
| func testFuseFileSystem(t *testing.T, f func(*api.Api)) { | ||||
| 
 | ||||
| 	datadir, err := ioutil.TempDir("", "fuse") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unable to create temp dir: %v", err) | ||||
| 	} | ||||
| 	os.RemoveAll(datadir) | ||||
| 
 | ||||
| 	dpa, err := storage.NewLocalDPA(datadir) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	api := api.NewApi(dpa, nil) | ||||
| 	dpa.Start() | ||||
| 	f(api) | ||||
| 	dpa.Stop() | ||||
| } | ||||
| 
 | ||||
| func createTestFilesAndUploadToSwarm(t *testing.T, api *api.Api, files map[string]fileInfo, uploadDir string) string { | ||||
| 
 | ||||
| 	os.RemoveAll(uploadDir) | ||||
| 
 | ||||
| 	for fname, finfo := range files { | ||||
| 		actualPath := filepath.Join(uploadDir, fname) | ||||
| 		filePath := filepath.Dir(actualPath) | ||||
| 
 | ||||
| 		err := os.MkdirAll(filePath, 0777) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Error creating directory '%v' : %v", filePath, err) | ||||
| 		} | ||||
| 
 | ||||
| 		fd, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(finfo.perm)) | ||||
| 		if err1 != nil { | ||||
| 			t.Fatalf("Error creating file %v: %v", actualPath, err1) | ||||
| 		} | ||||
| 
 | ||||
| 		fd.Write(finfo.contents) | ||||
| 		fd.Chown(finfo.uid, finfo.gid) | ||||
| 		fd.Chmod(os.FileMode(finfo.perm)) | ||||
| 		fd.Sync() | ||||
| 		fd.Close() | ||||
| 	} | ||||
| 
 | ||||
| 	bzzhash, err := api.Upload(uploadDir, "") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Error uploading directory %v: %v", uploadDir, err) | ||||
| 	} | ||||
| 
 | ||||
| 	return bzzhash | ||||
| } | ||||
| 
 | ||||
| func mountDir(t *testing.T, api *api.Api, files map[string]fileInfo, bzzHash string, mountDir string) *SwarmFS { | ||||
| 
 | ||||
| 	// Test Mount
 | ||||
| 	os.RemoveAll(mountDir) | ||||
| 	os.MkdirAll(mountDir, 0777) | ||||
| 	swarmfs := NewSwarmFS(api) | ||||
| 	_, err := swarmfs.Mount(bzzHash, mountDir) | ||||
| 	if isFUSEUnsupportedError(err) { | ||||
| 		t.Skip("FUSE not supported:", err) | ||||
| 	} else if err != nil { | ||||
| 		t.Fatalf("Error mounting hash %v: %v", bzzHash, err) | ||||
| 	} | ||||
| 
 | ||||
| 	found := false | ||||
| 	mi := swarmfs.Listmounts() | ||||
| 	for _, minfo := range mi { | ||||
| 		if minfo.MountPoint == mountDir { | ||||
| 			if minfo.StartManifest != bzzHash || | ||||
| 				minfo.LatestManifest != bzzHash || | ||||
| 				minfo.fuseConnection == nil { | ||||
| 				t.Fatalf("Error mounting: exp(%s): act(%s)", bzzHash, minfo.StartManifest) | ||||
| 			} | ||||
| 			found = true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Test listMounts
 | ||||
| 	if found == false { | ||||
| 		t.Fatalf("Error getting mounts information for %v: %v", mountDir, err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Check if file and their attributes are as expected
 | ||||
| 	compareGeneratedFileWithFileInMount(t, files, mountDir) | ||||
| 
 | ||||
| 	return swarmfs | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func compareGeneratedFileWithFileInMount(t *testing.T, files map[string]fileInfo, mountDir string) { | ||||
| 
 | ||||
| 	err := filepath.Walk(mountDir, func(path string, f os.FileInfo, err error) error { | ||||
| 		if f.IsDir() { | ||||
| 			return nil | ||||
| 		} | ||||
| 		fname := path[len(mountDir)+1:] | ||||
| 		if _, ok := files[fname]; !ok { | ||||
| 			t.Fatalf(" file %v present in mount dir and is not expected", fname) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Error walking dir %v", mountDir) | ||||
| 	} | ||||
| 
 | ||||
| 	for fname, finfo := range files { | ||||
| 
 | ||||
| 		destinationFile := filepath.Join(mountDir, fname) | ||||
| 
 | ||||
| 		dfinfo, err := os.Stat(destinationFile) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Destination file %v missing in mount: %v", fname, err) | ||||
| 		} | ||||
| 
 | ||||
| 		if int64(len(finfo.contents)) != dfinfo.Size() { | ||||
| 			t.Fatalf("file %v Size mismatch  source (%v) vs destination(%v)", fname, int64(len(finfo.contents)), dfinfo.Size()) | ||||
| 		} | ||||
| 
 | ||||
| 		if dfinfo.Mode().Perm().String() != "-rwx------" { | ||||
| 			t.Fatalf("file %v Permission mismatch source (-rwx------) vs destination(%v)", fname, dfinfo.Mode().Perm()) | ||||
| 		} | ||||
| 
 | ||||
| 		fileContents, err := ioutil.ReadFile(filepath.Join(mountDir, fname)) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Could not readfile %v : %v", fname, err) | ||||
| 		} | ||||
| 
 | ||||
| 		if bytes.Compare(fileContents, finfo.contents) != 0 { | ||||
| 			t.Fatalf("File %v contents mismatch: %v , %v", fname, fileContents, finfo.contents) | ||||
| 
 | ||||
| 		} | ||||
| 
 | ||||
| 		// TODO: check uid and gid
 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func checkFile(t *testing.T, testMountDir, fname string, contents []byte) { | ||||
| 
 | ||||
| 	destinationFile := filepath.Join(testMountDir, fname) | ||||
| 	dfinfo, err1 := os.Stat(destinationFile) | ||||
| 	if err1 != nil { | ||||
| 		t.Fatalf("Could not stat file %v", destinationFile) | ||||
| 	} | ||||
| 	if dfinfo.Size() != int64(len(contents)) { | ||||
| 		t.Fatalf("Mismatch in size  actual(%v) vs expected(%v)", dfinfo.Size(), int64(len(contents))) | ||||
| 	} | ||||
| 
 | ||||
| 	fd, err2 := os.OpenFile(destinationFile, os.O_RDONLY, os.FileMode(0665)) | ||||
| 	if err2 != nil { | ||||
| 		t.Fatalf("Could not open file %v", destinationFile) | ||||
| 	} | ||||
| 	newcontent := make([]byte, len(contents)) | ||||
| 	fd.Read(newcontent) | ||||
| 	fd.Close() | ||||
| 
 | ||||
| 	if !bytes.Equal(contents, newcontent) { | ||||
| 		t.Fatalf("File content mismatch expected (%v): received (%v) ", contents, newcontent) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func getRandomBtes(size int) []byte { | ||||
| 	contents := make([]byte, size) | ||||
| 	rand.Read(contents) | ||||
| 	return contents | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func IsDirEmpty(name string) bool { | ||||
| 	f, err := os.Open(name) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	defer f.Close() | ||||
| 
 | ||||
| 	_, err = f.Readdirnames(1) | ||||
| 	if err == io.EOF { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func testMountListAndUnmount(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "fuse-source") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "fuse-dest") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["2.txt"] = fileInfo{0711, 333, 444, getRandomBtes(10)} | ||||
| 	files["3.txt"] = fileInfo{0622, 333, 444, getRandomBtes(100)} | ||||
| 	files["4.txt"] = fileInfo{0533, 333, 444, getRandomBtes(1024)} | ||||
| 	files["5.txt"] = fileInfo{0544, 333, 444, getRandomBtes(10)} | ||||
| 	files["6.txt"] = fileInfo{0555, 333, 444, getRandomBtes(10)} | ||||
| 	files["7.txt"] = fileInfo{0666, 333, 444, getRandomBtes(10)} | ||||
| 	files["8.txt"] = fileInfo{0777, 333, 333, getRandomBtes(10)} | ||||
| 	files["11.txt"] = fileInfo{0777, 333, 444, getRandomBtes(10)} | ||||
| 	files["111.txt"] = fileInfo{0777, 333, 444, getRandomBtes(10)} | ||||
| 	files["two/2.txt"] = fileInfo{0777, 333, 444, getRandomBtes(10)} | ||||
| 	files["two/2/2.txt"] = fileInfo{0777, 333, 444, getRandomBtes(10)} | ||||
| 	files["two/2./2.txt"] = fileInfo{0777, 444, 444, getRandomBtes(10)} | ||||
| 	files["twice/2.txt"] = fileInfo{0777, 444, 333, getRandomBtes(200)} | ||||
| 	files["one/two/three/four/five/six/seven/eight/nine/10.txt"] = fileInfo{0777, 333, 444, getRandomBtes(10240)} | ||||
| 	files["one/two/three/four/five/six/six"] = fileInfo{0777, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs.Stop() | ||||
| 
 | ||||
| 	// Check unmount
 | ||||
| 	_, err := swarmfs.Unmount(testMountDir) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("could not unmount  %v", bzzHash) | ||||
| 	} | ||||
| 	if !IsDirEmpty(testMountDir) { | ||||
| 		t.Fatalf("unmount didnt work for %v", testMountDir) | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testMaxMounts(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	uploadDir1, _ := ioutil.TempDir(os.TempDir(), "max-upload1") | ||||
| 	bzzHash1 := createTestFilesAndUploadToSwarm(t, api, files, uploadDir1) | ||||
| 	mount1, _ := ioutil.TempDir(os.TempDir(), "max-mount1") | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash1, mount1) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	files["2.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	uploadDir2, _ := ioutil.TempDir(os.TempDir(), "max-upload2") | ||||
| 	bzzHash2 := createTestFilesAndUploadToSwarm(t, api, files, uploadDir2) | ||||
| 	mount2, _ := ioutil.TempDir(os.TempDir(), "max-mount2") | ||||
| 	swarmfs2 := mountDir(t, api, files, bzzHash2, mount2) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| 	files["3.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	uploadDir3, _ := ioutil.TempDir(os.TempDir(), "max-upload3") | ||||
| 	bzzHash3 := createTestFilesAndUploadToSwarm(t, api, files, uploadDir3) | ||||
| 	mount3, _ := ioutil.TempDir(os.TempDir(), "max-mount3") | ||||
| 	swarmfs3 := mountDir(t, api, files, bzzHash3, mount3) | ||||
| 	defer swarmfs3.Stop() | ||||
| 
 | ||||
| 	files["4.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	uploadDir4, _ := ioutil.TempDir(os.TempDir(), "max-upload4") | ||||
| 	bzzHash4 := createTestFilesAndUploadToSwarm(t, api, files, uploadDir4) | ||||
| 	mount4, _ := ioutil.TempDir(os.TempDir(), "max-mount4") | ||||
| 	swarmfs4 := mountDir(t, api, files, bzzHash4, mount4) | ||||
| 	defer swarmfs4.Stop() | ||||
| 
 | ||||
| 	files["5.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	uploadDir5, _ := ioutil.TempDir(os.TempDir(), "max-upload5") | ||||
| 	bzzHash5 := createTestFilesAndUploadToSwarm(t, api, files, uploadDir5) | ||||
| 	mount5, _ := ioutil.TempDir(os.TempDir(), "max-mount5") | ||||
| 	swarmfs5 := mountDir(t, api, files, bzzHash5, mount5) | ||||
| 	defer swarmfs5.Stop() | ||||
| 
 | ||||
| 	files["6.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	uploadDir6, _ := ioutil.TempDir(os.TempDir(), "max-upload6") | ||||
| 	bzzHash6 := createTestFilesAndUploadToSwarm(t, api, files, uploadDir6) | ||||
| 	mount6, _ := ioutil.TempDir(os.TempDir(), "max-mount6") | ||||
| 
 | ||||
| 	os.RemoveAll(mount6) | ||||
| 	os.MkdirAll(mount6, 0777) | ||||
| 	_, err := swarmfs.Mount(bzzHash6, mount6) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("Error: Going beyond max mounts  %v", bzzHash6) | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testReMounts(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	uploadDir1, _ := ioutil.TempDir(os.TempDir(), "re-upload1") | ||||
| 	bzzHash1 := createTestFilesAndUploadToSwarm(t, api, files, uploadDir1) | ||||
| 	testMountDir1, _ := ioutil.TempDir(os.TempDir(), "re-mount1") | ||||
| 	swarmfs := mountDir(t, api, files, bzzHash1, testMountDir1) | ||||
| 	defer swarmfs.Stop() | ||||
| 
 | ||||
| 	uploadDir2, _ := ioutil.TempDir(os.TempDir(), "re-upload2") | ||||
| 	bzzHash2 := createTestFilesAndUploadToSwarm(t, api, files, uploadDir2) | ||||
| 	testMountDir2, _ := ioutil.TempDir(os.TempDir(), "re-mount2") | ||||
| 
 | ||||
| 	// try mounting the same hash second time
 | ||||
| 	os.RemoveAll(testMountDir2) | ||||
| 	os.MkdirAll(testMountDir2, 0777) | ||||
| 	_, err := swarmfs.Mount(bzzHash1, testMountDir2) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Error mounting hash  %v", bzzHash1) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount a different hash in already mounted point
 | ||||
| 	_, err = swarmfs.Mount(bzzHash2, testMountDir1) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("Error mounting hash  %v", bzzHash2) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount nonexistent hash
 | ||||
| 	_, err = swarmfs.Mount("0xfea11223344", testMountDir1) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("Error mounting hash  %v", bzzHash2) | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testUnmount(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	uploadDir, _ := ioutil.TempDir(os.TempDir(), "ex-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "ex-mount") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, uploadDir) | ||||
| 
 | ||||
| 	swarmfs := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs.Stop() | ||||
| 
 | ||||
| 	swarmfs.Unmount(testMountDir) | ||||
| 
 | ||||
| 	mi := swarmfs.Listmounts() | ||||
| 	for _, minfo := range mi { | ||||
| 		if minfo.MountPoint == testMountDir { | ||||
| 			t.Fatalf("mount state not cleaned up in unmount case %v", testMountDir) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testUnmountWhenResourceBusy(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "ex-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "ex-mount") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs.Stop() | ||||
| 
 | ||||
| 	actualPath := filepath.Join(testMountDir, "2.txt") | ||||
| 	d, err := os.OpenFile(actualPath, os.O_RDWR, os.FileMode(0700)) | ||||
| 	d.Write(getRandomBtes(10)) | ||||
| 
 | ||||
| 	_, err = swarmfs.Unmount(testMountDir) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("could not unmount  %v", bzzHash) | ||||
| 	} | ||||
| 	d.Close() | ||||
| 
 | ||||
| 	mi := swarmfs.Listmounts() | ||||
| 	for _, minfo := range mi { | ||||
| 		if minfo.MountPoint == testMountDir { | ||||
| 			t.Fatalf("mount state not cleaned up in unmount case %v", testMountDir) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| func testSeekInMultiChunkFile(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "seek-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "seek-mount") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10240)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs.Stop() | ||||
| 
 | ||||
| 	// Create a new file seek the second chunk
 | ||||
| 	actualPath := filepath.Join(testMountDir, "1.txt") | ||||
| 	d, _ := os.OpenFile(actualPath, os.O_RDONLY, os.FileMode(0700)) | ||||
| 
 | ||||
| 	d.Seek(5000, 0) | ||||
| 
 | ||||
| 	contents := make([]byte, 1024) | ||||
| 	d.Read(contents) | ||||
| 	finfo := files["1.txt"] | ||||
| 
 | ||||
| 	if bytes.Compare(finfo.contents[:6024][5000:], contents) != 0 { | ||||
| 		t.Fatalf("File seek contents mismatch") | ||||
| 	} | ||||
| 	d.Close() | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testCreateNewFile(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "create-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "create-mount") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["five.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["six.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	// Create a new file in the root dir and check
 | ||||
| 	actualPath := filepath.Join(testMountDir, "2.txt") | ||||
| 	d, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(0665)) | ||||
| 	if err1 != nil { | ||||
| 		t.Fatalf("Could not create file %s : %v", actualPath, err1) | ||||
| 	} | ||||
| 	contents := make([]byte, 11) | ||||
| 	rand.Read(contents) | ||||
| 	d.Write(contents) | ||||
| 	d.Close() | ||||
| 
 | ||||
| 	mi, err2 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err2 != nil { | ||||
| 		t.Fatalf("Could not unmount %v", err2) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	files["2.txt"] = fileInfo{0700, 333, 444, contents} | ||||
| 	swarmfs2 := mountDir(t, api, files, mi.LatestManifest, testMountDir) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| 	checkFile(t, testMountDir, "2.txt", contents) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testCreateNewFileInsideDirectory(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "createinsidedir-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "createinsidedir-mount") | ||||
| 
 | ||||
| 	files["one/1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	// Create a new file inside a existing dir and check
 | ||||
| 	dirToCreate := filepath.Join(testMountDir, "one") | ||||
| 	actualPath := filepath.Join(dirToCreate, "2.txt") | ||||
| 	d, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(0665)) | ||||
| 	if err1 != nil { | ||||
| 		t.Fatalf("Could not create file %s : %v", actualPath, err1) | ||||
| 	} | ||||
| 	contents := make([]byte, 11) | ||||
| 	rand.Read(contents) | ||||
| 	d.Write(contents) | ||||
| 	d.Close() | ||||
| 
 | ||||
| 	mi, err2 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err2 != nil { | ||||
| 		t.Fatalf("Could not unmount %v", err2) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	files["one/2.txt"] = fileInfo{0700, 333, 444, contents} | ||||
| 	swarmfs2 := mountDir(t, api, files, mi.LatestManifest, testMountDir) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| 	checkFile(t, testMountDir, "one/2.txt", contents) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testCreateNewFileInsideNewDirectory(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "createinsidenewdir-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "createinsidenewdir-mount") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	// Create a new file inside a existing dir and check
 | ||||
| 	dirToCreate := filepath.Join(testMountDir, "one") | ||||
| 	os.MkdirAll(dirToCreate, 0777) | ||||
| 	actualPath := filepath.Join(dirToCreate, "2.txt") | ||||
| 	d, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(0665)) | ||||
| 	if err1 != nil { | ||||
| 		t.Fatalf("Could not create file %s : %v", actualPath, err1) | ||||
| 	} | ||||
| 	contents := make([]byte, 11) | ||||
| 	rand.Read(contents) | ||||
| 	d.Write(contents) | ||||
| 	d.Close() | ||||
| 
 | ||||
| 	mi, err2 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err2 != nil { | ||||
| 		t.Fatalf("Could not unmount %v", err2) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	files["one/2.txt"] = fileInfo{0700, 333, 444, contents} | ||||
| 	swarmfs2 := mountDir(t, api, files, mi.LatestManifest, testMountDir) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| 	checkFile(t, testMountDir, "one/2.txt", contents) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testRemoveExistingFile(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "remove-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "remove-mount") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["five.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["six.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	// Remove a file in the root dir and check
 | ||||
| 	actualPath := filepath.Join(testMountDir, "five.txt") | ||||
| 	os.Remove(actualPath) | ||||
| 
 | ||||
| 	mi, err2 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err2 != nil { | ||||
| 		t.Fatalf("Could not unmount %v", err2) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	delete(files, "five.txt") | ||||
| 	swarmfs2 := mountDir(t, api, files, mi.LatestManifest, testMountDir) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testRemoveExistingFileInsideADir(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "remove-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "remove-mount") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["one/five.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["one/six.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	// Remove a file in the root dir and check
 | ||||
| 	actualPath := filepath.Join(testMountDir, "one/five.txt") | ||||
| 	os.Remove(actualPath) | ||||
| 
 | ||||
| 	mi, err2 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err2 != nil { | ||||
| 		t.Fatalf("Could not unmount %v", err2) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	delete(files, "one/five.txt") | ||||
| 	swarmfs2 := mountDir(t, api, files, mi.LatestManifest, testMountDir) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testRemoveNewlyAddedFile(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "removenew-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "removenew-mount") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["five.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["six.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	// Adda a new file and remove it
 | ||||
| 	dirToCreate := filepath.Join(testMountDir, "one") | ||||
| 	os.MkdirAll(dirToCreate, os.FileMode(0665)) | ||||
| 	actualPath := filepath.Join(dirToCreate, "2.txt") | ||||
| 	d, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(0665)) | ||||
| 	if err1 != nil { | ||||
| 		t.Fatalf("Could not create file %s : %v", actualPath, err1) | ||||
| 	} | ||||
| 	contents := make([]byte, 11) | ||||
| 	rand.Read(contents) | ||||
| 	d.Write(contents) | ||||
| 	d.Close() | ||||
| 
 | ||||
| 	checkFile(t, testMountDir, "one/2.txt", contents) | ||||
| 
 | ||||
| 	os.Remove(actualPath) | ||||
| 
 | ||||
| 	mi, err2 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err2 != nil { | ||||
| 		t.Fatalf("Could not unmount %v", err2) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	swarmfs2 := mountDir(t, api, files, mi.LatestManifest, testMountDir) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| 	if bzzHash != mi.LatestManifest { | ||||
| 		t.Fatalf("same contents different hash orig(%v): new(%v)", bzzHash, mi.LatestManifest) | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testAddNewFileAndModifyContents(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "modifyfile-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "modifyfile-mount") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["five.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["six.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	// Create a new file in the root dir and check
 | ||||
| 	actualPath := filepath.Join(testMountDir, "2.txt") | ||||
| 	d, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(0665)) | ||||
| 	if err1 != nil { | ||||
| 		t.Fatalf("Could not create file %s : %v", actualPath, err1) | ||||
| 	} | ||||
| 	line1 := []byte("Line 1") | ||||
| 	rand.Read(line1) | ||||
| 	d.Write(line1) | ||||
| 	d.Close() | ||||
| 
 | ||||
| 	mi1, err2 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err2 != nil { | ||||
| 		t.Fatalf("Could not unmount %v", err2) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	files["2.txt"] = fileInfo{0700, 333, 444, line1} | ||||
| 	swarmfs2 := mountDir(t, api, files, mi1.LatestManifest, testMountDir) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| 	checkFile(t, testMountDir, "2.txt", line1) | ||||
| 
 | ||||
| 	mi2, err3 := swarmfs2.Unmount(testMountDir) | ||||
| 	if err3 != nil { | ||||
| 		t.Fatalf("Could not unmount %v", err3) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and modify
 | ||||
| 	swarmfs3 := mountDir(t, api, files, mi2.LatestManifest, testMountDir) | ||||
| 	defer swarmfs3.Stop() | ||||
| 
 | ||||
| 	fd, err4 := os.OpenFile(actualPath, os.O_RDWR|os.O_APPEND, os.FileMode(0665)) | ||||
| 	if err4 != nil { | ||||
| 		t.Fatalf("Could not create file %s : %v", actualPath, err4) | ||||
| 	} | ||||
| 	line2 := []byte("Line 2") | ||||
| 	rand.Read(line2) | ||||
| 	fd.Seek(int64(len(line1)), 0) | ||||
| 	fd.Write(line2) | ||||
| 	fd.Close() | ||||
| 
 | ||||
| 	mi3, err5 := swarmfs3.Unmount(testMountDir) | ||||
| 	if err5 != nil { | ||||
| 		t.Fatalf("Could not unmount %v", err5) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	b := [][]byte{line1, line2} | ||||
| 	line1and2 := bytes.Join(b, []byte("")) | ||||
| 	files["2.txt"] = fileInfo{0700, 333, 444, line1and2} | ||||
| 	swarmfs4 := mountDir(t, api, files, mi3.LatestManifest, testMountDir) | ||||
| 	defer swarmfs4.Stop() | ||||
| 
 | ||||
| 	checkFile(t, testMountDir, "2.txt", line1and2) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testRemoveEmptyDir(api *api.Api, t *testing.T) { | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "rmdir-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "rmdir-mount") | ||||
| 
 | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["five.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["six.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	os.MkdirAll(filepath.Join(testMountDir, "newdir"), 0777) | ||||
| 
 | ||||
| 	mi, err3 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err3 != nil { | ||||
| 		t.Fatalf("Could not unmount %v", err3) | ||||
| 	} | ||||
| 
 | ||||
| 	if bzzHash != mi.LatestManifest { | ||||
| 		t.Fatalf("same contents different hash orig(%v): new(%v)", bzzHash, mi.LatestManifest) | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testRemoveDirWhichHasFiles(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "rmdir-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "rmdir-mount") | ||||
| 
 | ||||
| 	files["one/1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["two/five.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["two/six.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	dirPath := filepath.Join(testMountDir, "two") | ||||
| 	os.RemoveAll(dirPath) | ||||
| 
 | ||||
| 	mi, err2 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err2 != nil { | ||||
| 		t.Fatalf("Could not unmount %v ", err2) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	delete(files, "two/five.txt") | ||||
| 	delete(files, "two/six.txt") | ||||
| 
 | ||||
| 	swarmfs2 := mountDir(t, api, files, mi.LatestManifest, testMountDir) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testRemoveDirWhichHasSubDirs(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "rmsubdir-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "rmsubdir-mount") | ||||
| 
 | ||||
| 	files["one/1.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["two/three/2.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["two/three/3.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["two/four/5.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["two/four/6.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 	files["two/four/six/7.txt"] = fileInfo{0700, 333, 444, getRandomBtes(10)} | ||||
| 
 | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	dirPath := filepath.Join(testMountDir, "two") | ||||
| 	os.RemoveAll(dirPath) | ||||
| 
 | ||||
| 	mi, err2 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err2 != nil { | ||||
| 		t.Fatalf("Could not unmount %v ", err2) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	delete(files, "two/three/2.txt") | ||||
| 	delete(files, "two/three/3.txt") | ||||
| 	delete(files, "two/four/5.txt") | ||||
| 	delete(files, "two/four/6.txt") | ||||
| 	delete(files, "two/four/six/7.txt") | ||||
| 
 | ||||
| 	swarmfs2 := mountDir(t, api, files, mi.LatestManifest, testMountDir) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func testAppendFileContentsToEnd(api *api.Api, t *testing.T) { | ||||
| 
 | ||||
| 	files := make(map[string]fileInfo) | ||||
| 	testUploadDir, _ := ioutil.TempDir(os.TempDir(), "appendlargefile-upload") | ||||
| 	testMountDir, _ := ioutil.TempDir(os.TempDir(), "appendlargefile-mount") | ||||
| 
 | ||||
| 	line1 := make([]byte, 10) | ||||
| 	rand.Read(line1) | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, line1} | ||||
| 	bzzHash := createTestFilesAndUploadToSwarm(t, api, files, testUploadDir) | ||||
| 
 | ||||
| 	swarmfs1 := mountDir(t, api, files, bzzHash, testMountDir) | ||||
| 	defer swarmfs1.Stop() | ||||
| 
 | ||||
| 	actualPath := filepath.Join(testMountDir, "1.txt") | ||||
| 	fd, err4 := os.OpenFile(actualPath, os.O_RDWR|os.O_APPEND, os.FileMode(0665)) | ||||
| 	if err4 != nil { | ||||
| 		t.Fatalf("Could not create file %s : %v", actualPath, err4) | ||||
| 	} | ||||
| 	line2 := make([]byte, 5) | ||||
| 	rand.Read(line2) | ||||
| 	fd.Seek(int64(len(line1)), 0) | ||||
| 	fd.Write(line2) | ||||
| 	fd.Close() | ||||
| 
 | ||||
| 	mi1, err5 := swarmfs1.Unmount(testMountDir) | ||||
| 	if err5 != nil { | ||||
| 		t.Fatalf("Could not unmount %v ", err5) | ||||
| 	} | ||||
| 
 | ||||
| 	// mount again and see if things are okay
 | ||||
| 	b := [][]byte{line1, line2} | ||||
| 	line1and2 := bytes.Join(b, []byte("")) | ||||
| 	files["1.txt"] = fileInfo{0700, 333, 444, line1and2} | ||||
| 	swarmfs2 := mountDir(t, api, files, mi1.LatestManifest, testMountDir) | ||||
| 	defer swarmfs2.Stop() | ||||
| 
 | ||||
| 	checkFile(t, testMountDir, "1.txt", line1and2) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func TestSwarmFileSystem(t *testing.T) { | ||||
| 	testFuseFileSystem(t, func(api *api.Api) { | ||||
| 
 | ||||
| 		testMountListAndUnmount(api, t) | ||||
| 
 | ||||
| 		testMaxMounts(api, t) | ||||
| 
 | ||||
| 		testReMounts(api, t) | ||||
| 
 | ||||
| 		testUnmount(api, t) | ||||
| 
 | ||||
| 		testUnmountWhenResourceBusy(api, t) | ||||
| 
 | ||||
| 		testSeekInMultiChunkFile(api, t) | ||||
| 
 | ||||
| 		testCreateNewFile(api, t) | ||||
| 
 | ||||
| 		testCreateNewFileInsideDirectory(api, t) | ||||
| 
 | ||||
| 		testCreateNewFileInsideNewDirectory(api, t) | ||||
| 
 | ||||
| 		testRemoveExistingFile(api, t) | ||||
| 
 | ||||
| 		testRemoveExistingFileInsideADir(api, t) | ||||
| 
 | ||||
| 		testRemoveNewlyAddedFile(api, t) | ||||
| 
 | ||||
| 		testAddNewFileAndModifyContents(api, t) | ||||
| 
 | ||||
| 		testRemoveEmptyDir(api, t) | ||||
| 
 | ||||
| 		testRemoveDirWhichHasFiles(api, t) | ||||
| 
 | ||||
| 		testRemoveDirWhichHasSubDirs(api, t) | ||||
| 
 | ||||
| 		testAppendFileContentsToEnd(api, t) | ||||
| 
 | ||||
| 	}) | ||||
| } | ||||
| @ -16,33 +16,28 @@ | ||||
| 
 | ||||
| // +build linux darwin freebsd
 | ||||
| 
 | ||||
| package api | ||||
| package fuse | ||||
| 
 | ||||
| import ( | ||||
| 	"bazil.org/fuse" | ||||
| 	"bazil.org/fuse/fs" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/log" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/api" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"bazil.org/fuse" | ||||
| 	"bazil.org/fuse/fs" | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/log" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/storage" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	inode     uint64 = 1 | ||||
| 	inodeLock sync.RWMutex | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	errEmptyMountPoint = errors.New("need non-empty mount point") | ||||
| 	errMaxMountCount   = errors.New("max FUSE mount count reached") | ||||
| 	errMountTimeout    = errors.New("mount timeout") | ||||
| 	errAlreadyMounted  = errors.New("mount point is already serving") | ||||
| ) | ||||
| 
 | ||||
| func isFUSEUnsupportedError(err error) bool { | ||||
| @ -52,16 +47,17 @@ func isFUSEUnsupportedError(err error) bool { | ||||
| 	return err == fuse.ErrOSXFUSENotFound | ||||
| } | ||||
| 
 | ||||
| // MountInfo contains information about every active mount
 | ||||
| // information about every active mount
 | ||||
| type MountInfo struct { | ||||
| 	MountPoint     string | ||||
| 	ManifestHash   string | ||||
| 	resolvedKey    storage.Key | ||||
| 	rootDir        *Dir | ||||
| 	StartManifest  string | ||||
| 	LatestManifest string | ||||
| 	rootDir        *SwarmDir | ||||
| 	fuseConnection *fuse.Conn | ||||
| 	swarmApi       *api.Api | ||||
| 	lock           *sync.RWMutex | ||||
| } | ||||
| 
 | ||||
| // newInode creates a new inode number.
 | ||||
| // Inode numbers need to be unique, they are used for caching inside fuse
 | ||||
| func newInode() uint64 { | ||||
| 	inodeLock.Lock() | ||||
| @ -70,7 +66,21 @@ func newInode() uint64 { | ||||
| 	return inode | ||||
| } | ||||
| 
 | ||||
| func NewMountInfo(mhash, mpoint string, sapi *api.Api) *MountInfo { | ||||
| 	newMountInfo := &MountInfo{ | ||||
| 		MountPoint:     mpoint, | ||||
| 		StartManifest:  mhash, | ||||
| 		LatestManifest: mhash, | ||||
| 		rootDir:        nil, | ||||
| 		fuseConnection: nil, | ||||
| 		swarmApi:       sapi, | ||||
| 		lock:           &sync.RWMutex{}, | ||||
| 	} | ||||
| 	return newMountInfo | ||||
| } | ||||
| 
 | ||||
| func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { | ||||
| 
 | ||||
| 	if mountpoint == "" { | ||||
| 		return nil, errEmptyMountPoint | ||||
| 	} | ||||
| @ -79,8 +89,8 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	self.activeLock.Lock() | ||||
| 	defer self.activeLock.Unlock() | ||||
| 	self.swarmFsLock.Lock() | ||||
| 	defer self.swarmFsLock.Unlock() | ||||
| 
 | ||||
| 	noOfActiveMounts := len(self.activeMounts) | ||||
| 	if noOfActiveMounts >= maxFuseMounts { | ||||
| @ -88,44 +98,27 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { | ||||
| 	} | ||||
| 
 | ||||
| 	if _, ok := self.activeMounts[cleanedMountPoint]; ok { | ||||
| 		return nil, fmt.Errorf("%s is already mounted", cleanedMountPoint) | ||||
| 		return nil, errAlreadyMounted | ||||
| 	} | ||||
| 
 | ||||
| 	uri, err := Parse("bzz:/" + mhash) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	key, err := self.swarmApi.Resolve(uri) | ||||
| 	log.Info(fmt.Sprintf("Attempting to mount %s ", cleanedMountPoint)) | ||||
| 	key, manifestEntryMap, err := self.swarmApi.BuildDirectoryTree(mhash, true) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	path := uri.Path | ||||
| 	if len(path) > 0 { | ||||
| 		path += "/" | ||||
| 	} | ||||
| 	mi := NewMountInfo(mhash, cleanedMountPoint, self.swarmApi) | ||||
| 
 | ||||
| 	quitC := make(chan bool) | ||||
| 	trie, err := loadManifest(self.swarmApi.dpa, key, quitC) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("can't load manifest %v: %v", key.String(), err) | ||||
| 	} | ||||
| 	dirTree := map[string]*SwarmDir{} | ||||
| 	rootDir := NewSwarmDir("/", mi) | ||||
| 	dirTree["/"] = rootDir | ||||
| 	mi.rootDir = rootDir | ||||
| 
 | ||||
| 	dirTree := map[string]*Dir{} | ||||
| 	for suffix, entry := range manifestEntryMap { | ||||
| 
 | ||||
| 	rootDir := &Dir{ | ||||
| 		inode:       newInode(), | ||||
| 		name:        "root", | ||||
| 		directories: nil, | ||||
| 		files:       nil, | ||||
| 	} | ||||
| 	dirTree["root"] = rootDir | ||||
| 
 | ||||
| 	err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) { | ||||
| 		key = common.Hex2Bytes(entry.Hash) | ||||
| 		fullpath := "/" + suffix | ||||
| 		basepath := filepath.Dir(fullpath) | ||||
| 		filename := filepath.Base(fullpath) | ||||
| 
 | ||||
| 		parentDir := rootDir | ||||
| 		dirUntilNow := "" | ||||
| @ -136,13 +129,7 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { | ||||
| 				dirUntilNow = dirUntilNow + "/" + thisDir | ||||
| 
 | ||||
| 				if _, ok := dirTree[dirUntilNow]; !ok { | ||||
| 					dirTree[dirUntilNow] = &Dir{ | ||||
| 						inode:       newInode(), | ||||
| 						name:        thisDir, | ||||
| 						path:        dirUntilNow, | ||||
| 						directories: nil, | ||||
| 						files:       nil, | ||||
| 					} | ||||
| 					dirTree[dirUntilNow] = NewSwarmDir(dirUntilNow, mi) | ||||
| 					parentDir.directories = append(parentDir.directories, dirTree[dirUntilNow]) | ||||
| 					parentDir = dirTree[dirUntilNow] | ||||
| 
 | ||||
| @ -152,29 +139,32 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { | ||||
| 
 | ||||
| 			} | ||||
| 		} | ||||
| 		thisFile := &File{ | ||||
| 			inode:    newInode(), | ||||
| 			name:     filename, | ||||
| 			path:     fullpath, | ||||
| 			key:      key, | ||||
| 			swarmApi: self.swarmApi, | ||||
| 		} | ||||
| 		thisFile := NewSwarmFile(basepath, filepath.Base(fullpath), mi) | ||||
| 		thisFile.key = key | ||||
| 
 | ||||
| 		parentDir.files = append(parentDir.files, thisFile) | ||||
| 	}) | ||||
| 	} | ||||
| 
 | ||||
| 	fconn, err := fuse.Mount(cleanedMountPoint, fuse.FSName("swarmfs"), fuse.VolumeName(mhash)) | ||||
| 	if err != nil { | ||||
| 	if isFUSEUnsupportedError(err) { | ||||
| 		log.Warn("Fuse not installed", "mountpoint", cleanedMountPoint, "err", err) | ||||
| 		return nil, err | ||||
| 	} else if err != nil { | ||||
| 		fuse.Unmount(cleanedMountPoint) | ||||
| 		log.Warn("Error mounting swarm manifest", "mountpoint", cleanedMountPoint, "err", err) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	mi.fuseConnection = fconn | ||||
| 
 | ||||
| 	mounterr := make(chan error, 1) | ||||
| 	serverr := make(chan error, 1) | ||||
| 	go func() { | ||||
| 		filesys := &FS{root: rootDir} | ||||
| 		log.Info(fmt.Sprintf("Serving %s at %s", mhash, cleanedMountPoint)) | ||||
| 		filesys := &SwarmRoot{root: rootDir} | ||||
| 		if err := fs.Serve(fconn, filesys); err != nil { | ||||
| 			mounterr <- err | ||||
| 			log.Warn(fmt.Sprintf("Could not Serve SwarmFileSystem error: %v", err)) | ||||
| 			serverr <- err | ||||
| 		} | ||||
| 
 | ||||
| 	}() | ||||
| 
 | ||||
| 	// Check if the mount process has an error to report.
 | ||||
| @ -183,7 +173,8 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { | ||||
| 		fuse.Unmount(cleanedMountPoint) | ||||
| 		return nil, errMountTimeout | ||||
| 
 | ||||
| 	case err := <-mounterr: | ||||
| 	case err := <-serverr: | ||||
| 		fuse.Unmount(cleanedMountPoint) | ||||
| 		log.Warn("Error serving swarm FUSE FS", "mountpoint", cleanedMountPoint, "err", err) | ||||
| 		return nil, err | ||||
| 
 | ||||
| @ -191,46 +182,47 @@ func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { | ||||
| 		log.Info("Now serving swarm FUSE FS", "manifest", mhash, "mountpoint", cleanedMountPoint) | ||||
| 	} | ||||
| 
 | ||||
| 	// Assemble and Store the mount information for future use
 | ||||
| 	mi := &MountInfo{ | ||||
| 		MountPoint:     cleanedMountPoint, | ||||
| 		ManifestHash:   mhash, | ||||
| 		resolvedKey:    key, | ||||
| 		rootDir:        rootDir, | ||||
| 		fuseConnection: fconn, | ||||
| 	} | ||||
| 	self.activeMounts[cleanedMountPoint] = mi | ||||
| 	return mi, nil | ||||
| } | ||||
| 
 | ||||
| func (self *SwarmFS) Unmount(mountpoint string) (bool, error) { | ||||
| 	self.activeLock.Lock() | ||||
| 	defer self.activeLock.Unlock() | ||||
| func (self *SwarmFS) Unmount(mountpoint string) (*MountInfo, error) { | ||||
| 
 | ||||
| 	self.swarmFsLock.Lock() | ||||
| 	defer self.swarmFsLock.Unlock() | ||||
| 
 | ||||
| 	cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	mountInfo := self.activeMounts[cleanedMountPoint] | ||||
| 
 | ||||
| 	if mountInfo == nil || mountInfo.MountPoint != cleanedMountPoint { | ||||
| 		return false, fmt.Errorf("%s is not mounted", cleanedMountPoint) | ||||
| 		return nil, fmt.Errorf("%s is not mounted", cleanedMountPoint) | ||||
| 	} | ||||
| 	err = fuse.Unmount(cleanedMountPoint) | ||||
| 	if err != nil { | ||||
| 		// TODO(jmozah): try forceful unmount if normal unmount fails
 | ||||
| 		return false, err | ||||
| 		err1 := externalUnMount(cleanedMountPoint) | ||||
| 		if err1 != nil { | ||||
| 			errStr := fmt.Sprintf("UnMount error: %v", err) | ||||
| 			log.Warn(errStr) | ||||
| 			return nil, err1 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// remove the mount information from the active map
 | ||||
| 	mountInfo.fuseConnection.Close() | ||||
| 	delete(self.activeMounts, cleanedMountPoint) | ||||
| 	return true, nil | ||||
| 
 | ||||
| 	succString := fmt.Sprintf("UnMounting %v succeeded", cleanedMountPoint) | ||||
| 	log.Info(succString) | ||||
| 
 | ||||
| 	return mountInfo, nil | ||||
| } | ||||
| 
 | ||||
| func (self *SwarmFS) Listmounts() []*MountInfo { | ||||
| 	self.activeLock.RLock() | ||||
| 	defer self.activeLock.RUnlock() | ||||
| 	self.swarmFsLock.RLock() | ||||
| 	defer self.swarmFsLock.RUnlock() | ||||
| 
 | ||||
| 	rows := make([]*MountInfo, 0, len(self.activeMounts)) | ||||
| 	for _, mi := range self.activeMounts { | ||||
							
								
								
									
										144
									
								
								swarm/fuse/swarmfs_util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								swarm/fuse/swarmfs_util.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,144 @@ | ||||
| // Copyright 2017 The go-ethereum Authors
 | ||||
| // This file is part of the go-ethereum library.
 | ||||
| //
 | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU Lesser General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| //
 | ||||
| // The go-ethereum library is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | ||||
| // GNU Lesser General Public License for more details.
 | ||||
| //
 | ||||
| // You should have received a copy of the GNU Lesser General Public License
 | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| // +build linux darwin freebsd
 | ||||
| 
 | ||||
| package fuse | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/ethereum/go-ethereum/log" | ||||
| 	"os/exec" | ||||
| 	"runtime" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| func externalUnMount(mountPoint string) error { | ||||
| 
 | ||||
| 	var cmd *exec.Cmd | ||||
| 
 | ||||
| 	switch runtime.GOOS { | ||||
| 
 | ||||
| 	case "darwin": | ||||
| 		cmd = exec.Command("/usr/bin/diskutil", "umount", "force", mountPoint) | ||||
| 
 | ||||
| 	case "linux": | ||||
| 		cmd = exec.Command("fusermount", "-u", mountPoint) | ||||
| 
 | ||||
| 	default: | ||||
| 		return fmt.Errorf("unmount: unimplemented") | ||||
| 	} | ||||
| 
 | ||||
| 	errc := make(chan error, 1) | ||||
| 	go func() { | ||||
| 		defer close(errc) | ||||
| 
 | ||||
| 		if err := exec.Command("umount", mountPoint).Run(); err == nil { | ||||
| 			return | ||||
| 		} | ||||
| 		errc <- cmd.Run() | ||||
| 	}() | ||||
| 
 | ||||
| 	select { | ||||
| 
 | ||||
| 	case <-time.After(unmountTimeout): | ||||
| 		return fmt.Errorf("umount timeout") | ||||
| 
 | ||||
| 	case err := <-errc: | ||||
| 		return err | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func addFileToSwarm(sf *SwarmFile, content []byte, size int) error { | ||||
| 
 | ||||
| 	fkey, mhash, err := sf.mountInfo.swarmApi.AddFile(sf.mountInfo.LatestManifest, sf.path, sf.name, content, true) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	sf.lock.Lock() | ||||
| 	defer sf.lock.Unlock() | ||||
| 	sf.key = fkey | ||||
| 	sf.fileSize = int64(size) | ||||
| 
 | ||||
| 	sf.mountInfo.lock.Lock() | ||||
| 	defer sf.mountInfo.lock.Unlock() | ||||
| 	sf.mountInfo.LatestManifest = mhash | ||||
| 
 | ||||
| 	log.Info("Added new file:", "fname", sf.name, "New Manifest hash", mhash) | ||||
| 	return nil | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func removeFileFromSwarm(sf *SwarmFile) error { | ||||
| 
 | ||||
| 	mkey, err := sf.mountInfo.swarmApi.RemoveFile(sf.mountInfo.LatestManifest, sf.path, sf.name, true) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	sf.mountInfo.lock.Lock() | ||||
| 	defer sf.mountInfo.lock.Unlock() | ||||
| 	sf.mountInfo.LatestManifest = mkey | ||||
| 
 | ||||
| 	log.Info("Removed file:", "fname", sf.name, "New Manifest hash", mkey) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func removeDirectoryFromSwarm(sd *SwarmDir) error { | ||||
| 
 | ||||
| 	if len(sd.directories) == 0 && len(sd.files) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	for _, d := range sd.directories { | ||||
| 		err := removeDirectoryFromSwarm(d) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, f := range sd.files { | ||||
| 		err := removeFileFromSwarm(f) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func appendToExistingFileInSwarm(sf *SwarmFile, content []byte, offset int64, length int64) error { | ||||
| 
 | ||||
| 	fkey, mhash, err := sf.mountInfo.swarmApi.AppendFile(sf.mountInfo.LatestManifest, sf.path, sf.name, sf.fileSize, content, sf.key, offset, length, true) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	sf.lock.Lock() | ||||
| 	defer sf.lock.Unlock() | ||||
| 	sf.key = fkey | ||||
| 	sf.fileSize = sf.fileSize + int64(len(content)) | ||||
| 
 | ||||
| 	sf.mountInfo.lock.Lock() | ||||
| 	defer sf.mountInfo.lock.Unlock() | ||||
| 	sf.mountInfo.LatestManifest = mhash | ||||
| 
 | ||||
| 	log.Info("Appended file:", "fname", sf.name, "New Manifest hash", mhash) | ||||
| 	return nil | ||||
| 
 | ||||
| } | ||||
| @ -34,6 +34,7 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/rpc" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/api" | ||||
| 	httpapi "github.com/ethereum/go-ethereum/swarm/api/http" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/fuse" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/network" | ||||
| 	"github.com/ethereum/go-ethereum/swarm/storage" | ||||
| ) | ||||
| @ -54,7 +55,7 @@ type Swarm struct { | ||||
| 	corsString  string | ||||
| 	swapEnabled bool | ||||
| 	lstore      *storage.LocalStore // local store, needs to store for releasing resources after node stopped
 | ||||
| 	sfs         *api.SwarmFS        // need this to cleanup all the active mounts on node exit
 | ||||
| 	sfs         *fuse.SwarmFS       // need this to cleanup all the active mounts on node exit
 | ||||
| } | ||||
| 
 | ||||
| type SwarmAPI struct { | ||||
| @ -143,7 +144,7 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api. | ||||
| 	// Manifests for Smart Hosting
 | ||||
| 	log.Debug(fmt.Sprintf("-> Web3 virtual server API")) | ||||
| 
 | ||||
| 	self.sfs = api.NewSwarmFS(self.api) | ||||
| 	self.sfs = fuse.NewSwarmFS(self.api) | ||||
| 	log.Debug("-> Initializing Fuse file system") | ||||
| 
 | ||||
| 	return self, nil | ||||
| @ -262,7 +263,7 @@ func (self *Swarm) APIs() []rpc.API { | ||||
| 		}, | ||||
| 		{ | ||||
| 			Namespace: "swarmfs", | ||||
| 			Version:   api.Swarmfs_Version, | ||||
| 			Version:   fuse.Swarmfs_Version, | ||||
| 			Service:   self.sfs, | ||||
| 			Public:    false, | ||||
| 		}, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user