Jelajahi Sumber

Added experimental fix to auto stoarge pool reload

Toby Chui 2 tahun lalu
induk
melakukan
07b8635ad2
4 mengubah file dengan 1062 tambahan dan 1019 penghapusan
  1. 108 108
      main.flags.go
  2. 458 458
      mod/filesystem/filesystem.go
  3. 136 123
      mod/storage/storage.go
  4. 360 330
      storage.go

+ 108 - 108
main.flags.go

@@ -1,108 +1,108 @@
-package main
-
-import (
-	"flag"
-	"os"
-	"time"
-
-	apt "imuslab.com/arozos/mod/apt"
-	auth "imuslab.com/arozos/mod/auth"
-	db "imuslab.com/arozos/mod/database"
-	"imuslab.com/arozos/mod/info/logger"
-	permission "imuslab.com/arozos/mod/permission"
-	user "imuslab.com/arozos/mod/user"
-	"imuslab.com/arozos/mod/www"
-)
-
-/*
-	System Flags
-*/
-
-// =========== SYSTEM PARAMTERS  ==============
-var sysdb *db.Database        //System database
-var authAgent *auth.AuthAgent //System authentication agent
-var permissionHandler *permission.PermissionHandler
-var userHandler *user.UserHandler         //User Handler
-var packageManager *apt.AptPackageManager //Manager for package auto installation
-var userWwwHandler *www.Handler           //User Webroot handler
-var subserviceBasePort = 12810            //Next subservice port
-
-// =========== SYSTEM BUILD INFORMATION ==============
-var build_version = "development"                      //System build flag, this can be either {development / production / stable}
-var internal_version = "0.2.004"                       //Internal build version, [fork_id].[major_release_no].[minor_release_no]
-var deviceUUID string                                  //The device uuid of this host
-var deviceVendor = "IMUSLAB.INC"                       //Vendor of the system
-var deviceVendorURL = "http://imuslab.com"             //Vendor contact information
-var deviceModel = "AR100"                              //Hardware Model of the system
-var deviceModelDesc = "General Purpose Cloud Platform" //Device Model Description
-var iconVendor = "img/vendor/vendor_icon.png"          //Vendor icon location
-var iconSystem = "img/vendor/system_icon.png"          //System icon location
-
-// =========== RUNTTIME RELATED ================
-var max_upload_size int64 = 8192 << 20                         //Maxmium upload size, default 8GB
-var sudo_mode bool = (os.Geteuid() == 0 || os.Geteuid() == -1) //Check if the program is launched as sudo mode or -1 on windows
-var startupTime int64 = time.Now().Unix()                      //The startup time of the ArozOS Core
-var systemWideLogger *logger.Logger                            //The sync map to store all system wide loggers
-
-// =========== SYSTEM FLAGS ==============
-// Flags related to System startup
-var listen_port = flag.Int("port", 8080, "Listening port for HTTP server")
-var tls_listen_port = flag.Int("tls_port", 8443, "Listening port for HTTPS server")
-var show_version = flag.Bool("version", false, "Show system build version")
-var host_name = flag.String("hostname", "My ArOZ", "Default name for this host")
-var system_uuid = flag.String("uuid", "", "System UUID for clustering and distributed computing. Only need to config once for first time startup. Leave empty for auto generation.")
-var disable_subservices = flag.Bool("disable_subservice", false, "Disable subservices completely")
-
-// Flags related to Networking
-var allow_upnp = flag.Bool("allow_upnp", false, "Enable uPNP service, recommended for host under NAT router")
-var allow_ssdp = flag.Bool("allow_ssdp", true, "Enable SSDP service, disable this if you do not want your device to be scanned by Windows's Network Neighborhood Page")
-var allow_mdns = flag.Bool("allow_mdns", true, "Enable MDNS service. Allow device to be scanned by nearby ArOZ Hosts")
-var disable_ip_resolve_services = flag.Bool("disable_ip_resolver", false, "Disable IP resolving if the system is running under reverse proxy environment")
-var enable_gzip = flag.Bool("gzip", true, "Enable gzip compress on file server")
-
-// Flags related to Security
-var use_tls = flag.Bool("tls", false, "Enable TLS on HTTP serving (HTTPS Mode)")
-var disable_http = flag.Bool("disable_http", false, "Disable HTTP server, require tls=true")
-var tls_cert = flag.String("cert", "localhost.crt", "TLS certificate file (.crt)")
-var tls_key = flag.String("key", "localhost.key", "TLS key file (.key)")
-var session_key = flag.String("session_key", "", "Session key, must be 16, 24 or 32 bytes long (AES-128, AES-192 or AES-256). Leave empty for auto generated.")
-
-// Flags related to hardware or interfaces
-var allow_hardware_management = flag.Bool("enable_hwman", true, "Enable hardware management functions in system")
-var wpa_supplicant_path = flag.String("wpa_supplicant_config", "/etc/wpa_supplicant/wpa_supplicant.conf", "Path for the wpa_supplicant config")
-var wan_interface_name = flag.String("wlan_interface_name", "wlan0", "The default wireless interface for connecting to an AP")
-
-// Flags related to files and uploads
-var max_upload = flag.Int("max_upload_size", 8192, "Maxmium upload size in MB. Must not exceed the available ram on your system")
-var upload_buf = flag.Int("upload_buf", 25, "Upload buffer memory in MB. Any file larger than this size will be buffered to disk (slower).")
-var storage_config_file = flag.String("storage_config", "./system/storage.json", "File location of the storage config file")
-var tmp_directory = flag.String("tmp", "./", "Temporary storage, can be access via tmp:/. A tmp/ folder will be created in this path. Recommend fast storage devices like SSD")
-var root_directory = flag.String("root", "./files/", "User root directories")
-var file_opr_buff = flag.Int("iobuf", 1024, "Amount of buffer memory for IO operations")
-var enable_dir_listing = flag.Bool("dir_list", true, "Enable directory listing")
-var enable_asyncFileUpload = flag.Bool("upload_async", false, "Enable file upload buffering to run in async mode (Faster upload, require RAM >= 8GB)")
-
-// Flags related to file system abstractions
-var bufferPoolSize = flag.Int("buffpool_size", 1024, "Maxmium buffer pool size (in MB) for buffer required file system abstractions")
-var bufferFileMaxSize = flag.Int("bufffile_size", 25, "Maxmium buffer file size (in MB) for buffer required file system abstractions")
-var enable_buffering = flag.Bool("enable_buffpool", true, "Enable buffer pool for buffer required file system abstractions")
-
-// Flags related to compatibility or testing
-var enable_beta_scanning_support = flag.Bool("beta_scan", false, "Allow compatibility to ArOZ Online Beta Clusters")
-var enable_console = flag.Bool("console", false, "Enable the debugging console.")
-var enable_logging = flag.Bool("logging", true, "Enable logging to file for debug purpose")
-
-// Flags related to running on Cloud Environment or public domain
-var allow_public_registry = flag.Bool("public_reg", false, "Enable public register interface for account creation")
-var allow_autologin = flag.Bool("allow_autologin", true, "Allow RESTFUL login redirection that allow machines like billboards to login to the system on boot")
-var demo_mode = flag.Bool("demo_mode", false, "Run the system in demo mode. All directories and database are read only.")
-var allow_package_autoInstall = flag.Bool("allow_pkg_install", true, "Allow the system to install package using Advanced Package Tool (aka apt or apt-get)")
-var allow_homepage = flag.Bool("homepage", true, "Enable user homepage. Accessible via /www/{username}/")
-
-// Scheduling and System Service Related
-var nightlyTaskRunTime = flag.Int("ntt", 3, "Nightly tasks execution time. Default 3 = 3 am in the morning")
-var maxTempFileKeepTime = flag.Int("tmp_time", 86400, "Time before tmp file will be deleted in seconds. Default 86400 seconds = 24 hours")
-
-// Flags related to ArozOS Cluster services
-var allow_clustering = flag.Bool("allow_cluster", true, "Enable cluster operations within LAN. Require allow_mdns=true flag")
-var allow_iot = flag.Bool("allow_iot", true, "Enable IoT related APIs and scanner. Require MDNS enabled")
+package main
+
+import (
+	"flag"
+	"os"
+	"time"
+
+	apt "imuslab.com/arozos/mod/apt"
+	auth "imuslab.com/arozos/mod/auth"
+	db "imuslab.com/arozos/mod/database"
+	"imuslab.com/arozos/mod/info/logger"
+	permission "imuslab.com/arozos/mod/permission"
+	user "imuslab.com/arozos/mod/user"
+	"imuslab.com/arozos/mod/www"
+)
+
+/*
+	System Flags
+*/
+
+// =========== SYSTEM PARAMTERS  ==============
+var sysdb *db.Database        //System database
+var authAgent *auth.AuthAgent //System authentication agent
+var permissionHandler *permission.PermissionHandler
+var userHandler *user.UserHandler         //User Handler
+var packageManager *apt.AptPackageManager //Manager for package auto installation
+var userWwwHandler *www.Handler           //User Webroot handler
+var subserviceBasePort = 12810            //Next subservice port
+
+// =========== SYSTEM BUILD INFORMATION ==============
+var build_version = "development"                      //System build flag, this can be either {development / production / stable}
+var internal_version = "0.2.005"                       //Internal build version, [fork_id].[major_release_no].[minor_release_no]
+var deviceUUID string                                  //The device uuid of this host
+var deviceVendor = "IMUSLAB.INC"                       //Vendor of the system
+var deviceVendorURL = "http://imuslab.com"             //Vendor contact information
+var deviceModel = "AR100"                              //Hardware Model of the system
+var deviceModelDesc = "General Purpose Cloud Platform" //Device Model Description
+var iconVendor = "img/vendor/vendor_icon.png"          //Vendor icon location
+var iconSystem = "img/vendor/system_icon.png"          //System icon location
+
+// =========== RUNTTIME RELATED ================
+var max_upload_size int64 = 8192 << 20                         //Maxmium upload size, default 8GB
+var sudo_mode bool = (os.Geteuid() == 0 || os.Geteuid() == -1) //Check if the program is launched as sudo mode or -1 on windows
+var startupTime int64 = time.Now().Unix()                      //The startup time of the ArozOS Core
+var systemWideLogger *logger.Logger                            //The sync map to store all system wide loggers
+
+// =========== SYSTEM FLAGS ==============
+// Flags related to System startup
+var listen_port = flag.Int("port", 8080, "Listening port for HTTP server")
+var tls_listen_port = flag.Int("tls_port", 8443, "Listening port for HTTPS server")
+var show_version = flag.Bool("version", false, "Show system build version")
+var host_name = flag.String("hostname", "My ArOZ", "Default name for this host")
+var system_uuid = flag.String("uuid", "", "System UUID for clustering and distributed computing. Only need to config once for first time startup. Leave empty for auto generation.")
+var disable_subservices = flag.Bool("disable_subservice", false, "Disable subservices completely")
+
+// Flags related to Networking
+var allow_upnp = flag.Bool("allow_upnp", false, "Enable uPNP service, recommended for host under NAT router")
+var allow_ssdp = flag.Bool("allow_ssdp", true, "Enable SSDP service, disable this if you do not want your device to be scanned by Windows's Network Neighborhood Page")
+var allow_mdns = flag.Bool("allow_mdns", true, "Enable MDNS service. Allow device to be scanned by nearby ArOZ Hosts")
+var disable_ip_resolve_services = flag.Bool("disable_ip_resolver", false, "Disable IP resolving if the system is running under reverse proxy environment")
+var enable_gzip = flag.Bool("gzip", true, "Enable gzip compress on file server")
+
+// Flags related to Security
+var use_tls = flag.Bool("tls", false, "Enable TLS on HTTP serving (HTTPS Mode)")
+var disable_http = flag.Bool("disable_http", false, "Disable HTTP server, require tls=true")
+var tls_cert = flag.String("cert", "localhost.crt", "TLS certificate file (.crt)")
+var tls_key = flag.String("key", "localhost.key", "TLS key file (.key)")
+var session_key = flag.String("session_key", "", "Session key, must be 16, 24 or 32 bytes long (AES-128, AES-192 or AES-256). Leave empty for auto generated.")
+
+// Flags related to hardware or interfaces
+var allow_hardware_management = flag.Bool("enable_hwman", true, "Enable hardware management functions in system")
+var wpa_supplicant_path = flag.String("wpa_supplicant_config", "/etc/wpa_supplicant/wpa_supplicant.conf", "Path for the wpa_supplicant config")
+var wan_interface_name = flag.String("wlan_interface_name", "wlan0", "The default wireless interface for connecting to an AP")
+
+// Flags related to files and uploads
+var max_upload = flag.Int("max_upload_size", 8192, "Maxmium upload size in MB. Must not exceed the available ram on your system")
+var upload_buf = flag.Int("upload_buf", 25, "Upload buffer memory in MB. Any file larger than this size will be buffered to disk (slower).")
+var storage_config_file = flag.String("storage_config", "./system/storage.json", "File location of the storage config file")
+var tmp_directory = flag.String("tmp", "./", "Temporary storage, can be access via tmp:/. A tmp/ folder will be created in this path. Recommend fast storage devices like SSD")
+var root_directory = flag.String("root", "./files/", "User root directories")
+var file_opr_buff = flag.Int("iobuf", 1024, "Amount of buffer memory for IO operations")
+var enable_dir_listing = flag.Bool("dir_list", true, "Enable directory listing")
+var enable_asyncFileUpload = flag.Bool("upload_async", false, "Enable file upload buffering to run in async mode (Faster upload, require RAM >= 8GB)")
+
+// Flags related to file system abstractions
+var bufferPoolSize = flag.Int("buffpool_size", 1024, "Maxmium buffer pool size (in MB) for buffer required file system abstractions")
+var bufferFileMaxSize = flag.Int("bufffile_size", 25, "Maxmium buffer file size (in MB) for buffer required file system abstractions")
+var enable_buffering = flag.Bool("enable_buffpool", true, "Enable buffer pool for buffer required file system abstractions")
+
+// Flags related to compatibility or testing
+var enable_beta_scanning_support = flag.Bool("beta_scan", false, "Allow compatibility to ArOZ Online Beta Clusters")
+var enable_console = flag.Bool("console", false, "Enable the debugging console.")
+var enable_logging = flag.Bool("logging", true, "Enable logging to file for debug purpose")
+
+// Flags related to running on Cloud Environment or public domain
+var allow_public_registry = flag.Bool("public_reg", false, "Enable public register interface for account creation")
+var allow_autologin = flag.Bool("allow_autologin", true, "Allow RESTFUL login redirection that allow machines like billboards to login to the system on boot")
+var demo_mode = flag.Bool("demo_mode", false, "Run the system in demo mode. All directories and database are read only.")
+var allow_package_autoInstall = flag.Bool("allow_pkg_install", true, "Allow the system to install package using Advanced Package Tool (aka apt or apt-get)")
+var allow_homepage = flag.Bool("homepage", true, "Enable user homepage. Accessible via /www/{username}/")
+
+// Scheduling and System Service Related
+var nightlyTaskRunTime = flag.Int("ntt", 3, "Nightly tasks execution time. Default 3 = 3 am in the morning")
+var maxTempFileKeepTime = flag.Int("tmp_time", 86400, "Time before tmp file will be deleted in seconds. Default 86400 seconds = 24 hours")
+
+// Flags related to ArozOS Cluster services
+var allow_clustering = flag.Bool("allow_cluster", true, "Enable cluster operations within LAN. Require allow_mdns=true flag")
+var allow_iot = flag.Bool("allow_iot", true, "Enable IoT related APIs and scanner. Require MDNS enabled")

