Compare commits

...

4 Commits

Author SHA1 Message Date
KirCute 525d004887 . 2025-06-12 15:46:47 +08:00
KirCute af49100134 Merge branch 'main' into feat/smb-server 2025-04-14 11:42:11 +08:00
KirCute 3338c018b0 fix some bugs 2025-03-28 22:56:23 +08:00
KirCute 5f12e75584 feat(smb-server): smb server support 2025-03-28 17:54:14 +08:00
12 changed files with 1046 additions and 0 deletions
+26
View File
@@ -14,6 +14,7 @@ import (
"time"
ftpserver "github.com/KirCute/ftpserverlib-pasvportmap"
smb2 "github.com/KirCute/go-smb2-alist/server"
"github.com/KirCute/sftpd-alist"
"github.com/alist-org/alist/v3/cmd/flags"
"github.com/alist-org/alist/v3/internal/bootstrap"
@@ -157,6 +158,22 @@ the address is defined in config file`,
}()
}
}
var smbServer *smb2.Server
if conf.Conf.SMB.Listen != "" && conf.Conf.SMB.Enable {
var err error
smbServer, err = server.NewSmbServer()
if err != nil {
utils.Log.Fatalf("failed to start smb server: %s", err.Error())
} else {
utils.Log.Infof("start smb server on %s", conf.Conf.SMB.Listen)
go func() {
err = smbServer.Serve(conf.Conf.SMB.Listen)
if err != nil {
utils.Log.Fatalf("problem smb server listening: %s", err.Error())
}
}()
}
}
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 1 second.
quit := make(chan os.Signal, 1)
@@ -217,6 +234,15 @@ the address is defined in config file`,
}
}()
}
if conf.Conf.SMB.Listen != "" && conf.Conf.SMB.Enable && smbServer != nil {
wg.Add(1)
go func() {
defer wg.Done()
if err := smbServer.Shutdown(); err != nil {
utils.Log.Fatal("SMB server shutdown err: ", err)
}
}()
}
wg.Wait()
utils.Log.Println("Server exit")
},
+1
View File
@@ -4,6 +4,7 @@ go 1.23.4
require (
github.com/KirCute/ftpserverlib-pasvportmap v1.25.0
github.com/KirCute/go-smb2-alist v0.0.3
github.com/KirCute/sftpd-alist v0.0.12
github.com/ProtonMail/go-crypto v1.0.0
github.com/SheltonZhu/115driver v1.0.34
+2
View File
@@ -30,6 +30,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/KirCute/ftpserverlib-pasvportmap v1.25.0 h1:ikwCzeqoqN6wvBHOB9OI6dde/jbV7EoTMpUcxtYl5Po=
github.com/KirCute/ftpserverlib-pasvportmap v1.25.0/go.mod h1:v0NgMtKDDi/6CM6r4P+daCljCW3eO9yS+Z+pZDTKo1E=
github.com/KirCute/go-smb2-alist v0.0.3 h1:NgL2F6cb0iW4GWa+rR75Bh8Z4TjDoDnt4mBCnhiSqck=
github.com/KirCute/go-smb2-alist v0.0.3/go.mod h1:BzUuyzAaJbS4Eb733i9SBxIllZQYDZe5UBsGpkoP6U4=
github.com/KirCute/sftpd-alist v0.0.12 h1:GNVM5QLbQLAfXP4wGUlXFA2IO6fVek0n0IsGnOuISdg=
github.com/KirCute/sftpd-alist v0.0.12/go.mod h1:2wNK7yyW2XfjyJq10OY6xB4COLac64hOwfV6clDJn6s=
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE=
+26
View File
@@ -93,6 +93,19 @@ type SFTP struct {
Listen string `json:"listen" env:"LISTEN"`
}
type SMB struct {
Enable bool `json:"enable" env:"ENABLE"`
Listen string `json:"listen" env:"LISTEN"`
MaxIOReads int `json:"max_io_reads" env:"MAX_IO_READS"`
MaxIOWrites int `json:"max_io_writes" env:"MAX_IO_WRITES"`
TargetSPN string `json:"target_spn" env:"TARGET_SPN"`
NbDomain string `json:"nb_domain" env:"NB_DOMAIN"`
NbName string `json:"nb_name" env:"NB_NAME"`
DnsName string `json:"dns_name" env:"DNS_NAME"`
DnsDomain string `json:"dns_domain" env:"DNS_DOMAIN"`
ShareName string `json:"share_name" env:"SHARE_NAME"`
}
type Config struct {
Force bool `json:"force" env:"FORCE"`
SiteURL string `json:"site_url" env:"SITE_URL"`
@@ -115,6 +128,7 @@ type Config struct {
S3 S3 `json:"s3" envPrefix:"S3_"`
FTP FTP `json:"ftp" envPrefix:"FTP_"`
SFTP SFTP `json:"sftp" envPrefix:"SFTP_"`
SMB SMB `json:"smb" envPrefix:"SMB_"`
LastLaunchedVersion string `json:"last_launched_version"`
}
@@ -212,6 +226,18 @@ func DefaultConfig() *Config {
Enable: false,
Listen: ":5222",
},
SMB: SMB{
Enable: false,
Listen: ":5445",
MaxIOReads: 4,
MaxIOWrites: 4,
TargetSPN: "",
NbDomain: "",
NbName: "",
DnsName: "",
DnsDomain: "",
ShareName: "AList",
},
LastLaunchedVersion: "",
}
}
+10
View File
@@ -46,6 +46,8 @@ type User struct {
// 11: ftp/sftp write
// 12: can read archives
// 13: can decompress archives
// 14: smb read
// 15: smb write
Permission int32 `json:"permission"`
OtpSecret string `json:"-"`
SsoID string `json:"sso_id"` // unique by sso platform
@@ -137,6 +139,14 @@ func (u *User) CanDecompress() bool {
return (u.Permission>>13)&1 == 1
}
func (u *User) CanSMBAccess() bool {
return (u.Permission>>14)&1 == 1
}
func (u *User) CanSMBManage() bool {
return (u.Permission>>15)&1 == 1
}
func (u *User) JoinPath(reqPath string) (string, error) {
return utils.JoinBasePath(u.BasePath, reqPath)
}
+75
View File
@@ -0,0 +1,75 @@
package server
import (
"context"
"github.com/alist-org/alist/v3/internal/errs"
smb2 "github.com/KirCute/go-smb2-alist/server"
"github.com/KirCute/go-smb2-alist/vfs"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/server/smb"
)
func NewSmbServer() (*smb2.Server, error) {
srv := smb2.NewServer(
&smb2.ServerConfig{
MaxIOReads: conf.Conf.SMB.MaxIOReads,
MaxIOWrites: conf.Conf.SMB.MaxIOWrites,
Xatrrs: false,
},
&smb2.NTLMAuthenticator{
TargetSPN: conf.Conf.SMB.TargetSPN,
NbDomain: conf.Conf.SMB.NbDomain,
NbName: conf.Conf.SMB.NbName,
DnsName: conf.Conf.SMB.DnsName,
DnsDomain: conf.Conf.SMB.DnsDomain,
UserPassword: GetUserPassword,
AllowGuest: AllowGuest,
},
GetUserFileSystem,
)
return srv, nil
}
func AllowGuest() bool {
guest, err := op.GetGuest()
return err == nil && !guest.Disabled
}
func GetUserPassword(user string) (string, bool) {
u, err := op.GetUserByName(user)
if err != nil {
return "", false
}
if u.IsGuest() || !u.CanSMBAccess() {
return "", false
}
return u.PwdHash[:16], true
}
func GetUserFileSystem(user string) (map[string]vfs.VFSFileSystem, error) {
var userObj *model.User
var err error
if user == "" {
userObj, err = op.GetGuest()
} else {
userObj, err = op.GetUserByName(user)
}
if err != nil {
return nil, err
}
if !userObj.CanSMBAccess() { // For allow guest case
return nil, errs.PermissionDenied
}
ctx := context.Background()
ctx = context.WithValue(ctx, "user", userObj)
fs, err := smb.NewVFS(ctx)
if err != nil {
return nil, err
}
return map[string]vfs.VFSFileSystem{
conf.Conf.SMB.ShareName: fs,
}, nil
}
+8
View File
@@ -0,0 +1,8 @@
package smb
import "github.com/pkg/errors"
var (
ErrBadHandle = errors.New("bad handle")
TestError = errors.New("test error")
)
+79
View File
@@ -0,0 +1,79 @@
package smb
import (
"context"
stdpath "path"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/fs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/server/common"
"github.com/pkg/errors"
)
func Mkdir(ctx context.Context, path string) (model.Obj, error) {
user := ctx.Value("user").(*model.User)
reqPath, err := user.JoinPath(path)
if err != nil {
return nil, err
}
if !user.CanWrite() || !user.CanSMBManage() {
meta, err := op.GetNearestMeta(stdpath.Dir(reqPath))
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
return nil, err
}
}
if !common.CanWrite(meta, reqPath) {
return nil, errs.PermissionDenied
}
}
if err = fs.MakeDir(ctx, reqPath); err != nil {
return nil, err
}
return fs.Get(ctx, reqPath, &fs.GetArgs{})
}
func Remove(ctx context.Context, path string) error {
user := ctx.Value("user").(*model.User)
if !user.CanRemove() || !user.CanSMBManage() {
return errs.PermissionDenied
}
reqPath, err := user.JoinPath(path)
if err != nil {
return err
}
return fs.Remove(ctx, reqPath)
}
func Rename(ctx context.Context, oldPath, newPath string) error {
user := ctx.Value("user").(*model.User)
srcPath, err := user.JoinPath(oldPath)
if err != nil {
return err
}
dstPath, err := user.JoinPath(newPath)
if err != nil {
return err
}
srcDir, srcBase := stdpath.Split(srcPath)
dstDir, dstBase := stdpath.Split(dstPath)
if srcDir == dstDir {
if !user.CanRename() || !user.CanSMBManage() {
return errs.PermissionDenied
}
return fs.Rename(ctx, srcPath, dstBase)
} else {
if !user.CanSMBManage() || !user.CanMove() || (srcBase != dstBase && !user.CanRename()) {
return errs.PermissionDenied
}
if err = fs.Move(ctx, srcPath, dstDir); err != nil {
return err
}
if srcBase != dstBase {
return fs.Rename(ctx, stdpath.Join(dstDir, srcBase), dstBase)
}
return nil
}
}
+183
View File
@@ -0,0 +1,183 @@
package smb
import (
"context"
"hash/fnv"
"os"
"github.com/KirCute/go-smb2-alist/vfs"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/fs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/internal/stream"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
"github.com/pkg/errors"
)
type readingFile struct {
path string
obj model.Obj
s stream.SStreamReadAtSeeker
dirRead bool
}
func newRead(path string, obj model.Obj) *readingFile {
return &readingFile{path: utils.FixAndCleanPath(path), obj: obj, s: nil}
}
func (f *readingFile) initDownload(ctx context.Context) error {
if f.s != nil {
return nil
}
if f.obj.IsDir() {
return errs.NotFile
}
user := ctx.Value("user").(*model.User)
reqPath, err := user.JoinPath(f.path)
if err != nil {
return err
}
meta, err := op.GetNearestMeta(reqPath)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
return err
}
}
ctx = context.WithValue(ctx, "meta", meta)
if !common.CanAccess(user, meta, reqPath, "") {
return errs.PermissionDenied
}
link, obj, err := fs.Link(ctx, reqPath, model.LinkArgs{})
if err != nil {
return err
}
f.obj = obj
fileStream := stream.FileStream{
Obj: obj,
Ctx: ctx,
}
ss, err := stream.NewSeekableStream(fileStream, link)
if err != nil {
return err
}
reader, err := stream.NewReadAtSeeker(ss, 0)
if err != nil {
_ = ss.Close()
return err
}
f.s = reader
return nil
}
func (f *readingFile) Close() error {
if f.s == nil {
return nil
}
return f.s.Close()
}
func FsGet(ctx context.Context, path string) (model.Obj, error) {
user := ctx.Value("user").(*model.User)
reqPath, err := user.JoinPath(path)
if err != nil {
return nil, err
}
meta, err := op.GetNearestMeta(reqPath)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
return nil, err
}
}
ctx = context.WithValue(ctx, "meta", meta)
if !common.CanAccess(user, meta, reqPath, "") {
return nil, errs.PermissionDenied
}
return fs.Get(ctx, reqPath, &fs.GetArgs{})
}
func List(ctx context.Context, path string) ([]vfs.DirInfo, error) {
user := ctx.Value("user").(*model.User)
reqPath, err := user.JoinPath(path)
if err != nil {
return nil, err
}
meta, err := op.GetNearestMeta(reqPath)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
return nil, err
}
}
ctx = context.WithValue(ctx, "meta", meta)
if !common.CanAccess(user, meta, reqPath, "") {
return nil, errs.PermissionDenied
}
self, err := fs.Get(ctx, reqPath, &fs.GetArgs{})
if err != nil {
return nil, err
}
attr, err := MakeObjAttribute(self)
if err != nil {
return nil, err
}
objs, err := fs.List(ctx, reqPath, &fs.ListArgs{})
if err != nil {
return nil, err
}
ret := make([]vfs.DirInfo, 0, len(objs)+2)
ret = append(ret, vfs.DirInfo{Name: ".", Attributes: *attr})
if utils.FixAndCleanPath(user.BasePath) != reqPath {
ret = append(ret, vfs.DirInfo{Name: "..", Attributes: *attr})
}
for _, obj := range objs {
a, e := MakeObjAttribute(obj)
if e != nil {
continue
}
ret = append(ret, vfs.DirInfo{Name: obj.GetName(), Attributes: *a})
}
return ret, nil
}
func MakeFileAttribute(file *os.File) (*vfs.Attributes, error) {
a := &vfs.Attributes{}
a.SetInodeNumber(uint64(file.Fd())) // 用fd没什么依据,纯随便给了个不会冲突的整数
stat, err := file.Stat()
if err != nil {
return nil, errors.WithStack(err)
}
a.SetSizeBytes(uint64(stat.Size()))
a.SetUnixMode(uint32(stat.Mode()))
a.SetPermissions(vfs.NewPermissionsFromMode(uint32(stat.Mode().Perm())))
a.SetLastDataModificationTime(stat.ModTime())
if stat.IsDir() {
a.SetFileType(vfs.FileTypeDirectory)
} else {
a.SetFileType(vfs.FileTypeRegularFile)
}
return a, nil
}
func MakeObjAttribute(obj model.Obj) (*vfs.Attributes, error) {
a := &vfs.Attributes{}
h := fnv.New64()
_, err := h.Write([]byte(obj.GetPath()))
if err != nil {
return nil, errors.WithStack(err)
}
a.SetInodeNumber(h.Sum64())
a.SetSizeBytes(uint64(obj.GetSize()))
a.SetLastDataModificationTime(obj.ModTime())
if obj.IsDir() {
a.SetFileType(vfs.FileTypeDirectory)
a.SetUnixMode(0755)
a.SetPermissions(vfs.NewPermissionsFromMode(0755))
} else {
a.SetFileType(vfs.FileTypeRegularFile)
a.SetUnixMode(0644)
a.SetPermissions(vfs.NewPermissionsFromMode(0644))
}
return a, nil
}
+112
View File
@@ -0,0 +1,112 @@
package smb
import (
"context"
"hash/fnv"
"io"
"net/http"
"os"
stdpath "path"
"time"
"github.com/KirCute/go-smb2-alist/vfs"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/fs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/internal/stream"
"github.com/alist-org/alist/v3/internal/task"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
"github.com/pkg/errors"
)
type writingFile struct {
path string
f *os.File
}
func newUpload(ctx context.Context, path string) (*writingFile, error) {
user := ctx.Value("user").(*model.User)
reqPath, err := user.JoinPath(path)
if err != nil {
return nil, errors.WithStack(err)
}
err = uploadAuth(ctx, reqPath)
if err != nil {
return nil, errors.WithStack(err)
}
tmpFile, err := os.CreateTemp(conf.Conf.TempDir, "file-*")
if err != nil {
return nil, errors.WithStack(err)
}
return &writingFile{f: tmpFile, path: utils.FixAndCleanPath(path)}, nil
}
func uploadAuth(ctx context.Context, path string) error {
user := ctx.Value("user").(*model.User)
meta, err := op.GetNearestMeta(stdpath.Dir(path))
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
return errors.WithStack(err)
}
}
if !(common.CanAccess(user, meta, path, "") &&
((user.CanSMBManage() && user.CanWrite()) || common.CanWrite(meta, stdpath.Dir(path)))) {
return errors.WithStack(errs.PermissionDenied)
}
return nil
}
func (f *writingFile) close(ctx context.Context) (task.TaskExtensionInfo, error) {
user := ctx.Value("user").(*model.User)
reqPath, err := user.JoinPath(f.path)
if err != nil {
return nil, errors.WithStack(err)
}
dir, name := stdpath.Split(reqPath)
size, err := f.f.Seek(0, io.SeekCurrent)
if err != nil {
return nil, errors.WithStack(err)
}
if _, err := f.f.Seek(0, io.SeekStart); err != nil {
return nil, errors.WithStack(err)
}
arr := make([]byte, 512)
if _, err := f.f.Read(arr); err != nil {
return nil, errors.WithStack(err)
}
contentType := http.DetectContentType(arr)
if _, err := f.f.Seek(0, io.SeekStart); err != nil {
return nil, errors.WithStack(err)
}
_ = fs.Remove(ctx, reqPath)
s := &stream.FileStream{
Obj: &model.Object{
Name: name,
Size: size,
Modified: time.Now(),
},
Mimetype: contentType,
WebPutAsTask: true,
}
s.SetTmpFile(f.f)
return fs.PutAsTask(ctx, dir, s)
}
func MakeTaskAttribute(tsk task.TaskExtensionInfo) (*vfs.Attributes, error) {
a := &vfs.Attributes{}
h := fnv.New64()
_, err := h.Write([]byte(tsk.GetID()))
if err != nil {
return nil, errors.WithStack(err)
}
a.SetInodeNumber(h.Sum64())
a.SetSizeBytes(uint64(tsk.GetTotalBytes()))
a.SetLastDataModificationTime(*tsk.GetStartTime())
a.SetFileType(vfs.FileTypeRegularFile)
a.SetUnixMode(0644)
a.SetPermissions(vfs.NewPermissionsFromMode(0644))
return a, nil
}
+353
View File
@@ -0,0 +1,353 @@
package smb
import (
"context"
"io"
"os"
stdpath "path"
"sync"
"github.com/KirCute/go-smb2-alist/vfs"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/stream"
"github.com/alist-org/alist/v3/internal/task"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/xhofe/tache"
)
type VFS struct {
ctx context.Context
openedPath map[vfs.VfsHandle]*readingFile
openedTmpFile map[vfs.VfsHandle]*writingFile
uploadingFile map[string]task.TaskExtensionInfo
nextHandle vfs.VfsHandle
mutex sync.RWMutex
}
func NewVFS(ctx context.Context) (vfs.VFSFileSystem, error) {
fs := &VFS{
ctx: ctx,
openedPath: make(map[vfs.VfsHandle]*readingFile),
openedTmpFile: make(map[vfs.VfsHandle]*writingFile),
uploadingFile: make(map[string]task.TaskExtensionInfo),
nextHandle: vfs.VfsHandle(1),
mutex: sync.RWMutex{},
}
root, err := FsGet(ctx, ".")
if err != nil {
return nil, err
}
fs.openedPath[vfs.VfsHandle(0)] = newRead(".", root)
user := ctx.Value("user").(*model.User)
log.Infof("User %s logged in the SMB endpoint", user.Username)
return &VFSWrapper{fs: fs, user: user.Username}, nil
}
func (fs *VFS) GetAttr(handle vfs.VfsHandle) (*vfs.Attributes, error) {
fs.mutex.RLock()
defer fs.mutex.RUnlock()
if tmp, ok := fs.openedTmpFile[handle]; ok {
return MakeFileAttribute(tmp.f)
}
if r, ok := fs.openedPath[handle]; ok {
return MakeObjAttribute(r.obj)
}
return nil, ErrBadHandle
}
func (fs *VFS) Flush(handle vfs.VfsHandle) error {
fs.mutex.RLock()
tmp, ok := fs.openedTmpFile[handle]
fs.mutex.RUnlock()
if ok {
return tmp.f.Sync()
}
return nil
}
func (fs *VFS) newUpload(path string) (vfs.VfsHandle, error) {
f, err := newUpload(fs.ctx, path)
if err != nil {
return 0, errors.WithStack(err)
}
fs.mutex.Lock()
defer fs.mutex.Unlock()
handle := fs.nextHandle
fs.nextHandle++
fs.openedTmpFile[handle] = f
return handle, nil
}
func (fs *VFS) Open(path string, flags int, _ int) (vfs.VfsHandle, error) {
if (flags & os.O_APPEND) != 0 {
return 0, errors.WithStack(errs.NotSupport)
}
path = utils.FixAndCleanPath(path)
fs.mutex.RLock()
for h, tmp := range fs.openedTmpFile {
if tmp.path == path {
fs.mutex.RUnlock()
return h, nil
}
}
t, ok := fs.uploadingFile[path]
fs.mutex.RUnlock()
if ok {
if t.GetState() == tache.StateSucceeded {
fs.mutex.Lock()
delete(fs.uploadingFile, path)
fs.mutex.Unlock()
} else if t.GetState() == tache.StateFailed || t.GetState() == tache.StateCanceled {
fs.mutex.Lock()
delete(fs.uploadingFile, path)
fs.mutex.Unlock()
return 0, errors.WithStack(os.ErrNotExist)
} else {
return 0, errors.WithStack(os.ErrPermission)
}
}
obj, err := FsGet(fs.ctx, path)
if errors.Is(err, errs.ObjectNotFound) {
if (flags&os.O_RDWR) == 0 || (flags&os.O_CREATE) == 0 {
return 0, errors.WithStack(os.ErrNotExist)
}
return fs.newUpload(path)
} else if err == nil {
if (flags&os.O_CREATE) != 0 && (flags&os.O_EXCL) != 0 {
return 0, errors.WithStack(os.ErrExist)
}
if (flags & os.O_RDWR) != 0 {
if (flags&os.O_CREATE) != 0 && (flags&os.O_TRUNC) != 0 {
return fs.newUpload(path)
}
return 0, errors.WithStack(errs.NotSupport)
}
fs.mutex.Lock()
defer fs.mutex.Unlock()
handle := fs.nextHandle
fs.nextHandle++
fs.openedPath[handle] = newRead(path, obj)
return handle, nil
}
return 0, errors.WithStack(err)
}
func (fs *VFS) Close(handle vfs.VfsHandle) error {
if handle == 0 {
return ErrBadHandle
}
fs.mutex.RLock()
tmp, ok := fs.openedTmpFile[handle]
fs.mutex.RUnlock()
if ok {
tsk, err := tmp.close(fs.ctx)
fs.mutex.Lock()
delete(fs.openedTmpFile, handle)
if tsk != nil {
fs.uploadingFile[tmp.path] = tsk
}
fs.mutex.Unlock()
return errors.WithStack(err)
}
fs.mutex.RLock()
r, ok := fs.openedPath[handle]
fs.mutex.RUnlock()
if ok {
err := r.Close()
fs.mutex.Lock()
delete(fs.openedPath, handle)
fs.mutex.Unlock()
return errors.WithStack(err)
}
return ErrBadHandle
}
func (fs *VFS) Lookup(handle vfs.VfsHandle, name string) (*vfs.Attributes, error) {
fs.mutex.RLock()
var p *readingFile
var ok bool
if p, ok = fs.openedPath[handle]; !ok {
fs.mutex.RUnlock()
return nil, ErrBadHandle
}
if name == "/" {
fs.mutex.RUnlock()
return MakeObjAttribute(p.obj)
}
path := utils.FixAndCleanPath(stdpath.Join(p.path, name))
for _, tmp := range fs.openedTmpFile {
if tmp.path == path {
fs.mutex.RUnlock()
return MakeFileAttribute(tmp.f)
}
}
t, ok := fs.uploadingFile[path]
fs.mutex.RUnlock()
if ok {
if t.GetState() == tache.StateSucceeded {
fs.mutex.Lock()
delete(fs.uploadingFile, path)
fs.mutex.Unlock()
} else if t.GetState() == tache.StateFailed || t.GetState() == tache.StateCanceled {
fs.mutex.Lock()
delete(fs.uploadingFile, path)
fs.mutex.Unlock()
return nil, errors.WithStack(os.ErrNotExist)
} else {
return MakeTaskAttribute(t)
}
}
obj, err := FsGet(fs.ctx, path)
if err != nil {
return nil, errors.WithStack(err)
}
return MakeObjAttribute(obj)
}
func (fs *VFS) Mkdir(path string, _ int) (*vfs.Attributes, error) {
obj, err := Mkdir(fs.ctx, path)
if err != nil {
return nil, err
}
return MakeObjAttribute(obj)
}
func (fs *VFS) Read(handle vfs.VfsHandle, buf []byte, offset uint64, _ int) (n int, err error) {
fs.mutex.RLock()
if tmp, ok := fs.openedTmpFile[handle]; ok {
fs.mutex.RUnlock()
n, err = tmp.f.ReadAt(buf, int64(offset))
} else if r, ok := fs.openedPath[handle]; ok {
fs.mutex.RUnlock()
err = r.initDownload(fs.ctx)
if err == nil {
n, err = r.s.ReadAt(buf, int64(offset))
}
} else {
fs.mutex.RUnlock()
err = ErrBadHandle
}
_ = stream.ClientDownloadLimit.WaitN(fs.ctx, n)
return
}
func (fs *VFS) Write(handle vfs.VfsHandle, buf []byte, offset uint64, _ int) (n int, err error) {
fs.mutex.RLock()
tmp, ok := fs.openedTmpFile[handle]
fs.mutex.RUnlock()
if ok {
n, err = tmp.f.WriteAt(buf, int64(offset))
} else {
err = ErrBadHandle
}
_ = stream.ClientUploadLimit.WaitN(fs.ctx, n)
return
}
func (fs *VFS) OpenDir(path string) (vfs.VfsHandle, error) {
obj, err := FsGet(fs.ctx, path)
if err != nil {
return 0, err
}
fs.mutex.Lock()
defer fs.mutex.Unlock()
handle := fs.nextHandle
fs.nextHandle++
fs.openedPath[handle] = newRead(path, obj)
return handle, nil
}
func (fs *VFS) ReadDir(handle vfs.VfsHandle, pos int, _ int) ([]vfs.DirInfo, error) {
fs.mutex.RLock()
p, ok := fs.openedPath[handle]
fs.mutex.RUnlock()
if !ok {
return nil, ErrBadHandle
}
if pos == 0 && p.dirRead {
return nil, io.EOF
}
p.dirRead = true
return List(fs.ctx, p.path)
}
func (fs *VFS) Unlink(handle vfs.VfsHandle) error {
if handle == 0 {
return ErrBadHandle
}
fs.mutex.RLock()
r, ok := fs.openedPath[handle]
fs.mutex.RUnlock()
if ok {
_ = r.Close()
fs.mutex.Lock()
delete(fs.openedPath, handle)
fs.mutex.Unlock()
return Remove(fs.ctx, r.path)
}
return ErrBadHandle
}
func (fs *VFS) Rename(handle vfs.VfsHandle, to string, _ int) error {
if handle == 0 {
return ErrBadHandle
}
fs.mutex.RLock()
r, ok := fs.openedPath[handle]
fs.mutex.RUnlock()
if ok {
_ = r.Close()
fs.mutex.Lock()
delete(fs.openedPath, handle)
fs.mutex.Unlock()
return Rename(fs.ctx, r.path, to)
}
return ErrBadHandle
}
func (fs *VFS) StatFS(vfs.VfsHandle) (*vfs.FSAttributes, error) {
return &vfs.FSAttributes{}, nil
}
func (fs *VFS) SetAttr(vfs.VfsHandle, *vfs.Attributes) (*vfs.Attributes, error) {
return nil, nil
}
func (fs *VFS) FSync(vfs.VfsHandle) error {
return nil
}
func (fs *VFS) Readlink(vfs.VfsHandle) (string, error) {
return "", errs.NotSupport
}
func (fs *VFS) Truncate(vfs.VfsHandle, uint64) error {
return errs.NotSupport
}
func (fs *VFS) Symlink(vfs.VfsHandle, string, int) (*vfs.Attributes, error) {
return nil, errs.NotSupport
}
func (fs *VFS) Link(vfs.VfsNode, vfs.VfsNode, string) (*vfs.Attributes, error) {
return nil, nil
}
func (fs *VFS) Listxattr(vfs.VfsHandle) ([]string, error) {
return []string{}, nil
}
func (fs *VFS) Getxattr(vfs.VfsHandle, string, []byte) (int, error) {
return 0, errs.NotSupport
}
func (fs *VFS) Setxattr(vfs.VfsHandle, string, []byte) error {
return errs.NotSupport
}
func (fs *VFS) Removexattr(vfs.VfsHandle, string) error {
return errs.NotSupport
}
+171
View File
@@ -0,0 +1,171 @@
package smb
import (
"errors"
"io"
"github.com/KirCute/go-smb2-alist/vfs"
"github.com/alist-org/alist/v3/internal/errs"
log "github.com/sirupsen/logrus"
)
type VFSWrapper struct {
fs *VFS
user string
}
func (v *VFSWrapper) GetAttr(handle vfs.VfsHandle) (*vfs.Attributes, error) {
a, e := v.fs.GetAttr(handle)
if e != nil {
log.Errorf("SMB: %s called GetAttr(%d) and got error: %+v", v.user, handle, e)
}
return a, e
}
func (v *VFSWrapper) SetAttr(handle vfs.VfsHandle, attributes *vfs.Attributes) (*vfs.Attributes, error) {
return v.fs.SetAttr(handle, attributes)
}
func (v *VFSWrapper) StatFS(handle vfs.VfsHandle) (*vfs.FSAttributes, error) {
return v.fs.StatFS(handle)
}
func (v *VFSWrapper) FSync(handle vfs.VfsHandle) error {
return v.fs.FSync(handle)
}
func (v *VFSWrapper) Flush(handle vfs.VfsHandle) error {
return v.fs.Flush(handle)
}
func (v *VFSWrapper) Open(s string, i int, i2 int) (vfs.VfsHandle, error) {
h, e := v.fs.Open(s, i, i2)
if e == nil {
log.Infof("SMB: %s called Open(%s, %o) and got result: %d", v.user, s, i, h)
} else {
log.Errorf("SMB: %s called Open(%s, %o) and got error: %+v", v.user, s, i, e)
}
return h, e
}
func (v *VFSWrapper) Close(handle vfs.VfsHandle) error {
e := v.fs.Close(handle)
if e == nil {
log.Infof("SMB: %s called Close(%d)", v.user, handle)
} else if errors.Is(e, ErrBadHandle) {
log.Warnf("SMB: %s called Close(%d) but duplicate", v.user, handle)
} else {
log.Errorf("SMB: %s called Close(%d) and got error: %+v", v.user, handle, e)
}
return e
}
func (v *VFSWrapper) Lookup(handle vfs.VfsHandle, s string) (*vfs.Attributes, error) {
a, e := v.fs.Lookup(handle, s)
if errors.Is(e, errs.ObjectNotFound) {
log.Warnf("SMB: %s called Lookup(%d, %s) but not found", v.user, handle, s)
} else if e != nil {
log.Errorf("SMB: %s called Lookup(%d, %s) and got error: %+v", v.user, handle, s, e)
}
return a, e
}
func (v *VFSWrapper) Mkdir(s string, i int) (*vfs.Attributes, error) {
a, e := v.fs.Mkdir(s, i)
if e == nil {
log.Infof("SMB: %s called Mkdir(%s, %d)", v.user, s, i)
} else {
log.Errorf("SMB: %s called Mkdir(%s, %d) and got error: %+v", v.user, s, i, e)
}
return a, e
}
func (v *VFSWrapper) Read(handle vfs.VfsHandle, bytes []byte, u uint64, i int) (int, error) {
n, err := v.fs.Read(handle, bytes, u, i)
if err != nil {
log.Errorf("SMB: %s called Read(%d, len=%d, offset=%d), read %d bytes and got error %+v", v.user, handle, len(bytes), u, n, err)
} else {
log.Infof("SMB: %s called Read(%d, len=%d, offset=%d), read %d bytes", v.user, handle, len(bytes), u, n)
}
return n, err
}
func (v *VFSWrapper) Write(handle vfs.VfsHandle, bytes []byte, u uint64, i int) (int, error) {
n, err := v.fs.Write(handle, bytes, u, i)
if err != nil {
log.Errorf("SMB: %s called Write(%d, len=%d, offset=%d, mode=%d), write %d bytes and got error %+v", v.user, handle, len(bytes), u, i, n, err)
} else {
log.Infof("SMB: %s called Write(%d, len=%d, offset=%d, mode=%d), write %d bytes", v.user, handle, len(bytes), u, i, n)
}
return n, err
}
func (v *VFSWrapper) OpenDir(s string) (vfs.VfsHandle, error) {
h, e := v.fs.OpenDir(s)
if e == nil {
log.Infof("SMB: %s called OpenDir(%s) and got result: %d", v.user, s, h)
} else {
log.Errorf("SMB: %s called OpenDir(%s) and got error: %+v", v.user, s, e)
}
return h, e
}
func (v *VFSWrapper) ReadDir(handle vfs.VfsHandle, i int, i2 int) ([]vfs.DirInfo, error) {
info, e := v.fs.ReadDir(handle, i, i2)
if e != nil && !errors.Is(e, io.EOF) {
log.Errorf("SMB: %s called ReadDir(%d, %d, %d) and got error: %+v", v.user, handle, i, i2, e)
}
return info, e
}
func (v *VFSWrapper) Readlink(handle vfs.VfsHandle) (string, error) {
return v.fs.Readlink(handle)
}
func (v *VFSWrapper) Unlink(handle vfs.VfsHandle) error {
e := v.fs.Unlink(handle)
if e == nil {
log.Infof("SMB: %s called Unlink(%d)", v.user, handle)
} else {
log.Errorf("SMB: %s called Unlink(%d) and got error: %+v", v.user, handle, e)
}
return e
}
func (v *VFSWrapper) Truncate(handle vfs.VfsHandle, u uint64) error {
return v.fs.Truncate(handle, u)
}
func (v *VFSWrapper) Rename(handle vfs.VfsHandle, s string, i int) error {
e := v.fs.Rename(handle, s, i)
if e == nil {
log.Infof("SMB: %s called Rename(%d, %s, %d)", v.user, handle, s, i)
} else {
log.Errorf("SMB: %s called Rename(%d, %s, %d) and got error: %+v", v.user, handle, s, i, e)
}
return e
}
func (v *VFSWrapper) Symlink(handle vfs.VfsHandle, s string, i int) (*vfs.Attributes, error) {
return v.fs.Symlink(handle, s, i)
}
func (v *VFSWrapper) Link(node vfs.VfsNode, node2 vfs.VfsNode, s string) (*vfs.Attributes, error) {
return v.fs.Link(node, node2, s)
}
func (v *VFSWrapper) Listxattr(handle vfs.VfsHandle) ([]string, error) {
return v.fs.Listxattr(handle)
}
func (v *VFSWrapper) Getxattr(handle vfs.VfsHandle, s string, bytes []byte) (int, error) {
return v.fs.Getxattr(handle, s, bytes)
}
func (v *VFSWrapper) Setxattr(handle vfs.VfsHandle, s string, bytes []byte) error {
return v.fs.Setxattr(handle, s, bytes)
}
func (v *VFSWrapper) Removexattr(handle vfs.VfsHandle, s string) error {
return v.fs.Removexattr(handle, s)
}