|
|
@ -18,118 +18,12 @@ package main |
|
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
import ( |
|
|
|
"errors" |
|
|
|
"errors" |
|
|
|
"io" |
|
|
|
|
|
|
|
"os" |
|
|
|
"os" |
|
|
|
"path/filepath" |
|
|
|
"path/filepath" |
|
|
|
"sort" |
|
|
|
|
|
|
|
"strings" |
|
|
|
"strings" |
|
|
|
"time" |
|
|
|
"time" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// DirEntry - directory entry
|
|
|
|
|
|
|
|
type DirEntry struct { |
|
|
|
|
|
|
|
Name string |
|
|
|
|
|
|
|
Size int64 |
|
|
|
|
|
|
|
Mode os.FileMode |
|
|
|
|
|
|
|
ModTime time.Time |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// IsDir - returns true if DirEntry is a directory
|
|
|
|
|
|
|
|
func (entry DirEntry) IsDir() bool { |
|
|
|
|
|
|
|
return entry.Mode.IsDir() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// IsSymlink - returns true if DirEntry is a symbolic link
|
|
|
|
|
|
|
|
func (entry DirEntry) IsSymlink() bool { |
|
|
|
|
|
|
|
return entry.Mode&os.ModeSymlink == os.ModeSymlink |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// IsRegular - returns true if DirEntry is a regular file
|
|
|
|
|
|
|
|
func (entry DirEntry) IsRegular() bool { |
|
|
|
|
|
|
|
return entry.Mode.IsRegular() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// sort interface for DirEntry slice
|
|
|
|
|
|
|
|
type byEntryName []DirEntry |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (f byEntryName) Len() int { return len(f) } |
|
|
|
|
|
|
|
func (f byEntryName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } |
|
|
|
|
|
|
|
func (f byEntryName) Less(i, j int) bool { return f[i].Name < f[j].Name } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func filteredReaddir(dirname string, filter func(DirEntry) bool, appendPath bool) ([]DirEntry, error) { |
|
|
|
|
|
|
|
result := []DirEntry{} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
d, err := os.Open(dirname) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return result, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
defer d.Close() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for { |
|
|
|
|
|
|
|
fis, err := d.Readdir(1000) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
if err == io.EOF { |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, fi := range fis { |
|
|
|
|
|
|
|
name := fi.Name() |
|
|
|
|
|
|
|
if appendPath { |
|
|
|
|
|
|
|
name = filepath.Join(dirname, name) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if fi.IsDir() { |
|
|
|
|
|
|
|
name += string(os.PathSeparator) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
entry := DirEntry{Name: name, Size: fi.Size(), Mode: fi.Mode(), ModTime: fi.ModTime()} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if filter == nil || filter(entry) { |
|
|
|
|
|
|
|
result = append(result, entry) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sort.Sort(byEntryName(result)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func filteredReaddirnames(dirname string, filter func(string) bool) ([]string, error) { |
|
|
|
|
|
|
|
result := []string{} |
|
|
|
|
|
|
|
d, err := os.Open(dirname) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return result, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
defer d.Close() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for { |
|
|
|
|
|
|
|
names, err := d.Readdirnames(1000) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
if err == io.EOF { |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, name := range names { |
|
|
|
|
|
|
|
if filter == nil || filter(name) { |
|
|
|
|
|
|
|
result = append(result, name) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sort.Strings(result) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func scanMultipartDir(bucketDir, prefixPath, markerPath, uploadIDMarker string, recursive bool) multipartObjectInfoChannel { |
|
|
|
func scanMultipartDir(bucketDir, prefixPath, markerPath, uploadIDMarker string, recursive bool) multipartObjectInfoChannel { |
|
|
|
objectInfoCh := make(chan multipartObjectInfo, listObjectsLimit) |
|
|
|
objectInfoCh := make(chan multipartObjectInfo, listObjectsLimit) |
|
|
|
timeoutCh := make(chan struct{}, 1) |
|
|
|
timeoutCh := make(chan struct{}, 1) |
|
|
@ -218,39 +112,49 @@ func scanMultipartDir(bucketDir, prefixPath, markerPath, uploadIDMarker string, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for { |
|
|
|
for { |
|
|
|
entries, err := filteredReaddir(scanDir, |
|
|
|
dirents, err := scandir(scanDir, |
|
|
|
func(entry DirEntry) bool { |
|
|
|
func(dirent fsDirent) bool { |
|
|
|
if entry.IsDir() || (entry.IsRegular() && strings.HasSuffix(entry.Name, uploadIDSuffix)) { |
|
|
|
if dirent.IsDir() || (dirent.IsRegular() && strings.HasSuffix(dirent.name, uploadIDSuffix)) { |
|
|
|
return strings.HasPrefix(entry.Name, prefixPath) && entry.Name > markerPath |
|
|
|
return strings.HasPrefix(dirent.name, prefixPath) && dirent.name > markerPath |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return false |
|
|
|
return false |
|
|
|
}, |
|
|
|
}, |
|
|
|
true) |
|
|
|
false) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
send(multipartObjectInfo{Err: err}) |
|
|
|
send(multipartObjectInfo{Err: err}) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var entry DirEntry |
|
|
|
var dirent fsDirent |
|
|
|
for len(entries) > 0 { |
|
|
|
for len(dirents) > 0 { |
|
|
|
entry, entries = entries[0], entries[1:] |
|
|
|
dirent, dirents = dirents[0], dirents[1:] |
|
|
|
|
|
|
|
|
|
|
|
if entry.IsRegular() { |
|
|
|
if dirent.IsRegular() { |
|
|
|
// Handle uploadid file
|
|
|
|
// Handle uploadid file
|
|
|
|
name := strings.Replace(filepath.Dir(entry.Name), bucketDir, "", 1) |
|
|
|
name := strings.Replace(filepath.Dir(dirent.name), bucketDir, "", 1) |
|
|
|
if name == "" { |
|
|
|
if name == "" { |
|
|
|
// This should not happen ie uploadid file should not be in bucket directory
|
|
|
|
// This should not happen ie uploadid file should not be in bucket directory
|
|
|
|
send(multipartObjectInfo{Err: errors.New("corrupted meta data")}) |
|
|
|
send(multipartObjectInfo{Err: errors.New("corrupted meta data")}) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
uploadID := strings.Split(filepath.Base(entry.Name), uploadIDSuffix)[0] |
|
|
|
uploadID := strings.Split(filepath.Base(dirent.name), uploadIDSuffix)[0] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// In some OS modTime is empty and use os.Stat() to fill missing values
|
|
|
|
|
|
|
|
if dirent.modTime.IsZero() { |
|
|
|
|
|
|
|
if fi, e := os.Stat(dirent.name); e == nil { |
|
|
|
|
|
|
|
dirent.modTime = fi.ModTime() |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
send(multipartObjectInfo{Err: e}) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
objInfo := multipartObjectInfo{ |
|
|
|
objInfo := multipartObjectInfo{ |
|
|
|
Name: name, |
|
|
|
Name: name, |
|
|
|
UploadID: uploadID, |
|
|
|
UploadID: uploadID, |
|
|
|
ModifiedTime: entry.ModTime, |
|
|
|
ModifiedTime: dirent.modTime, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if !send(objInfo) { |
|
|
|
if !send(objInfo) { |
|
|
@ -260,21 +164,21 @@ func scanMultipartDir(bucketDir, prefixPath, markerPath, uploadIDMarker string, |
|
|
|
continue |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
subentries, err := filteredReaddir(entry.Name, |
|
|
|
subDirents, err := scandir(dirent.name, |
|
|
|
func(entry DirEntry) bool { |
|
|
|
func(dirent fsDirent) bool { |
|
|
|
return entry.IsDir() || (entry.IsRegular() && strings.HasSuffix(entry.Name, uploadIDSuffix)) |
|
|
|
return dirent.IsDir() || (dirent.IsRegular() && strings.HasSuffix(dirent.name, uploadIDSuffix)) |
|
|
|
}, |
|
|
|
}, |
|
|
|
true) |
|
|
|
false) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
send(multipartObjectInfo{Err: err}) |
|
|
|
send(multipartObjectInfo{Err: err}) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
subDirFound := false |
|
|
|
subDirFound := false |
|
|
|
uploadIDEntries := []DirEntry{} |
|
|
|
uploadIDDirents := []fsDirent{} |
|
|
|
// If subentries has a directory, then current entry needs to be sent
|
|
|
|
// If subDirents has a directory, then current dirent needs to be sent
|
|
|
|
for _, subentry := range subentries { |
|
|
|
for _, subdirent := range subDirents { |
|
|
|
if subentry.IsDir() { |
|
|
|
if subdirent.IsDir() { |
|
|
|
subDirFound = true |
|
|
|
subDirFound = true |
|
|
|
|
|
|
|
|
|
|
|
if recursive { |
|
|
|
if recursive { |
|
|
@ -282,15 +186,26 @@ func scanMultipartDir(bucketDir, prefixPath, markerPath, uploadIDMarker string, |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if !recursive && subentry.IsRegular() { |
|
|
|
if !recursive && subdirent.IsRegular() { |
|
|
|
uploadIDEntries = append(uploadIDEntries, subentry) |
|
|
|
uploadIDDirents = append(uploadIDDirents, subdirent) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if subDirFound || len(subentries) == 0 { |
|
|
|
// send directory only for non-recursive listing
|
|
|
|
|
|
|
|
if !recursive && (subDirFound || len(subDirents) == 0) { |
|
|
|
|
|
|
|
// In some OS modTime is empty and use os.Stat() to fill missing values
|
|
|
|
|
|
|
|
if dirent.modTime.IsZero() { |
|
|
|
|
|
|
|
if fi, e := os.Stat(dirent.name); e == nil { |
|
|
|
|
|
|
|
dirent.modTime = fi.ModTime() |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
send(multipartObjectInfo{Err: e}) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
objInfo := multipartObjectInfo{ |
|
|
|
objInfo := multipartObjectInfo{ |
|
|
|
Name: strings.Replace(entry.Name, bucketDir, "", 1), |
|
|
|
Name: strings.Replace(dirent.name, bucketDir, "", 1), |
|
|
|
ModifiedTime: entry.ModTime, |
|
|
|
ModifiedTime: dirent.modTime, |
|
|
|
IsDir: true, |
|
|
|
IsDir: true, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -300,9 +215,9 @@ func scanMultipartDir(bucketDir, prefixPath, markerPath, uploadIDMarker string, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if recursive { |
|
|
|
if recursive { |
|
|
|
entries = append(subentries, entries...) |
|
|
|
dirents = append(subDirents, dirents...) |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
entries = append(uploadIDEntries, entries...) |
|
|
|
dirents = append(uploadIDDirents, dirents...) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|