filesystem.go 12 KB

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