github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/backend/azureblob/azureblob.go (about)

     1  // Package azureblob provides an interface to the Microsoft Azure blob object storage system
     2  
     3  // +build !plan9,!solaris
     4  
     5  package azureblob
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"crypto/md5"
    11  	"encoding/base64"
    12  	"encoding/binary"
    13  	"encoding/hex"
    14  	"fmt"
    15  	"io"
    16  	"net/http"
    17  	"net/url"
    18  	"path"
    19  	"strconv"
    20  	"strings"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/Azure/azure-pipeline-go/pipeline"
    25  	"github.com/Azure/azure-storage-blob-go/azblob"
    26  	"github.com/pkg/errors"
    27  	"github.com/rclone/rclone/fs"
    28  	"github.com/rclone/rclone/fs/accounting"
    29  	"github.com/rclone/rclone/fs/config"
    30  	"github.com/rclone/rclone/fs/config/configmap"
    31  	"github.com/rclone/rclone/fs/config/configstruct"
    32  	"github.com/rclone/rclone/fs/fserrors"
    33  	"github.com/rclone/rclone/fs/fshttp"
    34  	"github.com/rclone/rclone/fs/hash"
    35  	"github.com/rclone/rclone/fs/walk"
    36  	"github.com/rclone/rclone/lib/bucket"
    37  	"github.com/rclone/rclone/lib/encoder"
    38  	"github.com/rclone/rclone/lib/pacer"
    39  )
    40  
    41  const (
    42  	minSleep              = 10 * time.Millisecond
    43  	maxSleep              = 10 * time.Second
    44  	decayConstant         = 1    // bigger for slower decay, exponential
    45  	maxListChunkSize      = 5000 // number of items to read at once
    46  	modTimeKey            = "mtime"
    47  	timeFormatIn          = time.RFC3339
    48  	timeFormatOut         = "2006-01-02T15:04:05.000000000Z07:00"
    49  	maxTotalParts         = 50000 // in multipart upload
    50  	storageDefaultBaseURL = "blob.core.windows.net"
    51  	// maxUncommittedSize = 9 << 30 // can't upload bigger than this
    52  	defaultChunkSize    = 4 * fs.MebiByte
    53  	maxChunkSize        = 100 * fs.MebiByte
    54  	defaultUploadCutoff = 256 * fs.MebiByte
    55  	maxUploadCutoff     = 256 * fs.MebiByte
    56  	defaultAccessTier   = azblob.AccessTierNone
    57  	maxTryTimeout       = time.Hour * 24 * 365 //max time of an azure web request response window (whether or not data is flowing)
    58  	// Default storage account, key and blob endpoint for emulator support,
    59  	// though it is a base64 key checked in here, it is publicly available secret.
    60  	emulatorAccount      = "devstoreaccount1"
    61  	emulatorAccountKey   = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
    62  	emulatorBlobEndpoint = "http://127.0.0.1:10000/devstoreaccount1"
    63  )
    64  
    65  // Register with Fs
    66  func init() {
    67  	fs.Register(&fs.RegInfo{
    68  		Name:        "azureblob",
    69  		Description: "Microsoft Azure Blob Storage",
    70  		NewFs:       NewFs,
    71  		Options: []fs.Option{{
    72  			Name: "account",
    73  			Help: "Storage Account Name (leave blank to use SAS URL or Emulator)",
    74  		}, {
    75  			Name: "key",
    76  			Help: "Storage Account Key (leave blank to use SAS URL or Emulator)",
    77  		}, {
    78  			Name: "sas_url",
    79  			Help: "SAS URL for container level access only\n(leave blank if using account/key or Emulator)",
    80  		}, {
    81  			Name:    "use_emulator",
    82  			Help:    "Uses local storage emulator if provided as 'true' (leave blank if using real azure storage endpoint)",
    83  			Default: false,
    84  		}, {
    85  			Name:     "endpoint",
    86  			Help:     "Endpoint for the service\nLeave blank normally.",
    87  			Advanced: true,
    88  		}, {
    89  			Name:     "upload_cutoff",
    90  			Help:     "Cutoff for switching to chunked upload (<= 256MB).",
    91  			Default:  defaultUploadCutoff,
    92  			Advanced: true,
    93  		}, {
    94  			Name: "chunk_size",
    95  			Help: `Upload chunk size (<= 100MB).
    96  
    97  Note that this is stored in memory and there may be up to
    98  "--transfers" chunks stored at once in memory.`,
    99  			Default:  defaultChunkSize,
   100  			Advanced: true,
   101  		}, {
   102  			Name: "list_chunk",
   103  			Help: `Size of blob list.
   104  
   105  This sets the number of blobs requested in each listing chunk. Default
   106  is the maximum, 5000. "List blobs" requests are permitted 2 minutes
   107  per megabyte to complete. If an operation is taking longer than 2
   108  minutes per megabyte on average, it will time out (
   109  [source](https://docs.microsoft.com/en-us/rest/api/storageservices/setting-timeouts-for-blob-service-operations#exceptions-to-default-timeout-interval)
   110  ). This can be used to limit the number of blobs items to return, to
   111  avoid the time out.`,
   112  			Default:  maxListChunkSize,
   113  			Advanced: true,
   114  		}, {
   115  			Name: "access_tier",
   116  			Help: `Access tier of blob: hot, cool or archive.
   117  
   118  Archived blobs can be restored by setting access tier to hot or
   119  cool. Leave blank if you intend to use default access tier, which is
   120  set at account level
   121  
   122  If there is no "access tier" specified, rclone doesn't apply any tier.
   123  rclone performs "Set Tier" operation on blobs while uploading, if objects
   124  are not modified, specifying "access tier" to new one will have no effect.
   125  If blobs are in "archive tier" at remote, trying to perform data transfer
   126  operations from remote will not be allowed. User should first restore by
   127  tiering blob to "Hot" or "Cool".`,
   128  			Advanced: true,
   129  		}, {
   130  			Name:     config.ConfigEncoding,
   131  			Help:     config.ConfigEncodingHelp,
   132  			Advanced: true,
   133  			Default: (encoder.EncodeInvalidUtf8 |
   134  				encoder.EncodeSlash |
   135  				encoder.EncodeCtl |
   136  				encoder.EncodeDel |
   137  				encoder.EncodeBackSlash |
   138  				encoder.EncodeRightPeriod),
   139  		}},
   140  	})
   141  }
   142  
   143  // Options defines the configuration for this backend
   144  type Options struct {
   145  	Account       string               `config:"account"`
   146  	Key           string               `config:"key"`
   147  	Endpoint      string               `config:"endpoint"`
   148  	SASURL        string               `config:"sas_url"`
   149  	UploadCutoff  fs.SizeSuffix        `config:"upload_cutoff"`
   150  	ChunkSize     fs.SizeSuffix        `config:"chunk_size"`
   151  	ListChunkSize uint                 `config:"list_chunk"`
   152  	AccessTier    string               `config:"access_tier"`
   153  	UseEmulator   bool                 `config:"use_emulator"`
   154  	Enc           encoder.MultiEncoder `config:"encoding"`
   155  }
   156  
   157  // Fs represents a remote azure server
   158  type Fs struct {
   159  	name          string                          // name of this remote
   160  	root          string                          // the path we are working on if any
   161  	opt           Options                         // parsed config options
   162  	features      *fs.Features                    // optional features
   163  	client        *http.Client                    // http client we are using
   164  	svcURL        *azblob.ServiceURL              // reference to serviceURL
   165  	cntURLcacheMu sync.Mutex                      // mutex to protect cntURLcache
   166  	cntURLcache   map[string]*azblob.ContainerURL // reference to containerURL per container
   167  	rootContainer string                          // container part of root (if any)
   168  	rootDirectory string                          // directory part of root (if any)
   169  	isLimited     bool                            // if limited to one container
   170  	cache         *bucket.Cache                   // cache for container creation status
   171  	pacer         *fs.Pacer                       // To pace and retry the API calls
   172  	uploadToken   *pacer.TokenDispenser           // control concurrency
   173  }
   174  
   175  // Object describes a azure object
   176  type Object struct {
   177  	fs         *Fs                   // what this object is part of
   178  	remote     string                // The remote path
   179  	modTime    time.Time             // The modified time of the object if known
   180  	md5        string                // MD5 hash if known
   181  	size       int64                 // Size of the object
   182  	mimeType   string                // Content-Type of the object
   183  	accessTier azblob.AccessTierType // Blob Access Tier
   184  	meta       map[string]string     // blob metadata
   185  }
   186  
   187  // ------------------------------------------------------------
   188  
   189  // Name of the remote (as passed into NewFs)
   190  func (f *Fs) Name() string {
   191  	return f.name
   192  }
   193  
   194  // Root of the remote (as passed into NewFs)
   195  func (f *Fs) Root() string {
   196  	return f.root
   197  }
   198  
   199  // String converts this Fs to a string
   200  func (f *Fs) String() string {
   201  	if f.rootContainer == "" {
   202  		return fmt.Sprintf("Azure root")
   203  	}
   204  	if f.rootDirectory == "" {
   205  		return fmt.Sprintf("Azure container %s", f.rootContainer)
   206  	}
   207  	return fmt.Sprintf("Azure container %s path %s", f.rootContainer, f.rootDirectory)
   208  }
   209  
   210  // Features returns the optional features of this Fs
   211  func (f *Fs) Features() *fs.Features {
   212  	return f.features
   213  }
   214  
   215  // parsePath parses a remote 'url'
   216  func parsePath(path string) (root string) {
   217  	root = strings.Trim(path, "/")
   218  	return
   219  }
   220  
   221  // split returns container and containerPath from the rootRelativePath
   222  // relative to f.root
   223  func (f *Fs) split(rootRelativePath string) (containerName, containerPath string) {
   224  	containerName, containerPath = bucket.Split(path.Join(f.root, rootRelativePath))
   225  	return f.opt.Enc.FromStandardName(containerName), f.opt.Enc.FromStandardPath(containerPath)
   226  }
   227  
   228  // split returns container and containerPath from the object
   229  func (o *Object) split() (container, containerPath string) {
   230  	return o.fs.split(o.remote)
   231  }
   232  
   233  // validateAccessTier checks if azureblob supports user supplied tier
   234  func validateAccessTier(tier string) bool {
   235  	switch tier {
   236  	case string(azblob.AccessTierHot),
   237  		string(azblob.AccessTierCool),
   238  		string(azblob.AccessTierArchive):
   239  		// valid cases
   240  		return true
   241  	default:
   242  		return false
   243  	}
   244  }
   245  
   246  // retryErrorCodes is a slice of error codes that we will retry
   247  var retryErrorCodes = []int{
   248  	401, // Unauthorized (eg "Token has expired")
   249  	408, // Request Timeout
   250  	429, // Rate exceeded.
   251  	500, // Get occasional 500 Internal Server Error
   252  	503, // Service Unavailable
   253  	504, // Gateway Time-out
   254  }
   255  
   256  // shouldRetry returns a boolean as to whether this resp and err
   257  // deserve to be retried.  It returns the err as a convenience
   258  func (f *Fs) shouldRetry(err error) (bool, error) {
   259  	// FIXME interpret special errors - more to do here
   260  	if storageErr, ok := err.(azblob.StorageError); ok {
   261  		statusCode := storageErr.Response().StatusCode
   262  		for _, e := range retryErrorCodes {
   263  			if statusCode == e {
   264  				return true, err
   265  			}
   266  		}
   267  	}
   268  	return fserrors.ShouldRetry(err), err
   269  }
   270  
   271  func checkUploadChunkSize(cs fs.SizeSuffix) error {
   272  	const minChunkSize = fs.Byte
   273  	if cs < minChunkSize {
   274  		return errors.Errorf("%s is less than %s", cs, minChunkSize)
   275  	}
   276  	if cs > maxChunkSize {
   277  		return errors.Errorf("%s is greater than %s", cs, maxChunkSize)
   278  	}
   279  	return nil
   280  }
   281  
   282  func (f *Fs) setUploadChunkSize(cs fs.SizeSuffix) (old fs.SizeSuffix, err error) {
   283  	err = checkUploadChunkSize(cs)
   284  	if err == nil {
   285  		old, f.opt.ChunkSize = f.opt.ChunkSize, cs
   286  	}
   287  	return
   288  }
   289  
   290  func checkUploadCutoff(cs fs.SizeSuffix) error {
   291  	if cs > maxUploadCutoff {
   292  		return errors.Errorf("%v must be less than or equal to %v", cs, maxUploadCutoff)
   293  	}
   294  	return nil
   295  }
   296  
   297  func (f *Fs) setUploadCutoff(cs fs.SizeSuffix) (old fs.SizeSuffix, err error) {
   298  	err = checkUploadCutoff(cs)
   299  	if err == nil {
   300  		old, f.opt.UploadCutoff = f.opt.UploadCutoff, cs
   301  	}
   302  	return
   303  }
   304  
   305  // httpClientFactory creates a Factory object that sends HTTP requests
   306  // to a rclone's http.Client.
   307  //
   308  // copied from azblob.newDefaultHTTPClientFactory
   309  func httpClientFactory(client *http.Client) pipeline.Factory {
   310  	return pipeline.FactoryFunc(func(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.PolicyFunc {
   311  		return func(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
   312  			r, err := client.Do(request.WithContext(ctx))
   313  			if err != nil {
   314  				err = pipeline.NewError(err, "HTTP request failed")
   315  			}
   316  			return pipeline.NewHTTPResponse(r), err
   317  		}
   318  	})
   319  }
   320  
   321  // newPipeline creates a Pipeline using the specified credentials and options.
   322  //
   323  // this code was copied from azblob.NewPipeline
   324  func (f *Fs) newPipeline(c azblob.Credential, o azblob.PipelineOptions) pipeline.Pipeline {
   325  	// Don't log stuff to syslog/Windows Event log
   326  	pipeline.SetForceLogEnabled(false)
   327  
   328  	// Closest to API goes first; closest to the wire goes last
   329  	factories := []pipeline.Factory{
   330  		azblob.NewTelemetryPolicyFactory(o.Telemetry),
   331  		azblob.NewUniqueRequestIDPolicyFactory(),
   332  		azblob.NewRetryPolicyFactory(o.Retry),
   333  		c,
   334  		pipeline.MethodFactoryMarker(), // indicates at what stage in the pipeline the method factory is invoked
   335  		azblob.NewRequestLogPolicyFactory(o.RequestLog),
   336  	}
   337  	return pipeline.NewPipeline(factories, pipeline.Options{HTTPSender: httpClientFactory(f.client), Log: o.Log})
   338  }
   339  
   340  // setRoot changes the root of the Fs
   341  func (f *Fs) setRoot(root string) {
   342  	f.root = parsePath(root)
   343  	f.rootContainer, f.rootDirectory = bucket.Split(f.root)
   344  }
   345  
   346  // NewFs constructs an Fs from the path, container:path
   347  func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
   348  	ctx := context.Background()
   349  	// Parse config into Options struct
   350  	opt := new(Options)
   351  	err := configstruct.Set(m, opt)
   352  	if err != nil {
   353  		return nil, err
   354  	}
   355  
   356  	err = checkUploadCutoff(opt.UploadCutoff)
   357  	if err != nil {
   358  		return nil, errors.Wrap(err, "azure: upload cutoff")
   359  	}
   360  	err = checkUploadChunkSize(opt.ChunkSize)
   361  	if err != nil {
   362  		return nil, errors.Wrap(err, "azure: chunk size")
   363  	}
   364  	if opt.ListChunkSize > maxListChunkSize {
   365  		return nil, errors.Errorf("azure: blob list size can't be greater than %v - was %v", maxListChunkSize, opt.ListChunkSize)
   366  	}
   367  	if opt.Endpoint == "" {
   368  		opt.Endpoint = storageDefaultBaseURL
   369  	}
   370  
   371  	if opt.AccessTier == "" {
   372  		opt.AccessTier = string(defaultAccessTier)
   373  	} else if !validateAccessTier(opt.AccessTier) {
   374  		return nil, errors.Errorf("Azure Blob: Supported access tiers are %s, %s and %s",
   375  			string(azblob.AccessTierHot), string(azblob.AccessTierCool), string(azblob.AccessTierArchive))
   376  	}
   377  
   378  	f := &Fs{
   379  		name:        name,
   380  		opt:         *opt,
   381  		pacer:       fs.NewPacer(pacer.NewS3(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
   382  		uploadToken: pacer.NewTokenDispenser(fs.Config.Transfers),
   383  		client:      fshttp.NewClient(fs.Config),
   384  		cache:       bucket.NewCache(),
   385  		cntURLcache: make(map[string]*azblob.ContainerURL, 1),
   386  	}
   387  	f.setRoot(root)
   388  	f.features = (&fs.Features{
   389  		ReadMimeType:      true,
   390  		WriteMimeType:     true,
   391  		BucketBased:       true,
   392  		BucketBasedRootOK: true,
   393  		SetTier:           true,
   394  		GetTier:           true,
   395  	}).Fill(f)
   396  
   397  	var (
   398  		u          *url.URL
   399  		serviceURL azblob.ServiceURL
   400  	)
   401  	switch {
   402  	case opt.UseEmulator:
   403  		credential, err := azblob.NewSharedKeyCredential(emulatorAccount, emulatorAccountKey)
   404  		if err != nil {
   405  			return nil, errors.Wrapf(err, "Failed to parse credentials")
   406  		}
   407  		u, err = url.Parse(emulatorBlobEndpoint)
   408  		if err != nil {
   409  			return nil, errors.Wrap(err, "failed to make azure storage url from account and endpoint")
   410  		}
   411  		pipeline := f.newPipeline(credential, azblob.PipelineOptions{Retry: azblob.RetryOptions{TryTimeout: maxTryTimeout}})
   412  		serviceURL = azblob.NewServiceURL(*u, pipeline)
   413  	case opt.Account != "" && opt.Key != "":
   414  		credential, err := azblob.NewSharedKeyCredential(opt.Account, opt.Key)
   415  		if err != nil {
   416  			return nil, errors.Wrapf(err, "Failed to parse credentials")
   417  		}
   418  
   419  		u, err = url.Parse(fmt.Sprintf("https://%s.%s", opt.Account, opt.Endpoint))
   420  		if err != nil {
   421  			return nil, errors.Wrap(err, "failed to make azure storage url from account and endpoint")
   422  		}
   423  		pipeline := f.newPipeline(credential, azblob.PipelineOptions{Retry: azblob.RetryOptions{TryTimeout: maxTryTimeout}})
   424  		serviceURL = azblob.NewServiceURL(*u, pipeline)
   425  	case opt.SASURL != "":
   426  		u, err = url.Parse(opt.SASURL)
   427  		if err != nil {
   428  			return nil, errors.Wrapf(err, "failed to parse SAS URL")
   429  		}
   430  		// use anonymous credentials in case of sas url
   431  		pipeline := f.newPipeline(azblob.NewAnonymousCredential(), azblob.PipelineOptions{Retry: azblob.RetryOptions{TryTimeout: maxTryTimeout}})
   432  		// Check if we have container level SAS or account level sas
   433  		parts := azblob.NewBlobURLParts(*u)
   434  		if parts.ContainerName != "" {
   435  			if f.rootContainer != "" && parts.ContainerName != f.rootContainer {
   436  				return nil, errors.New("Container name in SAS URL and container provided in command do not match")
   437  			}
   438  			containerURL := azblob.NewContainerURL(*u, pipeline)
   439  			f.cntURLcache[parts.ContainerName] = &containerURL
   440  			f.isLimited = true
   441  		} else {
   442  			serviceURL = azblob.NewServiceURL(*u, pipeline)
   443  		}
   444  	default:
   445  		return nil, errors.New("Need account+key or connectionString or sasURL")
   446  	}
   447  	f.svcURL = &serviceURL
   448  
   449  	if f.rootContainer != "" && f.rootDirectory != "" {
   450  		// Check to see if the (container,directory) is actually an existing file
   451  		oldRoot := f.root
   452  		newRoot, leaf := path.Split(oldRoot)
   453  		f.setRoot(newRoot)
   454  		_, err := f.NewObject(ctx, leaf)
   455  		if err != nil {
   456  			if err == fs.ErrorObjectNotFound || err == fs.ErrorNotAFile {
   457  				// File doesn't exist or is a directory so return old f
   458  				f.setRoot(oldRoot)
   459  				return f, nil
   460  			}
   461  			return nil, err
   462  		}
   463  		// return an error with an fs which points to the parent
   464  		return f, fs.ErrorIsFile
   465  	}
   466  	return f, nil
   467  }
   468  
   469  // return the container URL for the container passed in
   470  func (f *Fs) cntURL(container string) (containerURL *azblob.ContainerURL) {
   471  	f.cntURLcacheMu.Lock()
   472  	defer f.cntURLcacheMu.Unlock()
   473  	var ok bool
   474  	if containerURL, ok = f.cntURLcache[container]; !ok {
   475  		cntURL := f.svcURL.NewContainerURL(container)
   476  		containerURL = &cntURL
   477  		f.cntURLcache[container] = containerURL
   478  	}
   479  	return containerURL
   480  
   481  }
   482  
   483  // Return an Object from a path
   484  //
   485  // If it can't be found it returns the error fs.ErrorObjectNotFound.
   486  func (f *Fs) newObjectWithInfo(remote string, info *azblob.BlobItem) (fs.Object, error) {
   487  	o := &Object{
   488  		fs:     f,
   489  		remote: remote,
   490  	}
   491  	if info != nil {
   492  		err := o.decodeMetaDataFromBlob(info)
   493  		if err != nil {
   494  			return nil, err
   495  		}
   496  	} else {
   497  		err := o.readMetaData() // reads info and headers, returning an error
   498  		if err != nil {
   499  			return nil, err
   500  		}
   501  	}
   502  	return o, nil
   503  }
   504  
   505  // NewObject finds the Object at remote.  If it can't be found
   506  // it returns the error fs.ErrorObjectNotFound.
   507  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   508  	return f.newObjectWithInfo(remote, nil)
   509  }
   510  
   511  // getBlobReference creates an empty blob reference with no metadata
   512  func (f *Fs) getBlobReference(container, containerPath string) azblob.BlobURL {
   513  	return f.cntURL(container).NewBlobURL(containerPath)
   514  }
   515  
   516  // updateMetadataWithModTime adds the modTime passed in to o.meta.
   517  func (o *Object) updateMetadataWithModTime(modTime time.Time) {
   518  	// Make sure o.meta is not nil
   519  	if o.meta == nil {
   520  		o.meta = make(map[string]string, 1)
   521  	}
   522  
   523  	// Set modTimeKey in it
   524  	o.meta[modTimeKey] = modTime.Format(timeFormatOut)
   525  }
   526  
   527  // Returns whether file is a directory marker or not
   528  func isDirectoryMarker(size int64, metadata azblob.Metadata, remote string) bool {
   529  	// Directory markers are 0 length
   530  	if size == 0 {
   531  		// Note that metadata with hdi_isfolder = true seems to be a
   532  		// defacto standard for marking blobs as directories.
   533  		endsWithSlash := strings.HasSuffix(remote, "/")
   534  		if endsWithSlash || remote == "" || metadata["hdi_isfolder"] == "true" {
   535  			return true
   536  		}
   537  
   538  	}
   539  	return false
   540  }
   541  
   542  // listFn is called from list to handle an object
   543  type listFn func(remote string, object *azblob.BlobItem, isDirectory bool) error
   544  
   545  // list lists the objects into the function supplied from
   546  // the container and root supplied
   547  //
   548  // dir is the starting directory, "" for root
   549  //
   550  // The remote has prefix removed from it and if addContainer is set then
   551  // it adds the container to the start.
   552  func (f *Fs) list(ctx context.Context, container, directory, prefix string, addContainer bool, recurse bool, maxResults uint, fn listFn) error {
   553  	if f.cache.IsDeleted(container) {
   554  		return fs.ErrorDirNotFound
   555  	}
   556  	if prefix != "" {
   557  		prefix += "/"
   558  	}
   559  	if directory != "" {
   560  		directory += "/"
   561  	}
   562  	delimiter := ""
   563  	if !recurse {
   564  		delimiter = "/"
   565  	}
   566  
   567  	options := azblob.ListBlobsSegmentOptions{
   568  		Details: azblob.BlobListingDetails{
   569  			Copy:             false,
   570  			Metadata:         true,
   571  			Snapshots:        false,
   572  			UncommittedBlobs: false,
   573  			Deleted:          false,
   574  		},
   575  		Prefix:     directory,
   576  		MaxResults: int32(maxResults),
   577  	}
   578  	for marker := (azblob.Marker{}); marker.NotDone(); {
   579  		var response *azblob.ListBlobsHierarchySegmentResponse
   580  		err := f.pacer.Call(func() (bool, error) {
   581  			var err error
   582  			response, err = f.cntURL(container).ListBlobsHierarchySegment(ctx, marker, delimiter, options)
   583  			return f.shouldRetry(err)
   584  		})
   585  
   586  		if err != nil {
   587  			// Check http error code along with service code, current SDK doesn't populate service code correctly sometimes
   588  			if storageErr, ok := err.(azblob.StorageError); ok && (storageErr.ServiceCode() == azblob.ServiceCodeContainerNotFound || storageErr.Response().StatusCode == http.StatusNotFound) {
   589  				return fs.ErrorDirNotFound
   590  			}
   591  			return err
   592  		}
   593  		// Advance marker to next
   594  		marker = response.NextMarker
   595  		for i := range response.Segment.BlobItems {
   596  			file := &response.Segment.BlobItems[i]
   597  			// Finish if file name no longer has prefix
   598  			// if prefix != "" && !strings.HasPrefix(file.Name, prefix) {
   599  			// 	return nil
   600  			// }
   601  			remote := f.opt.Enc.ToStandardPath(file.Name)
   602  			if !strings.HasPrefix(remote, prefix) {
   603  				fs.Debugf(f, "Odd name received %q", remote)
   604  				continue
   605  			}
   606  			remote = remote[len(prefix):]
   607  			if isDirectoryMarker(*file.Properties.ContentLength, file.Metadata, remote) {
   608  				continue // skip directory marker
   609  			}
   610  			if addContainer {
   611  				remote = path.Join(container, remote)
   612  			}
   613  			// Send object
   614  			err = fn(remote, file, false)
   615  			if err != nil {
   616  				return err
   617  			}
   618  		}
   619  		// Send the subdirectories
   620  		for _, remote := range response.Segment.BlobPrefixes {
   621  			remote := strings.TrimRight(remote.Name, "/")
   622  			remote = f.opt.Enc.ToStandardPath(remote)
   623  			if !strings.HasPrefix(remote, prefix) {
   624  				fs.Debugf(f, "Odd directory name received %q", remote)
   625  				continue
   626  			}
   627  			remote = remote[len(prefix):]
   628  			if addContainer {
   629  				remote = path.Join(container, remote)
   630  			}
   631  			// Send object
   632  			err = fn(remote, nil, true)
   633  			if err != nil {
   634  				return err
   635  			}
   636  		}
   637  	}
   638  	return nil
   639  }
   640  
   641  // Convert a list item into a DirEntry
   642  func (f *Fs) itemToDirEntry(remote string, object *azblob.BlobItem, isDirectory bool) (fs.DirEntry, error) {
   643  	if isDirectory {
   644  		d := fs.NewDir(remote, time.Time{})
   645  		return d, nil
   646  	}
   647  	o, err := f.newObjectWithInfo(remote, object)
   648  	if err != nil {
   649  		return nil, err
   650  	}
   651  	return o, nil
   652  }
   653  
   654  // listDir lists a single directory
   655  func (f *Fs) listDir(ctx context.Context, container, directory, prefix string, addContainer bool) (entries fs.DirEntries, err error) {
   656  	err = f.list(ctx, container, directory, prefix, addContainer, false, f.opt.ListChunkSize, func(remote string, object *azblob.BlobItem, isDirectory bool) error {
   657  		entry, err := f.itemToDirEntry(remote, object, isDirectory)
   658  		if err != nil {
   659  			return err
   660  		}
   661  		if entry != nil {
   662  			entries = append(entries, entry)
   663  		}
   664  		return nil
   665  	})
   666  	if err != nil {
   667  		return nil, err
   668  	}
   669  	// container must be present if listing succeeded
   670  	f.cache.MarkOK(container)
   671  	return entries, nil
   672  }
   673  
   674  // listContainers returns all the containers to out
   675  func (f *Fs) listContainers(ctx context.Context) (entries fs.DirEntries, err error) {
   676  	if f.isLimited {
   677  		f.cntURLcacheMu.Lock()
   678  		for container := range f.cntURLcache {
   679  			d := fs.NewDir(container, time.Time{})
   680  			entries = append(entries, d)
   681  		}
   682  		f.cntURLcacheMu.Unlock()
   683  		return entries, nil
   684  	}
   685  	err = f.listContainersToFn(func(container *azblob.ContainerItem) error {
   686  		d := fs.NewDir(f.opt.Enc.ToStandardName(container.Name), container.Properties.LastModified)
   687  		f.cache.MarkOK(container.Name)
   688  		entries = append(entries, d)
   689  		return nil
   690  	})
   691  	if err != nil {
   692  		return nil, err
   693  	}
   694  	return entries, nil
   695  }
   696  
   697  // List the objects and directories in dir into entries.  The
   698  // entries can be returned in any order but should be for a
   699  // complete directory.
   700  //
   701  // dir should be "" to list the root, and should not have
   702  // trailing slashes.
   703  //
   704  // This should return ErrDirNotFound if the directory isn't
   705  // found.
   706  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   707  	container, directory := f.split(dir)
   708  	if container == "" {
   709  		if directory != "" {
   710  			return nil, fs.ErrorListBucketRequired
   711  		}
   712  		return f.listContainers(ctx)
   713  	}
   714  	return f.listDir(ctx, container, directory, f.rootDirectory, f.rootContainer == "")
   715  }
   716  
   717  // ListR lists the objects and directories of the Fs starting
   718  // from dir recursively into out.
   719  //
   720  // dir should be "" to start from the root, and should not
   721  // have trailing slashes.
   722  //
   723  // This should return ErrDirNotFound if the directory isn't
   724  // found.
   725  //
   726  // It should call callback for each tranche of entries read.
   727  // These need not be returned in any particular order.  If
   728  // callback returns an error then the listing will stop
   729  // immediately.
   730  //
   731  // Don't implement this unless you have a more efficient way
   732  // of listing recursively that doing a directory traversal.
   733  func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (err error) {
   734  	container, directory := f.split(dir)
   735  	list := walk.NewListRHelper(callback)
   736  	listR := func(container, directory, prefix string, addContainer bool) error {
   737  		return f.list(ctx, container, directory, prefix, addContainer, true, f.opt.ListChunkSize, func(remote string, object *azblob.BlobItem, isDirectory bool) error {
   738  			entry, err := f.itemToDirEntry(remote, object, isDirectory)
   739  			if err != nil {
   740  				return err
   741  			}
   742  			return list.Add(entry)
   743  		})
   744  	}
   745  	if container == "" {
   746  		entries, err := f.listContainers(ctx)
   747  		if err != nil {
   748  			return err
   749  		}
   750  		for _, entry := range entries {
   751  			err = list.Add(entry)
   752  			if err != nil {
   753  				return err
   754  			}
   755  			container := entry.Remote()
   756  			err = listR(container, "", f.rootDirectory, true)
   757  			if err != nil {
   758  				return err
   759  			}
   760  			// container must be present if listing succeeded
   761  			f.cache.MarkOK(container)
   762  		}
   763  	} else {
   764  		err = listR(container, directory, f.rootDirectory, f.rootContainer == "")
   765  		if err != nil {
   766  			return err
   767  		}
   768  		// container must be present if listing succeeded
   769  		f.cache.MarkOK(container)
   770  	}
   771  	return list.Flush()
   772  }
   773  
   774  // listContainerFn is called from listContainersToFn to handle a container
   775  type listContainerFn func(*azblob.ContainerItem) error
   776  
   777  // listContainersToFn lists the containers to the function supplied
   778  func (f *Fs) listContainersToFn(fn listContainerFn) error {
   779  	params := azblob.ListContainersSegmentOptions{
   780  		MaxResults: int32(f.opt.ListChunkSize),
   781  	}
   782  	ctx := context.Background()
   783  	for marker := (azblob.Marker{}); marker.NotDone(); {
   784  		var response *azblob.ListContainersSegmentResponse
   785  		err := f.pacer.Call(func() (bool, error) {
   786  			var err error
   787  			response, err = f.svcURL.ListContainersSegment(ctx, marker, params)
   788  			return f.shouldRetry(err)
   789  		})
   790  		if err != nil {
   791  			return err
   792  		}
   793  
   794  		for i := range response.ContainerItems {
   795  			err = fn(&response.ContainerItems[i])
   796  			if err != nil {
   797  				return err
   798  			}
   799  		}
   800  		marker = response.NextMarker
   801  	}
   802  
   803  	return nil
   804  }
   805  
   806  // Put the object into the container
   807  //
   808  // Copy the reader in to the new object which is returned
   809  //
   810  // The new object may have been created if an error is returned
   811  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   812  	// Temporary Object under construction
   813  	fs := &Object{
   814  		fs:     f,
   815  		remote: src.Remote(),
   816  	}
   817  	return fs, fs.Update(ctx, in, src, options...)
   818  }
   819  
   820  // Mkdir creates the container if it doesn't exist
   821  func (f *Fs) Mkdir(ctx context.Context, dir string) error {
   822  	container, _ := f.split(dir)
   823  	return f.makeContainer(ctx, container)
   824  }
   825  
   826  // makeContainer creates the container if it doesn't exist
   827  func (f *Fs) makeContainer(ctx context.Context, container string) error {
   828  	return f.cache.Create(container, func() error {
   829  		// now try to create the container
   830  		return f.pacer.Call(func() (bool, error) {
   831  			_, err := f.cntURL(container).Create(ctx, azblob.Metadata{}, azblob.PublicAccessNone)
   832  			if err != nil {
   833  				if storageErr, ok := err.(azblob.StorageError); ok {
   834  					switch storageErr.ServiceCode() {
   835  					case azblob.ServiceCodeContainerAlreadyExists:
   836  						return false, nil
   837  					case azblob.ServiceCodeContainerBeingDeleted:
   838  						// From https://docs.microsoft.com/en-us/rest/api/storageservices/delete-container
   839  						// When a container is deleted, a container with the same name cannot be created
   840  						// for at least 30 seconds; the container may not be available for more than 30
   841  						// seconds if the service is still processing the request.
   842  						time.Sleep(6 * time.Second) // default 10 retries will be 60 seconds
   843  						f.cache.MarkDeleted(container)
   844  						return true, err
   845  					}
   846  				}
   847  			}
   848  			return f.shouldRetry(err)
   849  		})
   850  	}, nil)
   851  }
   852  
   853  // isEmpty checks to see if a given (container, directory) is empty and returns an error if not
   854  func (f *Fs) isEmpty(ctx context.Context, container, directory string) (err error) {
   855  	empty := true
   856  	err = f.list(ctx, container, directory, f.rootDirectory, f.rootContainer == "", true, 1, func(remote string, object *azblob.BlobItem, isDirectory bool) error {
   857  		empty = false
   858  		return nil
   859  	})
   860  	if err != nil {
   861  		return err
   862  	}
   863  	if !empty {
   864  		return fs.ErrorDirectoryNotEmpty
   865  	}
   866  	return nil
   867  }
   868  
   869  // deleteContainer deletes the container.  It can delete a full
   870  // container so use isEmpty if you don't want that.
   871  func (f *Fs) deleteContainer(ctx context.Context, container string) error {
   872  	return f.cache.Remove(container, func() error {
   873  		options := azblob.ContainerAccessConditions{}
   874  		return f.pacer.Call(func() (bool, error) {
   875  			_, err := f.cntURL(container).GetProperties(ctx, azblob.LeaseAccessConditions{})
   876  			if err == nil {
   877  				_, err = f.cntURL(container).Delete(ctx, options)
   878  			}
   879  
   880  			if err != nil {
   881  				// Check http error code along with service code, current SDK doesn't populate service code correctly sometimes
   882  				if storageErr, ok := err.(azblob.StorageError); ok && (storageErr.ServiceCode() == azblob.ServiceCodeContainerNotFound || storageErr.Response().StatusCode == http.StatusNotFound) {
   883  					return false, fs.ErrorDirNotFound
   884  				}
   885  
   886  				return f.shouldRetry(err)
   887  			}
   888  
   889  			return f.shouldRetry(err)
   890  		})
   891  	})
   892  }
   893  
   894  // Rmdir deletes the container if the fs is at the root
   895  //
   896  // Returns an error if it isn't empty
   897  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   898  	container, directory := f.split(dir)
   899  	if container == "" || directory != "" {
   900  		return nil
   901  	}
   902  	err := f.isEmpty(ctx, container, directory)
   903  	if err != nil {
   904  		return err
   905  	}
   906  	return f.deleteContainer(ctx, container)
   907  }
   908  
   909  // Precision of the remote
   910  func (f *Fs) Precision() time.Duration {
   911  	return time.Nanosecond
   912  }
   913  
   914  // Hashes returns the supported hash sets.
   915  func (f *Fs) Hashes() hash.Set {
   916  	return hash.Set(hash.MD5)
   917  }
   918  
   919  // Purge deletes all the files and directories including the old versions.
   920  func (f *Fs) Purge(ctx context.Context) error {
   921  	dir := "" // forward compat!
   922  	container, directory := f.split(dir)
   923  	if container == "" || directory != "" {
   924  		// Delegate to caller if not root of a container
   925  		return fs.ErrorCantPurge
   926  	}
   927  	return f.deleteContainer(ctx, container)
   928  }
   929  
   930  // Copy src to this remote using server side copy operations.
   931  //
   932  // This is stored with the remote path given
   933  //
   934  // It returns the destination Object and a possible error
   935  //
   936  // Will only be called if src.Fs().Name() == f.Name()
   937  //
   938  // If it isn't possible then return fs.ErrorCantCopy
   939  func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   940  	dstContainer, dstPath := f.split(remote)
   941  	err := f.makeContainer(ctx, dstContainer)
   942  	if err != nil {
   943  		return nil, err
   944  	}
   945  	srcObj, ok := src.(*Object)
   946  	if !ok {
   947  		fs.Debugf(src, "Can't copy - not same remote type")
   948  		return nil, fs.ErrorCantCopy
   949  	}
   950  	dstBlobURL := f.getBlobReference(dstContainer, dstPath)
   951  	srcBlobURL := srcObj.getBlobReference()
   952  
   953  	source, err := url.Parse(srcBlobURL.String())
   954  	if err != nil {
   955  		return nil, err
   956  	}
   957  
   958  	options := azblob.BlobAccessConditions{}
   959  	var startCopy *azblob.BlobStartCopyFromURLResponse
   960  
   961  	err = f.pacer.Call(func() (bool, error) {
   962  		startCopy, err = dstBlobURL.StartCopyFromURL(ctx, *source, nil, azblob.ModifiedAccessConditions{}, options)
   963  		return f.shouldRetry(err)
   964  	})
   965  	if err != nil {
   966  		return nil, err
   967  	}
   968  
   969  	copyStatus := startCopy.CopyStatus()
   970  	for copyStatus == azblob.CopyStatusPending {
   971  		time.Sleep(1 * time.Second)
   972  		getMetadata, err := dstBlobURL.GetProperties(ctx, options)
   973  		if err != nil {
   974  			return nil, err
   975  		}
   976  		copyStatus = getMetadata.CopyStatus()
   977  	}
   978  
   979  	return f.NewObject(ctx, remote)
   980  }
   981  
   982  // ------------------------------------------------------------
   983  
   984  // Fs returns the parent Fs
   985  func (o *Object) Fs() fs.Info {
   986  	return o.fs
   987  }
   988  
   989  // Return a string version
   990  func (o *Object) String() string {
   991  	if o == nil {
   992  		return "<nil>"
   993  	}
   994  	return o.remote
   995  }
   996  
   997  // Remote returns the remote path
   998  func (o *Object) Remote() string {
   999  	return o.remote
  1000  }
  1001  
  1002  // Hash returns the MD5 of an object returning a lowercase hex string
  1003  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
  1004  	if t != hash.MD5 {
  1005  		return "", hash.ErrUnsupported
  1006  	}
  1007  	// Convert base64 encoded md5 into lower case hex
  1008  	if o.md5 == "" {
  1009  		return "", nil
  1010  	}
  1011  	data, err := base64.StdEncoding.DecodeString(o.md5)
  1012  	if err != nil {
  1013  		return "", errors.Wrapf(err, "Failed to decode Content-MD5: %q", o.md5)
  1014  	}
  1015  	return hex.EncodeToString(data), nil
  1016  }
  1017  
  1018  // Size returns the size of an object in bytes
  1019  func (o *Object) Size() int64 {
  1020  	return o.size
  1021  }
  1022  
  1023  func (o *Object) setMetadata(metadata azblob.Metadata) {
  1024  	if len(metadata) > 0 {
  1025  		o.meta = metadata
  1026  		if modTime, ok := metadata[modTimeKey]; ok {
  1027  			when, err := time.Parse(timeFormatIn, modTime)
  1028  			if err != nil {
  1029  				fs.Debugf(o, "Couldn't parse %v = %q: %v", modTimeKey, modTime, err)
  1030  			}
  1031  			o.modTime = when
  1032  		}
  1033  	} else {
  1034  		o.meta = nil
  1035  	}
  1036  }
  1037  
  1038  // decodeMetaDataFromPropertiesResponse sets the metadata from the data passed in
  1039  //
  1040  // Sets
  1041  //  o.id
  1042  //  o.modTime
  1043  //  o.size
  1044  //  o.md5
  1045  //  o.meta
  1046  func (o *Object) decodeMetaDataFromPropertiesResponse(info *azblob.BlobGetPropertiesResponse) (err error) {
  1047  	metadata := info.NewMetadata()
  1048  	size := info.ContentLength()
  1049  	if isDirectoryMarker(size, metadata, o.remote) {
  1050  		return fs.ErrorNotAFile
  1051  	}
  1052  	// NOTE - Client library always returns MD5 as base64 decoded string, Object needs to maintain
  1053  	// this as base64 encoded string.
  1054  	o.md5 = base64.StdEncoding.EncodeToString(info.ContentMD5())
  1055  	o.mimeType = info.ContentType()
  1056  	o.size = size
  1057  	o.modTime = info.LastModified()
  1058  	o.accessTier = azblob.AccessTierType(info.AccessTier())
  1059  	o.setMetadata(metadata)
  1060  
  1061  	return nil
  1062  }
  1063  
  1064  func (o *Object) decodeMetaDataFromBlob(info *azblob.BlobItem) (err error) {
  1065  	metadata := info.Metadata
  1066  	size := *info.Properties.ContentLength
  1067  	if isDirectoryMarker(size, metadata, o.remote) {
  1068  		return fs.ErrorNotAFile
  1069  	}
  1070  	// NOTE - Client library always returns MD5 as base64 decoded string, Object needs to maintain
  1071  	// this as base64 encoded string.
  1072  	o.md5 = base64.StdEncoding.EncodeToString(info.Properties.ContentMD5)
  1073  	o.mimeType = *info.Properties.ContentType
  1074  	o.size = size
  1075  	o.modTime = info.Properties.LastModified
  1076  	o.accessTier = info.Properties.AccessTier
  1077  	o.setMetadata(metadata)
  1078  	return nil
  1079  }
  1080  
  1081  // getBlobReference creates an empty blob reference with no metadata
  1082  func (o *Object) getBlobReference() azblob.BlobURL {
  1083  	container, directory := o.split()
  1084  	return o.fs.getBlobReference(container, directory)
  1085  }
  1086  
  1087  // clearMetaData clears enough metadata so readMetaData will re-read it
  1088  func (o *Object) clearMetaData() {
  1089  	o.modTime = time.Time{}
  1090  }
  1091  
  1092  // readMetaData gets the metadata if it hasn't already been fetched
  1093  //
  1094  // Sets
  1095  //  o.id
  1096  //  o.modTime
  1097  //  o.size
  1098  //  o.md5
  1099  func (o *Object) readMetaData() (err error) {
  1100  	if !o.modTime.IsZero() {
  1101  		return nil
  1102  	}
  1103  	blob := o.getBlobReference()
  1104  
  1105  	// Read metadata (this includes metadata)
  1106  	options := azblob.BlobAccessConditions{}
  1107  	ctx := context.Background()
  1108  	var blobProperties *azblob.BlobGetPropertiesResponse
  1109  	err = o.fs.pacer.Call(func() (bool, error) {
  1110  		blobProperties, err = blob.GetProperties(ctx, options)
  1111  		return o.fs.shouldRetry(err)
  1112  	})
  1113  	if err != nil {
  1114  		// On directories - GetProperties does not work and current SDK does not populate service code correctly hence check regular http response as well
  1115  		if storageErr, ok := err.(azblob.StorageError); ok && (storageErr.ServiceCode() == azblob.ServiceCodeBlobNotFound || storageErr.Response().StatusCode == http.StatusNotFound) {
  1116  			return fs.ErrorObjectNotFound
  1117  		}
  1118  		return err
  1119  	}
  1120  
  1121  	return o.decodeMetaDataFromPropertiesResponse(blobProperties)
  1122  }
  1123  
  1124  // parseTimeString converts a decimal string number of milliseconds
  1125  // elapsed since January 1, 1970 UTC into a time.Time and stores it in
  1126  // the modTime variable.
  1127  func (o *Object) parseTimeString(timeString string) (err error) {
  1128  	if timeString == "" {
  1129  		return nil
  1130  	}
  1131  	unixMilliseconds, err := strconv.ParseInt(timeString, 10, 64)
  1132  	if err != nil {
  1133  		fs.Debugf(o, "Failed to parse mod time string %q: %v", timeString, err)
  1134  		return err
  1135  	}
  1136  	o.modTime = time.Unix(unixMilliseconds/1e3, (unixMilliseconds%1e3)*1e6).UTC()
  1137  	return nil
  1138  }
  1139  
  1140  // ModTime returns the modification time of the object
  1141  //
  1142  // It attempts to read the objects mtime and if that isn't present the
  1143  // LastModified returned in the http headers
  1144  func (o *Object) ModTime(ctx context.Context) (result time.Time) {
  1145  	// The error is logged in readMetaData
  1146  	_ = o.readMetaData()
  1147  	return o.modTime
  1148  }
  1149  
  1150  // SetModTime sets the modification time of the local fs object
  1151  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
  1152  	// Make sure o.meta is not nil
  1153  	if o.meta == nil {
  1154  		o.meta = make(map[string]string, 1)
  1155  	}
  1156  	// Set modTimeKey in it
  1157  	o.meta[modTimeKey] = modTime.Format(timeFormatOut)
  1158  
  1159  	blob := o.getBlobReference()
  1160  	err := o.fs.pacer.Call(func() (bool, error) {
  1161  		_, err := blob.SetMetadata(ctx, o.meta, azblob.BlobAccessConditions{})
  1162  		return o.fs.shouldRetry(err)
  1163  	})
  1164  	if err != nil {
  1165  		return err
  1166  	}
  1167  	o.modTime = modTime
  1168  	return nil
  1169  }
  1170  
  1171  // Storable returns if this object is storable
  1172  func (o *Object) Storable() bool {
  1173  	return true
  1174  }
  1175  
  1176  // Open an object for read
  1177  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
  1178  	// Offset and Count for range download
  1179  	var offset int64
  1180  	var count int64
  1181  	if o.AccessTier() == azblob.AccessTierArchive {
  1182  		return nil, errors.Errorf("Blob in archive tier, you need to set tier to hot or cool first")
  1183  	}
  1184  	fs.FixRangeOption(options, o.size)
  1185  	for _, option := range options {
  1186  		switch x := option.(type) {
  1187  		case *fs.RangeOption:
  1188  			offset, count = x.Decode(o.size)
  1189  			if count < 0 {
  1190  				count = o.size - offset
  1191  			}
  1192  		case *fs.SeekOption:
  1193  			offset = x.Offset
  1194  		default:
  1195  			if option.Mandatory() {
  1196  				fs.Logf(o, "Unsupported mandatory option: %v", option)
  1197  			}
  1198  		}
  1199  	}
  1200  	blob := o.getBlobReference()
  1201  	ac := azblob.BlobAccessConditions{}
  1202  	var dowloadResponse *azblob.DownloadResponse
  1203  	err = o.fs.pacer.Call(func() (bool, error) {
  1204  		dowloadResponse, err = blob.Download(ctx, offset, count, ac, false)
  1205  		return o.fs.shouldRetry(err)
  1206  	})
  1207  	if err != nil {
  1208  		return nil, errors.Wrap(err, "failed to open for download")
  1209  	}
  1210  	in = dowloadResponse.Body(azblob.RetryReaderOptions{})
  1211  	return in, nil
  1212  }
  1213  
  1214  // dontEncode is the characters that do not need percent-encoding
  1215  //
  1216  // The characters that do not need percent-encoding are a subset of
  1217  // the printable ASCII characters: upper-case letters, lower-case
  1218  // letters, digits, ".", "_", "-", "/", "~", "!", "$", "'", "(", ")",
  1219  // "*", ";", "=", ":", and "@". All other byte values in a UTF-8 must
  1220  // be replaced with "%" and the two-digit hex value of the byte.
  1221  const dontEncode = (`abcdefghijklmnopqrstuvwxyz` +
  1222  	`ABCDEFGHIJKLMNOPQRSTUVWXYZ` +
  1223  	`0123456789` +
  1224  	`._-/~!$'()*;=:@`)
  1225  
  1226  // noNeedToEncode is a bitmap of characters which don't need % encoding
  1227  var noNeedToEncode [256]bool
  1228  
  1229  func init() {
  1230  	for _, c := range dontEncode {
  1231  		noNeedToEncode[c] = true
  1232  	}
  1233  }
  1234  
  1235  // readSeeker joins an io.Reader and an io.Seeker
  1236  type readSeeker struct {
  1237  	io.Reader
  1238  	io.Seeker
  1239  }
  1240  
  1241  // uploadMultipart uploads a file using multipart upload
  1242  //
  1243  // Write a larger blob, using CreateBlockBlob, PutBlock, and PutBlockList.
  1244  func (o *Object) uploadMultipart(in io.Reader, size int64, blob *azblob.BlobURL, httpHeaders *azblob.BlobHTTPHeaders) (err error) {
  1245  	// Calculate correct chunkSize
  1246  	chunkSize := int64(o.fs.opt.ChunkSize)
  1247  	var totalParts int64
  1248  	for {
  1249  		// Calculate number of parts
  1250  		var remainder int64
  1251  		totalParts, remainder = size/chunkSize, size%chunkSize
  1252  		if remainder != 0 {
  1253  			totalParts++
  1254  		}
  1255  		if totalParts < maxTotalParts {
  1256  			break
  1257  		}
  1258  		// Double chunk size if the number of parts is too big
  1259  		chunkSize *= 2
  1260  		if chunkSize > int64(maxChunkSize) {
  1261  			return errors.Errorf("can't upload as it is too big %v - takes more than %d chunks of %v", fs.SizeSuffix(size), totalParts, fs.SizeSuffix(chunkSize/2))
  1262  		}
  1263  	}
  1264  	fs.Debugf(o, "Multipart upload session started for %d parts of size %v", totalParts, fs.SizeSuffix(chunkSize))
  1265  
  1266  	// https://godoc.org/github.com/Azure/azure-storage-blob-go/2017-07-29/azblob#example-BlockBlobURL
  1267  	// Utilities are cloned from above example
  1268  	// These helper functions convert a binary block ID to a base-64 string and vice versa
  1269  	// NOTE: The blockID must be <= 64 bytes and ALL blockIDs for the block must be the same length
  1270  	blockIDBinaryToBase64 := func(blockID []byte) string { return base64.StdEncoding.EncodeToString(blockID) }
  1271  	// These helper functions convert an int block ID to a base-64 string and vice versa
  1272  	blockIDIntToBase64 := func(blockID uint64) string {
  1273  		binaryBlockID := (&[8]byte{})[:] // All block IDs are 8 bytes long
  1274  		binary.LittleEndian.PutUint64(binaryBlockID, blockID)
  1275  		return blockIDBinaryToBase64(binaryBlockID)
  1276  	}
  1277  
  1278  	// block ID variables
  1279  	var (
  1280  		rawID   uint64
  1281  		blockID = "" // id in base64 encoded form
  1282  		blocks  []string
  1283  	)
  1284  
  1285  	// increment the blockID
  1286  	nextID := func() {
  1287  		rawID++
  1288  		blockID = blockIDIntToBase64(rawID)
  1289  		blocks = append(blocks, blockID)
  1290  	}
  1291  
  1292  	// Get BlockBlobURL, we will use default pipeline here
  1293  	blockBlobURL := blob.ToBlockBlobURL()
  1294  	ctx := context.Background()
  1295  	ac := azblob.LeaseAccessConditions{} // Use default lease access conditions
  1296  
  1297  	// unwrap the accounting from the input, we use wrap to put it
  1298  	// back on after the buffering
  1299  	in, wrap := accounting.UnWrap(in)
  1300  
  1301  	// Upload the chunks
  1302  	remaining := size
  1303  	position := int64(0)
  1304  	errs := make(chan error, 1)
  1305  	var wg sync.WaitGroup
  1306  outer:
  1307  	for part := 0; part < int(totalParts); part++ {
  1308  		// Check any errors
  1309  		select {
  1310  		case err = <-errs:
  1311  			break outer
  1312  		default:
  1313  		}
  1314  
  1315  		reqSize := remaining
  1316  		if reqSize >= chunkSize {
  1317  			reqSize = chunkSize
  1318  		}
  1319  
  1320  		// Make a block of memory
  1321  		buf := make([]byte, reqSize)
  1322  
  1323  		// Read the chunk
  1324  		_, err = io.ReadFull(in, buf)
  1325  		if err != nil {
  1326  			err = errors.Wrap(err, "multipart upload failed to read source")
  1327  			break outer
  1328  		}
  1329  
  1330  		// Transfer the chunk
  1331  		nextID()
  1332  		wg.Add(1)
  1333  		o.fs.uploadToken.Get()
  1334  		go func(part int, position int64, blockID string) {
  1335  			defer wg.Done()
  1336  			defer o.fs.uploadToken.Put()
  1337  			fs.Debugf(o, "Uploading part %d/%d offset %v/%v part size %v", part+1, totalParts, fs.SizeSuffix(position), fs.SizeSuffix(size), fs.SizeSuffix(chunkSize))
  1338  
  1339  			// Upload the block, with MD5 for check
  1340  			md5sum := md5.Sum(buf)
  1341  			transactionalMD5 := md5sum[:]
  1342  			err = o.fs.pacer.Call(func() (bool, error) {
  1343  				bufferReader := bytes.NewReader(buf)
  1344  				wrappedReader := wrap(bufferReader)
  1345  				rs := readSeeker{wrappedReader, bufferReader}
  1346  				_, err = blockBlobURL.StageBlock(ctx, blockID, &rs, ac, transactionalMD5)
  1347  				return o.fs.shouldRetry(err)
  1348  			})
  1349  
  1350  			if err != nil {
  1351  				err = errors.Wrap(err, "multipart upload failed to upload part")
  1352  				select {
  1353  				case errs <- err:
  1354  				default:
  1355  				}
  1356  				return
  1357  			}
  1358  		}(part, position, blockID)
  1359  
  1360  		// ready for next block
  1361  		remaining -= chunkSize
  1362  		position += chunkSize
  1363  	}
  1364  	wg.Wait()
  1365  	if err == nil {
  1366  		select {
  1367  		case err = <-errs:
  1368  		default:
  1369  		}
  1370  	}
  1371  	if err != nil {
  1372  		return err
  1373  	}
  1374  
  1375  	// Finalise the upload session
  1376  	err = o.fs.pacer.Call(func() (bool, error) {
  1377  		_, err := blockBlobURL.CommitBlockList(ctx, blocks, *httpHeaders, o.meta, azblob.BlobAccessConditions{})
  1378  		return o.fs.shouldRetry(err)
  1379  	})
  1380  	if err != nil {
  1381  		return errors.Wrap(err, "multipart upload failed to finalize")
  1382  	}
  1383  	return nil
  1384  }
  1385  
  1386  // Update the object with the contents of the io.Reader, modTime and size
  1387  //
  1388  // The new object may have been created if an error is returned
  1389  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
  1390  	container, _ := o.split()
  1391  	err = o.fs.makeContainer(ctx, container)
  1392  	if err != nil {
  1393  		return err
  1394  	}
  1395  	size := src.Size()
  1396  	// Update Mod time
  1397  	o.updateMetadataWithModTime(src.ModTime(ctx))
  1398  	if err != nil {
  1399  		return err
  1400  	}
  1401  
  1402  	blob := o.getBlobReference()
  1403  	httpHeaders := azblob.BlobHTTPHeaders{}
  1404  	httpHeaders.ContentType = fs.MimeType(ctx, o)
  1405  	// Compute the Content-MD5 of the file, for multiparts uploads it
  1406  	// will be set in PutBlockList API call using the 'x-ms-blob-content-md5' header
  1407  	// Note: If multipart, a MD5 checksum will also be computed for each uploaded block
  1408  	// 		 in order to validate its integrity during transport
  1409  	if sourceMD5, _ := src.Hash(ctx, hash.MD5); sourceMD5 != "" {
  1410  		sourceMD5bytes, err := hex.DecodeString(sourceMD5)
  1411  		if err == nil {
  1412  			httpHeaders.ContentMD5 = sourceMD5bytes
  1413  		} else {
  1414  			fs.Debugf(o, "Failed to decode %q as MD5: %v", sourceMD5, err)
  1415  		}
  1416  	}
  1417  
  1418  	putBlobOptions := azblob.UploadStreamToBlockBlobOptions{
  1419  		BufferSize:      int(o.fs.opt.ChunkSize),
  1420  		MaxBuffers:      4,
  1421  		Metadata:        o.meta,
  1422  		BlobHTTPHeaders: httpHeaders,
  1423  	}
  1424  	// FIXME Until https://github.com/Azure/azure-storage-blob-go/pull/75
  1425  	// is merged the SDK can't upload a single blob of exactly the chunk
  1426  	// size, so upload with a multpart upload to work around.
  1427  	// See: https://github.com/rclone/rclone/issues/2653
  1428  	multipartUpload := size >= int64(o.fs.opt.UploadCutoff)
  1429  	if size == int64(o.fs.opt.ChunkSize) {
  1430  		multipartUpload = true
  1431  		fs.Debugf(o, "Setting multipart upload for file of chunk size (%d) to work around SDK bug", size)
  1432  	}
  1433  
  1434  	// Don't retry, return a retry error instead
  1435  	err = o.fs.pacer.CallNoRetry(func() (bool, error) {
  1436  		if multipartUpload {
  1437  			// If a large file upload in chunks
  1438  			err = o.uploadMultipart(in, size, &blob, &httpHeaders)
  1439  		} else {
  1440  			// Write a small blob in one transaction
  1441  			blockBlobURL := blob.ToBlockBlobURL()
  1442  			_, err = azblob.UploadStreamToBlockBlob(ctx, in, blockBlobURL, putBlobOptions)
  1443  		}
  1444  		return o.fs.shouldRetry(err)
  1445  	})
  1446  	if err != nil {
  1447  		return err
  1448  	}
  1449  	// Refresh metadata on object
  1450  	o.clearMetaData()
  1451  	err = o.readMetaData()
  1452  	if err != nil {
  1453  		return err
  1454  	}
  1455  
  1456  	// If tier is not changed or not specified, do not attempt to invoke `SetBlobTier` operation
  1457  	if o.fs.opt.AccessTier == string(defaultAccessTier) || o.fs.opt.AccessTier == string(o.AccessTier()) {
  1458  		return nil
  1459  	}
  1460  
  1461  	// Now, set blob tier based on configured access tier
  1462  	return o.SetTier(o.fs.opt.AccessTier)
  1463  }
  1464  
  1465  // Remove an object
  1466  func (o *Object) Remove(ctx context.Context) error {
  1467  	blob := o.getBlobReference()
  1468  	snapShotOptions := azblob.DeleteSnapshotsOptionNone
  1469  	ac := azblob.BlobAccessConditions{}
  1470  	return o.fs.pacer.Call(func() (bool, error) {
  1471  		_, err := blob.Delete(ctx, snapShotOptions, ac)
  1472  		return o.fs.shouldRetry(err)
  1473  	})
  1474  }
  1475  
  1476  // MimeType of an Object if known, "" otherwise
  1477  func (o *Object) MimeType(ctx context.Context) string {
  1478  	return o.mimeType
  1479  }
  1480  
  1481  // AccessTier of an object, default is of type none
  1482  func (o *Object) AccessTier() azblob.AccessTierType {
  1483  	return o.accessTier
  1484  }
  1485  
  1486  // SetTier performs changing object tier
  1487  func (o *Object) SetTier(tier string) error {
  1488  	if !validateAccessTier(tier) {
  1489  		return errors.Errorf("Tier %s not supported by Azure Blob Storage", tier)
  1490  	}
  1491  
  1492  	// Check if current tier already matches with desired tier
  1493  	if o.GetTier() == tier {
  1494  		return nil
  1495  	}
  1496  	desiredAccessTier := azblob.AccessTierType(tier)
  1497  	blob := o.getBlobReference()
  1498  	ctx := context.Background()
  1499  	err := o.fs.pacer.Call(func() (bool, error) {
  1500  		_, err := blob.SetTier(ctx, desiredAccessTier, azblob.LeaseAccessConditions{})
  1501  		return o.fs.shouldRetry(err)
  1502  	})
  1503  
  1504  	if err != nil {
  1505  		return errors.Wrap(err, "Failed to set Blob Tier")
  1506  	}
  1507  
  1508  	// Set access tier on local object also, this typically
  1509  	// gets updated on get blob properties
  1510  	o.accessTier = desiredAccessTier
  1511  	fs.Debugf(o, "Successfully changed object tier to %s", tier)
  1512  
  1513  	return nil
  1514  }
  1515  
  1516  // GetTier returns object tier in azure as string
  1517  func (o *Object) GetTier() string {
  1518  	return string(o.accessTier)
  1519  }
  1520  
  1521  // Check the interfaces are satisfied
  1522  var (
  1523  	_ fs.Fs        = &Fs{}
  1524  	_ fs.Copier    = &Fs{}
  1525  	_ fs.Purger    = &Fs{}
  1526  	_ fs.ListRer   = &Fs{}
  1527  	_ fs.Object    = &Object{}
  1528  	_ fs.MimeTyper = &Object{}
  1529  	_ fs.GetTierer = &Object{}
  1530  	_ fs.SetTierer = &Object{}
  1531  )