aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/pkg/pkg.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/pkg/pkg.go')
-rw-r--r--internal/pkg/pkg.go102
1 files changed, 99 insertions, 3 deletions
diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go
index d029eff9..b3b08510 100644
--- a/internal/pkg/pkg.go
+++ b/internal/pkg/pkg.go
@@ -215,6 +215,20 @@ func (f *FContext) GetArtifact(a Artifact) (
panic(InvalidLookupError(f.cache.Ident(a).Value()))
}
+// RContext is passed to [FileArtifact.Cure] and provides helper methods useful
+// for curing the [FileArtifact].
+//
+// Methods of RContext are safe for concurrent use. RContext is valid
+// until [FileArtifact.Cure] returns.
+type RContext struct {
+ // Address of underlying [Cache], should be zeroed or made unusable after
+ // [FileArtifact.Cure] returns and must not be exposed directly.
+ cache *Cache
+}
+
+// Unwrap returns the underlying [context.Context].
+func (r *RContext) Unwrap() context.Context { return r.cache.ctx }
+
// An Artifact is a read-only reference to a piece of data that may be created
// deterministically but might not currently be available in memory or on the
// filesystem.
@@ -323,7 +337,7 @@ type FileArtifact interface {
// Callers are responsible for closing the resulting [io.ReadCloser].
//
// Result must remain identical across multiple invocations.
- Cure(ctx context.Context) (io.ReadCloser, error)
+ Cure(r *RContext) (io.ReadCloser, error)
Artifact
}
@@ -952,7 +966,7 @@ func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
}
}()
}
- return f.Cure(c.ctx)
+ return f.Cure(&RContext{c})
}
return
}
@@ -1229,6 +1243,88 @@ func (c *Cache) getWriter(w io.Writer) *bufio.Writer {
return bw
}
+// measuredReader implements [io.ReadCloser] and measures the checksum during
+// Close. If the underlying reader is not read to EOF, Close blocks until all
+// remaining data is consumed and validated.
+type measuredReader struct {
+ // Underlying reader. Never exposed directly.
+ r io.ReadCloser
+ // For validating checksum. Never exposed directly.
+ h hash.Hash
+ // Buffers writes to h, initialised by [Cache]. Never exposed directly.
+ hbw *bufio.Writer
+ // Expected checksum, compared during Close.
+ want unique.Handle[Checksum]
+
+ // For accessing free lists.
+ c *Cache
+
+ // Set up via [io.TeeReader] by [Cache].
+ io.Reader
+}
+
+// Close reads the underlying [io.ReadCloser] to EOF, closes it and measures its
+// outcome. It returns a [ChecksumMismatchError] for an unexpected checksum.
+func (mr *measuredReader) Close() (err error) {
+ if mr.hbw == nil || mr.Reader == nil {
+ return os.ErrInvalid
+ }
+ err = mr.hbw.Flush()
+ mr.c.putWriter(mr.hbw)
+ mr.hbw, mr.Reader = nil, nil
+ if err != nil {
+ _ = mr.r.Close()
+ return
+ }
+ var n int64
+ if n, err = io.Copy(mr.h, mr.r); err != nil {
+ _ = mr.r.Close()
+ return
+ }
+
+ if n > 0 {
+ mr.c.msg.Verbosef("missed %d bytes on measured reader", n)
+ }
+
+ if err = mr.r.Close(); err != nil {
+ return
+ }
+
+ buf := mr.c.getIdentBuf()
+ mr.h.Sum(buf[:0])
+
+ if got := Checksum(buf[:]); got != mr.want.Value() {
+ err = &ChecksumMismatchError{
+ Got: got,
+ Want: mr.want.Value(),
+ }
+ }
+
+ mr.c.putIdentBuf(buf)
+ return
+}
+
+// newMeasuredReader implements [RContext.NewMeasuredReader].
+func (c *Cache) newMeasuredReader(
+ r io.ReadCloser,
+ checksum unique.Handle[Checksum],
+) io.ReadCloser {
+ mr := measuredReader{r: r, h: sha512.New384(), want: checksum, c: c}
+ mr.hbw = c.getWriter(mr.h)
+ mr.Reader = io.TeeReader(r, mr.hbw)
+ return &mr
+}
+
+// NewMeasuredReader returns an [io.ReadCloser] implementing behaviour required
+// by [FileArtifact]. The resulting [io.ReadCloser] holds a buffer originating
+// from [Cache] and must be closed to return this buffer.
+func (r *RContext) NewMeasuredReader(
+ rc io.ReadCloser,
+ checksum unique.Handle[Checksum],
+) io.ReadCloser {
+ return r.cache.newMeasuredReader(rc, checksum)
+}
+
// putWriter adds bw to bufioPool.
func (c *Cache) putWriter(bw *bufio.Writer) { c.bufioPool.Put(bw) }
@@ -1363,7 +1459,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
if err = c.enterCure(curesExempt); err != nil {
return
}
- r, err = f.Cure(c.ctx)
+ r, err = f.Cure(&RContext{c})
if err == nil {
if checksumPathname == nil || c.IsStrict() {
h := sha512.New384()