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

     1  /*
     2   * MinIO Cloud Storage, (C) 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  	"context"
    21  	"io"
    22  	"net/http"
    23  	"net/url"
    24  	"os"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"github.com/gorilla/mux"
    30  
    31  	"storj.io/minio/cmd/logger"
    32  	xioutil "storj.io/minio/pkg/ioutil"
    33  )
    34  
    35  // WalkDirOptions provides options for WalkDir operations.
    36  type WalkDirOptions struct {
    37  	// Bucket to scanner
    38  	Bucket string
    39  
    40  	// Directory inside the bucket.
    41  	BaseDir string
    42  
    43  	// Do a full recursive scan.
    44  	Recursive bool
    45  
    46  	// ReportNotFound will return errFileNotFound if all disks reports the BaseDir cannot be found.
    47  	ReportNotFound bool
    48  
    49  	// FilterPrefix will only return results with given prefix within folder.
    50  	// Should never contain a slash.
    51  	FilterPrefix string
    52  
    53  	// ForwardTo will forward to the given object path.
    54  	ForwardTo string
    55  }
    56  
    57  // WalkDir will traverse a directory and return all entries found.
    58  // On success a sorted meta cache stream will be returned.
    59  // Metadata has data stripped, if any.
    60  func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writer) error {
    61  	// Verify if volume is valid and it exists.
    62  	volumeDir, err := s.getVolDir(opts.Bucket)
    63  	if err != nil {
    64  		return err
    65  	}
    66  
    67  	// Stat a volume entry.
    68  	_, err = os.Lstat(volumeDir)
    69  	if err != nil {
    70  		if osIsNotExist(err) {
    71  			return errVolumeNotFound
    72  		} else if isSysErrIO(err) {
    73  			return errFaultyDisk
    74  		}
    75  		return err
    76  	}
    77  
    78  	// Use a small block size to start sending quickly
    79  	w := newMetacacheWriter(wr, 16<<10)
    80  	defer w.Close()
    81  	out, err := w.stream()
    82  	if err != nil {
    83  		return err
    84  	}
    85  	defer close(out)
    86  
    87  	// Fast exit track to check if we are listing an object with
    88  	// a trailing slash, this will avoid to list the object content.
    89  	if HasSuffix(opts.BaseDir, SlashSeparator) {
    90  		metadata, err := xioutil.ReadFile(pathJoin(volumeDir,
    91  			opts.BaseDir[:len(opts.BaseDir)-1]+globalDirSuffix,
    92  			xlStorageFormatFile))
    93  		if err == nil {
    94  			// if baseDir is already a directory object, consider it
    95  			// as part of the list call, this is a AWS S3 specific
    96  			// behavior.
    97  			out <- metaCacheEntry{
    98  				name:     opts.BaseDir,
    99  				metadata: xlMetaV2TrimData(metadata),
   100  			}
   101  		} else {
   102  			if st, err := os.Lstat(pathJoin(volumeDir, opts.BaseDir, xlStorageFormatFile)); err == nil && st.Mode().IsRegular() {
   103  				return errFileNotFound
   104  			}
   105  		}
   106  	}
   107  
   108  	prefix := opts.FilterPrefix
   109  	forward := opts.ForwardTo
   110  	var scanDir func(path string) error
   111  	scanDir = func(current string) error {
   112  		if contextCanceled(ctx) {
   113  			return ctx.Err()
   114  		}
   115  		entries, err := s.ListDir(ctx, opts.Bucket, current, -1)
   116  		if err != nil {
   117  			// Folder could have gone away in-between
   118  			if err != errVolumeNotFound && err != errFileNotFound {
   119  				logger.LogIf(ctx, err)
   120  			}
   121  			if opts.ReportNotFound && err == errFileNotFound && current == opts.BaseDir {
   122  				return errFileNotFound
   123  			}
   124  			// Forward some errors?
   125  			return nil
   126  		}
   127  		dirObjects := make(map[string]struct{})
   128  		for i, entry := range entries {
   129  			if len(prefix) > 0 && !strings.HasPrefix(entry, prefix) {
   130  				continue
   131  			}
   132  			if len(forward) > 0 && entry < forward {
   133  				continue
   134  			}
   135  			if strings.HasSuffix(entry, slashSeparator) {
   136  				if strings.HasSuffix(entry, globalDirSuffixWithSlash) {
   137  					// Add without extension so it is sorted correctly.
   138  					entry = strings.TrimSuffix(entry, globalDirSuffixWithSlash) + slashSeparator
   139  					dirObjects[entry] = struct{}{}
   140  					entries[i] = entry
   141  					continue
   142  				}
   143  				// Trim slash, maybe compiler is clever?
   144  				entries[i] = entries[i][:len(entry)-1]
   145  				continue
   146  			}
   147  			// Do do not retain the file.
   148  			entries[i] = ""
   149  
   150  			if contextCanceled(ctx) {
   151  				return ctx.Err()
   152  			}
   153  			// If root was an object return it as such.
   154  			if HasSuffix(entry, xlStorageFormatFile) {
   155  				var meta metaCacheEntry
   156  				meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, current, entry))
   157  				if err != nil {
   158  					logger.LogIf(ctx, err)
   159  					continue
   160  				}
   161  				meta.metadata = xlMetaV2TrimData(meta.metadata)
   162  				meta.name = strings.TrimSuffix(entry, xlStorageFormatFile)
   163  				meta.name = strings.TrimSuffix(meta.name, SlashSeparator)
   164  				meta.name = pathJoin(current, meta.name)
   165  				meta.name = decodeDirObject(meta.name)
   166  				out <- meta
   167  				return nil
   168  			}
   169  			// Check legacy.
   170  			if HasSuffix(entry, xlStorageFormatFileV1) {
   171  				var meta metaCacheEntry
   172  				meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, current, entry))
   173  				if err != nil {
   174  					logger.LogIf(ctx, err)
   175  					continue
   176  				}
   177  				meta.name = strings.TrimSuffix(entry, xlStorageFormatFileV1)
   178  				meta.name = strings.TrimSuffix(meta.name, SlashSeparator)
   179  				meta.name = pathJoin(current, meta.name)
   180  				out <- meta
   181  				return nil
   182  			}
   183  			// Skip all other files.
   184  		}
   185  
   186  		// Process in sort order.
   187  		sort.Strings(entries)
   188  		dirStack := make([]string, 0, 5)
   189  		prefix = "" // Remove prefix after first level.
   190  
   191  		for _, entry := range entries {
   192  			if entry == "" {
   193  				continue
   194  			}
   195  			if contextCanceled(ctx) {
   196  				return ctx.Err()
   197  			}
   198  			meta := metaCacheEntry{name: PathJoin(current, entry)}
   199  
   200  			// If directory entry on stack before this, pop it now.
   201  			for len(dirStack) > 0 && dirStack[len(dirStack)-1] < meta.name {
   202  				pop := dirStack[len(dirStack)-1]
   203  				out <- metaCacheEntry{name: pop}
   204  				if opts.Recursive {
   205  					// Scan folder we found. Should be in correct sort order where we are.
   206  					forward = ""
   207  					if len(opts.ForwardTo) > 0 && strings.HasPrefix(opts.ForwardTo, pop) {
   208  						forward = strings.TrimPrefix(opts.ForwardTo, pop)
   209  					}
   210  					logger.LogIf(ctx, scanDir(pop))
   211  				}
   212  				dirStack = dirStack[:len(dirStack)-1]
   213  			}
   214  
   215  			// All objects will be returned as directories, there has been no object check yet.
   216  			// Check it by attempting to read metadata.
   217  			_, isDirObj := dirObjects[entry]
   218  			if isDirObj {
   219  				meta.name = meta.name[:len(meta.name)-1] + globalDirSuffixWithSlash
   220  			}
   221  
   222  			meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFile))
   223  			switch {
   224  			case err == nil:
   225  				// It was an object
   226  				if isDirObj {
   227  					meta.name = strings.TrimSuffix(meta.name, globalDirSuffixWithSlash) + slashSeparator
   228  				}
   229  				out <- meta
   230  			case osIsNotExist(err):
   231  				meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1))
   232  				if err == nil {
   233  					// Maybe rename? Would make it inconsistent across disks though.
   234  					// os.Rename(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1), pathJoin(volumeDir, meta.name, xlStorageFormatFile))
   235  					// It was an object
   236  					out <- meta
   237  					continue
   238  				}
   239  
   240  				// NOT an object, append to stack (with slash)
   241  				// If dirObject, but no metadata (which is unexpected) we skip it.
   242  				if !isDirObj {
   243  					if !isDirEmpty(pathJoin(volumeDir, meta.name+slashSeparator)) {
   244  						dirStack = append(dirStack, meta.name+slashSeparator)
   245  					}
   246  				}
   247  			case isSysErrNotDir(err):
   248  				// skip
   249  			default:
   250  				logger.LogIf(ctx, err)
   251  			}
   252  		}
   253  		// If directory entry left on stack, pop it now.
   254  		for len(dirStack) > 0 {
   255  			pop := dirStack[len(dirStack)-1]
   256  			out <- metaCacheEntry{name: pop}
   257  			if opts.Recursive {
   258  				// Scan folder we found. Should be in correct sort order where we are.
   259  				forward = ""
   260  				if len(opts.ForwardTo) > 0 && strings.HasPrefix(opts.ForwardTo, pop) {
   261  					forward = strings.TrimPrefix(opts.ForwardTo, pop)
   262  				}
   263  				logger.LogIf(ctx, scanDir(pop))
   264  			}
   265  			dirStack = dirStack[:len(dirStack)-1]
   266  		}
   267  		return nil
   268  	}
   269  
   270  	// Stream output.
   271  	return scanDir(opts.BaseDir)
   272  }
   273  
   274  func (p *xlStorageDiskIDCheck) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writer) error {
   275  	defer p.updateStorageMetrics(storageMetricWalkDir)()
   276  	if err := p.checkDiskStale(); err != nil {
   277  		return err
   278  	}
   279  	return p.storage.WalkDir(ctx, opts, wr)
   280  }
   281  
   282  // WalkDir will traverse a directory and return all entries found.
   283  // On success a meta cache stream will be returned, that should be closed when done.
   284  func (client *storageRESTClient) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writer) error {
   285  	values := make(url.Values)
   286  	values.Set(storageRESTVolume, opts.Bucket)
   287  	values.Set(storageRESTDirPath, opts.BaseDir)
   288  	values.Set(storageRESTRecursive, strconv.FormatBool(opts.Recursive))
   289  	values.Set(storageRESTReportNotFound, strconv.FormatBool(opts.ReportNotFound))
   290  	values.Set(storageRESTPrefixFilter, opts.FilterPrefix)
   291  	values.Set(storageRESTForwardFilter, opts.ForwardTo)
   292  	respBody, err := client.call(ctx, storageRESTMethodWalkDir, values, nil, -1)
   293  	if err != nil {
   294  		logger.LogIf(ctx, err)
   295  		return err
   296  	}
   297  	return waitForHTTPStream(respBody, wr)
   298  }
   299  
   300  // WalkDirHandler - remote caller to list files and folders in a requested directory path.
   301  func (s *storageRESTServer) WalkDirHandler(w http.ResponseWriter, r *http.Request) {
   302  	if !s.IsValid(w, r) {
   303  		return
   304  	}
   305  	vars := mux.Vars(r)
   306  	volume := vars[storageRESTVolume]
   307  	dirPath := vars[storageRESTDirPath]
   308  	recursive, err := strconv.ParseBool(vars[storageRESTRecursive])
   309  	if err != nil {
   310  		s.WriteErrorResponse(w, err)
   311  		return
   312  	}
   313  
   314  	var reportNotFound bool
   315  	if v := vars[storageRESTReportNotFound]; v != "" {
   316  		reportNotFound, err = strconv.ParseBool(v)
   317  		if err != nil {
   318  			s.WriteErrorResponse(w, err)
   319  			return
   320  		}
   321  	}
   322  
   323  	prefix := r.URL.Query().Get(storageRESTPrefixFilter)
   324  	forward := r.URL.Query().Get(storageRESTForwardFilter)
   325  	writer := streamHTTPResponse(w)
   326  	writer.CloseWithError(s.storage.WalkDir(r.Context(), WalkDirOptions{
   327  		Bucket:         volume,
   328  		BaseDir:        dirPath,
   329  		Recursive:      recursive,
   330  		ReportNotFound: reportNotFound,
   331  		FilterPrefix:   prefix,
   332  		ForwardTo:      forward,
   333  	}, writer))
   334  }