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) + } +}