filesystem.go 16 KB

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