vfs.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  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(virtualPath 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.RoleARN != other.RoleARN {
  158. return false
  159. }
  160. if c.Endpoint != other.Endpoint {
  161. return false
  162. }
  163. if c.StorageClass != other.StorageClass {
  164. return false
  165. }
  166. if c.ACL != other.ACL {
  167. return false
  168. }
  169. if !c.areMultipartFieldsEqual(other) {
  170. return false
  171. }
  172. if c.ForcePathStyle != other.ForcePathStyle {
  173. return false
  174. }
  175. return c.isSecretEqual(other)
  176. }
  177. func (c *S3FsConfig) areMultipartFieldsEqual(other *S3FsConfig) bool {
  178. if c.UploadPartSize != other.UploadPartSize {
  179. return false
  180. }
  181. if c.UploadConcurrency != other.UploadConcurrency {
  182. return false
  183. }
  184. if c.DownloadConcurrency != other.DownloadConcurrency {
  185. return false
  186. }
  187. if c.DownloadPartSize != other.DownloadPartSize {
  188. return false
  189. }
  190. if c.DownloadPartMaxTime != other.DownloadPartMaxTime {
  191. return false
  192. }
  193. if c.UploadPartMaxTime != other.UploadPartMaxTime {
  194. return false
  195. }
  196. return true
  197. }
  198. func (c *S3FsConfig) isSecretEqual(other *S3FsConfig) bool {
  199. if c.AccessSecret == nil {
  200. c.AccessSecret = kms.NewEmptySecret()
  201. }
  202. if other.AccessSecret == nil {
  203. other.AccessSecret = kms.NewEmptySecret()
  204. }
  205. return c.AccessSecret.IsEqual(other.AccessSecret)
  206. }
  207. func (c *S3FsConfig) checkCredentials() error {
  208. if c.AccessKey == "" && !c.AccessSecret.IsEmpty() {
  209. return errors.New("access_key cannot be empty with access_secret not empty")
  210. }
  211. if c.AccessSecret.IsEmpty() && c.AccessKey != "" {
  212. return errors.New("access_secret cannot be empty with access_key not empty")
  213. }
  214. if c.AccessSecret.IsEncrypted() && !c.AccessSecret.IsValid() {
  215. return errors.New("invalid encrypted access_secret")
  216. }
  217. if !c.AccessSecret.IsEmpty() && !c.AccessSecret.IsValidInput() {
  218. return errors.New("invalid access_secret")
  219. }
  220. return nil
  221. }
  222. // ValidateAndEncryptCredentials validates the configuration and encrypts access secret if it is in plain text
  223. func (c *S3FsConfig) ValidateAndEncryptCredentials(additionalData string) error {
  224. if err := c.validate(); err != nil {
  225. return util.NewValidationError(fmt.Sprintf("could not validate s3config: %v", err))
  226. }
  227. if c.AccessSecret.IsPlain() {
  228. c.AccessSecret.SetAdditionalData(additionalData)
  229. err := c.AccessSecret.Encrypt()
  230. if err != nil {
  231. return util.NewValidationError(fmt.Sprintf("could not encrypt s3 access secret: %v", err))
  232. }
  233. }
  234. return nil
  235. }
  236. func (c *S3FsConfig) checkPartSizeAndConcurrency() error {
  237. if c.UploadPartSize != 0 && (c.UploadPartSize < 5 || c.UploadPartSize > 5000) {
  238. return errors.New("upload_part_size cannot be != 0, lower than 5 (MB) or greater than 5000 (MB)")
  239. }
  240. if c.UploadConcurrency < 0 || c.UploadConcurrency > 64 {
  241. return fmt.Errorf("invalid upload concurrency: %v", c.UploadConcurrency)
  242. }
  243. if c.DownloadPartSize != 0 && (c.DownloadPartSize < 5 || c.DownloadPartSize > 5000) {
  244. return errors.New("download_part_size cannot be != 0, lower than 5 (MB) or greater than 5000 (MB)")
  245. }
  246. if c.DownloadConcurrency < 0 || c.DownloadConcurrency > 64 {
  247. return fmt.Errorf("invalid download concurrency: %v", c.DownloadConcurrency)
  248. }
  249. return nil
  250. }
  251. // validate returns an error if the configuration is not valid
  252. func (c *S3FsConfig) validate() error {
  253. if c.AccessSecret == nil {
  254. c.AccessSecret = kms.NewEmptySecret()
  255. }
  256. if c.Bucket == "" {
  257. return errors.New("bucket cannot be empty")
  258. }
  259. if c.Region == "" {
  260. return errors.New("region cannot be empty")
  261. }
  262. if err := c.checkCredentials(); err != nil {
  263. return err
  264. }
  265. if c.KeyPrefix != "" {
  266. if strings.HasPrefix(c.KeyPrefix, "/") {
  267. return errors.New("key_prefix cannot start with /")
  268. }
  269. c.KeyPrefix = path.Clean(c.KeyPrefix)
  270. if !strings.HasSuffix(c.KeyPrefix, "/") {
  271. c.KeyPrefix += "/"
  272. }
  273. }
  274. c.StorageClass = strings.TrimSpace(c.StorageClass)
  275. c.ACL = strings.TrimSpace(c.ACL)
  276. return c.checkPartSizeAndConcurrency()
  277. }
  278. // GCSFsConfig defines the configuration for Google Cloud Storage based filesystem
  279. type GCSFsConfig struct {
  280. sdk.BaseGCSFsConfig
  281. Credentials *kms.Secret `json:"credentials,omitempty"`
  282. }
  283. // HideConfidentialData hides confidential data
  284. func (c *GCSFsConfig) HideConfidentialData() {
  285. if c.Credentials != nil {
  286. c.Credentials.Hide()
  287. }
  288. }
  289. // ValidateAndEncryptCredentials validates the configuration and encrypts credentials if they are in plain text
  290. func (c *GCSFsConfig) ValidateAndEncryptCredentials(additionalData string) error {
  291. if err := c.validate(); err != nil {
  292. return util.NewValidationError(fmt.Sprintf("could not validate GCS config: %v", err))
  293. }
  294. if c.Credentials.IsPlain() {
  295. c.Credentials.SetAdditionalData(additionalData)
  296. err := c.Credentials.Encrypt()
  297. if err != nil {
  298. return util.NewValidationError(fmt.Sprintf("could not encrypt GCS credentials: %v", err))
  299. }
  300. }
  301. return nil
  302. }
  303. func (c *GCSFsConfig) isEqual(other *GCSFsConfig) bool {
  304. if c.Bucket != other.Bucket {
  305. return false
  306. }
  307. if c.KeyPrefix != other.KeyPrefix {
  308. return false
  309. }
  310. if c.AutomaticCredentials != other.AutomaticCredentials {
  311. return false
  312. }
  313. if c.StorageClass != other.StorageClass {
  314. return false
  315. }
  316. if c.ACL != other.ACL {
  317. return false
  318. }
  319. if c.Credentials == nil {
  320. c.Credentials = kms.NewEmptySecret()
  321. }
  322. if other.Credentials == nil {
  323. other.Credentials = kms.NewEmptySecret()
  324. }
  325. return c.Credentials.IsEqual(other.Credentials)
  326. }
  327. // validate returns an error if the configuration is not valid
  328. func (c *GCSFsConfig) validate() error {
  329. if c.Credentials == nil || c.AutomaticCredentials == 1 {
  330. c.Credentials = kms.NewEmptySecret()
  331. }
  332. if c.Bucket == "" {
  333. return errors.New("bucket cannot be empty")
  334. }
  335. if c.KeyPrefix != "" {
  336. if strings.HasPrefix(c.KeyPrefix, "/") {
  337. return errors.New("key_prefix cannot start with /")
  338. }
  339. c.KeyPrefix = path.Clean(c.KeyPrefix)
  340. if !strings.HasSuffix(c.KeyPrefix, "/") {
  341. c.KeyPrefix += "/"
  342. }
  343. }
  344. if c.Credentials.IsEncrypted() && !c.Credentials.IsValid() {
  345. return errors.New("invalid encrypted credentials")
  346. }
  347. if c.AutomaticCredentials == 0 && !c.Credentials.IsValidInput() {
  348. return errors.New("invalid credentials")
  349. }
  350. c.StorageClass = strings.TrimSpace(c.StorageClass)
  351. c.ACL = strings.TrimSpace(c.ACL)
  352. return nil
  353. }
  354. // AzBlobFsConfig defines the configuration for Azure Blob Storage based filesystem
  355. type AzBlobFsConfig struct {
  356. sdk.BaseAzBlobFsConfig
  357. // Storage Account Key leave blank to use SAS URL.
  358. // The access key is stored encrypted based on the kms configuration
  359. AccountKey *kms.Secret `json:"account_key,omitempty"`
  360. // Shared access signature URL, leave blank if using account/key
  361. SASURL *kms.Secret `json:"sas_url,omitempty"`
  362. }
  363. // HideConfidentialData hides confidential data
  364. func (c *AzBlobFsConfig) HideConfidentialData() {
  365. if c.AccountKey != nil {
  366. c.AccountKey.Hide()
  367. }
  368. if c.SASURL != nil {
  369. c.SASURL.Hide()
  370. }
  371. }
  372. func (c *AzBlobFsConfig) isEqual(other *AzBlobFsConfig) bool {
  373. if c.Container != other.Container {
  374. return false
  375. }
  376. if c.AccountName != other.AccountName {
  377. return false
  378. }
  379. if c.Endpoint != other.Endpoint {
  380. return false
  381. }
  382. if c.SASURL.IsEmpty() {
  383. c.SASURL = kms.NewEmptySecret()
  384. }
  385. if other.SASURL.IsEmpty() {
  386. other.SASURL = kms.NewEmptySecret()
  387. }
  388. if !c.SASURL.IsEqual(other.SASURL) {
  389. return false
  390. }
  391. if c.KeyPrefix != other.KeyPrefix {
  392. return false
  393. }
  394. if c.UploadPartSize != other.UploadPartSize {
  395. return false
  396. }
  397. if c.UploadConcurrency != other.UploadConcurrency {
  398. return false
  399. }
  400. if c.DownloadPartSize != other.DownloadPartSize {
  401. return false
  402. }
  403. if c.DownloadConcurrency != other.DownloadConcurrency {
  404. return false
  405. }
  406. if c.UseEmulator != other.UseEmulator {
  407. return false
  408. }
  409. if c.AccessTier != other.AccessTier {
  410. return false
  411. }
  412. return c.isSecretEqual(other)
  413. }
  414. func (c *AzBlobFsConfig) isSecretEqual(other *AzBlobFsConfig) bool {
  415. if c.AccountKey == nil {
  416. c.AccountKey = kms.NewEmptySecret()
  417. }
  418. if other.AccountKey == nil {
  419. other.AccountKey = kms.NewEmptySecret()
  420. }
  421. return c.AccountKey.IsEqual(other.AccountKey)
  422. }
  423. // ValidateAndEncryptCredentials validates the configuration and encrypts access secret if it is in plain text
  424. func (c *AzBlobFsConfig) ValidateAndEncryptCredentials(additionalData string) error {
  425. if err := c.validate(); err != nil {
  426. return util.NewValidationError(fmt.Sprintf("could not validate Azure Blob config: %v", err))
  427. }
  428. if c.AccountKey.IsPlain() {
  429. c.AccountKey.SetAdditionalData(additionalData)
  430. if err := c.AccountKey.Encrypt(); err != nil {
  431. return util.NewValidationError(fmt.Sprintf("could not encrypt Azure blob account key: %v", err))
  432. }
  433. }
  434. if c.SASURL.IsPlain() {
  435. c.SASURL.SetAdditionalData(additionalData)
  436. if err := c.SASURL.Encrypt(); err != nil {
  437. return util.NewValidationError(fmt.Sprintf("could not encrypt Azure blob SAS URL: %v", err))
  438. }
  439. }
  440. return nil
  441. }
  442. func (c *AzBlobFsConfig) checkCredentials() error {
  443. if c.SASURL.IsPlain() {
  444. _, err := url.Parse(c.SASURL.GetPayload())
  445. return err
  446. }
  447. if c.SASURL.IsEncrypted() && !c.SASURL.IsValid() {
  448. return errors.New("invalid encrypted sas_url")
  449. }
  450. if !c.SASURL.IsEmpty() {
  451. return nil
  452. }
  453. if c.AccountName == "" || !c.AccountKey.IsValidInput() {
  454. return errors.New("credentials cannot be empty or invalid")
  455. }
  456. if c.AccountKey.IsEncrypted() && !c.AccountKey.IsValid() {
  457. return errors.New("invalid encrypted account_key")
  458. }
  459. return nil
  460. }
  461. func (c *AzBlobFsConfig) checkPartSizeAndConcurrency() error {
  462. if c.UploadPartSize < 0 || c.UploadPartSize > 100 {
  463. return fmt.Errorf("invalid upload part size: %v", c.UploadPartSize)
  464. }
  465. if c.UploadConcurrency < 0 || c.UploadConcurrency > 64 {
  466. return fmt.Errorf("invalid upload concurrency: %v", c.UploadConcurrency)
  467. }
  468. if c.DownloadPartSize < 0 || c.DownloadPartSize > 100 {
  469. return fmt.Errorf("invalid download part size: %v", c.DownloadPartSize)
  470. }
  471. if c.DownloadConcurrency < 0 || c.DownloadConcurrency > 64 {
  472. return fmt.Errorf("invalid upload concurrency: %v", c.DownloadConcurrency)
  473. }
  474. return nil
  475. }
  476. func (c *AzBlobFsConfig) tryDecrypt() error {
  477. if err := c.AccountKey.TryDecrypt(); err != nil {
  478. return fmt.Errorf("unable to decrypt account key: %w", err)
  479. }
  480. if err := c.SASURL.TryDecrypt(); err != nil {
  481. return fmt.Errorf("unable to decrypt SAS URL: %w", err)
  482. }
  483. return nil
  484. }
  485. // validate returns an error if the configuration is not valid
  486. func (c *AzBlobFsConfig) validate() error {
  487. if c.AccountKey == nil {
  488. c.AccountKey = kms.NewEmptySecret()
  489. }
  490. if c.SASURL == nil {
  491. c.SASURL = kms.NewEmptySecret()
  492. }
  493. // container could be embedded within SAS URL we check this at runtime
  494. if c.SASURL.IsEmpty() && c.Container == "" {
  495. return errors.New("container cannot be empty")
  496. }
  497. if err := c.checkCredentials(); err != nil {
  498. return err
  499. }
  500. if c.KeyPrefix != "" {
  501. if strings.HasPrefix(c.KeyPrefix, "/") {
  502. return errors.New("key_prefix cannot start with /")
  503. }
  504. c.KeyPrefix = path.Clean(c.KeyPrefix)
  505. if !strings.HasSuffix(c.KeyPrefix, "/") {
  506. c.KeyPrefix += "/"
  507. }
  508. }
  509. if err := c.checkPartSizeAndConcurrency(); err != nil {
  510. return err
  511. }
  512. if !util.Contains(validAzAccessTier, c.AccessTier) {
  513. return fmt.Errorf("invalid access tier %#v, valid values: \"''%v\"", c.AccessTier, strings.Join(validAzAccessTier, ", "))
  514. }
  515. return nil
  516. }
  517. // CryptFsConfig defines the configuration to store local files as encrypted
  518. type CryptFsConfig struct {
  519. Passphrase *kms.Secret `json:"passphrase,omitempty"`
  520. }
  521. // HideConfidentialData hides confidential data
  522. func (c *CryptFsConfig) HideConfidentialData() {
  523. if c.Passphrase != nil {
  524. c.Passphrase.Hide()
  525. }
  526. }
  527. func (c *CryptFsConfig) isEqual(other *CryptFsConfig) bool {
  528. if c.Passphrase == nil {
  529. c.Passphrase = kms.NewEmptySecret()
  530. }
  531. if other.Passphrase == nil {
  532. other.Passphrase = kms.NewEmptySecret()
  533. }
  534. return c.Passphrase.IsEqual(other.Passphrase)
  535. }
  536. // ValidateAndEncryptCredentials validates the configuration and encrypts the passphrase if it is in plain text
  537. func (c *CryptFsConfig) ValidateAndEncryptCredentials(additionalData string) error {
  538. if err := c.validate(); err != nil {
  539. return util.NewValidationError(fmt.Sprintf("could not validate Crypt fs config: %v", err))
  540. }
  541. if c.Passphrase.IsPlain() {
  542. c.Passphrase.SetAdditionalData(additionalData)
  543. if err := c.Passphrase.Encrypt(); err != nil {
  544. return util.NewValidationError(fmt.Sprintf("could not encrypt Crypt fs passphrase: %v", err))
  545. }
  546. }
  547. return nil
  548. }
  549. // validate returns an error if the configuration is not valid
  550. func (c *CryptFsConfig) validate() error {
  551. if c.Passphrase == nil || c.Passphrase.IsEmpty() {
  552. return errors.New("invalid passphrase")
  553. }
  554. if !c.Passphrase.IsValidInput() {
  555. return errors.New("passphrase cannot be empty or invalid")
  556. }
  557. if c.Passphrase.IsEncrypted() && !c.Passphrase.IsValid() {
  558. return errors.New("invalid encrypted passphrase")
  559. }
  560. return nil
  561. }
  562. // PipeWriter defines a wrapper for pipeat.PipeWriterAt.
  563. type PipeWriter struct {
  564. writer *pipeat.PipeWriterAt
  565. err error
  566. done chan bool
  567. }
  568. // NewPipeWriter initializes a new PipeWriter
  569. func NewPipeWriter(w *pipeat.PipeWriterAt) *PipeWriter {
  570. return &PipeWriter{
  571. writer: w,
  572. err: nil,
  573. done: make(chan bool),
  574. }
  575. }
  576. // Close waits for the upload to end, closes the pipeat.PipeWriterAt and returns an error if any.
  577. func (p *PipeWriter) Close() error {
  578. p.writer.Close() //nolint:errcheck // the returned error is always null
  579. <-p.done
  580. return p.err
  581. }
  582. // Done unlocks other goroutines waiting on Close().
  583. // It must be called when the upload ends
  584. func (p *PipeWriter) Done(err error) {
  585. p.err = err
  586. p.done <- true
  587. }
  588. // WriteAt is a wrapper for pipeat WriteAt
  589. func (p *PipeWriter) WriteAt(data []byte, off int64) (int, error) {
  590. return p.writer.WriteAt(data, off)
  591. }
  592. // Write is a wrapper for pipeat Write
  593. func (p *PipeWriter) Write(data []byte) (int, error) {
  594. return p.writer.Write(data)
  595. }
  596. // IsDirectory checks if a path exists and is a directory
  597. func IsDirectory(fs Fs, path string) (bool, error) {
  598. fileInfo, err := fs.Stat(path)
  599. if err != nil {
  600. return false, err
  601. }
  602. return fileInfo.IsDir(), err
  603. }
  604. // IsLocalOsFs returns true if fs is a local filesystem implementation
  605. func IsLocalOsFs(fs Fs) bool {
  606. return fs.Name() == osFsName
  607. }
  608. // IsCryptOsFs returns true if fs is an encrypted local filesystem implementation
  609. func IsCryptOsFs(fs Fs) bool {
  610. return fs.Name() == cryptFsName
  611. }
  612. // IsSFTPFs returns true if fs is an SFTP filesystem
  613. func IsSFTPFs(fs Fs) bool {
  614. return strings.HasPrefix(fs.Name(), sftpFsName)
  615. }
  616. // IsBufferedSFTPFs returns true if this is a buffered SFTP filesystem
  617. func IsBufferedSFTPFs(fs Fs) bool {
  618. if !IsSFTPFs(fs) {
  619. return false
  620. }
  621. return !fs.IsUploadResumeSupported()
  622. }
  623. // IsLocalOrUnbufferedSFTPFs returns true if fs is local or SFTP with no buffer
  624. func IsLocalOrUnbufferedSFTPFs(fs Fs) bool {
  625. if IsLocalOsFs(fs) {
  626. return true
  627. }
  628. if IsSFTPFs(fs) {
  629. return fs.IsUploadResumeSupported()
  630. }
  631. return false
  632. }
  633. // IsLocalOrSFTPFs returns true if fs is local or SFTP
  634. func IsLocalOrSFTPFs(fs Fs) bool {
  635. return IsLocalOsFs(fs) || IsSFTPFs(fs)
  636. }
  637. // HasOpenRWSupport returns true if the fs can open a file
  638. // for reading and writing at the same time
  639. func HasOpenRWSupport(fs Fs) bool {
  640. if IsLocalOsFs(fs) {
  641. return true
  642. }
  643. if IsSFTPFs(fs) && fs.IsUploadResumeSupported() {
  644. return true
  645. }
  646. return false
  647. }
  648. // IsLocalOrCryptoFs returns true if fs is local or local encrypted
  649. func IsLocalOrCryptoFs(fs Fs) bool {
  650. return IsLocalOsFs(fs) || IsCryptOsFs(fs)
  651. }
  652. // SetPathPermissions calls fs.Chown.
  653. // It does nothing for local filesystem on windows
  654. func SetPathPermissions(fs Fs, path string, uid int, gid int) {
  655. if uid == -1 && gid == -1 {
  656. return
  657. }
  658. if IsLocalOsFs(fs) {
  659. if runtime.GOOS == "windows" {
  660. return
  661. }
  662. }
  663. if err := fs.Chown(path, uid, gid); err != nil {
  664. fsLog(fs, logger.LevelWarn, "error chowning path %v: %v", path, err)
  665. }
  666. }
  667. func updateFileInfoModTime(storageID, objectPath string, info *FileInfo) (*FileInfo, error) {
  668. if !plugin.Handler.HasMetadater() {
  669. return info, nil
  670. }
  671. if info.IsDir() {
  672. return info, nil
  673. }
  674. mTime, err := plugin.Handler.GetModificationTime(storageID, ensureAbsPath(objectPath), info.IsDir())
  675. if errors.Is(err, metadata.ErrNoSuchObject) {
  676. return info, nil
  677. }
  678. if err != nil {
  679. return info, err
  680. }
  681. info.modTime = util.GetTimeFromMsecSinceEpoch(mTime)
  682. return info, nil
  683. }
  684. func getFolderModTimes(storageID, dirName string) (map[string]int64, error) {
  685. var err error
  686. modTimes := make(map[string]int64)
  687. if plugin.Handler.HasMetadater() {
  688. modTimes, err = plugin.Handler.GetModificationTimes(storageID, ensureAbsPath(dirName))
  689. if err != nil && !errors.Is(err, metadata.ErrNoSuchObject) {
  690. return modTimes, err
  691. }
  692. }
  693. return modTimes, nil
  694. }
  695. func ensureAbsPath(name string) string {
  696. if path.IsAbs(name) {
  697. return name
  698. }
  699. return path.Join("/", name)
  700. }
  701. func fsMetadataCheck(fs fsMetadataChecker, storageID, keyPrefix string) error {
  702. if !plugin.Handler.HasMetadater() {
  703. return nil
  704. }
  705. limit := 100
  706. from := ""
  707. for {
  708. metadataFolders, err := plugin.Handler.GetMetadataFolders(storageID, from, limit)
  709. if err != nil {
  710. fsLog(fs, logger.LevelError, "unable to get folders: %v", err)
  711. return err
  712. }
  713. for _, folder := range metadataFolders {
  714. from = folder
  715. fsPrefix := folder
  716. if !strings.HasSuffix(folder, "/") {
  717. fsPrefix += "/"
  718. }
  719. if keyPrefix != "" {
  720. if !strings.HasPrefix(fsPrefix, "/"+keyPrefix) {
  721. fsLog(fs, logger.LevelDebug, "skip metadata check for folder %#v outside prefix %#v",
  722. folder, keyPrefix)
  723. continue
  724. }
  725. }
  726. fsLog(fs, logger.LevelDebug, "check metadata for folder %#v", folder)
  727. metadataValues, err := plugin.Handler.GetModificationTimes(storageID, folder)
  728. if err != nil {
  729. fsLog(fs, logger.LevelError, "unable to get modification times for folder %#v: %v", folder, err)
  730. return err
  731. }
  732. if len(metadataValues) == 0 {
  733. fsLog(fs, logger.LevelDebug, "no metadata for folder %#v", folder)
  734. continue
  735. }
  736. fileNames, err := fs.getFileNamesInPrefix(fsPrefix)
  737. if err != nil {
  738. fsLog(fs, logger.LevelError, "unable to get content for prefix %#v: %v", fsPrefix, err)
  739. return err
  740. }
  741. // now check if we have metadata for a missing object
  742. for k := range metadataValues {
  743. if _, ok := fileNames[k]; !ok {
  744. filePath := ensureAbsPath(path.Join(folder, k))
  745. if err = plugin.Handler.RemoveMetadata(storageID, filePath); err != nil {
  746. fsLog(fs, logger.LevelError, "unable to remove metadata for missing file %#v: %v", filePath, err)
  747. } else {
  748. fsLog(fs, logger.LevelDebug, "metadata removed for missing file %#v", filePath)
  749. }
  750. }
  751. }
  752. }
  753. if len(metadataFolders) < limit {
  754. return nil
  755. }
  756. }
  757. }
  758. func getMountPath(mountPath string) string {
  759. if mountPath == "/" {
  760. return ""
  761. }
  762. return mountPath
  763. }
  764. func fsLog(fs Fs, level logger.LogLevel, format string, v ...any) {
  765. logger.Log(level, fs.Name(), fs.ConnectionID(), format, v...)
  766. }