| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | /* | 
					
						
							|  |  |  |  * MinIO Cloud Storage, (C) 2020 MinIO, Inc. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 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 cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"io/ioutil" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"sort" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync/atomic" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/gorilla/mux" | 
					
						
							|  |  |  | 	"github.com/minio/minio/cmd/logger" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // WalkDirOptions provides options for WalkDir operations.
 | 
					
						
							|  |  |  | type WalkDirOptions struct { | 
					
						
							|  |  |  | 	// Bucket to crawl
 | 
					
						
							|  |  |  | 	Bucket string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Directory inside the bucket.
 | 
					
						
							|  |  |  | 	BaseDir string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Do a full recursive scan.
 | 
					
						
							|  |  |  | 	Recursive bool | 
					
						
							| 
									
										
										
										
											2020-11-19 02:44:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-02 04:07:39 +08:00
										 |  |  | 	// ReportNotFound will return errFileNotFound if all disks reports the BaseDir cannot be found.
 | 
					
						
							|  |  |  | 	ReportNotFound bool | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 02:44:18 +08:00
										 |  |  | 	// FilterPrefix will only return results with given prefix within folder.
 | 
					
						
							|  |  |  | 	// Should never contain a slash.
 | 
					
						
							|  |  |  | 	FilterPrefix string | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // WalkDir will traverse a directory and return all entries found.
 | 
					
						
							|  |  |  | // On success a sorted meta cache stream will be returned.
 | 
					
						
							|  |  |  | func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writer) error { | 
					
						
							|  |  |  | 	atomic.AddInt32(&s.activeIOCount, 1) | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		atomic.AddInt32(&s.activeIOCount, -1) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Verify if volume is valid and it exists.
 | 
					
						
							|  |  |  | 	volumeDir, err := s.getVolDir(opts.Bucket) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Stat a volume entry.
 | 
					
						
							|  |  |  | 	_, err = os.Stat(volumeDir) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-11-24 00:36:49 +08:00
										 |  |  | 		if osIsNotExist(err) { | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 			return errVolumeNotFound | 
					
						
							|  |  |  | 		} else if isSysErrIO(err) { | 
					
						
							|  |  |  | 			return errFaultyDisk | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Fast exit track to check if we are listing an object with
 | 
					
						
							|  |  |  | 	// a trailing slash, this will avoid to list the object content.
 | 
					
						
							|  |  |  | 	if HasSuffix(opts.BaseDir, SlashSeparator) { | 
					
						
							|  |  |  | 		if st, err := os.Stat(pathJoin(volumeDir, opts.BaseDir, xlStorageFormatFile)); err == nil && st.Mode().IsRegular() { | 
					
						
							|  |  |  | 			return errFileNotFound | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Use a small block size to start sending quickly
 | 
					
						
							|  |  |  | 	w := newMetacacheWriter(wr, 16<<10) | 
					
						
							|  |  |  | 	defer w.Close() | 
					
						
							|  |  |  | 	out, err := w.stream() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer close(out) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 02:44:18 +08:00
										 |  |  | 	prefix := opts.FilterPrefix | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 	var scanDir func(path string) error | 
					
						
							|  |  |  | 	scanDir = func(current string) error { | 
					
						
							|  |  |  | 		entries, err := s.ListDir(ctx, opts.Bucket, current, -1) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			// Folder could have gone away in-between
 | 
					
						
							|  |  |  | 			if err != errVolumeNotFound && err != errFileNotFound { | 
					
						
							|  |  |  | 				logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-12-02 04:07:39 +08:00
										 |  |  | 			if opts.ReportNotFound && err == errFileNotFound && current == opts.BaseDir { | 
					
						
							|  |  |  | 				return errFileNotFound | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 			// Forward some errors?
 | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-13 05:09:34 +08:00
										 |  |  | 		dirObjects := make(map[string]struct{}) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 		for i, entry := range entries { | 
					
						
							| 
									
										
										
										
											2020-11-19 02:44:18 +08:00
										 |  |  | 			if len(prefix) > 0 && !strings.HasPrefix(entry, prefix) { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 			if strings.HasSuffix(entry, slashSeparator) { | 
					
						
							| 
									
										
										
										
											2020-11-13 05:09:34 +08:00
										 |  |  | 				if strings.HasSuffix(entry, globalDirSuffixWithSlash) { | 
					
						
							|  |  |  | 					// Add without extension so it is sorted correctly.
 | 
					
						
							|  |  |  | 					entry = strings.TrimSuffix(entry, globalDirSuffixWithSlash) + slashSeparator | 
					
						
							|  |  |  | 					dirObjects[entry] = struct{}{} | 
					
						
							|  |  |  | 					entries[i] = entry | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 				// Trim slash, maybe compiler is clever?
 | 
					
						
							|  |  |  | 				entries[i] = entries[i][:len(entry)-1] | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Do do not retain the file.
 | 
					
						
							|  |  |  | 			entries[i] = "" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// If root was an object return it as such.
 | 
					
						
							|  |  |  | 			if HasSuffix(entry, xlStorageFormatFile) { | 
					
						
							|  |  |  | 				var meta metaCacheEntry | 
					
						
							| 
									
										
										
										
											2020-11-14 08:58:20 +08:00
										 |  |  | 				meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, current, entry)) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-11-14 08:58:20 +08:00
										 |  |  | 				meta.name = strings.TrimSuffix(entry, xlStorageFormatFile) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 				meta.name = strings.TrimSuffix(meta.name, SlashSeparator) | 
					
						
							| 
									
										
										
										
											2020-11-14 08:58:20 +08:00
										 |  |  | 				meta.name = pathJoin(current, meta.name) | 
					
						
							| 
									
										
										
										
											2020-11-13 05:09:34 +08:00
										 |  |  | 				meta.name = decodeDirObject(meta.name) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 				out <- meta | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Check legacy.
 | 
					
						
							|  |  |  | 			if HasSuffix(entry, xlStorageFormatFileV1) { | 
					
						
							|  |  |  | 				var meta metaCacheEntry | 
					
						
							| 
									
										
										
										
											2020-11-14 08:58:20 +08:00
										 |  |  | 				meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, current, entry)) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-11-14 08:58:20 +08:00
										 |  |  | 				meta.name = strings.TrimSuffix(entry, xlStorageFormatFileV1) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 				meta.name = strings.TrimSuffix(meta.name, SlashSeparator) | 
					
						
							| 
									
										
										
										
											2020-11-14 08:58:20 +08:00
										 |  |  | 				meta.name = pathJoin(current, meta.name) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 				out <- meta | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Skip all other files.
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Process in sort order.
 | 
					
						
							|  |  |  | 		sort.Strings(entries) | 
					
						
							|  |  |  | 		dirStack := make([]string, 0, 5) | 
					
						
							| 
									
										
										
										
											2020-11-19 02:44:18 +08:00
										 |  |  | 		prefix = "" // Remove prefix after first level.
 | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 		for _, entry := range entries { | 
					
						
							|  |  |  | 			if entry == "" { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			meta := metaCacheEntry{name: PathJoin(current, entry)} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// If directory entry on stack before this, pop it now.
 | 
					
						
							|  |  |  | 			for len(dirStack) > 0 && dirStack[len(dirStack)-1] < meta.name { | 
					
						
							|  |  |  | 				pop := dirStack[len(dirStack)-1] | 
					
						
							|  |  |  | 				out <- metaCacheEntry{name: pop} | 
					
						
							|  |  |  | 				if opts.Recursive { | 
					
						
							|  |  |  | 					// Scan folder we found. Should be in correct sort order where we are.
 | 
					
						
							|  |  |  | 					err := scanDir(pop) | 
					
						
							|  |  |  | 					logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				dirStack = dirStack[:len(dirStack)-1] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// All objects will be returned as directories, there has been no object check yet.
 | 
					
						
							|  |  |  | 			// Check it by attempting to read metadata.
 | 
					
						
							| 
									
										
										
										
											2020-11-13 05:09:34 +08:00
										 |  |  | 			_, isDirObj := dirObjects[entry] | 
					
						
							|  |  |  | 			if isDirObj { | 
					
						
							|  |  |  | 				meta.name = meta.name[:len(meta.name)-1] + globalDirSuffixWithSlash | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 			meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFile)) | 
					
						
							|  |  |  | 			switch { | 
					
						
							|  |  |  | 			case err == nil: | 
					
						
							|  |  |  | 				// It was an object
 | 
					
						
							| 
									
										
										
										
											2020-11-13 05:09:34 +08:00
										 |  |  | 				if isDirObj { | 
					
						
							|  |  |  | 					meta.name = strings.TrimSuffix(meta.name, globalDirSuffixWithSlash) + slashSeparator | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 				out <- meta | 
					
						
							| 
									
										
										
										
											2020-11-24 00:36:49 +08:00
										 |  |  | 			case osIsNotExist(err): | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 				meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1)) | 
					
						
							|  |  |  | 				if err == nil { | 
					
						
							|  |  |  | 					// Maybe rename? Would make it inconsistent across disks though.
 | 
					
						
							|  |  |  | 					// os.Rename(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1), pathJoin(volumeDir, meta.name, xlStorageFormatFile))
 | 
					
						
							|  |  |  | 					// It was an object
 | 
					
						
							|  |  |  | 					out <- meta | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// NOT an object, append to stack (with slash)
 | 
					
						
							| 
									
										
										
										
											2020-11-13 05:09:34 +08:00
										 |  |  | 				// If dirObject, but no metadata (which is unexpected) we skip it.
 | 
					
						
							|  |  |  | 				if !isDirObj { | 
					
						
							|  |  |  | 					dirStack = append(dirStack, meta.name+slashSeparator) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-11-20 01:15:09 +08:00
										 |  |  | 			case isSysErrNotDir(err): | 
					
						
							|  |  |  | 				// skip
 | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 			default: | 
					
						
							|  |  |  | 				logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// If directory entry left on stack, pop it now.
 | 
					
						
							|  |  |  | 		for len(dirStack) > 0 { | 
					
						
							|  |  |  | 			pop := dirStack[len(dirStack)-1] | 
					
						
							|  |  |  | 			out <- metaCacheEntry{name: pop} | 
					
						
							|  |  |  | 			if opts.Recursive { | 
					
						
							|  |  |  | 				// Scan folder we found. Should be in correct sort order where we are.
 | 
					
						
							|  |  |  | 				err := scanDir(pop) | 
					
						
							|  |  |  | 				logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			dirStack = dirStack[:len(dirStack)-1] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Stream output.
 | 
					
						
							|  |  |  | 	return scanDir(opts.BaseDir) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *xlStorageDiskIDCheck) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writer) error { | 
					
						
							|  |  |  | 	if err := p.checkDiskStale(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return p.storage.WalkDir(ctx, opts, wr) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // WalkDir will traverse a directory and return all entries found.
 | 
					
						
							|  |  |  | // On success a meta cache stream will be returned, that should be closed when done.
 | 
					
						
							|  |  |  | func (client *storageRESTClient) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writer) error { | 
					
						
							|  |  |  | 	values := make(url.Values) | 
					
						
							|  |  |  | 	values.Set(storageRESTVolume, opts.Bucket) | 
					
						
							|  |  |  | 	values.Set(storageRESTDirPath, opts.BaseDir) | 
					
						
							|  |  |  | 	values.Set(storageRESTRecursive, strconv.FormatBool(opts.Recursive)) | 
					
						
							| 
									
										
										
										
											2020-12-29 02:31:00 +08:00
										 |  |  | 	values.Set(storageRESTReportNotFound, strconv.FormatBool(opts.ReportNotFound)) | 
					
						
							| 
									
										
										
										
											2020-11-19 02:44:18 +08:00
										 |  |  | 	values.Set(storageRESTPrefixFilter, opts.FilterPrefix) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 	respBody, err := client.call(ctx, storageRESTMethodWalkDir, values, nil, -1) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return waitForHTTPStream(respBody, wr) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // WalkDirHandler - remote caller to list files and folders in a requested directory path.
 | 
					
						
							|  |  |  | func (s *storageRESTServer) WalkDirHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 	if !s.IsValid(w, r) { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  | 	volume := vars[storageRESTVolume] | 
					
						
							|  |  |  | 	dirPath := vars[storageRESTDirPath] | 
					
						
							|  |  |  | 	recursive, err := strconv.ParseBool(vars[storageRESTRecursive]) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.writeErrorResponse(w, err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-12-29 02:31:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	var reportNotFound bool | 
					
						
							|  |  |  | 	if v := vars[storageRESTReportNotFound]; v != "" { | 
					
						
							|  |  |  | 		reportNotFound, err = strconv.ParseBool(v) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			s.writeErrorResponse(w, err) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 04:03:16 +08:00
										 |  |  | 	prefix := r.URL.Query().Get(storageRESTPrefixFilter) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | 	writer := streamHTTPResponse(w) | 
					
						
							| 
									
										
										
										
											2020-11-19 02:44:18 +08:00
										 |  |  | 	writer.CloseWithError(s.storage.WalkDir(r.Context(), WalkDirOptions{ | 
					
						
							| 
									
										
										
										
											2020-12-29 02:31:00 +08:00
										 |  |  | 		Bucket:         volume, | 
					
						
							|  |  |  | 		BaseDir:        dirPath, | 
					
						
							|  |  |  | 		Recursive:      recursive, | 
					
						
							|  |  |  | 		ReportNotFound: reportNotFound, | 
					
						
							|  |  |  | 		FilterPrefix:   prefix, | 
					
						
							| 
									
										
										
										
											2020-11-19 02:44:18 +08:00
										 |  |  | 	}, writer)) | 
					
						
							| 
									
										
										
										
											2020-10-29 00:18:35 +08:00
										 |  |  | } |