mirror of
https://github.com/minio/mc.git
synced 2025-11-12 01:02:26 +03:00
299 lines
6.9 KiB
Go
299 lines
6.9 KiB
Go
/*
|
|
* Minio Client (C) 2015 Minio, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this fs 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 (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"io/ioutil"
|
|
|
|
"github.com/minio-io/mc/pkg/client"
|
|
"github.com/minio-io/minio/pkg/iodine"
|
|
)
|
|
|
|
type fsClient struct {
|
|
path string
|
|
}
|
|
|
|
// New - instantiate a new fs client
|
|
func New(path string) client.Client {
|
|
if strings.TrimSpace(path) == "" {
|
|
return nil
|
|
}
|
|
return &fsClient{path: path}
|
|
}
|
|
|
|
/// Object operations
|
|
|
|
// fsStat - wrapper function to get file stat
|
|
func (f *fsClient) fsStat() (os.FileInfo, error) {
|
|
st, err := os.Lstat(filepath.Clean(f.path))
|
|
if os.IsNotExist(err) {
|
|
return nil, iodine.New(NotFound{path: f.path}, nil)
|
|
}
|
|
if err != nil {
|
|
return nil, iodine.New(err, nil)
|
|
}
|
|
return st, nil
|
|
}
|
|
|
|
// Get - download an object from bucket
|
|
func (f *fsClient) Get() (io.ReadCloser, int64, string, error) {
|
|
content, err := f.getFSMetadata()
|
|
if err != nil {
|
|
return nil, 0, "", iodine.New(err, nil)
|
|
}
|
|
if content.FileType.IsDir() {
|
|
return nil, 0, "", iodine.New(ISFolder{path: f.path}, nil)
|
|
}
|
|
body, err := os.Open(f.path)
|
|
if err != nil {
|
|
return nil, 0, "", iodine.New(err, nil)
|
|
}
|
|
h := md5.New()
|
|
// calculate md5sum
|
|
_, err = io.Copy(h, body)
|
|
if err != nil {
|
|
return nil, 0, "", iodine.New(err, nil)
|
|
}
|
|
// seek back
|
|
_, err = body.Seek(0, 0)
|
|
if err != nil {
|
|
return nil, 0, "", iodine.New(err, nil)
|
|
}
|
|
md5Str := hex.EncodeToString(h.Sum(nil))
|
|
return body, content.Size, md5Str, nil
|
|
}
|
|
|
|
// GetPartial - download a partial object from bucket
|
|
func (f *fsClient) GetPartial(offset, length int64) (io.ReadCloser, int64, string, error) {
|
|
if offset < 0 {
|
|
return nil, 0, "", iodine.New(client.InvalidRange{Offset: offset}, nil)
|
|
}
|
|
content, err := f.getFSMetadata()
|
|
if err != nil {
|
|
return nil, 0, "", iodine.New(err, nil)
|
|
}
|
|
if content.FileType.IsDir() {
|
|
return nil, 0, "", iodine.New(ISFolder{path: f.path}, nil)
|
|
}
|
|
if offset > content.Size || (offset+length-1) > content.Size {
|
|
return nil, 0, "", iodine.New(client.InvalidRange{Offset: offset}, nil)
|
|
}
|
|
body, err := os.Open(f.path)
|
|
if err != nil {
|
|
return nil, 0, "", iodine.New(err, nil)
|
|
}
|
|
_, err = io.CopyN(ioutil.Discard, body, offset)
|
|
if err != nil {
|
|
return nil, 0, "", iodine.New(err, nil)
|
|
}
|
|
h := md5.New()
|
|
// calculate md5sum
|
|
_, err = io.Copy(h, body)
|
|
if err != nil {
|
|
return nil, 0, "", iodine.New(err, nil)
|
|
}
|
|
// seek back
|
|
_, err = body.Seek(0, 0)
|
|
if err != nil {
|
|
return nil, 0, "", iodine.New(err, nil)
|
|
}
|
|
md5Str := hex.EncodeToString(h.Sum(nil))
|
|
return body, length, md5Str, nil
|
|
}
|
|
|
|
// List - list files and folders
|
|
func (f *fsClient) List() <-chan client.ContentOnChannel {
|
|
contentCh := make(chan client.ContentOnChannel)
|
|
go f.list(contentCh)
|
|
return contentCh
|
|
}
|
|
|
|
func (f *fsClient) list(contentCh chan client.ContentOnChannel) {
|
|
defer close(contentCh)
|
|
dir, err := os.Open(f.path)
|
|
if err != nil {
|
|
contentCh <- client.ContentOnChannel{
|
|
Content: nil,
|
|
Err: iodine.New(err, nil),
|
|
}
|
|
}
|
|
fi, err := os.Lstat(f.path)
|
|
if err != nil {
|
|
contentCh <- client.ContentOnChannel{
|
|
Content: nil,
|
|
Err: iodine.New(err, nil),
|
|
}
|
|
}
|
|
defer dir.Close()
|
|
switch fi.Mode().IsDir() {
|
|
case true:
|
|
// do not use ioutil.ReadDir(), since it tries to sort its
|
|
// output at our scale we are expecting that to slow down
|
|
// instead we take raw output and provide it back to the user
|
|
// - such a thing is helpful when we are moving in and out
|
|
// large quantities of files
|
|
files, err := dir.Readdir(-1)
|
|
if err != nil {
|
|
contentCh <- client.ContentOnChannel{
|
|
Content: nil,
|
|
Err: iodine.New(err, nil),
|
|
}
|
|
}
|
|
for _, file := range files {
|
|
content := &client.Content{
|
|
Name: file.Name(),
|
|
Time: file.ModTime(),
|
|
Size: file.Size(),
|
|
FileType: file.Mode(),
|
|
}
|
|
contentCh <- client.ContentOnChannel{
|
|
Content: content,
|
|
Err: nil,
|
|
}
|
|
}
|
|
default:
|
|
content := &client.Content{
|
|
Name: dir.Name(),
|
|
Time: fi.ModTime(),
|
|
Size: fi.Size(),
|
|
FileType: fi.Mode(),
|
|
}
|
|
contentCh <- client.ContentOnChannel{
|
|
Content: content,
|
|
Err: nil,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ListRecursive - list files and folders recursively
|
|
func (f *fsClient) ListRecursive() <-chan client.ContentOnChannel {
|
|
contentCh := make(chan client.ContentOnChannel)
|
|
go f.listRecursive(contentCh)
|
|
return contentCh
|
|
}
|
|
|
|
func (f *fsClient) listRecursive(contentCh chan client.ContentOnChannel) {
|
|
defer close(contentCh)
|
|
visitFS := func(fp string, fi os.FileInfo, err error) error {
|
|
if err != nil {
|
|
if os.IsPermission(err) { // skip inaccessible files
|
|
return nil
|
|
}
|
|
return err // fatal
|
|
}
|
|
content := &client.Content{
|
|
Name: fp,
|
|
Time: fi.ModTime(),
|
|
Size: fi.Size(),
|
|
FileType: fi.Mode(),
|
|
}
|
|
contentCh <- client.ContentOnChannel{
|
|
Content: content,
|
|
Err: nil,
|
|
}
|
|
return nil
|
|
}
|
|
err := filepath.Walk(f.path, visitFS)
|
|
if err != nil {
|
|
contentCh <- client.ContentOnChannel{
|
|
Content: nil,
|
|
Err: iodine.New(err, nil),
|
|
}
|
|
}
|
|
}
|
|
|
|
// isValidBucketACL - is acl a valid ACL?
|
|
func isValidBucketACL(acl string) bool {
|
|
switch acl {
|
|
case "private":
|
|
fallthrough
|
|
case "public-read":
|
|
fallthrough
|
|
case "public-read-write":
|
|
fallthrough
|
|
case "":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// aclToPerm - convert acl to filesystem mode
|
|
func aclToPerm(acl string) os.FileMode {
|
|
switch acl {
|
|
case "private":
|
|
return os.FileMode(0700)
|
|
case "public-read":
|
|
return os.FileMode(0500)
|
|
case "public-read-write":
|
|
return os.FileMode(0777)
|
|
default:
|
|
return os.FileMode(0700)
|
|
}
|
|
}
|
|
|
|
// PutBucket - create a new bucket
|
|
func (f *fsClient) PutBucket() error {
|
|
err := os.MkdirAll(f.path, 0775)
|
|
if err != nil {
|
|
return iodine.New(err, nil)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PutBucket - create a new bucket
|
|
func (f *fsClient) PutBucketACL(acl string) error {
|
|
if !isValidBucketACL(acl) {
|
|
return iodine.New(errors.New("invalid acl"), nil)
|
|
}
|
|
err := os.MkdirAll(f.path, aclToPerm(acl))
|
|
if err != nil {
|
|
return iodine.New(err, nil)
|
|
}
|
|
err = os.Chmod(f.path, aclToPerm(acl))
|
|
if err != nil {
|
|
return iodine.New(err, nil)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getFSMetadata -
|
|
func (f *fsClient) getFSMetadata() (content *client.Content, err error) {
|
|
st, err := f.fsStat()
|
|
if err != nil {
|
|
return nil, iodine.New(err, nil)
|
|
}
|
|
content = new(client.Content)
|
|
content.Name = st.Name()
|
|
content.Size = st.Size()
|
|
content.Time = st.ModTime()
|
|
return content, nil
|
|
}
|
|
|
|
// Stat - get metadata from path
|
|
func (f *fsClient) Stat() (content *client.Content, err error) {
|
|
return f.getFSMetadata()
|
|
}
|