+ 458 - 458
mod/filesystem/filesystem.go

@@ -1,458 +1,458 @@
-package filesystem
-
-/*
-	ArOZ Online File System Handler Wrappers
-	author: tobychui
-
-	This is a module design to do the followings
-	1. Mount / Create a fs when open
-	2. Provide the basic function and operations of a file system type
-	3. THIS MODULE **SHOULD NOT CONTAIN** CROSS FILE SYSTEM TYPE OPERATIONS
-*/
-
-import (
-	"crypto/md5"
-	"encoding/hex"
-	"errors"
-	"io"
-	"io/fs"
-	"log"
-	"os"
-	"path/filepath"
-	"strings"
-	"time"
-
-	db "imuslab.com/arozos/mod/database"
-	"imuslab.com/arozos/mod/filesystem/abstractions/ftpfs"
-	"imuslab.com/arozos/mod/filesystem/abstractions/localfs"
-	"imuslab.com/arozos/mod/filesystem/abstractions/smbfs"
-	"imuslab.com/arozos/mod/filesystem/abstractions/webdavfs"
-	"imuslab.com/arozos/mod/filesystem/arozfs"
-)
-
-//Options for creating new file system handler
-/*
-type FileSystemOpeningOptions struct{
-	Name      string `json:"name"`						//Display name of this device
-	Uuid      string `json:"uuid"`						//UUID of this device, e.g. S1
-	Path      string `json:"path"`						//Path for the storage root
-	Access    string `json:"access,omitempty"`			//Access right, allow {readonly, readwrite}
-	Hierarchy string `json:"hierarchy"`					//Folder hierarchy, allow {public, user}
-	Automount bool   `json:"automount"`					//Automount this device if exists
-	Filesystem string `json:"filesystem,omitempty"`		//Support {"ext4","ext2", "ext3", "fat", "vfat", "ntfs"}
-	Mountdev  string `json:"mountdev,omitempty"`		//Device file (e.g. /dev/sda1)
-	Mountpt  string `json:"mountpt,omitempty"`			//Device mount point (e.g. /media/storage1)
-}
-*/
-
-/*
-	An interface for storing data related to a specific hierarchy settings.
-	Example like the account information of network drive,
-	backup mode of backup drive etc
-*/
-type HierarchySpecificConfig interface{}
-
-type FileSystemAbstraction interface {
-	//Fundamental Functions
-	Chmod(string, os.FileMode) error
-	Chown(string, int, int) error
-	Chtimes(string, time.Time, time.Time) error
-	Create(string) (arozfs.File, error)
-	Mkdir(string, os.FileMode) error
-	MkdirAll(string, os.FileMode) error
-	Name() string
-	Open(string) (arozfs.File, error)
-	OpenFile(string, int, os.FileMode) (arozfs.File, error)
-	Remove(string) error
-	RemoveAll(string) error
-	Rename(string, string) error
-	Stat(string) (os.FileInfo, error)
-	Close() error
-
-	//Utils Functions
-	VirtualPathToRealPath(string, string) (string, error)
-	RealPathToVirtualPath(string, string) (string, error)
-	FileExists(string) bool
-	IsDir(string) bool
-	Glob(string) ([]string, error)
-	GetFileSize(string) int64
-	GetModTime(string) (int64, error)
-	WriteFile(string, []byte, os.FileMode) error
-	ReadFile(string) ([]byte, error)
-	ReadDir(string) ([]fs.DirEntry, error)
-	WriteStream(string, io.Reader, os.FileMode) error
-	ReadStream(string) (io.ReadCloser, error)
-	Walk(string, filepath.WalkFunc) error
-	Heartbeat() error
-}
-
-//System Handler for returing
-type FileSystemHandler struct {
-	Name                  string
-	UUID                  string
-	Path                  string
-	Hierarchy             string
-	HierarchyConfig       HierarchySpecificConfig
-	ReadOnly              bool
-	RequireBuffer         bool //Set this to true if the fsh do not provide file header functions like Open() or Create(), require WriteStream() and ReadStream()
-	Parentuid             string
-	InitiationTime        int64
-	FilesystemDatabase    *db.Database
-	FileSystemAbstraction FileSystemAbstraction
-	Filesystem            string
-	StartOptions          FileSystemOption
-	Closed                bool
-}
-
-//Create a list of file system handler from the given json content
-func NewFileSystemHandlersFromJSON(jsonContent []byte) ([]*FileSystemHandler, error) {
-	//Generate a list of handler option from json file
-	options, err := loadConfigFromJSON(jsonContent)
-	if err != nil {
-		return []*FileSystemHandler{}, err
-	}
-
-	resultingHandlers := []*FileSystemHandler{}
-	for _, option := range options {
-		thisHandler, err := NewFileSystemHandler(option)
-		if err != nil {
-			log.Println("[File System] Failed to create system handler for " + option.Name)
-			//log.Println(err.Error())
-			continue
-		}
-		resultingHandlers = append(resultingHandlers, thisHandler)
-	}
-
-	return resultingHandlers, nil
-}
-
-//Create a new file system handler with the given config
-func NewFileSystemHandler(option FileSystemOption) (*FileSystemHandler, error) {
-	fstype := strings.ToLower(option.Filesystem)
-	if inSlice([]string{"ext4", "ext2", "ext3", "fat", "vfat", "ntfs"}, fstype) || fstype == "" {
-		//Check if the target fs require mounting
-		if option.Automount == true {
-			err := MountDevice(option.Mountpt, option.Mountdev, option.Filesystem)
-			if err != nil {
-				return &FileSystemHandler{}, err
-			}
-		}
-
-		//Check if the path exists
-		if !FileExists(option.Path) {
-			return &FileSystemHandler{}, errors.New("Mount point not exists!")
-		}
-
-		//Handle Hierarchy branching
-		if option.Hierarchy == "user" {
-			//Create user hierarchy for this virtual device
-			os.MkdirAll(filepath.ToSlash(filepath.Clean(option.Path))+"/users", 0755)
-		}
-
-		//Create the fsdb for this handler
-		var fsdb *db.Database = nil
-		dbp, err := db.NewDatabase(filepath.ToSlash(filepath.Join(filepath.Clean(option.Path), "aofs.db")), false)
-		if err != nil {
-			if option.Access != arozfs.FsReadOnly {
-				log.Println("[File System] Invalid config: Trying to mount a read only path as read-write mount point. Changing " + option.Name + " mount point to READONLY.")
-				option.Access = arozfs.FsReadOnly
-			}
-		} else {
-			fsdb = dbp
-		}
-		rootpath := filepath.ToSlash(filepath.Clean(option.Path)) + "/"
-		return &FileSystemHandler{
-			Name:                  option.Name,
-			UUID:                  option.Uuid,
-			Path:                  filepath.ToSlash(filepath.Clean(option.Path)) + "/",
-			ReadOnly:              option.Access == arozfs.FsReadOnly,
-			RequireBuffer:         false,
-			Hierarchy:             option.Hierarchy,
-			HierarchyConfig:       DefaultEmptyHierarchySpecificConfig,
-			InitiationTime:        time.Now().Unix(),
-			FilesystemDatabase:    fsdb,
-			FileSystemAbstraction: localfs.NewLocalFileSystemAbstraction(option.Uuid, rootpath, option.Hierarchy, option.Access == arozfs.FsReadOnly),
-			Filesystem:            fstype,
-			StartOptions:          option,
-			Closed:                false,
-		}, nil
-
-	} else if fstype == "webdav" {
-		//WebDAV. Create an object and mount it
-		root := option.Path
-		user := option.Username
-		password := option.Password
-
-		webdavfs, err := webdavfs.NewWebDAVMount(option.Uuid, option.Hierarchy, root, user, password)
-		if err != nil {
-			return nil, err
-		}
-		return &FileSystemHandler{
-			Name:                  option.Name,
-			UUID:                  option.Uuid,
-			Path:                  option.Path,
-			ReadOnly:              option.Access == arozfs.FsReadOnly,
-			RequireBuffer:         true,
-			Hierarchy:             option.Hierarchy,
-			HierarchyConfig:       nil,
-			InitiationTime:        time.Now().Unix(),
-			FilesystemDatabase:    nil,
-			FileSystemAbstraction: webdavfs,
-			Filesystem:            fstype,
-			StartOptions:          option,
-			Closed:                false,
-		}, nil
-	} else if fstype == "smb" {
-		//WebDAV. Create an object and mount it
-		pathChunks := strings.Split(strings.ReplaceAll(option.Path, "\\", "/"), "/")
-		if len(pathChunks) != 2 {
-			return nil, errors.New("Invalid configured smb filepath: Path format not matching [ip_addr]:[port]/[root_share]")
-		}
-		ipAddr := pathChunks[0]
-		rootShare := pathChunks[1]
-		user := option.Username
-		password := option.Password
-		smbfs, err := smbfs.NewServerMessageBlockFileSystemAbstraction(
-			option.Uuid,
-			option.Hierarchy,
-			ipAddr,
-			rootShare,
-			user,
-			password,
-		)
-		if err != nil {
-			return nil, err
-		}
-
-		thisFsh := FileSystemHandler{
-			Name:                  option.Name,
-			UUID:                  option.Uuid,
-			Path:                  option.Path,
-			ReadOnly:              option.Access == arozfs.FsReadOnly,
-			RequireBuffer:         false,
-			Hierarchy:             option.Hierarchy,
-			HierarchyConfig:       nil,
-			InitiationTime:        time.Now().Unix(),
-			FilesystemDatabase:    nil,
-			FileSystemAbstraction: smbfs,
-			Filesystem:            fstype,
-			StartOptions:          option,
-			Closed:                false,
-		}
-
-		return &thisFsh, nil
-	} else if fstype == "ftp" {
-
-		ftpfs, err := ftpfs.NewFTPFSAbstraction(option.Uuid, option.Hierarchy, option.Path, option.Username, option.Password)
-		if err != nil {
-			return nil, err
-		}
-		return &FileSystemHandler{
-			Name:                  option.Name,
-			UUID:                  option.Uuid,
-			Path:                  option.Path,
-			ReadOnly:              option.Access == arozfs.FsReadOnly,
-			RequireBuffer:         true,
-			Hierarchy:             option.Hierarchy,
-			HierarchyConfig:       nil,
-			InitiationTime:        time.Now().Unix(),
-			FilesystemDatabase:    nil,
-			FileSystemAbstraction: ftpfs,
-			Filesystem:            fstype,
-			StartOptions:          option,
-			Closed:                false,
-		}, nil
-
-	} else if option.Filesystem == "virtual" {
-		//Virtual filesystem, deprecated
-		log.Println("[File System] Deprecated file system type: Virtual")
-	}
-
-	return nil, errors.New("Not supported file system: " + fstype)
-}
-
-func (fsh *FileSystemHandler) IsNetworkDrive() bool {
-	return arozfs.IsNetworkDrive(fsh.Filesystem)
-}
-
-//Check if a fsh is virtual (e.g. Network or fs Abstractions that cannot be listed with normal fs API)
-/*
-func (fsh *FileSystemHandler) IsVirtual() bool {
-	if fsh.Hierarchy == "virtual" || fsh.Filesystem == "webdav" {
-		//Check if the config return placeholder
-		c, ok := fsh.HierarchyConfig.(EmptyHierarchySpecificConfig)
-		if ok && c.HierarchyType == "placeholder" {
-			//Real file system.
-			return false
-		}
-
-		//Do more checking here if needed
-		return true
-	}
-	return false
-}
-*/
-
-func (fsh *FileSystemHandler) IsRootOf(vpath string) bool {
-	return strings.HasPrefix(vpath, fsh.UUID+":")
-}
-
-func (fsh *FileSystemHandler) GetUniquePathHash(vpath string, username string) (string, error) {
-	fshAbs := fsh.FileSystemAbstraction
-	rpath := ""
-	if strings.Contains(vpath, ":/") {
-		r, err := fshAbs.VirtualPathToRealPath(vpath, username)
-		if err != nil {
-			return "", err
-		}
-		rpath = filepath.ToSlash(r)
-	} else {
-		//Passed in realpath as vpath.
-		rpath = vpath
-	}
-	hash := md5.Sum([]byte(fsh.UUID + "_" + rpath))
-	return hex.EncodeToString(hash[:]), nil
-}
-
-func (fsh *FileSystemHandler) GetDirctorySizeFromRealPath(rpath string, includeHidden bool) (int64, int) {
-	var size int64 = 0
-	var fileCount int = 0
-	err := fsh.FileSystemAbstraction.Walk(rpath, func(thisFilename string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-		if !info.IsDir() {
-			if includeHidden {
-				//append all into the file count and size
-				size += info.Size()
-				fileCount++
-			} else {
-				//Check if this is hidden
-				if !IsInsideHiddenFolder(thisFilename) {
-					size += info.Size()
-					fileCount++
-				}
-
-			}
-
-		}
-		return nil
-	})
-	if err != nil {
-		return 0, fileCount
-	}
-	return size, fileCount
-}
-
-func (fsh *FileSystemHandler) GetDirctorySizeFromVpath(vpath string, username string, includeHidden bool) (int64, int) {
-	realpath, _ := fsh.FileSystemAbstraction.VirtualPathToRealPath(vpath, username)
-	return fsh.GetDirctorySizeFromRealPath(realpath, includeHidden)
-}
-
-/*
-	File Record Related Functions
-	fsh database that keep track of which files is owned by whom
-*/
-
-//Create a file ownership record
-func (fsh *FileSystemHandler) CreateFileRecord(rpath string, owner string) error {
-	if fsh.FilesystemDatabase == nil {
-		//Not supported file system type
-		return errors.New("Not supported filesystem type")
-	}
-	fsh.FilesystemDatabase.NewTable("owner")
-	fsh.FilesystemDatabase.Write("owner", "owner/"+rpath, owner)
-	return nil
-}
-
-//Read the owner of a file
-func (fsh *FileSystemHandler) GetFileRecord(rpath string) (string, error) {
-	if fsh.FilesystemDatabase == nil {
-		//Not supported file system type
-		return "", errors.New("Not supported filesystem type")
-	}
-
-	fsh.FilesystemDatabase.NewTable("owner")
-	if fsh.FilesystemDatabase.KeyExists("owner", "owner/"+rpath) {
-		owner := ""
-		fsh.FilesystemDatabase.Read("owner", "owner/"+rpath, &owner)
-		return owner, nil
-	} else {
-		return "", errors.New("Owner not exists")
-	}
-}
-
-//Delete a file ownership record
-func (fsh *FileSystemHandler) DeleteFileRecord(rpath string) error {
-	if fsh.FilesystemDatabase == nil {
-		//Not supported file system type
-		return errors.New("Not supported filesystem type")
-	}
-
-	fsh.FilesystemDatabase.NewTable("owner")
-	if fsh.FilesystemDatabase.KeyExists("owner", "owner/"+rpath) {
-		fsh.FilesystemDatabase.Delete("owner", "owner/"+rpath)
-	}
-
-	return nil
-}
-
-//Reload the target file system abstraction
-func (fsh *FileSystemHandler) ReloadFileSystelAbstraction() error {
-	log.Println("[File System] Reloading File System Abstraction for " + fsh.Name)
-	//Load the start option for this fsh
-	originalStartOption := fsh.StartOptions
-
-	//Close the file system handler
-	fsh.Close()
-
-	//Give it a few ms to do physical disk stuffs
-	time.Sleep(100 * time.Millisecond)
-
-	//Generate a new fsh from original start option
-	reloadedFsh, err := NewFileSystemHandler(originalStartOption)
-	if err != nil {
-		return err
-	}
-
-	//Overwrite the pointers to target fsa
-	fsh.FileSystemAbstraction = reloadedFsh.FileSystemAbstraction
-	fsh.FilesystemDatabase = reloadedFsh.FilesystemDatabase
-	fsh.Closed = false
-	return nil
-}
-
-//Close an openeded File System
-func (fsh *FileSystemHandler) Close() {
-	//Set the close flag to true so others function wont access it
-	fsh.Closed = true
-
-	//Close the fsh database
-	if fsh.FilesystemDatabase != nil {
-		fsh.FilesystemDatabase.Close()
-	}
-
-	//Close the file system object
-	err := fsh.FileSystemAbstraction.Close()
-	if err != nil {
-		log.Println("[File System]  Unable to close File System Abstraction for Handler: " + fsh.UUID + ". Skipping.")
-	}
-}
-
-//Helper function
-func inSlice(slice []string, val string) bool {
-	for _, item := range slice {
-		if item == val {
-			return true
-		}
-	}
-	return false
-}
-
-func FileExists(filename string) bool {
-	_, err := os.Stat(filename)
-	if os.IsNotExist(err) {
-		return false
-	}
-	return true
-}
+package filesystem
+
+/*
+	ArOZ Online File System Handler Wrappers
+	author: tobychui
+
+	This is a module design to do the followings
+	1. Mount / Create a fs when open
+	2. Provide the basic function and operations of a file system type
+	3. THIS MODULE **SHOULD NOT CONTAIN** CROSS FILE SYSTEM TYPE OPERATIONS
+*/
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"errors"
+	"io"
+	"io/fs"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	db "imuslab.com/arozos/mod/database"
+	"imuslab.com/arozos/mod/filesystem/abstractions/ftpfs"
+	"imuslab.com/arozos/mod/filesystem/abstractions/localfs"
+	"imuslab.com/arozos/mod/filesystem/abstractions/smbfs"
+	"imuslab.com/arozos/mod/filesystem/abstractions/webdavfs"
+	"imuslab.com/arozos/mod/filesystem/arozfs"
+)
+
+//Options for creating new file system handler
+/*
+type FileSystemOpeningOptions struct{
+	Name      string `json:"name"`						//Display name of this device
+	Uuid      string `json:"uuid"`						//UUID of this device, e.g. S1
+	Path      string `json:"path"`						//Path for the storage root
+	Access    string `json:"access,omitempty"`			//Access right, allow {readonly, readwrite}
+	Hierarchy string `json:"hierarchy"`					//Folder hierarchy, allow {public, user}
+	Automount bool   `json:"automount"`					//Automount this device if exists
+	Filesystem string `json:"filesystem,omitempty"`		//Support {"ext4","ext2", "ext3", "fat", "vfat", "ntfs"}
+	Mountdev  string `json:"mountdev,omitempty"`		//Device file (e.g. /dev/sda1)
+	Mountpt  string `json:"mountpt,omitempty"`			//Device mount point (e.g. /media/storage1)
+}
+*/
+
+/*
+	An interface for storing data related to a specific hierarchy settings.
+	Example like the account information of network drive,
+	backup mode of backup drive etc
+*/
+type HierarchySpecificConfig interface{}
+
+type FileSystemAbstraction interface {
+	//Fundamental Functions
+	Chmod(string, os.FileMode) error
+	Chown(string, int, int) error
+	Chtimes(string, time.Time, time.Time) error
+	Create(string) (arozfs.File, error)
+	Mkdir(string, os.FileMode) error
+	MkdirAll(string, os.FileMode) error
+	Name() string
+	Open(string) (arozfs.File, error)
+	OpenFile(string, int, os.FileMode) (arozfs.File, error)
+	Remove(string) error
+	RemoveAll(string) error
+	Rename(string, string) error
+	Stat(string) (os.FileInfo, error)
+	Close() error
+
+	//Utils Functions
+	VirtualPathToRealPath(string, string) (string, error)
+	RealPathToVirtualPath(string, string) (string, error)
+	FileExists(string) bool
+	IsDir(string) bool
+	Glob(string) ([]string, error)
+	GetFileSize(string) int64
+	GetModTime(string) (int64, error)
+	WriteFile(string, []byte, os.FileMode) error
+	ReadFile(string) ([]byte, error)
+	ReadDir(string) ([]fs.DirEntry, error)
+	WriteStream(string, io.Reader, os.FileMode) error
+	ReadStream(string) (io.ReadCloser, error)
+	Walk(string, filepath.WalkFunc) error
+	Heartbeat() error
+}
+
+//System Handler for returing
+type FileSystemHandler struct {
+	Name                  string
+	UUID                  string
+	Path                  string
+	Hierarchy             string
+	HierarchyConfig       HierarchySpecificConfig
+	ReadOnly              bool
+	RequireBuffer         bool //Set this to true if the fsh do not provide file header functions like Open() or Create(), require WriteStream() and ReadStream()
+	Parentuid             string
+	InitiationTime        int64
+	FilesystemDatabase    *db.Database
+	FileSystemAbstraction FileSystemAbstraction
+	Filesystem            string
+	StartOptions          FileSystemOption
+	Closed                bool
+}
+
+//Create a list of file system handler from the given json content
+func NewFileSystemHandlersFromJSON(jsonContent []byte) ([]*FileSystemHandler, error) {
+	//Generate a list of handler option from json file
+	options, err := loadConfigFromJSON(jsonContent)
+	if err != nil {
+		return []*FileSystemHandler{}, err
+	}
+
+	resultingHandlers := []*FileSystemHandler{}
+	for _, option := range options {
+		thisHandler, err := NewFileSystemHandler(option)
+		if err != nil {
+			log.Println("[File System] Failed to create system handler for " + option.Name)
+			//log.Println(err.Error())
+			continue
+		}
+		resultingHandlers = append(resultingHandlers, thisHandler)
+	}
+
+	return resultingHandlers, nil
+}
+
+//Create a new file system handler with the given config
+func NewFileSystemHandler(option FileSystemOption) (*FileSystemHandler, error) {
+	fstype := strings.ToLower(option.Filesystem)
+	if inSlice([]string{"ext4", "ext2", "ext3", "fat", "vfat", "ntfs"}, fstype) || fstype == "" {
+		//Check if the target fs require mounting
+		if option.Automount == true {
+			err := MountDevice(option.Mountpt, option.Mountdev, option.Filesystem)
+			if err != nil {
+				return &FileSystemHandler{}, err
+			}
+		}
+
+		//Check if the path exists
+		if !FileExists(option.Path) {
+			return &FileSystemHandler{}, errors.New("Mount point not exists!")
+		}
+
+		//Handle Hierarchy branching
+		if option.Hierarchy == "user" {
+			//Create user hierarchy for this virtual device
+			os.MkdirAll(filepath.ToSlash(filepath.Clean(option.Path))+"/users", 0755)
+		}
+
+		//Create the fsdb for this handler
+		var fsdb *db.Database = nil
+		dbp, err := db.NewDatabase(filepath.ToSlash(filepath.Join(filepath.Clean(option.Path), "aofs.db")), false)
+		if err != nil {
+			if option.Access != arozfs.FsReadOnly {
+				log.Println("[File System] Invalid config: Trying to mount a read only path as read-write mount point. Changing " + option.Name + " mount point to READONLY.")
+				option.Access = arozfs.FsReadOnly
+			}
+		} else {
+			fsdb = dbp
+		}
+		rootpath := filepath.ToSlash(filepath.Clean(option.Path)) + "/"
+		return &FileSystemHandler{
+			Name:                  option.Name,
+			UUID:                  option.Uuid,
+			Path:                  filepath.ToSlash(filepath.Clean(option.Path)) + "/",
+			ReadOnly:              option.Access == arozfs.FsReadOnly,
+			RequireBuffer:         false,
+			Hierarchy:             option.Hierarchy,
+			HierarchyConfig:       DefaultEmptyHierarchySpecificConfig,
+			InitiationTime:        time.Now().Unix(),
+			FilesystemDatabase:    fsdb,
+			FileSystemAbstraction: localfs.NewLocalFileSystemAbstraction(option.Uuid, rootpath, option.Hierarchy, option.Access == arozfs.FsReadOnly),
+			Filesystem:            fstype,
+			StartOptions:          option,
+			Closed:                false,
+		}, nil
+
+	} else if fstype == "webdav" {
+		//WebDAV. Create an object and mount it
+		root := option.Path
+		user := option.Username
+		password := option.Password
+
+		webdavfs, err := webdavfs.NewWebDAVMount(option.Uuid, option.Hierarchy, root, user, password)
+		if err != nil {
+			return nil, err
+		}
+		return &FileSystemHandler{
+			Name:                  option.Name,
+			UUID:                  option.Uuid,
+			Path:                  option.Path,
+			ReadOnly:              option.Access == arozfs.FsReadOnly,
+			RequireBuffer:         true,
+			Hierarchy:             option.Hierarchy,
+			HierarchyConfig:       nil,
+			InitiationTime:        time.Now().Unix(),
+			FilesystemDatabase:    nil,
+			FileSystemAbstraction: webdavfs,
+			Filesystem:            fstype,
+			StartOptions:          option,
+			Closed:                false,
+		}, nil
+	} else if fstype == "smb" {
+		//WebDAV. Create an object and mount it
+		pathChunks := strings.Split(strings.ReplaceAll(option.Path, "\\", "/"), "/")
+		if len(pathChunks) != 2 {
+			return nil, errors.New("Invalid configured smb filepath: Path format not matching [ip_addr]:[port]/[root_share]")
+		}
+		ipAddr := pathChunks[0]
+		rootShare := pathChunks[1]
+		user := option.Username
+		password := option.Password
+		smbfs, err := smbfs.NewServerMessageBlockFileSystemAbstraction(
+			option.Uuid,
+			option.Hierarchy,
+			ipAddr,
+			rootShare,
+			user,
+			password,
+		)
+		if err != nil {
+			return nil, err
+		}
+
+		thisFsh := FileSystemHandler{
+			Name:                  option.Name,
+			UUID:                  option.Uuid,
+			Path:                  option.Path,
+			ReadOnly:              option.Access == arozfs.FsReadOnly,
+			RequireBuffer:         false,
+			Hierarchy:             option.Hierarchy,
+			HierarchyConfig:       nil,
+			InitiationTime:        time.Now().Unix(),
+			FilesystemDatabase:    nil,
+			FileSystemAbstraction: smbfs,
+			Filesystem:            fstype,
+			StartOptions:          option,
+			Closed:                false,
+		}
+
+		return &thisFsh, nil
+	} else if fstype == "ftp" {
+
+		ftpfs, err := ftpfs.NewFTPFSAbstraction(option.Uuid, option.Hierarchy, option.Path, option.Username, option.Password)
+		if err != nil {
+			return nil, err
+		}
+		return &FileSystemHandler{
+			Name:                  option.Name,
+			UUID:                  option.Uuid,
+			Path:                  option.Path,
+			ReadOnly:              option.Access == arozfs.FsReadOnly,
+			RequireBuffer:         true,
+			Hierarchy:             option.Hierarchy,
+			HierarchyConfig:       nil,
+			InitiationTime:        time.Now().Unix(),
+			FilesystemDatabase:    nil,
+			FileSystemAbstraction: ftpfs,
+			Filesystem:            fstype,
+			StartOptions:          option,
+			Closed:                false,
+		}, nil
+
+	} else if option.Filesystem == "virtual" {
+		//Virtual filesystem, deprecated
+		log.Println("[File System] Deprecated file system type: Virtual")
+	}
+
+	return nil, errors.New("Not supported file system: " + fstype)
+}
+
+func (fsh *FileSystemHandler) IsNetworkDrive() bool {
+	return arozfs.IsNetworkDrive(fsh.Filesystem)
+}
+
+//Check if a fsh is virtual (e.g. Network or fs Abstractions that cannot be listed with normal fs API)
+/*
+func (fsh *FileSystemHandler) IsVirtual() bool {
+	if fsh.Hierarchy == "virtual" || fsh.Filesystem == "webdav" {
+		//Check if the config return placeholder
+		c, ok := fsh.HierarchyConfig.(EmptyHierarchySpecificConfig)
+		if ok && c.HierarchyType == "placeholder" {
+			//Real file system.
+			return false
+		}
+
+		//Do more checking here if needed
+		return true
+	}
+	return false
+}
+*/
+
+func (fsh *FileSystemHandler) IsRootOf(vpath string) bool {
+	return strings.HasPrefix(vpath, fsh.UUID+":")
+}
+
+func (fsh *FileSystemHandler) GetUniquePathHash(vpath string, username string) (string, error) {
+	fshAbs := fsh.FileSystemAbstraction
+	rpath := ""
+	if strings.Contains(vpath, ":/") {
+		r, err := fshAbs.VirtualPathToRealPath(vpath, username)
+		if err != nil {
+			return "", err
+		}
+		rpath = filepath.ToSlash(r)
+	} else {
+		//Passed in realpath as vpath.
+		rpath = vpath
+	}
+	hash := md5.Sum([]byte(fsh.UUID + "_" + rpath))
+	return hex.EncodeToString(hash[:]), nil
+}
+
+func (fsh *FileSystemHandler) GetDirctorySizeFromRealPath(rpath string, includeHidden bool) (int64, int) {
+	var size int64 = 0
+	var fileCount int = 0
+	err := fsh.FileSystemAbstraction.Walk(rpath, func(thisFilename string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if !info.IsDir() {
+			if includeHidden {
+				//append all into the file count and size
+				size += info.Size()
+				fileCount++
+			} else {
+				//Check if this is hidden
+				if !IsInsideHiddenFolder(thisFilename) {
+					size += info.Size()
+					fileCount++
+				}
+
+			}
+
+		}
+		return nil
+	})
+	if err != nil {
+		return 0, fileCount
+	}
+	return size, fileCount
+}
+
+func (fsh *FileSystemHandler) GetDirctorySizeFromVpath(vpath string, username string, includeHidden bool) (int64, int) {
+	realpath, _ := fsh.FileSystemAbstraction.VirtualPathToRealPath(vpath, username)
+	return fsh.GetDirctorySizeFromRealPath(realpath, includeHidden)
+}
+
+/*
+	File Record Related Functions
+	fsh database that keep track of which files is owned by whom
+*/
+
+//Create a file ownership record
+func (fsh *FileSystemHandler) CreateFileRecord(rpath string, owner string) error {
+	if fsh.FilesystemDatabase == nil {
+		//Not supported file system type
+		return errors.New("Not supported filesystem type")
+	}
+	fsh.FilesystemDatabase.NewTable("owner")
+	fsh.FilesystemDatabase.Write("owner", "owner/"+rpath, owner)
+	return nil
+}
+
+//Read the owner of a file
+func (fsh *FileSystemHandler) GetFileRecord(rpath string) (string, error) {
+	if fsh.FilesystemDatabase == nil {
+		//Not supported file system type
+		return "", errors.New("Not supported filesystem type")
+	}
+
+	fsh.FilesystemDatabase.NewTable("owner")
+	if fsh.FilesystemDatabase.KeyExists("owner", "owner/"+rpath) {
+		owner := ""
+		fsh.FilesystemDatabase.Read("owner", "owner/"+rpath, &owner)
+		return owner, nil
+	} else {
+		return "", errors.New("Owner not exists")
+	}
+}
+
+//Delete a file ownership record
+func (fsh *FileSystemHandler) DeleteFileRecord(rpath string) error {
+	if fsh.FilesystemDatabase == nil {
+		//Not supported file system type
+		return errors.New("Not supported filesystem type")
+	}
+
+	fsh.FilesystemDatabase.NewTable("owner")
+	if fsh.FilesystemDatabase.KeyExists("owner", "owner/"+rpath) {
+		fsh.FilesystemDatabase.Delete("owner", "owner/"+rpath)
+	}
+
+	return nil
+}
+
+//Reload the target file system abstraction
+func (fsh *FileSystemHandler) ReloadFileSystelAbstraction() error {
+	log.Println("[File System] Reloading File System Abstraction for " + fsh.Name)
+	//Load the start option for this fsh
+	originalStartOption := fsh.StartOptions
+
+	//Close the file system handler
+	fsh.Close()
+
+	//Give it a few ms to do physical disk stuffs
+	time.Sleep(800 * time.Millisecond)
+
+	//Generate a new fsh from original start option
+	reloadedFsh, err := NewFileSystemHandler(originalStartOption)
+	if err != nil {
+		return err
+	}
+
+	//Overwrite the pointers to target fsa
+	fsh.FileSystemAbstraction = reloadedFsh.FileSystemAbstraction
+	fsh.FilesystemDatabase = reloadedFsh.FilesystemDatabase
+	fsh.Closed = false
+	return nil
+}
+
+//Close an openeded File System
+func (fsh *FileSystemHandler) Close() {
+	//Set the close flag to true so others function wont access it
+	fsh.Closed = true
+
+	//Close the fsh database
+	if fsh.FilesystemDatabase != nil {
+		fsh.FilesystemDatabase.Close()
+	}
+
+	//Close the file system object
+	err := fsh.FileSystemAbstraction.Close()
+	if err != nil {
+		log.Println("[File System]  Unable to close File System Abstraction for Handler: " + fsh.UUID + ". Skipping.")
+	}
+}
+
+//Helper function
+func inSlice(slice []string, val string) bool {
+	for _, item := range slice {
+		if item == val {
+			return true
+		}
+	}
+	return false
+}
+
+func FileExists(filename string) bool {
+	_, err := os.Stat(filename)
+	if os.IsNotExist(err) {
+		return false
+	}
+	return true
+}

