osfs.go 11 KB

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