mirror of
https://github.com/harness/drone.git
synced 2025-05-04 08:12:12 +08:00
141 lines
4.5 KiB
Go
141 lines
4.5 KiB
Go
// Source: https://github.com/distribution/distribution
|
|
|
|
// Copyright 2014 https://github.com/distribution/distribution Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package driver
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// ErrSkipDir is used as a return value from onFileFunc to indicate that
|
|
// the directory named in the call is to be skipped. It is not returned
|
|
// as an error by any function.
|
|
var ErrSkipDir = errors.New("skip this directory")
|
|
|
|
// ErrFilledBuffer is used as a return value from onFileFunc to indicate
|
|
// that the requested number of entries has been reached and the walk can
|
|
// stop.
|
|
var ErrFilledBuffer = errors.New("we have enough entries")
|
|
|
|
// WalkFn is called once per file by Walk.
|
|
type WalkFn func(fileInfo FileInfo) error
|
|
|
|
// WalkFallback traverses a filesystem defined within driver, starting
|
|
// from the given path, calling f on each file. It uses the List method and Stat to drive itself.
|
|
// If the returned error from the WalkFn is ErrSkipDir the directory will not be entered and Walk
|
|
// will continue the traversal. If the returned error from the WalkFn is ErrFilledBuffer, the walk
|
|
// stops.
|
|
func WalkFallback(
|
|
ctx context.Context,
|
|
driver StorageDriver,
|
|
from string,
|
|
f WalkFn,
|
|
options ...func(*WalkOptions),
|
|
) error {
|
|
walkOptions := &WalkOptions{}
|
|
for _, o := range options {
|
|
o(walkOptions)
|
|
}
|
|
|
|
startAfterHint := walkOptions.StartAfterHint
|
|
// Ensure that we are checking the hint is contained within from by adding a "/".
|
|
// Add to both in case the hint and form are the same, which would still count.
|
|
rel, err := filepath.Rel(from, startAfterHint)
|
|
if err != nil || strings.HasPrefix(rel, "..") {
|
|
// The startAfterHint is outside from, so check if we even need to walk anything
|
|
// Replace any path separators with \x00 so that the sort works in a depth-first way
|
|
if strings.ReplaceAll(startAfterHint, "/", "\x00") < strings.ReplaceAll(from, "/", "\x00") {
|
|
_, err := doWalkFallback(ctx, driver, from, "", f)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
// The startAfterHint is within from.
|
|
// Walk up the tree until we hit from - we know it is contained.
|
|
// Ensure startAfterHint is never deeper than a child of the base
|
|
// directory so that doWalkFallback doesn't have to worry about
|
|
// depth-first comparisons
|
|
base := startAfterHint
|
|
for strings.HasPrefix(base, from) {
|
|
_, err = doWalkFallback(ctx, driver, base, startAfterHint, f)
|
|
if !(errors.As(err, &PathNotFoundError{}) || err == nil) {
|
|
return err
|
|
}
|
|
if base == from {
|
|
break
|
|
}
|
|
startAfterHint = base
|
|
base, _ = filepath.Split(startAfterHint)
|
|
if len(base) > 1 {
|
|
base = strings.TrimSuffix(base, "/")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// doWalkFallback performs a depth first walk using recursion.
|
|
// from is the directory that this iteration of the function should walk.
|
|
// startAfterHint is the child within from to start the walk after.
|
|
// It should only ever be a child of from, or the empty string.
|
|
func doWalkFallback(
|
|
ctx context.Context,
|
|
driver StorageDriver,
|
|
from string,
|
|
startAfterHint string,
|
|
f WalkFn,
|
|
) (bool, error) {
|
|
children, err := driver.List(ctx, from)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
sort.Strings(children)
|
|
for _, child := range children {
|
|
// The startAfterHint has been sanitised in WalkFallback and will either be
|
|
// empty, or be suitable for an <= check for this _from_.
|
|
if child <= startAfterHint {
|
|
continue
|
|
}
|
|
|
|
fileInfo, err := driver.Stat(ctx, child)
|
|
if err != nil {
|
|
if errors.As(err, &PathNotFoundError{}) {
|
|
// repository was removed in between listing and enumeration. Ignore it.
|
|
log.Ctx(ctx).Info().Interface("path", child).Msg("ignoring deleted path")
|
|
} else {
|
|
return false, err
|
|
}
|
|
}
|
|
err = f(fileInfo)
|
|
switch {
|
|
case err == nil && fileInfo.IsDir():
|
|
if ok, err := doWalkFallback(ctx, driver, child, startAfterHint, f); err != nil || !ok {
|
|
return ok, err
|
|
}
|
|
case errors.Is(err, ErrFilledBuffer):
|
|
return false, nil // no error but stop iteration
|
|
case err != nil:
|
|
return false, err
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|