+ 136 - 123
mod/storage/storage.go

@@ -1,123 +1,136 @@
-package storage
-
-/*
-	ArOZ Online Storage Handler Module
-	author: tobychui
-
-	This is a system for allowing generic interfacing to the filesystems
-	To add more supports for different type of file system, add more storage handlers.
-*/
-
-import (
-	"os"
-	"strings"
-
-	"imuslab.com/arozos/mod/filesystem"
-	fs "imuslab.com/arozos/mod/filesystem"
-	"imuslab.com/arozos/mod/filesystem/arozfs"
-)
-
-type StoragePool struct {
-	Owner           string                  //Owner of the storage pool, also act as the resolver's username
-	OtherPermission string                  //Permissions on other users but not the owner
-	Storages        []*fs.FileSystemHandler //Storage pool accessable by this owner
-	//HyperBackupManager *hybridBackup.Manager   //HyperBackup Manager
-}
-
-/*
-	Permission Levels (From TOP to BOTTOM -> HIGHEST to LOWEST)
-	1. readwrite
-	2. readonly
-	3. denied
-*/
-
-//Create all the required folder structure if it didn't exists
-func init() {
-	os.MkdirAll("./system/storage", 0755)
-}
-
-//Create a new StoragePool objects with given uuids
-func NewStoragePool(fsHandlers []*fs.FileSystemHandler, owner string) (*StoragePool, error) {
-	//Move all fshandler into the storageHandler
-	storageHandlers := []*fs.FileSystemHandler{}
-	for _, fsHandler := range fsHandlers {
-		//Move the handler pointer to the target
-		storageHandlers = append(storageHandlers, fsHandler)
-	}
-
-	return &StoragePool{
-		Owner:           owner,
-		OtherPermission: arozfs.FsReadOnly,
-		Storages:        storageHandlers,
-	}, nil
-}
-
-//Check if this storage pool contain this particular disk ID
-func (s *StoragePool) ContainDiskID(diskID string) bool {
-	for _, fsh := range s.Storages {
-		if fsh.UUID == diskID {
-			return true
-		}
-	}
-
-	return false
-}
-
-//Use to compare two StoragePool permissions leve
-func (s *StoragePool) HasHigherOrEqualPermissionThan(a *StoragePool) bool {
-	if s.OtherPermission == arozfs.FsReadOnly && a.OtherPermission == arozfs.FsReadWrite {
-		return false
-	} else if s.OtherPermission == arozfs.FsDenied && a.OtherPermission != arozfs.FsDenied {
-		return false
-	}
-	return true
-}
-
-//Get fsh from virtual path
-func (s *StoragePool) GetFSHandlerFromVirtualPath(vpath string) (*fs.FileSystemHandler, string, error) {
-	fshid, subpath, err := filesystem.GetIDFromVirtualPath(vpath)
-	if err != nil {
-		return nil, subpath, err
-	}
-
-	fsh, err := s.GetFsHandlerByUUID(fshid)
-	if err != nil {
-		return nil, subpath, err
-	}
-
-	return fsh, subpath, nil
-}
-
-func (s *StoragePool) GetFsHandlerByUUID(uuid string) (*fs.FileSystemHandler, error) {
-	//Filter out the :/ fropm uuid if exists
-	if strings.Contains(uuid, ":") {
-		uuid = strings.Split(uuid, ":")[0]
-	}
-
-	for _, fsh := range s.Storages {
-		if fsh.UUID == uuid {
-			return fsh, nil
-		}
-	}
-
-	return nil, arozfs.ErrFSHNotFOund
-}
-
-//Close all fsHandler under this storage pool
-func (s *StoragePool) Close() {
-	//For each storage pool, close it
-	for _, fsh := range s.Storages {
-		fsh.Close()
-	}
-
-}
-
-//Helper function
-func inSlice(slice []string, val string) bool {
-	for _, item := range slice {
-		if item == val {
-			return true
-		}
-	}
-	return false
-}
+package storage
+
+/*
+	ArOZ Online Storage Handler Module
+	author: tobychui
+
+	This is a system for allowing generic interfacing to the filesystems
+	To add more supports for different type of file system, add more storage handlers.
+*/
+
+import (
+	"errors"
+	"os"
+	"strings"
+
+	"imuslab.com/arozos/mod/filesystem"
+	fs "imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/filesystem/arozfs"
+)
+
+type StoragePool struct {
+	Owner           string                  //Owner of the storage pool, also act as the resolver's username
+	OtherPermission string                  //Permissions on other users but not the owner
+	Storages        []*fs.FileSystemHandler //Storage pool accessable by this owner
+	//HyperBackupManager *hybridBackup.Manager   //HyperBackup Manager
+}
+
+/*
+	Permission Levels (From TOP to BOTTOM -> HIGHEST to LOWEST)
+	1. readwrite
+	2. readonly
+	3. denied
+*/
+
+//Create all the required folder structure if it didn't exists
+func init() {
+	os.MkdirAll("./system/storage", 0755)
+}
+
+//Create a new StoragePool objects with given uuids
+func NewStoragePool(fsHandlers []*fs.FileSystemHandler, owner string) (*StoragePool, error) {
+	//Move all fshandler into the storageHandler
+	storageHandlers := []*fs.FileSystemHandler{}
+	for _, fsHandler := range fsHandlers {
+		//Move the handler pointer to the target
+		storageHandlers = append(storageHandlers, fsHandler)
+	}
+
+	return &StoragePool{
+		Owner:           owner,
+		OtherPermission: arozfs.FsReadOnly,
+		Storages:        storageHandlers,
+	}, nil
+}
+
+//Check if this storage pool contain this particular disk ID
+func (s *StoragePool) ContainDiskID(diskID string) bool {
+	for _, fsh := range s.Storages {
+		if fsh.UUID == diskID {
+			return true
+		}
+	}
+
+	return false
+}
+
+//Use to compare two StoragePool permissions leve
+func (s *StoragePool) HasHigherOrEqualPermissionThan(a *StoragePool) bool {
+	if s.OtherPermission == arozfs.FsReadOnly && a.OtherPermission == arozfs.FsReadWrite {
+		return false
+	} else if s.OtherPermission == arozfs.FsDenied && a.OtherPermission != arozfs.FsDenied {
+		return false
+	}
+	return true
+}
+
+//Get fsh from virtual path
+func (s *StoragePool) GetFSHandlerFromVirtualPath(vpath string) (*fs.FileSystemHandler, string, error) {
+	fshid, subpath, err := filesystem.GetIDFromVirtualPath(vpath)
+	if err != nil {
+		return nil, subpath, err
+	}
+
+	fsh, err := s.GetFsHandlerByUUID(fshid)
+	if err != nil {
+		return nil, subpath, err
+	}
+
+	return fsh, subpath, nil
+}
+
+func (s *StoragePool) GetFsHandlerByUUID(uuid string) (*fs.FileSystemHandler, error) {
+	//Filter out the :/ fropm uuid if exists
+	if strings.Contains(uuid, ":") {
+		uuid = strings.Split(uuid, ":")[0]
+	}
+
+	for _, fsh := range s.Storages {
+		if fsh.UUID == uuid {
+			return fsh, nil
+		}
+	}
+
+	return nil, arozfs.ErrFSHNotFOund
+}
+
+//Attach a file system handler to this pool
+func (s *StoragePool) AttachFsHandler(fsh *filesystem.FileSystemHandler) error {
+	if s.ContainDiskID(fsh.UUID) {
+		return errors.New("file system handler with same uuid already exists in this pool")
+	}
+
+	s.Storages = append(s.Storages, fsh)
+	return nil
+}
+
+//Detech a file system handler from this pool array
+func (s *StoragePool) DetachFsHandler(uuid string) {
+	newFshList := []*fs.FileSystemHandler{}
+	for _, fsh := range s.Storages {
+		if fsh.UUID != uuid {
+			newFshList = append(newFshList, fsh)
+		}
+	}
+
+	s.Storages = newFshList
+}
+
+//Close all fsHandler under this storage pool
+func (s *StoragePool) Close() {
+	//For each storage pool, close it
+	for _, fsh := range s.Storages {
+		fsh.Close()
+	}
+
+}

