| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- // nardump is like nix-store --dump, but in Go, writing a NAR
- // file (tar-like, but focused on being reproducible) to stdout
- // or to a hash with the --sri flag.
- //
- // It lets us calculate a Nix sha256 without the person running
- // git-pull-oss.sh having Nix available.
- package main
- // For the format, see:
- // See https://gist.github.com/jbeda/5c79d2b1434f0018d693
- import (
- "bufio"
- "crypto/sha256"
- "encoding/base64"
- "encoding/binary"
- "flag"
- "fmt"
- "io"
- "io/fs"
- "log"
- "os"
- "path"
- "sort"
- )
- var sri = flag.Bool("sri", false, "print SRI")
- func main() {
- flag.Parse()
- if flag.NArg() != 1 {
- log.Fatal("usage: nardump <dir>")
- }
- arg := flag.Arg(0)
- if err := os.Chdir(arg); err != nil {
- log.Fatal(err)
- }
- if *sri {
- hash := sha256.New()
- if err := writeNAR(hash, os.DirFS(".")); err != nil {
- log.Fatal(err)
- }
- fmt.Printf("sha256-%s\n", base64.StdEncoding.EncodeToString(hash.Sum(nil)))
- return
- }
- bw := bufio.NewWriter(os.Stdout)
- if err := writeNAR(bw, os.DirFS(".")); err != nil {
- log.Fatal(err)
- }
- bw.Flush()
- }
- // writeNARError is a sentinel panic type that's recovered by writeNAR
- // and converted into the wrapped error.
- type writeNARError struct{ err error }
- // narWriter writes NAR files.
- type narWriter struct {
- w io.Writer
- fs fs.FS
- }
- // writeNAR writes a NAR file to w from the root of fs.
- func writeNAR(w io.Writer, fs fs.FS) (err error) {
- defer func() {
- if e := recover(); e != nil {
- if we, ok := e.(writeNARError); ok {
- err = we.err
- return
- }
- panic(e)
- }
- }()
- nw := &narWriter{w: w, fs: fs}
- nw.str("nix-archive-1")
- return nw.writeDir(".")
- }
- func (nw *narWriter) writeDir(dirPath string) error {
- ents, err := fs.ReadDir(nw.fs, dirPath)
- if err != nil {
- return err
- }
- sort.Slice(ents, func(i, j int) bool {
- return ents[i].Name() < ents[j].Name()
- })
- nw.str("(")
- nw.str("type")
- nw.str("directory")
- for _, ent := range ents {
- nw.str("entry")
- nw.str("(")
- nw.str("name")
- nw.str(ent.Name())
- nw.str("node")
- mode := ent.Type()
- sub := path.Join(dirPath, ent.Name())
- var err error
- switch {
- case mode.IsRegular():
- err = nw.writeRegular(sub)
- case mode.IsDir():
- err = nw.writeDir(sub)
- default:
- // TODO(bradfitz): symlink, but requires fighting io/fs a bit
- // to get at Readlink or the osFS via fs. But for now
- // we don't need symlinks because they're not in Go's archive.
- return fmt.Errorf("unsupported file type %v at %q", sub, mode)
- }
- if err != nil {
- return err
- }
- nw.str(")")
- }
- nw.str(")")
- return nil
- }
- func (nw *narWriter) writeRegular(path string) error {
- nw.str("(")
- nw.str("type")
- nw.str("regular")
- fi, err := fs.Stat(nw.fs, path)
- if err != nil {
- return err
- }
- if fi.Mode()&0111 != 0 {
- nw.str("executable")
- nw.str("")
- }
- contents, err := fs.ReadFile(nw.fs, path)
- if err != nil {
- return err
- }
- nw.str("contents")
- if err := writeBytes(nw.w, contents); err != nil {
- return err
- }
- nw.str(")")
- return nil
- }
- func (nw *narWriter) str(s string) {
- if err := writeString(nw.w, s); err != nil {
- panic(writeNARError{err})
- }
- }
- func writeString(w io.Writer, s string) error {
- var buf [8]byte
- binary.LittleEndian.PutUint64(buf[:], uint64(len(s)))
- if _, err := w.Write(buf[:]); err != nil {
- return err
- }
- if _, err := io.WriteString(w, s); err != nil {
- return err
- }
- return writePad(w, len(s))
- }
- func writeBytes(w io.Writer, b []byte) error {
- var buf [8]byte
- binary.LittleEndian.PutUint64(buf[:], uint64(len(b)))
- if _, err := w.Write(buf[:]); err != nil {
- return err
- }
- if _, err := w.Write(b); err != nil {
- return err
- }
- return writePad(w, len(b))
- }
- func writePad(w io.Writer, n int) error {
- pad := n % 8
- if pad == 0 {
- return nil
- }
- var zeroes [8]byte
- _, err := w.Write(zeroes[:8-pad])
- return err
- }
|