filesystem.go 13 KB

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