internal/pkg: standardise artifact IR

This should hopefully provide good separation between the artifact curing backend implementation and the (still work in progress) language. Making the IR parseable also guarantees uniqueness of the representation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-02-04 14:41:18 +09:00
parent f982b13a59
commit e0c720681b
15 changed files with 1164 additions and 203 deletions

View File

@@ -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))

View File

@@ -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) {

View File

@@ -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

View File

@@ -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")},
})
}

View File

@@ -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 }

View File

@@ -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",
)},
})
}

761
internal/pkg/ir.go Normal file
View File

@@ -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
}

114
internal/pkg/ir_test.go Normal file
View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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")},
})
}

View File

@@ -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].

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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))
}()

View File

@@ -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")