storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/utils.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2015-2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto/tls"
    23  	"encoding/json"
    24  	"encoding/xml"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"io/ioutil"
    29  	"net"
    30  	"net/http"
    31  	"net/url"
    32  	"os"
    33  	"path"
    34  	"path/filepath"
    35  	"reflect"
    36  	"runtime"
    37  	"runtime/pprof"
    38  	"runtime/trace"
    39  	"strings"
    40  	"sync"
    41  	"time"
    42  
    43  	humanize "github.com/dustin/go-humanize"
    44  	"github.com/gorilla/mux"
    45  	"golang.org/x/net/http2"
    46  
    47  	xhttp "storj.io/minio/cmd/http"
    48  	"storj.io/minio/cmd/logger"
    49  	"storj.io/minio/cmd/rest"
    50  	"storj.io/minio/pkg/certs"
    51  	"storj.io/minio/pkg/handlers"
    52  	"storj.io/minio/pkg/madmin"
    53  )
    54  
    55  const (
    56  	slashSeparator = "/"
    57  )
    58  
    59  // IsErrIgnored returns whether given error is ignored or not.
    60  func IsErrIgnored(err error, ignoredErrs ...error) bool {
    61  	return IsErr(err, ignoredErrs...)
    62  }
    63  
    64  // IsErr returns whether given error is exact error.
    65  func IsErr(err error, errs ...error) bool {
    66  	for _, exactErr := range errs {
    67  		if errors.Is(err, exactErr) {
    68  			return true
    69  		}
    70  	}
    71  	return false
    72  }
    73  
    74  func request2BucketObjectName(r *http.Request) (bucketName, objectName string) {
    75  	path, err := getResource(r.URL.Path, r.Host, globalDomainNames)
    76  	if err != nil {
    77  		logger.CriticalIf(GlobalContext, err)
    78  	}
    79  
    80  	return path2BucketObject(path)
    81  }
    82  
    83  // path2BucketObjectWithBasePath returns bucket and prefix, if any,
    84  // of a 'path'. basePath is trimmed from the front of the 'path'.
    85  func path2BucketObjectWithBasePath(basePath, path string) (bucket, prefix string) {
    86  	path = strings.TrimPrefix(path, basePath)
    87  	path = strings.TrimPrefix(path, SlashSeparator)
    88  	m := strings.Index(path, SlashSeparator)
    89  	if m < 0 {
    90  		return path, ""
    91  	}
    92  	return path[:m], path[m+len(SlashSeparator):]
    93  }
    94  
    95  func path2BucketObject(s string) (bucket, prefix string) {
    96  	return path2BucketObjectWithBasePath("", s)
    97  }
    98  
    99  func getReadQuorum(drive int) int {
   100  	return drive - getDefaultParityBlocks(drive)
   101  }
   102  
   103  func getWriteQuorum(drive int) int {
   104  	parity := getDefaultParityBlocks(drive)
   105  	quorum := drive - parity
   106  	if quorum == parity {
   107  		quorum++
   108  	}
   109  	return quorum
   110  }
   111  
   112  // cloneMSS will clone a map[string]string.
   113  // If input is nil an empty map is returned, not nil.
   114  func cloneMSS(v map[string]string) map[string]string {
   115  	r := make(map[string]string, len(v))
   116  	for k, v := range v {
   117  		r[k] = v
   118  	}
   119  	return r
   120  }
   121  
   122  // URI scheme constants.
   123  const (
   124  	httpScheme  = "http"
   125  	httpsScheme = "https"
   126  )
   127  
   128  // nopCharsetConverter is a dummy charset convert which just copies input to output,
   129  // it is used to ignore custom encoding charset in S3 XML body.
   130  func nopCharsetConverter(label string, input io.Reader) (io.Reader, error) {
   131  	return input, nil
   132  }
   133  
   134  // xmlDecoder provide decoded value in xml.
   135  func xmlDecoder(body io.Reader, v interface{}, size int64) error {
   136  	var lbody io.Reader
   137  	if size > 0 {
   138  		lbody = io.LimitReader(body, size)
   139  	} else {
   140  		lbody = body
   141  	}
   142  	d := xml.NewDecoder(lbody)
   143  	// Ignore any encoding set in the XML body
   144  	d.CharsetReader = nopCharsetConverter
   145  	return d.Decode(v)
   146  }
   147  
   148  // hasContentMD5 returns true if Content-MD5 header is set.
   149  func hasContentMD5(h http.Header) bool {
   150  	_, ok := h[xhttp.ContentMD5]
   151  	return ok
   152  }
   153  
   154  /// http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html
   155  const (
   156  	// Maximum object size per PUT request is 5TB.
   157  	// This is a divergence from S3 limit on purpose to support
   158  	// use cases where users are going to upload large files
   159  	// using 'curl' and presigned URL.
   160  	globalMaxObjectSize = 5 * humanize.TiByte
   161  
   162  	// Minimum Part size for multipart upload is 5MiB
   163  	globalMinPartSize = 5 * humanize.MiByte
   164  
   165  	// Maximum Part size for multipart upload is 5GiB
   166  	globalMaxPartSize = 5 * humanize.GiByte
   167  
   168  	// Maximum Part ID for multipart upload is 10000
   169  	// (Acceptable values range from 1 to 10000 inclusive)
   170  	globalMaxPartID = 10000
   171  
   172  	// Default values used while communicating for gateway communication
   173  	defaultDialTimeout = 5 * time.Second
   174  )
   175  
   176  // isMaxObjectSize - verify if max object size
   177  func isMaxObjectSize(size int64) bool {
   178  	return size > globalMaxObjectSize
   179  }
   180  
   181  // // Check if part size is more than maximum allowed size.
   182  func isMaxAllowedPartSize(size int64) bool {
   183  	return size > globalMaxPartSize
   184  }
   185  
   186  // Check if part size is more than or equal to minimum allowed size.
   187  func isMinAllowedPartSize(size int64) bool {
   188  	return size >= globalMinPartSize
   189  }
   190  
   191  // isMaxPartNumber - Check if part ID is greater than the maximum allowed ID.
   192  func isMaxPartID(partID int) bool {
   193  	return partID > globalMaxPartID
   194  }
   195  
   196  func contains(slice interface{}, elem interface{}) bool {
   197  	v := reflect.ValueOf(slice)
   198  	if v.Kind() == reflect.Slice {
   199  		for i := 0; i < v.Len(); i++ {
   200  			if v.Index(i).Interface() == elem {
   201  				return true
   202  			}
   203  		}
   204  	}
   205  	return false
   206  }
   207  
   208  // profilerWrapper is created becauses pkg/profiler doesn't
   209  // provide any API to calculate the profiler file path in the
   210  // disk since the name of this latter is randomly generated.
   211  type profilerWrapper struct {
   212  	// Profile recorded at start of benchmark.
   213  	base   []byte
   214  	stopFn func() ([]byte, error)
   215  	ext    string
   216  }
   217  
   218  // recordBase will record the profile and store it as the base.
   219  func (p *profilerWrapper) recordBase(name string, debug int) {
   220  	var buf bytes.Buffer
   221  	p.base = nil
   222  	err := pprof.Lookup(name).WriteTo(&buf, debug)
   223  	if err != nil {
   224  		return
   225  	}
   226  	p.base = buf.Bytes()
   227  }
   228  
   229  // Base returns the recorded base if any.
   230  func (p profilerWrapper) Base() []byte {
   231  	return p.base
   232  }
   233  
   234  // Stop the currently running benchmark.
   235  func (p profilerWrapper) Stop() ([]byte, error) {
   236  	return p.stopFn()
   237  }
   238  
   239  // Extension returns the extension without dot prefix.
   240  func (p profilerWrapper) Extension() string {
   241  	return p.ext
   242  }
   243  
   244  // Returns current profile data, returns error if there is no active
   245  // profiling in progress. Stops an active profile.
   246  func getProfileData() (map[string][]byte, error) {
   247  	globalProfilerMu.Lock()
   248  	defer globalProfilerMu.Unlock()
   249  
   250  	if len(globalProfiler) == 0 {
   251  		return nil, errors.New("profiler not enabled")
   252  	}
   253  
   254  	dst := make(map[string][]byte, len(globalProfiler))
   255  	for typ, prof := range globalProfiler {
   256  		// Stop the profiler
   257  		var err error
   258  		buf, err := prof.Stop()
   259  		delete(globalProfiler, typ)
   260  		if err == nil {
   261  			dst[typ+"."+prof.Extension()] = buf
   262  		}
   263  		buf = prof.Base()
   264  		if len(buf) > 0 {
   265  			dst[typ+"-before"+"."+prof.Extension()] = buf
   266  		}
   267  	}
   268  	return dst, nil
   269  }
   270  
   271  func setDefaultProfilerRates() {
   272  	runtime.MemProfileRate = 4096      // 512K -> 4K - Must be constant throughout application lifetime.
   273  	runtime.SetMutexProfileFraction(0) // Disable until needed
   274  	runtime.SetBlockProfileRate(0)     // Disable until needed
   275  }
   276  
   277  // Starts a profiler returns nil if profiler is not enabled, caller needs to handle this.
   278  func startProfiler(profilerType string) (minioProfiler, error) {
   279  	var prof profilerWrapper
   280  	prof.ext = "pprof"
   281  	// Enable profiler and set the name of the file that pkg/pprof
   282  	// library creates to store profiling data.
   283  	switch madmin.ProfilerType(profilerType) {
   284  	case madmin.ProfilerCPU:
   285  		dirPath, err := ioutil.TempDir("", "profile")
   286  		if err != nil {
   287  			return nil, err
   288  		}
   289  		fn := filepath.Join(dirPath, "cpu.out")
   290  		f, err := os.Create(fn)
   291  		if err != nil {
   292  			return nil, err
   293  		}
   294  		err = pprof.StartCPUProfile(f)
   295  		if err != nil {
   296  			return nil, err
   297  		}
   298  		prof.stopFn = func() ([]byte, error) {
   299  			pprof.StopCPUProfile()
   300  			err := f.Close()
   301  			if err != nil {
   302  				return nil, err
   303  			}
   304  			defer os.RemoveAll(dirPath)
   305  			return ioutil.ReadFile(fn)
   306  		}
   307  	case madmin.ProfilerMEM:
   308  		runtime.GC()
   309  		prof.recordBase("heap", 0)
   310  		prof.stopFn = func() ([]byte, error) {
   311  			runtime.GC()
   312  			var buf bytes.Buffer
   313  			err := pprof.Lookup("heap").WriteTo(&buf, 0)
   314  			return buf.Bytes(), err
   315  		}
   316  	case madmin.ProfilerBlock:
   317  		runtime.SetBlockProfileRate(100)
   318  		prof.stopFn = func() ([]byte, error) {
   319  			var buf bytes.Buffer
   320  			err := pprof.Lookup("block").WriteTo(&buf, 0)
   321  			runtime.SetBlockProfileRate(0)
   322  			return buf.Bytes(), err
   323  		}
   324  	case madmin.ProfilerMutex:
   325  		prof.recordBase("mutex", 0)
   326  		runtime.SetMutexProfileFraction(1)
   327  		prof.stopFn = func() ([]byte, error) {
   328  			var buf bytes.Buffer
   329  			err := pprof.Lookup("mutex").WriteTo(&buf, 0)
   330  			runtime.SetMutexProfileFraction(0)
   331  			return buf.Bytes(), err
   332  		}
   333  	case madmin.ProfilerThreads:
   334  		prof.recordBase("threadcreate", 0)
   335  		prof.stopFn = func() ([]byte, error) {
   336  			var buf bytes.Buffer
   337  			err := pprof.Lookup("threadcreate").WriteTo(&buf, 0)
   338  			return buf.Bytes(), err
   339  		}
   340  	case madmin.ProfilerGoroutines:
   341  		prof.ext = "txt"
   342  		prof.recordBase("goroutine", 1)
   343  		prof.stopFn = func() ([]byte, error) {
   344  			var buf bytes.Buffer
   345  			err := pprof.Lookup("goroutine").WriteTo(&buf, 1)
   346  			return buf.Bytes(), err
   347  		}
   348  	case madmin.ProfilerTrace:
   349  		dirPath, err := ioutil.TempDir("", "profile")
   350  		if err != nil {
   351  			return nil, err
   352  		}
   353  		fn := filepath.Join(dirPath, "trace.out")
   354  		f, err := os.Create(fn)
   355  		if err != nil {
   356  			return nil, err
   357  		}
   358  		err = trace.Start(f)
   359  		if err != nil {
   360  			return nil, err
   361  		}
   362  		prof.ext = "trace"
   363  		prof.stopFn = func() ([]byte, error) {
   364  			trace.Stop()
   365  			err := f.Close()
   366  			if err != nil {
   367  				return nil, err
   368  			}
   369  			defer os.RemoveAll(dirPath)
   370  			return ioutil.ReadFile(fn)
   371  		}
   372  	default:
   373  		return nil, errors.New("profiler type unknown")
   374  	}
   375  
   376  	return prof, nil
   377  }
   378  
   379  // minioProfiler - minio profiler interface.
   380  type minioProfiler interface {
   381  	// Return base profile. 'nil' if none.
   382  	Base() []byte
   383  	// Stop the profiler
   384  	Stop() ([]byte, error)
   385  	// Return extension of profile
   386  	Extension() string
   387  }
   388  
   389  // Global profiler to be used by service go-routine.
   390  var globalProfiler map[string]minioProfiler
   391  var globalProfilerMu sync.Mutex
   392  
   393  // dump the request into a string in JSON format.
   394  func dumpRequest(r *http.Request) string {
   395  	header := r.Header.Clone()
   396  	header.Set("Host", r.Host)
   397  	// Replace all '%' to '%%' so that printer format parser
   398  	// to ignore URL encoded values.
   399  	rawURI := strings.Replace(r.RequestURI, "%", "%%", -1)
   400  	req := struct {
   401  		Method     string      `json:"method"`
   402  		RequestURI string      `json:"reqURI"`
   403  		Header     http.Header `json:"header"`
   404  	}{r.Method, rawURI, header}
   405  
   406  	var buffer bytes.Buffer
   407  	enc := json.NewEncoder(&buffer)
   408  	enc.SetEscapeHTML(false)
   409  	if err := enc.Encode(&req); err != nil {
   410  		// Upon error just return Go-syntax representation of the value
   411  		return fmt.Sprintf("%#v", req)
   412  	}
   413  
   414  	// Formatted string.
   415  	return strings.TrimSpace(buffer.String())
   416  }
   417  
   418  // isFile - returns whether given path is a file or not.
   419  func isFile(path string) bool {
   420  	if fi, err := os.Stat(path); err == nil {
   421  		return fi.Mode().IsRegular()
   422  	}
   423  
   424  	return false
   425  }
   426  
   427  // UTCNow - returns current UTC time.
   428  func UTCNow() time.Time {
   429  	return time.Now().UTC()
   430  }
   431  
   432  // GenETag - generate UUID based ETag
   433  func GenETag() string {
   434  	return ToS3ETag(getMD5Hash([]byte(mustGetUUID())))
   435  }
   436  
   437  // ToS3ETag - return checksum to ETag
   438  func ToS3ETag(etag string) string {
   439  	etag = canonicalizeETag(etag)
   440  
   441  	if !strings.HasSuffix(etag, "-1") {
   442  		// Tools like s3cmd uses ETag as checksum of data to validate.
   443  		// Append "-1" to indicate ETag is not a checksum.
   444  		etag += "-1"
   445  	}
   446  
   447  	return etag
   448  }
   449  
   450  func newInternodeHTTPTransport(tlsConfig *tls.Config, dialTimeout time.Duration) func() http.RoundTripper {
   451  	// For more details about various values used here refer
   452  	// https://golang.org/pkg/net/http/#Transport documentation
   453  	tr := &http.Transport{
   454  		Proxy:                 http.ProxyFromEnvironment,
   455  		DialContext:           xhttp.DialContextWithDNSCache(globalDNSCache, xhttp.NewInternodeDialContext(dialTimeout)),
   456  		MaxIdleConnsPerHost:   1024,
   457  		WriteBufferSize:       32 << 10, // 32KiB moving up from 4KiB default
   458  		ReadBufferSize:        32 << 10, // 32KiB moving up from 4KiB default
   459  		IdleConnTimeout:       15 * time.Second,
   460  		ResponseHeaderTimeout: 15 * time.Minute, // Set conservative timeouts for MinIO internode.
   461  		TLSHandshakeTimeout:   15 * time.Second,
   462  		ExpectContinueTimeout: 15 * time.Second,
   463  		TLSClientConfig:       tlsConfig,
   464  		// Go net/http automatically unzip if content-type is
   465  		// gzip disable this feature, as we are always interested
   466  		// in raw stream.
   467  		DisableCompression: true,
   468  	}
   469  
   470  	// https://github.com/golang/go/issues/23559
   471  	// https://github.com/golang/go/issues/42534
   472  	// https://github.com/golang/go/issues/43989
   473  	// https://github.com/golang/go/issues/33425
   474  	// https://github.com/golang/go/issues/29246
   475  	// if tlsConfig != nil {
   476  	// 	trhttp2, _ := http2.ConfigureTransports(tr)
   477  	// 	if trhttp2 != nil {
   478  	// 		// ReadIdleTimeout is the timeout after which a health check using ping
   479  	// 		// frame will be carried out if no frame is received on the
   480  	// 		// connection. 5 minutes is sufficient time for any idle connection.
   481  	// 		trhttp2.ReadIdleTimeout = 5 * time.Minute
   482  	// 		// PingTimeout is the timeout after which the connection will be closed
   483  	// 		// if a response to Ping is not received.
   484  	// 		trhttp2.PingTimeout = dialTimeout
   485  	// 		// DisableCompression, if true, prevents the Transport from
   486  	// 		// requesting compression with an "Accept-Encoding: gzip"
   487  	// 		trhttp2.DisableCompression = true
   488  	// 	}
   489  	// }
   490  
   491  	return func() http.RoundTripper {
   492  		return tr
   493  	}
   494  }
   495  
   496  // Used by only proxied requests, specifically only supports HTTP/1.1
   497  func newCustomHTTPProxyTransport(tlsConfig *tls.Config, dialTimeout time.Duration) func() *http.Transport {
   498  	// For more details about various values used here refer
   499  	// https://golang.org/pkg/net/http/#Transport documentation
   500  	tr := &http.Transport{
   501  		Proxy:                 http.ProxyFromEnvironment,
   502  		DialContext:           xhttp.DialContextWithDNSCache(globalDNSCache, xhttp.NewInternodeDialContext(dialTimeout)),
   503  		MaxIdleConnsPerHost:   1024,
   504  		WriteBufferSize:       16 << 10, // 16KiB moving up from 4KiB default
   505  		ReadBufferSize:        16 << 10, // 16KiB moving up from 4KiB default
   506  		IdleConnTimeout:       15 * time.Second,
   507  		ResponseHeaderTimeout: 30 * time.Minute, // Set larger timeouts for proxied requests.
   508  		TLSHandshakeTimeout:   10 * time.Second,
   509  		ExpectContinueTimeout: 10 * time.Second,
   510  		TLSClientConfig:       tlsConfig,
   511  		// Go net/http automatically unzip if content-type is
   512  		// gzip disable this feature, as we are always interested
   513  		// in raw stream.
   514  		DisableCompression: true,
   515  	}
   516  
   517  	return func() *http.Transport {
   518  		return tr
   519  	}
   520  }
   521  
   522  func newCustomHTTPTransportWithHTTP2(tlsConfig *tls.Config, dialTimeout time.Duration) func() *http.Transport {
   523  	// For more details about various values used here refer
   524  	// https://golang.org/pkg/net/http/#Transport documentation
   525  	tr := &http.Transport{
   526  		Proxy:                 http.ProxyFromEnvironment,
   527  		DialContext:           xhttp.DialContextWithDNSCache(globalDNSCache, xhttp.NewInternodeDialContext(dialTimeout)),
   528  		MaxIdleConnsPerHost:   1024,
   529  		IdleConnTimeout:       15 * time.Second,
   530  		ResponseHeaderTimeout: 1 * time.Minute,
   531  		TLSHandshakeTimeout:   10 * time.Second,
   532  		ExpectContinueTimeout: 10 * time.Second,
   533  		TLSClientConfig:       tlsConfig,
   534  		// Go net/http automatically unzip if content-type is
   535  		// gzip disable this feature, as we are always interested
   536  		// in raw stream.
   537  		DisableCompression: true,
   538  	}
   539  
   540  	if tlsConfig != nil {
   541  		trhttp2, _ := http2.ConfigureTransports(tr)
   542  		if trhttp2 != nil {
   543  			// ReadIdleTimeout is the timeout after which a health check using ping
   544  			// frame will be carried out if no frame is received on the
   545  			// connection. 5 minutes is sufficient time for any idle connection.
   546  			trhttp2.ReadIdleTimeout = 5 * time.Minute
   547  			// PingTimeout is the timeout after which the connection will be closed
   548  			// if a response to Ping is not received.
   549  			trhttp2.PingTimeout = dialTimeout
   550  			// DisableCompression, if true, prevents the Transport from
   551  			// requesting compression with an "Accept-Encoding: gzip"
   552  			trhttp2.DisableCompression = true
   553  		}
   554  	}
   555  
   556  	return func() *http.Transport {
   557  		return tr
   558  	}
   559  }
   560  
   561  func newCustomHTTPTransport(tlsConfig *tls.Config, dialTimeout time.Duration) func() *http.Transport {
   562  	// For more details about various values used here refer
   563  	// https://golang.org/pkg/net/http/#Transport documentation
   564  	tr := &http.Transport{
   565  		Proxy:                 http.ProxyFromEnvironment,
   566  		DialContext:           xhttp.DialContextWithDNSCache(globalDNSCache, xhttp.NewInternodeDialContext(dialTimeout)),
   567  		MaxIdleConnsPerHost:   1024,
   568  		WriteBufferSize:       16 << 10, // 16KiB moving up from 4KiB default
   569  		ReadBufferSize:        16 << 10, // 16KiB moving up from 4KiB default
   570  		IdleConnTimeout:       15 * time.Second,
   571  		ResponseHeaderTimeout: 3 * time.Minute, // Set conservative timeouts for MinIO internode.
   572  		TLSHandshakeTimeout:   10 * time.Second,
   573  		ExpectContinueTimeout: 10 * time.Second,
   574  		TLSClientConfig:       tlsConfig,
   575  		// Go net/http automatically unzip if content-type is
   576  		// gzip disable this feature, as we are always interested
   577  		// in raw stream.
   578  		DisableCompression: true,
   579  	}
   580  
   581  	// https://github.com/golang/go/issues/23559
   582  	// https://github.com/golang/go/issues/42534
   583  	// https://github.com/golang/go/issues/43989
   584  	// https://github.com/golang/go/issues/33425
   585  	// https://github.com/golang/go/issues/29246
   586  	// if tlsConfig != nil {
   587  	// 	trhttp2, _ := http2.ConfigureTransports(tr)
   588  	// 	if trhttp2 != nil {
   589  	// 		// ReadIdleTimeout is the timeout after which a health check using ping
   590  	// 		// frame will be carried out if no frame is received on the
   591  	// 		// connection. 5 minutes is sufficient time for any idle connection.
   592  	// 		trhttp2.ReadIdleTimeout = 5 * time.Minute
   593  	// 		// PingTimeout is the timeout after which the connection will be closed
   594  	// 		// if a response to Ping is not received.
   595  	// 		trhttp2.PingTimeout = dialTimeout
   596  	// 		// DisableCompression, if true, prevents the Transport from
   597  	// 		// requesting compression with an "Accept-Encoding: gzip"
   598  	// 		trhttp2.DisableCompression = true
   599  	// 	}
   600  	// }
   601  
   602  	return func() *http.Transport {
   603  		return tr
   604  	}
   605  }
   606  
   607  // NewGatewayHTTPTransportWithClientCerts returns a new http configuration
   608  // used while communicating with the cloud backends.
   609  func NewGatewayHTTPTransportWithClientCerts(clientCert, clientKey string) *http.Transport {
   610  	transport := newGatewayHTTPTransport(1 * time.Minute)
   611  	if clientCert != "" && clientKey != "" {
   612  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   613  		defer cancel()
   614  		c, err := certs.NewManager(ctx, clientCert, clientKey, tls.LoadX509KeyPair)
   615  		if err != nil {
   616  			logger.LogIf(ctx, fmt.Errorf("failed to load client key and cert, please check your endpoint configuration: %s",
   617  				err.Error()))
   618  		}
   619  		if c != nil {
   620  			transport.TLSClientConfig.GetClientCertificate = c.GetClientCertificate
   621  		}
   622  	}
   623  	return transport
   624  }
   625  
   626  // NewGatewayHTTPTransport returns a new http configuration
   627  // used while communicating with the cloud backends.
   628  func NewGatewayHTTPTransport() *http.Transport {
   629  	return newGatewayHTTPTransport(1 * time.Minute)
   630  }
   631  
   632  func newGatewayHTTPTransport(timeout time.Duration) *http.Transport {
   633  	tr := newCustomHTTPTransport(&tls.Config{
   634  		RootCAs: globalRootCAs,
   635  	}, defaultDialTimeout)()
   636  
   637  	// Customize response header timeout for gateway transport.
   638  	tr.ResponseHeaderTimeout = timeout
   639  	return tr
   640  }
   641  
   642  // NewRemoteTargetHTTPTransport returns a new http configuration
   643  // used while communicating with the remote replication targets.
   644  func NewRemoteTargetHTTPTransport() *http.Transport {
   645  	// For more details about various values used here refer
   646  	// https://golang.org/pkg/net/http/#Transport documentation
   647  	tr := &http.Transport{
   648  		Proxy: http.ProxyFromEnvironment,
   649  		DialContext: (&net.Dialer{
   650  			Timeout:   15 * time.Second,
   651  			KeepAlive: 30 * time.Second,
   652  		}).DialContext,
   653  		MaxIdleConnsPerHost:   1024,
   654  		WriteBufferSize:       16 << 10, // 16KiB moving up from 4KiB default
   655  		ReadBufferSize:        16 << 10, // 16KiB moving up from 4KiB default
   656  		IdleConnTimeout:       15 * time.Second,
   657  		TLSHandshakeTimeout:   5 * time.Second,
   658  		ExpectContinueTimeout: 5 * time.Second,
   659  		TLSClientConfig: &tls.Config{
   660  			RootCAs: globalRootCAs,
   661  		},
   662  		// Go net/http automatically unzip if content-type is
   663  		// gzip disable this feature, as we are always interested
   664  		// in raw stream.
   665  		DisableCompression: true,
   666  	}
   667  	return tr
   668  }
   669  
   670  // Load the json (typically from disk file).
   671  func jsonLoad(r io.ReadSeeker, data interface{}) error {
   672  	if _, err := r.Seek(0, io.SeekStart); err != nil {
   673  		return err
   674  	}
   675  	return json.NewDecoder(r).Decode(data)
   676  }
   677  
   678  // Save to disk file in json format.
   679  func jsonSave(f interface {
   680  	io.WriteSeeker
   681  	Truncate(int64) error
   682  }, data interface{}) error {
   683  	b, err := json.Marshal(data)
   684  	if err != nil {
   685  		return err
   686  	}
   687  	if err = f.Truncate(0); err != nil {
   688  		return err
   689  	}
   690  	if _, err = f.Seek(0, io.SeekStart); err != nil {
   691  		return err
   692  	}
   693  	_, err = f.Write(b)
   694  	if err != nil {
   695  		return err
   696  	}
   697  	return nil
   698  }
   699  
   700  // ceilFrac takes a numerator and denominator representing a fraction
   701  // and returns its ceiling. If denominator is 0, it returns 0 instead
   702  // of crashing.
   703  func ceilFrac(numerator, denominator int64) (ceil int64) {
   704  	if denominator == 0 {
   705  		// do nothing on invalid input
   706  		return
   707  	}
   708  	// Make denominator positive
   709  	if denominator < 0 {
   710  		numerator = -numerator
   711  		denominator = -denominator
   712  	}
   713  	ceil = numerator / denominator
   714  	if numerator > 0 && numerator%denominator != 0 {
   715  		ceil++
   716  	}
   717  	return
   718  }
   719  
   720  // pathClean is like path.Clean but does not return "." for
   721  // empty inputs, instead returns "empty" as is.
   722  func pathClean(p string) string {
   723  	cp := path.Clean(p)
   724  	if cp == "." {
   725  		return ""
   726  	}
   727  	return cp
   728  }
   729  
   730  func trimLeadingSlash(ep string) string {
   731  	if len(ep) > 0 && ep[0] == '/' {
   732  		// Path ends with '/' preserve it
   733  		if ep[len(ep)-1] == '/' && len(ep) > 1 {
   734  			ep = path.Clean(ep)
   735  			ep += slashSeparator
   736  		} else {
   737  			ep = path.Clean(ep)
   738  		}
   739  		ep = ep[1:]
   740  	}
   741  	return ep
   742  }
   743  
   744  // unescapeGeneric is similar to url.PathUnescape or url.QueryUnescape
   745  // depending on input, additionally also handles situations such as
   746  // `//` are normalized as `/`, also removes any `/` prefix before
   747  // returning.
   748  func unescapeGeneric(p string, escapeFn func(string) (string, error)) (string, error) {
   749  	ep, err := escapeFn(p)
   750  	if err != nil {
   751  		return "", err
   752  	}
   753  	return trimLeadingSlash(ep), nil
   754  }
   755  
   756  // unescapePath is similar to unescapeGeneric but for specifically
   757  // path unescaping.
   758  func unescapePath(p string) (string, error) {
   759  	return unescapeGeneric(p, url.PathUnescape)
   760  }
   761  
   762  // similar to unescapeGeneric but never returns any error if the unescaping
   763  // fails, returns the input as is in such occasion, not meant to be
   764  // used where strict validation is expected.
   765  func likelyUnescapeGeneric(p string, escapeFn func(string) (string, error)) string {
   766  	ep, err := unescapeGeneric(p, escapeFn)
   767  	if err != nil {
   768  		return p
   769  	}
   770  	return ep
   771  }
   772  
   773  // Returns context with ReqInfo details set in the context.
   774  func NewContext(r *http.Request, w http.ResponseWriter, api string) context.Context {
   775  	vars := mux.Vars(r)
   776  	bucket := vars["bucket"]
   777  	object := likelyUnescapeGeneric(vars["object"], url.PathUnescape)
   778  	prefix := likelyUnescapeGeneric(vars["prefix"], url.QueryUnescape)
   779  	if prefix != "" {
   780  		object = prefix
   781  	}
   782  	reqInfo := &logger.ReqInfo{
   783  		DeploymentID: globalDeploymentID,
   784  		RequestID:    w.Header().Get(xhttp.AmzRequestID),
   785  		RemoteHost:   handlers.GetSourceIP(r),
   786  		Host:         getHostName(r),
   787  		UserAgent:    r.UserAgent(),
   788  		API:          api,
   789  		BucketName:   bucket,
   790  		ObjectName:   object,
   791  	}
   792  	return logger.SetReqInfo(r.Context(), reqInfo)
   793  }
   794  
   795  // Used for registering with rest handlers (have a look at registerStorageRESTHandlers for usage example)
   796  // If it is passed ["aaaa", "bbbb"], it returns ["aaaa", "{aaaa:.*}", "bbbb", "{bbbb:.*}"]
   797  func restQueries(keys ...string) []string {
   798  	var accumulator []string
   799  	for _, key := range keys {
   800  		accumulator = append(accumulator, key, "{"+key+":.*}")
   801  	}
   802  	return accumulator
   803  }
   804  
   805  // Suffix returns the longest common suffix of the provided strings
   806  func lcpSuffix(strs []string) string {
   807  	return lcp(strs, false)
   808  }
   809  
   810  func lcp(strs []string, pre bool) string {
   811  	// short-circuit empty list
   812  	if len(strs) == 0 {
   813  		return ""
   814  	}
   815  	xfix := strs[0]
   816  	// short-circuit single-element list
   817  	if len(strs) == 1 {
   818  		return xfix
   819  	}
   820  	// compare first to rest
   821  	for _, str := range strs[1:] {
   822  		xfixl := len(xfix)
   823  		strl := len(str)
   824  		// short-circuit empty strings
   825  		if xfixl == 0 || strl == 0 {
   826  			return ""
   827  		}
   828  		// maximum possible length
   829  		maxl := xfixl
   830  		if strl < maxl {
   831  			maxl = strl
   832  		}
   833  		// compare letters
   834  		if pre {
   835  			// prefix, iterate left to right
   836  			for i := 0; i < maxl; i++ {
   837  				if xfix[i] != str[i] {
   838  					xfix = xfix[:i]
   839  					break
   840  				}
   841  			}
   842  		} else {
   843  			// suffix, iterate right to left
   844  			for i := 0; i < maxl; i++ {
   845  				xi := xfixl - i - 1
   846  				si := strl - i - 1
   847  				if xfix[xi] != str[si] {
   848  					xfix = xfix[xi+1:]
   849  					break
   850  				}
   851  			}
   852  		}
   853  	}
   854  	return xfix
   855  }
   856  
   857  // Returns the mode in which MinIO is running
   858  func getMinioMode() string {
   859  	mode := globalMinioModeFS
   860  	if globalIsDistErasure {
   861  		mode = globalMinioModeDistErasure
   862  	} else if globalIsErasure {
   863  		mode = globalMinioModeErasure
   864  	} else if GlobalIsGateway {
   865  		mode = globalMinioModeGatewayPrefix + globalGatewayName
   866  	}
   867  	return mode
   868  }
   869  
   870  func iamPolicyClaimNameOpenID() string {
   871  	return globalOpenIDConfig.ClaimPrefix + globalOpenIDConfig.ClaimName
   872  }
   873  
   874  func iamPolicyClaimNameSA() string {
   875  	return "sa-policy"
   876  }
   877  
   878  // timedValue contains a synchronized value that is considered valid
   879  // for a specific amount of time.
   880  // An Update function must be set to provide an updated value when needed.
   881  type timedValue struct {
   882  	// Update must return an updated value.
   883  	// If an error is returned the cached value is not set.
   884  	// Only one caller will call this function at any time, others will be blocking.
   885  	// The returned value can no longer be modified once returned.
   886  	// Should be set before calling Get().
   887  	Update func() (interface{}, error)
   888  
   889  	// TTL for a cached value.
   890  	// If not set 1 second TTL is assumed.
   891  	// Should be set before calling Get().
   892  	TTL time.Duration
   893  
   894  	// Once can be used to initialize values for lazy initialization.
   895  	// Should be set before calling Get().
   896  	Once sync.Once
   897  
   898  	// Managed values.
   899  	value      interface{}
   900  	lastUpdate time.Time
   901  	mu         sync.RWMutex
   902  }
   903  
   904  // Get will return a cached value or fetch a new one.
   905  // If the Update function returns an error the value is forwarded as is and not cached.
   906  func (t *timedValue) Get() (interface{}, error) {
   907  	v := t.get()
   908  	if v != nil {
   909  		return v, nil
   910  	}
   911  
   912  	v, err := t.Update()
   913  	if err != nil {
   914  		return v, err
   915  	}
   916  
   917  	t.update(v)
   918  	return v, nil
   919  }
   920  
   921  func (t *timedValue) get() (v interface{}) {
   922  	ttl := t.TTL
   923  	if ttl <= 0 {
   924  		ttl = time.Second
   925  	}
   926  	t.mu.RLock()
   927  	defer t.mu.RUnlock()
   928  	v = t.value
   929  	if time.Since(t.lastUpdate) < ttl {
   930  		return v
   931  	}
   932  	return nil
   933  }
   934  
   935  func (t *timedValue) update(v interface{}) {
   936  	t.mu.Lock()
   937  	defer t.mu.Unlock()
   938  	t.value = v
   939  	t.lastUpdate = time.Now()
   940  }
   941  
   942  // On MinIO a directory object is stored as a regular object with "__XLDIR__" suffix.
   943  // For ex. "prefix/" is stored as "prefix__XLDIR__"
   944  func encodeDirObject(object string) string {
   945  	if HasSuffix(object, slashSeparator) {
   946  		return strings.TrimSuffix(object, slashSeparator) + globalDirSuffix
   947  	}
   948  	return object
   949  }
   950  
   951  // Reverse process of encodeDirObject()
   952  func decodeDirObject(object string) string {
   953  	if HasSuffix(object, globalDirSuffix) {
   954  		return strings.TrimSuffix(object, globalDirSuffix) + slashSeparator
   955  	}
   956  	return object
   957  }
   958  
   959  // This is used by metrics to show the number of failed RPC calls
   960  // between internodes
   961  func loadAndResetRPCNetworkErrsCounter() uint64 {
   962  	defer rest.ResetNetworkErrsCounter()
   963  	return rest.GetNetworkErrsCounter()
   964  }