filesystem.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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/ftpfs"
  23. "imuslab.com/arozos/mod/filesystem/abstractions/localfs"
  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. //Fundamental 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. Heartbeat() error
  79. }
  80. //System Handler for returing
  81. type FileSystemHandler struct {
  82. Name string
  83. UUID string
  84. Path string
  85. Hierarchy string
  86. HierarchyConfig HierarchySpecificConfig
  87. ReadOnly bool
  88. RequireBuffer bool //Set this to true if the fsh do not provide file header functions like Open() or Create(), require WriteStream() and ReadStream()
  89. Parentuid string
  90. InitiationTime int64
  91. FilesystemDatabase *db.Database
  92. FileSystemAbstraction FileSystemAbstraction
  93. Filesystem string
  94. StartOptions FileSystemOption
  95. Closed bool
  96. }
  97. //Create a list of file system handler from the given json content
  98. func NewFileSystemHandlersFromJSON(jsonContent []byte) ([]*FileSystemHandler, error) {
  99. //Generate a list of handler option from json file
  100. options, err := loadConfigFromJSON(jsonContent)
  101. if err != nil {
  102. return []*FileSystemHandler{}, err
  103. }
  104. resultingHandlers := []*FileSystemHandler{}
  105. for _, option := range options {
  106. thisHandler, err := NewFileSystemHandler(option)
  107. if err != nil {
  108. log.Println("[File System] Failed to create system handler for " + option.Name)
  109. //log.Println(err.Error())
  110. continue
  111. }
  112. resultingHandlers = append(resultingHandlers, thisHandler)
  113. }
  114. return resultingHandlers, nil
  115. }
  116. //Create a new file system handler with the given config
  117. func NewFileSystemHandler(option FileSystemOption) (*FileSystemHandler, error) {
  118. fstype := strings.ToLower(option.Filesystem)
  119. if inSlice([]string{"ext4", "ext2", "ext3", "fat", "vfat", "ntfs"}, fstype) || fstype == "" {
  120. //Check if the target fs require mounting
  121. if option.Automount == true {
  122. err := MountDevice(option.Mountpt, option.Mountdev, option.Filesystem)
  123. if err != nil {
  124. return &FileSystemHandler{}, err
  125. }
  126. }
  127. //Check if the path exists
  128. if !FileExists(option.Path) {
  129. return &FileSystemHandler{}, errors.New("Mount point not exists!")
  130. }
  131. //Handle Hierarchy branching
  132. if option.Hierarchy == "user" {
  133. //Create user hierarchy for this virtual device
  134. os.MkdirAll(filepath.ToSlash(filepath.Clean(option.Path))+"/users", 0755)
  135. }
  136. //Create the fsdb for this handler
  137. var fsdb *db.Database = nil
  138. dbp, err := db.NewDatabase(filepath.ToSlash(filepath.Join(filepath.Clean(option.Path), "aofs.db")), false)
  139. if err != nil {
  140. if option.Access != arozfs.FsReadOnly {
  141. 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.")
  142. option.Access = arozfs.FsReadOnly
  143. }
  144. } else {
  145. fsdb = dbp
  146. }
  147. rootpath := filepath.ToSlash(filepath.Clean(option.Path)) + "/"
  148. return &FileSystemHandler{
  149. Name: option.Name,
  150. UUID: option.Uuid,
  151. Path: filepath.ToSlash(filepath.Clean(option.Path)) + "/",
  152. ReadOnly: option.Access == arozfs.FsReadOnly,
  153. RequireBuffer: false,
  154. Hierarchy: option.Hierarchy,
  155. HierarchyConfig: DefaultEmptyHierarchySpecificConfig,
  156. InitiationTime: time.Now().Unix(),
  157. FilesystemDatabase: fsdb,
  158. FileSystemAbstraction: localfs.NewLocalFileSystemAbstraction(option.Uuid, rootpath, option.Hierarchy, option.Access == arozfs.FsReadOnly),
  159. Filesystem: fstype,
  160. StartOptions: option,
  161. Closed: false,
  162. }, nil
  163. } else if fstype == "webdav" {
  164. //WebDAV. Create an object and mount it
  165. root := option.Path
  166. user := option.Username
  167. password := option.Password
  168. webdavfs, err := webdavfs.NewWebDAVMount(option.Uuid, option.Hierarchy, root, user, password)
  169. if err != nil {
  170. return nil, err
  171. }
  172. return &FileSystemHandler{
  173. Name: option.Name,
  174. UUID: option.Uuid,
  175. Path: option.Path,
  176. ReadOnly: option.Access == arozfs.FsReadOnly,
  177. RequireBuffer: true,
  178. Hierarchy: option.Hierarchy,
  179. HierarchyConfig: nil,
  180. InitiationTime: time.Now().Unix(),
  181. FilesystemDatabase: nil,
  182. FileSystemAbstraction: webdavfs,
  183. Filesystem: fstype,
  184. StartOptions: option,
  185. Closed: false,
  186. }, nil
  187. } else if fstype == "smb" {
  188. //WebDAV. Create an object and mount it
  189. pathChunks := strings.Split(strings.ReplaceAll(option.Path, "\\", "/"), "/")
  190. if len(pathChunks) != 2 {
  191. return nil, errors.New("Invalid configured smb filepath: Path format not matching [ip_addr]:[port]/[root_share]")
  192. }
  193. ipAddr := pathChunks[0]
  194. rootShare := pathChunks[1]
  195. user := option.Username
  196. password := option.Password
  197. smbfs, err := smbfs.NewServerMessageBlockFileSystemAbstraction(
  198. option.Uuid,
  199. option.Hierarchy,
  200. ipAddr,
  201. rootShare,
  202. user,
  203. password,
  204. )
  205. if err != nil {
  206. return nil, err
  207. }
  208. thisFsh := FileSystemHandler{
  209. Name: option.Name,
  210. UUID: option.Uuid,
  211. Path: option.Path,
  212. ReadOnly: option.Access == arozfs.FsReadOnly,
  213. RequireBuffer: false,
  214. Hierarchy: option.Hierarchy,
  215. HierarchyConfig: nil,
  216. InitiationTime: time.Now().Unix(),
  217. FilesystemDatabase: nil,
  218. FileSystemAbstraction: smbfs,
  219. Filesystem: fstype,
  220. StartOptions: option,
  221. Closed: false,
  222. }
  223. return &thisFsh, nil
  224. } else if fstype == "ftp" {
  225. ftpfs, err := ftpfs.NewFTPFSAbstraction(option.Uuid, option.Hierarchy, option.Path, option.Username, option.Password)
  226. if err != nil {
  227. return nil, err
  228. }
  229. return &FileSystemHandler{
  230. Name: option.Name,
  231. UUID: option.Uuid,
  232. Path: option.Path,
  233. ReadOnly: option.Access == arozfs.FsReadOnly,
  234. RequireBuffer: true,
  235. Hierarchy: option.Hierarchy,
  236. HierarchyConfig: nil,
  237. InitiationTime: time.Now().Unix(),
  238. FilesystemDatabase: nil,
  239. FileSystemAbstraction: ftpfs,
  240. Filesystem: fstype,
  241. StartOptions: option,
  242. Closed: false,
  243. }, nil
  244. } else if option.Filesystem == "virtual" {
  245. //Virtual filesystem, deprecated
  246. log.Println("[File System] Deprecated file system type: Virtual")
  247. }
  248. return nil, errors.New("Not supported file system: " + fstype)
  249. }
  250. func (fsh *FileSystemHandler) IsNetworkDrive() bool {
  251. return arozfs.IsNetworkDrive(fsh.Filesystem)
  252. }
  253. //Check if a fsh is virtual (e.g. Network or fs Abstractions that cannot be listed with normal fs API)
  254. /*
  255. func (fsh *FileSystemHandler) IsVirtual() bool {
  256. if fsh.Hierarchy == "virtual" || fsh.Filesystem == "webdav" {
  257. //Check if the config return placeholder
  258. c, ok := fsh.HierarchyConfig.(EmptyHierarchySpecificConfig)
  259. if ok && c.HierarchyType == "placeholder" {
  260. //Real file system.
  261. return false
  262. }
  263. //Do more checking here if needed
  264. return true
  265. }
  266. return false
  267. }
  268. */
  269. func (fsh *FileSystemHandler) IsRootOf(vpath string) bool {
  270. return strings.HasPrefix(vpath, fsh.UUID+":")
  271. }
  272. func (fsh *FileSystemHandler) GetUniquePathHash(vpath string, username string) (string, error) {
  273. fshAbs := fsh.FileSystemAbstraction
  274. rpath := ""
  275. if strings.Contains(vpath, ":/") {
  276. r, err := fshAbs.VirtualPathToRealPath(vpath, username)
  277. if err != nil {
  278. return "", err
  279. }
  280. rpath = filepath.ToSlash(r)
  281. } else {
  282. //Passed in realpath as vpath.
  283. rpath = vpath
  284. }
  285. hash := md5.Sum([]byte(fsh.UUID + "_" + rpath))
  286. return hex.EncodeToString(hash[:]), nil
  287. }
  288. func (fsh *FileSystemHandler) GetDirctorySizeFromRealPath(rpath string, includeHidden bool) (int64, int) {
  289. var size int64 = 0
  290. var fileCount int = 0
  291. err := fsh.FileSystemAbstraction.Walk(rpath, func(thisFilename string, info os.FileInfo, err error) error {
  292. if err != nil {
  293. return err
  294. }
  295. if !info.IsDir() {
  296. if includeHidden {
  297. //append all into the file count and size
  298. size += info.Size()
  299. fileCount++
  300. } else {
  301. //Check if this is hidden
  302. if !IsInsideHiddenFolder(thisFilename) {
  303. size += info.Size()
  304. fileCount++
  305. }
  306. }
  307. }
  308. return nil
  309. })
  310. if err != nil {
  311. return 0, fileCount
  312. }
  313. return size, fileCount
  314. }
  315. func (fsh *FileSystemHandler) GetDirctorySizeFromVpath(vpath string, username string, includeHidden bool) (int64, int) {
  316. realpath, _ := fsh.FileSystemAbstraction.VirtualPathToRealPath(vpath, username)
  317. return fsh.GetDirctorySizeFromRealPath(realpath, includeHidden)
  318. }
  319. /*
  320. File Record Related Functions
  321. fsh database that keep track of which files is owned by whom
  322. */
  323. //Create a file ownership record
  324. func (fsh *FileSystemHandler) CreateFileRecord(rpath string, owner string) error {
  325. if fsh.FilesystemDatabase == nil {
  326. //Not supported file system type
  327. return errors.New("Not supported filesystem type")
  328. }
  329. fsh.FilesystemDatabase.NewTable("owner")
  330. fsh.FilesystemDatabase.Write("owner", "owner/"+rpath, owner)
  331. return nil
  332. }
  333. //Read the owner of a file
  334. func (fsh *FileSystemHandler) GetFileRecord(rpath string) (string, error) {
  335. if fsh.FilesystemDatabase == nil {
  336. //Not supported file system type
  337. return "", errors.New("Not supported filesystem type")
  338. }
  339. fsh.FilesystemDatabase.NewTable("owner")
  340. if fsh.FilesystemDatabase.KeyExists("owner", "owner/"+rpath) {
  341. owner := ""
  342. fsh.FilesystemDatabase.Read("owner", "owner/"+rpath, &owner)
  343. return owner, nil
  344. } else {
  345. return "", errors.New("Owner not exists")
  346. }
  347. }
  348. //Delete a file ownership record
  349. func (fsh *FileSystemHandler) DeleteFileRecord(rpath string) error {
  350. if fsh.FilesystemDatabase == nil {
  351. //Not supported file system type
  352. return errors.New("Not supported filesystem type")
  353. }
  354. fsh.FilesystemDatabase.NewTable("owner")
  355. if fsh.FilesystemDatabase.KeyExists("owner", "owner/"+rpath) {
  356. fsh.FilesystemDatabase.Delete("owner", "owner/"+rpath)
  357. }
  358. return nil
  359. }
  360. //Reload the target file system abstraction
  361. func (fsh *FileSystemHandler) ReloadFileSystelAbstraction() error {
  362. log.Println("[File System] Reloading File System Abstraction for " + fsh.Name)
  363. //Load the start option for this fsh
  364. originalStartOption := fsh.StartOptions
  365. //Close the file system handler
  366. fsh.Close()
  367. //Give it a few ms to do physical disk stuffs
  368. time.Sleep(800 * time.Millisecond)
  369. //Generate a new fsh from original start option
  370. reloadedFsh, err := NewFileSystemHandler(originalStartOption)
  371. if err != nil {
  372. return err
  373. }
  374. //Overwrite the pointers to target fsa
  375. fsh.FileSystemAbstraction = reloadedFsh.FileSystemAbstraction
  376. fsh.FilesystemDatabase = reloadedFsh.FilesystemDatabase
  377. fsh.Closed = false
  378. return nil
  379. }
  380. //Close an openeded File System
  381. func (fsh *FileSystemHandler) Close() {
  382. //Set the close flag to true so others function wont access it
  383. fsh.Closed = true
  384. //Close the fsh database
  385. if fsh.FilesystemDatabase != nil {
  386. fsh.FilesystemDatabase.Close()
  387. }
  388. //Close the file system object
  389. err := fsh.FileSystemAbstraction.Close()
  390. if err != nil {
  391. log.Println("[File System] Unable to close File System Abstraction for Handler: " + fsh.UUID + ". Skipping.")
  392. }
  393. }
  394. //Helper function
  395. func inSlice(slice []string, val string) bool {
  396. for _, item := range slice {
  397. if item == val {
  398. return true
  399. }
  400. }
  401. return false
  402. }
  403. func FileExists(filename string) bool {
  404. _, err := os.Stat(filename)
  405. if os.IsNotExist(err) {
  406. return false
  407. }
  408. return true
  409. }