cpio API

cpio

package

API reference for the cpio package.

F
function

parseHex8

Parameters

b
[]byte

Returns

uint32
error
pkg/cpio/reader.go:8-26
func parseHex8(b []byte) (uint32, error)

{
	var v uint32
	for i := 0; i < 8 && i < len(b); i++ {
		c := b[i]
		var n uint32
		switch {
		case c >= '0' && c <= '9':
			n = uint32(c - '0')
		case c >= 'A' && c <= 'F':
			n = uint32(c-'A') + 10
		case c >= 'a' && c <= 'f':
			n = uint32(c-'a') + 10
		default:
			return 0, errors.New("cpio: invalid hex")
		}
		v = (v << 4) | n
	}
	return v, nil
}
F
function

pad4

Parameters

n
uint32

Returns

uint32
pkg/cpio/reader.go:28-34
func pad4(n uint32) uint32

{
	rem := n & 3
	if rem == 0 {
		return 0
	}
	return 4 - rem
}
F
function

discardN

Parameters

n
uint32

Returns

error
pkg/cpio/reader.go:36-53
func discardN(r io.Reader, n uint32) error

{
	if n == 0 {
		return nil
	}
	buf := make([]byte, 128)
	for n > 0 {
		chunk := uint32(len(buf))
		if chunk > n {
			chunk = n
		}
		_, err := io.ReadFull(r, buf[:chunk])
		if err != nil {
			return err
		}
		n -= chunk
	}
	return nil
}
F
function

safeJoin

Parameters

dst
string
name
string

Returns

string
error
pkg/cpio/unpack.go:12-23
func safeJoin(dst, name string) (string, error)

{
	name = filepath.ToSlash(name)
	name = strings.TrimPrefix(name, "/")
	clean := filepath.Clean(name)
	if clean == "." || clean == "" {
		return "", errors.New("cpio: invalid name")
	}
	if strings.HasPrefix(clean, "..") || strings.Contains(clean, "../") {
		return "", errors.New("cpio: path traversal")
	}
	return filepath.Join(dst, filepath.FromSlash(clean)), nil
}
F
function

permsFromMode

Parameters

mode
uint32

Returns

pkg/cpio/unpack.go:25-27
func permsFromMode(mode uint32) fs.FileMode

{
	return fs.FileMode(mode & 0777)
}
F
function

UnpackToDir

UnpackToDir unpacks a CPIO archive into dst.

Parameters

dst
string

Returns

error
pkg/cpio/unpack.go:30-61
func UnpackToDir(r io.Reader, dst string) error

{
	rd := NewReader(r)
	for {
		e, err := rd.Next()
		if err != nil {
			if errors.Is(err, io.EOF) {
				return nil
			}
			return err
		}

		outPath, err := safeJoin(dst, e.Name)
		if err != nil {
			return err
		}

		isDir := (e.Mode & 0170000) == 0040000
		if isDir {
			if err := os.MkdirAll(outPath, 0755); err != nil {
				return err
			}
			continue
		}

		if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
			return err
		}
		if err := os.WriteFile(outPath, e.Data, permsFromMode(e.Mode)); err != nil {
			return err
		}
	}
}
F
function

hex8To

Parameters

dst
[]byte
v
uint32

Returns

[]byte
pkg/cpio/writer.go:14-21
func hex8To(dst []byte, v uint32) []byte

{
	var out [8]byte
	for i := 7; i >= 0; i-- {
		out[i] = hexDigits[v&0xF]
		v >>= 4
	}
	return append(dst, out[:]...)
}
F
function

writeNewcHeader

Parameters

ino
uint32
mode
uint32
uid
uint32
gid
uint32
nlink
uint32
mtime
uint32
filesize
uint32
devmaj
uint32
devmin
uint32
rdevmaj
uint32
rdevmin
uint32
namesize
uint32

Returns

error
pkg/cpio/writer.go:23-41
func writeNewcHeader(w io.Writer, ino, mode, uid, gid, nlink, mtime, filesize, devmaj, devmin, rdevmaj, rdevmin, namesize uint32) error

{
	b := make([]byte, 0, 110)
	b = append(b, magicNewc...)
	b = hex8To(b, ino)
	b = hex8To(b, mode)
	b = hex8To(b, uid)
	b = hex8To(b, gid)
	b = hex8To(b, nlink)
	b = hex8To(b, mtime)
	b = hex8To(b, filesize)
	b = hex8To(b, devmaj)
	b = hex8To(b, devmin)
	b = hex8To(b, rdevmaj)
	b = hex8To(b, rdevmin)
	b = hex8To(b, namesize)
	b = hex8To(b, 0) // check
	_, err := w.Write(b)
	return err
}
F
function

