ftpfs.go 9.0 KB

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