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 }