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 }