diff --git a/pkg/initrd/initrd.go b/pkg/initrd/initrd.go new file mode 100644 index 000000000..51495b2aa --- /dev/null +++ b/pkg/initrd/initrd.go @@ -0,0 +1,136 @@ +package initrd + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "errors" + "io" + + "github.com/docker/moby/pkg/pad4" + "github.com/surma/gocpio" +) + +// Writer is an io.WriteCloser that writes to an initrd +// This is a compressed cpio archive, zero padded to 4 bytes +type Writer struct { + pw *pad4.Writer + gw *gzip.Writer + cw *cpio.Writer +} + +func typeconv(t byte) int64 { + switch t { + case tar.TypeReg: + return cpio.TYPE_REG + case tar.TypeRegA: + return cpio.TYPE_REG + // Currently hard links not supported + case tar.TypeLink: + return cpio.TYPE_REG + case tar.TypeSymlink: + return cpio.TYPE_SYMLINK + case tar.TypeChar: + return cpio.TYPE_CHAR + case tar.TypeBlock: + return cpio.TYPE_BLK + case tar.TypeDir: + return cpio.TYPE_DIR + case tar.TypeFifo: + return cpio.TYPE_FIFO + default: + return -1 + } +} + +// CopyTar copies a tar stream into an initrd +func CopyTar(w *Writer, r *tar.Reader) (written int64, err error) { + for { + var thdr *tar.Header + thdr, err = r.Next() + if err == io.EOF { + return written, nil + } + if err != nil { + return + } + tp := typeconv(thdr.Typeflag) + if tp == -1 { + return written, errors.New("cannot convert tar file") + } + size := thdr.Size + if tp == cpio.TYPE_SYMLINK { + size = int64(len(thdr.Linkname)) + } + chdr := cpio.Header{ + Mode: thdr.Mode, + Uid: thdr.Uid, + Gid: thdr.Gid, + Mtime: thdr.ModTime.Unix(), + Size: size, + Devmajor: thdr.Devmajor, + Devminor: thdr.Devminor, + Type: tp, + Name: thdr.Name, + } + err = w.WriteHeader(&chdr) + if err != nil { + return + } + var n int64 + if tp == cpio.TYPE_SYMLINK { + buffer := bytes.NewBufferString(thdr.Linkname) + n, err = io.Copy(w, buffer) + } else { + n, err = io.Copy(w, r) + } + written += n + if err != nil { + return + } + } +} + +// NewWriter creates a writer that will output an initrd stream +func NewWriter(w io.Writer) *Writer { + initrd := new(Writer) + initrd.pw = pad4.NewWriter(w) + initrd.gw = gzip.NewWriter(initrd.pw) + initrd.cw = cpio.NewWriter(initrd.gw) + + return initrd +} + +// WriteHeader writes a cpio header into an initrd +func (w *Writer) WriteHeader(hdr *cpio.Header) error { + return w.cw.WriteHeader(hdr) +} + +// Write writes a cpio file into an initrd +func (w *Writer) Write(b []byte) (n int, e error) { + return w.cw.Write(b) +} + +// Close closes the writer +func (initrd *Writer) Close() error { + err1 := initrd.cw.Close() + err2 := initrd.gw.Close() + err3 := initrd.pw.Close() + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + if err3 != nil { + return err3 + } + return nil +} + +// Copy reads a tarball in a stream and outputs a compressed init ram disk +func Copy(w *Writer, r io.Reader) (int64, error) { + tr := tar.NewReader(r) + + return CopyTar(w, tr) +} diff --git a/pkg/pad4/pad4.go b/pkg/pad4/pad4.go new file mode 100644 index 000000000..008153b26 --- /dev/null +++ b/pkg/pad4/pad4.go @@ -0,0 +1,46 @@ +package pad4 + +import ( + "bytes" + "io" +) + +// A Writer is an io.WriteCloser. Writes are padded with zeros to 4 byte boundary +type Writer struct { + w io.Writer + count int +} + +// Write writes output +func (pad Writer) Write(p []byte) (int, error) { + n, err := pad.w.Write(p) + if err != nil { + return 0, err + } + pad.count += n + return n, nil +} + +// Close adds the padding +func (pad Writer) Close() error { + mod4 := pad.count & 3 + if mod4 == 0 { + return nil + } + zero := make([]byte, 4-mod4) + buf := bytes.NewBuffer(zero) + n, err := io.Copy(pad.w, buf) + if err != nil { + return err + } + pad.count += int(n) + return nil +} + +// NewWriter provides a new io.WriteCloser that zero pads the +// output to a multiple of four bytes +func NewWriter(w io.Writer) *Writer { + pad := new(Writer) + pad.w = w + return pad +}