Added samples/roloopbackfs (#95)

geesefs-0-30-9
Liri S 2021-03-30 14:24:55 +03:00 committed by GitHub
parent e0296dec95
commit 9677d03922
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 605 additions and 0 deletions

View File

@ -0,0 +1,74 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"flag"
"log"
"os"
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/samples/roloopbackfs"
)
var fPhysicalPath = flag.String("path", "", "Physical path to loopback.")
var fMountPoint = flag.String("mount_point", "", "Path to mount point.")
var fDebug = flag.Bool("debug", false, "Enable debug logging.")
func main() {
flag.Parse()
debugLogger := log.New(os.Stdout, "fuse: ", 0)
errorLogger := log.New(os.Stderr, "fuse: ", 0)
if *fPhysicalPath == "" {
log.Fatalf("You must set --path.")
}
if *fMountPoint == "" {
log.Fatalf("You must set --mount_point.")
}
err := os.MkdirAll(*fMountPoint, 0777)
if err != nil {
log.Fatalf("Failed to create mount point at '%v'", *fMountPoint)
}
server, err := roloopbackfs.NewReadonlyLoopbackServer(*fPhysicalPath, errorLogger)
if err != nil {
log.Fatalf("makeFS: %v", err)
}
cfg := &fuse.MountConfig{
ReadOnly: true,
ErrorLogger: errorLogger,
}
if *fDebug {
cfg.DebugLogger = debugLogger
}
mfs, err := fuse.Mount(*fMountPoint, server, cfg)
if err != nil {
log.Fatalf("Mount: %v", err)
}
// Wait for it to be unmounted.
if err = mfs.Join(context.Background()); err != nil {
log.Fatalf("Join: %v", err)
}
}

View File

@ -0,0 +1,151 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package roloopbackfs
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"sync/atomic"
"time"
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil"
)
var (
uid = uint32(os.Getuid())
gid = uint32(os.Getgid())
allocatedInodeId uint64 = fuseops.RootInodeID
)
func nextInodeID() (next fuseops.InodeID) {
nextInodeId := atomic.AddUint64(&allocatedInodeId, 1)
return fuseops.InodeID(nextInodeId)
}
type Inode interface {
Id() fuseops.InodeID
Path() string
String() string
Attributes() (*fuseops.InodeAttributes, error)
ListChildren(inodes *sync.Map) ([]*fuseutil.Dirent, error)
Contents() ([]byte, error)
}
func getOrCreateInode(inodes *sync.Map, parentId fuseops.InodeID, name string) (Inode, error) {
parent, found := inodes.Load(parentId)
if !found {
return nil, nil
}
parentPath := parent.(Inode).Path()
entries, err := ioutil.ReadDir(parentPath)
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.Name() == name {
inodeEntry := &inodeEntry{
id: nextInodeID(),
path: filepath.Join(parentPath, name),
}
storedEntry, _ := inodes.LoadOrStore(inodeEntry.id, inodeEntry)
return storedEntry.(Inode), nil
}
}
return nil, nil
}
type inodeEntry struct {
id fuseops.InodeID
path string
}
var _ Inode = &inodeEntry{}
func NewInode(path string) (Inode, error) {
return &inodeEntry{
id: nextInodeID(),
path: path,
}, nil
}
func (in *inodeEntry) Id() fuseops.InodeID {
return in.id
}
func (in *inodeEntry) Path() string {
return in.path
}
func (in *inodeEntry) String() string {
return fmt.Sprintf("%v::%v", in.id, in.path)
}
func (in *inodeEntry) Attributes() (*fuseops.InodeAttributes, error) {
fileInfo, err := os.Stat(in.path)
if err != nil {
return &fuseops.InodeAttributes{}, err
}
return &fuseops.InodeAttributes{
Size: uint64(fileInfo.Size()),
Nlink: 1,
Mode: fileInfo.Mode(),
Atime: fileInfo.ModTime(),
Mtime: fileInfo.ModTime(),
Ctime: time.Now(),
Crtime: time.Now(),
Uid: uid,
Gid: gid,
}, nil
}
func (in *inodeEntry) ListChildren(inodes *sync.Map) ([]*fuseutil.Dirent, error) {
children, err := ioutil.ReadDir(in.path)
if err != nil {
return nil, err
}
dirents := make([]*fuseutil.Dirent, len(children))
for i, child := range children {
childInode, err := getOrCreateInode(inodes, in.id, child.Name())
if err != nil || childInode == nil {
return nil, nil
}
var childType fuseutil.DirentType
if child.IsDir() {
childType = fuseutil.DT_Directory
} else if child.Mode()&os.ModeSymlink != 0 {
childType = fuseutil.DT_Link
} else {
childType = fuseutil.DT_File
}
dirents[i] = &fuseutil.Dirent{
Offset: fuseops.DirOffset(i + 1),
Inode: childInode.Id(),
Name: child.Name(),
Type: childType,
}
}
return dirents, nil
}
func (in *inodeEntry) Contents() ([]byte, error) {
return ioutil.ReadFile(in.path)
}

