ftpfs.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. package ftpfs
  2. import (
  3. "bytes"
  4. "io"
  5. "io/fs"
  6. "io/ioutil"
  7. "log"
  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. c, err := ftp.Dial(l.hostname, ftp.DialWithTimeout(3*time.Second))
  57. if err != nil {
  58. log.Println("[FTPFS] Unable to dial TCP: " + err.Error())
  59. return nil, err
  60. }
  61. if username == "" && password == "" {
  62. username = "anonymouss"
  63. password = "anonymous"
  64. }
  65. //Login to the FTP account
  66. err = c.Login(username, password)
  67. if err != nil {
  68. return nil, err
  69. }
  70. return c, nil
  71. }
  72. func (l FTPFSAbstraction) Chmod(filename string, mode os.FileMode) error {
  73. return arozfs.ErrOperationNotSupported
  74. }
  75. func (l FTPFSAbstraction) Chown(filename string, uid int, gid int) error {
  76. return arozfs.ErrOperationNotSupported
  77. }
  78. func (l FTPFSAbstraction) Chtimes(filename string, atime time.Time, mtime time.Time) error {
  79. return arozfs.ErrOperationNotSupported
  80. }
  81. func (l FTPFSAbstraction) Create(filename string) (arozfs.File, error) {
  82. return nil, arozfs.ErrOperationNotSupported
  83. }
  84. func (l FTPFSAbstraction) Mkdir(filename string, mode os.FileMode) error {
  85. c, err := l.makeConn()
  86. if err != nil {
  87. return err
  88. }
  89. defer c.Quit()
  90. err = c.MakeDir(filename)
  91. if err != nil {
  92. return err
  93. }
  94. return nil
  95. }
  96. func (l FTPFSAbstraction) MkdirAll(filename string, mode os.FileMode) error {
  97. return l.Mkdir(filename, mode)
  98. }
  99. func (l FTPFSAbstraction) Name() string {
  100. return ""
  101. }
  102. func (l FTPFSAbstraction) Open(filename string) (arozfs.File, error) {
  103. return nil, arozfs.ErrOperationNotSupported
  104. }
  105. func (l FTPFSAbstraction) OpenFile(filename string, flag int, perm os.FileMode) (arozfs.File, error) {
  106. return nil, arozfs.ErrOperationNotSupported
  107. }
  108. func (l FTPFSAbstraction) Remove(filename string) error {
  109. filename = filterFilepath(filename)
  110. c, err := l.makeConn()
  111. if err != nil {
  112. return err
  113. }
  114. defer c.Quit()
  115. err = c.Delete(filename)
  116. if err != nil {
  117. return err
  118. }
  119. return nil
  120. }
  121. func (l FTPFSAbstraction) RemoveAll(path string) error {
  122. path = filterFilepath(path)
  123. return l.Remove(path)
  124. }
  125. func (l FTPFSAbstraction) Rename(oldname, newname string) error {
  126. oldname = filterFilepath(oldname)
  127. newname = filterFilepath(newname)
  128. c, err := l.makeConn()
  129. if err != nil {
  130. return err
  131. }
  132. defer c.Quit()
  133. err = c.Rename(oldname, newname)
  134. if err != nil {
  135. return err
  136. }
  137. return nil
  138. }
  139. func (l FTPFSAbstraction) Stat(filename string) (os.FileInfo, error) {
  140. return nil, arozfs.ErrNullOperation
  141. }
  142. func (l FTPFSAbstraction) Close() error {
  143. return nil
  144. }
  145. /*
  146. Abstraction Utilities
  147. */
  148. func (l FTPFSAbstraction) VirtualPathToRealPath(subpath string, username string) (string, error) {
  149. return arozfs.GenericVirtualPathToRealPathTranslator(l.uuid, l.hierarchy, subpath, username)
  150. }
  151. func (l FTPFSAbstraction) RealPathToVirtualPath(fullpath string, username string) (string, error) {
  152. return arozfs.GenericRealPathToVirtualPathTranslator(l.uuid, l.hierarchy, fullpath, username)
  153. }
  154. func (l FTPFSAbstraction) FileExists(realpath string) bool {
  155. realpath = filterFilepath(realpath)
  156. c, err := l.makeConn()
  157. if err != nil {
  158. return false
  159. }
  160. _, err = c.GetEntry(realpath)
  161. c.Quit()
  162. return err == nil
  163. }
  164. func (l FTPFSAbstraction) IsDir(realpath string) bool {
  165. realpath = filterFilepath(realpath)
  166. c, err := l.makeConn()
  167. if err != nil {
  168. return false
  169. }
  170. defer c.Quit()
  171. entry, err := c.GetEntry(realpath)
  172. if err != nil {
  173. return false
  174. }
  175. return entry.Type == ftp.EntryTypeFolder
  176. }
  177. func (l FTPFSAbstraction) Glob(realpathWildcard string) ([]string, error) {
  178. return []string{}, arozfs.ErrNullOperation
  179. }
  180. func (l FTPFSAbstraction) GetFileSize(realpath string) int64 {
  181. realpath = filterFilepath(realpath)
  182. c, err := l.makeConn()
  183. if err != nil {
  184. return 0
  185. }
  186. entry, err := c.GetEntry(realpath)
  187. if err != nil {
  188. return 0
  189. }
  190. return int64(entry.Size)
  191. }
  192. func (l FTPFSAbstraction) GetModTime(realpath string) (int64, error) {
  193. realpath = filterFilepath(realpath)
  194. c, err := l.makeConn()
  195. if err != nil {
  196. return 0, err
  197. }
  198. defer c.Quit()
  199. entry, err := c.GetEntry(realpath)
  200. if err != nil {
  201. return 0, err
  202. }
  203. return entry.Time.Unix(), nil
  204. }
  205. func (l FTPFSAbstraction) WriteFile(filename string, content []byte, mode os.FileMode) error {
  206. filename = filterFilepath(filename)
  207. c, err := l.makeConn()
  208. if err != nil {
  209. return err
  210. }
  211. defer c.Quit()
  212. reader := bytes.NewReader(content)
  213. return c.Stor(filename, reader)
  214. }
  215. func (l FTPFSAbstraction) ReadFile(filename string) ([]byte, error) {
  216. filename = filterFilepath(filename)
  217. c, err := l.makeConn()
  218. if err != nil {
  219. return []byte{}, err
  220. }
  221. defer c.Quit()
  222. r, err := c.Retr(filename)
  223. if err != nil {
  224. return []byte{}, err
  225. }
  226. defer r.Close()
  227. return ioutil.ReadAll(r)
  228. }
  229. func (l FTPFSAbstraction) ReadDir(filename string) ([]fs.DirEntry, error) {
  230. results := []fs.DirEntry{}
  231. filename = filterFilepath(filename)
  232. c, err := l.makeConn()
  233. if err != nil {
  234. return []fs.DirEntry{}, err
  235. }
  236. defer c.Quit()
  237. entries, err := c.List(filename)
  238. if err != nil {
  239. return results, err
  240. }
  241. for _, entry := range entries {
  242. entryFilename := arozfs.ToSlash(filepath.Join(filename, entry.Name))
  243. //fmt.Println(entryFilename)
  244. thisDirEntry := newDirEntryFromFTPEntry(entry, c, entryFilename)
  245. results = append(results, thisDirEntry)
  246. }
  247. return results, nil
  248. }
  249. func (l FTPFSAbstraction) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
  250. filename = filterFilepath(filename)
  251. c, err := l.makeConn()
  252. if err != nil {
  253. return err
  254. }
  255. defer c.Quit()
  256. return c.Stor(filename, stream)
  257. }
  258. func (l FTPFSAbstraction) ReadStream(filename string) (io.ReadCloser, error) {
  259. filename = filterFilepath(filename)
  260. c, err := l.makeConn()
  261. if err != nil {
  262. return nil, err
  263. }
  264. defer c.Quit()
  265. return c.Retr(filename)
  266. }
  267. func (l FTPFSAbstraction) Walk(root string, walkFn filepath.WalkFunc) error {
  268. root = filterFilepath(root)
  269. log.Println("[FTP FS] Walking a root on FTP is extremely slow. Please consider rewritting this function. Scanning: " + root)
  270. c, err := l.makeConn()
  271. if err != nil {
  272. return err
  273. }
  274. defer c.Quit()
  275. rootStat, err := c.GetEntry(root)
  276. rootStatInfo := NewFileInfoFromEntry(rootStat, c, root)
  277. err = walkFn(root, rootStatInfo, err)
  278. if err != nil {
  279. return err
  280. }
  281. return l.walk(root, walkFn)
  282. }
  283. func (l FTPFSAbstraction) Heartbeat() error {
  284. return nil
  285. }
  286. //Utilities
  287. func filterFilepath(rawpath string) string {
  288. rawpath = arozfs.ToSlash(filepath.Clean(strings.TrimSpace(rawpath)))
  289. if strings.HasPrefix(rawpath, "./") {
  290. return rawpath[1:]
  291. } else if rawpath == "." || rawpath == "" {
  292. return "/"
  293. }
  294. return rawpath
  295. }
  296. func (l FTPFSAbstraction) walk(thisPath string, walkFun filepath.WalkFunc) error {
  297. files, err := l.ReadDir(thisPath)
  298. if err != nil {
  299. return err
  300. }
  301. for _, file := range files {
  302. thisFileFullPath := filepath.ToSlash(filepath.Join(thisPath, file.Name()))
  303. finfo, _ := file.Info()
  304. if file.IsDir() {
  305. err = walkFun(thisFileFullPath, finfo, nil)
  306. if err != nil {
  307. return err
  308. }
  309. err = l.walk(thisFileFullPath, walkFun)
  310. if err != nil {
  311. return err
  312. }
  313. } else {
  314. err = walkFun(thisFileFullPath, finfo, nil)
  315. if err != nil {
  316. return err
  317. }
  318. }
  319. }
  320. return nil
  321. }