ftpfs.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. package ftpfs
  2. import (
  3. "bytes"
  4. "io"
  5. "io/fs"
  6. "log"
  7. "math/rand"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "github.com/jlaffaye/ftp"
  13. "imuslab.com/arozos/mod/filesystem/arozfs"
  14. )
  15. /*
  16. FTPFS.go
  17. FTP Server as File System Abstraction
  18. */
  19. type FTPFSAbstraction struct {
  20. uuid string
  21. hierarchy string
  22. hostname string
  23. username string
  24. password string
  25. //conn *ftp.ServerConn
  26. //closer chan bool
  27. }
  28. func NewFTPFSAbstraction(uuid string, hierarchy string, hostname string, username string, password string) (FTPFSAbstraction, error) {
  29. //Create a ticker to prevent connection close
  30. /*
  31. ticker := time.NewTicker(180 * time.Second)
  32. done := make(chan bool)
  33. go func() {
  34. for {
  35. select {
  36. case <-done:
  37. return
  38. case <-ticker.C:
  39. c.NoOp()
  40. }
  41. }
  42. }()
  43. */
  44. log.Println("[FTP FS] " + hostname + " mounted via FTP-FS")
  45. return FTPFSAbstraction{
  46. uuid: uuid,
  47. hierarchy: hierarchy,
  48. hostname: hostname,
  49. username: username,
  50. password: password,
  51. }, nil
  52. }
  53. func (l FTPFSAbstraction) makeConn() (*ftp.ServerConn, error) {
  54. username := l.username
  55. password := l.password
  56. retryCount := 0
  57. var lastError error = nil
  58. succ := false
  59. var c *ftp.ServerConn
  60. for retryCount < 5 && !succ {
  61. c, lastError = ftp.Dial(l.hostname, ftp.DialWithTimeout(3*time.Second))
  62. if lastError != nil {
  63. //Connection failed. Delay and retry
  64. retryCount++
  65. r := rand.Intn(500)
  66. time.Sleep(time.Duration(r) * time.Microsecond)
  67. continue
  68. }
  69. //Connection established.
  70. succ = true
  71. lastError = nil
  72. }
  73. if !succ && lastError != nil {
  74. log.Println("[FTPFS] Unable to dial TCP: " + lastError.Error())
  75. return nil, lastError
  76. }
  77. if username == "" && password == "" {
  78. username = "anonymouss"
  79. password = "anonymous"
  80. }
  81. //Login to the FTP account
  82. err := c.Login(username, password)
  83. if err != nil {
  84. return nil, err
  85. }
  86. return c, nil
  87. }
  88. func (l FTPFSAbstraction) Chmod(filename string, mode os.FileMode) error {
  89. return arozfs.ErrOperationNotSupported
  90. }
  91. func (l FTPFSAbstraction) Chown(filename string, uid int, gid int) error {
  92. return arozfs.ErrOperationNotSupported
  93. }
  94. func (l FTPFSAbstraction) Chtimes(filename string, atime time.Time, mtime time.Time) error {
  95. return arozfs.ErrOperationNotSupported
  96. }
  97. func (l FTPFSAbstraction) Create(filename string) (arozfs.File, error) {
  98. return nil, arozfs.ErrOperationNotSupported
  99. }
  100. func (l FTPFSAbstraction) Mkdir(filename string, mode os.FileMode) error {
  101. c, err := l.makeConn()
  102. if err != nil {
  103. return err
  104. }
  105. defer c.Quit()
  106. err = c.MakeDir(filename)
  107. if err != nil {
  108. return err
  109. }
  110. return nil
  111. }
  112. func (l FTPFSAbstraction) MkdirAll(filename string, mode os.FileMode) error {
  113. return l.Mkdir(filename, mode)
  114. }
  115. func (l FTPFSAbstraction) Name() string {
  116. return ""
  117. }
  118. func (l FTPFSAbstraction) Open(filename string) (arozfs.File, error) {
  119. return nil, arozfs.ErrOperationNotSupported
  120. }
  121. func (l FTPFSAbstraction) OpenFile(filename string, flag int, perm os.FileMode) (arozfs.File, error) {
  122. return nil, arozfs.ErrOperationNotSupported
  123. }
  124. func (l FTPFSAbstraction) Remove(filename string) error {
  125. filename = filterFilepath(filename)
  126. c, err := l.makeConn()
  127. if err != nil {
  128. return err
  129. }
  130. defer c.Quit()
  131. err = c.Delete(filename)
  132. if err != nil {
  133. return err
  134. }
  135. return nil
  136. }
  137. func (l FTPFSAbstraction) RemoveAll(path string) error {
  138. path = filterFilepath(path)
  139. return l.Remove(path)
  140. }
  141. func (l FTPFSAbstraction) Rename(oldname, newname string) error {
  142. oldname = filterFilepath(oldname)
  143. newname = filterFilepath(newname)
  144. c, err := l.makeConn()
  145. if err != nil {
  146. return err
  147. }
  148. defer c.Quit()
  149. err = c.Rename(oldname, newname)
  150. if err != nil {
  151. return err
  152. }
  153. return nil
  154. }
  155. func (l FTPFSAbstraction) Stat(filename string) (os.FileInfo, error) {
  156. return nil, arozfs.ErrNullOperation
  157. }
  158. func (l FTPFSAbstraction) Close() error {
  159. return nil
  160. }
  161. /*
  162. Abstraction Utilities
  163. */
  164. func (l FTPFSAbstraction) VirtualPathToRealPath(subpath string, username string) (string, error) {
  165. return arozfs.GenericVirtualPathToRealPathTranslator(l.uuid, l.hierarchy, subpath, username)
  166. }
  167. func (l FTPFSAbstraction) RealPathToVirtualPath(fullpath string, username string) (string, error) {
  168. return arozfs.GenericRealPathToVirtualPathTranslator(l.uuid, l.hierarchy, fullpath, username)
  169. }
  170. func (l FTPFSAbstraction) FileExists(realpath string) bool {
  171. realpath = filterFilepath(realpath)
  172. c, err := l.makeConn()
  173. if err != nil {
  174. return false
  175. }
  176. _, err = c.GetEntry(realpath)
  177. c.Quit()
  178. return err == nil
  179. }
  180. func (l FTPFSAbstraction) IsDir(realpath string) bool {
  181. realpath = filterFilepath(realpath)
  182. c, err := l.makeConn()
  183. if err != nil {
  184. return false
  185. }
  186. defer c.Quit()
  187. entry, err := c.GetEntry(realpath)
  188. if err != nil {
  189. return false
  190. }
  191. return entry.Type == ftp.EntryTypeFolder
  192. }
  193. func (l FTPFSAbstraction) Glob(realpathWildcard string) ([]string, error) {
  194. return []string{}, arozfs.ErrOperationNotSupported
  195. }
  196. func (l FTPFSAbstraction) GetFileSize(realpath string) int64 {
  197. realpath = filterFilepath(realpath)
  198. c, err := l.makeConn()
  199. if err != nil {
  200. return 0
  201. }
  202. entry, err := c.GetEntry(realpath)
  203. if err != nil {
  204. return 0
  205. }
  206. return int64(entry.Size)
  207. }
  208. func (l FTPFSAbstraction) GetModTime(realpath string) (int64, error) {
  209. realpath = filterFilepath(realpath)
  210. c, err := l.makeConn()
  211. if err != nil {
  212. return 0, err
  213. }
  214. defer c.Quit()
  215. entry, err := c.GetEntry(realpath)
  216. if err != nil {
  217. return 0, err
  218. }
  219. return entry.Time.Unix(), nil
  220. }
  221. func (l FTPFSAbstraction) WriteFile(filename string, content []byte, mode os.FileMode) error {
  222. filename = filterFilepath(filename)
  223. c, err := l.makeConn()
  224. if err != nil {
  225. return err
  226. }
  227. defer c.Quit()
  228. reader := bytes.NewReader(content)
  229. return c.Stor(filename, reader)
  230. }
  231. func (l FTPFSAbstraction) ReadFile(filename string) ([]byte, error) {
  232. filename = filterFilepath(filename)
  233. c, err := l.makeConn()
  234. if err != nil {
  235. return []byte{}, err
  236. }
  237. defer c.Quit()
  238. r, err := c.Retr(filename)
  239. if err != nil {
  240. return []byte{}, err
  241. }
  242. defer r.Close()
  243. return io.ReadAll(r)
  244. }
  245. func (l FTPFSAbstraction) ReadDir(filename string) ([]fs.DirEntry, error) {
  246. results := []fs.DirEntry{}
  247. filename = filterFilepath(filename)
  248. c, err := l.makeConn()
  249. if err != nil {
  250. return []fs.DirEntry{}, err
  251. }
  252. defer c.Quit()
  253. entries, err := c.List(filename)
  254. if err != nil {
  255. return results, err
  256. }
  257. for _, entry := range entries {
  258. entryFilename := arozfs.ToSlash(filepath.Join(filename, entry.Name))
  259. //fmt.Println(entryFilename)
  260. thisDirEntry := newDirEntryFromFTPEntry(entry, c, entryFilename)
  261. results = append(results, thisDirEntry)
  262. }
  263. return results, nil
  264. }
  265. func (l FTPFSAbstraction) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
  266. filename = filterFilepath(filename)
  267. c, err := l.makeConn()
  268. if err != nil {
  269. return err
  270. }
  271. defer c.Quit()
  272. return c.Stor(filename, stream)
  273. }
  274. func (l FTPFSAbstraction) ReadStream(filename string) (io.ReadCloser, error) {
  275. filename = filterFilepath(filename)
  276. c, err := l.makeConn()
  277. if err != nil {
  278. return nil, err
  279. }
  280. defer c.Quit()
  281. retryCount := 0
  282. succ := false
  283. var lastErr error
  284. for retryCount < 5 && !succ {
  285. resp, err := c.Retr(filename)
  286. if err != nil {
  287. lastErr = err
  288. retryCount++
  289. r := rand.Intn(500)
  290. time.Sleep(time.Duration(r) * time.Microsecond)
  291. continue
  292. } else {
  293. succ = true
  294. return resp, nil
  295. }
  296. }
  297. return nil, lastErr
  298. }
  299. func (l FTPFSAbstraction) Walk(root string, walkFn filepath.WalkFunc) error {
  300. root = filterFilepath(root)
  301. log.Println("[FTP FS] Walking a root on FTP is extremely slow. Please consider rewritting this function. Scanning: " + root)
  302. c, err := l.makeConn()
  303. if err != nil {
  304. return err
  305. }
  306. defer c.Quit()
  307. rootStat, err := c.GetEntry(root)
  308. rootStatInfo := NewFileInfoFromEntry(rootStat, c, root)
  309. err = walkFn(root, rootStatInfo, err)
  310. if err != nil {
  311. return err
  312. }
  313. return l.walk(root, walkFn)
  314. }
  315. func (l FTPFSAbstraction) Heartbeat() error {
  316. return nil
  317. }
  318. // Utilities
  319. func filterFilepath(rawpath string) string {
  320. rawpath = arozfs.ToSlash(filepath.Clean(strings.TrimSpace(rawpath)))
  321. if strings.HasPrefix(rawpath, "./") {
  322. return rawpath[1:]
  323. } else if rawpath == "." || rawpath == "" {
  324. return "/"
  325. }
  326. return rawpath
  327. }
  328. func (l FTPFSAbstraction) walk(thisPath string, walkFun filepath.WalkFunc) error {
  329. files, err := l.ReadDir(thisPath)
  330. if err != nil {
  331. return err
  332. }
  333. for _, file := range files {
  334. thisFileFullPath := filepath.ToSlash(filepath.Join(thisPath, file.Name()))
  335. finfo, _ := file.Info()
  336. if file.IsDir() {
  337. err = walkFun(thisFileFullPath, finfo, nil)
  338. if err != nil {
  339. return err
  340. }
  341. err = l.walk(thisFileFullPath, walkFun)
  342. if err != nil {
  343. return err
  344. }
  345. } else {
  346. err = walkFun(thisFileFullPath, finfo, nil)
  347. if err != nil {
  348. return err
  349. }
  350. }
  351. }
  352. return nil
  353. }