mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2026-10-10 13:03:09 +08:00
Compare commits
4 Commits
beta
...
feat/smb-server
| Author | SHA1 | Date | |
|---|---|---|---|
| 525d004887 | |||
| af49100134 | |||
| 3338c018b0 | |||
| 5f12e75584 |
@@ -14,6 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
ftpserver "github.com/KirCute/ftpserverlib-pasvportmap"
|
ftpserver "github.com/KirCute/ftpserverlib-pasvportmap"
|
||||||
|
smb2 "github.com/KirCute/go-smb2-alist/server"
|
||||||
"github.com/KirCute/sftpd-alist"
|
"github.com/KirCute/sftpd-alist"
|
||||||
"github.com/alist-org/alist/v3/cmd/flags"
|
"github.com/alist-org/alist/v3/cmd/flags"
|
||||||
"github.com/alist-org/alist/v3/internal/bootstrap"
|
"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
|
// Wait for interrupt signal to gracefully shutdown the server with
|
||||||
// a timeout of 1 second.
|
// a timeout of 1 second.
|
||||||
quit := make(chan os.Signal, 1)
|
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()
|
wg.Wait()
|
||||||
utils.Log.Println("Server exit")
|
utils.Log.Println("Server exit")
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ go 1.23.4
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/KirCute/ftpserverlib-pasvportmap v1.25.0
|
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/KirCute/sftpd-alist v0.0.12
|
||||||
github.com/ProtonMail/go-crypto v1.0.0
|
github.com/ProtonMail/go-crypto v1.0.0
|
||||||
github.com/SheltonZhu/115driver v1.0.34
|
github.com/SheltonZhu/115driver v1.0.34
|
||||||
|
|||||||
@@ -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/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 h1:ikwCzeqoqN6wvBHOB9OI6dde/jbV7EoTMpUcxtYl5Po=
|
||||||
github.com/KirCute/ftpserverlib-pasvportmap v1.25.0/go.mod h1:v0NgMtKDDi/6CM6r4P+daCljCW3eO9yS+Z+pZDTKo1E=
|
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 h1:GNVM5QLbQLAfXP4wGUlXFA2IO6fVek0n0IsGnOuISdg=
|
||||||
github.com/KirCute/sftpd-alist v0.0.12/go.mod h1:2wNK7yyW2XfjyJq10OY6xB4COLac64hOwfV6clDJn6s=
|
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=
|
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE=
|
||||||
|
|||||||
@@ -93,6 +93,19 @@ type SFTP struct {
|
|||||||
Listen string `json:"listen" env:"LISTEN"`
|
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 {
|
type Config struct {
|
||||||
Force bool `json:"force" env:"FORCE"`
|
Force bool `json:"force" env:"FORCE"`
|
||||||
SiteURL string `json:"site_url" env:"SITE_URL"`
|
SiteURL string `json:"site_url" env:"SITE_URL"`
|
||||||
@@ -115,6 +128,7 @@ type Config struct {
|
|||||||
S3 S3 `json:"s3" envPrefix:"S3_"`
|
S3 S3 `json:"s3" envPrefix:"S3_"`
|
||||||
FTP FTP `json:"ftp" envPrefix:"FTP_"`
|
FTP FTP `json:"ftp" envPrefix:"FTP_"`
|
||||||
SFTP SFTP `json:"sftp" envPrefix:"SFTP_"`
|
SFTP SFTP `json:"sftp" envPrefix:"SFTP_"`
|
||||||
|
SMB SMB `json:"smb" envPrefix:"SMB_"`
|
||||||
LastLaunchedVersion string `json:"last_launched_version"`
|
LastLaunchedVersion string `json:"last_launched_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +226,18 @@ func DefaultConfig() *Config {
|
|||||||
Enable: false,
|
Enable: false,
|
||||||
Listen: ":5222",
|
Listen: ":5222",
|
||||||
},
|
},
|
||||||
|
SMB: SMB{
|
||||||
|
Enable: false,
|
||||||
|
Listen: ":5445",
|
||||||
|
MaxIOReads: 4,
|
||||||
|
MaxIOWrites: 4,
|
||||||
|
TargetSPN: "",
|
||||||
|
NbDomain: "",
|
||||||
|
NbName: "",
|
||||||
|
DnsName: "",
|
||||||
|
DnsDomain: "",
|
||||||
|
ShareName: "AList",
|
||||||
|
},
|
||||||
LastLaunchedVersion: "",
|
LastLaunchedVersion: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ type User struct {
|
|||||||
// 11: ftp/sftp write
|
// 11: ftp/sftp write
|
||||||
// 12: can read archives
|
// 12: can read archives
|
||||||
// 13: can decompress archives
|
// 13: can decompress archives
|
||||||
|
// 14: smb read
|
||||||
|
// 15: smb write
|
||||||
Permission int32 `json:"permission"`
|
Permission int32 `json:"permission"`
|
||||||
OtpSecret string `json:"-"`
|
OtpSecret string `json:"-"`
|
||||||
SsoID string `json:"sso_id"` // unique by sso platform
|
SsoID string `json:"sso_id"` // unique by sso platform
|
||||||
@@ -137,6 +139,14 @@ func (u *User) CanDecompress() bool {
|
|||||||
return (u.Permission>>13)&1 == 1
|
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) {
|
func (u *User) JoinPath(reqPath string) (string, error) {
|
||||||
return utils.JoinBasePath(u.BasePath, reqPath)
|
return utils.JoinBasePath(u.BasePath, reqPath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package smb
|
||||||
|
|
||||||
|
import "github.com/pkg/errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadHandle = errors.New("bad handle")
|
||||||
|
TestError = errors.New("test error")
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user