162 lines
3.2 KiB
Go
162 lines
3.2 KiB
Go
package nar
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"path"
|
|
"sort"
|
|
|
|
"git.lukegb.com/lukegb/depot/go/nix/nixwire"
|
|
)
|
|
|
|
type FS interface {
|
|
Open(string) (fs.File, error)
|
|
Stat(string) (fs.FileInfo, error)
|
|
Lstat(string) (fs.FileInfo, error)
|
|
Readlink(string) (string, error)
|
|
}
|
|
|
|
func packFile(sw nixwire.Serializer, root FS, fn string, stat fs.FileInfo) (int64, error) {
|
|
var nSoFar int64
|
|
write := func(data ...any) (int64, error) {
|
|
for _, datum := range data {
|
|
var n int64
|
|
var err error
|
|
switch datum := datum.(type) {
|
|
case string:
|
|
n, err = sw.WriteString(datum)
|
|
case uint64:
|
|
n, err = sw.WriteUint64(datum)
|
|
default:
|
|
return nSoFar, fmt.Errorf("unknown data type %T (%s)", datum, err)
|
|
}
|
|
if err != nil {
|
|
return nSoFar + n, err
|
|
}
|
|
nSoFar += n
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
if n, err := write("("); err != nil {
|
|
return n, err
|
|
}
|
|
|
|
switch {
|
|
case stat.Mode()&fs.ModeDir != 0:
|
|
// Directory.
|
|
if n, err := write("type", "directory"); err != nil {
|
|
return n, err
|
|
}
|
|
|
|
f, err := root.Open(fn)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer f.Close()
|
|
|
|
dirF, ok := f.(fs.ReadDirFile)
|
|
if !ok {
|
|
return nSoFar, fmt.Errorf("%v didn't get me a ReadDirFile", fn)
|
|
}
|
|
|
|
dents, err := dirF.ReadDir(-1)
|
|
if err != nil {
|
|
return nSoFar, fmt.Errorf("reading dents from %v: %w", fn, err)
|
|
}
|
|
|
|
sort.Slice(dents, func(i, j int) bool {
|
|
return dents[i].Name() < dents[j].Name()
|
|
})
|
|
|
|
for _, dent := range dents {
|
|
if n, err := write("entry", "(", "name", dent.Name(), "node"); err != nil {
|
|
return n, err
|
|
}
|
|
|
|
dentStat, err := dent.Info()
|
|
if err != nil {
|
|
return nSoFar, fmt.Errorf("stat for %v: %w", path.Join(fn, dent.Name()), err)
|
|
}
|
|
|
|
n, err := packFile(sw, root, path.Join(fn, dent.Name()), dentStat)
|
|
if err != nil {
|
|
return nSoFar + n, err
|
|
}
|
|
nSoFar += n
|
|
|
|
if n, err := write(")"); err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
case stat.Mode()&fs.ModeSymlink != 0:
|
|
// Symlink.
|
|
target, err := root.Readlink(fn)
|
|
if err != nil {
|
|
return nSoFar, err
|
|
}
|
|
|
|
if n, err := write("type", "symlink", "target", target); err != nil {
|
|
return n, err
|
|
}
|
|
case stat.Mode().Type() != 0:
|
|
return 0, fmt.Errorf("not implemented (other: %s)", stat.Mode())
|
|
default:
|
|
// Regular file.
|
|
if n, err := write("type", "regular"); err != nil {
|
|
return n, err
|
|
}
|
|
if stat.Mode()&0o100 != 0 {
|
|
// Executable.
|
|
if n, err := write("executable", ""); err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
|
|
if n, err := write("contents", uint64(stat.Size())); err != nil {
|
|
return n, err
|
|
}
|
|
|
|
f, err := root.Open(fn)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer f.Close()
|
|
|
|
wrote, err := io.Copy(sw, f)
|
|
if err != nil {
|
|
return nSoFar + wrote, err
|
|
}
|
|
nSoFar += wrote
|
|
|
|
n, err := sw.WritePadding(wrote)
|
|
if err != nil {
|
|
return nSoFar + n, err
|
|
}
|
|
nSoFar += n
|
|
}
|
|
|
|
if n, err := write(")"); err != nil {
|
|
return n, err
|
|
}
|
|
|
|
return nSoFar, nil
|
|
}
|
|
|
|
func Pack(w io.Writer, fs FS, fn string) (int64, error) {
|
|
sw := nixwire.Serializer{Writer: w}
|
|
|
|
n, err := sw.WriteString("nix-archive-1")
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
stat, err := fs.Lstat(fn)
|
|
if err != nil {
|
|
return n, fmt.Errorf("lstat(%q): %w", fn, err)
|
|
}
|
|
|
|
npf, err := packFile(sw, fs, fn, stat)
|
|
return npf + n, err
|
|
}
|