aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/pkg/dir.go8
-rw-r--r--internal/pkg/dir_test.go100
-rw-r--r--internal/pkg/exec.go140
-rw-r--r--internal/pkg/exec_test.go14
-rw-r--r--internal/pkg/file.go24
-rw-r--r--internal/pkg/file_test.go4
-rw-r--r--internal/pkg/ir.go761
-rw-r--r--internal/pkg/ir_test.go114
-rw-r--r--internal/pkg/net.go29
-rw-r--r--internal/pkg/net_test.go4
-rw-r--r--internal/pkg/pkg.go88
-rw-r--r--internal/pkg/pkg_test.go19
-rw-r--r--internal/pkg/tar.go19
-rw-r--r--internal/pkg/tar_test.go31
-rw-r--r--internal/pkg/testdata/main.go12
15 files changed, 1164 insertions, 203 deletions
diff --git a/internal/pkg/dir.go b/internal/pkg/dir.go
index d0c3668d..1d98350f 100644
--- a/internal/pkg/dir.go
+++ b/internal/pkg/dir.go
@@ -28,14 +28,6 @@ type FlatEntry struct {
| data []byte |
*/
-// wordSize is the boundary which binary segments are always aligned to.
-const wordSize = 8
-
-// alignSize returns the padded size for aligning sz.
-func alignSize(sz int) int {
- return sz + (wordSize-(sz)%wordSize)%wordSize
-}
-
// Encode encodes the entry for transmission or hashing.
func (ent *FlatEntry) Encode(w io.Writer) (n int, err error) {
pPathSize := alignSize(len(ent.Path))
diff --git a/internal/pkg/dir_test.go b/internal/pkg/dir_test.go
index 3f298d5d..98e6ab4a 100644
--- a/internal/pkg/dir_test.go
+++ b/internal/pkg/dir_test.go
@@ -76,7 +76,7 @@ func TestFlatten(t *testing.T) {
"checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU": {Mode: 0400, Data: []byte("\x7f\xe1\x69\xa2\xdd\x63\x96\x26\x83\x79\x61\x8b\xf0\x3f\xd5\x16\x9a\x39\x3a\xdb\xcf\xb1\xbc\x8d\x33\xff\x75\xee\x62\x56\xa9\xf0\x27\xac\x13\x94\x69")},
"identifier": {Mode: fs.ModeDir | 0700},
- "identifier/00BNNr-PsNMtowTpEG86ZeI7eQKoD-pjSCPAal1e5MYqr_N7FLpyXKdXLXE8WEBF": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
+ "identifier/oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
"work": {Mode: fs.ModeDir | 0700},
}, []pkg.FlatEntry{
@@ -86,10 +86,10 @@ func TestFlatten(t *testing.T) {
{Mode: 0400, Path: "checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU", Data: []byte("\x7f\xe1\x69\xa2\xdd\x63\x96\x26\x83\x79\x61\x8b\xf0\x3f\xd5\x16\x9a\x39\x3a\xdb\xcf\xb1\xbc\x8d\x33\xff\x75\xee\x62\x56\xa9\xf0\x27\xac\x13\x94\x69")},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/00BNNr-PsNMtowTpEG86ZeI7eQKoD-pjSCPAal1e5MYqr_N7FLpyXKdXLXE8WEBF", Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_", Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
{Mode: fs.ModeDir | 0700, Path: "work"},
- }, pkg.MustDecode("KkdL8x2a84V8iYZop5jSTyba54xSgf_NZ1R0c4nSp9xTdk3SK_zUKGhNJ2uK8wMY"), nil},
+ }, pkg.MustDecode("L_0RFHpr9JUS4Zp14rz2dESSRvfLzpvqsLhR1-YjQt8hYlmEdVl7vI3_-v8UNPKs"), nil},
{"sample directory step simple", fstest.MapFS{
".": {Mode: fs.ModeDir | 0500},
@@ -208,8 +208,8 @@ func TestFlatten(t *testing.T) {
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/work": {Mode: fs.ModeDir | 0500},
"identifier": {Mode: fs.ModeDir | 0700},
- "identifier/5-NfJKBlUgVhAP_JeCjjo1UxF72x8QVMgeKPWd8s0J-RYvRJy6veTQDwmgxOvr6v": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
- "identifier/VWakDFDSjNMvdzwxG0Y1IKFdCzExgiQnjg-vv2srsZObwh-5WOJx7H5HtCgDXHcq": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
+ "identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
+ "identifier/rg7F1D5hwv6o4xctjD5zDq4i5MD0mArTsUIWfhUbik8xC6Bsyt3mjXXOm3goojTz": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
"temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
@@ -230,12 +230,12 @@ func TestFlatten(t *testing.T) {
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/work"},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/5-NfJKBlUgVhAP_JeCjjo1UxF72x8QVMgeKPWd8s0J-RYvRJy6veTQDwmgxOvr6v", Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/VWakDFDSjNMvdzwxG0Y1IKFdCzExgiQnjg-vv2srsZObwh-5WOJx7H5HtCgDXHcq", Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw", Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/rg7F1D5hwv6o4xctjD5zDq4i5MD0mArTsUIWfhUbik8xC6Bsyt3mjXXOm3goojTz", Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
- }, pkg.MustDecode("nnOiyjjjvgZChsGtO4rA1JHckwYBBbxwNfecPJp62OFP6aoYUxHQ5UtYsrDpnwan"), nil},
+ }, pkg.MustDecode("NQTlc466JmSVLIyWklm_u8_g95jEEb98PxJU-kjwxLpfdjwMWJq0G8ze9R4Vo1Vu"), nil},
{"sample tar expand step unpack", fstest.MapFS{
".": {Mode: fs.ModeDir | 0500},
@@ -255,8 +255,8 @@ func TestFlatten(t *testing.T) {
"checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
"identifier": {Mode: fs.ModeDir | 0700},
- "identifier/VWakDFDSjNMvdzwxG0Y1IKFdCzExgiQnjg-vv2srsZObwh-5WOJx7H5HtCgDXHcq": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
- "identifier/v3z1m-ofUqJz4_rasXRlTw5NgKk63RLvd5JKBpDeiNaYiUKSGN5KJbJGJHMt7cTf": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
+ "identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
+ "identifier/_v1blm2h-_KA-dVaawdpLas6MjHc6rbhhFS8JWwx8iJxZGUu8EBbRrhr5AaZ9PJL": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
"temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
@@ -268,12 +268,12 @@ func TestFlatten(t *testing.T) {
{Mode: fs.ModeSymlink | 0777, Path: "checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN/libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/VWakDFDSjNMvdzwxG0Y1IKFdCzExgiQnjg-vv2srsZObwh-5WOJx7H5HtCgDXHcq", Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/v3z1m-ofUqJz4_rasXRlTw5NgKk63RLvd5JKBpDeiNaYiUKSGN5KJbJGJHMt7cTf", Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw", Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/_v1blm2h-_KA-dVaawdpLas6MjHc6rbhhFS8JWwx8iJxZGUu8EBbRrhr5AaZ9PJL", Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
- }, pkg.MustDecode("bQVH19N7dX50SdQ6JNVYbFdDZV4t8IaM4dhxGvjACpdoEgJ2jZJfYKLH4ya7ZD_s"), nil},
+ }, pkg.MustDecode("hSoSSgCYTNonX3Q8FjvjD1fBl-E-BQyA6OTXro2OadXqbST4tZ-akGXszdeqphRe"), nil},
{"testtool", fstest.MapFS{
".": {Mode: fs.ModeDir | 0500},
@@ -295,9 +295,9 @@ func TestFlatten(t *testing.T) {
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
"identifier": {Mode: fs.ModeDir | 0700},
- "identifier/LRxdkRYNKnZT6bKiu5W8ATeAAmq3n_5AAJkF6G0EpAOEloiZvADJBkfixgtgF1Z9": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
- "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/dztPS6jRjiZtCF4_p8AzfnxGp6obkhrgFVsxdodbKWUoAEVtDz3MykepJB4kI_ks": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
"temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
@@ -311,13 +311,13 @@ func TestFlatten(t *testing.T) {
{Mode: 0400, Path: "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb", Data: []byte{}},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/LRxdkRYNKnZT6bKiu5W8ATeAAmq3n_5AAJkF6G0EpAOEloiZvADJBkfixgtgF1Z9", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/dztPS6jRjiZtCF4_p8AzfnxGp6obkhrgFVsxdodbKWUoAEVtDz3MykepJB4kI_ks", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
- }, pkg.MustDecode("BQb5SCAo0Rw0YBGqjGemK1jH3jk0cgxAQ-JyeqVRqaqmEevJ3jtXNL8HB470XNnB"), nil},
+ }, pkg.MustDecode("Q5DluWQCAeohLoiGRImurwFp3vdz9IfQCoj7Fuhh73s4KQPRHpEQEnHTdNHmB8Fx"), nil},
{"testtool net", fstest.MapFS{
".": {Mode: fs.ModeDir | 0500},
@@ -339,9 +339,9 @@ func TestFlatten(t *testing.T) {
"checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W/check": {Mode: 0400, Data: []byte("net")},
"identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/G8qPxD9puvvoOVV7lrT80eyDeIl3G_CCFoKw12c8mCjMdG1zF7NEPkwYpNubClK3": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
- "identifier/TAspufRsG2I_TsxUUj2b7bUnCHgcVSdh6aOZpzL0W5Bjn4EZmOGzjofaOWd8J11H": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
- "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
"temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
@@ -355,13 +355,13 @@ func TestFlatten(t *testing.T) {
{Mode: 0400, Path: "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W/check", Data: []byte("net")},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/TAspufRsG2I_TsxUUj2b7bUnCHgcVSdh6aOZpzL0W5Bjn4EZmOGzjofaOWd8J11H", Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/G8qPxD9puvvoOVV7lrT80eyDeIl3G_CCFoKw12c8mCjMdG1zF7NEPkwYpNubClK3", Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
- }, pkg.MustDecode("hJ7nCMLea_09Z7Fi4ALXOgubMNwK7C61THdQobpQJhH3tnr7PJ86aY98Mte3rBje"), nil},
+ }, pkg.MustDecode("bPYvvqxpfV7xcC1EptqyKNK1klLJgYHMDkzBcoOyK6j_Aj5hb0mXNPwTwPSK5F6Z"), nil},
{"sample exec container overlay root", fstest.MapFS{
".": {Mode: fs.ModeDir | 0700},
@@ -372,8 +372,8 @@ func TestFlatten(t *testing.T) {
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
"identifier": {Mode: fs.ModeDir | 0700},
- "identifier/UB9HPeMgMPJf3Ut4jLWwnCtu_P3Lr29i8Erf084bHe8jjzBMKPDNxQ3RMrirkH6H": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
- "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/RdMA-mubnrHuu3Ky1wWyxauSYCO0ZH_zCPUj3uDHqkfwv5sGcByoF_g5PjlGiClb": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
"temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
@@ -386,12 +386,12 @@ func TestFlatten(t *testing.T) {
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/UB9HPeMgMPJf3Ut4jLWwnCtu_P3Lr29i8Erf084bHe8jjzBMKPDNxQ3RMrirkH6H", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/RdMA-mubnrHuu3Ky1wWyxauSYCO0ZH_zCPUj3uDHqkfwv5sGcByoF_g5PjlGiClb", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
- }, pkg.MustDecode("PAZyVTxxROg4eCQX3yKuiIlB1k9VFXmtvvyy7QxoqaFhYwGZpT4wYec4R2dTtfyh"), nil},
+ }, pkg.MustDecode("PO2DSSCa4yoSgEYRcCSZfQfwow1yRigL3Ry-hI0RDI4aGuFBha-EfXeSJnG_5_Rl"), nil},
{"sample exec container overlay work", fstest.MapFS{
".": {Mode: fs.ModeDir | 0700},
@@ -402,8 +402,8 @@ func TestFlatten(t *testing.T) {
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
"identifier": {Mode: fs.ModeDir | 0700},
- "identifier/Fud5ldJfpsgLt-rkLWrLO-aVYhQm-esTswetjxydPeQMK4jHNJ_1fGHVahaiCZ9y": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
- "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/5hlaukCirnXE4W_RSLJFOZN47Z5RiHnacXzdFp_70cLgiJUGR6cSb_HaFftkzi0-": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
"temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
@@ -416,12 +416,12 @@ func TestFlatten(t *testing.T) {
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/Fud5ldJfpsgLt-rkLWrLO-aVYhQm-esTswetjxydPeQMK4jHNJ_1fGHVahaiCZ9y", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/5hlaukCirnXE4W_RSLJFOZN47Z5RiHnacXzdFp_70cLgiJUGR6cSb_HaFftkzi0-", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
- }, pkg.MustDecode("uMZyWOQGjhI1oNKfJyw8I6EtUmWkOsZNeUEZLjy1lmkAV7cR1hmOKsOlXs4RkuEC"), nil},
+ }, pkg.MustDecode("iaRt6l_Wm2n-h5UsDewZxQkCmjZjyL8r7wv32QT2kyV55-Lx09Dq4gfg9BiwPnKs"), nil},
{"sample exec container multiple layers", fstest.MapFS{
".": {Mode: fs.ModeDir | 0700},
@@ -435,10 +435,10 @@ func TestFlatten(t *testing.T) {
"checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK/check": {Mode: 0400, Data: []byte("layers")},
"identifier": {Mode: fs.ModeDir | 0700},
- "identifier/YK1yDoi_qaUuXSPeVLJnaL8CBuZC4LoCarId5vdBCTLU82-vZFIfLDlaJuLM1iBj": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
- "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
- "identifier/g6gj2JWNXN-oNikou626vDqcMeZCn_TcV4xKuizBaPAWcasG2sVvItb5kZovMrzE": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ "identifier/B-kc5iJMx8GtlCua4dz6BiJHnDAOUfPjgpbKq4e-QEn0_CZkSYs3fOA1ve06qMs2": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
+ "identifier/p1t_drXr34i-jZNuxDMLaMOdL6tZvQqhavNafGynGqxOZoXAUTSn7kqNh3Ovv3DT": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
"temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
@@ -454,14 +454,14 @@ func TestFlatten(t *testing.T) {
{Mode: 0400, Path: "checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK/check", Data: []byte("layers")},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/YK1yDoi_qaUuXSPeVLJnaL8CBuZC4LoCarId5vdBCTLU82-vZFIfLDlaJuLM1iBj", Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/B-kc5iJMx8GtlCua4dz6BiJHnDAOUfPjgpbKq4e-QEn0_CZkSYs3fOA1ve06qMs2", Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/g6gj2JWNXN-oNikou626vDqcMeZCn_TcV4xKuizBaPAWcasG2sVvItb5kZovMrzE", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/p1t_drXr34i-jZNuxDMLaMOdL6tZvQqhavNafGynGqxOZoXAUTSn7kqNh3Ovv3DT", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
- }, pkg.MustDecode("OG6C_fL-U4dZndkiKJvXf31qrM7DNpmCGxbWASwhWK_e8twIwC_ZvMvw142pVqz-"), nil},
+ }, pkg.MustDecode("O2YzyR7IUGU5J2CADy0hUZ3A5NkP_Vwzs4UadEdn2oMZZVWRtH0xZGJ3HXiimTnZ"), nil},
{"sample exec container layer promotion", fstest.MapFS{
".": {Mode: fs.ModeDir | 0700},
@@ -472,9 +472,9 @@ func TestFlatten(t *testing.T) {
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
"identifier": {Mode: fs.ModeDir | 0700},
- "identifier/CuKcA4aAApOvWqI6-KzZEDyGLltRdBjOTyrTibam2fFVxtXmGL_RVuElOFTVlKfq": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
- "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
- "identifier/ywzI31S5McuYu7vzI2kqpSC_nsNzpWBXVCwPoLAYi9QVT0mODgzqoo9jYYaczPbf": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ "identifier/kvJIqZo5DKFOxC2ZQ-8_nPaQzEAz9cIm3p6guO-uLqm-xaiPu7oRkSnsu411jd_U": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/xXTIYcXmgJWNLC91c417RRrNM9cjELwEZHpGvf8Fk_GNP5agRJp_SicD0w9aMeLJ": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
"temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
@@ -487,13 +487,13 @@ func TestFlatten(t *testing.T) {
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/CuKcA4aAApOvWqI6-KzZEDyGLltRdBjOTyrTibam2fFVxtXmGL_RVuElOFTVlKfq", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/cGvuAdKA2tThRa4w3ZI2c5S5TXDx4j2qvfFM7pzf7y5vBz7NisBV06ThBXw_97xK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/ywzI31S5McuYu7vzI2kqpSC_nsNzpWBXVCwPoLAYi9QVT0mODgzqoo9jYYaczPbf", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/kvJIqZo5DKFOxC2ZQ-8_nPaQzEAz9cIm3p6guO-uLqm-xaiPu7oRkSnsu411jd_U", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/xXTIYcXmgJWNLC91c417RRrNM9cjELwEZHpGvf8Fk_GNP5agRJp_SicD0w9aMeLJ", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
{Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"},
- }, pkg.MustDecode("CKt376G_wCHRR26hBxLnoXyz5boEOX12AiHbQM2qz4bejKtfmys3Swqh60eKFn9y"), nil},
+ }, pkg.MustDecode("3EaW6WibLi9gl03_UieiFPaFcPy5p4x3JPxrnLJxGaTI-bh3HU9DK9IMx7c3rrNm"), nil},
{"sample file short", fstest.MapFS{
".": {Mode: fs.ModeDir | 0700},
@@ -502,7 +502,7 @@ func TestFlatten(t *testing.T) {
"checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX": {Mode: 0400, Data: []byte{0}},
"identifier": {Mode: fs.ModeDir | 0700},
- "identifier/3nNZXzfgfDW2aHqmgf1VpJUYxe1GMzU7eA9Q_NnNVTDPpPOCTYKEVX-yscOiLT-e": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
+ "identifier/3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
"work": {Mode: fs.ModeDir | 0700},
}, []pkg.FlatEntry{
@@ -511,10 +511,10 @@ func TestFlatten(t *testing.T) {
{Mode: 0400, Path: "checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX", Data: []byte{0}},
{Mode: fs.ModeDir | 0700, Path: "identifier"},
- {Mode: fs.ModeSymlink | 0777, Path: "identifier/3nNZXzfgfDW2aHqmgf1VpJUYxe1GMzU7eA9Q_NnNVTDPpPOCTYKEVX-yscOiLT-e", Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
+ {Mode: fs.ModeSymlink | 0777, Path: "identifier/3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi", Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
{Mode: fs.ModeDir | 0700, Path: "work"},
- }, pkg.MustDecode("azjTvqkTjLhFzvPDM4DEHiHyLupOnRq9GCikVN6DEElR1Gxz_BDo4SA0zZzaYUGa"), nil},
+ }, pkg.MustDecode("iR6H5OIsyOW4EwEgtm9rGzGF6DVtyHLySEtwnFE8bnus9VJcoCbR4JIek7Lw-vwT"), nil},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go
index f5270801..39955637 100644
--- a/internal/pkg/exec.go
+++ b/internal/pkg/exec.go
@@ -103,8 +103,7 @@ type execArtifact struct {
args []string
// Duration the initial process is allowed to run. The zero value is
- // equivalent to execTimeoutDefault. This value is never encoded in Params
- // because it cannot affect outcome.
+ // equivalent to [ExecTimeoutDefault].
timeout time.Duration
// Caller-supplied exclusivity value, returned as is by IsExclusive.
@@ -129,12 +128,6 @@ func (a *execNetArtifact) Checksum() Checksum { return a.checksum }
// Kind returns the hardcoded [Kind] constant.
func (*execNetArtifact) Kind() Kind { return KindExecNet }
-// Params is [Checksum] concatenated with [KindExec] params.
-func (a *execNetArtifact) Params(ctx *IContext) {
- ctx.GetHash().Write(a.checksum[:])
- a.execArtifact.Params(ctx)
-}
-
// Cure cures the [Artifact] in the container described by the caller. The
// container retains host networking.
func (a *execNetArtifact) Cure(f *FContext) error {
@@ -198,38 +191,131 @@ func (*execArtifact) Kind() Kind { return KindExec }
// Params writes paths, executable pathname and args.
func (a *execArtifact) Params(ctx *IContext) {
- h := ctx.GetHash()
+ ctx.WriteString(a.name)
- _0, _1 := []byte{0}, []byte{1}
+ ctx.WriteUint32(uint32(len(a.paths)))
for _, p := range a.paths {
- if p.W {
- h.Write(_1)
- } else {
- h.Write(_0)
- }
if p.P != nil {
- h.Write([]byte(p.P.String()))
+ ctx.WriteString(p.P.String())
} else {
- h.Write([]byte("invalid P\x00"))
+ ctx.WriteString("invalid P\x00")
}
- h.Write(_0)
+
+ ctx.WriteUint32(uint32(len(p.A)))
for _, d := range p.A {
ctx.WriteIdent(d)
}
- h.Write(_0)
+
+ if p.W {
+ ctx.WriteUint32(1)
+ } else {
+ ctx.WriteUint32(0)
+ }
}
- h.Write(_0)
- h.Write([]byte(a.dir.String()))
- h.Write(_0)
+
+ ctx.WriteString(a.dir.String())
+
+ ctx.WriteUint32(uint32(len(a.env)))
for _, e := range a.env {
- h.Write([]byte(e))
+ ctx.WriteString(e)
}
- h.Write(_0)
- h.Write([]byte(a.path.String()))
- h.Write(_0)
+
+ ctx.WriteString(a.path.String())
+
+ ctx.WriteUint32(uint32(len(a.args)))
for _, arg := range a.args {
- h.Write([]byte(arg))
+ ctx.WriteString(arg)
+ }
+
+ ctx.WriteUint32(uint32(a.timeout & 0xffffffff))
+ ctx.WriteUint32(uint32(a.timeout >> 32))
+
+ if a.exclusive {
+ ctx.WriteUint32(1)
+ } else {
+ ctx.WriteUint32(0)
+ }
+}
+
+// readExecArtifact interprets IR values and returns the address of execArtifact
+// or execNetArtifact.
+func readExecArtifact(r *IRReader, net bool) Artifact {
+ r.DiscardAll()
+
+ name := r.ReadString()
+
+ sz := r.ReadUint32()
+ if sz > irMaxDeps {
+ panic(ErrIRDepend)
+ }
+ paths := make([]ExecPath, sz)
+ for i := range paths {
+ paths[i].P = check.MustAbs(r.ReadString())
+
+ sz = r.ReadUint32()
+ if sz > irMaxDeps {
+ panic(ErrIRDepend)
+ }
+ paths[i].A = make([]Artifact, sz)
+ for j := range paths[i].A {
+ paths[i].A[j] = r.ReadIdent()
+ }
+
+ paths[i].W = r.ReadUint32() != 0
+ }
+
+ dir := check.MustAbs(r.ReadString())
+
+ sz = r.ReadUint32()
+ if sz > irMaxValues {
+ panic(ErrIRValues)
+ }
+ env := make([]string, sz)
+ for i := range env {
+ env[i] = r.ReadString()
+ }
+
+ pathname := check.MustAbs(r.ReadString())
+
+ sz = r.ReadUint32()
+ if sz > irMaxValues {
+ panic(ErrIRValues)
+ }
+ args := make([]string, sz)
+ for i := range args {
+ args[i] = r.ReadString()
}
+
+ timeout := time.Duration(r.ReadUint32())
+ timeout |= time.Duration(r.ReadUint32()) << 32
+
+ exclusive := r.ReadUint32() != 0
+
+ checksum, ok := r.Finalise()
+
+ var checksumP *Checksum
+ if net {
+ if !ok {
+ panic(ErrExpectedChecksum)
+ }
+ checksumVal := checksum.Value()
+ checksumP = &checksumVal
+ } else {
+ if ok {
+ panic(ErrUnexpectedChecksum)
+ }
+ }
+
+ return NewExec(
+ name, checksumP, timeout, exclusive, dir, env, pathname, args, paths...,
+ )
+}
+
+func init() {
+ register(KindExec,
+ func(r *IRReader) Artifact { return readExecArtifact(r, false) })
+ register(KindExecNet,
+ func(r *IRReader) Artifact { return readExecArtifact(r, true) })
}
// Dependencies returns a slice of all artifacts collected from caller-supplied
diff --git a/internal/pkg/exec_test.go b/internal/pkg/exec_test.go
index b573bf5b..16e5031c 100644
--- a/internal/pkg/exec_test.go
+++ b/internal/pkg/exec_test.go
@@ -78,7 +78,7 @@ func TestExec(t *testing.T) {
), nil, pkg.Checksum{}, &pkg.DependencyCureError{
{
Ident: unique.Make(pkg.ID(pkg.MustDecode(
- "zegItlAz7Lr1xFyzCtHTz_eL08KNuccT3S8b7zqekz3lZxqdELdqTlGjvUYPVykW",
+ "Sowo6oZRmG6xVtUaxB6bDWZhVsqAJsIJWUp0OPKlE103cY0lodx7dem8J-qQF0Z1",
))),
Err: stub.UniqueError(0xcafe),
},
@@ -109,7 +109,7 @@ func TestExec(t *testing.T) {
}
testtoolDestroy(t, base, c)
- }, pkg.MustDecode("BQb5SCAo0Rw0YBGqjGemK1jH3jk0cgxAQ-JyeqVRqaqmEevJ3jtXNL8HB470XNnB")},
+ }, pkg.MustDecode("Q5DluWQCAeohLoiGRImurwFp3vdz9IfQCoj7Fuhh73s4KQPRHpEQEnHTdNHmB8Fx")},
{"net", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
c.SetStrict(true)
@@ -144,7 +144,7 @@ func TestExec(t *testing.T) {
})
testtoolDestroy(t, base, c)
- }, pkg.MustDecode("hJ7nCMLea_09Z7Fi4ALXOgubMNwK7C61THdQobpQJhH3tnr7PJ86aY98Mte3rBje")},
+ }, pkg.MustDecode("bPYvvqxpfV7xcC1EptqyKNK1klLJgYHMDkzBcoOyK6j_Aj5hb0mXNPwTwPSK5F6Z")},
{"overlay root", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
c.SetStrict(true)
@@ -170,7 +170,7 @@ func TestExec(t *testing.T) {
})
testtoolDestroy(t, base, c)
- }, pkg.MustDecode("PAZyVTxxROg4eCQX3yKuiIlB1k9VFXmtvvyy7QxoqaFhYwGZpT4wYec4R2dTtfyh")},
+ }, pkg.MustDecode("PO2DSSCa4yoSgEYRcCSZfQfwow1yRigL3Ry-hI0RDI4aGuFBha-EfXeSJnG_5_Rl")},
{"overlay work", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
c.SetStrict(true)
@@ -201,7 +201,7 @@ func TestExec(t *testing.T) {
})
testtoolDestroy(t, base, c)
- }, pkg.MustDecode("uMZyWOQGjhI1oNKfJyw8I6EtUmWkOsZNeUEZLjy1lmkAV7cR1hmOKsOlXs4RkuEC")},
+ }, pkg.MustDecode("iaRt6l_Wm2n-h5UsDewZxQkCmjZjyL8r7wv32QT2kyV55-Lx09Dq4gfg9BiwPnKs")},
{"multiple layers", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
c.SetStrict(true)
@@ -254,7 +254,7 @@ func TestExec(t *testing.T) {
})
testtoolDestroy(t, base, c)
- }, pkg.MustDecode("OG6C_fL-U4dZndkiKJvXf31qrM7DNpmCGxbWASwhWK_e8twIwC_ZvMvw142pVqz-")},
+ }, pkg.MustDecode("O2YzyR7IUGU5J2CADy0hUZ3A5NkP_Vwzs4UadEdn2oMZZVWRtH0xZGJ3HXiimTnZ")},
{"overlay layer promotion", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
c.SetStrict(true)
@@ -286,7 +286,7 @@ func TestExec(t *testing.T) {
})
testtoolDestroy(t, base, c)
- }, pkg.MustDecode("CKt376G_wCHRR26hBxLnoXyz5boEOX12AiHbQM2qz4bejKtfmys3Swqh60eKFn9y")},
+ }, pkg.MustDecode("3EaW6WibLi9gl03_UieiFPaFcPy5p4x3JPxrnLJxGaTI-bh3HU9DK9IMx7c3rrNm")},
})
}
diff --git a/internal/pkg/file.go b/internal/pkg/file.go
index c2093d6b..149ca817 100644
--- a/internal/pkg/file.go
+++ b/internal/pkg/file.go
@@ -25,6 +25,12 @@ var _ KnownChecksum = new(fileArtifactNamed)
// String returns the caller-supplied reporting name.
func (a *fileArtifactNamed) String() string { return a.name }
+// Params writes the caller-supplied reporting name and the file body.
+func (a *fileArtifactNamed) Params(ctx *IContext) {
+ ctx.WriteString(a.name)
+ ctx.Write(a.fileArtifact)
+}
+
// NewFile returns a [FileArtifact] that cures into a caller-supplied byte slice.
//
// Caller must not modify data after NewFile returns.
@@ -39,8 +45,22 @@ func NewFile(name string, data []byte) FileArtifact {
// Kind returns the hardcoded [Kind] constant.
func (*fileArtifact) Kind() Kind { return KindFile }
-// Params writes the result of Cure.
-func (a *fileArtifact) Params(ctx *IContext) { ctx.GetHash().Write(*a) }
+// Params writes an empty string and the file body.
+func (a *fileArtifact) Params(ctx *IContext) {
+ ctx.WriteString("")
+ ctx.Write(*a)
+}
+
+func init() {
+ register(KindFile, func(r *IRReader) Artifact {
+ name := r.ReadString()
+ data := r.ReadStringBytes()
+ if _, ok := r.Finalise(); !ok {
+ panic(ErrExpectedChecksum)
+ }
+ return NewFile(name, data)
+ })
+}
// Dependencies returns a nil slice.
func (*fileArtifact) Dependencies() []Artifact { return nil }
diff --git a/internal/pkg/file_test.go b/internal/pkg/file_test.go
index ec5e98cd..ee5359ee 100644
--- a/internal/pkg/file_test.go
+++ b/internal/pkg/file_test.go
@@ -17,13 +17,13 @@ func TestFile(t *testing.T) {
cureMany(t, c, []cureStep{
{"short", pkg.NewFile("null", []byte{0}), base.Append(
"identifier",
- "3nNZXzfgfDW2aHqmgf1VpJUYxe1GMzU7eA9Q_NnNVTDPpPOCTYKEVX-yscOiLT-e",
+ "3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi",
), pkg.MustDecode(
"vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX",
), nil},
})
}, pkg.MustDecode(
- "azjTvqkTjLhFzvPDM4DEHiHyLupOnRq9GCikVN6DEElR1Gxz_BDo4SA0zZzaYUGa",
+ "iR6H5OIsyOW4EwEgtm9rGzGF6DVtyHLySEtwnFE8bnus9VJcoCbR4JIek7Lw-vwT",
)},
})
}
diff --git a/internal/pkg/ir.go b/internal/pkg/ir.go
new file mode 100644
index 00000000..f40cd874
--- /dev/null
+++ b/internal/pkg/ir.go
@@ -0,0 +1,761 @@
+package pkg
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "crypto/sha512"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "slices"
+ "strconv"
+ "syscall"
+ "unique"
+ "unsafe"
+)
+
+// wordSize is the boundary which binary segments are always aligned to.
+const wordSize = 8
+
+// alignSize returns the padded size for aligning sz.
+func alignSize(sz int) int {
+ return sz + (wordSize-(sz)%wordSize)%wordSize
+}
+
+// panicToError recovers from a panic and replaces a nil error with the panicked
+// error value. If the value does not implement error, it is re-panicked.
+func panicToError(errP *error) {
+ r := recover()
+ if r == nil {
+ return
+ }
+
+ if err, ok := r.(error); !ok {
+ panic(r)
+ } else if *errP == nil {
+ *errP = err
+ }
+}
+
+// IContext is passed to [Artifact.Params] and provides methods for writing
+// values to the IR writer. It does not expose the underlying [io.Writer].
+//
+// IContext is valid until [Artifact.Params] returns.
+type IContext struct {
+ // Address of underlying [Cache], should be zeroed or made unusable after
+ // [Artifact.Params] returns and must not be exposed directly.
+ cache *Cache
+ // Written to by various methods, should be zeroed after [Artifact.Params]
+ // returns and must not be exposed directly.
+ w io.Writer
+}
+
+// Unwrap returns the underlying [context.Context].
+func (i *IContext) Unwrap() context.Context { return i.cache.ctx }
+
+// irZero is a zero IR word.
+var irZero [wordSize]byte
+
+// IRValueKind denotes the kind of encoded value.
+type IRValueKind uint32
+
+const (
+ // IRKindEnd denotes the end of the current parameters stream. The ancillary
+ // value is interpreted as [IREndFlag].
+ IRKindEnd IRValueKind = iota
+ // IRKindIdent denotes the identifier of a dependency [Artifact]. The
+ // ancillary value is reserved for future use.
+ IRKindIdent
+ // IRKindUint32 denotes an inlined uint32 value.
+ IRKindUint32
+ // IRKindString denotes a string with its true length encoded in header
+ // ancillary data. Its wire length is always aligned to 8 byte boundary.
+ IRKindString
+
+ irHeaderShift = 32
+ irHeaderMask = 0xffffffff
+)
+
+// String returns a user-facing name of k.
+func (k IRValueKind) String() string {
+ switch k {
+ case IRKindEnd:
+ return "terminator"
+ case IRKindIdent:
+ return "ident"
+ case IRKindUint32:
+ return "uint32"
+ case IRKindString:
+ return "string"
+ default:
+ return "invalid kind " + strconv.Itoa(int(k))
+ }
+}
+
+// irValueHeader encodes [IRValueKind] and a 32-bit ancillary value.
+type irValueHeader uint64
+
+// encodeHeader returns irValueHeader encoding [IRValueKind] and ancillary data.
+func (k IRValueKind) encodeHeader(v uint32) irValueHeader {
+ return irValueHeader(v)<<irHeaderShift | irValueHeader(k)
+}
+
+// put stores h in b[0:8].
+func (h irValueHeader) put(b []byte) {
+ binary.LittleEndian.PutUint64(b[:], uint64(h))
+}
+
+// append appends the bytes of h to b and returns the appended slice.
+func (h irValueHeader) append(b []byte) []byte {
+ return binary.LittleEndian.AppendUint64(b, uint64(h))
+}
+
+// IREndFlag is ancillary data encoded in the header of an [IRKindEnd] value and
+// specifies the presence of optional fields in the remaining [IRKindEnd] data.
+// Order of present fields is the order of their corresponding constants defined
+// below.
+type IREndFlag uint32
+
+const (
+ // IREndKnownChecksum denotes a [KnownChecksum] artifact. For an [IRKindEnd]
+ // value with this flag set, the remaining data contains the [Checksum].
+ IREndKnownChecksum IREndFlag = 1 << iota
+)
+
+// mustWrite writes to IContext.w and panics on error. The panic is recovered
+// from by the caller and used as the return value.
+func (i *IContext) mustWrite(p []byte) {
+ if _, err := i.w.Write(p); err != nil {
+ panic(err)
+ }
+}
+
+// WriteIdent writes the identifier of [Artifact] to the IR. The behaviour of
+// WriteIdent is not defined for an [Artifact] not part of the slice returned by
+// [Artifact.Dependencies].
+func (i *IContext) WriteIdent(a Artifact) {
+ buf := i.cache.getIdentBuf()
+ defer i.cache.putIdentBuf(buf)
+
+ IRKindIdent.encodeHeader(0).put(buf[:])
+ *(*ID)(buf[wordSize:]) = i.cache.Ident(a).Value()
+ i.mustWrite(buf[:])
+}
+
+// WriteUint32 writes a uint32 value to the IR.
+func (i *IContext) WriteUint32(v uint32) {
+ i.mustWrite(IRKindUint32.encodeHeader(v).append(nil))
+}
+
+// irMaxStringLength is the maximum acceptable wire size of [IRKindString].
+const irMaxStringLength = 1 << 20
+
+// IRStringError is a string value too big to encode in IR.
+type IRStringError string
+
+func (IRStringError) Error() string {
+ return "params value too big to encode in IR"
+}
+
+// Write writes p as a string value to the IR.
+func (i *IContext) Write(p []byte) {
+ sz := alignSize(len(p))
+ if len(p) > irMaxStringLength || sz > irMaxStringLength {
+ panic(IRStringError(p))
+ }
+
+ i.mustWrite(IRKindString.encodeHeader(uint32(len(p))).append(nil))
+ i.mustWrite(p)
+
+ psz := sz - len(p)
+ if psz > 0 {
+ i.mustWrite(irZero[:psz])
+ }
+}
+
+// WriteString writes s as a string value to the IR.
+func (i *IContext) WriteString(s string) {
+ p := unsafe.Slice(unsafe.StringData(s), len(s))
+ i.Write(p)
+}
+
+// Encode writes a deterministic, efficient representation of a to w and returns
+// the first non-nil error encountered while writing to w.
+func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
+ deps := a.Dependencies()
+ idents := make([]*extIdent, len(deps))
+ for i, d := range deps {
+ dbuf, did := c.unsafeIdent(d, true)
+ if dbuf == nil {
+ dbuf = c.getIdentBuf()
+ binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
+ *(*ID)(dbuf[wordSize:]) = did.Value()
+ } else {
+ c.storeIdent(d, dbuf)
+ }
+ defer c.putIdentBuf(dbuf)
+ idents[i] = dbuf
+ }
+ slices.SortFunc(idents, func(a, b *extIdent) int {
+ return bytes.Compare(a[:], b[:])
+ })
+ idents = slices.CompactFunc(idents, func(a, b *extIdent) bool {
+ return *a == *b
+ })
+
+ // kind uint64 | deps_sz uint64
+ var buf [wordSize * 2]byte
+ binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
+ binary.LittleEndian.PutUint64(buf[wordSize:], uint64(len(idents)))
+ if _, err = w.Write(buf[:]); err != nil {
+ return
+ }
+
+ for _, dn := range idents {
+ // kind uint64 | ident ID
+ if _, err = w.Write(dn[:]); err != nil {
+ return
+ }
+ }
+
+ func() {
+ i := IContext{c, w}
+
+ defer panicToError(&err)
+ defer func() { i.cache, i.w = nil, nil }()
+
+ a.Params(&i)
+ }()
+ if err != nil {
+ return
+ }
+
+ var f IREndFlag
+ kcBuf := c.getIdentBuf()
+ sz := wordSize
+ if kc, ok := a.(KnownChecksum); ok {
+ f |= IREndKnownChecksum
+ *(*Checksum)(kcBuf[wordSize:]) = kc.Checksum()
+ sz += len(Checksum{})
+ }
+ IRKindEnd.encodeHeader(uint32(f)).put(kcBuf[:])
+
+ _, err = w.Write(kcBuf[:sz])
+ c.putIdentBuf(kcBuf)
+ return
+}
+
+// encodeAll implements EncodeAll by recursively encoding dependencies and
+// performs deduplication by value via the encoded map.
+func (c *Cache) encodeAll(
+ w io.Writer,
+ a Artifact,
+ encoded map[Artifact]struct{},
+) (err error) {
+ for _, d := range a.Dependencies() {
+ if err = c.encodeAll(w, d, encoded); err != nil {
+ return
+ }
+ }
+
+ if _, ok := encoded[a]; ok {
+ return
+ }
+ encoded[a] = struct{}{}
+ return c.Encode(w, a)
+}
+
+// EncodeAll writes a self-describing IR stream of a to w and returns the first
+// non-nil error encountered while writing to w.
+//
+// EncodeAll tries to avoid encoding the same [Artifact] more than once, however
+// it will fail to do so if they do not compare equal by value, as that will
+// require buffering and greatly reduce performance. It is therefore up to the
+// caller to avoid causing dependencies to be represented in a way such that
+// two equivalent artifacts do not compare equal. While an IR stream with
+// repeated artifacts is valid, it is somewhat inefficient, and the reference
+// [IRDecoder] implementation produces a warning for it.
+//
+// Note that while EncodeAll makes use of the ident free list, it does not use
+// the ident cache, nor does it contribute identifiers it computes back to the
+// ident cache. Because of this, multiple invocations of EncodeAll will have
+// similar cost and does not amortise when combined with a call to Cure.
+func (c *Cache) EncodeAll(w io.Writer, a Artifact) error {
+ return c.encodeAll(w, a, make(map[Artifact]struct{}))
+}
+
+// ErrRemainingIR is returned for a [IRReadFunc] that failed to call
+// [IRReader.Finalise] before returning.
+var ErrRemainingIR = errors.New("implementation did not consume final value")
+
+// DanglingIdentError is an identifier in a [IRKindIdent] value that was never
+// described in the IR stream before it was encountered.
+type DanglingIdentError unique.Handle[ID]
+
+func (e DanglingIdentError) Error() string {
+ return "artifact " + Encode(unique.Handle[ID](e).Value()) +
+ " was never described"
+}
+
+type (
+ // IRDecoder decodes [Artifact] from an IR stream. The stream is read to
+ // EOF and the final [Artifact] is returned. Previous artifacts may be
+ // looked up by their identifier.
+ //
+ // An [Artifact] may appear more than once in the same IR stream. A
+ // repeating [Artifact] generates a warning via [Cache] and will appear if
+ // verbose logging is enabled. Artifacts may only depend on artifacts
+ // previously described in the IR stream.
+ //
+ // Methods of IRDecoder are not safe for concurrent use.
+ IRDecoder struct {
+ // Address of underlying [Cache], must not be exposed directly.
+ c *Cache
+
+ // Underlying IR reader. Methods of [IRReader] must not use this as it
+ // bypasses ident measurement.
+ r io.Reader
+ // Artifacts already seen in the IR stream.
+ ident map[unique.Handle[ID]]Artifact
+
+ // Whether Decode returned, and the entire IR stream was decoded.
+ done, ok bool
+ }
+
+ // IRReader provides methods to decode the IR wire format and read values
+ // from the reader embedded in the underlying [IRDecoder]. It is
+ // deliberately impossible to obtain the [IRValueKind] of the next value,
+ // and callers must never recover from panics in any read method.
+ //
+ // It is the responsibility of the caller to call Finalise after all IR
+ // values have been read. Failure to call Finalise causes the resulting
+ // [Artifact] to be rejected with [ErrRemainingIR].
+ //
+ // For an [Artifact] expected to have dependencies, the caller must consume
+ // all dependencies by calling Next until all dependencies are depleted, or
+ // call DiscardAll to explicitly discard them and rely on values encoded as
+ // [IRKindIdent] instead. Failure to consume all unstructured dependencies
+ // causes the resulting [Artifact] to be rejected with [MissedDependencyError].
+ //
+ // Requesting the value of an unstructured dependency not yet described in
+ // the IR stream via Next, or reading an [IRKindIdent] value not part of
+ // unstructured dependencies via ReadIdent may cause the resulting
+ // [Artifact] to be rejected with [DanglingIdentError], however either
+ // method may return a non-nil [Artifact] implementation of unspecified
+ // value.
+ IRReader struct {
+ // Address of underlying [IRDecoder], should be zeroed or made unusable
+ // after finalisation and must not be exposed directly.
+ d *IRDecoder
+ // Common buffer for word-sized reads.
+ buf [wordSize]byte
+
+ // Dependencies sent before params, sorted by identifier. Resliced on
+ // each call to Next and checked to be depleted during Finalise.
+ deps []*extIdent
+
+ // Number of values already read, -1 denotes a finalised IRReader.
+ count int
+ // Header of value currently being read.
+ h irValueHeader
+
+ // Measured IR reader. All reads for the current [Artifact] must go
+ // through this to produce a correct ident.
+ r io.Reader
+ // Buffers measure writes. Flushed and returned to d during Finalise.
+ ibw *bufio.Writer
+ }
+
+ // IRReadFunc reads IR values written by [Artifact.Params] to produce an
+ // instance of [Artifact] identical to the one to produce these values.
+ IRReadFunc func(r *IRReader) Artifact
+)
+
+// kind returns the [IRValueKind] encoded in h.
+func (h irValueHeader) kind() IRValueKind {
+ return IRValueKind(h & irHeaderMask)
+}
+
+// value returns ancillary data encoded in h.
+func (h irValueHeader) value() uint32 {
+ return uint32(h >> irHeaderShift)
+}
+
+// irArtifact refers to artifact IR interpretation functions and must not be
+// written to directly.
+var irArtifact = make(map[Kind]IRReadFunc)
+
+// InvalidKindError is an unregistered [Kind] value.
+type InvalidKindError Kind
+
+func (e InvalidKindError) Error() string {
+ return "invalid artifact kind " + strconv.Itoa(int(e))
+}
+
+// register records the [IRReadFunc] of an implementation of [Artifact] under
+// the specified [Kind]. Expecting to be used only during initialization, it
+// panics if the mapping between [Kind] and [IRReadFunc] is not a bijection.
+//
+// register is not safe for concurrent use. register must not be called after
+// the first instance of [Cache] has been opened.
+func register(k Kind, f IRReadFunc) {
+ if _, ok := irArtifact[k]; ok {
+ panic("attempting to register " + strconv.Itoa(int(k)) + " twice")
+ }
+ irArtifact[k] = f
+}
+
+// Register records the [IRReadFunc] of a custom implementation of [Artifact]
+// under the specified [Kind]. Expecting to be used only during initialization,
+// it panics if the mapping between [Kind] and [IRReadFunc] is not a bijection,
+// or the specified [Kind] is below [KindCustomOffset].
+//
+// Register is not safe for concurrent use. Register must not be called after
+// the first instance of [Cache] has been opened.
+func Register(k Kind, f IRReadFunc) {
+ if k < KindCustomOffset {
+ panic("attempting to register within internal kind range")
+ }
+ register(k, f)
+}
+
+// NewDecoder returns a new [IRDecoder] that reads from the [io.Reader].
+func (c *Cache) NewDecoder(r io.Reader) *IRDecoder {
+ return &IRDecoder{c, r, make(map[unique.Handle[ID]]Artifact), false, false}
+}
+
+const (
+ // irMaxValues is the arbitrary maximum number of values allowed to be
+ // written by [Artifact.Params] and subsequently read via [IRReader].
+ irMaxValues = 1 << 12
+
+ // irMaxDeps is the arbitrary maximum number of direct dependencies allowed
+ // to be returned by [Artifact.Dependencies] and subsequently decoded by
+ // [IRDecoder].
+ irMaxDeps = 1 << 10
+)
+
+var (
+ // ErrIRValues is returned for an [Artifact] with too many parameter values.
+ ErrIRValues = errors.New("artifact has too many IR parameter values")
+
+ // ErrIRDepend is returned for an [Artifact] with too many dependencies.
+ ErrIRDepend = errors.New("artifact has too many dependencies")
+
+ // ErrAlreadyFinalised is returned when attempting to use an [IRReader] that
+ // has already been finalised.
+ ErrAlreadyFinalised = errors.New("reader has already finalised")
+)
+
+// enterReader panics with an appropriate error for an out-of-bounds count and
+// must be called at some point in any exported method.
+func (ir *IRReader) enterReader(read bool) {
+ if ir.count < 0 {
+ panic(ErrAlreadyFinalised)
+ }
+ if ir.count >= irMaxValues {
+ panic(ErrIRValues)
+ }
+
+ if read {
+ ir.count++
+ }
+}
+
+// IRKindError describes an attempt to read an IR value of unexpected kind.
+type IRKindError struct {
+ Got, Want IRValueKind
+ Ancillary uint32
+}
+
+func (e *IRKindError) Error() string {
+ return fmt.Sprintf(
+ "got %s IR value (%#x) instead of %s",
+ e.Got, e.Ancillary, e.Want,
+ )
+}
+
+// readFull reads until either p is filled or an error is encountered.
+func (ir *IRReader) readFull(p []byte) (n int, err error) {
+ for n < len(p) && err == nil {
+ var nn int
+ nn, err = ir.r.Read(p[n:])
+ n += nn
+ }
+ return
+}
+
+// mustRead reads from the underlying measured reader and panics on error. If
+// an [io.EOF] is encountered and n != len(p), the error is promoted to a
+// [io.ErrUnexpectedEOF], if n == 0, [io.EOF] is kept as is, otherwise it is
+// zeroed.
+func (ir *IRReader) mustRead(p []byte) {
+ n, err := ir.readFull(p)
+ if err == nil {
+ return
+ }
+
+ if errors.Is(err, io.EOF) {
+ if n == len(p) {
+ return
+ }
+ err = io.ErrUnexpectedEOF
+ }
+ panic(err)
+}
+
+// mustReadHeader reads the next header via d and checks its kind.
+func (ir *IRReader) mustReadHeader(k IRValueKind) {
+ ir.mustRead(ir.buf[:])
+ ir.h = irValueHeader(binary.LittleEndian.Uint64(ir.buf[:]))
+ if wk := ir.h.kind(); wk != k {
+ panic(&IRKindError{wk, k, ir.h.value()})
+ }
+}
+
+// putAll returns all dependency buffers to the underlying [Cache].
+func (ir *IRReader) putAll() {
+ for _, buf := range ir.deps {
+ ir.d.c.putIdentBuf(buf)
+ }
+ ir.deps = nil
+}
+
+// DiscardAll discards all unstructured dependencies. This is useful to
+// implementations that encode dependencies as [IRKindIdent] which are read back
+// via ReadIdent.
+func (ir *IRReader) DiscardAll() {
+ if ir.deps == nil {
+ panic("attempting to discard dependencies twice")
+ }
+ ir.putAll()
+}
+
+// ErrDependencyDepleted is returned when attempting to advance to the next
+// unstructured dependency when there are none left.
+var ErrDependencyDepleted = errors.New("reading past end of dependencies")
+
+// Next returns the next unstructured dependency.
+func (ir *IRReader) Next() Artifact {
+ if len(ir.deps) == 0 {
+ panic(ErrDependencyDepleted)
+ }
+
+ id := unique.Make(ID(ir.deps[0][wordSize:]))
+ ir.d.c.putIdentBuf(ir.deps[0])
+ ir.deps = ir.deps[1:]
+
+ if a, ok := ir.d.ident[id]; !ok {
+ ir.putAll()
+ panic(DanglingIdentError(id))
+ } else {
+ return a
+ }
+}
+
+// MissedDependencyError is the number of unstructured dependencies remaining
+// in [IRReader] that was never requested or explicitly discarded before
+// finalisation.
+type MissedDependencyError int
+
+func (e MissedDependencyError) Error() string {
+ return "missed " + strconv.Itoa(int(e)) + " unstructured dependencies"
+}
+
+var (
+ // ErrUnexpectedChecksum is returned by a [IRReadFunc] that does not expect
+ // a checksum but received one in [IRKindEnd] anyway.
+ ErrUnexpectedChecksum = errors.New("checksum specified on unsupported artifact")
+ // ErrExpectedChecksum is returned by a [IRReadFunc] that expects a checksum
+ // but did not receive one in [IRKindEnd].
+ ErrExpectedChecksum = errors.New("checksum required but not specified")
+)
+
+// Finalise reads the final [IRKindEnd] value and marks r as finalised. Methods
+// of r are invalid upon entry into Finalise. If a [Checksum] is available via
+// [IREndKnownChecksum], its handle is returned and the caller must store its
+// value in the resulting [Artifact].
+func (ir *IRReader) Finalise() (checksum unique.Handle[Checksum], ok bool) {
+ ir.enterReader(true)
+ ir.count = -1
+
+ ir.mustReadHeader(IRKindEnd)
+ f := IREndFlag(ir.h.value())
+
+ if f&IREndKnownChecksum != 0 {
+ buf := ir.d.c.getIdentBuf()
+ defer ir.d.c.putIdentBuf(buf)
+
+ ir.mustRead(buf[wordSize:])
+ checksum = unique.Make(Checksum(buf[wordSize:]))
+ ok = true
+ }
+
+ if err := ir.ibw.Flush(); err != nil {
+ panic(err)
+ }
+ ir.r, ir.ibw = nil, nil
+
+ if len(ir.deps) != 0 {
+ panic(MissedDependencyError(len(ir.deps)))
+ }
+
+ return
+}
+
+// ReadIdent reads the next value as [IRKindIdent].
+func (ir *IRReader) ReadIdent() Artifact {
+ ir.enterReader(true)
+ ir.mustReadHeader(IRKindIdent)
+
+ buf := ir.d.c.getIdentBuf()
+ defer ir.d.c.putIdentBuf(buf)
+
+ ir.mustRead(buf[wordSize:])
+ id := unique.Make(ID(buf[wordSize:]))
+
+ if a, ok := ir.d.ident[id]; !ok {
+ panic(DanglingIdentError(id))
+ } else {
+ return a
+ }
+}
+
+// ReadUint32 reads the next value as [IRKindUint32].
+func (ir *IRReader) ReadUint32() uint32 {
+ ir.enterReader(true)
+ ir.mustReadHeader(IRKindUint32)
+ return ir.h.value()
+}
+
+// ReadStringBytes reads the next value as [IRKindString] but returns it as a
+// byte slice instead.
+func (ir *IRReader) ReadStringBytes() []byte {
+ ir.enterReader(true)
+ ir.mustReadHeader(IRKindString)
+
+ sz := int(ir.h.value())
+ szWire := alignSize(sz)
+ if szWire > irMaxStringLength {
+ panic(IRStringError("\x00"))
+ }
+
+ p := make([]byte, szWire)
+ ir.mustRead(p)
+ return p[:sz]
+}
+
+// ReadString reads the next value as [IRKindString].
+func (ir *IRReader) ReadString() string {
+ p := ir.ReadStringBytes()
+ return unsafe.String(unsafe.SliceData(p), len(p))
+}
+
+// decode decodes the next [Artifact] in the IR stream and returns any buffer
+// originating from [Cache] before returning. decode returns [io.EOF] if and
+// only if the underlying [io.Reader] is already read to EOF.
+func (d *IRDecoder) decode() (a Artifact, err error) {
+ defer panicToError(&err)
+ var ir IRReader
+
+ defer func() { ir.d = nil }()
+ ir.d = d
+
+ h := sha512.New384()
+ ir.ibw = d.c.getWriter(h)
+ defer d.c.putWriter(ir.ibw)
+ ir.r = io.TeeReader(d.r, ir.ibw)
+
+ if n, _err := ir.readFull(ir.buf[:]); _err != nil {
+ if errors.Is(_err, io.EOF) {
+ if n != 0 {
+ _err = io.ErrUnexpectedEOF
+ }
+ }
+
+ err = _err
+ return
+ }
+ ak := Kind(binary.LittleEndian.Uint64(ir.buf[:]))
+ f, ok := irArtifact[ak]
+ if !ok {
+ err = InvalidKindError(ak)
+ return
+ }
+
+ defer ir.putAll()
+ ir.mustRead(ir.buf[:])
+ sz := binary.LittleEndian.Uint64(ir.buf[:])
+ if sz > irMaxDeps {
+ err = ErrIRDepend
+ return
+ }
+ ir.deps = make([]*extIdent, sz)
+ for i := range ir.deps {
+ ir.deps[i] = d.c.getIdentBuf()
+ }
+ for _, buf := range ir.deps {
+ ir.mustRead(buf[:])
+ }
+
+ a = f(&ir)
+ if a == nil {
+ err = syscall.ENOTRECOVERABLE
+ return
+ }
+
+ if ir.count != -1 {
+ err = ErrRemainingIR
+ return
+ }
+
+ buf := d.c.getIdentBuf()
+ h.Sum(buf[wordSize:wordSize])
+ id := unique.Make(ID(buf[wordSize:]))
+ d.c.putIdentBuf(buf)
+ if _, ok = d.ident[id]; !ok {
+ d.ident[id] = a
+ } else {
+ d.c.msg.Verbosef(
+ "artifact %s appeared more than once in IR stream",
+ Encode(id.Value()),
+ )
+ }
+
+ return
+}
+
+// Decode consumes the IR stream to EOF and returns the final [Artifact]. After
+// Decode returns, Lookup is available and Decode must not be called again.
+func (d *IRDecoder) Decode() (a Artifact, err error) {
+ if d.done {
+ panic("attempting to decode an IR stream twice")
+ }
+ defer func() { d.done = true }()
+
+ var cur Artifact
+next:
+ a, err = d.decode()
+
+ if err == nil {
+ cur = a
+ goto next
+ }
+
+ if errors.Is(err, io.EOF) {
+ a, err = cur, nil
+ d.ok = true
+ }
+ return
+}
+
+// Lookup looks up an [Artifact] described by the IR stream by its identifier.
+func (d *IRDecoder) Lookup(id unique.Handle[ID]) (a Artifact, ok bool) {
+ if !d.ok {
+ panic("attempting to look up artifact without full IR stream")
+ }
+ a, ok = d.ident[id]
+ return
+}
diff --git a/internal/pkg/ir_test.go b/internal/pkg/ir_test.go
new file mode 100644
index 00000000..c5ffc6dc
--- /dev/null
+++ b/internal/pkg/ir_test.go
@@ -0,0 +1,114 @@
+package pkg_test
+
+import (
+ "bytes"
+ "io"
+ "reflect"
+ "testing"
+
+ "hakurei.app/container/check"
+ "hakurei.app/internal/pkg"
+)
+
+func TestIRRoundtrip(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ a pkg.Artifact
+ }{
+ {"http get aligned", pkg.NewHTTPGet(
+ nil, "file:///testdata",
+ pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))),
+ )},
+ {"http get unaligned", pkg.NewHTTPGet(
+ nil, "https://hakurei.app",
+ pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
+ )},
+
+ {"http get tar", pkg.NewHTTPGetTar(
+ nil, "file:///testdata",
+ pkg.Checksum(bytes.Repeat([]byte{0xff}, len(pkg.Checksum{}))),
+ pkg.TarBzip2,
+ )},
+ {"http get tar unaligned", pkg.NewHTTPGetTar(
+ nil, "https://hakurei.app",
+ pkg.Checksum(bytes.Repeat([]byte{0xfe}, len(pkg.Checksum{}))),
+ pkg.TarUncompressed,
+ )},
+
+ {"exec offline", pkg.NewExec(
+ "exec-offline", nil, 0, false,
+ pkg.AbsWork,
+ []string{"HAKUREI_TEST=1"},
+ check.MustAbs("/opt/bin/testtool"),
+ []string{"testtool"},
+
+ pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
+ "stub file",
+ ))), pkg.MustPath("/.hakurei", false, pkg.NewHTTPGetTar(
+ nil, "file:///hakurei.tar",
+ pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
+ pkg.TarUncompressed,
+ )), pkg.MustPath("/opt", false, pkg.NewHTTPGetTar(
+ nil, "file:///testtool.tar.gz",
+ pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
+ pkg.TarGzip,
+ )),
+ )},
+
+ {"exec net", pkg.NewExec(
+ "exec-net",
+ (*pkg.Checksum)(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
+ 0, false,
+ pkg.AbsWork,
+ []string{"HAKUREI_TEST=1"},
+ check.MustAbs("/opt/bin/testtool"),
+ []string{"testtool", "net"},
+
+ pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
+ "stub file",
+ ))), pkg.MustPath("/.hakurei", false, pkg.NewHTTPGetTar(
+ nil, "file:///hakurei.tar",
+ pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
+ pkg.TarUncompressed,
+ )), pkg.MustPath("/opt", false, pkg.NewHTTPGetTar(
+ nil, "file:///testtool.tar.gz",
+ pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
+ pkg.TarGzip,
+ )),
+ )},
+
+ {"file anonymous", pkg.NewFile("", []byte{0})},
+ {"file", pkg.NewFile("stub", []byte("stub"))},
+ }
+ testCasesCache := make([]cacheTestCase, len(testCases))
+ for i, tc := range testCases {
+ want := tc.a
+ testCasesCache[i] = cacheTestCase{tc.name, nil,
+ func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ r, w := io.Pipe()
+
+ done := make(chan error, 1)
+ go func() {
+ t.Helper()
+ done <- c.EncodeAll(w, want)
+ _ = w.Close()
+ }()
+
+ if got, err := c.NewDecoder(r).Decode(); err != nil {
+ t.Fatalf("Decode: error = %v", err)
+ } else if !reflect.DeepEqual(got, want) {
+ t.Fatalf("Decode: %#v, want %#v", got, want)
+ }
+
+ if err := <-done; err != nil {
+ t.Fatalf("EncodeAll: error = %v", err)
+ }
+ }, pkg.MustDecode(
+ "E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C",
+ ),
+ }
+ }
+ checkWithCache(t, testCasesCache)
+}
diff --git a/internal/pkg/net.go b/internal/pkg/net.go
index b8fb8fb3..1f37a92d 100644
--- a/internal/pkg/net.go
+++ b/internal/pkg/net.go
@@ -19,8 +19,8 @@ type httpArtifact struct {
// closing the [io.ReadCloser] returned by Cure.
checksum unique.Handle[Checksum]
- // doFunc is the Do method of [http.Client] supplied by the caller.
- doFunc func(req *http.Request) (*http.Response, error)
+ // client is the address of the caller-supplied [http.Client].
+ client *http.Client
}
var _ KnownChecksum = new(httpArtifact)
@@ -33,10 +33,7 @@ func NewHTTPGet(
url string,
checksum Checksum,
) FileArtifact {
- if c == nil {
- c = http.DefaultClient
- }
- return &httpArtifact{url: url, checksum: unique.Make(checksum), doFunc: c.Do}
+ return &httpArtifact{url: url, checksum: unique.Make(checksum), client: c}
}
// Kind returns the hardcoded [Kind] constant.
@@ -44,8 +41,17 @@ func (*httpArtifact) Kind() Kind { return KindHTTPGet }
// Params writes the backing url string. Client is not represented as it does
// not affect [Cache.Cure] outcome.
-func (a *httpArtifact) Params(ctx *IContext) {
- ctx.GetHash().Write([]byte(a.url))
+func (a *httpArtifact) Params(ctx *IContext) { ctx.WriteString(a.url) }
+
+func init() {
+ register(KindHTTPGet, func(r *IRReader) Artifact {
+ url := r.ReadString()
+ checksum, ok := r.Finalise()
+ if !ok {
+ panic(ErrExpectedChecksum)
+ }
+ return NewHTTPGet(nil, url, checksum.Value())
+ })
}
// Dependencies returns a nil slice.
@@ -80,8 +86,13 @@ func (a *httpArtifact) Cure(r *RContext) (rc io.ReadCloser, err error) {
}
req.Header.Set("User-Agent", "Hakurei/1.1")
+ c := a.client
+ if c == nil {
+ c = http.DefaultClient
+ }
+
var resp *http.Response
- if resp, err = a.doFunc(req); err != nil {
+ if resp, err = c.Do(req); err != nil {
return
}
diff --git a/internal/pkg/net_test.go b/internal/pkg/net_test.go
index f953f662..5f92c2c0 100644
--- a/internal/pkg/net_test.go
+++ b/internal/pkg/net_test.go
@@ -109,7 +109,7 @@ func TestHTTPGet(t *testing.T) {
)
wantPathname := base.Append(
"identifier",
- "00BNNr-PsNMtowTpEG86ZeI7eQKoD-pjSCPAal1e5MYqr_N7FLpyXKdXLXE8WEBF",
+ "oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_",
)
if pathname, checksum, err := c.Cure(f); err != nil {
t.Fatalf("Cure: error = %v", err)
@@ -156,6 +156,6 @@ func TestHTTPGet(t *testing.T) {
if _, _, err := c.Cure(f); !reflect.DeepEqual(err, wantErrNotFound) {
t.Fatalf("Pathname: error = %#v, want %#v", err, wantErrNotFound)
}
- }, pkg.MustDecode("KkdL8x2a84V8iYZop5jSTyba54xSgf_NZ1R0c4nSp9xTdk3SK_zUKGhNJ2uK8wMY")},
+ }, pkg.MustDecode("L_0RFHpr9JUS4Zp14rz2dESSRvfLzpvqsLhR1-YjQt8hYlmEdVl7vI3_-v8UNPKs")},
})
}
diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go
index 76bae916..154e20b0 100644
--- a/internal/pkg/pkg.go
+++ b/internal/pkg/pkg.go
@@ -65,35 +65,6 @@ func MustDecode(s string) (checksum Checksum) {
return
}
-// IContext is passed to [Artifact.Params] and provides identifier information
-// and the target [hash.Hash] for writing params into.
-//
-// Methods of IContext are safe for concurrent use. IContext is valid
-// until [Artifact.Params] returns.
-type IContext struct {
- // Address of underlying [Cache], should be zeroed or made unusable after
- // [Artifact.Params] returns and must not be exposed directly.
- cache *Cache
- // Made available for writing, should be zeroed after [Artifact.Params]
- // returns. Internal state must not be inspected.
- h hash.Hash
-}
-
-// Unwrap returns the underlying [context.Context].
-func (i *IContext) Unwrap() context.Context { return i.cache.ctx }
-
-// GetHash returns the underlying [hash.Hash] for writing. Callers must not
-// attempt to inspect its internal state.
-func (i *IContext) GetHash() hash.Hash { return i.h }
-
-// WriteIdent writes the identifier of [Artifact] to the underlying [hash.Hash].
-func (i *IContext) WriteIdent(a Artifact) {
- buf := i.cache.getIdentBuf()
- *(*ID)(buf[wordSize:]) = i.cache.Ident(a).Value()
- i.h.Write(buf[wordSize:])
- i.cache.putIdentBuf(buf)
-}
-
// TContext is passed to [TrivialArtifact.Cure] and provides information and
// methods required for curing the [TrivialArtifact].
//
@@ -238,10 +209,12 @@ type Artifact interface {
// [Artifact] is allowed to return the same [Kind] value.
Kind() Kind
- // Params writes opaque bytes that describes [Artifact]. Implementations
+ // Params writes deterministic values describing [Artifact]. Implementations
// must guarantee that these values are unique among differing instances
- // of the same implementation with the same dependencies. Callers must not
- // attempt to interpret these params.
+ // of the same implementation with identical dependencies and conveys enough
+ // information to create another instance of [Artifact] identical to the
+ // instance emitting these values. The new instance created via [IRReadFunc]
+ // from these values must then produce identical IR values.
//
// Result must remain identical across multiple invocations.
Params(ctx *IContext)
@@ -564,47 +537,13 @@ func (c *Cache) unsafeIdent(a Artifact, encodeKind bool) (
return
}
- deps := a.Dependencies()
- idents := make([]*extIdent, len(deps))
- for i, d := range deps {
- dbuf, did := c.unsafeIdent(d, true)
- if dbuf == nil {
- dbuf = c.getIdentBuf()
- binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
- *(*ID)(dbuf[wordSize:]) = did.Value()
- } else {
- c.storeIdent(d, dbuf)
- }
- defer c.putIdentBuf(dbuf)
- idents[i] = dbuf
- }
- slices.SortFunc(idents, func(a, b *extIdent) int {
- return bytes.Compare(a[:], b[:])
- })
- idents = slices.CompactFunc(idents, func(a, b *extIdent) bool {
- return *a == *b
- })
-
buf = c.getIdentBuf()
h := sha512.New384()
- binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
- h.Write(buf[:wordSize])
- for _, dn := range idents {
- h.Write(dn[:])
- }
- kcBuf := c.getIdentBuf()
- if kc, ok := a.(KnownChecksum); ok {
- *(*Checksum)(kcBuf[:]) = kc.Checksum()
- } else {
- *(*Checksum)(kcBuf[:]) = Checksum{}
+ if err := c.Encode(h, a); err != nil {
+ // unreachable
+ panic(err)
}
- h.Write((*Checksum)(kcBuf[:])[:])
- c.putIdentBuf(kcBuf)
-
- i := IContext{c, h}
- a.Params(&i)
- i.cache, i.h = nil, nil
-
+ binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
h.Sum(buf[wordSize:wordSize])
return
}
@@ -1000,8 +939,9 @@ func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
return
}
-// InvalidFileModeError describes an [Artifact.Cure] that did not result in
-// a regular file or directory located at the work pathname.
+// InvalidFileModeError describes a [FloodArtifact.Cure] or
+// [TrivialArtifact.Cure] that did not result in a regular file or directory
+// located at the work pathname.
type InvalidFileModeError fs.FileMode
// Error returns a constant string.
@@ -1009,8 +949,8 @@ func (e InvalidFileModeError) Error() string {
return "artifact did not produce a regular file or directory"
}
-// NoOutputError describes an [Artifact.Cure] that did not populate its
-// work pathname despite completing successfully.
+// NoOutputError describes a [FloodArtifact.Cure] or [TrivialArtifact.Cure]
+// that did not populate its work pathname despite completing successfully.
type NoOutputError struct{}
// Unwrap returns [os.ErrNotExist].
diff --git a/internal/pkg/pkg_test.go b/internal/pkg/pkg_test.go
index d03cd833..700aa962 100644
--- a/internal/pkg/pkg_test.go
+++ b/internal/pkg/pkg_test.go
@@ -94,7 +94,7 @@ type stubArtifact struct {
}
func (a *stubArtifact) Kind() pkg.Kind { return a.kind }
-func (a *stubArtifact) Params(ctx *pkg.IContext) { ctx.GetHash().Write(a.params) }
+func (a *stubArtifact) Params(ctx *pkg.IContext) { ctx.Write(a.params) }
func (a *stubArtifact) Dependencies() []pkg.Artifact { return a.deps }
func (a *stubArtifact) Cure(t *pkg.TContext) error { return a.cure(t) }
func (*stubArtifact) IsExclusive() bool { return false }
@@ -110,7 +110,7 @@ type stubArtifactF struct {
}
func (a *stubArtifactF) Kind() pkg.Kind { return a.kind }
-func (a *stubArtifactF) Params(ctx *pkg.IContext) { ctx.GetHash().Write(a.params) }
+func (a *stubArtifactF) Params(ctx *pkg.IContext) { ctx.Write(a.params) }
func (a *stubArtifactF) Dependencies() []pkg.Artifact { return a.deps }
func (a *stubArtifactF) Cure(f *pkg.FContext) error { return a.cure(f) }
func (a *stubArtifactF) IsExclusive() bool { return a.excl }
@@ -219,7 +219,7 @@ func TestIdent(t *testing.T) {
},
nil,
}, unique.Make[pkg.ID](pkg.MustDecode(
- "v86qCz5fDqUsjA3KY_4LIrEh3aQnp04plNiWJ5_ap06McHSSBlROyKIFEwx3c0O7",
+ "WKErnjTOVbuH2P9a0gM4OcAAO4p-CoX2HQu7CbZrg8ZOzApvWoO3-ISzPw6av_rN",
))},
}
@@ -532,7 +532,7 @@ func TestCache(t *testing.T) {
kind: pkg.KindExec,
params: []byte("artifact overridden to be incomplete"),
}}, nil, pkg.Checksum{}, pkg.InvalidArtifactError(pkg.MustDecode(
- "0z3fA0YngFaRRCQRrxKburhpAGz3gkYIZ346X_tAwOr_ldelYg1nTifI3-WX8hQD",
+ "E__uZ1sLIvb84vzSm5Uezb03RogsiaeTt1nfIVv8TKnnf4LqwtSi-smdHhlkZrUJ",
))},
{"error passthrough", newStubFile(
@@ -954,6 +954,17 @@ func TestErrors(t *testing.T) {
{"NoOutputError", pkg.NoOutputError{
// empty struct
}, "artifact cured successfully but did not produce any output"},
+
+ {"IRKindError", &pkg.IRKindError{
+ Got: pkg.IRKindEnd,
+ Want: pkg.IRKindIdent,
+ Ancillary: 0xcafebabe,
+ }, "got terminator IR value (0xcafebabe) instead of ident"},
+ {"IRKindError invalid", &pkg.IRKindError{
+ Got: 0xbeef,
+ Want: pkg.IRKindIdent,
+ Ancillary: 0xcafe,
+ }, "got invalid kind 48879 IR value (0xcafe) instead of ident"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
diff --git a/internal/pkg/tar.go b/internal/pkg/tar.go
index 474df034..1215adbd 100644
--- a/internal/pkg/tar.go
+++ b/internal/pkg/tar.go
@@ -4,7 +4,6 @@ import (
"archive/tar"
"compress/bzip2"
"compress/gzip"
- "encoding/binary"
"errors"
"fmt"
"io"
@@ -29,7 +28,7 @@ type tarArtifact struct {
// Caller-supplied backing tarball.
f Artifact
// Compression on top of the tarball.
- compression uint64
+ compression uint32
}
// tarArtifactNamed embeds tarArtifact for a [fmt.Stringer] tarball.
@@ -47,7 +46,7 @@ func (a *tarArtifactNamed) String() string { return a.name + "-unpack" }
// NewTar returns a new [Artifact] backed by the supplied [Artifact] and
// compression method. The source [Artifact] must be compatible with
// [TContext.Open].
-func NewTar(a Artifact, compression uint64) Artifact {
+func NewTar(a Artifact, compression uint32) Artifact {
ta := tarArtifact{a, compression}
if s, ok := a.(fmt.Stringer); ok {
if name := s.String(); name != "" {
@@ -62,7 +61,7 @@ func NewHTTPGetTar(
hc *http.Client,
url string,
checksum Checksum,
- compression uint64,
+ compression uint32,
) Artifact {
return NewTar(NewHTTPGet(hc, url, checksum), compression)
}
@@ -71,8 +70,16 @@ func NewHTTPGetTar(
func (a *tarArtifact) Kind() Kind { return KindTar }
// Params writes compression encoded in little endian.
-func (a *tarArtifact) Params(ctx *IContext) {
- ctx.GetHash().Write(binary.LittleEndian.AppendUint64(nil, a.compression))
+func (a *tarArtifact) Params(ctx *IContext) { ctx.WriteUint32(a.compression) }
+
+func init() {
+ register(KindTar, func(r *IRReader) Artifact {
+ a := NewTar(r.Next(), r.ReadUint32())
+ if _, ok := r.Finalise(); ok {
+ panic(ErrUnexpectedChecksum)
+ }
+ return a
+ })
}
// Dependencies returns a slice containing the backing file.
diff --git a/internal/pkg/tar_test.go b/internal/pkg/tar_test.go
index 3e611df6..26a67d6c 100644
--- a/internal/pkg/tar_test.go
+++ b/internal/pkg/tar_test.go
@@ -40,7 +40,7 @@ func TestTar(t *testing.T) {
}, pkg.MustDecode(
"cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM",
))
- }, pkg.MustDecode("nnOiyjjjvgZChsGtO4rA1JHckwYBBbxwNfecPJp62OFP6aoYUxHQ5UtYsrDpnwan")},
+ }, pkg.MustDecode("NQTlc466JmSVLIyWklm_u8_g95jEEb98PxJU-kjwxLpfdjwMWJq0G8ze9R4Vo1Vu")},
{"http expand", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
checkTarHTTP(t, base, c, fstest.MapFS{
@@ -51,7 +51,7 @@ func TestTar(t *testing.T) {
}, pkg.MustDecode(
"CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN",
))
- }, pkg.MustDecode("bQVH19N7dX50SdQ6JNVYbFdDZV4t8IaM4dhxGvjACpdoEgJ2jZJfYKLH4ya7ZD_s")},
+ }, pkg.MustDecode("hSoSSgCYTNonX3Q8FjvjD1fBl-E-BQyA6OTXro2OadXqbST4tZ-akGXszdeqphRe")},
})
}
@@ -98,17 +98,36 @@ func checkTarHTTP(
wantIdent := func() pkg.ID {
h := sha512.New384()
+
+ // kind uint64
h.Write([]byte{byte(pkg.KindTar), 0, 0, 0, 0, 0, 0, 0})
+ // deps_sz uint64
+ h.Write([]byte{1, 0, 0, 0, 0, 0, 0, 0})
+ // kind uint64
h.Write([]byte{byte(pkg.KindHTTPGet), 0, 0, 0, 0, 0, 0, 0})
+ // ident ID
h0 := sha512.New384()
+ // kind uint64
h0.Write([]byte{byte(pkg.KindHTTPGet), 0, 0, 0, 0, 0, 0, 0})
- h0.Write(testdataChecksum[:])
+ // deps_sz uint64
+ h0.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0})
+ // url string
+ h0.Write([]byte{byte(pkg.IRKindString), 0, 0, 0})
+ h0.Write([]byte{0x10, 0, 0, 0})
h0.Write([]byte("file:///testdata"))
+ // end(KnownChecksum)
+ h0.Write([]byte{byte(pkg.IRKindEnd), 0, 0, 0})
+ h0.Write([]byte{byte(pkg.IREndKnownChecksum), 0, 0, 0})
+ // checksum Checksum
+ h0.Write(testdataChecksum[:])
h.Write(h0.Sum(nil))
-
- h.Write(make([]byte, len(pkg.Checksum{})))
- h.Write([]byte{pkg.TarGzip, 0, 0, 0, 0, 0, 0, 0})
+ // compression uint32
+ h.Write([]byte{byte(pkg.IRKindUint32), 0, 0, 0})
+ h.Write([]byte{pkg.TarGzip, 0, 0, 0})
+ // end
+ h.Write([]byte{byte(pkg.IRKindEnd), 0, 0, 0})
+ h.Write([]byte{0, 0, 0, 0})
return pkg.ID(h.Sum(nil))
}()
diff --git a/internal/pkg/testdata/main.go b/internal/pkg/testdata/main.go
index 4c348802..ad047a2a 100644
--- a/internal/pkg/testdata/main.go
+++ b/internal/pkg/testdata/main.go
@@ -142,12 +142,12 @@ func main() {
}
const checksumEmptyDir = "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"
- ident := "LRxdkRYNKnZT6bKiu5W8ATeAAmq3n_5AAJkF6G0EpAOEloiZvADJBkfixgtgF1Z9"
+ ident := "dztPS6jRjiZtCF4_p8AzfnxGp6obkhrgFVsxdodbKWUoAEVtDz3MykepJB4kI_ks"
log.Println(m)
next := func() { m = m.Next; log.Println(m) }
if overlayRoot {
- ident = "UB9HPeMgMPJf3Ut4jLWwnCtu_P3Lr29i8Erf084bHe8jjzBMKPDNxQ3RMrirkH6H"
+ ident = "RdMA-mubnrHuu3Ky1wWyxauSYCO0ZH_zCPUj3uDHqkfwv5sGcByoF_g5PjlGiClb"
if m.Root != "/" || m.Target != "/" ||
m.Source != "overlay" || m.FsType != "overlay" {
@@ -165,7 +165,7 @@ func main() {
log.Fatal("unexpected artifact checksum")
}
} else {
- ident = "g6gj2JWNXN-oNikou626vDqcMeZCn_TcV4xKuizBaPAWcasG2sVvItb5kZovMrzE"
+ ident = "p1t_drXr34i-jZNuxDMLaMOdL6tZvQqhavNafGynGqxOZoXAUTSn7kqNh3Ovv3DT"
lowerdirsEscaped := strings.Split(lowerdir, ":")
lowerdirs := lowerdirsEscaped[:0]
@@ -194,7 +194,7 @@ func main() {
}
} else {
if hostNet {
- ident = "TAspufRsG2I_TsxUUj2b7bUnCHgcVSdh6aOZpzL0W5Bjn4EZmOGzjofaOWd8J11H"
+ ident = "G8qPxD9puvvoOVV7lrT80eyDeIl3G_CCFoKw12c8mCjMdG1zF7NEPkwYpNubClK3"
}
if m.Root != "/sysroot" || m.Target != "/" {
@@ -213,14 +213,14 @@ func main() {
}
if promote {
- ident = "ywzI31S5McuYu7vzI2kqpSC_nsNzpWBXVCwPoLAYi9QVT0mODgzqoo9jYYaczPbf"
+ ident = "xXTIYcXmgJWNLC91c417RRrNM9cjELwEZHpGvf8Fk_GNP5agRJp_SicD0w9aMeLJ"
}
next() // testtool artifact
next()
if overlayWork {
- ident = "Fud5ldJfpsgLt-rkLWrLO-aVYhQm-esTswetjxydPeQMK4jHNJ_1fGHVahaiCZ9y"
+ ident = "5hlaukCirnXE4W_RSLJFOZN47Z5RiHnacXzdFp_70cLgiJUGR6cSb_HaFftkzi0-"
if m.Root != "/" || m.Target != "/work" ||
m.Source != "overlay" || m.FsType != "overlay" {
log.Fatal("unexpected work mount entry")