blob: 26a6689c71f5cfd6ee509c135fa04ab095deedc2 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Brun6b13bf12021-01-26 19:54:24 +01002// SPDX-License-Identifier: Apache-2.0
Lorenz Brun6b13bf12021-01-26 19:54:24 +01003
Serge Bazanski216fe7b2021-05-21 18:36:16 +02004// mkerofs takes a specification in the form of a prototext file (see fsspec
5// next to this) and assembles an EROFS filesystem according to it. The output
6// is fully reproducible.
Lorenz Brun6b13bf12021-01-26 19:54:24 +01007package main
8
9import (
10 "flag"
Serge Bazanskia3938142022-04-04 17:04:47 +020011 "fmt"
Lorenz Brun6b13bf12021-01-26 19:54:24 +010012 "io"
Lorenz Brun6b13bf12021-01-26 19:54:24 +010013 "log"
14 "os"
15 "path"
16 "sort"
17 "strings"
18
Tim Windelschmidtc2290c22024-08-15 19:56:00 +020019 "source.monogon.dev/osbase/build/fsspec"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020020 "source.monogon.dev/osbase/erofs"
Lorenz Brun6b13bf12021-01-26 19:54:24 +010021)
22
23func (spec *entrySpec) writeRecursive(w *erofs.Writer, pathname string) {
24 switch inode := spec.data.Type.(type) {
25 case *fsspec.Inode_Directory:
26 // Sort children for reproducibility
27 var sortedChildren []string
28 for name := range spec.children {
29 sortedChildren = append(sortedChildren, name)
30 }
31 sort.Strings(sortedChildren)
32
33 err := w.Create(pathname, &erofs.Directory{
34 Base: erofs.Base{
35 Permissions: uint16(inode.Directory.Mode),
36 UID: uint16(inode.Directory.Uid),
37 GID: uint16(inode.Directory.Gid),
38 },
39 Children: sortedChildren,
40 })
41 if err != nil {
42 log.Fatalf("failed to write directory: %s", err)
43 }
44 for _, name := range sortedChildren {
45 spec.children[name].writeRecursive(w, path.Join(pathname, name))
46 }
47 case *fsspec.Inode_File:
48 iw := w.CreateFile(pathname, &erofs.FileMeta{
49 Base: erofs.Base{
50 Permissions: uint16(inode.File.Mode),
51 UID: uint16(inode.File.Uid),
52 GID: uint16(inode.File.Gid),
53 },
54 })
55
56 sourceFile, err := os.Open(inode.File.SourcePath)
57 if err != nil {
58 log.Fatalf("failed to open source file %s: %s", inode.File.SourcePath, err)
59 }
60
61 _, err = io.Copy(iw, sourceFile)
62 if err != nil {
63 log.Fatalf("failed to copy file into filesystem: %s", err)
64 }
65 sourceFile.Close()
66 if err := iw.Close(); err != nil {
67 log.Fatalf("failed to close target file: %s", err)
68 }
69 case *fsspec.Inode_SymbolicLink:
70 err := w.Create(pathname, &erofs.SymbolicLink{
71 Base: erofs.Base{
72 Permissions: 0777, // Nominal, Linux forces that mode anyways, see symlink(7)
73 },
74 Target: inode.SymbolicLink.TargetPath,
75 })
76 if err != nil {
77 log.Fatalf("failed to create symbolic link: %s", err)
78 }
Serge Bazanskia3938142022-04-04 17:04:47 +020079 case *fsspec.Inode_SpecialFile:
80 err := fmt.Errorf("unimplemented special file type %s", inode.SpecialFile.Type)
81 base := erofs.Base{
82 Permissions: uint16(inode.SpecialFile.Mode),
83 UID: uint16(inode.SpecialFile.Uid),
84 GID: uint16(inode.SpecialFile.Gid),
85 }
86 switch inode.SpecialFile.Type {
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010087 case fsspec.SpecialFile_TYPE_FIFO:
Serge Bazanskia3938142022-04-04 17:04:47 +020088 err = w.Create(pathname, &erofs.FIFO{
89 Base: base,
90 })
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010091 case fsspec.SpecialFile_TYPE_CHARACTER_DEV:
Serge Bazanskia3938142022-04-04 17:04:47 +020092 err = w.Create(pathname, &erofs.CharacterDevice{
93 Base: base,
94 Major: inode.SpecialFile.Major,
95 Minor: inode.SpecialFile.Minor,
96 })
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010097 case fsspec.SpecialFile_TYPE_BLOCK_DEV:
Serge Bazanskia3938142022-04-04 17:04:47 +020098 err = w.Create(pathname, &erofs.BlockDevice{
99 Base: base,
100 Major: inode.SpecialFile.Major,
101 Minor: inode.SpecialFile.Minor,
102 })
103 }
104 if err != nil {
105 log.Fatalf("failed to make special file: %v", err)
106 }
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100107 }
108}
109
110// entrySpec is a recursive structure representing the filesystem tree
111type entrySpec struct {
112 data fsspec.Inode
113 children map[string]*entrySpec
114}
115
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200116// pathRef gets the entrySpec at the leaf of the given path, inferring
117// directories if necessary
Tim Windelschmidt0c57d342024-04-11 01:38:47 +0200118func (spec *entrySpec) pathRef(p string) *entrySpec {
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200119 // This block gets a path array starting at the root of the filesystem. The
120 // root folder is the zero-length array.
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100121 pathParts := strings.Split(path.Clean("./"+p), "/")
122 if pathParts[0] == "." {
123 pathParts = pathParts[1:]
124 }
125
Tim Windelschmidt0c57d342024-04-11 01:38:47 +0200126 entryRef := spec
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100127 for _, part := range pathParts {
128 childRef, ok := entryRef.children[part]
129 if !ok {
130 childRef = &entrySpec{
131 data: fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}},
132 children: make(map[string]*entrySpec),
133 }
134 entryRef.children[part] = childRef
135 }
136 entryRef = childRef
137 }
138 return entryRef
139}
140
141var (
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100142 outPath = flag.String("out", "", "Output file path")
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100143)
144
145func main() {
146 flag.Parse()
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100147
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100148 spec, err := fsspec.ReadMergeSpecs(flag.Args())
149 if err != nil {
150 log.Fatalf("failed to load specs: %v", err)
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100151 }
152
153 var fsRoot = &entrySpec{
154 data: fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}},
155 children: make(map[string]*entrySpec),
156 }
157
158 for _, dir := range spec.Directory {
159 entryRef := fsRoot.pathRef(dir.Path)
160 entryRef.data.Type = &fsspec.Inode_Directory{Directory: dir}
161 }
162
163 for _, file := range spec.File {
164 entryRef := fsRoot.pathRef(file.Path)
165 entryRef.data.Type = &fsspec.Inode_File{File: file}
166 }
167
168 for _, symlink := range spec.SymbolicLink {
169 entryRef := fsRoot.pathRef(symlink.Path)
170 entryRef.data.Type = &fsspec.Inode_SymbolicLink{SymbolicLink: symlink}
171 }
172
Serge Bazanskia3938142022-04-04 17:04:47 +0200173 for _, specialFile := range spec.SpecialFile {
174 entryRef := fsRoot.pathRef(specialFile.Path)
175 entryRef.data.Type = &fsspec.Inode_SpecialFile{SpecialFile: specialFile}
Lorenz Brun4c326022022-01-25 13:42:45 +0100176 }
177
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100178 fs, err := os.Create(*outPath)
179 if err != nil {
180 log.Fatalf("failed to open output file: %v", err)
181 }
182 writer, err := erofs.NewWriter(fs)
183 if err != nil {
184 log.Fatalf("failed to initialize EROFS writer: %v", err)
185 }
186
187 fsRoot.writeRecursive(writer, ".")
188
189 if err := writer.Close(); err != nil {
190 panic(err)
191 }
192 if err := fs.Close(); err != nil {
193 panic(err)
194 }
195}