diff --git a/internal/pkg/dir.go b/internal/pkg/dir.go index d0c3668..1d98350 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 3f298d5..98e6ab4 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 f527080..3995563 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 b573bf5..16e5031 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 c2093d6..149ca81 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 ec5e98c..ee5359e 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 0000000..f40cd87 --- /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)< 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 0000000..c5ffc6d --- /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 b8fb8fb..1f37a92 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 f953f66..5f92c2c 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 76bae91..154e20b 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() + if err := c.Encode(h, a); err != nil { + // unreachable + panic(err) + } 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{} - } - h.Write((*Checksum)(kcBuf[:])[:]) - c.putIdentBuf(kcBuf) - - i := IContext{c, h} - a.Params(&i) - i.cache, i.h = nil, nil - 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 d03cd83..700aa96 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 474df03..1215adb 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 3e611df..26a67d6 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 4c34880..ad047a2 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")