2022-09-20 08:54:47 +02:00
// Package s3 provides an interface to Amazon S3 object storage
2013-06-27 20:13:07 +01:00
package s3
2013-01-08 18:53:35 +00:00
2022-07-28 11:49:19 +01:00
//go:generate go run gen_setfrom.go -o setfrom.go
2013-01-08 18:53:35 +00:00
import (
2019-06-17 10:34:30 +02:00
"context"
2019-12-30 23:17:06 +00:00
"crypto/md5"
2020-10-13 21:41:22 +05:30
"crypto/tls"
2018-01-06 09:30:10 -05:00
"encoding/base64"
"encoding/hex"
2022-06-18 15:29:21 +08:00
"encoding/json"
2019-09-16 20:25:55 +01:00
"encoding/xml"
2021-11-04 10:12:57 +00:00
"errors"
2013-01-08 18:53:35 +00:00
"fmt"
"io"
2023-11-14 12:39:50 +00:00
"math"
2016-02-01 14:11:27 +01:00
"net/http"
2019-07-23 12:24:10 +01:00
"net/url"
2013-01-08 18:53:35 +00:00
"path"
"regexp"
build: modernize Go usage
This commit modernizes Go usage. This was done with:
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
Then files needed to be `go fmt`ed and a few comments needed to be
restored.
The modernizations include replacing
- if/else conditional assignment by a call to the built-in min or max functions added in go1.21
- sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] } by a call to slices.Sort(s), added in go1.21
- interface{} by the 'any' type added in go1.18
- append([]T(nil), s...) by slices.Clone(s) or slices.Concat(s), added in go1.21
- loop around an m[k]=v map update by a call to one of the Collect, Copy, Clone, or Insert functions from the maps package, added in go1.21
- []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...), added in go1.19
- append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1), added in go1.21
- a 3-clause for i := 0; i < n; i++ {} loop by for i := range n {}, added in go1.22
2025-02-26 21:08:12 +00:00
"slices"
2019-12-30 23:17:06 +00:00
"sort"
2019-10-04 23:49:06 +08:00
"strconv"
2013-01-08 18:53:35 +00:00
"strings"
2019-11-06 10:41:03 +00:00
"sync"
2013-01-08 18:53:35 +00:00
"time"
2024-08-03 11:35:32 +01:00
"github.com/aws/aws-sdk-go-v2/aws"
v4signer "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/smithy-go"
2024-08-06 10:33:17 +01:00
"github.com/aws/smithy-go/logging"
2024-08-03 11:35:32 +01:00
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
2021-01-22 17:23:51 +00:00
"github.com/ncw/swift/v2"
2025-02-03 05:29:31 -06:00
2025-11-12 23:15:13 +08:00
"golang.org/x/net/http/httpguts"
"golang.org/x/sync/errgroup"
2019-07-28 18:47:38 +01:00
"github.com/rclone/rclone/fs"
2022-07-26 15:03:32 +01:00
"github.com/rclone/rclone/fs/accounting"
2022-05-06 15:25:44 -04:00
"github.com/rclone/rclone/fs/chunksize"
2020-01-14 17:33:35 +00:00
"github.com/rclone/rclone/fs/config"
2019-07-28 18:47:38 +01:00
"github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash"
2024-12-09 15:08:36 +00:00
"github.com/rclone/rclone/fs/list"
2020-06-24 11:02:34 +01:00
"github.com/rclone/rclone/fs/operations"
2020-06-04 11:09:27 +01:00
"github.com/rclone/rclone/lib/atexit"
2019-08-09 11:29:36 +01:00
"github.com/rclone/rclone/lib/bucket"
2020-01-14 17:33:35 +00:00
"github.com/rclone/rclone/lib/encoder"
2023-08-15 20:38:02 +01:00
"github.com/rclone/rclone/lib/multipart"
2019-07-28 18:47:38 +01:00
"github.com/rclone/rclone/lib/pacer"
2023-08-24 17:15:18 +01:00
"github.com/rclone/rclone/lib/pool"
2019-12-30 23:17:06 +00:00
"github.com/rclone/rclone/lib/readers"
2019-07-28 18:47:38 +01:00
"github.com/rclone/rclone/lib/rest"
2022-07-25 16:06:15 +01:00
"github.com/rclone/rclone/lib/version"
2014-03-15 16:06:11 +00:00
)
2013-06-27 20:13:07 +01:00
2025-10-14 17:50:28 +01:00
// Register with Fs
func init ( ) {
fs . Register ( addProvidersToInfo ( & fs . RegInfo {
Name : "s3" ,
Description : "Amazon S3 Compliant Storage Providers including " ,
NewFs : NewFs ,
CommandHelp : commandHelp ,
Config : func ( ctx context . Context , name string , m configmap . Mapper , config fs . ConfigIn ) ( * fs . ConfigOut , error ) {
switch config . State {
case "" :
return nil , setEndpointValueForIDriveE2 ( m )
}
return nil , fmt . Errorf ( "unknown state %q" , config . State )
} ,
MetadataInfo : & fs . MetadataInfo {
System : systemMetadataInfo ,
Help : ` User metadata is stored as x-amz-meta- keys. S3 metadata keys are case insensitive and are always returned in lower case. ` ,
} ,
Options : [ ] fs . Option { {
Name : fs . ConfigProvider ,
Help : "Choose your S3 provider." ,
2022-03-24 19:57:00 +08:00
} , {
2025-10-14 17:50:28 +01:00
Name : "env_auth" ,
Help : "Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).\n\nOnly applies if access_key_id and secret_access_key is blank." ,
Default : false ,
2022-03-24 19:57:00 +08:00
Examples : [ ] fs . OptionExample { {
2025-10-14 17:50:28 +01:00
Value : "false" ,
Help : "Enter AWS credentials in the next step." ,
2022-03-24 19:57:00 +08:00
} , {
2025-10-14 17:50:28 +01:00
Value : "true" ,
Help : "Get AWS credentials from the environment (env vars or IAM)." ,
2022-03-24 19:57:00 +08:00
} } ,
2018-04-13 16:08:00 +01:00
} , {
2025-10-14 17:50:28 +01:00
Name : "access_key_id" ,
Help : "AWS Access Key ID.\n\nLeave blank for anonymous access or runtime credentials." ,
Sensitive : true ,
2025-09-22 13:07:45 +01:00
} , {
2025-10-14 17:50:28 +01:00
Name : "secret_access_key" ,
Help : "AWS Secret Access Key (password).\n\nLeave blank for anonymous access or runtime credentials." ,
Sensitive : true ,
2025-10-10 14:29:22 +01:00
} , {
2025-10-14 17:50:28 +01:00
Name : "region" ,
Help : "Region to connect to.\n\nLeave blank if you are using an S3 clone and you don't have a region." ,
2021-11-09 22:46:58 +11:00
} , {
2025-10-14 17:50:28 +01:00
Name : "endpoint" ,
Help : "Endpoint for S3 API.\n\nRequired when using an S3 clone." ,
2018-04-13 16:08:00 +01:00
} , {
2025-10-14 17:50:28 +01:00
Name : "location_constraint" ,
Help : "Location constraint - must be set to match the Region.\n\nLeave blank if not sure. Used when creating buckets only." ,
2018-04-13 16:08:00 +01:00
} , {
Name : "acl" ,
2018-10-25 22:19:36 +01:00
Help : ` Canned ACL used when creating buckets and storing or copying objects .
2019-01-16 17:23:37 +00:00
This ACL is used for creating objects and if bucket_acl isn ' t set , for creating buckets too .
2018-10-25 22:19:36 +01:00
For more info visit https : //docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
2020-10-13 17:43:40 -04:00
Note that this ACL is applied when server - side copying objects as S3
2022-09-14 16:24:57 +01:00
doesn ' t copy the ACL from the source but rather writes a fresh one .
If the acl is an empty string then no X - Amz - Acl : header is added and
the default ( private ) will be used .
` ,
2019-01-16 17:23:37 +00:00
} , {
Name : "bucket_acl" ,
Help : ` Canned ACL used when creating buckets .
For more info visit https : //docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
Note that this ACL is applied when only when creating buckets . If it
2022-09-14 16:24:57 +01:00
isn ' t set then "acl" is used instead .
If the "acl" and "bucket_acl" are empty strings then no X - Amz - Acl :
header is added and the default ( private ) will be used .
` ,
2019-01-16 17:23:37 +00:00
Advanced : true ,
Examples : [ ] fs . OptionExample { {
Value : "private" ,
2021-08-16 11:30:01 +02:00
Help : "Owner gets FULL_CONTROL.\nNo one else has access rights (default)." ,
2019-01-16 17:23:37 +00:00
} , {
Value : "public-read" ,
2021-08-16 11:30:01 +02:00
Help : "Owner gets FULL_CONTROL.\nThe AllUsers group gets READ access." ,
2019-01-16 17:23:37 +00:00
} , {
Value : "public-read-write" ,
2021-08-16 11:30:01 +02:00
Help : "Owner gets FULL_CONTROL.\nThe AllUsers group gets READ and WRITE access.\nGranting this on a bucket is generally not recommended." ,
2019-01-16 17:23:37 +00:00
} , {
Value : "authenticated-read" ,
2021-08-16 11:30:01 +02:00
Help : "Owner gets FULL_CONTROL.\nThe AuthenticatedUsers group gets READ access." ,
2019-01-16 17:23:37 +00:00
} } ,
2020-12-03 10:30:06 +08:00
} , {
Name : "requester_pays" ,
Help : "Enables requester pays option when interacting with S3 bucket." ,
Default : false ,
Advanced : true ,
2018-04-13 16:08:00 +01:00
} , {
2025-10-14 17:50:28 +01:00
Name : "server_side_encryption" ,
Help : "The server-side encryption algorithm used when storing this object in S3." ,
2020-03-30 11:26:52 +01:00
} , {
Name : "sse_customer_algorithm" ,
Help : "If using SSE-C, the server-side encryption algorithm used when storing this object in S3." ,
Advanced : true ,
Examples : [ ] fs . OptionExample { {
Value : "" ,
Help : "None" ,
} , {
Value : "AES256" ,
Help : "AES256" ,
} } ,
2018-08-30 12:08:27 -04:00
} , {
2025-10-14 17:50:28 +01:00
Name : "sse_kms_key_id" ,
Help : "If using KMS ID you must provide the ARN of Key." ,
2018-08-30 12:08:27 -04:00
Examples : [ ] fs . OptionExample { {
Value : "" ,
Help : "None" ,
} , {
Value : "arn:aws:kms:us-east-1:*" ,
Help : "arn:aws:kms:*" ,
2018-04-13 16:08:00 +01:00
} } ,
2023-07-06 17:55:53 +01:00
Sensitive : true ,
2020-03-30 11:26:52 +01:00
} , {
2022-09-17 10:28:44 -06:00
Name : "sse_customer_key" ,
Help : ` To use SSE - C you may provide the secret encryption key used to encrypt / decrypt your data .
Alternatively you can provide -- sse - customer - key - base64 . ` ,
Advanced : true ,
Examples : [ ] fs . OptionExample { {
Value : "" ,
Help : "None" ,
} } ,
2023-07-06 17:55:53 +01:00
Sensitive : true ,
2022-09-17 10:28:44 -06:00
} , {
Name : "sse_customer_key_base64" ,
Help : ` If using SSE - C you must provide the secret encryption key encoded in base64 format to encrypt / decrypt your data .
Alternatively you can provide -- sse - customer - key . ` ,
2020-03-30 11:26:52 +01:00
Advanced : true ,
Examples : [ ] fs . OptionExample { {
Value : "" ,
Help : "None" ,
} } ,
2023-07-06 17:55:53 +01:00
Sensitive : true ,
2020-03-30 11:26:52 +01:00
} , {
2020-11-20 11:15:48 +00:00
Name : "sse_customer_key_md5" ,
Help : ` If using SSE - C you may provide the secret encryption key MD5 checksum ( optional ) .
If you leave it blank , this is calculated automatically from the sse_customer_key provided .
` ,
2020-03-30 11:26:52 +01:00
Advanced : true ,
Examples : [ ] fs . OptionExample { {
Value : "" ,
Help : "None" ,
} } ,
2023-07-06 17:55:53 +01:00
Sensitive : true ,
2018-04-13 16:08:00 +01:00
} , {
2025-10-14 17:50:28 +01:00
Name : "storage_class" ,
Help : "The storage class to use when storing new objects in S3." ,
2018-11-26 21:09:23 +00:00
} , {
Name : "upload_cutoff" ,
2021-08-16 11:30:01 +02:00
Help : ` Cutoff for switching to chunked upload .
2018-11-26 21:09:23 +00:00
Any files larger than this will be uploaded in chunks of chunk_size .
2021-03-02 20:11:57 +01:00
The minimum is 0 and the maximum is 5 GiB . ` ,
2018-11-26 21:09:23 +00:00
Default : defaultUploadCutoff ,
Advanced : true ,
2018-05-14 18:06:57 +01:00
} , {
2018-10-01 18:36:15 +01:00
Name : "chunk_size" ,
Help : ` Chunk size to use for uploading .
2019-11-06 10:41:03 +00:00
When uploading files larger than upload_cutoff or files with unknown
2020-10-13 17:49:58 -04:00
size ( e . g . from "rclone rcat" or uploaded with "rclone mount" or google
2019-11-06 10:41:03 +00:00
photos or google docs ) they will be uploaded as multipart uploads
using this chunk size .
2018-10-01 18:36:15 +01:00
Note that "--s3-upload-concurrency" chunks of this size are buffered
in memory per transfer .
2020-10-13 17:50:53 -04:00
If you are transferring large files over high - speed links and you have
2019-11-06 10:41:03 +00:00
enough memory , then increasing this will speed up the transfers .
Rclone will automatically increase the chunk size when uploading a
large file of known size to stay below the 10 , 000 chunks limit .
Files of unknown size are uploaded with the configured
2021-03-02 20:11:57 +01:00
chunk_size . Since the default chunk size is 5 MiB and there can be at
2019-11-06 10:41:03 +00:00
most 10 , 000 chunks , this means that by default the maximum size of
2021-03-02 20:11:57 +01:00
a file you can stream upload is 48 GiB . If you wish to stream upload
2022-06-16 22:29:36 +02:00
larger files then you will need to increase chunk_size .
Increasing the chunk size decreases the accuracy of the progress
statistics displayed with "-P" flag . Rclone treats chunk as sent when
it ' s buffered by the AWS SDK , when in fact it may still be uploading .
A bigger chunk size means a bigger AWS SDK buffer and progress
reporting more deviating from the truth .
` ,
2018-09-07 13:02:27 +02:00
Default : minChunkSize ,
2018-05-14 18:06:57 +01:00
Advanced : true ,
2020-06-08 19:22:34 +02:00
} , {
Name : "max_upload_parts" ,
Help : ` Maximum number of parts in a multipart upload .
This option defines the maximum number of multipart chunks to use
when doing a multipart upload .
This can be useful if a service does not support the AWS S3
specification of 10 , 000 chunks .
Rclone will automatically increase the chunk size when uploading a
large file of a known size to stay below this number of chunks limit .
` ,
Default : maxUploadParts ,
Advanced : true ,
2019-12-02 17:14:57 +00:00
} , {
Name : "copy_cutoff" ,
2021-08-16 11:30:01 +02:00
Help : ` Cutoff for switching to multipart copy .
2019-12-02 17:14:57 +00:00
2020-10-13 17:43:40 -04:00
Any files larger than this that need to be server - side copied will be
2019-12-02 17:14:57 +00:00
copied in chunks of this size .
2021-03-02 20:11:57 +01:00
The minimum is 0 and the maximum is 5 GiB . ` ,
2019-12-02 17:14:57 +00:00
Default : fs . SizeSuffix ( maxSizeForCopy ) ,
Advanced : true ,
2018-05-14 18:06:57 +01:00
} , {
2020-04-23 19:47:48 +01:00
Name : "disable_checksum" ,
2021-08-16 11:30:01 +02:00
Help : ` Don ' t store MD5 checksum with object metadata .
2020-04-23 19:47:48 +01:00
Normally rclone will calculate the MD5 checksum of the input before
uploading it so it can add it to metadata on the object . This is great
for data integrity checking but can cause long delays for large files
2023-03-21 11:43:35 +00:00
to start uploading . ` ,
2018-05-14 18:06:57 +01:00
Default : false ,
Advanced : true ,
2020-06-26 11:14:40 +01:00
} , {
Name : "shared_credentials_file" ,
2021-08-16 11:30:01 +02:00
Help : ` Path to the shared credentials file .
2020-06-26 11:14:40 +01:00
If env_auth = true then rclone can use a shared credentials file .
If this variable is empty rclone will look for the
"AWS_SHARED_CREDENTIALS_FILE" env variable . If the env value is empty
it will default to the current user ' s home directory .
Linux / OSX : "$HOME/.aws/credentials"
Windows : "%USERPROFILE%\.aws\credentials"
` ,
Advanced : true ,
} , {
Name : "profile" ,
2021-08-16 11:30:01 +02:00
Help : ` Profile to use in the shared credentials file .
2020-06-26 11:14:40 +01:00
If env_auth = true then rclone can use a shared credentials file . This
variable controls which profile is used in that file .
If empty it will default to the environment variable "AWS_PROFILE" or
"default" if that environment variable is also not set .
` ,
Advanced : true ,
2018-05-14 18:06:57 +01:00
} , {
2023-07-06 17:55:53 +01:00
Name : "session_token" ,
Help : "An AWS session token." ,
Advanced : true ,
Sensitive : true ,
2018-05-14 18:06:57 +01:00
} , {
2018-10-01 18:36:15 +01:00
Name : "upload_concurrency" ,
2023-12-01 10:30:44 +00:00
Help : ` Concurrency for multipart uploads and copies .
2018-10-01 18:36:15 +01:00
This is the number of chunks of the same file that are uploaded
2023-12-01 10:30:44 +00:00
concurrently for multipart uploads and copies .
2018-10-01 18:36:15 +01:00
2020-10-13 17:59:13 -04:00
If you are uploading small numbers of large files over high - speed links
2018-10-01 18:36:15 +01:00
and these uploads do not fully utilize your bandwidth , then increasing
this may help to speed up the transfers . ` ,
2018-12-02 17:51:14 +00:00
Default : 4 ,
2018-05-14 18:06:57 +01:00
Advanced : true ,
2018-07-18 16:40:59 +01:00
} , {
2018-10-01 18:36:15 +01:00
Name : "force_path_style" ,
Help : ` If true use path style access if false use virtual hosted style .
If this is true ( the default ) then rclone will use path style access ,
if false then rclone will use virtual path style . See [ the AWS S3
docs ] ( https : //docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html#access-bucket-intro)
for more info .
2020-10-13 17:43:00 -04:00
Some providers ( e . g . AWS , Aliyun OSS , Netease COS , or Tencent COS ) require this set to
2020-01-05 17:53:45 +00:00
false - rclone will do this automatically based on the provider
2024-07-23 11:34:08 +01:00
setting .
Note that if your bucket isn ' t a valid DNS name , i . e . has '.' or '_' in ,
you ' ll need to set this to true .
` ,
2018-07-18 16:40:59 +01:00
Default : true ,
Advanced : true ,
2018-10-09 13:03:37 +01:00
} , {
Name : "v2_auth" ,
Help : ` If true use v2 authentication .
If this is false ( the default ) then rclone will use v4 authentication .
If it is set then rclone will use v2 authentication .
2020-10-13 17:49:58 -04:00
Use this only if v4 signatures don ' t work , e . g . pre Jewel / v10 CEPH . ` ,
2018-10-09 13:03:37 +01:00
Default : false ,
Advanced : true ,
2023-12-02 16:12:54 -08:00
} , {
Name : "use_dual_stack" ,
Help : ` If true use AWS S3 dual - stack endpoint ( IPv6 support ) .
See [ AWS Docs on Dualstack Endpoints ] ( https : //docs.aws.amazon.com/AmazonS3/latest/userguide/dual-stack-endpoints.html)`,
Default : false ,
Advanced : true ,
2019-04-26 10:19:00 +01:00
} , {
2025-10-14 17:50:28 +01:00
Name : "use_accelerate_endpoint" ,
2019-04-26 10:19:00 +01:00
Help : ` If true use the AWS S3 accelerated endpoint .
See : [ AWS S3 Transfer acceleration ] ( https : //docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration-examples.html)`,
Default : false ,
Advanced : true ,
2025-07-16 15:48:41 +01:00
} , {
Name : "use_arn_region" ,
Help : ` If true, enables arn region support for the service. ` ,
Default : false ,
Advanced : true ,
2019-07-24 11:03:38 +02:00
} , {
2025-10-14 17:50:28 +01:00
Name : "leave_parts_on_error" ,
2019-07-24 11:03:38 +02:00
Help : ` If true avoid calling abort upload on a failure , leaving all successfully uploaded parts on S3 for manual recovery .
It should be set to true for resuming uploads across different sessions .
WARNING : Storing parts of an incomplete multipart upload counts towards space usage on S3 and will add additional costs if not cleaned up .
` ,
Default : false ,
Advanced : true ,
2019-12-26 11:05:00 +00:00
} , {
Name : "list_chunk" ,
Help : ` Size of listing chunk ( response list for each ListObject S3 request ) .
This option is also known as "MaxKeys" , "max-items" , or "page-size" from the AWS S3 specification .
Most services truncate the response list to 1000 objects even if requested more than that .
In AWS S3 this is a global maximum and cannot be changed , see [ AWS S3 ] ( https : //docs.aws.amazon.com/cli/latest/reference/s3/ls.html).
In Ceph , this can be increased with the "rgw list buckets max chunk" option .
` ,
Default : 1000 ,
Advanced : true ,
2021-11-03 18:01:23 +00:00
} , {
Name : "list_version" ,
Help : ` Version of ListObjects to use : 1 , 2 or 0 for auto .
When S3 originally launched it only provided the ListObjects call to
enumerate objects in a bucket .
However in May 2016 the ListObjectsV2 call was introduced . This is
much higher performance and should be used if at all possible .
If set to the default , 0 , rclone will guess according to the provider
set which list objects method to call . If it guesses wrong , then it
may be set manually here .
` ,
Default : 0 ,
Advanced : true ,
2021-11-03 19:40:16 +00:00
} , {
Name : "list_url_encode" ,
Help : ` Whether to url encode listings : true / false / unset
Some providers support URL encoding listings and where this is
available this is more reliable when using control characters in file
names . If this is set to unset ( the default ) then rclone will choose
according to the provider setting what to apply , but you can override
rclone ' s choice here .
` ,
Default : fs . Tristate { } ,
Advanced : true ,
2020-07-22 12:02:17 +01:00
} , {
Name : "no_check_bucket" ,
2021-08-16 11:30:01 +02:00
Help : ` If set , don ' t attempt to check the bucket exists or create it .
2020-07-22 12:02:17 +01:00
This can be useful when trying to minimise the number of transactions
rclone does if you know the bucket exists already .
2021-01-10 17:53:32 +00:00
It can also be needed if the user you are using does not have bucket
creation permissions . Before v1 .52 .0 this would have passed silently
due to a bug .
2021-01-29 11:13:42 +00:00
` ,
Default : false ,
Advanced : true ,
} , {
Name : "no_head" ,
2021-08-16 11:30:01 +02:00
Help : ` If set , don ' t HEAD uploaded objects to check integrity .
2021-01-29 11:13:42 +00:00
This can be useful when trying to minimise the number of transactions
rclone does .
Setting it means that if rclone receives a 200 OK message after
uploading an object with PUT then it will assume that it got uploaded
properly .
In particular it will assume :
- the metadata , including modtime , storage class and content type was as uploaded
- the size was as uploaded
It reads the following items from the response for a single part PUT :
- the MD5SUM
- The uploaded date
For multipart uploads these items aren ' t read .
If an source object of unknown length is uploaded then rclone * * will * * do a
HEAD request .
Setting this flag increases the chance for undetected upload failures ,
in particular an incorrect size , so it isn ' t recommended for normal
operation . In practice the chance of an undetected upload failure is
very small even with this flag .
2020-07-22 12:02:17 +01:00
` ,
Default : false ,
Advanced : true ,
2021-04-28 19:05:54 +09:00
} , {
Name : "no_head_object" ,
2021-09-06 18:41:54 +09:00
Help : ` If set, do not do HEAD before GET when getting objects. ` ,
2021-04-28 19:05:54 +09:00
Default : false ,
Advanced : true ,
2020-01-14 17:33:35 +00:00
} , {
Name : config . ConfigEncoding ,
Help : config . ConfigEncodingHelp ,
Advanced : true ,
2020-01-14 21:51:49 +00:00
// Any UTF-8 character is valid in a key, however it can't handle
// invalid UTF-8 and / have a special meaning.
//
// The SDK can't seem to handle uploading files called '.'
//
// FIXME would be nice to add
// - initial / encoding
// - doubled / encoding
// - trailing / encoding
// so that AWS keys are always valid file names
2020-02-19 11:17:25 +01:00
Default : encoder . EncodeInvalidUtf8 |
2020-01-14 21:51:49 +00:00
encoder . EncodeSlash |
2020-02-19 11:17:25 +01:00
encoder . EncodeDot ,
} , {
Name : "memory_pool_flush_time" ,
2023-08-15 20:38:02 +01:00
Default : fs . Duration ( time . Minute ) ,
2020-02-19 11:17:25 +01:00
Advanced : true ,
2023-08-15 20:38:02 +01:00
Hide : fs . OptionHideBoth ,
Help : ` How often internal memory buffer pools will be flushed. (no longer used) ` ,
2020-02-19 11:17:25 +01:00
} , {
Name : "memory_pool_use_mmap" ,
2023-08-15 20:38:02 +01:00
Default : false ,
2020-02-19 11:17:25 +01:00
Advanced : true ,
2023-08-15 20:38:02 +01:00
Hide : fs . OptionHideBoth ,
Help : ` Whether to use mmap buffers in internal memory pool. (no longer used) ` ,
2020-10-13 21:41:22 +05:30
} , {
Name : "disable_http2" ,
Default : false ,
Advanced : true ,
2021-08-16 11:30:01 +02:00
Help : ` Disable usage of http2 for S3 backends .
2020-10-13 21:41:22 +05:30
There is currently an unsolved issue with the s3 ( specifically minio ) backend
and HTTP / 2. HTTP / 2 is enabled by default for the s3 backend but can be
disabled here . When the issue is solved this flag will be removed .
See : https : //github.com/rclone/rclone/issues/4673, https://github.com/rclone/rclone/issues/3631
` ,
2021-10-14 15:49:38 +05:30
} , {
Name : "download_url" ,
Help : ` Custom endpoint for downloads .
This is usually set to a CloudFront CDN URL as AWS S3 offers
cheaper egress for data downloaded through the CloudFront network . ` ,
Advanced : true ,
2022-09-26 09:59:57 +03:00
} , {
Name : "directory_markers" ,
Default : false ,
Advanced : true ,
2023-04-26 10:59:17 +01:00
Help : ` Upload an empty object with a trailing slash when a new directory is created
2022-09-26 09:59:57 +03:00
2023-04-26 10:59:17 +01:00
Empty folders are unsupported for bucket based remotes , this option creates an empty
object ending with "/" , to persist the folder .
2022-09-26 09:59:57 +03:00
` ,
2022-02-27 15:47:31 +00:00
} , {
Name : "use_multipart_etag" ,
Help : ` Whether to use ETag in multipart uploads for verification
2024-08-03 11:35:32 +01:00
This should be true , false or left unset to use the default for the provider .
` ,
Default : fs . Tristate { } ,
Advanced : true ,
} , {
Name : "use_unsigned_payload" ,
Help : ` Whether to use an unsigned payload in PutObject
Rclone has to avoid the AWS SDK seeking the body when calling
PutObject . The AWS provider can add checksums in the trailer to avoid
seeking but other providers can ' t .
2022-02-27 15:47:31 +00:00
This should be true , false or left unset to use the default for the provider .
` ,
Default : fs . Tristate { } ,
Advanced : true ,
2022-05-03 17:39:01 +01:00
} , {
Name : "use_presigned_request" ,
Help : ` Whether to use a presigned request or PutObject for single part uploads
If this is false rclone will use PutObject from the AWS SDK to upload
an object .
Versions of rclone < 1.59 use presigned requests to upload a single
part object and setting this flag to true will re - enable that
functionality . This shouldn ' t be necessary except in exceptional
circumstances or for testing .
` ,
Default : false ,
Advanced : true ,
2025-11-12 23:15:13 +08:00
} , {
Name : "use_data_integrity_protections" ,
Help : ` If true use AWS S3 data integrity protections .
See [ AWS Docs on Data Integrity Protections ] ( https : //docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html)`,
Default : fs . Tristate { } ,
Advanced : true ,
2022-07-25 16:06:15 +01:00
} , {
Name : "versions" ,
Help : "Include old versions in directory listings." ,
Default : false ,
Advanced : true ,
2022-07-26 17:58:57 +01:00
} , {
Name : "version_at" ,
Help : ` Show file versions as they were at the specified time .
The parameter should be a date , "2006-01-02" , datetime " 2006 - 01 - 02
15 : 04 : 05 " or a duration for that long ago, eg " 100 d " or " 1 h " .
Note that when using this no file write operations are permitted ,
so you can ' t upload files or delete them .
2025-01-17 14:20:57 +01:00
See [ the time option docs ] ( / docs / # time - options ) for valid formats .
2022-07-26 17:58:57 +01:00
` ,
Default : fs . Time { } ,
Advanced : true ,
2023-11-08 15:29:23 +00:00
} , {
Name : "version_deleted" ,
Help : ` Show deleted file markers when using versions .
This shows deleted file markers in the listing when using versions . These will appear
as 0 size files . The only operation which can be performed on them is deletion .
Deleting a delete marker will reveal the previous version .
Deleted files will always show with a timestamp .
` ,
Default : false ,
Advanced : true ,
2022-07-29 17:01:59 +01:00
} , {
Name : "decompress" ,
Help : ` If set this will decompress gzip encoded objects .
It is possible to upload objects to S3 with "Content-Encoding: gzip"
2022-08-13 22:56:32 -04:00
set . Normally rclone will download these files as compressed objects .
2022-07-29 17:01:59 +01:00
If this flag is set then rclone will decompress these files with
"Content-Encoding: gzip" as they are received . This means that rclone
can ' t check the size and hash but the file contents will be decompressed .
` ,
Advanced : true ,
Default : false ,
2022-10-28 23:23:29 +01:00
} , {
Name : "might_gzip" ,
Help : strings . ReplaceAll ( ` Set this if the backend might gzip objects .
Normally providers will not alter objects when they are downloaded . If
an object was not uploaded with | Content - Encoding : gzip | then it won ' t
be set on download .
However some providers may gzip objects even if they weren ' t uploaded
with | Content - Encoding : gzip | ( eg Cloudflare ) .
A symptom of this would be receiving errors like
ERROR corrupted on transfer : sizes differ NNN vs MMM
If you set this flag and rclone downloads an object with
Content - Encoding : gzip set and chunked transfer encoding , then rclone
will decompress the object on the fly .
If this is set to unset ( the default ) then rclone will choose
according to the provider setting what to apply , but you can override
rclone ' s choice here .
2023-01-09 07:54:51 +01:00
` , "|", " ` " ) ,
Default : fs . Tristate { } ,
Advanced : true ,
} , {
Name : "use_accept_encoding_gzip" ,
Help : strings . ReplaceAll ( ` Whether to send | Accept - Encoding : gzip | header .
By default , rclone will append | Accept - Encoding : gzip | to the request to download
compressed objects whenever possible .
However some providers such as Google Cloud Storage may alter the HTTP headers , breaking
the signature of the request .
A symptom of this would be receiving errors like
SignatureDoesNotMatch : The request signature we calculated does not match the signature you provided .
In this case , you might want to try disabling this option .
2022-10-28 23:23:29 +01:00
` , "|", " ` " ) ,
Default : fs . Tristate { } ,
Advanced : true ,
2022-10-12 08:55:58 +01:00
} , {
Name : "no_system_metadata" ,
Help : ` Suppress setting and reading of system metadata ` ,
Advanced : true ,
Default : false ,
2023-03-02 09:56:09 +00:00
} , {
Name : "sts_endpoint" ,
2024-08-03 11:35:32 +01:00
Help : "Endpoint for STS (deprecated).\n\nLeave blank if using AWS to use the default endpoint for the region." ,
2023-03-02 09:56:09 +00:00
Advanced : true ,
2024-08-03 11:35:32 +01:00
Hide : fs . OptionHideBoth ,
2023-10-06 11:45:03 +01:00
} , {
Name : "use_already_exists" ,
Help : strings . ReplaceAll ( ` Set if rclone should report BucketAlreadyExists errors on bucket creation .
At some point during the evolution of the s3 protocol , AWS started
returning an | AlreadyOwnedByYou | error when attempting to create a
bucket that the user already owned , rather than a
| BucketAlreadyExists | error .
Unfortunately exactly what has been implemented by s3 clones is a
little inconsistent , some return | AlreadyOwnedByYou | , some return
| BucketAlreadyExists | and some return no error at all .
This is important to rclone because it ensures the bucket exists by
creating it on quite a lot of operations ( unless
| -- s3 - no - check - bucket | is used ) .
If rclone knows the provider can return | AlreadyOwnedByYou | or returns
no error then it can report | BucketAlreadyExists | errors when the user
attempts to create a bucket not owned by them . Otherwise rclone
ignores the | BucketAlreadyExists | error which can lead to confusion .
This should be automatically set correctly for all providers rclone
knows about - please make a bug report if not .
` , "|", " ` " ) ,
Default : fs . Tristate { } ,
Advanced : true ,
2023-11-14 12:39:50 +00:00
} , {
Name : "use_multipart_uploads" ,
Help : ` Set if rclone should use multipart uploads .
You can change this if you want to disable the use of multipart uploads .
This shouldn ' t be necessary in normal operation .
2025-02-11 20:22:03 +00:00
This should be automatically set correctly for all providers rclone
knows about - please make a bug report if not .
` ,
Default : fs . Tristate { } ,
Advanced : true ,
} , {
Name : "use_x_id" ,
Help : ` Set if rclone should add x - id URL parameters .
You can change this if you want to disable the AWS SDK from
adding x - id URL parameters .
This shouldn ' t be necessary in normal operation .
This should be automatically set correctly for all providers rclone
knows about - please make a bug report if not .
` ,
Default : fs . Tristate { } ,
Advanced : true ,
} , {
Name : "sign_accept_encoding" ,
Help : ` Set if rclone should include Accept - Encoding as part of the signature .
You can change this if you want to stop rclone including
Accept - Encoding as part of the signature .
This shouldn ' t be necessary in normal operation .
2023-11-14 12:39:50 +00:00
This should be automatically set correctly for all providers rclone
knows about - please make a bug report if not .
` ,
Default : fs . Tristate { } ,
Advanced : true ,
2024-09-13 18:56:22 +01:00
} , {
Name : "directory_bucket" ,
Help : strings . ReplaceAll ( ` Set to use AWS Directory Buckets
If you are using an AWS Directory Bucket then set this flag .
This will ensure no | Content - Md5 | headers are sent and ensure | ETag |
headers are not interpreted as MD5 sums . | X - Amz - Meta - Md5chksum | will
be set on all objects whether single or multipart uploaded .
This also sets | no_check_bucket = true | .
Note that Directory Buckets do not support :
- Versioning
- | Content - Encoding : gzip |
Rclone limitations with Directory Buckets :
- rclone does not support creating Directory Buckets with | rclone mkdir |
- ... or removing them with | rclone rmdir | yet
- Directory Buckets do not appear when doing | rclone lsf | at the top level .
- Rclone can ' t remove auto created directories yet . In theory this should
work with | directory_markers = true | but it doesn ' t .
- Directories don ' t seem to appear in recursive ( ListR ) listings .
` , "|", " ` " ) ,
Default : false ,
Advanced : true ,
2024-08-06 10:33:17 +01:00
} , {
Name : "sdk_log_mode" ,
Help : strings . ReplaceAll ( ` Set to debug the SDK
This can be set to a comma separated list of the following functions :
- | Signing |
- | Retries |
- | Request |
- | RequestWithBody |
- | Response |
- | ResponseWithBody |
- | DeprecatedUsage |
- | RequestEventMessage |
- | ResponseEventMessage |
Use | Off | to disable and | All | to set all log levels . You will need to
use | - vv | to see the debug level logs .
` , "|", " ` " ) ,
Default : sdkLogMode ( 0 ) ,
Advanced : true ,
2025-10-14 17:50:28 +01:00
} , {
Name : "ibm_api_key" ,
Help : "IBM API Key to be used to obtain IAM token" ,
} , {
Name : "ibm_resource_instance_id" ,
Help : "IBM service instance id" ,
2020-02-19 11:17:25 +01:00
} ,
2025-10-14 17:50:28 +01:00
} } ) )
2013-06-27 20:13:07 +01:00
}
2013-01-08 18:53:35 +00:00
// Constants
const (
2022-05-24 12:32:39 +01:00
metaMtime = "mtime" // the meta key to store mtime in - e.g. X-Amz-Meta-Mtime
metaMD5Hash = "md5chksum" // the meta key to store md5hash in
2021-03-02 20:11:57 +01:00
// The maximum size of object we can COPY - this should be 5 GiB but is < 5 GB for b2 compatibility
2020-09-01 18:53:29 +01:00
// See https://forum.rclone.org/t/copying-files-within-a-b2-bucket/16680/76
maxSizeForCopy = 4768 * 1024 * 1024
maxUploadParts = 10000 // maximum allowed number of parts in a multi-part upload
2019-12-30 23:17:06 +00:00
minChunkSize = fs . SizeSuffix ( 1024 * 1024 * 5 )
2018-11-26 21:09:23 +00:00
defaultUploadCutoff = fs . SizeSuffix ( 200 * 1024 * 1024 )
maxUploadCutoff = fs . SizeSuffix ( 5 * 1024 * 1024 * 1024 )
2023-08-15 20:38:02 +01:00
minSleep = 10 * time . Millisecond // In case of error, start at 10ms sleep.
2020-06-18 17:50:50 +01:00
maxExpireDuration = fs . Duration ( 7 * 24 * time . Hour ) // max expiry is 1 week
2013-01-08 18:53:35 +00:00
)
2024-08-06 10:33:17 +01:00
type (
sdkLogMode = fs . Bits [ sdkLogModeChoices ]
sdkLogModeChoices struct { }
)
func ( sdkLogModeChoices ) Choices ( ) [ ] fs . BitsChoicesInfo {
return [ ] fs . BitsChoicesInfo {
{ Bit : uint64 ( 0 ) , Name : "Off" } ,
{ Bit : uint64 ( aws . LogSigning ) , Name : "Signing" } ,
{ Bit : uint64 ( aws . LogRetries ) , Name : "Retries" } ,
{ Bit : uint64 ( aws . LogRequest ) , Name : "Request" } ,
{ Bit : uint64 ( aws . LogRequestWithBody ) , Name : "RequestWithBody" } ,
{ Bit : uint64 ( aws . LogResponse ) , Name : "Response" } ,
{ Bit : uint64 ( aws . LogResponseWithBody ) , Name : "ResponseWithBody" } ,
{ Bit : uint64 ( aws . LogDeprecatedUsage ) , Name : "DeprecatedUsage" } ,
{ Bit : uint64 ( aws . LogRequestEventMessage ) , Name : "RequestEventMessage" } ,
{ Bit : uint64 ( aws . LogResponseEventMessage ) , Name : "ResponseEventMessage" } ,
{ Bit : math . MaxUint64 , Name : "All" } ,
}
}
2022-07-26 17:58:57 +01:00
// globals
var (
errNotWithVersionAt = errors . New ( "can't modify or delete files in --s3-version-at mode" )
)
2022-05-24 12:32:39 +01:00
// system metadata keys which this backend owns
var systemMetadataInfo = map [ string ] fs . MetadataHelp {
"cache-control" : {
Help : "Cache-Control header" ,
Type : "string" ,
Example : "no-cache" ,
} ,
"content-disposition" : {
Help : "Content-Disposition header" ,
Type : "string" ,
Example : "inline" ,
} ,
"content-encoding" : {
Help : "Content-Encoding header" ,
Type : "string" ,
Example : "gzip" ,
} ,
"content-language" : {
Help : "Content-Language header" ,
Type : "string" ,
Example : "en-US" ,
} ,
"content-type" : {
Help : "Content-Type header" ,
Type : "string" ,
Example : "text/plain" ,
} ,
// "tagging": {
// Help: "x-amz-tagging header",
// Type: "string",
// Example: "tag1=value1&tag2=value2",
// },
"tier" : {
Help : "Tier of the object" ,
Type : "string" ,
Example : "GLACIER" ,
ReadOnly : true ,
} ,
"mtime" : {
Help : "Time of last modification, read from rclone metadata" ,
Type : "RFC 3339" ,
Example : "2006-01-02T15:04:05.999999999Z07:00" ,
} ,
"btime" : {
Help : "Time of file birth (creation) read from Last-Modified header" ,
Type : "RFC 3339" ,
Example : "2006-01-02T15:04:05.999999999Z07:00" ,
ReadOnly : true ,
} ,
}
2018-05-14 18:06:57 +01:00
// Options defines the configuration for this backend
type Options struct {
2025-11-12 23:15:13 +08:00
Provider string ` config:"provider" `
EnvAuth bool ` config:"env_auth" `
AccessKeyID string ` config:"access_key_id" `
SecretAccessKey string ` config:"secret_access_key" `
Region string ` config:"region" `
Endpoint string ` config:"endpoint" `
STSEndpoint string ` config:"sts_endpoint" `
UseDualStack bool ` config:"use_dual_stack" `
LocationConstraint string ` config:"location_constraint" `
ACL string ` config:"acl" `
BucketACL string ` config:"bucket_acl" `
RequesterPays bool ` config:"requester_pays" `
ServerSideEncryption string ` config:"server_side_encryption" `
SSEKMSKeyID string ` config:"sse_kms_key_id" `
SSECustomerAlgorithm string ` config:"sse_customer_algorithm" `
SSECustomerKey string ` config:"sse_customer_key" `
SSECustomerKeyBase64 string ` config:"sse_customer_key_base64" `
SSECustomerKeyMD5 string ` config:"sse_customer_key_md5" `
StorageClass string ` config:"storage_class" `
UploadCutoff fs . SizeSuffix ` config:"upload_cutoff" `
CopyCutoff fs . SizeSuffix ` config:"copy_cutoff" `
ChunkSize fs . SizeSuffix ` config:"chunk_size" `
MaxUploadParts int ` config:"max_upload_parts" `
DisableChecksum bool ` config:"disable_checksum" `
SharedCredentialsFile string ` config:"shared_credentials_file" `
Profile string ` config:"profile" `
SessionToken string ` config:"session_token" `
UploadConcurrency int ` config:"upload_concurrency" `
ForcePathStyle bool ` config:"force_path_style" `
V2Auth bool ` config:"v2_auth" `
UseAccelerateEndpoint bool ` config:"use_accelerate_endpoint" `
UseARNRegion bool ` config:"use_arn_region" `
LeavePartsOnError bool ` config:"leave_parts_on_error" `
ListChunk int32 ` config:"list_chunk" `
ListVersion int ` config:"list_version" `
ListURLEncode fs . Tristate ` config:"list_url_encode" `
NoCheckBucket bool ` config:"no_check_bucket" `
NoHead bool ` config:"no_head" `
NoHeadObject bool ` config:"no_head_object" `
Enc encoder . MultiEncoder ` config:"encoding" `
DisableHTTP2 bool ` config:"disable_http2" `
DownloadURL string ` config:"download_url" `
DirectoryMarkers bool ` config:"directory_markers" `
UseMultipartEtag fs . Tristate ` config:"use_multipart_etag" `
UsePresignedRequest bool ` config:"use_presigned_request" `
UseDataIntegrityProtections fs . Tristate ` config:"use_data_integrity_protections" `
Versions bool ` config:"versions" `
VersionAt fs . Time ` config:"version_at" `
VersionDeleted bool ` config:"version_deleted" `
Decompress bool ` config:"decompress" `
MightGzip fs . Tristate ` config:"might_gzip" `
UseAcceptEncodingGzip fs . Tristate ` config:"use_accept_encoding_gzip" `
NoSystemMetadata bool ` config:"no_system_metadata" `
UseAlreadyExists fs . Tristate ` config:"use_already_exists" `
UseMultipartUploads fs . Tristate ` config:"use_multipart_uploads" `
UseUnsignedPayload fs . Tristate ` config:"use_unsigned_payload" `
SDKLogMode sdkLogMode ` config:"sdk_log_mode" `
DirectoryBucket bool ` config:"directory_bucket" `
IBMAPIKey string ` config:"ibm_api_key" `
IBMInstanceID string ` config:"ibm_resource_instance_id" `
UseXID fs . Tristate ` config:"use_x_id" `
SignAcceptEncoding fs . Tristate ` config:"sign_accept_encoding" `
2018-05-14 18:06:57 +01:00
}
2016-09-02 00:27:50 +03:00
2015-11-07 11:14:46 +00:00
// Fs represents a remote s3 server
type Fs struct {
2024-08-03 11:35:32 +01:00
name string // the name of the remote
root string // root of the bucket - ignore all objects above this
opt Options // parsed options
ci * fs . ConfigInfo // global config
ctx context . Context // global context for reading config
features * fs . Features // optional features
c * s3 . Client // the connection to the s3 server
rootBucket string // bucket part of root (if any)
rootDirectory string // directory part of root (if any)
cache * bucket . Cache // cache for bucket creation status
pacer * fs . Pacer // To pace the API calls
srv * http . Client // a plain http client
srvRest * rest . Client // the rest connection to the server
etagIsNotMD5 bool // if set ETags are not MD5s
2022-07-29 17:01:59 +01:00
versioningMu sync . Mutex
versioning fs . Tristate // if set bucket is using versions
warnCompressed sync . Once // warn once about compressed files
2013-01-08 18:53:35 +00:00
}
2015-11-07 11:14:46 +00:00
// Object describes a s3 object
type Object struct {
2013-01-08 18:53:35 +00:00
// Will definitely have everything but meta which may be nil
//
2016-09-21 22:13:24 +01:00
// List will read everything but meta & mimeType - to fill
// that in you need to call readMetaData
2022-06-22 15:40:30 +01:00
fs * Fs // what this object is part of
remote string // The remote path
md5 string // md5sum of the object
bytes int64 // size of the object
lastModified time . Time // Last modified
meta map [ string ] string // The object metadata if known - may be nil - with lower case keys
mimeType string // MimeType of object - may be ""
2022-07-25 16:06:15 +01:00
versionID * string // If present this points to an object version
2022-05-24 12:32:39 +01:00
// Metadata as pointers to strings as they often won't be present
storageClass * string // e.g. GLACIER
cacheControl * string // Cache-Control: header
contentDisposition * string // Content-Disposition: header
contentEncoding * string // Content-Encoding: header
contentLanguage * string // Content-Language: header
2013-01-08 18:53:35 +00:00
}
2024-08-03 11:35:32 +01:00
// safely dereference the pointer, returning a zero T if nil
func deref [ T any ] ( p * T ) T {
if p == nil {
var zero T
return zero
}
return * p
}
// gets an http status code from err or returns -1
func getHTTPStatusCode ( err error ) int {
var httpErr interface { HTTPStatusCode ( ) int }
if errors . As ( err , & httpErr ) {
return httpErr . HTTPStatusCode ( )
}
return - 1
}
2013-01-08 18:53:35 +00:00
// ------------------------------------------------------------
2015-09-22 18:47:16 +01:00
// Name of the remote (as passed into NewFs)
2015-11-07 11:14:46 +00:00
func ( f * Fs ) Name ( ) string {
2015-08-22 16:53:11 +01:00
return f . name
}
2015-09-22 18:47:16 +01:00
// Root of the remote (as passed into NewFs)
2015-11-07 11:14:46 +00:00
func ( f * Fs ) Root ( ) string {
2019-08-09 11:29:36 +01:00
return f . root
2015-09-01 20:45:27 +01:00
}
2015-11-07 11:14:46 +00:00
// String converts this Fs to a string
func ( f * Fs ) String ( ) string {
2019-08-09 11:29:36 +01:00
if f . rootBucket == "" {
2022-06-08 22:25:17 +02:00
return "S3 root"
2019-08-09 11:29:36 +01:00
}
if f . rootDirectory == "" {
return fmt . Sprintf ( "S3 bucket %s" , f . rootBucket )
2014-05-05 18:25:32 +01:00
}
2019-08-09 11:29:36 +01:00
return fmt . Sprintf ( "S3 bucket %s path %s" , f . rootBucket , f . rootDirectory )
2013-01-08 18:53:35 +00:00
}
2017-01-13 17:21:47 +00:00
// Features returns the optional features of this Fs
func ( f * Fs ) Features ( ) * fs . Features {
return f . features
}
2018-09-03 16:41:04 +12:00
// retryErrorCodes is a slice of error codes that we will retry
// See: https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
var retryErrorCodes = [ ] int {
2020-12-01 19:55:25 +05:30
429 , // Too Many Requests
2020-02-11 16:30:32 +01:00
500 , // Internal Server Error - "We encountered an internal error. Please try again."
2018-09-03 16:41:04 +12:00
503 , // Service Unavailable/Slow Down - "Reduce your request rate"
}
2022-08-05 16:35:41 +01:00
// S3 is pretty resilient, and the built in retry handling is probably sufficient
2018-09-03 16:41:04 +12:00
// as it should notice closed connections and timeouts which are the most likely
// sort of failure modes
2021-03-16 15:50:02 +00:00
func ( f * Fs ) shouldRetry ( ctx context . Context , err error ) ( bool , error ) {
if fserrors . ContextError ( ctx , & err ) {
return false , err
}
2024-08-03 11:35:32 +01:00
// https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md#error-handling
2018-09-03 16:41:04 +12:00
// If this is an awserr object, try and extract more useful information to determine if we should retry
2024-08-03 11:35:32 +01:00
var awsError smithy . APIError
if errors . As ( err , & awsError ) {
2019-02-07 18:41:17 +01:00
// Simple case, check the original embedded error in case it's generically retryable
2024-08-03 11:35:32 +01:00
if fserrors . ShouldRetry ( awsError ) {
2018-09-03 16:41:04 +12:00
return true , err
}
2021-11-24 12:48:57 +00:00
// If it is a timeout then we want to retry that
2024-08-03 11:35:32 +01:00
if awsError . ErrorCode ( ) == "RequestTimeout" {
2021-11-24 12:48:57 +00:00
return true , err
}
2024-08-03 11:35:32 +01:00
}
// Check http status code if available
if httpStatusCode := getHTTPStatusCode ( err ) ; httpStatusCode > 0 {
// 301 if wrong region for bucket - can only update if running from a bucket
if f . rootBucket != "" {
if httpStatusCode == http . StatusMovedPermanently {
urfbErr := f . updateRegionForBucket ( ctx , f . rootBucket )
if urfbErr != nil {
fs . Errorf ( f , "Failed to update region for bucket: %v" , urfbErr )
return false , err
2019-01-16 13:35:19 +00:00
}
2024-08-03 11:35:32 +01:00
return true , err
2019-01-16 13:35:19 +00:00
}
2024-08-03 11:35:32 +01:00
}
build: modernize Go usage
This commit modernizes Go usage. This was done with:
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
Then files needed to be `go fmt`ed and a few comments needed to be
restored.
The modernizations include replacing
- if/else conditional assignment by a call to the built-in min or max functions added in go1.21
- sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] } by a call to slices.Sort(s), added in go1.21
- interface{} by the 'any' type added in go1.18
- append([]T(nil), s...) by slices.Clone(s) or slices.Concat(s), added in go1.21
- loop around an m[k]=v map update by a call to one of the Collect, Copy, Clone, or Insert functions from the maps package, added in go1.21
- []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...), added in go1.19
- append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1), added in go1.21
- a 3-clause for i := 0; i < n; i++ {} loop by for i := range n {}, added in go1.22
2025-02-26 21:08:12 +00:00
if slices . Contains ( retryErrorCodes , httpStatusCode ) {
return true , err
2018-09-03 16:41:04 +12:00
}
}
2019-01-12 16:46:45 +00:00
// Ok, not an awserr, check for generic failure conditions
2018-09-03 16:41:04 +12:00
return fserrors . ShouldRetry ( err ) , err
}
2019-08-09 11:29:36 +01:00
// parsePath parses a remote 'url'
func parsePath ( path string ) ( root string ) {
root = strings . Trim ( path , "/" )
2013-01-08 18:53:35 +00:00
return
}
2019-08-09 11:29:36 +01:00
// split returns bucket and bucketPath from the rootRelativePath
// relative to f.root
func ( f * Fs ) split ( rootRelativePath string ) ( bucketName , bucketPath string ) {
2023-03-03 10:45:29 +00:00
bucketName , bucketPath = bucket . Split ( bucket . Join ( f . root , rootRelativePath ) )
2025-06-16 15:07:05 +01:00
if f . opt . DirectoryMarkers && strings . HasSuffix ( bucketPath , "//" ) {
bucketPath = bucketPath [ : len ( bucketPath ) - 1 ]
}
2020-01-14 17:33:35 +00:00
return f . opt . Enc . FromStandardName ( bucketName ) , f . opt . Enc . FromStandardPath ( bucketPath )
2019-08-09 11:29:36 +01:00
}
// split returns bucket and bucketPath from the object
func ( o * Object ) split ( ) ( bucket , bucketPath string ) {
2022-07-25 16:06:15 +01:00
bucket , bucketPath = o . fs . split ( o . remote )
// If there is an object version, then the path may have a
// version suffix, if so remove it.
//
// If we are unlucky enough to have a file name with a valid
// version path where this wasn't required (eg using
// --s3-version-at) then this will go wrong.
if o . versionID != nil {
_ , bucketPath = version . Remove ( bucketPath )
}
return bucket , bucketPath
2019-08-09 11:29:36 +01:00
}
2020-10-13 21:41:22 +05:30
// getClient makes an http client according to the options
2020-11-05 11:33:32 +00:00
func getClient ( ctx context . Context , opt * Options ) * http . Client {
2020-10-13 21:41:22 +05:30
// TODO: Do we need cookies too?
2020-11-13 15:24:43 +00:00
t := fshttp . NewTransportCustom ( ctx , func ( t * http . Transport ) {
2020-10-13 21:41:22 +05:30
if opt . DisableHTTP2 {
t . TLSNextProto = map [ string ] func ( string , * tls . Conn ) http . RoundTripper { }
}
} )
return & http . Client {
Transport : t ,
}
}
2025-02-11 20:22:03 +00:00
// Fixup the request if needed.
//
2024-08-05 15:48:29 +01:00
// Google Cloud Storage alters the Accept-Encoding header, which
2025-02-11 20:22:03 +00:00
// breaks the v2 request signature. This is set with opt.SignAcceptEncoding.
2024-08-05 15:48:29 +01:00
//
// It also doesn't like the x-id URL parameter SDKv2 puts in so we
2025-02-11 20:22:03 +00:00
// remove that too. This is set with opt.UseXID.Value.
2024-08-05 15:48:29 +01:00
//
// See https://github.com/aws/aws-sdk-go-v2/issues/1816.
// Adapted from: https://github.com/aws/aws-sdk-go-v2/issues/1816#issuecomment-1927281540
2025-02-11 20:22:03 +00:00
func fixupRequest ( o * s3 . Options , opt * Options ) {
2024-08-05 15:48:29 +01:00
type ignoredHeadersKey struct { }
headers := [ ] string { "Accept-Encoding" }
fixup := middleware . FinalizeMiddlewareFunc (
2025-02-11 20:22:03 +00:00
"FixupRequest" ,
2024-08-05 15:48:29 +01:00
func ( ctx context . Context , in middleware . FinalizeInput , next middleware . FinalizeHandler ) ( out middleware . FinalizeOutput , metadata middleware . Metadata , err error ) {
req , ok := in . Request . ( * smithyhttp . Request )
if ! ok {
2025-02-11 20:22:03 +00:00
return out , metadata , fmt . Errorf ( "fixupRequest: unexpected request middleware type %T" , in . Request )
2024-08-05 15:48:29 +01:00
}
2025-02-11 20:22:03 +00:00
if ! opt . SignAcceptEncoding . Value {
// Delete headers from being signed - will restore later
ignored := make ( map [ string ] string , len ( headers ) )
for _ , h := range headers {
ignored [ h ] = req . Header . Get ( h )
req . Header . Del ( h )
}
2024-08-05 15:48:29 +01:00
2025-02-11 20:22:03 +00:00
// Store ignored on context
ctx = middleware . WithStackValue ( ctx , ignoredHeadersKey { } , ignored )
2024-08-05 15:48:29 +01:00
}
2025-02-11 20:22:03 +00:00
if ! opt . UseXID . Value {
// Remove x-id
if query := req . URL . Query ( ) ; query . Has ( "x-id" ) {
query . Del ( "x-id" )
req . URL . RawQuery = query . Encode ( )
}
}
2024-08-05 15:48:29 +01:00
return next . HandleFinalize ( ctx , in )
} ,
)
// Restore headers if necessary
restore := middleware . FinalizeMiddlewareFunc (
2025-02-11 20:22:03 +00:00
"FixupRequestRestoreHeaders" ,
2024-08-05 15:48:29 +01:00
func ( ctx context . Context , in middleware . FinalizeInput , next middleware . FinalizeHandler ) ( out middleware . FinalizeOutput , metadata middleware . Metadata , err error ) {
req , ok := in . Request . ( * smithyhttp . Request )
if ! ok {
2025-02-11 20:22:03 +00:00
return out , metadata , fmt . Errorf ( "fixupRequest: unexpected request middleware type %T" , in . Request )
2024-08-05 15:48:29 +01:00
}
2025-02-11 20:22:03 +00:00
if ! opt . SignAcceptEncoding . Value {
// Restore ignored from ctx
ignored , _ := middleware . GetStackValue ( ctx , ignoredHeadersKey { } ) . ( map [ string ] string )
for k , v := range ignored {
req . Header . Set ( k , v )
}
2024-08-05 15:48:29 +01:00
}
return next . HandleFinalize ( ctx , in )
} ,
)
o . APIOptions = append ( o . APIOptions , func ( stack * middleware . Stack ) error {
if err := stack . Finalize . Insert ( fixup , "Signing" , middleware . Before ) ; err != nil {
return err
}
if err := stack . Finalize . Insert ( restore , "Signing" , middleware . After ) ; err != nil {
return err
}
return nil
} )
}
2024-08-06 10:33:17 +01:00
// A logger for the S3 SDK
type s3logger struct { }
// Logf is expected to support the standard fmt package "verbs".
build: modernize Go usage
This commit modernizes Go usage. This was done with:
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
Then files needed to be `go fmt`ed and a few comments needed to be
restored.
The modernizations include replacing
- if/else conditional assignment by a call to the built-in min or max functions added in go1.21
- sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] } by a call to slices.Sort(s), added in go1.21
- interface{} by the 'any' type added in go1.18
- append([]T(nil), s...) by slices.Clone(s) or slices.Concat(s), added in go1.21
- loop around an m[k]=v map update by a call to one of the Collect, Copy, Clone, or Insert functions from the maps package, added in go1.21
- []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...), added in go1.19
- append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1), added in go1.21
- a 3-clause for i := 0; i < n; i++ {} loop by for i := range n {}, added in go1.22
2025-02-26 21:08:12 +00:00
func ( s3logger ) Logf ( classification logging . Classification , format string , v ... any ) {
2024-08-06 10:33:17 +01:00
switch classification {
default :
case logging . Debug :
fs . Debugf ( "S3 SDK" , format , v ... )
case logging . Warn :
fs . Infof ( "S3 SDK" , format , v ... )
}
}
2013-01-08 18:53:35 +00:00
// s3Connection makes a connection to s3
2025-10-14 17:50:28 +01:00
func s3Connection ( ctx context . Context , opt * Options , client * http . Client ) ( s3Client * s3 . Client , provider * Provider , err error ) {
2021-08-13 17:27:49 +01:00
ci := fs . GetConfig ( ctx )
2024-08-03 11:35:32 +01:00
var awsConfig aws . Config
2024-09-13 12:45:31 +01:00
// Make the default static auth
v := aws . Credentials {
AccessKeyID : opt . AccessKeyID ,
SecretAccessKey : opt . SecretAccessKey ,
SessionToken : opt . SessionToken ,
}
awsConfig . Credentials = & credentials . StaticCredentialsProvider { Value : v }
2017-11-22 13:21:36 -08:00
2024-08-03 11:35:32 +01:00
// Try to fill in the config from the environment if env_auth=true
2024-09-13 12:45:31 +01:00
if opt . EnvAuth && opt . AccessKeyID == "" && opt . SecretAccessKey == "" {
2025-02-03 05:29:31 -06:00
2024-08-03 11:35:32 +01:00
configOpts := [ ] func ( * awsconfig . LoadOptions ) error { }
// Set the name of the profile if supplied
if opt . Profile != "" {
configOpts = append ( configOpts , awsconfig . WithSharedConfigProfile ( opt . Profile ) )
}
// Set the shared config file if supplied
if opt . SharedCredentialsFile != "" {
configOpts = append ( configOpts , awsconfig . WithSharedConfigFiles ( [ ] string { opt . SharedCredentialsFile } ) )
}
awsConfig , err = awsconfig . LoadDefaultConfig ( ctx , configOpts ... )
if err != nil {
2025-10-14 17:50:28 +01:00
return nil , nil , fmt . Errorf ( "couldn't load configuration with env_auth=true: %w" , err )
2024-08-03 11:35:32 +01:00
}
2025-02-03 05:29:31 -06:00
2024-08-03 11:35:32 +01:00
} else {
switch {
2025-02-03 05:29:31 -06:00
case opt . Provider == "IBMCOS" && opt . V2Auth :
awsConfig . Credentials = & NoOpCredentialsProvider { }
fs . Debugf ( nil , "Using IBM IAM" )
2024-08-03 11:35:32 +01:00
case opt . AccessKeyID == "" && opt . SecretAccessKey == "" :
// if no access key/secret and iam is explicitly disabled then fall back to anon interaction
awsConfig . Credentials = aws . AnonymousCredentials { }
fs . Debugf ( nil , "Using anonymous credentials - did you mean to set env_auth=true?" )
case opt . AccessKeyID == "" :
2025-10-14 17:50:28 +01:00
return nil , nil , errors . New ( "access_key_id not found" )
2024-08-03 11:35:32 +01:00
case opt . SecretAccessKey == "" :
2025-10-14 17:50:28 +01:00
return nil , nil , errors . New ( "secret_access_key not found" )
2024-08-03 11:35:32 +01:00
default :
2024-09-13 12:45:31 +01:00
// static credentials are already set
2024-08-03 11:35:32 +01:00
}
2013-01-08 18:53:35 +00:00
}
2018-05-14 18:06:57 +01:00
if opt . Region == "" {
opt . Region = "us-east-1"
2013-01-08 18:53:35 +00:00
}
2025-10-14 17:50:28 +01:00
provider = loadProvider ( opt . Provider )
if provider == nil {
fs . Logf ( "s3" , "s3 provider %q not known - please set correctly" , opt . Provider )
provider = loadProvider ( "Other" )
}
setQuirks ( opt , provider )
2024-08-03 11:35:32 +01:00
awsConfig . RetryMaxAttempts = ci . LowLevelRetries
awsConfig . HTTPClient = client
options := [ ] func ( * s3 . Options ) { }
options = append ( options , func ( s3Opt * s3 . Options ) {
s3Opt . UsePathStyle = opt . ForcePathStyle
s3Opt . UseAccelerate = opt . UseAccelerateEndpoint
2025-07-16 15:48:41 +01:00
s3Opt . UseARNRegion = opt . UseARNRegion
2024-08-03 11:35:32 +01:00
// FIXME maybe this should be a tristate so can default to DualStackEndpointStateUnset?
if opt . UseDualStack {
s3Opt . EndpointOptions . UseDualStackEndpoint = aws . DualStackEndpointStateEnabled
} else {
s3Opt . EndpointOptions . UseDualStackEndpoint = aws . DualStackEndpointStateDisabled
}
2025-11-12 23:15:13 +08:00
if ! opt . UseDataIntegrityProtections . Value {
s3Opt . RequestChecksumCalculation = aws . RequestChecksumCalculationWhenRequired
s3Opt . ResponseChecksumValidation = aws . ResponseChecksumValidationWhenRequired
}
2024-08-03 11:35:32 +01:00
// FIXME not ported from SDK v1 - not sure what this does
// s3Opt.UsEast1RegionalEndpoint = endpoints.RegionalS3UsEast1Endpoint
} )
2020-06-26 14:09:29 +01:00
2019-01-16 13:35:19 +00:00
if opt . Region != "" {
2024-08-03 11:35:32 +01:00
awsConfig . Region = opt . Region
2019-01-16 13:35:19 +00:00
}
2024-08-03 11:35:32 +01:00
if opt . STSEndpoint != "" {
// FIXME not sure if anyone is using this
// Haven't figured out how to do it with the v2 SDK
2025-10-14 17:50:28 +01:00
return nil , nil , errors . New ( "--s3-sts-endpoint is no longer supported with the v2 SDK - please make an issue" )
2019-01-16 13:35:19 +00:00
}
2024-08-03 11:35:32 +01:00
if opt . Endpoint != "" {
if ! strings . HasPrefix ( opt . Endpoint , "http" ) {
opt . Endpoint = "https://" + opt . Endpoint
2021-10-29 12:39:34 +01:00
}
2024-08-03 11:35:32 +01:00
options = append ( options , func ( s3Opt * s3 . Options ) {
s3Opt . BaseEndpoint = & opt . Endpoint
} )
2022-06-16 22:55:45 +01:00
}
2024-08-03 11:35:32 +01:00
2022-06-16 22:55:45 +01:00
if opt . V2Auth || opt . Region == "other-v2-signature" {
fs . Debugf ( nil , "Using v2 auth" )
2025-02-03 05:29:31 -06:00
if opt . Provider == "IBMCOS" && opt . IBMAPIKey != "" && opt . IBMInstanceID != "" {
options = append ( options , func ( s3Opt * s3 . Options ) {
s3Opt . HTTPSignerV4 = & IbmIamSigner { APIKey : opt . IBMAPIKey , InstanceID : opt . IBMInstanceID }
} )
} else {
options = append ( options , func ( s3Opt * s3 . Options ) {
s3Opt . HTTPSignerV4 = & v2Signer { opt : opt }
} )
}
2013-01-08 18:53:35 +00:00
}
2024-08-03 11:35:32 +01:00
2025-02-11 20:22:03 +00:00
// Fixup the request if needed
if ! opt . UseXID . Value || ! opt . SignAcceptEncoding . Value {
2024-08-05 15:48:29 +01:00
options = append ( options , func ( o * s3 . Options ) {
2025-02-11 20:22:03 +00:00
fixupRequest ( o , opt )
2024-08-05 15:48:29 +01:00
} )
}
2024-08-06 10:33:17 +01:00
// Enable SDK logging if requested
if opt . SDKLogMode != 0 {
awsConfig . ClientLogMode = aws . ClientLogMode ( opt . SDKLogMode )
awsConfig . Logger = s3logger { }
}
2024-08-03 11:35:32 +01:00
c := s3 . NewFromConfig ( awsConfig , options ... )
2025-10-14 17:50:28 +01:00
return c , provider , nil
2013-01-08 18:53:35 +00:00
}
2018-09-07 13:02:27 +02:00
func checkUploadChunkSize ( cs fs . SizeSuffix ) error {
if cs < minChunkSize {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "%s is less than %s" , cs , minChunkSize )
2018-09-07 13:02:27 +02:00
}
return nil
}
func ( f * Fs ) setUploadChunkSize ( cs fs . SizeSuffix ) ( old fs . SizeSuffix , err error ) {
err = checkUploadChunkSize ( cs )
if err == nil {
old , f . opt . ChunkSize = f . opt . ChunkSize , cs
}
return
}
2024-03-13 19:53:38 +08:00
func checkCopyCutoff ( cs fs . SizeSuffix ) error {
minCopySize := fs . SizeSuffixBase
if cs < minCopySize {
return fmt . Errorf ( "value is too small (%v is less than %v)" , cs , minCopySize )
}
return nil
}
2018-11-26 21:09:23 +00:00
func checkUploadCutoff ( cs fs . SizeSuffix ) error {
if cs > maxUploadCutoff {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "%s is greater than %s" , cs , maxUploadCutoff )
2018-11-26 21:09:23 +00:00
}
return nil
}
func ( f * Fs ) setUploadCutoff ( cs fs . SizeSuffix ) ( old fs . SizeSuffix , err error ) {
2023-11-24 16:32:06 +00:00
if f . opt . Provider != "Rclone" {
err = checkUploadCutoff ( cs )
}
2018-11-26 21:09:23 +00:00
if err == nil {
old , f . opt . UploadCutoff = f . opt . UploadCutoff , cs
}
return
}
2023-11-24 12:36:48 +00:00
func ( f * Fs ) setCopyCutoff ( cs fs . SizeSuffix ) ( old fs . SizeSuffix , err error ) {
2024-08-06 11:50:21 +01:00
if f . opt . CopyCutoff == math . MaxInt64 {
return f . opt . CopyCutoff , fmt . Errorf ( "--s3-copy-cutoff not supported: %w" , fs . ErrorNotImplemented )
}
2023-11-24 12:36:48 +00:00
err = checkUploadChunkSize ( cs )
if err == nil {
old , f . opt . CopyCutoff = f . opt . CopyCutoff , cs
}
return
}
2022-06-18 15:29:21 +08:00
// setEndpointValueForIDriveE2 gets user region endpoint against the Access Key details by calling the API
func setEndpointValueForIDriveE2 ( m configmap . Mapper ) ( err error ) {
value , ok := m . Get ( fs . ConfigProvider )
if ! ok || value != "IDrive" {
return
}
value , ok = m . Get ( "access_key_id" )
if ! ok || value == "" {
return
}
client := & http . Client { Timeout : time . Second * 3 }
// API to get user region endpoint against the Access Key details: https://www.idrive.com/e2/guides/get_region_endpoint
resp , err := client . Post ( "https://api.idrivee2.com/api/service/get_region_end_point" ,
"application/json" ,
2024-08-15 20:15:10 +02:00
strings . NewReader ( ` { "access_key": ` + strconv . Quote ( value ) + ` } ` ) )
2022-06-18 15:29:21 +08:00
if err != nil {
return
}
defer fs . CheckClose ( resp . Body , & err )
decoder := json . NewDecoder ( resp . Body )
var data = & struct {
RespCode int ` json:"resp_code" `
RespMsg string ` json:"resp_msg" `
DomainName string ` json:"domain_name" `
} { }
if err = decoder . Decode ( data ) ; err == nil && data . RespCode == 0 {
m . Set ( "endpoint" , data . DomainName )
}
return
}
2021-11-03 18:01:23 +00:00
// Set the provider quirks
//
2021-11-03 19:40:16 +00:00
// There should be no testing against opt.Provider anywhere in the
// code except in here to localise the setting of the quirks.
//
2023-09-21 12:35:23 +01:00
// Run the integration tests to check you have the quirks correct.
//
// go test -v -remote NewS3Provider:
2025-10-14 17:50:28 +01:00
func setQuirks ( opt * Options , provider * Provider ) {
// Set tristate to the ultimate default value or the override
// in provider.Quirks. Pass in the ultimate default as value.
set := func ( tristate * fs . Tristate , value bool , override * bool ) {
if override != nil {
value = * override
2021-11-03 19:40:16 +00:00
}
2025-10-14 17:50:28 +01:00
if ! tristate . Valid {
tristate . Valid = true
tristate . Value = value
2021-09-29 15:43:06 +02:00
}
2021-11-03 19:40:16 +00:00
}
2025-10-14 17:50:28 +01:00
// Set Path Style vs Virtual Host style
var virtualHostStyle = true // Default use bucket.provider.com instead of putting the bucket in the URL
if provider . Quirks . ForcePathStyle != nil {
// "don't force path style" so inverted
virtualHostStyle = ! * provider . Quirks . ForcePathStyle
}
2021-11-03 19:40:16 +00:00
if virtualHostStyle || opt . UseAccelerateEndpoint {
opt . ForcePathStyle = false
}
2021-11-03 18:01:23 +00:00
// Set the correct list version if not manually set
if opt . ListVersion == 0 {
2025-10-14 17:50:28 +01:00
if provider . Quirks . ListVersion != nil {
opt . ListVersion = * provider . Quirks . ListVersion
2021-11-03 19:40:16 +00:00
} else {
2025-10-14 17:50:28 +01:00
opt . ListVersion = 2
2021-11-03 18:01:23 +00:00
}
}
2022-02-27 15:47:31 +00:00
2025-10-14 17:50:28 +01:00
// Set the copy cutoff if not manually set
// Check equality with strings as config values get round tripped via strings
if opt . CopyCutoff . String ( ) == fs . SizeSuffix ( maxSizeForCopy ) . String ( ) {
if provider . Quirks . CopyCutoff != nil {
opt . CopyCutoff = fs . SizeSuffix ( * provider . Quirks . CopyCutoff )
}
2022-10-28 23:23:29 +01:00
}
2023-01-09 07:54:51 +01:00
2025-10-14 17:50:28 +01:00
// Clip the max upload parts to the quirk
if provider . Quirks . MaxUploadParts != nil {
opt . MaxUploadParts = min ( opt . MaxUploadParts , * provider . Quirks . MaxUploadParts )
2023-01-09 07:54:51 +01:00
}
2023-10-06 11:45:03 +01:00
2025-10-14 17:50:28 +01:00
// Clip the chunk size to the quirk
if provider . Quirks . MinChunkSize != nil {
opt . ChunkSize = max ( opt . ChunkSize , fs . SizeSuffix ( * provider . Quirks . MinChunkSize ) )
2023-10-06 11:45:03 +01:00
}
2023-11-14 12:39:50 +00:00
2025-10-14 17:50:28 +01:00
// Set new style Tristate quirks
set ( & opt . ListURLEncode , true , provider . Quirks . ListURLEncode )
set ( & opt . UseMultipartEtag , true , provider . Quirks . UseMultipartEtag )
set ( & opt . UseAcceptEncodingGzip , true , provider . Quirks . UseAcceptEncodingGzip )
2025-11-12 23:15:13 +08:00
set ( & opt . UseDataIntegrityProtections , false , provider . Quirks . UseDataIntegrityProtections )
2025-10-14 17:50:28 +01:00
set ( & opt . MightGzip , true , provider . Quirks . MightGzip )
set ( & opt . UseAlreadyExists , true , provider . Quirks . UseAlreadyExists )
set ( & opt . UseMultipartUploads , true , provider . Quirks . UseMultipartUploads )
2023-11-14 12:39:50 +00:00
if ! opt . UseMultipartUploads . Value {
opt . UploadCutoff = math . MaxInt64
}
2025-10-14 17:50:28 +01:00
set ( & opt . UseUnsignedPayload , true , provider . Quirks . UseUnsignedPayload )
set ( & opt . UseXID , true , provider . Quirks . UseXID )
set ( & opt . SignAcceptEncoding , true , provider . Quirks . SignAcceptEncoding )
2021-11-03 18:01:23 +00:00
}
2019-08-09 11:29:36 +01:00
// setRoot changes the root of the Fs
func ( f * Fs ) setRoot ( root string ) {
f . root = parsePath ( root )
f . rootBucket , f . rootDirectory = bucket . Split ( f . root )
}
2016-09-02 00:27:50 +03:00
// NewFs constructs an Fs from the path, bucket:path
2020-11-05 15:18:51 +00:00
func NewFs ( ctx context . Context , name , root string , m configmap . Mapper ) ( fs . Fs , error ) {
2018-05-14 18:06:57 +01:00
// Parse config into Options struct
opt := new ( Options )
err := configstruct . Set ( m , opt )
if err != nil {
return nil , err
}
2018-09-07 13:02:27 +02:00
err = checkUploadChunkSize ( opt . ChunkSize )
if err != nil {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "s3: chunk size: %w" , err )
2018-05-14 18:06:57 +01:00
}
2018-11-26 21:09:23 +00:00
err = checkUploadCutoff ( opt . UploadCutoff )
if err != nil {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "s3: upload cutoff: %w" , err )
2018-11-26 21:09:23 +00:00
}
2024-03-13 19:53:38 +08:00
err = checkCopyCutoff ( opt . CopyCutoff )
if err != nil {
return nil , fmt . Errorf ( "s3: --s3-copy-cutoff: %w" , err )
}
2022-07-26 17:58:57 +01:00
if opt . Versions && opt . VersionAt . IsSet ( ) {
2022-09-20 08:54:47 +02:00
return nil , errors . New ( "s3: can't use --s3-versions and --s3-version-at at the same time" )
2022-07-26 17:58:57 +01:00
}
2019-01-16 17:23:37 +00:00
if opt . BucketACL == "" {
opt . BucketACL = opt . ACL
}
2022-09-17 10:28:44 -06:00
if opt . SSECustomerKeyBase64 != "" && opt . SSECustomerKey != "" {
return nil , errors . New ( "s3: can't use sse_customer_key and sse_customer_key_base64 at the same time" )
} else if opt . SSECustomerKeyBase64 != "" {
// Decode the base64-encoded key and store it in the SSECustomerKey field
decoded , err := base64 . StdEncoding . DecodeString ( opt . SSECustomerKeyBase64 )
if err != nil {
return nil , fmt . Errorf ( "s3: Could not decode sse_customer_key_base64: %w" , err )
}
opt . SSECustomerKey = string ( decoded )
2024-08-07 10:14:58 +01:00
} else {
// Encode the raw key as base64
opt . SSECustomerKeyBase64 = base64 . StdEncoding . EncodeToString ( [ ] byte ( opt . SSECustomerKey ) )
2022-09-17 10:28:44 -06:00
}
2020-11-20 11:15:48 +00:00
if opt . SSECustomerKey != "" && opt . SSECustomerKeyMD5 == "" {
// calculate CustomerKeyMD5 if not supplied
md5sumBinary := md5 . Sum ( [ ] byte ( opt . SSECustomerKey ) )
opt . SSECustomerKeyMD5 = base64 . StdEncoding . EncodeToString ( md5sumBinary [ : ] )
}
2021-03-05 11:04:57 +00:00
srv := getClient ( ctx , opt )
2025-10-14 17:50:28 +01:00
c , provider , err := s3Connection ( ctx , opt , srv )
2013-01-08 18:53:35 +00:00
if err != nil {
return nil , err
}
2020-02-19 11:17:25 +01:00
2020-11-05 11:33:32 +00:00
ci := fs . GetConfig ( ctx )
2021-08-13 17:27:49 +01:00
pc := fs . NewPacer ( ctx , pacer . NewS3 ( pacer . MinSleep ( minSleep ) ) )
// Set pacer retries to 2 (1 try and 1 retry) because we are
// relying on SDK retry mechanism, but we allow 2 attempts to
// retry directory listings after XMLSyntaxError
pc . SetRetries ( 2 )
2015-11-07 11:14:46 +00:00
f := & Fs {
2021-10-14 15:49:38 +05:30
name : name ,
opt : * opt ,
ci : ci ,
ctx : ctx ,
c : c ,
2021-08-13 17:27:49 +01:00
pacer : pc ,
2021-10-14 15:49:38 +05:30
cache : bucket . NewCache ( ) ,
srv : srv ,
srvRest : rest . NewClient ( fshttp . NewClient ( ctx ) ) ,
2019-08-09 11:29:36 +01:00
}
2020-11-20 12:15:56 +00:00
if opt . ServerSideEncryption == "aws:kms" || opt . SSECustomerAlgorithm != "" {
// From: https://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html
//
// Objects encrypted by SSE-S3 or plaintext have ETags that are an MD5
// digest of their data.
//
// Objects encrypted by SSE-C or SSE-KMS have ETags that are not an
// MD5 digest of their object data.
f . etagIsNotMD5 = true
}
2024-09-13 18:56:22 +01:00
if opt . DirectoryBucket {
// Objects uploaded to directory buckets appear to have random ETags
//
// This doesn't appear to be documented
f . etagIsNotMD5 = true
// The normal API doesn't work for creating directory buckets, so don't try
f . opt . NoCheckBucket = true
}
2019-08-09 11:29:36 +01:00
f . setRoot ( root )
2017-08-09 15:27:43 +01:00
f . features = ( & fs . Features {
2019-08-09 11:29:36 +01:00
ReadMimeType : true ,
WriteMimeType : true ,
2022-05-24 12:32:39 +01:00
ReadMetadata : true ,
WriteMetadata : true ,
UserMetadata : true ,
2019-08-09 11:29:36 +01:00
BucketBased : true ,
BucketBasedRootOK : true ,
2025-10-14 17:50:28 +01:00
SetTier : provider . StorageClass . Len ( ) > 0 ,
GetTier : provider . StorageClass . Len ( ) > 0 ,
2020-06-19 10:28:34 +01:00
SlowModTime : true ,
2020-11-05 16:00:40 +00:00
} ) . Fill ( ctx , f )
2025-01-04 10:24:06 +00:00
if opt . Provider == "AWS" {
f . features . DoubleSlash = true
}
2025-10-10 14:29:22 +01:00
if opt . Provider == "Rabata" {
f . features . Copy = nil
}
2023-04-26 10:59:17 +01:00
if opt . DirectoryMarkers {
f . features . CanHaveEmptyDirectories = true
}
2023-02-02 19:12:00 +01:00
// f.listMultipartUploads()
2023-11-14 12:39:50 +00:00
if ! opt . UseMultipartUploads . Value {
fs . Debugf ( f , "Disabling multipart uploads" )
f . features . OpenChunkWriter = nil
}
2023-02-02 19:12:00 +01:00
2021-02-07 17:18:52 +00:00
if f . rootBucket != "" && f . rootDirectory != "" && ! opt . NoHeadObject && ! strings . HasSuffix ( root , "/" ) {
2020-11-20 11:15:48 +00:00
// Check to see if the (bucket,directory) is actually an existing file
oldRoot := f . root
newRoot , leaf := path . Split ( oldRoot )
f . setRoot ( newRoot )
_ , err := f . NewObject ( ctx , leaf )
2025-11-19 01:01:07 +08:00
if errors . Is ( err , fs . ErrorObjectNotFound ) {
2021-02-08 13:22:12 +00:00
// File doesn't exist or is a directory so return old f
f . setRoot ( oldRoot )
return f , nil
2014-05-05 19:52:52 +01:00
}
2025-11-19 01:01:07 +08:00
if err != nil {
return nil , err
}
2020-11-20 11:15:48 +00:00
// return an error with an fs which points to the parent
return f , fs . ErrorIsFile
2014-05-05 19:52:52 +01:00
}
2013-01-08 18:53:35 +00:00
return f , nil
}
2022-07-25 16:06:15 +01:00
// getMetaDataListing gets the metadata from the object unconditionally from the listing
//
// This is needed to find versioned objects from their paths.
//
// It may return info == nil and err == nil if a HEAD would be more appropriate
2024-08-03 11:35:32 +01:00
func ( f * Fs ) getMetaDataListing ( ctx context . Context , wantRemote string ) ( info * types . Object , versionID * string , err error ) {
2022-07-25 16:06:15 +01:00
bucket , bucketPath := f . split ( wantRemote )
2022-07-26 17:58:57 +01:00
// Strip the version string off if using versions
if f . opt . Versions {
var timestamp time . Time
timestamp , bucketPath = version . Remove ( bucketPath )
// If the path had no version string return no info, to force caller to look it up
if timestamp . IsZero ( ) {
return nil , nil , nil
}
2022-07-25 16:06:15 +01:00
}
2022-07-27 10:46:28 +01:00
err = f . list ( ctx , listOpt {
bucket : bucket ,
directory : bucketPath ,
2023-03-21 12:44:45 +00:00
prefix : f . rootDirectory ,
2022-07-27 10:46:28 +01:00
recurse : true ,
withVersions : f . opt . Versions ,
findFile : true ,
2022-07-26 17:58:57 +01:00
versionAt : f . opt . VersionAt ,
2023-11-08 15:29:23 +00:00
hidden : f . opt . VersionDeleted ,
2024-08-03 11:35:32 +01:00
} , func ( gotRemote string , object * types . Object , objectVersionID * string , isDirectory bool ) error {
2022-07-25 16:06:15 +01:00
if isDirectory {
return nil
}
if wantRemote != gotRemote {
return nil
}
info = object
versionID = objectVersionID
return errEndList // read only 1 item
} )
if err != nil {
if err == fs . ErrorDirNotFound {
return nil , nil , fs . ErrorObjectNotFound
}
return nil , nil , err
}
if info == nil {
return nil , nil , fs . ErrorObjectNotFound
}
return info , versionID , nil
}
2024-08-03 11:35:32 +01:00
// stringClone clones the string s into new memory. This is useful to
// stop us keeping references to small strings carved out of large XML
// responses.
func stringClone ( s string ) * string {
var sNew = strings . Clone ( s )
return & sNew
}
2022-12-08 12:41:23 +00:00
// stringClonePointer clones the string pointed to by sp into new
// memory. This is useful to stop us keeping references to small
// strings carved out of large XML responses.
func stringClonePointer ( sp * string ) * string {
if sp == nil {
return nil
}
2024-08-03 11:35:32 +01:00
var s = strings . Clone ( * sp )
2022-12-08 12:41:23 +00:00
return & s
}
2016-06-25 21:58:34 +01:00
// Return an Object from a path
2013-01-08 18:53:35 +00:00
//
2022-08-05 16:35:41 +01:00
// If it can't be found it returns the error ErrorObjectNotFound.
2024-08-03 11:35:32 +01:00
func ( f * Fs ) newObjectWithInfo ( ctx context . Context , remote string , info * types . Object , versionID * string ) ( obj fs . Object , err error ) {
2015-11-07 11:14:46 +00:00
o := & Object {
fs : f ,
2013-01-08 18:53:35 +00:00
remote : remote ,
}
2022-07-26 17:58:57 +01:00
if info == nil && ( ( f . opt . Versions && version . Match ( remote ) ) || f . opt . VersionAt . IsSet ( ) ) {
// If versions, have to read the listing to find the correct version ID
2022-07-25 16:06:15 +01:00
info , versionID , err = f . getMetaDataListing ( ctx , remote )
if err != nil {
return nil , err
}
}
2013-01-08 18:53:35 +00:00
if info != nil {
// Set info but not meta
2014-12-23 12:09:02 +00:00
if info . LastModified == nil {
2017-02-09 11:01:20 +00:00
fs . Logf ( o , "Failed to read last modified" )
2013-06-27 20:13:07 +01:00
o . lastModified = time . Now ( )
2014-12-23 12:09:02 +00:00
} else {
o . lastModified = * info . LastModified
2013-01-08 18:53:35 +00:00
}
2024-08-03 11:35:32 +01:00
o . setMD5FromEtag ( deref ( info . ETag ) )
o . bytes = deref ( info . Size )
o . storageClass = stringClone ( string ( info . StorageClass ) )
2022-12-13 11:50:51 +00:00
o . versionID = stringClonePointer ( versionID )
2023-11-08 15:29:23 +00:00
// If is delete marker, show that metadata has been read as there is none to read
if info . Size == isDeleteMarker {
o . meta = map [ string ] string { }
}
2021-04-28 19:05:54 +09:00
} else if ! o . fs . opt . NoHeadObject {
2019-06-17 10:34:30 +02:00
err := o . readMetaData ( ctx ) // reads info and meta, returning an error
2013-01-08 18:53:35 +00:00
if err != nil {
2016-06-25 21:23:20 +01:00
return nil , err
2013-01-08 18:53:35 +00:00
}
}
2016-06-25 21:23:20 +01:00
return o , nil
2013-01-08 18:53:35 +00:00
}
2016-06-25 21:23:20 +01:00
// NewObject finds the Object at remote. If it can't be found
// it returns the error fs.ErrorObjectNotFound.
2019-06-17 10:34:30 +02:00
func ( f * Fs ) NewObject ( ctx context . Context , remote string ) ( fs . Object , error ) {
2022-07-25 16:06:15 +01:00
return f . newObjectWithInfo ( ctx , remote , nil , nil )
2013-01-08 18:53:35 +00:00
}
2019-01-16 13:35:19 +00:00
// Gets the bucket location
2021-03-16 15:50:02 +00:00
func ( f * Fs ) getBucketLocation ( ctx context . Context , bucket string ) ( string , error ) {
2024-08-03 11:35:32 +01:00
region , err := manager . GetBucketRegion ( ctx , f . c , bucket )
2019-01-16 13:35:19 +00:00
if err != nil {
return "" , err
}
2022-10-29 09:37:08 +02:00
return region , nil
2019-01-16 13:35:19 +00:00
}
// Updates the region for the bucket by reading the region from the
// bucket then updating the session.
2021-03-16 15:50:02 +00:00
func ( f * Fs ) updateRegionForBucket ( ctx context . Context , bucket string ) error {
region , err := f . getBucketLocation ( ctx , bucket )
2019-01-16 13:35:19 +00:00
if err != nil {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "reading bucket location failed: %w" , err )
2019-01-16 13:35:19 +00:00
}
2024-08-03 11:35:32 +01:00
if f . opt . Endpoint != "" {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "can't set region to %q as endpoint is set" , region )
2019-01-16 13:35:19 +00:00
}
2024-08-03 11:35:32 +01:00
if f . opt . Region == region {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "region is already %q - not updating" , region )
2019-01-16 13:35:19 +00:00
}
// Make a new session with the new region
oldRegion := f . opt . Region
f . opt . Region = region
2025-10-14 17:50:28 +01:00
c , _ , err := s3Connection ( f . ctx , & f . opt , f . srv )
2019-01-16 13:35:19 +00:00
if err != nil {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "creating new session failed: %w" , err )
2019-01-16 13:35:19 +00:00
}
f . c = c
fs . Logf ( f , "Switched region to %q from %q" , region , oldRegion )
return nil
}
2022-07-25 15:56:17 +01:00
// Common interface for bucket listers
type bucketLister interface {
2022-07-26 17:58:57 +01:00
List ( ctx context . Context ) ( resp * s3 . ListObjectsV2Output , versionIDs [ ] * string , err error )
2022-07-25 15:56:17 +01:00
URLEncodeListings ( bool )
}
// V1 bucket lister
type v1List struct {
f * Fs
req s3 . ListObjectsInput
}
// Create a new V1 bucket lister
func ( f * Fs ) newV1List ( req * s3 . ListObjectsV2Input ) bucketLister {
l := & v1List {
f : f ,
}
// Convert v2 req into v1 req
2022-07-28 11:49:19 +01:00
//structs.SetFrom(&l.req, req)
setFrom_s3ListObjectsInput_s3ListObjectsV2Input ( & l . req , req )
2022-07-25 15:56:17 +01:00
return l
}
// List a bucket with V1 listing
2022-07-26 17:58:57 +01:00
func ( ls * v1List ) List ( ctx context . Context ) ( resp * s3 . ListObjectsV2Output , versionIDs [ ] * string , err error ) {
2024-08-03 11:35:32 +01:00
respv1 , err := ls . f . c . ListObjects ( ctx , & ls . req )
2022-07-25 15:56:17 +01:00
if err != nil {
return nil , nil , err
}
// Set up the request for next time
ls . req . Marker = respv1 . NextMarker
2024-08-03 11:35:32 +01:00
if deref ( respv1 . IsTruncated ) && ls . req . Marker == nil {
2022-07-25 15:56:17 +01:00
if len ( respv1 . Contents ) == 0 {
return nil , nil , errors . New ( "s3 protocol error: received listing v1 with IsTruncated set, no NextMarker and no Contents" )
}
// Use the last Key received if no NextMarker and isTruncated
ls . req . Marker = respv1 . Contents [ len ( respv1 . Contents ) - 1 ] . Key
}
// If we are URL encoding then must decode the marker
2024-08-03 11:35:32 +01:00
if ls . req . Marker != nil && ls . req . EncodingType == types . EncodingTypeUrl {
2022-07-25 15:56:17 +01:00
* ls . req . Marker , err = url . QueryUnescape ( * ls . req . Marker )
if err != nil {
return nil , nil , fmt . Errorf ( "failed to URL decode Marker %q: %w" , * ls . req . Marker , err )
}
}
// convert v1 resp into v2 resp
resp = new ( s3 . ListObjectsV2Output )
2022-07-28 11:49:19 +01:00
//structs.SetFrom(resp, respv1)
setFrom_s3ListObjectsV2Output_s3ListObjectsOutput ( resp , respv1 )
2022-07-25 15:56:17 +01:00
return resp , nil , nil
}
// URL Encode the listings
func ( ls * v1List ) URLEncodeListings ( encode bool ) {
if encode {
2024-08-03 11:35:32 +01:00
ls . req . EncodingType = types . EncodingTypeUrl
2022-07-25 15:56:17 +01:00
} else {
2024-08-03 11:35:32 +01:00
ls . req . EncodingType = types . EncodingType ( "" )
2022-07-25 15:56:17 +01:00
}
}
// V2 bucket lister
type v2List struct {
f * Fs
req s3 . ListObjectsV2Input
}
// Create a new V2 bucket lister
func ( f * Fs ) newV2List ( req * s3 . ListObjectsV2Input ) bucketLister {
return & v2List {
f : f ,
req : * req ,
}
}
// Do a V2 listing
2022-07-26 17:58:57 +01:00
func ( ls * v2List ) List ( ctx context . Context ) ( resp * s3 . ListObjectsV2Output , versionIDs [ ] * string , err error ) {
2024-08-03 11:35:32 +01:00
resp , err = ls . f . c . ListObjectsV2 ( ctx , & ls . req )
2022-11-10 06:52:59 -05:00
if err != nil {
return nil , nil , err
}
2024-08-03 11:35:32 +01:00
if deref ( resp . IsTruncated ) && ( resp . NextContinuationToken == nil || * resp . NextContinuationToken == "" ) {
2022-12-13 12:19:00 +00:00
return nil , nil , errors . New ( "s3 protocol error: received listing v2 with IsTruncated set and no NextContinuationToken. Should you be using `--s3-list-version 1`?" )
2022-12-05 16:59:48 +00:00
}
2022-07-25 15:56:17 +01:00
ls . req . ContinuationToken = resp . NextContinuationToken
2022-11-10 06:52:59 -05:00
return resp , nil , nil
2022-07-25 15:56:17 +01:00
}
// URL Encode the listings
func ( ls * v2List ) URLEncodeListings ( encode bool ) {
if encode {
2024-08-03 11:35:32 +01:00
ls . req . EncodingType = types . EncodingTypeUrl
2022-07-25 15:56:17 +01:00
} else {
2024-08-03 11:35:32 +01:00
ls . req . EncodingType = types . EncodingType ( "" )
2022-07-25 15:56:17 +01:00
}
}
2022-07-25 16:06:15 +01:00
// Versions bucket lister
type versionsList struct {
2022-07-26 17:58:57 +01:00
f * Fs
req s3 . ListObjectVersionsInput
versionAt time . Time // set if we want only versions before this
usingVersionAt bool // set if we need to use versionAt
hidden bool // set to see hidden versions
lastKeySent string // last Key sent to the receiving function
2022-07-25 16:06:15 +01:00
}
// Create a new Versions bucket lister
2022-07-26 17:58:57 +01:00
func ( f * Fs ) newVersionsList ( req * s3 . ListObjectsV2Input , hidden bool , versionAt time . Time ) bucketLister {
2022-07-25 16:06:15 +01:00
l := & versionsList {
2022-07-26 17:58:57 +01:00
f : f ,
versionAt : versionAt ,
usingVersionAt : ! versionAt . IsZero ( ) ,
hidden : hidden ,
2022-07-25 16:06:15 +01:00
}
// Convert v2 req into withVersions req
2022-07-28 11:49:19 +01:00
//structs.SetFrom(&l.req, req)
setFrom_s3ListObjectVersionsInput_s3ListObjectsV2Input ( & l . req , req )
2022-07-25 16:06:15 +01:00
return l
}
2024-08-03 11:35:32 +01:00
// Any types.Object or types.ObjectVersion with this as their Size are delete markers
2022-07-25 16:06:15 +01:00
var isDeleteMarker = new ( int64 )
2024-08-03 11:35:32 +01:00
// Compare two types.ObjectVersions, sorted alphabetically by key with
2022-07-26 17:58:57 +01:00
// the newest first if the Keys match or the one with IsLatest set if
// everything matches.
2024-08-03 11:35:32 +01:00
func versionLess ( a , b * types . ObjectVersion ) bool {
2022-07-26 17:58:57 +01:00
if a == nil || a . Key == nil || a . LastModified == nil {
return true
}
if b == nil || b . Key == nil || b . LastModified == nil {
return false
}
if * a . Key < * b . Key {
return true
}
if * a . Key > * b . Key {
return false
}
2024-05-31 15:14:40 +02:00
dt := a . LastModified . Sub ( * b . LastModified )
2022-07-26 17:58:57 +01:00
if dt > 0 {
return true
}
if dt < 0 {
return false
}
2024-08-03 11:35:32 +01:00
if deref ( a . IsLatest ) {
2022-07-26 17:58:57 +01:00
return true
}
return false
}
// Merge the DeleteMarkers into the Versions.
//
// These are delivered by S3 sorted by key then by LastUpdated
// newest first but annoyingly the SDK splits them up into two
// so we need to merge them back again
//
// We do this by converting the s3.DeleteEntry into
2024-08-03 11:35:32 +01:00
// types.ObjectVersion with Size = isDeleteMarker to tell them apart
2022-07-26 17:58:57 +01:00
//
// We then merge them back into the Versions in the correct order
2024-08-03 11:35:32 +01:00
func mergeDeleteMarkers ( oldVersions [ ] types . ObjectVersion , deleteMarkers [ ] types . DeleteMarkerEntry ) ( newVersions [ ] types . ObjectVersion ) {
newVersions = make ( [ ] types . ObjectVersion , 0 , len ( oldVersions ) + len ( deleteMarkers ) )
2022-07-26 17:58:57 +01:00
for _ , deleteMarker := range deleteMarkers {
2024-08-03 11:35:32 +01:00
var obj types . ObjectVersion
2022-07-28 11:49:19 +01:00
//structs.SetFrom(obj, deleteMarker)
2024-08-03 11:35:32 +01:00
setFrom_typesObjectVersion_typesDeleteMarkerEntry ( & obj , & deleteMarker )
2022-07-26 17:58:57 +01:00
obj . Size = isDeleteMarker
2024-08-03 11:35:32 +01:00
for len ( oldVersions ) > 0 && versionLess ( & oldVersions [ 0 ] , & obj ) {
2022-07-26 17:58:57 +01:00
newVersions = append ( newVersions , oldVersions [ 0 ] )
oldVersions = oldVersions [ 1 : ]
}
newVersions = append ( newVersions , obj )
}
// Merge any remaining versions
newVersions = append ( newVersions , oldVersions ... )
return newVersions
}
2022-07-25 16:06:15 +01:00
// List a bucket with versions
2022-07-26 17:58:57 +01:00
func ( ls * versionsList ) List ( ctx context . Context ) ( resp * s3 . ListObjectsV2Output , versionIDs [ ] * string , err error ) {
2024-08-03 11:35:32 +01:00
respVersions , err := ls . f . c . ListObjectVersions ( ctx , & ls . req )
2022-07-25 16:06:15 +01:00
if err != nil {
return nil , nil , err
}
// Set up the request for next time
ls . req . KeyMarker = respVersions . NextKeyMarker
ls . req . VersionIdMarker = respVersions . NextVersionIdMarker
2024-08-03 11:35:32 +01:00
if deref ( respVersions . IsTruncated ) && ls . req . KeyMarker == nil {
2023-11-21 10:30:47 +00:00
return nil , nil , errors . New ( "s3 protocol error: received versions listing with IsTruncated set with no NextKeyMarker" )
}
2022-07-25 16:06:15 +01:00
// If we are URL encoding then must decode the marker
2024-08-03 11:35:32 +01:00
if ls . req . KeyMarker != nil && ls . req . EncodingType == types . EncodingTypeUrl {
2022-07-25 16:06:15 +01:00
* ls . req . KeyMarker , err = url . QueryUnescape ( * ls . req . KeyMarker )
if err != nil {
return nil , nil , fmt . Errorf ( "failed to URL decode KeyMarker %q: %w" , * ls . req . KeyMarker , err )
}
}
// convert Versions resp into v2 resp
resp = new ( s3 . ListObjectsV2Output )
2022-07-28 11:49:19 +01:00
//structs.SetFrom(resp, respVersions)
setFrom_s3ListObjectsV2Output_s3ListObjectVersionsOutput ( resp , respVersions )
2022-07-25 16:06:15 +01:00
2024-08-03 11:35:32 +01:00
// Merge in delete Markers as types.ObjectVersion if we need them
2022-07-26 17:58:57 +01:00
if ls . hidden || ls . usingVersionAt {
respVersions . Versions = mergeDeleteMarkers ( respVersions . Versions , respVersions . DeleteMarkers )
}
2024-08-03 11:35:32 +01:00
// Convert the Versions and the DeleteMarkers into an array of types.Object
2022-07-25 16:06:15 +01:00
//
// These are returned in the order that they are stored with the most recent first.
// With the annoyance that the Versions and DeleteMarkers are split into two
2024-08-03 11:35:32 +01:00
objs := make ( [ ] types . Object , 0 , len ( respVersions . Versions ) )
2022-07-25 16:06:15 +01:00
for _ , objVersion := range respVersions . Versions {
2022-07-26 17:58:57 +01:00
if ls . usingVersionAt {
if objVersion . LastModified . After ( ls . versionAt ) {
// Ignore versions that were created after the specified time
continue
}
if * objVersion . Key == ls . lastKeySent {
// Ignore versions before the already returned version
continue
}
}
ls . lastKeySent = * objVersion . Key
// Don't send delete markers if we don't want hidden things
if ! ls . hidden && objVersion . Size == isDeleteMarker {
continue
}
2024-08-03 11:35:32 +01:00
var obj types . Object
2022-07-28 11:49:19 +01:00
//structs.SetFrom(obj, objVersion)
2024-08-03 11:35:32 +01:00
setFrom_typesObject_typesObjectVersion ( & obj , & objVersion )
2022-07-25 16:06:15 +01:00
// Adjust the file names
2024-08-03 11:35:32 +01:00
if ! ls . usingVersionAt && ( ! deref ( objVersion . IsLatest ) || objVersion . Size == isDeleteMarker ) {
2022-07-25 16:06:15 +01:00
if obj . Key != nil && objVersion . LastModified != nil {
* obj . Key = version . Add ( * obj . Key , * objVersion . LastModified )
}
}
objs = append ( objs , obj )
versionIDs = append ( versionIDs , objVersion . VersionId )
}
resp . Contents = objs
return resp , versionIDs , nil
}
// URL Encode the listings
func ( ls * versionsList ) URLEncodeListings ( encode bool ) {
if encode {
2024-08-03 11:35:32 +01:00
ls . req . EncodingType = types . EncodingTypeUrl
2022-07-25 16:06:15 +01:00
} else {
2024-08-03 11:35:32 +01:00
ls . req . EncodingType = types . EncodingType ( "" )
2022-07-25 16:06:15 +01:00
}
}
2016-04-21 20:06:21 +01:00
// listFn is called from list to handle an object.
2024-08-03 11:35:32 +01:00
type listFn func ( remote string , object * types . Object , versionID * string , isDirectory bool ) error
2022-07-25 16:06:15 +01:00
// errEndList is a sentinel used to end the list iteration now.
// listFn should return it to end the iteration with no errors.
var errEndList = errors . New ( "end list" )
2016-04-21 20:06:21 +01:00
2022-07-27 10:46:28 +01:00
// list options
type listOpt struct {
2023-02-27 15:32:59 +00:00
bucket string // bucket to list
directory string // directory with bucket
prefix string // prefix to remove from listing
addBucket bool // if set, the bucket is added to the start of the remote
recurse bool // if set, recurse to read sub directories
withVersions bool // if set, versions are produced
hidden bool // if set, return delete markers as objects with size == isDeleteMarker
findFile bool // if set, it will look for files called (bucket, directory)
versionAt fs . Time // if set only show versions <= this time
noSkipMarkers bool // if set return dir marker objects
2023-09-06 12:42:03 +01:00
restoreStatus bool // if set return restore status in listing too
2022-07-27 10:46:28 +01:00
}
// list lists the objects into the function supplied with the opt
// supplied.
func ( f * Fs ) list ( ctx context . Context , opt listOpt , fn listFn ) error {
2023-03-21 12:44:45 +00:00
if opt . prefix != "" {
opt . prefix += "/"
}
2022-07-27 10:46:28 +01:00
if ! opt . findFile {
2025-01-04 10:24:06 +00:00
if opt . directory != "" && ( opt . prefix == "" && ! bucket . IsAllSlashes ( opt . directory ) || opt . prefix != "" && ! strings . HasSuffix ( opt . directory , "/" ) ) {
2022-07-27 10:46:28 +01:00
opt . directory += "/"
2022-07-25 16:06:15 +01:00
}
2016-04-23 21:46:52 +01:00
}
2014-05-05 18:25:32 +01:00
delimiter := ""
2022-07-27 10:46:28 +01:00
if ! opt . recurse {
2014-05-05 18:25:32 +01:00
delimiter = "/"
}
2019-09-16 20:25:55 +01:00
// URL encode the listings so we can use control characters in object names
// See: https://github.com/aws/aws-sdk-go/issues/1914
//
// However this doesn't work perfectly under Ceph (and hence DigitalOcean/Dreamhost) because
// it doesn't encode CommonPrefixes.
// See: https://tracker.ceph.com/issues/41870
//
// This does not work under IBM COS also: See https://github.com/rclone/rclone/issues/3345
// though maybe it does on some versions.
//
// This does work with minio but was only added relatively recently
// https://github.com/minio/minio/pull/7265
//
// So we enable only on providers we know supports it properly, all others can retry when a
// XML Syntax error is detected.
2021-11-03 19:40:16 +00:00
urlEncodeListings := f . opt . ListURLEncode . Value
2022-07-25 15:56:17 +01:00
req := s3 . ListObjectsV2Input {
2022-07-27 10:46:28 +01:00
Bucket : & opt . bucket ,
2022-07-25 15:56:17 +01:00
Delimiter : & delimiter ,
2022-07-27 10:46:28 +01:00
Prefix : & opt . directory ,
2022-07-25 15:56:17 +01:00
MaxKeys : & f . opt . ListChunk ,
}
2023-09-06 12:42:03 +01:00
if opt . restoreStatus {
2024-08-03 11:35:32 +01:00
req . OptionalObjectAttributes = [ ] types . OptionalObjectAttributes { types . OptionalObjectAttributesRestoreStatus }
2023-09-06 12:42:03 +01:00
}
2022-07-25 15:56:17 +01:00
if f . opt . RequesterPays {
2024-08-03 11:35:32 +01:00
req . RequestPayer = types . RequestPayerRequester
2022-07-25 15:56:17 +01:00
}
var listBucket bucketLister
switch {
2022-07-26 17:58:57 +01:00
case opt . withVersions || opt . versionAt . IsSet ( ) :
listBucket = f . newVersionsList ( & req , opt . hidden , time . Time ( opt . versionAt ) )
2022-07-25 15:56:17 +01:00
case f . opt . ListVersion == 1 :
listBucket = f . newV1List ( & req )
default :
listBucket = f . newV2List ( & req )
}
2023-04-26 10:59:17 +01:00
foundItems := 0
2015-02-10 17:58:29 +00:00
for {
2021-03-02 23:36:50 +01:00
var resp * s3 . ListObjectsV2Output
2018-09-03 16:41:04 +12:00
var err error
2022-07-25 16:06:15 +01:00
var versionIDs [ ] * string
2018-09-03 16:41:04 +12:00
err = f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
2022-07-25 15:56:17 +01:00
listBucket . URLEncodeListings ( urlEncodeListings )
2022-07-26 17:58:57 +01:00
resp , versionIDs , err = listBucket . List ( ctx )
2019-09-16 20:25:55 +01:00
if err != nil && ! urlEncodeListings {
2024-08-03 11:35:32 +01:00
var xmlErr * xml . SyntaxError
if errors . As ( err , & xmlErr ) {
// Retry the listing with URL encoding as there were characters that XML can't encode
urlEncodeListings = true
fs . Debugf ( f , "Retrying listing because of characters which can't be XML encoded" )
return true , err
2019-09-16 20:25:55 +01:00
}
}
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2018-09-03 16:41:04 +12:00
} )
2015-02-10 17:58:29 +00:00
if err != nil {
2024-08-03 11:35:32 +01:00
if getHTTPStatusCode ( err ) == http . StatusNotFound {
err = fs . ErrorDirNotFound
2017-06-11 22:43:31 +01:00
}
2019-08-09 11:29:36 +01:00
if f . rootBucket == "" {
// if listing from the root ignore wrong region requests returning
// empty directory
2024-08-03 11:35:32 +01:00
// 301 if wrong region for bucket
if getHTTPStatusCode ( err ) == http . StatusMovedPermanently {
fs . Errorf ( f , "Can't change region for bucket %q with no bucket specified" , opt . bucket )
return nil
2019-08-09 11:29:36 +01:00
}
}
2016-04-21 20:06:21 +01:00
return err
}
2022-07-27 10:46:28 +01:00
if ! opt . recurse {
2023-04-26 10:59:17 +01:00
foundItems += len ( resp . CommonPrefixes )
2016-04-21 20:06:21 +01:00
for _ , commonPrefix := range resp . CommonPrefixes {
if commonPrefix . Prefix == nil {
2017-02-09 11:01:20 +00:00
fs . Logf ( f , "Nil common prefix received" )
2016-04-21 20:06:21 +01:00
continue
2015-02-10 17:58:29 +00:00
}
2016-04-21 20:06:21 +01:00
remote := * commonPrefix . Prefix
2019-09-16 20:25:55 +01:00
if urlEncodeListings {
remote , err = url . QueryUnescape ( remote )
if err != nil {
fs . Logf ( f , "failed to URL decode %q in listing common prefix: %v" , * commonPrefix . Prefix , err )
continue
}
2019-07-23 12:24:10 +01:00
}
2020-01-14 17:33:35 +00:00
remote = f . opt . Enc . ToStandardPath ( remote )
2022-07-27 10:46:28 +01:00
if ! strings . HasPrefix ( remote , opt . prefix ) {
2025-01-04 10:24:06 +00:00
fs . Logf ( f , "Odd directory name received %q" , remote )
2016-04-21 20:06:21 +01:00
continue
}
2022-07-27 10:46:28 +01:00
remote = remote [ len ( opt . prefix ) : ]
2025-01-04 10:24:06 +00:00
// Trim one slash off the remote name
remote , _ = strings . CutSuffix ( remote , "/" )
if remote == "" || bucket . IsAllSlashes ( remote ) {
remote += "/"
}
2022-07-27 10:46:28 +01:00
if opt . addBucket {
2023-03-03 10:45:29 +00:00
remote = bucket . Join ( opt . bucket , remote )
2019-08-09 11:29:36 +01:00
}
2024-08-03 11:35:32 +01:00
err = fn ( remote , & types . Object { Key : & remote } , nil , true )
2016-04-21 20:06:21 +01:00
if err != nil {
2022-07-25 16:06:15 +01:00
if err == errEndList {
return nil
}
2016-04-21 20:06:21 +01:00
return err
2013-01-08 18:53:35 +00:00
}
}
2016-04-21 20:06:21 +01:00
}
2023-04-26 10:59:17 +01:00
foundItems += len ( resp . Contents )
2022-07-25 16:06:15 +01:00
for i , object := range resp . Contents {
2025-07-14 12:25:10 -04:00
remote := * stringClone ( deref ( object . Key ) )
2019-09-16 20:25:55 +01:00
if urlEncodeListings {
remote , err = url . QueryUnescape ( remote )
if err != nil {
2024-08-03 11:35:32 +01:00
fs . Logf ( f , "failed to URL decode %q in listing: %v" , deref ( object . Key ) , err )
2019-09-16 20:25:55 +01:00
continue
}
2019-07-23 12:24:10 +01:00
}
2020-01-14 17:33:35 +00:00
remote = f . opt . Enc . ToStandardPath ( remote )
2022-07-27 10:46:28 +01:00
if ! strings . HasPrefix ( remote , opt . prefix ) {
2019-08-09 11:29:36 +01:00
fs . Logf ( f , "Odd name received %q" , remote )
2016-04-21 20:06:21 +01:00
continue
2014-12-23 12:09:02 +00:00
}
2023-04-26 10:59:17 +01:00
isDirectory := ( remote == "" || strings . HasSuffix ( remote , "/" ) ) && object . Size != nil && * object . Size == 0
2018-03-19 17:41:46 +00:00
// is this a directory marker?
2023-04-26 10:59:17 +01:00
if isDirectory {
if opt . noSkipMarkers {
// process directory markers as files
isDirectory = false
2024-05-31 15:18:56 +02:00
} else if remote == f . opt . Enc . ToStandardPath ( opt . directory ) {
2023-04-26 10:59:17 +01:00
// Don't insert the root directory
2024-05-31 15:18:56 +02:00
continue
2023-04-26 10:59:17 +01:00
}
2018-03-19 17:41:46 +00:00
}
2023-06-10 14:18:59 +01:00
remote = remote [ len ( opt . prefix ) : ]
2023-09-24 16:51:58 +01:00
if isDirectory {
// process directory markers as directories
2025-06-16 15:07:05 +01:00
remote , _ = strings . CutSuffix ( remote , "/" )
2023-09-24 16:51:58 +01:00
}
2023-06-10 14:18:59 +01:00
if opt . addBucket {
remote = bucket . Join ( opt . bucket , remote )
}
2022-07-25 16:06:15 +01:00
if versionIDs != nil {
2024-08-03 11:35:32 +01:00
err = fn ( remote , & object , versionIDs [ i ] , isDirectory )
2022-07-25 16:06:15 +01:00
} else {
2024-08-03 11:35:32 +01:00
err = fn ( remote , & object , nil , isDirectory )
2022-07-25 16:06:15 +01:00
}
2016-04-21 20:06:21 +01:00
if err != nil {
2022-07-25 16:06:15 +01:00
if err == errEndList {
return nil
}
2016-04-21 20:06:21 +01:00
return err
2014-12-23 12:09:02 +00:00
}
2015-02-10 17:58:29 +00:00
}
2024-08-03 11:35:32 +01:00
if ! deref ( resp . IsTruncated ) {
2016-04-21 20:06:21 +01:00
break
}
2014-05-05 18:25:32 +01:00
}
2023-04-26 10:59:17 +01:00
if f . opt . DirectoryMarkers && foundItems == 0 && opt . directory != "" {
// Determine whether the directory exists or not by whether it has a marker
req := s3 . HeadObjectInput {
Bucket : & opt . bucket ,
Key : & opt . directory ,
}
_ , err := f . headObject ( ctx , & req )
if err != nil {
if err == fs . ErrorObjectNotFound {
return fs . ErrorDirNotFound
}
return err
}
}
2016-04-21 20:06:21 +01:00
return nil
2014-05-05 18:25:32 +01:00
}
2017-06-30 10:54:14 +01:00
// Convert a list item into a DirEntry
2024-08-03 11:35:32 +01:00
func ( f * Fs ) itemToDirEntry ( ctx context . Context , remote string , object * types . Object , versionID * string , isDirectory bool ) ( fs . DirEntry , error ) {
2017-06-11 22:43:31 +01:00
if isDirectory {
size := int64 ( 0 )
if object . Size != nil {
size = * object . Size
}
2017-06-30 13:37:29 +01:00
d := fs . NewDir ( remote , time . Time { } ) . SetSize ( size )
2017-06-11 22:43:31 +01:00
return d , nil
2016-04-21 20:06:21 +01:00
}
2022-07-25 16:06:15 +01:00
o , err := f . newObjectWithInfo ( ctx , remote , object , versionID )
2017-06-11 22:43:31 +01:00
if err != nil {
return nil , err
}
return o , nil
}
// listDir lists files and directories to out
2024-11-25 12:50:27 +00:00
func ( f * Fs ) listDir ( ctx context . Context , bucket , directory , prefix string , addBucket bool , callback func ( fs . DirEntry ) error ) ( err error ) {
2016-04-21 20:06:21 +01:00
// List the objects and directories
2022-07-27 10:46:28 +01:00
err = f . list ( ctx , listOpt {
bucket : bucket ,
directory : directory ,
prefix : prefix ,
addBucket : addBucket ,
withVersions : f . opt . Versions ,
2022-07-26 17:58:57 +01:00
versionAt : f . opt . VersionAt ,
2023-11-08 15:29:23 +00:00
hidden : f . opt . VersionDeleted ,
2024-08-03 11:35:32 +01:00
} , func ( remote string , object * types . Object , versionID * string , isDirectory bool ) error {
2022-07-25 16:06:15 +01:00
entry , err := f . itemToDirEntry ( ctx , remote , object , versionID , isDirectory )
2017-06-11 22:43:31 +01:00
if err != nil {
return err
}
if entry != nil {
2024-11-25 12:50:27 +00:00
return callback ( entry )
2016-04-21 20:06:21 +01:00
}
return nil
} )
if err != nil {
2024-11-25 12:50:27 +00:00
return err
2016-04-21 20:06:21 +01:00
}
2018-03-01 12:11:34 +00:00
// bucket must be present if listing succeeded
2019-08-09 11:29:36 +01:00
f . cache . MarkOK ( bucket )
2024-11-25 12:50:27 +00:00
return nil
2016-04-21 20:06:21 +01:00
}
// listBuckets lists the buckets to out
2019-08-22 21:30:55 +01:00
func ( f * Fs ) listBuckets ( ctx context . Context ) ( entries fs . DirEntries , err error ) {
2016-04-21 20:06:21 +01:00
req := s3 . ListBucketsInput { }
2018-09-03 16:41:04 +12:00
var resp * s3 . ListBucketsOutput
err = f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
resp , err = f . c . ListBuckets ( ctx , & req )
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2018-09-03 16:41:04 +12:00
} )
2016-04-21 20:06:21 +01:00
if err != nil {
2017-06-11 22:43:31 +01:00
return nil , err
2016-04-21 20:06:21 +01:00
}
for _ , bucket := range resp . Buckets {
2024-08-03 11:35:32 +01:00
bucketName := f . opt . Enc . ToStandardName ( deref ( bucket . Name ) )
2019-08-09 11:29:36 +01:00
f . cache . MarkOK ( bucketName )
2024-08-03 11:35:32 +01:00
d := fs . NewDir ( bucketName , deref ( bucket . CreationDate ) )
2017-06-11 22:43:31 +01:00
entries = append ( entries , d )
2014-05-05 18:25:32 +01:00
}
2017-06-11 22:43:31 +01:00
return entries , nil
2013-01-08 18:53:35 +00:00
}
2017-06-11 22:43:31 +01:00
// List the objects and directories in dir into entries. The
// entries can be returned in any order but should be for a
// complete directory.
//
// dir should be "" to list the root, and should not have
// trailing slashes.
//
// This should return ErrDirNotFound if the directory isn't
// found.
2019-06-17 10:34:30 +02:00
func ( f * Fs ) List ( ctx context . Context , dir string ) ( entries fs . DirEntries , err error ) {
2024-11-25 12:50:27 +00:00
return list . WithListP ( ctx , dir , f )
}
// ListP lists the objects and directories of the Fs starting
// from dir non recursively into out.
//
// dir should be "" to start from the root, and should not
// have trailing slashes.
//
// This should return ErrDirNotFound if the directory isn't
// found.
//
// It should call callback for each tranche of entries read.
// These need not be returned in any particular order. If
// callback returns an error then the listing will stop
// immediately.
func ( f * Fs ) ListP ( ctx context . Context , dir string , callback fs . ListRCallback ) error {
list := list . NewHelper ( callback )
2019-08-09 11:29:36 +01:00
bucket , directory := f . split ( dir )
if bucket == "" {
2019-08-22 21:30:55 +01:00
if directory != "" {
2024-11-25 12:50:27 +00:00
return fs . ErrorListBucketRequired
}
entries , err := f . listBuckets ( ctx )
if err != nil {
return err
}
for _ , entry := range entries {
err = list . Add ( entry )
if err != nil {
return err
}
}
} else {
err := f . listDir ( ctx , bucket , directory , f . rootDirectory , f . rootBucket == "" , list . Add )
if err != nil {
return err
2019-08-22 21:30:55 +01:00
}
2014-05-05 18:25:32 +01:00
}
2024-11-25 12:50:27 +00:00
return list . Flush ( )
2013-01-23 22:43:20 +00:00
}
2017-06-05 16:14:24 +01:00
// ListR lists the objects and directories of the Fs starting
// from dir recursively into out.
2017-06-11 22:43:31 +01:00
//
// dir should be "" to start from the root, and should not
// have trailing slashes.
//
// This should return ErrDirNotFound if the directory isn't
// found.
//
// It should call callback for each tranche of entries read.
// These need not be returned in any particular order. If
// callback returns an error then the listing will stop
// immediately.
//
// Don't implement this unless you have a more efficient way
2019-08-09 11:29:36 +01:00
// of listing recursively than doing a directory traversal.
2019-06-17 10:34:30 +02:00
func ( f * Fs ) ListR ( ctx context . Context , dir string , callback fs . ListRCallback ) ( err error ) {
2019-08-09 11:29:36 +01:00
bucket , directory := f . split ( dir )
2024-12-09 15:08:36 +00:00
list := list . NewHelper ( callback )
2019-08-09 11:29:36 +01:00
listR := func ( bucket , directory , prefix string , addBucket bool ) error {
2022-07-27 10:46:28 +01:00
return f . list ( ctx , listOpt {
bucket : bucket ,
directory : directory ,
prefix : prefix ,
addBucket : addBucket ,
recurse : true ,
withVersions : f . opt . Versions ,
2022-07-26 17:58:57 +01:00
versionAt : f . opt . VersionAt ,
2023-11-08 15:29:23 +00:00
hidden : f . opt . VersionDeleted ,
2024-08-03 11:35:32 +01:00
} , func ( remote string , object * types . Object , versionID * string , isDirectory bool ) error {
2022-07-25 16:06:15 +01:00
entry , err := f . itemToDirEntry ( ctx , remote , object , versionID , isDirectory )
2019-08-09 11:29:36 +01:00
if err != nil {
return err
}
return list . Add ( entry )
} )
}
if bucket == "" {
2019-08-22 21:30:55 +01:00
entries , err := f . listBuckets ( ctx )
2019-08-09 11:29:36 +01:00
if err != nil {
return err
}
for _ , entry := range entries {
err = list . Add ( entry )
if err != nil {
return err
}
bucket := entry . Remote ( )
err = listR ( bucket , "" , f . rootDirectory , true )
if err != nil {
return err
}
2019-08-22 21:30:55 +01:00
// bucket must be present if listing succeeded
f . cache . MarkOK ( bucket )
2019-08-09 11:29:36 +01:00
}
} else {
err = listR ( bucket , directory , f . rootDirectory , f . rootBucket == "" )
2017-06-11 22:43:31 +01:00
if err != nil {
return err
}
2019-08-22 21:30:55 +01:00
// bucket must be present if listing succeeded
f . cache . MarkOK ( bucket )
2017-06-11 22:43:31 +01:00
}
return list . Flush ( )
2017-06-05 16:14:24 +01:00
}
2016-06-25 21:58:34 +01:00
// Put the Object into the bucket
2019-06-17 10:34:30 +02:00
func ( f * Fs ) Put ( ctx context . Context , in io . Reader , src fs . ObjectInfo , options ... fs . OpenOption ) ( fs . Object , error ) {
2015-11-07 11:14:46 +00:00
// Temporary Object under construction
fs := & Object {
fs : f ,
2016-02-18 12:35:25 +01:00
remote : src . Remote ( ) ,
2015-11-07 11:14:46 +00:00
}
2019-06-17 10:34:30 +02:00
return fs , fs . Update ( ctx , in , src , options ... )
2013-01-08 18:53:35 +00:00
}
2017-09-15 20:20:32 +02:00
// PutStream uploads to the remote path with the modTime given of indeterminate size
2019-06-17 10:34:30 +02:00
func ( f * Fs ) PutStream ( ctx context . Context , in io . Reader , src fs . ObjectInfo , options ... fs . OpenOption ) ( fs . Object , error ) {
return f . Put ( ctx , in , src , options ... )
2017-09-15 20:20:32 +02:00
}
2016-02-23 18:58:55 -07:00
// Check if the bucket exists
2017-06-29 12:26:14 +01:00
//
// NB this can return incorrect results if called immediately after bucket deletion
2019-08-09 11:29:36 +01:00
func ( f * Fs ) bucketExists ( ctx context . Context , bucket string ) ( bool , error ) {
2016-02-23 18:58:55 -07:00
req := s3 . HeadBucketInput {
2019-08-09 11:29:36 +01:00
Bucket : & bucket ,
2016-02-23 18:58:55 -07:00
}
2018-09-03 16:41:04 +12:00
err := f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
_ , err := f . c . HeadBucket ( ctx , & req )
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2018-09-03 16:41:04 +12:00
} )
2016-02-23 18:58:55 -07:00
if err == nil {
return true , nil
}
2024-08-03 11:35:32 +01:00
if getHTTPStatusCode ( err ) == http . StatusNotFound {
return false , nil
2016-02-23 18:58:55 -07:00
}
return false , err
}
2023-04-26 10:59:17 +01:00
// Create directory marker file and parents
func ( f * Fs ) createDirectoryMarker ( ctx context . Context , bucket , dir string ) error {
if ! f . opt . DirectoryMarkers || bucket == "" {
return nil
}
// Object to be uploaded
o := & Object {
fs : f ,
meta : map [ string ] string {
metaMtime : swift . TimeToFloatString ( time . Now ( ) ) ,
} ,
}
for {
_ , bucketPath := f . split ( dir )
// Don't create the directory marker if it is the bucket or at the very root
if bucketPath == "" {
break
}
o . remote = dir + "/"
// Check to see if object already exists
_ , err := o . headObject ( ctx )
if err == nil {
return nil
}
// Upload it if not
fs . Debugf ( o , "Creating directory marker" )
content := io . Reader ( strings . NewReader ( "" ) )
2023-04-28 17:22:45 +01:00
err = o . Update ( ctx , content , o )
2023-04-26 10:59:17 +01:00
if err != nil {
return fmt . Errorf ( "creating directory marker failed: %w" , err )
}
// Now check parent directory exists
dir = path . Dir ( dir )
if dir == "/" || dir == "." {
break
}
}
return nil
}
2013-01-08 18:53:35 +00:00
// Mkdir creates the bucket if it doesn't exist
2019-06-17 10:34:30 +02:00
func ( f * Fs ) Mkdir ( ctx context . Context , dir string ) error {
2019-08-09 11:29:36 +01:00
bucket , _ := f . split ( dir )
2022-09-26 09:59:57 +03:00
e := f . makeBucket ( ctx , bucket )
if e != nil {
return e
}
2023-04-26 10:59:17 +01:00
return f . createDirectoryMarker ( ctx , bucket , dir )
}
// mkdirParent creates the parent bucket/directory if it doesn't exist
func ( f * Fs ) mkdirParent ( ctx context . Context , remote string ) error {
2025-06-16 15:07:05 +01:00
remote , _ = strings . CutSuffix ( remote , "/" )
2023-04-26 10:59:17 +01:00
dir := path . Dir ( remote )
if dir == "/" || dir == "." {
dir = ""
2022-09-26 09:59:57 +03:00
}
2023-04-26 10:59:17 +01:00
return f . Mkdir ( ctx , dir )
2019-08-22 21:30:55 +01:00
}
// makeBucket creates the bucket if it doesn't exist
func ( f * Fs ) makeBucket ( ctx context . Context , bucket string ) error {
2020-07-22 12:02:17 +01:00
if f . opt . NoCheckBucket {
return nil
}
2019-08-09 11:29:36 +01:00
return f . cache . Create ( bucket , func ( ) error {
req := s3 . CreateBucketInput {
Bucket : & bucket ,
2024-08-03 11:35:32 +01:00
ACL : types . BucketCannedACL ( f . opt . BucketACL ) ,
2017-06-29 12:26:14 +01:00
}
2019-08-09 11:29:36 +01:00
if f . opt . LocationConstraint != "" {
2024-08-03 11:35:32 +01:00
req . CreateBucketConfiguration = & types . CreateBucketConfiguration {
LocationConstraint : types . BucketLocationConstraint ( f . opt . LocationConstraint ) ,
2019-08-09 11:29:36 +01:00
}
2017-06-29 12:26:14 +01:00
}
2019-08-09 11:29:36 +01:00
err := f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
_ , err := f . c . CreateBucket ( ctx , & req )
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2019-08-09 11:29:36 +01:00
} )
if err == nil {
fs . Infof ( f , "Bucket %q created with ACL %q" , bucket , f . opt . BucketACL )
2014-12-23 12:09:02 +00:00
}
2024-08-03 11:35:32 +01:00
var awsErr smithy . APIError
if errors . As ( err , & awsErr ) {
switch awsErr . ErrorCode ( ) {
2023-10-06 11:45:03 +01:00
case "BucketAlreadyOwnedByYou" :
2019-08-09 11:29:36 +01:00
err = nil
2023-10-06 11:45:03 +01:00
case "BucketAlreadyExists" , "BucketNameUnavailable" :
if f . opt . UseAlreadyExists . Value {
// We can trust BucketAlreadyExists to mean not owned by us, so make it non retriable
err = fserrors . NoRetryError ( err )
} else {
// We can't trust BucketAlreadyExists to mean not owned by us, so ignore it
err = nil
}
2019-08-09 11:29:36 +01:00
}
2013-01-08 22:31:16 +00:00
}
2020-04-15 13:13:13 +01:00
return err
2019-08-09 11:29:36 +01:00
} , func ( ) ( bool , error ) {
return f . bucketExists ( ctx , bucket )
} )
2013-01-08 18:53:35 +00:00
}
2015-11-07 15:31:04 +00:00
// Rmdir deletes the bucket if the fs is at the root
2013-01-08 18:53:35 +00:00
//
// Returns an error if it isn't empty
2019-06-17 10:34:30 +02:00
func ( f * Fs ) Rmdir ( ctx context . Context , dir string ) error {
2019-08-09 11:29:36 +01:00
bucket , directory := f . split ( dir )
2022-09-26 09:59:57 +03:00
// Remove directory marker file
if f . opt . DirectoryMarkers && bucket != "" && dir != "" {
2023-04-26 10:59:17 +01:00
o := & Object {
2022-09-26 09:59:57 +03:00
fs : f ,
2023-04-26 10:59:17 +01:00
remote : dir + "/" ,
}
fs . Debugf ( o , "Removing directory marker" )
err := o . Remove ( ctx )
if err != nil {
return fmt . Errorf ( "removing directory marker failed: %w" , err )
2022-09-26 09:59:57 +03:00
}
}
2019-08-09 11:29:36 +01:00
if bucket == "" || directory != "" {
2015-11-07 15:31:04 +00:00
return nil
}
2019-08-09 11:29:36 +01:00
return f . cache . Remove ( bucket , func ( ) error {
req := s3 . DeleteBucketInput {
Bucket : & bucket ,
}
err := f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
_ , err := f . c . DeleteBucket ( ctx , & req )
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2019-08-09 11:29:36 +01:00
} )
if err == nil {
fs . Infof ( f , "Bucket %q deleted" , bucket )
}
return err
2018-09-03 16:41:04 +12:00
} )
2013-01-08 18:53:35 +00:00
}
2015-09-22 18:47:16 +01:00
// Precision of the remote
2015-11-07 11:14:46 +00:00
func ( f * Fs ) Precision ( ) time . Duration {
2013-01-18 23:21:02 +00:00
return time . Nanosecond
}
2018-01-23 10:50:50 +00:00
// pathEscape escapes s as for a URL path. It uses rest.URLPathEscape
// but also escapes '+' for S3 and Digital Ocean spaces compatibility
func pathEscape ( s string ) string {
2022-05-17 00:11:45 +08:00
return strings . ReplaceAll ( rest . URLPathEscape ( s ) , "+" , "%2B" )
2018-01-23 10:50:50 +00:00
}
2020-10-13 17:43:40 -04:00
// copy does a server-side copy
2019-09-09 20:44:50 +01:00
//
// It adds the boiler plate to the req passed in and calls the s3
// method
2020-07-30 10:52:32 +01:00
func ( f * Fs ) copy ( ctx context . Context , req * s3 . CopyObjectInput , dstBucket , dstPath , srcBucket , srcPath string , src * Object ) error {
2019-09-09 20:44:50 +01:00
req . Bucket = & dstBucket
2024-08-03 11:35:32 +01:00
req . ACL = types . ObjectCannedACL ( f . opt . ACL )
2019-09-09 20:44:50 +01:00
req . Key = & dstPath
2023-03-03 10:45:29 +00:00
source := pathEscape ( bucket . Join ( srcBucket , srcPath ) )
2022-07-25 16:06:15 +01:00
if src . versionID != nil {
source += fmt . Sprintf ( "?versionId=%s" , * src . versionID )
}
2019-09-09 20:44:50 +01:00
req . CopySource = & source
2020-12-03 10:30:06 +08:00
if f . opt . RequesterPays {
2024-08-03 11:35:32 +01:00
req . RequestPayer = types . RequestPayerRequester
2020-12-03 10:30:06 +08:00
}
2019-09-09 20:44:50 +01:00
if f . opt . ServerSideEncryption != "" {
2024-08-03 11:35:32 +01:00
req . ServerSideEncryption = types . ServerSideEncryption ( f . opt . ServerSideEncryption )
2019-09-09 20:44:50 +01:00
}
2020-11-20 11:15:48 +00:00
if f . opt . SSECustomerAlgorithm != "" {
req . SSECustomerAlgorithm = & f . opt . SSECustomerAlgorithm
req . CopySourceSSECustomerAlgorithm = & f . opt . SSECustomerAlgorithm
}
2024-08-07 10:14:58 +01:00
if f . opt . SSECustomerKeyBase64 != "" {
req . SSECustomerKey = & f . opt . SSECustomerKeyBase64
req . CopySourceSSECustomerKey = & f . opt . SSECustomerKeyBase64
2020-11-20 11:15:48 +00:00
}
if f . opt . SSECustomerKeyMD5 != "" {
req . SSECustomerKeyMD5 = & f . opt . SSECustomerKeyMD5
req . CopySourceSSECustomerKeyMD5 = & f . opt . SSECustomerKeyMD5
}
2019-09-09 20:44:50 +01:00
if f . opt . SSEKMSKeyID != "" {
req . SSEKMSKeyId = & f . opt . SSEKMSKeyID
}
2024-08-03 11:35:32 +01:00
if req . StorageClass == types . StorageClass ( "" ) && f . opt . StorageClass != "" {
req . StorageClass = types . StorageClass ( f . opt . StorageClass )
2019-09-09 20:44:50 +01:00
}
2019-10-04 23:49:06 +08:00
2020-07-30 10:52:32 +01:00
if src . bytes >= int64 ( f . opt . CopyCutoff ) {
return f . copyMultipart ( ctx , req , dstBucket , dstPath , srcBucket , srcPath , src )
2019-10-04 23:49:06 +08:00
}
2019-09-09 20:44:50 +01:00
return f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
_ , err := f . c . CopyObject ( ctx , req )
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2019-09-09 20:44:50 +01:00
} )
}
2019-10-04 23:49:06 +08:00
func calculateRange ( partSize , partIndex , numParts , totalSize int64 ) string {
start := partIndex * partSize
var ends string
if partIndex == numParts - 1 {
2019-12-02 17:00:54 +00:00
if totalSize >= 1 {
ends = strconv . FormatInt ( totalSize - 1 , 10 )
2019-10-04 23:49:06 +08:00
}
} else {
ends = strconv . FormatInt ( start + partSize - 1 , 10 )
}
return fmt . Sprintf ( "bytes=%v-%v" , start , ends )
}
2020-07-30 10:52:32 +01:00
func ( f * Fs ) copyMultipart ( ctx context . Context , copyReq * s3 . CopyObjectInput , dstBucket , dstPath , srcBucket , srcPath string , src * Object ) ( err error ) {
info , err := src . headObject ( ctx )
if err != nil {
return err
}
req := & s3 . CreateMultipartUploadInput { }
// Fill in the request from the head info
2022-07-28 11:49:19 +01:00
//structs.SetFrom(req, info)
setFrom_s3CreateMultipartUploadInput_s3HeadObjectOutput ( req , info )
2020-07-30 10:52:32 +01:00
// If copy metadata was set then set the Metadata to that read
// from the head request
2024-08-03 11:35:32 +01:00
if copyReq . MetadataDirective == types . MetadataDirectiveCopy {
2020-07-30 10:52:32 +01:00
copyReq . Metadata = info . Metadata
}
// Overwrite any from the copyReq
2022-07-28 11:49:19 +01:00
//structs.SetFrom(req, copyReq)
setFrom_s3CreateMultipartUploadInput_s3CopyObjectInput ( req , copyReq )
2020-07-30 10:52:32 +01:00
req . Bucket = & dstBucket
req . Key = & dstPath
2019-10-04 23:49:06 +08:00
var cout * s3 . CreateMultipartUploadOutput
if err := f . pacer . Call ( func ( ) ( bool , error ) {
var err error
2024-08-03 11:35:32 +01:00
cout , err = f . c . CreateMultipartUpload ( ctx , req )
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2019-10-04 23:49:06 +08:00
} ) ; err != nil {
return err
}
uid := cout . UploadId
2020-06-04 11:09:27 +01:00
defer atexit . OnError ( & err , func ( ) {
// Try to abort the upload, but ignore the error.
2020-07-30 10:52:32 +01:00
fs . Debugf ( src , "Cancelling multipart copy" )
2020-06-04 11:09:27 +01:00
_ = f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
_ , err := f . c . AbortMultipartUpload ( context . Background ( ) , & s3 . AbortMultipartUploadInput {
2020-06-04 11:09:27 +01:00
Bucket : & dstBucket ,
Key : & dstPath ,
UploadId : uid ,
RequestPayer : req . RequestPayer ,
2019-10-04 23:49:06 +08:00
} )
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2020-06-04 11:09:27 +01:00
} )
} ) ( )
2019-10-04 23:49:06 +08:00
2020-07-30 10:52:32 +01:00
srcSize := src . bytes
2019-12-02 17:14:57 +00:00
partSize := int64 ( f . opt . CopyCutoff )
2019-10-04 23:49:06 +08:00
numParts := ( srcSize - 1 ) / partSize + 1
2020-07-30 10:52:32 +01:00
fs . Debugf ( src , "Starting multipart copy with %d parts" , numParts )
2023-12-01 10:30:44 +00:00
var (
2024-08-03 11:35:32 +01:00
parts = make ( [ ] types . CompletedPart , numParts )
2023-12-01 10:30:44 +00:00
g , gCtx = errgroup . WithContext ( ctx )
)
g . SetLimit ( f . opt . UploadConcurrency )
2024-08-03 11:35:32 +01:00
for partNum := int32 ( 1 ) ; int64 ( partNum ) <= numParts ; partNum ++ {
2023-12-01 10:30:44 +00:00
// Fail fast, in case an errgroup managed function returns an error
// gCtx is cancelled. There is no point in uploading all the other parts.
if gCtx . Err ( ) != nil {
break
}
partNum := partNum // for closure
g . Go ( func ( ) error {
var uout * s3 . UploadPartCopyOutput
2020-07-30 10:52:32 +01:00
uploadPartReq := & s3 . UploadPartCopyInput { }
2022-07-28 11:49:19 +01:00
//structs.SetFrom(uploadPartReq, copyReq)
setFrom_s3UploadPartCopyInput_s3CopyObjectInput ( uploadPartReq , copyReq )
2020-07-30 10:52:32 +01:00
uploadPartReq . Bucket = & dstBucket
uploadPartReq . Key = & dstPath
uploadPartReq . PartNumber = & partNum
uploadPartReq . UploadId = uid
2024-08-03 11:35:32 +01:00
uploadPartReq . CopySourceRange = aws . String ( calculateRange ( partSize , int64 ( partNum - 1 ) , numParts , srcSize ) )
2023-12-01 10:30:44 +00:00
err := f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
uout , err = f . c . UploadPartCopy ( gCtx , uploadPartReq )
2023-12-01 10:30:44 +00:00
return f . shouldRetry ( gCtx , err )
} )
2019-10-04 23:49:06 +08:00
if err != nil {
2023-12-01 10:30:44 +00:00
return err
2019-10-04 23:49:06 +08:00
}
2024-08-03 11:35:32 +01:00
parts [ partNum - 1 ] = types . CompletedPart {
2019-10-04 23:49:06 +08:00
PartNumber : & partNum ,
ETag : uout . CopyPartResult . ETag ,
2023-12-01 10:30:44 +00:00
}
return nil
} )
}
err = g . Wait ( )
if err != nil {
return err
2019-10-04 23:49:06 +08:00
}
return f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
_ , err := f . c . CompleteMultipartUpload ( ctx , & s3 . CompleteMultipartUploadInput {
2019-10-04 23:49:06 +08:00
Bucket : & dstBucket ,
Key : & dstPath ,
2024-08-03 11:35:32 +01:00
MultipartUpload : & types . CompletedMultipartUpload {
2019-10-04 23:49:06 +08:00
Parts : parts ,
} ,
2025-07-17 15:29:31 +02:00
RequestPayer : req . RequestPayer ,
SSECustomerAlgorithm : req . SSECustomerAlgorithm ,
SSECustomerKey : req . SSECustomerKey ,
SSECustomerKeyMD5 : req . SSECustomerKeyMD5 ,
UploadId : uid ,
2019-10-04 23:49:06 +08:00
} )
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2019-10-04 23:49:06 +08:00
} )
}
2020-10-13 17:43:40 -04:00
// Copy src to this remote using server-side copy operations.
2015-02-14 18:48:08 +00:00
//
2022-08-05 16:35:41 +01:00
// This is stored with the remote path given.
2015-02-14 18:48:08 +00:00
//
2022-08-05 16:35:41 +01:00
// It returns the destination Object and a possible error.
2015-02-14 18:48:08 +00:00
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantCopy
2019-06-17 10:34:30 +02:00
func ( f * Fs ) Copy ( ctx context . Context , src fs . Object , remote string ) ( fs . Object , error ) {
2022-07-26 17:58:57 +01:00
if f . opt . VersionAt . IsSet ( ) {
return nil , errNotWithVersionAt
}
2019-08-09 11:29:36 +01:00
dstBucket , dstPath := f . split ( remote )
2023-04-26 10:59:17 +01:00
err := f . mkdirParent ( ctx , remote )
2017-06-28 21:14:53 +01:00
if err != nil {
return nil , err
}
2015-11-07 11:14:46 +00:00
srcObj , ok := src . ( * Object )
2015-02-14 18:48:08 +00:00
if ! ok {
2017-02-09 11:01:20 +00:00
fs . Debugf ( src , "Can't copy - not same remote type" )
2015-02-14 18:48:08 +00:00
return nil , fs . ErrorCantCopy
}
2024-03-05 17:21:06 +00:00
2019-08-09 11:29:36 +01:00
srcBucket , srcPath := srcObj . split ( )
2015-02-14 18:48:08 +00:00
req := s3 . CopyObjectInput {
2024-08-03 11:35:32 +01:00
MetadataDirective : types . MetadataDirectiveCopy ,
2015-02-14 18:48:08 +00:00
}
2024-03-05 17:21:06 +00:00
// Update the metadata if it is in use
if ci := fs . GetConfig ( ctx ) ; ci . Metadata {
ui , err := srcObj . prepareUpload ( ctx , src , fs . MetadataAsOpenOptions ( ctx ) , true )
if err != nil {
return nil , fmt . Errorf ( "failed to prepare upload: %w" , err )
}
setFrom_s3CopyObjectInput_s3PutObjectInput ( & req , ui . req )
2024-08-03 11:35:32 +01:00
req . MetadataDirective = types . MetadataDirectiveReplace
2024-03-05 17:21:06 +00:00
}
2020-07-30 10:52:32 +01:00
err = f . copy ( ctx , & req , dstBucket , dstPath , srcBucket , srcPath , srcObj )
2015-02-14 18:48:08 +00:00
if err != nil {
return nil , err
}
2019-06-17 10:34:30 +02:00
return f . NewObject ( ctx , remote )
2015-02-14 18:48:08 +00:00
}
2016-01-11 13:39:33 +01:00
// Hashes returns the supported hash sets.
2018-01-12 16:30:54 +00:00
func ( f * Fs ) Hashes ( ) hash . Set {
2018-01-18 20:27:52 +00:00
return hash . Set ( hash . MD5 )
2016-01-11 13:39:33 +01:00
}
2020-06-05 00:03:12 +03:00
// PublicLink generates a public link to the remote path (usually readable by anyone)
func ( f * Fs ) PublicLink ( ctx context . Context , remote string , expire fs . Duration , unlink bool ) ( link string , err error ) {
2020-06-18 17:50:50 +01:00
if strings . HasSuffix ( remote , "/" ) {
return "" , fs . ErrorCantShareDirectories
}
2022-07-25 16:06:15 +01:00
obj , err := f . NewObject ( ctx , remote )
if err != nil {
2020-06-05 00:03:12 +03:00
return "" , err
}
2022-07-25 16:06:15 +01:00
o := obj . ( * Object )
2020-06-18 17:50:50 +01:00
if expire > maxExpireDuration {
fs . Logf ( f , "Public Link: Reducing expiry to %v as %v is greater than the max time allowed" , maxExpireDuration , expire )
expire = maxExpireDuration
}
2024-08-03 11:35:32 +01:00
bucket , bucketPath := f . split ( remote )
httpReq , err := s3 . NewPresignClient ( f . c ) . PresignGetObject ( ctx , & s3 . GetObjectInput {
2022-07-25 16:06:15 +01:00
Bucket : & bucket ,
Key : & bucketPath ,
VersionId : o . versionID ,
2024-08-03 11:35:32 +01:00
} , s3 . WithPresignExpires ( time . Duration ( expire ) ) )
if err != nil {
return "" , err
}
return httpReq . URL , nil
2020-06-05 00:03:12 +03:00
}
2020-06-24 11:02:34 +01:00
var commandHelp = [ ] fs . CommandHelp { {
Name : "restore" ,
2025-11-02 12:52:31 +01:00
Short : "Restore objects from GLACIER or INTELLIGENT-TIERING archive tier." ,
2025-11-02 13:19:36 +01:00
Long : ` This command can be used to restore one or more objects from GLACIER to normal
storage or from INTELLIGENT - TIERING Archive Access / Deep Archive Access tier
to the Frequent Access tier .
2020-06-24 11:02:34 +01:00
2025-11-02 13:19:36 +01:00
Usage examples :
2020-06-24 11:02:34 +01:00
2025-11-02 13:06:08 +01:00
` + " ` ` ` console " + `
rclone backend restore s3 : bucket / path / to / -- include / object - o priority = PRIORITY - o lifetime = DAYS
rclone backend restore s3 : bucket / path / to / directory - o priority = PRIORITY - o lifetime = DAYS
rclone backend restore s3 : bucket - o priority = PRIORITY - o lifetime = DAYS
rclone backend restore s3 : bucket / path / to / directory - o priority = PRIORITY
` + " ` ` ` " + `
2020-06-24 11:02:34 +01:00
2025-11-02 13:19:36 +01:00
This flag also obeys the filters . Test first with -- interactive / - i or -- dry - run
flags .
2020-06-24 11:02:34 +01:00
2025-11-02 13:06:08 +01:00
` + " ` ` ` console " + `
rclone -- interactive backend restore -- include "*.txt" s3 : bucket / path - o priority = Standard - o lifetime = 1
` + " ` ` ` " + `
2020-06-24 11:02:34 +01:00
2025-11-02 13:19:36 +01:00
All the objects shown will be marked for restore , then :
2020-06-24 11:02:34 +01:00
2025-11-02 13:06:08 +01:00
` + " ` ` ` console " + `
rclone backend restore -- include "*.txt" s3 : bucket / path - o priority = Standard - o lifetime = 1
` + " ` ` ` " + `
2020-06-24 11:02:34 +01:00
It returns a list of status dictionaries with Remote and Status
Spelling fixes
Fix spelling of: above, already, anonymous, associated,
authentication, bandwidth, because, between, blocks, calculate,
candidates, cautious, changelog, cleaner, clipboard, command,
completely, concurrently, considered, constructs, corrupt, current,
daemon, dependencies, deprecated, directory, dispatcher, download,
eligible, ellipsis, encrypter, endpoint, entrieslist, essentially,
existing writers, existing, expires, filesystem, flushing, frequently,
hierarchy, however, implementation, implements, inaccurate,
individually, insensitive, longer, maximum, metadata, modified,
multipart, namedirfirst, nextcloud, obscured, opened, optional,
owncloud, pacific, passphrase, password, permanently, persimmon,
positive, potato, protocol, quota, receiving, recommends, referring,
requires, revisited, satisfied, satisfies, satisfy, semver,
serialized, session, storage, strategies, stringlist, successful,
supported, surprise, temporarily, temporary, transactions, unneeded,
update, uploads, wrapped
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2020-10-08 20:17:24 -04:00
keys . The Status will be OK if it was successful or an error message
2020-06-24 11:02:34 +01:00
if not .
2025-11-02 13:06:08 +01:00
` + " ` ` ` json " + `
[
{
"Status" : "OK" ,
"Remote" : "test.txt"
} ,
{
"Status" : "OK" ,
"Remote" : "test/file4.txt"
}
]
2025-11-02 13:19:36 +01:00
` + " ` ` ` " ,
2020-06-24 11:02:34 +01:00
Opts : map [ string ] string {
2025-11-02 13:19:36 +01:00
"priority" : "Priority of restore: Standard|Expedited|Bulk" ,
"lifetime" : ` Lifetime of the active copy in days , ignored for INTELLIGENT - TIERING
storage . ` ,
2020-06-24 11:02:34 +01:00
"description" : "The optional description for the job." ,
} ,
2023-09-06 12:42:03 +01:00
} , {
Name : "restore-status" ,
2025-11-02 13:19:36 +01:00
Short : "Show the status for objects being restored from GLACIER or INTELLIGENT-TIERING." ,
Long : ` This command can be used to show the status for objects being restored from
GLACIER to normal storage or from INTELLIGENT - TIERING Archive Access / Deep
Archive Access tier to the Frequent Access tier .
2023-09-06 12:42:03 +01:00
2025-11-02 13:19:36 +01:00
Usage examples :
2023-09-06 12:42:03 +01:00
2025-11-02 13:06:08 +01:00
` + " ` ` ` console " + `
rclone backend restore - status s3 : bucket / path / to / object
rclone backend restore - status s3 : bucket / path / to / directory
rclone backend restore - status - o all s3 : bucket / path / to / directory
` + " ` ` ` " + `
2023-09-06 12:42:03 +01:00
This command does not obey the filters .
2025-11-02 13:19:36 +01:00
It returns a list of status dictionaries :
2023-09-06 12:42:03 +01:00
2025-11-02 13:06:08 +01:00
` + " ` ` ` json " + `
[
{
"Remote" : "file.txt" ,
"VersionID" : null ,
"RestoreStatus" : {
"IsRestoreInProgress" : true ,
"RestoreExpiryDate" : "2023-09-06T12:29:19+01:00"
2023-09-06 12:42:03 +01:00
} ,
2025-11-02 13:06:08 +01:00
"StorageClass" : "GLACIER"
} ,
{
"Remote" : "test.pdf" ,
"VersionID" : null ,
"RestoreStatus" : {
"IsRestoreInProgress" : false ,
"RestoreExpiryDate" : "2023-09-06T12:29:19+01:00"
2024-08-24 21:22:17 +02:00
} ,
2025-11-02 13:06:08 +01:00
"StorageClass" : "DEEP_ARCHIVE"
} ,
{
"Remote" : "test.gz" ,
"VersionID" : null ,
"RestoreStatus" : {
"IsRestoreInProgress" : true ,
"RestoreExpiryDate" : "null"
} ,
"StorageClass" : "INTELLIGENT_TIERING"
}
]
2025-11-02 13:19:36 +01:00
` + " ` ` ` " ,
2023-09-06 12:42:03 +01:00
Opts : map [ string ] string {
2025-11-02 13:19:36 +01:00
"all" : "If set then show all objects, not just ones with restore status." ,
2023-09-06 12:42:03 +01:00
} ,
2020-06-25 16:11:05 +01:00
} , {
Name : "list-multipart-uploads" ,
2025-11-02 12:52:31 +01:00
Short : "List the unfinished multipart uploads." ,
2020-06-25 16:11:05 +01:00
Long : ` This command lists the unfinished multipart uploads in JSON format .
2025-11-02 13:19:36 +01:00
Usage examples :
2025-11-02 13:06:08 +01:00
` + " ` ` ` console " + `
rclone backend list - multipart s3 : bucket / path / to / object
` + " ` ` ` " + `
2020-06-25 16:11:05 +01:00
It returns a dictionary of buckets with values as lists of unfinished
multipart uploads .
You can call it with no bucket in which case it lists all bucket , with
a bucket or with a bucket and path .
2025-11-02 13:06:08 +01:00
` + " ` ` ` json " + `
{
"rclone" : [
2020-06-25 16:11:05 +01:00
{
2025-11-02 13:06:08 +01:00
"Initiated" : "2020-06-26T14:20:36Z" ,
"Initiator" : {
"DisplayName" : "XXX" ,
"ID" : "arn:aws:iam::XXX:user/XXX"
} ,
"Key" : "KEY" ,
"Owner" : {
"DisplayName" : null ,
"ID" : "XXX"
} ,
"StorageClass" : "STANDARD" ,
"UploadId" : "XXX"
2020-06-25 16:11:05 +01:00
}
2025-11-02 13:06:08 +01:00
] ,
"rclone-1000files" : [ ] ,
"rclone-dst" : [ ]
}
2025-11-02 13:19:36 +01:00
` + " ` ` ` " ,
2020-06-25 16:11:05 +01:00
} , {
Name : "cleanup" ,
Short : "Remove unfinished multipart uploads." ,
Long : ` This command removes unfinished multipart uploads of age greater than
max - age which defaults to 24 hours .
2025-11-02 13:19:36 +01:00
Note that you can use -- interactive / - i or -- dry - run with this command to see
what it would do .
2020-06-25 16:11:05 +01:00
2025-11-02 13:19:36 +01:00
Usage examples :
2025-11-02 13:06:08 +01:00
` + " ` ` ` console " + `
rclone backend cleanup s3 : bucket / path / to / object
rclone backend cleanup - o max - age = 7 w s3 : bucket / path / to / object
` + " ` ` ` " + `
2020-06-25 16:11:05 +01:00
2025-11-02 13:19:36 +01:00
Durations are parsed as per the rest of rclone , 2 h , 7 d , 7 w etc . ` ,
2020-06-25 16:11:05 +01:00
Opts : map [ string ] string {
2025-11-02 13:19:36 +01:00
"max-age" : "Max age of upload to delete." ,
2020-06-25 16:11:05 +01:00
} ,
2022-07-26 15:03:32 +01:00
} , {
Name : "cleanup-hidden" ,
Short : "Remove old versions of files." ,
Long : ` This command removes any old hidden versions of files
on a versions enabled bucket .
2025-11-02 13:19:36 +01:00
Note that you can use -- interactive / - i or -- dry - run with this command to see
what it would do .
2022-07-26 15:03:32 +01:00
2025-11-02 13:19:36 +01:00
Usage example :
2025-11-02 13:06:08 +01:00
` + " ` ` ` console " + `
rclone backend cleanup - hidden s3 : bucket / path / to / dir
2025-11-02 13:19:36 +01:00
` + " ` ` ` " ,
2022-07-25 16:05:17 +01:00
} , {
Name : "versioning" ,
Short : "Set/get versioning support for a bucket." ,
Long : ` This command sets versioning support if a parameter is
passed and then returns the current versioning status for the bucket
supplied .
2025-11-02 13:19:36 +01:00
Usage examples :
2025-11-02 13:06:08 +01:00
` + " ` ` ` console " + `
rclone backend versioning s3 : bucket # read status only
rclone backend versioning s3 : bucket Enabled
rclone backend versioning s3 : bucket Suspended
` + " ` ` ` " + `
2022-07-25 16:05:17 +01:00
2025-11-02 13:19:36 +01:00
It may return "Enabled" , "Suspended" or "Unversioned" . Note that once
versioning has been enabled the status can ' t be set back to "Unversioned" . ` ,
2023-07-15 18:56:58 +01:00
} , {
Name : "set" ,
Short : "Set command for updating the config parameters." ,
Long : ` This set command can be used to update the config parameters
for a running s3 backend .
2025-11-02 13:19:36 +01:00
Usage examples :
2023-07-15 18:56:58 +01:00
2025-11-02 13:06:08 +01:00
` + " ` ` ` console " + `
rclone backend set s3 : [ - o opt_name = opt_value ] [ - o opt_name2 = opt_value2 ]
rclone rc backend / command command = set fs = s3 : [ - o opt_name = opt_value ] [ - o opt_name2 = opt_value2 ]
rclone rc backend / command command = set fs = s3 : - o session_token = X - o access_key_id = X - o secret_access_key = X
` + " ` ` ` " + `
2023-07-15 18:56:58 +01:00
The option keys are named as they are in the config file .
This rebuilds the connection to the s3 backend when it is called with
the new parameters . Only new parameters need be passed as the values
will default to those currently in use .
2025-11-02 13:19:36 +01:00
It doesn ' t return anything . ` ,
2020-06-24 11:02:34 +01:00
} }
// Command the backend to run a named command
//
// The command run is name
// args may be used to read arguments from
// opts may be used to read optional arguments from
//
// The result should be capable of being JSON encoded
// If it is a string or a []string it will be shown to the user
// otherwise it will be JSON encoded and shown to the user like that
build: modernize Go usage
This commit modernizes Go usage. This was done with:
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
Then files needed to be `go fmt`ed and a few comments needed to be
restored.
The modernizations include replacing
- if/else conditional assignment by a call to the built-in min or max functions added in go1.21
- sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] } by a call to slices.Sort(s), added in go1.21
- interface{} by the 'any' type added in go1.18
- append([]T(nil), s...) by slices.Clone(s) or slices.Concat(s), added in go1.21
- loop around an m[k]=v map update by a call to one of the Collect, Copy, Clone, or Insert functions from the maps package, added in go1.21
- []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...), added in go1.19
- append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1), added in go1.21
- a 3-clause for i := 0; i < n; i++ {} loop by for i := range n {}, added in go1.22
2025-02-26 21:08:12 +00:00
func ( f * Fs ) Command ( ctx context . Context , name string , arg [ ] string , opt map [ string ] string ) ( out any , err error ) {
2020-06-24 11:02:34 +01:00
switch name {
case "restore" :
req := s3 . RestoreObjectInput {
//Bucket: &f.rootBucket,
//Key: &encodedDirectory,
2024-08-03 11:35:32 +01:00
RestoreRequest : & types . RestoreRequest { } ,
2020-06-24 11:02:34 +01:00
}
if lifetime := opt [ "lifetime" ] ; lifetime != "" {
2024-08-15 20:20:10 +02:00
ilifetime , err := strconv . ParseInt ( lifetime , 10 , 32 )
2020-06-24 11:02:34 +01:00
if err != nil {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "bad lifetime: %w" , err )
2020-06-24 11:02:34 +01:00
}
2024-08-15 20:20:10 +02:00
ilifetime32 := int32 ( ilifetime )
2024-08-03 11:35:32 +01:00
req . RestoreRequest . Days = & ilifetime32
2020-06-24 11:02:34 +01:00
}
if priority := opt [ "priority" ] ; priority != "" {
2024-08-03 11:35:32 +01:00
req . RestoreRequest . GlacierJobParameters = & types . GlacierJobParameters {
Tier : types . Tier ( priority ) ,
2020-06-24 11:02:34 +01:00
}
}
if description := opt [ "description" ] ; description != "" {
req . RestoreRequest . Description = & description
}
type status struct {
Status string
Remote string
}
var (
outMu sync . Mutex
out = [ ] status { }
)
err = operations . ListFn ( ctx , f , func ( obj fs . Object ) {
// Remember this is run --checkers times concurrently
o , ok := obj . ( * Object )
st := status { Status : "OK" , Remote : obj . Remote ( ) }
defer func ( ) {
outMu . Lock ( )
out = append ( out , st )
outMu . Unlock ( )
} ( )
if operations . SkipDestructive ( ctx , obj , "restore" ) {
return
}
if ! ok {
st . Status = "Not an S3 object"
return
}
2024-08-24 21:22:17 +02:00
if o . storageClass == nil || ( * o . storageClass != "GLACIER" && * o . storageClass != "DEEP_ARCHIVE" && * o . storageClass != "INTELLIGENT_TIERING" ) {
st . Status = "Not GLACIER or DEEP_ARCHIVE or INTELLIGENT_TIERING storage class"
2022-05-12 20:42:37 +01:00
return
}
2020-06-24 11:02:34 +01:00
bucket , bucketPath := o . split ( )
reqCopy := req
2024-08-24 21:22:17 +02:00
if * o . storageClass == "INTELLIGENT_TIERING" {
reqCopy . RestoreRequest . Days = nil
}
2020-06-24 11:02:34 +01:00
reqCopy . Bucket = & bucket
reqCopy . Key = & bucketPath
2022-07-25 16:06:15 +01:00
reqCopy . VersionId = o . versionID
2020-06-24 11:02:34 +01:00
err = f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
_ , err = f . c . RestoreObject ( ctx , & reqCopy )
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2020-06-24 11:02:34 +01:00
} )
if err != nil {
st . Status = err . Error ( )
}
} )
if err != nil {
return out , err
}
return out , nil
2023-09-06 12:42:03 +01:00
case "restore-status" :
_ , all := opt [ "all" ]
return f . restoreStatus ( ctx , all )
2020-06-25 16:11:05 +01:00
case "list-multipart-uploads" :
return f . listMultipartUploadsAll ( ctx )
case "cleanup" :
maxAge := 24 * time . Hour
if opt [ "max-age" ] != "" {
maxAge , err = fs . ParseDuration ( opt [ "max-age" ] )
if err != nil {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "bad max-age: %w" , err )
2020-06-25 16:11:05 +01:00
}
}
return nil , f . cleanUp ( ctx , maxAge )
2022-07-26 15:03:32 +01:00
case "cleanup-hidden" :
return nil , f . CleanUpHidden ( ctx )
2022-07-25 16:05:17 +01:00
case "versioning" :
return f . setGetVersioning ( ctx , arg ... )
2023-07-15 18:56:58 +01:00
case "set" :
newOpt := f . opt
err := configstruct . Set ( configmap . Simple ( opt ) , & newOpt )
if err != nil {
return nil , fmt . Errorf ( "reading config: %w" , err )
}
2025-10-14 17:50:28 +01:00
c , _ , err := s3Connection ( f . ctx , & newOpt , f . srv )
2023-07-15 18:56:58 +01:00
if err != nil {
return nil , fmt . Errorf ( "updating session: %w" , err )
}
f . c = c
f . opt = newOpt
keys := [ ] string { }
for k := range opt {
keys = append ( keys , k )
}
fs . Logf ( f , "Updated config values: %s" , strings . Join ( keys , ", " ) )
return nil , nil
2020-06-24 11:02:34 +01:00
default :
return nil , fs . ErrorCommandNotFound
}
}
2023-09-06 12:42:03 +01:00
// Returned from "restore-status"
type restoreStatusOut struct {
Remote string
VersionID * string
2024-08-03 11:35:32 +01:00
RestoreStatus * types . RestoreStatus
StorageClass types . ObjectStorageClass
2023-09-06 12:42:03 +01:00
}
// Recursively enumerate the current fs to find objects with a restore status
func ( f * Fs ) restoreStatus ( ctx context . Context , all bool ) ( out [ ] restoreStatusOut , err error ) {
fs . Debugf ( f , "all = %v" , all )
bucket , directory := f . split ( "" )
out = [ ] restoreStatusOut { }
err = f . list ( ctx , listOpt {
bucket : bucket ,
directory : directory ,
prefix : f . rootDirectory ,
addBucket : f . rootBucket == "" ,
recurse : true ,
withVersions : f . opt . Versions ,
versionAt : f . opt . VersionAt ,
2023-11-08 15:29:23 +00:00
hidden : f . opt . VersionDeleted ,
2023-09-06 12:42:03 +01:00
restoreStatus : true ,
2024-08-03 11:35:32 +01:00
} , func ( remote string , object * types . Object , versionID * string , isDirectory bool ) error {
2023-09-06 12:42:03 +01:00
entry , err := f . itemToDirEntry ( ctx , remote , object , versionID , isDirectory )
if err != nil {
return err
}
if entry != nil {
if o , ok := entry . ( * Object ) ; ok && ( all || object . RestoreStatus != nil ) {
out = append ( out , restoreStatusOut {
Remote : o . remote ,
VersionID : o . versionID ,
RestoreStatus : object . RestoreStatus ,
StorageClass : object . StorageClass ,
} )
}
}
return nil
} )
if err != nil {
return nil , err
}
// bucket must be present if listing succeeded
f . cache . MarkOK ( bucket )
return out , nil
}
2020-06-25 16:11:05 +01:00
// listMultipartUploads lists all outstanding multipart uploads for (bucket, key)
//
// Note that rather lazily we treat key as a prefix so it matches
Spelling fixes
Fix spelling of: above, already, anonymous, associated,
authentication, bandwidth, because, between, blocks, calculate,
candidates, cautious, changelog, cleaner, clipboard, command,
completely, concurrently, considered, constructs, corrupt, current,
daemon, dependencies, deprecated, directory, dispatcher, download,
eligible, ellipsis, encrypter, endpoint, entrieslist, essentially,
existing writers, existing, expires, filesystem, flushing, frequently,
hierarchy, however, implementation, implements, inaccurate,
individually, insensitive, longer, maximum, metadata, modified,
multipart, namedirfirst, nextcloud, obscured, opened, optional,
owncloud, pacific, passphrase, password, permanently, persimmon,
positive, potato, protocol, quota, receiving, recommends, referring,
requires, revisited, satisfied, satisfies, satisfy, semver,
serialized, session, storage, strategies, stringlist, successful,
supported, surprise, temporarily, temporary, transactions, unneeded,
update, uploads, wrapped
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2020-10-08 20:17:24 -04:00
// directories and objects. This could surprise the user if they ask
2020-06-25 16:11:05 +01:00
// for "dir" and it returns "dirKey"
2024-08-03 11:35:32 +01:00
func ( f * Fs ) listMultipartUploads ( ctx context . Context , bucket , key string ) ( uploads [ ] types . MultipartUpload , err error ) {
2020-06-25 16:11:05 +01:00
var (
keyMarker * string
uploadIDMarker * string
)
2024-08-03 11:35:32 +01:00
uploads = [ ] types . MultipartUpload { }
2020-06-25 16:11:05 +01:00
for {
req := s3 . ListMultipartUploadsInput {
Bucket : & bucket ,
MaxUploads : & f . opt . ListChunk ,
KeyMarker : keyMarker ,
UploadIdMarker : uploadIDMarker ,
Prefix : & key ,
}
var resp * s3 . ListMultipartUploadsOutput
err = f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
resp , err = f . c . ListMultipartUploads ( ctx , & req )
2021-03-16 15:50:02 +00:00
return f . shouldRetry ( ctx , err )
2020-06-25 16:11:05 +01:00
} )
if err != nil {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "list multipart uploads bucket %q key %q: %w" , bucket , key , err )
2020-06-25 16:11:05 +01:00
}
uploads = append ( uploads , resp . Uploads ... )
2024-08-03 11:35:32 +01:00
if ! deref ( resp . IsTruncated ) {
2020-06-25 16:11:05 +01:00
break
}
keyMarker = resp . NextKeyMarker
uploadIDMarker = resp . NextUploadIdMarker
}
return uploads , nil
}
2024-08-03 11:35:32 +01:00
func ( f * Fs ) listMultipartUploadsAll ( ctx context . Context ) ( uploadsMap map [ string ] [ ] types . MultipartUpload , err error ) {
uploadsMap = make ( map [ string ] [ ] types . MultipartUpload )
2020-06-25 16:11:05 +01:00
bucket , directory := f . split ( "" )
if bucket != "" {
uploads , err := f . listMultipartUploads ( ctx , bucket , directory )
if err != nil {
return uploadsMap , err
}
uploadsMap [ bucket ] = uploads
return uploadsMap , nil
}
entries , err := f . listBuckets ( ctx )
if err != nil {
return uploadsMap , err
}
for _ , entry := range entries {
bucket := entry . Remote ( )
uploads , listErr := f . listMultipartUploads ( ctx , bucket , "" )
if listErr != nil {
err = listErr
fs . Errorf ( f , "%v" , err )
}
uploadsMap [ bucket ] = uploads
}
return uploadsMap , err
}
// cleanUpBucket removes all pending multipart uploads for a given bucket over the age of maxAge
2024-08-03 11:35:32 +01:00
func ( f * Fs ) cleanUpBucket ( ctx context . Context , bucket string , maxAge time . Duration , uploads [ ] types . MultipartUpload ) ( err error ) {
2020-06-25 16:11:05 +01:00
fs . Infof ( f , "cleaning bucket %q of pending multipart uploads older than %v" , bucket , maxAge )
for _ , upload := range uploads {
if upload . Initiated != nil && upload . Key != nil && upload . UploadId != nil {
age := time . Since ( * upload . Initiated )
what := fmt . Sprintf ( "pending multipart upload for bucket %q key %q dated %v (%v ago)" , bucket , * upload . Key , upload . Initiated , age )
if age > maxAge {
fs . Infof ( f , "removing %s" , what )
if operations . SkipDestructive ( ctx , what , "remove pending upload" ) {
continue
}
req := s3 . AbortMultipartUploadInput {
Bucket : & bucket ,
UploadId : upload . UploadId ,
Key : upload . Key ,
}
2024-08-03 11:35:32 +01:00
_ , abortErr := f . c . AbortMultipartUpload ( ctx , & req )
2020-06-25 16:11:05 +01:00
if abortErr != nil {
2021-11-04 10:12:57 +00:00
err = fmt . Errorf ( "failed to remove %s: %w" , what , abortErr )
2020-06-25 16:11:05 +01:00
fs . Errorf ( f , "%v" , err )
}
} else {
fs . Debugf ( f , "ignoring %s" , what )
}
}
}
return err
}
// CleanUp removes all pending multipart uploads
func ( f * Fs ) cleanUp ( ctx context . Context , maxAge time . Duration ) ( err error ) {
uploadsMap , err := f . listMultipartUploadsAll ( ctx )
if err != nil {
return err
}
for bucket , uploads := range uploadsMap {
cleanErr := f . cleanUpBucket ( ctx , bucket , maxAge , uploads )
if err != nil {
fs . Errorf ( f , "Failed to cleanup bucket %q: %v" , bucket , cleanErr )
err = cleanErr
}
}
return err
}
2022-07-25 16:05:17 +01:00
// Read whether the bucket is versioned or not
func ( f * Fs ) isVersioned ( ctx context . Context ) bool {
f . versioningMu . Lock ( )
defer f . versioningMu . Unlock ( )
if ! f . versioning . Valid {
_ , _ = f . setGetVersioning ( ctx )
fs . Debugf ( f , "bucket is versioned: %v" , f . versioning . Value )
}
return f . versioning . Value
}
// Set or get bucket versioning.
//
// Pass no arguments to get, or pass "Enabled" or "Suspended"
//
// Updates f.versioning
2024-08-03 11:35:32 +01:00
func ( f * Fs ) setGetVersioning ( ctx context . Context , arg ... string ) ( status types . BucketVersioningStatus , err error ) {
2022-07-25 16:05:17 +01:00
if len ( arg ) > 1 {
return "" , errors . New ( "too many arguments" )
}
if f . rootBucket == "" {
return "" , errors . New ( "need a bucket" )
}
if len ( arg ) == 1 {
2024-08-03 11:35:32 +01:00
var versioning = types . VersioningConfiguration {
Status : types . BucketVersioningStatus ( arg [ 0 ] ) ,
2022-07-25 16:05:17 +01:00
}
// Disabled is indicated by the parameter missing
2024-08-03 11:35:32 +01:00
if versioning . Status == types . BucketVersioningStatus ( "Disabled" ) {
versioning . Status = types . BucketVersioningStatus ( "" )
2022-07-25 16:05:17 +01:00
}
req := s3 . PutBucketVersioningInput {
Bucket : & f . rootBucket ,
VersioningConfiguration : & versioning ,
}
err := f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
_ , err = f . c . PutBucketVersioning ( ctx , & req )
2022-07-25 16:05:17 +01:00
return f . shouldRetry ( ctx , err )
} )
if err != nil {
return "" , err
}
}
req := s3 . GetBucketVersioningInput {
Bucket : & f . rootBucket ,
}
var resp * s3 . GetBucketVersioningOutput
err = f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
resp , err = f . c . GetBucketVersioning ( ctx , & req )
2022-07-25 16:05:17 +01:00
return f . shouldRetry ( ctx , err )
} )
f . versioning . Valid = true
f . versioning . Value = false
if err != nil {
fs . Errorf ( f , "Failed to read versioning status, assuming unversioned: %v" , err )
return "" , err
}
2024-08-03 11:35:32 +01:00
if len ( resp . Status ) == 0 {
2022-07-25 16:05:17 +01:00
return "Unversioned" , err
}
f . versioning . Value = true
2024-08-03 11:35:32 +01:00
return resp . Status , err
2022-07-25 16:05:17 +01:00
}
2020-06-25 16:11:05 +01:00
// CleanUp removes all pending multipart uploads older than 24 hours
func ( f * Fs ) CleanUp ( ctx context . Context ) ( err error ) {
return f . cleanUp ( ctx , 24 * time . Hour )
}
2022-07-26 15:03:32 +01:00
// purge deletes all the files and directories
//
// if oldOnly is true then it deletes only non current files.
//
// Implemented here so we can make sure we delete old versions.
func ( f * Fs ) purge ( ctx context . Context , dir string , oldOnly bool ) error {
2022-07-26 17:58:57 +01:00
if f . opt . VersionAt . IsSet ( ) {
return errNotWithVersionAt
}
2022-07-26 15:03:32 +01:00
bucket , directory := f . split ( dir )
if bucket == "" {
return errors . New ( "can't purge from root" )
}
versioned := f . isVersioned ( ctx )
if ! versioned && oldOnly {
fs . Infof ( f , "bucket is not versioned so not removing old versions" )
return nil
}
var errReturn error
var checkErrMutex sync . Mutex
var checkErr = func ( err error ) {
if err == nil {
return
}
checkErrMutex . Lock ( )
defer checkErrMutex . Unlock ( )
if errReturn == nil {
errReturn = err
}
}
// Delete Config.Transfers in parallel
delChan := make ( fs . ObjectsChan , f . ci . Transfers )
delErr := make ( chan error , 1 )
go func ( ) {
delErr <- operations . DeleteFiles ( ctx , delChan )
} ( )
2022-07-27 10:46:28 +01:00
checkErr ( f . list ( ctx , listOpt {
2023-02-27 15:32:59 +00:00
bucket : bucket ,
directory : directory ,
prefix : f . rootDirectory ,
addBucket : f . rootBucket == "" ,
recurse : true ,
withVersions : versioned ,
hidden : true ,
noSkipMarkers : true ,
2024-08-03 11:35:32 +01:00
} , func ( remote string , object * types . Object , versionID * string , isDirectory bool ) error {
2022-07-26 15:03:32 +01:00
if isDirectory {
return nil
}
2023-08-25 17:39:16 +01:00
// If the root is a dirmarker it will have lost its trailing /
if remote == "" {
remote = "/"
}
2022-07-26 15:03:32 +01:00
oi , err := f . newObjectWithInfo ( ctx , remote , object , versionID )
if err != nil {
fs . Errorf ( object , "Can't create object %+v" , err )
return nil
}
2023-02-06 10:30:22 +00:00
tr := accounting . Stats ( ctx ) . NewCheckingTransfer ( oi , "checking" )
2022-07-26 15:03:32 +01:00
// Work out whether the file is the current version or not
isCurrentVersion := ! versioned || ! version . Match ( remote )
fs . Debugf ( nil , "%q version %v" , remote , version . Match ( remote ) )
if oldOnly && isCurrentVersion {
// Check current version of the file
if object . Size == isDeleteMarker {
2024-08-03 11:35:32 +01:00
fs . Debugf ( remote , "Deleting current version (id %q) as it is a delete marker" , deref ( versionID ) )
2022-07-26 15:03:32 +01:00
delChan <- oi
} else {
2024-08-03 11:35:32 +01:00
fs . Debugf ( remote , "Not deleting current version %q" , deref ( versionID ) )
2022-07-26 15:03:32 +01:00
}
} else {
if object . Size == isDeleteMarker {
2024-08-03 11:35:32 +01:00
fs . Debugf ( remote , "Deleting delete marker (id %q)" , deref ( versionID ) )
2022-07-26 15:03:32 +01:00
} else {
2024-08-03 11:35:32 +01:00
fs . Debugf ( remote , "Deleting (id %q)" , deref ( versionID ) )
2022-07-26 15:03:32 +01:00
}
delChan <- oi
}
tr . Done ( ctx , nil )
return nil
} ) )
close ( delChan )
checkErr ( <- delErr )
if ! oldOnly {
checkErr ( f . Rmdir ( ctx , dir ) )
}
return errReturn
}
// Purge deletes all the files and directories including the old versions.
func ( f * Fs ) Purge ( ctx context . Context , dir string ) error {
return f . purge ( ctx , dir , false )
}
// CleanUpHidden deletes all the hidden files.
func ( f * Fs ) CleanUpHidden ( ctx context . Context ) error {
return f . purge ( ctx , "" , true )
}
2013-01-08 18:53:35 +00:00
// ------------------------------------------------------------
2015-09-22 18:47:16 +01:00
// Fs returns the parent Fs
2016-02-18 12:35:25 +01:00
func ( o * Object ) Fs ( ) fs . Info {
2015-11-07 11:14:46 +00:00
return o . fs
2014-03-28 17:56:04 +00:00
}
// Return a string version
2015-11-07 11:14:46 +00:00
func ( o * Object ) String ( ) string {
2014-03-28 17:56:04 +00:00
if o == nil {
return "<nil>"
}
return o . remote
}
2015-09-22 18:47:16 +01:00
// Remote returns the remote path
2015-11-07 11:14:46 +00:00
func ( o * Object ) Remote ( ) string {
2013-06-27 20:13:07 +01:00
return o . remote
2013-01-08 18:53:35 +00:00
}
2015-05-09 10:37:43 +01:00
var matchMd5 = regexp . MustCompile ( ` ^[0-9a-f] { 32}$ ` )
2020-11-23 11:23:06 +00:00
// Set the MD5 from the etag
func ( o * Object ) setMD5FromEtag ( etag string ) {
if o . fs . etagIsNotMD5 {
o . md5 = ""
return
}
if etag == "" {
o . md5 = ""
return
}
hash := strings . Trim ( strings . ToLower ( etag ) , ` " ` )
// Check the etag is a valid md5sum
if ! matchMd5 . MatchString ( hash ) {
o . md5 = ""
return
}
o . md5 = hash
}
2016-01-11 13:39:33 +01:00
// Hash returns the Md5sum of an object returning a lowercase hex string
2019-06-17 10:34:30 +02:00
func ( o * Object ) Hash ( ctx context . Context , t hash . Type ) ( string , error ) {
2018-01-18 20:27:52 +00:00
if t != hash . MD5 {
return "" , hash . ErrUnsupported
2016-01-11 13:39:33 +01:00
}
2022-07-29 17:01:59 +01:00
// If decompressing, erase the hash
if o . bytes < 0 {
return "" , nil
}
2020-11-23 11:23:06 +00:00
// If we haven't got an MD5, then check the metadata
if o . md5 == "" {
2019-06-17 10:34:30 +02:00
err := o . readMetaData ( ctx )
2018-01-06 09:30:10 -05:00
if err != nil {
return "" , err
}
2015-05-09 10:37:43 +01:00
}
2020-11-23 11:23:06 +00:00
return o . md5 , nil
2013-01-08 18:53:35 +00:00
}
// Size returns the size of an object in bytes
2015-11-07 11:14:46 +00:00
func ( o * Object ) Size ( ) int64 {
2013-06-27 20:13:07 +01:00
return o . bytes
2013-01-08 18:53:35 +00:00
}
2020-07-30 10:52:32 +01:00
func ( o * Object ) headObject ( ctx context . Context ) ( resp * s3 . HeadObjectOutput , err error ) {
2019-08-09 11:29:36 +01:00
bucket , bucketPath := o . split ( )
2014-12-23 12:09:02 +00:00
req := s3 . HeadObjectInput {
2022-07-25 16:06:15 +01:00
Bucket : & bucket ,
Key : & bucketPath ,
VersionId : o . versionID ,
2014-07-28 22:32:15 +01:00
}
2023-04-26 10:59:17 +01:00
return o . fs . headObject ( ctx , & req )
}
func ( f * Fs ) headObject ( ctx context . Context , req * s3 . HeadObjectInput ) ( resp * s3 . HeadObjectOutput , err error ) {
if f . opt . RequesterPays {
2024-08-03 11:35:32 +01:00
req . RequestPayer = types . RequestPayerRequester
2020-12-03 10:30:06 +08:00
}
2023-04-26 10:59:17 +01:00
if f . opt . SSECustomerAlgorithm != "" {
req . SSECustomerAlgorithm = & f . opt . SSECustomerAlgorithm
2020-11-20 11:15:48 +00:00
}
2024-08-07 10:14:58 +01:00
if f . opt . SSECustomerKeyBase64 != "" {
req . SSECustomerKey = & f . opt . SSECustomerKeyBase64
2020-11-20 11:15:48 +00:00
}
2023-04-26 10:59:17 +01:00
if f . opt . SSECustomerKeyMD5 != "" {
req . SSECustomerKeyMD5 = & f . opt . SSECustomerKeyMD5
2020-11-20 11:15:48 +00:00
}
2023-04-26 10:59:17 +01:00
err = f . pacer . Call ( func ( ) ( bool , error ) {
2018-09-03 16:41:04 +12:00
var err error
2024-08-03 11:35:32 +01:00
resp , err = f . c . HeadObject ( ctx , req )
2023-04-26 10:59:17 +01:00
return f . shouldRetry ( ctx , err )
2018-09-03 16:41:04 +12:00
} )
2013-01-08 18:53:35 +00:00
if err != nil {
2024-08-03 11:35:32 +01:00
if statusCode := getHTTPStatusCode ( err ) ; statusCode == http . StatusNotFound || statusCode == http . StatusMethodNotAllowed {
return nil , fs . ErrorObjectNotFound
2016-06-25 21:23:20 +01:00
}
2020-07-30 10:52:32 +01:00
return nil , err
2013-01-08 18:53:35 +00:00
}
2023-04-26 10:59:17 +01:00
if req . Bucket != nil {
f . cache . MarkOK ( * req . Bucket )
}
2020-07-30 10:52:32 +01:00
return resp , nil
}
// readMetaData gets the metadata if it hasn't already been fetched
//
// it also sets the info
func ( o * Object ) readMetaData ( ctx context . Context ) ( err error ) {
if o . meta != nil {
return nil
}
resp , err := o . headObject ( ctx )
if err != nil {
return err
}
2022-05-24 12:32:39 +01:00
o . setMetaData ( resp )
// resp.ETag, resp.ContentLength, resp.LastModified, resp.Metadata, resp.ContentType, resp.StorageClass)
2021-04-28 19:05:54 +09:00
return nil
}
2024-08-03 11:35:32 +01:00
// Convert S3 metadata into a map[string]string while lowercasing the
// keys
func s3MetadataToMap ( s3Meta map [ string ] string ) map [ string ] string {
2022-06-22 15:40:30 +01:00
meta := make ( map [ string ] string , len ( s3Meta ) )
for k , v := range s3Meta {
2025-07-14 12:25:10 -04:00
meta [ strings . ToLower ( k ) ] = * stringClone ( v )
2022-06-22 15:40:30 +01:00
}
return meta
}
// Convert our metadata back into S3 metadata
2024-08-03 11:35:32 +01:00
func mapToS3Metadata ( meta map [ string ] string ) map [ string ] string {
return meta
2022-06-22 15:40:30 +01:00
}
2022-05-24 12:32:39 +01:00
func ( o * Object ) setMetaData ( resp * s3 . HeadObjectOutput ) {
2014-05-16 16:27:53 +01:00
// Ignore missing Content-Length assuming it is 0
// Some versions of ceph do this due their apache proxies
2022-05-24 12:32:39 +01:00
if resp . ContentLength != nil {
o . bytes = * resp . ContentLength
2013-01-08 18:53:35 +00:00
}
2024-08-03 11:35:32 +01:00
o . setMD5FromEtag ( deref ( resp . ETag ) )
2022-05-24 12:32:39 +01:00
o . meta = s3MetadataToMap ( resp . Metadata )
2020-11-23 11:23:06 +00:00
// Read MD5 from metadata if present
if md5sumBase64 , ok := o . meta [ metaMD5Hash ] ; ok {
2022-06-22 15:40:30 +01:00
md5sumBytes , err := base64 . StdEncoding . DecodeString ( md5sumBase64 )
2020-11-23 11:23:06 +00:00
if err != nil {
2022-06-22 15:40:30 +01:00
fs . Debugf ( o , "Failed to read md5sum from metadata %q: %v" , md5sumBase64 , err )
2020-11-23 11:23:06 +00:00
} else if len ( md5sumBytes ) != 16 {
2022-06-22 15:40:30 +01:00
fs . Debugf ( o , "Failed to read md5sum from metadata %q: wrong length" , md5sumBase64 )
2020-11-23 11:23:06 +00:00
} else {
o . md5 = hex . EncodeToString ( md5sumBytes )
}
}
2022-05-24 12:32:39 +01:00
if resp . LastModified == nil {
2013-06-27 20:13:07 +01:00
o . lastModified = time . Now ( )
2022-02-16 17:50:11 +00:00
fs . Logf ( o , "Failed to read last modified" )
2014-12-23 12:09:02 +00:00
} else {
2022-10-08 11:27:17 +01:00
// Try to keep the maximum precision in lastModified. If we read
// it from listings then it may have millisecond precision, but
// if we read it from a HEAD/GET request then it will have
// second precision.
2024-05-31 15:14:40 +02:00
equalToWithinOneSecond := o . lastModified . Truncate ( time . Second ) . Equal ( resp . LastModified . Truncate ( time . Second ) )
newHasNs := resp . LastModified . Nanosecond ( ) != 0
2022-10-08 11:27:17 +01:00
if ! equalToWithinOneSecond || newHasNs {
o . lastModified = * resp . LastModified
}
2013-01-08 18:53:35 +00:00
}
2025-07-14 12:25:10 -04:00
o . mimeType = strings . Clone ( deref ( resp . ContentType ) )
2022-05-24 12:32:39 +01:00
// Set system metadata
2025-07-14 12:25:10 -04:00
o . storageClass = stringClone ( string ( resp . StorageClass ) )
o . cacheControl = stringClonePointer ( resp . CacheControl )
o . contentDisposition = stringClonePointer ( resp . ContentDisposition )
2025-08-16 10:36:50 +01:00
o . contentEncoding = stringClonePointer ( removeAWSChunked ( resp . ContentEncoding ) )
2025-07-14 12:25:10 -04:00
o . contentLanguage = stringClonePointer ( resp . ContentLanguage )
2022-07-29 17:01:59 +01:00
// If decompressing then size and md5sum are unknown
2024-08-03 11:35:32 +01:00
if o . fs . opt . Decompress && deref ( o . contentEncoding ) == "gzip" {
2022-07-29 17:01:59 +01:00
o . bytes = - 1
o . md5 = ""
}
2013-01-08 18:53:35 +00:00
}
// ModTime returns the modification time of the object
//
// It attempts to read the objects mtime and if that isn't present the
// LastModified returned in the http headers
2019-06-17 10:34:30 +02:00
func ( o * Object ) ModTime ( ctx context . Context ) time . Time {
2020-11-05 11:33:32 +00:00
if o . fs . ci . UseServerModTime {
2018-04-13 06:32:17 -06:00
return o . lastModified
}
2019-06-17 10:34:30 +02:00
err := o . readMetaData ( ctx )
2013-01-08 18:53:35 +00:00
if err != nil {
2017-02-09 11:01:20 +00:00
fs . Logf ( o , "Failed to read metadata: %v" , err )
2013-01-08 18:53:35 +00:00
return time . Now ( )
}
// read mtime out of metadata if available
2013-06-27 20:13:07 +01:00
d , ok := o . meta [ metaMtime ]
2022-06-22 15:40:30 +01:00
if ! ok {
2017-02-09 11:01:20 +00:00
// fs.Debugf(o, "No metadata")
2013-06-27 20:13:07 +01:00
return o . lastModified
2013-01-08 18:53:35 +00:00
}
2022-06-22 15:40:30 +01:00
modTime , err := swift . FloatStringToTime ( d )
2013-01-08 18:53:35 +00:00
if err != nil {
2017-02-09 11:01:20 +00:00
fs . Logf ( o , "Failed to read mtime from object: %v" , err )
2013-06-27 20:13:07 +01:00
return o . lastModified
2013-01-08 18:53:35 +00:00
}
return modTime
}
2015-09-22 18:47:16 +01:00
// SetModTime sets the modification time of the local fs object
2019-06-17 10:34:30 +02:00
func ( o * Object ) SetModTime ( ctx context . Context , modTime time . Time ) error {
2025-10-10 14:29:22 +01:00
if o . fs . opt . Provider == "Rabata" {
// Rabata does not support copying objects
return fs . ErrorCantSetModTime
}
2019-06-17 10:34:30 +02:00
err := o . readMetaData ( ctx )
2013-01-08 22:31:16 +00:00
if err != nil {
2016-03-22 15:07:10 +00:00
return err
2013-01-08 22:31:16 +00:00
}
2022-06-22 15:40:30 +01:00
o . meta [ metaMtime ] = swift . TimeToFloatString ( modTime )
2014-12-23 12:09:02 +00:00
2019-09-09 20:44:50 +01:00
// Can't update metadata here, so return this error to force a recopy
2022-05-24 12:32:39 +01:00
if o . storageClass != nil && ( * o . storageClass == "GLACIER" || * o . storageClass == "DEEP_ARCHIVE" ) {
2019-09-09 20:44:50 +01:00
return fs . ErrorCantSetModTime
}
2016-01-02 03:58:48 -05:00
2014-12-23 12:09:02 +00:00
// Copy the object to itself to update the metadata
2019-08-09 11:29:36 +01:00
bucket , bucketPath := o . split ( )
2014-12-23 12:09:02 +00:00
req := s3 . CopyObjectInput {
2019-09-09 20:44:50 +01:00
ContentType : aws . String ( fs . MimeType ( ctx , o ) ) , // Guess the content type
2022-06-22 15:40:30 +01:00
Metadata : mapToS3Metadata ( o . meta ) ,
2024-08-03 11:35:32 +01:00
MetadataDirective : types . MetadataDirectiveReplace , // replace metadata with that passed in
2019-06-03 08:28:19 -06:00
}
2020-12-03 10:30:06 +08:00
if o . fs . opt . RequesterPays {
2024-08-03 11:35:32 +01:00
req . RequestPayer = types . RequestPayerRequester
2020-12-03 10:30:06 +08:00
}
2020-07-30 10:52:32 +01:00
return o . fs . copy ( ctx , & req , bucket , bucketPath , bucket , bucketPath , o )
2013-01-08 18:53:35 +00:00
}
2015-09-22 18:47:16 +01:00
// Storable raturns a boolean indicating if this object is storable
2015-11-07 11:14:46 +00:00
func ( o * Object ) Storable ( ) bool {
2013-01-08 18:53:35 +00:00
return true
}
2025-08-16 10:36:50 +01:00
// removeAWSChunked removes the "aws-chunked" content-coding from a
// Content-Encoding field value (RFC 9110). Comparison is case-insensitive.
// Returns nil if encoding is empty after removal.
func removeAWSChunked ( pv * string ) * string {
if pv == nil {
return nil
}
v := * pv
if v == "" {
return nil
}
if ! strings . Contains ( strings . ToLower ( v ) , "aws-chunked" ) {
return pv
}
parts := strings . Split ( v , "," )
out := make ( [ ] string , 0 , len ( parts ) )
for _ , p := range parts {
tok := strings . TrimSpace ( p )
if tok == "" || strings . EqualFold ( tok , "aws-chunked" ) {
continue
}
out = append ( out , tok )
}
if len ( out ) == 0 {
return nil
}
v = strings . Join ( out , "," )
return & v
}
2021-10-14 15:49:38 +05:30
func ( o * Object ) downloadFromURL ( ctx context . Context , bucketPath string , options ... fs . OpenOption ) ( in io . ReadCloser , err error ) {
url := o . fs . opt . DownloadURL + bucketPath
var resp * http . Response
opts := rest . Opts {
Method : "GET" ,
RootURL : url ,
Options : options ,
}
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
resp , err = o . fs . srvRest . Call ( ctx , & opts )
return o . fs . shouldRetry ( ctx , err )
} )
if err != nil {
return nil , err
}
2022-03-20 18:12:48 +08:00
contentLength := rest . ParseSizeFromHeaders ( resp . Header )
if contentLength < 0 {
fs . Debugf ( o , "Failed to parse file size from headers" )
2021-10-14 15:49:38 +05:30
}
2022-03-20 18:12:48 +08:00
lastModified , err := http . ParseTime ( resp . Header . Get ( "Last-Modified" ) )
2021-10-14 15:49:38 +05:30
if err != nil {
fs . Debugf ( o , "Failed to parse last modified from string %s, %v" , resp . Header . Get ( "Last-Modified" ) , err )
}
2024-08-03 11:35:32 +01:00
metaData := make ( map [ string ] string )
2021-10-14 15:49:38 +05:30
for key , value := range resp . Header {
2022-05-24 12:32:39 +01:00
key = strings . ToLower ( key )
2025-09-14 16:17:40 +01:00
if after , ok := strings . CutPrefix ( key , "x-amz-meta-" ) ; ok {
metaKey := after
2024-08-03 11:35:32 +01:00
metaData [ metaKey ] = value [ 0 ]
2021-10-14 15:49:38 +05:30
}
}
2022-05-24 12:32:39 +01:00
header := func ( k string ) * string {
v := resp . Header . Get ( k )
if v == "" {
return nil
}
return & v
}
var head = s3 . HeadObjectOutput {
ETag : header ( "Etag" ) ,
2022-03-20 18:12:48 +08:00
ContentLength : & contentLength ,
2022-05-24 12:32:39 +01:00
LastModified : & lastModified ,
Metadata : metaData ,
CacheControl : header ( "Cache-Control" ) ,
ContentDisposition : header ( "Content-Disposition" ) ,
ContentEncoding : header ( "Content-Encoding" ) ,
ContentLanguage : header ( "Content-Language" ) ,
ContentType : header ( "Content-Type" ) ,
2024-10-03 10:21:05 +01:00
StorageClass : types . StorageClass ( deref ( header ( "X-Amz-Storage-Class" ) ) ) ,
2022-05-24 12:32:39 +01:00
}
o . setMetaData ( & head )
2021-10-14 15:49:38 +05:30
return resp . Body , err
}
2024-10-22 20:12:18 +01:00
// middleware to stop the SDK adding `Accept-Encoding: identity`
func removeDisableGzip ( ) func ( * middleware . Stack ) error {
return func ( stack * middleware . Stack ) error {
_ , err := stack . Finalize . Remove ( "DisableAcceptEncodingGzip" )
return err
}
}
// middleware to set Accept-Encoding to how we want it
//
// This make sure we download compressed files as-is from all platforms
func ( f * Fs ) acceptEncoding ( ) ( APIOptions [ ] func ( * middleware . Stack ) error ) {
APIOptions = append ( APIOptions , removeDisableGzip ( ) )
if f . opt . UseAcceptEncodingGzip . Value {
APIOptions = append ( APIOptions , smithyhttp . AddHeaderValue ( "Accept-Encoding" , "gzip" ) )
}
return APIOptions
}
2013-01-08 18:53:35 +00:00
// Open an object for read
2019-06-17 10:34:30 +02:00
func ( o * Object ) Open ( ctx context . Context , options ... fs . OpenOption ) ( in io . ReadCloser , err error ) {
2019-08-09 11:29:36 +01:00
bucket , bucketPath := o . split ( )
2021-10-14 15:49:38 +05:30
if o . fs . opt . DownloadURL != "" {
return o . downloadFromURL ( ctx , bucketPath , options ... )
}
2014-12-23 12:09:02 +00:00
req := s3 . GetObjectInput {
2022-07-25 16:06:15 +01:00
Bucket : & bucket ,
Key : & bucketPath ,
VersionId : o . versionID ,
2014-12-23 12:09:02 +00:00
}
2020-12-03 10:30:06 +08:00
if o . fs . opt . RequesterPays {
2024-08-03 11:35:32 +01:00
req . RequestPayer = types . RequestPayerRequester
2020-12-03 10:30:06 +08:00
}
2020-03-30 11:26:52 +01:00
if o . fs . opt . SSECustomerAlgorithm != "" {
req . SSECustomerAlgorithm = & o . fs . opt . SSECustomerAlgorithm
}
2024-08-07 10:14:58 +01:00
if o . fs . opt . SSECustomerKeyBase64 != "" {
req . SSECustomerKey = & o . fs . opt . SSECustomerKeyBase64
2020-03-30 11:26:52 +01:00
}
if o . fs . opt . SSECustomerKeyMD5 != "" {
req . SSECustomerKeyMD5 = & o . fs . opt . SSECustomerKeyMD5
}
2024-08-03 11:35:32 +01:00
// httpReq, err := s3.NewPresignClient(o.fs.c).PresignGetObject(ctx, &req)
// if err != nil {
// return nil, err
// }
2019-08-06 15:18:08 +01:00
fs . FixRangeOption ( options , o . bytes )
2022-07-29 17:01:59 +01:00
2024-08-03 11:35:32 +01:00
var APIOptions [ ] func ( * middleware . Stack ) error
2024-10-22 20:12:18 +01:00
// Set the SDK to always download compressed files as-is
APIOptions = append ( APIOptions , o . fs . acceptEncoding ( ) ... )
2022-07-29 17:01:59 +01:00
2016-09-10 11:29:57 +01:00
for _ , option := range options {
switch option . ( type ) {
case * fs . RangeOption , * fs . SeekOption :
_ , value := option . Header ( )
req . Range = & value
2020-02-10 01:02:04 -08:00
case * fs . HTTPOption :
key , value := option . Header ( )
2024-08-03 11:35:32 +01:00
APIOptions = append ( APIOptions , smithyhttp . AddHeaderValue ( key , value ) )
2016-09-10 11:29:57 +01:00
default :
if option . Mandatory ( ) {
2017-02-09 11:01:20 +00:00
fs . Logf ( o , "Unsupported mandatory option: %v" , option )
2016-09-10 11:29:57 +01:00
}
}
}
2024-08-03 11:35:32 +01:00
var resp * s3 . GetObjectOutput
2018-09-03 16:41:04 +12:00
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
var err error
2024-08-03 11:35:32 +01:00
resp , err = o . fs . c . GetObject ( ctx , & req , s3 . WithAPIOptions ( APIOptions ... ) )
2021-03-16 15:50:02 +00:00
return o . fs . shouldRetry ( ctx , err )
2018-09-03 16:41:04 +12:00
} )
2024-08-03 11:35:32 +01:00
var awsError smithy . APIError
if errors . As ( err , & awsError ) {
if awsError . ErrorCode ( ) == "InvalidObjectState" {
2021-11-04 10:12:57 +00:00
return nil , fmt . Errorf ( "Object in GLACIER, restore first: bucket=%q, key=%q" , bucket , bucketPath )
2017-09-09 15:02:26 +03:00
}
}
2014-12-23 12:09:02 +00:00
if err != nil {
return nil , err
}
2022-02-16 17:50:11 +00:00
2021-04-28 19:05:54 +09:00
// read size from ContentLength or ContentRange
size := resp . ContentLength
if resp . ContentRange != nil {
var contentRange = * resp . ContentRange
slash := strings . IndexRune ( contentRange , '/' )
if slash >= 0 {
i , err := strconv . ParseInt ( contentRange [ slash + 1 : ] , 10 , 64 )
if err == nil {
size = & i
} else {
fs . Debugf ( o , "Failed to find parse integer from in %q: %v" , contentRange , err )
}
} else {
fs . Debugf ( o , "Failed to find length in %q" , contentRange )
}
}
2022-05-24 12:32:39 +01:00
var head s3 . HeadObjectOutput
2022-07-28 11:49:19 +01:00
//structs.SetFrom(&head, resp)
setFrom_s3HeadObjectOutput_s3GetObjectOutput ( & head , resp )
2022-05-24 12:32:39 +01:00
head . ContentLength = size
o . setMetaData ( & head )
2022-07-29 17:01:59 +01:00
// Decompress body if necessary
2025-08-16 10:36:50 +01:00
if deref ( removeAWSChunked ( resp . ContentEncoding ) ) == "gzip" {
2022-10-28 23:23:29 +01:00
if o . fs . opt . Decompress || ( resp . ContentLength == nil && o . fs . opt . MightGzip . Value ) {
2022-07-29 17:01:59 +01:00
return readers . NewGzipReader ( resp . Body )
}
o . fs . warnCompressed . Do ( func ( ) {
fs . Logf ( o , "Not decompressing 'Content-Encoding: gzip' compressed file. Use --s3-decompress to override" )
} )
}
2014-12-23 12:09:02 +00:00
return resp . Body , nil
2013-01-08 18:53:35 +00:00
}
2019-11-06 10:41:03 +00:00
var warnStreamUpload sync . Once
2023-08-15 20:38:02 +01:00
// state of ChunkWriter
type s3ChunkWriter struct {
chunkSize int64
size int64
f * Fs
bucket * string
key * string
2023-08-29 12:57:33 +01:00
uploadID * string
2023-08-15 20:38:02 +01:00
multiPartUploadInput * s3 . CreateMultipartUploadInput
completedPartsMu sync . Mutex
2024-08-03 11:35:32 +01:00
completedParts [ ] types . CompletedPart
2023-08-15 20:38:02 +01:00
eTag string
versionID string
md5sMu sync . Mutex
md5s [ ] byte
ui uploadInfo
o * Object
}
2023-08-15 14:27:21 +01:00
// OpenChunkWriter returns the chunk size and a ChunkWriter
//
// Pass in the remote and the src object
// You can also use options to hint at the desired chunk size
2023-09-01 17:25:15 +01:00
func ( f * Fs ) OpenChunkWriter ( ctx context . Context , remote string , src fs . ObjectInfo , options ... fs . OpenOption ) ( info fs . ChunkWriterInfo , writer fs . ChunkWriter , err error ) {
2023-07-18 20:37:31 +02:00
// Temporary Object under construction
o := & Object {
fs : f ,
remote : remote ,
2019-12-30 23:17:06 +00:00
}
2024-03-05 17:21:06 +00:00
ui , err := o . prepareUpload ( ctx , src , options , false )
2023-07-18 20:37:31 +02:00
if err != nil {
2023-09-01 17:25:15 +01:00
return info , nil , fmt . Errorf ( "failed to prepare upload: %w" , err )
2023-07-18 20:37:31 +02:00
}
//structs.SetFrom(&mReq, req)
var mReq s3 . CreateMultipartUploadInput
2023-08-15 20:38:02 +01:00
setFrom_s3CreateMultipartUploadInput_s3PutObjectInput ( & mReq , ui . req )
2019-12-30 23:17:06 +00:00
2020-06-08 19:22:34 +02:00
uploadParts := f . opt . MaxUploadParts
if uploadParts < 1 {
uploadParts = 1
} else if uploadParts > maxUploadParts {
uploadParts = maxUploadParts
}
2023-07-18 20:37:31 +02:00
size := src . Size ( )
2020-06-08 19:22:34 +02:00
2019-12-30 23:17:06 +00:00
// calculate size of parts
2023-07-18 20:37:31 +02:00
chunkSize := f . opt . ChunkSize
2019-12-30 23:17:06 +00:00
2021-03-02 20:11:57 +01:00
// size can be -1 here meaning we don't know the size of the incoming file. We use ChunkSize
// buffers here (default 5 MiB). With a maximum number of parts (10,000) this will be a file of
// 48 GiB which seems like a not too unreasonable limit.
2019-12-30 23:17:06 +00:00
if size == - 1 {
warnStreamUpload . Do ( func ( ) {
fs . Logf ( f , "Streaming uploads using chunk size %v will have maximum file size of %v" ,
2023-07-18 20:37:31 +02:00
f . opt . ChunkSize , fs . SizeSuffix ( int64 ( chunkSize ) * int64 ( uploadParts ) ) )
2019-12-30 23:17:06 +00:00
} )
} else {
2023-07-18 20:37:31 +02:00
chunkSize = chunksize . Calculator ( src , size , uploadParts , chunkSize )
2019-12-30 23:17:06 +00:00
}
2023-08-15 14:27:21 +01:00
var mOut * s3 . CreateMultipartUploadOutput
err = f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
mOut , err = f . c . CreateMultipartUpload ( ctx , & mReq )
2023-12-16 11:34:38 +00:00
if err == nil {
if mOut == nil {
err = fserrors . RetryErrorf ( "internal error: no info from multipart upload" )
} else if mOut . UploadId == nil {
2025-01-15 14:01:06 +01:00
err = fserrors . RetryErrorf ( "internal error: no UploadId in multipart upload: %#v" , * mOut )
2023-12-16 11:34:38 +00:00
}
}
2023-08-15 14:27:21 +01:00
return f . shouldRetry ( ctx , err )
} )
2023-07-18 20:37:31 +02:00
if err != nil {
2023-09-01 17:25:15 +01:00
return info , nil , fmt . Errorf ( "create multipart upload failed: %w" , err )
2023-07-18 20:37:31 +02:00
}
chunkWriter := & s3ChunkWriter {
chunkSize : int64 ( chunkSize ) ,
2023-08-15 14:27:21 +01:00
size : size ,
2023-07-18 20:37:31 +02:00
f : f ,
2024-11-14 15:27:14 +00:00
bucket : ui . req . Bucket ,
key : ui . req . Key ,
2023-08-29 12:57:33 +01:00
uploadID : mOut . UploadId ,
2023-07-18 20:37:31 +02:00
multiPartUploadInput : & mReq ,
2024-08-03 11:35:32 +01:00
completedParts : make ( [ ] types . CompletedPart , 0 ) ,
2023-08-15 20:38:02 +01:00
ui : ui ,
o : o ,
2023-07-18 20:37:31 +02:00
}
2023-09-01 17:25:15 +01:00
info = fs . ChunkWriterInfo {
ChunkSize : int64 ( chunkSize ) ,
Concurrency : o . fs . opt . UploadConcurrency ,
LeavePartsOnError : o . fs . opt . LeavePartsOnError ,
}
2023-08-15 20:38:02 +01:00
fs . Debugf ( o , "open chunk writer: started multipart upload: %v" , * mOut . UploadId )
2023-09-01 17:25:15 +01:00
return info , chunkWriter , err
2023-07-18 20:37:31 +02:00
}
2020-02-19 11:17:25 +01:00
2023-08-15 14:27:21 +01:00
// add a part number and etag to the completed parts
2024-08-03 11:35:32 +01:00
func ( w * s3ChunkWriter ) addCompletedPart ( partNum * int32 , eTag * string ) {
2023-08-15 14:27:21 +01:00
w . completedPartsMu . Lock ( )
defer w . completedPartsMu . Unlock ( )
2024-08-03 11:35:32 +01:00
w . completedParts = append ( w . completedParts , types . CompletedPart {
2023-08-15 14:27:21 +01:00
PartNumber : partNum ,
ETag : eTag ,
} )
}
// addMd5 adds a binary md5 to the md5 calculated so far
func ( w * s3ChunkWriter ) addMd5 ( md5binary * [ ] byte , chunkNumber int64 ) {
w . md5sMu . Lock ( )
defer w . md5sMu . Unlock ( )
start := chunkNumber * md5 . Size
end := start + md5 . Size
if extend := end - int64 ( len ( w . md5s ) ) ; extend > 0 {
w . md5s = append ( w . md5s , make ( [ ] byte , extend ) ... )
}
2024-05-31 14:33:10 +02:00
copy ( w . md5s [ start : end ] , ( * md5binary ) )
2023-08-15 14:27:21 +01:00
}
// WriteChunk will write chunk number with reader bytes, where chunk number >= 0
2023-08-19 17:30:55 +01:00
func ( w * s3ChunkWriter ) WriteChunk ( ctx context . Context , chunkNumber int , reader io . ReadSeeker ) ( int64 , error ) {
2023-07-18 20:37:31 +02:00
if chunkNumber < 0 {
err := fmt . Errorf ( "invalid chunk number provided: %v" , chunkNumber )
return - 1 , err
}
2023-08-24 17:15:18 +01:00
// Only account after the checksum reads have been done
if do , ok := reader . ( pool . DelayAccountinger ) ; ok {
// To figure out this number, do a transfer and if the accounted size is 0 or a
// multiple of what it should be, increase or decrease this number.
2024-08-31 08:32:55 +01:00
//
// For transfers over https the SDK does not sign the body whereas over http it does
if len ( w . f . opt . Endpoint ) >= 5 && strings . EqualFold ( w . f . opt . Endpoint [ : 5 ] , "http:" ) {
do . DelayAccounting ( 3 )
} else {
do . DelayAccounting ( 2 )
}
2023-08-24 17:15:18 +01:00
}
2023-07-18 20:37:31 +02:00
// create checksum of buffer for integrity checking
// currently there is no way to calculate the md5 without reading the chunk a 2nd time (1st read is in uploadMultipart)
// possible in AWS SDK v2 with trailers?
m := md5 . New ( )
currentChunkSize , err := io . Copy ( m , reader )
2023-08-15 20:38:02 +01:00
if err != nil {
2023-07-18 20:37:31 +02:00
return - 1 , err
}
2023-09-03 12:37:20 +01:00
// If no data read and not the first chunk, don't write the chunk
if currentChunkSize == 0 && chunkNumber != 0 {
2023-08-15 20:38:02 +01:00
return 0 , nil
}
2023-07-18 20:37:31 +02:00
md5sumBinary := m . Sum ( [ ] byte { } )
2023-08-15 14:27:21 +01:00
w . addMd5 ( & md5sumBinary , int64 ( chunkNumber ) )
2024-05-31 14:33:10 +02:00
md5sum := base64 . StdEncoding . EncodeToString ( md5sumBinary )
2023-07-18 20:37:31 +02:00
// S3 requires 1 <= PartNumber <= 10000
2024-08-03 11:35:32 +01:00
s3PartNumber := aws . Int32 ( int32 ( chunkNumber + 1 ) )
2023-07-18 20:37:31 +02:00
uploadPartReq := & s3 . UploadPartInput {
Body : reader ,
Bucket : w . bucket ,
Key : w . key ,
PartNumber : s3PartNumber ,
2023-08-29 12:57:33 +01:00
UploadId : w . uploadID ,
2023-07-18 20:37:31 +02:00
ContentMD5 : & md5sum ,
ContentLength : aws . Int64 ( currentChunkSize ) ,
RequestPayer : w . multiPartUploadInput . RequestPayer ,
SSECustomerAlgorithm : w . multiPartUploadInput . SSECustomerAlgorithm ,
SSECustomerKey : w . multiPartUploadInput . SSECustomerKey ,
SSECustomerKeyMD5 : w . multiPartUploadInput . SSECustomerKeyMD5 ,
}
2024-09-13 18:56:22 +01:00
if w . f . opt . DirectoryBucket {
// Directory buckets do not support "Content-Md5" header
uploadPartReq . ContentMD5 = nil
}
2023-08-15 14:27:21 +01:00
var uout * s3 . UploadPartOutput
err = w . f . pacer . Call ( func ( ) ( bool , error ) {
2023-08-15 20:38:02 +01:00
// rewind the reader on retry and after reading md5
_ , err = reader . Seek ( 0 , io . SeekStart )
if err != nil {
return false , err
}
2024-08-03 11:35:32 +01:00
uout , err = w . f . c . UploadPart ( ctx , uploadPartReq )
2023-08-15 14:27:21 +01:00
if err != nil {
if chunkNumber <= 8 {
2023-08-15 20:38:02 +01:00
return w . f . shouldRetry ( ctx , err )
2023-08-15 14:27:21 +01:00
}
2024-12-20 16:45:57 +00:00
if fserrors . ContextError ( ctx , & err ) {
return false , err
}
2023-08-15 14:27:21 +01:00
// retry all chunks once have done the first few
return true , err
}
return false , nil
} )
2023-07-18 20:37:31 +02:00
if err != nil {
2023-08-15 14:27:21 +01:00
return - 1 , fmt . Errorf ( "failed to upload chunk %d with %v bytes: %w" , chunkNumber + 1 , currentChunkSize , err )
2023-07-18 20:37:31 +02:00
}
2023-08-15 14:27:21 +01:00
w . addCompletedPart ( s3PartNumber , uout . ETag )
2023-07-18 20:37:31 +02:00
2023-08-15 20:38:02 +01:00
fs . Debugf ( w . o , "multipart upload wrote chunk %d with %v bytes and etag %v" , chunkNumber + 1 , currentChunkSize , * uout . ETag )
2023-07-18 20:37:31 +02:00
return currentChunkSize , err
}
2023-09-23 13:20:01 +02:00
// Abort the multipart upload
2023-08-19 17:30:55 +01:00
func ( w * s3ChunkWriter ) Abort ( ctx context . Context ) error {
2023-08-15 14:27:21 +01:00
err := w . f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
_ , err := w . f . c . AbortMultipartUpload ( context . Background ( ) , & s3 . AbortMultipartUploadInput {
2023-08-15 14:27:21 +01:00
Bucket : w . bucket ,
Key : w . key ,
2023-08-29 12:57:33 +01:00
UploadId : w . uploadID ,
2023-08-15 14:27:21 +01:00
RequestPayer : w . multiPartUploadInput . RequestPayer ,
} )
2023-08-15 20:38:02 +01:00
return w . f . shouldRetry ( ctx , err )
2023-07-18 20:37:31 +02:00
} )
if err != nil {
2023-08-29 12:57:33 +01:00
return fmt . Errorf ( "failed to abort multipart upload %q: %w" , * w . uploadID , err )
2023-07-18 20:37:31 +02:00
}
2023-08-29 12:57:33 +01:00
fs . Debugf ( w . o , "multipart upload %q aborted" , * w . uploadID )
2023-07-18 20:37:31 +02:00
return err
}
2023-08-15 14:27:21 +01:00
// Close and finalise the multipart upload
2023-08-19 17:30:55 +01:00
func ( w * s3ChunkWriter ) Close ( ctx context . Context ) ( err error ) {
2023-07-18 20:37:31 +02:00
// sort the completed parts by part number
sort . Slice ( w . completedParts , func ( i , j int ) bool {
return * w . completedParts [ i ] . PartNumber < * w . completedParts [ j ] . PartNumber
} )
2023-08-15 14:27:21 +01:00
var resp * s3 . CompleteMultipartUploadOutput
err = w . f . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
resp , err = w . f . c . CompleteMultipartUpload ( ctx , & s3 . CompleteMultipartUploadInput {
2023-08-15 14:27:21 +01:00
Bucket : w . bucket ,
Key : w . key ,
2024-08-03 11:35:32 +01:00
MultipartUpload : & types . CompletedMultipartUpload {
2023-08-15 14:27:21 +01:00
Parts : w . completedParts ,
} ,
2025-07-17 15:29:31 +02:00
RequestPayer : w . multiPartUploadInput . RequestPayer ,
SSECustomerAlgorithm : w . multiPartUploadInput . SSECustomerAlgorithm ,
SSECustomerKey : w . multiPartUploadInput . SSECustomerKey ,
SSECustomerKeyMD5 : w . multiPartUploadInput . SSECustomerKeyMD5 ,
UploadId : w . uploadID ,
2023-08-15 14:27:21 +01:00
} )
2023-08-15 20:38:02 +01:00
return w . f . shouldRetry ( ctx , err )
2023-07-18 20:37:31 +02:00
} )
if err != nil {
2023-08-29 12:57:33 +01:00
return fmt . Errorf ( "failed to complete multipart upload %q: %w" , * w . uploadID , err )
2023-07-18 20:37:31 +02:00
}
if resp != nil {
if resp . ETag != nil {
w . eTag = * resp . ETag
}
if resp . VersionId != nil {
w . versionID = * resp . VersionId
}
}
2023-08-29 12:57:33 +01:00
fs . Debugf ( w . o , "multipart upload %q finished" , * w . uploadID )
2023-07-18 20:37:31 +02:00
return err
}
2023-08-15 20:38:02 +01:00
func ( o * Object ) uploadMultipart ( ctx context . Context , src fs . ObjectInfo , in io . Reader , options ... fs . OpenOption ) ( wantETag , gotETag string , versionID * string , ui uploadInfo , err error ) {
chunkWriter , err := multipart . UploadMultipart ( ctx , src , in , multipart . UploadMultipartOptions {
2023-09-01 17:25:15 +01:00
Open : o . fs ,
OpenOptions : options ,
2023-08-15 20:38:02 +01:00
} )
2019-12-30 23:17:06 +00:00
if err != nil {
2023-08-15 20:38:02 +01:00
return wantETag , gotETag , versionID , ui , err
2019-12-30 23:17:06 +00:00
}
2023-07-18 20:37:31 +02:00
2025-08-19 14:57:29 +02:00
s3cw := chunkWriter . ( * s3ChunkWriter )
2025-07-14 12:25:10 -04:00
gotETag = * stringClone ( s3cw . eTag )
versionID = stringClone ( s3cw . versionID )
2023-07-18 20:37:31 +02:00
hashOfHashes := md5 . Sum ( s3cw . md5s )
wantETag = fmt . Sprintf ( "%s-%d" , hex . EncodeToString ( hashOfHashes [ : ] ) , len ( s3cw . completedParts ) )
2023-08-15 20:38:02 +01:00
return wantETag , gotETag , versionID , s3cw . ui , nil
2019-12-30 23:17:06 +00:00
}
2022-05-03 17:39:01 +01:00
// Upload a single part using PutObject
2022-07-25 16:06:15 +01:00
func ( o * Object ) uploadSinglepartPutObject ( ctx context . Context , req * s3 . PutObjectInput , size int64 , in io . Reader ) ( etag string , lastModified time . Time , versionID * string , err error ) {
2024-08-03 11:35:32 +01:00
req . Body = io . NopCloser ( in )
var options = [ ] func ( * s3 . Options ) { }
if o . fs . opt . UseUnsignedPayload . Value {
options = append ( options , s3 . WithAPIOptions (
// avoids operation error S3: PutObject, failed to compute payload hash: failed to seek body to start, request stream is not seekable
v4signer . SwapComputePayloadSHA256ForUnsignedPayloadMiddleware ,
) )
}
// Can't retry single part uploads as only have an io.Reader
options = append ( options , func ( s3opt * s3 . Options ) {
s3opt . RetryMaxAttempts = 1
} )
var resp * s3 . PutObjectOutput
2022-05-03 17:39:01 +01:00
err = o . fs . pacer . CallNoRetry ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
resp , err = o . fs . c . PutObject ( ctx , req , options ... )
2022-05-03 17:39:01 +01:00
return o . fs . shouldRetry ( ctx , err )
} )
if err != nil {
2022-07-25 16:06:15 +01:00
return etag , lastModified , nil , err
2022-05-03 17:39:01 +01:00
}
lastModified = time . Now ( )
2022-07-25 16:06:15 +01:00
if resp != nil {
2025-07-14 12:25:10 -04:00
etag = * stringClone ( deref ( resp . ETag ) )
versionID = stringClonePointer ( resp . VersionId )
2022-07-25 16:06:15 +01:00
}
return etag , lastModified , versionID , nil
2022-05-03 17:39:01 +01:00
}
// Upload a single part using a presigned request
2022-07-25 16:06:15 +01:00
func ( o * Object ) uploadSinglepartPresignedRequest ( ctx context . Context , req * s3 . PutObjectInput , size int64 , in io . Reader ) ( etag string , lastModified time . Time , versionID * string , err error ) {
2024-08-03 11:35:32 +01:00
// Create the presigned request
putReq , err := s3 . NewPresignClient ( o . fs . c ) . PresignPutObject ( ctx , req , s3 . WithPresignExpires ( 15 * time . Minute ) )
2022-05-03 17:39:01 +01:00
if err != nil {
2022-07-25 16:06:15 +01:00
return etag , lastModified , nil , fmt . Errorf ( "s3 upload: sign request: %w" , err )
2022-05-03 17:39:01 +01:00
}
// Set request to nil if empty so as not to make chunked encoding
if size == 0 {
in = nil
}
// create the vanilla http request
2024-08-03 11:35:32 +01:00
httpReq , err := http . NewRequestWithContext ( ctx , "PUT" , putReq . URL , in )
2022-05-03 17:39:01 +01:00
if err != nil {
2022-07-25 16:06:15 +01:00
return etag , lastModified , nil , fmt . Errorf ( "s3 upload: new request: %w" , err )
2022-05-03 17:39:01 +01:00
}
// set the headers we signed and the length
2024-08-03 11:35:32 +01:00
httpReq . Header = putReq . SignedHeader
2022-05-03 17:39:01 +01:00
httpReq . ContentLength = size
var resp * http . Response
err = o . fs . pacer . CallNoRetry ( func ( ) ( bool , error ) {
var err error
resp , err = o . fs . srv . Do ( httpReq )
if err != nil {
return o . fs . shouldRetry ( ctx , err )
}
body , err := rest . ReadBody ( resp )
if err != nil {
return o . fs . shouldRetry ( ctx , err )
}
if resp . StatusCode >= 200 && resp . StatusCode < 299 {
return false , nil
}
err = fmt . Errorf ( "s3 upload: %s: %s" , resp . Status , body )
return fserrors . ShouldRetryHTTP ( resp , retryErrorCodes ) , err
} )
if err != nil {
2022-07-25 16:06:15 +01:00
return etag , lastModified , nil , err
2022-05-03 17:39:01 +01:00
}
if resp != nil {
if date , err := http . ParseTime ( resp . Header . Get ( "Date" ) ) ; err != nil {
lastModified = date
}
2025-07-14 12:25:10 -04:00
etag = * stringClone ( resp . Header . Get ( "Etag" ) )
vID := * stringClone ( resp . Header . Get ( "x-amz-version-id" ) )
2022-07-25 16:06:15 +01:00
if vID != "" {
versionID = & vID
}
2022-05-03 17:39:01 +01:00
}
2022-07-25 16:06:15 +01:00
return etag , lastModified , versionID , nil
2022-05-03 17:39:01 +01:00
}
2023-08-15 20:38:02 +01:00
// Info needed for an upload
type uploadInfo struct {
req * s3 . PutObjectInput
md5sumHex string
}
// Prepare object for being uploaded
2024-03-05 17:21:06 +00:00
//
// If noHash is true the md5sum will not be calculated
func ( o * Object ) prepareUpload ( ctx context . Context , src fs . ObjectInfo , options [ ] fs . OpenOption , noHash bool ) ( ui uploadInfo , err error ) {
2019-08-09 11:29:36 +01:00
bucket , bucketPath := o . split ( )
2023-04-26 10:59:17 +01:00
// Create parent dir/bucket if not saving directory marker
if ! strings . HasSuffix ( o . remote , "/" ) {
err := o . fs . mkdirParent ( ctx , o . remote )
if err != nil {
2023-08-15 20:38:02 +01:00
return ui , err
2023-04-26 10:59:17 +01:00
}
2017-06-07 14:16:50 +01:00
}
2019-06-17 10:34:30 +02:00
modTime := src . ModTime ( ctx )
2014-04-18 17:04:21 +01:00
2023-08-15 20:38:02 +01:00
ui . req = & s3 . PutObjectInput {
2022-05-24 12:32:39 +01:00
Bucket : & bucket ,
2024-08-03 11:35:32 +01:00
ACL : types . ObjectCannedACL ( o . fs . opt . ACL ) ,
2022-05-24 12:32:39 +01:00
Key : & bucketPath ,
}
// Fetch metadata if --metadata is in use
2023-10-23 23:47:18 +01:00
meta , err := fs . GetMetadataOptions ( ctx , o . fs , src , options )
2022-05-24 12:32:39 +01:00
if err != nil {
2023-08-15 20:38:02 +01:00
return ui , fmt . Errorf ( "failed to read metadata from source object: %w" , err )
2022-05-24 12:32:39 +01:00
}
2024-08-03 11:35:32 +01:00
ui . req . Metadata = make ( map [ string ] string , len ( meta ) + 2 )
2022-05-24 12:32:39 +01:00
// merge metadata into request and user metadata
for k , v := range meta {
pv := aws . String ( v )
k = strings . ToLower ( k )
2022-10-12 08:55:58 +01:00
if o . fs . opt . NoSystemMetadata {
2024-08-03 11:35:32 +01:00
ui . req . Metadata [ k ] = v
2022-10-12 08:55:58 +01:00
continue
}
2022-05-24 12:32:39 +01:00
switch k {
case "cache-control" :
2023-08-15 20:38:02 +01:00
ui . req . CacheControl = pv
2022-05-24 12:32:39 +01:00
case "content-disposition" :
2023-08-15 20:38:02 +01:00
ui . req . ContentDisposition = pv
2022-05-24 12:32:39 +01:00
case "content-encoding" :
2025-08-16 10:36:50 +01:00
ui . req . ContentEncoding = removeAWSChunked ( pv )
2022-05-24 12:32:39 +01:00
case "content-language" :
2023-08-15 20:38:02 +01:00
ui . req . ContentLanguage = pv
2022-05-24 12:32:39 +01:00
case "content-type" :
2023-08-15 20:38:02 +01:00
ui . req . ContentType = pv
2022-05-24 12:32:39 +01:00
case "x-amz-tagging" :
2023-08-15 20:38:02 +01:00
ui . req . Tagging = pv
2022-05-24 12:32:39 +01:00
case "tier" :
// ignore
case "mtime" :
// mtime in meta overrides source ModTime
metaModTime , err := time . Parse ( time . RFC3339Nano , v )
if err != nil {
fs . Debugf ( o , "failed to parse metadata %s: %q: %v" , k , v , err )
} else {
modTime = metaModTime
}
case "btime" :
// write as metadata since we can't set it
2024-08-03 11:35:32 +01:00
ui . req . Metadata [ k ] = v
2022-05-24 12:32:39 +01:00
default :
2024-08-03 11:35:32 +01:00
ui . req . Metadata [ k ] = v
2022-05-24 12:32:39 +01:00
}
2014-12-23 12:09:02 +00:00
}
2022-05-24 12:32:39 +01:00
// Set the mtime in the meta data
2024-08-03 11:35:32 +01:00
ui . req . Metadata [ metaMtime ] = swift . TimeToFloatString ( modTime )
2022-05-24 12:32:39 +01:00
2020-01-07 19:23:08 +00:00
// read the md5sum if available
Spelling fixes
Fix spelling of: above, already, anonymous, associated,
authentication, bandwidth, because, between, blocks, calculate,
candidates, cautious, changelog, cleaner, clipboard, command,
completely, concurrently, considered, constructs, corrupt, current,
daemon, dependencies, deprecated, directory, dispatcher, download,
eligible, ellipsis, encrypter, endpoint, entrieslist, essentially,
existing writers, existing, expires, filesystem, flushing, frequently,
hierarchy, however, implementation, implements, inaccurate,
individually, insensitive, longer, maximum, metadata, modified,
multipart, namedirfirst, nextcloud, obscured, opened, optional,
owncloud, pacific, passphrase, password, permanently, persimmon,
positive, potato, protocol, quota, receiving, recommends, referring,
requires, revisited, satisfied, satisfies, satisfy, semver,
serialized, session, storage, strategies, stringlist, successful,
supported, surprise, temporarily, temporary, transactions, unneeded,
update, uploads, wrapped
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2020-10-08 20:17:24 -04:00
// - for non multipart
2020-01-07 19:23:08 +00:00
// - so we can add a ContentMD5
2020-11-23 11:53:31 +00:00
// - so we can add the md5sum in the metadata as metaMD5Hash if using SSE/SSE-C
2020-01-07 19:23:08 +00:00
// - for multipart provided checksums aren't disabled
// - so we can add the md5sum in the metadata as metaMD5Hash
2022-01-28 16:18:40 +00:00
var md5sumBase64 string
2023-07-18 20:37:31 +02:00
size := src . Size ( )
multipart := size < 0 || size >= int64 ( o . fs . opt . UploadCutoff )
2024-03-05 17:21:06 +00:00
if ! noHash && ( ! multipart || ! o . fs . opt . DisableChecksum ) {
2023-08-15 20:38:02 +01:00
ui . md5sumHex , err = src . Hash ( ctx , hash . MD5 )
if err == nil && matchMd5 . MatchString ( ui . md5sumHex ) {
hashBytes , err := hex . DecodeString ( ui . md5sumHex )
2018-01-06 09:30:10 -05:00
if err == nil {
2022-01-28 16:18:40 +00:00
md5sumBase64 = base64 . StdEncoding . EncodeToString ( hashBytes )
2020-11-23 11:53:31 +00:00
if ( multipart || o . fs . etagIsNotMD5 ) && ! o . fs . opt . DisableChecksum {
// Set the md5sum as metadata on the object if
// - a multipart upload
2024-09-13 18:56:22 +01:00
// - the Etag is not an MD5, eg when using SSE/SSE-C or directory buckets
2020-11-23 11:53:31 +00:00
// provided checksums aren't disabled
2024-08-03 11:35:32 +01:00
ui . req . Metadata [ metaMD5Hash ] = md5sumBase64
2018-11-26 21:09:23 +00:00
}
2018-01-06 09:30:10 -05:00
}
}
}
2023-07-18 20:37:31 +02:00
// Set the content type if it isn't set already
2023-08-15 20:38:02 +01:00
if ui . req . ContentType == nil {
ui . req . ContentType = aws . String ( fs . MimeType ( ctx , src ) )
2019-12-30 23:17:06 +00:00
}
2022-05-03 17:39:01 +01:00
if size >= 0 {
2023-08-15 20:38:02 +01:00
ui . req . ContentLength = & size
2022-05-03 17:39:01 +01:00
}
2024-09-13 18:56:22 +01:00
if md5sumBase64 != "" && ! o . fs . opt . DirectoryBucket {
2023-08-15 20:38:02 +01:00
ui . req . ContentMD5 = & md5sumBase64
2019-12-30 23:17:06 +00:00
}
2020-12-03 10:30:06 +08:00
if o . fs . opt . RequesterPays {
2024-08-03 11:35:32 +01:00
ui . req . RequestPayer = types . RequestPayerRequester
2020-12-03 10:30:06 +08:00
}
2019-12-30 23:17:06 +00:00
if o . fs . opt . ServerSideEncryption != "" {
2024-08-03 11:35:32 +01:00
ui . req . ServerSideEncryption = types . ServerSideEncryption ( o . fs . opt . ServerSideEncryption )
2019-12-30 23:17:06 +00:00
}
2020-03-30 11:26:52 +01:00
if o . fs . opt . SSECustomerAlgorithm != "" {
2023-08-15 20:38:02 +01:00
ui . req . SSECustomerAlgorithm = & o . fs . opt . SSECustomerAlgorithm
2020-03-30 11:26:52 +01:00
}
2024-08-07 10:14:58 +01:00
if o . fs . opt . SSECustomerKeyBase64 != "" {
ui . req . SSECustomerKey = & o . fs . opt . SSECustomerKeyBase64
2020-03-30 11:26:52 +01:00
}
if o . fs . opt . SSECustomerKeyMD5 != "" {
2023-08-15 20:38:02 +01:00
ui . req . SSECustomerKeyMD5 = & o . fs . opt . SSECustomerKeyMD5
2020-03-30 11:26:52 +01:00
}
2019-12-30 23:17:06 +00:00
if o . fs . opt . SSEKMSKeyID != "" {
2023-08-15 20:38:02 +01:00
ui . req . SSEKMSKeyId = & o . fs . opt . SSEKMSKeyID
2019-12-30 23:17:06 +00:00
}
if o . fs . opt . StorageClass != "" {
2024-08-03 11:35:32 +01:00
ui . req . StorageClass = types . StorageClass ( o . fs . opt . StorageClass )
2019-12-30 23:17:06 +00:00
}
2020-06-05 11:45:54 +01:00
// Apply upload options
for _ , option := range options {
key , value := option . Header ( )
lowerKey := strings . ToLower ( key )
switch lowerKey {
case "" :
// ignore
case "cache-control" :
2023-08-15 20:38:02 +01:00
ui . req . CacheControl = aws . String ( value )
2020-06-05 11:45:54 +01:00
case "content-disposition" :
2023-08-15 20:38:02 +01:00
ui . req . ContentDisposition = aws . String ( value )
2020-06-05 11:45:54 +01:00
case "content-encoding" :
2025-08-16 10:36:50 +01:00
ui . req . ContentEncoding = removeAWSChunked ( aws . String ( value ) )
2020-06-05 11:45:54 +01:00
case "content-language" :
2023-08-15 20:38:02 +01:00
ui . req . ContentLanguage = aws . String ( value )
2020-06-05 11:45:54 +01:00
case "content-type" :
2023-08-15 20:38:02 +01:00
ui . req . ContentType = aws . String ( value )
2025-11-13 14:50:47 +01:00
case "if-match" :
ui . req . IfMatch = aws . String ( value )
case "if-none-match" :
ui . req . IfNoneMatch = aws . String ( value )
2020-06-05 11:45:54 +01:00
case "x-amz-tagging" :
2023-08-15 20:38:02 +01:00
ui . req . Tagging = aws . String ( value )
2020-06-05 11:45:54 +01:00
default :
const amzMetaPrefix = "x-amz-meta-"
if strings . HasPrefix ( lowerKey , amzMetaPrefix ) {
metaKey := lowerKey [ len ( amzMetaPrefix ) : ]
2024-08-03 11:35:32 +01:00
ui . req . Metadata [ metaKey ] = value
2020-06-05 11:45:54 +01:00
} else {
fs . Errorf ( o , "Don't know how to set key %q on upload" , key )
}
}
}
2019-12-30 23:17:06 +00:00
2022-10-11 17:54:22 +01:00
// Check metadata keys and values are valid
2023-08-15 20:38:02 +01:00
for key , value := range ui . req . Metadata {
2022-10-11 17:54:22 +01:00
if ! httpguts . ValidHeaderFieldName ( key ) {
fs . Errorf ( o , "Dropping invalid metadata key %q" , key )
2023-08-15 20:38:02 +01:00
delete ( ui . req . Metadata , key )
2024-08-03 11:35:32 +01:00
} else if ! httpguts . ValidHeaderFieldValue ( value ) {
fs . Errorf ( o , "Dropping invalid metadata value %q for key %q" , value , key )
2023-08-15 20:38:02 +01:00
delete ( ui . req . Metadata , key )
2022-10-11 17:54:22 +01:00
}
}
2023-08-15 20:38:02 +01:00
return ui , nil
2023-07-18 20:37:31 +02:00
}
// Update the Object from in with modTime and size
func ( o * Object ) Update ( ctx context . Context , in io . Reader , src fs . ObjectInfo , options ... fs . OpenOption ) error {
if o . fs . opt . VersionAt . IsSet ( ) {
return errNotWithVersionAt
}
size := src . Size ( )
multipart := size < 0 || size >= int64 ( o . fs . opt . UploadCutoff )
2022-05-03 17:39:01 +01:00
var wantETag string // Multipart upload Etag to check
2023-02-13 10:31:31 +00:00
var gotETag string // Etag we got from the upload
2022-05-03 17:39:01 +01:00
var lastModified time . Time // Time we got from the upload
2022-07-25 16:06:15 +01:00
var versionID * string // versionID we got from the upload
2023-07-18 20:37:31 +02:00
var err error
2023-08-15 20:38:02 +01:00
var ui uploadInfo
2018-11-26 21:09:23 +00:00
if multipart {
2023-10-06 17:53:36 +02:00
wantETag , gotETag , versionID , ui , err = o . uploadMultipart ( ctx , src , in , options ... )
2018-11-26 21:09:23 +00:00
} else {
2024-03-05 17:21:06 +00:00
ui , err = o . prepareUpload ( ctx , src , options , false )
2023-07-18 20:37:31 +02:00
if err != nil {
2023-08-15 20:38:02 +01:00
return fmt . Errorf ( "failed to prepare upload: %w" , err )
2023-07-18 20:37:31 +02:00
}
2022-05-03 17:39:01 +01:00
if o . fs . opt . UsePresignedRequest {
2023-08-15 20:38:02 +01:00
gotETag , lastModified , versionID , err = o . uploadSinglepartPresignedRequest ( ctx , ui . req , size , in )
2022-05-03 17:39:01 +01:00
} else {
2023-08-15 20:38:02 +01:00
gotETag , lastModified , versionID , err = o . uploadSinglepartPutObject ( ctx , ui . req , size , in )
2018-11-26 21:09:23 +00:00
}
2014-07-19 12:37:11 +01:00
}
2022-05-03 17:39:01 +01:00
if err != nil {
return err
}
2022-12-16 11:44:58 +00:00
// Only record versionID if we are using --s3-versions or --s3-version-at
if o . fs . opt . Versions || o . fs . opt . VersionAt . IsSet ( ) {
o . versionID = versionID
} else {
o . versionID = nil
}
2014-12-23 12:09:02 +00:00
2021-01-29 11:13:42 +00:00
// User requested we don't HEAD the object after uploading it
// so make up the object as best we can assuming it got
// uploaded properly. If size < 0 then we need to do the HEAD.
2023-02-13 10:31:31 +00:00
var head * s3 . HeadObjectOutput
2021-01-29 11:13:42 +00:00
if o . fs . opt . NoHead && size >= 0 {
2023-02-13 10:31:31 +00:00
head = new ( s3 . HeadObjectOutput )
//structs.SetFrom(head, &req)
2023-08-15 20:38:02 +01:00
setFrom_s3HeadObjectOutput_s3PutObjectInput ( head , ui . req )
head . ETag = & ui . md5sumHex // doesn't matter quotes are missing
2022-05-24 12:32:39 +01:00
head . ContentLength = & size
2023-02-13 10:31:31 +00:00
// We get etag back from single and multipart upload so fill it in here
if gotETag != "" {
head . ETag = & gotETag
2022-05-03 17:39:01 +01:00
}
2022-05-24 12:32:39 +01:00
if lastModified . IsZero ( ) {
lastModified = time . Now ( )
2021-01-29 11:13:42 +00:00
}
2022-05-24 12:32:39 +01:00
head . LastModified = & lastModified
2022-07-25 16:06:15 +01:00
head . VersionId = versionID
2023-02-13 10:31:31 +00:00
} else {
// Read the metadata from the newly created object
o . meta = nil // wipe old metadata
head , err = o . headObject ( ctx )
if err != nil {
return err
}
2022-02-16 17:50:11 +00:00
}
2022-05-24 12:32:39 +01:00
o . setMetaData ( head )
2023-02-13 10:31:31 +00:00
// Check multipart upload ETag if required
2022-02-27 15:47:31 +00:00
if o . fs . opt . UseMultipartEtag . Value && ! o . fs . etagIsNotMD5 && wantETag != "" && head . ETag != nil && * head . ETag != "" {
2022-02-16 17:50:11 +00:00
gotETag := strings . Trim ( strings . ToLower ( * head . ETag ) , ` " ` )
if wantETag != gotETag {
return fmt . Errorf ( "multipart upload corrupted: Etag differ: expecting %s but got %s" , wantETag , gotETag )
}
fs . Debugf ( o , "Multipart upload Etag: %s OK" , wantETag )
}
2014-04-18 17:04:21 +01:00
return err
}
2013-01-08 18:53:35 +00:00
// Remove an object
2019-06-17 10:34:30 +02:00
func ( o * Object ) Remove ( ctx context . Context ) error {
2022-07-26 17:58:57 +01:00
if o . fs . opt . VersionAt . IsSet ( ) {
return errNotWithVersionAt
}
2019-08-09 11:29:36 +01:00
bucket , bucketPath := o . split ( )
2014-12-23 12:09:02 +00:00
req := s3 . DeleteObjectInput {
2022-07-25 16:06:15 +01:00
Bucket : & bucket ,
Key : & bucketPath ,
VersionId : o . versionID ,
2014-12-23 12:09:02 +00:00
}
2020-12-03 10:30:06 +08:00
if o . fs . opt . RequesterPays {
2024-08-03 11:35:32 +01:00
req . RequestPayer = types . RequestPayerRequester
2020-12-03 10:30:06 +08:00
}
2018-09-03 16:41:04 +12:00
err := o . fs . pacer . Call ( func ( ) ( bool , error ) {
2024-08-03 11:35:32 +01:00
_ , err := o . fs . c . DeleteObject ( ctx , & req )
2021-03-16 15:50:02 +00:00
return o . fs . shouldRetry ( ctx , err )
2018-09-03 16:41:04 +12:00
} )
2014-12-23 12:09:02 +00:00
return err
2013-01-08 18:53:35 +00:00
}
2016-09-21 22:13:24 +01:00
// MimeType of an Object if known, "" otherwise
2019-06-17 10:34:30 +02:00
func ( o * Object ) MimeType ( ctx context . Context ) string {
err := o . readMetaData ( ctx )
2016-09-21 22:13:24 +01:00
if err != nil {
2017-02-09 11:01:20 +00:00
fs . Logf ( o , "Failed to read metadata: %v" , err )
2016-09-21 22:13:24 +01:00
return ""
}
return o . mimeType
}
2019-09-09 20:44:50 +01:00
// SetTier performs changing storage class
func ( o * Object ) SetTier ( tier string ) ( err error ) {
ctx := context . TODO ( )
tier = strings . ToUpper ( tier )
bucket , bucketPath := o . split ( )
req := s3 . CopyObjectInput {
2024-08-03 11:35:32 +01:00
MetadataDirective : types . MetadataDirectiveCopy ,
StorageClass : types . StorageClass ( tier ) ,
2019-09-09 20:44:50 +01:00
}
2020-07-30 10:52:32 +01:00
err = o . fs . copy ( ctx , & req , bucket , bucketPath , bucket , bucketPath , o )
2019-09-09 20:44:50 +01:00
if err != nil {
return err
}
2022-05-24 12:32:39 +01:00
o . storageClass = & tier
2019-09-09 20:44:50 +01:00
return err
}
// GetTier returns storage class as string
func ( o * Object ) GetTier ( ) string {
2022-05-24 12:32:39 +01:00
if o . storageClass == nil || * o . storageClass == "" {
2019-09-09 20:44:50 +01:00
return "STANDARD"
}
2022-05-24 12:32:39 +01:00
return * o . storageClass
}
// Metadata returns metadata for an object
//
// It should return nil if there is no Metadata
func ( o * Object ) Metadata ( ctx context . Context ) ( metadata fs . Metadata , err error ) {
err = o . readMetaData ( ctx )
if err != nil {
return nil , err
}
metadata = make ( fs . Metadata , len ( o . meta ) + 7 )
for k , v := range o . meta {
switch k {
case metaMtime :
if modTime , err := swift . FloatStringToTime ( v ) ; err == nil {
metadata [ "mtime" ] = modTime . Format ( time . RFC3339Nano )
}
case metaMD5Hash :
// don't write hash metadata
default :
metadata [ k ] = v
}
}
if o . mimeType != "" {
metadata [ "content-type" ] = o . mimeType
}
// metadata["x-amz-tagging"] = ""
if ! o . lastModified . IsZero ( ) {
metadata [ "btime" ] = o . lastModified . Format ( time . RFC3339Nano )
}
// Set system metadata
setMetadata := func ( k string , v * string ) {
2022-10-12 08:55:58 +01:00
if o . fs . opt . NoSystemMetadata {
return
}
2022-05-24 12:32:39 +01:00
if v == nil || * v == "" {
return
}
metadata [ k ] = * v
}
setMetadata ( "cache-control" , o . cacheControl )
setMetadata ( "content-disposition" , o . contentDisposition )
setMetadata ( "content-encoding" , o . contentEncoding )
setMetadata ( "content-language" , o . contentLanguage )
2023-04-27 15:27:31 +01:00
metadata [ "tier" ] = o . GetTier ( )
2022-05-24 12:32:39 +01:00
return metadata , nil
2019-09-09 20:44:50 +01:00
}
2013-01-08 18:53:35 +00:00
// Check the interfaces are satisfied
2015-11-07 11:14:46 +00:00
var (
2023-08-15 20:38:02 +01:00
_ fs . Fs = & Fs { }
_ fs . Purger = & Fs { }
_ fs . Copier = & Fs { }
_ fs . PutStreamer = & Fs { }
_ fs . ListRer = & Fs { }
2024-11-25 12:50:27 +00:00
_ fs . ListPer = & Fs { }
2023-08-15 20:38:02 +01:00
_ fs . Commander = & Fs { }
_ fs . CleanUpper = & Fs { }
_ fs . OpenChunkWriter = & Fs { }
_ fs . Object = & Object { }
_ fs . MimeTyper = & Object { }
_ fs . GetTierer = & Object { }
_ fs . SetTierer = & Object { }
_ fs . Metadataer = & Object { }
2015-11-07 11:14:46 +00:00
)