|  | package main | 
|  |  | 
|  | import ( | 
|  | "flag" | 
|  | "io" | 
|  | "log" | 
|  | "os" | 
|  | "path" | 
|  | "sort" | 
|  | "strings" | 
|  |  | 
|  | "github.com/cavaliergopher/cpio" | 
|  | "github.com/pierrec/lz4/v4" | 
|  | "golang.org/x/sys/unix" | 
|  |  | 
|  | "source.monogon.dev/metropolis/node/build/fsspec" | 
|  | ) | 
|  |  | 
|  | var ( | 
|  | outPath = flag.String("out", "", "Output file path") | 
|  | ) | 
|  |  | 
|  | type placeEnum int | 
|  |  | 
|  | const ( | 
|  | // placeNone implies that currently nothing is placed at that path. | 
|  | // Can be overridden by everything. | 
|  | placeNone placeEnum = 0 | 
|  | // placeDirImplicit means that there is currently a implied directory | 
|  | // at the given path. It can be overridden by (and only by) an explicit | 
|  | // directory. | 
|  | placeDirImplicit placeEnum = 1 | 
|  | // placeDirExplicit means that there is an explicit (i.e. specified by | 
|  | // the FSSpec) directory at the given path. Nothing else can override | 
|  | // this. | 
|  | placeDirExplicit placeEnum = 2 | 
|  | // placeNonDir means that there is a file-type resource (i.e a file, symlink | 
|  | // or special_file) at the given path. Nothing else can override this. | 
|  | placeNonDir placeEnum = 3 | 
|  | ) | 
|  |  | 
|  | // place represents the state a given canonical path is in during metadata | 
|  | // construction. Its zero value is { State: placeNone, Inode: nil }. | 
|  | type place struct { | 
|  | State placeEnum | 
|  | // Inode contains one of the types inside an FSSpec (e.g. *fsspec.File) | 
|  | Inode interface{} | 
|  | } | 
|  |  | 
|  | // Usage: -out <out-path.cpio.lz4> fsspec-path... | 
|  | func main() { | 
|  | flag.Parse() | 
|  | outFile, err := os.Create(*outPath) | 
|  | if err != nil { | 
|  | log.Fatalf("Failed to open CPIO output file: %v", err) | 
|  | } | 
|  | defer outFile.Close() | 
|  | compressedOut := lz4.NewWriter(outFile) | 
|  | compressedOut.Apply(lz4.LegacyOption(true)) | 
|  | defer compressedOut.Close() | 
|  | cpioWriter := cpio.NewWriter(compressedOut) | 
|  | defer cpioWriter.Close() | 
|  |  | 
|  | spec, err := fsspec.ReadMergeSpecs(flag.Args()) | 
|  | if err != nil { | 
|  | log.Fatalf("failed to load specs: %v", err) | 
|  | } | 
|  |  | 
|  | // Map of paths to metadata for validation & implicit directory injection | 
|  | places := make(map[string]place) | 
|  |  | 
|  | // The idea behind this machinery is that we try to place all files and | 
|  | // directories into a map while creating the required parent directories | 
|  | // on-the-fly as implicit directories. Overriding an implicit directory | 
|  | // with an explicit one is allowed thus the actual order in which this | 
|  | // structure is created does not matter. All non-directories cannot be | 
|  | // overridden anyways so their insertion order does not matter. | 
|  | // This also has the job of validating the FSSpec structure, ensuring that | 
|  | // there are no duplicate paths and that there is nothing placed below a | 
|  | // non-directory. | 
|  | var placeInode func(p string, isDir bool, inode interface{}) | 
|  | placeInode = func(p string, isDir bool, inode interface{}) { | 
|  | cleanPath := path.Clean(p) | 
|  | if !isDir { | 
|  | if places[cleanPath].State != placeNone { | 
|  | log.Fatalf("Invalid FSSpec: Duplicate Inode at %q", cleanPath) | 
|  | } | 
|  | places[cleanPath] = place{ | 
|  | State: placeNonDir, | 
|  | Inode: inode, | 
|  | } | 
|  | } else { | 
|  | switch places[cleanPath].State { | 
|  | case placeNone: | 
|  | if inode != nil { | 
|  | places[cleanPath] = place{ | 
|  | State: placeDirExplicit, | 
|  | Inode: inode, | 
|  | } | 
|  | } else { | 
|  | places[cleanPath] = place{ | 
|  | State: placeDirImplicit, | 
|  | Inode: &fsspec.Directory{Path: cleanPath, Mode: 0555}, | 
|  | } | 
|  | } | 
|  | case placeDirImplicit: | 
|  | if inode != nil { | 
|  | places[cleanPath] = place{ | 
|  | State: placeDirExplicit, | 
|  | Inode: inode, | 
|  | } | 
|  | } | 
|  | case placeDirExplicit: | 
|  | if inode != nil { | 
|  | log.Fatalf("Invalid FSSpec: Conflicting explicit directories at %v", cleanPath) | 
|  | } | 
|  | case placeNonDir: | 
|  | log.Fatalf("Invalid FSSpec: Trying to place inode below non-directory at #{cleanPath}") | 
|  | default: | 
|  | panic("unhandled placeEnum value") | 
|  | } | 
|  | } | 
|  | parentPath, _ := path.Split(p) | 
|  | parentPath = path.Clean(parentPath) | 
|  | if parentPath == "/" || parentPath == p { | 
|  | return | 
|  | } | 
|  | placeInode(parentPath, true, nil) | 
|  | } | 
|  | for _, d := range spec.Directory { | 
|  | placeInode(d.Path, true, d) | 
|  | } | 
|  | for _, f := range spec.File { | 
|  | placeInode(f.Path, false, f) | 
|  | } | 
|  | for _, s := range spec.SymbolicLink { | 
|  | placeInode(s.Path, false, s) | 
|  | } | 
|  | for _, s := range spec.SpecialFile { | 
|  | placeInode(s.Path, false, s) | 
|  | } | 
|  |  | 
|  | var writeOrder []string | 
|  | for path := range places { | 
|  | writeOrder = append(writeOrder, path) | 
|  | } | 
|  | // Sorting a list of normalized paths representing a tree gives us Depth- | 
|  | // first search (DFS) order which is the correct order for writing archives. | 
|  | // This also makes the output reproducible. | 
|  | sort.Strings(writeOrder) | 
|  |  | 
|  | for _, path := range writeOrder { | 
|  | place := places[path] | 
|  | switch i := place.Inode.(type) { | 
|  | case *fsspec.File: | 
|  | inF, err := os.Open(i.SourcePath) | 
|  | if err != nil { | 
|  | log.Fatalf("Failed to open source path for file %q: %v", i.Path, err) | 
|  | } | 
|  | inFStat, err := inF.Stat() | 
|  | if err != nil { | 
|  | log.Fatalf("Failed to stat source path for file %q: %v", i.Path, err) | 
|  | } | 
|  | if err := cpioWriter.WriteHeader(&cpio.Header{ | 
|  | Mode: cpio.FileMode(i.Mode), | 
|  | Name: strings.TrimPrefix(i.Path, "/"), | 
|  | Size: inFStat.Size(), | 
|  | }); err != nil { | 
|  | log.Fatalf("Failed to write cpio header for file %q: %v", i.Path, err) | 
|  | } | 
|  | if _, err := io.Copy(cpioWriter, inF); err != nil { | 
|  | log.Fatalf("Failed to copy file %q into cpio: %v", i.SourcePath, err) | 
|  | } | 
|  | inF.Close() | 
|  | case *fsspec.Directory: | 
|  | if err := cpioWriter.WriteHeader(&cpio.Header{ | 
|  | Mode: cpio.FileMode(i.Mode) | cpio.TypeDir, | 
|  | Name: strings.TrimPrefix(i.Path, "/"), | 
|  | }); err != nil { | 
|  | log.Fatalf("Failed to write cpio header for directory %q: %v", i.Path, err) | 
|  | } | 
|  | case *fsspec.SymbolicLink: | 
|  | if err := cpioWriter.WriteHeader(&cpio.Header{ | 
|  | // Symlinks are 0777 by definition (from man 7 symlink on Linux) | 
|  | Mode:     0777 | cpio.TypeSymlink, | 
|  | Name:     strings.TrimPrefix(i.Path, "/"), | 
|  | Linkname: i.TargetPath, | 
|  | }); err != nil { | 
|  | log.Fatalf("Failed to write cpio header for symlink %q: %v", i.Path, err) | 
|  | } | 
|  | case *fsspec.SpecialFile: | 
|  | mode := cpio.FileMode(i.Mode) | 
|  | switch i.Type { | 
|  | case fsspec.SpecialFile_CHARACTER_DEV: | 
|  | mode |= cpio.TypeChar | 
|  | case fsspec.SpecialFile_BLOCK_DEV: | 
|  | mode |= cpio.TypeBlock | 
|  | case fsspec.SpecialFile_FIFO: | 
|  | mode |= cpio.TypeFifo | 
|  | } | 
|  |  | 
|  | if err := cpioWriter.WriteHeader(&cpio.Header{ | 
|  | Mode:     mode, | 
|  | Name:     strings.TrimPrefix(i.Path, "/"), | 
|  | DeviceID: int(unix.Mkdev(i.Major, i.Minor)), | 
|  | }); err != nil { | 
|  | log.Fatalf("Failed to write CPIO header for special file %q: %v", i.Path, err) | 
|  | } | 
|  | default: | 
|  | panic("inode type not handled") | 
|  | } | 
|  | } | 
|  | } |