filesystem.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  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 option.Filesystem == "virtual" {
  211. //Virtual filesystem, deprecated
  212. log.Println("[File System] Deprecated file system type: Virtual")
  213. }
  214. return nil, errors.New("Not supported file system: " + fstype)
  215. }
  216. func (fsh *FileSystemHandler) IsNetworkDrive() bool {
  217. return arozfs.IsNetworkDrive(fsh.Filesystem)
  218. }
  219. //Check if a fsh is virtual (e.g. Network or fs Abstractions that cannot be listed with normal fs API)
  220. /*
  221. func (fsh *FileSystemHandler) IsVirtual() bool {
  222. if fsh.Hierarchy == "virtual" || fsh.Filesystem == "webdav" {
  223. //Check if the config return placeholder
  224. c, ok := fsh.HierarchyConfig.(EmptyHierarchySpecificConfig)
  225. if ok && c.HierarchyType == "placeholder" {
  226. //Real file system.
  227. return false
  228. }
  229. //Do more checking here if needed
  230. return true
  231. }
  232. return false
  233. }
  234. */
  235. func (fsh *FileSystemHandler) IsRootOf(vpath string) bool {
  236. return strings.HasPrefix(vpath, fsh.UUID+":")
  237. }
  238. func (fsh *FileSystemHandler) GetUniquePathHash(vpath string, username string) (string, error) {
  239. fshAbs := fsh.FileSystemAbstraction
  240. rpath := ""
  241. if strings.Contains(vpath, ":/") {
  242. r, err := fshAbs.VirtualPathToRealPath(vpath, username)
  243. if err != nil {
  244. return "", err
  245. }
  246. rpath = filepath.ToSlash(r)
  247. } else {
  248. //Passed in realpath as vpath.
  249. rpath = vpath
  250. }
  251. hash := md5.Sum([]byte(fsh.UUID + "_" + rpath))
  252. return hex.EncodeToString(hash[:]), nil
  253. }
  254. func (fsh *FileSystemHandler) GetDirctorySizeFromRealPath(rpath string, includeHidden bool) (int64, int) {
  255. var size int64 = 0
  256. var fileCount int = 0
  257. err := fsh.FileSystemAbstraction.Walk(rpath, func(thisFilename string, info os.FileInfo, err error) error {
  258. if err != nil {
  259. return err
  260. }
  261. if !info.IsDir() {
  262. if includeHidden {
  263. //append all into the file count and size
  264. size += info.Size()
  265. fileCount++
  266. } else {
  267. //Check if this is hidden
  268. if !IsInsideHiddenFolder(thisFilename) {
  269. size += info.Size()
  270. fileCount++
  271. }
  272. }
  273. }
  274. return nil
  275. })
  276. if err != nil {
  277. return 0, fileCount
  278. }
  279. return size, fileCount
  280. }
  281. func (fsh *FileSystemHandler) GetDirctorySizeFromVpath(vpath string, username string, includeHidden bool) (int64, int) {
  282. realpath, _ := fsh.FileSystemAbstraction.VirtualPathToRealPath(vpath, username)
  283. return fsh.GetDirctorySizeFromRealPath(realpath, includeHidden)
  284. }
  285. /*
  286. File Record Related Functions
  287. fsh database that keep track of which files is owned by whom
  288. */
  289. //Create a file ownership record
  290. func (fsh *FileSystemHandler) CreateFileRecord(rpath string, owner string) error {
  291. if fsh.FilesystemDatabase == nil {
  292. //Not supported file system type
  293. return errors.New("Not supported filesystem type")
  294. }
  295. fsh.FilesystemDatabase.NewTable("owner")
  296. fsh.FilesystemDatabase.Write("owner", "owner/"+rpath, owner)
  297. return nil
  298. }
  299. //Read the owner of a file
  300. func (fsh *FileSystemHandler) GetFileRecord(rpath string) (string, error) {
  301. if fsh.FilesystemDatabase == nil {
  302. //Not supported file system type
  303. return "", errors.New("Not supported filesystem type")
  304. }
  305. fsh.FilesystemDatabase.NewTable("owner")
  306. if fsh.FilesystemDatabase.KeyExists("owner", "owner/"+rpath) {
  307. owner := ""
  308. fsh.FilesystemDatabase.Read("owner", "owner/"+rpath, &owner)
  309. return owner, nil
  310. } else {
  311. return "", errors.New("Owner not exists")
  312. }
  313. }
  314. //Delete a file ownership record
  315. func (fsh *FileSystemHandler) DeleteFileRecord(rpath string) error {
  316. if fsh.FilesystemDatabase == nil {
  317. //Not supported file system type
  318. return errors.New("Not supported filesystem type")
  319. }
  320. fsh.FilesystemDatabase.NewTable("owner")
  321. if fsh.FilesystemDatabase.KeyExists("owner", "owner/"+rpath) {
  322. fsh.FilesystemDatabase.Delete("owner", "owner/"+rpath)
  323. }
  324. return nil
  325. }
  326. //Close an openeded File System
  327. func (fsh *FileSystemHandler) Close() {
  328. //Set the close flag to true so others function wont access it
  329. fsh.Closed = true
  330. //Close the fsh database
  331. if fsh.FilesystemDatabase != nil {
  332. fsh.FilesystemDatabase.Close()
  333. }
  334. //Close the file system object
  335. err := fsh.FileSystemAbstraction.Close()
  336. if err != nil {
  337. log.Println("[File System] Unable to close File System Abstraction for Handler: " + fsh.UUID + ". Skipping.")
  338. }
  339. }
  340. //Helper function
  341. func inSlice(slice []string, val string) bool {
  342. for _, item := range slice {
  343. if item == val {
  344. return true
  345. }
  346. }
  347. return false
  348. }
  349. func FileExists(filename string) bool {
  350. _, err := os.Stat(filename)
  351. if os.IsNotExist(err) {
  352. return false
  353. }
  354. return true
  355. }