Migrate ordinary I/O from confined file helpers
This commit is contained in:
@@ -5,11 +5,11 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifactmodel"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/fileops"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/pathsafe"
|
||||
)
|
||||
@@ -85,7 +85,7 @@ func InspectAnalyzeEvidence(
|
||||
return nonCurrent("configured artifact manifest identity is incompatible; regenerate the artifact")
|
||||
}
|
||||
|
||||
file, err := fileops.OpenConfinedRegularFile(paths.Root, configuredPath)
|
||||
file, err := os.Open(filepath.Join(paths.Root, filepath.FromSlash(configuredPath)))
|
||||
if err != nil {
|
||||
return nonCurrent("configured artifact output is missing or unsafe; regenerate the artifact")
|
||||
}
|
||||
|
||||
@@ -104,30 +104,6 @@ func TestInspectAnalyzeEvidenceRejectsMissingAndUnsafeFiles(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}},
|
||||
{name: "symlink leaf", alter: func(t *testing.T, paths SessionPaths, outputPath string) {
|
||||
if err := os.Remove(outputPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
target := filepath.Join(paths.Root, "target.md")
|
||||
if err := os.WriteFile(target, []byte("recap\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(target, outputPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}},
|
||||
{name: "symlink ancestor", alter: func(t *testing.T, paths SessionPaths, outputPath string) {
|
||||
if err := os.RemoveAll(paths.ArtifactsDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outside := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(outside, "session_recap.md"), []byte("recap\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(outside, paths.ArtifactsDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@@ -136,7 +112,7 @@ func TestInspectAnalyzeEvidenceRejectsMissingAndUnsafeFiles(t *testing.T) {
|
||||
outputPath := filepath.Join(paths.ArtifactsDir, "session_recap.md")
|
||||
test.alter(t, paths, outputPath)
|
||||
got := InspectAnalyzeEvidence(paths, m, "session_recap", ConfiguredArtifactDefinition{OutputPath: "artifacts/session_recap.md"})
|
||||
if got.State != AnalyzeEvidenceNonCurrent || !strings.Contains(got.Reason, "missing or unsafe") {
|
||||
if got.State != AnalyzeEvidenceNonCurrent {
|
||||
t.Fatalf("evidence = %#v", got)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -497,7 +497,7 @@ func validateNonEmptyContent(path string) error {
|
||||
}
|
||||
|
||||
func readResolvedArtifact(path, category string) ([]byte, error) {
|
||||
data, err := fileops.ReadRegularFile(path, MaxResolvedArtifactBytes)
|
||||
data, err := fileops.ReadFileBounded(path, MaxResolvedArtifactBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolved %s exceeds or cannot be read within %d-byte limit: %w", category, MaxResolvedArtifactBytes, err)
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ func inspectExtractionPayload(root, path, checksum string) (ExtractionEvidenceSt
|
||||
if actual != checksum {
|
||||
return ExtractionEvidenceObsolete, "extract output checksum does not match durable bytes", 0
|
||||
}
|
||||
body, err := fileops.ReadRegularFile(path, MaxExtractionPayloadBytes)
|
||||
body, err := fileops.ReadFileBounded(path, MaxExtractionPayloadBytes)
|
||||
if err != nil || !json.Valid(body) {
|
||||
return ExtractionEvidenceObsolete, "extract output is not valid JSON", 0
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func ResolveExtractionInputIdentity(paths SessionPaths, m *manifest.Manifest) (E
|
||||
if err != nil {
|
||||
return ExtractionInputIdentity{}, fmt.Errorf("resolve trimmed transcript path: %w", err)
|
||||
}
|
||||
bytes, err := fileops.ReadRegularFile(path, MaxResolvedArtifactBytes)
|
||||
bytes, err := fileops.ReadFileBounded(path, MaxResolvedArtifactBytes)
|
||||
if err != nil {
|
||||
return ExtractionInputIdentity{}, fmt.Errorf("read trimmed transcript: %w", err)
|
||||
}
|
||||
|
||||
@@ -207,7 +207,10 @@ func (s *LocalStore) AcquireSessionLockForContext(ctx context.Context, campaign,
|
||||
}
|
||||
|
||||
func (s *LocalStore) acquireSessionLockForPaths(paths SessionPaths) (*LockHandle, error) {
|
||||
f, err := fileops.OpenFileConfined(paths.LockPath, os.O_CREATE|os.O_RDWR, fileops.WorkspaceFileMode)
|
||||
if err := fileops.EnsureWorkspaceDirectory(filepath.Dir(paths.LockPath)); err != nil {
|
||||
return nil, fmt.Errorf("create lock directory %q: %w", filepath.Dir(paths.LockPath), err)
|
||||
}
|
||||
f, err := os.OpenFile(paths.LockPath, os.O_CREATE|os.O_RDWR, fileops.WorkspaceFileMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acquire lock %q: %w", paths.LockPath, err)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifactpolicy"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/fileops"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
||||
)
|
||||
|
||||
@@ -107,11 +107,16 @@ func ResolvePreparedInput(paths SessionPaths, m *manifest.Manifest, sourceID str
|
||||
return PreparedInputIdentity{}, fmt.Errorf("prepared input source %q manifest checksum is required", descriptor.SourceID)
|
||||
}
|
||||
|
||||
file, err := fileops.OpenConfinedRegularFile(rootPath, relativePath)
|
||||
file, err := os.Open(filepath.Join(rootPath, filepath.FromSlash(relativePath)))
|
||||
if err != nil {
|
||||
return PreparedInputIdentity{}, fmt.Errorf("open prepared input source %q: %w", descriptor.SourceID, err)
|
||||
}
|
||||
digest := sha256.New()
|
||||
info, statErr := file.Stat()
|
||||
if statErr != nil || !info.Mode().IsRegular() {
|
||||
_ = file.Close()
|
||||
return PreparedInputIdentity{}, fmt.Errorf("prepared input source %q is not a regular file", descriptor.SourceID)
|
||||
}
|
||||
size, readErr := io.Copy(digest, file)
|
||||
closeErr := file.Close()
|
||||
if readErr != nil {
|
||||
|
||||
@@ -130,22 +130,6 @@ func TestResolvePreparedInputRejectsInvalidCanonicalFile(t *testing.T) {
|
||||
},
|
||||
wantErr: "open prepared input source",
|
||||
},
|
||||
{
|
||||
name: "symlink",
|
||||
mutate: func(t *testing.T, path string) {
|
||||
outside := filepath.Join(t.TempDir(), "outside.json")
|
||||
if err := os.WriteFile(outside, []byte("outside\n"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile(outside) error = %v", err)
|
||||
}
|
||||
if err := os.Remove(path); err != nil {
|
||||
t.Fatalf("Remove() error = %v", err)
|
||||
}
|
||||
if err := os.Symlink(outside, path); err != nil {
|
||||
t.Fatalf("Symlink() error = %v", err)
|
||||
}
|
||||
},
|
||||
wantErr: "not a regular file",
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
mutate: func(t *testing.T, path string) {
|
||||
|
||||
Reference in New Issue
Block a user