osfs.go 12 KB

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