177 lines
3.8 KiB
Go
177 lines
3.8 KiB
Go
|
|
package services
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"io"
|
||
|
|
"net"
|
||
|
|
"os"
|
||
|
|
"path"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/hirochachacha/go-smb2"
|
||
|
|
)
|
||
|
|
|
||
|
|
type smbStorageBackend struct {
|
||
|
|
cfg StorageBackendConfig
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b smbStorageBackend) ID() string { return b.cfg.ID }
|
||
|
|
func (b smbStorageBackend) Type() string { return StorageBackendSMB }
|
||
|
|
|
||
|
|
func (b smbStorageBackend) Put(ctx context.Context, key string, body io.Reader, _ int64, _ string) error {
|
||
|
|
share, closer, err := b.share()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer closer()
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
remotePath := b.remotePath(key)
|
||
|
|
if err := share.MkdirAll(path.Dir(remotePath), 0o755); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
target, err := share.OpenFile(remotePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer target.Close()
|
||
|
|
_, err = io.Copy(target, body)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b smbStorageBackend) Get(ctx context.Context, key string) (StorageObject, error) {
|
||
|
|
share, closer, err := b.share()
|
||
|
|
if err != nil {
|
||
|
|
return StorageObject{}, err
|
||
|
|
}
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
closer()
|
||
|
|
return StorageObject{}, err
|
||
|
|
}
|
||
|
|
source, err := share.Open(b.remotePath(key))
|
||
|
|
if err != nil {
|
||
|
|
closer()
|
||
|
|
return StorageObject{}, err
|
||
|
|
}
|
||
|
|
stat, err := source.Stat()
|
||
|
|
if err != nil {
|
||
|
|
source.Close()
|
||
|
|
closer()
|
||
|
|
return StorageObject{}, err
|
||
|
|
}
|
||
|
|
return StorageObject{Key: key, Size: stat.Size(), ModTime: stat.ModTime(), Body: closeWith(source, closer)}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b smbStorageBackend) Delete(ctx context.Context, key string) error {
|
||
|
|
share, closer, err := b.share()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer closer()
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := share.Remove(b.remotePath(key)); err != nil && !os.IsNotExist(err) {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b smbStorageBackend) DeletePrefix(ctx context.Context, prefix string) error {
|
||
|
|
share, closer, err := b.share()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer closer()
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
err = share.RemoveAll(b.remotePath(prefix))
|
||
|
|
if err != nil && !os.IsNotExist(err) {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b smbStorageBackend) Usage(ctx context.Context) (int64, error) {
|
||
|
|
share, closer, err := b.share()
|
||
|
|
if err != nil {
|
||
|
|
return 0, err
|
||
|
|
}
|
||
|
|
defer closer()
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
return 0, err
|
||
|
|
}
|
||
|
|
return smbUsage(share, cleanRemoteRoot(b.cfg.RemotePath))
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b smbStorageBackend) Test(ctx context.Context) error {
|
||
|
|
key := ".warpbox-storage-test-" + randomID(6)
|
||
|
|
if err := b.Put(ctx, key, strings.NewReader("ok"), 2, "text/plain"); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return b.Delete(ctx, key)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b smbStorageBackend) share() (*smb2.Share, func(), error) {
|
||
|
|
conn, err := net.DialTimeout("tcp", b.cfg.Host+":"+strconv.Itoa(b.cfg.Port), 15*time.Second)
|
||
|
|
if err != nil {
|
||
|
|
return nil, nil, err
|
||
|
|
}
|
||
|
|
dialer := &smb2.Dialer{
|
||
|
|
Initiator: &smb2.NTLMInitiator{
|
||
|
|
User: b.cfg.Username,
|
||
|
|
Password: b.cfg.Password,
|
||
|
|
Domain: b.cfg.Domain,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
session, err := dialer.Dial(conn)
|
||
|
|
if err != nil {
|
||
|
|
conn.Close()
|
||
|
|
return nil, nil, err
|
||
|
|
}
|
||
|
|
share, err := session.Mount(b.cfg.Share)
|
||
|
|
if err != nil {
|
||
|
|
session.Logoff()
|
||
|
|
conn.Close()
|
||
|
|
return nil, nil, err
|
||
|
|
}
|
||
|
|
return share, func() {
|
||
|
|
share.Umount()
|
||
|
|
session.Logoff()
|
||
|
|
conn.Close()
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b smbStorageBackend) remotePath(key string) string {
|
||
|
|
return strings.TrimPrefix(path.Join(cleanRemoteRoot(b.cfg.RemotePath), cleanObjectKey(key)), "/")
|
||
|
|
}
|
||
|
|
|
||
|
|
func smbUsage(share *smb2.Share, root string) (int64, error) {
|
||
|
|
root = strings.TrimPrefix(root, "/")
|
||
|
|
entries, err := share.ReadDir(root)
|
||
|
|
if err != nil {
|
||
|
|
if os.IsNotExist(err) {
|
||
|
|
return 0, nil
|
||
|
|
}
|
||
|
|
return 0, err
|
||
|
|
}
|
||
|
|
var total int64
|
||
|
|
for _, entry := range entries {
|
||
|
|
item := path.Join(root, entry.Name())
|
||
|
|
if entry.IsDir() {
|
||
|
|
size, err := smbUsage(share, item)
|
||
|
|
if err != nil {
|
||
|
|
return 0, err
|
||
|
|
}
|
||
|
|
total += size
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
total += entry.Size()
|
||
|
|
}
|
||
|
|
return total, nil
|
||
|
|
}
|