cpio
packageAPI reference for the cpio
package.
Imports
(11)hex8To
Parameters
Returns
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[:]...)
}
writeNewcHeader
Parameters
Returns
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
}
writePad4
Parameters
Returns
func writePad4(w io.Writer, n uint32) error
{
p := pad4(n)
if p == 0 {
return nil
}
_, err := w.Write(make([]byte, p))
return err
}
normalizeName
Parameters
Returns
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
}
PackDir
PackDir packs an on-disk directory into a CPIO archive (deterministic order).
Parameters
Returns
func PackDir(root string, w io.Writer, opts ...WriterOption) error
{
wr := NewWriter(w, opts...)
root = filepath.Clean(root)
source, err := os.OpenRoot(root)
if err != nil {
return errors.Join(err, wr.Close())
}
defer source.Close()
walkErr := 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
}
if d.IsDir() {
return wr.AddDir(rel, 0755)
}
if d.Type()&fs.ModeSymlink != 0 {
return errors.New("cpio: symbolic links are not supported")
}
file, err := source.Open(filepath.FromSlash(rel))
if err != nil {
return err
}
info, statErr := file.Stat()
if statErr != nil {
file.Close()
return statErr
}
if !info.Mode().IsRegular() {
file.Close()
return errors.New("cpio: special files are not supported")
}
if info.Size() < 0 || uint64(info.Size()) > defaultMaxFileSize {
file.Close()
return errors.New("cpio: file exceeds archive limit")
}
b, readErr := io.ReadAll(io.LimitReader(file, int64(defaultMaxFileSize)+1))
closeErr := file.Close()
if readErr != nil || closeErr != nil {
return errors.Join(readErr, closeErr)
}
if uint64(len(b)) > defaultMaxFileSize {
return errors.New("cpio: file exceeds archive limit")
}
mode := fs.FileMode(0644)
if info.Mode()&0100 != 0 {
mode = 0755
}
return wr.AddFile(rel, mode, b)
})
return errors.Join(walkErr, wr.Close())
}
Entry
Entry represents a single CPIO newc entry.
The Data field is only set for regular files.
type Entry struct
Methods
IsTrailer reports whether the entry is the CPIO trailer.
Returns
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 |
Reader
Reader reads CPIO newc archives.
type Reader struct
Methods
Next returns the next entry. When the archive ends, it returns io.EOF.
Returns
func (*Reader) Next() (*Entry, error)
{
if rd == nil || rd.done {
return nil, io.EOF
}
var hdr [110]byte
n, err := io.ReadFull(rd.r, hdr[:])
if err != nil {
if errors.Is(err, io.EOF) && n == 0 {
return nil, errors.New("cpio: archive missing trailer")
}
return nil, err
}
magic := string(hdr[0:6])
if magic != magicNewc {
return nil, errors.New("cpio: invalid magic")
}
mode, err := parseHex8(hdr[14:22])
if err != nil {
return nil, err
}
uid, err := parseHex8(hdr[22:30])
if err != nil {
return nil, err
}
gid, err := parseHex8(hdr[30:38])
if err != nil {
return nil, err
}
nlink, err := parseHex8(hdr[38:46])
if err != nil {
return nil, err
}
mtime, err := parseHex8(hdr[46:54])
if err != nil {
return nil, err
}
filesize, err := parseHex8(hdr[54:62])
if err != nil {
return nil, err
}
namesize, err := parseHex8(hdr[94:102])
if err != nil {
return nil, err
}
if namesize == 0 {
return nil, errors.New("cpio: invalid namesize")
}
if uint64(namesize) > rd.maxNameSize {
return nil, errors.New("cpio: entry name exceeds configured limit")
}
if uint64(filesize) > rd.maxFileSize {
return nil, errors.New("cpio: entry data exceeds configured limit")
}
if uint64(filesize) > rd.maxTotalSize-rd.totalSize {
return nil, errors.New("cpio: archive data exceeds configured limit")
}
rd.totalSize += uint64(filesize)
nameb := make([]byte, namesize)
if _, err := io.ReadFull(rd.r, nameb); err != nil {
return nil, err
}
if nameb[len(nameb)-1] != 0 || bytes.IndexByte(nameb[:len(nameb)-1], 0) >= 0 {
return nil, errors.New("cpio: invalid entry name terminator")
}
name := string(nameb[:len(nameb)-1])
// Align after header + name.
if err := discardN(rd.r, pad4(110+namesize)); err != nil {
return nil, err
}
if name == "TRAILER!!!" {
rd.done = true
return nil, io.EOF
}
if rd.entries >= rd.maxEntries {
return nil, errors.New("cpio: archive entry count exceeds configured limit")
}
rd.entries++
data := make([]byte, filesize)
if filesize > 0 {
if _, err := io.ReadFull(rd.r, data); err != nil {
return nil, err
}
}
if err := discardN(rd.r, pad4(filesize)); err != nil {
return nil, err
}
return &Entry{
Name: name,
Mode: mode,
UID: uid,
GID: gid,
NLink: nlink,
MTime: mtime,
FileSize: filesize,
Data: data,
}, nil
}
Fields
| Name | Type | Description |
|---|---|---|
| r | io.Reader | |
| done | bool | |
| maxNameSize | uint64 | |
| maxFileSize | uint64 | |
| maxTotalSize | uint64 | |
| totalSize | uint64 | |
| maxEntries | uint64 | |
| entries | uint64 |
WithReaderMaxEntries
WithReaderMaxEntries sets the maximum number of non-trailer entries.
Parameters
Returns
func WithReaderMaxEntries(maxEntries uint64) ReaderOption
{
return func(reader *Reader) {
reader.maxEntries = maxEntries
}
}
Uses
ReaderOption
ReaderOption configures archive read limits.
type ReaderOption func(*Reader)
WithReaderLimits
WithReaderLimits sets maximum name, entry, and total data sizes.
Parameters
Returns
func WithReaderLimits(maxNameSize, maxFileSize, maxTotalSize uint64) ReaderOption
{
return func(reader *Reader) {
reader.maxNameSize = maxNameSize
reader.maxFileSize = maxFileSize
reader.maxTotalSize = maxTotalSize
}
}
Uses
NewReader
NewReader creates a new CPIO reader from the given io.Reader.
func NewReader(r io.Reader, opts ...ReaderOption) *Reader
{
reader := &Reader{
r: r,
maxNameSize: defaultMaxNameSize,
maxFileSize: defaultMaxFileSize,
maxTotalSize: defaultMaxTotalSize,
maxEntries: defaultMaxEntries,
}
for _, option := range opts {
option(reader)
}
return reader
}
Writer
Writer writes CPIO newc archives.
type Writer struct
Methods
Parameters
Returns
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
}
if wr.entries >= wr.maxEntries {
return errors.New("cpio: archive entry count exceeds limit")
}
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
}
if err := writePad4(wr.w, 110+namesize); err != nil {
return err
}
wr.entries++
return nil
}
Parameters
Returns
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
}
size := uint64(len(data))
if wr.entries >= wr.maxEntries {
return errors.New("cpio: archive entry count exceeds limit")
}
if size > wr.maxFileSize {
return errors.New("cpio: file data exceeds archive limit")
}
if size > wr.maxTotalSize-wr.totalSize {
return errors.New("cpio: archive data exceeds total limit")
}
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
}
}
if err := writePad4(wr.w, filesize); err != nil {
return err
}
wr.entries++
wr.totalSize += size
return nil
}
Close writes the TRAILER!!! entry.
Returns
func (*Writer) Close() error
{
if wr == nil || wr.closed {
return nil
}
wr.closed = true
trail := "TRAILER!!!"
namesize := uint32(len(trail) + 1)
if err := writeNewcHeader(wr.w, wr.ino, 0, 0, 0, 1, wr.mtime, 0, 0, 0, 0, 0, namesize); err != nil {
return err
}
if _, err := io.WriteString(wr.w, trail); err != nil {
return err
}
if _, err := wr.w.Write([]byte{0}); err != nil {
return err
}
return writePad4(wr.w, 110+namesize)
}
Fields
| Name | Type | Description |
|---|---|---|
| w | io.Writer | |
| ino | uint32 | |
| uid | uint32 | |
| gid | uint32 | |
| mtime | uint32 | |
| closed | bool | |
| maxFileSize | uint64 | |
| maxTotalSize | uint64 | |
| maxEntries | uint64 | |
| totalSize | uint64 | |
| entries | uint64 |
WriterOption
WriterOption configures a Writer.
type WriterOption func(*Writer)
WithMTimeUnix
WithMTimeUnix sets the mtime for the Writer.
Parameters
Returns
func WithMTimeUnix(mtime uint32) WriterOption
{ return func(w *Writer) { w.mtime = mtime } }
Uses
WithUIDGID
WithUIDGID sets the UID and GID for the Writer.
Parameters
Returns
func WithUIDGID(uid, gid uint32) WriterOption
{
return func(w *Writer) {
w.uid = uid
w.gid = gid
}
}
Uses
WithWriterLimits
WithWriterLimits sets maximum entry, total data, and entry count limits.
Parameters
Returns
func WithWriterLimits(maxFileSize, maxTotalSize, maxEntries uint64) WriterOption
{
return func(w *Writer) {
w.maxFileSize = maxFileSize
w.maxTotalSize = maxTotalSize
w.maxEntries = maxEntries
}
}
Uses
NewWriter
NewWriter creates a new CPIO newc Writer writing to w.
func NewWriter(w io.Writer, opts ...WriterOption) *Writer
{
wr := &Writer{
w: w,
ino: 1,
maxFileSize: defaultMaxFileSize,
maxTotalSize: defaultMaxTotalSize,
maxEntries: defaultMaxEntries,
}
for _, opt := range opts {
opt(wr)
}
return wr
}
TestPackAndReadRoundtrip
Parameters
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)
}
}
TestUnpackToDir
Parameters
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))
}
}
TestReaderEnforcesConfiguredLimits
Parameters
func TestReaderEnforcesConfiguredLimits(t *testing.T)
{
var archive bytes.Buffer
writer := NewWriter(&archive)
if err := writer.AddFile("large", 0644, []byte("0123456789")); err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
reader := NewReader(bytes.NewReader(archive.Bytes()), WithReaderLimits(32, 4, 4))
if _, err := reader.Next(); err == nil {
t.Fatal("Reader accepted an entry larger than the configured limit")
}
}
TestReaderEnforcesTotalLimit
Parameters
func TestReaderEnforcesTotalLimit(t *testing.T)
{
var archive bytes.Buffer
writer := NewWriter(&archive)
if err := writer.AddFile("one", 0644, []byte("1234")); err != nil {
t.Fatal(err)
}
if err := writer.AddFile("two", 0644, []byte("5678")); err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
reader := NewReader(bytes.NewReader(archive.Bytes()), WithReaderLimits(32, 8, 6))
if _, err := reader.Next(); err != nil {
t.Fatal(err)
}
if _, err := reader.Next(); err == nil {
t.Fatal("Reader accepted archive data beyond the total limit")
}
}
TestReaderEnforcesEntryCountLimit
Parameters
func TestReaderEnforcesEntryCountLimit(t *testing.T)
{
var archive bytes.Buffer
writer := NewWriter(&archive)
if err := writer.AddFile("one", 0644, nil); err != nil {
t.Fatal(err)
}
if err := writer.AddFile("two", 0644, nil); err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
reader := NewReader(bytes.NewReader(archive.Bytes()), WithReaderMaxEntries(1))
if _, err := reader.Next(); err != nil {
t.Fatal(err)
}
if _, err := reader.Next(); err == nil {
t.Fatal("Reader accepted too many empty entries")
}
}
TestWriterEnforcesConfiguredLimits
Parameters
func TestWriterEnforcesConfiguredLimits(t *testing.T)
{
t.Run("file size", func(t *testing.T) {
writer := NewWriter(io.Discard, WithWriterLimits(2, 10, 10))
if err := writer.AddFile("large", 0644, []byte("123")); err == nil {
t.Fatal("Writer accepted a file beyond its entry limit")
}
})
t.Run("total size", func(t *testing.T) {
writer := NewWriter(io.Discard, WithWriterLimits(4, 5, 10))
if err := writer.AddFile("one", 0644, []byte("123")); err != nil {
t.Fatal(err)
}
if err := writer.AddFile("two", 0644, []byte("456")); err == nil {
t.Fatal("Writer accepted data beyond its total limit")
}
})
t.Run("entry count", func(t *testing.T) {
writer := NewWriter(io.Discard, WithWriterLimits(4, 10, 1))
if err := writer.AddDir("one", 0755); err != nil {
t.Fatal(err)
}
if err := writer.AddFile("two", 0644, nil); err == nil {
t.Fatal("Writer accepted too many entries")
}
})
}
TestUnpackRejectsSymlinkEscape
Parameters
func TestUnpackRejectsSymlinkEscape(t *testing.T)
{
out := t.TempDir()
outside := t.TempDir()
if err := os.Symlink(outside, filepath.Join(out, "link")); err != nil {
t.Fatal(err)
}
var archive bytes.Buffer
writer := NewWriter(&archive)
if err := writer.AddFile("link/escaped", 0644, []byte("owned")); err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
if err := UnpackToDir(bytes.NewReader(archive.Bytes()), out); err == nil {
t.Fatal("UnpackToDir() followed a symlink outside the destination")
}
if _, err := os.Stat(filepath.Join(outside, "escaped")); !os.IsNotExist(err) {
t.Fatalf("outside file exists: %v", err)
}
}
TestPackDirRejectsSymlinks
Parameters
func TestPackDirRejectsSymlinks(t *testing.T)
{
root := t.TempDir()
outside := filepath.Join(t.TempDir(), "secret")
if err := os.WriteFile(outside, []byte("secret"), 0600); err != nil {
t.Fatal(err)
}
if err := os.Symlink(outside, filepath.Join(root, "secret")); err != nil {
t.Fatal(err)
}
var archive bytes.Buffer
if err := PackDir(root, &archive); err == nil {
t.Fatal("PackDir() followed a symbolic link")
}
}
rejectingWriter
type rejectingWriter struct
Methods
Parameters
Returns
func (rejectingWriter) Write([]byte) (int, error)
{
return 0, errors.New("write failed")
}
TestPackDirReturnsTrailerWriteError
Parameters
func TestPackDirReturnsTrailerWriteError(t *testing.T)
{
if err := PackDir(t.TempDir(), rejectingWriter{}); err == nil {
t.Fatal("PackDir() ignored trailer write failure")
}
}
TestReaderRejectsArchiveWithoutTrailer
Parameters
func TestReaderRejectsArchiveWithoutTrailer(t *testing.T)
{
var archive bytes.Buffer
writer := NewWriter(&archive)
if err := writer.AddFile("file", 0644, []byte("data")); err != nil {
t.Fatal(err)
}
reader := NewReader(bytes.NewReader(archive.Bytes()))
if _, err := reader.Next(); err != nil {
t.Fatal(err)
}
if _, err := reader.Next(); err == nil || errors.Is(err, io.EOF) {
t.Fatalf("Next() error = %v, want missing trailer error", err)
}
}
TestReaderRejectsCRCArchiveWithoutChecksumValidation
Parameters
func TestReaderRejectsCRCArchiveWithoutChecksumValidation(t *testing.T)
{
var archive bytes.Buffer
writer := NewWriter(&archive)
if err := writer.AddFile("file", 0644, []byte("data")); err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
data := archive.Bytes()
copy(data[:6], "070702")
if _, err := NewReader(bytes.NewReader(data)).Next(); err == nil {
t.Fatal("Reader accepted an unsupported CRC archive")
}
}
parseHex8
Parameters
Returns
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
}
pad4
Parameters
Returns
func pad4(n uint32) uint32
{
rem := n & 3
if rem == 0 {
return 0
}
return 4 - rem
}
discardN
Parameters
Returns
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
}
safeJoin
Parameters
Returns
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
}
permsFromMode
Parameters
Returns
func permsFromMode(mode uint32) fs.FileMode
{
return fs.FileMode(mode & 0777)
}
UnpackToDir
UnpackToDir unpacks a CPIO archive into dst.
Parameters
Returns
func UnpackToDir(r io.Reader, dst string) error
{
if err := os.MkdirAll(dst, 0755); err != nil {
return err
}
root, err := os.OpenRoot(dst)
if err != nil {
return err
}
defer root.Close()
if err := rejectDestinationSymlinks(dst); err != nil {
return err
}
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
}
relative, err := filepath.Rel(dst, outPath)
if err != nil {
return err
}
isDir := (e.Mode & 0170000) == 0040000
if isDir {
if err := ensureDirectories(root, relative); err != nil {
return err
}
continue
}
if (e.Mode & 0170000) != 0100000 {
return errors.New("cpio: unsupported archive entry type")
}
if err := ensureDirectories(root, filepath.Dir(relative)); err != nil {
return err
}
tempName, file, err := createTempFile(root, filepath.Dir(relative), permsFromMode(e.Mode))
if err != nil {
return err
}
if _, err := file.Write(e.Data); err != nil {
file.Close()
_ = root.Remove(tempName)
return err
}
if err := file.Close(); err != nil {
_ = root.Remove(tempName)
return err
}
if err := root.Rename(tempName, relative); err != nil {
_ = root.Remove(tempName)
return err
}
}
}
rejectDestinationSymlinks
Parameters
Returns
func rejectDestinationSymlinks(dst string) error
{
return filepath.WalkDir(dst, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if entry.Type()&fs.ModeSymlink != 0 {
return fmt.Errorf("cpio: destination contains symbolic link %q", path)
}
return nil
})
}
ensureDirectories
Parameters
Returns
func ensureDirectories(root *os.Root, relative string) error
{
clean := filepath.Clean(relative)
if clean == "." {
return nil
}
current := ""
for _, component := range strings.Split(clean, string(filepath.Separator)) {
if component == "" || component == "." {
continue
}
current = filepath.Join(current, component)
info, err := root.Lstat(current)
switch {
case err == nil && info.Mode()&fs.ModeSymlink != 0:
return fmt.Errorf("cpio: refusing symbolic link directory %q", current)
case err == nil && !info.IsDir():
return fmt.Errorf("cpio: path component %q is not a directory", current)
case err == nil:
continue
case !errors.Is(err, fs.ErrNotExist):
return err
}
if err := root.Mkdir(current, 0755); err != nil {
return err
}
}
return nil
}
createTempFile
Parameters
Returns
func createTempFile(root *os.Root, directory string, mode fs.FileMode) (string, *os.File, error)
{
for range 100 {
var random [16]byte
if _, err := rand.Read(random[:]); err != nil {
return "", nil, err
}
name := filepath.Join(directory, ".foundation-"+hex.EncodeToString(random[:])+".tmp")
file, err := root.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode)
if err == nil {
return name, file, nil
}
if !errors.Is(err, fs.ErrExist) {
return "", nil, err
}
}
return "", nil, errors.New("cpio: cannot allocate temporary extraction file")
}