View File

@ -0,0 +1,202 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package roloopbackfs
import (
"golang.org/x/net/context"
"log"
"os"
"sync"
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil"
)
type readonlyLoopbackFs struct {
fuseutil.NotImplementedFileSystem
loopbackPath string
inodes *sync.Map
logger *log.Logger
}
var _ fuseutil.FileSystem = &readonlyLoopbackFs{}
// Create a file system that mirrors an existing physical path, in a readonly mode
func NewReadonlyLoopbackServer(loopbackPath string, logger *log.Logger) (server fuse.Server, err error) {
if _, err = os.Stat(loopbackPath); err != nil {
return nil, err
}
inodes := &sync.Map{}
root := &inodeEntry{
id: fuseops.RootInodeID,
path: loopbackPath,
}
inodes.Store(root.Id(), root)
server = fuseutil.NewFileSystemServer(&readonlyLoopbackFs{
loopbackPath: loopbackPath,
inodes: inodes,
logger: logger,
})
return
}
func (fs *readonlyLoopbackFs) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) error {
return nil
}
func (fs *readonlyLoopbackFs) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) error {
entry, err := getOrCreateInode(fs.inodes, op.Parent, op.Name)
if err != nil {
fs.logger.Printf("fs.LookUpInode for '%v' on '%v': %v", entry, op.Name, err)
return fuse.EIO
}
if entry == nil {
return fuse.ENOENT
}
outputEntry := &op.Entry
outputEntry.Child = entry.Id()
attributes, err := entry.Attributes()
if err != nil {
fs.logger.Printf("fs.LookUpInode.Attributes for '%v' on '%v': %v", entry, op.Name, err)
return fuse.EIO
}
outputEntry.Attributes = *attributes
return nil
}
func (fs *readonlyLoopbackFs) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) error {
var entry, found = fs.inodes.Load(op.Inode)
if !found {
return fuse.ENOENT
}
attributes, err := entry.(Inode).Attributes()
if err != nil {
fs.logger.Printf("fs.GetInodeAttributes for '%v': %v", entry, err)
return fuse.EIO
}
op.Attributes = *attributes
return nil
}
func (fs *readonlyLoopbackFs) OpenDir(
ctx context.Context,
op *fuseops.OpenDirOp) error {
// Allow opening any directory.
return nil
}
func (fs *readonlyLoopbackFs) ReadDir(
ctx context.Context,
op *fuseops.ReadDirOp) error {
var entry, found = fs.inodes.Load(op.Inode)
if !found {
return fuse.ENOENT
}
children, err := entry.(Inode).ListChildren(fs.inodes)
if err != nil {
fs.logger.Printf("fs.ReadDir for '%v': %v", entry, err)
return fuse.EIO
}
if op.Offset > fuseops.DirOffset(len(children)) {
return fuse.EIO
}
children = children[op.Offset:]
for _, child := range children {
bytesWritten := fuseutil.WriteDirent(op.Dst[op.BytesRead:], *child)
if bytesWritten == 0 {
break
}
op.BytesRead += bytesWritten
}
return nil
}
func (fs *readonlyLoopbackFs) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) error {
// Allow opening any file.
return nil
}
func (fs *readonlyLoopbackFs) ReadFile(
ctx context.Context,
op *fuseops.ReadFileOp) error {
var entry, found = fs.inodes.Load(op.Inode)
if !found {
return fuse.ENOENT
}
contents, err := entry.(Inode).Contents()
if err != nil {
fs.logger.Printf("fs.ReadFile for '%v': %v", entry, err)
return fuse.EIO
}
if op.Offset > int64(len(contents)) {
return fuse.EIO
}
contents = contents[op.Offset:]
op.BytesRead = copy(op.Dst, contents)
return nil
}
func (fs *readonlyLoopbackFs) ReleaseDirHandle(
ctx context.Context,
op *fuseops.ReleaseDirHandleOp) error {
return nil
}
func (fs *readonlyLoopbackFs) GetXattr(
ctx context.Context,
op *fuseops.GetXattrOp) error {
return nil
}
func (fs *readonlyLoopbackFs) ListXattr(
ctx context.Context,
op *fuseops.ListXattrOp) error {
return nil
}
func (fs *readonlyLoopbackFs) ForgetInode(
ctx context.Context,
op *fuseops.ForgetInodeOp) error {
return nil
}
func (fs *readonlyLoopbackFs) ReleaseFileHandle(
ctx context.Context,
op *fuseops.ReleaseFileHandleOp) error {
return nil
}
func (fs *readonlyLoopbackFs) FlushFile(
ctx context.Context,
op *fuseops.FlushFileOp) error {
return nil
}

View File