+ 360 - 330
storage.go

@@ -1,330 +1,360 @@
-package main
-
-import (
-	"errors"
-	"io/ioutil"
-	"log"
-	"os"
-	"path/filepath"
-	"runtime"
-	"strings"
-	"time"
-
-	"imuslab.com/arozos/mod/filesystem/arozfs"
-	"imuslab.com/arozos/mod/permission"
-	"imuslab.com/arozos/mod/storage/bridge"
-
-	fs "imuslab.com/arozos/mod/filesystem"
-	storage "imuslab.com/arozos/mod/storage"
-)
-
-var (
-	baseStoragePool *storage.StoragePool //base storage pool, all user can access these virtual roots
-	//fsHandlers      []*fs.FileSystemHandler //All File system handlers. All opened handles must be registered in here
-	//storagePools    []*storage.StoragePool  //All Storage pool opened
-	bridgeManager *bridge.Record //Manager to handle bridged FSH
-)
-
-func StorageInit() {
-	//Load the default handler for the user storage root
-	if !fs.FileExists(filepath.Clean(*root_directory) + "/") {
-		os.MkdirAll(filepath.Clean(*root_directory)+"/", 0755)
-	}
-
-	//Start loading the base storage pool
-	err := LoadBaseStoragePool()
-	if err != nil {
-		panic(err)
-	}
-
-	//Create a brdige record manager
-	bm := bridge.NewBridgeRecord("system/bridge.json")
-	bridgeManager = bm
-
-}
-
-func LoadBaseStoragePool() error {
-	//All fsh for the base pool
-	fsHandlers := []*fs.FileSystemHandler{}
-	//Use for Debian buster local file system
-	localFileSystem := "ext4"
-	if runtime.GOOS == "windows" {
-		localFileSystem = "ntfs"
-	}
-
-	baseHandler, err := fs.NewFileSystemHandler(fs.FileSystemOption{
-		Name:       "User",
-		Uuid:       "user",
-		Path:       filepath.ToSlash(filepath.Clean(*root_directory)) + "/",
-		Hierarchy:  "user",
-		Automount:  false,
-		Filesystem: localFileSystem,
-	})
-
-	if err != nil {
-		systemWideLogger.PrintAndLog("Storage", "Failed to initiate user root storage directory: "+*root_directory+err.Error(), err)
-		return err
-	}
-	fsHandlers = append(fsHandlers, baseHandler)
-
-	//Load the tmp folder as storage unit
-	tmpHandler, err := fs.NewFileSystemHandler(fs.FileSystemOption{
-		Name:       "tmp",
-		Uuid:       "tmp",
-		Path:       filepath.ToSlash(filepath.Clean(*tmp_directory)) + "/",
-		Hierarchy:  "user",
-		Automount:  false,
-		Filesystem: localFileSystem,
-	})
-
-	if err != nil {
-		systemWideLogger.PrintAndLog("Storage", "Failed to initiate tmp storage directory: "+*tmp_directory+err.Error(), err)
-		return err
-	}
-	fsHandlers = append(fsHandlers, tmpHandler)
-
-	//Load all the storage config from file
-	rawConfig, err := ioutil.ReadFile(*storage_config_file)
-	if err != nil {
-		//File not found. Use internal storage only
-		systemWideLogger.PrintAndLog("Storage", "Storage configuration file not found. Using internal storage only.", err)
-	} else {
-		//Configuration loaded. Initializing handler
-		externalHandlers, err := fs.NewFileSystemHandlersFromJSON(rawConfig)
-		if err != nil {
-			systemWideLogger.PrintAndLog("Storage", "Failed to load storage configuration: "+err.Error()+" -- Skipping", err)
-		} else {
-			for _, thisHandler := range externalHandlers {
-				fsHandlers = append(fsHandlers, thisHandler)
-				systemWideLogger.PrintAndLog("Storage", thisHandler.Name+" Mounted as "+thisHandler.UUID+":/", err)
-			}
-
-		}
-	}
-
-	//Create a base storage pool for all users
-	sp, err := storage.NewStoragePool(fsHandlers, "system")
-	if err != nil {
-		systemWideLogger.PrintAndLog("Storage", "Failed to create base Storaeg Pool", err)
-		return err
-	}
-
-	//Update the storage pool permission to readwrite
-	sp.OtherPermission = arozfs.FsReadWrite
-	baseStoragePool = sp
-
-	return nil
-}
-
-//Initialize the storage connection health check for all fsh.
-func storageHeartbeatTickerInit() {
-	ticker := time.NewTicker(60 * time.Second)
-	done := make(chan bool)
-	go func() {
-		for {
-			select {
-			case <-done:
-				return
-			case <-ticker.C:
-				StoragePerformFileSystemAbstractionConnectionHeartbeat()
-			}
-		}
-	}()
-
-}
-
-//Perform heartbeat to all connected file system abstraction.
-//Blocking function, use with go routine if needed
-func StoragePerformFileSystemAbstractionConnectionHeartbeat() {
-	allFsh := GetAllLoadedFsh()
-	for _, thisFsh := range allFsh {
-		err := thisFsh.FileSystemAbstraction.Heartbeat()
-		if err != nil {
-			log.Println("[Storage] File System Abstraction from " + thisFsh.Name + " report an error: " + err.Error())
-			//Reload this storage pool abstraction
-			thisFsh.ReloadFileSystelAbstraction()
-		}
-	}
-}
-
-//Initialize group storage pool
-func GroupStoragePoolInit() {
-	//Mount permission groups
-	for _, pg := range permissionHandler.PermissionGroups {
-		//For each group, check does this group has a config file
-		err := LoadStoragePoolForGroup(pg)
-		if err != nil {
-			continue
-		}
-
-		//Do something else, WIP
-	}
-
-	//Start editing interface for Storage Pool Editor
-	StoragePoolEditorInit()
-}
-
-func LoadStoragePoolForGroup(pg *permission.PermissionGroup) error {
-	expectedConfigPath := "./system/storage/" + pg.Name + ".json"
-	if fs.FileExists(expectedConfigPath) {
-		//Read the config file
-		pgStorageConfig, err := os.ReadFile(expectedConfigPath)
-		if err != nil {
-			systemWideLogger.PrintAndLog("Storage", "Failed to read config for "+pg.Name+": "+err.Error(), err)
-			return errors.New("Failed to read config for " + pg.Name + ": " + err.Error())
-		}
-
-		//Generate fsHandler form json
-		thisGroupFsHandlers, err := fs.NewFileSystemHandlersFromJSON(pgStorageConfig)
-		if err != nil {
-			systemWideLogger.PrintAndLog("Storage", "Failed to load storage configuration: "+err.Error(), err)
-			return errors.New("Failed to load storage configuration: " + err.Error())
-		}
-
-		//Show debug message
-		for _, thisHandler := range thisGroupFsHandlers {
-			systemWideLogger.PrintAndLog("Storage", thisHandler.Name+" Mounted as "+thisHandler.UUID+":/ for group "+pg.Name, err)
-		}
-
-		//Create a storage pool from these handlers
-		sp, err := storage.NewStoragePool(thisGroupFsHandlers, pg.Name)
-		if err != nil {
-			systemWideLogger.PrintAndLog("Storage", "Failed to create storage pool for "+pg.Name, err)
-			return errors.New("Failed to create storage pool for " + pg.Name)
-		}
-
-		//Set other permission to denied by default
-		sp.OtherPermission = arozfs.FsDenied
-
-		//Assign storage pool to group
-		pg.StoragePool = sp
-
-	} else {
-		//Storage configuration not exists. Fill in the basic information and move to next storage pool
-
-		//Create a new empty storage pool for this group
-		sp, err := storage.NewStoragePool([]*fs.FileSystemHandler{}, pg.Name)
-		if err != nil {
-			systemWideLogger.PrintAndLog("Storage", "Failed to create empty storage pool for group: "+pg.Name, err)
-		}
-		pg.StoragePool = sp
-		pg.StoragePool.OtherPermission = arozfs.FsDenied
-	}
-
-	return nil
-}
-
-//Check if a storage pool exists by its group owner name
-func StoragePoolExists(poolOwner string) bool {
-	_, err := GetStoragePoolByOwner(poolOwner)
-	return err == nil
-}
-
-func GetAllStoragePools() []*storage.StoragePool {
-	//Append the base pool
-	results := []*storage.StoragePool{baseStoragePool}
-
-	//Add each permissionGroup's pool
-	for _, pg := range permissionHandler.PermissionGroups {
-		results = append(results, pg.StoragePool)
-	}
-
-	return results
-}
-
-func GetStoragePoolByOwner(owner string) (*storage.StoragePool, error) {
-	sps := GetAllStoragePools()
-	for _, pool := range sps {
-		if pool.Owner == owner {
-			return pool, nil
-		}
-	}
-	return nil, errors.New("Storage pool owned by " + owner + " not found")
-}
-
-func GetFSHandlerSubpathFromVpath(vpath string) (*fs.FileSystemHandler, string, error) {
-	VirtualRootID, subpath, err := fs.GetIDFromVirtualPath(vpath)
-	if err != nil {
-		return nil, "", errors.New("Unable to resolve requested path: " + err.Error())
-	}
-
-	fsh, err := GetFsHandlerByUUID(VirtualRootID)
-	if err != nil {
-		return nil, "", errors.New("Unable to resolve requested path: " + err.Error())
-	}
-
-	if fsh == nil || fsh.FileSystemAbstraction == nil {
-		return nil, "", errors.New("Unable to resolve requested path: " + err.Error())
-	}
-
-	if fsh.Closed {
-		return nil, "", errors.New("Target file system handler already closed")
-	}
-
-	return fsh, subpath, nil
-}
-
-func GetFsHandlerByUUID(uuid string) (*fs.FileSystemHandler, error) {
-	//Filter out the :/ fropm uuid if exists
-	if strings.Contains(uuid, ":") {
-		uuid = strings.Split(uuid, ":")[0]
-	}
-	var resultFsh *fs.FileSystemHandler = nil
-	allFsh := GetAllLoadedFsh()
-	for _, fsh := range allFsh {
-		if fsh.UUID == uuid && !fsh.Closed {
-			resultFsh = fsh
-		}
-	}
-	if resultFsh == nil {
-		return nil, errors.New("Filesystem handler with given UUID not found")
-	} else {
-		return resultFsh, nil
-	}
-}
-
-func GetAllLoadedFsh() []*fs.FileSystemHandler {
-	fshTmp := map[string]*fs.FileSystemHandler{}
-	allFsh := []*fs.FileSystemHandler{}
-	allStoragePools := GetAllStoragePools()
-	for _, thisSP := range allStoragePools {
-		for _, thisFsh := range thisSP.Storages {
-			fshPointer := thisFsh
-			fshTmp[thisFsh.UUID] = fshPointer
-		}
-	}
-
-	//Restructure the map to slice
-	for _, fsh := range fshTmp {
-		allFsh = append(allFsh, fsh)
-	}
-
-	return allFsh
-}
-
-func RegisterStorageSettings() {
-	//Storage Pool Configuration
-	registerSetting(settingModule{
-		Name:         "Storage Pools",
-		Desc:         "Storage Pool Mounting Configuration",
-		IconPath:     "SystemAO/disk/smart/img/small_icon.png",
-		Group:        "Disk",
-		StartDir:     "SystemAO/storage/poolList.html",
-		RequireAdmin: true,
-	})
-
-}
-
-//CloseAllStorages Close all storage database
-func CloseAllStorages() {
-	allFsh := GetAllLoadedFsh()
-	for _, fsh := range allFsh {
-		fsh.FilesystemDatabase.Close()
-	}
-}
-
-func closeAllStoragePools() {
-	for _, sp := range GetAllStoragePools() {
-		sp.Close()
-	}
-}
+package main
+
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"time"
+
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/filesystem/arozfs"
+	"imuslab.com/arozos/mod/permission"
+	"imuslab.com/arozos/mod/storage/bridge"
+
+	fs "imuslab.com/arozos/mod/filesystem"
+	storage "imuslab.com/arozos/mod/storage"
+)
+
+var (
+	baseStoragePool *storage.StoragePool //base storage pool, all user can access these virtual roots
+	//fsHandlers      []*fs.FileSystemHandler //All File system handlers. All opened handles must be registered in here
+	//storagePools    []*storage.StoragePool  //All Storage pool opened
+	bridgeManager *bridge.Record //Manager to handle bridged FSH
+)
+
+func StorageInit() {
+	//Load the default handler for the user storage root
+	if !fs.FileExists(filepath.Clean(*root_directory) + "/") {
+		os.MkdirAll(filepath.Clean(*root_directory)+"/", 0755)
+	}
+
+	//Start loading the base storage pool
+	err := LoadBaseStoragePool()
+	if err != nil {
+		panic(err)
+	}
+
+	//Create a brdige record manager
+	bm := bridge.NewBridgeRecord("system/bridge.json")
+	bridgeManager = bm
+
+}
+
+func LoadBaseStoragePool() error {
+	//All fsh for the base pool
+	fsHandlers := []*fs.FileSystemHandler{}
+	//Use for Debian buster local file system
+	localFileSystem := "ext4"
+	if runtime.GOOS == "windows" {
+		localFileSystem = "ntfs"
+	}
+
+	baseHandler, err := fs.NewFileSystemHandler(fs.FileSystemOption{
+		Name:       "User",
+		Uuid:       "user",
+		Path:       filepath.ToSlash(filepath.Clean(*root_directory)) + "/",
+		Hierarchy:  "user",
+		Automount:  false,
+		Filesystem: localFileSystem,
+	})
+
+	if err != nil {
+		systemWideLogger.PrintAndLog("Storage", "Failed to initiate user root storage directory: "+*root_directory+err.Error(), err)
+		return err
+	}
+	fsHandlers = append(fsHandlers, baseHandler)
+
+	//Load the tmp folder as storage unit
+	tmpHandler, err := fs.NewFileSystemHandler(fs.FileSystemOption{
+		Name:       "tmp",
+		Uuid:       "tmp",
+		Path:       filepath.ToSlash(filepath.Clean(*tmp_directory)) + "/",
+		Hierarchy:  "user",
+		Automount:  false,
+		Filesystem: localFileSystem,
+	})
+
+	if err != nil {
+		systemWideLogger.PrintAndLog("Storage", "Failed to initiate tmp storage directory: "+*tmp_directory+err.Error(), err)
+		return err
+	}
+	fsHandlers = append(fsHandlers, tmpHandler)
+
+	//Load all the storage config from file
+	rawConfig, err := ioutil.ReadFile(*storage_config_file)
+	if err != nil {
+		//File not found. Use internal storage only
+		systemWideLogger.PrintAndLog("Storage", "Storage configuration file not found. Using internal storage only.", err)
+	} else {
+		//Configuration loaded. Initializing handler
+		externalHandlers, err := fs.NewFileSystemHandlersFromJSON(rawConfig)
+		if err != nil {
+			systemWideLogger.PrintAndLog("Storage", "Failed to load storage configuration: "+err.Error()+" -- Skipping", err)
+		} else {
+			for _, thisHandler := range externalHandlers {
+				fsHandlers = append(fsHandlers, thisHandler)
+				systemWideLogger.PrintAndLog("Storage", thisHandler.Name+" Mounted as "+thisHandler.UUID+":/", err)
+			}
+
+		}
+	}
+
+	//Create a base storage pool for all users
+	sp, err := storage.NewStoragePool(fsHandlers, "system")
+	if err != nil {
+		systemWideLogger.PrintAndLog("Storage", "Failed to create base Storaeg Pool", err)
+		return err
+	}
+
+	//Update the storage pool permission to readwrite
+	sp.OtherPermission = arozfs.FsReadWrite
+	baseStoragePool = sp
+
+	return nil
+}
+
+//Initialize the storage connection health check for all fsh.
+func storageHeartbeatTickerInit() {
+	ticker := time.NewTicker(60 * time.Second)
+	done := make(chan bool)
+	go func() {
+		for {
+			select {
+			case <-done:
+				return
+			case <-ticker.C:
+				StoragePerformFileSystemAbstractionConnectionHeartbeat()
+			}
+		}
+	}()
+
+}
+
+//Perform heartbeat to all connected file system abstraction.
+//Blocking function, use with go routine if needed
+func StoragePerformFileSystemAbstractionConnectionHeartbeat() {
+	allFsh := GetAllLoadedFsh()
+	for _, thisFsh := range allFsh {
+		err := thisFsh.FileSystemAbstraction.Heartbeat()
+		if err != nil {
+			log.Println("[Storage] File System Abstraction from " + thisFsh.Name + " report an error: " + err.Error())
+			//Retreive the old startup config and close the pool
+			originalStartOption := filesystem.FileSystemOption{}
+			js, _ := json.Marshal(thisFsh.StartOptions)
+			json.Unmarshal(js, &originalStartOption)
+			thisFsh.Close()
+
+			//Create a new fsh from original start options
+			newfsh, err := filesystem.NewFileSystemHandler(originalStartOption)
+			if err != nil {
+				log.Println("[Storage] Unable to reconnect " + thisFsh.Name + ": " + err.Error())
+				continue
+			}
+
+			//Pop this fsh from all storage pool that mounted this
+			sp := GetAllStoragePools()
+			parentsp := []*storage.StoragePool{}
+			for _, thissp := range sp {
+				if thissp.ContainDiskID(originalStartOption.Uuid) {
+					parentsp = append(parentsp, thissp)
+					thissp.DetachFsHandler(originalStartOption.Uuid)
+				}
+			}
+
+			//Add the new fsh to all the storage pools that have it originally
+			for _, pool := range parentsp {
+				err := pool.AttachFsHandler(newfsh)
+				if err != nil {
+					log.Println("[Storage] Attach fsh to pool failed: " + err.Error())
+				}
+			}
+		}
+	}
+}
+
+//Initialize group storage pool
+func GroupStoragePoolInit() {
+	//Mount permission groups
+	for _, pg := range permissionHandler.PermissionGroups {
+		//For each group, check does this group has a config file
+		err := LoadStoragePoolForGroup(pg)
+		if err != nil {
+			continue
+		}
+
+		//Do something else, WIP
+	}
+
+	//Start editing interface for Storage Pool Editor
+	StoragePoolEditorInit()
+}
+
+func LoadStoragePoolForGroup(pg *permission.PermissionGroup) error {
+	expectedConfigPath := "./system/storage/" + pg.Name + ".json"
+	if fs.FileExists(expectedConfigPath) {
+		//Read the config file
+		pgStorageConfig, err := os.ReadFile(expectedConfigPath)
+		if err != nil {
+			systemWideLogger.PrintAndLog("Storage", "Failed to read config for "+pg.Name+": "+err.Error(), err)
+			return errors.New("Failed to read config for " + pg.Name + ": " + err.Error())
+		}
+
+		//Generate fsHandler form json
+		thisGroupFsHandlers, err := fs.NewFileSystemHandlersFromJSON(pgStorageConfig)
+		if err != nil {
+			systemWideLogger.PrintAndLog("Storage", "Failed to load storage configuration: "+err.Error(), err)
+			return errors.New("Failed to load storage configuration: " + err.Error())
+		}
+
+		//Show debug message
+		for _, thisHandler := range thisGroupFsHandlers {
+			systemWideLogger.PrintAndLog("Storage", thisHandler.Name+" Mounted as "+thisHandler.UUID+":/ for group "+pg.Name, err)
+		}
+
+		//Create a storage pool from these handlers
+		sp, err := storage.NewStoragePool(thisGroupFsHandlers, pg.Name)
+		if err != nil {
+			systemWideLogger.PrintAndLog("Storage", "Failed to create storage pool for "+pg.Name, err)
+			return errors.New("Failed to create storage pool for " + pg.Name)
+		}
+
+		//Set other permission to denied by default
+		sp.OtherPermission = arozfs.FsDenied
+
+		//Assign storage pool to group
+		pg.StoragePool = sp
+
+	} else {
+		//Storage configuration not exists. Fill in the basic information and move to next storage pool
+
+		//Create a new empty storage pool for this group
+		sp, err := storage.NewStoragePool([]*fs.FileSystemHandler{}, pg.Name)
+		if err != nil {
+			systemWideLogger.PrintAndLog("Storage", "Failed to create empty storage pool for group: "+pg.Name, err)
+		}
+		pg.StoragePool = sp
+		pg.StoragePool.OtherPermission = arozfs.FsDenied
+	}
+
+	return nil
+}
+
+//Check if a storage pool exists by its group owner name
+func StoragePoolExists(poolOwner string) bool {
+	_, err := GetStoragePoolByOwner(poolOwner)
+	return err == nil
+}
+
+func GetAllStoragePools() []*storage.StoragePool {
+	//Append the base pool
+	results := []*storage.StoragePool{baseStoragePool}
+
+	//Add each permissionGroup's pool
+	for _, pg := range permissionHandler.PermissionGroups {
+		results = append(results, pg.StoragePool)
+	}
+
+	return results
+}
+
+func GetStoragePoolByOwner(owner string) (*storage.StoragePool, error) {
+	sps := GetAllStoragePools()
+	for _, pool := range sps {
+		if pool.Owner == owner {
+			return pool, nil
+		}
+	}
+	return nil, errors.New("Storage pool owned by " + owner + " not found")
+}
+
+func GetFSHandlerSubpathFromVpath(vpath string) (*fs.FileSystemHandler, string, error) {
+	VirtualRootID, subpath, err := fs.GetIDFromVirtualPath(vpath)
+	if err != nil {
+		return nil, "", errors.New("Unable to resolve requested path: " + err.Error())
+	}
+
+	fsh, err := GetFsHandlerByUUID(VirtualRootID)
+	if err != nil {
+		return nil, "", errors.New("Unable to resolve requested path: " + err.Error())
+	}
+
+	if fsh == nil || fsh.FileSystemAbstraction == nil {
+		return nil, "", errors.New("Unable to resolve requested path: " + err.Error())
+	}
+
+	if fsh.Closed {
+		return nil, "", errors.New("Target file system handler already closed")
+	}
+
+	return fsh, subpath, nil
+}
+
+func GetFsHandlerByUUID(uuid string) (*fs.FileSystemHandler, error) {
+	//Filter out the :/ fropm uuid if exists
+	if strings.Contains(uuid, ":") {
+		uuid = strings.Split(uuid, ":")[0]
+	}
+	var resultFsh *fs.FileSystemHandler = nil
+	allFsh := GetAllLoadedFsh()
+	for _, fsh := range allFsh {
+		if fsh.UUID == uuid && !fsh.Closed {
+			resultFsh = fsh
+		}
+	}
+	if resultFsh == nil {
+		return nil, errors.New("Filesystem handler with given UUID not found")
+	} else {
+		return resultFsh, nil
+	}
+}
+
+func GetAllLoadedFsh() []*fs.FileSystemHandler {
+	fshTmp := map[string]*fs.FileSystemHandler{}
+	allFsh := []*fs.FileSystemHandler{}
+	allStoragePools := GetAllStoragePools()
+	for _, thisSP := range allStoragePools {
+		for _, thisFsh := range thisSP.Storages {
+			fshPointer := thisFsh
+			fshTmp[thisFsh.UUID] = fshPointer
+		}
+	}
+
+	//Restructure the map to slice
+	for _, fsh := range fshTmp {
+		allFsh = append(allFsh, fsh)
+	}
+
+	return allFsh
+}
+
+func RegisterStorageSettings() {
+	//Storage Pool Configuration
+	registerSetting(settingModule{
+		Name:         "Storage Pools",
+		Desc:         "Storage Pool Mounting Configuration",
+		IconPath:     "SystemAO/disk/smart/img/small_icon.png",
+		Group:        "Disk",
+		StartDir:     "SystemAO/storage/poolList.html",
+		RequireAdmin: true,
+	})
+
+}
+
+//CloseAllStorages Close all storage database
+func CloseAllStorages() {
+	allFsh := GetAllLoadedFsh()
+	for _, fsh := range allFsh {
+		fsh.FilesystemDatabase.Close()
+	}
+}
+
+func closeAllStoragePools() {
+	for _, sp := range GetAllStoragePools() {
+		sp.Close()
+	}
+}