package workspace import ( "errors" "fmt" "os" "path/filepath" "strings" "sync" "testing" "golang.org/x/sys/unix" iop "iop/proto/gen/iop" ) func openedRuntime(t *testing.T) (*Runtime, string) { t.Helper() root := t.TempDir() rt, err := NewRuntime([]*iop.WorkspaceConfig{testWorkspaceConfig(root)}, "darwin", nil) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = rt.Close() }) if _, err := rt.Open(testRequestAuthority("request-1")); err != nil { t.Fatal(err) } return rt, root } func TestFileExecutorReadListWriteDelete(t *testing.T) { rt, root := openedRuntime(t) if err := os.WriteFile(filepath.Join(root, "input.txt"), []byte("hello"), 0600); err != nil { t.Fatal(err) } read := rt.Read("request-1", "input.txt") if read.Status != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS || string(read.Content) != "hello" { t.Fatalf("read=%+v", read) } list := rt.List("request-1", ".") if list.Status != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS || len(list.Entries) != 1 || list.Entries[0] != "input.txt\tfile" { t.Fatalf("list=%+v", list) } if write := rt.Write("request-1", "nested/output.txt", []byte("written")); write.Status != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("write=%+v", write) } data, err := os.ReadFile(filepath.Join(root, "nested", "output.txt")) if err != nil || string(data) != "written" { t.Fatalf("output=%q err=%v", data, err) } if deleted := rt.Delete("request-1", "nested/output.txt"); deleted.Status != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("delete=%+v", deleted) } if _, err := os.Stat(filepath.Join(root, "nested", "output.txt")); !os.IsNotExist(err) { t.Fatalf("deleted file remains: %v", err) } } func TestFileExecutorRejectsReservedSymlinkAndBounds(t *testing.T) { rt, root := openedRuntime(t) outside := filepath.Join(t.TempDir(), "outside.txt") if err := os.WriteFile(outside, []byte("outside"), 0600); err != nil { t.Fatal(err) } if err := os.Symlink(outside, filepath.Join(root, "escape")); err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Join(root, ".iop"), 0700); err != nil { t.Fatal(err) } for _, target := range []string{".iop", ".iop/job/request-2/x", "escape"} { if result := rt.Read("request-1", target); result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("read admitted %q: %+v", target, result) } } if err := os.WriteFile(filepath.Join(root, "large"), []byte(strings.Repeat("x", 65)), 0600); err != nil { t.Fatal(err) } if result := rt.Read("request-1", "large"); !result.Truncated || len(result.Content) != 64 { t.Fatalf("bounded read=%+v", result) } if result := rt.Write("request-1", "too-large", []byte(strings.Repeat("x", 65))); result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("oversize write=%+v", result) } request, err := rt.Request("request-1") if err != nil { t.Fatal(err) } device := request.entry.device request.entry.device++ if result := rt.Read("request-1", "large"); result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("cross-filesystem read=%+v", result) } request.entry.device = device } func TestFileExecutorWriteFailurePreservesTarget(t *testing.T) { rt, root := openedRuntime(t) target := filepath.Join(root, "target.txt") if err := os.WriteFile(target, []byte("old"), 0600); err != nil { t.Fatal(err) } if result := rt.Write("request-1", "target.txt", []byte(strings.Repeat("x", 65))); result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("write=%+v", result) } data, err := os.ReadFile(target) if err != nil || string(data) != "old" { t.Fatalf("target=%q err=%v", data, err) } request, err := rt.Request("request-1") if err != nil { t.Fatal(err) } request.entry.beforeRename = func() error { return errors.New("injected before rename") } if result := rt.Write("request-1", "target.txt", []byte("new")); result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("injected write=%+v", result) } request.entry.beforeRename = nil data, err = os.ReadFile(target) if err != nil || string(data) != "old" { t.Fatalf("target after injected failure=%q err=%v", data, err) } assertNoWriteTemps(t, root) if result := rt.Delete("request-1", "."); result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("root delete=%+v", result) } if err := os.Mkdir(filepath.Join(root, "nonempty"), 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, "nonempty", "child"), []byte("x"), 0600); err != nil { t.Fatal(err) } if result := rt.Delete("request-1", "nonempty"); result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("recursive delete=%+v", result) } } func TestFileExecutorWriteRejectsUnsafeParentsWithoutEffects(t *testing.T) { rt, root := openedRuntime(t) outside := t.TempDir() if err := os.Symlink(outside, filepath.Join(root, "link")); err != nil { t.Fatal(err) } if result := rt.Write("request-1", "link/new/output.txt", []byte("bad")); result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("symlink-parent write=%+v", result) } if _, err := os.Stat(filepath.Join(outside, "new")); !os.IsNotExist(err) { t.Fatalf("rejected symlink write created outside parent: %v", err) } request, err := rt.Request("request-1") if err != nil { t.Fatal(err) } originalDevice := request.entry.device request.entry.device++ if result := rt.Write("request-1", "mount-substitute/output.txt", []byte("bad")); result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("foreign-device write=%+v", result) } request.entry.device = originalDevice if _, err := os.Stat(filepath.Join(root, "mount-substitute")); !os.IsNotExist(err) { t.Fatalf("foreign-device rejection created parent: %v", err) } parent := filepath.Join(root, "parent") if err := os.Mkdir(parent, 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(parent, "target.txt"), []byte("old"), 0o600); err != nil { t.Fatal(err) } moved := filepath.Join(root, "parent-moved") request.entry.beforeRename = func() error { if err := os.Rename(parent, moved); err != nil { return err } return os.Mkdir(parent, 0o700) } if result := rt.Write("request-1", "parent/target.txt", []byte("new")); result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("replaced-parent write=%+v", result) } request.entry.beforeRename = nil data, err := os.ReadFile(filepath.Join(moved, "target.txt")) if err != nil || string(data) != "old" { t.Fatalf("moved target=%q err=%v", data, err) } entries, err := os.ReadDir(parent) if err != nil || len(entries) != 0 { t.Fatalf("replacement parent entries=%v err=%v", entries, err) } assertNoWriteTemps(t, root) } func TestFileExecutorBoundedDeterministicLargeList(t *testing.T) { root := t.TempDir() config := testWorkspaceConfig(root) config.MaxOutputBytes = 256 rt, err := NewRuntime([]*iop.WorkspaceConfig{config}, "darwin", nil) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = rt.Close() }) authority := testRequestAuthority("request-1") authority.MaxOutputBytes = 256 if _, err := rt.Open(authority); err != nil { t.Fatal(err) } for index := 0; index < maxListEntries+200; index++ { name := fmt.Sprintf("entry-%04d-with-bounded-name", index) if err := os.WriteFile(filepath.Join(root, name), nil, 0o600); err != nil { t.Fatal(err) } } first := rt.List("request-1", ".") second := rt.List("request-1", ".") if first.Status != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS || !first.Truncated || len(first.Entries) == 0 { t.Fatalf("first list=%+v", first) } if strings.Join(first.Entries, "\n") != strings.Join(second.Entries, "\n") || first.Entries[0] != "entry-0000-with-bounded-name\tfile" { t.Fatalf("list is not deterministic: first=%v second=%v", first.Entries, second.Entries) } } func TestFileExecutorRejectsSpecialFileAndRunsParallelRequests(t *testing.T) { rt, root := openedRuntime(t) fifo := filepath.Join(root, "special") if err := unix.Mkfifo(fifo, 0o600); err != nil { t.Fatal(err) } for _, result := range []Result{ rt.Read("request-1", "special"), rt.Write("request-1", "special", []byte("bad")), rt.Delete("request-1", "special"), } { if result.Status == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Fatalf("special file operation succeeded: %+v", result) } } if err := os.Remove(fifo); err != nil { t.Fatal(err) } requestIDs := make([]string, 16) for index := 0; index < 16; index++ { requestID := fmt.Sprintf("request-%d", index+2) requestIDs[index] = requestID if _, err := rt.Open(testRequestAuthority(requestID)); err != nil { t.Fatal(err) } } var group sync.WaitGroup for index, requestID := range requestIDs { group.Add(1) go func(index int, requestID string) { defer group.Done() name := fmt.Sprintf("parallel/%02d.txt", index) if result := rt.Write(requestID, name, []byte(requestID)); result.Status != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Errorf("write %s=%+v", requestID, result) return } if result := rt.Read(requestID, name); result.Status != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS || string(result.Content) != requestID { t.Errorf("read %s=%+v", requestID, result) } }(index, requestID) } group.Wait() for _, requestID := range requestIDs { group.Add(1) go func(requestID string) { defer group.Done() if result := rt.List(requestID, "parallel"); result.Status != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Errorf("list %s=%+v", requestID, result) } }(requestID) } group.Wait() for index, requestID := range requestIDs { group.Add(1) go func(index int, requestID string) { defer group.Done() name := fmt.Sprintf("parallel/%02d.txt", index) if result := rt.Delete(requestID, name); result.Status != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { t.Errorf("delete %s=%+v", requestID, result) } }(index, requestID) } group.Wait() } func assertNoWriteTemps(t *testing.T, root string) { t.Helper() err := filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error { if err != nil { return err } if strings.HasPrefix(entry.Name(), ".iop-write-") { t.Fatalf("temporary write artifact remains: %s", path) } return nil }) if err != nil { t.Fatal(err) } }