// writeStdioSymlinks adds the /dev/stdin, /dev/stdout, /dev/stderr, and // /dev/fd symlinks expected by Docker to the converted ACIs so apps can find // them as expected func writeStdioSymlinks(tarWriter *tar.Writer, fileMap map[string]struct{}, pwl []string) ([]string, error) { stdioSymlinks := []symlink{ {"/dev/stdin", "/proc/self/fd/0"}, // Docker makes /dev/{stdout,stderr} point to /proc/self/fd/{1,2} but // we point to /dev/console instead in order to support the case when // stdout/stderr is a Unix socket (e.g. for the journal). {"/dev/stdout", "/dev/console"}, {"/dev/stderr", "/dev/console"}, {"/dev/fd", "/proc/self/fd"}, } for _, s := range stdioSymlinks { name := s.linkname target := s.target if _, exists := fileMap[name]; exists { continue } hdr := &tar.Header{ Name: filepath.Join("rootfs", name), Mode: 0777, Typeflag: tar.TypeSymlink, Linkname: target, } if err := tarWriter.WriteHeader(hdr); err != nil { return nil, err } if !util.In(pwl, name) { pwl = append(pwl, name) } } return pwl, nil }
func writeACI(layer io.ReadSeeker, manifest schema.ImageManifest, curPwl []string, output string, compression common.Compression) (*schema.ImageManifest, error) { 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 }