package ociimg import ( "archive/tar" "errors" "io/fs" "os" "path" "path/filepath" "strings" "code.icb4dc0.de/buildr/buildr/internal/ioutils" ) func buildDirFromContent(cwd string, content map[string]string) (*dirNode, error) { rootNode := newDirNode() for origSource, target := range content { source := origSource if !filepath.IsAbs(source) { source = filepath.Join(cwd, source) } info, err := os.Stat(source) if err != nil { return nil, err } if !info.IsDir() { if strings.HasSuffix(target, "/") { _, fileName := filepath.Split(source) target = filepath.Join(target, fileName) } rootNode.addFile(source, target) continue } walkErr := filepath.WalkDir(source, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } relativeToSource, err := filepath.Rel(source, path) if err != nil { return err } else if relativeToSource == "." { return nil } if d.IsDir() { rootNode.addDir(relativeToSource) } else { rootNode.addFile(path, filepath.Join(target, relativeToSource)) } return nil }) if walkErr != nil { return nil, walkErr } } return rootNode, nil } type fileToTar struct { sourcePath string targetFileName string } func newDirNode() *dirNode { return &dirNode{ children: make(map[string]*dirNode), } } type dirNode struct { children map[string]*dirNode files []fileToTar } func (n *dirNode) addDir(dirPath string) { current := n for _, segment := range strings.Split(dirPath, "/") { if child, ok := n.children[segment]; ok { current = child } else { newNode := newDirNode() current.children[segment] = newNode current = newNode } } } func (n *dirNode) addFile(sourcePath, targetPath string) { if !filepath.IsAbs(targetPath) { targetPath = filepath.Join("", targetPath) } dirPath, fileName := filepath.Split(targetPath) current := n for _, segment := range strings.Split(dirPath, "/") { if segment == "" { continue } if child, ok := current.children[segment]; ok { current = child } else { newNode := &dirNode{ children: make(map[string]*dirNode), } current.children[segment] = newNode current = newNode } } current.files = append(current.files, fileToTar{ sourcePath: sourcePath, targetFileName: fileName, }) } func (n *dirNode) writeToTar(writer *tar.Writer, parent string) error { for segment, child := range n.children { segmentPath := filepath.Join(parent, segment) header := &tar.Header{ Name: segmentPath, Typeflag: tar.TypeDir, // Use a fixed Mode, so that this isn't sensitive to the directory and umask // under which it was created. Additionally, windows can only set 0222, // 0444, or 0666, none of which are executable. Mode: 0o555, ModTime: defaultCreationTime, } if err := writer.WriteHeader(header); err != nil { return err } if err := child.writeToTar(writer, segmentPath); err != nil { return err } } for i := range n.files { fileSpec := n.files[i] info, err := os.Stat(fileSpec.sourcePath) if err != nil { return err } f, err := os.Open(fileSpec.sourcePath) if err != nil { return err } header := &tar.Header{ Name: path.Join(parent, fileSpec.targetFileName), Size: info.Size(), Typeflag: tar.TypeReg, // Use a fixed Mode, so that this isn't sensitive to the directory and umask // under which it was created. Additionally, windows can only set 0222, // 0444, or 0666, none of which are executable. Mode: 0o555, ModTime: defaultCreationTime, } if err = writer.WriteHeader(header); err != nil { return errors.Join(err, f.Close()) } if _, err = ioutils.CopyWithPooledBuffer(writer, f); err != nil { return errors.Join(err, f.Close()) } if err := f.Close(); err != nil { return err } } return nil }