diff --git a/box.go b/box.go index 74307bb2..f640c049 100644 --- a/box.go +++ b/box.go @@ -62,6 +62,7 @@ func New(options Options) (*Box, error) { defaultLogWriter = io.Discard } logFactory, err := log.New(log.Options{ + Context: ctx, Options: common.PtrValueOrDefault(options.Log), Observable: needClashAPI, DefaultWriter: defaultLogWriter, @@ -142,7 +143,7 @@ func New(options Options) (*Box, error) { preServices := make(map[string]adapter.Service) postServices := make(map[string]adapter.Service) if needClashAPI { - clashServer, err := experimental.NewClashServer(router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI)) + clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI)) if err != nil { return nil, E.Cause(err, "create clash api server") } diff --git a/constant/path.go b/constant/path.go index 6ce1270c..98acacdc 100644 --- a/constant/path.go +++ b/constant/path.go @@ -3,40 +3,13 @@ package constant import ( "os" "path/filepath" - "strings" "github.com/sagernet/sing/common/rw" ) const dirName = "sing-box" -var ( - basePath string - tempPath string - resourcePaths []string -) - -func BasePath(name string) string { - if basePath == "" || strings.HasPrefix(name, "/") { - return name - } - return filepath.Join(basePath, name) -} - -func CreateTemp(pattern string) (*os.File, error) { - if tempPath == "" { - tempPath = os.TempDir() - } - return os.CreateTemp(tempPath, pattern) -} - -func SetBasePath(path string) { - basePath = path -} - -func SetTempPath(path string) { - tempPath = path -} +var resourcePaths []string func FindPath(name string) (string, bool) { name = os.ExpandEnv(name) diff --git a/experimental/clashapi.go b/experimental/clashapi.go index e71f04be..c2b5eac7 100644 --- a/experimental/clashapi.go +++ b/experimental/clashapi.go @@ -1,6 +1,7 @@ package experimental import ( + "context" "os" "github.com/sagernet/sing-box/adapter" @@ -8,7 +9,7 @@ import ( "github.com/sagernet/sing-box/option" ) -type ClashServerConstructor = func(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) +type ClashServerConstructor = func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) var clashServerConstructor ClashServerConstructor @@ -16,9 +17,9 @@ func RegisterClashServerConstructor(constructor ClashServerConstructor) { clashServerConstructor = constructor } -func NewClashServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { +func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { if clashServerConstructor == nil { return nil, os.ErrInvalid } - return clashServerConstructor(router, logFactory, options) + return clashServerConstructor(ctx, router, logFactory, options) } diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index b40076b6..4ba61d2d 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -23,6 +23,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service/filemanager" "github.com/sagernet/websocket" "github.com/go-chi/chi/v5" @@ -37,6 +38,7 @@ func init() { var _ adapter.ClashServer = (*Server)(nil) type Server struct { + ctx context.Context router adapter.Router logger log.Logger httpServer *http.Server @@ -53,10 +55,11 @@ type Server struct { externalUIDownloadDetour string } -func NewServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { +func NewServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { trafficManager := trafficontrol.NewManager() chiRouter := chi.NewRouter() server := &Server{ + ctx: ctx, router: router, logger: logFactory.NewLogger("clash-api"), httpServer: &http.Server{ @@ -82,7 +85,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options if foundPath, loaded := C.FindPath(cachePath); loaded { cachePath = foundPath } else { - cachePath = C.BasePath(cachePath) + cachePath = filemanager.BasePath(ctx, cachePath) } server.cacheFilePath = cachePath } @@ -113,7 +116,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options server.setupMetaAPI(r) }) if options.ExternalUI != "" { - server.externalUI = C.BasePath(os.ExpandEnv(options.ExternalUI)) + server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI)) chiRouter.Group(func(r chi.Router) { fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI))) r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP) diff --git a/experimental/clashapi/server_resources.go b/experimental/clashapi/server_resources.go index 91d4652d..ad36641e 100644 --- a/experimental/clashapi/server_resources.go +++ b/experimental/clashapi/server_resources.go @@ -12,11 +12,11 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service/filemanager" ) func (s *Server) checkAndDownloadExternalUI() { @@ -79,7 +79,7 @@ func (s *Server) downloadExternalUI() error { } func (s *Server) downloadZIP(name string, body io.Reader, output string) error { - tempFile, err := C.CreateTemp(name) + tempFile, err := filemanager.CreateTemp(s.ctx, name) if err != nil { return err } @@ -112,7 +112,7 @@ func (s *Server) downloadZIP(name string, body io.Reader, output string) error { return err } savePath := filepath.Join(saveDirectory, pathElements[len(pathElements)-1]) - err = downloadZIPEntry(file, savePath) + err = downloadZIPEntry(s.ctx, file, savePath) if err != nil { return err } @@ -120,8 +120,8 @@ func (s *Server) downloadZIP(name string, body io.Reader, output string) error { return nil } -func downloadZIPEntry(zipFile *zip.File, savePath string) error { - saveFile, err := os.Create(savePath) +func downloadZIPEntry(ctx context.Context, zipFile *zip.File, savePath string) error { + saveFile, err := filemanager.Create(ctx, savePath) if err != nil { return err } diff --git a/experimental/libbox/log.go b/experimental/libbox/log.go index 58d78edb..e7fa129a 100644 --- a/experimental/libbox/log.go +++ b/experimental/libbox/log.go @@ -27,3 +27,27 @@ func RedirectStderr(path string) error { stderrFile = outputFile return nil } + +func RedirectStderrAsUser(path string, uid, gid int) error { + if stats, err := os.Stat(path); err == nil && stats.Size() > 0 { + _ = os.Rename(path, path+".old") + } + outputFile, err := os.Create(path) + if err != nil { + return err + } + err = outputFile.Chown(uid, gid) + if err != nil { + outputFile.Close() + os.Remove(outputFile.Name()) + return err + } + err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd())) + if err != nil { + outputFile.Close() + os.Remove(outputFile.Name()) + return err + } + stderrFile = outputFile + return nil +} diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index fe75049c..1236c38b 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -16,6 +16,7 @@ import ( "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service/filemanager" ) type BoxService struct { @@ -30,6 +31,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box return nil, err } ctx, cancel := context.WithCancel(context.Background()) + ctx = filemanager.WithDefault(ctx, sBasePath, sTempPath, sUserID, sGroupID) instance, err := box.New(box.Options{ Context: ctx, Options: options, diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index 9a9f128a..e9caf773 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -1,17 +1,31 @@ package libbox import ( + "os" + C "github.com/sagernet/sing-box/constant" "github.com/dustin/go-humanize" ) -func SetBasePath(path string) { - C.SetBasePath(path) -} +var ( + sBasePath string + sTempPath string + sUserID int + sGroupID int +) -func SetTempPath(path string) { - C.SetTempPath(path) +func Setup(basePath string, tempPath string, userID int, groupID int) { + sBasePath = basePath + sTempPath = tempPath + sUserID = userID + sGroupID = groupID + if sUserID == -1 { + sUserID = os.Getuid() + } + if sGroupID == -1 { + sGroupID = os.Getgid() + } } func Version() string { diff --git a/include/clashapi_stub.go b/include/clashapi_stub.go index 5f7e5aac..26e199b3 100644 --- a/include/clashapi_stub.go +++ b/include/clashapi_stub.go @@ -3,6 +3,8 @@ package include import ( + "context" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/log" @@ -11,7 +13,7 @@ import ( ) func init() { - experimental.RegisterClashServerConstructor(func(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { + experimental.RegisterClashServerConstructor(func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`) }) } diff --git a/log/log.go b/log/log.go index 21f1e41d..b6b0805c 100644 --- a/log/log.go +++ b/log/log.go @@ -1,14 +1,15 @@ package log import ( + "context" "io" "os" "time" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/service/filemanager" ) type factoryWithFile struct { @@ -36,6 +37,7 @@ func (f *observableFactoryWithFile) Close() error { } type Options struct { + Context context.Context Options option.LogOptions Observable bool DefaultWriter io.Writer @@ -65,7 +67,7 @@ func New(options Options) (Factory, error) { logWriter = os.Stdout default: var err error - logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + logFile, err = filemanager.OpenFile(options.Context, logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return nil, err } diff --git a/route/router_geo_resources.go b/route/router_geo_resources.go index a72b4bad..55631724 100644 --- a/route/router_geo_resources.go +++ b/route/router_geo_resources.go @@ -18,6 +18,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/service/filemanager" ) func (r *Router) GeoIPReader() *geoip.Reader { @@ -51,7 +52,7 @@ func (r *Router) prepareGeoIPDatabase() error { geoPath = foundPath } } - geoPath = C.BasePath(geoPath) + geoPath = filemanager.BasePath(r.ctx, geoPath) if rw.FileExists(geoPath) { geoReader, codes, err := geoip.Open(geoPath) if err == nil { @@ -95,7 +96,7 @@ func (r *Router) prepareGeositeDatabase() error { geoPath = foundPath } } - geoPath = C.BasePath(geoPath) + geoPath = filemanager.BasePath(r.ctx, geoPath) if !rw.FileExists(geoPath) { r.logger.Warn("geosite database not exists: ", geoPath) var err error @@ -142,10 +143,10 @@ func (r *Router) downloadGeoIPDatabase(savePath string) error { } if parentDir := filepath.Dir(savePath); parentDir != "" { - os.MkdirAll(parentDir, 0o755) + filemanager.MkdirAll(r.ctx, parentDir, 0o755) } - saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644) + saveFile, err := filemanager.Create(r.ctx, savePath) if err != nil { return E.Cause(err, "open output file: ", downloadURL) } @@ -190,10 +191,10 @@ func (r *Router) downloadGeositeDatabase(savePath string) error { } if parentDir := filepath.Dir(savePath); parentDir != "" { - os.MkdirAll(parentDir, 0o755) + filemanager.MkdirAll(r.ctx, parentDir, 0o755) } - saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644) + saveFile, err := filemanager.Create(r.ctx, savePath) if err != nil { return E.Cause(err, "open output file: ", downloadURL) }