blob: 9cacc5913c5478ecf667ecfb3c4af16eb8f852d2 [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"
Serge Bazanskia3938142022-04-04 17:04:47 +020024 "fmt"
Lorenz Brun6b13bf12021-01-26 19:54:24 +010025 "io"
Lorenz Brun6b13bf12021-01-26 19:54:24 +010026 "log"
27 "os"
28 "path"
29 "sort"
30 "strings"
31
Lorenz Brun4c326022022-01-25 13:42:45 +010032 "source.monogon.dev/metropolis/node/build/fsspec"
Lorenz Brun6b13bf12021-01-26 19:54:24 +010033 "source.monogon.dev/metropolis/pkg/erofs"
34)
35
36func (spec *entrySpec) writeRecursive(w *erofs.Writer, pathname string) {
37 switch inode := spec.data.Type.(type) {
38 case *fsspec.Inode_Directory:
39 // Sort children for reproducibility
40 var sortedChildren []string
41 for name := range spec.children {
42 sortedChildren = append(sortedChildren, name)
43 }
44 sort.Strings(sortedChildren)
45
46 err := w.Create(pathname, &erofs.Directory{
47 Base: erofs.Base{
48 Permissions: uint16(inode.Directory.Mode),
49 UID: uint16(inode.Directory.Uid),
50 GID: uint16(inode.Directory.Gid),
51 },
52 Children: sortedChildren,
53 })
54 if err != nil {
55 log.Fatalf("failed to write directory: %s", err)
56 }
57 for _, name := range sortedChildren {
58 spec.children[name].writeRecursive(w, path.Join(pathname, name))
59 }
60 case *fsspec.Inode_File:
61 iw := w.CreateFile(pathname, &erofs.FileMeta{
62 Base: erofs.Base{
63 Permissions: uint16(inode.File.Mode),
64 UID: uint16(inode.File.Uid),
65 GID: uint16(inode.File.Gid),
66 },
67 })
68
69 sourceFile, err := os.Open(inode.File.SourcePath)
70 if err != nil {
71 log.Fatalf("failed to open source file %s: %s", inode.File.SourcePath, err)
72 }
73
74 _, err = io.Copy(iw, sourceFile)
75 if err != nil {
76 log.Fatalf("failed to copy file into filesystem: %s", err)
77 }
78 sourceFile.Close()
79 if err := iw.Close(); err != nil {
80 log.Fatalf("failed to close target file: %s", err)
81 }
82 case *fsspec.Inode_SymbolicLink:
83 err := w.Create(pathname, &erofs.SymbolicLink{
84 Base: erofs.Base{
85 Permissions: 0777, // Nominal, Linux forces that mode anyways, see symlink(7)
86 },
87 Target: inode.SymbolicLink.TargetPath,
88 })
89 if err != nil {
90 log.Fatalf("failed to create symbolic link: %s", err)
91 }
Serge Bazanskia3938142022-04-04 17:04:47 +020092 case *fsspec.Inode_SpecialFile:
93 err := fmt.Errorf("unimplemented special file type %s", inode.SpecialFile.Type)
94 base := erofs.Base{
95 Permissions: uint16(inode.SpecialFile.Mode),
96 UID: uint16(inode.SpecialFile.Uid),
97 GID: uint16(inode.SpecialFile.Gid),
98 }
99 switch inode.SpecialFile.Type {
100 case fsspec.SpecialFile_FIFO:
101 err = w.Create(pathname, &erofs.FIFO{
102 Base: base,
103 })
104 case fsspec.SpecialFile_CHARACTER_DEV:
105 err = w.Create(pathname, &erofs.CharacterDevice{
106 Base: base,
107 Major: inode.SpecialFile.Major,
108 Minor: inode.SpecialFile.Minor,
109 })
110 case fsspec.SpecialFile_BLOCK_DEV:
111 err = w.Create(pathname, &erofs.BlockDevice{
112 Base: base,
113 Major: inode.SpecialFile.Major,
114 Minor: inode.SpecialFile.Minor,
115 })
116 }
117 if err != nil {
118 log.Fatalf("failed to make special file: %v", err)
119 }
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100120 }
121}
122
123// entrySpec is a recursive structure representing the filesystem tree
124type entrySpec struct {
125 data fsspec.Inode
126 children map[string]*entrySpec
127}
128
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200129// pathRef gets the entrySpec at the leaf of the given path, inferring
130// directories if necessary
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100131func (s *entrySpec) pathRef(p string) *entrySpec {
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200132 // This block gets a path array starting at the root of the filesystem. The
133 // root folder is the zero-length array.
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100134 pathParts := strings.Split(path.Clean("./"+p), "/")
135 if pathParts[0] == "." {
136 pathParts = pathParts[1:]
137 }
138
139 entryRef := s
140 for _, part := range pathParts {
141 childRef, ok := entryRef.children[part]
142 if !ok {
143 childRef = &entrySpec{
144 data: fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}},
145 children: make(map[string]*entrySpec),
146 }
147 entryRef.children[part] = childRef
148 }
149 entryRef = childRef
150 }
151 return entryRef
152}
153
154var (
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100155 outPath = flag.String("out", "", "Output file path")
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100156)
157
158func main() {
159 flag.Parse()
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100160
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100161 spec, err := fsspec.ReadMergeSpecs(flag.Args())
162 if err != nil {
163 log.Fatalf("failed to load specs: %v", err)
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100164 }
165
166 var fsRoot = &entrySpec{
167 data: fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}},
168 children: make(map[string]*entrySpec),
169 }
170
171 for _, dir := range spec.Directory {
172 entryRef := fsRoot.pathRef(dir.Path)
173 entryRef.data.Type = &fsspec.Inode_Directory{Directory: dir}
174 }
175
176 for _, file := range spec.File {
177 entryRef := fsRoot.pathRef(file.Path)
178 entryRef.data.Type = &fsspec.Inode_File{File: file}
179 }
180
181 for _, symlink := range spec.SymbolicLink {
182 entryRef := fsRoot.pathRef(symlink.Path)
183 entryRef.data.Type = &fsspec.Inode_SymbolicLink{SymbolicLink: symlink}
184 }
185
Serge Bazanskia3938142022-04-04 17:04:47 +0200186 for _, specialFile := range spec.SpecialFile {
187 entryRef := fsRoot.pathRef(specialFile.Path)
188 entryRef.data.Type = &fsspec.Inode_SpecialFile{SpecialFile: specialFile}
Lorenz Brun4c326022022-01-25 13:42:45 +0100189 }
190
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100191 fs, err := os.Create(*outPath)
192 if err != nil {
193 log.Fatalf("failed to open output file: %v", err)
194 }
195 writer, err := erofs.NewWriter(fs)
196 if err != nil {
197 log.Fatalf("failed to initialize EROFS writer: %v", err)
198 }
199
200 fsRoot.writeRecursive(writer, ".")
201
202 if err := writer.Close(); err != nil {
203 panic(err)
204 }
205 if err := fs.Close(); err != nil {
206 panic(err)
207 }
208}