From 1d1d988aa7caf60d6769bb474d4ec2f872eaaad4 Mon Sep 17 00:00:00 2001 From: Zahoor Mohamed Date: Wed, 12 Apr 2017 05:36:02 +0530 Subject: 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 --- swarm/api/api.go | 191 +++++++++++++++++++++++++++++++- swarm/api/filesystem.go | 5 +- swarm/api/fuse.go | 133 ---------------------- swarm/api/manifest.go | 3 +- swarm/api/storage.go | 2 +- swarm/api/swarmfs.go | 43 -------- swarm/api/swarmfs_fallback.go | 50 --------- swarm/api/swarmfs_test.go | 115 -------------------- swarm/api/swarmfs_unix.go | 248 ------------------------------------------ 9 files changed, 195 insertions(+), 595 deletions(-) delete mode 100644 swarm/api/fuse.go delete mode 100644 swarm/api/swarmfs.go delete mode 100644 swarm/api/swarmfs_fallback.go delete mode 100644 swarm/api/swarmfs_test.go delete mode 100644 swarm/api/swarmfs_unix.go (limited to 'swarm/api') diff --git a/swarm/api/api.go b/swarm/api/api.go index ba1156f7e..f58b7a53d 100644 --- a/swarm/api/api.go +++ b/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 +} diff --git a/swarm/api/filesystem.go b/swarm/api/filesystem.go index e7deaa32f..f5dc90e2e 100644 --- a/swarm/api/filesystem.go +++ b/swarm/api/filesystem.go @@ -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 } diff --git a/swarm/api/fuse.go b/swarm/api/fuse.go deleted file mode 100644 index 2a1cc9bf1..000000000 --- a/swarm/api/fuse.go +++ /dev/null @@ -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 . - -// +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 -} diff --git a/swarm/api/manifest.go b/swarm/api/manifest.go index 6b3630fd0..dbaaf4bff 100644 --- a/swarm/api/manifest.go +++ b/swarm/api/manifest.go @@ -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) diff --git a/swarm/api/storage.go b/swarm/api/storage.go index 7e94a9653..0e3abecfe 100644 --- a/swarm/api/storage.go +++ b/swarm/api/storage.go @@ -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 diff --git a/swarm/api/swarmfs.go b/swarm/api/swarmfs.go deleted file mode 100644 index 78a61cf9d..000000000 --- a/swarm/api/swarmfs.go +++ /dev/null @@ -1,43 +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 . - -package api - -import ( - "sync" - "time" -) - -const ( - Swarmfs_Version = "0.1" - mountTimeout = time.Second * 5 - maxFuseMounts = 5 -) - -type SwarmFS struct { - swarmApi *Api - activeMounts map[string]*MountInfo - activeLock *sync.RWMutex -} - -func NewSwarmFS(api *Api) *SwarmFS { - swarmfs := &SwarmFS{ - swarmApi: api, - activeLock: &sync.RWMutex{}, - activeMounts: map[string]*MountInfo{}, - } - return swarmfs -} diff --git a/swarm/api/swarmfs_fallback.go b/swarm/api/swarmfs_fallback.go deleted file mode 100644 index c6ac07d14..000000000 --- a/swarm/api/swarmfs_fallback.go +++ /dev/null @@ -1,50 +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 . - -// +build !linux,!darwin,!freebsd - -package api - -import ( - "errors" -) - -var errNoFUSE = errors.New("FUSE is not supported on this platform") - -func isFUSEUnsupportedError(err error) bool { - return err == errNoFUSE -} - -type MountInfo struct { - MountPoint string - ManifestHash string -} - -func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { - return nil, errNoFUSE -} - -func (self *SwarmFS) Unmount(mountpoint string) (bool, error) { - return false, errNoFUSE -} - -func (self *SwarmFS) Listmounts() ([]*MountInfo, error) { - return nil, errNoFUSE -} - -func (self *SwarmFS) Stop() error { - return nil -} diff --git a/swarm/api/swarmfs_test.go b/swarm/api/swarmfs_test.go deleted file mode 100644 index 45d2dc169..000000000 --- a/swarm/api/swarmfs_test.go +++ /dev/null @@ -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 . - -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") - }) -} diff --git a/swarm/api/swarmfs_unix.go b/swarm/api/swarmfs_unix.go deleted file mode 100644 index a704c1ec2..000000000 --- a/swarm/api/swarmfs_unix.go +++ /dev/null @@ -1,248 +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 . - -// +build linux darwin freebsd - -package api - -import ( - "errors" - "fmt" - "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") -) - -func isFUSEUnsupportedError(err error) bool { - if perr, ok := err.(*os.PathError); ok { - return perr.Op == "open" && perr.Path == "/dev/fuse" - } - return err == fuse.ErrOSXFUSENotFound -} - -// MountInfo contains information about every active mount -type MountInfo struct { - MountPoint string - ManifestHash string - resolvedKey storage.Key - rootDir *Dir - fuseConnection *fuse.Conn -} - -// newInode creates a new inode number. -// 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 -} - -func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { - if mountpoint == "" { - return nil, errEmptyMountPoint - } - cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) - if err != nil { - return nil, err - } - - self.activeLock.Lock() - defer self.activeLock.Unlock() - - noOfActiveMounts := len(self.activeMounts) - if noOfActiveMounts >= maxFuseMounts { - return nil, errMaxMountCount - } - - if _, ok := self.activeMounts[cleanedMountPoint]; ok { - return nil, fmt.Errorf("%s is already mounted", cleanedMountPoint) - } - - uri, err := Parse("bzz:/" + mhash) - if err != nil { - return nil, err - } - key, err := self.swarmApi.Resolve(uri) - if err != nil { - return nil, err - } - - path := uri.Path - if len(path) > 0 { - path += "/" - } - - 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]*Dir{} - - 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 := "" - paths := strings.Split(basepath, "/") - for i := range paths { - if paths[i] != "" { - thisDir := paths[i] - dirUntilNow = dirUntilNow + "/" + thisDir - - if _, ok := dirTree[dirUntilNow]; !ok { - dirTree[dirUntilNow] = &Dir{ - inode: newInode(), - name: thisDir, - path: dirUntilNow, - directories: nil, - files: nil, - } - parentDir.directories = append(parentDir.directories, dirTree[dirUntilNow]) - parentDir = dirTree[dirUntilNow] - - } else { - parentDir = dirTree[dirUntilNow] - } - - } - } - thisFile := &File{ - inode: newInode(), - name: filename, - path: fullpath, - key: key, - swarmApi: self.swarmApi, - } - parentDir.files = append(parentDir.files, thisFile) - }) - - fconn, err := fuse.Mount(cleanedMountPoint, fuse.FSName("swarmfs"), fuse.VolumeName(mhash)) - if err != nil { - fuse.Unmount(cleanedMountPoint) - log.Warn("Error mounting swarm manifest", "mountpoint", cleanedMountPoint, "err", err) - return nil, err - } - - mounterr := make(chan error, 1) - go func() { - filesys := &FS{root: rootDir} - if err := fs.Serve(fconn, filesys); err != nil { - mounterr <- err - } - }() - - // Check if the mount process has an error to report. - select { - case <-time.After(mountTimeout): - fuse.Unmount(cleanedMountPoint) - return nil, errMountTimeout - - case err := <-mounterr: - log.Warn("Error serving swarm FUSE FS", "mountpoint", cleanedMountPoint, "err", err) - return nil, err - - case <-fconn.Ready: - 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() - - cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) - if err != nil { - return false, err - } - - mountInfo := self.activeMounts[cleanedMountPoint] - if mountInfo == nil || mountInfo.MountPoint != cleanedMountPoint { - return false, 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 - } - - // remove the mount information from the active map - mountInfo.fuseConnection.Close() - delete(self.activeMounts, cleanedMountPoint) - return true, nil -} - -func (self *SwarmFS) Listmounts() []*MountInfo { - self.activeLock.RLock() - defer self.activeLock.RUnlock() - - rows := make([]*MountInfo, 0, len(self.activeMounts)) - for _, mi := range self.activeMounts { - rows = append(rows, mi) - } - return rows -} - -func (self *SwarmFS) Stop() bool { - for mp := range self.activeMounts { - mountInfo := self.activeMounts[mp] - self.Unmount(mountInfo.MountPoint) - } - return true -} -- cgit