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