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

     1  /*
     2   * MinIO Cloud Storage, (C) 2016-2019 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  	"context"
    21  	"strings"
    22  	"sync"
    23  
    24  	humanize "github.com/dustin/go-humanize"
    25  
    26  	"storj.io/minio/pkg/sync/errgroup"
    27  )
    28  
    29  const (
    30  	// Block size used for all internal operations version 1.
    31  
    32  	// TLDR..
    33  	// Not used anymore xl.meta captures the right blockSize
    34  	// so blockSizeV2 should be used for all future purposes.
    35  	// this value is kept here to calculate the max API
    36  	// requests based on RAM size for existing content.
    37  	blockSizeV1 = 10 * humanize.MiByte
    38  
    39  	// Block size used in erasure coding version 2.
    40  	blockSizeV2 = 1 * humanize.MiByte
    41  
    42  	// Buckets meta prefix.
    43  	bucketMetaPrefix = "buckets"
    44  
    45  	// ETag (hex encoded md5sum) of empty string.
    46  	emptyETag = "d41d8cd98f00b204e9800998ecf8427e"
    47  )
    48  
    49  // Global object layer mutex, used for safely updating object layer.
    50  var globalObjLayerMutex sync.RWMutex
    51  
    52  // Global object layer, only accessed by globalObjectAPI.
    53  var globalObjectAPI ObjectLayer
    54  
    55  //Global cacheObjects, only accessed by newCacheObjectsFn().
    56  var globalCacheObjectAPI CacheObjectLayer
    57  
    58  // Checks if the object is a directory, this logic uses
    59  // if size == 0 and object ends with SlashSeparator then
    60  // returns true.
    61  func isObjectDir(object string, size int64) bool {
    62  	return HasSuffix(object, SlashSeparator) && size == 0
    63  }
    64  
    65  func newStorageAPIWithoutHealthCheck(endpoint Endpoint) (storage StorageAPI, err error) {
    66  	if endpoint.IsLocal {
    67  		storage, err := newXLStorage(endpoint)
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  		return newXLStorageDiskIDCheck(storage), nil
    72  	}
    73  
    74  	return newStorageRESTClient(endpoint, false), nil
    75  }
    76  
    77  // Depending on the disk type network or local, initialize storage API.
    78  func newStorageAPI(endpoint Endpoint) (storage StorageAPI, err error) {
    79  	if endpoint.IsLocal {
    80  		storage, err := newXLStorage(endpoint)
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  		return newXLStorageDiskIDCheck(storage), nil
    85  	}
    86  
    87  	return newStorageRESTClient(endpoint, true), nil
    88  }
    89  
    90  func listObjectsNonSlash(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int, tpool *TreeWalkPool, listDir ListDirFunc, isLeaf IsLeafFunc, isLeafDir IsLeafDirFunc, getObjInfo func(context.Context, string, string) (ObjectInfo, error), getObjectInfoDirs ...func(context.Context, string, string) (ObjectInfo, error)) (loi ListObjectsInfo, err error) {
    91  	endWalkCh := make(chan struct{})
    92  	defer close(endWalkCh)
    93  	recursive := true
    94  	walkResultCh := startTreeWalk(ctx, bucket, prefix, "", recursive, listDir, isLeaf, isLeafDir, endWalkCh)
    95  
    96  	var objInfos []ObjectInfo
    97  	var eof bool
    98  	var prevPrefix string
    99  
   100  	for {
   101  		if len(objInfos) == maxKeys {
   102  			break
   103  		}
   104  		result, ok := <-walkResultCh
   105  		if !ok {
   106  			eof = true
   107  			break
   108  		}
   109  
   110  		var objInfo ObjectInfo
   111  		var err error
   112  
   113  		index := strings.Index(strings.TrimPrefix(result.entry, prefix), delimiter)
   114  		if index == -1 {
   115  			objInfo, err = getObjInfo(ctx, bucket, result.entry)
   116  			if err != nil {
   117  				// Ignore errFileNotFound as the object might have got
   118  				// deleted in the interim period of listing and getObjectInfo(),
   119  				// ignore quorum error as it might be an entry from an outdated disk.
   120  				if IsErrIgnored(err, []error{
   121  					errFileNotFound,
   122  					errErasureReadQuorum,
   123  				}...) {
   124  					continue
   125  				}
   126  				return loi, toObjectErr(err, bucket, prefix)
   127  			}
   128  		} else {
   129  			index = len(prefix) + index + len(delimiter)
   130  			currPrefix := result.entry[:index]
   131  			if currPrefix == prevPrefix {
   132  				continue
   133  			}
   134  			prevPrefix = currPrefix
   135  
   136  			objInfo = ObjectInfo{
   137  				Bucket: bucket,
   138  				Name:   currPrefix,
   139  				IsDir:  true,
   140  			}
   141  		}
   142  
   143  		if objInfo.Name <= marker {
   144  			continue
   145  		}
   146  
   147  		objInfos = append(objInfos, objInfo)
   148  		if result.end {
   149  			eof = true
   150  			break
   151  		}
   152  	}
   153  
   154  	result := ListObjectsInfo{}
   155  	for _, objInfo := range objInfos {
   156  		if objInfo.IsDir {
   157  			result.Prefixes = append(result.Prefixes, objInfo.Name)
   158  			continue
   159  		}
   160  		result.Objects = append(result.Objects, objInfo)
   161  	}
   162  
   163  	if !eof {
   164  		result.IsTruncated = true
   165  		if len(objInfos) > 0 {
   166  			result.NextMarker = objInfos[len(objInfos)-1].Name
   167  		}
   168  	}
   169  
   170  	return result, nil
   171  }
   172  
   173  // Walk a bucket, optionally prefix recursively, until we have returned
   174  // all the content to objectInfo channel, it is callers responsibility
   175  // to allocate a receive channel for ObjectInfo, upon any unhandled
   176  // error walker returns error. Optionally if context.Done() is received
   177  // then Walk() stops the walker.
   178  func fsWalk(ctx context.Context, obj ObjectLayer, bucket, prefix string, listDir ListDirFunc, isLeaf IsLeafFunc, isLeafDir IsLeafDirFunc, results chan<- ObjectInfo, getObjInfo func(context.Context, string, string) (ObjectInfo, error), getObjectInfoDirs ...func(context.Context, string, string) (ObjectInfo, error)) error {
   179  	if err := checkListObjsArgs(ctx, bucket, prefix, "", obj); err != nil {
   180  		// Upon error close the channel.
   181  		close(results)
   182  		return err
   183  	}
   184  
   185  	walkResultCh := startTreeWalk(ctx, bucket, prefix, "", true, listDir, isLeaf, isLeafDir, ctx.Done())
   186  
   187  	go func() {
   188  		defer close(results)
   189  
   190  		for {
   191  			walkResult, ok := <-walkResultCh
   192  			if !ok {
   193  				break
   194  			}
   195  
   196  			var objInfo ObjectInfo
   197  			var err error
   198  			if HasSuffix(walkResult.entry, SlashSeparator) {
   199  				for _, getObjectInfoDir := range getObjectInfoDirs {
   200  					objInfo, err = getObjectInfoDir(ctx, bucket, walkResult.entry)
   201  					if err == nil {
   202  						break
   203  					}
   204  					if err == errFileNotFound {
   205  						err = nil
   206  						objInfo = ObjectInfo{
   207  							Bucket: bucket,
   208  							Name:   walkResult.entry,
   209  							IsDir:  true,
   210  						}
   211  					}
   212  				}
   213  			} else {
   214  				objInfo, err = getObjInfo(ctx, bucket, walkResult.entry)
   215  			}
   216  			if err != nil {
   217  				continue
   218  			}
   219  			results <- objInfo
   220  			if walkResult.end {
   221  				break
   222  			}
   223  		}
   224  	}()
   225  	return nil
   226  }
   227  
   228  func listObjects(ctx context.Context, obj ObjectLayer, bucket, prefix, marker, delimiter string, maxKeys int, tpool *TreeWalkPool, listDir ListDirFunc, isLeaf IsLeafFunc, isLeafDir IsLeafDirFunc, getObjInfo func(context.Context, string, string) (ObjectInfo, error), getObjectInfoDirs ...func(context.Context, string, string) (ObjectInfo, error)) (loi ListObjectsInfo, err error) {
   229  	if delimiter != SlashSeparator && delimiter != "" {
   230  		return listObjectsNonSlash(ctx, bucket, prefix, marker, delimiter, maxKeys, tpool, listDir, isLeaf, isLeafDir, getObjInfo, getObjectInfoDirs...)
   231  	}
   232  
   233  	if err := checkListObjsArgs(ctx, bucket, prefix, marker, obj); err != nil {
   234  		return loi, err
   235  	}
   236  
   237  	// Marker is set validate pre-condition.
   238  	if marker != "" {
   239  		// Marker not common with prefix is not implemented. Send an empty response
   240  		if !HasPrefix(marker, prefix) {
   241  			return loi, nil
   242  		}
   243  	}
   244  
   245  	// With max keys of zero we have reached eof, return right here.
   246  	if maxKeys == 0 {
   247  		return loi, nil
   248  	}
   249  
   250  	// For delimiter and prefix as '/' we do not list anything at all
   251  	// since according to s3 spec we stop at the 'delimiter'
   252  	// along // with the prefix. On a flat namespace with 'prefix'
   253  	// as '/' we don't have any entries, since all the keys are
   254  	// of form 'keyName/...'
   255  	if delimiter == SlashSeparator && prefix == SlashSeparator {
   256  		return loi, nil
   257  	}
   258  
   259  	// Over flowing count - reset to maxObjectList.
   260  	if maxKeys < 0 || maxKeys > maxObjectList {
   261  		maxKeys = maxObjectList
   262  	}
   263  
   264  	// Default is recursive, if delimiter is set then list non recursive.
   265  	recursive := true
   266  	if delimiter == SlashSeparator {
   267  		recursive = false
   268  	}
   269  
   270  	walkResultCh, endWalkCh := tpool.Release(listParams{bucket, recursive, marker, prefix})
   271  	if walkResultCh == nil {
   272  		endWalkCh = make(chan struct{})
   273  		walkResultCh = startTreeWalk(ctx, bucket, prefix, marker, recursive, listDir, isLeaf, isLeafDir, endWalkCh)
   274  	}
   275  
   276  	var eof bool
   277  	var nextMarker string
   278  
   279  	// List until maxKeys requested.
   280  	g := errgroup.WithNErrs(maxKeys).WithConcurrency(10)
   281  	ctx, cancel := g.WithCancelOnError(ctx)
   282  	defer cancel()
   283  
   284  	objInfoFound := make([]*ObjectInfo, maxKeys)
   285  	var i int
   286  	for i = 0; i < maxKeys; i++ {
   287  		i := i
   288  		walkResult, ok := <-walkResultCh
   289  		if !ok {
   290  			// Closed channel.
   291  			eof = true
   292  		}
   293  
   294  		if HasSuffix(walkResult.entry, SlashSeparator) {
   295  			g.Go(func() error {
   296  				for _, getObjectInfoDir := range getObjectInfoDirs {
   297  					objInfo, err := getObjectInfoDir(ctx, bucket, walkResult.entry)
   298  					if err == nil {
   299  						objInfoFound[i] = &objInfo
   300  						// Done...
   301  						return nil
   302  					}
   303  
   304  					// Add temp, may be overridden,
   305  					if err == errFileNotFound {
   306  						objInfoFound[i] = &ObjectInfo{
   307  							Bucket: bucket,
   308  							Name:   walkResult.entry,
   309  							IsDir:  true,
   310  						}
   311  						continue
   312  					}
   313  					return toObjectErr(err, bucket, prefix)
   314  				}
   315  				return nil
   316  			}, i)
   317  		} else {
   318  			g.Go(func() error {
   319  				objInfo, err := getObjInfo(ctx, bucket, walkResult.entry)
   320  				if err != nil {
   321  					// Ignore errFileNotFound as the object might have got
   322  					// deleted in the interim period of listing and getObjectInfo(),
   323  					// ignore quorum error as it might be an entry from an outdated disk.
   324  					if IsErrIgnored(err, []error{
   325  						errFileNotFound,
   326  						errErasureReadQuorum,
   327  					}...) {
   328  						return nil
   329  					}
   330  					return toObjectErr(err, bucket, prefix)
   331  				}
   332  				objInfoFound[i] = &objInfo
   333  				return nil
   334  			}, i)
   335  		}
   336  
   337  		if walkResult.end {
   338  			eof = true
   339  			break
   340  		}
   341  	}
   342  	if err := g.WaitErr(); err != nil {
   343  		return loi, err
   344  	}
   345  	// Copy found objects
   346  	objInfos := make([]ObjectInfo, 0, i+1)
   347  	for _, objInfo := range objInfoFound {
   348  		if objInfo == nil {
   349  			continue
   350  		}
   351  		objInfos = append(objInfos, *objInfo)
   352  		nextMarker = objInfo.Name
   353  	}
   354  
   355  	// Save list routine for the next marker if we haven't reached EOF.
   356  	params := listParams{bucket, recursive, nextMarker, prefix}
   357  	if !eof {
   358  		tpool.Set(params, walkResultCh, endWalkCh)
   359  	}
   360  
   361  	result := ListObjectsInfo{}
   362  	for _, objInfo := range objInfos {
   363  		if objInfo.IsDir && delimiter == SlashSeparator {
   364  			result.Prefixes = append(result.Prefixes, objInfo.Name)
   365  			continue
   366  		}
   367  		result.Objects = append(result.Objects, objInfo)
   368  	}
   369  
   370  	if !eof {
   371  		result.IsTruncated = true
   372  		if len(objInfos) > 0 {
   373  			result.NextMarker = objInfos[len(objInfos)-1].Name
   374  		}
   375  	}
   376  
   377  	// Success.
   378  	return result, nil
   379  }