osbase/structfs: add package

This adds the structfs package, which defines a data structure for a
file system and a blob interface.

Change-Id: I646205faf7d28ec016d2038b0a8931b64e7afc83
Reviewed-on: https://review.monogon.dev/c/monogon/+/4036
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/osbase/structfs/blob.go b/osbase/structfs/blob.go
new file mode 100644
index 0000000..74fd0f5
--- /dev/null
+++ b/osbase/structfs/blob.go
@@ -0,0 +1,68 @@
+// Copyright The Monogon Project Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package structfs
+
+import (
+	"bytes"
+	"errors"
+	"io"
+	"io/fs"
+	"os"
+)
+
+// Blob is a binary large object, a read-only sequence of bytes of a known size.
+type Blob interface {
+	Open() (io.ReadCloser, error)
+	Size() int64
+}
+
+// Bytes implements [Blob] for a byte slice.
+type Bytes []byte
+
+func (b Bytes) Open() (io.ReadCloser, error) {
+	return &bytesReadCloser{*bytes.NewReader(b)}, nil
+}
+
+func (b Bytes) Size() int64 {
+	return int64(len(b))
+}
+
+type bytesReadCloser struct {
+	bytes.Reader
+}
+
+func (*bytesReadCloser) Close() error {
+	return nil
+}
+
+var errNotRegular = errors.New("not a regular file")
+
+// OSPathBlob creates a [Blob] for an OS path.
+func OSPathBlob(path string) (Blob, error) {
+	info, err := os.Stat(path)
+	if err != nil {
+		return nil, err
+	}
+	if !info.Mode().IsRegular() {
+		return nil, &fs.PathError{Op: "blob", Path: path, Err: errNotRegular}
+	}
+	b := &osPathBlob{
+		path: path,
+		size: info.Size(),
+	}
+	return b, nil
+}
+
+type osPathBlob struct {
+	path string
+	size int64
+}
+
+func (b *osPathBlob) Open() (io.ReadCloser, error) {
+	return os.Open(b.path)
+}
+
+func (b *osPathBlob) Size() int64 {
+	return b.size
+}