m/n/build: implement new fsspec infrastructure

This makes the node_initramfs and erofs_image use the new common fsspec
infrastructure. It also adds the fsspecs attribute to both which can
later be used to add arbitrary fsspecs.

Change-Id: I384e04712c0a70f82c5c975911cbb1d0d5e6cabc
Reviewed-on: https://review.monogon.dev/c/monogon/+/530
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/node/build/fsspec/utils.go b/metropolis/node/build/fsspec/utils.go
new file mode 100644
index 0000000..2438220
--- /dev/null
+++ b/metropolis/node/build/fsspec/utils.go
@@ -0,0 +1,38 @@
+package fsspec
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/golang/protobuf/proto"
+)
+
+// ReadMergeSpecs reads FSSpecs from all files in paths and merges them into
+// a single FSSpec.
+func ReadMergeSpecs(paths []string) (*FSSpec, error) {
+	var mergedSpec FSSpec
+	for _, p := range paths {
+		specRaw, err := os.ReadFile(p)
+		if err != nil {
+			return nil, fmt.Errorf("failed to open spec: %w", err)
+		}
+
+		var spec FSSpec
+		if err := proto.UnmarshalText(string(specRaw), &spec); err != nil {
+			return nil, fmt.Errorf("failed to parse spec %q: %w", p, err)
+		}
+		for _, f := range spec.File {
+			mergedSpec.File = append(mergedSpec.File, f)
+		}
+		for _, d := range spec.Directory {
+			mergedSpec.Directory = append(mergedSpec.Directory, d)
+		}
+		for _, s := range spec.SymbolicLink {
+			mergedSpec.SymbolicLink = append(mergedSpec.SymbolicLink, s)
+		}
+		for _, s := range spec.SpecialFile {
+			mergedSpec.SpecialFile = append(mergedSpec.SpecialFile, s)
+		}
+	}
+	return &mergedSpec, nil
+}