Toby Chui 1 сар өмнө
parent
commit
a24421f59e

+ 1 - 0
go.mod

@@ -4,6 +4,7 @@ go 1.23.2
 
 require (
 	github.com/anatol/smart.go v0.0.0-20241126061019-f03d79b340d2
+	github.com/google/uuid v1.6.0
 	golang.org/x/net v0.35.0
 )
 

+ 2 - 0
go.sum

@@ -4,6 +4,8 @@ github.com/anatol/vmtest v0.0.0-20230711210602-87511df0d4bc h1:xMQuzBhj6hXQZufed
 github.com/anatol/vmtest v0.0.0-20230711210602-87511df0d4bc/go.mod h1:NC+g66bgkUjV1unIJXhHO35RHxVViWUzNeeKAkkO7DU=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

+ 17 - 3
main.go

@@ -2,9 +2,11 @@ package main
 
 import (
 	"flag"
+	"fmt"
 	"strconv"
 
-	"imuslab.com/bokofs/bokofsd/mod/webdav"
+	"imuslab.com/bokofs/bokofsd/mod/bokofs"
+	"imuslab.com/bokofs/bokofsd/mod/bokofs/bokoworker"
 )
 
 func main() {
@@ -14,16 +16,28 @@ func main() {
 
 	flag.Parse()
 
-	webdavHandler := webdav.NewWebdavInterfaceServer(webdav.Options{
+	//Create a test worker register at /test
+	testWorker, err := bokoworker.GetDefaultWorker("/teacat")
+	if err != nil {
+		panic(err)
+	}
+
+	webdavHandler, err := bokofs.NewWebdavInterfaceServer(bokofs.Options{
 		ListeningAddress: ":" + strconv.Itoa(*httpPort),
 		SecureServe:      *serveSecure,
 	})
+	if err != nil {
+		panic(err)
+	}
+
+	webdavHandler.AddWorker(testWorker)
 
-	err := webdavHandler.Start()
+	err = webdavHandler.Start()
 	if err != nil {
 		panic(err)
 	}
 
+	fmt.Println("Bokofs daemon started")
 	select {}
 }
 

+ 89 - 0
mod/bokofs/bokofile/bokodir.go

@@ -0,0 +1,89 @@
+package bokofile
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"golang.org/x/net/webdav"
+)
+
+/*
+	bokodir.go
+
+	The bokodir implements a disk based file system from the webdav.FileSystem interface
+	A file in this implementation corrisponding to a real file on disk
+*/
+
+type RouterDir struct {
+	Prefix   string //Path prefix to trim, usually is the root path of the worker
+	DiskPath string //Disk path to create a file system from
+	ReadOnly bool   //Label this worker as read only
+
+	/* Private Properties */
+	dir webdav.Dir
+}
+
+// Create a routerdir from a directory
+func CreateRouterFromDir(dir string, prefix string, readonly bool) (*RouterDir, error) {
+	if _, err := os.Stat(dir); os.IsNotExist(err) {
+		return nil, err
+	}
+
+	//Initiate the dir
+	fs := webdav.Dir(dir)
+
+	return &RouterDir{
+		Prefix:   prefix,
+		DiskPath: dir,
+		ReadOnly: readonly,
+		dir:      fs,
+	}, nil
+}
+
+func (r *RouterDir) CleanPrefix(name string) string {
+	name = filepath.ToSlash(filepath.Clean(name)) + "/"
+	fmt.Println("[Bokodir]", r.Prefix, name, strings.TrimPrefix(name, r.Prefix))
+	return strings.TrimPrefix(name, r.Prefix)
+}
+
+func (r *RouterDir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
+	// Implement the Mkdir method
+	name = r.CleanPrefix(name)
+	fmt.Println("[Bokodir]", "Mkdir called to "+name)
+	return r.dir.Mkdir(ctx, name, perm)
+}
+
+func (r *RouterDir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
+	// Implement the OpenFile method
+	name = r.CleanPrefix(name)
+	fmt.Println("[Bokodir]", "OpenFile called to "+name)
+	return r.dir.OpenFile(ctx, name, flag, perm)
+}
+
+func (r *RouterDir) RemoveAll(ctx context.Context, name string) error {
+	// Implement the RemoveAll method
+	name = r.CleanPrefix(name)
+	fmt.Println("[Bokodir]", "RemoveAll called to "+name)
+	return r.dir.RemoveAll(ctx, name)
+}
+
+func (r *RouterDir) Rename(ctx context.Context, oldName, newName string) error {
+	// Implement the Rename method
+	oldName = r.CleanPrefix(oldName)
+	newName = r.CleanPrefix(newName)
+	fmt.Println("[Bokodir]", "Rename called from "+oldName+" to "+newName)
+	return r.dir.Rename(ctx, oldName, newName)
+}
+
+func (r *RouterDir) Stat(ctx context.Context, name string) (os.FileInfo, error) {
+	// Implement the Stat method
+	name = r.CleanPrefix(name)
+	fmt.Println("[Bokodir]", "Stat called to "+name)
+	return r.dir.Stat(ctx, name)
+}
+
+// Ensure RouterDir implements the FileSystem interface
+var _ webdav.FileSystem = (*RouterDir)(nil)

+ 0 - 0
mod/bokofile/bokofile.go → mod/bokofs/bokofile/bokofile.go


+ 25 - 7
mod/webdav/webdav.go → mod/bokofs/bokofs.go

@@ -1,4 +1,4 @@
-package webdav
+package bokofs
 
 import (
 	"context"
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"golang.org/x/net/webdav"
+	"imuslab.com/bokofs/bokofsd/mod/bokofs/bokoworker"
 )
 
 /*
@@ -28,23 +29,39 @@ type Options struct {
 
 type Server struct {
 	LoadedWorkers sync.Map //Storing uuid to bokoworker pointer (*bokoworker.Worker)
-	RouterDir     *RouterDir
+	RootRouter    FlowRouter
 	Options       *Options
 }
 
 /* NewWebdavInterfaceServer creates a new WebDAV server instance */
-func NewWebdavInterfaceServer(options Options) *Server {
-	newRouter := &RouterDir{}
-	return &Server{
+func NewWebdavInterfaceServer(options Options) (*Server, error) {
+	thisServer := Server{
 		LoadedWorkers: sync.Map{},
-		RouterDir:     newRouter,
 		Options:       &options,
 	}
+
+	//TODO: Load all the middlewares
+
+	//Initiate the root router file system
+	err := thisServer.InitiateRootRouter()
+	if err != nil {
+		return nil, err
+	}
+
+	return &thisServer, nil
+}
+
+func (s *Server) AddWorker(worker *bokoworker.Worker) {
+	s.LoadedWorkers.Store(worker.RootPath, worker)
+}
+
+func (s *Server) RemoveWorker(workerRootPath string) {
+	s.LoadedWorkers.Delete(workerRootPath)
 }
 
 func (s *Server) Start() error {
 	srv := &webdav.Handler{
-		FileSystem: s.RouterDir,
+		FileSystem: s.RootRouter,
 		LockSystem: webdav.NewMemLS(),
 		Logger: func(r *http.Request, err error) {
 			if err != nil {
@@ -54,6 +71,7 @@ func (s *Server) Start() error {
 			}
 		},
 	}
+
 	http.Handle("/", srv)
 
 	if s.Options.SecureServe {

+ 74 - 0
mod/bokofs/bokoworker/bokoworker.go

@@ -0,0 +1,74 @@
+package bokoworker
+
+import (
+	"encoding/json"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/uuid"
+	"imuslab.com/bokofs/bokofsd/mod/bokofs/bokofile"
+)
+
+/*
+	Boko Worker
+
+	A boko worker is an instance of WebDAV file server that serves a specific
+	disk partition or subpath in which the user can interact with the disk
+	via WebDAV interface
+*/
+
+type Worker struct {
+	/* Worker Properties */
+	RootPath  string //The root path (also act as ID) request by this worker, e.g. /disk1
+	DiskUUID  string // Disk UUID, when provided, will be used instead of disk path
+	DiskPath  string // Disk device path (e.g. /dev/sda1), Disk UUID will have higher priority
+	Subpath   string // Subpath to serve, default is root
+	ReadOnly  bool   //Label this worker as read only
+	AutoMount bool   //Automatically mount the disk if not mounted
+
+	/* Private Properties */
+	Filesystem *bokofile.RouterDir
+}
+
+// GetDefaultWorker Generate and return a default worker serving ./ (CWD)
+func GetDefaultWorker(rootdir string) (*Worker, error) {
+	if !strings.HasPrefix(rootdir, "/") {
+		rootdir = "/" + rootdir
+	}
+
+	mountPath, _ := filepath.Abs("./")
+	fs, err := bokofile.CreateRouterFromDir(mountPath, rootdir, false)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Worker{
+		RootPath:  rootdir,
+		DiskUUID:  "",
+		DiskPath:  "",
+		Subpath:   mountPath,
+		ReadOnly:  false,
+		AutoMount: false,
+
+		Filesystem: fs,
+	}, nil
+}
+
+func GetWorkerFromConfig(configFilePath string) (*Worker, error) {
+	randomRootPath := uuid.New().String()
+	thisWorker, _ := GetDefaultWorker("/" + randomRootPath)
+	configFile, err := os.ReadFile(configFilePath)
+	if err != nil {
+		return nil, err
+	}
+
+	//parse the config file into thisWorker
+	err = json.Unmarshal(configFile, &thisWorker)
+	if err != nil {
+		return nil, err
+	}
+
+	//TODO: Start the worker
+	return thisWorker, nil
+}

+ 118 - 0
mod/bokofs/router.go

@@ -0,0 +1,118 @@
+package bokofs
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"golang.org/x/net/webdav"
+	"imuslab.com/bokofs/bokofsd/mod/bokofs/bokoworker"
+)
+
+type RootRouter struct {
+	parent *Server
+}
+
+// InitiateRootRouter create and prepare a virtual root file system for
+// this bokoFS instance
+func (s *Server) InitiateRootRouter() error {
+	s.RootRouter = &RootRouter{
+		parent: s,
+	}
+	return nil
+}
+
+func (r *RootRouter) GetRootDir(name string) string {
+	if name == "" {
+		return "/"
+	}
+	name = filepath.ToSlash(filepath.Clean(name))
+	pathChunks := strings.Split(name, "/")
+	reqRootPath := "/" + pathChunks[1]
+	fmt.Println("Requesting Root Path: ", reqRootPath)
+	reqRootPath = strings.TrimSuffix(reqRootPath, "/")
+	return reqRootPath
+}
+
+func (r *RootRouter) GetWorkerByPath(name string) (*bokoworker.Worker, error) {
+	reqRootPath := r.GetRootDir(name)
+	targetWorker, ok := r.parent.LoadedWorkers.Load(reqRootPath)
+	if !ok {
+		return nil, os.ErrNotExist
+	}
+
+	return targetWorker.(*bokoworker.Worker), nil
+}
+
+func (r *RootRouter) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
+	// Implement the Mkdir method
+	fmt.Println("Mkdir called to " + name)
+	return nil
+}
+
+func (r *RootRouter) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
+	// Implement the OpenFile method
+	fmt.Println("OpenFile called to " + name)
+	if filepath.ToSlash(filepath.Base(name)) == "/" {
+		//Request to the vObject base path
+		thisVirtualObject := r.newVirtualObject(&vObjectProperties{
+			name:    name,
+			size:    0,
+			mode:    os.ModeDir,
+			modTime: time.Now(),
+			isDir:   true,
+		})
+
+		return thisVirtualObject, nil
+	}
+
+	targetWorker, err := r.GetWorkerByPath(name)
+	if err != nil {
+		return nil, err
+	}
+
+	return targetWorker.Filesystem.OpenFile(ctx, name, flag, perm)
+}
+
+func (r *RootRouter) RemoveAll(ctx context.Context, name string) error {
+	// Implement the RemoveAll method
+	fmt.Println("RemoveAll called to " + name)
+	return nil
+}
+
+func (r *RootRouter) Rename(ctx context.Context, oldName, newName string) error {
+	// Implement the Rename method
+	fmt.Println("Rename called from " + oldName + " to " + newName)
+	return nil
+}
+
+func (r *RootRouter) Stat(ctx context.Context, name string) (os.FileInfo, error) {
+	// Implement the Stat method
+	fmt.Println("Stat called to " + name)
+	if filepath.ToSlash(filepath.Base(name)) == "/" {
+		thisVirtualObject := r.newVirtualObject(&vObjectProperties{
+			name:    name,
+			size:    0,
+			mode:    os.ModeDir,
+			modTime: time.Now(),
+			isDir:   true,
+		})
+
+		thisVirtualObjectFileInfo := thisVirtualObject.GetFileInfo()
+
+		return thisVirtualObjectFileInfo, nil
+	}
+
+	targetWorker, err := r.GetWorkerByPath(name)
+	if err != nil {
+		return nil, err
+	}
+
+	return targetWorker.Filesystem.Stat(ctx, name)
+}
+
+// Ensure RootRouter implements the FileSystem interface
+var _ webdav.FileSystem = (*RootRouter)(nil)

+ 21 - 0
mod/bokofs/typedef.go

@@ -0,0 +1,21 @@
+package bokofs
+
+import (
+	"context"
+	"os"
+
+	"golang.org/x/net/webdav"
+)
+
+/*
+FlowRouter
+
+This interface is used to define the flow of the file system
+*/
+type FlowRouter interface {
+	Mkdir(ctx context.Context, name string, perm os.FileMode) error
+	OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error)
+	RemoveAll(ctx context.Context, name string) error
+	Rename(ctx context.Context, oldName, newName string) error
+	Stat(ctx context.Context, name string) (os.FileInfo, error)
+}

+ 16 - 0
mod/bokofs/utils.go

@@ -0,0 +1,16 @@
+package bokofs
+
+import "imuslab.com/bokofs/bokofsd/mod/bokofs/bokoworker"
+
+// GetRegisteredRootFolders returns all the registered root folders
+// by loaded bokoFS workers. This will be shown when the client
+// request the root path (/) of this bokoFS server
+func (s *Server) GetRegisteredRootFolders() ([]string, error) {
+	var rootFolders []string
+	s.LoadedWorkers.Range(func(key, value interface{}) bool {
+		thisWorker := value.(*bokoworker.Worker)
+		rootFolders = append(rootFolders, thisWorker.RootPath)
+		return true
+	})
+	return rootFolders, nil
+}

+ 136 - 0
mod/bokofs/vobject.go

@@ -0,0 +1,136 @@
+package bokofs
+
+/*
+	vObjects
+
+	The vObjects accept and forward the request of the WebDAV server to the
+	underlying workers that might be serving a file system or other
+	system management functions
+
+	vObjects and its definations shall only be used on the root layer of each
+	of the bokoFS instance.
+*/
+
+import (
+	"fmt"
+	"os"
+	"time"
+
+	"golang.org/x/net/webdav"
+)
+
+type vObjectProperties struct {
+	name    string
+	size    int64
+	mode    os.FileMode
+	modTime time.Time
+	isDir   bool
+}
+
+type vObjectFileInfo struct {
+	properties *vObjectProperties
+	sys        interface{}
+}
+
+type vObject struct {
+	properties *vObjectProperties
+	parent     *RootRouter
+}
+
+// newVirtualObject creates a new virtual object
+func (p *RootRouter) newVirtualObject(properties *vObjectProperties) *vObject {
+	return &vObject{
+		properties: properties,
+		parent:     p,
+	}
+}
+
+func (r *vObject) GetFileInfo() os.FileInfo {
+	return &vObjectFileInfo{
+		properties: r.properties,
+		sys:        nil,
+	}
+}
+
+/* File Info Interface */
+func (r *vObjectFileInfo) IsDir() bool {
+	return r.properties.isDir
+}
+
+func (r *vObjectFileInfo) ModTime() time.Time {
+	return r.properties.modTime
+}
+
+func (r *vObjectFileInfo) Mode() os.FileMode {
+	return r.properties.mode
+}
+
+func (r *vObjectFileInfo) Name() string {
+	return r.properties.name
+}
+
+func (r *vObjectFileInfo) Size() int64 {
+	return r.properties.size
+}
+
+func (r *vObjectFileInfo) Sys() interface{} {
+	return r.sys
+}
+
+/* File Interface */
+func (r *vObject) Close() error {
+	// Implement the Close method
+	fmt.Println("Close called")
+	return nil
+}
+
+func (r *vObject) Read(p []byte) (n int, err error) {
+	// Implement the Read method
+	fmt.Println("Read called")
+	return 0, nil
+}
+
+func (r *vObject) Readdir(count int) ([]os.FileInfo, error) {
+	// Generate a emulated folder structure from worker registered paths
+	fmt.Println("Readdir called")
+	rootFolders, err := r.parent.parent.GetRegisteredRootFolders()
+	if err != nil {
+		return nil, err
+	}
+
+	// Generate the folder structure
+	var folderList []os.FileInfo
+	for _, folder := range rootFolders {
+		thisVirtualObject := r.parent.newVirtualObject(&vObjectProperties{
+			name:    folder,
+			size:    0,
+			mode:    os.ModeDir,
+			modTime: time.Now(),
+			isDir:   true,
+		})
+
+		folderList = append(folderList, thisVirtualObject.GetFileInfo())
+	}
+	return folderList, nil
+}
+
+func (r *vObject) Seek(offset int64, whence int) (int64, error) {
+	// Implement the Seek method
+	fmt.Println("Seek called")
+	return 0, nil
+}
+
+func (r *vObject) Write(p []byte) (n int, err error) {
+	// Implement the Write method
+	fmt.Println("Write called")
+	return 0, nil
+}
+
+func (r *vObject) Stat() (os.FileInfo, error) {
+	// Implement the Stat method
+	fmt.Println("Stat called")
+	return r.GetFileInfo(), nil
+}
+
+// Ensure vObject implements the File interface
+var _ webdav.File = (*vObject)(nil)

+ 0 - 20
mod/bokoworker/bokoworker.go

@@ -1,20 +0,0 @@
-package bokoworker
-
-/*
-	Boko Worker
-
-	A boko worker is an instance of WebDAV file server that serves a specific
-	disk partition or subpath in which the user can interact with the disk
-	via WebDAV interface
-*/
-
-type Worker struct {
-	/* Worker Properties */
-	UUID     string //UUID of this worker
-	RootPath string //The root path request by this worker, e.g. /disk1
-	DiskUUID string // Disk UUID, when provided, will be used instead of disk path
-	DiskPath string // Disk device path (e.g. /dev/sda1), Disk UUID will have higher priority
-	Subpath  string // Subpath to serve, default is root
-	ReadOnly bool   //Label this worker as read only
-
-}

+ 0 - 55
mod/webdav/router.go

@@ -1,55 +0,0 @@
-package webdav
-
-import (
-	"context"
-	"fmt"
-	"os"
-
-	"golang.org/x/net/webdav"
-	"imuslab.com/bokofs/bokofsd/mod/bokofile"
-)
-
-/*
-	router.go
-
-	This implement a emulated file system abstraction
-	that routes the request to the correct worker
-
-*/
-
-type RouterDir struct {
-}
-
-func (r *RouterDir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
-	// Implement the Mkdir method
-	fmt.Println("Mkdir called to " + name)
-	return nil
-}
-
-func (r *RouterDir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
-	// Implement the OpenFile method
-	fmt.Println("OpenFile called to " + name)
-	thisFile := &bokofile.File{}
-	return thisFile, nil
-}
-
-func (r *RouterDir) RemoveAll(ctx context.Context, name string) error {
-	// Implement the RemoveAll method
-	fmt.Println("RemoveAll called to " + name)
-	return nil
-}
-
-func (r *RouterDir) Rename(ctx context.Context, oldName, newName string) error {
-	// Implement the Rename method
-	fmt.Println("Rename called from " + oldName + " to " + newName)
-	return nil
-}
-
-func (r *RouterDir) Stat(ctx context.Context, name string) (os.FileInfo, error) {
-	// Implement the Stat method
-	fmt.Println("Stat called to " + name)
-	return nil, nil
-}
-
-// Ensure RouterDir implements the FileSystem interface
-var _ webdav.FileSystem = (*RouterDir)(nil)