From b19f97e3fce66f8fc01d17ef1d909b745d3692c8 Mon Sep 17 00:00:00 2001 From: zerone0x Date: Wed, 11 Mar 2026 04:59:19 +0100 Subject: [PATCH] fix(execd): auto-recreate temp dir in stdLogDescriptor and combinedOutputDescriptor When the /tmp directory is deleted inside a sandbox container, subsequent command execution fails with 'failed to get stdlog descriptor' because os.OpenFile with O_CREATE only creates the file, not the parent directory. Add os.MkdirAll(os.TempDir(), 0755) before file creation in both stdLogDescriptor and combinedOutputDescriptor so the temp directory is automatically recreated if it has been removed. Also fix a file descriptor leak in stdLogDescriptor where the stdout file was not closed when the stderr open failed. Fixes #400 Co-Authored-By: Claude --- .../execd/pkg/runtime/command_common.go | 13 +++++ components/execd/pkg/runtime/command_test.go | 57 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/components/execd/pkg/runtime/command_common.go b/components/execd/pkg/runtime/command_common.go index 39db4be7..960ff273 100644 --- a/components/execd/pkg/runtime/command_common.go +++ b/components/execd/pkg/runtime/command_common.go @@ -17,6 +17,7 @@ package runtime import ( "bufio" "bytes" + "fmt" "io" "os" "path/filepath" @@ -60,13 +61,21 @@ func (c *Controller) storeCommandKernel(sessionID string, kernel *commandKernel) } // stdLogDescriptor creates temporary files for capturing command output. +// It ensures the temp directory exists before opening files, so that commands +// continue to work even after the /tmp directory has been removed and recreated. func (c *Controller) stdLogDescriptor(session string) (io.WriteCloser, io.WriteCloser, error) { + logDir := os.TempDir() + if err := os.MkdirAll(logDir, 0o755); err != nil { + return nil, nil, fmt.Errorf("failed to create temp dir %s: %w", logDir, err) + } + stdout, err := os.OpenFile(c.stdoutFileName(session), os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) if err != nil { return nil, nil, err } stderr, err := os.OpenFile(c.stderrFileName(session), os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) if err != nil { + stdout.Close() return nil, nil, err } @@ -74,6 +83,10 @@ func (c *Controller) stdLogDescriptor(session string) (io.WriteCloser, io.WriteC } func (c *Controller) combinedOutputDescriptor(session string) (io.WriteCloser, error) { + logDir := os.TempDir() + if err := os.MkdirAll(logDir, 0o755); err != nil { + return nil, fmt.Errorf("failed to create temp dir %s: %w", logDir, err) + } return os.OpenFile(c.combinedOutputFileName(session), os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) } diff --git a/components/execd/pkg/runtime/command_test.go b/components/execd/pkg/runtime/command_test.go index 1e201330..8f09c9d5 100644 --- a/components/execd/pkg/runtime/command_test.go +++ b/components/execd/pkg/runtime/command_test.go @@ -253,3 +253,60 @@ func TestRunCommand_Error(t *testing.T) { t.Fatalf("unexpected error payload: %+v", gotErr) } } + +// TestStdLogDescriptor_AutoCreatesTempDir verifies that stdLogDescriptor +// recreates the temp directory when it has been deleted, rather than failing. +// Regression test for https://github.com/alibaba/OpenSandbox/issues/400. +func TestStdLogDescriptor_AutoCreatesTempDir(t *testing.T) { + if goruntime.GOOS == "windows" { + t.Skip("TMPDIR env var has no effect on Windows") + } + + // Point os.TempDir() at a path that does not yet exist. + missingDir := filepath.Join(t.TempDir(), "deleted_tmp") + t.Setenv("TMPDIR", missingDir) + + c := NewController("", "") + stdout, stderr, err := c.stdLogDescriptor("test-session") + if err != nil { + t.Fatalf("stdLogDescriptor failed with missing temp dir: %v", err) + } + stdout.Close() + stderr.Close() + + // The directory must have been created. + info, err := os.Stat(missingDir) + if err != nil { + t.Fatalf("expected temp dir to be created, stat error: %v", err) + } + if !info.IsDir() { + t.Fatalf("expected %s to be a directory", missingDir) + } +} + +// TestCombinedOutputDescriptor_AutoCreatesTempDir verifies that +// combinedOutputDescriptor also recreates the temp directory when missing. +// Regression test for https://github.com/alibaba/OpenSandbox/issues/400. +func TestCombinedOutputDescriptor_AutoCreatesTempDir(t *testing.T) { + if goruntime.GOOS == "windows" { + t.Skip("TMPDIR env var has no effect on Windows") + } + + missingDir := filepath.Join(t.TempDir(), "deleted_tmp") + t.Setenv("TMPDIR", missingDir) + + c := NewController("", "") + f, err := c.combinedOutputDescriptor("test-session") + if err != nil { + t.Fatalf("combinedOutputDescriptor failed with missing temp dir: %v", err) + } + f.Close() + + info, err := os.Stat(missingDir) + if err != nil { + t.Fatalf("expected temp dir to be created, stat error: %v", err) + } + if !info.IsDir() { + t.Fatalf("expected %s to be a directory", missingDir) + } +}