vfs.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. // Package vfs provides local and remote filesystems support
  2. package vfs
  3. import (
  4. "errors"
  5. "fmt"
  6. "io"
  7. "net/url"
  8. "os"
  9. "path"
  10. "path/filepath"
  11. "runtime"
  12. "strings"
  13. "time"
  14. "github.com/eikenb/pipeat"
  15. "github.com/pkg/sftp"
  16. "github.com/sftpgo/sdk"
  17. "github.com/sftpgo/sdk/plugin/metadata"
  18. "github.com/drakkan/sftpgo/v2/kms"
  19. "github.com/drakkan/sftpgo/v2/logger"
  20. "github.com/drakkan/sftpgo/v2/plugin"
  21. "github.com/drakkan/sftpgo/v2/util"
  22. )
  23. const dirMimeType = "inode/directory"
  24. var (
  25. validAzAccessTier = []string{"", "Archive", "Hot", "Cool"}
  26. // ErrStorageSizeUnavailable is returned if the storage backend does not support getting the size
  27. ErrStorageSizeUnavailable = errors.New("unable to get available size for this storage backend")
  28. // ErrVfsUnsupported defines the error for an unsupported VFS operation
  29. ErrVfsUnsupported = errors.New("not supported")
  30. credentialsDirPath string
  31. tempPath string
  32. sftpFingerprints []string
  33. )
  34. // SetCredentialsDirPath sets the credentials dir path
  35. func SetCredentialsDirPath(credentialsPath string) {
  36. credentialsDirPath = credentialsPath
  37. }
  38. // GetCredentialsDirPath returns the credentials dir path
  39. func GetCredentialsDirPath() string {
  40. return credentialsDirPath
  41. }
  42. // SetTempPath sets the path for temporary files
  43. func SetTempPath(fsPath string) {
  44. tempPath = fsPath
  45. }
  46. // GetTempPath returns the path for temporary files
  47. func GetTempPath() string {
  48. return tempPath
  49. }
  50. // SetSFTPFingerprints sets the SFTP host key fingerprints
  51. func SetSFTPFingerprints(fp []string) {
  52. sftpFingerprints = fp
  53. }
  54. // Fs defines the interface for filesystem backends
  55. type Fs interface {
  56. Name() string
  57. ConnectionID() string
  58. Stat(name string) (os.FileInfo, error)
  59. Lstat(name string) (os.FileInfo, error)
  60. Open(name string, offset int64) (File, *pipeat.PipeReaderAt, func(), error)
  61. Create(name string, flag int) (File, *PipeWriter, func(), error)
  62. Rename(source, target string) error
  63. Remove(name string, isDir bool) error
  64. Mkdir(name string) error
  65. Symlink(source, target string) error
  66. Chown(name string, uid int, gid int) error
  67. Chmod(name string, mode os.FileMode) error
  68. Chtimes(name string, atime, mtime time.Time, isUploading bool) error
  69. Truncate(name string, size int64) error
  70. ReadDir(dirname string) ([]os.FileInfo, error)
  71. Readlink(name string) (string, error)
  72. IsUploadResumeSupported() bool
  73. IsAtomicUploadSupported() bool
  74. CheckRootPath(username string, uid int, gid int) bool
  75. ResolvePath(sftpPath string) (string, error)
  76. IsNotExist(err error) bool
  77. IsPermission(err error) bool
  78. IsNotSupported(err error) bool
  79. ScanRootDirContents() (int, int64, error)
  80. GetDirSize(dirname string) (int, int64, error)
  81. GetAtomicUploadPath(name string) string
  82. GetRelativePath(name string) string
  83. Walk(root string, walkFn filepath.WalkFunc) error
  84. Join(elem ...string) string
  85. HasVirtualFolders() bool
  86. GetMimeType(name string) (string, error)
  87. GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error)
  88. CheckMetadata() error
  89. Close() error
  90. }
  91. // fsMetadataChecker is a Fs that implements the getFileNamesInPrefix method.
  92. // This interface is used to abstract metadata consistency checks
  93. type fsMetadataChecker interface {
  94. Fs
  95. getFileNamesInPrefix(fsPrefix string) (map[string]bool, error)
  96. }
  97. // File defines an interface representing a SFTPGo file
  98. type File interface {
  99. io.Reader
  100. io.Writer
  101. io.Closer
  102. io.ReaderAt
  103. io.WriterAt
  104. io.Seeker
  105. Stat() (os.FileInfo, error)
  106. Name() string
  107. Truncate(size int64) error
  108. }
  109. // QuotaCheckResult defines the result for a quota check
  110. type QuotaCheckResult struct {
  111. HasSpace bool
  112. AllowedSize int64
  113. AllowedFiles int
  114. UsedSize int64
  115. UsedFiles int
  116. QuotaSize int64
  117. QuotaFiles int
  118. }
  119. // GetRemainingSize returns the remaining allowed size
  120. func (q *QuotaCheckResult) GetRemainingSize() int64 {
  121. if q.QuotaSize > 0 {
  122. return q.QuotaSize - q.UsedSize
  123. }
  124. return 0
  125. }
  126. // GetRemainingFiles returns the remaining allowed files
  127. func (q *QuotaCheckResult) GetRemainingFiles() int {
  128. if q.QuotaFiles > 0 {
  129. return q.QuotaFiles - q.UsedFiles
  130. }
  131. return 0
  132. }
  133. // S3FsConfig defines the configuration for S3 based filesystem
  134. type S3FsConfig struct {
  135. sdk.BaseS3FsConfig
  136. AccessSecret *kms.Secret `json:"access_secret,omitempty"`
  137. }
  138. // HideConfidentialData hides confidential data
  139. func (c *S3FsConfig) HideConfidentialData() {
  140. if c.AccessSecret != nil {
  141. c.AccessSecret.Hide()
  142. }
  143. }
  144. func (c *S3FsConfig) isEqual(other *S3FsConfig) bool {
  145. if c.Bucket != other.Bucket {
  146. return false
  147. }
  148. if c.KeyPrefix != other.KeyPrefix {
  149. return false
  150. }
  151. if c.Region != other.Region {
  152. return false
  153. }
  154. if c.AccessKey != other.AccessKey {
  155. return false
  156. }
  157. if c.Endpoint != other.Endpoint {
  158. return false
  159. }
  160. if c.StorageClass != other.StorageClass {
  161. return false
  162. }
  163. if c.ACL != other.ACL {
  164. return false
  165. }
  166. if c.UploadPartSize != other.UploadPartSize {
  167. return false
  168. }
  169. if c.UploadConcurrency != other.UploadConcurrency {
  170. return false
  171. }
  172. if c.DownloadConcurrency != other.DownloadConcurrency {
  173. return false
  174. }
  175. if c.DownloadPartSize != other.DownloadPartSize {
  176. return false
  177. }
  178. if c.DownloadPartMaxTime != other.DownloadPartMaxTime {
  179. return false
  180. }
  181. if c.UploadPartMaxTime != other.UploadPartMaxTime {
  182. return false
  183. }
  184. if c.ForcePathStyle != other.ForcePathStyle {
  185. return false
  186. }
  187. return c.isSecretEqual(other)
  188. }
  189. func (c *S3FsConfig) isSecretEqual(other *S3FsConfig) bool {
  190. if c.AccessSecret == nil {
  191. c.AccessSecret = kms.NewEmptySecret()
  192. }
  193. if other.AccessSecret == nil {
  194. other.AccessSecret = kms.NewEmptySecret()
  195. }
  196. return c.AccessSecret.IsEqual(other.AccessSecret)
  197. }
  198. func (c *S3FsConfig) checkCredentials() error {
  199. if c.AccessKey == "" && !c.AccessSecret.IsEmpty() {
  200. return errors.New("access_key cannot be empty with access_secret not empty")
  201. }
  202. if c.AccessSecret.IsEmpty() && c.AccessKey != "" {
  203. return errors.New("access_secret cannot be empty with access_key not empty")
  204. }
  205. if c.AccessSecret.IsEncrypted() && !c.AccessSecret.IsValid() {
  206. return errors.New("invalid encrypted access_secret")
  207. }
  208. if !c.AccessSecret.IsEmpty() && !c.AccessSecret.IsValidInput() {
  209. return errors.New("invalid access_secret")
  210. }
  211. return nil
  212. }
  213. // EncryptCredentials encrypts access secret if it is in plain text
  214. func (c *S3FsConfig) EncryptCredentials(additionalData string) error {
  215. if c.AccessSecret.IsPlain() {
  216. c.AccessSecret.SetAdditionalData(additionalData)
  217. err := c.AccessSecret.Encrypt()
  218. if err != nil {
  219. return err
  220. }
  221. }
  222. return nil
  223. }
  224. func (c *S3FsConfig) checkPartSizeAndConcurrency() error {
  225. if c.UploadPartSize != 0 && (c.UploadPartSize < 5 || c.UploadPartSize > 5000) {
  226. return errors.New("upload_part_size cannot be != 0, lower than 5 (MB) or greater than 5000 (MB)")
  227. }
  228. if c.UploadConcurrency < 0 || c.UploadConcurrency > 64 {
  229. return fmt.Errorf("invalid upload concurrency: %v", c.UploadConcurrency)
  230. }
  231. if c.DownloadPartSize != 0 && (c.DownloadPartSize < 5 || c.DownloadPartSize > 5000) {
  232. return errors.New("download_part_size cannot be != 0, lower than 5 (MB) or greater than 5000 (MB)")
  233. }
  234. if c.DownloadConcurrency < 0 || c.DownloadConcurrency > 64 {
  235. return fmt.Errorf("invalid download concurrency: %v", c.DownloadConcurrency)
  236. }
  237. return nil
  238. }
  239. // Validate returns an error if the configuration is not valid
  240. func (c *S3FsConfig) Validate() error {
  241. if c.AccessSecret == nil {
  242. c.AccessSecret = kms.NewEmptySecret()
  243. }
  244. if c.Bucket == "" {
  245. return errors.New("bucket cannot be empty")
  246. }
  247. if c.Region == "" {
  248. return errors.New("region cannot be empty")
  249. }
  250. if err := c.checkCredentials(); err != nil {
  251. return err
  252. }
  253. if c.KeyPrefix != "" {
  254. if strings.HasPrefix(c.KeyPrefix, "/") {
  255. return errors.New("key_prefix cannot start with /")
  256. }
  257. c.KeyPrefix = path.Clean(c.KeyPrefix)
  258. if !strings.HasSuffix(c.KeyPrefix, "/") {
  259. c.KeyPrefix += "/"
  260. }
  261. }
  262. c.StorageClass = strings.TrimSpace(c.StorageClass)
  263. c.ACL = strings.TrimSpace(c.ACL)
  264. return c.checkPartSizeAndConcurrency()
  265. }
  266. // GCSFsConfig defines the configuration for Google Cloud Storage based filesystem
  267. type GCSFsConfig struct {
  268. sdk.BaseGCSFsConfig
  269. Credentials *kms.Secret `json:"credentials,omitempty"`
  270. }
  271. // HideConfidentialData hides confidential data
  272. func (c *GCSFsConfig) HideConfidentialData() {
  273. if c.Credentials != nil {
  274. c.Credentials.Hide()
  275. }
  276. }
  277. func (c *GCSFsConfig) isEqual(other *GCSFsConfig) bool {
  278. if c.Bucket != other.Bucket {
  279. return false
  280. }
  281. if c.KeyPrefix != other.KeyPrefix {
  282. return false
  283. }
  284. if c.AutomaticCredentials != other.AutomaticCredentials {
  285. return false
  286. }
  287. if c.StorageClass != other.StorageClass {
  288. return false
  289. }
  290. if c.ACL != other.ACL {
  291. return false
  292. }
  293. if c.Credentials == nil {
  294. c.Credentials = kms.NewEmptySecret()
  295. }
  296. if other.Credentials == nil {
  297. other.Credentials = kms.NewEmptySecret()
  298. }
  299. return c.Credentials.IsEqual(other.Credentials)
  300. }
  301. // Validate returns an error if the configuration is not valid
  302. func (c *GCSFsConfig) Validate(credentialsFilePath string) error {
  303. if c.Credentials == nil || c.AutomaticCredentials == 1 {
  304. c.Credentials = kms.NewEmptySecret()
  305. }
  306. if c.Bucket == "" {
  307. return errors.New("bucket cannot be empty")
  308. }
  309. if c.KeyPrefix != "" {
  310. if strings.HasPrefix(c.KeyPrefix, "/") {
  311. return errors.New("key_prefix cannot start with /")
  312. }
  313. c.KeyPrefix = path.Clean(c.KeyPrefix)
  314. if !strings.HasSuffix(c.KeyPrefix, "/") {
  315. c.KeyPrefix += "/"
  316. }
  317. }
  318. if c.Credentials.IsEncrypted() && !c.Credentials.IsValid() {
  319. return errors.New("invalid encrypted credentials")
  320. }
  321. if c.AutomaticCredentials == 0 && !c.Credentials.IsValidInput() {
  322. fi, err := os.Stat(credentialsFilePath)
  323. if err != nil {
  324. return fmt.Errorf("invalid credentials %v", err)
  325. }
  326. if fi.Size() == 0 {
  327. return errors.New("credentials cannot be empty")
  328. }
  329. }
  330. c.StorageClass = strings.TrimSpace(c.StorageClass)
  331. c.ACL = strings.TrimSpace(c.ACL)
  332. return nil
  333. }
  334. // AzBlobFsConfig defines the configuration for Azure Blob Storage based filesystem
  335. type AzBlobFsConfig struct {
  336. sdk.BaseAzBlobFsConfig
  337. // Storage Account Key leave blank to use SAS URL.
  338. // The access key is stored encrypted based on the kms configuration
  339. AccountKey *kms.Secret `json:"account_key,omitempty"`
  340. // Shared access signature URL, leave blank if using account/key
  341. SASURL *kms.Secret `json:"sas_url,omitempty"`
  342. }
  343. // HideConfidentialData hides confidential data
  344. func (c *AzBlobFsConfig) HideConfidentialData() {
  345. if c.AccountKey != nil {
  346. c.AccountKey.Hide()
  347. }
  348. if c.SASURL != nil {
  349. c.SASURL.Hide()
  350. }
  351. }
  352. func (c *AzBlobFsConfig) isEqual(other *AzBlobFsConfig) bool {
  353. if c.Container != other.Container {
  354. return false
  355. }
  356. if c.AccountName != other.AccountName {
  357. return false
  358. }
  359. if c.Endpoint != other.Endpoint {
  360. return false
  361. }
  362. if c.SASURL.IsEmpty() {
  363. c.SASURL = kms.NewEmptySecret()
  364. }
  365. if other.SASURL.IsEmpty() {
  366. other.SASURL = kms.NewEmptySecret()
  367. }
  368. if !c.SASURL.IsEqual(other.SASURL) {
  369. return false
  370. }
  371. if c.KeyPrefix != other.KeyPrefix {
  372. return false
  373. }
  374. if c.UploadPartSize != other.UploadPartSize {
  375. return false
  376. }
  377. if c.UploadConcurrency != other.UploadConcurrency {
  378. return false
  379. }
  380. if c.DownloadPartSize != other.DownloadPartSize {
  381. return false
  382. }
  383. if c.DownloadConcurrency != other.DownloadConcurrency {
  384. return false
  385. }
  386. if c.UseEmulator != other.UseEmulator {
  387. return false
  388. }
  389. if c.AccessTier != other.AccessTier {
  390. return false
  391. }
  392. return c.isSecretEqual(other)
  393. }
  394. func (c *AzBlobFsConfig) isSecretEqual(other *AzBlobFsConfig) bool {
  395. if c.AccountKey == nil {
  396. c.AccountKey = kms.NewEmptySecret()
  397. }
  398. if other.AccountKey == nil {
  399. other.AccountKey = kms.NewEmptySecret()
  400. }
  401. return c.AccountKey.IsEqual(other.AccountKey)
  402. }
  403. // EncryptCredentials encrypts access secret if it is in plain text
  404. func (c *AzBlobFsConfig) EncryptCredentials(additionalData string) error {
  405. if c.AccountKey.IsPlain() {
  406. c.AccountKey.SetAdditionalData(additionalData)
  407. if err := c.AccountKey.Encrypt(); err != nil {
  408. return err
  409. }
  410. }
  411. if c.SASURL.IsPlain() {
  412. c.SASURL.SetAdditionalData(additionalData)
  413. if err := c.SASURL.Encrypt(); err != nil {
  414. return err
  415. }
  416. }
  417. return nil
  418. }
  419. func (c *AzBlobFsConfig) checkCredentials() error {
  420. if c.SASURL.IsPlain() {
  421. _, err := url.Parse(c.SASURL.GetPayload())
  422. return err
  423. }
  424. if c.SASURL.IsEncrypted() && !c.SASURL.IsValid() {
  425. return errors.New("invalid encrypted sas_url")
  426. }
  427. if !c.SASURL.IsEmpty() {
  428. return nil
  429. }
  430. if c.AccountName == "" || !c.AccountKey.IsValidInput() {
  431. return errors.New("credentials cannot be empty or invalid")
  432. }
  433. if c.AccountKey.IsEncrypted() && !c.AccountKey.IsValid() {
  434. return errors.New("invalid encrypted account_key")
  435. }
  436. return nil
  437. }
  438. func (c *AzBlobFsConfig) checkPartSizeAndConcurrency() error {
  439. if c.UploadPartSize < 0 || c.UploadPartSize > 100 {
  440. return fmt.Errorf("invalid upload part size: %v", c.UploadPartSize)
  441. }
  442. if c.UploadConcurrency < 0 || c.UploadConcurrency > 64 {
  443. return fmt.Errorf("invalid upload concurrency: %v", c.UploadConcurrency)
  444. }
  445. if c.DownloadPartSize < 0 || c.DownloadPartSize > 100 {
  446. return fmt.Errorf("invalid download part size: %v", c.DownloadPartSize)
  447. }
  448. if c.DownloadConcurrency < 0 || c.DownloadConcurrency > 64 {
  449. return fmt.Errorf("invalid upload concurrency: %v", c.DownloadConcurrency)
  450. }
  451. return nil
  452. }
  453. // Validate returns an error if the configuration is not valid
  454. func (c *AzBlobFsConfig) Validate() error {
  455. if c.AccountKey == nil {
  456. c.AccountKey = kms.NewEmptySecret()
  457. }
  458. if c.SASURL == nil {
  459. c.SASURL = kms.NewEmptySecret()
  460. }
  461. // container could be embedded within SAS URL we check this at runtime
  462. if c.SASURL.IsEmpty() && c.Container == "" {
  463. return errors.New("container cannot be empty")
  464. }
  465. if err := c.checkCredentials(); err != nil {
  466. return err
  467. }
  468. if c.KeyPrefix != "" {
  469. if strings.HasPrefix(c.KeyPrefix, "/") {
  470. return errors.New("key_prefix cannot start with /")
  471. }
  472. c.KeyPrefix = path.Clean(c.KeyPrefix)
  473. if !strings.HasSuffix(c.KeyPrefix, "/") {
  474. c.KeyPrefix += "/"
  475. }
  476. }
  477. if err := c.checkPartSizeAndConcurrency(); err != nil {
  478. return err
  479. }
  480. if !util.IsStringInSlice(c.AccessTier, validAzAccessTier) {
  481. return fmt.Errorf("invalid access tier %#v, valid values: \"''%v\"", c.AccessTier, strings.Join(validAzAccessTier, ", "))
  482. }
  483. return nil
  484. }
  485. // CryptFsConfig defines the configuration to store local files as encrypted
  486. type CryptFsConfig struct {
  487. Passphrase *kms.Secret `json:"passphrase,omitempty"`
  488. }
  489. // HideConfidentialData hides confidential data
  490. func (c *CryptFsConfig) HideConfidentialData() {
  491. if c.Passphrase != nil {
  492. c.Passphrase.Hide()
  493. }
  494. }
  495. func (c *CryptFsConfig) isEqual(other *CryptFsConfig) bool {
  496. if c.Passphrase == nil {
  497. c.Passphrase = kms.NewEmptySecret()
  498. }
  499. if other.Passphrase == nil {
  500. other.Passphrase = kms.NewEmptySecret()
  501. }
  502. return c.Passphrase.IsEqual(other.Passphrase)
  503. }
  504. // EncryptCredentials encrypts access secret if it is in plain text
  505. func (c *CryptFsConfig) EncryptCredentials(additionalData string) error {
  506. if c.Passphrase.IsPlain() {
  507. c.Passphrase.SetAdditionalData(additionalData)
  508. if err := c.Passphrase.Encrypt(); err != nil {
  509. return err
  510. }
  511. }
  512. return nil
  513. }
  514. // Validate returns an error if the configuration is not valid
  515. func (c *CryptFsConfig) Validate() error {
  516. if c.Passphrase == nil || c.Passphrase.IsEmpty() {
  517. return errors.New("invalid passphrase")
  518. }
  519. if !c.Passphrase.IsValidInput() {
  520. return errors.New("passphrase cannot be empty or invalid")
  521. }
  522. if c.Passphrase.IsEncrypted() && !c.Passphrase.IsValid() {
  523. return errors.New("invalid encrypted passphrase")
  524. }
  525. return nil
  526. }
  527. // PipeWriter defines a wrapper for pipeat.PipeWriterAt.
  528. type PipeWriter struct {
  529. writer *pipeat.PipeWriterAt
  530. err error
  531. done chan bool
  532. }
  533. // NewPipeWriter initializes a new PipeWriter
  534. func NewPipeWriter(w *pipeat.PipeWriterAt) *PipeWriter {
  535. return &PipeWriter{
  536. writer: w,
  537. err: nil,
  538. done: make(chan bool),
  539. }
  540. }
  541. // Close waits for the upload to end, closes the pipeat.PipeWriterAt and returns an error if any.
  542. func (p *PipeWriter) Close() error {
  543. p.writer.Close() //nolint:errcheck // the returned error is always null
  544. <-p.done
  545. return p.err
  546. }
  547. // Done unlocks other goroutines waiting on Close().
  548. // It must be called when the upload ends
  549. func (p *PipeWriter) Done(err error) {
  550. p.err = err
  551. p.done <- true
  552. }
  553. // WriteAt is a wrapper for pipeat WriteAt
  554. func (p *PipeWriter) WriteAt(data []byte, off int64) (int, error) {
  555. return p.writer.WriteAt(data, off)
  556. }
  557. // Write is a wrapper for pipeat Write
  558. func (p *PipeWriter) Write(data []byte) (int, error) {
  559. return p.writer.Write(data)
  560. }
  561. // IsDirectory checks if a path exists and is a directory
  562. func IsDirectory(fs Fs, path string) (bool, error) {
  563. fileInfo, err := fs.Stat(path)
  564. if err != nil {
  565. return false, err
  566. }
  567. return fileInfo.IsDir(), err
  568. }
  569. // IsLocalOsFs returns true if fs is a local filesystem implementation
  570. func IsLocalOsFs(fs Fs) bool {
  571. return fs.Name() == osFsName
  572. }
  573. // IsCryptOsFs returns true if fs is an encrypted local filesystem implementation
  574. func IsCryptOsFs(fs Fs) bool {
  575. return fs.Name() == cryptFsName
  576. }
  577. // IsSFTPFs returns true if fs is an SFTP filesystem
  578. func IsSFTPFs(fs Fs) bool {
  579. return strings.HasPrefix(fs.Name(), sftpFsName)
  580. }
  581. // IsBufferedSFTPFs returns true if this is a buffered SFTP filesystem
  582. func IsBufferedSFTPFs(fs Fs) bool {
  583. if !IsSFTPFs(fs) {
  584. return false
  585. }
  586. return !fs.IsUploadResumeSupported()
  587. }
  588. // IsLocalOrUnbufferedSFTPFs returns true if fs is local or SFTP with no buffer
  589. func IsLocalOrUnbufferedSFTPFs(fs Fs) bool {
  590. if IsLocalOsFs(fs) {
  591. return true
  592. }
  593. if IsSFTPFs(fs) {
  594. return fs.IsUploadResumeSupported()
  595. }
  596. return false
  597. }
  598. // IsLocalOrSFTPFs returns true if fs is local or SFTP
  599. func IsLocalOrSFTPFs(fs Fs) bool {
  600. return IsLocalOsFs(fs) || IsSFTPFs(fs)
  601. }
  602. // HasOpenRWSupport returns true if the fs can open a file
  603. // for reading and writing at the same time
  604. func HasOpenRWSupport(fs Fs) bool {
  605. if IsLocalOsFs(fs) {
  606. return true
  607. }
  608. if IsSFTPFs(fs) && fs.IsUploadResumeSupported() {
  609. return true
  610. }
  611. return false
  612. }
  613. // IsLocalOrCryptoFs returns true if fs is local or local encrypted
  614. func IsLocalOrCryptoFs(fs Fs) bool {
  615. return IsLocalOsFs(fs) || IsCryptOsFs(fs)
  616. }
  617. // SetPathPermissions calls fs.Chown.
  618. // It does nothing for local filesystem on windows
  619. func SetPathPermissions(fs Fs, path string, uid int, gid int) {
  620. if uid == -1 && gid == -1 {
  621. return
  622. }
  623. if IsLocalOsFs(fs) {
  624. if runtime.GOOS == "windows" {
  625. return
  626. }
  627. }
  628. if err := fs.Chown(path, uid, gid); err != nil {
  629. fsLog(fs, logger.LevelWarn, "error chowning path %v: %v", path, err)
  630. }
  631. }
  632. func updateFileInfoModTime(storageID, objectPath string, info *FileInfo) (*FileInfo, error) {
  633. if !plugin.Handler.HasMetadater() {
  634. return info, nil
  635. }
  636. if info.IsDir() {
  637. return info, nil
  638. }
  639. mTime, err := plugin.Handler.GetModificationTime(storageID, ensureAbsPath(objectPath), info.IsDir())
  640. if errors.Is(err, metadata.ErrNoSuchObject) {
  641. return info, nil
  642. }
  643. if err != nil {
  644. return info, err
  645. }
  646. info.modTime = util.GetTimeFromMsecSinceEpoch(mTime)
  647. return info, nil
  648. }
  649. func getFolderModTimes(storageID, dirName string) (map[string]int64, error) {
  650. var err error
  651. modTimes := make(map[string]int64)
  652. if plugin.Handler.HasMetadater() {
  653. modTimes, err = plugin.Handler.GetModificationTimes(storageID, ensureAbsPath(dirName))
  654. if err != nil && !errors.Is(err, metadata.ErrNoSuchObject) {
  655. return modTimes, err
  656. }
  657. }
  658. return modTimes, nil
  659. }
  660. func ensureAbsPath(name string) string {
  661. if path.IsAbs(name) {
  662. return name
  663. }
  664. return path.Join("/", name)
  665. }
  666. func fsMetadataCheck(fs fsMetadataChecker, storageID, keyPrefix string) error {
  667. if !plugin.Handler.HasMetadater() {
  668. return nil
  669. }
  670. limit := 100
  671. from := ""
  672. for {
  673. metadataFolders, err := plugin.Handler.GetMetadataFolders(storageID, from, limit)
  674. if err != nil {
  675. fsLog(fs, logger.LevelError, "unable to get folders: %v", err)
  676. return err
  677. }
  678. for _, folder := range metadataFolders {
  679. from = folder
  680. fsPrefix := folder
  681. if !strings.HasSuffix(folder, "/") {
  682. fsPrefix += "/"
  683. }
  684. if keyPrefix != "" {
  685. if !strings.HasPrefix(fsPrefix, "/"+keyPrefix) {
  686. fsLog(fs, logger.LevelDebug, "skip metadata check for folder %#v outside prefix %#v",
  687. folder, keyPrefix)
  688. continue
  689. }
  690. }
  691. fsLog(fs, logger.LevelDebug, "check metadata for folder %#v", folder)
  692. metadataValues, err := plugin.Handler.GetModificationTimes(storageID, folder)
  693. if err != nil {
  694. fsLog(fs, logger.LevelError, "unable to get modification times for folder %#v: %v", folder, err)
  695. return err
  696. }
  697. if len(metadataValues) == 0 {
  698. fsLog(fs, logger.LevelDebug, "no metadata for folder %#v", folder)
  699. continue
  700. }
  701. fileNames, err := fs.getFileNamesInPrefix(fsPrefix)
  702. if err != nil {
  703. fsLog(fs, logger.LevelError, "unable to get content for prefix %#v: %v", fsPrefix, err)
  704. return err
  705. }
  706. // now check if we have metadata for a missing object
  707. for k := range metadataValues {
  708. if _, ok := fileNames[k]; !ok {
  709. filePath := ensureAbsPath(path.Join(folder, k))
  710. if err = plugin.Handler.RemoveMetadata(storageID, filePath); err != nil {
  711. fsLog(fs, logger.LevelError, "unable to remove metadata for missing file %#v: %v", filePath, err)
  712. } else {
  713. fsLog(fs, logger.LevelDebug, "metadata removed for missing file %#v", filePath)
  714. }
  715. }
  716. }
  717. }
  718. if len(metadataFolders) < limit {
  719. return nil
  720. }
  721. }
  722. }
  723. func fsLog(fs Fs, level logger.LogLevel, format string, v ...interface{}) {
  724. logger.Log(level, fs.Name(), fs.ConnectionID(), format, v...)
  725. }