1616package core
1717
1818import (
19- "github.com/yandex-cloud/geesefs/core/cfg"
20- "golang.org/x/sync/errgroup"
21-
19+ "bytes"
20+ "encoding/base64"
2221 "encoding/json"
22+ "encoding/xml"
2323 "errors"
2424 "fmt"
25+ "io"
2526 "io/ioutil"
2627 "net/http"
2728 "net/url"
@@ -39,6 +40,8 @@ import (
3940 "github.com/aws/aws-sdk-go/aws/credentials"
4041 "github.com/aws/aws-sdk-go/aws/request"
4142 "github.com/aws/aws-sdk-go/service/s3"
43+ "github.com/yandex-cloud/geesefs/core/cfg"
44+ "golang.org/x/sync/errgroup"
4245)
4346
4447type S3Backend struct {
@@ -437,83 +440,179 @@ func withHeader(req *request.Request, key, value string) {
437440 req .HTTPRequest .Header .Set (key , value )
438441}
439442
440- func (s * S3Backend ) ListObjectsV2 (params * s3.ListObjectsV2Input ) (* s3.ListObjectsV2Output , string , error ) {
441- /*
442- if s.config.ListV1Ext {
443- in := s3.ListObjectsV1ExtInput(*params)
444- req, resp := s.S3.ListObjectsV1ExtRequest(&in)
445- if s.flags.TigrisPrefetch {
446- withHeader(req, "X-Tigris-Prefetch", "true")
447- }
448- err := req.Send()
449- if err != nil {
450- if awsErr, ok := err.(awserr.Error); ok {
451- if awsErr.Code() == "InvalidArgument" || awsErr.Code() == "NotImplemented" {
452- // Fallback to list v1
453- s.config.ListV1Ext = false
454- return s.ListObjectsV2(params)
455- }
456- }
457- return nil, "", err
458- }
459- out := s3.ListObjectsV2Output(*resp)
460- for _, obj := range out.Contents {
461- // Make non-nil maps for all objects so that we know metadata is empty
462- if obj.UserMetadata == nil {
463- obj.UserMetadata = make(map[string]*string)
464- }
465- }
466- return &out, s.getRequestId(req), nil
467- } else
468- */
443+ func (s * S3Backend ) listObjects (params * s3.ListObjectsV2Input ) (* s3.ListObjectsV2Output , string , error ) {
469444 if s .config .ListV2 {
470- req , resp := s .S3 .ListObjectsV2Request (params )
471- if s .flags .TigrisPrefetch {
472- withHeader (req , "X-Tigris-Prefetch" , "true" )
473- }
474- err := req .Send ()
475- if err != nil {
476- return nil , "" , err
477- }
478- return resp , s .getRequestId (req ), nil
445+ return s .listObjectsV2 (params )
446+ }
447+ return s .listObjectsV1 (params )
448+ }
449+
450+ func (s * S3Backend ) listObjectsV2 (params * s3.ListObjectsV2Input ) (* s3.ListObjectsV2Output , string , error ) {
451+ req , resp := s .S3 .ListObjectsV2Request (params )
452+ if s .flags .TigrisPrefetch {
453+ withHeader (req , "X-Tigris-Prefetch" , "true" )
454+ }
455+ err := req .Send ()
456+ if err != nil {
457+ return nil , "" , err
458+ }
459+ return resp , s .getRequestId (req ), nil
460+ }
461+
462+ func (s * S3Backend ) listObjectsV1 (params * s3.ListObjectsV2Input ) (* s3.ListObjectsV2Output , string , error ) {
463+ v1 := s3.ListObjectsInput {
464+ Bucket : params .Bucket ,
465+ Delimiter : params .Delimiter ,
466+ EncodingType : params .EncodingType ,
467+ MaxKeys : params .MaxKeys ,
468+ Prefix : params .Prefix ,
469+ RequestPayer : params .RequestPayer ,
470+ }
471+ if params .StartAfter != nil {
472+ v1 .Marker = params .StartAfter
479473 } else {
480- v1 := s3.ListObjectsInput {
481- Bucket : params .Bucket ,
482- Delimiter : params .Delimiter ,
483- EncodingType : params .EncodingType ,
484- MaxKeys : params .MaxKeys ,
485- Prefix : params .Prefix ,
486- RequestPayer : params .RequestPayer ,
487- }
488- if params .StartAfter != nil {
489- v1 .Marker = params .StartAfter
490- } else {
491- v1 .Marker = params .ContinuationToken
492- }
474+ v1 .Marker = params .ContinuationToken
475+ }
493476
494- objs , err := s .S3 .ListObjects (& v1 )
495- if err != nil {
496- return nil , "" , err
497- }
477+ objs , err := s .S3 .ListObjects (& v1 )
478+ if err != nil {
479+ return nil , "" , err
480+ }
481+
482+ count := int64 (len (objs .Contents ))
483+ v2Objs := s3.ListObjectsV2Output {
484+ CommonPrefixes : objs .CommonPrefixes ,
485+ Contents : objs .Contents ,
486+ ContinuationToken : objs .Marker ,
487+ Delimiter : objs .Delimiter ,
488+ EncodingType : objs .EncodingType ,
489+ IsTruncated : objs .IsTruncated ,
490+ KeyCount : & count ,
491+ MaxKeys : objs .MaxKeys ,
492+ Name : objs .Name ,
493+ NextContinuationToken : objs .NextMarker ,
494+ Prefix : objs .Prefix ,
495+ StartAfter : objs .Marker ,
496+ }
497+
498+ return & v2Objs , "" , nil
499+ }
498500
499- count := int64 (len (objs .Contents ))
500- v2Objs := s3.ListObjectsV2Output {
501- CommonPrefixes : objs .CommonPrefixes ,
502- Contents : objs .Contents ,
503- ContinuationToken : objs .Marker ,
504- Delimiter : objs .Delimiter ,
505- EncodingType : objs .EncodingType ,
506- IsTruncated : objs .IsTruncated ,
507- KeyCount : & count ,
508- MaxKeys : objs .MaxKeys ,
509- Name : objs .Name ,
510- NextContinuationToken : objs .NextMarker ,
511- Prefix : objs .Prefix ,
512- StartAfter : objs .Marker ,
513- }
501+ type RestoreStatus struct {
502+ IsRestoreInProgress * bool `type:"boolean"`
503+ RestoreExpiryDate * time.Time `type:"timestamp"`
504+ }
505+
506+ type Owner struct {
507+ DisplayName * string `type:"string"`
508+ ID * string `type:"string"`
509+ }
510+
511+ type CommonPrefix struct {
512+ Prefix * string `type:"string"`
513+ }
514+
515+ type xsdBase64Binary []byte
516+
517+ func (b * xsdBase64Binary ) UnmarshalText (text []byte ) (err error ) {
518+ * b , err = base64 .StdEncoding .DecodeString (string (text ))
519+ return
520+ }
521+ func (b xsdBase64Binary ) MarshalText () ([]byte , error ) {
522+ var buf bytes.Buffer
523+ enc := base64 .NewEncoder (base64 .StdEncoding , & buf )
524+ if _ , err := enc .Write ([]byte (b )); err != nil {
525+ return nil , err
526+ }
527+ if err := enc .Close (); err != nil {
528+ return nil , err
529+ }
530+ return buf .Bytes (), nil
531+ }
532+
533+ type MetadataEntry struct {
534+ Name string
535+ Value string
536+ }
537+
538+ type Object struct {
539+ ChecksumAlgorithm []* string `type:"list" flattened:"true" enum:"ChecksumAlgorithm"`
540+ ETag * string `type:"string"`
541+ Key * string `min:"1" type:"string"`
542+ LastModified * time.Time `type:"timestamp"`
543+ Owner * Owner `type:"structure"`
544+ RestoreStatus * RestoreStatus `type:"structure"`
545+ Size * int64 `type:"long"`
546+ StorageClass * string `type:"string" enum:"ObjectStorageClass"`
547+ Metadata []MetadataEntry `type:"list" flattened:"true"`
548+ Data xsdBase64Binary `type:"blob"`
549+ }
550+
551+ type ListObjectsV2Output struct {
552+ _ struct {} `type:"structure"`
553+ CommonPrefixes []* CommonPrefix `type:"list" flattened:"true"`
554+ Contents []* Object `type:"list" flattened:"true"`
555+ ContinuationToken * string `type:"string"`
556+ Delimiter * string `type:"string"`
557+ EncodingType * string `type:"string" enum:"EncodingType"`
558+ IsTruncated * bool `type:"boolean"`
559+ KeyCount * int64 `type:"integer"`
560+ MaxKeys * int64 `type:"integer"`
561+ Name * string `type:"string"`
562+ NextContinuationToken * string `type:"string"`
563+ Prefix * string `type:"string"`
564+ RequestCharged * string `location:"header" locationName:"x-amz-request-charged" type:"string" enum:"RequestCharged"`
565+ StartAfter * string `type:"string"`
566+ }
567+
568+ func unmarshalListObjectsV2Response (r * request.Request ) {
569+ if r .Error != nil {
570+ return
571+ }
572+
573+ b , err := io .ReadAll (r .HTTPResponse .Body )
574+ if err != nil {
575+ r .Error = fmt .Errorf ("custom unmarshal read body: %w" , err )
576+ return
577+ }
578+
579+ var response * ListObjectsV2Output
580+ if err := xml .Unmarshal (b , & response ); err != nil {
581+ r .Error = fmt .Errorf ("custom unmarshal failed: %w" , err )
582+ return
583+ }
584+
585+ r .Data = response
586+ }
587+
588+ func (s * S3Backend ) listObjectsV2Special (params * s3.ListObjectsV2Input ) (* ListBlobsOutput , error ) {
589+ req , _ := s .S3 .ListObjectsV2Request (params )
590+
591+ if s .flags .TigrisPrefetch {
592+ withHeader (req , "X-Tigris-Prefetch" , "true" )
593+ }
594+
595+ withHeader (req , "X-Tigris-List-Metadata" , "true" )
514596
515- return & v2Objs , "" , nil
597+ if s .flags .TigrisListContent {
598+ withHeader (req , "X-Tigris-List-Content" , "true" )
516599 }
600+
601+ req .Handlers .Unmarshal .Clear ()
602+ req .Handlers .Unmarshal .PushFront (unmarshalListObjectsV2Response )
603+
604+ if err := req .Send (); err != nil {
605+ return nil , err
606+ }
607+
608+ reqId := s .getRequestId (req )
609+
610+ resp , ok := req .Data .(* ListObjectsV2Output )
611+ if ! ok {
612+ return nil , fmt .Errorf ("unexpected response type: %T" , req .Data )
613+ }
614+
615+ return s .convertListOutputSpecial (resp , reqId )
517616}
518617
519618func metadataToLower (m map [string ]* string ) map [string ]* string {
@@ -568,25 +667,48 @@ func (s *S3Backend) HeadBlob(param *HeadBlobInput) (*HeadBlobOutput, error) {
568667 }, nil
569668}
570669
571- func (s * S3Backend ) ListBlobs (param * ListBlobsInput ) (* ListBlobsOutput , error ) {
572- var maxKeys * int64
670+ func (s * S3Backend ) convertListMetadataSpecial (in []MetadataEntry ) map [string ]* string {
671+ if len (in ) == 0 {
672+ return nil
673+ }
573674
574- if param .MaxKeys != nil {
575- maxKeys = aws .Int64 (int64 (* param .MaxKeys ))
675+ out := make (map [string ]* string )
676+ for _ , v := range in {
677+ out [v .Name ] = & v .Value
576678 }
577679
578- resp , reqId , err := s .ListObjectsV2 (& s3.ListObjectsV2Input {
579- Bucket : & s .bucket ,
580- Prefix : param .Prefix ,
581- Delimiter : param .Delimiter ,
582- MaxKeys : maxKeys ,
583- StartAfter : param .StartAfter ,
584- ContinuationToken : param .ContinuationToken ,
585- })
586- if err != nil {
587- return nil , err
680+ return out
681+ }
682+
683+ func (s * S3Backend ) convertListOutputSpecial (resp * ListObjectsV2Output , reqId string ) (* ListBlobsOutput , error ) {
684+ prefixes := make ([]BlobPrefixOutput , 0 )
685+ items := make ([]BlobItemOutput , 0 )
686+
687+ for _ , p := range resp .CommonPrefixes {
688+ prefixes = append (prefixes , BlobPrefixOutput {Prefix : p .Prefix })
588689 }
690+ for _ , i := range resp .Contents {
691+ items = append (items , BlobItemOutput {
692+ Key : i .Key ,
693+ ETag : i .ETag ,
694+ LastModified : i .LastModified ,
695+ Size : uint64 (* i .Size ),
696+ StorageClass : i .StorageClass ,
697+ Metadata : s .convertListMetadataSpecial (i .Metadata ),
698+ Content : i .Data ,
699+ })
700+ }
701+
702+ return & ListBlobsOutput {
703+ Prefixes : prefixes ,
704+ Items : items ,
705+ NextContinuationToken : resp .NextContinuationToken ,
706+ IsTruncated : * resp .IsTruncated ,
707+ RequestId : reqId ,
708+ }, nil
709+ }
589710
711+ func (s * S3Backend ) convertListOutput (resp * s3.ListObjectsV2Output , reqId string ) (* ListBlobsOutput , error ) {
590712 prefixes := make ([]BlobPrefixOutput , 0 )
591713 items := make ([]BlobItemOutput , 0 )
592714
@@ -613,6 +735,34 @@ func (s *S3Backend) ListBlobs(param *ListBlobsInput) (*ListBlobsOutput, error) {
613735 }, nil
614736}
615737
738+ func (s * S3Backend ) ListBlobs (param * ListBlobsInput ) (* ListBlobsOutput , error ) {
739+ var maxKeys * int64
740+
741+ if param .MaxKeys != nil {
742+ maxKeys = aws .Int64 (int64 (* param .MaxKeys ))
743+ }
744+
745+ req := & s3.ListObjectsV2Input {
746+ Bucket : & s .bucket ,
747+ Prefix : param .Prefix ,
748+ Delimiter : param .Delimiter ,
749+ MaxKeys : maxKeys ,
750+ StartAfter : param .StartAfter ,
751+ ContinuationToken : param .ContinuationToken ,
752+ }
753+
754+ if s .config .EnableSpecials {
755+ return s .listObjectsV2Special (req )
756+ }
757+
758+ resp , reqId , err := s .listObjects (req )
759+ if err != nil {
760+ return nil , err
761+ }
762+
763+ return s .convertListOutput (resp , reqId )
764+ }
765+
616766func (s * S3Backend ) DeleteBlob (param * DeleteBlobInput ) (* DeleteBlobOutput , error ) {
617767 req , _ := s .DeleteObjectRequest (& s3.DeleteObjectInput {
618768 Bucket : & s .bucket ,
0 commit comments