about summary refs log tree commit diff
path: root/tvix/store/protos/pathinfo.go
blob: 9b51b5266cdcacde52a35e34dd9d63a5ed5663ac (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package storev1

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"fmt"

	"github.com/nix-community/go-nix/pkg/storepath"
)

// Validate performs some checks on the PathInfo struct, returning either the
// StorePath of the root node, or an error.
func (p *PathInfo) Validate() (*storepath.StorePath, error) {
	// ensure References has the right number of bytes.
	for i, reference := range p.GetReferences() {
		if len(reference) != storepath.PathHashSize {
			return nil, fmt.Errorf("invalid length of digest at position %d, expected %d, got %d", i, storepath.PathHashSize, len(reference))
		}
	}

	// If there's a Narinfo field populated..
	if narInfo := p.GetNarinfo(); narInfo != nil {
		// ensure the NarSha256 digest has the correct length.
		if len(narInfo.GetNarSha256()) != sha256.Size {
			return nil, fmt.Errorf("invalid number of bytes for NarSha256: expected %d, got %d", sha256.Size, len(narInfo.GetNarSha256()))
		}

		// ensure the number of references matches len(References).
		if len(narInfo.GetReferenceNames()) != len(p.GetReferences()) {
			return nil, fmt.Errorf("inconsistent number of references: %d (references) vs %d (narinfo)", len(narInfo.GetReferenceNames()), len(p.GetReferences()))
		}

		// for each ReferenceName…
		for i, referenceName := range narInfo.GetReferenceNames() {
			// ensure it parses to a store path
			storePath, err := storepath.FromString(referenceName)
			if err != nil {
				return nil, fmt.Errorf("invalid ReferenceName at position %d: %w", i, err)
			}

			// ensure the digest matches the one at References[i]
			if !bytes.Equal(p.GetReferences()[i], storePath.Digest) {
				return nil, fmt.Errorf(
					"digest in ReferenceName at position %d does not match digest in PathInfo, expected %s, got %s",
					i,
					base64.StdEncoding.EncodeToString(p.GetReferences()[i]),
					base64.StdEncoding.EncodeToString(storePath.Digest),
				)
			}
		}
	}

	// ensure there is a (root) node present
	rootNode := p.GetNode()
	if rootNode == nil {
		return nil, fmt.Errorf("root node must be set")
	}

	// for all three node types, ensure the name properly parses to a store path,
	// and in case it refers to a digest, ensure it has the right length.

	var storePath *storepath.StorePath
	var err error

	if node := rootNode.GetDirectory(); node != nil {
		if len(node.Digest) != 32 {
			return nil, fmt.Errorf("invalid digest size for %s, expected %d, got %d", node.Name, 32, len(node.Digest))
		}

		storePath, err = storepath.FromString(string(node.GetName()))

		if err != nil {
			return nil, fmt.Errorf("unable to parse %s as StorePath: %w", node.Name, err)
		}

	} else if node := rootNode.GetFile(); node != nil {
		if len(node.Digest) != 32 {
			return nil, fmt.Errorf("invalid digest size for %s, expected %d, got %d", node.Name, 32, len(node.Digest))
		}

		storePath, err = storepath.FromString(string(node.GetName()))
		if err != nil {
			return nil, fmt.Errorf("unable to parse %s as StorePath: %w", node.Name, err)
		}

	} else if node := rootNode.GetSymlink(); node != nil {
		storePath, err = storepath.FromString(string(node.GetName()))

		if err != nil {
			return nil, fmt.Errorf("unable to parse %s as StorePath: %w", node.Name, err)
		}

	} else {
		// this would only happen if we introduced a new type
		panic("unreachable")
	}

	// If the Deriver field is populated, ensure it parses to a StorePath.
	// We can't check for it to *not* end with .drv, as the .drv files produced by
	// recursive Nix end with multiple .drv suffixes, and only one is popped when
	// converting to this field.
	if p.Deriver != nil {
		storePath := storepath.StorePath{
			Name:   string(p.Deriver.GetName()),
			Digest: p.Deriver.GetDigest(),
		}
		if err := storePath.Validate(); err != nil {
			return nil, fmt.Errorf("invalid deriver field: %w", err)
		}
	}

	return storePath, nil
}