writePad4

Parameters

n
uint32

Returns

error
pkg/cpio/writer.go:43-50
func writePad4(w io.Writer, n uint32) error

{
	p := pad4(n)
	if p == 0 {
		return nil
	}
	_, err := w.Write(make([]byte, p))
	return err
}
F
function

normalizeName

Parameters

name
string

Returns

string
error
pkg/cpio/writer.go:52-68
func normalizeName(name string) (string, error)

{
	name = filepath.ToSlash(name)
	name = strings.TrimPrefix(name, "./")
	name = strings.TrimPrefix(name, "/")
	name = strings.TrimSpace(name)
	if name == "" {
		return "", errors.New("cpio: empty name")
	}

	clean := filepath.Clean(name)
	clean = filepath.ToSlash(clean)
	clean = strings.TrimPrefix(clean, "./")
	if clean == "." || clean == "" || clean == ".." || strings.HasPrefix(clean, "../") || strings.Contains(clean, "/../") {
		return "", errors.New("cpio: invalid name")
	}
	return clean, nil
}
F
function

PackDir

PackDir packs an on-disk directory into a CPIO archive (deterministic order).

Parameters

root
string
opts
...WriterOption

Returns

error
pkg/cpio/writer.go:142-180
func PackDir(root string, w io.Writer, opts ...WriterOption) error

{
	wr := NewWriter(w, opts...)
	defer wr.Close()

	root = filepath.Clean(root)
	return filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		rel, err := filepath.Rel(root, path)
		if err != nil {
			return err
		}
		rel = filepath.ToSlash(rel)
		if rel == "." {
			return nil
		}

		info, err := d.Info()
		if err != nil {
			return err
		}

		if d.IsDir() {
			return wr.AddDir(rel, 0755)
		}

		mode := fs.FileMode(0644)
		if info.Mode()&0100 != 0 {
			mode = 0755
		}

		b, err := os.ReadFile(path)
		if err != nil {
			return err
		}
		return wr.AddFile(rel, mode, b)
	})
}
S
struct

Entry

Entry represents a single CPIO newc entry.

The Data field is only set for regular files.

pkg/cpio/cpio.go:13-22
type Entry struct

Methods

IsTrailer
Method

Returns

bool
func (*Entry) IsTrailer() bool
{ return e != nil && e.Name == "TRAILER!!!" }

Fields

Name Type Description
Name string
Mode uint32
UID uint32
GID uint32
NLink uint32
MTime uint32
FileSize uint32
Data []byte
S
struct

Reader

Reader reads CPIO newc/crc archives.

pkg/cpio/cpio.go:27-30
type Reader struct

Methods

Fields

Name Type Description
r io.Reader
done bool
F
function

NewReader

Parameters

Returns

pkg/cpio/cpio.go:32-32
func NewReader(r io.Reader) *Reader

{ return &Reader{r: r} }
S
struct

Writer

Writer writes CPIO newc archives.

pkg/cpio/cpio.go:35-42
type Writer struct

Methods

AddDir
Method

Parameters

name string

Returns

error
func (*Writer) AddDir(name string, mode fs.FileMode) error
{
	if wr == nil || wr.closed {
		return errors.New("cpio: writer closed")
	}
	name, err := normalizeName(name)
	if err != nil {
		return err
	}
	perm := uint32(mode.Perm())
	cmode := uint32(0040000) | perm
	namesize := uint32(len(name) + 1)
	if err := writeNewcHeader(wr.w, wr.ino, cmode, wr.uid, wr.gid, 2, wr.mtime, 0, 0, 0, 0, 0, namesize); err != nil {
		return err
	}
	wr.ino++
	if _, err := io.WriteString(wr.w, name); err != nil {
		return err
	}
	if _, err := wr.w.Write([]byte{0}); err != nil {
		return err
	}
	return writePad4(wr.w, 110+namesize)
}
AddFile
Method

Parameters

name string
data []byte

Returns