@ -0,0 +1,178 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package roloopbackfs_test
import (
"fmt"
"io/ioutil"
"log"
"math/rand"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/jacobsa/fuse/samples"
"github.com/jacobsa/fuse/samples/roloopbackfs"
. "github.com/jacobsa/ogletest"
)
var (
letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
)
func TestReadonlyLoopbackFS(t *testing.T) { RunTests(t) }
type ReadonlyLoopbackFSTest struct {
samples.SampleTest
physicalPath string
}
func init() {
RegisterTestSuite(&ReadonlyLoopbackFSTest{})
rand.Seed(time.Now().UnixNano())
}
func (t *ReadonlyLoopbackFSTest) SetUp(ti *TestInfo) {
var err error
t.physicalPath, err = ioutil.TempDir("", "")
if err != nil {
panic(err)
}
err = os.MkdirAll(t.physicalPath, 0777)
if err != nil {
panic(err)
}
t.fillPhysicalFS()
t.Server, err = roloopbackfs.NewReadonlyLoopbackServer(
t.physicalPath,
log.New(os.Stdout, "", 0),
)
AssertEq(nil, err)
t.SampleTest.SetUp(ti)
}
func (t *ReadonlyLoopbackFSTest) TearDown() {
t.SampleTest.TearDown()
err := os.RemoveAll(t.physicalPath)
if err != nil {
panic(err)
}
}
func createDirectories(parentPath string, namePrefix string, count int, onDir func(dirPath string)) {
var err error
for i := 0; i < count; i++ {
dirName := fmt.Sprintf("%v_%v", namePrefix, i+1)
dirPath := filepath.Join(parentPath, dirName)
err = os.Mkdir(dirPath, 0777)
if err != nil {
panic(err)
}
if onDir != nil {
onDir(dirPath)
}
}
}
func randomString(n int) []byte {
bytes := make([]byte, n)
for i := range bytes {
bytes[i] = letters[rand.Intn(len(letters))]
}
return bytes
}
func (t *ReadonlyLoopbackFSTest) fillPhysicalFS() {
var err error
createDirectories(t.physicalPath, "top_dir", 10, func(dirPath string) {
fileName := fmt.Sprintf("secondary_file.txt")
contents := randomString(17)
err = ioutil.WriteFile(filepath.Join(dirPath, fileName), contents, 0777)
if err != nil {
panic(err)
}
createDirectories(dirPath, "secondary_dir", 5, func(dirPath string) {
for i := 0; i < 3; i++ {
fileName := fmt.Sprintf("file_%v.txt", i+1)
contents := randomString(i * 10)
err = ioutil.WriteFile(filepath.Join(dirPath, fileName), contents, 0777)
if err != nil {
panic(err)
}
}
})
})
}
func (t *ReadonlyLoopbackFSTest) ListDirUsingWalk() {
countedFiles, countedDirs := 0, 0
err := filepath.Walk(t.Dir, func(path string, info os.FileInfo, err error) error {
AssertNe(nil, info)
if info.IsDir() {
countedDirs++
} else {
if strings.Contains(path, "file_1.txt") {
AssertEq(0, info.Size())
} else {
AssertTrue(info.Size() > 0)
}
countedFiles++
}
return nil
})
AssertEq(nil, err)
AssertEq(1+10+10*5, countedDirs)
AssertEq(10+10*5*3, countedFiles)
}
func (t *ReadonlyLoopbackFSTest) ListDirUsingDirectQuery() {
infos, err := ioutil.ReadDir(filepath.Join(t.Dir, "top_dir_3"))
AssertEq(nil, err)
AssertEq(1+5, len(infos))
for i := 0; i < 5; i++ {
AssertEq(fmt.Sprintf("secondary_dir_%v", i+1), infos[i].Name())
AssertTrue(infos[i].IsDir())
}
AssertEq("secondary_file.txt", infos[5].Name())
AssertFalse(infos[5].IsDir())
infos, err = ioutil.ReadDir(filepath.Join(t.Dir, "top_dir_4", "secondary_dir_1"))
AssertEq(nil, err)
AssertEq(3, len(infos))
for i := 0; i < 3; i++ {
AssertEq(fmt.Sprintf("file_%v.txt", i+1), infos[i].Name())
AssertFalse(infos[i].IsDir())
}
}
func (t *ReadonlyLoopbackFSTest) ReadFile() {
bytes, err := ioutil.ReadFile(filepath.Join(t.Dir, "top_dir_1", "secondary_file.txt"))
AssertEq(nil, err)
AssertEq(17, len(bytes))
bytes, err = ioutil.ReadFile(filepath.Join(t.Dir, "top_dir_1", "secondary_dir_3", "file_1.txt"))
AssertEq(nil, err)
AssertEq(0, len(bytes))
bytes, err = ioutil.ReadFile(filepath.Join(t.Dir, "top_dir_1", "secondary_dir_3", "file_3.txt"))
AssertEq(nil, err)
AssertEq(20, len(bytes))
}