| // Copyright 2020 The Monogon Project Authors. | 
 | // | 
 | // SPDX-License-Identifier: Apache-2.0 | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //     http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | // mkerofs takes a specification in the form of a prototext file (see fsspec | 
 | // next to this) and assembles an EROFS filesystem according to it. The output | 
 | // is fully reproducible. | 
 | package main | 
 |  | 
 | import ( | 
 | 	"flag" | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"log" | 
 | 	"os" | 
 | 	"path" | 
 | 	"sort" | 
 | 	"strings" | 
 |  | 
 | 	"source.monogon.dev/metropolis/node/build/fsspec" | 
 | 	"source.monogon.dev/metropolis/pkg/erofs" | 
 | ) | 
 |  | 
 | func (spec *entrySpec) writeRecursive(w *erofs.Writer, pathname string) { | 
 | 	switch inode := spec.data.Type.(type) { | 
 | 	case *fsspec.Inode_Directory: | 
 | 		// Sort children for reproducibility | 
 | 		var sortedChildren []string | 
 | 		for name := range spec.children { | 
 | 			sortedChildren = append(sortedChildren, name) | 
 | 		} | 
 | 		sort.Strings(sortedChildren) | 
 |  | 
 | 		err := w.Create(pathname, &erofs.Directory{ | 
 | 			Base: erofs.Base{ | 
 | 				Permissions: uint16(inode.Directory.Mode), | 
 | 				UID:         uint16(inode.Directory.Uid), | 
 | 				GID:         uint16(inode.Directory.Gid), | 
 | 			}, | 
 | 			Children: sortedChildren, | 
 | 		}) | 
 | 		if err != nil { | 
 | 			log.Fatalf("failed to write directory: %s", err) | 
 | 		} | 
 | 		for _, name := range sortedChildren { | 
 | 			spec.children[name].writeRecursive(w, path.Join(pathname, name)) | 
 | 		} | 
 | 	case *fsspec.Inode_File: | 
 | 		iw := w.CreateFile(pathname, &erofs.FileMeta{ | 
 | 			Base: erofs.Base{ | 
 | 				Permissions: uint16(inode.File.Mode), | 
 | 				UID:         uint16(inode.File.Uid), | 
 | 				GID:         uint16(inode.File.Gid), | 
 | 			}, | 
 | 		}) | 
 |  | 
 | 		sourceFile, err := os.Open(inode.File.SourcePath) | 
 | 		if err != nil { | 
 | 			log.Fatalf("failed to open source file %s: %s", inode.File.SourcePath, err) | 
 | 		} | 
 |  | 
 | 		_, err = io.Copy(iw, sourceFile) | 
 | 		if err != nil { | 
 | 			log.Fatalf("failed to copy file into filesystem: %s", err) | 
 | 		} | 
 | 		sourceFile.Close() | 
 | 		if err := iw.Close(); err != nil { | 
 | 			log.Fatalf("failed to close target file: %s", err) | 
 | 		} | 
 | 	case *fsspec.Inode_SymbolicLink: | 
 | 		err := w.Create(pathname, &erofs.SymbolicLink{ | 
 | 			Base: erofs.Base{ | 
 | 				Permissions: 0777, // Nominal, Linux forces that mode anyways, see symlink(7) | 
 | 			}, | 
 | 			Target: inode.SymbolicLink.TargetPath, | 
 | 		}) | 
 | 		if err != nil { | 
 | 			log.Fatalf("failed to create symbolic link: %s", err) | 
 | 		} | 
 | 	case *fsspec.Inode_SpecialFile: | 
 | 		err := fmt.Errorf("unimplemented special file type %s", inode.SpecialFile.Type) | 
 | 		base := erofs.Base{ | 
 | 			Permissions: uint16(inode.SpecialFile.Mode), | 
 | 			UID:         uint16(inode.SpecialFile.Uid), | 
 | 			GID:         uint16(inode.SpecialFile.Gid), | 
 | 		} | 
 | 		switch inode.SpecialFile.Type { | 
 | 		case fsspec.SpecialFile_FIFO: | 
 | 			err = w.Create(pathname, &erofs.FIFO{ | 
 | 				Base: base, | 
 | 			}) | 
 | 		case fsspec.SpecialFile_CHARACTER_DEV: | 
 | 			err = w.Create(pathname, &erofs.CharacterDevice{ | 
 | 				Base:  base, | 
 | 				Major: inode.SpecialFile.Major, | 
 | 				Minor: inode.SpecialFile.Minor, | 
 | 			}) | 
 | 		case fsspec.SpecialFile_BLOCK_DEV: | 
 | 			err = w.Create(pathname, &erofs.BlockDevice{ | 
 | 				Base:  base, | 
 | 				Major: inode.SpecialFile.Major, | 
 | 				Minor: inode.SpecialFile.Minor, | 
 | 			}) | 
 | 		} | 
 | 		if err != nil { | 
 | 			log.Fatalf("failed to make special file: %v", err) | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | // entrySpec is a recursive structure representing the filesystem tree | 
 | type entrySpec struct { | 
 | 	data     fsspec.Inode | 
 | 	children map[string]*entrySpec | 
 | } | 
 |  | 
 | // pathRef gets the entrySpec at the leaf of the given path, inferring | 
 | // directories if necessary | 
 | func (s *entrySpec) pathRef(p string) *entrySpec { | 
 | 	// This block gets a path array starting at the root of the filesystem. The | 
 | 	// root folder is the zero-length array. | 
 | 	pathParts := strings.Split(path.Clean("./"+p), "/") | 
 | 	if pathParts[0] == "." { | 
 | 		pathParts = pathParts[1:] | 
 | 	} | 
 |  | 
 | 	entryRef := s | 
 | 	for _, part := range pathParts { | 
 | 		childRef, ok := entryRef.children[part] | 
 | 		if !ok { | 
 | 			childRef = &entrySpec{ | 
 | 				data:     fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}}, | 
 | 				children: make(map[string]*entrySpec), | 
 | 			} | 
 | 			entryRef.children[part] = childRef | 
 | 		} | 
 | 		entryRef = childRef | 
 | 	} | 
 | 	return entryRef | 
 | } | 
 |  | 
 | var ( | 
 | 	outPath = flag.String("out", "", "Output file path") | 
 | ) | 
 |  | 
 | func main() { | 
 | 	flag.Parse() | 
 |  | 
 | 	spec, err := fsspec.ReadMergeSpecs(flag.Args()) | 
 | 	if err != nil { | 
 | 		log.Fatalf("failed to load specs: %v", err) | 
 | 	} | 
 |  | 
 | 	var fsRoot = &entrySpec{ | 
 | 		data:     fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}}, | 
 | 		children: make(map[string]*entrySpec), | 
 | 	} | 
 |  | 
 | 	for _, dir := range spec.Directory { | 
 | 		entryRef := fsRoot.pathRef(dir.Path) | 
 | 		entryRef.data.Type = &fsspec.Inode_Directory{Directory: dir} | 
 | 	} | 
 |  | 
 | 	for _, file := range spec.File { | 
 | 		entryRef := fsRoot.pathRef(file.Path) | 
 | 		entryRef.data.Type = &fsspec.Inode_File{File: file} | 
 | 	} | 
 |  | 
 | 	for _, symlink := range spec.SymbolicLink { | 
 | 		entryRef := fsRoot.pathRef(symlink.Path) | 
 | 		entryRef.data.Type = &fsspec.Inode_SymbolicLink{SymbolicLink: symlink} | 
 | 	} | 
 |  | 
 | 	for _, specialFile := range spec.SpecialFile { | 
 | 		entryRef := fsRoot.pathRef(specialFile.Path) | 
 | 		entryRef.data.Type = &fsspec.Inode_SpecialFile{SpecialFile: specialFile} | 
 | 	} | 
 |  | 
 | 	fs, err := os.Create(*outPath) | 
 | 	if err != nil { | 
 | 		log.Fatalf("failed to open output file: %v", err) | 
 | 	} | 
 | 	writer, err := erofs.NewWriter(fs) | 
 | 	if err != nil { | 
 | 		log.Fatalf("failed to initialize EROFS writer: %v", err) | 
 | 	} | 
 |  | 
 | 	fsRoot.writeRecursive(writer, ".") | 
 |  | 
 | 	if err := writer.Close(); err != nil { | 
 | 		panic(err) | 
 | 	} | 
 | 	if err := fs.Close(); err != nil { | 
 | 		panic(err) | 
 | 	} | 
 | } |