filesystem.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. package filesystem
  2. /*
  3. ArOZ Online File System Handler Wrappers
  4. author: tobychui
  5. This is a module design to do the followings
  6. 1. Mount / Create a fs when open
  7. 2. Provide the basic function and operations of a file system type
  8. 3. THIS MODULE **SHOULD NOT CONTAIN** CROSS FILE SYSTEM TYPE OPERATIONS
  9. */
  10. import (
  11. "crypto/md5"
  12. "encoding/hex"
  13. "errors"
  14. "io"
  15. "io/fs"
  16. "log"
  17. "os"
  18. "path/filepath"
  19. "strings"
  20. "time"
  21. db "imuslab.com/arozos/mod/database"
  22. "imuslab.com/arozos/mod/filesystem/abstractions/localfs"
  23. "imuslab.com/arozos/mod/filesystem/abstractions/nfsfs"
  24. "imuslab.com/arozos/mod/filesystem/abstractions/smbfs"
  25. "imuslab.com/arozos/mod/filesystem/abstractions/webdavfs"
  26. "imuslab.com/arozos/mod/filesystem/arozfs"
  27. )
  28. //Options for creating new file system handler
  29. /*
  30. type FileSystemOpeningOptions struct{
  31. Name string `json:"name"` //Display name of this device
  32. Uuid string `json:"uuid"` //UUID of this device, e.g. S1
  33. Path string `json:"path"` //Path for the storage root
  34. Access string `json:"access,omitempty"` //Access right, allow {readonly, readwrite}
  35. Hierarchy string `json:"hierarchy"` //Folder hierarchy, allow {public, user}
  36. Automount bool `json:"automount"` //Automount this device if exists
  37. Filesystem string `json:"filesystem,omitempty"` //Support {"ext4","ext2", "ext3", "fat", "vfat", "ntfs"}
  38. Mountdev string `json:"mountdev,omitempty"` //Device file (e.g. /dev/sda1)
  39. Mountpt string `json:"mountpt,omitempty"` //Device mount point (e.g. /media/storage1)
  40. }
  41. */
  42. /*
  43. An interface for storing data related to a specific hierarchy settings.
  44. Example like the account information of network drive,
  45. backup mode of backup drive etc
  46. */
  47. type HierarchySpecificConfig interface{}
  48. type FileSystemAbstraction interface {
  49. //Fundemental Functions
  50. Chmod(string, os.FileMode) error
  51. Chown(string, int, int) error
  52. Chtimes(string, time.Time, time.Time) error
  53. Create(string) (arozfs.File, error)
  54. Mkdir(string, os.FileMode) error
  55. MkdirAll(string, os.FileMode) error
  56. Name() string
  57. Open(string) (arozfs.File, error)
  58. OpenFile(string, int, os.FileMode) (arozfs.File, error)
  59. Remove(string) error
  60. RemoveAll(string) error
  61. Rename(string, string) error
  62. Stat(string) (os.FileInfo, error)
  63. Close() error
  64. //Utils Functions
  65. VirtualPathToRealPath(string, string) (string, error)
  66. RealPathToVirtualPath(string, string) (string, error)
  67. FileExists(string) bool
  68. IsDir(string) bool
  69. Glob(string) ([]string, error)
  70. GetFileSize(string) int64
  71. GetModTime(string) (int64, error)
  72. WriteFile(string, []byte, os.FileMode) error
  73. ReadFile(string) ([]byte, error)
  74. ReadDir(string) ([]fs.DirEntry, error)
  75. WriteStream(string, io.Reader, os.FileMode) error
  76. ReadStream(string) (io.ReadCloser, error)
  77. Walk(string, filepath.WalkFunc) error
  78. }
  79. //System Handler for returing
  80. type FileSystemHandler struct {
  81. Name string
  82. UUID string
  83. Path string
  84. Hierarchy string
  85. HierarchyConfig HierarchySpecificConfig
  86. ReadOnly bool
  87. RequireBuffer bool //Set this to true if the fsh do not provide file header functions like Open() or Create(), require WriteStream() and ReadStream()
  88. Parentuid string
  89. InitiationTime int64
  90. FilesystemDatabase *db.Database
  91. FileSystemAbstraction FileSystemAbstraction
  92. Filesystem string
  93. Closed bool
  94. }
  95. //Create a list of file system handler from the given json content
  96. func NewFileSystemHandlersFromJSON(jsonContent []byte) ([]*FileSystemHandler, error) {
  97. //Generate a list of handler option from json file
  98. options, err := loadConfigFromJSON(jsonContent)
  99. if err != nil {
  100. return []*FileSystemHandler{}, err
  101. }
  102. resultingHandlers := []*FileSystemHandler{}
  103. for _, option := range options {
  104. thisHandler, err := NewFileSystemHandler(option)
  105. if err != nil {
  106. log.Println("[File System] Failed to create system handler for " + option.Name)
  107. //log.Println(err.Error())
  108. continue
  109. }
  110. resultingHandlers = append(resultingHandlers, thisHandler)
  111. }
  112. return resultingHandlers, nil
  113. }
  114. //Create a new file system handler with the given config
  115. func NewFileSystemHandler(option FileSystemOption) (*FileSystemHandler, error) {
  116. fstype := strings.ToLower(option.Filesystem)
  117. if inSlice([]string{"ext4", "ext2", "ext3", "fat", "vfat", "ntfs"}, fstype) || fstype == "" {
  118. //Check if the target fs require mounting
  119. if option.Automount == true {
  120. err := MountDevice(option.Mountpt, option.Mountdev, option.Filesystem)
  121. if err != nil {
  122. return &FileSystemHandler{}, err
  123. }
  124. }
  125. //Check if the path exists
  126. if !FileExists(option.Path) {
  127. return &FileSystemHandler{}, errors.New("Mount point not exists!")
  128. }
  129. //Handle Hierarchy branching
  130. if option.Hierarchy == "user" {
  131. //Create user hierarchy for this virtual device
  132. os.MkdirAll(filepath.ToSlash(filepath.Clean(option.Path))+"/users", 0755)
  133. }
  134. //Create the fsdb for this handler
  135. var fsdb *db.Database = nil
  136. dbp, err := db.NewDatabase(filepath.ToSlash(filepath.Join(filepath.Clean(option.Path), "aofs.db")), false)
  137. if err != nil {
  138. if option.Access != arozfs.FsReadOnly {
  139. 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.")
  140. option.Access = arozfs.FsReadOnly
  141. }
  142. } else {
  143. fsdb = dbp
  144. }
  145. rootpath := filepath.ToSlash(filepath.Clean(option.Path)) + "/"
  146. return &FileSystemHandler{
  147. Name: option.Name,
  148. UUID: option.Uuid,
  149. Path: filepath.ToSlash(filepath.Clean(option.Path)) + "/",
  150. ReadOnly: option.Access == arozfs.FsReadOnly,
  151. RequireBuffer: false,
  152. Hierarchy: option.Hierarchy,
  153. HierarchyConfig: DefaultEmptyHierarchySpecificConfig,
  154. InitiationTime: time.Now().Unix(),
  155. FilesystemDatabase: fsdb,
  156. FileSystemAbstraction: localfs.NewLocalFileSystemAbstraction(option.Uuid, rootpath, option.Hierarchy, option.Access == arozfs.FsReadOnly),
  157. Filesystem: fstype,
  158. Closed: false,
  159. }, nil
  160. } else if fstype == "webdav" {
  161. //WebDAV. Create an object and mount it
  162. root := option.Path
  163. user := option.Username
  164. password := option.Password
  165. webdavfs, err := webdavfs.NewWebDAVMount(option.Uuid, option.Hierarchy, root, user, password)
  166. if err != nil {
  167. return nil, err
  168. }
  169. return &FileSystemHandler{
  170. Name: option.Name,
  171. UUID: option.Uuid,
  172. Path: option.Path,
  173. ReadOnly: option.Access == arozfs.FsReadOnly,
  174. RequireBuffer: true,
  175. Hierarchy: option.Hierarchy,
  176. HierarchyConfig: nil,
  177. InitiationTime: time.Now().Unix(),
  178. FilesystemDatabase: nil,
  179. FileSystemAbstraction: webdavfs,
  180. Filesystem: fstype,
  181. Closed: false,
  182. }, nil
  183. } else if fstype == "smb" {
  184. //WebDAV. Create an object and mount it
  185. pathChunks := strings.Split(strings.ReplaceAll(option.Path, "\\", "/"), "/")
  186. if len(pathChunks) != 2 {
  187. return nil, errors.New("Invalid configured smb filepath: Path format not matching [ip_addr]:[port]/[root_share]")
  188. }
  189. ipAddr := pathChunks[0]
  190. rootShare := pathChunks[1]
  191. user := option.Username
  192. password := option.Password
  193. smbfs, err := smbfs.NewServerMessageBlockFileSystemAbstraction(option.Uuid, option.Hierarchy, ipAddr, rootShare, user, password)
  194. if err != nil {
  195. return nil, err
  196. }
  197. return &FileSystemHandler{
  198. Name: option.Name,
  199. UUID: option.Uuid,
  200. Path: option.Path,
  201. ReadOnly: option.Access == arozfs.FsReadOnly,
  202. RequireBuffer: false,
  203. Hierarchy: option.Hierarchy,
  204. HierarchyConfig: nil,
  205. InitiationTime: time.Now().Unix(),
  206. FilesystemDatabase: nil,
  207. FileSystemAbstraction: smbfs,
  208. Filesystem: fstype,
  209. Closed: false,
  210. }, nil
  211. } else if fstype == "nfs" {
  212. ftpfs, err := nfsfs.NewNFSFileSystemAbstraction(option.Uuid, option.Hierarchy, option.Path, option.Username, option.Password)
  213. if err != nil {
  214. return nil, err
  215. }
  216. return &FileSystemHandler{
  217. Name: option.Name,
  218. UUID: option.Uuid,
  219. Path: option.Path,
  220. ReadOnly: option.Access == arozfs.FsReadOnly,
  221. RequireBuffer: true,
  222. Hierarchy: option.Hierarchy,
  223. HierarchyConfig: nil,
  224. InitiationTime: time.Now().Unix(),
  225. FilesystemDatabase: nil,
  226. FileSystemAbstraction: ftpfs,
  227. Filesystem: fstype,
  228. Closed: false,
  229. }, nil
  230. } else if option.Filesystem == "virtual" {
  231. //Virtual filesystem, deprecated
  232. log.Println("[File System] Deprecated file system type: Virtual")
  233. }
  234. return nil, errors.New("Not supported file system: " + fstype)
  235. }
  236. func (fsh *FileSystemHandler) IsNetworkDrive() bool {
  237. return arozfs.IsNetworkDrive(fsh.Filesystem)
  238. }
  239. //Check if a fsh is virtual (e.g. Network or fs Abstractions that cannot be listed with normal fs API)
  240. /*
  241. func (fsh *FileSystemHandler) IsVirtual() bool {
  242. if fsh.Hierarchy == "virtual" || fsh.Filesystem == "webdav" {
  243. //Check if the config return placeholder
  244. c, ok := fsh.HierarchyConfig.(EmptyHierarchySpecificConfig)
  245. if ok && c.HierarchyType == "placeholder" {
  246. //Real file system.
  247. return false
  248. }
  249. //Do more checking here if needed
  250. return true
  251. }
  252. return false
  253. }
  254. */
  255. func (fsh *FileSystemHandler) IsRootOf(vpath string) bool {
  256. return strings.HasPrefix(vpath, fsh.UUID+":")
  257. }
  258. func (fsh *FileSystemHandler) GetUniquePathHash(vpath string, username string) (string, error) {
  259. fshAbs := fsh.FileSystemAbstraction
  260. rpath := ""
  261. if strings.Contains(vpath, ":/") {
  262. r, err := fshAbs.VirtualPathToRealPath(vpath, username)
  263. if err != nil {
  264. return "", err
  265. }
  266. rpath = filepath.ToSlash(r)
  267. } else {
  268. //Passed in realpath as vpath.
  269. rpath = vpath
  270. }
  271. hash := md5.Sum([]byte(fsh.UUID + "_" + rpath))
  272. return hex.EncodeToString(hash[:]), nil
  273. }
  274. func (fsh *FileSystemHandler) GetDirctorySizeFromRealPath(rpath string, includeHidden bool) (int64, int) {
  275. var size int64 = 0
  276. var fileCount int = 0
  277. err := fsh.FileSystemAbstraction.Walk(rpath, func(thisFilename string, info os.FileInfo, err error) error {
  278. if err != nil {
  279. return err
  280. }
  281. if !info.IsDir() {
  282. if includeHidden {
  283. //append all into the file count and size
  284. size += info.Size()
  285. fileCount++
  286. } else {
  287. //Check if this is hidden
  288. if !IsInsideHiddenFolder(thisFilename) {
  289. size += info.Size()
  290. fileCount++
  291. }
  292. }
  293. }
  294. return nil
  295. })
  296. if err != nil {
  297. return 0, fileCount
  298. }
  299. return size, fileCount
  300. }
  301. func (fsh *FileSystemHandler) GetDirctorySizeFromVpath(vpath string, username string, includeHidden bool) (int64, int) {
  302. realpath, _ := fsh.FileSystemAbstraction.VirtualPathToRealPath(vpath, username)
  303. return fsh.GetDirctorySizeFromRealPath(realpath, includeHidden)
  304. }
  305. /*
  306. File Record Related Functions
  307. fsh database that keep track of which files is owned by whom
  308. */
  309. //Create a file ownership record
  310. func (fsh *FileSystemHandler) CreateFileRecord(rpath string, owner string) error {
  311. if fsh.FilesystemDatabase == nil {
  312. //Not supported file system type
  313. return errors.New("Not supported filesystem type")
  314. }
  315. fsh.FilesystemDatabase.NewTable("owner")
  316. fsh.FilesystemDatabase.Write("owner", "owner/"+rpath, owner)
  317. return nil
  318. }
  319. //Read the owner of a file
  320. func (fsh *FileSystemHandler) GetFileRecord(rpath string) (string, error) {
  321. if fsh.FilesystemDatabase == nil {
  322. //Not supported file system type
  323. return "", errors.New("Not supported filesystem type")
  324. }
  325. fsh.FilesystemDatabase.NewTable("owner")
  326. if fsh.FilesystemDatabase.KeyExists("owner", "owner/"+rpath) {
  327. owner := ""
  328. fsh.FilesystemDatabase.Read("owner", "owner/"+rpath, &owner)
  329. return owner, nil
  330. } else {
  331. return "", errors.New("Owner not exists")
  332. }
  333. }
  334. //Delete a file ownership record
  335. func (fsh *FileSystemHandler) DeleteFileRecord(rpath string) error {
  336. if fsh.FilesystemDatabase == nil {
  337. //Not supported file system type
  338. return errors.New("Not supported filesystem type")
  339. }
  340. fsh.FilesystemDatabase.NewTable("owner")
  341. if fsh.FilesystemDatabase.KeyExists("owner", "owner/"+rpath) {
  342. fsh.FilesystemDatabase.Delete("owner", "owner/"+rpath)
  343. }
  344. return nil
  345. }
  346. //Close an openeded File System
  347. func (fsh *FileSystemHandler) Close() {
  348. //Set the close flag to true so others function wont access it
  349. fsh.Closed = true
  350. //Close the fsh database
  351. if fsh.FilesystemDatabase != nil {
  352. fsh.FilesystemDatabase.Close()
  353. }
  354. //Close the file system object
  355. err := fsh.FileSystemAbstraction.Close()
  356. if err != nil {
  357. log.Println("[File System] Unable to close File System Abstraction for Handler: " + fsh.UUID + ". Skipping.")
  358. }
  359. }
  360. //Helper function
  361. func inSlice(slice []string, val string) bool {
  362. for _, item := range slice {
  363. if item == val {
  364. return true
  365. }
  366. }
  367. return false
  368. }
  369. func FileExists(filename string) bool {
  370. _, err := os.Stat(filename)
  371. if os.IsNotExist(err) {
  372. return false
  373. }
  374. return true
  375. }