| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  |  * Minio Cloud Storage, (C) 2015-2016 Minio, Inc. | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * 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 fs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/minio/minio-xl/pkg/probe" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | func (fs Filesystem) listWorker(startReq listObjectsReq) (chan<- listWorkerReq, *probe.Error) { | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	bucket := startReq.Bucket | 
					
						
							|  |  |  | 	prefix := startReq.Prefix | 
					
						
							|  |  |  | 	marker := startReq.Marker | 
					
						
							|  |  |  | 	delimiter := startReq.Delimiter | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 	quitWalker := make(chan bool) | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	reqCh := make(chan listWorkerReq) | 
					
						
							|  |  |  | 	walkerCh := make(chan ObjectMetadata) | 
					
						
							|  |  |  | 	go func() { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 		var rootPath string | 
					
						
							|  |  |  | 		bucketPath := filepath.Join(fs.path, bucket) | 
					
						
							|  |  |  | 		trimBucketPathPrefix := bucketPath + string(os.PathSeparator) | 
					
						
							|  |  |  | 		prefixPath := trimBucketPathPrefix + prefix | 
					
						
							|  |  |  | 		st, err := os.Stat(prefixPath) | 
					
						
							|  |  |  | 		if err != nil && os.IsNotExist(err) { | 
					
						
							|  |  |  | 			rootPath = bucketPath | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			if st.IsDir() && !strings.HasSuffix(prefix, delimiter) { | 
					
						
							|  |  |  | 				rootPath = bucketPath | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				rootPath = prefixPath | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 		filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error { | 
					
						
							|  |  |  | 			if path == rootPath { | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 			if info.IsDir() { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 				path = path + string(os.PathSeparator) | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 			objectName := strings.TrimPrefix(path, trimBucketPathPrefix) | 
					
						
							|  |  |  | 			if strings.HasPrefix(objectName, prefix) { | 
					
						
							|  |  |  | 				if marker >= objectName { | 
					
						
							|  |  |  | 					return nil | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				object := ObjectMetadata{ | 
					
						
							|  |  |  | 					Object:  objectName, | 
					
						
							|  |  |  | 					Created: info.ModTime(), | 
					
						
							|  |  |  | 					Mode:    info.Mode(), | 
					
						
							|  |  |  | 					Size:    info.Size(), | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				select { | 
					
						
							|  |  |  | 				case walkerCh <- object: | 
					
						
							|  |  |  | 					// Do nothing
 | 
					
						
							|  |  |  | 				case <-quitWalker: | 
					
						
							|  |  |  | 					// Returning error ends the Walk()
 | 
					
						
							|  |  |  | 					return errors.New("Ending") | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if delimiter != "" && info.IsDir() { | 
					
						
							|  |  |  | 					return filepath.SkipDir | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 			return nil | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		close(walkerCh) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 	go func() { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 		resp := ListObjectsResult{} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 		for { | 
					
						
							|  |  |  | 			select { | 
					
						
							|  |  |  | 			case <-time.After(10 * time.Second): | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 				quitWalker <- true | 
					
						
							|  |  |  | 				timeoutReq := listObjectsReq{bucket, prefix, marker, delimiter, 0} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 				fs.timeoutReqCh <- timeoutReq | 
					
						
							|  |  |  | 				// FIXME: can there be a race such that sender on reqCh panics?
 | 
					
						
							|  |  |  | 				return | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 			case req, ok := <-reqCh: | 
					
						
							|  |  |  | 				if !ok { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				resp = ListObjectsResult{} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 				resp.Objects = make([]ObjectMetadata, 0) | 
					
						
							|  |  |  | 				resp.Prefixes = make([]string, 0) | 
					
						
							|  |  |  | 				count := 0 | 
					
						
							|  |  |  | 				for object := range walkerCh { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 					if count == req.req.MaxKeys { | 
					
						
							|  |  |  | 						resp.IsTruncated = true | 
					
						
							|  |  |  | 						break | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 					if object.Mode.IsDir() { | 
					
						
							|  |  |  | 						if delimiter == "" { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 							// Skip directories for recursive list
 | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 							continue | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 						} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 						resp.Prefixes = append(resp.Prefixes, object.Object) | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 					} else { | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 						resp.Objects = append(resp.Objects, object) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					resp.NextMarker = object.Object | 
					
						
							|  |  |  | 					count++ | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 				req.respCh <- resp | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 	return reqCh, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (fs *Filesystem) startListService() *probe.Error { | 
					
						
							|  |  |  | 	listServiceReqCh := make(chan listServiceReq) | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 	timeoutReqCh := make(chan listObjectsReq) | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	reqToListWorkerReqCh := make(map[string](chan<- listWorkerReq)) | 
					
						
							|  |  |  | 	reqToStr := func(bucket string, prefix string, marker string, delimiter string) string { | 
					
						
							|  |  |  | 		return strings.Join([]string{bucket, prefix, marker, delimiter}, ":") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			select { | 
					
						
							|  |  |  | 			case timeoutReq := <-timeoutReqCh: | 
					
						
							|  |  |  | 				reqStr := reqToStr(timeoutReq.Bucket, timeoutReq.Prefix, timeoutReq.Marker, timeoutReq.Delimiter) | 
					
						
							|  |  |  | 				listWorkerReqCh, ok := reqToListWorkerReqCh[reqStr] | 
					
						
							|  |  |  | 				if ok { | 
					
						
							|  |  |  | 					close(listWorkerReqCh) | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 				delete(reqToListWorkerReqCh, reqStr) | 
					
						
							|  |  |  | 			case serviceReq := <-listServiceReqCh: | 
					
						
							|  |  |  | 				reqStr := reqToStr(serviceReq.req.Bucket, serviceReq.req.Prefix, serviceReq.req.Marker, serviceReq.req.Delimiter) | 
					
						
							|  |  |  | 				listWorkerReqCh, ok := reqToListWorkerReqCh[reqStr] | 
					
						
							|  |  |  | 				if !ok { | 
					
						
							|  |  |  | 					var err *probe.Error | 
					
						
							|  |  |  | 					listWorkerReqCh, err = fs.listWorker(serviceReq.req) | 
					
						
							|  |  |  | 					if err != nil { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 						serviceReq.respCh <- ListObjectsResult{} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 						return | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 					} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 					reqToListWorkerReqCh[reqStr] = listWorkerReqCh | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 				respCh := make(chan ListObjectsResult) | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 				listWorkerReqCh <- listWorkerReq{serviceReq.req, respCh} | 
					
						
							|  |  |  | 				resp, ok := <-respCh | 
					
						
							|  |  |  | 				if !ok { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 					serviceReq.respCh <- ListObjectsResult{} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				delete(reqToListWorkerReqCh, reqStr) | 
					
						
							|  |  |  | 				if !resp.IsTruncated { | 
					
						
							|  |  |  | 					close(listWorkerReqCh) | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					reqStr = reqToStr(serviceReq.req.Bucket, serviceReq.req.Prefix, resp.NextMarker, serviceReq.req.Delimiter) | 
					
						
							|  |  |  | 					reqToListWorkerReqCh[reqStr] = listWorkerReqCh | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				serviceReq.respCh <- resp | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	}() | 
					
						
							|  |  |  | 	fs.timeoutReqCh = timeoutReqCh | 
					
						
							|  |  |  | 	fs.listServiceReqCh = listServiceReqCh | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | // ListObjects -
 | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | func (fs Filesystem) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) { | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	fs.lock.Lock() | 
					
						
							|  |  |  | 	defer fs.lock.Unlock() | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	if !IsValidBucketName(bucket) { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 		return ListObjectsResult{}, probe.NewError(BucketNameInvalid{Bucket: bucket}) | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket = fs.denormalizeBucket(bucket) | 
					
						
							|  |  |  | 	rootPrefix := filepath.Join(fs.path, bucket) | 
					
						
							|  |  |  | 	// check bucket exists
 | 
					
						
							|  |  |  | 	if _, e := os.Stat(rootPrefix); e != nil { | 
					
						
							|  |  |  | 		if os.IsNotExist(e) { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 			return ListObjectsResult{}, probe.NewError(BucketNotFound{Bucket: bucket}) | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 		return ListObjectsResult{}, probe.NewError(e) | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 	req := listObjectsReq{} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	req.Bucket = bucket | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 	req.Prefix = filepath.FromSlash(prefix) | 
					
						
							|  |  |  | 	req.Marker = filepath.FromSlash(marker) | 
					
						
							|  |  |  | 	req.Delimiter = filepath.FromSlash(delimiter) | 
					
						
							|  |  |  | 	req.MaxKeys = maxKeys | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 	respCh := make(chan ListObjectsResult) | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	fs.listServiceReqCh <- listServiceReq{req, respCh} | 
					
						
							|  |  |  | 	resp := <-respCh | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < len(resp.Prefixes); i++ { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 		resp.Prefixes[i] = filepath.ToSlash(resp.Prefixes[i]) | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	for i := 0; i < len(resp.Objects); i++ { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 		resp.Objects[i].Object = filepath.ToSlash(resp.Objects[i].Object) | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if req.Delimiter == "" { | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 		// This element is set only if you have delimiter set.
 | 
					
						
							|  |  |  | 		// If response does not include the NextMaker and it is
 | 
					
						
							|  |  |  | 		// truncated, you can use the value of the last Key in the
 | 
					
						
							|  |  |  | 		// response as the marker in the subsequent request to get the
 | 
					
						
							|  |  |  | 		// next set of object keys.
 | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 		resp.NextMarker = "" | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	return resp, nil | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | } |