1
0

vfs.go 25 KB

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