sftpfs.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. package sftpfs
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "io/fs"
  7. "log"
  8. "net/url"
  9. "os"
  10. "path/filepath"
  11. "time"
  12. "github.com/pkg/sftp"
  13. "golang.org/x/crypto/ssh"
  14. "imuslab.com/arozos/mod/filesystem/arozfs"
  15. )
  16. /*
  17. SFTP-FS.go
  18. SSH File Transfer Protocol as File System Abstraction
  19. */
  20. type SFTPFileSystemAbstraction struct {
  21. uuid string
  22. hierarchy string
  23. url string
  24. port int
  25. username string
  26. password string
  27. mountFolder string
  28. client *sftp.Client
  29. conn *ssh.Client
  30. }
  31. func NewSFTPFileSystemAbstraction(uuid string, hierarchy string, serverUrl string, port int, mountFolder string, username string, password string) (SFTPFileSystemAbstraction, error) {
  32. _, err := url.Parse(serverUrl)
  33. if err != nil {
  34. return SFTPFileSystemAbstraction{}, errors.New("[SFTP] to parse url: " + err.Error())
  35. }
  36. // Get user name and pass
  37. //user := parsedUrl.User.Username()
  38. //, _ := parsedUrl.User.Password()
  39. // Parse Host and Port
  40. log.Println("[SFTP FS] Establishing connection with " + serverUrl + "...")
  41. var auths []ssh.AuthMethod
  42. // Use password authentication if provided
  43. if password != "" {
  44. auths = append(auths, ssh.Password(password))
  45. }
  46. // Initialize client configuration
  47. config := ssh.ClientConfig{
  48. User: username,
  49. Auth: auths,
  50. // Uncomment to ignore host key check
  51. HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  52. //HostKeyCallback: ssh.FixedHostKey(hostKey),
  53. }
  54. addr := fmt.Sprintf("%s:%d", serverUrl, port)
  55. // Connect to server
  56. conn, err := ssh.Dial("tcp", addr, &config)
  57. if err != nil {
  58. log.Printf("[SFTP FS] Failed to connect to [%s] %v\n", addr, err)
  59. return SFTPFileSystemAbstraction{}, err
  60. }
  61. // Create new SFTP client
  62. sc, err := sftp.NewClient(conn)
  63. if err != nil {
  64. log.Printf("[SFTP FS] Unable to start SFTP subsystem: %v\n", err)
  65. return SFTPFileSystemAbstraction{}, err
  66. }
  67. log.Println("[SFTP FS] Connected to remote: " + addr)
  68. return SFTPFileSystemAbstraction{
  69. uuid: uuid,
  70. hierarchy: hierarchy,
  71. url: serverUrl,
  72. port: port,
  73. username: username,
  74. password: password,
  75. mountFolder: mountFolder,
  76. client: sc,
  77. conn: conn,
  78. }, nil
  79. }
  80. func (s SFTPFileSystemAbstraction) Chmod(filename string, mode os.FileMode) error {
  81. filename = arozfs.GenericPathFilter(filename)
  82. return s.client.Chmod(filename, mode)
  83. }
  84. func (s SFTPFileSystemAbstraction) Chown(filename string, uid int, gid int) error {
  85. filename = arozfs.GenericPathFilter(filename)
  86. return s.client.Chown(filename, uid, gid)
  87. }
  88. func (s SFTPFileSystemAbstraction) Chtimes(filename string, atime time.Time, mtime time.Time) error {
  89. filename = arozfs.GenericPathFilter(filename)
  90. return s.client.Chtimes(filename, atime, mtime)
  91. }
  92. func (s SFTPFileSystemAbstraction) Create(filename string) (arozfs.File, error) {
  93. filename = arozfs.GenericPathFilter(filename)
  94. //TODO: ADD FILE TYPE CONVERSION
  95. return nil, arozfs.ErrNullOperation
  96. }
  97. func (s SFTPFileSystemAbstraction) Mkdir(filename string, mode os.FileMode) error {
  98. filename = arozfs.GenericPathFilter(filename)
  99. return s.client.Mkdir(filename)
  100. }
  101. func (s SFTPFileSystemAbstraction) MkdirAll(filename string, mode os.FileMode) error {
  102. filename = arozfs.GenericPathFilter(filename)
  103. return s.client.MkdirAll(filename)
  104. }
  105. func (s SFTPFileSystemAbstraction) Name() string {
  106. return ""
  107. }
  108. func (s SFTPFileSystemAbstraction) Open(filename string) (arozfs.File, error) {
  109. filename = arozfs.GenericPathFilter(filename)
  110. f, err := s.client.Open(filename)
  111. if err != nil {
  112. return nil, err
  113. }
  114. stats, err := f.Stat()
  115. if err != nil {
  116. return nil, err
  117. }
  118. isDir := stats.IsDir()
  119. de := []fs.DirEntry{}
  120. if isDir {
  121. dirEntries, err := s.ReadDir(filename)
  122. if err == nil {
  123. de = dirEntries
  124. }
  125. }
  126. //Wrap the file and return
  127. wf := newSftpFsFile(f, isDir, de)
  128. return wf, nil
  129. }
  130. func (s SFTPFileSystemAbstraction) OpenFile(filename string, flag int, perm os.FileMode) (arozfs.File, error) {
  131. filename = arozfs.GenericPathFilter(filename)
  132. f, err := s.client.OpenFile(filename, flag)
  133. if err != nil {
  134. return nil, err
  135. }
  136. stats, err := f.Stat()
  137. if err != nil {
  138. return nil, err
  139. }
  140. isDir := stats.IsDir()
  141. de := []fs.DirEntry{}
  142. if isDir {
  143. dirEntries, err := s.ReadDir(filename)
  144. if err == nil {
  145. de = dirEntries
  146. }
  147. }
  148. //Wrap the file and return
  149. wf := newSftpFsFile(f, isDir, de)
  150. return wf, nil
  151. }
  152. func (s SFTPFileSystemAbstraction) Remove(filename string) error {
  153. filename = arozfs.GenericPathFilter(filename)
  154. return s.client.Remove(filename)
  155. }
  156. func (s SFTPFileSystemAbstraction) RemoveAll(filename string) error {
  157. filename = arozfs.GenericPathFilter(filename)
  158. if s.IsDir(filename) {
  159. return s.client.RemoveDirectory(filename)
  160. }
  161. return s.Remove(filename)
  162. }
  163. func (s SFTPFileSystemAbstraction) Rename(oldname, newname string) error {
  164. oldname = arozfs.GenericPathFilter(oldname)
  165. newname = arozfs.GenericPathFilter(newname)
  166. return s.client.Rename(oldname, newname)
  167. }
  168. func (s SFTPFileSystemAbstraction) Stat(filename string) (os.FileInfo, error) {
  169. filename = arozfs.GenericPathFilter(filename)
  170. return s.client.Stat(filename)
  171. }
  172. func (s SFTPFileSystemAbstraction) Close() error {
  173. err := s.client.Close()
  174. if err != nil {
  175. return err
  176. }
  177. err = s.conn.Close()
  178. if err != nil {
  179. return err
  180. }
  181. return nil
  182. }
  183. /*
  184. Abstraction Utilities
  185. */
  186. func (s SFTPFileSystemAbstraction) VirtualPathToRealPath(subpath string, username string) (string, error) {
  187. rpath, err := arozfs.GenericVirtualPathToRealPathTranslator(s.uuid, s.hierarchy, subpath, username)
  188. if err != nil {
  189. return "", err
  190. }
  191. if !(len(rpath) >= len(s.mountFolder) && rpath[:len(s.mountFolder)] == s.mountFolder) {
  192. //Prepend the mount folder (aka root folder) to the translated output from generic path translator
  193. rpath = arozfs.ToSlash(filepath.Join(s.mountFolder, rpath))
  194. }
  195. return rpath, nil
  196. }
  197. func (s SFTPFileSystemAbstraction) RealPathToVirtualPath(fullpath string, username string) (string, error) {
  198. if len(fullpath) >= len(s.mountFolder) && fullpath[:len(s.mountFolder)] == s.mountFolder {
  199. //Trim out the mount folder path from the full path before passing into the generic path translator
  200. fullpath = fullpath[len(s.mountFolder):]
  201. }
  202. return arozfs.GenericRealPathToVirtualPathTranslator(s.uuid, s.hierarchy, fullpath, username)
  203. }
  204. func (s SFTPFileSystemAbstraction) FileExists(realpath string) bool {
  205. _, err := s.Stat(realpath)
  206. return err == nil
  207. }
  208. func (s SFTPFileSystemAbstraction) IsDir(realpath string) bool {
  209. info, err := s.Stat(realpath)
  210. if err != nil {
  211. return false
  212. }
  213. return info.IsDir()
  214. }
  215. func (s SFTPFileSystemAbstraction) Glob(realpathWildcard string) ([]string, error) {
  216. realpathWildcard = arozfs.GenericPathFilter(realpathWildcard)
  217. return s.client.Glob(realpathWildcard)
  218. }
  219. func (s SFTPFileSystemAbstraction) GetFileSize(realpath string) int64 {
  220. info, err := s.Stat(realpath)
  221. if err != nil {
  222. return 0
  223. }
  224. return info.Size()
  225. }
  226. func (s SFTPFileSystemAbstraction) GetModTime(realpath string) (int64, error) {
  227. info, err := s.Stat(realpath)
  228. if err != nil {
  229. return 0, err
  230. }
  231. return info.ModTime().Unix(), nil
  232. }
  233. func (s SFTPFileSystemAbstraction) WriteFile(filename string, content []byte, mode os.FileMode) error {
  234. filename = arozfs.GenericPathFilter(filename)
  235. f, err := s.client.OpenFile(filename, os.O_CREATE|os.O_WRONLY)
  236. if err != nil {
  237. return err
  238. }
  239. _, err = f.Write(content)
  240. return err
  241. }
  242. func (s SFTPFileSystemAbstraction) ReadFile(filename string) ([]byte, error) {
  243. filename = arozfs.GenericPathFilter(filename)
  244. f, err := s.client.OpenFile(filename, os.O_RDONLY)
  245. if err != nil {
  246. return []byte(""), err
  247. }
  248. return io.ReadAll(f)
  249. }
  250. func (s SFTPFileSystemAbstraction) ReadDir(filename string) ([]fs.DirEntry, error) {
  251. filename = arozfs.GenericPathFilter(filename)
  252. result := []fs.DirEntry{}
  253. infos, err := s.client.ReadDir(filename)
  254. if err != nil {
  255. return result, err
  256. }
  257. for _, finfo := range infos {
  258. de := newDirEntryFromFileInfo(finfo)
  259. result = append(result, de)
  260. }
  261. return result, nil
  262. }
  263. func (s SFTPFileSystemAbstraction) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
  264. filename = arozfs.GenericPathFilter(filename)
  265. f, err := s.client.OpenFile(filename, os.O_CREATE|os.O_WRONLY)
  266. if err != nil {
  267. return err
  268. }
  269. _, err = io.Copy(f, stream)
  270. return err
  271. }
  272. func (s SFTPFileSystemAbstraction) ReadStream(filename string) (io.ReadCloser, error) {
  273. filename = arozfs.GenericPathFilter(filename)
  274. f, err := s.client.OpenFile(filename, os.O_RDONLY)
  275. if err != nil {
  276. return nil, err
  277. }
  278. return f, nil
  279. }
  280. func (s SFTPFileSystemAbstraction) Walk(root string, walkFn filepath.WalkFunc) error {
  281. root = arozfs.GenericPathFilter(root)
  282. walker := s.client.Walk(root)
  283. for walker.Step() {
  284. walkFn(walker.Path(), walker.Stat(), walker.Err())
  285. }
  286. return nil
  287. }
  288. func (s SFTPFileSystemAbstraction) Heartbeat() error {
  289. return nil
  290. }