2015-10-22 18:37:45 -04:00
|
|
|
/*
|
2016-01-19 20:49:48 -05:00
|
|
|
* Minio Cloud Storage, (C) 2015-2016 Minio, Inc.
|
2015-10-22 18:37:45 -04: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 06:10:11 -05:00
|
|
|
"errors"
|
2015-10-22 18:37:45 -04:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2015-11-10 06:10:11 -05:00
|
|
|
"time"
|
2015-10-22 18:37:45 -04:00
|
|
|
|
|
|
|
"github.com/minio/minio-xl/pkg/probe"
|
|
|
|
)
|
|
|
|
|
2016-01-19 20:49:48 -05:00
|
|
|
func (fs Filesystem) listWorker(startReq listObjectsReq) (chan<- listWorkerReq, *probe.Error) {
|
2015-11-10 06:10:11 -05:00
|
|
|
bucket := startReq.Bucket
|
|
|
|
prefix := startReq.Prefix
|
|
|
|
marker := startReq.Marker
|
|
|
|
delimiter := startReq.Delimiter
|
2016-01-19 20:49:48 -05:00
|
|
|
quitWalker := make(chan bool)
|
2015-11-10 06:10:11 -05:00
|
|
|
reqCh := make(chan listWorkerReq)
|
|
|
|
walkerCh := make(chan ObjectMetadata)
|
|
|
|
go func() {
|
2016-01-19 20:49:48 -05: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 06:10:11 -05:00
|
|
|
filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
|
|
|
|
if path == rootPath {
|
2015-10-22 18:37:45 -04:00
|
|
|
return nil
|
|
|
|
}
|
2015-11-10 06:10:11 -05:00
|
|
|
if info.IsDir() {
|
2016-01-19 20:49:48 -05:00
|
|
|
path = path + string(os.PathSeparator)
|
2015-10-22 18:37:45 -04:00
|
|
|
}
|
2016-01-19 20:49:48 -05: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-22 18:37:45 -04:00
|
|
|
}
|
2015-11-10 06:10:11 -05:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
close(walkerCh)
|
|
|
|
}()
|
|
|
|
go func() {
|
2016-01-19 20:49:48 -05:00
|
|
|
resp := ListObjectsResult{}
|
2015-11-10 06:10:11 -05:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-time.After(10 * time.Second):
|
2016-01-19 20:49:48 -05:00
|
|
|
quitWalker <- true
|
|
|
|
timeoutReq := listObjectsReq{bucket, prefix, marker, delimiter, 0}
|
2015-11-10 06:10:11 -05:00
|
|
|
fs.timeoutReqCh <- timeoutReq
|
|
|
|
// FIXME: can there be a race such that sender on reqCh panics?
|
|
|
|
return
|
2016-01-19 20:49:48 -05:00
|
|
|
case req, ok := <-reqCh:
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
resp = ListObjectsResult{}
|
2015-11-10 06:10:11 -05:00
|
|
|
resp.Objects = make([]ObjectMetadata, 0)
|
|
|
|
resp.Prefixes = make([]string, 0)
|
|
|
|
count := 0
|
|
|
|
for object := range walkerCh {
|
2016-01-19 20:49:48 -05:00
|
|
|
if count == req.req.MaxKeys {
|
|
|
|
resp.IsTruncated = true
|
|
|
|
break
|
|
|
|
}
|
2015-11-10 06:10:11 -05:00
|
|
|
if object.Mode.IsDir() {
|
|
|
|
if delimiter == "" {
|
2016-01-19 20:49:48 -05:00
|
|
|
// Skip directories for recursive list
|
2015-11-10 06:10:11 -05:00
|
|
|
continue
|
2015-10-22 18:37:45 -04:00
|
|
|
}
|
2015-11-10 06:10:11 -05:00
|
|
|
resp.Prefixes = append(resp.Prefixes, object.Object)
|
2015-10-22 18:37:45 -04:00
|
|
|
} else {
|
2015-11-10 06:10:11 -05:00
|
|
|
resp.Objects = append(resp.Objects, object)
|
|
|
|
}
|
|
|
|
resp.NextMarker = object.Object
|
|
|
|
count++
|
2015-10-22 18:37:45 -04:00
|
|
|
}
|
2015-11-10 06:10:11 -05:00
|
|
|
req.respCh <- resp
|
2015-10-22 18:37:45 -04:00
|
|
|
}
|
2015-11-10 06:10:11 -05:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
return reqCh, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *Filesystem) startListService() *probe.Error {
|
|
|
|
listServiceReqCh := make(chan listServiceReq)
|
2016-01-19 20:49:48 -05:00
|
|
|
timeoutReqCh := make(chan listObjectsReq)
|
2015-11-10 06:10:11 -05: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-22 18:37:45 -04:00
|
|
|
}
|
2015-11-10 06:10:11 -05: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-19 20:49:48 -05:00
|
|
|
serviceReq.respCh <- ListObjectsResult{}
|
2015-11-10 06:10:11 -05:00
|
|
|
return
|
2015-10-22 18:37:45 -04:00
|
|
|
}
|
2015-11-10 06:10:11 -05:00
|
|
|
reqToListWorkerReqCh[reqStr] = listWorkerReqCh
|
2015-10-22 18:37:45 -04:00
|
|
|
}
|
2016-01-19 20:49:48 -05:00
|
|
|
respCh := make(chan ListObjectsResult)
|
2015-11-10 06:10:11 -05:00
|
|
|
listWorkerReqCh <- listWorkerReq{serviceReq.req, respCh}
|
|
|
|
resp, ok := <-respCh
|
|
|
|
if !ok {
|
2016-01-19 20:49:48 -05:00
|
|
|
serviceReq.respCh <- ListObjectsResult{}
|
2015-11-10 06:10:11 -05: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-22 18:37:45 -04:00
|
|
|
}
|
|
|
|
}
|
2015-11-10 06:10:11 -05:00
|
|
|
}()
|
|
|
|
fs.timeoutReqCh = timeoutReqCh
|
|
|
|
fs.listServiceReqCh = listServiceReqCh
|
|
|
|
return nil
|
|
|
|
}
|
2015-10-22 18:37:45 -04:00
|
|
|
|
2015-11-10 06:10:11 -05:00
|
|
|
// ListObjects -
|
2016-01-19 20:49:48 -05:00
|
|
|
func (fs Filesystem) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) {
|
2015-11-10 06:10:11 -05:00
|
|
|
fs.lock.Lock()
|
|
|
|
defer fs.lock.Unlock()
|
2015-10-22 18:37:45 -04:00
|
|
|
|
2015-11-10 06:10:11 -05:00
|
|
|
if !IsValidBucketName(bucket) {
|
2016-01-19 20:49:48 -05:00
|
|
|
return ListObjectsResult{}, probe.NewError(BucketNameInvalid{Bucket: bucket})
|
2015-11-10 06:10:11 -05: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-19 20:49:48 -05:00
|
|
|
return ListObjectsResult{}, probe.NewError(BucketNotFound{Bucket: bucket})
|
2015-10-22 18:37:45 -04:00
|
|
|
}
|
2016-01-19 20:49:48 -05:00
|
|
|
return ListObjectsResult{}, probe.NewError(e)
|
2015-10-22 18:37:45 -04:00
|
|
|
}
|
|
|
|
|
2016-01-19 20:49:48 -05:00
|
|
|
req := listObjectsReq{}
|
2015-11-10 06:10:11 -05:00
|
|
|
req.Bucket = bucket
|
2016-01-19 20:49:48 -05:00
|
|
|
req.Prefix = filepath.FromSlash(prefix)
|
|
|
|
req.Marker = filepath.FromSlash(marker)
|
|
|
|
req.Delimiter = filepath.FromSlash(delimiter)
|
|
|
|
req.MaxKeys = maxKeys
|
2015-11-10 06:10:11 -05:00
|
|
|
|
2016-01-19 20:49:48 -05:00
|
|
|
respCh := make(chan ListObjectsResult)
|
2015-11-10 06:10:11 -05:00
|
|
|
fs.listServiceReqCh <- listServiceReq{req, respCh}
|
|
|
|
resp := <-respCh
|
|
|
|
|
|
|
|
for i := 0; i < len(resp.Prefixes); i++ {
|
2016-01-19 20:49:48 -05:00
|
|
|
resp.Prefixes[i] = filepath.ToSlash(resp.Prefixes[i])
|
2015-11-10 06:10:11 -05:00
|
|
|
}
|
|
|
|
for i := 0; i < len(resp.Objects); i++ {
|
2016-01-19 20:49:48 -05:00
|
|
|
resp.Objects[i].Object = filepath.ToSlash(resp.Objects[i].Object)
|
2015-11-10 06:10:11 -05:00
|
|
|
}
|
|
|
|
if req.Delimiter == "" {
|
2016-01-19 20:49:48 -05: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 06:10:11 -05:00
|
|
|
resp.NextMarker = ""
|
2015-10-22 18:37:45 -04:00
|
|
|
}
|
2015-11-10 06:10:11 -05:00
|
|
|
return resp, nil
|
2015-10-22 18:37:45 -04:00
|
|
|
}
|