From f505a47bca890ef5eedb2cbbb48c0e7fde8909b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 13 Jul 2026 15:42:06 +0800 Subject: [PATCH] platform: Fix daemon data --- experimental/boxdd/crash_report.go | 18 ++-- experimental/boxdd/desktop_service.go | 97 ++++++++++++------- experimental/boxdd/managed.go | 16 ++-- experimental/boxdd/oom_report.go | 18 ++-- experimental/boxdd/report.go | 24 +++-- experimental/boxdd/server.go | 133 ++++++++++++++------------ experimental/boxdd/snapshot.go | 59 +++++++++--- 7 files changed, 217 insertions(+), 148 deletions(-) diff --git a/experimental/boxdd/crash_report.go b/experimental/boxdd/crash_report.go index eb128b1d3..390dee1d5 100644 --- a/experimental/boxdd/crash_report.go +++ b/experimental/boxdd/crash_report.go @@ -14,8 +14,7 @@ const crashReportsDirectoryName = "crash_reports" var crashReportFileOrder = []string{metadataFileName, nativeLogFileName, goLogFileName, configSnapshotFileName} func (s *desktopService) ListCrashReports(ctx context.Context, empty *emptypb.Empty) (*CrashReportList, error) { - reportsDirectory := filepath.Join(workingDirectory, crashReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, crashReportsDirectoryName) if err != nil { return nil, err } @@ -48,8 +47,7 @@ func (s *desktopService) ListCrashReports(ctx context.Context, empty *emptypb.Em } func (s *desktopService) ReadCrashReport(ctx context.Context, request *CrashReportRequest) (*CrashReportContent, error) { - reportsDirectory := filepath.Join(workingDirectory, crashReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, crashReportsDirectoryName) if err != nil { return nil, err } @@ -75,8 +73,7 @@ func (s *desktopService) ReadCrashReport(ctx context.Context, request *CrashRepo } func (s *desktopService) MarkCrashReportRead(ctx context.Context, request *CrashReportRequest) (*emptypb.Empty, error) { - reportsDirectory := filepath.Join(workingDirectory, crashReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, crashReportsDirectoryName) if err != nil { return nil, err } @@ -92,8 +89,7 @@ func (s *desktopService) MarkCrashReportRead(ctx context.Context, request *Crash } func (s *desktopService) ExportCrashReport(ctx context.Context, request *CrashReportExportRequest) (*CrashReportArchive, error) { - reportsDirectory := filepath.Join(workingDirectory, crashReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, crashReportsDirectoryName) if err != nil { return nil, err } @@ -101,8 +97,7 @@ func (s *desktopService) ExportCrashReport(ctx context.Context, request *CrashRe } func (s *desktopService) DeleteCrashReport(ctx context.Context, request *CrashReportRequest) (*emptypb.Empty, error) { - reportsDirectory := filepath.Join(workingDirectory, crashReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, crashReportsDirectoryName) if err != nil { return nil, err } @@ -118,8 +113,7 @@ func (s *desktopService) DeleteCrashReport(ctx context.Context, request *CrashRe } func (s *desktopService) DeleteAllCrashReports(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { - reportsDirectory := filepath.Join(workingDirectory, crashReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, crashReportsDirectoryName) if err != nil { return nil, err } diff --git a/experimental/boxdd/desktop_service.go b/experimental/boxdd/desktop_service.go index 3e1a9c546..afd59ffa6 100644 --- a/experimental/boxdd/desktop_service.go +++ b/experimental/boxdd/desktop_service.go @@ -28,13 +28,13 @@ func (s *desktopService) GetDaemonInfo(ctx context.Context, empty *emptypb.Empty return nil, err } ownership := DaemonOwnership_DAEMON_OWNERSHIP_AVAILABLE - options, err := loadStartOptions() + ownerUserID, err := loadOwner() if err != nil && !os.IsNotExist(err) { return nil, err } - if options.OwnerUserID == identity.UserID { + if ownerUserID == identity.UserID { ownership = DaemonOwnership_DAEMON_OWNERSHIP_CALLER - } else if options.OwnerUserID != "" { + } else if ownerUserID != "" { ownership = DaemonOwnership_DAEMON_OWNERSHIP_OTHER } return &DaemonInfo{ @@ -53,29 +53,39 @@ func (s *desktopService) StartService(ctx context.Context, request *StartService if s.daemon.closed { return nil, os.ErrClosed } - currentOptions, err := loadStartOptions() + ownerUserID, err := loadOwner() if err != nil && !os.IsNotExist(err) { return nil, err } - if currentOptions.OwnerUserID != "" && currentOptions.OwnerUserID != identity.UserID { + if ownerUserID != "" && ownerUserID != identity.UserID { return nil, status.Error(codes.PermissionDenied, "the service is owned by another user") } + if ownerUserID == "" { + err = saveOwner(identity.UserID) + if err != nil { + return nil, err + } + } + currentOptions, err := loadStartOptions(identity.UserID) + if err != nil && !os.IsNotExist(err) { + return nil, err + } mergedOptions := currentOptions mergedOptions.WasRunning = true - mergedOptions.OwnerUserID = identity.UserID if request.Options != nil { mergedOptions.OOMKillerEnabled = request.Options.OomKillerEnabled mergedOptions.OOMKillerDisabled = request.Options.OomKillerDisabled mergedOptions.OOMMemoryLimit = request.Options.OomMemoryLimit } - err = s.daemon.startService(request.ConfigContent, mergedOptions) + err = s.daemon.startServiceLocked(identity.UserID, request.ConfigContent, mergedOptions) if err != nil { - return nil, s.daemon.cleanFailedStartLocked(identity.UserID, err) + return nil, s.daemon.cleanFailedStartLocked(identity.UserID, mergedOptions, err) } - configError := atomicfile.WriteFile(filepath.Join(workingDirectory, serviceConfigFileName), []byte(request.ConfigContent), 0o600) - optionsError := saveStartOptions(mergedOptions) + directory := userWorkingDirectory(identity.UserID) + configError := atomicfile.WriteFile(filepath.Join(directory, serviceConfigFileName), []byte(request.ConfigContent), 0o600) + optionsError := saveStartOptions(identity.UserID, mergedOptions) if configError != nil || optionsError != nil { - return nil, s.daemon.cleanFailedStartLocked(identity.UserID, E.Errors(configError, optionsError)) + return nil, s.daemon.cleanFailedStartLocked(identity.UserID, mergedOptions, E.Errors(configError, optionsError)) } return &emptypb.Empty{}, nil } @@ -90,17 +100,21 @@ func (s *desktopService) ClaimService(ctx context.Context, empty *emptypb.Empty) if s.daemon.closed { return nil, os.ErrClosed } - options, err := loadStartOptions() + ownerUserID, err := loadOwner() if err != nil && !os.IsNotExist(err) { return nil, err } - if options.OwnerUserID == identity.UserID { + if ownerUserID == identity.UserID { return &emptypb.Empty{}, nil } - if options.OwnerUserID != "" { + if ownerUserID != "" { return nil, status.Error(codes.Aborted, "the service was claimed by another user") } - err = s.daemon.resetRuntimeOwnerLocked(identity.UserID) + err = s.daemon.configureWorkingDirectoryLocked(userWorkingDirectory(identity.UserID)) + if err != nil { + return nil, err + } + err = saveOwner(identity.UserID) if err != nil { return nil, err } @@ -117,14 +131,24 @@ func (s *desktopService) TakeOverService(ctx context.Context, empty *emptypb.Emp if s.daemon.closed { return nil, os.ErrClosed } - options, err := loadStartOptions() + ownerUserID, err := loadOwner() if err != nil && !os.IsNotExist(err) { return nil, err } - if options.OwnerUserID == identity.UserID { + if ownerUserID == identity.UserID { return &emptypb.Empty{}, nil } - err = s.daemon.stopServiceLocked(identity.UserID) + if ownerUserID != "" { + err = s.daemon.stopServiceLocked(ownerUserID) + if err != nil { + return nil, err + } + } + err = s.daemon.configureWorkingDirectoryLocked(userWorkingDirectory(identity.UserID)) + if err != nil { + return nil, err + } + err = saveOwner(identity.UserID) if err != nil { return nil, err } @@ -132,12 +156,14 @@ func (s *desktopService) TakeOverService(ctx context.Context, empty *emptypb.Emp return &emptypb.Empty{}, nil } -func (d *Daemon) cleanFailedStartLocked(ownerUserID string, startError error) error { +func (d *Daemon) cleanFailedStartLocked(ownerUserID string, options startOptions, startError error) error { closeError := d.startedService.CloseService() - crashReportError := tagUnownedReports(filepath.Join(workingDirectory, crashReportsDirectoryName), ownerUserID) - oomReportError := tagUnownedReports(filepath.Join(workingDirectory, oomReportsDirectoryName), ownerUserID) - resetError := d.resetRuntimeOwnerLocked(ownerUserID) - return E.Errors(startError, closeError, crashReportError, oomReportError, resetError) + directory := userWorkingDirectory(ownerUserID) + crashReportError := tagUnownedReports(filepath.Join(directory, crashReportsDirectoryName), ownerUserID) + oomReportError := tagUnownedReports(filepath.Join(directory, oomReportsDirectoryName), ownerUserID) + options.WasRunning = false + snapshotError := saveStartOptions(ownerUserID, options) + return E.Errors(startError, closeError, crashReportError, oomReportError, snapshotError) } func (s *desktopService) GetWorkingDirectory(ctx context.Context, empty *emptypb.Empty) (*WorkingDirectoryInfo, error) { @@ -147,19 +173,20 @@ func (s *desktopService) GetWorkingDirectory(ctx context.Context, empty *emptypb } s.daemon.lifecycleAccess.Lock() defer s.daemon.lifecycleAccess.Unlock() - options, err := loadStartOptions() + ownerUserID, err := loadOwner() if err != nil { return nil, err } - if options.OwnerUserID != identity.UserID { + if ownerUserID != identity.UserID { return nil, status.Error(codes.PermissionDenied, "the service is owned by another user") } - size, err := directorySize(workingDirectory) + directory := userWorkingDirectory(identity.UserID) + size, err := directorySize(directory) if err != nil { return nil, err } return &WorkingDirectoryInfo{ - Path: workingDirectory, + Path: directory, Size: size, }, nil } @@ -177,22 +204,24 @@ func (s *desktopService) DestroyWorkingDirectory(ctx context.Context, empty *emp if s.daemon.startedService.Instance() != nil { return nil, status.Error(codes.FailedPrecondition, "the service must be stopped before destroying the working directory") } - options, err := loadStartOptions() - if err != nil && !os.IsNotExist(err) { + ownerUserID, err := loadOwner() + if err != nil { return nil, err } - if options.OwnerUserID != "" && options.OwnerUserID != identity.UserID { + if ownerUserID != identity.UserID { return nil, status.Error(codes.PermissionDenied, "the service is owned by another user") } - err = s.daemon.resetRuntimeOwnerLocked(identity.UserID) + directory := userWorkingDirectory(identity.UserID) + err = s.daemon.configureWorkingDirectoryLocked(workingDirectory) if err != nil { return nil, err } - err = deleteReportsForUser(filepath.Join(workingDirectory, crashReportsDirectoryName), identity.UserID) + err = os.RemoveAll(directory) if err != nil { - return nil, err + restoreError := s.daemon.configureWorkingDirectoryLocked(directory) + return nil, E.Errors(err, restoreError) } - err = deleteReportsForUser(filepath.Join(workingDirectory, oomReportsDirectoryName), identity.UserID) + err = s.daemon.configureWorkingDirectoryLocked(directory) if err != nil { return nil, err } diff --git a/experimental/boxdd/managed.go b/experimental/boxdd/managed.go index 811bf07cb..1aa395dde 100644 --- a/experimental/boxdd/managed.go +++ b/experimental/boxdd/managed.go @@ -20,31 +20,35 @@ func (h *managedHandler) ServiceStop() error { if h.daemon.closed { return os.ErrClosed } - options, err := loadStartOptions() + ownerUserID, err := loadOwner() if err != nil { return err } - return h.daemon.stopServiceLocked(options.OwnerUserID) + return h.daemon.stopServiceLocked(ownerUserID) } func (h *managedHandler) ServiceReload() error { if h.daemon.closed { return os.ErrClosed } - configContent, err := loadServiceConfig() + ownerUserID, err := loadOwner() if err != nil { return err } - options, err := loadStartOptions() + configContent, err := loadServiceConfig(ownerUserID) if err != nil { return err } - err = h.daemon.startService(configContent, options) + options, err := loadStartOptions(ownerUserID) + if err != nil { + return err + } + err = h.daemon.startServiceLocked(ownerUserID, configContent, options) if err != nil { return err } options.WasRunning = true - return saveStartOptions(options) + return saveStartOptions(ownerUserID, options) } func (h *managedHandler) SystemProxyStatus() (*daemon.SystemProxyStatus, error) { diff --git a/experimental/boxdd/oom_report.go b/experimental/boxdd/oom_report.go index 57365288b..e976cee73 100644 --- a/experimental/boxdd/oom_report.go +++ b/experimental/boxdd/oom_report.go @@ -19,8 +19,7 @@ const oomReportsDirectoryName = "oom_reports" var oomReportLeadingFileOrder = []string{metadataFileName, configSnapshotFileName, goLogFileName} func (s *desktopService) ListOOMReports(ctx context.Context, empty *emptypb.Empty) (*OOMReportList, error) { - reportsDirectory := filepath.Join(workingDirectory, oomReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, oomReportsDirectoryName) if err != nil { return nil, err } @@ -53,8 +52,7 @@ func (s *desktopService) ListOOMReports(ctx context.Context, empty *emptypb.Empt } func (s *desktopService) ReadOOMReport(ctx context.Context, request *OOMReportRequest) (*OOMReportContent, error) { - reportsDirectory := filepath.Join(workingDirectory, oomReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, oomReportsDirectoryName) if err != nil { return nil, err } @@ -102,8 +100,7 @@ func (s *desktopService) ReadOOMReport(ctx context.Context, request *OOMReportRe } func (s *desktopService) MarkOOMReportRead(ctx context.Context, request *OOMReportRequest) (*emptypb.Empty, error) { - reportsDirectory := filepath.Join(workingDirectory, oomReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, oomReportsDirectoryName) if err != nil { return nil, err } @@ -119,8 +116,7 @@ func (s *desktopService) MarkOOMReportRead(ctx context.Context, request *OOMRepo } func (s *desktopService) ExportOOMReport(ctx context.Context, request *OOMReportExportRequest) (*CrashReportArchive, error) { - reportsDirectory := filepath.Join(workingDirectory, oomReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, oomReportsDirectoryName) if err != nil { return nil, err } @@ -128,8 +124,7 @@ func (s *desktopService) ExportOOMReport(ctx context.Context, request *OOMReport } func (s *desktopService) DeleteOOMReport(ctx context.Context, request *OOMReportRequest) (*emptypb.Empty, error) { - reportsDirectory := filepath.Join(workingDirectory, oomReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, oomReportsDirectoryName) if err != nil { return nil, err } @@ -145,8 +140,7 @@ func (s *desktopService) DeleteOOMReport(ctx context.Context, request *OOMReport } func (s *desktopService) DeleteAllOOMReports(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { - reportsDirectory := filepath.Join(workingDirectory, oomReportsDirectoryName) - userID, err := s.daemon.reportCaller(ctx, reportsDirectory) + reportsDirectory, userID, err := s.daemon.reportCaller(ctx, oomReportsDirectoryName) if err != nil { return nil, err } diff --git a/experimental/boxdd/report.go b/experimental/boxdd/report.go index 5d5b5b961..e230f44fd 100644 --- a/experimental/boxdd/report.go +++ b/experimental/boxdd/report.go @@ -92,22 +92,26 @@ func tagUnownedReports(reportsDirectory string, userID string) error { return nil } -func (d *Daemon) reportCaller(ctx context.Context, reportsDirectory string) (string, error) { +func (d *Daemon) reportCaller(ctx context.Context, reportsDirectoryName string) (string, string, error) { identity, err := peerIdentityFromContext(ctx) if err != nil { - return "", err + return "", "", err } d.lifecycleAccess.Lock() defer d.lifecycleAccess.Unlock() - options, err := loadStartOptions() - if err != nil && !os.IsNotExist(err) { - return "", err - } - err = tagUnownedReports(reportsDirectory, options.OwnerUserID) + ownerUserID, err := loadOwner() if err != nil { - return "", err + return "", "", err } - return identity.UserID, nil + if ownerUserID != identity.UserID { + return "", "", status.Error(codes.PermissionDenied, "the service is owned by another user") + } + reportsDirectory := filepath.Join(userWorkingDirectory(identity.UserID), reportsDirectoryName) + err = tagUnownedReports(reportsDirectory, identity.UserID) + if err != nil { + return "", "", err + } + return reportsDirectory, identity.UserID, nil } func deleteReportsForUser(reportsDirectory string, userID string) error { @@ -165,7 +169,7 @@ func exportReportArchive(reportsDirectory string, name string, userID string, wi if err != nil { return nil, err } - tempRoot := filepath.Join(workingDirectory, "temp") + tempRoot := filepath.Join(filepath.Dir(reportsDirectory), "temp") err = os.MkdirAll(tempRoot, 0o700) if err != nil { return nil, err diff --git a/experimental/boxdd/server.go b/experimental/boxdd/server.go index 32cb4d414..f97a349c2 100644 --- a/experimental/boxdd/server.go +++ b/experimental/boxdd/server.go @@ -24,23 +24,22 @@ import ( "google.golang.org/grpc/status" ) -const daemonCrashOutputFileName = "CrashReport-Daemon.log" - type Daemon struct { - logger log.ContextLogger - startedService *daemon.StartedService - server *grpc.Server - listenerPath string - lifecycleAccess sync.Mutex - closed bool - peerAccess sync.Mutex - peerConnections map[peerConnection]peerIdentity + logger log.ContextLogger + startedService *daemon.StartedService + server *grpc.Server + runtimeWorkingDirectory string + lifecycleAccess sync.Mutex + closed bool + peerAccess sync.Mutex + peerConnections map[peerConnection]peerIdentity } func newDaemon() (*Daemon, error) { ctx := include.Context(context.Background()) d := &Daemon{ - logger: log.StdLogger(), + logger: log.StdLogger(), + runtimeWorkingDirectory: workingDirectory, } d.startedService = daemon.NewStartedService(daemon.ServiceOptions{ Context: ctx, @@ -91,13 +90,6 @@ func (d *Daemon) Start() error { if err != nil { return err } - if listener.Addr().Network() == "unix" { - d.listenerPath, err = filepath.Abs(listener.Addr().String()) - if err != nil { - listener.Close() - return err - } - } d.logger.Info("daemon listening at ", listener.Addr()) go func() { serveError := d.server.Serve(listener) @@ -115,38 +107,82 @@ func (d *Daemon) restore() { if d.closed { return } - options, err := loadStartOptions() + ownerUserID, err := loadOwner() + if err != nil { + if !os.IsNotExist(err) { + d.logger.Warn("load owner: ", err) + } + return + } + ownerWorkingDirectory := userWorkingDirectory(ownerUserID) + err = d.configureWorkingDirectoryLocked(ownerWorkingDirectory) + if err != nil { + d.logger.Warn("configure working directory: ", err) + return + } + options, err := loadStartOptions(ownerUserID) if err != nil { if !os.IsNotExist(err) { d.logger.Warn("load start options: ", err) } return } - err = tagUnownedReports(filepath.Join(workingDirectory, crashReportsDirectoryName), options.OwnerUserID) + err = tagUnownedReports(filepath.Join(ownerWorkingDirectory, crashReportsDirectoryName), ownerUserID) if err != nil { d.logger.Warn("tag crash reports: ", err) } - err = tagUnownedReports(filepath.Join(workingDirectory, oomReportsDirectoryName), options.OwnerUserID) + err = tagUnownedReports(filepath.Join(ownerWorkingDirectory, oomReportsDirectoryName), ownerUserID) if err != nil { d.logger.Warn("tag OOM reports: ", err) } if !options.WasRunning { return } - configContent, err := loadServiceConfig() + configContent, err := loadServiceConfig(ownerUserID) if err != nil { d.logger.Error("restore service: ", err) return } d.logger.Info("restoring service") - err = d.startService(configContent, options) + err = d.startServiceLocked(ownerUserID, configContent, options) if err != nil { d.logger.Error("restore service: ", err) } } -func (d *Daemon) startService(configContent string, options startOptions) error { - _ = os.WriteFile(filepath.Join(workingDirectory, configSnapshotFileName), []byte(configContent), 0o600) +func (d *Daemon) configureWorkingDirectoryLocked(directory string) error { + if d.runtimeWorkingDirectory == directory { + return nil + } + err := os.MkdirAll(directory, 0o700) + if err != nil { + return err + } + err = os.Chdir(directory) + if err != nil { + return err + } + err = libbox.Setup(&libbox.SetupOptions{ + BasePath: directory, + WorkingPath: directory, + TempPath: directory, + CrashReportSource: "Daemon", + }) + if err != nil { + return err + } + libbox.PromoteOOMDraft() + d.runtimeWorkingDirectory = directory + return nil +} + +func (d *Daemon) startServiceLocked(ownerUserID string, configContent string, options startOptions) error { + directory := userWorkingDirectory(ownerUserID) + err := d.configureWorkingDirectoryLocked(directory) + if err != nil { + return err + } + _ = os.WriteFile(filepath.Join(directory, configSnapshotFileName), []byte(configContent), 0o600) libbox.ReloadSetupOptions(&libbox.SetupOptions{ OomKillerEnabled: options.OOMKillerEnabled, OomKillerDisabled: options.OOMKillerDisabled, @@ -156,39 +192,8 @@ func (d *Daemon) startService(configContent string, options startOptions) error return d.startedService.StartOrReloadService(configContent, nil) } -func (d *Daemon) clearRuntimeData() error { - entries, err := os.ReadDir(workingDirectory) - if err != nil { - return err - } - for _, entry := range entries { - if entry.Name() == crashReportsDirectoryName || - entry.Name() == oomReportsDirectoryName || - entry.Name() == daemonCrashOutputFileName { - continue - } - entryPath := filepath.Join(workingDirectory, entry.Name()) - if d.listenerPath != "" && entryPath == d.listenerPath { - continue - } - err = os.RemoveAll(entryPath) - if err != nil { - return err - } - } - return nil -} - -func (d *Daemon) resetRuntimeOwnerLocked(ownerUserID string) error { - err := d.clearRuntimeData() - if err != nil { - return err - } - return saveStartOptions(startOptions{OwnerUserID: ownerUserID}) -} - -func (d *Daemon) stopServiceLocked(nextOwnerUserID string) error { - options, err := loadStartOptions() +func (d *Daemon) stopServiceLocked(ownerUserID string) error { + options, err := loadStartOptions(ownerUserID) if err != nil && !os.IsNotExist(err) { return err } @@ -198,15 +203,17 @@ func (d *Daemon) stopServiceLocked(nextOwnerUserID string) error { return err } } - crashReportError := tagUnownedReports(filepath.Join(workingDirectory, crashReportsDirectoryName), options.OwnerUserID) + directory := userWorkingDirectory(ownerUserID) + crashReportError := tagUnownedReports(filepath.Join(directory, crashReportsDirectoryName), ownerUserID) if crashReportError != nil { return crashReportError } - oomReportError := tagUnownedReports(filepath.Join(workingDirectory, oomReportsDirectoryName), options.OwnerUserID) + oomReportError := tagUnownedReports(filepath.Join(directory, oomReportsDirectoryName), ownerUserID) if oomReportError != nil { return oomReportError } - return d.resetRuntimeOwnerLocked(nextOwnerUserID) + options.WasRunning = false + return saveStartOptions(ownerUserID, options) } func (d *Daemon) Close() { @@ -311,14 +318,14 @@ func ownerProtectedMethod(method string) bool { } func (d *Daemon) authorizeOwnerLocked(userID string) error { - options, err := loadStartOptions() + ownerUserID, err := loadOwner() if err != nil { if os.IsNotExist(err) { return status.Error(codes.PermissionDenied, "the service has no owner") } return err } - if options.OwnerUserID == "" || options.OwnerUserID != userID { + if ownerUserID == "" || ownerUserID != userID { return status.Error(codes.PermissionDenied, "the service is owned by another user") } return nil diff --git a/experimental/boxdd/snapshot.go b/experimental/boxdd/snapshot.go index b48eb9bbb..09eb65b5d 100644 --- a/experimental/boxdd/snapshot.go +++ b/experimental/boxdd/snapshot.go @@ -1,6 +1,8 @@ package main import ( + "crypto/sha256" + "encoding/hex" "os" "path/filepath" @@ -11,26 +13,56 @@ import ( const ( serviceConfigFileName = "config.json" startOptionsFileName = "start_options.json" + ownerFileName = "owner.json" + usersDirectoryName = "users" ) type startOptions struct { - WasRunning bool `json:"was_running"` - OwnerUserID string `json:"owner_user_id"` - OOMKillerEnabled bool `json:"oom_killer_enabled"` - OOMKillerDisabled bool `json:"oom_killer_disabled"` - OOMMemoryLimit int64 `json:"oom_memory_limit"` + WasRunning bool `json:"was_running"` + OOMKillerEnabled bool `json:"oom_killer_enabled"` + OOMKillerDisabled bool `json:"oom_killer_disabled"` + OOMMemoryLimit int64 `json:"oom_memory_limit"` } -func loadServiceConfig() (string, error) { - content, err := os.ReadFile(filepath.Join(workingDirectory, serviceConfigFileName)) +type ownerState struct { + UserID string `json:"user_id"` +} + +func userWorkingDirectory(userID string) string { + digest := sha256.Sum256([]byte(userID)) + return filepath.Join(workingDirectory, usersDirectoryName, hex.EncodeToString(digest[:])) +} + +func loadOwner() (string, error) { + content, err := os.ReadFile(filepath.Join(workingDirectory, ownerFileName)) + if err != nil { + return "", err + } + state, err := json.UnmarshalExtended[ownerState](content) + if err != nil { + return "", err + } + return state.UserID, nil +} + +func saveOwner(userID string) error { + content, err := json.Marshal(ownerState{UserID: userID}) + if err != nil { + return err + } + return atomicfile.WriteFile(filepath.Join(workingDirectory, ownerFileName), content, 0o600) +} + +func loadServiceConfig(userID string) (string, error) { + content, err := os.ReadFile(filepath.Join(userWorkingDirectory(userID), serviceConfigFileName)) if err != nil { return "", err } return string(content), nil } -func loadStartOptions() (startOptions, error) { - content, err := os.ReadFile(filepath.Join(workingDirectory, startOptionsFileName)) +func loadStartOptions(userID string) (startOptions, error) { + content, err := os.ReadFile(filepath.Join(userWorkingDirectory(userID), startOptionsFileName)) if err != nil { return startOptions{}, err } @@ -41,10 +73,15 @@ func loadStartOptions() (startOptions, error) { return options, nil } -func saveStartOptions(options startOptions) error { +func saveStartOptions(userID string, options startOptions) error { content, err := json.Marshal(options) if err != nil { return err } - return atomicfile.WriteFile(filepath.Join(workingDirectory, startOptionsFileName), content, 0o600) + directory := userWorkingDirectory(userID) + err = os.MkdirAll(directory, 0o700) + if err != nil { + return err + } + return atomicfile.WriteFile(filepath.Join(directory, startOptionsFileName), content, 0o600) }