From a39dcdba791603b8b0e84af1e67f49d453f9b951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 21 Apr 2023 16:43:06 +0800 Subject: [PATCH] Add filemanager api --- common/ntp/context.go | 2 +- {common/service => service}/context.go | 0 service/filemanager/default.go | 164 ++++++++++++++++++++++++ service/filemanager/manager.go | 84 ++++++++++++ {common/service => service}/registry.go | 0 5 files changed, 249 insertions(+), 1 deletion(-) rename {common/service => service}/context.go (100%) create mode 100644 service/filemanager/default.go create mode 100644 service/filemanager/manager.go rename {common/service => service}/registry.go (100%) diff --git a/common/ntp/context.go b/common/ntp/context.go index fff29da..6ed21f8 100644 --- a/common/ntp/context.go +++ b/common/ntp/context.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/sagernet/sing/common/service" + "github.com/sagernet/sing/service" ) func TimeFuncFromContext(ctx context.Context) func() time.Time { diff --git a/common/service/context.go b/service/context.go similarity index 100% rename from common/service/context.go rename to service/context.go diff --git a/service/filemanager/default.go b/service/filemanager/default.go new file mode 100644 index 0000000..96e7c4c --- /dev/null +++ b/service/filemanager/default.go @@ -0,0 +1,164 @@ +package filemanager + +import ( + "context" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/service" +) + +var _ Manager = (*defaultManager)(nil) + +type defaultManager struct { + basePath string + tempPath string + chown bool + userID int + groupID int +} + +func WithDefault(ctx context.Context, basePath string, tempPath string, userID int, groupID int) context.Context { + chown := userID != os.Getuid() || groupID != os.Getgid() + if tempPath == "" { + tempPath = os.TempDir() + } + return service.ContextWith[Manager](ctx, &defaultManager{ + basePath: basePath, + tempPath: tempPath, + chown: chown, + userID: userID, + groupID: groupID, + }) +} + +func (m *defaultManager) BasePath(name string) string { + if m.basePath == "" || strings.HasPrefix(name, "/") { + return name + } + return filepath.Join(m.basePath, name) +} + +func (m *defaultManager) OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) { + name = m.BasePath(name) + willCreate := flag&os.O_CREATE != 0 && !rw.FileExists(name) + file, err := os.OpenFile(name, flag, perm) + if err != nil { + return nil, err + } + if m.chown && willCreate { + err = file.Chown(m.userID, m.groupID) + if err != nil { + file.Close() + os.Remove(file.Name()) + return nil, err + } + } + return file, nil +} + +func (m *defaultManager) Create(name string) (*os.File, error) { + name = m.BasePath(name) + file, err := os.Create(name) + if err != nil { + return nil, err + } + if m.chown { + err = file.Chown(m.userID, m.groupID) + if err != nil { + file.Close() + os.Remove(file.Name()) + return nil, err + } + } + return file, nil +} + +func (m *defaultManager) CreateTemp(pattern string) (*os.File, error) { + file, err := os.CreateTemp(m.tempPath, pattern) + if err != nil { + return nil, err + } + if m.chown { + err = file.Chown(m.userID, m.groupID) + if err != nil { + file.Close() + os.Remove(file.Name()) + return nil, err + } + } + return file, nil +} + +func (m *defaultManager) Mkdir(path string, perm os.FileMode) error { + path = m.BasePath(path) + err := os.Mkdir(path, perm) + if err != nil { + return err + } + if m.chown { + err = os.Chown(path, m.userID, m.groupID) + if err != nil { + os.Remove(path) + return err + } + } + return nil +} + +func (m *defaultManager) MkdirAll(path string, perm os.FileMode) error { + path = m.BasePath(path) + dir, err := os.Stat(path) + if err == nil { + if dir.IsDir() { + return nil + } + return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} + } + + i := len(path) + for i > 0 && os.IsPathSeparator(path[i-1]) { + i-- + } + + j := i + for j > 0 && !os.IsPathSeparator(path[j-1]) { + j-- + } + + if j > 1 { + err = m.MkdirAll(fixRootDirectory(path[:j-1]), perm) + if err != nil { + return err + } + } + + err = os.Mkdir(path, perm) + if err != nil { + dir, err1 := os.Lstat(path) + if err1 == nil && dir.IsDir() { + return nil + } + return err + } + if m.chown { + err = os.Chown(path, m.userID, m.groupID) + if err != nil { + os.Remove(path) + return err + } + } + return nil +} + +func fixRootDirectory(p string) string { + if len(p) == len(`\\?\c:`) { + if os.IsPathSeparator(p[0]) && os.IsPathSeparator(p[1]) && p[2] == '?' && os.IsPathSeparator(p[3]) && p[5] == ':' { + return p + `\` + } + } + return p +} diff --git a/service/filemanager/manager.go b/service/filemanager/manager.go new file mode 100644 index 0000000..f4af5b9 --- /dev/null +++ b/service/filemanager/manager.go @@ -0,0 +1,84 @@ +package filemanager + +import ( + "context" + "os" + + "github.com/sagernet/sing/service" +) + +type Manager interface { + BasePath(name string) string + OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) + Create(name string) (*os.File, error) + CreateTemp(pattern string) (*os.File, error) + Mkdir(path string, perm os.FileMode) error + MkdirAll(path string, perm os.FileMode) error +} + +func BasePath(ctx context.Context, name string) string { + manager := service.FromContext[Manager](ctx) + if manager == nil { + return name + } + return manager.BasePath(name) +} + +func OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (*os.File, error) { + manager := service.FromContext[Manager](ctx) + if manager == nil { + return os.OpenFile(name, flag, perm) + } + return manager.OpenFile(name, flag, perm) +} + +func Create(ctx context.Context, name string) (*os.File, error) { + manager := service.FromContext[Manager](ctx) + if manager == nil { + return os.Create(name) + } + return manager.Create(name) +} + +func CreateTemp(ctx context.Context, pattern string) (*os.File, error) { + manager := service.FromContext[Manager](ctx) + if manager == nil { + return os.CreateTemp("", pattern) + } + return manager.CreateTemp(pattern) +} + +func Mkdir(ctx context.Context, path string, perm os.FileMode) error { + manager := service.FromContext[Manager](ctx) + if manager == nil { + return os.Mkdir(path, perm) + } + return manager.Mkdir(path, perm) +} + +func MkdirAll(ctx context.Context, path string, perm os.FileMode) error { + manager := service.FromContext[Manager](ctx) + if manager == nil { + return os.MkdirAll(path, perm) + } + return manager.MkdirAll(path, perm) +} + +func WriteFile(ctx context.Context, name string, data []byte, perm os.FileMode) error { + manager := service.FromContext[Manager](ctx) + if manager == nil { + return os.WriteFile(name, data, perm) + } + file, err := manager.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) + if err != nil { + return err + } + if err != nil { + return err + } + _, err = file.Write(data) + if err1 := file.Close(); err1 != nil && err == nil { + err = err1 + } + return err +} diff --git a/common/service/registry.go b/service/registry.go similarity index 100% rename from common/service/registry.go rename to service/registry.go