osfs.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. package vfs
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. "strings"
  10. "time"
  11. "github.com/eikenb/pipeat"
  12. "github.com/rs/xid"
  13. "github.com/drakkan/sftpgo/logger"
  14. "github.com/drakkan/sftpgo/utils"
  15. )
  16. const (
  17. // osFsName is the name for the local Fs implementation
  18. osFsName = "osfs"
  19. )
  20. // OsFs is a Fs implementation that uses functions provided by the os package.
  21. type OsFs struct {
  22. name string
  23. connectionID string
  24. rootDir string
  25. virtualFolders []VirtualFolder
  26. }
  27. // NewOsFs returns an OsFs object that allows to interact with local Os filesystem
  28. func NewOsFs(connectionID, rootDir string, virtualFolders []VirtualFolder) Fs {
  29. return &OsFs{
  30. name: osFsName,
  31. connectionID: connectionID,
  32. rootDir: rootDir,
  33. virtualFolders: virtualFolders,
  34. }
  35. }
  36. // Name returns the name for the Fs implementation
  37. func (fs *OsFs) Name() string {
  38. return fs.name
  39. }
  40. // ConnectionID returns the SSH connection ID associated to this Fs implementation
  41. func (fs *OsFs) ConnectionID() string {
  42. return fs.connectionID
  43. }
  44. // Stat returns a FileInfo describing the named file
  45. func (fs *OsFs) Stat(name string) (os.FileInfo, error) {
  46. fi, err := os.Stat(name)
  47. if err != nil {
  48. return fi, err
  49. }
  50. for _, v := range fs.virtualFolders {
  51. if v.MappedPath == name {
  52. info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime(), false)
  53. return info, nil
  54. }
  55. }
  56. return fi, err
  57. }
  58. // Lstat returns a FileInfo describing the named file
  59. func (fs *OsFs) Lstat(name string) (os.FileInfo, error) {
  60. fi, err := os.Lstat(name)
  61. if err != nil {
  62. return fi, err
  63. }
  64. for _, v := range fs.virtualFolders {
  65. if v.MappedPath == name {
  66. info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime(), false)
  67. return info, nil
  68. }
  69. }
  70. return fi, err
  71. }
  72. // Open opens the named file for reading
  73. func (*OsFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, func(), error) {
  74. f, err := os.Open(name)
  75. return f, nil, nil, err
  76. }
  77. // Create creates or opens the named file for writing
  78. func (*OsFs) Create(name string, flag int) (File, *PipeWriter, func(), error) {
  79. var err error
  80. var f *os.File
  81. if flag == 0 {
  82. f, err = os.Create(name)
  83. } else {
  84. f, err = os.OpenFile(name, flag, os.ModePerm)
  85. }
  86. return f, nil, nil, err
  87. }
  88. // Rename renames (moves) source to target
  89. func (*OsFs) Rename(source, target string) error {
  90. return os.Rename(source, target)
  91. }
  92. // Remove removes the named file or (empty) directory.
  93. func (*OsFs) Remove(name string, isDir bool) error {
  94. return os.Remove(name)
  95. }
  96. // Mkdir creates a new directory with the specified name and default permissions
  97. func (*OsFs) Mkdir(name string) error {
  98. return os.Mkdir(name, os.ModePerm)
  99. }
  100. // Symlink creates source as a symbolic link to target.
  101. func (*OsFs) Symlink(source, target string) error {
  102. return os.Symlink(source, target)
  103. }
  104. // Readlink returns the destination of the named symbolic link
  105. // as absolute virtual path
  106. func (fs *OsFs) Readlink(name string) (string, error) {
  107. p, err := os.Readlink(name)
  108. if err != nil {
  109. return p, err
  110. }
  111. return fs.GetRelativePath(p), err
  112. }
  113. // Chown changes the numeric uid and gid of the named file.
  114. func (*OsFs) Chown(name string, uid int, gid int) error {
  115. return os.Chown(name, uid, gid)
  116. }
  117. // Chmod changes the mode of the named file to mode
  118. func (*OsFs) Chmod(name string, mode os.FileMode) error {
  119. return os.Chmod(name, mode)
  120. }
  121. // Chtimes changes the access and modification times of the named file
  122. func (*OsFs) Chtimes(name string, atime, mtime time.Time) error {
  123. return os.Chtimes(name, atime, mtime)
  124. }
  125. // Truncate changes the size of the named file
  126. func (*OsFs) Truncate(name string, size int64) error {
  127. return os.Truncate(name, size)
  128. }
  129. // ReadDir reads the directory named by dirname and returns
  130. // a list of directory entries.
  131. func (*OsFs) ReadDir(dirname string) ([]os.FileInfo, error) {
  132. f, err := os.Open(dirname)
  133. if err != nil {
  134. return nil, err
  135. }
  136. list, err := f.Readdir(-1)
  137. f.Close()
  138. if err != nil {
  139. return nil, err
  140. }
  141. return list, nil
  142. }
  143. // IsUploadResumeSupported returns true if upload resume is supported
  144. func (*OsFs) IsUploadResumeSupported() bool {
  145. return true
  146. }
  147. // IsAtomicUploadSupported returns true if atomic upload is supported
  148. func (*OsFs) IsAtomicUploadSupported() bool {
  149. return true
  150. }
  151. // IsNotExist returns a boolean indicating whether the error is known to
  152. // report that a file or directory does not exist
  153. func (*OsFs) IsNotExist(err error) bool {
  154. return os.IsNotExist(err)
  155. }
  156. // IsPermission returns a boolean indicating whether the error is known to
  157. // report that permission is denied.
  158. func (*OsFs) IsPermission(err error) bool {
  159. return os.IsPermission(err)
  160. }
  161. // IsNotSupported returns true if the error indicate an unsupported operation
  162. func (*OsFs) IsNotSupported(err error) bool {
  163. if err == nil {
  164. return false
  165. }
  166. return err == ErrVfsUnsupported
  167. }
  168. // CheckRootPath creates the root directory if it does not exists
  169. func (fs *OsFs) CheckRootPath(username string, uid int, gid int) bool {
  170. var err error
  171. if _, err = fs.Stat(fs.rootDir); fs.IsNotExist(err) {
  172. err = os.MkdirAll(fs.rootDir, os.ModePerm)
  173. fsLog(fs, logger.LevelDebug, "root directory %#v for user %#v does not exist, try to create, mkdir error: %v",
  174. fs.rootDir, username, err)
  175. if err == nil {
  176. SetPathPermissions(fs, fs.rootDir, uid, gid)
  177. }
  178. }
  179. // create any missing dirs to the defined virtual dirs
  180. for _, v := range fs.virtualFolders {
  181. p := filepath.Clean(filepath.Join(fs.rootDir, v.VirtualPath))
  182. err = fs.createMissingDirs(p, uid, gid)
  183. if err != nil {
  184. return false
  185. }
  186. }
  187. return (err == nil)
  188. }
  189. // ScanRootDirContents returns the number of files contained in the root
  190. // directory and their size
  191. func (fs *OsFs) ScanRootDirContents() (int, int64, error) {
  192. numFiles, size, err := fs.GetDirSize(fs.rootDir)
  193. for _, v := range fs.virtualFolders {
  194. if !v.IsIncludedInUserQuota() {
  195. continue
  196. }
  197. num, s, err := fs.GetDirSize(v.MappedPath)
  198. if err != nil {
  199. if fs.IsNotExist(err) {
  200. fsLog(fs, logger.LevelWarn, "unable to scan contents for non-existent mapped path: %#v", v.MappedPath)
  201. continue
  202. }
  203. return numFiles, size, err
  204. }
  205. numFiles += num
  206. size += s
  207. }
  208. return numFiles, size, err
  209. }
  210. // GetAtomicUploadPath returns the path to use for an atomic upload
  211. func (*OsFs) GetAtomicUploadPath(name string) string {
  212. dir := filepath.Dir(name)
  213. guid := xid.New().String()
  214. return filepath.Join(dir, ".sftpgo-upload."+guid+"."+filepath.Base(name))
  215. }
  216. // GetRelativePath returns the path for a file relative to the user's home dir.
  217. // This is the path as seen by SFTP users
  218. func (fs *OsFs) GetRelativePath(name string) string {
  219. basePath := fs.rootDir
  220. virtualPath := "/"
  221. for _, v := range fs.virtualFolders {
  222. if strings.HasPrefix(name, v.MappedPath+string(os.PathSeparator)) ||
  223. filepath.Clean(name) == v.MappedPath {
  224. basePath = v.MappedPath
  225. virtualPath = v.VirtualPath
  226. }
  227. }
  228. rel, err := filepath.Rel(basePath, filepath.Clean(name))
  229. if err != nil {
  230. return ""
  231. }
  232. if rel == "." || strings.HasPrefix(rel, "..") {
  233. rel = ""
  234. }
  235. return path.Join(virtualPath, filepath.ToSlash(rel))
  236. }
  237. // Walk walks the file tree rooted at root, calling walkFn for each file or
  238. // directory in the tree, including root
  239. func (*OsFs) Walk(root string, walkFn filepath.WalkFunc) error {
  240. return filepath.Walk(root, walkFn)
  241. }
  242. // Join joins any number of path elements into a single path
  243. func (*OsFs) Join(elem ...string) string {
  244. return filepath.Join(elem...)
  245. }
  246. // ResolvePath returns the matching filesystem path for the specified sftp path
  247. func (fs *OsFs) ResolvePath(sftpPath string) (string, error) {
  248. if !filepath.IsAbs(fs.rootDir) {
  249. return "", fmt.Errorf("Invalid root path: %v", fs.rootDir)
  250. }
  251. basePath, r := fs.GetFsPaths(sftpPath)
  252. p, err := filepath.EvalSymlinks(r)
  253. if err != nil && !os.IsNotExist(err) {
  254. return "", err
  255. } else if os.IsNotExist(err) {
  256. // The requested path doesn't exist, so at this point we need to iterate up the
  257. // path chain until we hit a directory that _does_ exist and can be validated.
  258. _, err = fs.findFirstExistingDir(r, basePath)
  259. if err != nil {
  260. fsLog(fs, logger.LevelWarn, "error resolving non-existent path: %#v", err)
  261. }
  262. return r, err
  263. }
  264. err = fs.isSubDir(p, basePath)
  265. if err != nil {
  266. fsLog(fs, logger.LevelWarn, "Invalid path resolution, dir: %#v outside user home: %#v err: %v", p, fs.rootDir, err)
  267. }
  268. return r, err
  269. }
  270. // GetDirSize returns the number of files and the size for a folder
  271. // including any subfolders
  272. func (fs *OsFs) GetDirSize(dirname string) (int, int64, error) {
  273. numFiles := 0
  274. size := int64(0)
  275. isDir, err := IsDirectory(fs, dirname)
  276. if err == nil && isDir {
  277. err = filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
  278. if err != nil {
  279. return err
  280. }
  281. if info != nil && info.Mode().IsRegular() {
  282. size += info.Size()
  283. numFiles++
  284. }
  285. return err
  286. })
  287. }
  288. return numFiles, size, err
  289. }
  290. // HasVirtualFolders returns true if folders are emulated
  291. func (*OsFs) HasVirtualFolders() bool {
  292. return false
  293. }
  294. // GetFsPaths returns the base path and filesystem path for the given sftpPath.
  295. // base path is the root dir or matching the virtual folder dir for the sftpPath.
  296. // file path is the filesystem path matching the sftpPath
  297. func (fs *OsFs) GetFsPaths(sftpPath string) (string, string) {
  298. basePath := fs.rootDir
  299. virtualPath, mappedPath := fs.getMappedFolderForPath(sftpPath)
  300. if len(mappedPath) > 0 {
  301. basePath = mappedPath
  302. sftpPath = strings.TrimPrefix(utils.CleanPath(sftpPath), virtualPath)
  303. }
  304. r := filepath.Clean(filepath.Join(basePath, sftpPath))
  305. return basePath, r
  306. }
  307. // returns the path for the mapped folders or an empty string
  308. func (fs *OsFs) getMappedFolderForPath(p string) (virtualPath, mappedPath string) {
  309. if len(fs.virtualFolders) == 0 {
  310. return
  311. }
  312. dirsForPath := utils.GetDirsForSFTPPath(p)
  313. // dirsForPath contains all the dirs for a given path in reverse order
  314. // for example if the path is: /1/2/3/4 it contains:
  315. // [ "/1/2/3/4", "/1/2/3", "/1/2", "/1", "/" ]
  316. // so the first match is the one we are interested to
  317. for _, val := range dirsForPath {
  318. for _, v := range fs.virtualFolders {
  319. if val == v.VirtualPath {
  320. return v.VirtualPath, v.MappedPath
  321. }
  322. }
  323. }
  324. return
  325. }
  326. func (fs *OsFs) findNonexistentDirs(path, rootPath string) ([]string, error) {
  327. results := []string{}
  328. cleanPath := filepath.Clean(path)
  329. parent := filepath.Dir(cleanPath)
  330. _, err := os.Stat(parent)
  331. for os.IsNotExist(err) {
  332. results = append(results, parent)
  333. parent = filepath.Dir(parent)
  334. _, err = os.Stat(parent)
  335. }
  336. if err != nil {
  337. return results, err
  338. }
  339. p, err := filepath.EvalSymlinks(parent)
  340. if err != nil {
  341. return results, err
  342. }
  343. err = fs.isSubDir(p, rootPath)
  344. if err != nil {
  345. fsLog(fs, logger.LevelWarn, "error finding non existing dir: %v", err)
  346. }
  347. return results, err
  348. }
  349. func (fs *OsFs) findFirstExistingDir(path, rootPath string) (string, error) {
  350. results, err := fs.findNonexistentDirs(path, rootPath)
  351. if err != nil {
  352. fsLog(fs, logger.LevelWarn, "unable to find non existent dirs: %v", err)
  353. return "", err
  354. }
  355. var parent string
  356. if len(results) > 0 {
  357. lastMissingDir := results[len(results)-1]
  358. parent = filepath.Dir(lastMissingDir)
  359. } else {
  360. parent = rootPath
  361. }
  362. p, err := filepath.EvalSymlinks(parent)
  363. if err != nil {
  364. return "", err
  365. }
  366. fileInfo, err := os.Stat(p)
  367. if err != nil {
  368. return "", err
  369. }
  370. if !fileInfo.IsDir() {
  371. return "", fmt.Errorf("resolved path is not a dir: %#v", p)
  372. }
  373. err = fs.isSubDir(p, rootPath)
  374. return p, err
  375. }
  376. func (fs *OsFs) isSubDir(sub, rootPath string) error {
  377. // rootPath must exist and it is already a validated absolute path
  378. parent, err := filepath.EvalSymlinks(rootPath)
  379. if err != nil {
  380. fsLog(fs, logger.LevelWarn, "invalid root path %#v: %v", rootPath, err)
  381. return err
  382. }
  383. if parent == sub {
  384. return nil
  385. }
  386. if len(sub) < len(parent) {
  387. err = fmt.Errorf("path %#v is not inside: %#v", sub, parent)
  388. fsLog(fs, logger.LevelWarn, "error: %v ", err)
  389. return err
  390. }
  391. if !strings.HasPrefix(sub, parent+string(os.PathSeparator)) {
  392. err = fmt.Errorf("path %#v is not inside: %#v", sub, parent)
  393. fsLog(fs, logger.LevelWarn, "error: %v ", err)
  394. return err
  395. }
  396. return nil
  397. }
  398. func (fs *OsFs) createMissingDirs(filePath string, uid, gid int) error {
  399. dirsToCreate, err := fs.findNonexistentDirs(filePath, fs.rootDir)
  400. if err != nil {
  401. return err
  402. }
  403. last := len(dirsToCreate) - 1
  404. for i := range dirsToCreate {
  405. d := dirsToCreate[last-i]
  406. if err := os.Mkdir(d, os.ModePerm); err != nil {
  407. fsLog(fs, logger.LevelError, "error creating missing dir: %#v", d)
  408. return err
  409. }
  410. SetPathPermissions(fs, d, uid, gid)
  411. }
  412. return nil
  413. }
  414. // GetMimeType returns the content type
  415. func (fs *OsFs) GetMimeType(name string) (string, error) {
  416. f, err := os.OpenFile(name, os.O_RDONLY, 0)
  417. if err != nil {
  418. return "", err
  419. }
  420. defer f.Close()
  421. var buf [512]byte
  422. n, err := io.ReadFull(f, buf[:])
  423. if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
  424. return "", err
  425. }
  426. ctype := http.DetectContentType(buf[:n])
  427. // Rewind file.
  428. _, err = f.Seek(0, io.SeekStart)
  429. return ctype, err
  430. }