error
func (*Writer) AddFile(name string, mode fs.FileMode, data []byte) error
{
	if wr == nil || wr.closed {
		return errors.New("cpio: writer closed")
	}
	name, err := normalizeName(name)
	if err != nil {
		return err
	}
	perm := uint32(mode.Perm())
	cmode := uint32(0100000) | perm
	filesize := uint32(len(data))
	namesize := uint32(len(name) + 1)
	if err := writeNewcHeader(wr.w, wr.ino, cmode, wr.uid, wr.gid, 1, wr.mtime, filesize, 0, 0, 0, 0, namesize); err != nil {
		return err
	}
	wr.ino++
	if _, err := io.WriteString(wr.w, name); err != nil {
		return err
	}
	if _, err := wr.w.Write([]byte{0}); err != nil {
		return err
	}
	if err := writePad4(wr.w, 110+namesize); err != nil {
		return err
	}
	if filesize > 0 {
		if _, err := wr.w.Write(data); err != nil {
			return err
		}
	}
	return writePad4(wr.w, filesize)
}
Close
Method

Close writes the TRAILER!!! entry.

Returns

error
func (*Writer) Close() error
{
	if wr == nil || wr.closed {
		return nil
	}
	wr.closed = true
	trail := "TRAILER!!!"
	namesize := uint32(len(trail) + 1)
	_ = writeNewcHeader(wr.w, wr.ino, 0, 0, 0, 1, wr.mtime, 0, 0, 0, 0, 0, namesize)
	_, _ = io.WriteString(wr.w, trail)
	_, _ = wr.w.Write([]byte{0})
	return writePad4(wr.w, 110+namesize)
}

Fields

Name Type Description
w io.Writer
ino uint32
uid uint32
gid uint32
mtime uint32
closed bool
T
type

WriterOption

pkg/cpio/cpio.go:44-44
type WriterOption func(*Writer)
F
function

WithMTimeUnix

Parameters

mtime
uint32

Returns

pkg/cpio/cpio.go:46-46
func WithMTimeUnix(mtime uint32) WriterOption

{ return func(w *Writer) { w.mtime = mtime } }
F
function

WithUIDGID

Parameters

uid
uint32
gid
uint32

Returns

pkg/cpio/cpio.go:47-52
func WithUIDGID(uid, gid uint32) WriterOption

{
	return func(w *Writer) {
		w.uid = uid
		w.gid = gid
	}
}
F
function

NewWriter

Parameters

opts
...WriterOption

Returns

pkg/cpio/cpio.go:54-60
func NewWriter(w io.Writer, opts ...WriterOption) *Writer

{
	wr := &Writer{w: w, ino: 1}
	for _, opt := range opts {
		opt(wr)
	}
	return wr
}
F
function

TestPackAndReadRoundtrip

Parameters

pkg/cpio/cpio_test.go:11-41
func TestPackAndReadRoundtrip(t *testing.T)

{
	tmp := t.TempDir()
	if err := os.MkdirAll(filepath.Join(tmp, "bin"), 0755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(tmp, "bin", "hello.txt"), []byte("hello"), 0644); err != nil {
		t.Fatal(err)
	}

	var buf bytes.Buffer
	if err := PackDir(tmp, &buf, WithMTimeUnix(0)); err != nil {
		t.Fatal(err)
	}

	rd := NewReader(bytes.NewReader(buf.Bytes()))
	seen := map[string][]byte{}
	for {
		e, err := rd.Next()
		if err != nil {
			if err == io.EOF {
				break
			}
			t.Fatal(err)
		}
		seen[e.Name] = e.Data
	}

	if got := string(seen["bin/hello.txt"]); got != "hello" {
		t.Fatalf("got %q", got)
	}
}
F
function

TestUnpackToDir

Parameters

pkg/cpio/cpio_test.go:43-68
func TestUnpackToDir(t *testing.T)

{
	tmp := t.TempDir()
	if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(tmp, "etc", "conf"), []byte("x=y"), 0644); err != nil {
		t.Fatal(err)
	}

	var buf bytes.Buffer
	if err := PackDir(tmp, &buf, WithMTimeUnix(0)); err != nil {
		t.Fatal(err)
	}

	out := t.TempDir()
	if err := UnpackToDir(bytes.NewReader(buf.Bytes()), out); err != nil {
		t.Fatal(err)
	}
	b, err := os.ReadFile(filepath.Join(out, "etc", "conf"))
	if err != nil {
		t.Fatal(err)
	}
	if string(b) != "x=y" {
		t.Fatalf("got %q", string(b))
	}
}