blob: 0d35eff23902551acbd53bdc2d8a2b9d8e8ccbe1 [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
Lorenz Brun4c326022022-01-25 13:42:45 +010031 "source.monogon.dev/metropolis/node/build/fsspec"
Lorenz Brun6b13bf12021-01-26 19:54:24 +010032 "source.monogon.dev/metropolis/pkg/erofs"
33)
34
35func (spec *entrySpec) writeRecursive(w *erofs.Writer, pathname string) {
36 switch inode := spec.data.Type.(type) {
37 case *fsspec.Inode_Directory:
38 // Sort children for reproducibility
39 var sortedChildren []string
40 for name := range spec.children {
41 sortedChildren = append(sortedChildren, name)
42 }
43 sort.Strings(sortedChildren)
44
45 err := w.Create(pathname, &erofs.Directory{
46 Base: erofs.Base{
47 Permissions: uint16(inode.Directory.Mode),
48 UID: uint16(inode.Directory.Uid),
49 GID: uint16(inode.Directory.Gid),
50 },
51 Children: sortedChildren,
52 })
53 if err != nil {
54 log.Fatalf("failed to write directory: %s", err)
55 }
56 for _, name := range sortedChildren {
57 spec.children[name].writeRecursive(w, path.Join(pathname, name))
58 }
59 case *fsspec.Inode_File:
60 iw := w.CreateFile(pathname, &erofs.FileMeta{
61 Base: erofs.Base{
62 Permissions: uint16(inode.File.Mode),
63 UID: uint16(inode.File.Uid),
64 GID: uint16(inode.File.Gid),
65 },
66 })
67
68 sourceFile, err := os.Open(inode.File.SourcePath)
69 if err != nil {
70 log.Fatalf("failed to open source file %s: %s", inode.File.SourcePath, err)
71 }
72
73 _, err = io.Copy(iw, sourceFile)
74 if err != nil {
75 log.Fatalf("failed to copy file into filesystem: %s", err)
76 }
77 sourceFile.Close()
78 if err := iw.Close(); err != nil {
79 log.Fatalf("failed to close target file: %s", err)
80 }
81 case *fsspec.Inode_SymbolicLink:
82 err := w.Create(pathname, &erofs.SymbolicLink{
83 Base: erofs.Base{
84 Permissions: 0777, // Nominal, Linux forces that mode anyways, see symlink(7)
85 },
86 Target: inode.SymbolicLink.TargetPath,
87 })
88 if err != nil {
89 log.Fatalf("failed to create symbolic link: %s", err)
90 }
91 }
92}
93
94// entrySpec is a recursive structure representing the filesystem tree
95type entrySpec struct {
96 data fsspec.Inode
97 children map[string]*entrySpec
98}
99
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200100// pathRef gets the entrySpec at the leaf of the given path, inferring
101// directories if necessary
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100102func (s *entrySpec) pathRef(p string) *entrySpec {
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200103 // This block gets a path array starting at the root of the filesystem. The
104 // root folder is the zero-length array.
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100105 pathParts := strings.Split(path.Clean("./"+p), "/")
106 if pathParts[0] == "." {
107 pathParts = pathParts[1:]
108 }
109
110 entryRef := s
111 for _, part := range pathParts {
112 childRef, ok := entryRef.children[part]
113 if !ok {
114 childRef = &entrySpec{
115 data: fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}},
116 children: make(map[string]*entrySpec),
117 }
118 entryRef.children[part] = childRef
119 }
120 entryRef = childRef
121 }
122 return entryRef
123}
124
125var (
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100126 outPath = flag.String("out", "", "Output file path")
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100127)
128
129func main() {
130 flag.Parse()
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100131
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100132 spec, err := fsspec.ReadMergeSpecs(flag.Args())
133 if err != nil {
134 log.Fatalf("failed to load specs: %v", err)
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100135 }
136
137 var fsRoot = &entrySpec{
138 data: fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}},
139 children: make(map[string]*entrySpec),
140 }
141
142 for _, dir := range spec.Directory {
143 entryRef := fsRoot.pathRef(dir.Path)
144 entryRef.data.Type = &fsspec.Inode_Directory{Directory: dir}
145 }
146
147 for _, file := range spec.File {
148 entryRef := fsRoot.pathRef(file.Path)
149 entryRef.data.Type = &fsspec.Inode_File{File: file}
150 }
151
152 for _, symlink := range spec.SymbolicLink {
153 entryRef := fsRoot.pathRef(symlink.Path)
154 entryRef.data.Type = &fsspec.Inode_SymbolicLink{SymbolicLink: symlink}
155 }
156
Lorenz Brun4c326022022-01-25 13:42:45 +0100157 if len(spec.SpecialFile) > 0 {
158 log.Fatalf("special files are currently unimplemented in mkerofs")
159 }
160
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100161 fs, err := os.Create(*outPath)
162 if err != nil {
163 log.Fatalf("failed to open output file: %v", err)
164 }
165 writer, err := erofs.NewWriter(fs)
166 if err != nil {
167 log.Fatalf("failed to initialize EROFS writer: %v", err)
168 }
169
170 fsRoot.writeRecursive(writer, ".")
171
172 if err := writer.Close(); err != nil {
173 panic(err)
174 }
175 if err := fs.Close(); err != nil {
176 panic(err)
177 }
178}