osfs.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package vfs
  15. import (
  16. "bufio"
  17. "errors"
  18. "fmt"
  19. "io"
  20. "io/fs"
  21. "net/http"
  22. "os"
  23. "path"
  24. "path/filepath"
  25. "strings"
  26. "time"
  27. "github.com/eikenb/pipeat"
  28. fscopy "github.com/otiai10/copy"
  29. "github.com/pkg/sftp"
  30. "github.com/rs/xid"
  31. "github.com/sftpgo/sdk"
  32. "github.com/drakkan/sftpgo/v2/internal/logger"
  33. "github.com/drakkan/sftpgo/v2/internal/util"
  34. )
  35. const (
  36. // osFsName is the name for the local Fs implementation
  37. osFsName = "osfs"
  38. )
  39. type pathResolutionError struct {
  40. err string
  41. }
  42. func (e *pathResolutionError) Error() string {
  43. return fmt.Sprintf("Path resolution error: %s", e.err)
  44. }
  45. // OsFs is a Fs implementation that uses functions provided by the os package.
  46. type OsFs struct {
  47. name string
  48. connectionID string
  49. rootDir string
  50. // if not empty this fs is mouted as virtual folder in the specified path
  51. mountPath string
  52. localTempDir string
  53. readBufferSize int
  54. writeBufferSize int
  55. }
  56. // NewOsFs returns an OsFs object that allows to interact with local Os filesystem
  57. func NewOsFs(connectionID, rootDir, mountPath string, config *sdk.OSFsConfig) Fs {
  58. var readBufferSize, writeBufferSize int
  59. if config != nil {
  60. readBufferSize = config.ReadBufferSize * 1024 * 1024
  61. writeBufferSize = config.WriteBufferSize * 1024 * 1024
  62. }
  63. return &OsFs{
  64. name: osFsName,
  65. connectionID: connectionID,
  66. rootDir: rootDir,
  67. mountPath: getMountPath(mountPath),
  68. localTempDir: getLocalTempDir(),
  69. readBufferSize: readBufferSize,
  70. writeBufferSize: writeBufferSize,
  71. }
  72. }
  73. // Name returns the name for the Fs implementation
  74. func (fs *OsFs) Name() string {
  75. return fs.name
  76. }
  77. // ConnectionID returns the SSH connection ID associated to this Fs implementation
  78. func (fs *OsFs) ConnectionID() string {
  79. return fs.connectionID
  80. }
  81. // Stat returns a FileInfo describing the named file
  82. func (fs *OsFs) Stat(name string) (os.FileInfo, error) {
  83. return os.Stat(name)
  84. }
  85. // Lstat returns a FileInfo describing the named file
  86. func (fs *OsFs) Lstat(name string) (os.FileInfo, error) {
  87. return os.Lstat(name)
  88. }
  89. // Open opens the named file for reading
  90. func (fs *OsFs) Open(name string, offset int64) (File, PipeReader, func(), error) {
  91. f, err := os.Open(name)
  92. if err != nil {
  93. return nil, nil, nil, err
  94. }
  95. if offset > 0 {
  96. _, err = f.Seek(offset, io.SeekStart)
  97. if err != nil {
  98. f.Close()
  99. return nil, nil, nil, err
  100. }
  101. }
  102. if fs.readBufferSize <= 0 {
  103. return f, nil, nil, err
  104. }
  105. r, w, err := pipeat.PipeInDir(fs.localTempDir)
  106. if err != nil {
  107. f.Close()
  108. return nil, nil, nil, err
  109. }
  110. p := NewPipeReader(r)
  111. go func() {
  112. br := bufio.NewReaderSize(f, fs.readBufferSize)
  113. n, err := doCopy(w, br, nil)
  114. w.CloseWithError(err) //nolint:errcheck
  115. f.Close()
  116. fsLog(fs, logger.LevelDebug, "download completed, path: %q size: %v, err: %v", name, n, err)
  117. }()
  118. return nil, p, nil, nil
  119. }
  120. // Create creates or opens the named file for writing
  121. func (fs *OsFs) Create(name string, flag, _ int) (File, PipeWriter, func(), error) {
  122. if !fs.useWriteBuffering(flag) {
  123. var err error
  124. var f *os.File
  125. if flag == 0 {
  126. f, err = os.Create(name)
  127. } else {
  128. f, err = os.OpenFile(name, flag, 0666)
  129. }
  130. return f, nil, nil, err
  131. }
  132. f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  133. if err != nil {
  134. return nil, nil, nil, err
  135. }
  136. r, w, err := pipeat.PipeInDir(fs.localTempDir)
  137. if err != nil {
  138. f.Close()
  139. return nil, nil, nil, err
  140. }
  141. p := NewPipeWriter(w)
  142. go func() {
  143. bw := bufio.NewWriterSize(f, fs.writeBufferSize)
  144. n, err := doCopy(bw, r, nil)
  145. errFlush := bw.Flush()
  146. if err == nil && errFlush != nil {
  147. err = errFlush
  148. }
  149. errClose := f.Close()
  150. if err == nil && errClose != nil {
  151. err = errClose
  152. }
  153. r.CloseWithError(err) //nolint:errcheck
  154. p.Done(err)
  155. fsLog(fs, logger.LevelDebug, "upload completed, path: %q, readed bytes: %v, err: %v", name, n, err)
  156. }()
  157. return nil, p, nil, nil
  158. }
  159. // Rename renames (moves) source to target
  160. func (fs *OsFs) Rename(source, target string) (int, int64, error) {
  161. if source == target {
  162. return -1, -1, nil
  163. }
  164. err := os.Rename(source, target)
  165. if err != nil && isCrossDeviceError(err) {
  166. fsLog(fs, logger.LevelError, "cross device error detected while renaming %q -> %q. Trying a copy and remove, this could take a long time",
  167. source, target)
  168. var readBufferSize uint
  169. if fs.readBufferSize > 0 {
  170. readBufferSize = uint(fs.readBufferSize)
  171. }
  172. err = fscopy.Copy(source, target, fscopy.Options{
  173. OnSymlink: func(_ string) fscopy.SymlinkAction {
  174. return fscopy.Skip
  175. },
  176. CopyBufferSize: readBufferSize,
  177. })
  178. if err != nil {
  179. fsLog(fs, logger.LevelError, "cross device copy error: %v", err)
  180. return -1, -1, err
  181. }
  182. err = os.RemoveAll(source)
  183. return -1, -1, err
  184. }
  185. return -1, -1, err
  186. }
  187. // Remove removes the named file or (empty) directory.
  188. func (*OsFs) Remove(name string, _ bool) error {
  189. return os.Remove(name)
  190. }
  191. // Mkdir creates a new directory with the specified name and default permissions
  192. func (*OsFs) Mkdir(name string) error {
  193. return os.Mkdir(name, os.ModePerm)
  194. }
  195. // Symlink creates source as a symbolic link to target.
  196. func (*OsFs) Symlink(source, target string) error {
  197. return os.Symlink(source, target)
  198. }
  199. // Readlink returns the destination of the named symbolic link
  200. // as absolute virtual path
  201. func (fs *OsFs) Readlink(name string) (string, error) {
  202. // we don't have to follow multiple links:
  203. // https://github.com/openssh/openssh-portable/blob/7bf2eb958fbb551e7d61e75c176bb3200383285d/sftp-server.c#L1329
  204. resolved, err := os.Readlink(name)
  205. if err != nil {
  206. return "", err
  207. }
  208. resolved = filepath.Clean(resolved)
  209. if !filepath.IsAbs(resolved) {
  210. resolved = filepath.Join(filepath.Dir(name), resolved)
  211. }
  212. return fs.GetRelativePath(resolved), nil
  213. }
  214. // Chown changes the numeric uid and gid of the named file.
  215. func (*OsFs) Chown(name string, uid int, gid int) error {
  216. return os.Chown(name, uid, gid)
  217. }
  218. // Chmod changes the mode of the named file to mode
  219. func (*OsFs) Chmod(name string, mode os.FileMode) error {
  220. return os.Chmod(name, mode)
  221. }
  222. // Chtimes changes the access and modification times of the named file
  223. func (*OsFs) Chtimes(name string, atime, mtime time.Time, _ bool) error {
  224. return os.Chtimes(name, atime, mtime)
  225. }
  226. // Truncate changes the size of the named file
  227. func (*OsFs) Truncate(name string, size int64) error {
  228. return os.Truncate(name, size)
  229. }
  230. // ReadDir reads the directory named by dirname and returns
  231. // a list of directory entries.
  232. func (*OsFs) ReadDir(dirname string) (DirLister, error) {
  233. f, err := os.Open(dirname)
  234. if err != nil {
  235. if isInvalidNameError(err) {
  236. err = os.ErrNotExist
  237. }
  238. return nil, err
  239. }
  240. return &osFsDirLister{f}, nil
  241. }
  242. // IsUploadResumeSupported returns true if resuming uploads is supported
  243. func (*OsFs) IsUploadResumeSupported() bool {
  244. return true
  245. }
  246. // IsConditionalUploadResumeSupported returns if resuming uploads is supported
  247. // for the specified size
  248. func (*OsFs) IsConditionalUploadResumeSupported(_ int64) bool {
  249. return true
  250. }
  251. // IsAtomicUploadSupported returns true if atomic upload is supported
  252. func (*OsFs) IsAtomicUploadSupported() bool {
  253. return true
  254. }
  255. // IsNotExist returns a boolean indicating whether the error is known to
  256. // report that a file or directory does not exist
  257. func (*OsFs) IsNotExist(err error) bool {
  258. return errors.Is(err, fs.ErrNotExist)
  259. }
  260. // IsPermission returns a boolean indicating whether the error is known to
  261. // report that permission is denied.
  262. func (*OsFs) IsPermission(err error) bool {
  263. if _, ok := err.(*pathResolutionError); ok {
  264. return true
  265. }
  266. return errors.Is(err, fs.ErrPermission)
  267. }
  268. // IsNotSupported returns true if the error indicate an unsupported operation
  269. func (*OsFs) IsNotSupported(err error) bool {
  270. if err == nil {
  271. return false
  272. }
  273. return err == ErrVfsUnsupported
  274. }
  275. // CheckRootPath creates the root directory if it does not exists
  276. func (fs *OsFs) CheckRootPath(username string, uid int, gid int) bool {
  277. var err error
  278. if _, err = fs.Stat(fs.rootDir); fs.IsNotExist(err) {
  279. err = os.MkdirAll(fs.rootDir, os.ModePerm)
  280. if err == nil {
  281. SetPathPermissions(fs, fs.rootDir, uid, gid)
  282. } else {
  283. fsLog(fs, logger.LevelError, "error creating root directory %q for user %q: %v", fs.rootDir, username, err)
  284. }
  285. }
  286. return err == nil
  287. }
  288. // ScanRootDirContents returns the number of files contained in the root
  289. // directory and their size
  290. func (fs *OsFs) ScanRootDirContents() (int, int64, error) {
  291. return fs.GetDirSize(fs.rootDir)
  292. }
  293. // CheckMetadata checks the metadata consistency
  294. func (*OsFs) CheckMetadata() error {
  295. return nil
  296. }
  297. // GetAtomicUploadPath returns the path to use for an atomic upload
  298. func (*OsFs) GetAtomicUploadPath(name string) string {
  299. dir := filepath.Dir(name)
  300. if tempPath != "" {
  301. dir = tempPath
  302. }
  303. guid := xid.New().String()
  304. return filepath.Join(dir, ".sftpgo-upload."+guid+"."+filepath.Base(name))
  305. }
  306. // GetRelativePath returns the path for a file relative to the user's home dir.
  307. // This is the path as seen by SFTPGo users
  308. func (fs *OsFs) GetRelativePath(name string) string {
  309. virtualPath := "/"
  310. if fs.mountPath != "" {
  311. virtualPath = fs.mountPath
  312. }
  313. rel, err := filepath.Rel(fs.rootDir, filepath.Clean(name))
  314. if err != nil {
  315. return ""
  316. }
  317. if rel == "." || strings.HasPrefix(rel, "..") {
  318. rel = ""
  319. }
  320. return path.Join(virtualPath, filepath.ToSlash(rel))
  321. }
  322. // Walk walks the file tree rooted at root, calling walkFn for each file or
  323. // directory in the tree, including root
  324. func (*OsFs) Walk(root string, walkFn filepath.WalkFunc) error {
  325. return filepath.Walk(root, walkFn)
  326. }
  327. // Join joins any number of path elements into a single path
  328. func (*OsFs) Join(elem ...string) string {
  329. return filepath.Join(elem...)
  330. }
  331. // ResolvePath returns the matching filesystem path for the specified sftp path
  332. func (fs *OsFs) ResolvePath(virtualPath string) (string, error) {
  333. if !filepath.IsAbs(fs.rootDir) {
  334. return "", fmt.Errorf("invalid root path %q", fs.rootDir)
  335. }
  336. if fs.mountPath != "" {
  337. virtualPath = strings.TrimPrefix(virtualPath, fs.mountPath)
  338. }
  339. r := filepath.Clean(filepath.Join(fs.rootDir, virtualPath))
  340. p, err := filepath.EvalSymlinks(r)
  341. if isInvalidNameError(err) {
  342. err = os.ErrNotExist
  343. }
  344. isNotExist := fs.IsNotExist(err)
  345. if err != nil && !isNotExist {
  346. return "", err
  347. } else if isNotExist {
  348. // The requested path doesn't exist, so at this point we need to iterate up the
  349. // path chain until we hit a directory that _does_ exist and can be validated.
  350. _, err = fs.findFirstExistingDir(r)
  351. if err != nil {
  352. fsLog(fs, logger.LevelError, "error resolving non-existent path %q", err)
  353. }
  354. return r, err
  355. }
  356. err = fs.isSubDir(p)
  357. if err != nil {
  358. fsLog(fs, logger.LevelError, "Invalid path resolution, path %q original path %q resolved %q err: %v",
  359. p, virtualPath, r, err)
  360. }
  361. return r, err
  362. }
  363. // RealPath implements the FsRealPather interface
  364. func (fs *OsFs) RealPath(p string) (string, error) {
  365. linksWalked := 0
  366. for {
  367. info, err := os.Lstat(p)
  368. if err != nil {
  369. if errors.Is(err, os.ErrNotExist) {
  370. return fs.GetRelativePath(p), nil
  371. }
  372. return "", err
  373. }
  374. if info.Mode()&os.ModeSymlink == 0 {
  375. return fs.GetRelativePath(p), nil
  376. }
  377. resolvedLink, err := os.Readlink(p)
  378. if err != nil {
  379. return "", err
  380. }
  381. resolvedLink = filepath.Clean(resolvedLink)
  382. if filepath.IsAbs(resolvedLink) {
  383. p = resolvedLink
  384. } else {
  385. p = filepath.Join(filepath.Dir(p), resolvedLink)
  386. }
  387. linksWalked++
  388. if linksWalked > 10 {
  389. fsLog(fs, logger.LevelError, "unable to get real path, too many links: %d", linksWalked)
  390. return "", &pathResolutionError{err: "too many links"}
  391. }
  392. }
  393. }
  394. // GetDirSize returns the number of files and the size for a folder
  395. // including any subfolders
  396. func (fs *OsFs) GetDirSize(dirname string) (int, int64, error) {
  397. numFiles := 0
  398. size := int64(0)
  399. isDir, err := isDirectory(fs, dirname)
  400. if err == nil && isDir {
  401. err = filepath.Walk(dirname, func(_ string, info os.FileInfo, err error) error {
  402. if err != nil {
  403. return err
  404. }
  405. if info != nil && info.Mode().IsRegular() {
  406. size += info.Size()
  407. numFiles++
  408. if numFiles%1000 == 0 {
  409. fsLog(fs, logger.LevelDebug, "dirname %q scan in progress, files: %d, size: %d", dirname, numFiles, size)
  410. }
  411. }
  412. return err
  413. })
  414. }
  415. return numFiles, size, err
  416. }
  417. // HasVirtualFolders returns true if folders are emulated
  418. func (*OsFs) HasVirtualFolders() bool {
  419. return false
  420. }
  421. func (fs *OsFs) findNonexistentDirs(filePath string) ([]string, error) {
  422. results := []string{}
  423. cleanPath := filepath.Clean(filePath)
  424. parent := filepath.Dir(cleanPath)
  425. _, err := os.Stat(parent)
  426. for fs.IsNotExist(err) {
  427. results = append(results, parent)
  428. parent = filepath.Dir(parent)
  429. if util.Contains(results, parent) {
  430. break
  431. }
  432. _, err = os.Stat(parent)
  433. }
  434. if err != nil {
  435. return results, err
  436. }
  437. p, err := filepath.EvalSymlinks(parent)
  438. if err != nil {
  439. return results, err
  440. }
  441. err = fs.isSubDir(p)
  442. if err != nil {
  443. fsLog(fs, logger.LevelError, "error finding non existing dir: %v", err)
  444. }
  445. return results, err
  446. }
  447. func (fs *OsFs) findFirstExistingDir(path string) (string, error) {
  448. results, err := fs.findNonexistentDirs(path)
  449. if err != nil {
  450. fsLog(fs, logger.LevelError, "unable to find non existent dirs: %v", err)
  451. return "", err
  452. }
  453. var parent string
  454. if len(results) > 0 {
  455. lastMissingDir := results[len(results)-1]
  456. parent = filepath.Dir(lastMissingDir)
  457. } else {
  458. parent = fs.rootDir
  459. }
  460. p, err := filepath.EvalSymlinks(parent)
  461. if err != nil {
  462. return "", err
  463. }
  464. fileInfo, err := os.Stat(p)
  465. if err != nil {
  466. return "", err
  467. }
  468. if !fileInfo.IsDir() {
  469. return "", fmt.Errorf("resolved path is not a dir: %q", p)
  470. }
  471. err = fs.isSubDir(p)
  472. return p, err
  473. }
  474. func (fs *OsFs) isSubDir(sub string) error {
  475. // fs.rootDir must exist and it is already a validated absolute path
  476. parent, err := filepath.EvalSymlinks(fs.rootDir)
  477. if err != nil {
  478. fsLog(fs, logger.LevelError, "invalid root path %q: %v", fs.rootDir, err)
  479. return err
  480. }
  481. if parent == sub {
  482. return nil
  483. }
  484. if len(sub) < len(parent) {
  485. err = fmt.Errorf("path %q is not inside %q", sub, parent)
  486. return &pathResolutionError{err: err.Error()}
  487. }
  488. separator := string(os.PathSeparator)
  489. if parent == filepath.Dir(parent) {
  490. // parent is the root dir, on Windows we can have C:\, D:\ and so on here
  491. // so we still need the prefix check
  492. separator = ""
  493. }
  494. if !strings.HasPrefix(sub, parent+separator) {
  495. err = fmt.Errorf("path %q is not inside %q", sub, parent)
  496. return &pathResolutionError{err: err.Error()}
  497. }
  498. return nil
  499. }
  500. // GetMimeType returns the content type
  501. func (fs *OsFs) GetMimeType(name string) (string, error) {
  502. f, err := os.OpenFile(name, os.O_RDONLY, 0)
  503. if err != nil {
  504. return "", err
  505. }
  506. defer f.Close()
  507. var buf [512]byte
  508. n, err := io.ReadFull(f, buf[:])
  509. if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
  510. return "", err
  511. }
  512. ctype := http.DetectContentType(buf[:n])
  513. // Rewind file.
  514. _, err = f.Seek(0, io.SeekStart)
  515. return ctype, err
  516. }
  517. // Close closes the fs
  518. func (*OsFs) Close() error {
  519. return nil
  520. }
  521. // GetAvailableDiskSize returns the available size for the specified path
  522. func (*OsFs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) {
  523. return getStatFS(dirName)
  524. }
  525. func (fs *OsFs) useWriteBuffering(flag int) bool {
  526. if fs.writeBufferSize <= 0 {
  527. return false
  528. }
  529. if flag == 0 {
  530. return true
  531. }
  532. if flag&os.O_TRUNC == 0 {
  533. fsLog(fs, logger.LevelDebug, "truncate flag missing, buffering write not possible")
  534. return false
  535. }
  536. if flag&os.O_RDWR != 0 {
  537. fsLog(fs, logger.LevelDebug, "read and write flag found, buffering write not possible")
  538. return false
  539. }
  540. return true
  541. }
  542. type osFsDirLister struct {
  543. f *os.File
  544. }
  545. func (l *osFsDirLister) Next(limit int) ([]os.FileInfo, error) {
  546. if limit <= 0 {
  547. return nil, errInvalidDirListerLimit
  548. }
  549. return l.f.Readdir(limit)
  550. }
  551. func (l *osFsDirLister) Close() error {
  552. return l.f.Close()
  553. }