forked from rosa/hakurei
Compare commits
378 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
e231341e48
|
|||
|
70f977627d
|
|||
|
f3a6f7ddf9
|
|||
|
2a9aa3b400
|
|||
|
68a91523b9
|
|||
|
5647321622
|
|||
|
cdd31dd27b
|
|||
|
0615899e56
|
|||
|
0914569e62
|
|||
|
25d9edfc64
|
|||
|
af4c3bbff2
|
|||
|
54aae9d72a
|
|||
|
58646f8ea5
|
|||
|
9d7a27d8ac
|
|||
|
6546ddc64b
|
|||
|
cbf18b302d
|
|||
|
1acb5b0105
|
|||
|
40b33f9fc7
|
|||
|
443a7a30f6
|
|||
|
497e4a5642
|
|||
|
c0e3841ddb
|
|||
|
9ce2c325db
|
|||
|
9836030c59
|
|||
|
b482fd4abf
|
|||
|
2e502ede6c
|
|||
|
4bec0b890c
|
|||
|
7770ccf0aa
|
|||
|
656059278d
|
|||
|
1a9974ffdc
|
|||
|
1a2699b486
|
|||
|
1d3d621e2f
|
|||
|
47f4e287fc
|
|||
|
2e710328a4
|
|||
|
2e7b52d701
|
|||
|
d728607505
|
|||
|
ef414ab01a
|
|||
|
96abf266dd
|
|||
|
fcba32e9c4
|
|||
|
a7f5a5802d
|
|||
|
bb230378e0
|
|||
|
f638c73933
|
|||
|
98d915af3d
|
|||
|
c0593e8325
|
|||
|
608d8303ec
|
|||
|
1c6f30379e
|
|||
|
009a4e0d58
|
|||
|
e7c8656691
|
|||
|
d6be116ff8
|
|||
|
962b02cf25
|
|||
|
6fd6d971ed
|
|||
|
548c96c7ec
|
|||
|
6e8bfa6c4c
|
|||
|
a770d62b9b
|
|||
|
ff44060763
|
|||
|
3010a209b5
|
|||
|
e65a3b435c
|
|||
|
23515f67c8
|
|||
|
4389df60ae
|
|||
|
8092492018
|
|||
|
a7877844bf
|
|||
|
1ed027846d
|
|||
|
2f376d4813
|
|||
|
dc3810b530
|
|||
|
6e9e8c74f3
|
|||
|
4d60fa5632
|
|||
|
8807cbc730
|
|||
|
0e95573f18
|
|||
|
eb2b53307a
|
|||
|
682b3a2ce5
|
|||
|
594221eb78
|
|||
|
34822925e1
|
|||
|
37df040d85
|
|||
|
0360e779f3
|
|||
|
3e236333a7
|
|||
|
f24ae21af1
|
|||
|
99b324fb17
|
|||
|
6f50811dc9
|
|||
|
6b87bac401
|
|||
|
a967aa3b6e
|
|||
|
38bc2c7508
|
|||
|
30eb0d6a61
|
|||
|
c2ff9c9fa5
|
|||
|
d38d306147
|
|||
|
c32c06b2e8
|
|||
|
61199f734c
|
|||
|
87cf0d4e6b
|
|||
|
cf0dffa0f5
|
|||
|
686d7ec63a
|
|||
|
4c653b1151
|
|||
|
0b0a63d151
|
|||
|
6231cfe2aa
|
|||
|
712e80890b
|
|||
|
3fe7d48014
|
|||
|
16f9d39427
|
|||
|
c1cd5ba07b
|
|||
|
7b0cd2e472
|
|||
|
e580307528
|
|||
|
ee1dffb676
|
|||
|
f095fcf181
|
|||
|
ca8a130130
|
|||
|
0ad6b00e41
|
|||
|
ad0f1cf36b
|
|||
|
b12d924fa2
|
|||
|
c31d8ae41a
|
|||
|
6dbbf15c0e
|
|||
|
be7de68a42
|
|||
|
a759cf3666
|
|||
|
8c2dd3e984
|
|||
|
67038d5af4
|
|||
|
53d8d12e7f
|
|||
|
7997d79e56
|
|||
|
f2f1726190
|
|||
|
f63203cb0a
|
|||
|
19555c7670
|
|||
|
a3beab8959
|
|||
|
2ea786d6a9
|
|||
|
747d4ec4b0
|
|||
|
b76e6f6519
|
|||
|
840d8f68bf
|
|||
|
4bede7ecdd
|
|||
|
487a03b5a3
|
|||
|
8f3c22896a
|
|||
|
a167c1aba5
|
|||
|
a6008ef68b
|
|||
|
5228b27362
|
|||
|
f00d3a07ad
|
|||
|
f9538bc21b
|
|||
|
6ae5efec56
|
|||
|
14f4c59c8c
|
|||
|
688d43417b
|
|||
|
9f8fafa39b
|
|||
|
6643cfbeee
|
|||
|
dcde38f2e9
|
|||
|
deebbf6b1a
|
|||
|
0c557798bc
|
|||
|
327e6ed5a2
|
|||
|
76c7a423a9
|
|||
|
6e113b8836
|
|||
|
ce9f4b5f71
|
|||
|
8f727273ef
|
|||
|
d0a63b942e
|
|||
|
7f2126df32
|
|||
|
0cf0e18e35
|
|||
|
ee5c0dd135
|
|||
|
92c48d82e2
|
|||
|
c79a4fe7f8
|
|||
|
0aeb2bccfb
|
|||
|
50e079b99f
|
|||
|
fb2cb5005a
|
|||
|
6e73c28a92
|
|||
|
2c08aa3674
|
|||
|
1af73ae7b4
|
|||
|
c9aa5e04b1
|
|||
|
70a38bd3b0
|
|||
|
533b15da89
|
|||
|
a890e1d0e5
|
|||
|
e3520835bb
|
|||
|
0e56847754
|
|||
|
145d03b366
|
|||
|
2886228d40
|
|||
|
e1e499b79e
|
|||
|
65b7dd8b37
|
|||
|
8d72b9e5bd
|
|||
|
8a3c3d145a
|
|||
|
575ef307ad
|
|||
|
d4144fcf7f
|
|||
|
bad66facbc
|
|||
|
4aba014eac
|
|||
|
779ba994ce
|
|||
|
917be2de93
|
|||
|
9aad98d409
|
|||
|
b0d06b67dc
|
|||
|
089100f29d
|
|||
|
dfd26abf6c
|
|||
|
617ee21647
|
|||
|
15cdb37ec2
|
|||
|
1f0bdc7aca
|
|||
|
e3ffe85670
|
|||
|
f4403ba5cd
|
|||
|
5a26895a22
|
|||
|
09d9f766a9
|
|||
|
6558169666
|
|||
|
cccf970c57
|
|||
|
57ffb21690
|
|||
|
9c560b455a
|
|||
|
4c7c0fbfc6
|
|||
|
18b3b7904e
|
|||
|
fefefdf734
|
|||
|
b84bb09a80
|
|||
|
337bf20f50
|
|||
|
1cb792cf6e
|
|||
|
b2b40b07e8
|
|||
|
da11b26ec1
|
|||
|
024489e800
|
|||
|
0f795712b0
|
|||
|
7e2210ff71
|
|||
|
a71a008f3c
|
|||
|
162265b47e
|
|||
|
3fa7ac04e4
|
|||
|
bf2867d653
|
|||
|
ec0f0f6507
|
|||
|
a77a802955
|
|||
|
4407e14dfc
|
|||
|
e024d3184a
|
|||
|
8e1bf00c2d
|
|||
|
b111e22050
|
|||
|
1fa458c0be
|
|||
|
2c7ae67a67
|
|||
|
3826621b21
|
|||
|
041b505c2e
|
|||
|
e6debce649
|
|||
|
aa26b86fce
|
|||
|
a57a8fd5d8
|
|||
|
1d5d063d6a
|
|||
|
e61628a34e
|
|||
|
5a18f14929
|
|||
|
f12880688d
|
|||
|
bb5bbfe16a
|
|||
|
427e1ca37c
|
|||
|
96fdd9ecc5
|
|||
|
02771b655b
|
|||
|
d1c8d2c39b
|
|||
|
0efd742e8a
|
|||
|
ae1fe638d5
|
|||
|
445d95023b
|
|||
|
fc66f0bb47
|
|||
|
2cd6b35bee
|
|||
|
09a216c6ec
|
|||
|
44d17325c2
|
|||
|
544ce77cbc
|
|||
|
63c3c30b23
|
|||
|
d23c4ecc7c
|
|||
|
a46656dff8
|
|||
|
77db153ff5
|
|||
|
520d95bc07
|
|||
|
451df3f4e7
|
|||
|
011fac15ed
|
|||
|
347682ad0b
|
|||
|
1a2b979add
|
|||
|
b1c90cc380
|
|||
|
3a66b8143a
|
|||
|
64bbd3aabd
|
|||
|
08799a13d0
|
|||
|
1aef9c3bbb
|
|||
|
1f38303747
|
|||
|
640777b00c
|
|||
|
1d657193cf
|
|||
|
bab5406295
|
|||
|
725ae7d64d
|
|||
|
37a0c3967e
|
|||
|
ea0692548f
|
|||
|
48ea23e648
|
|||
|
40320e4920
|
|||
|
3ca0f61632
|
|||
|
6ffaac96e3
|
|||
|
13c7713d0c
|
|||
|
42389f7ec5
|
|||
|
30f130c691
|
|||
|
ceb4d26087
|
|||
|
852f3a9b3d
|
|||
|
5e02dbdb0d
|
|||
|
6a3248d472
|
|||
|
67404c98d9
|
|||
|
b9bf69cfce
|
|||
|
4648f98272
|
|||
|
11d99439ac
|
|||
|
39e4c5b8ac
|
|||
|
e8f6db38b6
|
|||
|
20d5b71575
|
|||
|
e903e7f542
|
|||
|
1caa051f4d
|
|||
|
dcdc6f7f6d
|
|||
|
5ad6f26b46
|
|||
|
7ba75a79f4
|
|||
|
9ef84d3904
|
|||
|
3b7b6e51fb
|
|||
|
b1b4debb82
|
|||
|
021cbbc2a8
|
|||
|
a4a54a4a4d
|
|||
|
04a344aac6
|
|||
|
6b98156a3d
|
|||
|
753432cf09
|
|||
|
f8902e3679
|
|||
|
8ee53a5164
|
|||
|
3981d44757
|
|||
|
9fd67e47b4
|
|||
|
4dcec40156
|
|||
|
9a274c78a3
|
|||
|
5647c3a91f
|
|||
|
992139c75d
|
|||
|
57c69b533e
|
|||
|
6f0c2a80f2
|
|||
|
08dfefb28d
|
|||
|
b081629662
|
|||
|
fba541f301
|
|||
|
5f0da3d5c2
|
|||
|
4d5841dd62
|
|||
|
9e752b588a
|
|||
|
27b1aaae38
|
|||
|
9e18de1dc2
|
|||
|
b80ea91a42
|
|||
|
30a9dfa4b8
|
|||
|
8d657b6fdf
|
|||
|
ae9b9adfd2
|
|||
|
dd6a480a21
|
|||
|
3942272c30
|
|||
|
9036986156
|
|||
|
a394971dd7
|
|||
|
9daba60809
|
|||
|
bcd79a22ff
|
|||
|
0ff7ab915b
|
|||
|
823575acac
|
|||
|
136bc0917b
|
|||
|
d6b082dd0b
|
|||
|
89d6d9576b
|
|||
|
fafce04a5d
|
|||
|
5d760a1db9
|
|||
|
d197e40b2a
|
|||
|
2008902247
|
|||
|
30ac985fd2
|
|||
|
e9fec368f8
|
|||
|
46add42f58
|
|||
|
377b61e342
|
|||
|
520c36db6d
|
|||
|
3352bb975b
|
|||
|
f7f48d57e9
|
|||
|
5c2345128e
|
|||
|
78f9676b1f
|
|||
|
5b5b676132
|
|||
|
78383fb6e8
|
|||
|
e97f6a393f
|
|||
|
eeffefd22b
|
|||
|
ac825640ab
|
|||
|
a7f7ce1795
|
|||
|
38c639e35c
|
|||
|
b2cb13e94c
|
|||
|
46f98d12d6
|
|||
|
503c7f953c
|
|||
|
15c9f6545d
|
|||
|
83b0e32c55
|
|||
|
eeaf26e7a2
|
|||
|
b587caf2e8
|
|||
|
f1c2ca4928
|
|||
|
0ca301219f
|
|||
|
e2199e1276
|
|||
|
86eacb3208
|
|||
|
8541bdd858
|
|||
|
46be0b0dc8
|
|||
|
cbe37e87e7
|
|||
|
66d741fb07
|
|||
|
0d449011f6
|
|||
|
46428ed85d
|
|||
|
081d6b463c
|
|||
|
11b3171180
|
|||
|
adbb84c3dd
|
|||
|
1084e31d95
|
|||
|
27a1b8fe0a
|
|||
|
b2141a41d7
|
|||
|
c0dff5bc87
|
|||
|
04513c0510
|
|||
|
28ebf973d6
|
|||
|
41aeb404ec
|
|||
|
0b1009786f
|
|||
|
b390640376
|
|||
|
ad2c9f36cd
|
|||
|
67db3fbb8d
|
|||
|
560cb626a1
|
|||
|
c33a6a5b7e
|
|||
|
952082bd9b
|
|||
|
24a9b24823
|
|||
|
c2e61e7987
|
|||
|
86787b3bc5
|
|||
|
cdfcfe6ce0
|
|||
|
68a2f0c240
|
|||
|
7319c7adf9
|
|||
|
e9c890cbb2
|
|||
|
6f924336fc
|
|||
|
bd88f10524
|
+2
-6
@@ -7,12 +7,8 @@
|
|||||||
|
|
||||||
# go generate
|
# go generate
|
||||||
/cmd/hakurei/LICENSE
|
/cmd/hakurei/LICENSE
|
||||||
/cmd/pkgserver/.sass-cache
|
/cmd/mbf/internal/pkgserver/ui/static
|
||||||
/cmd/pkgserver/ui/static/*.js
|
/internal/pkg/internal/testtool/testtool
|
||||||
/cmd/pkgserver/ui/static/*.css*
|
|
||||||
/cmd/pkgserver/ui/static/*.css.map
|
|
||||||
/cmd/pkgserver/ui_test/static
|
|
||||||
/internal/pkg/testdata/testtool
|
|
||||||
/internal/rosa/hakurei_current.tar.gz
|
/internal/rosa/hakurei_current.tar.gz
|
||||||
|
|
||||||
# cmd/dist default destination
|
# cmd/dist default destination
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#!/bin/sh -e
|
#!/bin/sh -e
|
||||||
|
|
||||||
TOOLCHAIN_VERSION="$(go version)"
|
HAKUREI_DIST_MAKE='' exec "$(dirname -- "$0")/cmd/dist/dist.sh"
|
||||||
cd "$(dirname -- "$0")/"
|
|
||||||
echo "# Building cmd/dist using ${TOOLCHAIN_VERSION}."
|
|
||||||
go run -v --tags=dist ./cmd/dist
|
|
||||||
|
|||||||
+22
-23
@@ -2,7 +2,7 @@
|
|||||||
package check
|
package check
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -30,6 +30,16 @@ func (e AbsoluteError) Is(target error) bool {
|
|||||||
// Absolute holds a pathname checked to be absolute.
|
// Absolute holds a pathname checked to be absolute.
|
||||||
type Absolute struct{ pathname unique.Handle[string] }
|
type Absolute struct{ pathname unique.Handle[string] }
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ encoding.TextAppender = new(Absolute)
|
||||||
|
_ encoding.TextMarshaler = new(Absolute)
|
||||||
|
_ encoding.TextUnmarshaler = new(Absolute)
|
||||||
|
|
||||||
|
_ encoding.BinaryAppender = new(Absolute)
|
||||||
|
_ encoding.BinaryMarshaler = new(Absolute)
|
||||||
|
_ encoding.BinaryUnmarshaler = new(Absolute)
|
||||||
|
)
|
||||||
|
|
||||||
// ok returns whether [Absolute] is not the zero value.
|
// ok returns whether [Absolute] is not the zero value.
|
||||||
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
|
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
|
||||||
|
|
||||||
@@ -84,13 +94,16 @@ func (a *Absolute) Append(elem ...string) *Absolute {
|
|||||||
// Dir calls [filepath.Dir] with [Absolute] as its argument.
|
// Dir calls [filepath.Dir] with [Absolute] as its argument.
|
||||||
func (a *Absolute) Dir() *Absolute { return unsafeAbs(filepath.Dir(a.String())) }
|
func (a *Absolute) Dir() *Absolute { return unsafeAbs(filepath.Dir(a.String())) }
|
||||||
|
|
||||||
// GobEncode returns the checked pathname.
|
// AppendText appends the checked pathname.
|
||||||
func (a *Absolute) GobEncode() ([]byte, error) {
|
func (a *Absolute) AppendText(data []byte) ([]byte, error) {
|
||||||
return []byte(a.String()), nil
|
return append(data, a.String()...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GobDecode stores data if it represents an absolute pathname.
|
// MarshalText returns the checked pathname.
|
||||||
func (a *Absolute) GobDecode(data []byte) error {
|
func (a *Absolute) MarshalText() ([]byte, error) { return a.AppendText(nil) }
|
||||||
|
|
||||||
|
// UnmarshalText stores data if it represents an absolute pathname.
|
||||||
|
func (a *Absolute) UnmarshalText(data []byte) error {
|
||||||
pathname := string(data)
|
pathname := string(data)
|
||||||
if !filepath.IsAbs(pathname) {
|
if !filepath.IsAbs(pathname) {
|
||||||
return AbsoluteError(pathname)
|
return AbsoluteError(pathname)
|
||||||
@@ -99,23 +112,9 @@ func (a *Absolute) GobDecode(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON returns a JSON representation of the checked pathname.
|
func (a *Absolute) AppendBinary(data []byte) ([]byte, error) { return a.AppendText(data) }
|
||||||
func (a *Absolute) MarshalJSON() ([]byte, error) {
|
func (a *Absolute) MarshalBinary() ([]byte, error) { return a.MarshalText() }
|
||||||
return json.Marshal(a.String())
|
func (a *Absolute) UnmarshalBinary(data []byte) error { return a.UnmarshalText(data) }
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON stores data if it represents an absolute pathname.
|
|
||||||
func (a *Absolute) UnmarshalJSON(data []byte) error {
|
|
||||||
var pathname string
|
|
||||||
if err := json.Unmarshal(data, &pathname); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !filepath.IsAbs(pathname) {
|
|
||||||
return AbsoluteError(pathname)
|
|
||||||
}
|
|
||||||
a.pathname = unique.Make(pathname)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortAbs calls [slices.SortFunc] for a slice of [Absolute].
|
// SortAbs calls [slices.SortFunc] for a slice of [Absolute].
|
||||||
func SortAbs(x []*Absolute) {
|
func SortAbs(x []*Absolute) {
|
||||||
|
|||||||
+6
-15
@@ -170,20 +170,20 @@ func TestCodecAbsolute(t *testing.T) {
|
|||||||
|
|
||||||
{"good", MustAbs("/etc"),
|
{"good", MustAbs("/etc"),
|
||||||
nil,
|
nil,
|
||||||
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\b\xff\x80\x00\x04/etc",
|
"\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\b\xff\x80\x00\x04/etc",
|
||||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x04/etc\x01\xfc\xc0\xed\x00\x00\x00",
|
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x04/etc\x01\xfc\xc0\xed\x00\x00\x00",
|
||||||
|
|
||||||
`"/etc"`, `{"val":"/etc","magic":3236757504}`},
|
`"/etc"`, `{"val":"/etc","magic":3236757504}`},
|
||||||
{"not absolute", nil,
|
{"not absolute", nil,
|
||||||
AbsoluteError("etc"),
|
AbsoluteError("etc"),
|
||||||
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc",
|
"\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc",
|
||||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00",
|
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00",
|
||||||
|
|
||||||
`"etc"`, `{"val":"etc","magic":3236757504}`},
|
`"etc"`, `{"val":"etc","magic":3236757504}`},
|
||||||
{"zero", nil,
|
{"zero", nil,
|
||||||
new(AbsoluteError),
|
new(AbsoluteError),
|
||||||
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x04\xff\x80\x00\x00",
|
"\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x04\xff\x80\x00\x00",
|
||||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\f\xff\x84\x01\x00\x01\xfb\x01\x81\xda\x00\x00\x00",
|
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\f\xff\x84\x01\x00\x01\xfb\x01\x81\xda\x00\x00\x00",
|
||||||
`""`, `{"val":"","magic":3236757504}`},
|
`""`, `{"val":"","magic":3236757504}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,15 +347,6 @@ func TestCodecAbsolute(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("json passthrough", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
wantErr := "invalid character ':' looking for beginning of value"
|
|
||||||
if err := new(Absolute).UnmarshalJSON([]byte(":3")); err == nil || err.Error() != wantErr {
|
|
||||||
t.Errorf("UnmarshalJSON: error = %v, want %s", err, wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAbsoluteWrap(t *testing.T) {
|
func TestAbsoluteWrap(t *testing.T) {
|
||||||
|
|||||||
@@ -4,15 +4,23 @@ import "strings"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// SpecialOverlayEscape is the escape string for overlay mount options.
|
// SpecialOverlayEscape is the escape string for overlay mount options.
|
||||||
|
//
|
||||||
|
// Deprecated: This is no longer used and will be removed in 0.5.
|
||||||
SpecialOverlayEscape = `\`
|
SpecialOverlayEscape = `\`
|
||||||
// SpecialOverlayOption is the separator string between overlay mount options.
|
// SpecialOverlayOption is the separator string between overlay mount options.
|
||||||
|
//
|
||||||
|
// Deprecated: This is no longer used and will be removed in 0.5.
|
||||||
SpecialOverlayOption = ","
|
SpecialOverlayOption = ","
|
||||||
// SpecialOverlayPath is the separator string between overlay paths.
|
// SpecialOverlayPath is the separator string between overlay paths.
|
||||||
|
//
|
||||||
|
// Deprecated: This is no longer used and will be removed in 0.5.
|
||||||
SpecialOverlayPath = ":"
|
SpecialOverlayPath = ":"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EscapeOverlayDataSegment escapes a string for formatting into the data
|
// EscapeOverlayDataSegment escapes a string for formatting into the data
|
||||||
// argument of an overlay mount system call.
|
// argument of an overlay mount system call.
|
||||||
|
//
|
||||||
|
// Deprecated: This is no longer used and will be removed in 0.5.
|
||||||
func EscapeOverlayDataSegment(s string) string {
|
func EscapeOverlayDataSegment(s string) string {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
v0.4.3
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
|
||||||
|
TOOLCHAIN_VERSION="$(go version)"
|
||||||
|
cd "$(dirname -- "$0")/../.."
|
||||||
|
echo "Building cmd/dist using ${TOOLCHAIN_VERSION}."
|
||||||
|
FLAGS=''
|
||||||
|
if test -n "$VERBOSE"; then
|
||||||
|
FLAGS="$FLAGS -v"
|
||||||
|
fi
|
||||||
|
go run $FLAGS --tags=dist ./cmd/dist
|
||||||
Vendored
+32
-15
@@ -18,8 +18,13 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate sh -c "git describe --tags > VERSION"
|
||||||
|
//go:embed VERSION
|
||||||
|
var version string
|
||||||
|
|
||||||
// getenv looks up an environment variable, and returns fallback if it is unset.
|
// getenv looks up an environment variable, and returns fallback if it is unset.
|
||||||
func getenv(key, fallback string) string {
|
func getenv(key, fallback string) string {
|
||||||
if v, ok := os.LookupEnv(key); ok {
|
if v, ok := os.LookupEnv(key); ok {
|
||||||
@@ -42,14 +47,19 @@ func mustRun(ctx context.Context, name string, arg ...string) {
|
|||||||
var comp []byte
|
var comp []byte
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println()
|
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("# ")
|
log.SetPrefix("")
|
||||||
|
|
||||||
version := getenv("HAKUREI_VERSION", "untagged")
|
verbose := os.Getenv("VERBOSE") != ""
|
||||||
|
runTests := os.Getenv("HAKUREI_DIST_MAKE") == ""
|
||||||
|
version = getenv("HAKUREI_VERSION", strings.TrimSpace(version))
|
||||||
prefix := getenv("PREFIX", "/usr")
|
prefix := getenv("PREFIX", "/usr")
|
||||||
destdir := getenv("DESTDIR", "dist")
|
destdir := getenv("DESTDIR", "dist")
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
log.Println()
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(destdir, 0755); err != nil {
|
if err := os.MkdirAll(destdir, 0755); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -76,12 +86,17 @@ func main() {
|
|||||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
log.Println("Building hakurei.")
|
verboseFlag := "-v"
|
||||||
|
if !verbose {
|
||||||
|
verboseFlag = "-buildvcs=false"
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Building hakurei for %s/%s.", runtime.GOOS, runtime.GOARCH)
|
||||||
mustRun(ctx, "go", "generate", "./...")
|
mustRun(ctx, "go", "generate", "./...")
|
||||||
mustRun(
|
mustRun(
|
||||||
ctx, "go", "build",
|
ctx, "go", "build",
|
||||||
"-trimpath",
|
"-trimpath",
|
||||||
"-v", "-o", s,
|
verboseFlag, "-o", s,
|
||||||
"-ldflags=-s -w "+
|
"-ldflags=-s -w "+
|
||||||
"-buildid= -linkmode external -extldflags=-static "+
|
"-buildid= -linkmode external -extldflags=-static "+
|
||||||
"-X hakurei.app/internal/info.buildVersion="+version+" "+
|
"-X hakurei.app/internal/info.buildVersion="+version+" "+
|
||||||
@@ -90,17 +105,19 @@ func main() {
|
|||||||
"-X main.hakureiPath="+prefix+"/bin/hakurei",
|
"-X main.hakureiPath="+prefix+"/bin/hakurei",
|
||||||
"./...",
|
"./...",
|
||||||
)
|
)
|
||||||
fmt.Println()
|
log.Println()
|
||||||
|
|
||||||
log.Println("Testing Hakurei.")
|
if runTests {
|
||||||
mustRun(
|
log.Println("##### Testing Hakurei.")
|
||||||
ctx, "go", "test",
|
mustRun(
|
||||||
"-ldflags=-buildid= -linkmode external -extldflags=-static",
|
ctx, "go", "test",
|
||||||
"./...",
|
"-ldflags=-buildid= -linkmode external -extldflags=-static",
|
||||||
)
|
"./...",
|
||||||
fmt.Println()
|
)
|
||||||
|
log.Println()
|
||||||
|
}
|
||||||
|
|
||||||
log.Println("Creating distribution.")
|
log.Println("##### Creating distribution.")
|
||||||
const suffix = ".tar.gz"
|
const suffix = ".tar.gz"
|
||||||
distName := "hakurei-" + version + "-" + runtime.GOARCH
|
distName := "hakurei-" + version + "-" + runtime.GOARCH
|
||||||
var f *os.File
|
var f *os.File
|
||||||
@@ -121,7 +138,7 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
h := sha512.New()
|
h := sha512.New()
|
||||||
gw := gzip.NewWriter(io.MultiWriter(f, h))
|
gw, _ := gzip.NewWriterLevel(io.MultiWriter(f, h), gzip.BestCompression)
|
||||||
tw := tar.NewWriter(gw)
|
tw := tar.NewWriter(gw)
|
||||||
|
|
||||||
mustWriteHeader := func(name string, size int64, mode os.FileMode) {
|
mustWriteHeader := func(name string, size int64, mode os.FileMode) {
|
||||||
|
|||||||
+13
-6
@@ -38,8 +38,9 @@ var errSuccess = errors.New("success")
|
|||||||
|
|
||||||
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
||||||
var (
|
var (
|
||||||
flagVerbose bool
|
flagVerbose bool
|
||||||
flagJSON bool
|
flagInsecure bool
|
||||||
|
flagJSON bool
|
||||||
)
|
)
|
||||||
c := command.New(out, log.Printf, "hakurei", func([]string) error {
|
c := command.New(out, log.Printf, "hakurei", func([]string) error {
|
||||||
msg.SwapVerbose(flagVerbose)
|
msg.SwapVerbose(flagVerbose)
|
||||||
@@ -57,6 +58,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
return nil
|
return nil
|
||||||
}).
|
}).
|
||||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
||||||
|
Flag(&flagInsecure, "insecure", command.BoolFlag(false), "Allow use of insecure compatibility options").
|
||||||
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
||||||
|
|
||||||
c.Command("shim", command.UsageInternal, func([]string) error { outcome.Shim(msg); return errSuccess })
|
c.Command("shim", command.UsageInternal, func([]string) error { outcome.Shim(msg); return errSuccess })
|
||||||
@@ -75,7 +77,12 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
config.Container.Args = append(config.Container.Args, args[1:]...)
|
config.Container.Args = append(config.Container.Args, args[1:]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
outcome.Main(ctx, msg, config, flagIdentifierFile)
|
var flags int
|
||||||
|
if flagInsecure {
|
||||||
|
flags |= hst.VAllowInsecure
|
||||||
|
}
|
||||||
|
|
||||||
|
outcome.Main(ctx, msg, config, flags, flagIdentifierFile)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}).
|
}).
|
||||||
Flag(&flagIdentifierFile, "identifier-fd", command.IntFlag(-1),
|
Flag(&flagIdentifierFile, "identifier-fd", command.IntFlag(-1),
|
||||||
@@ -145,7 +152,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var et hst.Enablement
|
var et hst.Enablements
|
||||||
if flagWayland {
|
if flagWayland {
|
||||||
et |= hst.EWayland
|
et |= hst.EWayland
|
||||||
}
|
}
|
||||||
@@ -163,7 +170,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
ID: flagID,
|
ID: flagID,
|
||||||
Identity: flagIdentity,
|
Identity: flagIdentity,
|
||||||
Groups: flagGroups,
|
Groups: flagGroups,
|
||||||
Enablements: hst.NewEnablements(et),
|
Enablements: &et,
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
@@ -282,7 +289,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outcome.Main(ctx, msg, &config, -1)
|
outcome.Main(ctx, msg, &config, 0, -1)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}).
|
}).
|
||||||
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestHelp(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"main", []string{}, `
|
"main", []string{}, `
|
||||||
Usage: hakurei [-h | --help] [-v] [--json] COMMAND [OPTIONS]
|
Usage: hakurei [-h | --help] [-v] [--insecure] [--json] COMMAND [OPTIONS]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
run Load and start container from configuration file
|
run Load and start container from configuration file
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func printShowInstance(
|
|||||||
t := newPrinter(output)
|
t := newPrinter(output)
|
||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
if err := config.Validate(); err != nil {
|
if err := config.Validate(hst.VAllowInsecure); err != nil {
|
||||||
valid = false
|
valid = false
|
||||||
if m, ok := message.GetMessage(err); ok {
|
if m, ok := message.GetMessage(err); ok {
|
||||||
mustPrint(output, "Error: "+m+"!\n\n")
|
mustPrint(output, "Error: "+m+"!\n\n")
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ var (
|
|||||||
PID: 0xbeef,
|
PID: 0xbeef,
|
||||||
ShimPID: 0xcafe,
|
ShimPID: 0xcafe,
|
||||||
Config: &hst.Config{
|
Config: &hst.Config{
|
||||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EPipeWire),
|
Enablements: new(hst.EWayland | hst.EPipeWire),
|
||||||
Identity: 1,
|
Identity: 1,
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Shell: check.MustAbs("/bin/sh"),
|
Shell: check.MustAbs("/bin/sh"),
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cache refers to an instance of [pkg.Cache] that might be open.
|
||||||
|
type cache struct {
|
||||||
|
ctx context.Context
|
||||||
|
msg message.Msg
|
||||||
|
|
||||||
|
// Should generally not be used directly.
|
||||||
|
c *pkg.Cache
|
||||||
|
|
||||||
|
cures, jobs int
|
||||||
|
// Primarily to work around missing landlock LSM.
|
||||||
|
hostAbstract bool
|
||||||
|
// Set SCHED_IDLE.
|
||||||
|
idle bool
|
||||||
|
// Unset [pkg.CSuppressInit].
|
||||||
|
verboseInit bool
|
||||||
|
// Loaded artifact of [rosa.QEMU].
|
||||||
|
qemu pkg.Artifact
|
||||||
|
|
||||||
|
base string
|
||||||
|
}
|
||||||
|
|
||||||
|
// open opens the underlying [pkg.Cache].
|
||||||
|
func (cache *cache) open() (err error) {
|
||||||
|
if cache.c != nil {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var base *check.Absolute
|
||||||
|
if cache.base, err = filepath.Abs(cache.base); err != nil {
|
||||||
|
return
|
||||||
|
} else if base, err = check.NewAbs(cache.base); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags int
|
||||||
|
if cache.idle {
|
||||||
|
flags |= pkg.CSchedIdle
|
||||||
|
}
|
||||||
|
if cache.hostAbstract {
|
||||||
|
flags |= pkg.CHostAbstract
|
||||||
|
}
|
||||||
|
if !cache.verboseInit {
|
||||||
|
flags |= pkg.CSuppressInit
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-cache.ctx.Done():
|
||||||
|
if testing.Testing() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Exit(2)
|
||||||
|
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cache.msg.Verbosef("opening cache at %s", base)
|
||||||
|
cache.c, err = pkg.Open(
|
||||||
|
cache.ctx,
|
||||||
|
cache.msg,
|
||||||
|
flags,
|
||||||
|
cache.cures,
|
||||||
|
cache.jobs,
|
||||||
|
base,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done <- struct{}{}
|
||||||
|
|
||||||
|
if cache.qemu != nil {
|
||||||
|
var pathname *check.Absolute
|
||||||
|
pathname, _, err = cache.c.Cure(cache.qemu)
|
||||||
|
if err != nil {
|
||||||
|
cache.c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.RegisterArch("riscv64", container.BinfmtEntry{
|
||||||
|
Offset: 0,
|
||||||
|
Magic: "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00",
|
||||||
|
Mask: "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
|
||||||
|
Interpreter: pathname.Append(
|
||||||
|
"system/bin",
|
||||||
|
"qemu-riscv64",
|
||||||
|
),
|
||||||
|
})
|
||||||
|
pkg.RegisterArch("arm64", container.BinfmtEntry{
|
||||||
|
Offset: 0,
|
||||||
|
Magic: "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00",
|
||||||
|
Mask: "\xff\xff\xff\xff\xff\xff\xff\xfc\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
|
||||||
|
Interpreter: pathname.Append(
|
||||||
|
"system/bin",
|
||||||
|
"qemu-aarch64",
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the underlying [pkg.Cache] if it is open.
|
||||||
|
func (cache *cache) Close() {
|
||||||
|
if cache.c != nil {
|
||||||
|
cache.c.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do calls f on the underlying cache and returns its error value.
|
||||||
|
func (cache *cache) Do(f func(cache *pkg.Cache) error) error {
|
||||||
|
if cache.c == nil {
|
||||||
|
if err := cache.open(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f(cache.c)
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCache(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cm := cache{
|
||||||
|
ctx: t.Context(),
|
||||||
|
msg: message.New(log.New(os.Stderr, "check: ", 0)),
|
||||||
|
base: t.TempDir(),
|
||||||
|
|
||||||
|
hostAbstract: true, idle: true,
|
||||||
|
}
|
||||||
|
defer cm.Close()
|
||||||
|
cm.Close()
|
||||||
|
|
||||||
|
if err := cm.open(); err != nil {
|
||||||
|
t.Fatalf("open: error = %v", err)
|
||||||
|
}
|
||||||
|
if err := cm.open(); err != os.ErrInvalid {
|
||||||
|
t.Errorf("(duplicate) open: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cm.Do(func(cache *pkg.Cache) error {
|
||||||
|
return cache.Scrub(0)
|
||||||
|
}); err != nil {
|
||||||
|
t.Errorf("Scrub: error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
"unique"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// daemonTimeout is the maximum amount of time cureFromIR will wait on I/O.
|
||||||
|
const daemonTimeout = 30 * time.Second
|
||||||
|
|
||||||
|
// daemonDeadline returns the deadline corresponding to daemonTimeout, or the
|
||||||
|
// zero value when running in a test.
|
||||||
|
func daemonDeadline() time.Time {
|
||||||
|
if testing.Testing() {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return time.Now().Add(daemonTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// remoteNoReply notifies that the client will not receive a cure reply.
|
||||||
|
remoteNoReply = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// cureFromIR services an IR curing request.
|
||||||
|
func cureFromIR(
|
||||||
|
cache *pkg.Cache,
|
||||||
|
conn net.Conn,
|
||||||
|
flags uint64,
|
||||||
|
) (pkg.Artifact, error) {
|
||||||
|
a, decodeErr := cache.NewDecoder(conn).Decode()
|
||||||
|
if decodeErr != nil {
|
||||||
|
_, err := conn.Write([]byte("\x00" + decodeErr.Error()))
|
||||||
|
return nil, errors.Join(decodeErr, err, conn.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
pathname, _, cureErr := cache.Cure(a)
|
||||||
|
if flags&remoteNoReply != 0 {
|
||||||
|
return a, errors.Join(cureErr, conn.Close())
|
||||||
|
}
|
||||||
|
if err := conn.SetWriteDeadline(daemonDeadline()); err != nil {
|
||||||
|
return a, errors.Join(cureErr, err, conn.Close())
|
||||||
|
}
|
||||||
|
if cureErr != nil {
|
||||||
|
_, err := conn.Write([]byte("\x00" + cureErr.Error()))
|
||||||
|
return a, errors.Join(cureErr, err, conn.Close())
|
||||||
|
}
|
||||||
|
_, err := conn.Write([]byte(pathname.String()))
|
||||||
|
if testing.Testing() && errors.Is(err, io.ErrClosedPipe) {
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
return a, errors.Join(err, conn.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// specialCancel is a message consisting of a single identifier referring
|
||||||
|
// to a curing artifact to be cancelled.
|
||||||
|
specialCancel = iota
|
||||||
|
// specialAbort requests for all pending cures to be aborted. It has no
|
||||||
|
// message body.
|
||||||
|
specialAbort
|
||||||
|
|
||||||
|
// remoteSpecial denotes a special message with custom layout.
|
||||||
|
remoteSpecial = math.MaxUint64
|
||||||
|
)
|
||||||
|
|
||||||
|
// writeSpecialHeader writes the header of a remoteSpecial message.
|
||||||
|
func writeSpecialHeader(conn net.Conn, kind uint64) error {
|
||||||
|
var sh [16]byte
|
||||||
|
binary.LittleEndian.PutUint64(sh[:], remoteSpecial)
|
||||||
|
binary.LittleEndian.PutUint64(sh[8:], kind)
|
||||||
|
if n, err := conn.Write(sh[:]); err != nil {
|
||||||
|
return err
|
||||||
|
} else if n != len(sh) {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancelIdent reads an identifier from conn and cancels the corresponding cure.
|
||||||
|
func cancelIdent(
|
||||||
|
cache *pkg.Cache,
|
||||||
|
conn net.Conn,
|
||||||
|
) (*pkg.ID, bool, error) {
|
||||||
|
var ident pkg.ID
|
||||||
|
if _, err := io.ReadFull(conn, ident[:]); err != nil {
|
||||||
|
return nil, false, errors.Join(err, conn.Close())
|
||||||
|
}
|
||||||
|
ok := cache.Cancel(unique.Make(ident))
|
||||||
|
return &ident, ok, conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// serve services connections from a [net.UnixListener].
|
||||||
|
func serve(
|
||||||
|
ctx context.Context,
|
||||||
|
log *log.Logger,
|
||||||
|
cm *cache,
|
||||||
|
ul *net.UnixListener,
|
||||||
|
) error {
|
||||||
|
ul.SetUnlinkOnClose(true)
|
||||||
|
if cm.c == nil {
|
||||||
|
if err := cm.open(); err != nil {
|
||||||
|
return errors.Join(err, ul.Close())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
defer wg.Wait()
|
||||||
|
|
||||||
|
wg.Go(func() {
|
||||||
|
for {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := ul.AcceptUnix()
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wg.Go(func() {
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
_ = conn.SetDeadline(time.Now())
|
||||||
|
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _err := conn.SetReadDeadline(daemonDeadline()); _err != nil {
|
||||||
|
log.Println(_err)
|
||||||
|
if _err = conn.Close(); _err != nil {
|
||||||
|
log.Println(_err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var word [8]byte
|
||||||
|
if _, _err := io.ReadFull(conn, word[:]); _err != nil {
|
||||||
|
log.Println(_err)
|
||||||
|
if _err = conn.Close(); _err != nil {
|
||||||
|
log.Println(_err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
flags := binary.LittleEndian.Uint64(word[:])
|
||||||
|
|
||||||
|
if flags == remoteSpecial {
|
||||||
|
if _, _err := io.ReadFull(conn, word[:]); _err != nil {
|
||||||
|
log.Println(_err)
|
||||||
|
if _err = conn.Close(); _err != nil {
|
||||||
|
log.Println(_err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch special := binary.LittleEndian.Uint64(word[:]); special {
|
||||||
|
default:
|
||||||
|
log.Printf("invalid special %d", special)
|
||||||
|
|
||||||
|
case specialCancel:
|
||||||
|
if id, ok, _err := cancelIdent(cm.c, conn); _err != nil {
|
||||||
|
log.Println(_err)
|
||||||
|
} else if !ok {
|
||||||
|
log.Println(
|
||||||
|
"attempting to cancel invalid artifact",
|
||||||
|
pkg.Encode(*id),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log.Println(
|
||||||
|
"cancelled artifact",
|
||||||
|
pkg.Encode(*id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case specialAbort:
|
||||||
|
log.Println("aborting all pending cures")
|
||||||
|
cm.c.Abort()
|
||||||
|
if _err := conn.Close(); _err != nil {
|
||||||
|
log.Println(_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if a, _err := cureFromIR(cm.c, conn, flags); _err != nil {
|
||||||
|
log.Println(_err)
|
||||||
|
} else {
|
||||||
|
log.Printf(
|
||||||
|
"fulfilled artifact %s",
|
||||||
|
pkg.Encode(cm.c.Ident(a).Value()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
if err := ul.SetDeadline(time.Now()); err != nil {
|
||||||
|
return errors.Join(err, ul.Close())
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return ul.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// dial wraps [net.DialUnix] with a context.
|
||||||
|
func dial(ctx context.Context, addr *net.UnixAddr) (
|
||||||
|
done chan<- struct{},
|
||||||
|
conn *net.UnixConn,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
|
conn, err = net.DialUnix("unix", nil, addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d := make(chan struct{})
|
||||||
|
done = d
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
_ = conn.SetDeadline(time.Now())
|
||||||
|
|
||||||
|
case <-d:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// cureRemote cures a [pkg.Artifact] on a daemon.
|
||||||
|
func cureRemote(
|
||||||
|
ctx context.Context,
|
||||||
|
addr *net.UnixAddr,
|
||||||
|
a pkg.Artifact,
|
||||||
|
flags uint64,
|
||||||
|
) (*check.Absolute, error) {
|
||||||
|
if flags == remoteSpecial {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
done, conn, err := dial(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
if n, flagErr := conn.Write(binary.LittleEndian.AppendUint64(nil, flags)); flagErr != nil {
|
||||||
|
return nil, errors.Join(flagErr, conn.Close())
|
||||||
|
} else if n != 8 {
|
||||||
|
return nil, errors.Join(io.ErrShortWrite, conn.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = pkg.NewIR().EncodeAll(conn, a); err != nil {
|
||||||
|
return nil, errors.Join(err, conn.Close())
|
||||||
|
} else if err = conn.CloseWrite(); err != nil {
|
||||||
|
return nil, errors.Join(err, conn.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags&remoteNoReply != 0 {
|
||||||
|
return nil, conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, recvErr := io.ReadAll(conn)
|
||||||
|
if err = errors.Join(recvErr, conn.Close()); err != nil {
|
||||||
|
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||||
|
if cancelErr := ctx.Err(); cancelErr != nil {
|
||||||
|
err = cancelErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(payload) > 0 && payload[0] == 0 {
|
||||||
|
return nil, errors.New(string(payload[1:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *check.Absolute
|
||||||
|
p, err = check.NewAbs(string(payload))
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancelRemote cancels a [pkg.Artifact] curing on a daemon.
|
||||||
|
func cancelRemote(
|
||||||
|
ctx context.Context,
|
||||||
|
addr *net.UnixAddr,
|
||||||
|
a pkg.Artifact,
|
||||||
|
wait bool,
|
||||||
|
) error {
|
||||||
|
done, conn, err := dial(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
if err = writeSpecialHeader(conn, specialCancel); err != nil {
|
||||||
|
return errors.Join(err, conn.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
id := pkg.NewIR().Ident(a).Value()
|
||||||
|
if n, err = conn.Write(id[:]); err != nil {
|
||||||
|
return errors.Join(err, conn.Close())
|
||||||
|
} else if n != len(id) {
|
||||||
|
return errors.Join(io.ErrShortWrite, conn.Close())
|
||||||
|
}
|
||||||
|
if wait {
|
||||||
|
if _, err = conn.Read(make([]byte, 1)); err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Join(err, conn.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
// abortRemote aborts all [pkg.Artifact] curing on a daemon.
|
||||||
|
func abortRemote(
|
||||||
|
ctx context.Context,
|
||||||
|
addr *net.UnixAddr,
|
||||||
|
wait bool,
|
||||||
|
) error {
|
||||||
|
done, conn, err := dial(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
err = writeSpecialHeader(conn, specialAbort)
|
||||||
|
if wait && err == nil {
|
||||||
|
if _, err = conn.Read(make([]byte, 1)); err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Join(err, conn.Close())
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoReply(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if !daemonDeadline().IsZero() {
|
||||||
|
t.Fatal("daemonDeadline did not return the zero value")
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := pkg.Open(
|
||||||
|
t.Context(),
|
||||||
|
message.New(log.New(os.Stderr, "cir: ", 0)),
|
||||||
|
0, 0, 0,
|
||||||
|
check.MustAbs(t.TempDir()),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Open: error = %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
client, server := net.Pipe()
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
go func() {
|
||||||
|
<-t.Context().Done()
|
||||||
|
if _err := client.SetDeadline(time.Now()); _err != nil && !errors.Is(_err, io.ErrClosedPipe) {
|
||||||
|
panic(_err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _err := c.EncodeAll(
|
||||||
|
client,
|
||||||
|
pkg.NewFile("check", []byte{0}),
|
||||||
|
); _err != nil {
|
||||||
|
panic(_err)
|
||||||
|
} else if _err = client.Close(); _err != nil {
|
||||||
|
panic(_err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
a, cureErr := cureFromIR(c, server, remoteNoReply)
|
||||||
|
if cureErr != nil {
|
||||||
|
t.Fatalf("cureFromIR: error = %v", cureErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-done
|
||||||
|
wantIdent := pkg.MustDecode("fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG")
|
||||||
|
if gotIdent := c.Ident(a).Value(); gotIdent != wantIdent {
|
||||||
|
t.Errorf(
|
||||||
|
"cureFromIR: %s, want %s",
|
||||||
|
pkg.Encode(gotIdent), pkg.Encode(wantIdent),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDaemon(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
logger := log.New(&buf, "daemon: ", 0)
|
||||||
|
|
||||||
|
addr := net.UnixAddr{
|
||||||
|
Name: filepath.Join(t.TempDir(), "daemon"),
|
||||||
|
Net: "unix",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cm := cache{
|
||||||
|
ctx: ctx,
|
||||||
|
msg: message.New(logger),
|
||||||
|
base: t.TempDir(),
|
||||||
|
}
|
||||||
|
defer cm.Close()
|
||||||
|
|
||||||
|
ul, err := net.ListenUnix("unix", &addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListenUnix: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
if _err := serve(ctx, logger, &cm, ul); _err != nil {
|
||||||
|
panic(_err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = cancelRemote(ctx, &addr, pkg.NewFile("nonexistent", nil), true); err != nil {
|
||||||
|
t.Fatalf("cancelRemote: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = abortRemote(ctx, &addr, true); err != nil {
|
||||||
|
t.Fatalf("abortRemote: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep this last for synchronisation
|
||||||
|
var p *check.Absolute
|
||||||
|
p, err = cureRemote(ctx, &addr, pkg.NewFile("check", []byte{0}), 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cureRemote: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
<-done
|
||||||
|
|
||||||
|
const want = "fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG"
|
||||||
|
if got := filepath.Base(p.String()); got != want {
|
||||||
|
t.Errorf("cureRemote: %s, want %s", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantLog := []string{
|
||||||
|
"",
|
||||||
|
"daemon: aborting all pending cures",
|
||||||
|
"daemon: attempting to cancel invalid artifact kQm9fmnCmXST1-MMmxzcau2oKZCXXrlZydo4PkeV5hO_2PKfeC8t98hrbV_ZZx_j",
|
||||||
|
"daemon: fulfilled artifact fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG",
|
||||||
|
}
|
||||||
|
gotLog := strings.Split(buf.String(), "\n")
|
||||||
|
slices.Sort(gotLog)
|
||||||
|
if !slices.Equal(gotLog, wantLog) {
|
||||||
|
t.Errorf(
|
||||||
|
"serve: logged\n%s\nwant\n%s",
|
||||||
|
strings.Join(gotLog, "\n"), strings.Join(wantLog, "\n"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unique"
|
||||||
|
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/internal/rosa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// commandInfo implements the info subcommand.
|
||||||
|
func commandInfo(
|
||||||
|
cm *cache,
|
||||||
|
args []string,
|
||||||
|
w io.Writer,
|
||||||
|
writeStatus bool,
|
||||||
|
r *rosa.Report,
|
||||||
|
) (err error) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return errors.New("info requires at least 1 argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
// recovered by HandleAccess
|
||||||
|
mustPrintln := func(a ...any) {
|
||||||
|
if _, _err := fmt.Fprintln(w, a...); _err != nil {
|
||||||
|
panic(_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mustPrint := func(a ...any) {
|
||||||
|
if _, _err := fmt.Fprint(w, a...); _err != nil {
|
||||||
|
panic(_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := rosa.Native().Std()
|
||||||
|
for i, name := range args {
|
||||||
|
handle := rosa.ArtifactH(unique.Make(name))
|
||||||
|
if meta, a := t.Load(handle); meta == nil {
|
||||||
|
return fmt.Errorf("unknown artifact %q", name)
|
||||||
|
} else {
|
||||||
|
var suffix string
|
||||||
|
|
||||||
|
if meta.Version != rosa.Unversioned {
|
||||||
|
suffix += "-" + meta.Version
|
||||||
|
}
|
||||||
|
mustPrintln("name : " + name + suffix)
|
||||||
|
|
||||||
|
mustPrintln("description : " + meta.Description)
|
||||||
|
if meta.Website != "" {
|
||||||
|
mustPrintln("website : " +
|
||||||
|
strings.TrimSuffix(meta.Website, "/"))
|
||||||
|
}
|
||||||
|
if len(meta.Dependencies) > 0 {
|
||||||
|
mustPrint("depends on :")
|
||||||
|
for _, d := range meta.Dependencies {
|
||||||
|
_meta, _ := rosa.Native().Std().MustLoad(d)
|
||||||
|
s := _meta.Name
|
||||||
|
if _meta.Version != rosa.Unversioned {
|
||||||
|
s += "-" + _meta.Version
|
||||||
|
}
|
||||||
|
mustPrint(" " + s)
|
||||||
|
}
|
||||||
|
mustPrintln()
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusPrefix = "status : "
|
||||||
|
if writeStatus {
|
||||||
|
if r == nil {
|
||||||
|
var f io.ReadSeekCloser
|
||||||
|
err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
|
f, err = cache.OpenStatus(a)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
mustPrintln(
|
||||||
|
statusPrefix + "not yet cured",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mustPrint(statusPrefix)
|
||||||
|
_, err = io.Copy(w, f)
|
||||||
|
if err = errors.Join(err, f.Close()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
|
status, n := r.ArtifactOf(cache.Ident(a))
|
||||||
|
if status == nil {
|
||||||
|
mustPrintln(
|
||||||
|
statusPrefix + "not in report",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
mustPrintln("size :", n)
|
||||||
|
mustPrint(statusPrefix)
|
||||||
|
if _, err = w.Write(status); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != len(args)-1 {
|
||||||
|
mustPrintln()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"unique"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/internal/rosa"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInfo(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
_t := rosa.Native().Std()
|
||||||
|
qemuMeta, _ := _t.Load(rosa.H("qemu"))
|
||||||
|
glibMeta, _ := _t.Load(rosa.H("glib"))
|
||||||
|
zlibMeta, zlib := _t.Load(rosa.H("zlib"))
|
||||||
|
zstdMeta, _ := _t.Load(rosa.H("zstd"))
|
||||||
|
hakureiMeta, _ := _t.Load(rosa.H("hakurei"))
|
||||||
|
hakureiDistMeta, _ := _t.Load(rosa.H("hakurei-dist"))
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
status map[string]string
|
||||||
|
report string
|
||||||
|
want string
|
||||||
|
wantErr any
|
||||||
|
}{
|
||||||
|
{"qemu", []string{"qemu"}, nil, "", `
|
||||||
|
name : qemu-` + qemuMeta.Version + `
|
||||||
|
description : a generic and open source machine emulator and virtualizer
|
||||||
|
website : https://www.qemu.org
|
||||||
|
depends on : glib-` + glibMeta.Version + ` zstd-` + zstdMeta.Version + `
|
||||||
|
`, nil},
|
||||||
|
|
||||||
|
{"multi", []string{"hakurei", "hakurei-dist"}, nil, "", `
|
||||||
|
name : hakurei-` + hakureiMeta.Version + `
|
||||||
|
description : low-level userspace tooling for Rosa OS
|
||||||
|
website : https://hakurei.app
|
||||||
|
|
||||||
|
name : hakurei-dist-` + hakureiDistMeta.Version + `
|
||||||
|
description : low-level userspace tooling for Rosa OS (distribution tarball)
|
||||||
|
website : https://hakurei.app
|
||||||
|
`, nil},
|
||||||
|
|
||||||
|
{"nonexistent", []string{"zlib", "\x00"}, nil, "", `
|
||||||
|
name : zlib-` + zlibMeta.Version + `
|
||||||
|
description : lossless data-compression library
|
||||||
|
website : https://zlib.net
|
||||||
|
|
||||||
|
`, fmt.Errorf("unknown artifact %q", "\x00")},
|
||||||
|
|
||||||
|
{"status cache", []string{"zlib", "zstd"}, map[string]string{
|
||||||
|
"zstd": "internal/pkg (amd64) on satori\n",
|
||||||
|
"hakurei": "internal/pkg (amd64) on satori\n\n",
|
||||||
|
}, "", `
|
||||||
|
name : zlib-` + zlibMeta.Version + `
|
||||||
|
description : lossless data-compression library
|
||||||
|
website : https://zlib.net
|
||||||
|
status : not yet cured
|
||||||
|
|
||||||
|
name : zstd-` + zstdMeta.Version + `
|
||||||
|
description : a fast compression algorithm
|
||||||
|
website : https://facebook.github.io/zstd
|
||||||
|
status : internal/pkg (amd64) on satori
|
||||||
|
`, nil},
|
||||||
|
|
||||||
|
{"status cache perm", []string{"zlib"}, map[string]string{
|
||||||
|
"zlib": "\x00",
|
||||||
|
}, "", `
|
||||||
|
name : zlib-` + zlibMeta.Version + `
|
||||||
|
description : lossless data-compression library
|
||||||
|
website : https://zlib.net
|
||||||
|
`, func(cm *cache) error {
|
||||||
|
return &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(zlib).Value())),
|
||||||
|
Err: syscall.EACCES,
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"status report", []string{"zlib"}, nil, strings.Repeat("\x00", len(pkg.Checksum{})+8), `
|
||||||
|
name : zlib-` + zlibMeta.Version + `
|
||||||
|
description : lossless data-compression library
|
||||||
|
website : https://zlib.net
|
||||||
|
status : not in report
|
||||||
|
`, nil},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
cm *cache
|
||||||
|
buf strings.Builder
|
||||||
|
r *rosa.Report
|
||||||
|
)
|
||||||
|
|
||||||
|
if tc.status != nil || tc.report != "" {
|
||||||
|
cm = &cache{
|
||||||
|
ctx: context.Background(),
|
||||||
|
msg: message.New(log.New(os.Stderr, "info: ", 0)),
|
||||||
|
base: t.TempDir(),
|
||||||
|
}
|
||||||
|
defer cm.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.report != "" {
|
||||||
|
pathname := filepath.Join(t.TempDir(), "report")
|
||||||
|
err := os.WriteFile(
|
||||||
|
pathname,
|
||||||
|
unsafe.Slice(unsafe.StringData(tc.report), len(tc.report)),
|
||||||
|
0400,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err = rosa.OpenReport(pathname)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err = r.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.status != nil {
|
||||||
|
for name, status := range tc.status {
|
||||||
|
_, a := _t.Load(rosa.ArtifactH(unique.Make(name)))
|
||||||
|
if a == nil {
|
||||||
|
t.Fatalf("invalid name %q", name)
|
||||||
|
}
|
||||||
|
perm := os.FileMode(0400)
|
||||||
|
if status == "\x00" {
|
||||||
|
perm = 0
|
||||||
|
}
|
||||||
|
if err := cm.Do(func(cache *pkg.Cache) error {
|
||||||
|
return os.WriteFile(filepath.Join(
|
||||||
|
cm.base,
|
||||||
|
"status",
|
||||||
|
pkg.Encode(cache.Ident(a).Value()),
|
||||||
|
), unsafe.Slice(unsafe.StringData(status), len(status)), perm)
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("Do: error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var wantErr error
|
||||||
|
switch c := tc.wantErr.(type) {
|
||||||
|
case error:
|
||||||
|
wantErr = c
|
||||||
|
case func(cm *cache) error:
|
||||||
|
wantErr = c(cm)
|
||||||
|
default:
|
||||||
|
if tc.wantErr != nil {
|
||||||
|
t.Fatalf("invalid wantErr %#v", tc.wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := commandInfo(
|
||||||
|
cm,
|
||||||
|
tc.args,
|
||||||
|
&buf,
|
||||||
|
cm != nil,
|
||||||
|
r,
|
||||||
|
); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
t.Fatalf("commandInfo: error = %v, want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := buf.String(); got != strings.TrimPrefix(tc.want, "\n") {
|
||||||
|
t.Errorf("commandInfo:\n%s\nwant\n%s", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package main
|
// Package pkgserver implements the package metadata service backend.
|
||||||
|
package pkgserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -8,6 +10,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"hakurei.app/internal/info"
|
"hakurei.app/internal/info"
|
||||||
"hakurei.app/internal/rosa"
|
"hakurei.app/internal/rosa"
|
||||||
@@ -27,7 +30,7 @@ var (
|
|||||||
// handleInfo writes constant system information.
|
// handleInfo writes constant system information.
|
||||||
func handleInfo(w http.ResponseWriter, _ *http.Request) {
|
func handleInfo(w http.ResponseWriter, _ *http.Request) {
|
||||||
infoPayloadOnce.Do(func() {
|
infoPayloadOnce.Do(func() {
|
||||||
infoPayload.Count = int(rosa.PresetUnexportedStart)
|
infoPayload.Count = len(rosa.Native().Collect())
|
||||||
infoPayload.HakureiVersion = info.Version()
|
infoPayload.HakureiVersion = info.Version()
|
||||||
})
|
})
|
||||||
// TODO(mae): cache entire response if no additional fields are planned
|
// TODO(mae): cache entire response if no additional fields are planned
|
||||||
@@ -88,7 +91,7 @@ func (index *packageIndex) handleGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
||||||
http.Error(
|
http.Error(
|
||||||
w, "index must be an integer between 0 and "+
|
w, "index must be an integer between 0 and "+
|
||||||
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
strconv.Itoa(len(index.sorts[0])-1),
|
||||||
http.StatusBadRequest,
|
http.StatusBadRequest,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -122,7 +125,7 @@ func (index *packageIndex) handleSearch(w http.ResponseWriter, r *http.Request)
|
|||||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
||||||
http.Error(
|
http.Error(
|
||||||
w, "index must be an integer between 0 and "+
|
w, "index must be an integer between 0 and "+
|
||||||
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
strconv.Itoa(len(index.sorts[0])-1),
|
||||||
http.StatusBadRequest,
|
http.StatusBadRequest,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -158,6 +161,29 @@ func (index *packageIndex) registerAPI(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("GET /status/", index.newStatusHandler(true))
|
mux.HandleFunc("GET /status/", index.newStatusHandler(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register arranges for mux to service API requests.
|
||||||
|
func Register(ctx context.Context, mux *http.ServeMux, report *rosa.Report) error {
|
||||||
|
var index packageIndex
|
||||||
|
index.search = make(searchCache)
|
||||||
|
if err := index.populate(report); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
index.search.clean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
index.registerAPI(mux)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// writeAPIPayload sets headers common to API responses and encodes payload as
|
// writeAPIPayload sets headers common to API responses and encodes payload as
|
||||||
// JSON for the response body.
|
// JSON for the response body.
|
||||||
func writeAPIPayload(w http.ResponseWriter, payload any) {
|
func writeAPIPayload(w http.ResponseWriter, payload any) {
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package main
|
package pkgserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"slices"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@ func TestAPIInfo(t *testing.T) {
|
|||||||
checkPayload(t, resp, struct {
|
checkPayload(t, resp, struct {
|
||||||
Count int `json:"count"`
|
Count int `json:"count"`
|
||||||
HakureiVersion string `json:"hakurei_version"`
|
HakureiVersion string `json:"hakurei_version"`
|
||||||
}{int(rosa.PresetUnexportedStart), info.Version()})
|
}{len(rosa.Native().Collect()), info.Version()})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIGet(t *testing.T) {
|
func TestAPIGet(t *testing.T) {
|
||||||
@@ -93,11 +92,12 @@ func TestAPIGet(t *testing.T) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
count := len(rosa.Native().Collect())
|
||||||
t.Run("index", func(t *testing.T) {
|
t.Run("index", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
checkValidate(
|
checkValidate(
|
||||||
t, "limit=1&sort=0&index", 0, int(rosa.PresetUnexportedStart-1),
|
t, "limit=1&sort=0&index", 0, count-1,
|
||||||
"index must be an integer between 0 and "+strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
"index must be an integer between 0 and "+strconv.Itoa(count-1),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -108,76 +108,4 @@ func TestAPIGet(t *testing.T) {
|
|||||||
"sort must be an integer between 0 and "+strconv.Itoa(int(sortOrderEnd)),
|
"sort must be an integer between 0 and "+strconv.Itoa(int(sortOrderEnd)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
checkWithSuffix := func(name, suffix string, want []*metadata) {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
w := newRequest(suffix)
|
|
||||||
resp := w.Result()
|
|
||||||
checkStatus(t, resp, http.StatusOK)
|
|
||||||
checkAPIHeader(t, w.Header())
|
|
||||||
checkPayloadFunc(t, resp, func(got *struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
Values []*metadata `json:"values"`
|
|
||||||
}) bool {
|
|
||||||
return got.Count == len(want) &&
|
|
||||||
slices.EqualFunc(got.Values, want, func(a, b *metadata) bool {
|
|
||||||
return (a.Version == b.Version ||
|
|
||||||
a.Version == rosa.Unversioned ||
|
|
||||||
b.Version == rosa.Unversioned) &&
|
|
||||||
a.HasReport == b.HasReport &&
|
|
||||||
a.Name == b.Name &&
|
|
||||||
a.Description == b.Description &&
|
|
||||||
a.Website == b.Website
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
checkWithSuffix("declarationAscending", "?limit=2&index=0&sort=0", []*metadata{
|
|
||||||
{
|
|
||||||
Metadata: rosa.GetMetadata(0),
|
|
||||||
Version: rosa.Std.Version(0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Metadata: rosa.GetMetadata(1),
|
|
||||||
Version: rosa.Std.Version(1),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
checkWithSuffix("declarationAscending offset", "?limit=3&index=5&sort=0", []*metadata{
|
|
||||||
{
|
|
||||||
Metadata: rosa.GetMetadata(5),
|
|
||||||
Version: rosa.Std.Version(5),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Metadata: rosa.GetMetadata(6),
|
|
||||||
Version: rosa.Std.Version(6),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Metadata: rosa.GetMetadata(7),
|
|
||||||
Version: rosa.Std.Version(7),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
checkWithSuffix("declarationDescending", "?limit=3&index=0&sort=1", []*metadata{
|
|
||||||
{
|
|
||||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 1),
|
|
||||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 2),
|
|
||||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 3),
|
|
||||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 3),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
checkWithSuffix("declarationDescending offset", "?limit=1&index=37&sort=1", []*metadata{
|
|
||||||
{
|
|
||||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 38),
|
|
||||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 38),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package pkgserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
@@ -23,7 +23,7 @@ const (
|
|||||||
|
|
||||||
// packageIndex refers to metadata by name and various sort orders.
|
// packageIndex refers to metadata by name and various sort orders.
|
||||||
type packageIndex struct {
|
type packageIndex struct {
|
||||||
sorts [sortOrderEnd + 1][rosa.PresetUnexportedStart]*metadata
|
sorts [sortOrderEnd + 1][]*metadata
|
||||||
names map[string]*metadata
|
names map[string]*metadata
|
||||||
search searchCache
|
search searchCache
|
||||||
// Taken from [rosa.Report] if available.
|
// Taken from [rosa.Report] if available.
|
||||||
@@ -32,11 +32,11 @@ type packageIndex struct {
|
|||||||
|
|
||||||
// metadata holds [rosa.Metadata] extended with additional information.
|
// metadata holds [rosa.Metadata] extended with additional information.
|
||||||
type metadata struct {
|
type metadata struct {
|
||||||
p rosa.PArtifact
|
handle rosa.ArtifactH
|
||||||
*rosa.Metadata
|
*rosa.Metadata
|
||||||
|
|
||||||
// Populated via [rosa.Toolchain.Version], [rosa.Unversioned] is equivalent
|
// Copied from [rosa.Metadata], [rosa.Unversioned] is equivalent to the zero
|
||||||
// to the zero value. Otherwise, the zero value is invalid.
|
// value. Otherwise, the zero value is invalid.
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
// Output data size, available if present in report.
|
// Output data size, available if present in report.
|
||||||
Size int64 `json:"size,omitempty"`
|
Size int64 `json:"size,omitempty"`
|
||||||
@@ -50,20 +50,23 @@ type metadata struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// populate deterministically populates packageIndex, optionally with a report.
|
// populate deterministically populates packageIndex, optionally with a report.
|
||||||
func (index *packageIndex) populate(cache *pkg.Cache, report *rosa.Report) (err error) {
|
func (index *packageIndex) populate(report *rosa.Report) (err error) {
|
||||||
if report != nil {
|
if report != nil {
|
||||||
defer report.HandleAccess(&err)()
|
defer report.HandleAccess(&err)()
|
||||||
index.handleAccess = report.HandleAccess
|
index.handleAccess = report.HandleAccess
|
||||||
}
|
}
|
||||||
|
|
||||||
var work [rosa.PresetUnexportedStart]*metadata
|
handles := rosa.Native().Collect()
|
||||||
|
work := make([]*metadata, len(handles))
|
||||||
index.names = make(map[string]*metadata)
|
index.names = make(map[string]*metadata)
|
||||||
for p := range rosa.PresetUnexportedStart {
|
ir := pkg.NewIR()
|
||||||
|
for i, handle := range handles {
|
||||||
|
meta, a := rosa.Native().Std().MustLoad(handle)
|
||||||
m := metadata{
|
m := metadata{
|
||||||
p: p,
|
handle: handle,
|
||||||
|
|
||||||
Metadata: rosa.GetMetadata(p),
|
Metadata: meta,
|
||||||
Version: rosa.Std.Version(p),
|
Version: meta.Version,
|
||||||
}
|
}
|
||||||
if m.Version == "" {
|
if m.Version == "" {
|
||||||
return errors.New("invalid version from " + m.Name)
|
return errors.New("invalid version from " + m.Name)
|
||||||
@@ -72,33 +75,33 @@ func (index *packageIndex) populate(cache *pkg.Cache, report *rosa.Report) (err
|
|||||||
m.Version = ""
|
m.Version = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if cache != nil && report != nil {
|
if report != nil {
|
||||||
id := cache.Ident(rosa.Std.Load(p))
|
id := ir.Ident(a)
|
||||||
m.ids = pkg.Encode(id.Value())
|
m.ids = pkg.Encode(id.Value())
|
||||||
m.status, m.Size = report.ArtifactOf(id)
|
m.status, m.Size = report.ArtifactOf(id)
|
||||||
m.HasReport = m.Size >= 0
|
m.HasReport = m.Size >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
work[p] = &m
|
work[i] = &m
|
||||||
index.names[m.Name] = &m
|
index.names[m.Name] = &m
|
||||||
}
|
}
|
||||||
|
|
||||||
index.sorts[declarationAscending] = work
|
index.sorts[declarationAscending] = work
|
||||||
index.sorts[declarationDescending] = work
|
index.sorts[declarationDescending] = slices.Clone(work)
|
||||||
slices.Reverse(index.sorts[declarationDescending][:])
|
slices.Reverse(index.sorts[declarationDescending][:])
|
||||||
|
|
||||||
index.sorts[nameAscending] = work
|
index.sorts[nameAscending] = slices.Clone(work)
|
||||||
slices.SortFunc(index.sorts[nameAscending][:], func(a, b *metadata) int {
|
slices.SortFunc(index.sorts[nameAscending][:], func(a, b *metadata) int {
|
||||||
return strings.Compare(a.Name, b.Name)
|
return strings.Compare(a.Name, b.Name)
|
||||||
})
|
})
|
||||||
index.sorts[nameDescending] = index.sorts[nameAscending]
|
index.sorts[nameDescending] = slices.Clone(index.sorts[nameAscending])
|
||||||
slices.Reverse(index.sorts[nameDescending][:])
|
slices.Reverse(index.sorts[nameDescending][:])
|
||||||
|
|
||||||
index.sorts[sizeAscending] = work
|
index.sorts[sizeAscending] = slices.Clone(work)
|
||||||
slices.SortFunc(index.sorts[sizeAscending][:], func(a, b *metadata) int {
|
slices.SortFunc(index.sorts[sizeAscending][:], func(a, b *metadata) int {
|
||||||
return cmp.Compare(a.Size, b.Size)
|
return cmp.Compare(a.Size, b.Size)
|
||||||
})
|
})
|
||||||
index.sorts[sizeDescending] = index.sorts[sizeAscending]
|
index.sorts[sizeDescending] = slices.Clone(index.sorts[sizeAscending])
|
||||||
slices.Reverse(index.sorts[sizeDescending][:])
|
slices.Reverse(index.sorts[sizeDescending][:])
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package pkgserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -15,7 +15,7 @@ func newIndex(t *testing.T) *packageIndex {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
var index packageIndex
|
var index packageIndex
|
||||||
if err := index.populate(nil, nil); err != nil {
|
if err := index.populate(nil); err != nil {
|
||||||
t.Fatalf("populate: error = %v", err)
|
t.Fatalf("populate: error = %v", err)
|
||||||
}
|
}
|
||||||
return &index
|
return &index
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package pkgserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
@@ -3,12 +3,13 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="static/style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
<title>Hakurei PkgServer</title>
|
<link rel="icon" href="https://hakurei.app/favicon.ico"/>
|
||||||
<script src="static/index.js"></script>
|
<title>Rosa OS Packages</title>
|
||||||
|
<script src="index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Hakurei PkgServer</h1>
|
<h1>Rosa OS Packages</h1>
|
||||||
<div class="top-controls" id="top-controls-regular">
|
<div class="top-controls" id="top-controls-regular">
|
||||||
<p>Showing entries <span id="entry-counter"></span>.</p>
|
<p>Showing entries <span id="entry-counter"></span>.</p>
|
||||||
<span id="search-bar">
|
<span id="search-bar">
|
||||||
@@ -54,4 +55,4 @@
|
|||||||
</footer>
|
</footer>
|
||||||
<script>main();</script>
|
<script>main();</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -124,8 +124,8 @@ interface SearchPayload {
|
|||||||
async function searchRequest(limit: number, index: number, search: string, desc: boolean): Promise<SearchPayload> {
|
async function searchRequest(limit: number, index: number, search: string, desc: boolean): Promise<SearchPayload> {
|
||||||
const res = await fetch(`${ENDPOINT}/search?limit=${limit}&index=${index}&search=${encodeURIComponent(search)}&desc=${desc}`)
|
const res = await fetch(`${ENDPOINT}/search?limit=${limit}&index=${index}&search=${encodeURIComponent(search)}&desc=${desc}`)
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
alert("invalid search query!")
|
|
||||||
exitSearch()
|
exitSearch()
|
||||||
|
alert("invalid search query!")
|
||||||
return Promise.reject(res.statusText)
|
return Promise.reject(res.statusText)
|
||||||
}
|
}
|
||||||
const payload = await res.json()
|
const payload = await res.json()
|
||||||
@@ -214,6 +214,10 @@ class State {
|
|||||||
}
|
}
|
||||||
STATE.maxTotal = res.count!
|
STATE.maxTotal = res.count!
|
||||||
STATE.updateRange()
|
STATE.updateRange()
|
||||||
|
if(res.count! < 1) {
|
||||||
|
exitSearch()
|
||||||
|
alert("no results found!")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
getRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSortOrder())
|
getRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSortOrder())
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// Package ui holds the static web UI.
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Register arranges for mux to serve the embedded frontend.
|
||||||
|
func Register(mux *http.ServeMux) {
|
||||||
|
mux.Handle("GET /", http.FileServer(http.FS(static)))
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
//go:build frontend
|
||||||
|
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate tsc
|
||||||
|
//go:generate cp index.html style.css static
|
||||||
|
//go:embed static
|
||||||
|
var _static embed.FS
|
||||||
|
|
||||||
|
var static = func() fs.FS {
|
||||||
|
if f, err := fs.Sub(_static, "static"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}()
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//go:build !frontend
|
//go:build !frontend
|
||||||
|
|
||||||
package main
|
package ui
|
||||||
|
|
||||||
import "testing/fstest"
|
import "testing/fstest"
|
||||||
|
|
||||||
var content fstest.MapFS
|
var static fstest.MapFS
|
||||||
+453
-236
@@ -14,16 +14,18 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha512"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -40,6 +42,9 @@ import (
|
|||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
"hakurei.app/internal/rosa"
|
"hakurei.app/internal/rosa"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
|
|
||||||
|
"hakurei.app/cmd/mbf/internal/pkgserver"
|
||||||
|
"hakurei.app/cmd/mbf/internal/pkgserver/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -53,77 +58,167 @@ func main() {
|
|||||||
log.Fatal("this program must not run as root")
|
log.Fatal("this program must not run as root")
|
||||||
}
|
}
|
||||||
|
|
||||||
var cache *pkg.Cache
|
defer func() {
|
||||||
|
r := recover()
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e, ok := r.(rosa.LoadError)
|
||||||
|
if !ok {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
log.Fatal(e)
|
||||||
|
}()
|
||||||
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
ctx, stop := signal.NotifyContext(context.Background(),
|
||||||
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||||
defer stop()
|
defer stop()
|
||||||
defer func() {
|
|
||||||
if cache != nil {
|
|
||||||
cache.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if r := recover(); r != nil {
|
var cm cache
|
||||||
fmt.Println(r)
|
defer func() { cm.Close() }()
|
||||||
log.Fatal("consider scrubbing the on-disk cache")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
flagQuiet bool
|
flagQuiet bool
|
||||||
flagCures int
|
flagQEMU bool
|
||||||
flagBase string
|
flagArch string
|
||||||
flagIdle bool
|
flagCheck bool
|
||||||
|
flagLTO bool
|
||||||
|
flagPT bool
|
||||||
|
|
||||||
flagHostAbstract bool
|
flagCrossOverride int
|
||||||
|
|
||||||
|
addr net.UnixAddr
|
||||||
)
|
)
|
||||||
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) {
|
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) error {
|
||||||
msg.SwapVerbose(!flagQuiet)
|
if !rosa.Native().HasStageEarly() {
|
||||||
|
return pkg.UnsupportedArchError(runtime.GOARCH)
|
||||||
flagBase = os.ExpandEnv(flagBase)
|
|
||||||
if flagBase == "" {
|
|
||||||
flagBase = "cache"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var base *check.Absolute
|
if flagPT {
|
||||||
if flagBase, err = filepath.Abs(flagBase); err != nil {
|
log.Println("parsed in", rosa.ParseTime())
|
||||||
return
|
}
|
||||||
} else if base, err = check.NewAbs(flagBase); err != nil {
|
|
||||||
return
|
msg.SwapVerbose(!flagQuiet)
|
||||||
|
cm.ctx, cm.msg = ctx, msg
|
||||||
|
cm.base = os.ExpandEnv(cm.base)
|
||||||
|
if cm.base == "" {
|
||||||
|
cm.base = "cache"
|
||||||
|
}
|
||||||
|
|
||||||
|
addr.Net = "unix"
|
||||||
|
addr.Name = os.ExpandEnv(addr.Name)
|
||||||
|
if addr.Name == "" {
|
||||||
|
addr.Name = filepath.Join(cm.base, "daemon")
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags int
|
var flags int
|
||||||
if flagIdle {
|
if !flagCheck {
|
||||||
flags |= pkg.CSchedIdle
|
flags |= rosa.OptSkipCheck
|
||||||
}
|
}
|
||||||
if flagHostAbstract {
|
if !flagLTO {
|
||||||
flags |= pkg.CHostAbstract
|
flags |= rosa.OptLLVMNoLTO
|
||||||
|
}
|
||||||
|
rosa.Native().DropCaches("", flags)
|
||||||
|
cross := flagArch != "" && flagArch != runtime.GOARCH
|
||||||
|
if flagQEMU || cross {
|
||||||
|
_, cm.qemu = rosa.Native().Std().MustLoad(rosa.H("qemu"))
|
||||||
}
|
}
|
||||||
cache, err = pkg.Open(ctx, msg, flags, flagCures, base)
|
|
||||||
|
|
||||||
return
|
if cross {
|
||||||
|
if flagCrossOverride != -1 {
|
||||||
|
flags = flagCrossOverride
|
||||||
|
}
|
||||||
|
|
||||||
|
rosa.Native().DropCaches(flagArch, flags)
|
||||||
|
if !rosa.Native().HasStageEarly() {
|
||||||
|
return pkg.UnsupportedArchError(flagArch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}).Flag(
|
}).Flag(
|
||||||
&flagQuiet,
|
&flagQuiet,
|
||||||
"q", command.BoolFlag(false),
|
"q", command.BoolFlag(false),
|
||||||
"Do not print cure messages",
|
"Do not print cure messages",
|
||||||
).Flag(
|
).Flag(
|
||||||
&flagCures,
|
&flagQEMU,
|
||||||
|
"register", command.BoolFlag(false),
|
||||||
|
"Enable additional target architectures",
|
||||||
|
).Flag(
|
||||||
|
&flagArch,
|
||||||
|
"arch", command.StringFlag(runtime.GOARCH),
|
||||||
|
"Target architecture",
|
||||||
|
).Flag(
|
||||||
|
&flagLTO,
|
||||||
|
"lto", command.BoolFlag(false),
|
||||||
|
"Enable LTO in stage2 and stage3 LLVM toolchains",
|
||||||
|
).Flag(
|
||||||
|
&flagCheck,
|
||||||
|
"check", command.BoolFlag(true),
|
||||||
|
"Run test suites",
|
||||||
|
).Flag(
|
||||||
|
&flagCrossOverride,
|
||||||
|
"cross-flags", command.IntFlag(-1),
|
||||||
|
"Override non-native target preset flags",
|
||||||
|
).Flag(
|
||||||
|
&cm.verboseInit,
|
||||||
|
"v", command.BoolFlag(false),
|
||||||
|
"Do not suppress verbose output from init",
|
||||||
|
).Flag(
|
||||||
|
&cm.cures,
|
||||||
"cures", command.IntFlag(0),
|
"cures", command.IntFlag(0),
|
||||||
"Maximum number of dependencies to cure at any given time",
|
"Maximum number of dependencies to cure at any given time",
|
||||||
).Flag(
|
).Flag(
|
||||||
&flagBase,
|
&cm.jobs,
|
||||||
|
"jobs", command.IntFlag(0),
|
||||||
|
"Preferred number of jobs to run, when applicable",
|
||||||
|
).Flag(
|
||||||
|
&cm.base,
|
||||||
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
||||||
"Directory to store cured artifacts",
|
"Directory to store cured artifacts",
|
||||||
).Flag(
|
).Flag(
|
||||||
&flagIdle,
|
&cm.idle,
|
||||||
"sched-idle", command.BoolFlag(false),
|
"sched-idle", command.BoolFlag(false),
|
||||||
"Set SCHED_IDLE scheduling policy",
|
"Set SCHED_IDLE scheduling policy",
|
||||||
).Flag(
|
).Flag(
|
||||||
&flagHostAbstract,
|
&cm.hostAbstract,
|
||||||
"host-abstract", command.BoolFlag(
|
"host-abstract", command.BoolFlag(
|
||||||
os.Getenv("MBF_HOST_ABSTRACT") != "",
|
os.Getenv("MBF_HOST_ABSTRACT") != "",
|
||||||
),
|
),
|
||||||
"Do not restrict networked cure containers from connecting to host "+
|
"Do not restrict networked cure containers from connecting to host "+
|
||||||
"abstract UNIX sockets",
|
"abstract UNIX sockets",
|
||||||
|
).Flag(
|
||||||
|
&addr.Name,
|
||||||
|
"socket", command.StringFlag("$MBF_DAEMON_SOCKET"),
|
||||||
|
"Pathname of socket to bind to",
|
||||||
|
).Flag(
|
||||||
|
&flagPT,
|
||||||
|
"parse-time", command.BoolFlag(false),
|
||||||
|
"Print duration of the initial azalea parse",
|
||||||
|
)
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"checksum", "Compute checksum of data read from standard input",
|
||||||
|
func([]string) error {
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
os.Exit(1)
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
h := sha512.New384()
|
||||||
|
if _, err := io.Copy(h, os.Stdin); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println(pkg.Encode(pkg.Checksum(h.Sum(nil))))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -137,7 +232,9 @@ func main() {
|
|||||||
if flagShifts < 0 || flagShifts > 31 {
|
if flagShifts < 0 || flagShifts > 31 {
|
||||||
flagShifts = 12
|
flagShifts = 12
|
||||||
}
|
}
|
||||||
return cache.Scrub(runtime.NumCPU() << flagShifts)
|
return cm.Do(func(cache *pkg.Cache) error {
|
||||||
|
return cache.Scrub(runtime.NumCPU() << flagShifts)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
).Flag(
|
).Flag(
|
||||||
&flagShifts,
|
&flagShifts,
|
||||||
@@ -148,6 +245,7 @@ func main() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
|
flagBind string
|
||||||
flagStatus bool
|
flagStatus bool
|
||||||
flagReport string
|
flagReport string
|
||||||
)
|
)
|
||||||
@@ -155,9 +253,7 @@ func main() {
|
|||||||
"info",
|
"info",
|
||||||
"Display out-of-band metadata of an artifact",
|
"Display out-of-band metadata of an artifact",
|
||||||
func(args []string) (err error) {
|
func(args []string) (err error) {
|
||||||
if len(args) == 0 {
|
const shutdownTimeout = 15 * time.Second
|
||||||
return errors.New("info requires at least 1 argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r *rosa.Report
|
var r *rosa.Report
|
||||||
if flagReport != "" {
|
if flagReport != "" {
|
||||||
@@ -172,88 +268,46 @@ func main() {
|
|||||||
defer r.HandleAccess(&err)()
|
defer r.HandleAccess(&err)()
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, name := range args {
|
if flagBind == "" {
|
||||||
if p, ok := rosa.ResolveName(name); !ok {
|
return commandInfo(&cm, args, os.Stdout, flagStatus, r)
|
||||||
return fmt.Errorf("unknown artifact %q", name)
|
|
||||||
} else {
|
|
||||||
var suffix string
|
|
||||||
if version := rosa.Std.Version(p); version != rosa.Unversioned {
|
|
||||||
suffix += "-" + version
|
|
||||||
}
|
|
||||||
fmt.Println("name : " + name + suffix)
|
|
||||||
|
|
||||||
meta := rosa.GetMetadata(p)
|
|
||||||
fmt.Println("description : " + meta.Description)
|
|
||||||
if meta.Website != "" {
|
|
||||||
fmt.Println("website : " +
|
|
||||||
strings.TrimSuffix(meta.Website, "/"))
|
|
||||||
}
|
|
||||||
if len(meta.Dependencies) > 0 {
|
|
||||||
fmt.Print("depends on :")
|
|
||||||
for _, d := range meta.Dependencies {
|
|
||||||
s := rosa.GetMetadata(d).Name
|
|
||||||
if version := rosa.Std.Version(d); version != rosa.Unversioned {
|
|
||||||
s += "-" + version
|
|
||||||
}
|
|
||||||
fmt.Print(" " + s)
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusPrefix = "status : "
|
|
||||||
if flagStatus {
|
|
||||||
if r == nil {
|
|
||||||
var f io.ReadSeekCloser
|
|
||||||
f, err = cache.OpenStatus(rosa.Std.Load(p))
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
fmt.Println(
|
|
||||||
statusPrefix + "not yet cured",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Print(statusPrefix)
|
|
||||||
_, err = io.Copy(os.Stdout, f)
|
|
||||||
if err = errors.Join(err, f.Close()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
|
|
||||||
if status == nil {
|
|
||||||
fmt.Println(
|
|
||||||
statusPrefix + "not in report",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Println("size :", n)
|
|
||||||
fmt.Print(statusPrefix)
|
|
||||||
if _, err = os.Stdout.Write(status); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i != len(args)-1 {
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
var mux http.ServeMux
|
||||||
|
ui.Register(&mux)
|
||||||
|
if err = pkgserver.Register(ctx, &mux, r); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
server := http.Server{Addr: flagBind, Handler: &mux}
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
cc, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if _err := server.Shutdown(cc); _err != nil {
|
||||||
|
log.Fatal(_err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
msg.Verbosef("listening on %q", flagBind)
|
||||||
|
err = server.ListenAndServe()
|
||||||
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
},
|
},
|
||||||
).
|
).Flag(
|
||||||
Flag(
|
&flagBind,
|
||||||
&flagStatus,
|
"bind", command.StringFlag(""),
|
||||||
"status", command.BoolFlag(false),
|
"TCP address for the server to listen on",
|
||||||
"Display cure status if available",
|
).Flag(
|
||||||
).
|
&flagStatus,
|
||||||
Flag(
|
"status", command.BoolFlag(false),
|
||||||
&flagReport,
|
"Display cure status if available",
|
||||||
"report", command.StringFlag(""),
|
).Flag(
|
||||||
"Load cure status from this report file instead of cache",
|
&flagReport,
|
||||||
)
|
"report", command.StringFlag(""),
|
||||||
|
"Load cure status from this report file instead of cache",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
@@ -287,7 +341,9 @@ func main() {
|
|||||||
if ext.Isatty(int(w.Fd())) {
|
if ext.Isatty(int(w.Fd())) {
|
||||||
return errors.New("output appears to be a terminal")
|
return errors.New("output appears to be a terminal")
|
||||||
}
|
}
|
||||||
return rosa.WriteReport(msg, w, cache)
|
return cm.Do(func(cache *pkg.Cache) error {
|
||||||
|
return rosa.WriteReport(msg, w, cache)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -301,12 +357,12 @@ func main() {
|
|||||||
n atomic.Uint64
|
n atomic.Uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
w := make(chan rosa.PArtifact)
|
w := make(chan rosa.ArtifactH)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for range max(flagJobs, 1) {
|
for range max(flagJobs, 1) {
|
||||||
wg.Go(func() {
|
wg.Go(func() {
|
||||||
for p := range w {
|
for p := range w {
|
||||||
meta := rosa.GetMetadata(p)
|
meta, _ := rosa.Native().Std().MustLoad(p)
|
||||||
if meta.ID == 0 {
|
if meta.ID == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -319,12 +375,9 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if current, latest :=
|
if latest := meta.GetLatest(v); meta.Version != latest {
|
||||||
rosa.Std.Version(p),
|
|
||||||
meta.GetLatest(v); current != latest {
|
|
||||||
|
|
||||||
n.Add(1)
|
n.Add(1)
|
||||||
log.Printf("%s %s < %s", meta.Name, current, latest)
|
log.Printf("%s %s < %s", meta.Name, meta.Version, latest)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,9 +387,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
for i := range rosa.PresetEnd {
|
for _, p := range rosa.Native().CollectAll() {
|
||||||
select {
|
select {
|
||||||
case w <- rosa.PArtifact(i):
|
case w <- p:
|
||||||
break
|
break
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
break done
|
break done
|
||||||
@@ -350,14 +403,26 @@ func main() {
|
|||||||
" package(s) are out of date"))
|
" package(s) are out of date"))
|
||||||
}
|
}
|
||||||
return errors.Join(errs...)
|
return errors.Join(errs...)
|
||||||
}).
|
}).Flag(
|
||||||
Flag(
|
&flagJobs,
|
||||||
&flagJobs,
|
"j", command.IntFlag(32),
|
||||||
"j", command.IntFlag(32),
|
"Maximum number of simultaneous connections",
|
||||||
"Maximum number of simultaneous connections",
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"daemon",
|
||||||
|
"Service artifact IR with Rosa OS extensions",
|
||||||
|
func(args []string) error {
|
||||||
|
ul, err := net.ListenUnix("unix", &addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("listening on pathname socket at %s", addr.Name)
|
||||||
|
return serve(ctx, log.Default(), &cm, ul)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
flagGentoo string
|
flagGentoo string
|
||||||
@@ -369,9 +434,9 @@ func main() {
|
|||||||
"stage3",
|
"stage3",
|
||||||
"Check for toolchain 3-stage non-determinism",
|
"Check for toolchain 3-stage non-determinism",
|
||||||
func(args []string) (err error) {
|
func(args []string) (err error) {
|
||||||
t := rosa.Std
|
s := rosa.Std
|
||||||
if flagGentoo != "" {
|
if flagGentoo != "" {
|
||||||
t -= 3 // magic number to discourage misuse
|
s -= 3 // magic number to discourage misuse
|
||||||
|
|
||||||
var checksum pkg.Checksum
|
var checksum pkg.Checksum
|
||||||
if len(flagChecksum) != 0 {
|
if len(flagChecksum) != 0 {
|
||||||
@@ -379,28 +444,38 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rosa.SetGentooStage3(flagGentoo, checksum)
|
rosa.Native().SetGentooStage3(flagGentoo, checksum)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, stage1 := (t - 2).NewLLVM()
|
|
||||||
_, _, _, stage2 := (t - 1).NewLLVM()
|
|
||||||
_, _, _, stage3 := t.NewLLVM()
|
|
||||||
var (
|
var (
|
||||||
pathname *check.Absolute
|
pathname *check.Absolute
|
||||||
checksum [2]unique.Handle[pkg.Checksum]
|
checksum [2]unique.Handle[pkg.Checksum]
|
||||||
)
|
)
|
||||||
|
|
||||||
if pathname, _, err = cache.Cure(stage1); err != nil {
|
_llvm := rosa.H("llvm")
|
||||||
return err
|
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
|
_, llvm := rosa.Native().New(s - 2).Load(_llvm)
|
||||||
|
pathname, _, err = cache.Cure(llvm)
|
||||||
|
return
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
log.Println("stage1:", pathname)
|
log.Println("stage1:", pathname)
|
||||||
|
|
||||||
if pathname, checksum[0], err = cache.Cure(stage2); err != nil {
|
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
return err
|
_, llvm := rosa.Native().New(s - 1).Load(_llvm)
|
||||||
|
pathname, checksum[0], err = cache.Cure(llvm)
|
||||||
|
return
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
log.Println("stage2:", pathname)
|
log.Println("stage2:", pathname)
|
||||||
if pathname, checksum[1], err = cache.Cure(stage3); err != nil {
|
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
return err
|
_, llvm := rosa.Native().New(s).Load(_llvm)
|
||||||
|
pathname, checksum[1], err = cache.Cure(llvm)
|
||||||
|
return
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
log.Println("stage3:", pathname)
|
log.Println("stage3:", pathname)
|
||||||
|
|
||||||
@@ -417,39 +492,44 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if flagStage0 {
|
if flagStage0 {
|
||||||
if pathname, _, err = cache.Cure(
|
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
t.Load(rosa.Stage0),
|
pathname, _, err = cache.Cure(rosa.Native().Std().NewStage0())
|
||||||
); err != nil {
|
return
|
||||||
return err
|
}); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
log.Println(pathname)
|
log.Println(pathname)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
).
|
).Flag(
|
||||||
Flag(
|
&flagGentoo,
|
||||||
&flagGentoo,
|
"gentoo", command.StringFlag(""),
|
||||||
"gentoo", command.StringFlag(""),
|
"Bootstrap from a Gentoo stage3 tarball",
|
||||||
"Bootstrap from a Gentoo stage3 tarball",
|
).Flag(
|
||||||
).
|
&flagChecksum,
|
||||||
Flag(
|
"checksum", command.StringFlag(""),
|
||||||
&flagChecksum,
|
"Checksum of Gentoo stage3 tarball",
|
||||||
"checksum", command.StringFlag(""),
|
).Flag(
|
||||||
"Checksum of Gentoo stage3 tarball",
|
&flagStage0,
|
||||||
).
|
"stage0", command.BoolFlag(false),
|
||||||
Flag(
|
"Create bootstrap stage0 tarball",
|
||||||
&flagStage0,
|
)
|
||||||
"stage0", command.BoolFlag(false),
|
|
||||||
"Create bootstrap stage0 tarball",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
flagDump string
|
flagDump string
|
||||||
flagEnter bool
|
flagEnter bool
|
||||||
flagExport string
|
flagExport string
|
||||||
|
flagRemote bool
|
||||||
|
flagNoReply bool
|
||||||
|
flagFaults bool
|
||||||
|
flagPop bool
|
||||||
|
|
||||||
|
flagBoot bool
|
||||||
|
flagStd bool
|
||||||
)
|
)
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
"cure",
|
"cure",
|
||||||
@@ -458,14 +538,26 @@ func main() {
|
|||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.New("cure requires 1 argument")
|
return errors.New("cure requires 1 argument")
|
||||||
}
|
}
|
||||||
p, ok := rosa.ResolveName(args[0])
|
|
||||||
if !ok {
|
t := rosa.Std
|
||||||
|
if flagBoot {
|
||||||
|
t -= 2
|
||||||
|
} else if flagStd {
|
||||||
|
t -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_, a := rosa.Native().New(t).Load(rosa.ArtifactH(unique.Make(args[0])))
|
||||||
|
if a == nil {
|
||||||
return fmt.Errorf("unknown artifact %q", args[0])
|
return fmt.Errorf("unknown artifact %q", args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
default:
|
default:
|
||||||
pathname, _, err := cache.Cure(rosa.Std.Load(p))
|
var pathname *check.Absolute
|
||||||
|
err := cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
|
pathname, _, err = cache.Cure(a)
|
||||||
|
return
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -505,7 +597,7 @@ func main() {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = cache.EncodeAll(f, rosa.Std.Load(p)); err != nil {
|
if err = pkg.NewIR().EncodeAll(f, a); err != nil {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -513,33 +605,149 @@ func main() {
|
|||||||
return f.Close()
|
return f.Close()
|
||||||
|
|
||||||
case flagEnter:
|
case flagEnter:
|
||||||
return cache.EnterExec(
|
return cm.Do(func(cache *pkg.Cache) error {
|
||||||
ctx,
|
return cache.EnterExec(
|
||||||
rosa.Std.Load(p),
|
ctx,
|
||||||
true, os.Stdin, os.Stdout, os.Stderr,
|
a,
|
||||||
rosa.AbsSystem.Append("bin", "mksh"),
|
true, os.Stdin, os.Stdout, os.Stderr,
|
||||||
"sh",
|
rosa.AbsSystem.Append("bin", "mksh"),
|
||||||
)
|
"sh",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
case flagRemote:
|
||||||
|
var flags uint64
|
||||||
|
if flagNoReply {
|
||||||
|
flags |= remoteNoReply
|
||||||
|
}
|
||||||
|
pathname, err := cureRemote(ctx, &addr, a, flags)
|
||||||
|
if !flagNoReply && err == nil {
|
||||||
|
log.Println(pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
cc, cancel := context.WithDeadline(context.Background(), daemonDeadline())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if _err := cancelRemote(cc, &addr, a, false); _err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
|
||||||
|
case flagFaults:
|
||||||
|
var faults []pkg.Fault
|
||||||
|
if err := cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
|
faults, err = cache.ReadFaults(a)
|
||||||
|
return
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fault := range faults {
|
||||||
|
log.Printf("%s: %s ago", fault.String(), time.Since(fault.Time()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case flagPop:
|
||||||
|
var faults []pkg.Fault
|
||||||
|
if err := cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
|
faults, err = cache.ReadFaults(a)
|
||||||
|
return
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(faults) == 0 {
|
||||||
|
return errors.New("no fault entries found")
|
||||||
|
}
|
||||||
|
fault := faults[len(faults)-1]
|
||||||
|
r, err := fault.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(os.Stdout, r); err != nil {
|
||||||
|
_ = r.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
if err = r.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("faulting cure terminated %s ago", time.Since(fault.Time()))
|
||||||
|
return fault.Destroy()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
).
|
).Flag(
|
||||||
Flag(
|
&flagDump,
|
||||||
&flagDump,
|
"dump", command.StringFlag(""),
|
||||||
"dump", command.StringFlag(""),
|
"Write IR to specified pathname and terminate",
|
||||||
"Write IR to specified pathname and terminate",
|
).Flag(
|
||||||
).
|
&flagExport,
|
||||||
Flag(
|
"export", command.StringFlag(""),
|
||||||
&flagExport,
|
"Export cured artifact to specified pathname",
|
||||||
"export", command.StringFlag(""),
|
).Flag(
|
||||||
"Export cured artifact to specified pathname",
|
&flagEnter,
|
||||||
).
|
"enter", command.BoolFlag(false),
|
||||||
Flag(
|
"Enter cure container with an interactive shell",
|
||||||
&flagEnter,
|
).Flag(
|
||||||
"enter", command.BoolFlag(false),
|
&flagRemote,
|
||||||
"Enter cure container with an interactive shell",
|
"daemon", command.BoolFlag(false),
|
||||||
)
|
"Cure artifact on the daemon",
|
||||||
|
).Flag(
|
||||||
|
&flagNoReply,
|
||||||
|
"no-reply", command.BoolFlag(false),
|
||||||
|
"Do not receive a reply from the daemon",
|
||||||
|
).Flag(
|
||||||
|
&flagBoot,
|
||||||
|
"boot", command.BoolFlag(false),
|
||||||
|
"Build on the stage0 toolchain",
|
||||||
|
).Flag(
|
||||||
|
&flagStd,
|
||||||
|
"std", command.BoolFlag(false),
|
||||||
|
"Build on the intermediate toolchain",
|
||||||
|
).Flag(
|
||||||
|
&flagFaults,
|
||||||
|
"faults", command.BoolFlag(false),
|
||||||
|
"Display fault entries of the specified artifact",
|
||||||
|
).Flag(
|
||||||
|
&flagPop,
|
||||||
|
"pop", command.BoolFlag(false),
|
||||||
|
"Display and destroy the most recent fault entry",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"clear",
|
||||||
|
"Remove all fault entries from the cache",
|
||||||
|
func([]string) error {
|
||||||
|
return cm.Do(func(*pkg.Cache) error {
|
||||||
|
pathname := filepath.Join(cm.base, "fault")
|
||||||
|
dents, err := os.ReadDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dent := range dents {
|
||||||
|
msg.Verbosef("destroying entry %s", dent.Name())
|
||||||
|
if err = os.Remove(filepath.Join(pathname, dent.Name())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("destroyed %d fault entries", len(dents))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"abort",
|
||||||
|
"Abort all pending cures on the daemon",
|
||||||
|
func([]string) error { return abortRemote(ctx, &addr, false) },
|
||||||
|
)
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
flagNet bool
|
flagNet bool
|
||||||
@@ -551,29 +759,31 @@ func main() {
|
|||||||
"shell",
|
"shell",
|
||||||
"Interactive shell in the specified Rosa OS environment",
|
"Interactive shell in the specified Rosa OS environment",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
presets := make([]rosa.PArtifact, len(args))
|
handles := make([]rosa.ArtifactH, len(args), len(args)+3)
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
p, ok := rosa.ResolveName(arg)
|
handles[i] = rosa.ArtifactH(unique.Make(arg))
|
||||||
if !ok {
|
if meta, _ := rosa.Native().Std().Load(handles[i]); meta == nil {
|
||||||
return fmt.Errorf("unknown artifact %q", arg)
|
return fmt.Errorf("unknown artifact %q", arg)
|
||||||
}
|
}
|
||||||
presets[i] = p
|
|
||||||
}
|
}
|
||||||
root := make(pkg.Collect, 0, 6+len(args))
|
|
||||||
root = rosa.Std.AppendPresets(root, presets...)
|
|
||||||
|
|
||||||
if flagWithToolchain {
|
base := rosa.H("llvm")
|
||||||
musl, compilerRT, runtimes, clang := (rosa.Std - 1).NewLLVM()
|
if !flagWithToolchain {
|
||||||
root = append(root, musl, compilerRT, runtimes, clang)
|
base = rosa.H("musl")
|
||||||
} else {
|
|
||||||
root = append(root, rosa.Std.Load(rosa.Musl))
|
|
||||||
}
|
}
|
||||||
root = append(root,
|
handles = append(handles,
|
||||||
rosa.Std.Load(rosa.Mksh),
|
base,
|
||||||
rosa.Std.Load(rosa.Toybox),
|
rosa.H("mksh"),
|
||||||
|
rosa.H("toybox"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if _, _, err := cache.Cure(&root); err == nil {
|
root := make(pkg.Collect, 0, 6+len(args))
|
||||||
|
root = rosa.Native().Std().Append(root, handles...)
|
||||||
|
|
||||||
|
if err := cm.Do(func(cache *pkg.Cache) error {
|
||||||
|
_, _, err := cache.Cure(&root)
|
||||||
|
return err
|
||||||
|
}); err == nil {
|
||||||
return errors.New("unreachable")
|
return errors.New("unreachable")
|
||||||
} else if !pkg.IsCollected(err) {
|
} else if !pkg.IsCollected(err) {
|
||||||
return err
|
return err
|
||||||
@@ -585,11 +795,22 @@ func main() {
|
|||||||
}
|
}
|
||||||
cured := make(map[pkg.Artifact]cureRes)
|
cured := make(map[pkg.Artifact]cureRes)
|
||||||
for _, a := range root {
|
for _, a := range root {
|
||||||
pathname, checksum, err := cache.Cure(a)
|
if err := cm.Do(func(cache *pkg.Cache) error {
|
||||||
if err != nil {
|
pathname, checksum, err := cache.Cure(a)
|
||||||
|
if err == nil {
|
||||||
|
cured[a] = cureRes{pathname, checksum}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// explicitly open for direct error-free use from this point
|
||||||
|
if cm.c == nil {
|
||||||
|
if err := cm.open(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cured[a] = cureRes{pathname, checksum}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
|
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
|
||||||
@@ -599,7 +820,7 @@ func main() {
|
|||||||
res := cured[a]
|
res := cured[a]
|
||||||
return res.pathname, res.checksum
|
return res.pathname, res.checksum
|
||||||
}, func(i int, d pkg.Artifact) {
|
}, func(i int, d pkg.Artifact) {
|
||||||
r := pkg.Encode(cache.Ident(d).Value())
|
r := pkg.Encode(cm.c.Ident(d).Value())
|
||||||
if s, ok := d.(fmt.Stringer); ok {
|
if s, ok := d.(fmt.Stringer); ok {
|
||||||
if name := s.String(); name != "" {
|
if name := s.String(); name != "" {
|
||||||
r += "-" + name
|
r += "-" + name
|
||||||
@@ -618,6 +839,7 @@ func main() {
|
|||||||
z.Hostname = "localhost"
|
z.Hostname = "localhost"
|
||||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
||||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
z.Quiet = !cm.verboseInit
|
||||||
if s, ok := os.LookupEnv("TERM"); ok {
|
if s, ok := os.LookupEnv("TERM"); ok {
|
||||||
z.Env = append(z.Env, "TERM="+s)
|
z.Env = append(z.Env, "TERM="+s)
|
||||||
}
|
}
|
||||||
@@ -663,22 +885,19 @@ func main() {
|
|||||||
}
|
}
|
||||||
return z.Wait()
|
return z.Wait()
|
||||||
},
|
},
|
||||||
).
|
).Flag(
|
||||||
Flag(
|
&flagNet,
|
||||||
&flagNet,
|
"net", command.BoolFlag(false),
|
||||||
"net", command.BoolFlag(false),
|
"Share host net namespace",
|
||||||
"Share host net namespace",
|
).Flag(
|
||||||
).
|
&flagSession,
|
||||||
Flag(
|
"session", command.BoolFlag(true),
|
||||||
&flagSession,
|
"Retain session",
|
||||||
"session", command.BoolFlag(true),
|
).Flag(
|
||||||
"Retain session",
|
&flagWithToolchain,
|
||||||
).
|
"with-toolchain", command.BoolFlag(false),
|
||||||
Flag(
|
"Include the stage2 LLVM toolchain",
|
||||||
&flagWithToolchain,
|
)
|
||||||
"with-toolchain", command.BoolFlag(false),
|
|
||||||
"Include the stage2 LLVM toolchain",
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,9 +908,7 @@ func main() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
c.MustParse(os.Args[1:], func(err error) {
|
||||||
if cache != nil {
|
cm.Close()
|
||||||
cache.Close()
|
|
||||||
}
|
|
||||||
if w, ok := err.(interface{ Unwrap() []error }); !ok {
|
if w, ok := err.(interface{ Unwrap() []error }); !ok {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/rosa"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
rosa.Native().DropCaches("", rosa.OptLLVMNoLTO)
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCureAll(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const env = "ROSA_TEST_DAEMON"
|
||||||
|
|
||||||
|
if !testing.Verbose() {
|
||||||
|
t.Skip("verbose flag not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
pathname, ok := os.LookupEnv(env)
|
||||||
|
if !ok {
|
||||||
|
t.Skip(env + " not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := net.UnixAddr{Net: "unix", Name: pathname}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if t.Failed() {
|
||||||
|
if err := abortRemote(t.Context(), &addr, false); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, handle := range rosa.Native().Collect() {
|
||||||
|
_, a := rosa.Native().Std().MustLoad(handle)
|
||||||
|
t.Run(handle.String(), func(t *testing.T) {
|
||||||
|
_, err := cureRemote(t.Context(), &addr, a, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/command"
|
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
"hakurei.app/internal/rosa"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
const shutdownTimeout = 15 * time.Second
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
log.SetPrefix("pkgserver: ")
|
|
||||||
|
|
||||||
var (
|
|
||||||
flagBaseDir string
|
|
||||||
flagAddr string
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
|
||||||
defer stop()
|
|
||||||
msg := message.New(log.Default())
|
|
||||||
|
|
||||||
c := command.New(os.Stderr, log.Printf, "pkgserver", func(args []string) error {
|
|
||||||
var (
|
|
||||||
cache *pkg.Cache
|
|
||||||
report *rosa.Report
|
|
||||||
)
|
|
||||||
switch len(args) {
|
|
||||||
case 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
baseDir, err := check.NewAbs(flagBaseDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cache, err = pkg.Open(ctx, msg, 0, 0, baseDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cache.Close()
|
|
||||||
|
|
||||||
report, err = rosa.OpenReport(args[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errors.New("pkgserver requires 1 argument")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var index packageIndex
|
|
||||||
index.search = make(searchCache)
|
|
||||||
if err := index.populate(cache, report); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
ticker.Stop()
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
index.search.clean()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
var mux http.ServeMux
|
|
||||||
uiRoutes(&mux)
|
|
||||||
testUIRoutes(&mux)
|
|
||||||
index.registerAPI(&mux)
|
|
||||||
server := http.Server{
|
|
||||||
Addr: flagAddr,
|
|
||||||
Handler: &mux,
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
c, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
|
||||||
defer cancel()
|
|
||||||
if err := server.Shutdown(c); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return server.ListenAndServe()
|
|
||||||
}).Flag(
|
|
||||||
&flagBaseDir,
|
|
||||||
"b", command.StringFlag(""),
|
|
||||||
"base directory for cache",
|
|
||||||
).Flag(
|
|
||||||
&flagAddr,
|
|
||||||
"addr", command.StringFlag(":8067"),
|
|
||||||
"TCP network address to listen on",
|
|
||||||
)
|
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
|
||||||
if errors.Is(err, http.ErrServerClosed) {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
log.Fatal(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
//go:build frontend && frontend_test
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Always remove ui_test/ui; if the previous tsc run failed, the rm never
|
|
||||||
// executes.
|
|
||||||
|
|
||||||
//go:generate sh -c "rm -r ui_test/ui/ 2>/dev/null || true"
|
|
||||||
//go:generate mkdir ui_test/ui
|
|
||||||
//go:generate sh -c "cp ui/static/*.ts ui_test/ui/"
|
|
||||||
//go:generate tsc -p ui_test
|
|
||||||
//go:generate rm -r ui_test/ui/
|
|
||||||
//go:generate cp ui_test/lib/ui.css ui_test/static/style.css
|
|
||||||
//go:generate cp ui_test/lib/ui.html ui_test/static/index.html
|
|
||||||
//go:generate sh -c "cd ui_test/lib && cp *.svg ../static/"
|
|
||||||
//go:embed ui_test/static
|
|
||||||
var _staticTest embed.FS
|
|
||||||
|
|
||||||
var staticTest = func() fs.FS {
|
|
||||||
if f, err := fs.Sub(_staticTest, "ui_test/static"); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
func testUIRoutes(mux *http.ServeMux) {
|
|
||||||
mux.Handle("GET /test/", http.StripPrefix("/test", http.FileServer(http.FS(staticTest))))
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
//go:build !(frontend && frontend_test)
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
func testUIRoutes(mux *http.ServeMux) {}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
func serveWebUI(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
||||||
w.Header().Set("Pragma", "no-cache")
|
|
||||||
w.Header().Set("Expires", "0")
|
|
||||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
||||||
w.Header().Set("X-XSS-Protection", "1")
|
|
||||||
w.Header().Set("X-Frame-Options", "DENY")
|
|
||||||
|
|
||||||
http.ServeFileFS(w, r, content, "ui/index.html")
|
|
||||||
}
|
|
||||||
func serveStaticContent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch r.URL.Path {
|
|
||||||
case "/static/style.css":
|
|
||||||
http.ServeFileFS(w, r, content, "ui/static/style.css")
|
|
||||||
case "/favicon.ico":
|
|
||||||
http.ServeFileFS(w, r, content, "ui/static/favicon.ico")
|
|
||||||
case "/static/index.js":
|
|
||||||
http.ServeFileFS(w, r, content, "ui/static/index.js")
|
|
||||||
default:
|
|
||||||
http.NotFound(w, r)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func uiRoutes(mux *http.ServeMux) {
|
|
||||||
mux.HandleFunc("GET /{$}", serveWebUI)
|
|
||||||
mux.HandleFunc("GET /favicon.ico", serveStaticContent)
|
|
||||||
mux.HandleFunc("GET /static/", serveStaticContent)
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,9 +0,0 @@
|
|||||||
//go:build frontend
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "embed"
|
|
||||||
|
|
||||||
//go:generate tsc -p ui
|
|
||||||
//go:embed ui/*
|
|
||||||
var content embed.FS
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
// Import all test files to register their test suites.
|
|
||||||
import "./index_test.js";
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
import { suite, test } from "./lib/test.js";
|
|
||||||
import "./ui/index.js";
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
// Many editors have terminal emulators built in, so running tests with NodeJS
|
|
||||||
// provides faster iteration, especially for those acclimated to test-driven
|
|
||||||
// development.
|
|
||||||
|
|
||||||
import "../all_tests.js";
|
|
||||||
import { StreamReporter, GLOBAL_REGISTRAR } from "./test.js";
|
|
||||||
|
|
||||||
// TypeScript doesn't like process and Deno as their type definitions aren't
|
|
||||||
// installed, but doesn't seem to complain if they're accessed through
|
|
||||||
// globalThis.
|
|
||||||
const process: any = (globalThis as any).process;
|
|
||||||
const Deno: any = (globalThis as any).Deno;
|
|
||||||
|
|
||||||
function getArgs(): string[] {
|
|
||||||
if (process) {
|
|
||||||
const [runtime, program, ...args] = process.argv;
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
if (Deno) return Deno.args;
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function exit(code?: number): never {
|
|
||||||
if (Deno) Deno.exit(code);
|
|
||||||
if (process) process.exit(code);
|
|
||||||
throw `exited with code ${code ?? 0}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = getArgs();
|
|
||||||
let verbose = false;
|
|
||||||
if (args.length > 1) {
|
|
||||||
console.error("Too many arguments");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (args.length === 1) {
|
|
||||||
if (args[0] === "-v" || args[0] === "--verbose" || args[0] === "-verbose") {
|
|
||||||
verbose = true;
|
|
||||||
} else if (args[0] !== "--") {
|
|
||||||
console.error(`Unknown argument '${args[0]}'`);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let reporter = new StreamReporter({ writeln: console.log }, verbose);
|
|
||||||
GLOBAL_REGISTRAR.run(reporter);
|
|
||||||
exit(reporter.succeeded() ? 0 : 1);
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<!-- See failure-open.svg for an explanation of the view box dimensions. -->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
|
||||||
<!-- This triangle should match success-closed.svg, fill and stroke color notwithstanding. -->
|
|
||||||
<polygon points="0,0 100,50 0,100" fill="red" stroke="red" stroke-width="15" stroke-linejoin="round"/>
|
|
||||||
<!--
|
|
||||||
! y-coordinates go before x-coordinates here to highlight the difference
|
|
||||||
! (or, lack thereof) between these numbers and the ones in failure-open.svg;
|
|
||||||
! try a textual diff. Make sure to keep the numbers in sync!
|
|
||||||
-->
|
|
||||||
<line y1="30" x1="10" y2="70" x2="50" stroke="white" stroke-width="16"/>
|
|
||||||
<line y1="30" x1="50" y2="70" x2="10" stroke="white" stroke-width="16"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 788 B |
@@ -1,35 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<!--
|
|
||||||
! This view box is a bit weird: the strokes assume they're working in a view
|
|
||||||
! box that spans from the (0,0) to (100,100), and indeed that is convenient
|
|
||||||
! conceptualizing the strokes, but the stroke itself has a considerable width
|
|
||||||
! that gets clipped by restrictive view box dimensions. Hence, the view is
|
|
||||||
! shifted from (0,0)–(100,100) to (-20,-20)–(120,120), to make room for the
|
|
||||||
! clipped stroke, while leaving behind an illusion of working in a view box
|
|
||||||
! spanning from (0,0) to (100,100).
|
|
||||||
!
|
|
||||||
! However, the resulting SVG is too close to the summary text, and CSS
|
|
||||||
! properties to add padding do not seem to work with `content:` (likely because
|
|
||||||
! they're anonymous replaced elements); thus, the width of the view is
|
|
||||||
! increased considerably to provide padding in the SVG itself, while leaving
|
|
||||||
! the strokes oblivious.
|
|
||||||
!
|
|
||||||
! It gets worse: the summary text isn't vertically aligned with the icon! As
|
|
||||||
! a flexbox cannot be used in a summary to align the marker with the text, the
|
|
||||||
! simplest and most effective solution is to reduce the height of the view box
|
|
||||||
! from 140 to 130, thereby removing some of the bottom padding present.
|
|
||||||
!
|
|
||||||
! All six SVGs use the same view box (and indeed, they refer to this comment)
|
|
||||||
! so that they all appear to be the same size and position relative to each
|
|
||||||
! other on the DOM—indeed, the view box dimensions, alongside the width,
|
|
||||||
! directly control their placement on the DOM.
|
|
||||||
!
|
|
||||||
! TL;DR: CSS is janky, overflow is weird, and SVG is awesome!
|
|
||||||
-->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
|
||||||
<!-- This triangle should match success-open.svg, fill and stroke color notwithstanding. -->
|
|
||||||
<polygon points="0,0 100,0 50,100" fill="red" stroke="red" stroke-width="15" stroke-linejoin="round"/>
|
|
||||||
<!-- See the comment in failure-closed.svg before modifying this. -->
|
|
||||||
<line x1="30" y1="10" x2="70" y2="50" stroke="white" stroke-width="16"/>
|
|
||||||
<line x1="30" y1="50" x2="70" y2="10" stroke="white" stroke-width="16"/>
|
|
||||||
</svg>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import "../all_tests.js";
|
|
||||||
import { GoTestReporter, GLOBAL_REGISTRAR } from "./test.js";
|
|
||||||
GLOBAL_REGISTRAR.run(new GoTestReporter());
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<!-- See failure-open.svg for an explanation of the view box dimensions. -->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
|
||||||
<!-- This triangle should match success-closed.svg, fill and stroke color notwithstanding. -->
|
|
||||||
<polygon points="0,0 100,50 0,100" fill="blue" stroke="blue" stroke-width="15" stroke-linejoin="round"/>
|
|
||||||
<!--
|
|
||||||
! This path is extremely similar to the one in skip-open.svg; before
|
|
||||||
! making minor modifications, diff the two to understand how they should
|
|
||||||
! remain in sync.
|
|
||||||
-->
|
|
||||||
<path
|
|
||||||
d="M 50,50
|
|
||||||
A 23,23 270,1,1 30,30
|
|
||||||
l -10,20
|
|
||||||
m 10,-20
|
|
||||||
l -20,-10"
|
|
||||||
fill="none"
|
|
||||||
stroke="white"
|
|
||||||
stroke-width="12"
|
|
||||||
stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 812 B |
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<!-- See failure-open.svg for an explanation of the view box dimensions. -->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
|
||||||
<!-- This triangle should match success-open.svg, fill and stroke color notwithstanding. -->
|
|
||||||
<polygon points="0,0 100,0 50,100" fill="blue" stroke="blue" stroke-width="15" stroke-linejoin="round"/>
|
|
||||||
<!--
|
|
||||||
! This path is extremely similar to the one in skip-closed.svg; before
|
|
||||||
! making minor modifications, diff the two to understand how they should
|
|
||||||
! remain in sync.
|
|
||||||
-->
|
|
||||||
<path
|
|
||||||
d="M 50,50
|
|
||||||
A 23,23 270,1,1 70,30
|
|
||||||
l 10,-20
|
|
||||||
m -10,20
|
|
||||||
l -20,-10"
|
|
||||||
fill="none"
|
|
||||||
stroke="white"
|
|
||||||
stroke-width="12"
|
|
||||||
stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 812 B |
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<!-- See failure-open.svg for an explanation of the view box dimensions. -->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
|
||||||
<style>
|
|
||||||
.adaptive-stroke {
|
|
||||||
stroke: black;
|
|
||||||
}
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.adaptive-stroke {
|
|
||||||
stroke: ghostwhite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<!-- When updating this triangle, also update the other five SVGs. -->
|
|
||||||
<polygon points="0,0 100,50 0,100" fill="none" class="adaptive-stroke" stroke-width="15" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 572 B |
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<!-- See failure-open.svg for an explanation of the view box dimensions. -->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
|
||||||
<style>
|
|
||||||
.adaptive-stroke {
|
|
||||||
stroke: black;
|
|
||||||
}
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.adaptive-stroke {
|
|
||||||
stroke: ghostwhite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<!-- When updating this triangle, also update the other five SVGs. -->
|
|
||||||
<polygon points="0,0 100,0 50,100" fill="none" class="adaptive-stroke" stroke-width="15" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 572 B |
@@ -1,403 +0,0 @@
|
|||||||
// =============================================================================
|
|
||||||
// DSL
|
|
||||||
|
|
||||||
type TestTree = TestGroup | Test;
|
|
||||||
type TestGroup = { name: string; children: TestTree[] };
|
|
||||||
type Test = { name: string; test: (t: TestController) => void };
|
|
||||||
|
|
||||||
export class TestRegistrar {
|
|
||||||
#suites: TestGroup[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.#suites = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
suite(name: string, children: TestTree[]) {
|
|
||||||
checkDuplicates(name, children);
|
|
||||||
this.#suites.push({ name, children });
|
|
||||||
}
|
|
||||||
|
|
||||||
run(reporter: Reporter) {
|
|
||||||
reporter.register(this.#suites);
|
|
||||||
for (const suite of this.#suites) {
|
|
||||||
for (const c of suite.children) runTests(reporter, [suite.name], c);
|
|
||||||
}
|
|
||||||
reporter.finalize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export let GLOBAL_REGISTRAR = new TestRegistrar();
|
|
||||||
|
|
||||||
// Register a suite in the global registrar.
|
|
||||||
export function suite(name: string, children: TestTree[]) {
|
|
||||||
GLOBAL_REGISTRAR.suite(name, children);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function group(name: string, children: TestTree[]): TestTree {
|
|
||||||
checkDuplicates(name, children);
|
|
||||||
return { name, children };
|
|
||||||
}
|
|
||||||
export const context = group;
|
|
||||||
export const describe = group;
|
|
||||||
|
|
||||||
export function test(name: string, test: (t: TestController) => void): TestTree {
|
|
||||||
return { name, test };
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkDuplicates(parent: string, names: { name: string }[]) {
|
|
||||||
let seen = new Set<string>();
|
|
||||||
for (const { name } of names) {
|
|
||||||
if (seen.has(name)) {
|
|
||||||
throw new RangeError(`duplicate name '${name}' in '${parent}'`);
|
|
||||||
}
|
|
||||||
seen.add(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TestState = "success" | "failure" | "skip";
|
|
||||||
|
|
||||||
class AbortSentinel {}
|
|
||||||
|
|
||||||
export class TestController {
|
|
||||||
#state: TestState;
|
|
||||||
logs: string[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.#state = "success";
|
|
||||||
this.logs = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
getState(): TestState {
|
|
||||||
return this.#state;
|
|
||||||
}
|
|
||||||
|
|
||||||
fail() {
|
|
||||||
this.#state = "failure";
|
|
||||||
}
|
|
||||||
|
|
||||||
failed(): boolean {
|
|
||||||
return this.#state === "failure";
|
|
||||||
}
|
|
||||||
|
|
||||||
failNow(): never {
|
|
||||||
this.fail();
|
|
||||||
throw new AbortSentinel();
|
|
||||||
}
|
|
||||||
|
|
||||||
log(message: string) {
|
|
||||||
this.logs.push(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
error(message: string) {
|
|
||||||
this.log(message);
|
|
||||||
this.fail();
|
|
||||||
}
|
|
||||||
|
|
||||||
fatal(message: string): never {
|
|
||||||
this.log(message);
|
|
||||||
this.failNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
skip(message?: string): never {
|
|
||||||
if (message != null) this.log(message);
|
|
||||||
if (this.#state !== "failure") this.#state = "skip";
|
|
||||||
throw new AbortSentinel();
|
|
||||||
}
|
|
||||||
|
|
||||||
skipped(): boolean {
|
|
||||||
return this.#state === "skip";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Execution
|
|
||||||
|
|
||||||
export interface TestResult {
|
|
||||||
state: TestState;
|
|
||||||
logs: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTests(reporter: Reporter, parents: string[], node: TestTree) {
|
|
||||||
const path = [...parents, node.name];
|
|
||||||
if ("children" in node) {
|
|
||||||
for (const c of node.children) runTests(reporter, path, c);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let controller = new TestController();
|
|
||||||
try {
|
|
||||||
node.test(controller);
|
|
||||||
} catch (e) {
|
|
||||||
if (!(e instanceof AbortSentinel)) {
|
|
||||||
controller.error(extractExceptionString(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reporter.update(path, { state: controller.getState(), logs: controller.logs });
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractExceptionString(e: any): string {
|
|
||||||
// String() instead of .toString() as null and undefined don't have
|
|
||||||
// properties.
|
|
||||||
const s = String(e);
|
|
||||||
if (!(e instanceof Error && e.stack)) return s;
|
|
||||||
// v8 (Chromium, NodeJS) includes the error message, while Firefox and
|
|
||||||
// WebKit do not.
|
|
||||||
if (e.stack.startsWith(s)) return e.stack;
|
|
||||||
return `${s}\n${e.stack}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Reporting
|
|
||||||
|
|
||||||
export interface Reporter {
|
|
||||||
register(suites: TestGroup[]): void;
|
|
||||||
update(path: string[], result: TestResult): void;
|
|
||||||
finalize(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NoOpReporter implements Reporter {
|
|
||||||
suites: TestGroup[];
|
|
||||||
results: ({ path: string[] } & TestResult)[];
|
|
||||||
finalized: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.suites = [];
|
|
||||||
this.results = [];
|
|
||||||
this.finalized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
register(suites: TestGroup[]) {
|
|
||||||
this.suites = suites;
|
|
||||||
}
|
|
||||||
|
|
||||||
update(path: string[], result: TestResult) {
|
|
||||||
this.results.push({ path, ...result });
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize() {
|
|
||||||
this.finalized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Stream {
|
|
||||||
writeln(s: string): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SEP = " ❯ ";
|
|
||||||
|
|
||||||
export class StreamReporter implements Reporter {
|
|
||||||
stream: Stream;
|
|
||||||
verbose: boolean;
|
|
||||||
#successes: ({ path: string[] } & TestResult)[];
|
|
||||||
#failures: ({ path: string[] } & TestResult)[];
|
|
||||||
#skips: ({ path: string[] } & TestResult)[];
|
|
||||||
|
|
||||||
constructor(stream: Stream, verbose: boolean = false) {
|
|
||||||
this.stream = stream;
|
|
||||||
this.verbose = verbose;
|
|
||||||
this.#successes = [];
|
|
||||||
this.#failures = [];
|
|
||||||
this.#skips = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
succeeded(): boolean {
|
|
||||||
return this.#successes.length > 0 && this.#failures.length === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
register(suites: TestGroup[]) {}
|
|
||||||
|
|
||||||
update(path: string[], result: TestResult) {
|
|
||||||
if (path.length === 0) throw new RangeError("path is empty");
|
|
||||||
const pathStr = path.join(SEP);
|
|
||||||
switch (result.state) {
|
|
||||||
case "success":
|
|
||||||
this.#successes.push({ path, ...result });
|
|
||||||
if (this.verbose) this.stream.writeln(`✅️ ${pathStr}`);
|
|
||||||
break;
|
|
||||||
case "failure":
|
|
||||||
this.#failures.push({ path, ...result });
|
|
||||||
this.stream.writeln(`⚠️ ${pathStr}`);
|
|
||||||
break;
|
|
||||||
case "skip":
|
|
||||||
this.#skips.push({ path, ...result });
|
|
||||||
this.stream.writeln(`⏭️ ${pathStr}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize() {
|
|
||||||
if (this.verbose) this.#displaySection("successes", this.#successes, true);
|
|
||||||
this.#displaySection("failures", this.#failures);
|
|
||||||
this.#displaySection("skips", this.#skips);
|
|
||||||
this.stream.writeln("");
|
|
||||||
this.stream.writeln(
|
|
||||||
`${this.#successes.length} succeeded, ${this.#failures.length} failed` +
|
|
||||||
(this.#skips.length ? `, ${this.#skips.length} skipped` : ""),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#displaySection(name: string, data: ({ path: string[] } & TestResult)[], ignoreEmpty: boolean = false) {
|
|
||||||
if (!data.length) return;
|
|
||||||
|
|
||||||
// Transform [{ path: ["a", "b", "c"] }, { path: ["a", "b", "d"] }]
|
|
||||||
// into { "a ❯ b": ["c", "d"] }.
|
|
||||||
let pathMap = new Map<string, ({ name: string } & TestResult)[]>();
|
|
||||||
for (const t of data) {
|
|
||||||
if (t.path.length === 0) throw new RangeError("path is empty");
|
|
||||||
const key = t.path.slice(0, -1).join(SEP);
|
|
||||||
if (!pathMap.has(key)) pathMap.set(key, []);
|
|
||||||
pathMap.get(key)!.push({ name: t.path.at(-1)!, ...t });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stream.writeln("");
|
|
||||||
this.stream.writeln(name.toUpperCase());
|
|
||||||
this.stream.writeln("=".repeat(name.length));
|
|
||||||
|
|
||||||
for (let [path, tests] of pathMap) {
|
|
||||||
if (ignoreEmpty) tests = tests.filter((t) => t.logs.length);
|
|
||||||
if (tests.length === 0) continue;
|
|
||||||
if (tests.length === 1) {
|
|
||||||
this.#writeOutput(tests[0], path ? `${path}${SEP}` : "", false);
|
|
||||||
} else {
|
|
||||||
this.stream.writeln(path);
|
|
||||||
for (const t of tests) this.#writeOutput(t, " - ", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#writeOutput(test: { name: string } & TestResult, prefix: string, nested: boolean) {
|
|
||||||
let output = "";
|
|
||||||
if (test.logs.length) {
|
|
||||||
// Individual logs might span multiple lines, so join them together
|
|
||||||
// then split it again.
|
|
||||||
const logStr = test.logs.join("\n");
|
|
||||||
const lines = logStr.split("\n");
|
|
||||||
if (lines.length <= 1) {
|
|
||||||
output = `: ${logStr}`;
|
|
||||||
} else {
|
|
||||||
const padding = nested ? " " : " ";
|
|
||||||
output = ":\n" + lines.map((line) => padding + line).join("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.stream.writeln(`${prefix}${test.name}${output}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertGetElementById(id: string): HTMLElement {
|
|
||||||
let elem = document.getElementById(id);
|
|
||||||
if (elem == null) throw new ReferenceError(`element with ID '${id}' missing from DOM`);
|
|
||||||
return elem;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DOMReporter implements Reporter {
|
|
||||||
register(suites: TestGroup[]) {}
|
|
||||||
|
|
||||||
update(path: string[], result: TestResult) {
|
|
||||||
if (path.length === 0) throw new RangeError("path is empty");
|
|
||||||
if (result.state === "skip") {
|
|
||||||
assertGetElementById("skip-counter-text").hidden = false;
|
|
||||||
}
|
|
||||||
const counter = assertGetElementById(`${result.state}-counter`);
|
|
||||||
counter.innerText = (Number(counter.innerText) + 1).toString();
|
|
||||||
|
|
||||||
let parent = assertGetElementById("root");
|
|
||||||
for (const node of path) {
|
|
||||||
let child: HTMLDetailsElement | null = null;
|
|
||||||
let summary: HTMLElement | null = null;
|
|
||||||
let d: Element;
|
|
||||||
outer: for (d of parent.children) {
|
|
||||||
if (!(d instanceof HTMLDetailsElement)) continue;
|
|
||||||
for (const s of d.children) {
|
|
||||||
if (!(s instanceof HTMLElement)) continue;
|
|
||||||
if (!(s.tagName === "SUMMARY" && s.innerText === node)) continue;
|
|
||||||
child = d;
|
|
||||||
summary = s;
|
|
||||||
break outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!child) {
|
|
||||||
child = document.createElement("details");
|
|
||||||
child.className = "test-node";
|
|
||||||
child.ariaRoleDescription = "test";
|
|
||||||
summary = document.createElement("summary");
|
|
||||||
summary.appendChild(document.createTextNode(node));
|
|
||||||
summary.ariaRoleDescription = "test name";
|
|
||||||
child.appendChild(summary);
|
|
||||||
parent.appendChild(child);
|
|
||||||
}
|
|
||||||
if (!summary) throw new Error("unreachable as assigned above");
|
|
||||||
|
|
||||||
switch (result.state) {
|
|
||||||
case "failure":
|
|
||||||
child.open = true;
|
|
||||||
child.classList.add("failure");
|
|
||||||
child.classList.remove("skip");
|
|
||||||
child.classList.remove("success");
|
|
||||||
// The summary marker does not appear in the AOM, so setting its
|
|
||||||
// alt text is fruitless; label the summary itself instead.
|
|
||||||
summary.setAttribute("aria-labelledby", "failure-description");
|
|
||||||
break;
|
|
||||||
case "skip":
|
|
||||||
if (child.classList.contains("failure")) break;
|
|
||||||
child.classList.add("skip");
|
|
||||||
child.classList.remove("success");
|
|
||||||
summary.setAttribute("aria-labelledby", "skip-description");
|
|
||||||
break;
|
|
||||||
case "success":
|
|
||||||
if (child.classList.contains("failure") || child.classList.contains("skip")) break;
|
|
||||||
child.classList.add("success");
|
|
||||||
summary.setAttribute("aria-labelledby", "success-description");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent = child;
|
|
||||||
}
|
|
||||||
|
|
||||||
const p = document.createElement("p");
|
|
||||||
p.classList.add("test-desc");
|
|
||||||
if (result.logs.length) {
|
|
||||||
const pre = document.createElement("pre");
|
|
||||||
pre.appendChild(document.createTextNode(result.logs.join("\n")));
|
|
||||||
p.appendChild(pre);
|
|
||||||
} else {
|
|
||||||
p.classList.add("italic");
|
|
||||||
p.appendChild(document.createTextNode("No output."));
|
|
||||||
}
|
|
||||||
parent.appendChild(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GoNode {
|
|
||||||
name: string;
|
|
||||||
subtests?: GoNode[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to display results via `go test`, via some glue code from the Go side.
|
|
||||||
export class GoTestReporter implements Reporter {
|
|
||||||
register(suites: TestGroup[]) {
|
|
||||||
console.log(JSON.stringify(suites.map(GoTestReporter.serialize)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a test tree into the one expected by the Go code.
|
|
||||||
static serialize(node: TestTree): GoNode {
|
|
||||||
return {
|
|
||||||
name: node.name,
|
|
||||||
subtests: "children" in node ? node.children.map(GoTestReporter.serialize) : undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
update(path: string[], result: TestResult) {
|
|
||||||
let state: number;
|
|
||||||
switch (result.state) {
|
|
||||||
case "success": state = 0; break;
|
|
||||||
case "failure": state = 1; break;
|
|
||||||
case "skip": state = 2; break;
|
|
||||||
}
|
|
||||||
console.log(JSON.stringify({ path, state, logs: result.logs }));
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize() {
|
|
||||||
console.log("null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* When updating the theme colors, also update them in success-closed.svg and
|
|
||||||
* success-open.svg!
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--bg: #d3d3d3;
|
|
||||||
--fg: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--bg: #2c2c2c;
|
|
||||||
--fg: ghostwhite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
background-color: var(--bg);
|
|
||||||
color: var(--fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, p, summary, noscript {
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
noscript {
|
|
||||||
font-size: 16pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
.root {
|
|
||||||
margin: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
details.test-node {
|
|
||||||
margin-left: 1rem;
|
|
||||||
padding: 0.2rem 0.5rem;
|
|
||||||
border-left: 2px dashed var(--fg);
|
|
||||||
> summary {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
&.success > summary::marker {
|
|
||||||
/*
|
|
||||||
* WebKit only supports color and font-size properties in ::marker [1], and
|
|
||||||
* its ::-webkit-details-marker only supports hiding the marker entirely
|
|
||||||
* [2], contrary to mdn's example [3]; thus, set a color as a fallback:
|
|
||||||
* while it may not be accessible for colorblind individuals, it's better
|
|
||||||
* than no indication of a test's state for anyone, as that there's no other
|
|
||||||
* way to include an indication in the marker on WebKit.
|
|
||||||
*
|
|
||||||
* [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/::marker#browser_compatibility
|
|
||||||
* [2]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/summary#default_style
|
|
||||||
* [3]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/summary#changing_the_summarys_icon
|
|
||||||
*/
|
|
||||||
color: var(--fg);
|
|
||||||
content: url("/test/success-closed.svg") / "success";
|
|
||||||
}
|
|
||||||
&.success[open] > summary::marker {
|
|
||||||
content: url("/test/success-open.svg") / "success";
|
|
||||||
}
|
|
||||||
&.failure > summary::marker {
|
|
||||||
color: red;
|
|
||||||
content: url("/test/failure-closed.svg") / "failure";
|
|
||||||
}
|
|
||||||
&.failure[open] > summary::marker {
|
|
||||||
content: url("/test/failure-open.svg") / "failure";
|
|
||||||
}
|
|
||||||
&.skip > summary::marker {
|
|
||||||
color: blue;
|
|
||||||
content: url("/test/skip-closed.svg") / "skip";
|
|
||||||
}
|
|
||||||
&.skip[open] > summary::marker {
|
|
||||||
content: url("/test/skip-open.svg") / "skip";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.test-desc {
|
|
||||||
margin: 0 0 0 1rem;
|
|
||||||
padding: 2px 0;
|
|
||||||
> pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.italic {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<link rel="stylesheet" href="/test/style.css">
|
|
||||||
<title>PkgServer Tests</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>
|
|
||||||
I hate JavaScript as much as you, but this page runs tests written in
|
|
||||||
JavaScript to test the functionality of code written in JavaScript, so it
|
|
||||||
wouldn't make sense for it to work without JavaScript. <strong>Please turn
|
|
||||||
JavaScript on!</strong>
|
|
||||||
</noscript>
|
|
||||||
|
|
||||||
<h1>PkgServer Tests</h1>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<p id="counters">
|
|
||||||
<span id="success-counter">0</span> succeeded, <span id="failure-counter">0</span>
|
|
||||||
failed<span id="skip-counter-text" hidden>, <span id="skip-counter">0</span> skipped</span>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p hidden id="success-description">Successful test</p>
|
|
||||||
<p hidden id="failure-description">Failed test</p>
|
|
||||||
<p hidden id="skip-description">Partially or fully skipped test</p>
|
|
||||||
|
|
||||||
<div id="root">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import "/test/all_tests.js";
|
|
||||||
import { DOMReporter, GLOBAL_REGISTRAR } from "/test/lib/test.js";
|
|
||||||
GLOBAL_REGISTRAR.run(new DOMReporter());
|
|
||||||
</script>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2024",
|
|
||||||
"strict": true,
|
|
||||||
"alwaysStrict": true,
|
|
||||||
"outDir": "static"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,11 +20,14 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
virtualisation = {
|
virtualisation = {
|
||||||
|
# Hopefully reduces spurious test failures:
|
||||||
|
memorySize = if pkgs.stdenv.hostPlatform.is32bit then 2046 else 8192;
|
||||||
|
|
||||||
diskSize = 6 * 1024;
|
diskSize = 6 * 1024;
|
||||||
|
|
||||||
qemu.options = [
|
qemu.options = [
|
||||||
# Increase test performance:
|
# Increase test performance:
|
||||||
"-smp 8"
|
"-smp 16"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ testers.nixosTest {
|
|||||||
# For go tests:
|
# For go tests:
|
||||||
(pkgs.writeShellScriptBin "sharefs-workload-hakurei-tests" ''
|
(pkgs.writeShellScriptBin "sharefs-workload-hakurei-tests" ''
|
||||||
cp -r "${self.packages.${system}.hakurei.src}" "/sdcard/hakurei" && cd "/sdcard/hakurei"
|
cp -r "${self.packages.${system}.hakurei.src}" "/sdcard/hakurei" && cd "/sdcard/hakurei"
|
||||||
${fhs}/bin/hakurei-fhs -c 'CC="clang -O3 -Werror" go test ./...'
|
${fhs}/bin/hakurei-fhs -c 'ROSA_SKIP_BINFMT=1 CC="clang -O3 -Werror" go test ./...'
|
||||||
'')
|
'')
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
// escapeBinfmt escapes magic/mask sequences in a [BinfmtEntry].
|
||||||
|
func escapeBinfmt(buf *strings.Builder, s string) string {
|
||||||
|
const lowerhex = "0123456789abcdef"
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
for _, c := range unsafe.Slice(unsafe.StringData(s), len(s)) {
|
||||||
|
switch c {
|
||||||
|
case 0, '\\', ':':
|
||||||
|
buf.WriteString(`\x`)
|
||||||
|
buf.WriteByte(lowerhex[c>>4])
|
||||||
|
buf.WriteByte(lowerhex[c&0xf])
|
||||||
|
|
||||||
|
default:
|
||||||
|
buf.WriteByte(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BinfmtEntry is an entry to be registered by the init process.
|
||||||
|
type BinfmtEntry struct {
|
||||||
|
// The offset of the magic/mask in the file, counted in bytes.
|
||||||
|
Offset byte
|
||||||
|
// The byte sequence binfmt_misc is matching for.
|
||||||
|
Magic string
|
||||||
|
// An (optional, defaults to all 0xff) mask.
|
||||||
|
Mask string
|
||||||
|
// The program that should be invoked with the binary as first argument.
|
||||||
|
Interpreter *check.Absolute
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns whether e can be registered into the kernel.
|
||||||
|
func (e *BinfmtEntry) Valid() bool {
|
||||||
|
return e != nil &&
|
||||||
|
int(e.Offset)+max(len(e.Magic), len(e.Mask)) < 128 &&
|
||||||
|
e.Interpreter != nil && len(e.Interpreter.String()) < 128
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/fhs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEscapeBinfmt(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
magic string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"packed DOS applications", "\x0eDEX", "\x0eDEX"},
|
||||||
|
|
||||||
|
{"riscv64 magic",
|
||||||
|
"\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00",
|
||||||
|
"\x7fELF\x02\x01\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\x02\\x00\xf3\\x00"},
|
||||||
|
{"riscv64 mask",
|
||||||
|
"\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
|
||||||
|
"\xff\xff\xff\xff\xff\xff\xff\\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got := escapeBinfmt(new(strings.Builder), tc.magic)
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("escapeBinfmt: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBinfmtEntry(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
e BinfmtEntry
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{"zero", BinfmtEntry{}, false},
|
||||||
|
{"large offset", BinfmtEntry{Offset: 128}, false},
|
||||||
|
{"long magic", BinfmtEntry{Magic: strings.Repeat("\x00", 128)}, false},
|
||||||
|
{"long mask", BinfmtEntry{Mask: strings.Repeat("\x00", 128)}, false},
|
||||||
|
{"valid", BinfmtEntry{Interpreter: fhs.AbsRoot}, true},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if tc.e.Valid() != tc.valid {
|
||||||
|
t.Errorf("Valid: %v", !tc.valid)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ const (
|
|||||||
CAP_SETPCAP = 0x8
|
CAP_SETPCAP = 0x8
|
||||||
CAP_NET_ADMIN = 0xc
|
CAP_NET_ADMIN = 0xc
|
||||||
CAP_DAC_OVERRIDE = 0x1
|
CAP_DAC_OVERRIDE = 0x1
|
||||||
|
CAP_SETFCAP = 0x1f
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|||||||
+35
-11
@@ -21,6 +21,7 @@ import (
|
|||||||
"hakurei.app/container/std"
|
"hakurei.app/container/std"
|
||||||
"hakurei.app/ext"
|
"hakurei.app/ext"
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
|
"hakurei.app/internal/landlock"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,6 +67,9 @@ type (
|
|||||||
// Copied to the underlying [exec.Cmd].
|
// Copied to the underlying [exec.Cmd].
|
||||||
WaitDelay time.Duration
|
WaitDelay time.Duration
|
||||||
|
|
||||||
|
// Suppress verbose output of init.
|
||||||
|
Quiet bool
|
||||||
|
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
msg message.Msg
|
msg message.Msg
|
||||||
@@ -87,12 +91,20 @@ type (
|
|||||||
// Time to wait for processes lingering after the initial process terminates.
|
// Time to wait for processes lingering after the initial process terminates.
|
||||||
AdoptWaitDelay time.Duration
|
AdoptWaitDelay time.Duration
|
||||||
|
|
||||||
|
// Map uid/gid 0 in the init process. Requires [FstypeProc] attached to
|
||||||
|
// [fhs.Proc] in the container filesystem.
|
||||||
|
InitAsRoot bool
|
||||||
// Mapped Uid in user namespace.
|
// Mapped Uid in user namespace.
|
||||||
Uid int
|
Uid int
|
||||||
// Mapped Gid in user namespace.
|
// Mapped Gid in user namespace.
|
||||||
Gid int
|
Gid int
|
||||||
// Hostname value in UTS namespace.
|
// Hostname value in UTS namespace.
|
||||||
Hostname string
|
Hostname string
|
||||||
|
// Register binfmt_misc entries.
|
||||||
|
Binfmt []BinfmtEntry
|
||||||
|
// Alternative pathname to attach binfmt_misc filesystem. The zero value
|
||||||
|
// requires [FstypeProc] to be made available at [fhs.Proc].
|
||||||
|
BinfmtPath *check.Absolute
|
||||||
// Sequential container setup ops.
|
// Sequential container setup ops.
|
||||||
*Ops
|
*Ops
|
||||||
|
|
||||||
@@ -212,6 +224,9 @@ func (p *Container) Start() error {
|
|||||||
if p.cmd.Process != nil {
|
if p.cmd.Process != nil {
|
||||||
return errors.New("container: already started")
|
return errors.New("container: already started")
|
||||||
}
|
}
|
||||||
|
if !p.InitAsRoot && len(p.Binfmt) > 0 {
|
||||||
|
return errors.New("container: init as root required, but not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
if err := ensureCloseOnExec(); err != nil {
|
if err := ensureCloseOnExec(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -282,6 +297,18 @@ func (p *Container) Start() error {
|
|||||||
if !p.HostNet {
|
if !p.HostNet {
|
||||||
p.cmd.SysProcAttr.Cloneflags |= CLONE_NEWNET
|
p.cmd.SysProcAttr.Cloneflags |= CLONE_NEWNET
|
||||||
}
|
}
|
||||||
|
if p.InitAsRoot {
|
||||||
|
p.cmd.SysProcAttr.AmbientCaps = append(p.cmd.SysProcAttr.AmbientCaps,
|
||||||
|
// mappings during init as root
|
||||||
|
CAP_SETFCAP,
|
||||||
|
)
|
||||||
|
|
||||||
|
if !p.SeccompDisable &&
|
||||||
|
len(p.SeccompRules) == 0 &&
|
||||||
|
p.SeccompPresets&std.PresetDenyNS != 0 {
|
||||||
|
return errors.New("container: as root requires late namespace creation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// place setup pipe before user supplied extra files, this is later restored by init
|
// place setup pipe before user supplied extra files, this is later restored by init
|
||||||
if r, w, err := os.Pipe(); err != nil {
|
if r, w, err := os.Pipe(); err != nil {
|
||||||
@@ -307,7 +334,7 @@ func (p *Container) Start() error {
|
|||||||
done <- func() error {
|
done <- func() error {
|
||||||
// PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes
|
// PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes
|
||||||
// created from the calling thread
|
// created from the calling thread
|
||||||
if err := SetNoNewPrivs(); err != nil {
|
if err := setNoNewPrivs(); err != nil {
|
||||||
return &StartError{
|
return &StartError{
|
||||||
Fatal: true,
|
Fatal: true,
|
||||||
Step: "prctl(PR_SET_NO_NEW_PRIVS)",
|
Step: "prctl(PR_SET_NO_NEW_PRIVS)",
|
||||||
@@ -317,12 +344,14 @@ func (p *Container) Start() error {
|
|||||||
|
|
||||||
// landlock: depends on per-thread state but acts on a process group
|
// landlock: depends on per-thread state but acts on a process group
|
||||||
{
|
{
|
||||||
rulesetAttr := &RulesetAttr{Scoped: LANDLOCK_SCOPE_SIGNAL}
|
rulesetAttr := &landlock.RulesetAttr{
|
||||||
|
Scoped: landlock.LANDLOCK_SCOPE_SIGNAL,
|
||||||
|
}
|
||||||
if !p.HostAbstract {
|
if !p.HostAbstract {
|
||||||
rulesetAttr.Scoped |= LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
|
rulesetAttr.Scoped |= landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
|
||||||
}
|
}
|
||||||
|
|
||||||
if abi, err := LandlockGetABI(); err != nil {
|
if abi, err := landlock.GetABI(); err != nil {
|
||||||
if p.HostAbstract || !p.HostNet {
|
if p.HostAbstract || !p.HostNet {
|
||||||
// landlock can be skipped here as it restricts access
|
// landlock can be skipped here as it restricts access
|
||||||
// to resources already covered by namespaces (pid, net)
|
// to resources already covered by namespaces (pid, net)
|
||||||
@@ -339,8 +368,6 @@ func (p *Container) Start() error {
|
|||||||
Err: ENOSYS,
|
Err: ENOSYS,
|
||||||
Origin: true,
|
Origin: true,
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
p.msg.Verbosef("landlock abi version %d", abi)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
||||||
@@ -350,8 +377,7 @@ func (p *Container) Start() error {
|
|||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
if err = landlock.RestrictSelf(rulesetFd, 0); err != nil {
|
||||||
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
|
|
||||||
_ = Close(rulesetFd)
|
_ = Close(rulesetFd)
|
||||||
return &StartError{
|
return &StartError{
|
||||||
Fatal: true,
|
Fatal: true,
|
||||||
@@ -407,7 +433,6 @@ func (p *Container) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.msg.Verbose("starting container init")
|
|
||||||
if err := p.cmd.Start(); err != nil {
|
if err := p.cmd.Start(); err != nil {
|
||||||
return &StartError{
|
return &StartError{
|
||||||
Step: "start container init",
|
Step: "start container init",
|
||||||
@@ -478,7 +503,6 @@ func (p *Container) Serve() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case <-done:
|
case <-done:
|
||||||
p.msg.Verbose("setup payload took", time.Since(t))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}(p.setup[1])
|
}(p.setup[1])
|
||||||
@@ -488,7 +512,7 @@ func (p *Container) Serve() (err error) {
|
|||||||
Getuid(),
|
Getuid(),
|
||||||
Getgid(),
|
Getgid(),
|
||||||
len(p.ExtraFiles),
|
len(p.ExtraFiles),
|
||||||
p.msg.IsVerbose(),
|
p.msg.IsVerbose() && !p.Quiet,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+205
-82
@@ -16,6 +16,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
@@ -26,6 +28,7 @@ import (
|
|||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/info"
|
"hakurei.app/internal/info"
|
||||||
|
"hakurei.app/internal/landlock"
|
||||||
"hakurei.app/internal/params"
|
"hakurei.app/internal/params"
|
||||||
"hakurei.app/ldd"
|
"hakurei.app/ldd"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
@@ -232,6 +235,9 @@ func earlyMnt(mnt ...*vfs.MountInfoEntry) func(*testing.T, context.Context) []*v
|
|||||||
return func(*testing.T, context.Context) []*vfs.MountInfoEntry { return mnt }
|
return func(*testing.T, context.Context) []*vfs.MountInfoEntry { return mnt }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname toHost hakurei.app/container.toHost
|
||||||
|
func toHost(name string) string
|
||||||
|
|
||||||
var containerTestCases = []struct {
|
var containerTestCases = []struct {
|
||||||
name string
|
name string
|
||||||
filter bool
|
filter bool
|
||||||
@@ -331,13 +337,15 @@ var containerTestCases = []struct {
|
|||||||
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
||||||
return []*vfs.MountInfoEntry{
|
return []*vfs.MountInfoEntry{
|
||||||
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
||||||
"rw,lowerdir="+
|
"rw"+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
|
",lowerdir+="+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+
|
||||||
|
",lowerdir+="+
|
||||||
|
toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
||||||
",upperdir="+
|
",upperdir="+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("upper")).(*check.Absolute).String())+
|
toHost(ctx.Value(testVal("upper")).(*check.Absolute).String())+
|
||||||
",workdir="+
|
",workdir="+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("work")).(*check.Absolute).String())+
|
toHost(ctx.Value(testVal("work")).(*check.Absolute).String())+
|
||||||
",redirect_dir=nofollow,uuid=on,userxattr"),
|
",redirect_dir=nofollow,uuid=on,userxattr"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -387,9 +395,11 @@ var containerTestCases = []struct {
|
|||||||
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
||||||
return []*vfs.MountInfoEntry{
|
return []*vfs.MountInfoEntry{
|
||||||
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
||||||
"ro,lowerdir="+
|
"ro"+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
|
",lowerdir+="+
|
||||||
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+
|
||||||
|
",lowerdir+="+
|
||||||
|
toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
||||||
",redirect_dir=nofollow,userxattr"),
|
",redirect_dir=nofollow,userxattr"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -399,39 +409,11 @@ var containerTestCases = []struct {
|
|||||||
func TestContainer(t *testing.T) {
|
func TestContainer(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) {
|
var suffix string
|
||||||
wantErr := context.Canceled
|
runTests:
|
||||||
wantExitCode := 0
|
|
||||||
if err := c.Wait(); !reflect.DeepEqual(err, wantErr) {
|
|
||||||
if m, ok := container.InternalMessageFromError(err); ok {
|
|
||||||
t.Error(m)
|
|
||||||
}
|
|
||||||
t.Errorf("Wait: error = %#v, want %#v", err, wantErr)
|
|
||||||
}
|
|
||||||
if ps := c.ProcessState(); ps == nil {
|
|
||||||
t.Errorf("ProcessState unexpectedly returned nil")
|
|
||||||
} else if code := ps.ExitCode(); code != wantExitCode {
|
|
||||||
t.Errorf("ExitCode: %d, want %d", code, wantExitCode)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
t.Run("forward", testContainerCancel(func(c *container.Container) {
|
|
||||||
c.ForwardCancel = true
|
|
||||||
}, func(t *testing.T, c *container.Container) {
|
|
||||||
var exitError *exec.ExitError
|
|
||||||
if err := c.Wait(); !errors.As(err, &exitError) {
|
|
||||||
if m, ok := container.InternalMessageFromError(err); ok {
|
|
||||||
t.Error(m)
|
|
||||||
}
|
|
||||||
t.Errorf("Wait: error = %v", err)
|
|
||||||
}
|
|
||||||
if code := exitError.ExitCode(); code != blockExitCodeInterrupt {
|
|
||||||
t.Errorf("ExitCode: %d, want %d", code, blockExitCodeInterrupt)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
for i, tc := range containerTestCases {
|
for i, tc := range containerTestCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
_suffix := suffix
|
||||||
|
t.Run(tc.name+_suffix, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantOps, wantOpsCtx := tc.ops(t)
|
wantOps, wantOpsCtx := tc.ops(t)
|
||||||
@@ -455,8 +437,10 @@ func TestContainer(t *testing.T) {
|
|||||||
c.SeccompDisable = !tc.filter
|
c.SeccompDisable = !tc.filter
|
||||||
c.RetainSession = tc.session
|
c.RetainSession = tc.session
|
||||||
c.HostNet = tc.net
|
c.HostNet = tc.net
|
||||||
|
c.InitAsRoot = _suffix != ""
|
||||||
|
c.Env = append(c.Env, "HAKUREI_TEST_SUFFIX="+_suffix)
|
||||||
if info.CanDegrade {
|
if info.CanDegrade {
|
||||||
if _, err := container.LandlockGetABI(); err != nil {
|
if _, err := landlock.GetABI(); err != nil {
|
||||||
if !errors.Is(err, syscall.ENOSYS) {
|
if !errors.Is(err, syscall.ENOSYS) {
|
||||||
t.Fatalf("LandlockGetABI: error = %v", err)
|
t.Fatalf("LandlockGetABI: error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -464,6 +448,9 @@ func TestContainer(t *testing.T) {
|
|||||||
t.Log("Landlock LSM is unavailable, enabling HostAbstract")
|
t.Log("Landlock LSM is unavailable, enabling HostAbstract")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if c.InitAsRoot {
|
||||||
|
c.SeccompPresets &= ^std.PresetDenyNS
|
||||||
|
}
|
||||||
|
|
||||||
c.
|
c.
|
||||||
Readonly(check.MustAbs(pathReadonly), 0755).
|
Readonly(check.MustAbs(pathReadonly), 0755).
|
||||||
@@ -532,6 +519,11 @@ func TestContainer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if suffix == "" {
|
||||||
|
suffix = " as root"
|
||||||
|
goto runTests
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ent(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoEntry {
|
func ent(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoEntry {
|
||||||
@@ -554,49 +546,118 @@ func hostnameFromTestCase(name string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testContainerCancel(
|
func testContainerCancel(
|
||||||
|
t *testing.T,
|
||||||
containerExtra func(c *container.Container),
|
containerExtra func(c *container.Container),
|
||||||
waitCheck func(t *testing.T, c *container.Container),
|
waitCheck func(ps *os.ProcessState, waitErr error),
|
||||||
) func(t *testing.T) {
|
) {
|
||||||
return func(t *testing.T) {
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
t.Parallel()
|
|
||||||
ctx, cancel := context.WithCancel(t.Context())
|
|
||||||
|
|
||||||
c := helperNewContainer(ctx, "block")
|
c := helperNewContainer(ctx, "block")
|
||||||
c.Stdout, c.Stderr = os.Stdout, os.Stderr
|
c.Stdout, c.Stderr = os.Stdout, os.Stderr
|
||||||
if containerExtra != nil {
|
if containerExtra != nil {
|
||||||
containerExtra(c)
|
containerExtra(c)
|
||||||
}
|
|
||||||
|
|
||||||
ready := make(chan struct{})
|
|
||||||
if r, w, err := os.Pipe(); err != nil {
|
|
||||||
t.Fatalf("cannot pipe: %v", err)
|
|
||||||
} else {
|
|
||||||
c.ExtraFiles = append(c.ExtraFiles, w)
|
|
||||||
go func() {
|
|
||||||
defer close(ready)
|
|
||||||
if _, err = r.Read(make([]byte, 1)); err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Start(); err != nil {
|
|
||||||
if m, ok := container.InternalMessageFromError(err); ok {
|
|
||||||
t.Fatal(m)
|
|
||||||
} else {
|
|
||||||
t.Fatalf("cannot start container: %v", err)
|
|
||||||
}
|
|
||||||
} else if err = c.Serve(); err != nil {
|
|
||||||
if m, ok := container.InternalMessageFromError(err); ok {
|
|
||||||
t.Error(m)
|
|
||||||
} else {
|
|
||||||
t.Errorf("cannot serve setup params: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<-ready
|
|
||||||
cancel()
|
|
||||||
waitCheck(t, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ready := make(chan struct{})
|
||||||
|
var waitErr error
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot pipe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ExtraFiles = append(c.ExtraFiles, w)
|
||||||
|
go func() {
|
||||||
|
defer close(ready)
|
||||||
|
if _, _err := r.Read(make([]byte, 1)); _err != nil {
|
||||||
|
panic(_err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = c.Start(); err != nil {
|
||||||
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
|
t.Fatal(m)
|
||||||
|
} else {
|
||||||
|
t.Fatalf("cannot start container: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
waitErr = c.Wait()
|
||||||
|
_ = r.SetReadDeadline(time.Now())
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = c.Serve(); err != nil {
|
||||||
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
|
t.Error(m)
|
||||||
|
} else {
|
||||||
|
t.Errorf("cannot serve setup params: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<-ready
|
||||||
|
cancel()
|
||||||
|
<-done
|
||||||
|
waitCheck(c.ProcessState(), waitErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForward(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
f := func(ps *os.ProcessState, waitErr error) {
|
||||||
|
var exitError *exec.ExitError
|
||||||
|
if !errors.As(waitErr, &exitError) {
|
||||||
|
if m, ok := container.InternalMessageFromError(waitErr); ok {
|
||||||
|
t.Error(m)
|
||||||
|
}
|
||||||
|
t.Errorf("Wait: error = %v", waitErr)
|
||||||
|
}
|
||||||
|
if code := exitError.ExitCode(); code != blockExitCodeInterrupt {
|
||||||
|
t.Errorf("ExitCode: %d, want %d", code, blockExitCodeInterrupt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Run("direct", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testContainerCancel(t, func(c *container.Container) {
|
||||||
|
c.ForwardCancel = true
|
||||||
|
}, f)
|
||||||
|
})
|
||||||
|
t.Run("as root", func(t *testing.T) {
|
||||||
|
testContainerCancel(t, func(c *container.Container) {
|
||||||
|
c.ForwardCancel = true
|
||||||
|
c.InitAsRoot = true
|
||||||
|
c.Proc(fhs.AbsProc)
|
||||||
|
}, f)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCancel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
f := func(ps *os.ProcessState, waitErr error) {
|
||||||
|
wantErr := context.Canceled
|
||||||
|
if !reflect.DeepEqual(waitErr, wantErr) {
|
||||||
|
if m, ok := container.InternalMessageFromError(waitErr); ok {
|
||||||
|
t.Error(m)
|
||||||
|
}
|
||||||
|
t.Errorf("Wait: error = %#v, want %#v", waitErr, wantErr)
|
||||||
|
}
|
||||||
|
if ps == nil {
|
||||||
|
t.Errorf("ProcessState unexpectedly returned nil")
|
||||||
|
} else if code := ps.ExitCode(); code != 0 {
|
||||||
|
t.Errorf("ExitCode: %d, want %d", code, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Run("direct", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testContainerCancel(t, nil, f)
|
||||||
|
})
|
||||||
|
t.Run("as root", func(t *testing.T) {
|
||||||
|
testContainerCancel(t, func(c *container.Container) {
|
||||||
|
c.InitAsRoot = true
|
||||||
|
c.Proc(fhs.AbsProc)
|
||||||
|
}, f)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContainerString(t *testing.T) {
|
func TestContainerString(t *testing.T) {
|
||||||
@@ -632,6 +693,8 @@ func init() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
c.Command("container", command.UsageInternal, func(args []string) error {
|
c.Command("container", command.UsageInternal, func(args []string) error {
|
||||||
|
asRoot := os.Getenv("HAKUREI_TEST_SUFFIX") == " as root"
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return syscall.EINVAL
|
return syscall.EINVAL
|
||||||
}
|
}
|
||||||
@@ -649,6 +712,66 @@ func init() {
|
|||||||
return fmt.Errorf("gid: %d, want %d", gid, tc.gid)
|
return fmt.Errorf("gid: %d, want %d", gid, tc.gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// no attack surface increase during as root due to no_new_privs
|
||||||
|
var wantBounding uintptr = 1
|
||||||
|
asRootNot := " not"
|
||||||
|
if !asRoot {
|
||||||
|
wantBounding = 0
|
||||||
|
asRootNot = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
PR_CAP_AMBIENT = 0x2f
|
||||||
|
PR_CAP_AMBIENT_IS_SET = 0x1
|
||||||
|
)
|
||||||
|
for i := range container.LastCap(nil) + 1 {
|
||||||
|
r, _, errno := syscall.Syscall(
|
||||||
|
syscall.SYS_PRCTL,
|
||||||
|
PR_CAP_AMBIENT,
|
||||||
|
PR_CAP_AMBIENT_IS_SET,
|
||||||
|
i,
|
||||||
|
)
|
||||||
|
if errno != 0 {
|
||||||
|
return os.NewSyscallError("prctl", errno)
|
||||||
|
}
|
||||||
|
if r != 0 {
|
||||||
|
return fmt.Errorf("capability %d in ambient set", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, errno = syscall.Syscall(
|
||||||
|
syscall.SYS_PRCTL,
|
||||||
|
syscall.PR_CAPBSET_READ,
|
||||||
|
i,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if errno != 0 {
|
||||||
|
return os.NewSyscallError("prctl", errno)
|
||||||
|
}
|
||||||
|
if r != wantBounding {
|
||||||
|
return fmt.Errorf("capability %d%s in bounding set", i, asRootNot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _LINUX_CAPABILITY_VERSION_3 = 0x20080522
|
||||||
|
var capData struct {
|
||||||
|
effective uint32
|
||||||
|
permitted uint32
|
||||||
|
inheritable uint32
|
||||||
|
}
|
||||||
|
if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&struct {
|
||||||
|
version uint32
|
||||||
|
pid int32
|
||||||
|
}{_LINUX_CAPABILITY_VERSION_3, 0})), uintptr(unsafe.Pointer(&capData)), 0); errno != 0 {
|
||||||
|
return os.NewSyscallError("capget", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
if max(capData.effective, capData.permitted, capData.inheritable) != 0 {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"effective = %d, permitted = %d, inheritable = %d",
|
||||||
|
capData.effective, capData.permitted, capData.inheritable,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
wantHost := hostnameFromTestCase(tc.name)
|
wantHost := hostnameFromTestCase(tc.name)
|
||||||
if host, err := os.Hostname(); err != nil {
|
if host, err := os.Hostname(); err != nil {
|
||||||
return fmt.Errorf("cannot get hostname: %v", err)
|
return fmt.Errorf("cannot get hostname: %v", err)
|
||||||
@@ -766,7 +889,7 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
c.MustParse(os.Args[1:], func(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ type syscallDispatcher interface {
|
|||||||
remount(msg message.Msg, target string, flags uintptr) error
|
remount(msg message.Msg, target string, flags uintptr) error
|
||||||
// mountTmpfs provides mountTmpfs.
|
// mountTmpfs provides mountTmpfs.
|
||||||
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
|
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
|
||||||
|
// mountOverlay provides mountOverlay.
|
||||||
|
mountOverlay(target string, options [][2]string) error
|
||||||
// ensureFile provides ensureFile.
|
// ensureFile provides ensureFile.
|
||||||
ensureFile(name string, perm, pperm os.FileMode) error
|
ensureFile(name string, perm, pperm os.FileMode) error
|
||||||
// mustLoopback provides mustLoopback.
|
// mustLoopback provides mustLoopback.
|
||||||
@@ -148,7 +150,7 @@ func (direct) lockOSThread() { runtime.LockOSThread() }
|
|||||||
|
|
||||||
func (direct) setPtracer(pid uintptr) error { return ext.SetPtracer(pid) }
|
func (direct) setPtracer(pid uintptr) error { return ext.SetPtracer(pid) }
|
||||||
func (direct) setDumpable(dumpable uintptr) error { return ext.SetDumpable(dumpable) }
|
func (direct) setDumpable(dumpable uintptr) error { return ext.SetDumpable(dumpable) }
|
||||||
func (direct) setNoNewPrivs() error { return SetNoNewPrivs() }
|
func (direct) setNoNewPrivs() error { return setNoNewPrivs() }
|
||||||
|
|
||||||
func (direct) lastcap(msg message.Msg) uintptr { return LastCap(msg) }
|
func (direct) lastcap(msg message.Msg) uintptr { return LastCap(msg) }
|
||||||
func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) }
|
func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) }
|
||||||
@@ -169,6 +171,9 @@ func (direct) remount(msg message.Msg, target string, flags uintptr) error {
|
|||||||
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
|
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
|
||||||
return mountTmpfs(k, fsname, target, flags, size, perm)
|
return mountTmpfs(k, fsname, target, flags, size, perm)
|
||||||
}
|
}
|
||||||
|
func (k direct) mountOverlay(target string, options [][2]string) error {
|
||||||
|
return mountOverlay(target, options)
|
||||||
|
}
|
||||||
func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
|
func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
|
||||||
return ensureFile(name, perm, pperm)
|
return ensureFile(name, perm, pperm)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -468,6 +468,14 @@ func (k *kstub) mountTmpfs(fsname, target string, flags uintptr, size int, perm
|
|||||||
stub.CheckArg(k.Stub, "perm", perm, 4))
|
stub.CheckArg(k.Stub, "perm", perm, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *kstub) mountOverlay(target string, options [][2]string) error {
|
||||||
|
k.Helper()
|
||||||
|
return k.Expects("mountOverlay").Error(
|
||||||
|
stub.CheckArg(k.Stub, "target", target, 0),
|
||||||
|
stub.CheckArgReflect(k.Stub, "options", options, 1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
|
func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
return k.Expects("ensureFile").Error(
|
return k.Expects("ensureFile").Error(
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
|||||||
|
|
||||||
// mount wraps syscall.Mount for error handling.
|
// mount wraps syscall.Mount for error handling.
|
||||||
func mount(source, target, fstype string, flags uintptr, data string) error {
|
func mount(source, target, fstype string, flags uintptr, data string) error {
|
||||||
|
if max(len(source), len(target), len(data))+1 > os.Getpagesize() {
|
||||||
|
return &MountError{source, target, fstype, flags, data, syscall.ENOMEM}
|
||||||
|
}
|
||||||
|
|
||||||
err := syscall.Mount(source, target, fstype, flags, data)
|
err := syscall.Mount(source, target, fstype, flags, data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+106
-23
@@ -11,11 +11,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/ext"
|
"hakurei.app/ext"
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
@@ -182,23 +184,33 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uid, gid := param.Uid, param.Gid
|
||||||
|
if param.InitAsRoot {
|
||||||
|
uid, gid = 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
// write uid/gid map here so parent does not need to set dumpable
|
// write uid/gid map here so parent does not need to set dumpable
|
||||||
if err := k.setDumpable(ext.SUID_DUMP_USER); err != nil {
|
if err := k.setDumpable(ext.SUID_DUMP_USER); err != nil {
|
||||||
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
|
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
|
||||||
}
|
}
|
||||||
if err := k.writeFile(fhs.Proc+"self/uid_map",
|
if err := k.writeFile(
|
||||||
append([]byte{}, strconv.Itoa(param.Uid)+" "+strconv.Itoa(param.HostUid)+" 1\n"...),
|
fhs.Proc+"self/uid_map",
|
||||||
0); err != nil {
|
[]byte(strconv.Itoa(uid)+" "+strconv.Itoa(param.HostUid)+" 1\n"),
|
||||||
|
0,
|
||||||
|
); err != nil {
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
if err := k.writeFile(fhs.Proc+"self/setgroups",
|
if err := k.writeFile(
|
||||||
|
fhs.Proc+"self/setgroups",
|
||||||
[]byte("deny\n"),
|
[]byte("deny\n"),
|
||||||
0); err != nil && !os.IsNotExist(err) {
|
0,
|
||||||
|
); err != nil && !os.IsNotExist(err) {
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
if err := k.writeFile(fhs.Proc+"self/gid_map",
|
if err := k.writeFile(fhs.Proc+"self/gid_map",
|
||||||
append([]byte{}, strconv.Itoa(param.Gid)+" "+strconv.Itoa(param.HostGid)+" 1\n"...),
|
[]byte(strconv.Itoa(gid)+" "+strconv.Itoa(param.HostGid)+" 1\n"),
|
||||||
0); err != nil {
|
0,
|
||||||
|
); err != nil {
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
if err := k.setDumpable(ext.SUID_DUMP_DISABLE); err != nil {
|
if err := k.setDumpable(ext.SUID_DUMP_DISABLE); err != nil {
|
||||||
@@ -223,6 +235,23 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
state := &setupState{process: make(map[int]WaitStatus), Params: ¶m.Params, Msg: msg, Context: ctx}
|
state := &setupState{process: make(map[int]WaitStatus), Params: ¶m.Params, Msg: msg, Context: ctx}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
|
||||||
|
k.fatalf(msg, "cannot mount intermediate root: %v", optionalErrorUnwrap(err))
|
||||||
|
}
|
||||||
|
if err := k.chdir(intermediateHostPath); err != nil {
|
||||||
|
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(param.Binfmt) > 0 {
|
||||||
|
for i, e := range param.Binfmt {
|
||||||
|
if pathname, err := k.evalSymlinks(e.Interpreter.String()); err != nil {
|
||||||
|
k.fatal(msg, err)
|
||||||
|
} else if param.Binfmt[i].Interpreter, err = check.NewAbs(pathname); err != nil {
|
||||||
|
k.fatal(msg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* early is called right before pivot_root into intermediate root;
|
/* early is called right before pivot_root into intermediate root;
|
||||||
this step is mostly for gathering information that would otherwise be
|
this step is mostly for gathering information that would otherwise be
|
||||||
difficult to obtain via library functions after pivot_root, and
|
difficult to obtain via library functions after pivot_root, and
|
||||||
@@ -242,13 +271,6 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
|
|
||||||
k.fatalf(msg, "cannot mount intermediate root: %v", optionalErrorUnwrap(err))
|
|
||||||
}
|
|
||||||
if err := k.chdir(intermediateHostPath); err != nil {
|
|
||||||
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := k.mkdir(sysrootDir, 0755); err != nil {
|
if err := k.mkdir(sysrootDir, 0755); err != nil {
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
@@ -285,6 +307,48 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(param.Binfmt) > 0 {
|
||||||
|
const interpreter = "/interpreter"
|
||||||
|
|
||||||
|
if param.BinfmtPath == nil {
|
||||||
|
param.BinfmtPath = fhs.AbsProcSys.Append("fs/binfmt_misc")
|
||||||
|
}
|
||||||
|
binfmt := sysrootPath + param.BinfmtPath.String()
|
||||||
|
if err := k.mkdirAll(binfmt, 0); err != nil {
|
||||||
|
k.fatal(msg, err)
|
||||||
|
}
|
||||||
|
if err := k.mount(
|
||||||
|
SourceBinfmtMisc,
|
||||||
|
binfmt,
|
||||||
|
FstypeBinfmtMisc,
|
||||||
|
MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||||
|
zeroString,
|
||||||
|
); err != nil {
|
||||||
|
k.fatal(msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
buf.Grow(1920)
|
||||||
|
|
||||||
|
register := binfmt + "/register"
|
||||||
|
for i, e := range param.Binfmt {
|
||||||
|
if err := k.symlink(hostPath+e.Interpreter.String(), interpreter); err != nil {
|
||||||
|
k.fatal(msg, err)
|
||||||
|
} else if err = k.writeFile(register, []byte(":"+
|
||||||
|
strconv.Itoa(i)+":"+
|
||||||
|
"M:"+
|
||||||
|
strconv.Itoa(int(e.Offset))+":"+
|
||||||
|
escapeBinfmt(&buf, e.Magic)+":"+
|
||||||
|
escapeBinfmt(&buf, e.Mask)+":"+
|
||||||
|
interpreter+":"+
|
||||||
|
"F"), 0); err != nil {
|
||||||
|
k.fatal(msg, err)
|
||||||
|
} else if err = k.remove(interpreter); err != nil {
|
||||||
|
k.fatal(msg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// setup requiring host root complete at this point
|
// setup requiring host root complete at this point
|
||||||
if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil {
|
if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil {
|
||||||
k.fatalf(msg, "cannot make host root rprivate: %v", optionalErrorUnwrap(err))
|
k.fatalf(msg, "cannot make host root rprivate: %v", optionalErrorUnwrap(err))
|
||||||
@@ -323,11 +387,19 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var keepCaps []uintptr
|
||||||
|
if param.Privileged {
|
||||||
|
keepCaps = append(keepCaps, CAP_SYS_ADMIN, CAP_SETPCAP)
|
||||||
|
}
|
||||||
|
if param.InitAsRoot {
|
||||||
|
keepCaps = append(keepCaps, CAP_SETFCAP)
|
||||||
|
}
|
||||||
|
|
||||||
if err := k.capAmbientClearAll(); err != nil {
|
if err := k.capAmbientClearAll(); err != nil {
|
||||||
k.fatalf(msg, "cannot clear the ambient capability set: %v", err)
|
k.fatalf(msg, "cannot clear the ambient capability set: %v", err)
|
||||||
}
|
}
|
||||||
for i := uintptr(0); i <= lastcap; i++ {
|
for i := range lastcap + 1 {
|
||||||
if param.Privileged && i == CAP_SYS_ADMIN {
|
if slices.Contains(keepCaps, i) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := k.capBoundingSetDrop(i); err != nil {
|
if err := k.capBoundingSetDrop(i); err != nil {
|
||||||
@@ -336,20 +408,23 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var keep [2]uint32
|
var keep [2]uint32
|
||||||
if param.Privileged {
|
for _, c := range keepCaps {
|
||||||
keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN)
|
keep[capToIndex(c)] |= capToMask(c)
|
||||||
|
|
||||||
if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil {
|
|
||||||
k.fatalf(msg, "cannot raise CAP_SYS_ADMIN: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.capset(
|
if err := k.capset(
|
||||||
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
|
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
|
||||||
&[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}},
|
&[2]capData{{keep[0], keep[0], keep[0]}, {keep[1], keep[1], keep[1]}},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
k.fatalf(msg, "cannot capset: %v", err)
|
k.fatalf(msg, "cannot capset: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, c := range keepCaps {
|
||||||
|
if err := k.capAmbientRaise(c); err != nil {
|
||||||
|
k.fatalf(msg, "cannot raise %#x: %v", c, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !param.SeccompDisable {
|
if !param.SeccompDisable {
|
||||||
rules := param.SeccompRules
|
rules := param.SeccompRules
|
||||||
if len(rules) == 0 { // non-empty rules slice always overrides presets
|
if len(rules) == 0 { // non-empty rules slice always overrides presets
|
||||||
@@ -474,6 +549,14 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
cmd.ExtraFiles = extraFiles
|
cmd.ExtraFiles = extraFiles
|
||||||
cmd.Dir = param.Dir.String()
|
cmd.Dir = param.Dir.String()
|
||||||
|
|
||||||
|
if param.InitAsRoot {
|
||||||
|
cmd.SysProcAttr = &SysProcAttr{
|
||||||
|
Cloneflags: CLONE_NEWUSER,
|
||||||
|
UidMappings: []SysProcIDMap{{ContainerID: param.Uid, HostID: 0, Size: 1}},
|
||||||
|
GidMappings: []SysProcIDMap{{ContainerID: param.Gid, HostID: 0, Size: 1}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
msg.Verbosef("starting initial process %s", param.Path)
|
msg.Verbosef("starting initial process %s", param.Path)
|
||||||
if err := k.start(cmd); err != nil {
|
if err := k.start(cmd); err != nil {
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
|
|||||||
+73
-73
@@ -332,6 +332,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
@@ -370,6 +372,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
@@ -408,6 +412,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", stub.UniqueError(61)),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", stub.UniqueError(61)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot prepare op at index %d: %v", []any{0, stub.UniqueError(61)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot prepare op at index %d: %v", []any{0, stub.UniqueError(61)}}, nil, nil),
|
||||||
@@ -447,6 +453,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", &os.PathError{Op: "readlink", Path: "/", Err: stub.UniqueError(60)}),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", &os.PathError{Op: "readlink", Path: "/", Err: stub.UniqueError(60)}),
|
||||||
call("fatal", stub.ExpectArgs{[]any{"cannot readlink /: unique error 60 injected by the test suite"}}, nil, nil),
|
call("fatal", stub.ExpectArgs{[]any{"cannot readlink /: unique error 60 injected by the test suite"}}, nil, nil),
|
||||||
@@ -486,9 +494,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
/* begin early */
|
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
|
||||||
/* end early */
|
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, stub.UniqueError(58)),
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, stub.UniqueError(58)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot mount intermediate root: %v", []any{stub.UniqueError(58)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot mount intermediate root: %v", []any{stub.UniqueError(58)}}, nil, nil),
|
||||||
},
|
},
|
||||||
@@ -526,9 +531,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
/* begin early */
|
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
|
||||||
/* end early */
|
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, stub.UniqueError(56)),
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, stub.UniqueError(56)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot enter intermediate host path: %v", []any{stub.UniqueError(56)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot enter intermediate host path: %v", []any{stub.UniqueError(56)}}, nil, nil),
|
||||||
@@ -567,11 +569,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, stub.UniqueError(54)),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, stub.UniqueError(54)),
|
||||||
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(54)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(54)}}, nil, nil),
|
||||||
},
|
},
|
||||||
@@ -609,11 +611,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, stub.UniqueError(52)),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, stub.UniqueError(52)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot bind sysroot: %v", []any{stub.UniqueError(52)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot bind sysroot: %v", []any{stub.UniqueError(52)}}, nil, nil),
|
||||||
@@ -652,11 +654,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, stub.UniqueError(50)),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, stub.UniqueError(50)),
|
||||||
@@ -696,11 +698,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -741,11 +743,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -787,11 +789,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -842,11 +844,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -897,11 +899,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -953,11 +955,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1010,11 +1012,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1069,11 +1071,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1129,11 +1131,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1190,11 +1192,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1252,11 +1254,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1315,11 +1317,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1379,11 +1381,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1444,11 +1446,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1510,11 +1512,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1584,11 +1586,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1622,7 +1624,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
||||||
@@ -1654,8 +1655,9 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
||||||
|
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, nil),
|
||||||
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, stub.UniqueError(19)),
|
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, stub.UniqueError(19)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot raise CAP_SYS_ADMIN: %v", []any{stub.UniqueError(19)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot raise %#x: %v", []any{uintptr(0x15), stub.UniqueError(19)}}, nil, nil),
|
||||||
},
|
},
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
@@ -1691,11 +1693,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1729,7 +1731,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
||||||
@@ -1761,8 +1762,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
||||||
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
|
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, stub.UniqueError(17)),
|
||||||
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0, 0x200000, 0x200000}, {0, 0, 0}}}, nil, stub.UniqueError(17)),
|
|
||||||
call("fatalf", stub.ExpectArgs{"cannot capset: %v", []any{stub.UniqueError(17)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot capset: %v", []any{stub.UniqueError(17)}}, nil, nil),
|
||||||
},
|
},
|
||||||
}, nil},
|
}, nil},
|
||||||
@@ -1799,11 +1799,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1837,7 +1837,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
||||||
@@ -1869,8 +1868,9 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
||||||
|
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, nil),
|
||||||
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
|
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
|
||||||
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0, 0x200000, 0x200000}, {0, 0, 0}}}, nil, nil),
|
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil),
|
||||||
call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, stub.UniqueError(15)),
|
call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, stub.UniqueError(15)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot load syscall filter: %v", []any{stub.UniqueError(15)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot load syscall filter: %v", []any{stub.UniqueError(15)}}, nil, nil),
|
||||||
@@ -1908,11 +1908,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2032,11 +2032,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2132,11 +2132,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2232,11 +2232,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2323,11 +2323,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2418,11 +2418,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2520,11 +2520,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2659,11 +2659,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2697,7 +2697,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
||||||
@@ -2729,8 +2728,9 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
||||||
|
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, nil),
|
||||||
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
|
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
|
||||||
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0, 0x200000, 0x200000}, {0, 0, 0}}}, nil, nil),
|
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil),
|
||||||
call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, nil),
|
call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"%d filter rules loaded", []any{73}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"%d filter rules loaded", []any{73}}, nil, nil),
|
||||||
|
|||||||
+40
-12
@@ -4,9 +4,9 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/ext"
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
|
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.upper = check.EscapeOverlayDataSegment(toHost(v))
|
o.upper = toHost(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
|
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.work = check.EscapeOverlayDataSegment(toHost(v))
|
o.work = toHost(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,12 +168,39 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(a.String()); err != nil {
|
if v, err := k.evalSymlinks(a.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.lower[i] = check.EscapeOverlayDataSegment(toHost(v))
|
o.lower[i] = toHost(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mountOverlay sets up an overlay mount via [ext.FS].
|
||||||
|
func mountOverlay(target string, options [][2]string) error {
|
||||||
|
fs, err := ext.OpenFS(SourceOverlay, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = fs.SetString("source", SourceOverlay); err != nil {
|
||||||
|
_ = fs.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
if err = fs.SetString(option[0], option[1]); err != nil {
|
||||||
|
_ = fs.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = fs.SetFlag(OptionOverlayUserxattr); err != nil {
|
||||||
|
_ = fs.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = fs.Mount(target, 0); err != nil {
|
||||||
|
_ = fs.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fs.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
target := o.Target.String()
|
target := o.Target.String()
|
||||||
if !o.noPrefix {
|
if !o.noPrefix {
|
||||||
@@ -194,7 +221,7 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options := make([]string, 0, 4)
|
options := make([][2]string, 0, 2+len(o.lower))
|
||||||
|
|
||||||
if o.upper == zeroString && o.work == zeroString { // readonly
|
if o.upper == zeroString && o.work == zeroString { // readonly
|
||||||
if len(o.Lower) < 2 {
|
if len(o.Lower) < 2 {
|
||||||
@@ -205,15 +232,16 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
if len(o.Lower) == 0 {
|
if len(o.Lower) == 0 {
|
||||||
return &OverlayArgumentError{OverlayEmptyLower, zeroString}
|
return &OverlayArgumentError{OverlayEmptyLower, zeroString}
|
||||||
}
|
}
|
||||||
options = append(options,
|
options = append(options, [][2]string{
|
||||||
OptionOverlayUpperdir+"="+o.upper,
|
{OptionOverlayUpperdir, o.upper},
|
||||||
OptionOverlayWorkdir+"="+o.work)
|
{OptionOverlayWorkdir, o.work},
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
for _, lower := range o.lower {
|
||||||
|
options = append(options, [2]string{OptionOverlayLowerdir + "+", lower})
|
||||||
}
|
}
|
||||||
options = append(options,
|
|
||||||
OptionOverlayLowerdir+"="+strings.Join(o.lower, check.SpecialOverlayPath),
|
|
||||||
OptionOverlayUserxattr)
|
|
||||||
|
|
||||||
return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, check.SpecialOverlayOption))
|
return k.mountOverlay(target, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *MountOverlayOp) late(*setupState, syscallDispatcher) error { return nil }
|
func (o *MountOverlayOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|||||||
@@ -97,13 +97,12 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil),
|
||||||
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil),
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil),
|
||||||
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil),
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil),
|
||||||
call("mount", stub.ExpectArgs{"overlay", "/sysroot", "overlay", uintptr(0), "" +
|
call("mountOverlay", stub.ExpectArgs{"/sysroot", [][2]string{
|
||||||
"upperdir=overlay.upper.32768," +
|
{"upperdir", "overlay.upper.32768"},
|
||||||
"workdir=overlay.work.32768," +
|
{"workdir", "overlay.work.32768"},
|
||||||
"lowerdir=" +
|
{"lowerdir+", `/host/var/lib/planterette/base/debian:f92c9052`},
|
||||||
`/host/var/lib/planterette/base/debian\:f92c9052:` +
|
{"lowerdir+", `/host/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052`},
|
||||||
`/host/var/lib/planterette/app/org.chromium.Chromium@debian\:f92c9052,` +
|
}}, nil, nil),
|
||||||
"userxattr"}, nil, nil),
|
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
@@ -129,11 +128,10 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/nix/store", os.FileMode(0755)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/nix/store", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"overlay", "/nix/store", "overlay", uintptr(0), "" +
|
call("mountOverlay", stub.ExpectArgs{"/nix/store", [][2]string{
|
||||||
"lowerdir=" +
|
{"lowerdir+", "/host/mnt-root/nix/.ro-store"},
|
||||||
"/host/mnt-root/nix/.ro-store:" +
|
{"lowerdir+", "/host/mnt-root/nix/.ro-store0"},
|
||||||
"/host/mnt-root/nix/.ro-store0," +
|
}}, nil, nil),
|
||||||
"userxattr"}, nil, nil),
|
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
@@ -147,11 +145,10 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
||||||
"lowerdir=" +
|
{"lowerdir+", "/host/mnt-root/nix/.ro-store"},
|
||||||
"/host/mnt-root/nix/.ro-store:" +
|
{"lowerdir+", "/host/mnt-root/nix/.ro-store0"},
|
||||||
"/host/mnt-root/nix/.ro-store0," +
|
}}, nil, nil),
|
||||||
"userxattr"}, nil, nil),
|
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
@@ -219,7 +216,11 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "upperdir=/host/mnt-root/nix/.rw-store/.upper,workdir=/host/mnt-root/nix/.rw-store/.work,lowerdir=/host/mnt-root/nix/ro-store,userxattr"}, nil, stub.UniqueError(0)),
|
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
||||||
|
{"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
|
||||||
|
{"workdir", "/host/mnt-root/nix/.rw-store/.work"},
|
||||||
|
{"lowerdir+", "/host/mnt-root/nix/ro-store"},
|
||||||
|
}}, nil, stub.UniqueError(0)),
|
||||||
}, stub.UniqueError(0)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
@@ -233,11 +234,11 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
||||||
"upperdir=/host/mnt-root/nix/.rw-store/.upper," +
|
{"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
|
||||||
"workdir=/host/mnt-root/nix/.rw-store/.work," +
|
{"workdir", "/host/mnt-root/nix/.rw-store/.work"},
|
||||||
"lowerdir=/host/mnt-root/nix/ro-store," +
|
{"lowerdir+", "/host/mnt-root/nix/ro-store"},
|
||||||
"userxattr"}, nil, nil),
|
}}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
@@ -261,16 +262,15 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
||||||
"upperdir=/host/mnt-root/nix/.rw-store/.upper," +
|
{"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
|
||||||
"workdir=/host/mnt-root/nix/.rw-store/.work," +
|
{"workdir", "/host/mnt-root/nix/.rw-store/.work"},
|
||||||
"lowerdir=" +
|
{"lowerdir+", "/host/mnt-root/nix/ro-store"},
|
||||||
"/host/mnt-root/nix/ro-store:" +
|
{"lowerdir+", "/host/mnt-root/nix/ro-store0"},
|
||||||
"/host/mnt-root/nix/ro-store0:" +
|
{"lowerdir+", "/host/mnt-root/nix/ro-store1"},
|
||||||
"/host/mnt-root/nix/ro-store1:" +
|
{"lowerdir+", "/host/mnt-root/nix/ro-store2"},
|
||||||
"/host/mnt-root/nix/ro-store2:" +
|
{"lowerdir+", "/host/mnt-root/nix/ro-store3"},
|
||||||
"/host/mnt-root/nix/ro-store3," +
|
}}, nil, nil),
|
||||||
"userxattr"}, nil, nil),
|
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
package container_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"hakurei.app/container"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLandlockString(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
rulesetAttr *container.RulesetAttr
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"nil", nil, "NULL"},
|
|
||||||
{"zero", new(container.RulesetAttr), "0"},
|
|
||||||
{"some", &container.RulesetAttr{Scoped: container.LANDLOCK_SCOPE_SIGNAL}, "scoped: signal"},
|
|
||||||
{"set", &container.RulesetAttr{
|
|
||||||
HandledAccessFS: container.LANDLOCK_ACCESS_FS_MAKE_SYM | container.LANDLOCK_ACCESS_FS_IOCTL_DEV | container.LANDLOCK_ACCESS_FS_WRITE_FILE,
|
|
||||||
HandledAccessNet: container.LANDLOCK_ACCESS_NET_BIND_TCP,
|
|
||||||
Scoped: container.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | container.LANDLOCK_SCOPE_SIGNAL,
|
|
||||||
}, "fs: write_file make_sym fs_ioctl_dev, net: bind_tcp, scoped: abstract_unix_socket signal"},
|
|
||||||
{"all", &container.RulesetAttr{
|
|
||||||
HandledAccessFS: container.LANDLOCK_ACCESS_FS_EXECUTE |
|
|
||||||
container.LANDLOCK_ACCESS_FS_WRITE_FILE |
|
|
||||||
container.LANDLOCK_ACCESS_FS_READ_FILE |
|
|
||||||
container.LANDLOCK_ACCESS_FS_READ_DIR |
|
|
||||||
container.LANDLOCK_ACCESS_FS_REMOVE_DIR |
|
|
||||||
container.LANDLOCK_ACCESS_FS_REMOVE_FILE |
|
|
||||||
container.LANDLOCK_ACCESS_FS_MAKE_CHAR |
|
|
||||||
container.LANDLOCK_ACCESS_FS_MAKE_DIR |
|
|
||||||
container.LANDLOCK_ACCESS_FS_MAKE_REG |
|
|
||||||
container.LANDLOCK_ACCESS_FS_MAKE_SOCK |
|
|
||||||
container.LANDLOCK_ACCESS_FS_MAKE_FIFO |
|
|
||||||
container.LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
|
||||||
container.LANDLOCK_ACCESS_FS_MAKE_SYM |
|
|
||||||
container.LANDLOCK_ACCESS_FS_REFER |
|
|
||||||
container.LANDLOCK_ACCESS_FS_TRUNCATE |
|
|
||||||
container.LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
|
||||||
HandledAccessNet: container.LANDLOCK_ACCESS_NET_BIND_TCP |
|
|
||||||
container.LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
|
||||||
Scoped: container.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
|
|
||||||
container.LANDLOCK_SCOPE_SIGNAL,
|
|
||||||
}, "fs: execute write_file read_file read_dir remove_dir remove_file make_char make_dir make_reg make_sock make_fifo make_block make_sym fs_refer fs_truncate fs_ioctl_dev, net: bind_tcp connect_tcp, scoped: abstract_unix_socket signal"},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
if got := tc.rulesetAttr.String(); got != tc.want {
|
|
||||||
t.Errorf("String: %s, want %s", got, tc.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLandlockAttrSize(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
want := 24
|
|
||||||
if got := unsafe.Sizeof(container.RulesetAttr{}); got != uintptr(want) {
|
|
||||||
t.Errorf("Sizeof: %d, want %d", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,6 +40,9 @@ const (
|
|||||||
// SourceMqueue is used when mounting mqueue.
|
// SourceMqueue is used when mounting mqueue.
|
||||||
// Note that any source value is allowed when fstype is [FstypeMqueue].
|
// Note that any source value is allowed when fstype is [FstypeMqueue].
|
||||||
SourceMqueue = "mqueue"
|
SourceMqueue = "mqueue"
|
||||||
|
// SourceBinfmtMisc is used when mounting binfmt_misc.
|
||||||
|
// Note that any source value is allowed when fstype is [SourceBinfmtMisc].
|
||||||
|
SourceBinfmtMisc = "binfmt_misc"
|
||||||
// SourceOverlay is used when mounting overlay.
|
// SourceOverlay is used when mounting overlay.
|
||||||
// Note that any source value is allowed when fstype is [FstypeOverlay].
|
// Note that any source value is allowed when fstype is [FstypeOverlay].
|
||||||
SourceOverlay = "overlay"
|
SourceOverlay = "overlay"
|
||||||
@@ -70,6 +73,9 @@ const (
|
|||||||
// FstypeMqueue represents the mqueue pseudo-filesystem.
|
// FstypeMqueue represents the mqueue pseudo-filesystem.
|
||||||
// This filesystem type is usually mounted on /dev/mqueue.
|
// This filesystem type is usually mounted on /dev/mqueue.
|
||||||
FstypeMqueue = "mqueue"
|
FstypeMqueue = "mqueue"
|
||||||
|
// FstypeBinfmtMisc represents the binfmt_misc pseudo-filesystem.
|
||||||
|
// This filesystem type is usually mounted on /proc/sys/fs/binfmt_misc.
|
||||||
|
FstypeBinfmtMisc = "binfmt_misc"
|
||||||
// FstypeOverlay represents the overlay pseudo-filesystem.
|
// FstypeOverlay represents the overlay pseudo-filesystem.
|
||||||
// This filesystem type can be mounted anywhere in the container filesystem.
|
// This filesystem type can be mounted anywhere in the container filesystem.
|
||||||
FstypeOverlay = "overlay"
|
FstypeOverlay = "overlay"
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/vfs"
|
"hakurei.app/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,9 +49,6 @@ func TestToHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalToHostOvlEscape exports toHost passed to [check.EscapeOverlayDataSegment].
|
|
||||||
func InternalToHostOvlEscape(s string) string { return check.EscapeOverlayDataSegment(toHost(s)) }
|
|
||||||
|
|
||||||
func TestCreateFile(t *testing.T) {
|
func TestCreateFile(t *testing.T) {
|
||||||
t.Run("nonexistent", func(t *testing.T) {
|
t.Run("nonexistent", func(t *testing.T) {
|
||||||
t.Run("mkdir", func(t *testing.T) {
|
t.Run("mkdir", func(t *testing.T) {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"hakurei.app/ext"
|
"hakurei.app/ext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetNoNewPrivs sets the calling thread's no_new_privs attribute.
|
// setNoNewPrivs sets the calling thread's no_new_privs attribute.
|
||||||
func SetNoNewPrivs() error {
|
func setNoNewPrivs() error {
|
||||||
return ext.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0)
|
return ext.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
package ext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// include/uapi/linux/mount.h
|
||||||
|
|
||||||
|
/*
|
||||||
|
* move_mount() flags.
|
||||||
|
*/
|
||||||
|
const (
|
||||||
|
MOVE_MOUNT_F_SYMLINKS = 1 << iota /* Follow symlinks on from path */
|
||||||
|
MOVE_MOUNT_F_AUTOMOUNTS /* Follow automounts on from path */
|
||||||
|
MOVE_MOUNT_F_EMPTY_PATH /* Empty from path permitted */
|
||||||
|
_
|
||||||
|
MOVE_MOUNT_T_SYMLINKS /* Follow symlinks on to path */
|
||||||
|
MOVE_MOUNT_T_AUTOMOUNTS /* Follow automounts on to path */
|
||||||
|
MOVE_MOUNT_T_EMPTY_PATH /* Empty to path permitted */
|
||||||
|
_
|
||||||
|
MOVE_MOUNT_SET_GROUP /* Set sharing group instead */
|
||||||
|
MOVE_MOUNT_BENEATH /* Mount beneath top mount */
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fsopen() flags.
|
||||||
|
*/
|
||||||
|
const (
|
||||||
|
FSOPEN_CLOEXEC = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fspick() flags.
|
||||||
|
*/
|
||||||
|
const (
|
||||||
|
FSPICK_CLOEXEC = 1 << iota
|
||||||
|
FSPICK_SYMLINK_NOFOLLOW
|
||||||
|
FSPICK_NO_AUTOMOUNT
|
||||||
|
FSPICK_EMPTY_PATH
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The type of fsconfig() call made.
|
||||||
|
*/
|
||||||
|
const (
|
||||||
|
FSCONFIG_SET_FLAG = iota /* Set parameter, supplying no value */
|
||||||
|
FSCONFIG_SET_STRING /* Set parameter, supplying a string value */
|
||||||
|
FSCONFIG_SET_BINARY /* Set parameter, supplying a binary blob value */
|
||||||
|
FSCONFIG_SET_PATH /* Set parameter, supplying an object by path */
|
||||||
|
FSCONFIG_SET_PATH_EMPTY /* Set parameter, supplying an object by (empty) path */
|
||||||
|
FSCONFIG_SET_FD /* Set parameter, supplying an object by fd */
|
||||||
|
FSCONFIG_CMD_CREATE /* Create new or reuse existing superblock */
|
||||||
|
FSCONFIG_CMD_RECONFIGURE /* Invoke superblock reconfiguration */
|
||||||
|
FSCONFIG_CMD_CREATE_EXCL /* Create new superblock, fail if reusing existing superblock */
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fsmount() flags.
|
||||||
|
*/
|
||||||
|
const (
|
||||||
|
FSMOUNT_CLOEXEC = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mount attributes.
|
||||||
|
*/
|
||||||
|
const (
|
||||||
|
MOUNT_ATTR_RDONLY = 0x00000001 /* Mount read-only */
|
||||||
|
MOUNT_ATTR_NOSUID = 0x00000002 /* Ignore suid and sgid bits */
|
||||||
|
MOUNT_ATTR_NODEV = 0x00000004 /* Disallow access to device special files */
|
||||||
|
MOUNT_ATTR_NOEXEC = 0x00000008 /* Disallow program execution */
|
||||||
|
MOUNT_ATTR__ATIME = 0x00000070 /* Setting on how atime should be updated */
|
||||||
|
MOUNT_ATTR_RELATIME = 0x00000000 /* - Update atime relative to mtime/ctime. */
|
||||||
|
MOUNT_ATTR_NOATIME = 0x00000010 /* - Do not update access times. */
|
||||||
|
MOUNT_ATTR_STRICTATIME = 0x00000020 /* - Always perform atime updates */
|
||||||
|
MOUNT_ATTR_NODIRATIME = 0x00000080 /* Do not update directory access times */
|
||||||
|
MOUNT_ATTR_IDMAP = 0x00100000 /* Idmap mount to @userns_fd in struct mount_attr. */
|
||||||
|
MOUNT_ATTR_NOSYMFOLLOW = 0x00200000 /* Do not follow symlinks */
|
||||||
|
)
|
||||||
|
|
||||||
|
// FS provides low-level wrappers around the suite of file-descriptor-based
|
||||||
|
// mount facilities in Linux.
|
||||||
|
type FS struct {
|
||||||
|
fd uintptr
|
||||||
|
c runtime.Cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFS allocates a new [FS] for the specified fd.
|
||||||
|
func newFS(fd uintptr) *FS {
|
||||||
|
fs := FS{fd: fd}
|
||||||
|
fs.c = runtime.AddCleanup(&fs, func(fd uintptr) {
|
||||||
|
_ = syscall.Close(int(fd))
|
||||||
|
}, fd)
|
||||||
|
return &fs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the underlying filesystem context.
|
||||||
|
func (fs *FS) Close() error {
|
||||||
|
if fs == nil {
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
err := syscall.Close(int(fs.fd))
|
||||||
|
fs.c.Stop()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFS creates a new filesystem context.
|
||||||
|
func OpenFS(fsname string, flags int) (fs *FS, err error) {
|
||||||
|
var s *byte
|
||||||
|
s, err = syscall.BytePtrFromString(fsname)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fd, _, errno := syscall.Syscall(
|
||||||
|
SYS_FSOPEN,
|
||||||
|
uintptr(unsafe.Pointer(s)),
|
||||||
|
uintptr(flags|FSOPEN_CLOEXEC),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if errno != 0 {
|
||||||
|
err = os.NewSyscallError("fsopen", errno)
|
||||||
|
} else {
|
||||||
|
fs = newFS(fd)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PickFS selects filesystem for reconfiguration.
|
||||||
|
func PickFS(dirfd int, pathname string, flags int) (fs *FS, err error) {
|
||||||
|
var s *byte
|
||||||
|
s, err = syscall.BytePtrFromString(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fd, _, errno := syscall.Syscall(
|
||||||
|
SYS_FSPICK,
|
||||||
|
uintptr(dirfd),
|
||||||
|
uintptr(unsafe.Pointer(s)),
|
||||||
|
uintptr(flags|FSPICK_CLOEXEC),
|
||||||
|
)
|
||||||
|
if errno != 0 {
|
||||||
|
err = os.NewSyscallError("fspick", errno)
|
||||||
|
} else {
|
||||||
|
fs = newFS(fd)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// config configures new or existing filesystem context.
|
||||||
|
func (fs *FS) config(cmd uint, key *byte, value unsafe.Pointer, aux int) (err error) {
|
||||||
|
_, _, errno := syscall.Syscall6(
|
||||||
|
SYS_FSCONFIG,
|
||||||
|
fs.fd,
|
||||||
|
uintptr(cmd),
|
||||||
|
uintptr(unsafe.Pointer(key)),
|
||||||
|
uintptr(value),
|
||||||
|
uintptr(aux),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if errno != 0 {
|
||||||
|
err = os.NewSyscallError("fsconfig", errno)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFlag sets the flag parameter named by key. ([FSCONFIG_SET_FLAG])
|
||||||
|
func (fs *FS) SetFlag(key string) (err error) {
|
||||||
|
var s *byte
|
||||||
|
s, err = syscall.BytePtrFromString(key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.config(FSCONFIG_SET_FLAG, s, nil, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetString sets the string parameter named by key to the value specified by
|
||||||
|
// value. ([FSCONFIG_SET_STRING])
|
||||||
|
func (fs *FS) SetString(key, value string) (err error) {
|
||||||
|
var s0 *byte
|
||||||
|
s0, err = syscall.BytePtrFromString(key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var s1 *byte
|
||||||
|
s1, err = syscall.BytePtrFromString(value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.config(FSCONFIG_SET_STRING, s0, unsafe.Pointer(s1), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mount instantiates mount object from filesystem context.
|
||||||
|
func (fs *FS) mount(flags, attrFlags int) (fsfd int, err error) {
|
||||||
|
r, _, errno := syscall.Syscall(
|
||||||
|
SYS_FSMOUNT,
|
||||||
|
fs.fd,
|
||||||
|
uintptr(flags|FSMOUNT_CLOEXEC),
|
||||||
|
uintptr(attrFlags),
|
||||||
|
)
|
||||||
|
fsfd = int(r)
|
||||||
|
if errno != 0 {
|
||||||
|
err = os.NewSyscallError("fsmount", errno)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveMount moves or attaches mount object to filesystem.
|
||||||
|
func MoveMount(
|
||||||
|
fromDirfd int,
|
||||||
|
fromPathname string,
|
||||||
|
toDirfd int,
|
||||||
|
toPathname string,
|
||||||
|
flags int,
|
||||||
|
) (err error) {
|
||||||
|
var s0 *byte
|
||||||
|
s0, err = syscall.BytePtrFromString(fromPathname)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var s1 *byte
|
||||||
|
s1, err = syscall.BytePtrFromString(toPathname)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, errno := syscall.Syscall6(
|
||||||
|
SYS_MOVE_MOUNT,
|
||||||
|
uintptr(fromDirfd),
|
||||||
|
uintptr(unsafe.Pointer(s0)),
|
||||||
|
uintptr(toDirfd),
|
||||||
|
uintptr(unsafe.Pointer(s1)),
|
||||||
|
uintptr(flags),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if errno != 0 {
|
||||||
|
err = os.NewSyscallError("move_mount", errno)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount attaches the underlying filesystem context to the specified pathname.
|
||||||
|
func (fs *FS) Mount(pathname string, attrFlags int) error {
|
||||||
|
if err := fs.config(FSCONFIG_CMD_CREATE_EXCL, nil, nil, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fd, err := fs.mount(0, attrFlags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = MoveMount(
|
||||||
|
fd, "",
|
||||||
|
-1, pathname,
|
||||||
|
MOVE_MOUNT_F_EMPTY_PATH,
|
||||||
|
)
|
||||||
|
closeErr := syscall.Close(fd)
|
||||||
|
if err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -42,6 +42,8 @@ var (
|
|||||||
AbsDevShm = unsafeAbs(DevShm)
|
AbsDevShm = unsafeAbs(DevShm)
|
||||||
// AbsProc is [Proc] as [check.Absolute].
|
// AbsProc is [Proc] as [check.Absolute].
|
||||||
AbsProc = unsafeAbs(Proc)
|
AbsProc = unsafeAbs(Proc)
|
||||||
|
// AbsProcSys is [ProcSys] as [check.Absolute].
|
||||||
|
AbsProcSys = unsafeAbs(ProcSys)
|
||||||
// AbsProcSelfExe is [ProcSelfExe] as [check.Absolute].
|
// AbsProcSelfExe is [ProcSelfExe] as [check.Absolute].
|
||||||
AbsProcSelfExe = unsafeAbs(ProcSelfExe)
|
AbsProcSelfExe = unsafeAbs(ProcSelfExe)
|
||||||
// AbsSys is [Sys] as [check.Absolute].
|
// AbsSys is [Sys] as [check.Absolute].
|
||||||
|
|||||||
@@ -139,7 +139,6 @@
|
|||||||
GOCACHE="$(mktemp -d)" \
|
GOCACHE="$(mktemp -d)" \
|
||||||
PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
|
PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
|
||||||
DESTDIR="$out" \
|
DESTDIR="$out" \
|
||||||
HAKUREI_VERSION="v${hakurei.version}" \
|
|
||||||
./all.sh
|
./all.sh
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-11
@@ -140,21 +140,29 @@ var (
|
|||||||
ErrInsecure = errors.New("configuration is insecure")
|
ErrInsecure = errors.New("configuration is insecure")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// VAllowInsecure allows use of compatibility options considered insecure
|
||||||
|
// under any configuration, to work around ecosystem-wide flaws.
|
||||||
|
VAllowInsecure = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
// Validate checks [Config] and returns [AppError] if an invalid value is encountered.
|
// Validate checks [Config] and returns [AppError] if an invalid value is encountered.
|
||||||
func (config *Config) Validate() error {
|
func (config *Config) Validate(flags int) error {
|
||||||
|
const step = "validate configuration"
|
||||||
|
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
return &AppError{Step: step, Err: ErrConfigNull,
|
||||||
Msg: "invalid configuration"}
|
Msg: "invalid configuration"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is checked again in hsu
|
// this is checked again in hsu
|
||||||
if config.Identity < IdentityStart || config.Identity > IdentityEnd {
|
if config.Identity < IdentityStart || config.Identity > IdentityEnd {
|
||||||
return &AppError{Step: "validate configuration", Err: ErrIdentityBounds,
|
return &AppError{Step: step, Err: ErrIdentityBounds,
|
||||||
Msg: "identity " + strconv.Itoa(config.Identity) + " out of range"}
|
Msg: "identity " + strconv.Itoa(config.Identity) + " out of range"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.SchedPolicy < 0 || config.SchedPolicy > ext.SCHED_LAST {
|
if config.SchedPolicy < 0 || config.SchedPolicy > ext.SCHED_LAST {
|
||||||
return &AppError{Step: "validate configuration", Err: ErrSchedPolicyBounds,
|
return &AppError{Step: step, Err: ErrSchedPolicyBounds,
|
||||||
Msg: "scheduling policy " +
|
Msg: "scheduling policy " +
|
||||||
strconv.Itoa(int(config.SchedPolicy)) +
|
strconv.Itoa(int(config.SchedPolicy)) +
|
||||||
" out of range"}
|
" out of range"}
|
||||||
@@ -168,34 +176,51 @@ func (config *Config) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Container == nil {
|
if config.Container == nil {
|
||||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
return &AppError{Step: step, Err: ErrConfigNull,
|
||||||
Msg: "configuration missing container state"}
|
Msg: "configuration missing container state"}
|
||||||
}
|
}
|
||||||
if config.Container.Home == nil {
|
if config.Container.Home == nil {
|
||||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
return &AppError{Step: step, Err: ErrConfigNull,
|
||||||
Msg: "container configuration missing path to home directory"}
|
Msg: "container configuration missing path to home directory"}
|
||||||
}
|
}
|
||||||
if config.Container.Shell == nil {
|
if config.Container.Shell == nil {
|
||||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
return &AppError{Step: step, Err: ErrConfigNull,
|
||||||
Msg: "container configuration missing path to shell"}
|
Msg: "container configuration missing path to shell"}
|
||||||
}
|
}
|
||||||
if config.Container.Path == nil {
|
if config.Container.Path == nil {
|
||||||
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
return &AppError{Step: step, Err: ErrConfigNull,
|
||||||
Msg: "container configuration missing path to initial program"}
|
Msg: "container configuration missing path to initial program"}
|
||||||
}
|
}
|
||||||
|
|
||||||
for key := range config.Container.Env {
|
for key := range config.Container.Env {
|
||||||
if strings.IndexByte(key, '=') != -1 || strings.IndexByte(key, 0) != -1 {
|
if strings.IndexByte(key, '=') != -1 || strings.IndexByte(key, 0) != -1 {
|
||||||
return &AppError{Step: "validate configuration", Err: ErrEnviron,
|
return &AppError{Step: step, Err: ErrEnviron,
|
||||||
Msg: "invalid environment variable " + strconv.Quote(key)}
|
Msg: "invalid environment variable " + strconv.Quote(key)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if et := config.Enablements.Unwrap(); !config.DirectPulse && et&EPulse != 0 {
|
et := config.Enablements.Unwrap()
|
||||||
return &AppError{Step: "validate configuration", Err: ErrInsecure,
|
if !config.DirectPulse && et&EPulse != 0 {
|
||||||
|
return &AppError{Step: step, Err: ErrInsecure,
|
||||||
Msg: "enablement PulseAudio is insecure and no longer supported"}
|
Msg: "enablement PulseAudio is insecure and no longer supported"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags&VAllowInsecure == 0 {
|
||||||
|
switch {
|
||||||
|
case et&EWayland != 0 && config.DirectWayland:
|
||||||
|
return &AppError{Step: step, Err: ErrInsecure,
|
||||||
|
Msg: "direct_wayland is insecure and no longer supported"}
|
||||||
|
|
||||||
|
case et&EPipeWire != 0 && config.DirectPipeWire:
|
||||||
|
return &AppError{Step: step, Err: ErrInsecure,
|
||||||
|
Msg: "direct_pipewire is insecure and no longer supported"}
|
||||||
|
|
||||||
|
case et&EPulse != 0 && config.DirectPulse:
|
||||||
|
return &AppError{Step: step, Err: ErrInsecure,
|
||||||
|
Msg: "direct_pulse is insecure and no longer supported"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+61
-17
@@ -14,65 +14,109 @@ func TestConfigValidate(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
config *hst.Config
|
config *hst.Config
|
||||||
|
flags int
|
||||||
wantErr error
|
wantErr error
|
||||||
}{
|
}{
|
||||||
{"nil", nil, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
{"nil", nil, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
Msg: "invalid configuration"}},
|
Msg: "invalid configuration"}},
|
||||||
{"identity lower", &hst.Config{Identity: -1}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
|
||||||
|
{"identity lower", &hst.Config{Identity: -1}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
||||||
Msg: "identity -1 out of range"}},
|
Msg: "identity -1 out of range"}},
|
||||||
{"identity upper", &hst.Config{Identity: 10000}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
{"identity upper", &hst.Config{Identity: 10000}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
||||||
Msg: "identity 10000 out of range"}},
|
Msg: "identity 10000 out of range"}},
|
||||||
{"sched lower", &hst.Config{SchedPolicy: -1}, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
|
||||||
|
{"sched lower", &hst.Config{SchedPolicy: -1}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
||||||
Msg: "scheduling policy -1 out of range"}},
|
Msg: "scheduling policy -1 out of range"}},
|
||||||
{"sched upper", &hst.Config{SchedPolicy: 0xcafe}, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
{"sched upper", &hst.Config{SchedPolicy: 0xcafe}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
||||||
Msg: "scheduling policy 51966 out of range"}},
|
Msg: "scheduling policy 51966 out of range"}},
|
||||||
{"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}},
|
|
||||||
|
{"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}}, 0,
|
||||||
&hst.BadInterfaceError{Interface: "", Segment: "session"}},
|
&hst.BadInterfaceError{Interface: "", Segment: "session"}},
|
||||||
{"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}},
|
{"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}}, 0,
|
||||||
&hst.BadInterfaceError{Interface: "", Segment: "system"}},
|
&hst.BadInterfaceError{Interface: "", Segment: "system"}},
|
||||||
{"container", &hst.Config{}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
|
||||||
|
{"container", &hst.Config{}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
Msg: "configuration missing container state"}},
|
Msg: "configuration missing container state"}},
|
||||||
{"home", &hst.Config{Container: &hst.ContainerConfig{}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
{"home", &hst.Config{Container: &hst.ContainerConfig{}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
Msg: "container configuration missing path to home directory"}},
|
Msg: "container configuration missing path to home directory"}},
|
||||||
{"shell", &hst.Config{Container: &hst.ContainerConfig{
|
{"shell", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
Msg: "container configuration missing path to shell"}},
|
Msg: "container configuration missing path to shell"}},
|
||||||
{"path", &hst.Config{Container: &hst.ContainerConfig{
|
{"path", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
Msg: "container configuration missing path to initial program"}},
|
Msg: "container configuration missing path to initial program"}},
|
||||||
|
|
||||||
{"env equals", &hst.Config{Container: &hst.ContainerConfig{
|
{"env equals", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
Path: fhs.AbsTmp,
|
Path: fhs.AbsTmp,
|
||||||
Env: map[string]string{"TERM=": ""},
|
Env: map[string]string{"TERM=": ""},
|
||||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||||
Msg: `invalid environment variable "TERM="`}},
|
Msg: `invalid environment variable "TERM="`}},
|
||||||
{"env NUL", &hst.Config{Container: &hst.ContainerConfig{
|
{"env NUL", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
Path: fhs.AbsTmp,
|
Path: fhs.AbsTmp,
|
||||||
Env: map[string]string{"TERM\x00": ""},
|
Env: map[string]string{"TERM\x00": ""},
|
||||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||||
Msg: `invalid environment variable "TERM\x00"`}},
|
Msg: `invalid environment variable "TERM\x00"`}},
|
||||||
{"insecure pulse", &hst.Config{Enablements: hst.NewEnablements(hst.EPulse), Container: &hst.ContainerConfig{
|
|
||||||
|
{"insecure pulse", &hst.Config{Enablements: new(hst.EPulse), Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
Path: fhs.AbsTmp,
|
Path: fhs.AbsTmp,
|
||||||
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||||
Msg: "enablement PulseAudio is insecure and no longer supported"}},
|
Msg: "enablement PulseAudio is insecure and no longer supported"}},
|
||||||
|
|
||||||
|
{"direct wayland", &hst.Config{Enablements: new(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
Path: fhs.AbsTmp,
|
||||||
|
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||||
|
Msg: "direct_wayland is insecure and no longer supported"}},
|
||||||
|
{"direct wayland allow", &hst.Config{Enablements: new(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
Path: fhs.AbsTmp,
|
||||||
|
}}, hst.VAllowInsecure, nil},
|
||||||
|
|
||||||
|
{"direct pipewire", &hst.Config{Enablements: new(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
Path: fhs.AbsTmp,
|
||||||
|
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||||
|
Msg: "direct_pipewire is insecure and no longer supported"}},
|
||||||
|
{"direct pipewire allow", &hst.Config{Enablements: new(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
Path: fhs.AbsTmp,
|
||||||
|
}}, hst.VAllowInsecure, nil},
|
||||||
|
|
||||||
|
{"direct pulse", &hst.Config{Enablements: new(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
Path: fhs.AbsTmp,
|
||||||
|
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||||
|
Msg: "direct_pulse is insecure and no longer supported"}},
|
||||||
|
{"direct pulse allow", &hst.Config{Enablements: new(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{
|
||||||
|
Home: fhs.AbsTmp,
|
||||||
|
Shell: fhs.AbsTmp,
|
||||||
|
Path: fhs.AbsTmp,
|
||||||
|
}}, hst.VAllowInsecure, nil},
|
||||||
|
|
||||||
{"valid", &hst.Config{Container: &hst.ContainerConfig{
|
{"valid", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
Path: fhs.AbsTmp,
|
Path: fhs.AbsTmp,
|
||||||
}}, nil},
|
}}, 0, nil},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
if err := tc.config.Validate(); !reflect.DeepEqual(err, tc.wantErr) {
|
if err := tc.config.Validate(tc.flags); !reflect.DeepEqual(err, tc.wantErr) {
|
||||||
t.Errorf("Validate: error = %#v, want %#v", err, tc.wantErr)
|
t.Errorf("Validate: error = %#v, want %#v", err, tc.wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+21
-31
@@ -7,12 +7,12 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enablement represents an optional host service to export to the target user.
|
// Enablements denotes optional host service to export to the target user.
|
||||||
type Enablement byte
|
type Enablements byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// EWayland exposes a Wayland pathname socket via security-context-v1.
|
// EWayland exposes a Wayland pathname socket via security-context-v1.
|
||||||
EWayland Enablement = 1 << iota
|
EWayland Enablements = 1 << iota
|
||||||
// EX11 adds the target user via X11 ChangeHosts and exposes the X11
|
// EX11 adds the target user via X11 ChangeHosts and exposes the X11
|
||||||
// pathname socket.
|
// pathname socket.
|
||||||
EX11
|
EX11
|
||||||
@@ -28,8 +28,8 @@ const (
|
|||||||
EM
|
EM
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a string representation of the flags set on [Enablement].
|
// String returns a string representation of the flags set on [Enablements].
|
||||||
func (e Enablement) String() string {
|
func (e Enablements) String() string {
|
||||||
switch e {
|
switch e {
|
||||||
case 0:
|
case 0:
|
||||||
return "(no enablements)"
|
return "(no enablements)"
|
||||||
@@ -47,7 +47,7 @@ func (e Enablement) String() string {
|
|||||||
buf := new(strings.Builder)
|
buf := new(strings.Builder)
|
||||||
buf.Grow(32)
|
buf.Grow(32)
|
||||||
|
|
||||||
for i := Enablement(1); i < EM; i <<= 1 {
|
for i := Enablements(1); i < EM; i <<= 1 {
|
||||||
if e&i != 0 {
|
if e&i != 0 {
|
||||||
buf.WriteString(", " + i.String())
|
buf.WriteString(", " + i.String())
|
||||||
}
|
}
|
||||||
@@ -60,12 +60,6 @@ func (e Enablement) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEnablements returns the address of [Enablement] as [Enablements].
|
|
||||||
func NewEnablements(e Enablement) *Enablements { return (*Enablements)(&e) }
|
|
||||||
|
|
||||||
// Enablements is the [json] adapter for [Enablement].
|
|
||||||
type Enablements Enablement
|
|
||||||
|
|
||||||
// enablementsJSON is the [json] representation of [Enablements].
|
// enablementsJSON is the [json] representation of [Enablements].
|
||||||
type enablementsJSON = struct {
|
type enablementsJSON = struct {
|
||||||
Wayland bool `json:"wayland,omitempty"`
|
Wayland bool `json:"wayland,omitempty"`
|
||||||
@@ -75,24 +69,21 @@ type enablementsJSON = struct {
|
|||||||
Pulse bool `json:"pulse,omitempty"`
|
Pulse bool `json:"pulse,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the underlying [Enablement].
|
// Unwrap returns the value pointed to by e.
|
||||||
func (e *Enablements) Unwrap() Enablement {
|
func (e *Enablements) Unwrap() Enablements {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return Enablement(*e)
|
return *e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Enablements) MarshalJSON() ([]byte, error) {
|
func (e Enablements) MarshalJSON() ([]byte, error) {
|
||||||
if e == nil {
|
|
||||||
return nil, syscall.EINVAL
|
|
||||||
}
|
|
||||||
return json.Marshal(&enablementsJSON{
|
return json.Marshal(&enablementsJSON{
|
||||||
Wayland: Enablement(*e)&EWayland != 0,
|
Wayland: e&EWayland != 0,
|
||||||
X11: Enablement(*e)&EX11 != 0,
|
X11: e&EX11 != 0,
|
||||||
DBus: Enablement(*e)&EDBus != 0,
|
DBus: e&EDBus != 0,
|
||||||
PipeWire: Enablement(*e)&EPipeWire != 0,
|
PipeWire: e&EPipeWire != 0,
|
||||||
Pulse: Enablement(*e)&EPulse != 0,
|
Pulse: e&EPulse != 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,22 +97,21 @@ func (e *Enablements) UnmarshalJSON(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ve Enablement
|
*e = 0
|
||||||
if v.Wayland {
|
if v.Wayland {
|
||||||
ve |= EWayland
|
*e |= EWayland
|
||||||
}
|
}
|
||||||
if v.X11 {
|
if v.X11 {
|
||||||
ve |= EX11
|
*e |= EX11
|
||||||
}
|
}
|
||||||
if v.DBus {
|
if v.DBus {
|
||||||
ve |= EDBus
|
*e |= EDBus
|
||||||
}
|
}
|
||||||
if v.PipeWire {
|
if v.PipeWire {
|
||||||
ve |= EPipeWire
|
*e |= EPipeWire
|
||||||
}
|
}
|
||||||
if v.Pulse {
|
if v.Pulse {
|
||||||
ve |= EPulse
|
*e |= EPulse
|
||||||
}
|
}
|
||||||
*e = Enablements(ve)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-12
@@ -13,7 +13,7 @@ func TestEnablementString(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
flags hst.Enablement
|
flags hst.Enablements
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{0, "(no enablements)"},
|
{0, "(no enablements)"},
|
||||||
@@ -59,13 +59,13 @@ func TestEnablements(t *testing.T) {
|
|||||||
sData string
|
sData string
|
||||||
}{
|
}{
|
||||||
{"nil", nil, "null", `{"value":null,"magic":3236757504}`},
|
{"nil", nil, "null", `{"value":null,"magic":3236757504}`},
|
||||||
{"zero", hst.NewEnablements(0), `{}`, `{"value":{},"magic":3236757504}`},
|
{"zero", new(hst.Enablements(0)), `{}`, `{"value":{},"magic":3236757504}`},
|
||||||
{"wayland", hst.NewEnablements(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
|
{"wayland", new(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
|
||||||
{"x11", hst.NewEnablements(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
{"x11", new(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
||||||
{"dbus", hst.NewEnablements(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
{"dbus", new(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
||||||
{"pipewire", hst.NewEnablements(hst.EPipeWire), `{"pipewire":true}`, `{"value":{"pipewire":true},"magic":3236757504}`},
|
{"pipewire", new(hst.EPipeWire), `{"pipewire":true}`, `{"value":{"pipewire":true},"magic":3236757504}`},
|
||||||
{"pulse", hst.NewEnablements(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
|
{"pulse", new(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
|
||||||
{"all", hst.NewEnablements(hst.EM - 1), `{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true},"magic":3236757504}`},
|
{"all", new(hst.EM - 1), `{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true},"magic":3236757504}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -137,7 +137,7 @@ func TestEnablements(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("val", func(t *testing.T) {
|
t.Run("val", func(t *testing.T) {
|
||||||
if got := hst.NewEnablements(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse {
|
if got := new(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse {
|
||||||
t.Errorf("Unwrap: %v", got)
|
t.Errorf("Unwrap: %v", got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -146,9 +146,6 @@ func TestEnablements(t *testing.T) {
|
|||||||
t.Run("passthrough", func(t *testing.T) {
|
t.Run("passthrough", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
if _, err := (*hst.Enablements)(nil).MarshalJSON(); !errors.Is(err, syscall.EINVAL) {
|
|
||||||
t.Errorf("MarshalJSON: error = %v", err)
|
|
||||||
}
|
|
||||||
if err := (*hst.Enablements)(nil).UnmarshalJSON(nil); !errors.Is(err, syscall.EINVAL) {
|
if err := (*hst.Enablements)(nil).UnmarshalJSON(nil); !errors.Is(err, syscall.EINVAL) {
|
||||||
t.Errorf("UnmarshalJSON: error = %v", err)
|
t.Errorf("UnmarshalJSON: error = %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ func Template() *Config {
|
|||||||
return &Config{
|
return &Config{
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
|
|
||||||
Enablements: NewEnablements(EWayland | EDBus | EPipeWire),
|
Enablements: new(EWayland | EDBus | EPipeWire),
|
||||||
|
|
||||||
SessionBus: &BusConfig{
|
SessionBus: &BusConfig{
|
||||||
See: nil,
|
See: nil,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package container
|
package landlock
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
@@ -14,11 +14,11 @@ const (
|
|||||||
LANDLOCK_CREATE_RULESET_VERSION = 1 << iota
|
LANDLOCK_CREATE_RULESET_VERSION = 1 << iota
|
||||||
)
|
)
|
||||||
|
|
||||||
// LandlockAccessFS is bitmask of handled filesystem actions.
|
// AccessFS is bitmask of handled filesystem actions.
|
||||||
type LandlockAccessFS uint64
|
type AccessFS uint64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LANDLOCK_ACCESS_FS_EXECUTE LandlockAccessFS = 1 << iota
|
LANDLOCK_ACCESS_FS_EXECUTE AccessFS = 1 << iota
|
||||||
LANDLOCK_ACCESS_FS_WRITE_FILE
|
LANDLOCK_ACCESS_FS_WRITE_FILE
|
||||||
LANDLOCK_ACCESS_FS_READ_FILE
|
LANDLOCK_ACCESS_FS_READ_FILE
|
||||||
LANDLOCK_ACCESS_FS_READ_DIR
|
LANDLOCK_ACCESS_FS_READ_DIR
|
||||||
@@ -38,8 +38,8 @@ const (
|
|||||||
_LANDLOCK_ACCESS_FS_DELIM
|
_LANDLOCK_ACCESS_FS_DELIM
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a space-separated string of [LandlockAccessFS] flags.
|
// String returns a space-separated string of [AccessFS] flags.
|
||||||
func (f LandlockAccessFS) String() string {
|
func (f AccessFS) String() string {
|
||||||
switch f {
|
switch f {
|
||||||
case LANDLOCK_ACCESS_FS_EXECUTE:
|
case LANDLOCK_ACCESS_FS_EXECUTE:
|
||||||
return "execute"
|
return "execute"
|
||||||
@@ -90,8 +90,8 @@ func (f LandlockAccessFS) String() string {
|
|||||||
return "fs_ioctl_dev"
|
return "fs_ioctl_dev"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var c []LandlockAccessFS
|
var c []AccessFS
|
||||||
for i := LandlockAccessFS(1); i < _LANDLOCK_ACCESS_FS_DELIM; i <<= 1 {
|
for i := AccessFS(1); i < _LANDLOCK_ACCESS_FS_DELIM; i <<= 1 {
|
||||||
if f&i != 0 {
|
if f&i != 0 {
|
||||||
c = append(c, i)
|
c = append(c, i)
|
||||||
}
|
}
|
||||||
@@ -107,18 +107,18 @@ func (f LandlockAccessFS) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LandlockAccessNet is bitmask of handled network actions.
|
// AccessNet is bitmask of handled network actions.
|
||||||
type LandlockAccessNet uint64
|
type AccessNet uint64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LANDLOCK_ACCESS_NET_BIND_TCP LandlockAccessNet = 1 << iota
|
LANDLOCK_ACCESS_NET_BIND_TCP AccessNet = 1 << iota
|
||||||
LANDLOCK_ACCESS_NET_CONNECT_TCP
|
LANDLOCK_ACCESS_NET_CONNECT_TCP
|
||||||
|
|
||||||
_LANDLOCK_ACCESS_NET_DELIM
|
_LANDLOCK_ACCESS_NET_DELIM
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a space-separated string of [LandlockAccessNet] flags.
|
// String returns a space-separated string of [AccessNet] flags.
|
||||||
func (f LandlockAccessNet) String() string {
|
func (f AccessNet) String() string {
|
||||||
switch f {
|
switch f {
|
||||||
case LANDLOCK_ACCESS_NET_BIND_TCP:
|
case LANDLOCK_ACCESS_NET_BIND_TCP:
|
||||||
return "bind_tcp"
|
return "bind_tcp"
|
||||||
@@ -127,8 +127,8 @@ func (f LandlockAccessNet) String() string {
|
|||||||
return "connect_tcp"
|
return "connect_tcp"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var c []LandlockAccessNet
|
var c []AccessNet
|
||||||
for i := LandlockAccessNet(1); i < _LANDLOCK_ACCESS_NET_DELIM; i <<= 1 {
|
for i := AccessNet(1); i < _LANDLOCK_ACCESS_NET_DELIM; i <<= 1 {
|
||||||
if f&i != 0 {
|
if f&i != 0 {
|
||||||
c = append(c, i)
|
c = append(c, i)
|
||||||
}
|
}
|
||||||
@@ -144,18 +144,18 @@ func (f LandlockAccessNet) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LandlockScope is bitmask of scopes restricting a Landlock domain from accessing outside resources.
|
// Scope is bitmask of scopes restricting a Landlock domain from accessing outside resources.
|
||||||
type LandlockScope uint64
|
type Scope uint64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET LandlockScope = 1 << iota
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET Scope = 1 << iota
|
||||||
LANDLOCK_SCOPE_SIGNAL
|
LANDLOCK_SCOPE_SIGNAL
|
||||||
|
|
||||||
_LANDLOCK_SCOPE_DELIM
|
_LANDLOCK_SCOPE_DELIM
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a space-separated string of [LandlockScope] flags.
|
// String returns a space-separated string of [Scope] flags.
|
||||||
func (f LandlockScope) String() string {
|
func (f Scope) String() string {
|
||||||
switch f {
|
switch f {
|
||||||
case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET:
|
case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET:
|
||||||
return "abstract_unix_socket"
|
return "abstract_unix_socket"
|
||||||
@@ -164,8 +164,8 @@ func (f LandlockScope) String() string {
|
|||||||
return "signal"
|
return "signal"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var c []LandlockScope
|
var c []Scope
|
||||||
for i := LandlockScope(1); i < _LANDLOCK_SCOPE_DELIM; i <<= 1 {
|
for i := Scope(1); i < _LANDLOCK_SCOPE_DELIM; i <<= 1 {
|
||||||
if f&i != 0 {
|
if f&i != 0 {
|
||||||
c = append(c, i)
|
c = append(c, i)
|
||||||
}
|
}
|
||||||
@@ -184,12 +184,12 @@ func (f LandlockScope) String() string {
|
|||||||
// RulesetAttr is equivalent to struct landlock_ruleset_attr.
|
// RulesetAttr is equivalent to struct landlock_ruleset_attr.
|
||||||
type RulesetAttr struct {
|
type RulesetAttr struct {
|
||||||
// Bitmask of handled filesystem actions.
|
// Bitmask of handled filesystem actions.
|
||||||
HandledAccessFS LandlockAccessFS
|
HandledAccessFS AccessFS
|
||||||
// Bitmask of handled network actions.
|
// Bitmask of handled network actions.
|
||||||
HandledAccessNet LandlockAccessNet
|
HandledAccessNet AccessNet
|
||||||
// Bitmask of scopes restricting a Landlock domain from accessing outside
|
// Bitmask of scopes restricting a Landlock domain from accessing outside
|
||||||
// resources (e.g. IPCs).
|
// resources (e.g. IPCs).
|
||||||
Scoped LandlockScope
|
Scoped Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a user-facing description of [RulesetAttr].
|
// String returns a user-facing description of [RulesetAttr].
|
||||||
@@ -239,13 +239,13 @@ func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
|
|||||||
return fd, nil
|
return fd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LandlockGetABI returns the ABI version supported by the kernel.
|
// GetABI returns the ABI version supported by the kernel.
|
||||||
func LandlockGetABI() (int, error) {
|
func GetABI() (int, error) {
|
||||||
return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION)
|
return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LandlockRestrictSelf applies a loaded ruleset to the calling thread.
|
// RestrictSelf applies a loaded ruleset to the calling thread.
|
||||||
func LandlockRestrictSelf(rulesetFd int, flags uintptr) error {
|
func RestrictSelf(rulesetFd int, flags uintptr) error {
|
||||||
r, _, errno := syscall.Syscall(
|
r, _, errno := syscall.Syscall(
|
||||||
ext.SYS_LANDLOCK_RESTRICT_SELF,
|
ext.SYS_LANDLOCK_RESTRICT_SELF,
|
||||||
uintptr(rulesetFd),
|
uintptr(rulesetFd),
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package landlock_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/internal/landlock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLandlockString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
rulesetAttr *landlock.RulesetAttr
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"nil", nil, "NULL"},
|
||||||
|
{"zero", new(landlock.RulesetAttr), "0"},
|
||||||
|
{"some", &landlock.RulesetAttr{Scoped: landlock.LANDLOCK_SCOPE_SIGNAL}, "scoped: signal"},
|
||||||
|
{"set", &landlock.RulesetAttr{
|
||||||
|
HandledAccessFS: landlock.LANDLOCK_ACCESS_FS_MAKE_SYM | landlock.LANDLOCK_ACCESS_FS_IOCTL_DEV | landlock.LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||||
|
HandledAccessNet: landlock.LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||||
|
Scoped: landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | landlock.LANDLOCK_SCOPE_SIGNAL,
|
||||||
|
}, "fs: write_file make_sym fs_ioctl_dev, net: bind_tcp, scoped: abstract_unix_socket signal"},
|
||||||
|
{"all", &landlock.RulesetAttr{
|
||||||
|
HandledAccessFS: landlock.LANDLOCK_ACCESS_FS_EXECUTE |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_WRITE_FILE |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_READ_FILE |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_READ_DIR |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_REMOVE_DIR |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_REMOVE_FILE |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_MAKE_CHAR |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_MAKE_DIR |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_MAKE_REG |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_MAKE_SOCK |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_MAKE_FIFO |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_MAKE_SYM |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_REFER |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||||
|
landlock.LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||||
|
HandledAccessNet: landlock.LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||||
|
landlock.LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||||
|
Scoped: landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
|
||||||
|
landlock.LANDLOCK_SCOPE_SIGNAL,
|
||||||
|
}, "fs: execute write_file read_file read_dir remove_dir remove_file make_char make_dir make_reg make_sock make_fifo make_block make_sym fs_refer fs_truncate fs_ioctl_dev, net: bind_tcp connect_tcp, scoped: abstract_unix_socket signal"},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if got := tc.rulesetAttr.String(); got != tc.want {
|
||||||
|
t.Errorf("String: %s, want %s", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLandlockAttrSize(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
want := 24
|
||||||
|
if got := unsafe.Sizeof(landlock.RulesetAttr{}); got != uintptr(want) {
|
||||||
|
t.Errorf("Sizeof: %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,14 @@ type outcome struct {
|
|||||||
syscallDispatcher
|
syscallDispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *outcome) finalise(ctx context.Context, msg message.Msg, id *hst.ID, config *hst.Config) error {
|
// finalise prepares an outcome for main.
|
||||||
|
func (k *outcome) finalise(
|
||||||
|
ctx context.Context,
|
||||||
|
msg message.Msg,
|
||||||
|
id *hst.ID,
|
||||||
|
config *hst.Config,
|
||||||
|
flags int,
|
||||||
|
) error {
|
||||||
if ctx == nil || id == nil {
|
if ctx == nil || id == nil {
|
||||||
// unreachable
|
// unreachable
|
||||||
panic("invalid call to finalise")
|
panic("invalid call to finalise")
|
||||||
@@ -43,7 +50,7 @@ func (k *outcome) finalise(ctx context.Context, msg message.Msg, id *hst.ID, con
|
|||||||
}
|
}
|
||||||
k.ctx = ctx
|
k.ctx = ctx
|
||||||
|
|
||||||
if err := config.Validate(); err != nil {
|
if err := config.Validate(flags); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ type outcomeStateSys struct {
|
|||||||
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
||||||
appId string
|
appId string
|
||||||
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
||||||
et hst.Enablement
|
et hst.Enablements
|
||||||
|
|
||||||
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
||||||
directWayland bool
|
directWayland bool
|
||||||
|
|||||||
@@ -297,12 +297,12 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
|||||||
// accumulate enablements of remaining instances
|
// accumulate enablements of remaining instances
|
||||||
var (
|
var (
|
||||||
// alive enablement bits
|
// alive enablement bits
|
||||||
rt hst.Enablement
|
rt hst.Enablements
|
||||||
// alive instance count
|
// alive instance count
|
||||||
n int
|
n int
|
||||||
)
|
)
|
||||||
for eh := range entries {
|
for eh := range entries {
|
||||||
var et hst.Enablement
|
var et hst.Enablements
|
||||||
if et, err = eh.Load(nil); err != nil {
|
if et, err = eh.Load(nil); err != nil {
|
||||||
perror(err, "read state header of instance "+eh.ID.String())
|
perror(err, "read state header of instance "+eh.ID.String())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ import (
|
|||||||
func IsPollDescriptor(fd uintptr) bool
|
func IsPollDescriptor(fd uintptr) bool
|
||||||
|
|
||||||
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
||||||
func Main(ctx context.Context, msg message.Msg, config *hst.Config, fd int) {
|
func Main(
|
||||||
|
ctx context.Context,
|
||||||
|
msg message.Msg,
|
||||||
|
config *hst.Config,
|
||||||
|
flags int,
|
||||||
|
fd int,
|
||||||
|
) {
|
||||||
// avoids runtime internals or standard streams
|
// avoids runtime internals or standard streams
|
||||||
if fd >= 0 {
|
if fd >= 0 {
|
||||||
if IsPollDescriptor(uintptr(fd)) || fd < 3 {
|
if IsPollDescriptor(uintptr(fd)) || fd < 3 {
|
||||||
@@ -34,7 +40,7 @@ func Main(ctx context.Context, msg message.Msg, config *hst.Config, fd int) {
|
|||||||
k := outcome{syscallDispatcher: direct{msg}}
|
k := outcome{syscallDispatcher: direct{msg}}
|
||||||
|
|
||||||
finaliseTime := time.Now()
|
finaliseTime := time.Now()
|
||||||
if err := k.finalise(ctx, msg, &id, config); err != nil {
|
if err := k.finalise(ctx, msg, &id, config, flags); err != nil {
|
||||||
printMessageError(msg.GetLogger().Fatalln, "cannot seal app:", err)
|
printMessageError(msg.GetLogger().Fatalln, "cannot seal app:", err)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
},
|
},
|
||||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
@@ -427,7 +427,7 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
DirectPipeWire: true,
|
DirectPipeWire: true,
|
||||||
|
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Env: nil,
|
Env: nil,
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
newConfig := func() *hst.Config {
|
newConfig := func() *hst.Config {
|
||||||
config := hst.Template()
|
config := hst.Template()
|
||||||
config.DirectPulse = true
|
config.DirectPulse = true
|
||||||
config.Enablements = hst.NewEnablements(hst.EPulse)
|
config.Enablements = new(hst.EPulse)
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,78 +64,6 @@ func TestFlatten(t *testing.T) {
|
|||||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C"), nil},
|
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C"), nil},
|
||||||
|
|
||||||
{"sample cache file", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq": {Mode: 0400, Data: []byte{0, 0, 0, 0, 0xad, 0xb, 0, 4, 0xfe, 0xfe, 0, 0, 0xfe, 0xca, 0, 0}},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
|
||||||
"identifier/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
|
||||||
"identifier/cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
|
||||||
"identifier/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
|
||||||
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: 0400, Path: "checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq", Data: []byte{0, 0, 0, 0, 0xad, 0xb, 0, 4, 0xfe, 0xfe, 0, 0, 0xfe, 0xca, 0, 0}},
|
|
||||||
{Mode: 0400, Path: "checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX", Data: []byte{0}},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq", Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe", Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX", Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
|
||||||
}, pkg.MustDecode("St9rlE-mGZ5gXwiv_hzQ_B8bZP-UUvSNmf4nHUZzCMOumb6hKnheZSe0dmnuc4Q2"), nil},
|
|
||||||
|
|
||||||
{"sample http get cure", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"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/oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
|
|
||||||
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{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/oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_", Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
|
||||||
}, pkg.MustDecode("L_0RFHpr9JUS4Zp14rz2dESSRvfLzpvqsLhR1-YjQt8hYlmEdVl7vI3_-v8UNPKs"), nil},
|
|
||||||
|
|
||||||
{"sample directory step simple", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"check": {Mode: 0400, Data: []byte{0, 0}},
|
|
||||||
|
|
||||||
"lib": {Mode: fs.ModeDir | 0700},
|
|
||||||
"lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
|
|
||||||
"lib/pkgconfig": {Mode: fs.ModeDir | 0700},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "."},
|
|
||||||
|
|
||||||
{Mode: 0400, Path: "check", Data: []byte{0, 0}},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "lib"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "lib/libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "lib/pkgconfig"},
|
|
||||||
}, pkg.MustDecode("qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b"), nil},
|
|
||||||
|
|
||||||
{"sample directory step garbage", fstest.MapFS{
|
{"sample directory step garbage", fstest.MapFS{
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
@@ -151,421 +79,6 @@ func TestFlatten(t *testing.T) {
|
|||||||
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "lib/pkgconfig"},
|
{Mode: fs.ModeDir | 0500, Path: "lib/pkgconfig"},
|
||||||
}, pkg.MustDecode("CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT"), nil},
|
}, pkg.MustDecode("CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT"), nil},
|
||||||
|
|
||||||
{"sample directory", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/check": {Mode: 0400, Data: []byte{0, 0}},
|
|
||||||
"checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib/pkgconfig": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
|
|
||||||
"identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
|
|
||||||
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b"},
|
|
||||||
{Mode: 0400, Path: "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/check", Data: []byte{0, 0}},
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib/libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib/pkgconfig"},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY", Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa", Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
|
||||||
}, pkg.MustDecode("WVpvsVqVKg9Nsh744x57h51AuWUoUR2nnh8Md-EYBQpk6ziyTuUn6PLtF2e0Eu_d"), nil},
|
|
||||||
|
|
||||||
{"sample no assume checksum", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M/check": {Mode: 0400, Data: []byte{}},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/_wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
|
||||||
"identifier/_wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
|
||||||
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M"},
|
|
||||||
{Mode: 0400, Path: "checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M/check", Data: []byte{}},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
|
||||||
}, pkg.MustDecode("OC290t23aimNo2Rp2pPwan5GI2KRLRdOwYxXQMD9jw0QROgHnNXWodoWdV0hwu2w"), nil},
|
|
||||||
|
|
||||||
{"sample tar step unpack", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check": {Mode: 0400, Data: []byte{0, 0}},
|
|
||||||
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0500},
|
|
||||||
"identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
"identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
|
|
||||||
"work": {Mode: fs.ModeDir | 0500},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP"},
|
|
||||||
{Mode: 0400, Path: "checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check", Data: []byte{0, 0}},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig"},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "identifier"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY", Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa", Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "work"},
|
|
||||||
}, pkg.MustDecode("cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM"), nil},
|
|
||||||
|
|
||||||
{"sample tar", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check": {Mode: 0400, Data: []byte{0, 0}},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/work": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"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},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP"},
|
|
||||||
{Mode: 0400, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check", Data: []byte{0, 0}},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY", Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa", Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/work"},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{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("NQTlc466JmSVLIyWklm_u8_g95jEEb98PxJU-kjwxLpfdjwMWJq0G8ze9R4Vo1Vu"), nil},
|
|
||||||
|
|
||||||
{"sample tar expand step unpack", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
}, pkg.MustDecode("CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN"), nil},
|
|
||||||
|
|
||||||
{"sample tar expand", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"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},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN"},
|
|
||||||
{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/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("hSoSSgCYTNonX3Q8FjvjD1fBl-E-BQyA6OTXro2OadXqbST4tZ-akGXszdeqphRe"), nil},
|
|
||||||
|
|
||||||
{"testtool", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "."},
|
|
||||||
|
|
||||||
{Mode: 0400, Path: "check", Data: []byte{0}},
|
|
||||||
}, pkg.MustDecode("GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"), nil},
|
|
||||||
|
|
||||||
{"sample exec container", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
|
||||||
"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},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
|
|
||||||
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
|
||||||
{Mode: 0400, Path: "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb", Data: []byte{}},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
|
||||||
{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("Q5DluWQCAeohLoiGRImurwFp3vdz9IfQCoj7Fuhh73s4KQPRHpEQEnHTdNHmB8Fx"), nil},
|
|
||||||
|
|
||||||
{"testtool net", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"check": {Mode: 0400, Data: []byte("net")},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "."},
|
|
||||||
|
|
||||||
{Mode: 0400, Path: "check", Data: []byte("net")},
|
|
||||||
}, pkg.MustDecode("a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W"), nil},
|
|
||||||
|
|
||||||
{"sample exec net container", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
|
||||||
"checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W": {Mode: fs.ModeDir | 0500},
|
|
||||||
"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/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
||||||
|
|
||||||
"temp": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
|
||||||
{Mode: 0400, Path: "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb", Data: []byte{}},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W"},
|
|
||||||
{Mode: 0400, Path: "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W/check", Data: []byte("net")},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{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/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "temp"},
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
|
||||||
}, pkg.MustDecode("bPYvvqxpfV7xcC1EptqyKNK1klLJgYHMDkzBcoOyK6j_Aj5hb0mXNPwTwPSK5F6Z"), nil},
|
|
||||||
|
|
||||||
{"sample exec container overlay root", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"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},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
|
|
||||||
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{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("PO2DSSCa4yoSgEYRcCSZfQfwow1yRigL3Ry-hI0RDI4aGuFBha-EfXeSJnG_5_Rl"), nil},
|
|
||||||
|
|
||||||
{"sample exec container overlay work", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"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},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
|
|
||||||
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{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("iaRt6l_Wm2n-h5UsDewZxQkCmjZjyL8r7wv32QT2kyV55-Lx09Dq4gfg9BiwPnKs"), nil},
|
|
||||||
|
|
||||||
{"sample exec container multiple layers", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
|
||||||
"checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK/check": {Mode: 0400, Data: []byte("layers")},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
|
||||||
"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},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
|
|
||||||
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
|
||||||
{Mode: 0400, Path: "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb", Data: []byte{}},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK"},
|
|
||||||
{Mode: 0400, Path: "checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK/check", Data: []byte("layers")},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{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/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("O2YzyR7IUGU5J2CADy0hUZ3A5NkP_Vwzs4UadEdn2oMZZVWRtH0xZGJ3HXiimTnZ"), nil},
|
|
||||||
|
|
||||||
{"sample exec container layer promotion", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"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},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
|
|
||||||
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{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("3EaW6WibLi9gl03_UieiFPaFcPy5p4x3JPxrnLJxGaTI-bh3HU9DK9IMx7c3rrNm"), nil},
|
|
||||||
|
|
||||||
{"sample file short", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX": {Mode: 0400, Data: []byte{0}},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
|
||||||
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: 0400, Path: "checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX", Data: []byte{0}},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi", Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
|
||||||
}, pkg.MustDecode("iR6H5OIsyOW4EwEgtm9rGzGF6DVtyHLySEtwnFE8bnus9VJcoCbR4JIek7Lw-vwT"), nil},
|
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|||||||
+115
-39
@@ -9,8 +9,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"unique"
|
"unique"
|
||||||
@@ -27,6 +29,11 @@ import (
|
|||||||
// AbsWork is the container pathname [TContext.GetWorkDir] is mounted on.
|
// AbsWork is the container pathname [TContext.GetWorkDir] is mounted on.
|
||||||
var AbsWork = fhs.AbsRoot.Append("work/")
|
var AbsWork = fhs.AbsRoot.Append("work/")
|
||||||
|
|
||||||
|
// EnvJobs is the name of the environment variable holding a decimal
|
||||||
|
// representation of the preferred job count. Its value must not affect cure
|
||||||
|
// outcome.
|
||||||
|
const EnvJobs = "CURE_JOBS"
|
||||||
|
|
||||||
// ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make
|
// ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make
|
||||||
// it available at under in the container.
|
// it available at under in the container.
|
||||||
type ExecPath struct {
|
type ExecPath struct {
|
||||||
@@ -89,6 +96,32 @@ func MustPath(pathname string, writable bool, a ...Artifact) ExecPath {
|
|||||||
return ExecPath{check.MustAbs(pathname), a, writable}
|
return ExecPath{check.MustAbs(pathname), a, writable}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
binfmt map[string]container.BinfmtEntry
|
||||||
|
binfmtMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterArch arranges for [KindExec] and [KindExecNet] to support a new
|
||||||
|
// architecture via a binfmt_misc entry. Each architecture must be registered
|
||||||
|
// at most once.
|
||||||
|
func RegisterArch(arch string, e container.BinfmtEntry) {
|
||||||
|
if arch == "" {
|
||||||
|
panic(UnsupportedArchError(arch))
|
||||||
|
}
|
||||||
|
|
||||||
|
binfmtMu.Lock()
|
||||||
|
defer binfmtMu.Unlock()
|
||||||
|
|
||||||
|
if binfmt == nil {
|
||||||
|
binfmt = make(map[string]container.BinfmtEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := binfmt[arch]; ok {
|
||||||
|
panic("attempting to register " + strconv.Quote(arch) + " twice")
|
||||||
|
}
|
||||||
|
binfmt[arch] = e
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ExecTimeoutDefault replaces out of range [NewExec] timeout values.
|
// ExecTimeoutDefault replaces out of range [NewExec] timeout values.
|
||||||
ExecTimeoutDefault = 15 * time.Minute
|
ExecTimeoutDefault = 15 * time.Minute
|
||||||
@@ -105,6 +138,8 @@ type execArtifact struct {
|
|||||||
// Caller-supplied user-facing reporting name, guaranteed to be nonzero
|
// Caller-supplied user-facing reporting name, guaranteed to be nonzero
|
||||||
// during initialisation.
|
// during initialisation.
|
||||||
name string
|
name string
|
||||||
|
// Target architecture.
|
||||||
|
arch string
|
||||||
// Caller-supplied inner mount points.
|
// Caller-supplied inner mount points.
|
||||||
paths []ExecPath
|
paths []ExecPath
|
||||||
|
|
||||||
@@ -127,28 +162,40 @@ type execArtifact struct {
|
|||||||
|
|
||||||
var _ fmt.Stringer = new(execArtifact)
|
var _ fmt.Stringer = new(execArtifact)
|
||||||
|
|
||||||
// execNetArtifact is like execArtifact but implements [KnownChecksum] and has
|
// execMeasuredArtifact is like execArtifact but implements [KnownChecksum] and
|
||||||
// its resulting container keep the host net namespace.
|
// has its resulting container optionally keep the host net namespace.
|
||||||
type execNetArtifact struct {
|
type execMeasuredArtifact struct {
|
||||||
checksum Checksum
|
checksum Checksum
|
||||||
|
|
||||||
|
// Whether to keep host net namespace.
|
||||||
|
hostNet bool
|
||||||
|
|
||||||
execArtifact
|
execArtifact
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ KnownChecksum = new(execNetArtifact)
|
var _ KnownChecksum = new(execMeasuredArtifact)
|
||||||
|
|
||||||
// Checksum returns the caller-supplied checksum.
|
// Checksum returns the caller-supplied checksum.
|
||||||
func (a *execNetArtifact) Checksum() Checksum { return a.checksum }
|
func (a *execMeasuredArtifact) Checksum() Checksum { return a.checksum }
|
||||||
|
|
||||||
// Kind returns the hardcoded [Kind] constant.
|
// Kind returns [KindExecNet], or [KindExec] if hostNet is false.
|
||||||
func (*execNetArtifact) Kind() Kind { return KindExecNet }
|
func (a *execMeasuredArtifact) Kind() Kind {
|
||||||
|
if a == nil || a.hostNet {
|
||||||
|
return KindExecNet
|
||||||
|
}
|
||||||
|
return KindExec
|
||||||
|
}
|
||||||
|
|
||||||
// Cure cures the [Artifact] in the container described by the caller. The
|
// Cure cures the [Artifact] in the container described by the caller. The
|
||||||
// container retains host networking.
|
// container optionally retains host networking.
|
||||||
func (a *execNetArtifact) Cure(f *FContext) error {
|
func (a *execMeasuredArtifact) Cure(f *FContext) error {
|
||||||
return a.cure(f, true)
|
return a.cure(f, a.hostNet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrNetChecksum is panicked by [NewExec] if host net namespace is requested
|
||||||
|
// with a nil checksum.
|
||||||
|
var ErrNetChecksum = errors.New("attempting to keep net namespace without checksum")
|
||||||
|
|
||||||
// NewExec returns a new [Artifact] that executes the program path in a
|
// NewExec returns a new [Artifact] that executes the program path in a
|
||||||
// container with specified paths bind mounted read-only in order. A private
|
// container with specified paths bind mounted read-only in order. A private
|
||||||
// instance of /proc and /dev is made available to the container.
|
// instance of /proc and /dev is made available to the container.
|
||||||
@@ -162,7 +209,7 @@ func (a *execNetArtifact) Cure(f *FContext) error {
|
|||||||
// regular or symlink.
|
// regular or symlink.
|
||||||
//
|
//
|
||||||
// If checksum is non-nil, the resulting [Artifact] implements [KnownChecksum]
|
// If checksum is non-nil, the resulting [Artifact] implements [KnownChecksum]
|
||||||
// and its container runs in the host net namespace.
|
// and its container optionally runs in the host net namespace.
|
||||||
//
|
//
|
||||||
// The container is allowed to run for the specified duration before the initial
|
// The container is allowed to run for the specified duration before the initial
|
||||||
// process and all processes originating from it is terminated. A zero or
|
// process and all processes originating from it is terminated. A zero or
|
||||||
@@ -173,10 +220,10 @@ func (a *execNetArtifact) Cure(f *FContext) error {
|
|||||||
// container and does not affect curing outcome. Because of this, it is omitted
|
// container and does not affect curing outcome. Because of this, it is omitted
|
||||||
// from parameter data for computing identifier.
|
// from parameter data for computing identifier.
|
||||||
func NewExec(
|
func NewExec(
|
||||||
name string,
|
name, arch string,
|
||||||
checksum *Checksum,
|
checksum *Checksum,
|
||||||
timeout time.Duration,
|
timeout time.Duration,
|
||||||
exclusive bool,
|
hostNet, exclusive bool,
|
||||||
|
|
||||||
dir *check.Absolute,
|
dir *check.Absolute,
|
||||||
env []string,
|
env []string,
|
||||||
@@ -188,17 +235,23 @@ func NewExec(
|
|||||||
if name == "" {
|
if name == "" {
|
||||||
name = "exec-" + filepath.Base(pathname.String())
|
name = "exec-" + filepath.Base(pathname.String())
|
||||||
}
|
}
|
||||||
|
if arch == "" {
|
||||||
|
arch = runtime.GOARCH
|
||||||
|
}
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = ExecTimeoutDefault
|
timeout = ExecTimeoutDefault
|
||||||
}
|
}
|
||||||
if timeout > ExecTimeoutMax {
|
if timeout > ExecTimeoutMax {
|
||||||
timeout = ExecTimeoutMax
|
timeout = ExecTimeoutMax
|
||||||
}
|
}
|
||||||
a := execArtifact{name, paths, dir, env, pathname, args, timeout, exclusive}
|
a := execArtifact{name, arch, paths, dir, env, pathname, args, timeout, exclusive}
|
||||||
if checksum == nil {
|
if checksum == nil {
|
||||||
|
if hostNet {
|
||||||
|
panic(ErrNetChecksum)
|
||||||
|
}
|
||||||
return &a
|
return &a
|
||||||
}
|
}
|
||||||
return &execNetArtifact{*checksum, a}
|
return &execMeasuredArtifact{*checksum, hostNet, a}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kind returns the hardcoded [Kind] constant.
|
// Kind returns the hardcoded [Kind] constant.
|
||||||
@@ -206,6 +259,7 @@ func (*execArtifact) Kind() Kind { return KindExec }
|
|||||||
|
|
||||||
// Params writes paths, executable pathname and args.
|
// Params writes paths, executable pathname and args.
|
||||||
func (a *execArtifact) Params(ctx *IContext) {
|
func (a *execArtifact) Params(ctx *IContext) {
|
||||||
|
ctx.WriteString(a.arch)
|
||||||
ctx.WriteString(a.name)
|
ctx.WriteString(a.name)
|
||||||
|
|
||||||
ctx.WriteUint32(uint32(len(a.paths)))
|
ctx.WriteUint32(uint32(len(a.paths)))
|
||||||
@@ -252,11 +306,26 @@ func (a *execArtifact) Params(ctx *IContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnsupportedArchError describes an unsupported or invalid architecture.
|
||||||
|
type UnsupportedArchError string
|
||||||
|
|
||||||
|
func (e UnsupportedArchError) Error() string {
|
||||||
|
if e == "" {
|
||||||
|
return "invalid architecture name"
|
||||||
|
}
|
||||||
|
return "unsupported architecture " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
// readExecArtifact interprets IR values and returns the address of execArtifact
|
// readExecArtifact interprets IR values and returns the address of execArtifact
|
||||||
// or execNetArtifact.
|
// or execNetArtifact.
|
||||||
func readExecArtifact(r *IRReader, net bool) Artifact {
|
func readExecArtifact(r *IRReader, net bool) Artifact {
|
||||||
r.DiscardAll()
|
r.DiscardAll()
|
||||||
|
|
||||||
|
arch := r.ReadString()
|
||||||
|
if arch == "" {
|
||||||
|
panic(UnsupportedArchError(arch))
|
||||||
|
}
|
||||||
|
|
||||||
name := r.ReadString()
|
name := r.ReadString()
|
||||||
|
|
||||||
sz := r.ReadUint32()
|
sz := r.ReadUint32()
|
||||||
@@ -307,22 +376,17 @@ func readExecArtifact(r *IRReader, net bool) Artifact {
|
|||||||
exclusive := r.ReadUint32() != 0
|
exclusive := r.ReadUint32() != 0
|
||||||
|
|
||||||
checksum, ok := r.Finalise()
|
checksum, ok := r.Finalise()
|
||||||
|
|
||||||
var checksumP *Checksum
|
var checksumP *Checksum
|
||||||
if net {
|
if ok {
|
||||||
if !ok {
|
checksumP = new(checksum.Value())
|
||||||
panic(ErrExpectedChecksum)
|
}
|
||||||
}
|
|
||||||
checksumVal := checksum.Value()
|
if net && !ok {
|
||||||
checksumP = &checksumVal
|
panic(ErrExpectedChecksum)
|
||||||
} else {
|
|
||||||
if ok {
|
|
||||||
panic(ErrUnexpectedChecksum)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewExec(
|
return NewExec(
|
||||||
name, checksumP, timeout, exclusive, dir, env, pathname, args, paths...,
|
name, arch, checksumP, timeout, net, exclusive, dir, env, pathname, args, paths...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,7 +461,7 @@ const SeccompPresets = std.PresetStrict &
|
|||||||
func (a *execArtifact) makeContainer(
|
func (a *execArtifact) makeContainer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
msg message.Msg,
|
msg message.Msg,
|
||||||
flags int,
|
flags, jobs int,
|
||||||
hostNet bool,
|
hostNet bool,
|
||||||
temp, work *check.Absolute,
|
temp, work *check.Absolute,
|
||||||
getArtifact GetArtifactFunc,
|
getArtifact GetArtifactFunc,
|
||||||
@@ -431,11 +495,23 @@ func (a *execArtifact) makeContainer(
|
|||||||
if z.HostNet {
|
if z.HostNet {
|
||||||
z.Hostname = "cure-net"
|
z.Hostname = "cure-net"
|
||||||
}
|
}
|
||||||
|
z.Quiet = flags&CSuppressInit != 0
|
||||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
||||||
|
z.Dir, z.Path, z.Args = a.dir, a.path, a.args
|
||||||
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
|
z.Env = slices.Concat(a.env, []string{EnvJobs + "=" + strconv.Itoa(jobs)})
|
||||||
z.Grow(len(a.paths) + 4)
|
z.Grow(len(a.paths) + 4)
|
||||||
|
|
||||||
|
if a.arch != runtime.GOARCH {
|
||||||
|
binfmtMu.RLock()
|
||||||
|
e, ok := binfmt[a.arch]
|
||||||
|
binfmtMu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return nil, UnsupportedArchError(a.arch)
|
||||||
|
}
|
||||||
|
z.Binfmt = []container.BinfmtEntry{e}
|
||||||
|
z.InitAsRoot = true
|
||||||
|
}
|
||||||
|
|
||||||
for i, b := range a.paths {
|
for i, b := range a.paths {
|
||||||
if i == overlayWorkIndex {
|
if i == overlayWorkIndex {
|
||||||
if err = os.MkdirAll(work.String(), 0700); err != nil {
|
if err = os.MkdirAll(work.String(), 0700); err != nil {
|
||||||
@@ -522,9 +598,9 @@ func (c *Cache) EnterExec(
|
|||||||
case *execArtifact:
|
case *execArtifact:
|
||||||
e = f
|
e = f
|
||||||
|
|
||||||
case *execNetArtifact:
|
case *execMeasuredArtifact:
|
||||||
e = &f.execArtifact
|
e = &f.execArtifact
|
||||||
hostNet = true
|
hostNet = f.hostNet
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ErrNotExec
|
return ErrNotExec
|
||||||
@@ -563,6 +639,7 @@ func (c *Cache) EnterExec(
|
|||||||
z, err = e.makeContainer(
|
z, err = e.makeContainer(
|
||||||
ctx, c.msg,
|
ctx, c.msg,
|
||||||
c.flags,
|
c.flags,
|
||||||
|
c.jobs,
|
||||||
hostNet,
|
hostNet,
|
||||||
temp, work,
|
temp, work,
|
||||||
func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
|
func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
|
||||||
@@ -602,7 +679,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
msg := f.GetMessage()
|
msg := f.GetMessage()
|
||||||
var z *container.Container
|
var z *container.Container
|
||||||
if z, err = a.makeContainer(
|
if z, err = a.makeContainer(
|
||||||
ctx, msg, f.cache.flags, hostNet,
|
ctx, msg, f.cache.flags, f.GetJobs(), hostNet,
|
||||||
f.GetTempDir(), f.GetWorkDir(),
|
f.GetTempDir(), f.GetWorkDir(),
|
||||||
f.GetArtifact,
|
f.GetArtifact,
|
||||||
f.cache.Ident,
|
f.cache.Ident,
|
||||||
@@ -624,12 +701,6 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
_ = stdout.Close()
|
_ = stdout.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
if err != nil && !errors.As(err, new(*exec.ExitError)) {
|
|
||||||
_ = stdout.Close()
|
|
||||||
_ = stderr.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
brStdout, brStderr := f.cache.getReader(stdout), f.cache.getReader(stderr)
|
brStdout, brStderr := f.cache.getReader(stdout), f.cache.getReader(stderr)
|
||||||
stdoutDone, stderrDone := make(chan struct{}), make(chan struct{})
|
stdoutDone, stderrDone := make(chan struct{}), make(chan struct{})
|
||||||
@@ -644,6 +715,11 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
io.TeeReader(brStderr, status),
|
io.TeeReader(brStderr, status),
|
||||||
)
|
)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
if err != nil && !errors.As(err, new(*exec.ExitError)) {
|
||||||
|
_ = stdout.Close()
|
||||||
|
_ = stderr.Close()
|
||||||
|
}
|
||||||
|
|
||||||
<-stdoutDone
|
<-stdoutDone
|
||||||
<-stderrDone
|
<-stderrDone
|
||||||
f.cache.putReader(brStdout)
|
f.cache.putReader(brStdout)
|
||||||
|
|||||||
+297
-50
@@ -1,44 +1,70 @@
|
|||||||
package pkg_test
|
package pkg_test
|
||||||
|
|
||||||
//go:generate env CGO_ENABLED=0 go build -tags testtool -o testdata/testtool ./testdata
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"unique"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/info"
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
"hakurei.app/internal/stub"
|
"hakurei.app/internal/stub"
|
||||||
|
|
||||||
|
"hakurei.app/internal/pkg/internal/testtool/expected"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testtoolBin is the container test tool binary made available to the
|
// testtoolBin is the container test tool binary made available to the
|
||||||
// execArtifact for testing its curing environment.
|
// execArtifact for testing its curing environment.
|
||||||
//
|
//
|
||||||
//go:embed testdata/testtool
|
//go:generate env CGO_ENABLED=0 go build -tags testtool -o internal/testtool ./internal/testtool
|
||||||
|
//go:embed internal/testtool/testtool
|
||||||
var testtoolBin []byte
|
var testtoolBin []byte
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pathname, err := filepath.Abs("internal/testtool/testtool")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
pkg.RegisterArch("cafe", container.BinfmtEntry{
|
||||||
|
Magic: expected.Magic,
|
||||||
|
Interpreter: check.MustAbs(pathname),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestExec(t *testing.T) {
|
func TestExec(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantChecksumOffline := pkg.MustDecode(
|
wantOffline := expectsFS{
|
||||||
"GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9",
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
)
|
|
||||||
|
"check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
}
|
||||||
|
wantOfflineEncode := pkg.Encode(wantOffline.hash())
|
||||||
|
failingArtifact := &stubArtifact{
|
||||||
|
kind: pkg.KindTar,
|
||||||
|
params: []byte("doomed artifact"),
|
||||||
|
cure: func(t *pkg.TContext) error {
|
||||||
|
return stub.UniqueError(0xcafe)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
checkWithCache(t, []cacheTestCase{
|
checkWithCache(t, []cacheTestCase{
|
||||||
{"offline", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"offline", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
|
|
||||||
cureMany(t, c, []cureStep{
|
cureMany(t, c, []cureStep{
|
||||||
{"container", pkg.NewExec(
|
{"container", pkg.NewExec(
|
||||||
"exec-offline", nil, 0, false,
|
"exec-offline", "", new(wantOffline.hash()), 0, false, false,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1"},
|
[]string{"HAKUREI_TEST=1"},
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
@@ -58,67 +84,128 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantChecksumOffline, nil},
|
), ignorePathname, wantOffline, nil},
|
||||||
|
|
||||||
{"error passthrough", pkg.NewExec(
|
{"substitution", pkg.NewExec(
|
||||||
"", nil, 0, true,
|
"exec-offline", "", new(wantOffline.hash()), 0, false, false,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1"},
|
[]string{"HAKUREI_TEST=1"},
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
[]string{"testtool"},
|
[]string{"testtool"},
|
||||||
|
|
||||||
pkg.MustPath("/proc/nonexistent", false, &stubArtifact{
|
pkg.MustPath("/file", false, newStubFile(
|
||||||
|
pkg.KindHTTPGet,
|
||||||
|
pkg.ID{0xfe, 0},
|
||||||
|
nil,
|
||||||
|
nil, nil,
|
||||||
|
)),
|
||||||
|
// substitution miss fails in testtool due to differing idents
|
||||||
|
pkg.MustPath("/.hakurei", false, &stubArtifact{
|
||||||
kind: pkg.KindTar,
|
kind: pkg.KindTar,
|
||||||
params: []byte("doomed artifact"),
|
params: []byte("empty directory (substituted)"),
|
||||||
cure: func(t *pkg.TContext) error {
|
cure: func(t *pkg.TContext) error {
|
||||||
return stub.UniqueError(0xcafe)
|
return os.MkdirAll(t.GetWorkDir().String(), 0700)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
), nil, pkg.Checksum{}, &pkg.DependencyCureError{
|
pkg.MustPath("/opt", false, testtool),
|
||||||
|
), ignorePathname, wantOffline, nil},
|
||||||
|
|
||||||
|
{"error passthrough", pkg.NewExec(
|
||||||
|
"", "", nil, 0, false, true,
|
||||||
|
pkg.AbsWork,
|
||||||
|
[]string{"HAKUREI_TEST=1"},
|
||||||
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
|
[]string{"testtool"},
|
||||||
|
|
||||||
|
pkg.MustPath("/proc/nonexistent", false, failingArtifact),
|
||||||
|
), nil, nil, &pkg.DependencyCureError{
|
||||||
{
|
{
|
||||||
Ident: unique.Make(pkg.ID(pkg.MustDecode(
|
A: failingArtifact,
|
||||||
"Sowo6oZRmG6xVtUaxB6bDWZhVsqAJsIJWUp0OPKlE103cY0lodx7dem8J-qQF0Z1",
|
|
||||||
))),
|
|
||||||
Err: stub.UniqueError(0xcafe),
|
Err: stub.UniqueError(0xcafe),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"invalid paths", pkg.NewExec(
|
{"invalid paths", pkg.NewExec(
|
||||||
"", nil, 0, false,
|
"", "", nil, 0, false, false,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1"},
|
[]string{"HAKUREI_TEST=1"},
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
[]string{"testtool"},
|
[]string{"testtool"},
|
||||||
|
|
||||||
pkg.ExecPath{},
|
pkg.ExecPath{},
|
||||||
), nil, pkg.Checksum{}, pkg.ErrInvalidPaths},
|
), nil, nil, pkg.ErrInvalidPaths},
|
||||||
})
|
})
|
||||||
|
|
||||||
// check init failure passthrough
|
// check init failure passthrough
|
||||||
var exitError *exec.ExitError
|
initFailureArtifact := pkg.NewExec(
|
||||||
if _, _, err := c.Cure(pkg.NewExec(
|
"", "", nil, 0, false, false,
|
||||||
"", nil, 0, false,
|
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
nil,
|
nil,
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
[]string{"testtool"},
|
[]string{"testtool"},
|
||||||
)); !errors.As(err, &exitError) ||
|
)
|
||||||
|
var exitError *exec.ExitError
|
||||||
|
if _, _, err := c.Cure(initFailureArtifact); !errors.As(err, &exitError) ||
|
||||||
exitError.ExitCode() != hst.ExitFailure {
|
exitError.ExitCode() != hst.ExitFailure {
|
||||||
t.Fatalf("Cure: error = %v, want init exit status 1", err)
|
t.Fatalf("Cure: error = %v, want init exit status 1", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
testtoolDestroy(t, base, c)
|
var faultStatus []byte
|
||||||
}, pkg.MustDecode("Q5DluWQCAeohLoiGRImurwFp3vdz9IfQCoj7Fuhh73s4KQPRHpEQEnHTdNHmB8Fx")},
|
if faults, err := c.ReadFaults(initFailureArtifact); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if len(faults) != 1 {
|
||||||
|
t.Fatalf("ReadFaults: %v", faults)
|
||||||
|
} else if faultStatus, err = os.ReadFile(faults[0].String()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err = faults[0].Destroy(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
t.Logf("destroyed expected fault at %s", faults[0].Time().UTC())
|
||||||
|
}
|
||||||
|
|
||||||
{"net", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
if !bytes.HasPrefix(faultStatus, []byte(
|
||||||
|
"internal/pkg ",
|
||||||
|
)) || !bytes.Contains(faultStatus, []byte(
|
||||||
|
"\ninit: fork/exec /opt/bin/testtool: no such file or directory\n",
|
||||||
|
)) {
|
||||||
|
t.Errorf("unexpected status:\n%s", string(faultStatus))
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyStatus(t, base, 2, 1)
|
||||||
|
testtoolDestroy(t, base, c)
|
||||||
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/QwS7SmiatdqryQYgESdGw7Yw2PcpNf0vNfpvUA0t92BTlKiUjfCrXyMW17G2X77X": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
|
"identifier/" + expected.Offline: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
|
"identifier/" + expected.OfflineS: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"temp": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"net", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
|
|
||||||
wantChecksum := pkg.MustDecode(
|
wantNet := expectsFS{
|
||||||
"a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W",
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
)
|
|
||||||
|
"check": {Mode: 0400, Data: []byte("net")},
|
||||||
|
}
|
||||||
cureMany(t, c, []cureStep{
|
cureMany(t, c, []cureStep{
|
||||||
{"container", pkg.NewExec(
|
{"container", pkg.NewExec(
|
||||||
"exec-net", &wantChecksum, 0, false,
|
"exec-net", "", new(wantNet.hash()), 0, true, false,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1"},
|
[]string{"HAKUREI_TEST=1"},
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
@@ -138,18 +225,37 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantChecksum, nil},
|
), ignorePathname, wantNet, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
destroyStatus(t, base, 2, 0)
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, pkg.MustDecode("bPYvvqxpfV7xcC1EptqyKNK1klLJgYHMDkzBcoOyK6j_Aj5hb0mXNPwTwPSK5F6Z")},
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
{"overlay root", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
||||||
|
"checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W/check": {Mode: 0400, Data: []byte("net")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/" + expected.Net: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
|
||||||
|
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"temp": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"overlay root", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
|
|
||||||
cureMany(t, c, []cureStep{
|
cureMany(t, c, []cureStep{
|
||||||
{"container", pkg.NewExec(
|
{"container", pkg.NewExec(
|
||||||
"exec-overlay-root", nil, 0, false,
|
"exec-overlay-root", "", nil, 0, false, false,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
|
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
@@ -163,18 +269,35 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantChecksumOffline, nil},
|
), ignorePathname, wantOffline, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
destroyStatus(t, base, 2, 0)
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, pkg.MustDecode("PO2DSSCa4yoSgEYRcCSZfQfwow1yRigL3Ry-hI0RDI4aGuFBha-EfXeSJnG_5_Rl")},
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
{"overlay work", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/" + expected.OvlRoot: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"temp": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"overlay work", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
|
|
||||||
cureMany(t, c, []cureStep{
|
cureMany(t, c, []cureStep{
|
||||||
{"container", pkg.NewExec(
|
{"container", pkg.NewExec(
|
||||||
"exec-overlay-work", nil, 0, false,
|
"exec-overlay-work", "", nil, 0, false, false,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
|
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
|
||||||
check.MustAbs("/work/bin/testtool"),
|
check.MustAbs("/work/bin/testtool"),
|
||||||
@@ -193,18 +316,35 @@ func TestExec(t *testing.T) {
|
|||||||
return os.MkdirAll(t.GetWorkDir().String(), 0700)
|
return os.MkdirAll(t.GetWorkDir().String(), 0700)
|
||||||
},
|
},
|
||||||
}), pkg.Path(pkg.AbsWork, false /* ignored */, testtool),
|
}), pkg.Path(pkg.AbsWork, false /* ignored */, testtool),
|
||||||
), ignorePathname, wantChecksumOffline, nil},
|
), ignorePathname, wantOffline, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
destroyStatus(t, base, 2, 0)
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, pkg.MustDecode("iaRt6l_Wm2n-h5UsDewZxQkCmjZjyL8r7wv32QT2kyV55-Lx09Dq4gfg9BiwPnKs")},
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
{"multiple layers", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/" + expected.Work: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"temp": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"multiple layers", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
|
|
||||||
cureMany(t, c, []cureStep{
|
cureMany(t, c, []cureStep{
|
||||||
{"container", pkg.NewExec(
|
{"container", pkg.NewExec(
|
||||||
"exec-multiple-layers", nil, 0, false,
|
"exec-multiple-layers", "", nil, 0, false, false,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
|
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
@@ -245,18 +385,40 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantChecksumOffline, nil},
|
), ignorePathname, wantOffline, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
destroyStatus(t, base, 2, 0)
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, pkg.MustDecode("O2YzyR7IUGU5J2CADy0hUZ3A5NkP_Vwzs4UadEdn2oMZZVWRtH0xZGJ3HXiimTnZ")},
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
{"overlay layer promotion", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
||||||
|
"checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK/check": {Mode: 0400, Data: []byte("layers")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
|
"identifier/B-kc5iJMx8GtlCua4dz6BiJHnDAOUfPjgpbKq4e-QEn0_CZkSYs3fOA1ve06qMs2": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
|
||||||
|
"identifier/" + expected.Layers: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"temp": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"overlay layer promotion", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
|
|
||||||
cureMany(t, c, []cureStep{
|
cureMany(t, c, []cureStep{
|
||||||
{"container", pkg.NewExec(
|
{"container", pkg.NewExec(
|
||||||
"exec-layer-promotion", nil, 0, true,
|
"exec-layer-promotion", "", nil, 0, false, true,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
|
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
@@ -276,11 +438,96 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantChecksumOffline, nil},
|
), ignorePathname, wantOffline, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
destroyStatus(t, base, 2, 0)
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, pkg.MustDecode("3EaW6WibLi9gl03_UieiFPaFcPy5p4x3JPxrnLJxGaTI-bh3HU9DK9IMx7c3rrNm")},
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"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/" + expected.Promote: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"temp": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"binfmt", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
|
if info.CanDegrade && os.Getenv("ROSA_SKIP_BINFMT") != "" {
|
||||||
|
t.Skip("binfmt_misc test explicitly skipped")
|
||||||
|
}
|
||||||
|
|
||||||
|
cureMany(t, c, []cureStep{
|
||||||
|
{"container", pkg.NewExec(
|
||||||
|
"exec-binfmt", "cafe", nil, 0, false, true,
|
||||||
|
pkg.AbsWork,
|
||||||
|
[]string{"HAKUREI_TEST=1", "HAKUREI_BINFMT=1"},
|
||||||
|
check.MustAbs("/opt/bin/sample"),
|
||||||
|
[]string{"sample"},
|
||||||
|
|
||||||
|
pkg.MustPath("/", true, &stubArtifact{
|
||||||
|
kind: pkg.KindTar,
|
||||||
|
params: []byte("empty directory"),
|
||||||
|
cure: func(t *pkg.TContext) error {
|
||||||
|
return os.MkdirAll(t.GetWorkDir().String(), 0700)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
pkg.MustPath("/opt", false, overrideIdent{pkg.ID{0xfe, 0xff}, &stubArtifact{
|
||||||
|
kind: pkg.KindTar,
|
||||||
|
cure: func(t *pkg.TContext) error {
|
||||||
|
work := t.GetWorkDir()
|
||||||
|
if err := os.MkdirAll(
|
||||||
|
work.Append("bin").String(),
|
||||||
|
0700,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(t.GetWorkDir().Append(
|
||||||
|
"bin",
|
||||||
|
"sample",
|
||||||
|
).String(), []byte(expected.Full), 0500)
|
||||||
|
},
|
||||||
|
}}),
|
||||||
|
), ignorePathname, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"check": {Mode: 0400, Data: []byte("binfmt")},
|
||||||
|
}, nil},
|
||||||
|
})
|
||||||
|
|
||||||
|
destroyStatus(t, base, 2, 0)
|
||||||
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/5aevg3YpDxjqQZ-pdvXK7YqgkL5JKqcoStYQxeD96kuYar6K2mRQWMHib6NQRnpV": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/5aevg3YpDxjqQZ-pdvXK7YqgkL5JKqcoStYQxeD96kuYar6K2mRQWMHib6NQRnpV/bin": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/5aevg3YpDxjqQZ-pdvXK7YqgkL5JKqcoStYQxeD96kuYar6K2mRQWMHib6NQRnpV/bin/sample": {Mode: 0500, Data: []byte("\xca\xfe\xba\xbe\xfd\xfd:3")},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/UnDo4B5KneEUY5b4vRUk_y9MWgkWuw2N8f8a2XayO686xXur-aZmX2-7n_8tKMe3": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/UnDo4B5KneEUY5b4vRUk_y9MWgkWuw2N8f8a2XayO686xXur-aZmX2-7n_8tKMe3/check": {Mode: 0400, Data: []byte("binfmt")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/6VQTJ1lI5BmVuI1YFYJ8ClO3MRORvTTrcWFDcUU-l5Ga8EofxCxGlSTYN-u8dKj_": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UnDo4B5KneEUY5b4vRUk_y9MWgkWuw2N8f8a2XayO686xXur-aZmX2-7n_8tKMe3")},
|
||||||
|
"identifier/_v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/5aevg3YpDxjqQZ-pdvXK7YqgkL5JKqcoStYQxeD96kuYar6K2mRQWMHib6NQRnpV")},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"temp": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package pkg_test
|
package pkg_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
@@ -10,18 +11,27 @@ import (
|
|||||||
func TestFile(t *testing.T) {
|
func TestFile(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
want := expectsFile{0}
|
||||||
checkWithCache(t, []cacheTestCase{
|
checkWithCache(t, []cacheTestCase{
|
||||||
{"file", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"file", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
cureMany(t, c, []cureStep{
|
cureMany(t, c, []cureStep{
|
||||||
{"short", pkg.NewFile("null", []byte{0}), base.Append(
|
{"short", pkg.NewFile("null", []byte{0}), base.Append(
|
||||||
"identifier",
|
"identifier",
|
||||||
"3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi",
|
"3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi",
|
||||||
), pkg.MustDecode(
|
), want, nil},
|
||||||
"vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX",
|
|
||||||
), nil},
|
|
||||||
})
|
})
|
||||||
}, pkg.MustDecode(
|
}, expectsFS{
|
||||||
"iR6H5OIsyOW4EwEgtm9rGzGF6DVtyHLySEtwnFE8bnus9VJcoCbR4JIek7Lw-vwT",
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
)},
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/" + pkg.Encode(want.hash()): {Mode: 0400, Data: []byte{0}},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// Package expected contains data shared between test helper and test harness.
|
||||||
|
package expected
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Magic are magic bytes in the binfmt test case.
|
||||||
|
Magic = "\xca\xfe\xba\xbe\xfd\xfd"
|
||||||
|
// Full is the full content of the binfmt test case executable.
|
||||||
|
Full = Magic + ":3"
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package expected
|
||||||
|
|
||||||
|
const (
|
||||||
|
Offline = "q5ktDTq0miP-VvB2blxqXQeaRXCUWgP_KbC18KNtUDtyoaI_h5mHmGuPMArVEBDs"
|
||||||
|
OfflineS = "IY91PCtOpCYy21AaIK0c9f8-Z6fb2_2ewoHWkt4dxoLf0GOrWqS8yAGFLV84b1Dw"
|
||||||
|
OvlRoot = "NacZGXwuRkTvcHaG08a22ujJ8qCWN0RSoFlRSR5FSt0ZcBbJ28FRvkYsHEtX7G8i"
|
||||||
|
Layers = "WBJDrATtX6rIE5yAu8ePX3WmDF0Tt9kFiue0m3cRnyRoVx1my8a67fh3CAW486oP"
|
||||||
|
Net = "CmYtj2sNB3LHtqiDuck_Lz3MjLLIiwyP8N4NDitQ1Icvv__LVP9p8tm-sHeQaKKp"
|
||||||
|
Promote = "TX3eCloaQFkV-SZIH6Jg6E5WKH--rcXY1P0jnZKmLFKWrNqnOzd4G9eIBh6i5ywN"
|
||||||
|
Work = "OuNiLSC68pZhAOr1YQ4WbV1tzASA0nxLEBcK7lO7MqxDY_j8dmP_C612RTuF23Lu"
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package expected
|
||||||
|
|
||||||
|
const (
|
||||||
|
Offline = "WapqyoPxbWSnq07dWHt71mHaJXq99pAjJfFlELlJljSiZMhTFqqlzU1_mN86shSj"
|
||||||
|
OfflineS = "ibQZHcdXgNQ1OiMX1FrburBbGPVvKEHvPilbQCkm_0oV0BQCHomyyTbYNrFMGIwl"
|
||||||
|
OvlRoot = "V9anFOiRvjGfAeBhLl14AL8TKdWZyD0WTPYe4fS9mOBw8iW5Lmarvt6TG6MV8uWm"
|
||||||
|
Layers = "tKx7JNRoSBdK_7MdzI-nwTNV2wmiPzwYdcd17oLmXKL_iLmUzUiA79qTqdrTasrv"
|
||||||
|
Net = "aXyDLzBCJ9XltXZIfetEVsEkrqHfcXuD5XE_FcUnYbN3emwL55N6P8LlHzNfGnM5"
|
||||||
|
Promote = "3k4V16n96Lq04gjFSKmm4sFjyQ883FFBNXgTy9s_DjeTwxT3pg_iacEh8yMb_S4m"
|
||||||
|
Work = "6Q49MhFWRE3Ne6MycwAotgl1GtoU5WCHqJNWG2byYZCY-zX-IxPrWiKk7bKkNzhE"
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package expected
|
||||||
|
|
||||||
|
const (
|
||||||
|
Offline = "Z6yXE5gOJScL3srmnVMWgCXccDiUNZ5snSrf6RkXuU1_U0rX_kGVwsfHUgNG_awd"
|
||||||
|
OfflineS = "zN16xv6LKRJRipUJwupyxg2rZcvf-qpsMn_qCxUmgxlTSuNwYI70ZEb7dHW5k0gO"
|
||||||
|
OvlRoot = "zYXJHFRLuxvUhuisZEXgGgVvdQd6piMfp5jmtT6jdVjvC2gICXquOq-UTwlrSD5I"
|
||||||
|
Layers = "_F8EDazHbcLeT0sVSQXRN_kn9IjduqJcDYgzXpsT-hpKU4EBcZ0PISN2zchpqMbm"
|
||||||
|
Net = "CA_FAaSIYJgapBEHV40doxpH23PdUEy_6s1TZc7wfSPN0XYqwGpMceXXDSabGveO"
|
||||||
|
Promote = "_3LPrLp--4h9k4GsNNApu9hHtAafq-GUhfU6d4hJKBDKT3bz_szOsvkXxc5sK53d"
|
||||||
|
Work = "FEgHeiCD_WT4wsfB-9kDH5n6cRWCEYtJmXdKZgmUUukAOoXumH_hLlosXREC-tqq"
|
||||||
|
)
|
||||||
@@ -9,18 +9,38 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
"hakurei.app/vfs"
|
"hakurei.app/vfs"
|
||||||
|
|
||||||
|
"hakurei.app/internal/pkg/internal/testtool/expected"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("testtool: ")
|
log.SetPrefix("testtool: ")
|
||||||
|
|
||||||
|
if os.Getenv("HAKUREI_BINFMT") == "1" {
|
||||||
|
wantArgs := []string{"/interpreter", "/opt/bin/sample"}
|
||||||
|
if !slices.Equal(os.Args, wantArgs) {
|
||||||
|
log.Fatalf("Args: %q, want %q", os.Args, wantArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile("check", []byte("binfmt"), 0400); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
environ := slices.DeleteFunc(slices.Clone(os.Environ()), func(s string) bool {
|
||||||
|
return s == "CURE_JOBS="+strconv.Itoa(runtime.NumCPU())
|
||||||
|
})
|
||||||
|
|
||||||
var hostNet, layers, promote bool
|
var hostNet, layers, promote bool
|
||||||
if len(os.Args) == 2 && os.Args[0] == "testtool" {
|
if len(os.Args) == 2 && os.Args[0] == "testtool" {
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
@@ -48,15 +68,15 @@ func main() {
|
|||||||
|
|
||||||
var overlayRoot bool
|
var overlayRoot bool
|
||||||
wantEnv := []string{"HAKUREI_TEST=1"}
|
wantEnv := []string{"HAKUREI_TEST=1"}
|
||||||
if len(os.Environ()) == 2 {
|
if len(environ) == 2 {
|
||||||
overlayRoot = true
|
overlayRoot = true
|
||||||
if !layers && !promote {
|
if !layers && !promote {
|
||||||
log.SetPrefix("testtool(overlay root): ")
|
log.SetPrefix("testtool(overlay root): ")
|
||||||
}
|
}
|
||||||
wantEnv = []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}
|
wantEnv = []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}
|
||||||
}
|
}
|
||||||
if !slices.Equal(wantEnv, os.Environ()) {
|
if !slices.Equal(wantEnv, environ) {
|
||||||
log.Fatalf("Environ: %q, want %q", os.Environ(), wantEnv)
|
log.Fatalf("Environ: %q, want %q", environ, wantEnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
var overlayWork bool
|
var overlayWork bool
|
||||||
@@ -142,59 +162,40 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const checksumEmptyDir = "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"
|
const checksumEmptyDir = "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"
|
||||||
ident := "dztPS6jRjiZtCF4_p8AzfnxGp6obkhrgFVsxdodbKWUoAEVtDz3MykepJB4kI_ks"
|
ident := expected.Offline
|
||||||
log.Println(m)
|
log.Println(m)
|
||||||
next := func() { m = m.Next; log.Println(m) }
|
next := func() { m = m.Next; log.Println(m) }
|
||||||
|
|
||||||
if overlayRoot {
|
if overlayRoot {
|
||||||
ident = "RdMA-mubnrHuu3Ky1wWyxauSYCO0ZH_zCPUj3uDHqkfwv5sGcByoF_g5PjlGiClb"
|
ident = expected.OvlRoot
|
||||||
|
|
||||||
if m.Root != "/" || m.Target != "/" ||
|
if m.Root != "/" || m.Target != "/" ||
|
||||||
m.Source != "overlay" || m.FsType != "overlay" {
|
m.Source != "overlay" || m.FsType != "overlay" {
|
||||||
log.Fatal("unexpected root mount entry")
|
log.Fatal("unexpected root mount entry")
|
||||||
}
|
}
|
||||||
var lowerdir string
|
var lowerdir []string
|
||||||
for _, o := range strings.Split(m.FsOptstr, ",") {
|
for _, o := range strings.Split(m.FsOptstr, ",") {
|
||||||
const lowerdirKey = "lowerdir="
|
const lowerdirKey = "lowerdir+="
|
||||||
if strings.HasPrefix(o, lowerdirKey) {
|
if strings.HasPrefix(o, lowerdirKey) {
|
||||||
lowerdir = o[len(lowerdirKey):]
|
lowerdir = append(lowerdir, o[len(lowerdirKey):])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !layers {
|
if !layers {
|
||||||
if filepath.Base(lowerdir) != checksumEmptyDir {
|
if len(lowerdir) != 1 || filepath.Base(lowerdir[0]) != checksumEmptyDir {
|
||||||
log.Fatal("unexpected artifact checksum")
|
log.Fatal("unexpected artifact checksum")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ident = "p1t_drXr34i-jZNuxDMLaMOdL6tZvQqhavNafGynGqxOZoXAUTSn7kqNh3Ovv3DT"
|
ident = expected.Layers
|
||||||
|
|
||||||
lowerdirsEscaped := strings.Split(lowerdir, ":")
|
if len(lowerdir) != 2 ||
|
||||||
lowerdirs := lowerdirsEscaped[:0]
|
filepath.Base(lowerdir[0]) != "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU" ||
|
||||||
// ignore the option separator since it does not appear in ident
|
filepath.Base(lowerdir[1]) != "nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK" {
|
||||||
for i, e := range lowerdirsEscaped {
|
log.Fatalf("unexpected lowerdirs %s", strings.Join(lowerdir, ", "))
|
||||||
if len(e) > 0 &&
|
|
||||||
e[len(e)-1] == check.SpecialOverlayEscape[0] &&
|
|
||||||
(len(e) == 1 || e[len(e)-2] != check.SpecialOverlayEscape[0]) {
|
|
||||||
// ignore escaped pathname separator since it does not
|
|
||||||
// appear in ident
|
|
||||||
|
|
||||||
e = e[:len(e)-1]
|
|
||||||
if len(lowerdirsEscaped) != i {
|
|
||||||
lowerdirsEscaped[i+1] = e + lowerdirsEscaped[i+1]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lowerdirs = append(lowerdirs, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(lowerdirs) != 2 ||
|
|
||||||
filepath.Base(lowerdirs[0]) != "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU" ||
|
|
||||||
filepath.Base(lowerdirs[1]) != "nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK" {
|
|
||||||
log.Fatalf("unexpected lowerdirs %s", strings.Join(lowerdirs, ", "))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if hostNet {
|
if hostNet {
|
||||||
ident = "G8qPxD9puvvoOVV7lrT80eyDeIl3G_CCFoKw12c8mCjMdG1zF7NEPkwYpNubClK3"
|
ident = expected.Net
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.Root != "/sysroot" || m.Target != "/" {
|
if m.Root != "/sysroot" || m.Target != "/" {
|
||||||
@@ -213,14 +214,14 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if promote {
|
if promote {
|
||||||
ident = "xXTIYcXmgJWNLC91c417RRrNM9cjELwEZHpGvf8Fk_GNP5agRJp_SicD0w9aMeLJ"
|
ident = expected.Promote
|
||||||
}
|
}
|
||||||
|
|
||||||
next() // testtool artifact
|
next() // testtool artifact
|
||||||
|
|
||||||
next()
|
next()
|
||||||
if overlayWork {
|
if overlayWork {
|
||||||
ident = "5hlaukCirnXE4W_RSLJFOZN47Z5RiHnacXzdFp_70cLgiJUGR6cSb_HaFftkzi0-"
|
ident = expected.Work
|
||||||
if m.Root != "/" || m.Target != "/work" ||
|
if m.Root != "/" || m.Target != "/work" ||
|
||||||
m.Source != "overlay" || m.FsType != "overlay" {
|
m.Source != "overlay" || m.FsType != "overlay" {
|
||||||
log.Fatal("unexpected work mount entry")
|
log.Fatal("unexpected work mount entry")
|
||||||
+93
-28
@@ -3,7 +3,6 @@ package pkg
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -11,6 +10,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unique"
|
"unique"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -39,22 +39,48 @@ func panicToError(errP *error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// irCache implements [IRCache].
|
||||||
|
type irCache struct {
|
||||||
|
// Artifact to [unique.Handle] of identifier cache.
|
||||||
|
artifact sync.Map
|
||||||
|
// Identifier free list, must not be accessed directly.
|
||||||
|
identPool sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// zeroIRCache returns the initialised value of irCache.
|
||||||
|
func zeroIRCache() irCache {
|
||||||
|
return irCache{
|
||||||
|
identPool: sync.Pool{New: func() any { return new(extIdent) }},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IRCache provides memory management and caching primitives for IR and
|
||||||
|
// identifier operations against [Artifact] implementations.
|
||||||
|
//
|
||||||
|
// The zero value is not safe for use.
|
||||||
|
type IRCache struct{ irCache }
|
||||||
|
|
||||||
|
// NewIR returns the address of a new [IRCache].
|
||||||
|
func NewIR() *IRCache {
|
||||||
|
return &IRCache{zeroIRCache()}
|
||||||
|
}
|
||||||
|
|
||||||
// IContext is passed to [Artifact.Params] and provides methods for writing
|
// IContext is passed to [Artifact.Params] and provides methods for writing
|
||||||
// values to the IR writer. It does not expose the underlying [io.Writer].
|
// values to the IR writer. It does not expose the underlying [io.Writer].
|
||||||
//
|
//
|
||||||
// IContext is valid until [Artifact.Params] returns.
|
// IContext is valid until [Artifact.Params] returns.
|
||||||
type IContext struct {
|
type IContext struct {
|
||||||
// Address of underlying [Cache], should be zeroed or made unusable after
|
// Address of underlying irCache, should be zeroed or made unusable after
|
||||||
// [Artifact.Params] returns and must not be exposed directly.
|
// [Artifact.Params] returns and must not be exposed directly.
|
||||||
cache *Cache
|
ic *irCache
|
||||||
// Written to by various methods, should be zeroed after [Artifact.Params]
|
// Written to by various methods, should be zeroed after [Artifact.Params]
|
||||||
// returns and must not be exposed directly.
|
// returns and must not be exposed directly.
|
||||||
w io.Writer
|
w io.Writer
|
||||||
|
// Optional [Artifact] to cureRes cache, replaces [IRKindIdent] with
|
||||||
|
// checksum values if non-nil.
|
||||||
|
inputs map[Artifact]cureRes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the underlying [context.Context].
|
|
||||||
func (i *IContext) Unwrap() context.Context { return i.cache.ctx }
|
|
||||||
|
|
||||||
// irZero is a zero IR word.
|
// irZero is a zero IR word.
|
||||||
var irZero [wordSize]byte
|
var irZero [wordSize]byte
|
||||||
|
|
||||||
@@ -136,11 +162,19 @@ func (i *IContext) mustWrite(p []byte) {
|
|||||||
// WriteIdent is not defined for an [Artifact] not part of the slice returned by
|
// WriteIdent is not defined for an [Artifact] not part of the slice returned by
|
||||||
// [Artifact.Dependencies].
|
// [Artifact.Dependencies].
|
||||||
func (i *IContext) WriteIdent(a Artifact) {
|
func (i *IContext) WriteIdent(a Artifact) {
|
||||||
buf := i.cache.getIdentBuf()
|
buf := i.ic.getIdentBuf()
|
||||||
defer i.cache.putIdentBuf(buf)
|
defer i.ic.putIdentBuf(buf)
|
||||||
|
|
||||||
IRKindIdent.encodeHeader(0).put(buf[:])
|
IRKindIdent.encodeHeader(0).put(buf[:])
|
||||||
*(*ID)(buf[wordSize:]) = i.cache.Ident(a).Value()
|
if i.inputs != nil {
|
||||||
|
res, ok := i.inputs[a]
|
||||||
|
if !ok {
|
||||||
|
panic(InvalidLookupError(i.ic.Ident(a).Value()))
|
||||||
|
}
|
||||||
|
*(*ID)(buf[wordSize:]) = res.checksum.Value()
|
||||||
|
} else {
|
||||||
|
*(*ID)(buf[wordSize:]) = i.ic.Ident(a).Value()
|
||||||
|
}
|
||||||
i.mustWrite(buf[:])
|
i.mustWrite(buf[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,20 +217,45 @@ func (i *IContext) WriteString(s string) {
|
|||||||
|
|
||||||
// Encode writes a deterministic, efficient representation of a to w and returns
|
// Encode writes a deterministic, efficient representation of a to w and returns
|
||||||
// the first non-nil error encountered while writing to w.
|
// the first non-nil error encountered while writing to w.
|
||||||
func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
|
func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
||||||
|
return ic.encode(w, a, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode implements Encode but replaces identifiers with their cured checksums
|
||||||
|
// for a non-nil ident. Caller must acquire Cache.identMu.
|
||||||
|
func (ic *irCache) encode(
|
||||||
|
w io.Writer,
|
||||||
|
a Artifact,
|
||||||
|
inputs map[Artifact]cureRes,
|
||||||
|
) (err error) {
|
||||||
deps := a.Dependencies()
|
deps := a.Dependencies()
|
||||||
idents := make([]*extIdent, len(deps))
|
idents := make([]*extIdent, len(deps))
|
||||||
for i, d := range deps {
|
if inputs == nil {
|
||||||
dbuf, did := c.unsafeIdent(d, true)
|
for i, d := range deps {
|
||||||
if dbuf == nil {
|
dbuf, did := ic.unsafeIdent(d, true)
|
||||||
dbuf = c.getIdentBuf()
|
if dbuf == nil {
|
||||||
|
dbuf = ic.getIdentBuf()
|
||||||
|
binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
|
||||||
|
*(*ID)(dbuf[wordSize:]) = did.Value()
|
||||||
|
} else {
|
||||||
|
ic.storeIdent(d, dbuf)
|
||||||
|
}
|
||||||
|
defer ic.putIdentBuf(dbuf)
|
||||||
|
idents[i] = dbuf
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i, d := range deps {
|
||||||
|
res, ok := inputs[d]
|
||||||
|
if !ok {
|
||||||
|
return InvalidLookupError(ic.Ident(d).Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
dbuf := ic.getIdentBuf()
|
||||||
binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
|
binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
|
||||||
*(*ID)(dbuf[wordSize:]) = did.Value()
|
*(*ID)(dbuf[wordSize:]) = res.checksum.Value()
|
||||||
} else {
|
defer ic.putIdentBuf(dbuf)
|
||||||
c.storeIdent(d, dbuf)
|
idents[i] = dbuf
|
||||||
}
|
}
|
||||||
defer c.putIdentBuf(dbuf)
|
|
||||||
idents[i] = dbuf
|
|
||||||
}
|
}
|
||||||
slices.SortFunc(idents, func(a, b *extIdent) int {
|
slices.SortFunc(idents, func(a, b *extIdent) int {
|
||||||
return bytes.Compare(a[:], b[:])
|
return bytes.Compare(a[:], b[:])
|
||||||
@@ -221,10 +280,10 @@ func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
i := IContext{c, w}
|
i := IContext{ic, w, inputs}
|
||||||
|
|
||||||
defer panicToError(&err)
|
defer panicToError(&err)
|
||||||
defer func() { i.cache, i.w = nil, nil }()
|
defer func() { i.ic, i.w = nil, nil }()
|
||||||
|
|
||||||
a.Params(&i)
|
a.Params(&i)
|
||||||
}()
|
}()
|
||||||
@@ -233,7 +292,7 @@ func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var f IREndFlag
|
var f IREndFlag
|
||||||
kcBuf := c.getIdentBuf()
|
kcBuf := ic.getIdentBuf()
|
||||||
sz := wordSize
|
sz := wordSize
|
||||||
if kc, ok := a.(KnownChecksum); ok {
|
if kc, ok := a.(KnownChecksum); ok {
|
||||||
f |= IREndKnownChecksum
|
f |= IREndKnownChecksum
|
||||||
@@ -243,13 +302,13 @@ func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
|
|||||||
IRKindEnd.encodeHeader(uint32(f)).put(kcBuf[:])
|
IRKindEnd.encodeHeader(uint32(f)).put(kcBuf[:])
|
||||||
|
|
||||||
_, err = w.Write(kcBuf[:sz])
|
_, err = w.Write(kcBuf[:sz])
|
||||||
c.putIdentBuf(kcBuf)
|
ic.putIdentBuf(kcBuf)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeAll implements EncodeAll by recursively encoding dependencies and
|
// encodeAll implements EncodeAll by recursively encoding dependencies and
|
||||||
// performs deduplication by value via the encoded map.
|
// performs deduplication by value via the encoded map.
|
||||||
func (c *Cache) encodeAll(
|
func (ic *irCache) encodeAll(
|
||||||
w io.Writer,
|
w io.Writer,
|
||||||
a Artifact,
|
a Artifact,
|
||||||
encoded map[Artifact]struct{},
|
encoded map[Artifact]struct{},
|
||||||
@@ -259,13 +318,13 @@ func (c *Cache) encodeAll(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range a.Dependencies() {
|
for _, d := range a.Dependencies() {
|
||||||
if err = c.encodeAll(w, d, encoded); err != nil {
|
if err = ic.encodeAll(w, d, encoded); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
encoded[a] = struct{}{}
|
encoded[a] = struct{}{}
|
||||||
return c.Encode(w, a)
|
return ic.Encode(w, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeAll writes a self-describing IR stream of a to w and returns the first
|
// EncodeAll writes a self-describing IR stream of a to w and returns the first
|
||||||
@@ -283,8 +342,8 @@ func (c *Cache) encodeAll(
|
|||||||
// the ident cache, nor does it contribute identifiers it computes back to the
|
// the ident cache, nor does it contribute identifiers it computes back to the
|
||||||
// ident cache. Because of this, multiple invocations of EncodeAll will have
|
// ident cache. Because of this, multiple invocations of EncodeAll will have
|
||||||
// similar cost and does not amortise when combined with a call to Cure.
|
// similar cost and does not amortise when combined with a call to Cure.
|
||||||
func (c *Cache) EncodeAll(w io.Writer, a Artifact) error {
|
func (ic *irCache) EncodeAll(w io.Writer, a Artifact) error {
|
||||||
return c.encodeAll(w, a, make(map[Artifact]struct{}))
|
return ic.encodeAll(w, a, make(map[Artifact]struct{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrRemainingIR is returned for a [IRReadFunc] that failed to call
|
// ErrRemainingIR is returned for a [IRReadFunc] that failed to call
|
||||||
@@ -409,6 +468,12 @@ func (e InvalidKindError) Error() string {
|
|||||||
// register is not safe for concurrent use. register must not be called after
|
// register is not safe for concurrent use. register must not be called after
|
||||||
// the first instance of [Cache] has been opened.
|
// the first instance of [Cache] has been opened.
|
||||||
func register(k Kind, f IRReadFunc) {
|
func register(k Kind, f IRReadFunc) {
|
||||||
|
openMu.Lock()
|
||||||
|
defer openMu.Unlock()
|
||||||
|
|
||||||
|
if opened {
|
||||||
|
panic("attempting to register after open")
|
||||||
|
}
|
||||||
if _, ok := irArtifact[k]; ok {
|
if _, ok := irArtifact[k]; ok {
|
||||||
panic("attempting to register " + strconv.Itoa(int(k)) + " twice")
|
panic("attempting to register " + strconv.Itoa(int(k)) + " twice")
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-6
@@ -3,6 +3,7 @@ package pkg_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
)},
|
)},
|
||||||
|
|
||||||
{"exec offline", pkg.NewExec(
|
{"exec offline", pkg.NewExec(
|
||||||
"exec-offline", nil, 0, false,
|
"exec-offline", "", nil, 0, false, false,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1"},
|
[]string{"HAKUREI_TEST=1"},
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
@@ -58,9 +59,9 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
)},
|
)},
|
||||||
|
|
||||||
{"exec net", pkg.NewExec(
|
{"exec net", pkg.NewExec(
|
||||||
"exec-net",
|
"exec-net", "",
|
||||||
(*pkg.Checksum)(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
(*pkg.Checksum)(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||||
0, false,
|
0, false, false,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1"},
|
[]string{"HAKUREI_TEST=1"},
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
@@ -79,6 +80,28 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
)),
|
)),
|
||||||
)},
|
)},
|
||||||
|
|
||||||
|
{"exec measured", pkg.NewExec(
|
||||||
|
"exec-measured", "",
|
||||||
|
(*pkg.Checksum)(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))),
|
||||||
|
0, false, false,
|
||||||
|
pkg.AbsWork,
|
||||||
|
[]string{"HAKUREI_TEST=1"},
|
||||||
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
|
[]string{"testtool", "measured"},
|
||||||
|
|
||||||
|
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{0xfd}, len(pkg.Checksum{}))),
|
||||||
|
pkg.TarUncompressed,
|
||||||
|
)), pkg.MustPath("/opt", false, pkg.NewHTTPGetTar(
|
||||||
|
nil, "file:///testtool.tar.gz",
|
||||||
|
pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))),
|
||||||
|
pkg.TarGzip,
|
||||||
|
)),
|
||||||
|
)},
|
||||||
|
|
||||||
{"file anonymous", pkg.NewFile("", []byte{0})},
|
{"file anonymous", pkg.NewFile("", []byte{0})},
|
||||||
{"file", pkg.NewFile("stub", []byte("stub"))},
|
{"file", pkg.NewFile("stub", []byte("stub"))},
|
||||||
}
|
}
|
||||||
@@ -105,9 +128,13 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
if err := <-done; err != nil {
|
if err := <-done; err != nil {
|
||||||
t.Fatalf("EncodeAll: error = %v", err)
|
t.Fatalf("EncodeAll: error = %v", err)
|
||||||
}
|
}
|
||||||
}, pkg.MustDecode(
|
}, expectsFS{
|
||||||
"E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C",
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
),
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkWithCache(t, testCasesCache)
|
checkWithCache(t, testCasesCache)
|
||||||
|
|||||||
+29
-22
@@ -3,12 +3,12 @@ package pkg_test
|
|||||||
import (
|
import (
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
"unique"
|
"unique"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
@@ -33,20 +33,14 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
|
|
||||||
checkWithCache(t, []cacheTestCase{
|
checkWithCache(t, []cacheTestCase{
|
||||||
{"direct", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"direct", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
var r pkg.RContext
|
r := newRContext(t, c)
|
||||||
rCacheVal := reflect.ValueOf(&r).Elem().FieldByName("cache")
|
|
||||||
reflect.NewAt(
|
|
||||||
rCacheVal.Type(),
|
|
||||||
unsafe.Pointer(rCacheVal.UnsafeAddr()),
|
|
||||||
).Elem().Set(reflect.ValueOf(c))
|
|
||||||
|
|
||||||
f := pkg.NewHTTPGet(
|
f := pkg.NewHTTPGet(
|
||||||
&client,
|
&client,
|
||||||
"file:///testdata",
|
"file:///testdata",
|
||||||
testdataChecksum.Value(),
|
testdataChecksum.Value(),
|
||||||
)
|
)
|
||||||
var got []byte
|
var got []byte
|
||||||
if rc, err := f.Cure(&r); err != nil {
|
if rc, err := f.Cure(r); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
} else if got, err = io.ReadAll(rc); err != nil {
|
} else if got, err = io.ReadAll(rc); err != nil {
|
||||||
t.Fatalf("ReadAll: error = %v", err)
|
t.Fatalf("ReadAll: error = %v", err)
|
||||||
@@ -65,7 +59,7 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
wantErrMismatch := &pkg.ChecksumMismatchError{
|
wantErrMismatch := &pkg.ChecksumMismatchError{
|
||||||
Got: testdataChecksum.Value(),
|
Got: testdataChecksum.Value(),
|
||||||
}
|
}
|
||||||
if rc, err := f.Cure(&r); err != nil {
|
if rc, err := f.Cure(r); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
} else if got, err = io.ReadAll(rc); err != nil {
|
} else if got, err = io.ReadAll(rc); err != nil {
|
||||||
t.Fatalf("ReadAll: error = %v", err)
|
t.Fatalf("ReadAll: error = %v", err)
|
||||||
@@ -76,7 +70,7 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check fallback validation
|
// check fallback validation
|
||||||
if rc, err := f.Cure(&r); err != nil {
|
if rc, err := f.Cure(r); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
} else if err = rc.Close(); !reflect.DeepEqual(err, wantErrMismatch) {
|
} else if err = rc.Close(); !reflect.DeepEqual(err, wantErrMismatch) {
|
||||||
t.Fatalf("Close: error = %#v, want %#v", err, wantErrMismatch)
|
t.Fatalf("Close: error = %#v, want %#v", err, wantErrMismatch)
|
||||||
@@ -89,18 +83,19 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
pkg.Checksum{},
|
pkg.Checksum{},
|
||||||
)
|
)
|
||||||
wantErrNotFound := pkg.ResponseStatusError(http.StatusNotFound)
|
wantErrNotFound := pkg.ResponseStatusError(http.StatusNotFound)
|
||||||
if _, err := f.Cure(&r); !reflect.DeepEqual(err, wantErrNotFound) {
|
if _, err := f.Cure(r); !reflect.DeepEqual(err, wantErrNotFound) {
|
||||||
t.Fatalf("Cure: error = %#v, want %#v", err, wantErrNotFound)
|
t.Fatalf("Cure: error = %#v, want %#v", err, wantErrNotFound)
|
||||||
}
|
}
|
||||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")},
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
|
|
||||||
{"cure", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"cure", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
var r pkg.RContext
|
r := newRContext(t, c)
|
||||||
rCacheVal := reflect.ValueOf(&r).Elem().FieldByName("cache")
|
|
||||||
reflect.NewAt(
|
|
||||||
rCacheVal.Type(),
|
|
||||||
unsafe.Pointer(rCacheVal.UnsafeAddr()),
|
|
||||||
).Elem().Set(reflect.ValueOf(c))
|
|
||||||
|
|
||||||
f := pkg.NewHTTPGet(
|
f := pkg.NewHTTPGet(
|
||||||
&client,
|
&client,
|
||||||
@@ -120,7 +115,7 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var got []byte
|
var got []byte
|
||||||
if rc, err := f.Cure(&r); err != nil {
|
if rc, err := f.Cure(r); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
} else if got, err = io.ReadAll(rc); err != nil {
|
} else if got, err = io.ReadAll(rc); err != nil {
|
||||||
t.Fatalf("ReadAll: error = %v", err)
|
t.Fatalf("ReadAll: error = %v", err)
|
||||||
@@ -136,7 +131,7 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
"file:///testdata",
|
"file:///testdata",
|
||||||
testdataChecksum.Value(),
|
testdataChecksum.Value(),
|
||||||
)
|
)
|
||||||
if rc, err := f.Cure(&r); err != nil {
|
if rc, err := f.Cure(r); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
} else if got, err = io.ReadAll(rc); err != nil {
|
} else if got, err = io.ReadAll(rc); err != nil {
|
||||||
t.Fatalf("ReadAll: error = %v", err)
|
t.Fatalf("ReadAll: error = %v", err)
|
||||||
@@ -156,6 +151,18 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
if _, _, err := c.Cure(f); !reflect.DeepEqual(err, wantErrNotFound) {
|
if _, _, err := c.Cure(f); !reflect.DeepEqual(err, wantErrNotFound) {
|
||||||
t.Fatalf("Pathname: error = %#v, want %#v", err, wantErrNotFound)
|
t.Fatalf("Pathname: error = %#v, want %#v", err, wantErrNotFound)
|
||||||
}
|
}
|
||||||
}, pkg.MustDecode("L_0RFHpr9JUS4Zp14rz2dESSRvfLzpvqsLhR1-YjQt8hYlmEdVl7vI3_-v8UNPKs")},
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"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/oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+643
-140
File diff suppressed because it is too large
Load Diff
+823
-141
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user