forked from rosa/hakurei
Compare commits
508 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
79342e3053
|
|||
|
825bf24731
|
|||
|
08112f0b90
|
|||
|
e1a1e1e399
|
|||
|
92b61889a6
|
|||
|
28e133c298
|
|||
|
323dcb2820
|
|||
|
f46a0370a7
|
|||
|
5ee66a86ee
|
|||
|
d75b8b6e32
|
|||
|
7c9481d38d
|
|||
|
983ab4378f
|
|||
|
5b5c096d31
|
|||
|
5f49127120
|
|||
|
164f8c873d
|
|||
|
9274b642ec
|
|||
|
5992be3dd3
|
|||
|
c661a3b63a
|
|||
|
df0bb877db
|
|||
|
f333b8fbd6
|
|||
|
928a9f61e9
|
|||
|
ce06539eca
|
|||
|
69908e5a41
|
|||
|
b5445573a8
|
|||
|
f869ff95a1
|
|||
|
725f2e0ef3
|
|||
|
4ffa20cd3f
|
|||
|
38450db74a
|
|||
|
9344f694c7
|
|||
|
e8bb5a622d
|
|||
|
22e508fe17
|
|||
|
b18ecf5832
|
|||
|
ec29e755fb
|
|||
|
9aaf160ff4
|
|||
|
e192fca762
|
|||
|
7eafc7b1e4
|
|||
|
282462c2f0
|
|||
|
d9f522d648
|
|||
|
a0b0c7ecc9
|
|||
|
2b8809da7a
|
|||
|
84cda19d63
|
|||
|
154ab953c1
|
|||
|
34e11dd312
|
|||
|
140cb3cc47
|
|||
|
82c974d656
|
|||
|
8c17f201e5
|
|||
|
63e0457538
|
|||
|
ca93264c1f
|
|||
|
94173eafff
|
|||
|
e28c4aa3c0
|
|||
|
8e8410ce38
|
|||
|
8fb6fdaa80
|
|||
|
db69dcf0be
|
|||
|
76c1fb84c8
|
|||
|
729be19af3
|
|||
|
4d04f86f4a
|
|||
|
42cea1e7c6
|
|||
|
83498b5a8a
|
|||
|
9e824452bd
|
|||
|
56937ac396
|
|||
|
6c2f7089b6
|
|||
|
74c18390b4
|
|||
|
1490b32387
|
|||
|
fbd2329d50
|
|||
|
f398f71fa9
|
|||
|
4d017b1309
|
|||
|
cd1d447664
|
|||
|
8b87e0eb76
|
|||
|
f4215ddda5
|
|||
|
652b4c2ba0
|
|||
|
75715f4590
|
|||
|
87c3b3663d
|
|||
|
6d792023b2
|
|||
|
3e62cf379f
|
|||
|
6d991f2644
|
|||
|
a2cc28f53c
|
|||
|
598c7aa30f
|
|||
|
b18f40d974
|
|||
|
12a6061051
|
|||
|
02c3823ed7
|
|||
|
cfbd8bd6f1
|
|||
|
fc7a339ed2
|
|||
|
97fdd5db8b
|
|||
|
a5d9f76f50
|
|||
|
b313dfefb0
|
|||
|
5b32297178
|
|||
|
32cc72821e
|
|||
|
958473e6f4
|
|||
|
cc52bb3035
|
|||
|
7816f8b523
|
|||
|
91e4229e32
|
|||
|
c346cb4c57
|
|||
|
0e0d5e8def
|
|||
|
ea875416d4
|
|||
|
8c7109a2d5
|
|||
|
caf3e0db4b
|
|||
|
12b9f51128
|
|||
|
d15f965d0c
|
|||
|
fadd1b14f7
|
|||
|
3458806685
|
|||
|
8ca70550ab
|
|||
|
7eebf49b98
|
|||
|
8c84883af6
|
|||
|
4e1359aa95
|
|||
|
a3867ad65f
|
|||
|
689f972976
|
|||
|
3f33b62dfd
|
|||
|
ac5488eef6
|
|||
|
77a15130c7
|
|||
|
4c1e823908
|
|||
|
5f5a398a5b
|
|||
|
d5e4a2e6a7
|
|||
|
57c6b84b60
|
|||
|
4269627b4b
|
|||
|
d50e3c3d5b
|
|||
|
eae2890d98
|
|||
|
f8ebfd71a7
|
|||
|
dce1a05f6c
|
|||
|
eff265837c
|
|||
|
7d809eb15f
|
|||
|
9accc2f961
|
|||
|
e5a4094298
|
|||
|
410c4f8bb0
|
|||
|
2e23c6d367
|
|||
|
f5e9a0c04e
|
|||
|
3bd4ef616c
|
|||
|
41402fd578
|
|||
|
b47fa1a214
|
|||
|
9e363cb2c9
|
|||
|
1389c77022
|
|||
|
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
|
+5
-6
@@ -7,13 +7,12 @@
|
|||||||
|
|
||||||
# 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
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
|
# local packages
|
||||||
|
/internal/rosa/package/local
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
+30
-25
@@ -2,7 +2,7 @@
|
|||||||
package check
|
package check
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -20,8 +20,8 @@ func (e AbsoluteError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e AbsoluteError) Is(target error) bool {
|
func (e AbsoluteError) Is(target error) bool {
|
||||||
var ce AbsoluteError
|
ce, ok := errors.AsType[AbsoluteError](target)
|
||||||
if !errors.As(target, &ce) {
|
if !ok {
|
||||||
return errors.Is(target, syscall.EINVAL)
|
return errors.Is(target, syscall.EINVAL)
|
||||||
}
|
}
|
||||||
return e == ce
|
return e == ce
|
||||||
@@ -30,6 +30,22 @@ 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 (
|
||||||
|
_ fmt.GoStringer = new(Absolute)
|
||||||
|
|
||||||
|
_ encoding.TextAppender = new(Absolute)
|
||||||
|
_ encoding.TextMarshaler = new(Absolute)
|
||||||
|
_ encoding.TextUnmarshaler = new(Absolute)
|
||||||
|
|
||||||
|
_ encoding.BinaryAppender = new(Absolute)
|
||||||
|
_ encoding.BinaryMarshaler = new(Absolute)
|
||||||
|
_ encoding.BinaryUnmarshaler = new(Absolute)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Absolute) GoString() string {
|
||||||
|
return fmt.Sprintf("check.MustAbs(%q)", a.String())
|
||||||
|
}
|
||||||
|
|
||||||
// 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 +100,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 +118,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 ""
|
||||||
|
|||||||
+264
@@ -0,0 +1,264 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/fhs"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parsePair parses a NUL-delimited quoted paths pair.
|
||||||
|
func parsePair(s string) (source, target *check.Absolute, err error) {
|
||||||
|
var p string
|
||||||
|
if p, err = strconv.Unquote(s); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_source, _target, ok := strings.Cut(p, "\x00")
|
||||||
|
if source, err = check.NewAbs(_source); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target, err = check.NewAbs(_target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse decodes a high-level configuration stream and returns its
|
||||||
|
// corresponding [hst.Config].
|
||||||
|
func parse(id string, base *check.Absolute, r io.Reader) (*hst.Config, error) {
|
||||||
|
shell := fhs.AbsRoot.Append("bin", "zsh")
|
||||||
|
home := hst.AbsPrivateTmp.Append("home")
|
||||||
|
|
||||||
|
c := hst.Config{
|
||||||
|
ID: id,
|
||||||
|
Enablements: new(hst.Enablements),
|
||||||
|
|
||||||
|
SessionBus: &hst.BusConfig{
|
||||||
|
Own: []string{
|
||||||
|
id + ".*",
|
||||||
|
"org.mpris.MediaPlayer2." + id + ".*",
|
||||||
|
},
|
||||||
|
Filter: true,
|
||||||
|
},
|
||||||
|
SystemBus: &hst.BusConfig{Filter: true},
|
||||||
|
|
||||||
|
Container: &hst.ContainerConfig{
|
||||||
|
Env: make(map[string]string),
|
||||||
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
|
{FilesystemConfig: &hst.FSOverlay{
|
||||||
|
Target: fhs.AbsRoot,
|
||||||
|
Lower: []*check.Absolute{
|
||||||
|
base.Append("template", "initial"),
|
||||||
|
},
|
||||||
|
Upper: base.Append("template", "upper"),
|
||||||
|
}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: home,
|
||||||
|
Source: base.Append("state", id),
|
||||||
|
Write: true,
|
||||||
|
Ensure: true,
|
||||||
|
}},
|
||||||
|
|
||||||
|
{FilesystemConfig: &hst.FSEphemeral{
|
||||||
|
Target: fhs.AbsVar.Append("tmp"),
|
||||||
|
Write: true,
|
||||||
|
Perm: 01777,
|
||||||
|
}},
|
||||||
|
|
||||||
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block")}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus")}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class")}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev")}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices")}},
|
||||||
|
},
|
||||||
|
|
||||||
|
Username: "chronos",
|
||||||
|
Shell: shell,
|
||||||
|
Home: home,
|
||||||
|
Path: shell,
|
||||||
|
Args: []string{"zsh", "-c"},
|
||||||
|
|
||||||
|
Flags: hst.FCoverRun,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s := bufio.NewScanner(r)
|
||||||
|
scanOnce := func() error {
|
||||||
|
if s.Scan() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanOnce(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if v, err := strconv.Atoi(s.Text()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
c.Identity = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanOnce(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Container.Args = append(c.Container.Args, s.Text())
|
||||||
|
|
||||||
|
var flagGPU, flagSystemBus bool
|
||||||
|
flags := map[string]*bool{
|
||||||
|
"gpu": &flagGPU,
|
||||||
|
"system_bus": &flagSystemBus,
|
||||||
|
}
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
key, value, ok := strings.Cut(s.Text(), " ")
|
||||||
|
if key != "" && key[0] == ';' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *bool
|
||||||
|
if p, ok = flags[key]; ok {
|
||||||
|
*p = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "wayland":
|
||||||
|
*c.Enablements |= hst.EWayland
|
||||||
|
case "x11":
|
||||||
|
*c.Enablements |= hst.EX11
|
||||||
|
case "dbus":
|
||||||
|
*c.Enablements |= hst.EDBus
|
||||||
|
case "pipewire":
|
||||||
|
*c.Enablements |= hst.EPipeWire
|
||||||
|
|
||||||
|
case "multiarch":
|
||||||
|
c.Container.Flags |= hst.FMultiarch
|
||||||
|
case "devel":
|
||||||
|
c.Container.Flags |= hst.FDevel
|
||||||
|
case "userns":
|
||||||
|
c.Container.Flags |= hst.FUserns
|
||||||
|
case "net":
|
||||||
|
c.Container.Flags |= hst.FHostNet
|
||||||
|
case "abstract":
|
||||||
|
c.Container.Flags |= hst.FHostAbstract
|
||||||
|
case "tty":
|
||||||
|
c.Container.Flags |= hst.FTty
|
||||||
|
case "mapuid":
|
||||||
|
c.Container.Flags |= hst.FMapRealUID
|
||||||
|
case "device":
|
||||||
|
c.Container.Flags |= hst.FDevice
|
||||||
|
|
||||||
|
case "share_runtime":
|
||||||
|
c.Container.Flags |= hst.FShareRuntime
|
||||||
|
case "share_tmpdir":
|
||||||
|
c.Container.Flags |= hst.FShareTmpdir
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid flag %q", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "group":
|
||||||
|
c.Groups = append(c.Groups, value)
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "env":
|
||||||
|
if key, value, ok = strings.Cut(value, "="); !ok {
|
||||||
|
return nil, fmt.Errorf("invalid environment %q", key)
|
||||||
|
}
|
||||||
|
c.Container.Env[key] = value
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "ro":
|
||||||
|
source, target, err := parsePair(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Container.Filesystem = append(c.Container.Filesystem,
|
||||||
|
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: target,
|
||||||
|
Source: source,
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "rw":
|
||||||
|
source, target, err := parsePair(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Container.Filesystem = append(c.Container.Filesystem,
|
||||||
|
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: target,
|
||||||
|
Source: source,
|
||||||
|
Write: true,
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "own":
|
||||||
|
c.SessionBus.Own = append(c.SessionBus.Own, value)
|
||||||
|
continue
|
||||||
|
case "own_system":
|
||||||
|
c.SystemBus.Own = append(c.SystemBus.Own, value)
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "talk":
|
||||||
|
c.SessionBus.Talk = append(c.SessionBus.Talk, value)
|
||||||
|
continue
|
||||||
|
case "talk_system":
|
||||||
|
c.SystemBus.Talk = append(c.SystemBus.Talk, value)
|
||||||
|
continue
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid key %q", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagGPU {
|
||||||
|
c.Container.Filesystem = append(c.Container.Filesystem, []hst.FilesystemConfigJSON{
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Source: fhs.AbsDev.Append("dri"),
|
||||||
|
Device: true,
|
||||||
|
Optional: true,
|
||||||
|
}},
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !flagSystemBus {
|
||||||
|
c.SystemBus = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Container.Flags&hst.FShareTmpdir == 0 {
|
||||||
|
c.Container.Filesystem = append(c.Container.Filesystem,
|
||||||
|
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{
|
||||||
|
Target: fhs.AbsTmp,
|
||||||
|
Write: true,
|
||||||
|
Perm: 01777,
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/fhs"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
base := fhs.AbsProc.Append("nonexistent")
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
data string
|
||||||
|
want *hst.Config
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{"com.discordapp.Discord", `8
|
||||||
|
exec Discord --ozone-platform-hint=wayland
|
||||||
|
|
||||||
|
gpu
|
||||||
|
wayland
|
||||||
|
dbus
|
||||||
|
system_bus
|
||||||
|
pipewire
|
||||||
|
userns
|
||||||
|
net
|
||||||
|
mapuid
|
||||||
|
|
||||||
|
share_runtime
|
||||||
|
share_tmpdir
|
||||||
|
|
||||||
|
group media_rw
|
||||||
|
env ELECTRON_TRASH=gio
|
||||||
|
rw "/sdcard"
|
||||||
|
; remove before reusing
|
||||||
|
ro "/bin\x00/.hakurei/bin"
|
||||||
|
|
||||||
|
talk org.kde.StatusNotifierWatcher
|
||||||
|
talk com.canonical.AppMenu.Registrar
|
||||||
|
talk com.canonical.indicator.application
|
||||||
|
talk com.canonical.Unity
|
||||||
|
`, &hst.Config{
|
||||||
|
Identity: 8,
|
||||||
|
ID: "com.discordapp.Discord",
|
||||||
|
Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire),
|
||||||
|
Groups: []string{"media_rw"},
|
||||||
|
|
||||||
|
SessionBus: &hst.BusConfig{
|
||||||
|
Talk: []string{
|
||||||
|
"org.kde.StatusNotifierWatcher",
|
||||||
|
"com.canonical.AppMenu.Registrar",
|
||||||
|
"com.canonical.indicator.application",
|
||||||
|
"com.canonical.Unity",
|
||||||
|
},
|
||||||
|
Own: []string{
|
||||||
|
"com.discordapp.Discord.*",
|
||||||
|
"org.mpris.MediaPlayer2.com.discordapp.Discord.*",
|
||||||
|
},
|
||||||
|
Filter: true,
|
||||||
|
},
|
||||||
|
SystemBus: &hst.BusConfig{Filter: true},
|
||||||
|
|
||||||
|
Container: &hst.ContainerConfig{
|
||||||
|
Env: map[string]string{
|
||||||
|
"ELECTRON_TRASH": "gio",
|
||||||
|
},
|
||||||
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
|
{FilesystemConfig: &hst.FSOverlay{
|
||||||
|
Target: fhs.AbsRoot,
|
||||||
|
Lower: []*check.Absolute{
|
||||||
|
base.Append("template", "initial"),
|
||||||
|
},
|
||||||
|
Upper: base.Append("template", "upper"),
|
||||||
|
}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: hst.AbsPrivateTmp.Append("home"),
|
||||||
|
Source: base.Append("state", "com.discordapp.Discord"),
|
||||||
|
Write: true,
|
||||||
|
Ensure: true,
|
||||||
|
}},
|
||||||
|
|
||||||
|
{FilesystemConfig: &hst.FSEphemeral{
|
||||||
|
Target: fhs.AbsVar.Append("tmp"),
|
||||||
|
Write: true,
|
||||||
|
Perm: 01777,
|
||||||
|
}},
|
||||||
|
|
||||||
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block")}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus")}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class")}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev")}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices")}},
|
||||||
|
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Source: check.MustAbs("/sdcard"),
|
||||||
|
Write: true,
|
||||||
|
}},
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Target: check.MustAbs("/.hakurei/bin"),
|
||||||
|
Source: check.MustAbs("/bin"),
|
||||||
|
}},
|
||||||
|
|
||||||
|
{FilesystemConfig: &hst.FSBind{
|
||||||
|
Source: fhs.AbsDev.Append("dri"),
|
||||||
|
Device: true,
|
||||||
|
Optional: true,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
|
||||||
|
Username: "chronos",
|
||||||
|
Shell: fhs.AbsRoot.Append("bin", "zsh"),
|
||||||
|
Home: hst.AbsPrivateTmp.Append("home"),
|
||||||
|
Path: fhs.AbsRoot.Append("bin", "zsh"),
|
||||||
|
Args: []string{
|
||||||
|
"zsh", "-c",
|
||||||
|
"exec Discord --ozone-platform-hint=wayland",
|
||||||
|
},
|
||||||
|
|
||||||
|
Flags: hst.FCoverRun | hst.FUserns | hst.FHostNet | hst.FMapRealUID |
|
||||||
|
hst.FShareRuntime | hst.FShareTmpdir,
|
||||||
|
},
|
||||||
|
}, nil},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got, err := parse(
|
||||||
|
tc.name,
|
||||||
|
base,
|
||||||
|
strings.NewReader(tc.data),
|
||||||
|
)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(err, tc.err) {
|
||||||
|
t.Errorf("parse: error = %v, want %v", err, tc.err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, tc.want) {
|
||||||
|
t.Errorf("parse: %#v, want %#v", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+170
@@ -0,0 +1,170 @@
|
|||||||
|
// The app program is a proof-of-concept frontend for cmd/hakurei.
|
||||||
|
//
|
||||||
|
// This program is not covered by the compatibility promise. The command line
|
||||||
|
// interface and configuration syntax may change at any time.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/command"
|
||||||
|
"hakurei.app/fhs"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("app: ")
|
||||||
|
msg := message.New(log.Default())
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(),
|
||||||
|
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagVerbose bool
|
||||||
|
flagBase string
|
||||||
|
|
||||||
|
base, template, initial, upper, work *check.Absolute
|
||||||
|
)
|
||||||
|
c := command.New(os.Stderr, log.Printf, "app", func([]string) (err error) {
|
||||||
|
msg.SwapVerbose(flagVerbose)
|
||||||
|
flagBase = os.ExpandEnv(flagBase)
|
||||||
|
if flagBase == "" {
|
||||||
|
flagBase = "state"
|
||||||
|
}
|
||||||
|
if flagBase, err = filepath.Abs(flagBase); err != nil {
|
||||||
|
return
|
||||||
|
} else if base, err = check.NewAbs(flagBase); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
template = base.Append("template")
|
||||||
|
initial = template.Append("initial")
|
||||||
|
upper = template.Append("upper")
|
||||||
|
work = template.Append("work")
|
||||||
|
return
|
||||||
|
}).Flag(
|
||||||
|
&flagVerbose,
|
||||||
|
"v", command.BoolFlag(false),
|
||||||
|
"Increase log verbosity",
|
||||||
|
).Flag(
|
||||||
|
&flagBase,
|
||||||
|
"d", command.StringFlag("$HAKUREI_APP_PATH"),
|
||||||
|
"Configuration and state directory",
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
|
var (
|
||||||
|
flagShell string
|
||||||
|
flagHome string
|
||||||
|
)
|
||||||
|
c.NewCommand(
|
||||||
|
"enter", "Enter mutable state template",
|
||||||
|
func([]string) error {
|
||||||
|
config := hst.Config{
|
||||||
|
ID: "app.hakurei.mutable",
|
||||||
|
Container: &hst.ContainerConfig{
|
||||||
|
Hostname: "mutable",
|
||||||
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
|
{FilesystemConfig: &hst.FSOverlay{
|
||||||
|
Target: fhs.AbsRoot,
|
||||||
|
Lower: []*check.Absolute{initial},
|
||||||
|
Upper: upper,
|
||||||
|
Work: work,
|
||||||
|
}},
|
||||||
|
{FilesystemConfig: &hst.FSEphemeral{
|
||||||
|
Target: fhs.AbsTmp,
|
||||||
|
Write: true,
|
||||||
|
Perm: 0755,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
Username: "chronos",
|
||||||
|
Flags: hst.FMultiarch |
|
||||||
|
hst.FDevel |
|
||||||
|
hst.FUserns |
|
||||||
|
hst.FHostNet |
|
||||||
|
hst.FTty,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if a, err := check.NewAbs(flagShell); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
config.Container.Shell = a
|
||||||
|
config.Container.Path = a
|
||||||
|
config.Container.Args = []string{
|
||||||
|
"-" + filepath.Base(flagShell),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if a, err := check.NewAbs(flagHome); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
config.Container.Home = a
|
||||||
|
}
|
||||||
|
|
||||||
|
return run(ctx, msg, &config)
|
||||||
|
},
|
||||||
|
).Flag(
|
||||||
|
&flagShell,
|
||||||
|
"shell", command.StringFlag("/bin/zsh"),
|
||||||
|
"Shell program within container",
|
||||||
|
).Flag(
|
||||||
|
&flagHome,
|
||||||
|
"home", command.StringFlag("/home/chronos"),
|
||||||
|
"Home directory within container",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"run", "Start the named application",
|
||||||
|
func(args []string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.New("run requires 1 argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
var config *hst.Config
|
||||||
|
f, err := os.Open(base.Append("app", args[0]).String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config, err = parse(args[0], base, f)
|
||||||
|
if closeErr := f.Close(); err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return run(ctx, msg, config)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
c.MustParse(os.Args[1:], func(err error) {
|
||||||
|
if e, ok := errors.AsType[*exec.ExitError](err); ok && e != nil {
|
||||||
|
os.Exit(e.ExitCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
if w, ok := err.(interface{ Unwrap() []error }); !ok {
|
||||||
|
log.Fatal(err)
|
||||||
|
} else {
|
||||||
|
errs := w.Unwrap()
|
||||||
|
for i, e := range errs {
|
||||||
|
if i == len(errs)-1 {
|
||||||
|
log.Fatal(e)
|
||||||
|
}
|
||||||
|
log.Println(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// run starts a container via cmd/hakurei and returns after it terminates.
|
||||||
|
func run(ctx context.Context, msg message.Msg, config *hst.Config) error {
|
||||||
|
c, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(c, "hakurei")
|
||||||
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
cmd.Cancel = func() error {
|
||||||
|
return cmd.Process.Signal(syscall.SIGINT)
|
||||||
|
}
|
||||||
|
if msg.IsVerbose() {
|
||||||
|
cmd.Args = append(cmd.Args, "-v")
|
||||||
|
}
|
||||||
|
cmd.Args = append(cmd.Args, "run", "3")
|
||||||
|
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.ExtraFiles = append(cmd.ExtraFiles, r)
|
||||||
|
|
||||||
|
if err = cmd.Start(); err != nil {
|
||||||
|
_, _ = r.Close(), w.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.Close(); err != nil {
|
||||||
|
_ = w.Close()
|
||||||
|
return err
|
||||||
|
} else if err = json.NewEncoder(w).Encode(&config); err != nil {
|
||||||
|
_ = w.Close()
|
||||||
|
return err
|
||||||
|
} else if err = w.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Wait()
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
v0.4.4
|
||||||
+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) {
|
||||||
|
|||||||
+153
-20
@@ -5,17 +5,91 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
|
|
||||||
|
"hakurei.app/internal/kobject"
|
||||||
|
"hakurei.app/internal/report"
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
var r report.Reporter
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("earlyinit: ")
|
||||||
|
r.SetOutput(log.Default())
|
||||||
|
|
||||||
|
// this handles SIGQUIT to provide useful debugging information without
|
||||||
|
// terminating, and prevents the runtime from throwing on the must family
|
||||||
|
// of early error reporting functions, DO NOT REMOVE
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, SIGQUIT)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
<-c
|
||||||
|
if p := pprof.Lookup("goroutine"); p == nil {
|
||||||
|
log.Println("initial built-in goroutine profile does not exist")
|
||||||
|
} else if err := p.WriteTo(os.Stderr, 2); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fatal calls [log.Println] with v and blocks forever. Must be called from
|
||||||
|
// main. Must not be used after error reporting is set up.
|
||||||
|
func fatal(v ...any) {
|
||||||
|
log.Println(v...)
|
||||||
|
log.Println("unable to continue, please reboot and resolve the problem manually")
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// must calls fatal with err if it is non-nil.
|
||||||
|
func must(err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustSyscall is like must, but with an additional action name.
|
||||||
|
func mustSyscall(action string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
fatal("cannot "+action+":", err)
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// must1 is like must, but with an additional passed through value.
|
||||||
|
func must1[T any](v T, err error) T {
|
||||||
|
must(err)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// optionSystem specifies devpath of the system device.
|
||||||
|
optionSystem = "system"
|
||||||
|
|
||||||
|
// flagVerbose increases output verbosity.
|
||||||
|
flagVerbose = "verbose"
|
||||||
|
// flagStrict sets [report.DStrict] on r.
|
||||||
|
flagStrict = "strict"
|
||||||
|
// flagNoRecover sets [report.DNoRecover] on r.
|
||||||
|
flagNoRecover = "no_recover"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
log.SetFlags(0)
|
|
||||||
log.SetPrefix("earlyinit: ")
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
option map[string]string
|
option map[string]string
|
||||||
@@ -33,15 +107,44 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Mount(
|
{
|
||||||
|
var flag uint64
|
||||||
|
if slices.Contains(flags, flagStrict) {
|
||||||
|
flag |= report.DStrict
|
||||||
|
}
|
||||||
|
if slices.Contains(flags, flagNoRecover) {
|
||||||
|
flag |= report.DNoRecover
|
||||||
|
}
|
||||||
|
log.Printf("reporting flags %x", flag)
|
||||||
|
r.SetFlags(flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := message.New(log.Default())
|
||||||
|
msg.SwapVerbose(slices.Contains(flags, flagVerbose))
|
||||||
|
|
||||||
|
mustSyscall("mount devtmpfs", Mount(
|
||||||
"devtmpfs",
|
"devtmpfs",
|
||||||
"/dev/",
|
"/dev/",
|
||||||
"devtmpfs",
|
"devtmpfs",
|
||||||
MS_NOSUID|MS_NOEXEC,
|
MS_NOSUID|MS_NOEXEC,
|
||||||
"",
|
"",
|
||||||
); err != nil {
|
))
|
||||||
log.Fatalf("cannot mount devtmpfs: %v", err)
|
must(os.Mkdir("/dev/pts/", 0))
|
||||||
}
|
mustSyscall("mount devpts", Mount(
|
||||||
|
"devpts",
|
||||||
|
"/dev/pts/",
|
||||||
|
"devpts",
|
||||||
|
MS_NOSUID|MS_NOEXEC,
|
||||||
|
"mode=620,ptmxmode=666",
|
||||||
|
))
|
||||||
|
must(os.Mkdir("/dev/shm/", 0))
|
||||||
|
mustSyscall("mount shm", Mount(
|
||||||
|
"shm",
|
||||||
|
"/dev/shm/",
|
||||||
|
"tmpfs",
|
||||||
|
MS_NOSUID|MS_NODEV,
|
||||||
|
"",
|
||||||
|
))
|
||||||
|
|
||||||
// The kernel might be unable to set up the console. When that happens,
|
// The kernel might be unable to set up the console. When that happens,
|
||||||
// printk is called with "Warning: unable to open an initial console."
|
// printk is called with "Warning: unable to open an initial console."
|
||||||
@@ -98,6 +201,49 @@ func main() {
|
|||||||
"",
|
"",
|
||||||
))
|
))
|
||||||
|
|
||||||
|
conn := must1(uevent.Dial(-128 * 1024 * 1024))
|
||||||
|
events := make(chan *uevent.Message, 1<<10)
|
||||||
|
var uuid uevent.UUID
|
||||||
|
must1(rand.Read(uuid[:]))
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
go consume(ctx, msg, &r, conn, uuid, events)
|
||||||
|
s := kobject.New(uuid, func(o *kobject.Object, env map[string]string) {
|
||||||
|
p := make([]string, 0, len(env))
|
||||||
|
for k, v := range env {
|
||||||
|
p = append(p, k+"="+v)
|
||||||
|
}
|
||||||
|
slices.Sort(p)
|
||||||
|
log.Printf("change %s: %s", o.DevPath, strings.Join(p, ", "))
|
||||||
|
}, func(err error) {
|
||||||
|
severity := report.Inconsistent
|
||||||
|
if e, ok := err.(kobject.EventError); ok && e.Kind == kobject.EBadTarget {
|
||||||
|
severity = report.Trivial
|
||||||
|
}
|
||||||
|
r.Dispatch(
|
||||||
|
severity,
|
||||||
|
"processed inconsistent uevent",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
s.Consume(ctx, events)
|
||||||
|
|
||||||
|
log.Println("closing NETLINK_KOBJECT_UEVENT socket")
|
||||||
|
cancel()
|
||||||
|
if err := conn.Close(); err != nil {
|
||||||
|
log.Fatal(err) // not reached
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
must(os.Mkdir("/system", 0))
|
||||||
|
if devpath := option[optionSystem]; devpath == "" {
|
||||||
|
fatal("system must be nonempty")
|
||||||
|
} else {
|
||||||
|
log.Printf("waiting for devpath pattern %q", devpath)
|
||||||
|
mustMountSystem(ctx, s, devpath)
|
||||||
|
}
|
||||||
|
|
||||||
// after top level has been set up
|
// after top level has been set up
|
||||||
mustSyscall("remount root", Mount(
|
mustSyscall("remount root", Mount(
|
||||||
"",
|
"",
|
||||||
@@ -113,19 +259,6 @@ func main() {
|
|||||||
[]byte("/system/lib/firmware"),
|
[]byte("/system/lib/firmware"),
|
||||||
0,
|
0,
|
||||||
))
|
))
|
||||||
|
go dispatchModprobe(ctx, s)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// mustSyscall calls [log.Fatalln] if err is non-nil.
|
|
||||||
func mustSyscall(action string, err error) {
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("cannot "+action+":", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// must calls [log.Fatal] with err if it is non-nil.
|
|
||||||
func must(err error) {
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hakurei.app/internal/kobject"
|
||||||
|
"hakurei.app/internal/report"
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ModprobeError describes an unsuccessful modprobe invocation.
|
||||||
|
type ModprobeError struct {
|
||||||
|
ModAlias string `json:"modalias"`
|
||||||
|
Stdout string `json:"stdout"`
|
||||||
|
Stderr string `json:"stderr"`
|
||||||
|
ExitCode int `json:"exit_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ report.RepresentableError = ModprobeError{}
|
||||||
|
|
||||||
|
func (ModprobeError) Representable() {}
|
||||||
|
func (e ModprobeError) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"modprobe exit status %d: %s",
|
||||||
|
e.ExitCode, strings.TrimSpace(e.Stderr),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatchModprobe invokes modprobe for [uevent.KOBJ_ADD] events raising new
|
||||||
|
// MODALIAS strings.
|
||||||
|
func dispatchModprobe(
|
||||||
|
ctx context.Context,
|
||||||
|
s *kobject.State,
|
||||||
|
) {
|
||||||
|
aliases := make(chan string, 1<<8)
|
||||||
|
go func() {
|
||||||
|
defer close(aliases)
|
||||||
|
s.Range(ctx, func(o *kobject.Object, act uevent.KobjectAction) bool {
|
||||||
|
if act == uevent.KOBJ_ADD && o.Driver == "" && o.ModAlias != "" {
|
||||||
|
aliases <- o.ModAlias
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
for alias := range aliases {
|
||||||
|
stdout, err := exec.Command("/system/sbin/modprobe", alias).Output()
|
||||||
|
if err == nil {
|
||||||
|
if len(stdout) > 0 {
|
||||||
|
log.Println(string(stdout))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
exitError, ok := errors.AsType[*exec.ExitError](err)
|
||||||
|
if !ok || exitError == nil {
|
||||||
|
r.Dispatch(report.Degraded, "invoke modprobe", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Dispatch(report.Trivial, "load device driver", ModprobeError{
|
||||||
|
ModAlias: alias,
|
||||||
|
Stdout: string(stdout),
|
||||||
|
Stderr: string(exitError.Stderr),
|
||||||
|
ExitCode: exitError.ExitCode(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/fhs"
|
||||||
|
"hakurei.app/internal/kobject"
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mustMountSystem waits for and mounts a system device matching pattern.
|
||||||
|
func mustMountSystem(
|
||||||
|
ctx context.Context,
|
||||||
|
s *kobject.State,
|
||||||
|
pattern string,
|
||||||
|
) {
|
||||||
|
c, stop := context.WithTimeout(ctx, 30*time.Second)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var matchErr error
|
||||||
|
var systemPath *check.Absolute
|
||||||
|
s.Range(c, func(o *kobject.Object, act uevent.KobjectAction) bool {
|
||||||
|
if (act != uevent.KOBJ_ADD && act != uevent.KOBJ_CHANGE) ||
|
||||||
|
o.Subsystem != "block" ||
|
||||||
|
o.Env["DEVTYPE"] != "disk" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := filepath.Match(pattern, o.DevPath); err != nil {
|
||||||
|
matchErr = err
|
||||||
|
return false
|
||||||
|
} else if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
name, ok := o.Env["DEVNAME"]
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
systemPath = fhs.AbsDev.Append(name)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if c.Err() != nil {
|
||||||
|
fatal("devpath", strconv.Quote(pattern), "never appeared")
|
||||||
|
}
|
||||||
|
if matchErr != nil {
|
||||||
|
fatal("cannot match system devpath:", matchErr)
|
||||||
|
}
|
||||||
|
err := syscall.Mount(
|
||||||
|
systemPath.String(),
|
||||||
|
"/system/",
|
||||||
|
"squashfs",
|
||||||
|
0,
|
||||||
|
"threads=multi",
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
fatal("cannot mount system:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/fhs"
|
||||||
|
"hakurei.app/internal/report"
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newRejectColdboot returns a function to be called on every subsequent pending
|
||||||
|
// coldboot, and returns whether coldboot should proceed. Rejection is sticky.
|
||||||
|
func newRejectColdboot() func() bool {
|
||||||
|
// one coldboot per five minutes, two consecutive coldboot
|
||||||
|
const (
|
||||||
|
coldbootInterval = 5 * time.Minute
|
||||||
|
coldbootBurst = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
s := make(chan struct{}, coldbootBurst)
|
||||||
|
s <- struct{}{} // for early fault before reporting is ready
|
||||||
|
go func() {
|
||||||
|
t := time.NewTicker(coldbootInterval)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-t.C:
|
||||||
|
select {
|
||||||
|
case s <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return func() bool {
|
||||||
|
select {
|
||||||
|
case <-s:
|
||||||
|
return true
|
||||||
|
|
||||||
|
case <-done:
|
||||||
|
return false
|
||||||
|
|
||||||
|
default:
|
||||||
|
close(done)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume continuously consumes events from conn with retries.
|
||||||
|
func consume(
|
||||||
|
ctx context.Context,
|
||||||
|
msg message.Msg,
|
||||||
|
r *report.Reporter,
|
||||||
|
conn *uevent.Conn,
|
||||||
|
uuid uevent.UUID,
|
||||||
|
events chan<- *uevent.Message,
|
||||||
|
) {
|
||||||
|
defer close(events)
|
||||||
|
|
||||||
|
nextColdboot := newRejectColdboot()
|
||||||
|
coldboot := true
|
||||||
|
retry:
|
||||||
|
if dispatchErr := conn.Consume(ctx, fhs.Sys, &uuid, events, coldboot, func(path string) {
|
||||||
|
msg.Verbose("coldboot visited", path)
|
||||||
|
}, func(err error) bool {
|
||||||
|
if _, ok := err.(uevent.NeedsColdboot); ok && !nextColdboot() {
|
||||||
|
r.Dispatch(
|
||||||
|
report.Degraded,
|
||||||
|
"rejecting coldboot loop",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r.Dispatch(
|
||||||
|
report.Inconsistent,
|
||||||
|
"consumed invalid message",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}, nil); dispatchErr != nil {
|
||||||
|
if _, ok := dispatchErr.(uevent.Recoverable); !ok {
|
||||||
|
r.Dispatch(
|
||||||
|
report.Fatal,
|
||||||
|
"discontinuing uevent processing due to nonrecoverable error",
|
||||||
|
dispatchErr,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := dispatchErr.(uevent.NeedsColdboot); ok {
|
||||||
|
// coldboot loop rejected by handler
|
||||||
|
coldboot = false
|
||||||
|
}
|
||||||
|
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"testing/synctest"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRejectColdboot(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
synctest.Test(t, func(t *testing.T) {
|
||||||
|
nextColdboot := newRejectColdboot()
|
||||||
|
want := func(want bool) {
|
||||||
|
if got := nextColdboot(); got != want {
|
||||||
|
t.Fatalf("nextColdboot: %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synctest.Wait()
|
||||||
|
want(true)
|
||||||
|
time.Sleep(time.Hour)
|
||||||
|
synctest.Wait()
|
||||||
|
want(true)
|
||||||
|
want(true)
|
||||||
|
time.Sleep(5 * time.Minute)
|
||||||
|
synctest.Wait()
|
||||||
|
want(true)
|
||||||
|
want(false)
|
||||||
|
time.Sleep(time.Hour)
|
||||||
|
synctest.Wait()
|
||||||
|
want(false)
|
||||||
|
want(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
+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
|
||||||
|
|||||||
+6
-5
@@ -7,7 +7,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// decodeJSON decodes json from r and stores it in v. A non-nil error results in a call to fatal.
|
// decodeJSON decodes json from r and stores it in v. A non-nil error results in
|
||||||
|
// a call to fatal.
|
||||||
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any) {
|
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any) {
|
||||||
err := json.NewDecoder(r).Decode(v)
|
err := json.NewDecoder(r).Decode(v)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -47,14 +48,14 @@ func encodeJSON(fatal func(v ...any), output io.Writer, short bool, v any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := encoder.Encode(v); err != nil {
|
if err := encoder.Encode(v); err != nil {
|
||||||
var marshalerError *json.MarshalerError
|
if e, ok := errors.AsType[*json.MarshalerError](err); ok && e != nil {
|
||||||
if errors.As(err, &marshalerError) && marshalerError != nil {
|
|
||||||
// this likely indicates an implementation error in hst
|
// this likely indicates an implementation error in hst
|
||||||
fatal("cannot encode json for " + marshalerError.Type.String() + ": " + marshalerError.Err.Error())
|
fatal("cannot encode json for " + e.Type.String() + ": " + e.Err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does not need to be handled
|
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does
|
||||||
|
// not need to be handled
|
||||||
fatal("cannot write json: " + err.Error())
|
fatal("cannot write json: " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"),
|
||||||
@@ -64,7 +64,7 @@ func TestPrintShowInstance(t *testing.T) {
|
|||||||
Identity: 9 (org.chromium.Chromium)
|
Identity: 9 (org.chromium.Chromium)
|
||||||
Enablements: wayland, dbus, pipewire
|
Enablements: wayland, dbus, pipewire
|
||||||
Groups: video, dialout, plugdev
|
Groups: video, dialout, plugdev
|
||||||
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir
|
||||||
Home: /data/data/org.chromium.Chromium
|
Home: /data/data/org.chromium.Chromium
|
||||||
Hostname: localhost
|
Hostname: localhost
|
||||||
Path: /run/current-system/sw/bin/chromium
|
Path: /run/current-system/sw/bin/chromium
|
||||||
@@ -161,7 +161,7 @@ App
|
|||||||
Identity: 9 (org.chromium.Chromium)
|
Identity: 9 (org.chromium.Chromium)
|
||||||
Enablements: wayland, dbus, pipewire
|
Enablements: wayland, dbus, pipewire
|
||||||
Groups: video, dialout, plugdev
|
Groups: video, dialout, plugdev
|
||||||
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir
|
||||||
Home: /data/data/org.chromium.Chromium
|
Home: /data/data/org.chromium.Chromium
|
||||||
Hostname: localhost
|
Hostname: localhost
|
||||||
Path: /run/current-system/sw/bin/chromium
|
Path: /run/current-system/sw/bin/chromium
|
||||||
@@ -355,6 +355,7 @@ App
|
|||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
"device": true,
|
"device": true,
|
||||||
|
"cover_run": true,
|
||||||
"share_runtime": true,
|
"share_runtime": true,
|
||||||
"share_tmpdir": true
|
"share_tmpdir": true
|
||||||
},
|
},
|
||||||
@@ -506,6 +507,7 @@ App
|
|||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
"device": true,
|
"device": true,
|
||||||
|
"cover_run": true,
|
||||||
"share_runtime": true,
|
"share_runtime": true,
|
||||||
"share_tmpdir": true
|
"share_tmpdir": true
|
||||||
}
|
}
|
||||||
@@ -704,6 +706,7 @@ func TestPrintPs(t *testing.T) {
|
|||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
"device": true,
|
"device": true,
|
||||||
|
"cover_run": true,
|
||||||
"share_runtime": true,
|
"share_runtime": true,
|
||||||
"share_tmpdir": true
|
"share_tmpdir": true
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-23
@@ -21,15 +21,6 @@
|
|||||||
// following paragraphs are considered an internal detail and not covered by the
|
// following paragraphs are considered an internal detail and not covered by the
|
||||||
// compatibility promise.
|
// compatibility promise.
|
||||||
//
|
//
|
||||||
// After checking credentials, hsu checks via /proc/ the absolute pathname of
|
|
||||||
// its parent process, and fails if it does not match the hakurei pathname set
|
|
||||||
// at link time. This is not a security feature: the priv-side is considered
|
|
||||||
// trusted, and this feature makes no attempt to address the racy nature of
|
|
||||||
// querying /proc/, or debuggers attached to the parent process. Instead, this
|
|
||||||
// aims to discourage misuse and reduce confusion if the user accidentally
|
|
||||||
// stumbles upon this program. It also prevents accidental use of the incorrect
|
|
||||||
// installation of hsu in some environments.
|
|
||||||
//
|
|
||||||
// Since target container environment variables are set up in shim via the
|
// Since target container environment variables are set up in shim via the
|
||||||
// [container] infrastructure, the environment is used for parameters from the
|
// [container] infrastructure, the environment is used for parameters from the
|
||||||
// parent process.
|
// parent process.
|
||||||
@@ -62,7 +53,6 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -107,18 +97,6 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var toolPath string
|
|
||||||
pexe := filepath.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
|
|
||||||
if p, err := os.Readlink(pexe); err != nil {
|
|
||||||
log.Fatalf("cannot read parent executable path: %v", err)
|
|
||||||
} else if strings.HasSuffix(p, " (deleted)") {
|
|
||||||
log.Fatal("hakurei executable has been deleted")
|
|
||||||
} else if p != hakureiPath {
|
|
||||||
log.Fatal("this program must be started by hakurei")
|
|
||||||
} else {
|
|
||||||
toolPath = p
|
|
||||||
}
|
|
||||||
|
|
||||||
// refuse to run if hsurc is not protected correctly
|
// refuse to run if hsurc is not protected correctly
|
||||||
if s, err := os.Stat(hsuConfPath); err != nil {
|
if s, err := os.Stat(hsuConfPath); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -205,7 +183,7 @@ func main() {
|
|||||||
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := syscall.Exec(toolPath, []string{
|
if err := syscall.Exec(hakureiPath, []string{
|
||||||
"hakurei",
|
"hakurei",
|
||||||
"shim",
|
"shim",
|
||||||
}, []string{
|
}, []string{
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/internal/rosa"
|
||||||
|
"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, mirror 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.mirror != "" {
|
||||||
|
var pub []byte
|
||||||
|
pub, err = os.ReadFile(base.Append("ed25519.pub").String())
|
||||||
|
if err != nil {
|
||||||
|
cache.c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var r rosa.Remote
|
||||||
|
if r, err = rosa.NewRemote(cache.mirror, pub, http.DefaultClient); err != nil {
|
||||||
|
cache.c.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cache.c.SetExternal(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cache.qemu != nil {
|
||||||
|
var pathname *check.Absolute
|
||||||
|
pathname, _, err = cache.c.Cure(cache.qemu)
|
||||||
|
if err != nil {
|
||||||
|
cache.c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for arch, entry := range rosa.Arches(pathname) {
|
||||||
|
pkg.RegisterArch(arch, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
@@ -74,7 +74,7 @@ func (s *searchCache) clean() {
|
|||||||
}
|
}
|
||||||
func indexsum(in [][]int) int {
|
func indexsum(in [][]int) int {
|
||||||
sum := 0
|
sum := 0
|
||||||
for i := 0; i < len(in); i++ {
|
for i := range in {
|
||||||
sum += in[i][1] - in[i][0]
|
sum += in[i][1] - in[i][0]
|
||||||
}
|
}
|
||||||
return sum
|
return sum
|
||||||
@@ -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">
|
||||||
@@ -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
|
||||||
+665
-334
File diff suppressed because it is too large
Load Diff
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -508,8 +508,8 @@ func _main(s ...string) (exitCode int) {
|
|||||||
|
|
||||||
if !z.AllowOrphan {
|
if !z.AllowOrphan {
|
||||||
if err := z.Wait(); err != nil {
|
if err := z.Wait(); err != nil {
|
||||||
var exitError *exec.ExitError
|
exitError, ok := errors.AsType[*exec.ExitError](err)
|
||||||
if !errors.As(err, &exitError) || exitError == nil {
|
if !ok || exitError == nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return 5
|
return 5
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ./...'
|
||||||
'')
|
'')
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -91,8 +91,8 @@ func (n *node) MustParse(arguments []string, handleError func(error)) {
|
|||||||
case ErrEmptyTree:
|
case ErrEmptyTree:
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
default:
|
default:
|
||||||
var flagError FlagError
|
flagError, ok := errors.AsType[FlagError](err)
|
||||||
if !errors.As(err, &flagError) { // returned by HandlerFunc
|
if !ok { // returned by HandlerFunc
|
||||||
handleError(err)
|
handleError(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
+37
-16
@@ -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
|
||||||
|
|
||||||
@@ -142,11 +154,8 @@ func (e *StartError) Error() string {
|
|||||||
return e.Step
|
return e.Step
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if se, ok := errors.AsType[*os.SyscallError](e.Err); ok && se != nil {
|
||||||
var syscallError *os.SyscallError
|
return e.Step + " " + se.Error()
|
||||||
if errors.As(e.Err, &syscallError) && syscallError != nil {
|
|
||||||
return e.Step + " " + syscallError.Error()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.Step + ": " + e.Err.Error()
|
return e.Step + ": " + e.Err.Error()
|
||||||
@@ -212,6 +221,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 +294,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 +331,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 +341,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 +365,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 +374,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 +430,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 +500,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 +509,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,8 +235,6 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func sliceAddr[S any](s []S) *[]S { return &s }
|
|
||||||
|
|
||||||
func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
|
func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
|
||||||
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
|
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
|
||||||
// check happens in Close, and cleanup is not guaranteed to run, so relying
|
// check happens in Close, and cleanup is not guaranteed to run, so relying
|
||||||
@@ -468,6 +466,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(
|
||||||
|
|||||||
+10
-8
@@ -46,9 +46,8 @@ func messageFromError(err error) (m string, ok bool) {
|
|||||||
// While this is usable for pointer errors, such use should be avoided as nil
|
// While this is usable for pointer errors, such use should be avoided as nil
|
||||||
// check is omitted.
|
// check is omitted.
|
||||||
func messagePrefix[T error](prefix string, err error) (string, bool) {
|
func messagePrefix[T error](prefix string, err error) (string, bool) {
|
||||||
var targetError T
|
if e, ok := errors.AsType[T](err); ok {
|
||||||
if errors.As(err, &targetError) {
|
return prefix + e.Error(), true
|
||||||
return prefix + targetError.Error(), true
|
|
||||||
}
|
}
|
||||||
return zeroString, false
|
return zeroString, false
|
||||||
}
|
}
|
||||||
@@ -58,9 +57,8 @@ func messagePrefixP[V any, T interface {
|
|||||||
*V
|
*V
|
||||||
error
|
error
|
||||||
}](prefix string, err error) (string, bool) {
|
}](prefix string, err error) (string, bool) {
|
||||||
var targetError T
|
if e, ok := errors.AsType[T](err); ok && e != nil {
|
||||||
if errors.As(err, &targetError) && targetError != nil {
|
return prefix + e.Error(), true
|
||||||
return prefix + targetError.Error(), true
|
|
||||||
}
|
}
|
||||||
return zeroString, false
|
return zeroString, false
|
||||||
}
|
}
|
||||||
@@ -109,8 +107,8 @@ func optionalErrorUnwrap(err error) error {
|
|||||||
|
|
||||||
// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
|
// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
|
||||||
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
||||||
var errno syscall.Errno
|
errno, ok := errors.AsType[syscall.Errno](err)
|
||||||
if !errors.As(err, &errno) {
|
if !ok {
|
||||||
return 0, &os.PathError{Op: op, Path: path, Err: err}
|
return 0, &os.PathError{Op: op, Path: path, Err: err}
|
||||||
}
|
}
|
||||||
return errno, nil
|
return errno, nil
|
||||||
@@ -118,6 +116,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)
|
||||||
|
|||||||
+81
-81
@@ -95,7 +95,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
Ops: new(make(Ops, 1)),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -123,7 +123,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
Ops: new(make(Ops, 1)),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -152,7 +152,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
Ops: new(make(Ops, 1)),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -182,7 +182,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
Ops: new(make(Ops, 1)),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -213,7 +213,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
Ops: new(make(Ops, 1)),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -245,7 +245,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
Ops: new(make(Ops, 1)),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -279,7 +279,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
Ops: new(make(Ops, 1)),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -315,7 +315,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
Ops: new(make(Ops, 1)),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ func TestSyscall(t *testing.T) {
|
|||||||
t.Errorf("Unmarshal: %v, want %v", got, tc.want)
|
t.Errorf("Unmarshal: %v, want %v", got, tc.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if errors.As(tc.err, new(ext.SyscallNameError)) {
|
if _, ok := errors.AsType[ext.SyscallNameError](tc.err); ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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].
|
||||||
|
|||||||
Generated
+8
-8
@@ -7,32 +7,32 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1772985280,
|
"lastModified": 1780361225,
|
||||||
"narHash": "sha256-FdrNykOoY9VStevU4zjSUdvsL9SzJTcXt4omdEDZDLk=",
|
"narHash": "sha256-wnV9ttf4fPWNonBIQmvlrSlNpQYgx5HgWWd007mwIFA=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "8f736f007139d7f70752657dff6a401a585d6cbc",
|
"rev": "e28654b71096e08c019d4861ca26acb646f583d8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"ref": "release-25.11",
|
"ref": "release-26.05",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1772822230,
|
"lastModified": 1780453794,
|
||||||
"narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=",
|
"narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "71caefce12ba78d84fe618cf61644dce01cf3a96",
|
"rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-25.11",
|
"ref": "nixos-26.05",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
description = "hakurei container tool and nixos module";
|
description = "hakurei container tool and nixos module";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
||||||
|
|
||||||
home-manager = {
|
home-manager = {
|
||||||
url = "github:nix-community/home-manager/release-25.11";
|
url = "github:nix-community/home-manager/release-26.05";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
inherit (pkgs)
|
inherit (pkgs)
|
||||||
runCommandLocal
|
runCommandLocal
|
||||||
callPackage
|
callPackage
|
||||||
nixfmt-rfc-style
|
nixfmt
|
||||||
deadnix
|
deadnix
|
||||||
statix
|
statix
|
||||||
;
|
;
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
|
sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
|
||||||
|
|
||||||
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
|
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt ]; } ''
|
||||||
cd ${./.}
|
cd ${./.}
|
||||||
|
|
||||||
echo "running nixfmt..."
|
echo "running nixfmt..."
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package hst
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -68,6 +69,8 @@ const (
|
|||||||
// FDevice mount /dev/ from the init mount namespace as is in the container
|
// FDevice mount /dev/ from the init mount namespace as is in the container
|
||||||
// mount namespace.
|
// mount namespace.
|
||||||
FDevice
|
FDevice
|
||||||
|
// FCoverRun covers /run/ in the container mount namespace early.
|
||||||
|
FCoverRun
|
||||||
|
|
||||||
// FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity.
|
// FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity.
|
||||||
FShareRuntime
|
FShareRuntime
|
||||||
@@ -100,6 +103,8 @@ func (flags Flags) String() string {
|
|||||||
return "mapuid"
|
return "mapuid"
|
||||||
case FDevice:
|
case FDevice:
|
||||||
return "device"
|
return "device"
|
||||||
|
case FCoverRun:
|
||||||
|
return "cover_run"
|
||||||
case FShareRuntime:
|
case FShareRuntime:
|
||||||
return "runtime"
|
return "runtime"
|
||||||
case FShareTmpdir:
|
case FShareTmpdir:
|
||||||
@@ -161,6 +166,10 @@ type ContainerConfig struct {
|
|||||||
Flags Flags `json:"-"`
|
Flags Flags `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ContainerConfig) GoString() string {
|
||||||
|
return fmt.Sprintf("&%#v", *c)
|
||||||
|
}
|
||||||
|
|
||||||
// ContainerConfigF is [ContainerConfig] stripped of its methods.
|
// ContainerConfigF is [ContainerConfig] stripped of its methods.
|
||||||
//
|
//
|
||||||
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
|
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
|
||||||
@@ -191,6 +200,8 @@ type containerConfigJSON = struct {
|
|||||||
|
|
||||||
// Corresponds to [FDevice].
|
// Corresponds to [FDevice].
|
||||||
Device bool `json:"device,omitempty"`
|
Device bool `json:"device,omitempty"`
|
||||||
|
// Corresponds to [FCoverRun].
|
||||||
|
CoverRun bool `json:"cover_run,omitempty"`
|
||||||
|
|
||||||
// Corresponds to [FShareRuntime].
|
// Corresponds to [FShareRuntime].
|
||||||
ShareRuntime bool `json:"share_runtime,omitempty"`
|
ShareRuntime bool `json:"share_runtime,omitempty"`
|
||||||
@@ -214,6 +225,7 @@ func (c *ContainerConfig) MarshalJSON() ([]byte, error) {
|
|||||||
Multiarch: c.Flags&FMultiarch != 0,
|
Multiarch: c.Flags&FMultiarch != 0,
|
||||||
MapRealUID: c.Flags&FMapRealUID != 0,
|
MapRealUID: c.Flags&FMapRealUID != 0,
|
||||||
Device: c.Flags&FDevice != 0,
|
Device: c.Flags&FDevice != 0,
|
||||||
|
CoverRun: c.Flags&FCoverRun != 0,
|
||||||
ShareRuntime: c.Flags&FShareRuntime != 0,
|
ShareRuntime: c.Flags&FShareRuntime != 0,
|
||||||
ShareTmpdir: c.Flags&FShareTmpdir != 0,
|
ShareTmpdir: c.Flags&FShareTmpdir != 0,
|
||||||
})
|
})
|
||||||
@@ -257,6 +269,9 @@ func (c *ContainerConfig) UnmarshalJSON(data []byte) error {
|
|||||||
if v.Device {
|
if v.Device {
|
||||||
c.Flags |= FDevice
|
c.Flags |= FDevice
|
||||||
}
|
}
|
||||||
|
if v.CoverRun {
|
||||||
|
c.Flags |= FCoverRun
|
||||||
|
}
|
||||||
if v.ShareRuntime {
|
if v.ShareRuntime {
|
||||||
c.Flags |= FShareRuntime
|
c.Flags |= FShareRuntime
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ func TestFlagsString(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"none", 0, "none"},
|
{"none", 0, "none"},
|
||||||
{"none high", hst.FAll + 1, "none"},
|
{"none high", hst.FAll + 1, "none"},
|
||||||
{"all", hst.FAll, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir"},
|
{"all", hst.FAll, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir"},
|
||||||
{"all high", math.MaxUint, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir"},
|
{"all high", math.MaxUint, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, cover_run, runtime, tmpdir"},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -53,7 +53,7 @@ func TestContainerConfig(t *testing.T) {
|
|||||||
{"hostnet hostabstract mapuid", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID},
|
{"hostnet hostabstract mapuid", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID},
|
||||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":true}`},
|
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":true}`},
|
||||||
{"all", &hst.ContainerConfig{Flags: hst.FAll},
|
{"all", &hst.ContainerConfig{Flags: hst.FAll},
|
||||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"device":true,"share_runtime":true,"share_tmpdir":true}`},
|
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"device":true,"cover_run":true,"share_runtime":true,"share_tmpdir":true}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package hst
|
package hst
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -61,6 +62,10 @@ type BusConfig struct {
|
|||||||
Filter bool `json:"filter"`
|
Filter bool `json:"filter"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *BusConfig) GoString() string {
|
||||||
|
return fmt.Sprintf("&%#v", *c)
|
||||||
|
}
|
||||||
|
|
||||||
// Interfaces iterates over all interface strings specified in [BusConfig].
|
// Interfaces iterates over all interface strings specified in [BusConfig].
|
||||||
func (c *BusConfig) Interfaces(yield func(string) bool) {
|
func (c *BusConfig) Interfaces(yield func(string) bool) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
|
|||||||
+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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
)
|
)
|
||||||
@@ -36,6 +37,8 @@ type Ops interface {
|
|||||||
Bind(source, target *check.Absolute, flags int) Ops
|
Bind(source, target *check.Absolute, flags int) Ops
|
||||||
// Overlay appends an op that mounts the overlay pseudo filesystem.
|
// Overlay appends an op that mounts the overlay pseudo filesystem.
|
||||||
Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) Ops
|
Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) Ops
|
||||||
|
// OverlayEphemeral appends a MountOverlayOp with an ephemeral upperdir and workdir.
|
||||||
|
OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) Ops
|
||||||
// OverlayReadonly appends an op that mounts the overlay pseudo filesystem readonly.
|
// OverlayReadonly appends an op that mounts the overlay pseudo filesystem readonly.
|
||||||
OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) Ops
|
OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) Ops
|
||||||
|
|
||||||
@@ -78,17 +81,17 @@ type FSImplError struct{ Value FilesystemConfig }
|
|||||||
|
|
||||||
func (f FSImplError) Error() string {
|
func (f FSImplError) Error() string {
|
||||||
implType := reflect.TypeOf(f.Value)
|
implType := reflect.TypeOf(f.Value)
|
||||||
var name string
|
var buf strings.Builder
|
||||||
for implType != nil && implType.Kind() == reflect.Ptr {
|
for implType != nil && implType.Kind() == reflect.Pointer {
|
||||||
name += "*"
|
buf.WriteByte('*')
|
||||||
implType = implType.Elem()
|
implType = implType.Elem()
|
||||||
}
|
}
|
||||||
if implType != nil {
|
if implType != nil {
|
||||||
name += implType.Name()
|
buf.WriteString(implType.Name())
|
||||||
} else {
|
} else {
|
||||||
name += "nil"
|
buf.WriteString("nil")
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("implementation %s not supported", name)
|
return "implementation " + buf.String() + " not supported"
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilesystemConfigJSON is the [json] adapter for [FilesystemConfig].
|
// FilesystemConfigJSON is the [json] adapter for [FilesystemConfig].
|
||||||
|
|||||||
+9
-4
@@ -3,6 +3,7 @@ package hst_test
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -103,7 +104,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
|||||||
t.Run("marshal", func(t *testing.T) {
|
t.Run("marshal", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
wantErr := tc.wantErr
|
wantErr := tc.wantErr
|
||||||
if errors.As(wantErr, new(hst.FSTypeError)) {
|
if _, ok := errors.AsType[hst.FSTypeError](wantErr); ok {
|
||||||
// for unsupported implementation tc
|
// for unsupported implementation tc
|
||||||
wantErr = hst.FSImplError{Value: stubFS{"cat"}}
|
wantErr = hst.FSImplError{Value: stubFS{"cat"}}
|
||||||
}
|
}
|
||||||
@@ -139,7 +140,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
|||||||
t.Run("unmarshal", func(t *testing.T) {
|
t.Run("unmarshal", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
if tc.data == "\x00" && tc.sData == "\x00" {
|
if tc.data == "\x00" && tc.sData == "\x00" {
|
||||||
if errors.As(tc.wantErr, new(hst.FSImplError)) {
|
if _, ok := errors.AsType[hst.FSImplError](tc.wantErr); ok {
|
||||||
// this error is only returned on marshal
|
// this error is only returned on marshal
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -283,11 +284,11 @@ func checkFs(t *testing.T, testCases []fsTestCase) {
|
|||||||
if !reflect.DeepEqual(ops, &tc.ops) {
|
if !reflect.DeepEqual(ops, &tc.ops) {
|
||||||
gotString := new(strings.Builder)
|
gotString := new(strings.Builder)
|
||||||
for _, op := range *ops {
|
for _, op := range *ops {
|
||||||
gotString.WriteString("\n" + op.String())
|
gotString.WriteString("\n" + fmt.Sprintf("%#v", op))
|
||||||
}
|
}
|
||||||
wantString := new(strings.Builder)
|
wantString := new(strings.Builder)
|
||||||
for _, op := range tc.ops {
|
for _, op := range tc.ops {
|
||||||
wantString.WriteString("\n" + op.String())
|
wantString.WriteString("\n" + fmt.Sprintf("%#v", op))
|
||||||
}
|
}
|
||||||
t.Errorf("Apply: %s, want %s", gotString, wantString)
|
t.Errorf("Apply: %s, want %s", gotString, wantString)
|
||||||
}
|
}
|
||||||
@@ -339,6 +340,10 @@ func (p opsAdapter) Overlay(target, state, work *check.Absolute, layers ...*chec
|
|||||||
return opsAdapter{p.Ops.Overlay(target, state, work, layers...)}
|
return opsAdapter{p.Ops.Overlay(target, state, work, layers...)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p opsAdapter) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
|
||||||
|
return opsAdapter{p.Ops.OverlayEphemeral(target, layers...)}
|
||||||
|
}
|
||||||
|
|
||||||
func (p opsAdapter) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
|
func (p opsAdapter) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
|
||||||
return opsAdapter{p.Ops.OverlayReadonly(target, layers...)}
|
return opsAdapter{p.Ops.OverlayReadonly(target, layers...)}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-6
@@ -43,18 +43,13 @@ func (e *FSEphemeral) Apply(z *ApplyState) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
size := e.Size
|
|
||||||
if size < 0 {
|
|
||||||
size = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
perm := e.Perm
|
perm := e.Perm
|
||||||
if perm == 0 {
|
if perm == 0 {
|
||||||
perm = fsEphemeralDefaultPerm
|
perm = fsEphemeralDefaultPerm
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Write {
|
if e.Write {
|
||||||
z.Tmpfs(e.Target, size, perm)
|
z.Tmpfs(e.Target, max(e.Size, 0), perm)
|
||||||
} else {
|
} else {
|
||||||
z.Readonly(e.Target, perm)
|
z.Readonly(e.Target, perm)
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-9
@@ -2,6 +2,7 @@ package hst
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
@@ -40,7 +41,7 @@ func (o *FSOverlay) Valid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if o.Upper != nil { // rw
|
if o.Upper != nil { // rw
|
||||||
return o.Work != nil && len(o.Lower) > 0
|
return o.Work != nil || len(o.Lower) > 0
|
||||||
} else { // ro
|
} else { // ro
|
||||||
return len(o.Lower) >= 2
|
return len(o.Lower) >= 2
|
||||||
}
|
}
|
||||||
@@ -58,8 +59,11 @@ func (o *FSOverlay) Host() []*check.Absolute {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
p := make([]*check.Absolute, 0, 2+len(o.Lower))
|
p := make([]*check.Absolute, 0, 2+len(o.Lower))
|
||||||
if o.Upper != nil && o.Work != nil {
|
if o.Upper != nil {
|
||||||
p = append(p, o.Upper, o.Work)
|
p = append(p, o.Upper)
|
||||||
|
if o.Work != nil {
|
||||||
|
p = append(p, o.Work)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
p = append(p, o.Lower...)
|
p = append(p, o.Lower...)
|
||||||
return p
|
return p
|
||||||
@@ -70,11 +74,18 @@ func (o *FSOverlay) Apply(z *ApplyState) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Upper != nil && o.Work != nil {
|
if o.Upper != nil {
|
||||||
z.Overlay(o.Target, o.Upper, o.Work, o.Lower...)
|
|
||||||
if o.Target.Is(fhs.AbsRoot) {
|
if o.Target.Is(fhs.AbsRoot) {
|
||||||
z.NoRemountRoot = true
|
z.NoRemountRoot = true
|
||||||
}
|
}
|
||||||
|
if o.Work != nil {
|
||||||
|
z.Overlay(o.Target, o.Upper, o.Work, o.Lower...)
|
||||||
|
} else {
|
||||||
|
z.OverlayEphemeral(o.Target, slices.Concat(
|
||||||
|
o.Lower,
|
||||||
|
[]*check.Absolute{o.Upper})...,
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
z.OverlayReadonly(o.Target, o.Lower...)
|
z.OverlayReadonly(o.Target, o.Lower...)
|
||||||
}
|
}
|
||||||
@@ -90,12 +101,19 @@ func (o *FSOverlay) String() string {
|
|||||||
lower[i] = check.EscapeOverlayDataSegment(a.String())
|
lower[i] = check.EscapeOverlayDataSegment(a.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Upper != nil && o.Work != nil {
|
if o.Upper != nil {
|
||||||
return "w*" + strings.Join(append([]string{
|
if o.Work != nil {
|
||||||
|
return "w*" + strings.Join(append([]string{
|
||||||
|
check.EscapeOverlayDataSegment(o.Target.String()),
|
||||||
|
check.EscapeOverlayDataSegment(o.Upper.String()),
|
||||||
|
check.EscapeOverlayDataSegment(o.Work.String())},
|
||||||
|
lower...), check.SpecialOverlayPath)
|
||||||
|
}
|
||||||
|
return "e*" + strings.Join(append([]string{
|
||||||
check.EscapeOverlayDataSegment(o.Target.String()),
|
check.EscapeOverlayDataSegment(o.Target.String()),
|
||||||
check.EscapeOverlayDataSegment(o.Upper.String()),
|
check.EscapeOverlayDataSegment(o.Upper.String())},
|
||||||
check.EscapeOverlayDataSegment(o.Work.String())},
|
|
||||||
lower...), check.SpecialOverlayPath)
|
lower...), check.SpecialOverlayPath)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return "*" + strings.Join(append([]string{
|
return "*" + strings.Join(append([]string{
|
||||||
check.EscapeOverlayDataSegment(o.Target.String())},
|
check.EscapeOverlayDataSegment(o.Target.String())},
|
||||||
|
|||||||
+13
-1
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ func TestFSOverlay(t *testing.T) {
|
|||||||
checkFs(t, []fsTestCase{
|
checkFs(t, []fsTestCase{
|
||||||
{"nil", (*hst.FSOverlay)(nil), false, nil, nil, nil, "<invalid>"},
|
{"nil", (*hst.FSOverlay)(nil), false, nil, nil, nil, "<invalid>"},
|
||||||
{"nil lower", &hst.FSOverlay{Target: m("/etc"), Lower: []*check.Absolute{nil}}, false, nil, nil, nil, "<invalid>"},
|
{"nil lower", &hst.FSOverlay{Target: m("/etc"), Lower: []*check.Absolute{nil}}, false, nil, nil, nil, "<invalid>"},
|
||||||
{"zero lower", &hst.FSOverlay{Target: m("/etc"), Upper: m("/"), Work: m("/")}, false, nil, nil, nil, "<invalid>"},
|
{"zero lower", &hst.FSOverlay{Target: m("/etc"), Work: m("/")}, false, nil, nil, nil, "<invalid>"},
|
||||||
{"zero lower ro", &hst.FSOverlay{Target: m("/etc")}, false, nil, nil, nil, "<invalid>"},
|
{"zero lower ro", &hst.FSOverlay{Target: m("/etc")}, false, nil, nil, nil, "<invalid>"},
|
||||||
{"short lower", &hst.FSOverlay{Target: m("/etc"), Lower: ms("/etc")}, false, nil, nil, nil, "<invalid>"},
|
{"short lower", &hst.FSOverlay{Target: m("/etc"), Lower: ms("/etc")}, false, nil, nil, nil, "<invalid>"},
|
||||||
|
|
||||||
@@ -62,5 +63,16 @@ func TestFSOverlay(t *testing.T) {
|
|||||||
Work: m("/tmp/work"),
|
Work: m("/tmp/work"),
|
||||||
}}, m("/"), ms("/tmp/upper", "/tmp/work", "/tmp/.src0", "/tmp/.src1"),
|
}}, m("/"), ms("/tmp/upper", "/tmp/work", "/tmp/.src0", "/tmp/.src1"),
|
||||||
"w*/:/tmp/upper:/tmp/work:/tmp/.src0:/tmp/.src1"},
|
"w*/:/tmp/upper:/tmp/work:/tmp/.src0:/tmp/.src1"},
|
||||||
|
|
||||||
|
{"ephemeral", &hst.FSOverlay{
|
||||||
|
Target: m("/"),
|
||||||
|
Lower: ms("/tmp/.src0", "/tmp/.src1"),
|
||||||
|
Upper: m("/tmp/upper"),
|
||||||
|
}, true, container.Ops{&container.MountOverlayOp{
|
||||||
|
Target: m("/"),
|
||||||
|
Lower: ms("/tmp/.src0", "/tmp/.src1", "/tmp/upper"),
|
||||||
|
Upper: fhs.AbsRoot,
|
||||||
|
}}, m("/"), ms("/tmp/upper", "/tmp/.src0", "/tmp/.src1"),
|
||||||
|
"e*/:/tmp/upper:/tmp/.src0:/tmp/.src1"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user