blob: 651096b99cd17c2b318dc3012dd60ee24b009c2b [file] [log] [blame]
Lorenz Brun6b13bf12021-01-26 19:54:24 +01001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
Serge Bazanski216fe7b2021-05-21 18:36:16 +020017// mkerofs takes a specification in the form of a prototext file (see fsspec
18// next to this) and assembles an EROFS filesystem according to it. The output
19// is fully reproducible.
Lorenz Brun6b13bf12021-01-26 19:54:24 +010020package main
21
22import (
23 "flag"
24 "io"
Lorenz Brun6b13bf12021-01-26 19:54:24 +010025 "log"
26 "os"
27 "path"
28 "sort"
29 "strings"
30
31 "github.com/golang/protobuf/proto"
32
Lorenz Brun4c326022022-01-25 13:42:45 +010033 "source.monogon.dev/metropolis/node/build/fsspec"
Lorenz Brun6b13bf12021-01-26 19:54:24 +010034 "source.monogon.dev/metropolis/pkg/erofs"
35)
36
37func (spec *entrySpec) writeRecursive(w *erofs.Writer, pathname string) {
38 switch inode := spec.data.Type.(type) {
39 case *fsspec.Inode_Directory:
40 // Sort children for reproducibility
41 var sortedChildren []string
42 for name := range spec.children {
43 sortedChildren = append(sortedChildren, name)
44 }
45 sort.Strings(sortedChildren)
46
47 err := w.Create(pathname, &erofs.Directory{
48 Base: erofs.Base{
49 Permissions: uint16(inode.Directory.Mode),
50 UID: uint16(inode.Directory.Uid),
51 GID: uint16(inode.Directory.Gid),
52 },
53 Children: sortedChildren,
54 })
55 if err != nil {
56 log.Fatalf("failed to write directory: %s", err)
57 }
58 for _, name := range sortedChildren {
59 spec.children[name].writeRecursive(w, path.Join(pathname, name))
60 }
61 case *fsspec.Inode_File:
62 iw := w.CreateFile(pathname, &erofs.FileMeta{
63 Base: erofs.Base{
64 Permissions: uint16(inode.File.Mode),
65 UID: uint16(inode.File.Uid),
66 GID: uint16(inode.File.Gid),
67 },
68 })
69
70 sourceFile, err := os.Open(inode.File.SourcePath)
71 if err != nil {
72 log.Fatalf("failed to open source file %s: %s", inode.File.SourcePath, err)
73 }
74
75 _, err = io.Copy(iw, sourceFile)
76 if err != nil {
77 log.Fatalf("failed to copy file into filesystem: %s", err)
78 }
79 sourceFile.Close()
80 if err := iw.Close(); err != nil {
81 log.Fatalf("failed to close target file: %s", err)
82 }
83 case *fsspec.Inode_SymbolicLink:
84 err := w.Create(pathname, &erofs.SymbolicLink{
85 Base: erofs.Base{
86 Permissions: 0777, // Nominal, Linux forces that mode anyways, see symlink(7)
87 },
88 Target: inode.SymbolicLink.TargetPath,
89 })
90 if err != nil {
91 log.Fatalf("failed to create symbolic link: %s", err)
92 }
93 }
94}
95
96// entrySpec is a recursive structure representing the filesystem tree
97type entrySpec struct {
98 data fsspec.Inode
99 children map[string]*entrySpec
100}
101
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200102// pathRef gets the entrySpec at the leaf of the given path, inferring
103// directories if necessary
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100104func (s *entrySpec) pathRef(p string) *entrySpec {
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200105 // This block gets a path array starting at the root of the filesystem. The
106 // root folder is the zero-length array.
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100107 pathParts := strings.Split(path.Clean("./"+p), "/")
108 if pathParts[0] == "." {
109 pathParts = pathParts[1:]
110 }
111
112 entryRef := s
113 for _, part := range pathParts {
114 childRef, ok := entryRef.children[part]
115 if !ok {
116 childRef = &entrySpec{
117 data: fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}},
118 children: make(map[string]*entrySpec),
119 }
120 entryRef.children[part] = childRef
121 }
122 entryRef = childRef
123 }
124 return entryRef
125}
126
127var (
128 specPath = flag.String("spec", "", "Path to the filesystem specification (spec.FSSpec)")
129 outPath = flag.String("out", "", "Output file path")
130)
131
132func main() {
133 flag.Parse()
Lorenz Brun764a2de2021-11-22 16:26:36 +0100134 specRaw, err := os.ReadFile(*specPath)
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100135 if err != nil {
136 log.Fatalf("failed to open spec: %v", err)
137 }
138
139 var spec fsspec.FSSpec
140 if err := proto.UnmarshalText(string(specRaw), &spec); err != nil {
141 log.Fatalf("failed to parse spec: %v", err)
142 }
143
144 var fsRoot = &entrySpec{
145 data: fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}},
146 children: make(map[string]*entrySpec),
147 }
148
149 for _, dir := range spec.Directory {
150 entryRef := fsRoot.pathRef(dir.Path)
151 entryRef.data.Type = &fsspec.Inode_Directory{Directory: dir}
152 }
153
154 for _, file := range spec.File {
155 entryRef := fsRoot.pathRef(file.Path)
156 entryRef.data.Type = &fsspec.Inode_File{File: file}
157 }
158
159 for _, symlink := range spec.SymbolicLink {
160 entryRef := fsRoot.pathRef(symlink.Path)
161 entryRef.data.Type = &fsspec.Inode_SymbolicLink{SymbolicLink: symlink}
162 }
163
Lorenz Brun4c326022022-01-25 13:42:45 +0100164 if len(spec.SpecialFile) > 0 {
165 log.Fatalf("special files are currently unimplemented in mkerofs")
166 }
167
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100168 fs, err := os.Create(*outPath)
169 if err != nil {
170 log.Fatalf("failed to open output file: %v", err)
171 }
172 writer, err := erofs.NewWriter(fs)
173 if err != nil {
174 log.Fatalf("failed to initialize EROFS writer: %v", err)
175 }
176
177 fsRoot.writeRecursive(writer, ".")
178
179 if err := writer.Close(); err != nil {
180 panic(err)
181 }
182 if err := fs.Close(); err != nil {
183 panic(err)
184 }
185}