func writeACI(layer io.ReadSeeker, manifest schema.ImageManifest, curPwl []string, output string, compression common.Compression) (*schema.ImageManifest, error) { dir, _ := path.Split(output) if dir != "" { err := os.MkdirAll(dir, 0755) if err != nil { return nil, fmt.Errorf("error creating ACI parent dir: %v", err) } } aciFile, err := os.Create(output) if err != nil { return nil, fmt.Errorf("error creating ACI file: %v", err) } defer aciFile.Close() var w io.WriteCloser = aciFile if compression == common.GzipCompression { w = gzip.NewWriter(aciFile) defer w.Close() } trw := tar.NewWriter(w) defer trw.Close() if err := WriteRootfsDir(trw); err != nil { return nil, fmt.Errorf("error writing rootfs entry: %v", err) } fileMap := make(map[string]struct{}) var whiteouts []string convWalker := func(t *tarball.TarFile) error { name := t.Name() if name == "./" { return nil } t.Header.Name = path.Join("rootfs", name) absolutePath := strings.TrimPrefix(t.Header.Name, "rootfs") if filepath.Clean(absolutePath) == "/dev" && t.Header.Typeflag != tar.TypeDir { return fmt.Errorf(`invalid layer: "/dev" is not a directory`) } fileMap[absolutePath] = struct{}{} if strings.Contains(t.Header.Name, "/.wh.") { whiteouts = append(whiteouts, strings.Replace(absolutePath, ".wh.", "", 1)) return nil } if t.Header.Typeflag == tar.TypeLink { t.Header.Linkname = path.Join("rootfs", t.Linkname()) } if err := trw.WriteHeader(t.Header); err != nil { return err } if _, err := io.Copy(trw, t.TarStream); err != nil { return err } if !util.In(curPwl, absolutePath) { curPwl = append(curPwl, absolutePath) } return nil } tr, err := aci.NewCompressedTarReader(layer) if err == nil { defer tr.Close() // write files in rootfs/ if err := tarball.Walk(*tr.Reader, convWalker); err != nil { return nil, err } } else { // ignore errors: empty layers in tars generated by docker save are not // valid tar files so we ignore errors trying to open them. Converted // ACIs will have the manifest and an empty rootfs directory in any // case. } newPwl := subtractWhiteouts(curPwl, whiteouts) newPwl, err = writeStdioSymlinks(trw, fileMap, newPwl) if err != nil { return nil, err } // Let's copy the newly generated PathWhitelist to avoid unintended // side-effects manifest.PathWhitelist = make([]string, len(newPwl)) copy(manifest.PathWhitelist, newPwl) if err := WriteManifest(trw, manifest); err != nil { return nil, fmt.Errorf("error writing manifest: %v", err) } return &manifest, nil }
func writeSquashedImage(outputFile *os.File, renderedACI acirenderer.RenderedACI, aciProvider acirenderer.ACIProvider, manifests []schema.ImageManifest, compression common.Compression) error { var tarWriterTarget io.WriteCloser = outputFile switch compression { case common.NoCompression: case common.GzipCompression: tarWriterTarget = gzip.NewWriter(outputFile) defer tarWriterTarget.Close() default: return fmt.Errorf("unexpected compression enum value: %d", compression) } outputWriter := tar.NewWriter(tarWriterTarget) defer outputWriter.Close() finalManifest := mergeManifests(manifests) if err := internal.WriteManifest(outputWriter, finalManifest); err != nil { return err } if err := internal.WriteRootfsDir(outputWriter); err != nil { return err } type hardLinkEntry struct { firstLinkCleanName string firstLinkHeader tar.Header keepOriginal bool walked bool } // map aciFileKey -> cleanTarget -> hardLinkEntry hardLinks := make(map[string]map[string]hardLinkEntry) // first pass: read all the entries and build the hardLinks map in memory // but don't write on disk for _, aciFile := range renderedACI { rs, err := aciProvider.ReadStream(aciFile.Key) if err != nil { return err } defer rs.Close() hardLinks[aciFile.Key] = map[string]hardLinkEntry{} squashWalker := func(t *tarball.TarFile) error { cleanName := filepath.Clean(t.Name()) // the rootfs and the squashed manifest are added separately if cleanName == "manifest" || cleanName == "rootfs" { return nil } _, keep := aciFile.FileMap[cleanName] if keep && t.Header.Typeflag == tar.TypeLink { cleanTarget := filepath.Clean(t.Linkname()) if _, ok := hardLinks[aciFile.Key][cleanTarget]; !ok { _, keepOriginal := aciFile.FileMap[cleanTarget] hardLinks[aciFile.Key][cleanTarget] = hardLinkEntry{cleanName, *t.Header, keepOriginal, false} } } return nil } tr := tar.NewReader(rs) if err := tarball.Walk(*tr, squashWalker); err != nil { return err } } // second pass: write on disk for _, aciFile := range renderedACI { rs, err := aciProvider.ReadStream(aciFile.Key) if err != nil { return err } defer rs.Close() squashWalker := func(t *tarball.TarFile) error { cleanName := filepath.Clean(t.Name()) // the rootfs and the squashed manifest are added separately if cleanName == "manifest" || cleanName == "rootfs" { return nil } _, keep := aciFile.FileMap[cleanName] if link, ok := hardLinks[aciFile.Key][cleanName]; ok { if keep != link.keepOriginal { return fmt.Errorf("logic error: should we keep file %q?", cleanName) } if keep { if err := outputWriter.WriteHeader(t.Header); err != nil { return fmt.Errorf("error writing header: %v", err) } if _, err := io.Copy(outputWriter, t.TarStream); err != nil { return fmt.Errorf("error copying file into the tar out: %v", err) } } else { // The current file does not remain but there is a hard link pointing to // it. Write the current file but with the filename of the first hard link // pointing to it. That first hard link will not be written later, see // variable "alreadyWritten". link.firstLinkHeader.Size = t.Header.Size link.firstLinkHeader.Typeflag = t.Header.Typeflag link.firstLinkHeader.Linkname = "" if err := outputWriter.WriteHeader(&link.firstLinkHeader); err != nil { return fmt.Errorf("error writing header: %v", err) } if _, err := io.Copy(outputWriter, t.TarStream); err != nil { return fmt.Errorf("error copying file into the tar out: %v", err) } } } else if keep { alreadyWritten := false if t.Header.Typeflag == tar.TypeLink { cleanTarget := filepath.Clean(t.Linkname()) if link, ok := hardLinks[aciFile.Key][cleanTarget]; ok { if !link.keepOriginal { if link.walked { t.Header.Linkname = link.firstLinkCleanName } else { alreadyWritten = true } } link.walked = true hardLinks[aciFile.Key][cleanTarget] = link } } if !alreadyWritten { if err := outputWriter.WriteHeader(t.Header); err != nil { return fmt.Errorf("error writing header: %v", err) } if _, err := io.Copy(outputWriter, t.TarStream); err != nil { return fmt.Errorf("error copying file into the tar out: %v", err) } } } return nil } tr := tar.NewReader(rs) if err := tarball.Walk(*tr, squashWalker); err != nil { return err } } return nil }
func FileNaClEncrypt(filename string, compress bool, key *[KeySize]byte) ([]byte, error) { fd, err := goutil.FileSHA256(filename) if err != nil { return nil, err } // test compressible var comp bool payloadHeader := Header{ Version: Version, Digest: *fd, Compression: CompNone, } payloadHeader.MimeType, comp, err = goutil.FileCompressible(filename) if err != nil { return nil, err } if compress { if comp { payloadHeader.Compression = CompGZIP } } else { comp = false } // set up reader f, err := os.Open(filename) if err != nil { return nil, err } defer func() { _ = f.Close() }() fi, err := f.Stat() if err != nil { return nil, err } payloadHeader.Size = uint64(fi.Size()) // encode payload [nonce][blob] var payload bytes.Buffer pw := bufio.NewWriter(&payload) // nonce nonce, err := NaClNonce() if err != nil { return nil, err } _, err = pw.Write(nonce[:]) if err != nil { return nil, err } // create payload var b bytes.Buffer // can't encode directly into b because of appended 0x0a _, err = xdr.Marshal(&b, payloadHeader) if err != nil { return nil, err } var w io.Writer if comp { // per https://github.com/klauspost/pgzip use pgzip on > 1MB if fi.Size() > 1024*1024 { w = pgzip.NewWriter(&b) } else { w = gzip.NewWriter(&b) } } else { w = bufio.NewWriter(&b) } // file content _, err = io.Copy(w, f) if err != nil { return nil, err } _, ok := w.(io.WriteCloser) if ok { w.(io.WriteCloser).Close() } else { w.(*bufio.Writer).Flush() } // encrypt encryptedPayload := secretbox.Seal(nil, b.Bytes(), nonce, key) // append encryptedPayload to payload pw.Write(encryptedPayload) pw.Flush() return payload.Bytes(), nil }