Compare commits
362
Commits
v0.4.3
...
807f4ccbeb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
807f4ccbeb
|
||
|
|
f1dd3a085b
|
||
|
|
b37c1d8993
|
||
|
|
6a7f124deb
|
||
|
|
a9a32284b6
|
||
|
|
255a1bbf74
|
||
|
|
96cdf4cc07
|
||
|
|
35a55854fd
|
||
|
|
2f057cc72f
|
||
|
|
3356ca3e46
|
||
|
|
ccef0a086a
|
||
|
|
edb694fb41
|
||
|
|
90535adc89
|
||
|
|
6fed6d67bd
|
||
|
|
dcaf67b3d7
|
||
|
|
c7de3d45e3
|
||
|
|
d9bddff160
|
||
|
|
5c6dcce543
|
||
|
|
905a69d2a0
|
||
|
|
e453bc67d0
|
||
|
|
0fa145a1a0
|
||
|
|
b5a65d1255
|
||
|
|
ccdc0fa37e
|
||
|
|
b97518552d
|
||
|
|
67d14dc12c
|
||
|
|
695b1b4845
|
||
|
|
2e5d954f49
|
||
|
|
e6af0c405a
|
||
|
|
599a761b27
|
||
|
|
b7aa91f08f
|
||
|
|
280a736ce3
|
||
|
|
4e41b9bc99
|
||
|
|
153c980a61
|
||
|
|
a2f97f7e0f
|
||
|
|
5e1b4bb0b8
|
||
|
|
e73f9e0626
|
||
|
|
ddd90b780b
|
||
|
|
89c32f5614
|
||
|
|
1567a3e582
|
||
|
|
aa9952cd9e
|
||
|
|
99efc551ad
|
||
|
|
4dbfee0698
|
||
|
|
4e65e41101
|
||
|
|
b8306fe9e2
|
||
|
|
6b246e9036
|
||
|
|
869820ec6d
|
||
|
|
6fb2065a51
|
||
|
|
698fd2d66a
|
||
|
|
4cfbc93aa0
|
||
|
|
feec4b0d22
|
||
|
|
6487b41d92
|
||
|
|
7908fb9583
|
||
|
|
95aa7c205c
|
||
|
|
bd0cc3656f
|
||
|
|
0219240adb
|
||
|
|
aa4a15fc2c
|
||
|
|
0d3efeb456
|
||
|
|
35147f6dfb
|
||
|
|
e5abf51296
|
||
|
|
e26914cd6f
|
||
|
|
b647170d0c
|
||
|
|
5e655a0bf6
|
||
|
|
89eb3d27aa
|
||
|
|
3b7a22ff9b
|
||
|
|
6c2ca507b9
|
||
|
|
1a8212ee02
|
||
|
|
84bb2a5038
|
||
|
|
46b247f911
|
||
|
|
390f3b2dae
|
||
|
|
5d3e47d8e7
|
||
|
|
6f2852547b
|
||
|
|
3cff369368
|
||
|
|
09de939955
|
||
|
|
e0eec632d0
|
||
|
|
67dbb68818
|
||
|
|
0e689d9983
|
||
|
|
21a4067592
|
||
|
|
639766dfca
|
||
|
|
e4f025b9f1
|
||
|
|
1b65c8107c
|
||
|
|
1857ad8763
|
||
|
|
c822ff9aca
|
||
|
|
9df1609660
|
||
|
|
22e9e85a03
|
||
|
|
b79aa3ea1c
|
||
|
|
8d9b14dfe1
|
||
|
|
5770f957e5
|
||
|
|
e7903cf7e2
|
||
|
|
df199a8774
|
||
|
|
d906bc2ee0
|
||
|
|
e0e9910243
|
||
|
|
c8d54cebe9
|
||
|
|
74e7fb7818
|
||
|
|
8366cdd224
|
||
|
|
05f9939077
|
||
|
|
ae30f627dd
|
||
|
|
b4916dd431
|
||
|
|
4cf235675a
|
||
|
|
098943ab46
|
||
|
|
fb3df67ba6
|
||
|
|
b1c882b4de
|
||
|
|
0ed0d8abde
|
||
|
|
51e4426a4f
|
||
|
|
316edeb17b
|
||
|
|
8facae53c7
|
||
|
|
7881bfd027
|
||
|
|
9e9469fe6c
|
||
|
|
b6d90eb8e0
|
||
|
|
879f911fd8
|
||
|
|
1066fc0097
|
||
|
|
3a3d8efb1e
|
||
|
|
cb8c9e4e68
|
||
|
|
1c4a3b72bf
|
||
|
|
fdca05487c
|
||
|
|
89db730bb9
|
||
|
|
5161fc24ec
|
||
|
|
e1f16e7720
|
||
|
|
8e9b3a2ed1
|
||
|
|
fd0a81b304
|
||
|
|
1d6d89a11a
|
||
|
|
6a6fdab92d
|
||
|
|
67c67574e8
|
||
|
|
79be25433b
|
||
|
|
a4d78e8a32
|
||
|
|
ab3c37f75a
|
||
|
|
e6757fe254
|
||
|
|
b0f0d9e060
|
||
|
|
ce58895a0d
|
||
|
|
3c78551b24
|
||
|
|
223e8e56b3
|
||
|
|
17383ab353
|
||
|
|
237e793895
|
||
|
|
6cfd8fb934
|
||
|
|
2f1534853a
|
||
|
|
3f8e111d1c
|
||
|
|
9fae451dc0
|
||
|
|
e3666cda21
|
||
|
|
9c51805b47
|
||
|
|
3dca68fc95
|
||
|
|
6495118165
|
||
|
|
472849cfd6
|
||
|
|
3c6e9b3d05
|
||
|
|
aa5e77ef27
|
||
|
|
76360979f9
|
||
|
|
6d55ee536e
|
||
|
|
c8e8651694
|
||
|
|
aa5d38981d
|
||
|
|
b08feac26c
|
||
|
|
810b806a15
|
||
|
|
4a42657d88
|
||
|
|
f0b6659865
|
||
|
|
a8ffaa20e0
|
||
|
|
fb62ae00e4
|
||
|
|
512d9b95bd
|
||
|
|
5bf87561f5
|
||
|
|
dde69dde61
|
||
|
|
3ba6609444
|
||
|
|
6dcc2fdc17
|
||
|
|
c553b74807
|
||
|
|
8c853c3eb6
|
||
|
|
d2f322e911
|
||
|
|
b538c69e01
|
||
|
|
c921ba4680
|
||
|
|
7404bf6025
|
||
|
|
3975dd3e0f
|
||
|
|
3ab7116b29
|
||
|
|
9be165d6ee
|
||
|
|
1c55e7c772
|
||
|
|
a981d185cf
|
||
|
|
d91951ca97
|
||
|
|
09a2ffc3bc
|
||
|
|
a6444658da
|
||
|
|
ed5615033c
|
||
|
|
d1319a497c
|
||
|
|
cf7c34555c
|
||
|
|
d96eecded0
|
||
|
|
6863bcafd1
|
||
|
|
39f023d0e5
|
||
|
|
a8a2f692e7
|
||
|
|
19f24c7206
|
||
|
|
320432774a
|
||
|
|
0721b0fe6d
|
||
|
|
418e4a874d
|
||
|
|
8378e7a2c9
|
||
|
|
6210c9f272
|
||
|
|
c2038fa925
|
||
|
|
d797cca1f2
|
||
|
|
2a51b433c8
|
||
|
|
e5ce36532b
|
||
|
|
4c647388b0
|
||
|
|
3a5f4af114
|
||
|
|
6195260480
|
||
|
|
21044d5a60
|
||
|
|
025810bf0f
|
||
|
|
20354c0411
|
||
|
|
9fbcd0daf2
|
||
|
|
248f44a5a7
|
||
|
|
401dd57cbc
|
||
|
|
854bcc998b
|
||
|
|
358247be5b
|
||
|
|
f517a8ef07
|
||
|
|
973218f91f
|
||
|
|
0ea195837b
|
||
|
|
1348991634
|
||
|
|
14b445fde5
|
||
|
|
218f7fa345
|
||
|
|
cd493fd95f
|
||
|
|
9db70c83e3
|
||
|
|
4e09241e5f
|
||
|
|
bd4b300ea6
|
||
|
|
6dc8214a1a
|
||
|
|
cada5a46ad
|
||
|
|
e747942829
|
||
|
|
33b855123e
|
||
|
|
58ce134718
|
||
|
|
2066093343
|
||
|
|
07509b3ba2
|
||
|
|
a7485d587a
|
||
|
|
4892beefc1
|
||
|
|
7ab54b8c94
|
||
|
|
a4fab67811
|
||
|
|
ed5cdd38a4
|
||
|
|
f6318304ee
|
||
|
|
cb618093d5
|
||
|
|
b0b2471c0c
|
||
|
|
344d2b8207
|
||
|
|
3938e8bce5
|
||
|
|
aee15b4f2a
|
||
|
|
18b1103fdc
|
||
|
|
c5a02da0f0
|
||
|
|
c0c2f3233a
|
||
|
|
bda00ac90e
|
||
|
|
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
|
@@ -0,0 +1 @@
|
|||||||
|
*.az linguist-language=Azalea
|
||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build for release
|
- name: Build for release
|
||||||
run: nix build --print-out-paths --print-build-logs .#dist
|
run: nix build --print-out-paths --print-build-logs ./test#dist
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: https://gitea.com/actions/release-action@main
|
uses: https://gitea.com/actions/release-action@main
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run NixOS test
|
- name: Run NixOS test
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.hakurei
|
run: nix build --out-link "result" --print-out-paths --print-build-logs ./test#checks.x86_64-linux.hakurei
|
||||||
|
|
||||||
- name: Upload test output
|
- name: Upload test output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run NixOS test
|
- name: Run NixOS test
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.race
|
run: nix build --out-link "result" --print-out-paths --print-build-logs ./test#checks.x86_64-linux.race
|
||||||
|
|
||||||
- name: Upload test output
|
- name: Upload test output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run NixOS test
|
- name: Run NixOS test
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.sandbox
|
run: nix build --out-link "result" --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox
|
||||||
|
|
||||||
- name: Upload test output
|
- name: Upload test output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run NixOS test
|
- name: Run NixOS test
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.sandbox-race
|
run: nix build --out-link "result" --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox-race
|
||||||
|
|
||||||
- name: Upload test output
|
- name: Upload test output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run NixOS test
|
- name: Run NixOS test
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.sharefs
|
run: nix build --out-link "result" --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sharefs
|
||||||
|
|
||||||
- name: Upload test output
|
- name: Upload test output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run checks
|
- name: Run checks
|
||||||
run: nix --print-build-logs --experimental-features 'nix-command flakes' flake check
|
run: nix --print-build-logs --experimental-features 'nix-command flakes' flake check ./test
|
||||||
|
|
||||||
dist:
|
dist:
|
||||||
name: Create distribution
|
name: Create distribution
|
||||||
@@ -116,9 +116,9 @@ jobs:
|
|||||||
id: build-test
|
id: build-test
|
||||||
run: >-
|
run: >-
|
||||||
export HAKUREI_REV="$(git rev-parse --short HEAD)" &&
|
export HAKUREI_REV="$(git rev-parse --short HEAD)" &&
|
||||||
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' package.nix &&
|
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' test/package.nix &&
|
||||||
nix build --print-out-paths --print-build-logs .#dist &&
|
nix build --print-out-paths --print-build-logs ./test#dist &&
|
||||||
mv package.nix.old package.nix &&
|
mv test/{package.nix.old,package.nix} &&
|
||||||
echo "rev=$HAKUREI_REV" >> $GITHUB_OUTPUT
|
echo "rev=$HAKUREI_REV" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload test build
|
- name: Upload test build
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# produced by tools and text editors
|
# produced by tools and text editors
|
||||||
|
*.swp
|
||||||
*.qcow2
|
*.qcow2
|
||||||
*.test
|
*.test
|
||||||
*.out
|
*.out
|
||||||
@@ -13,3 +14,6 @@
|
|||||||
|
|
||||||
# cmd/dist default destination
|
# cmd/dist default destination
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
|
# local packages
|
||||||
|
/internal/rosa/package/local
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<a href="https://git.gensokyo.uk/rosa/hakurei/actions"><img src="https://git.gensokyo.uk/rosa/hakurei/actions/workflows/test.yml/badge.svg?branch=staging&style=flat-square" alt="Gitea Workflow Status" /></a>
|
<a href="https://git.gensokyo.uk/rosa/hakurei/actions"><img src="https://git.gensokyo.uk/rosa/hakurei/actions/workflows/test.yml/badge.svg?branch=staging&style=flat-square" alt="Gitea Workflow Status" /></a>
|
||||||
<br/>
|
<br/>
|
||||||
<a href="https://git.gensokyo.uk/rosa/hakurei/releases"><img src="https://img.shields.io/gitea/v/release/rosa/hakurei?gitea_url=https%3A%2F%2Fgit.gensokyo.uk&color=purple" alt="Release" /></a>
|
<a href="https://git.gensokyo.uk/rosa/hakurei/releases"><img src="https://img.shields.io/gitea/v/release/rosa/hakurei?gitea_url=https%3A%2F%2Fgit.gensokyo.uk&color=purple" alt="Release" /></a>
|
||||||
<a href="https://goreportcard.com/report/hakurei.app"><img src="https://goreportcard.com/badge/hakurei.app" alt="Go Report Card" /></a>
|
<a href="https://git.gensokyo.uk/rosa/hakurei/src/branch/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-pink" alt="MIT License" /></a>
|
||||||
<a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
|
<a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -57,9 +57,4 @@ package [internal/rosa](https://pkg.go.dev/hakurei.app/internal/rosa).
|
|||||||
- [fuse](https://github.com/libfuse/libfuse) to implement the filesystem.
|
- [fuse](https://github.com/libfuse/libfuse) to implement the filesystem.
|
||||||
|
|
||||||
New dependencies will generally not be added. Patches adding new dependencies
|
New dependencies will generally not be added. Patches adding new dependencies
|
||||||
are very likely to be rejected.
|
are very likely to be rejected.
|
||||||
|
|
||||||
## NixOS Module (deprecated)
|
|
||||||
|
|
||||||
The NixOS module is in maintenance mode and will be removed once planterette is
|
|
||||||
feature-complete. Full module documentation can be found [here](options.md).
|
|
||||||
+8
-2
@@ -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
|
||||||
@@ -31,6 +31,8 @@ func (e AbsoluteError) Is(target error) bool {
|
|||||||
type Absolute struct{ pathname unique.Handle[string] }
|
type Absolute struct{ pathname unique.Handle[string] }
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
_ fmt.GoStringer = new(Absolute)
|
||||||
|
|
||||||
_ encoding.TextAppender = new(Absolute)
|
_ encoding.TextAppender = new(Absolute)
|
||||||
_ encoding.TextMarshaler = new(Absolute)
|
_ encoding.TextMarshaler = new(Absolute)
|
||||||
_ encoding.TextUnmarshaler = new(Absolute)
|
_ encoding.TextUnmarshaler = new(Absolute)
|
||||||
@@ -40,6 +42,10 @@ var (
|
|||||||
_ encoding.BinaryUnmarshaler = 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{}) }
|
||||||
|
|
||||||
|
|||||||
+354
@@ -0,0 +1,354 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/ext"
|
||||||
|
"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,
|
||||||
|
templateP *string,
|
||||||
|
) (*hst.Config, error) {
|
||||||
|
shell := fhs.AbsRoot.Append("bin", "zsh")
|
||||||
|
home := hst.AbsPrivateTmp.Append("home")
|
||||||
|
|
||||||
|
root := hst.FSOverlay{
|
||||||
|
Target: fhs.AbsRoot,
|
||||||
|
Lower: []*check.Absolute{base.Append("initial")},
|
||||||
|
}
|
||||||
|
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: &root},
|
||||||
|
{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,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
|
||||||
|
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 template, identity, ok := strings.Cut(s.Text(), ":"); !ok {
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
} else if v, err := strconv.Atoi(identity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
if templateP != nil {
|
||||||
|
*templateP = template
|
||||||
|
}
|
||||||
|
c.Identity = v
|
||||||
|
root.Upper = base.Append("template", template)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanOnce(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Container.Args = append(c.Container.Args, s.Text(), "")
|
||||||
|
|
||||||
|
var flagInteractive, flagGPU, flagSystemBus bool
|
||||||
|
flags := map[string]*bool{
|
||||||
|
"interactive": &flagInteractive,
|
||||||
|
"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 "username":
|
||||||
|
c.Container.Username = value
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "hostname":
|
||||||
|
c.Container.Hostname = value
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "group":
|
||||||
|
c.Groups = append(c.Groups, value)
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "sched_policy":
|
||||||
|
if err := c.SchedPolicy.UnmarshalText([]byte(value)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "sched_priority":
|
||||||
|
v, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.SchedPriority = ext.Int(v)
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "insecure":
|
||||||
|
switch value {
|
||||||
|
case "pipewire":
|
||||||
|
*c.Enablements |= hst.EPipeWire
|
||||||
|
c.DirectPipeWire = true
|
||||||
|
continue
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid insecure flag %q", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "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,
|
||||||
|
Optional: true,
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
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 "dev":
|
||||||
|
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,
|
||||||
|
Device: 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 flagInteractive {
|
||||||
|
c.Container.Args[1] += "i"
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Enablements.Unwrap()&hst.EX11 == 0 ||
|
||||||
|
c.Container.Flags&(hst.FHostNet|hst.FHostAbstract) ==
|
||||||
|
hst.FHostNet|hst.FHostAbstract) {
|
||||||
|
c.Container.Filesystem = append(c.Container.Filesystem,
|
||||||
|
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{
|
||||||
|
Target: fhs.AbsTmp,
|
||||||
|
Write: true,
|
||||||
|
Perm: 01777,
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// list prints names of non-hidden entries in pathname.
|
||||||
|
func list(pathname *check.Absolute, dir bool) error {
|
||||||
|
dents, err := os.ReadDir(pathname.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, dent := range dents {
|
||||||
|
name := dent.Name()
|
||||||
|
if (dent.IsDir() != dir) || (len(name) > 0 && name[0] == '.') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Println(dent.Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
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", `nonfree: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("initial"),
|
||||||
|
},
|
||||||
|
Upper: base.Append("template", "nonfree"),
|
||||||
|
}},
|
||||||
|
{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: 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),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/fhs"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/env"
|
||||||
|
"hakurei.app/internal/lockedfile"
|
||||||
|
"hakurei.app/internal/outcome"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MutationConflictError describes an active mutable instance.
|
||||||
|
type MutationConflictError string
|
||||||
|
|
||||||
|
func (e MutationConflictError) Error() string {
|
||||||
|
return "mutable instance active at " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// informTemplate guards intention of a template or its derivatives.
|
||||||
|
func informTemplate(base *check.Absolute, name string, mutable bool) (func() error, error) {
|
||||||
|
mu := lockedfile.MutexAt(base.Append("lock", name).String())
|
||||||
|
if unlock, err := mu.Lock(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
marker := base.Append("lock", "."+name)
|
||||||
|
if p, err := os.ReadFile(marker.String()); err == nil {
|
||||||
|
if _, err = os.Stat(fhs.AbsProc.Append(string(p)).String()); err == nil {
|
||||||
|
return nil, MutationConflictError(p)
|
||||||
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Printf("removing stale marker by %s", string(p))
|
||||||
|
if err = os.Remove(marker.String()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mutable {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var active []hst.ID
|
||||||
|
var sc hst.Paths
|
||||||
|
env.CopyPaths().Copy(&sc, new(outcome.Hsu).MustID(nil))
|
||||||
|
entries, copyError := outcome.NewStore(&sc).All()
|
||||||
|
var s hst.State
|
||||||
|
for eh := range entries {
|
||||||
|
s = hst.State{}
|
||||||
|
if _, err := eh.Load(&s); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Validate(0) != nil || len(s.Container.Filesystem) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
root, ok := s.Container.Filesystem[0].FilesystemConfig.(*hst.FSOverlay)
|
||||||
|
if !ok || root == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !root.Target.Is(fhs.AbsRoot) ||
|
||||||
|
len(root.Lower) != 1 ||
|
||||||
|
!root.Lower[0].Is(base.Append("initial")) ||
|
||||||
|
!root.Upper.Is(base.Append("template", name)) ||
|
||||||
|
root.Work != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
active = append(active, s.ID)
|
||||||
|
}
|
||||||
|
if err := copyError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(active) != 0 {
|
||||||
|
var buf strings.Builder
|
||||||
|
buf.WriteString("derivative instances still active:")
|
||||||
|
for _, id := range active {
|
||||||
|
buf.WriteString("\n\t")
|
||||||
|
buf.WriteString(id.String())
|
||||||
|
}
|
||||||
|
return nil, errors.New(buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() error { return os.RemoveAll(marker.String()) }, os.WriteFile(
|
||||||
|
marker.String(),
|
||||||
|
[]byte(strconv.Itoa(os.Getpid())),
|
||||||
|
0400,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// acquireTemplate obtains exclusivity of a template.
|
||||||
|
func acquireTemplate(base *check.Absolute, name string) (remove func() error, err error) {
|
||||||
|
return informTemplate(base, name, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// enterTemplate checks against exclusivity of a template.
|
||||||
|
func enterTemplate(base *check.Absolute, name string) error {
|
||||||
|
_, err := informTemplate(base, name, false)
|
||||||
|
return err
|
||||||
|
}
|
||||||
+226
@@ -0,0 +1,226 @@
|
|||||||
|
// 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"
|
||||||
|
"io"
|
||||||
|
"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
|
||||||
|
flagInsecure bool
|
||||||
|
|
||||||
|
base, template, initial *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 = base.Append("initial")
|
||||||
|
return
|
||||||
|
}).Flag(
|
||||||
|
&flagVerbose,
|
||||||
|
"v", command.BoolFlag(false),
|
||||||
|
"Increase log verbosity",
|
||||||
|
).Flag(
|
||||||
|
&flagBase,
|
||||||
|
"d", command.StringFlag("$ROSA_APP_PATH"),
|
||||||
|
"Configuration and state directory",
|
||||||
|
).Flag(
|
||||||
|
&flagInsecure,
|
||||||
|
"insecure", command.BoolFlag(false),
|
||||||
|
"Allow use of insecure compatibility options",
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
|
var (
|
||||||
|
flagShell string
|
||||||
|
flagHome string
|
||||||
|
)
|
||||||
|
c.NewCommand(
|
||||||
|
"enter", "Enter mutable state template",
|
||||||
|
func(args []string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return list(template, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := hst.Config{
|
||||||
|
ID: "app.hakurei.mutable." + args[0],
|
||||||
|
Container: &hst.ContainerConfig{
|
||||||
|
Hostname: args[0] + "-mutable",
|
||||||
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
|
{FilesystemConfig: &hst.FSOverlay{
|
||||||
|
Target: fhs.AbsRoot,
|
||||||
|
Lower: []*check.Absolute{initial},
|
||||||
|
Upper: template.Append(args[0]),
|
||||||
|
Work: base.Append("work", args[0]),
|
||||||
|
}},
|
||||||
|
{FilesystemConfig: &hst.FSEphemeral{
|
||||||
|
Target: fhs.AbsTmp,
|
||||||
|
Write: true,
|
||||||
|
Perm: 0755,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
Username: "chronos",
|
||||||
|
Flags: hst.FNoPlace |
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
remove, err := acquireTemplate(base, args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = run(ctx, msg, false, &config)
|
||||||
|
return errors.Join(err, remove())
|
||||||
|
},
|
||||||
|
).Flag(
|
||||||
|
&flagShell,
|
||||||
|
"shell", command.StringFlag("/bin/zsh"),
|
||||||
|
"Shell program within container",
|
||||||
|
).Flag(
|
||||||
|
&flagHome,
|
||||||
|
"home", command.StringFlag("/home/chronos"),
|
||||||
|
"Home directory within container",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var (
|
||||||
|
flagCommand string
|
||||||
|
)
|
||||||
|
c.NewCommand(
|
||||||
|
"run", "Start the named application",
|
||||||
|
func(args []string) error {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return list(base.Append("app"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
var config *hst.Config
|
||||||
|
var r io.Reader
|
||||||
|
f, err := os.Open(base.Append("app", args[0]).String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r = f
|
||||||
|
|
||||||
|
var common *os.File
|
||||||
|
if common, err = os.Open(base.Append("common").String()); err != nil {
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
_ = f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r = io.MultiReader(f, common)
|
||||||
|
}
|
||||||
|
|
||||||
|
var name string
|
||||||
|
config, err = parse(args[0], base, r, &name)
|
||||||
|
if closeErr := f.Close(); err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
if common != nil {
|
||||||
|
if closeErr := common.Close(); err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagCommand != "" {
|
||||||
|
config.Container.Args[2] = flagCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = enterTemplate(base, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return run(ctx, msg, flagInsecure, config, args[1:]...)
|
||||||
|
},
|
||||||
|
).
|
||||||
|
Flag(
|
||||||
|
&flagCommand,
|
||||||
|
"command", command.StringFlag(""),
|
||||||
|
"Override configured command",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
var m string
|
||||||
|
m, ok = message.GetMessage(err)
|
||||||
|
if !ok {
|
||||||
|
log.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Fatal(m)
|
||||||
|
} else {
|
||||||
|
errs := w.Unwrap()
|
||||||
|
for i, e := range errs {
|
||||||
|
if i == len(errs)-1 {
|
||||||
|
log.Fatal(e)
|
||||||
|
}
|
||||||
|
log.Println(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
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,
|
||||||
|
insecure bool,
|
||||||
|
config *hst.Config,
|
||||||
|
args ...string,
|
||||||
|
) 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")
|
||||||
|
}
|
||||||
|
if insecure {
|
||||||
|
cmd.Args = append(cmd.Args, "--insecure")
|
||||||
|
}
|
||||||
|
cmd.Args = append(cmd.Args, "run", "3")
|
||||||
|
cmd.Args = append(cmd.Args, args...)
|
||||||
|
|
||||||
|
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
-1
@@ -1 +1 @@
|
|||||||
v0.4.3
|
v0.4.5
|
||||||
|
|||||||
Vendored
+25
-10
@@ -35,8 +35,11 @@ func getenv(key, fallback string) string {
|
|||||||
|
|
||||||
// mustRun runs a command with the current process's environment and panics
|
// mustRun runs a command with the current process's environment and panics
|
||||||
// on error or non-zero exit code.
|
// on error or non-zero exit code.
|
||||||
func mustRun(ctx context.Context, name string, arg ...string) {
|
func mustRun(ctx context.Context, env []string, name string, arg ...string) {
|
||||||
cmd := exec.CommandContext(ctx, name, arg...)
|
cmd := exec.CommandContext(ctx, name, arg...)
|
||||||
|
if env != nil {
|
||||||
|
cmd.Env = append(cmd.Environ(), env...)
|
||||||
|
}
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -49,11 +52,12 @@ var comp []byte
|
|||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("")
|
log.SetPrefix("")
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
|
|
||||||
verbose := os.Getenv("VERBOSE") != ""
|
verbose := os.Getenv("VERBOSE") != ""
|
||||||
runTests := os.Getenv("HAKUREI_DIST_MAKE") == ""
|
runTests := os.Getenv("HAKUREI_DIST_MAKE") == ""
|
||||||
version = getenv("HAKUREI_VERSION", strings.TrimSpace(version))
|
version = getenv("HAKUREI_VERSION", strings.TrimSpace(version))
|
||||||
prefix := getenv("PREFIX", "/usr")
|
prefix := getenv("PREFIX", "/usr/local")
|
||||||
destdir := getenv("DESTDIR", "dist")
|
destdir := getenv("DESTDIR", "dist")
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
@@ -91,26 +95,37 @@ func main() {
|
|||||||
verboseFlag = "-buildvcs=false"
|
verboseFlag = "-buildvcs=false"
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Building hakurei for %s/%s.", runtime.GOOS, runtime.GOARCH)
|
log.Printf("Building hakurei %s for %s/%s.", version, runtime.GOOS, runtime.GOARCH)
|
||||||
mustRun(ctx, "go", "generate", "./...")
|
mustRun(ctx, nil, "go", "generate", "./...")
|
||||||
mustRun(
|
mustRun(
|
||||||
ctx, "go", "build",
|
ctx, nil, "go", "build",
|
||||||
"-trimpath",
|
"-trimpath",
|
||||||
verboseFlag, "-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+" "+
|
||||||
"-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+
|
"-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+
|
||||||
"-X hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu "+
|
"-X hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu",
|
||||||
"-X main.hakureiPath="+prefix+"/bin/hakurei",
|
"./cmd/hakurei",
|
||||||
"./...",
|
"./cmd/sharefs",
|
||||||
)
|
)
|
||||||
log.Println()
|
|
||||||
|
|
||||||
|
log.Printf("Building cmd/hsu for %s/%s.", runtime.GOOS, runtime.GOARCH)
|
||||||
|
mustRun(
|
||||||
|
ctx, []string{"CGO_ENABLED=0"}, "go", "build",
|
||||||
|
"-trimpath",
|
||||||
|
verboseFlag, "-o", s,
|
||||||
|
"-ldflags=-s -w "+
|
||||||
|
"-buildid= "+
|
||||||
|
"-X main.hakureiPath="+prefix+"/bin/hakurei",
|
||||||
|
"./cmd/hsu",
|
||||||
|
)
|
||||||
|
|
||||||
|
log.Println()
|
||||||
if runTests {
|
if runTests {
|
||||||
log.Println("##### Testing Hakurei.")
|
log.Println("##### Testing Hakurei.")
|
||||||
mustRun(
|
mustRun(
|
||||||
ctx, "go", "test",
|
ctx, nil, "go", "test",
|
||||||
"-ldflags=-buildid= -linkmode external -extldflags=-static",
|
"-ldflags=-buildid= -linkmode external -extldflags=-static",
|
||||||
"./...",
|
"./...",
|
||||||
)
|
)
|
||||||
|
|||||||
+155
-20
@@ -5,17 +5,93 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"io"
|
||||||
"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")
|
||||||
|
log.SetOutput(io.Discard)
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// must calls fatal with err if it is non-nil.
|
||||||
|
func must(err error) {
|
||||||
|
if err != nil {
|
||||||
|
fatal(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 +109,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 +203,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 +261,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,74 @@
|
|||||||
|
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(
|
||||||
|
"%s (exit status %d)",
|
||||||
|
strings.TrimPrefix(strings.TrimSpace(e.Stderr), "modprobe: "),
|
||||||
|
e.ExitCode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -19,10 +19,9 @@ func TestHelp(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"main", []string{}, `
|
"main", []string{}, `usage: hakurei [-h | --help] [-v] [--insecure] [--json] <command> [<args>]
|
||||||
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
|
||||||
exec Configure and start a permissive container
|
exec Configure and start a permissive container
|
||||||
show Show live or local instance configuration
|
show Show live or local instance configuration
|
||||||
@@ -35,10 +34,14 @@ Commands:
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exec", []string{"exec", "-h"}, `
|
"exec", []string{"exec", "-h"}, `usage: hakurei exec [-h | --help] [--dbus-config <value>]
|
||||||
Usage: hakurei exec [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--policy <value>] [--priority <int>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS]
|
[--dbus-system <value>] [--mpris] [--dbus-log]
|
||||||
|
[--id <value>] [-a <int>] [-g <value>] [-d <value>]
|
||||||
|
[-u <value>] [--policy <value>] [--priority <int>]
|
||||||
|
[--private-runtime] [--private-tmpdir] [--wayland] [-X]
|
||||||
|
[--dbus] [--pipewire] [--pulse] <command> [<args>]
|
||||||
|
|
||||||
Flags:
|
flags:
|
||||||
-X Enable direct connection to X11
|
-X Enable direct connection to X11
|
||||||
-a int
|
-a int
|
||||||
Application identity
|
Application identity
|
||||||
|
|||||||
+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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, noplace, 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, noplace, 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
|
||||||
@@ -354,7 +354,9 @@ App
|
|||||||
"tty": true,
|
"tty": true,
|
||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
|
"noplace": true,
|
||||||
"device": true,
|
"device": true,
|
||||||
|
"cover_run": true,
|
||||||
"share_runtime": true,
|
"share_runtime": true,
|
||||||
"share_tmpdir": true
|
"share_tmpdir": true
|
||||||
},
|
},
|
||||||
@@ -505,7 +507,9 @@ App
|
|||||||
"tty": true,
|
"tty": true,
|
||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
|
"noplace": true,
|
||||||
"device": true,
|
"device": true,
|
||||||
|
"cover_run": true,
|
||||||
"share_runtime": true,
|
"share_runtime": true,
|
||||||
"share_tmpdir": true
|
"share_tmpdir": true
|
||||||
}
|
}
|
||||||
@@ -703,7 +707,9 @@ func TestPrintPs(t *testing.T) {
|
|||||||
"tty": true,
|
"tty": true,
|
||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
|
"noplace": 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{
|
||||||
|
|||||||
+81
-28
@@ -2,13 +2,15 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/container"
|
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/internal/rosa"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,17 +22,29 @@ type cache struct {
|
|||||||
// Should generally not be used directly.
|
// Should generally not be used directly.
|
||||||
c *pkg.Cache
|
c *pkg.Cache
|
||||||
|
|
||||||
cures, jobs int
|
attr pkg.CacheAttr
|
||||||
// Primarily to work around missing landlock LSM.
|
// Primarily to work around missing landlock LSM.
|
||||||
hostAbstract bool
|
hostAbstract bool
|
||||||
// Set SCHED_IDLE.
|
// Set SCHED_IDLE.
|
||||||
idle bool
|
idle bool
|
||||||
// Unset [pkg.CSuppressInit].
|
// Unset [pkg.CSuppressInit].
|
||||||
verboseInit bool
|
verboseInit bool
|
||||||
|
// Whether to enable output colours.
|
||||||
|
color bool
|
||||||
|
// Unset [pkg.CExternShallow].
|
||||||
|
deep bool
|
||||||
// Loaded artifact of [rosa.QEMU].
|
// Loaded artifact of [rosa.QEMU].
|
||||||
qemu pkg.Artifact
|
qemu pkg.Artifact
|
||||||
|
|
||||||
base string
|
base, mirror string
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTitle sets title of the terminal connected to [message.Msg].
|
||||||
|
func writeTitle(msg message.Msg, s string) {
|
||||||
|
msg.Suspend()
|
||||||
|
w := msg.GetLogger().Writer()
|
||||||
|
_, _ = w.Write([]byte("\x1b]0;" + s + "\a"))
|
||||||
|
msg.Resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
// open opens the underlying [pkg.Cache].
|
// open opens the underlying [pkg.Cache].
|
||||||
@@ -39,22 +53,35 @@ func (cache *cache) open() (err error) {
|
|||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hostname string
|
||||||
|
if hostname, err = os.Hostname(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var base *check.Absolute
|
var base *check.Absolute
|
||||||
if cache.base, err = filepath.Abs(cache.base); err != nil {
|
if cache.base, err = filepath.Abs(cache.base); err != nil {
|
||||||
return
|
return
|
||||||
} else if base, err = check.NewAbs(cache.base); err != nil {
|
} else if base, err = check.NewAbs(cache.base); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var notify chan bool
|
||||||
|
|
||||||
var flags int
|
|
||||||
if cache.idle {
|
if cache.idle {
|
||||||
flags |= pkg.CSchedIdle
|
cache.attr.Flags |= pkg.CSchedIdle
|
||||||
}
|
}
|
||||||
if cache.hostAbstract {
|
if cache.hostAbstract {
|
||||||
flags |= pkg.CHostAbstract
|
cache.attr.Flags |= pkg.CHostAbstract
|
||||||
}
|
}
|
||||||
if !cache.verboseInit {
|
if !cache.verboseInit {
|
||||||
flags |= pkg.CSuppressInit
|
cache.attr.Flags |= pkg.CSuppressInit
|
||||||
|
}
|
||||||
|
if !cache.deep {
|
||||||
|
cache.attr.Flags |= pkg.CExternShallow
|
||||||
|
}
|
||||||
|
if cache.color {
|
||||||
|
cache.attr.Flags |= pkg.CColourOutput
|
||||||
|
notify = make(chan bool, 1<<12)
|
||||||
|
cache.attr.Notify = notify
|
||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
@@ -76,16 +103,52 @@ func (cache *cache) open() (err error) {
|
|||||||
cache.c, err = pkg.Open(
|
cache.c, err = pkg.Open(
|
||||||
cache.ctx,
|
cache.ctx,
|
||||||
cache.msg,
|
cache.msg,
|
||||||
flags,
|
|
||||||
cache.cures,
|
|
||||||
cache.jobs,
|
|
||||||
base,
|
base,
|
||||||
|
&cache.attr,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
done <- struct{}{}
|
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(http.DefaultClient, cache.mirror, pub); err != nil {
|
||||||
|
cache.c.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cache.c.SetExternal(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cache.attr.Notify != nil {
|
||||||
|
cache.msg.Suspend()
|
||||||
|
_, _ = cache.msg.GetLogger().Writer().Write([]byte("\x1b[22;0t"))
|
||||||
|
cache.msg.Resume()
|
||||||
|
|
||||||
|
prefix := hostname + ": mbf "
|
||||||
|
go func() {
|
||||||
|
var n, nc uint64
|
||||||
|
for enter := range notify {
|
||||||
|
if enter {
|
||||||
|
n++
|
||||||
|
nc++
|
||||||
|
} else {
|
||||||
|
nc--
|
||||||
|
}
|
||||||
|
|
||||||
|
writeTitle(cache.msg, prefix+
|
||||||
|
"("+strconv.FormatUint(nc, 10)+
|
||||||
|
" of "+strconv.FormatUint(n, 10)+")")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if cache.qemu != nil {
|
if cache.qemu != nil {
|
||||||
var pathname *check.Absolute
|
var pathname *check.Absolute
|
||||||
pathname, _, err = cache.c.Cure(cache.qemu)
|
pathname, _, err = cache.c.Cure(cache.qemu)
|
||||||
@@ -94,24 +157,9 @@ func (cache *cache) open() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg.RegisterArch("riscv64", container.BinfmtEntry{
|
for arch, entry := range rosa.Arches(pathname) {
|
||||||
Offset: 0,
|
pkg.RegisterArch(arch, entry)
|
||||||
Magic: "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00",
|
}
|
||||||
Mask: "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
|
|
||||||
Interpreter: pathname.Append(
|
|
||||||
"system/bin",
|
|
||||||
"qemu-riscv64",
|
|
||||||
),
|
|
||||||
})
|
|
||||||
pkg.RegisterArch("arm64", container.BinfmtEntry{
|
|
||||||
Offset: 0,
|
|
||||||
Magic: "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00",
|
|
||||||
Mask: "\xff\xff\xff\xff\xff\xff\xff\xfc\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
|
|
||||||
Interpreter: pathname.Append(
|
|
||||||
"system/bin",
|
|
||||||
"qemu-aarch64",
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -121,6 +169,11 @@ func (cache *cache) open() (err error) {
|
|||||||
func (cache *cache) Close() {
|
func (cache *cache) Close() {
|
||||||
if cache.c != nil {
|
if cache.c != nil {
|
||||||
cache.c.Close()
|
cache.c.Close()
|
||||||
|
if cache.attr.Notify != nil {
|
||||||
|
cache.msg.Suspend()
|
||||||
|
_, _ = cache.msg.GetLogger().Writer().Write([]byte("\x1b[23;0t"))
|
||||||
|
cache.msg.Resume()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ func TestNoReply(t *testing.T) {
|
|||||||
c, err := pkg.Open(
|
c, err := pkg.Open(
|
||||||
t.Context(),
|
t.Context(),
|
||||||
message.New(log.New(os.Stderr, "cir: ", 0)),
|
message.New(log.New(os.Stderr, "cir: ", 0)),
|
||||||
0, 0, 0,
|
|
||||||
check.MustAbs(t.TempDir()),
|
check.MustAbs(t.TempDir()),
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Open: error = %v", err)
|
t.Fatalf("Open: error = %v", err)
|
||||||
@@ -63,7 +63,7 @@ func TestNoReply(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<-done
|
<-done
|
||||||
wantIdent := pkg.MustDecode("fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG")
|
wantIdent := pkg.MustDecode("bpVsJ69hdKPBntMh_u777g7-quFNVmMlBJQOlM_thiQa9JnOFLAbe5OlR4zNjZ32")
|
||||||
if gotIdent := c.Ident(a).Value(); gotIdent != wantIdent {
|
if gotIdent := c.Ident(a).Value(); gotIdent != wantIdent {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"cureFromIR: %s, want %s",
|
"cureFromIR: %s, want %s",
|
||||||
@@ -124,7 +124,7 @@ func TestDaemon(t *testing.T) {
|
|||||||
cancel()
|
cancel()
|
||||||
<-done
|
<-done
|
||||||
|
|
||||||
const want = "fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG"
|
const want = "bpVsJ69hdKPBntMh_u777g7-quFNVmMlBJQOlM_thiQa9JnOFLAbe5OlR4zNjZ32"
|
||||||
if got := filepath.Base(p.String()); got != want {
|
if got := filepath.Base(p.String()); got != want {
|
||||||
t.Errorf("cureRemote: %s, want %s", got, want)
|
t.Errorf("cureRemote: %s, want %s", got, want)
|
||||||
}
|
}
|
||||||
@@ -132,8 +132,8 @@ func TestDaemon(t *testing.T) {
|
|||||||
wantLog := []string{
|
wantLog := []string{
|
||||||
"",
|
"",
|
||||||
"daemon: aborting all pending cures",
|
"daemon: aborting all pending cures",
|
||||||
"daemon: attempting to cancel invalid artifact kQm9fmnCmXST1-MMmxzcau2oKZCXXrlZydo4PkeV5hO_2PKfeC8t98hrbV_ZZx_j",
|
"daemon: attempting to cancel invalid artifact 58Vy_5beLZCvZX__KzRKyIE6vATleaZP1ZBxbqoWnekz9aZ1zkxNgcIUFI5V1_Jf",
|
||||||
"daemon: fulfilled artifact fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG",
|
"daemon: fulfilled artifact " + want,
|
||||||
}
|
}
|
||||||
gotLog := strings.Split(buf.String(), "\n")
|
gotLog := strings.Split(buf.String(), "\n")
|
||||||
slices.Sort(gotLog)
|
slices.Sort(gotLog)
|
||||||
|
|||||||
+4
-1
@@ -21,7 +21,10 @@ func commandInfo(
|
|||||||
r *rosa.Report,
|
r *rosa.Report,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.New("info requires at least 1 argument")
|
for _, h := range rosa.Native().CollectAll() {
|
||||||
|
fmt.Println(h)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// recovered by HandleAccess
|
// recovered by HandleAccess
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+453
-169
@@ -14,6 +14,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -26,17 +27,17 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"unique"
|
"unique"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/seccomp"
|
|
||||||
"hakurei.app/container/std"
|
|
||||||
"hakurei.app/ext"
|
"hakurei.app/ext"
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
@@ -47,6 +48,19 @@ import (
|
|||||||
"hakurei.app/cmd/mbf/internal/pkgserver/ui"
|
"hakurei.app/cmd/mbf/internal/pkgserver/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// writeFileExcl is like [os.WriteFile], but sets [os.O_EXCL] instead.
|
||||||
|
func writeFileExcl(name string, data []byte, perm os.FileMode) error {
|
||||||
|
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.Write(data)
|
||||||
|
if err1 := f.Close(); err1 != nil && err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
container.TryArgv0(nil)
|
container.TryArgv0(nil)
|
||||||
|
|
||||||
@@ -64,11 +78,12 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e, ok := r.(rosa.LoadError)
|
switch r.(type) {
|
||||||
if !ok {
|
case rosa.LoadError, pkg.IRStringError:
|
||||||
|
log.Fatal(r)
|
||||||
|
default:
|
||||||
panic(r)
|
panic(r)
|
||||||
}
|
}
|
||||||
log.Fatal(e)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
ctx, stop := signal.NotifyContext(context.Background(),
|
||||||
@@ -77,6 +92,11 @@ func main() {
|
|||||||
|
|
||||||
var cm cache
|
var cm cache
|
||||||
defer func() { cm.Close() }()
|
defer func() { cm.Close() }()
|
||||||
|
if cm.color = ext.Isatty(syscall.Stderr) &&
|
||||||
|
os.Getenv("NO_COLOR") == "" &&
|
||||||
|
os.Getenv("TERM") != "dumb"; cm.color {
|
||||||
|
log.SetPrefix("\x1b[2mmbf: \x1b[0m")
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
flagQuiet bool
|
flagQuiet bool
|
||||||
@@ -85,7 +105,10 @@ func main() {
|
|||||||
flagCheck bool
|
flagCheck bool
|
||||||
flagLTO bool
|
flagLTO bool
|
||||||
flagPT bool
|
flagPT bool
|
||||||
|
flagDry bool
|
||||||
|
flagPath string
|
||||||
|
|
||||||
|
flagSourcePath string
|
||||||
flagCrossOverride int
|
flagCrossOverride int
|
||||||
|
|
||||||
addr net.UnixAddr
|
addr net.UnixAddr
|
||||||
@@ -105,6 +128,8 @@ func main() {
|
|||||||
if cm.base == "" {
|
if cm.base == "" {
|
||||||
cm.base = "cache"
|
cm.base = "cache"
|
||||||
}
|
}
|
||||||
|
cm.mirror = os.ExpandEnv(cm.mirror)
|
||||||
|
azaleaPath := os.ExpandEnv(flagPath)
|
||||||
|
|
||||||
addr.Net = "unix"
|
addr.Net = "unix"
|
||||||
addr.Name = os.ExpandEnv(addr.Name)
|
addr.Name = os.ExpandEnv(addr.Name)
|
||||||
@@ -136,6 +161,23 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flagSourcePath != "" {
|
||||||
|
if err := rosa.Native().SetSource(os.DirFS(flagSourcePath)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if azaleaPath != "" {
|
||||||
|
var root *os.Root
|
||||||
|
if a, err := check.NewAbs(azaleaPath); err != nil {
|
||||||
|
return err
|
||||||
|
} else if root, err = os.OpenRoot(a.String()); err != nil {
|
||||||
|
return err
|
||||||
|
} else if err = rosa.Native().RegisterFS(root.FS()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}).Flag(
|
}).Flag(
|
||||||
&flagQuiet,
|
&flagQuiet,
|
||||||
@@ -166,17 +208,30 @@ func main() {
|
|||||||
"v", command.BoolFlag(false),
|
"v", command.BoolFlag(false),
|
||||||
"Do not suppress verbose output from init",
|
"Do not suppress verbose output from init",
|
||||||
).Flag(
|
).Flag(
|
||||||
&cm.cures,
|
&cm.attr.Cures,
|
||||||
"cures", command.IntFlag(0),
|
"cures", command.IntFlag(0),
|
||||||
"Maximum number of dependencies to cure at any given time",
|
"Maximum number of dependencies to cure at any given time",
|
||||||
).Flag(
|
).Flag(
|
||||||
&cm.jobs,
|
&cm.attr.Jobs,
|
||||||
"jobs", command.IntFlag(0),
|
"jobs", command.IntFlag(0),
|
||||||
"Preferred number of jobs to run, when applicable",
|
"Preferred number of jobs to run, when applicable",
|
||||||
|
).Flag(
|
||||||
|
&cm.attr.Load,
|
||||||
|
"load-average", command.IntFlag(0),
|
||||||
|
"Preferred load average number, when applicable",
|
||||||
).Flag(
|
).Flag(
|
||||||
&cm.base,
|
&cm.base,
|
||||||
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
||||||
"Directory to store cured artifacts",
|
"Directory to store cured artifacts",
|
||||||
|
).Flag(
|
||||||
|
&cm.mirror,
|
||||||
|
"r", command.StringFlag("$MBF_REMOTE"),
|
||||||
|
"URL of mirror service",
|
||||||
|
).Flag(
|
||||||
|
&cm.deep,
|
||||||
|
"deep", command.BoolFlag(false),
|
||||||
|
"Cure all inputs even when the required artifact is available "+
|
||||||
|
"via the mirror service",
|
||||||
).Flag(
|
).Flag(
|
||||||
&cm.idle,
|
&cm.idle,
|
||||||
"sched-idle", command.BoolFlag(false),
|
"sched-idle", command.BoolFlag(false),
|
||||||
@@ -196,6 +251,18 @@ func main() {
|
|||||||
&flagPT,
|
&flagPT,
|
||||||
"parse-time", command.BoolFlag(false),
|
"parse-time", command.BoolFlag(false),
|
||||||
"Print duration of the initial azalea parse",
|
"Print duration of the initial azalea parse",
|
||||||
|
).Flag(
|
||||||
|
&flagDry,
|
||||||
|
"dry", command.BoolFlag(false),
|
||||||
|
"Do not destroy cache entries",
|
||||||
|
).Flag(
|
||||||
|
&flagSourcePath,
|
||||||
|
"source", command.StringFlag(""),
|
||||||
|
"Override hakurei source tree",
|
||||||
|
).Flag(
|
||||||
|
&flagPath,
|
||||||
|
"p", command.StringFlag("$AZALEA_PATH"),
|
||||||
|
"Load additional azalea files",
|
||||||
)
|
)
|
||||||
|
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
@@ -216,7 +283,7 @@ func main() {
|
|||||||
if _, err := io.Copy(h, os.Stdin); err != nil {
|
if _, err := io.Copy(h, os.Stdin); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Println(pkg.Encode(pkg.Checksum(h.Sum(nil))))
|
fmt.Println(pkg.Encode(pkg.Checksum(h.Sum(nil))))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -348,7 +415,10 @@ func main() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
{
|
{
|
||||||
var flagJobs int
|
var (
|
||||||
|
flagJobs int
|
||||||
|
flagNoBlock bool
|
||||||
|
)
|
||||||
c.NewCommand("updates", command.UsageInternal, func([]string) error {
|
c.NewCommand("updates", command.UsageInternal, func([]string) error {
|
||||||
var (
|
var (
|
||||||
errsMu sync.Mutex
|
errsMu sync.Mutex
|
||||||
@@ -367,6 +437,11 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !flagNoBlock && meta.Blocked != "" {
|
||||||
|
msg.Verbosef("%s is blocked: %s", meta.Name, meta.Blocked)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
v, err := meta.GetVersions(ctx)
|
v, err := meta.GetVersions(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errsMu.Lock()
|
errsMu.Lock()
|
||||||
@@ -407,9 +482,23 @@ func main() {
|
|||||||
&flagJobs,
|
&flagJobs,
|
||||||
"j", command.IntFlag(32),
|
"j", command.IntFlag(32),
|
||||||
"Maximum number of simultaneous connections",
|
"Maximum number of simultaneous connections",
|
||||||
|
).Flag(
|
||||||
|
&flagNoBlock,
|
||||||
|
"ignore-block", command.BoolFlag(false),
|
||||||
|
"Inhibit update blocking",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.NewCommand("blocked", command.UsageInternal, func([]string) error {
|
||||||
|
for _, p := range rosa.Native().CollectAll() {
|
||||||
|
meta, _ := rosa.Native().Std().Load(p)
|
||||||
|
if meta.Blocked != "" {
|
||||||
|
fmt.Printf("%s: %s\n", meta.Name, meta.Blocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
"daemon",
|
"daemon",
|
||||||
"Service artifact IR with Rosa OS extensions",
|
"Service artifact IR with Rosa OS extensions",
|
||||||
@@ -423,30 +512,118 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
{
|
c.NewCommand(
|
||||||
var (
|
"keygen",
|
||||||
flagGentoo string
|
"Create keypair for local cache",
|
||||||
flagChecksum string
|
func([]string) error {
|
||||||
|
pub, priv, err := ed25519.GenerateKey(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
flagStage0 bool
|
return errors.Join(writeFileExcl(filepath.Join(
|
||||||
)
|
cm.base,
|
||||||
|
"ed25519.pub",
|
||||||
|
), pub, 0444), writeFileExcl(filepath.Join(
|
||||||
|
cm.base,
|
||||||
|
"ed25519",
|
||||||
|
), priv, 0400))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"serve",
|
||||||
|
"Export local cache as mirror",
|
||||||
|
func(args []string) error {
|
||||||
|
const shutdownTimeout = 15 * time.Second
|
||||||
|
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.New("serve requires 1 argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
var key ed25519.PrivateKey
|
||||||
|
if p, err := os.ReadFile(filepath.Join(cm.base, "ed25519")); err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(p) != ed25519.PrivateKeySize {
|
||||||
|
return errors.New("invalid private key")
|
||||||
|
} else {
|
||||||
|
key = p
|
||||||
|
}
|
||||||
|
|
||||||
|
var h http.Handler
|
||||||
|
if base, err := os.OpenRoot(cm.base); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
var mux http.ServeMux
|
||||||
|
rosa.NewMirror(msg, base.FS(), key).Register(&mux)
|
||||||
|
h = &mux
|
||||||
|
}
|
||||||
|
|
||||||
|
server := http.Server{Addr: args[0], Handler: h}
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
cc, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := server.Shutdown(cc); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
msg.Verbosef("listening on %q", args[0])
|
||||||
|
err := server.ListenAndServe()
|
||||||
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"snapshot",
|
||||||
|
"Populate an empty directory with a static mirror snapshot",
|
||||||
|
func(args []string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.New("snapshot requires 1 argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
var key ed25519.PrivateKey
|
||||||
|
if p, err := os.ReadFile(filepath.Join(cm.base, "ed25519")); err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(p) != ed25519.PrivateKeySize {
|
||||||
|
return errors.New("invalid private key")
|
||||||
|
} else {
|
||||||
|
key = p
|
||||||
|
}
|
||||||
|
|
||||||
|
var m rosa.Mirror
|
||||||
|
if base, err := os.OpenRoot(cm.base); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
m = rosa.NewMirror(msg, base.FS(), key)
|
||||||
|
}
|
||||||
|
|
||||||
|
retry:
|
||||||
|
root, err := os.OpenRoot(args[0])
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if err = os.MkdirAll(args[0], 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() { <-ctx.Done(); _ = root.Close() }()
|
||||||
|
return m.Populate(root)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
"stage3",
|
"stage4",
|
||||||
"Check for toolchain 3-stage non-determinism",
|
"Check for toolchain 3-stage non-determinism",
|
||||||
func(args []string) (err error) {
|
func(args []string) (err error) {
|
||||||
s := rosa.Std
|
|
||||||
if flagGentoo != "" {
|
|
||||||
s -= 3 // magic number to discourage misuse
|
|
||||||
|
|
||||||
var checksum pkg.Checksum
|
|
||||||
if len(flagChecksum) != 0 {
|
|
||||||
if err = pkg.Decode(&checksum, flagChecksum); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rosa.Native().SetGentooStage3(flagGentoo, checksum)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pathname *check.Absolute
|
pathname *check.Absolute
|
||||||
checksum [2]unique.Handle[pkg.Checksum]
|
checksum [2]unique.Handle[pkg.Checksum]
|
||||||
@@ -454,7 +631,7 @@ func main() {
|
|||||||
|
|
||||||
_llvm := rosa.H("llvm")
|
_llvm := rosa.H("llvm")
|
||||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
_, llvm := rosa.Native().New(s - 2).Load(_llvm)
|
_, llvm := rosa.Native().New(rosa.Std - 2).Load(_llvm)
|
||||||
pathname, _, err = cache.Cure(llvm)
|
pathname, _, err = cache.Cure(llvm)
|
||||||
return
|
return
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -463,22 +640,32 @@ func main() {
|
|||||||
log.Println("stage1:", pathname)
|
log.Println("stage1:", pathname)
|
||||||
|
|
||||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
_, llvm := rosa.Native().New(s - 1).Load(_llvm)
|
_, llvm := rosa.Native().New(rosa.Std - 1).Load(_llvm)
|
||||||
pathname, checksum[0], err = cache.Cure(llvm)
|
pathname, _, err = cache.Cure(llvm)
|
||||||
return
|
return
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("stage2:", pathname)
|
log.Println("stage2:", pathname)
|
||||||
|
|
||||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
_, llvm := rosa.Native().New(s).Load(_llvm)
|
_, llvm := rosa.Native().New(rosa.Std).Load(_llvm)
|
||||||
pathname, checksum[1], err = cache.Cure(llvm)
|
pathname, checksum[0], err = cache.Cure(llvm)
|
||||||
return
|
return
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("stage3:", pathname)
|
log.Println("stage3:", pathname)
|
||||||
|
|
||||||
|
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
|
_, llvm := rosa.Native().New(rosa.Stage3).Load(_llvm)
|
||||||
|
pathname, checksum[1], err = cache.Cure(llvm)
|
||||||
|
return
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("stage4:", pathname)
|
||||||
|
|
||||||
if checksum[0] != checksum[1] {
|
if checksum[0] != checksum[1] {
|
||||||
err = &pkg.ChecksumMismatchError{
|
err = &pkg.ChecksumMismatchError{
|
||||||
Got: checksum[0].Value(),
|
Got: checksum[0].Value(),
|
||||||
@@ -486,38 +673,34 @@ func main() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Println(
|
log.Println(
|
||||||
"stage2 is identical to stage3",
|
"stage3 is identical to stage4",
|
||||||
"("+pkg.Encode(checksum[0].Value())+")",
|
"("+pkg.Encode(checksum[0].Value())+")",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagStage0 {
|
|
||||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
|
||||||
pathname, _, err = cache.Cure(rosa.Native().Std().NewStage0())
|
|
||||||
return
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Println(pathname)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
).Flag(
|
|
||||||
&flagGentoo,
|
|
||||||
"gentoo", command.StringFlag(""),
|
|
||||||
"Bootstrap from a Gentoo stage3 tarball",
|
|
||||||
).Flag(
|
|
||||||
&flagChecksum,
|
|
||||||
"checksum", command.StringFlag(""),
|
|
||||||
"Checksum of Gentoo stage3 tarball",
|
|
||||||
).Flag(
|
|
||||||
&flagStage0,
|
|
||||||
"stage0", command.BoolFlag(false),
|
|
||||||
"Create bootstrap stage0 tarball",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"all",
|
||||||
|
command.UsageInternal,
|
||||||
|
func([]string) error {
|
||||||
|
all := rosa.Native().CollectAll()
|
||||||
|
a := make(pkg.Collect, len(all))
|
||||||
|
for i, h := range all {
|
||||||
|
_, a[i] = rosa.Native().Std().Load(h)
|
||||||
|
}
|
||||||
|
return cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
|
_, _, err = cache.Cure(&a)
|
||||||
|
if pkg.IsCollected(err) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
flagDump string
|
flagDump string
|
||||||
@@ -527,9 +710,13 @@ func main() {
|
|||||||
flagNoReply bool
|
flagNoReply bool
|
||||||
flagFaults bool
|
flagFaults bool
|
||||||
flagPop bool
|
flagPop bool
|
||||||
|
flagRebuild bool
|
||||||
|
|
||||||
flagBoot bool
|
flagBoot bool
|
||||||
flagStd bool
|
flagStd bool
|
||||||
|
|
||||||
|
flagGentoo string
|
||||||
|
flagChecksum string
|
||||||
)
|
)
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
"cure",
|
"cure",
|
||||||
@@ -546,6 +733,18 @@ func main() {
|
|||||||
t -= 1
|
t -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flagGentoo != "" {
|
||||||
|
var checksum pkg.Checksum
|
||||||
|
if len(flagChecksum) != 0 {
|
||||||
|
if err := pkg.Decode(&checksum, flagChecksum); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rosa.Native().SetGentooStage3(flagGentoo, checksum)
|
||||||
|
|
||||||
|
t -= 3 // magic number to discourage misuse
|
||||||
|
}
|
||||||
|
|
||||||
_, a := rosa.Native().New(t).Load(rosa.ArtifactH(unique.Make(args[0])))
|
_, a := rosa.Native().New(t).Load(rosa.ArtifactH(unique.Make(args[0])))
|
||||||
if a == nil {
|
if a == nil {
|
||||||
return fmt.Errorf("unknown artifact %q", args[0])
|
return fmt.Errorf("unknown artifact %q", args[0])
|
||||||
@@ -555,13 +754,17 @@ func main() {
|
|||||||
default:
|
default:
|
||||||
var pathname *check.Absolute
|
var pathname *check.Absolute
|
||||||
err := cm.Do(func(cache *pkg.Cache) (err error) {
|
err := cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
pathname, _, err = cache.Cure(a)
|
if flagRebuild {
|
||||||
|
pathname, _, err = cache.CureNew(a)
|
||||||
|
} else {
|
||||||
|
pathname, _, err = cache.Cure(a)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Println(pathname)
|
fmt.Println(pathname)
|
||||||
|
|
||||||
if flagExport != "" {
|
if flagExport != "" {
|
||||||
msg.Verbosef("exporting %s to %s...", args[0], flagExport)
|
msg.Verbosef("exporting %s to %s...", args[0], flagExport)
|
||||||
@@ -573,7 +776,7 @@ func main() {
|
|||||||
0400,
|
0400,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = pkg.Flatten(
|
} else if err = pkg.Write(
|
||||||
os.DirFS(pathname.String()),
|
os.DirFS(pathname.String()),
|
||||||
".",
|
".",
|
||||||
f,
|
f,
|
||||||
@@ -609,8 +812,8 @@ func main() {
|
|||||||
return cache.EnterExec(
|
return cache.EnterExec(
|
||||||
ctx,
|
ctx,
|
||||||
a,
|
a,
|
||||||
true, os.Stdin, os.Stdout, os.Stderr,
|
"", true, os.Stdin, os.Stdout, os.Stderr,
|
||||||
rosa.AbsSystem.Append("bin", "mksh"),
|
rosa.AbsSystem.Append("bin", "sh"),
|
||||||
"sh",
|
"sh",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -622,7 +825,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
pathname, err := cureRemote(ctx, &addr, a, flags)
|
pathname, err := cureRemote(ctx, &addr, a, flags)
|
||||||
if !flagNoReply && err == nil {
|
if !flagNoReply && err == nil {
|
||||||
log.Println(pathname)
|
fmt.Println(pathname)
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
@@ -716,11 +919,100 @@ func main() {
|
|||||||
&flagPop,
|
&flagPop,
|
||||||
"pop", command.BoolFlag(false),
|
"pop", command.BoolFlag(false),
|
||||||
"Display and destroy the most recent fault entry",
|
"Display and destroy the most recent fault entry",
|
||||||
|
).Flag(
|
||||||
|
&flagRebuild,
|
||||||
|
"rebuild", command.BoolFlag(false),
|
||||||
|
"Always enter the artifact implementation",
|
||||||
|
).Flag(
|
||||||
|
&flagGentoo,
|
||||||
|
"gentoo", command.StringFlag(""),
|
||||||
|
"Bootstrap from a Gentoo stage3 tarball",
|
||||||
|
).Flag(
|
||||||
|
&flagChecksum,
|
||||||
|
"checksum", command.StringFlag(""),
|
||||||
|
"Checksum of Gentoo stage3 tarball",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
"clear",
|
"validate",
|
||||||
|
"Repeatedly cure named artifact to validate determinism",
|
||||||
|
func(args []string) error {
|
||||||
|
stage := rosa.Std
|
||||||
|
if len(args) == 3 {
|
||||||
|
switch args[0] {
|
||||||
|
case "boot":
|
||||||
|
stage -= 2
|
||||||
|
case "std":
|
||||||
|
stage -= 1
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid stage %q", args[0])
|
||||||
|
}
|
||||||
|
args = args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) != 2 {
|
||||||
|
return errors.New("validate requires 2 arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := strconv.Atoi(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid passes %q", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < 2 {
|
||||||
|
return errors.New("validate must rebuild at least twice")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, a := rosa.Native().New(stage).Load(rosa.ArtifactH(unique.Make(args[1])))
|
||||||
|
if a == nil {
|
||||||
|
return fmt.Errorf("unknown artifact %q", args[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.IsExclusive() && cm.attr.Jobs < 1 {
|
||||||
|
cm.attr.Jobs = runtime.NumCPU()/n + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]unique.Handle[pkg.Checksum], n)
|
||||||
|
if err = cm.Do(func(cache *pkg.Cache) error {
|
||||||
|
errs := make([]error, n)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := range n {
|
||||||
|
wg.Go(func() {
|
||||||
|
_, res[i], errs[i] = cache.CureNew(a)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range n - 1 {
|
||||||
|
if res[i] == res[i+1] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
buf.WriteString("nondeterminism:")
|
||||||
|
for _, o := range res {
|
||||||
|
buf.WriteString("\n\t")
|
||||||
|
buf.WriteString(filepath.Join(
|
||||||
|
cm.base,
|
||||||
|
"checksum",
|
||||||
|
pkg.Encode(o.Value()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return errors.New(buf.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
cleanC := c.New("clean", "Remove unused entries from the cache")
|
||||||
|
cleanC.NewCommand(
|
||||||
|
"fault",
|
||||||
"Remove all fault entries from the cache",
|
"Remove all fault entries from the cache",
|
||||||
func([]string) error {
|
func([]string) error {
|
||||||
return cm.Do(func(*pkg.Cache) error {
|
return cm.Do(func(*pkg.Cache) error {
|
||||||
@@ -741,6 +1033,58 @@ func main() {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
cleanC.NewCommand(
|
||||||
|
"checksum",
|
||||||
|
"Remove unreachable checksum entries",
|
||||||
|
func([]string) error {
|
||||||
|
return cm.Do(func(cache *pkg.Cache) error {
|
||||||
|
_, checksums, err := cache.Clean(flagDry, false)
|
||||||
|
log.Printf("destroyed %d entries", len(checksums))
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var flagDeep bool
|
||||||
|
cleanC.NewCommand(
|
||||||
|
"all",
|
||||||
|
"Remove identifiers not reachable by loaded packages",
|
||||||
|
func([]string) error {
|
||||||
|
return cm.Do(func(cache *pkg.Cache) error {
|
||||||
|
t := rosa.Native().Clone().Std()
|
||||||
|
handles := t.CollectAll()
|
||||||
|
|
||||||
|
flags := t.Flags()
|
||||||
|
a := t.Append(nil, handles...)
|
||||||
|
for arch := range rosa.Arches(nil) {
|
||||||
|
if arch == runtime.GOARCH {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.DropCaches(arch, rosa.OptLLVMNoLTO|rosa.OptSkipCheck)
|
||||||
|
a = t.Append(a, handles...)
|
||||||
|
t.DropCaches(arch, flags)
|
||||||
|
a = t.Append(a, handles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, checksums, err := cache.Clean(
|
||||||
|
flagDry,
|
||||||
|
!flagDeep,
|
||||||
|
a...,
|
||||||
|
)
|
||||||
|
log.Printf(
|
||||||
|
"destroyed %d identifier and %d checksum entries",
|
||||||
|
len(ids), len(checksums),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
},
|
||||||
|
).Flag(
|
||||||
|
&flagDeep,
|
||||||
|
"deep", command.BoolFlag(false),
|
||||||
|
"Include transitive inputs",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
"abort",
|
"abort",
|
||||||
@@ -759,6 +1103,17 @@ func main() {
|
|||||||
"shell",
|
"shell",
|
||||||
"Interactive shell in the specified Rosa OS environment",
|
"Interactive shell in the specified Rosa OS environment",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
|
resolvconf := "nameserver 1.1.1.1\nnameserver 1.0.0.1\n"
|
||||||
|
if p, err := os.ReadFile(fhs.AbsEtc.Append(
|
||||||
|
"resolv.conf",
|
||||||
|
).String()); err != nil {
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolvconf = unsafe.String(unsafe.SliceData(p), len(p))
|
||||||
|
}
|
||||||
|
|
||||||
handles := make([]rosa.ArtifactH, len(args), len(args)+3)
|
handles := make([]rosa.ArtifactH, len(args), len(args)+3)
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
handles[i] = rosa.ArtifactH(unique.Make(arg))
|
handles[i] = rosa.ArtifactH(unique.Make(arg))
|
||||||
@@ -778,112 +1133,42 @@ func main() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
root := make(pkg.Collect, 0, 6+len(args))
|
root := make(pkg.Collect, 0, 6+len(args))
|
||||||
|
root = append(root, rosa.NewEtc(false))
|
||||||
root = rosa.Native().Std().Append(root, handles...)
|
root = rosa.Native().Std().Append(root, handles...)
|
||||||
|
|
||||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
return cm.Do(func(cache *pkg.Cache) error {
|
||||||
_, _, err := cache.Cure(&root)
|
return cache.EnterExec(
|
||||||
return err
|
ctx,
|
||||||
}); err == nil {
|
pkg.NewExec(
|
||||||
return errors.New("unreachable")
|
"",
|
||||||
} else if !pkg.IsCollected(err) {
|
rosa.Native().Arch(),
|
||||||
return err
|
new(pkg.Checksum),
|
||||||
}
|
1,
|
||||||
|
flagNet,
|
||||||
type cureRes struct {
|
false,
|
||||||
pathname *check.Absolute
|
fhs.AbsRoot,
|
||||||
checksum unique.Handle[pkg.Checksum]
|
[]string{
|
||||||
}
|
"SHELL=/system/bin/mksh",
|
||||||
cured := make(map[pkg.Artifact]cureRes)
|
"PATH=/system/bin",
|
||||||
for _, a := range root {
|
"HOME=/",
|
||||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
},
|
||||||
pathname, checksum, err := cache.Cure(a)
|
fhs.AbsProc.Append("nonexistent"),
|
||||||
if err == nil {
|
nil,
|
||||||
cured[a] = cureRes{pathname, checksum}
|
pkg.Path(fhs.AbsRoot, true, root...),
|
||||||
}
|
pkg.Path(
|
||||||
return err
|
fhs.AbsEtc.Append("resolv.conf"), false,
|
||||||
}); err != nil {
|
pkg.NewFile(
|
||||||
return err
|
"resolv.conf",
|
||||||
}
|
unsafe.Slice(unsafe.StringData(resolvconf), len(resolvconf)),
|
||||||
}
|
),
|
||||||
|
),
|
||||||
// explicitly open for direct error-free use from this point
|
),
|
||||||
if cm.c == nil {
|
"localhost",
|
||||||
if err := cm.open(); err != nil {
|
flagSession, os.Stdin, os.Stdout, os.Stderr,
|
||||||
return err
|
rosa.AbsSystem.Append("bin", "mksh"),
|
||||||
}
|
"sh",
|
||||||
}
|
)
|
||||||
|
|
||||||
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
|
|
||||||
*check.Absolute,
|
|
||||||
unique.Handle[pkg.Checksum],
|
|
||||||
) {
|
|
||||||
res := cured[a]
|
|
||||||
return res.pathname, res.checksum
|
|
||||||
}, func(i int, d pkg.Artifact) {
|
|
||||||
r := pkg.Encode(cm.c.Ident(d).Value())
|
|
||||||
if s, ok := d.(fmt.Stringer); ok {
|
|
||||||
if name := s.String(); name != "" {
|
|
||||||
r += "-" + name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg.Verbosef("promoted layer %d as %s", i, r)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
z := container.New(ctx, msg)
|
|
||||||
z.WaitDelay = 3 * time.Second
|
|
||||||
z.SeccompPresets = pkg.SeccompPresets
|
|
||||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
|
||||||
z.ParentPerm = 0700
|
|
||||||
z.HostNet = flagNet
|
|
||||||
z.RetainSession = flagSession
|
|
||||||
z.Hostname = "localhost"
|
|
||||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
|
||||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
||||||
z.Quiet = !cm.verboseInit
|
|
||||||
if s, ok := os.LookupEnv("TERM"); ok {
|
|
||||||
z.Env = append(z.Env, "TERM="+s)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tempdir *check.Absolute
|
|
||||||
if s, err := filepath.Abs(os.TempDir()); err != nil {
|
|
||||||
return err
|
|
||||||
} else if tempdir, err = check.NewAbs(s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
z.Dir = fhs.AbsRoot
|
|
||||||
z.Env = []string{
|
|
||||||
"SHELL=/system/bin/mksh",
|
|
||||||
"PATH=/system/bin",
|
|
||||||
"HOME=/",
|
|
||||||
}
|
|
||||||
z.Path = rosa.AbsSystem.Append("bin", "mksh")
|
|
||||||
z.Args = []string{"mksh"}
|
|
||||||
z.
|
|
||||||
OverlayEphemeral(fhs.AbsRoot, layers...).
|
|
||||||
Place(
|
|
||||||
fhs.AbsEtc.Append("hosts"),
|
|
||||||
[]byte("127.0.0.1 localhost\n"),
|
|
||||||
).
|
|
||||||
Place(
|
|
||||||
fhs.AbsEtc.Append("passwd"),
|
|
||||||
[]byte("media_rw:x:1023:1023::/:/system/bin/sh\n"+
|
|
||||||
"nobody:x:65534:65534::/proc/nonexistent:/system/bin/false\n"),
|
|
||||||
).
|
|
||||||
Place(
|
|
||||||
fhs.AbsEtc.Append("group"),
|
|
||||||
[]byte("media_rw:x:1023:\nnobody:x:65534:\n"),
|
|
||||||
).
|
|
||||||
Bind(tempdir, fhs.AbsTmp, std.BindWritable).
|
|
||||||
Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
|
|
||||||
|
|
||||||
if err := z.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := z.Serve(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return z.Wait()
|
|
||||||
},
|
},
|
||||||
).Flag(
|
).Flag(
|
||||||
&flagNet,
|
&flagNet,
|
||||||
@@ -898,7 +1183,6 @@ func main() {
|
|||||||
"with-toolchain", command.BoolFlag(false),
|
"with-toolchain", command.BoolFlag(false),
|
||||||
"Include the stage2 LLVM toolchain",
|
"Include the stage2 LLVM toolchain",
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Command(
|
c.Command(
|
||||||
|
|||||||
+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
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -13,6 +13,7 @@ func New(output io.Writer, logf LogFunc, name string, early HandlerFunc) Command
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newNode initialises a subcommand tree and returns its address.
|
||||||
func newNode(output io.Writer, logf LogFunc, name, usage string) *node {
|
func newNode(output io.Writer, logf LogFunc, name, usage string) *node {
|
||||||
n := &node{
|
n := &node{
|
||||||
name: name, usage: usage,
|
name: name, usage: usage,
|
||||||
@@ -22,8 +23,8 @@ func newNode(output io.Writer, logf LogFunc, name, usage string) *node {
|
|||||||
n.set.SetOutput(output)
|
n.set.SetOutput(output)
|
||||||
n.set.Usage = func() {
|
n.set.Usage = func() {
|
||||||
_ = n.writeHelp()
|
_ = n.writeHelp()
|
||||||
if n.suffix.Len() > 0 {
|
if len(n.suffix) > 0 {
|
||||||
_, _ = fmt.Fprintln(output, "Flags:")
|
_, _ = fmt.Fprintln(output, "flags:")
|
||||||
n.set.PrintDefaults()
|
n.set.PrintDefaults()
|
||||||
_, _ = fmt.Fprintln(output)
|
_, _ = fmt.Fprintln(output)
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-4
@@ -18,8 +18,14 @@ func TestBuild(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("direct zero length", func(t *testing.T) {
|
t.Run("direct zero length", func(t *testing.T) {
|
||||||
wantPanic := "invalid subcommand"
|
wantPanic := "invalid subcommand"
|
||||||
t.Run("zero length name", func(t *testing.T) { defer checkRecover(t, "Command", wantPanic); c.Command("", "usage", stubHandler) })
|
t.Run("zero length name", func(t *testing.T) {
|
||||||
t.Run("zero length usage", func(t *testing.T) { defer checkRecover(t, "Command", wantPanic); c.Command("name", "", stubHandler) })
|
defer checkRecover(t, "Command", wantPanic)
|
||||||
|
c.Command("", "usage", stubHandler)
|
||||||
|
})
|
||||||
|
t.Run("zero length usage", func(t *testing.T) {
|
||||||
|
defer checkRecover(t, "Command", wantPanic)
|
||||||
|
c.Command("name", "", stubHandler)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("direct adopt unique names", func(t *testing.T) {
|
t.Run("direct adopt unique names", func(t *testing.T) {
|
||||||
@@ -34,8 +40,14 @@ func TestBuild(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("zero length", func(t *testing.T) {
|
t.Run("zero length", func(t *testing.T) {
|
||||||
wantPanic := "invalid subcommand tree"
|
wantPanic := "invalid subcommand tree"
|
||||||
t.Run("zero length name", func(t *testing.T) { defer checkRecover(t, "New", wantPanic); c.New("", "usage") })
|
t.Run("zero length name", func(t *testing.T) {
|
||||||
t.Run("zero length usage", func(t *testing.T) { defer checkRecover(t, "New", wantPanic); c.New("name", "") })
|
defer checkRecover(t, "New", wantPanic)
|
||||||
|
c.New("", "usage")
|
||||||
|
})
|
||||||
|
t.Run("zero length usage", func(t *testing.T) {
|
||||||
|
defer checkRecover(t, "New", wantPanic)
|
||||||
|
c.New("name", "")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("direct adopt unique names", func(t *testing.T) {
|
t.Run("direct adopt unique names", func(t *testing.T) {
|
||||||
|
|||||||
+26
-6
@@ -6,36 +6,43 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsageInternal causes the command to be hidden from help text when set as the usage string.
|
// UsageInternal is a special usage string that hides the command from the
|
||||||
const UsageInternal = "internal"
|
// generated help message.
|
||||||
|
const UsageInternal = "\x00"
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// HandlerFunc is called when matching a directly handled subcommand tree.
|
// HandlerFunc is called when matching a directly handled subcommand tree.
|
||||||
HandlerFunc = func(args []string) error
|
HandlerFunc = func(args []string) error
|
||||||
|
|
||||||
// LogFunc is the function signature of a printf function.
|
// LogFunc is the function signature of a printf function. The zero value
|
||||||
|
// implies [log.Printf].
|
||||||
LogFunc = func(format string, a ...any)
|
LogFunc = func(format string, a ...any)
|
||||||
|
|
||||||
// FlagDefiner is a deferred flag definer value, usually encapsulating the default value.
|
// FlagDefiner is a deferred flag definer value, usually encapsulating the
|
||||||
|
// default value.
|
||||||
FlagDefiner interface {
|
FlagDefiner interface {
|
||||||
// Define defines the flag in set.
|
// Define defines the flag in set.
|
||||||
Define(b *strings.Builder, set *flag.FlagSet, p any, name, usage string)
|
Define(b *strings.Builder, set *flag.FlagSet, p any, name, usage string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A Flag is satisfied by command objects capable of receiving flags.
|
||||||
Flag[T any] interface {
|
Flag[T any] interface {
|
||||||
// Flag defines a generic flag type in Node's flag set.
|
// Flag defines a generic flag type in Node's flag set.
|
||||||
Flag(p any, name string, value FlagDefiner, usage string) T
|
Flag(p any, name string, value FlagDefiner, usage string) T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A Command is the root of a command tree.
|
||||||
Command interface {
|
Command interface {
|
||||||
Parse(arguments []string) error
|
Parse(arguments []string) error
|
||||||
|
|
||||||
// MustParse determines exit outcomes for Parse errors
|
// MustParse determines exit outcomes for Parse errors and calls
|
||||||
// and calls handleError if [HandlerFunc] returns a non-nil error.
|
// handleError if [HandlerFunc] returns a non-nil error.
|
||||||
MustParse(arguments []string, handleError func(error))
|
MustParse(arguments []string, handleError func(error))
|
||||||
|
|
||||||
baseNode[Command]
|
baseNode[Command]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A Node is a subcommand under a [Command].
|
||||||
Node baseNode[Node]
|
Node baseNode[Node]
|
||||||
|
|
||||||
baseNode[T any] interface {
|
baseNode[T any] interface {
|
||||||
@@ -53,3 +60,16 @@ type (
|
|||||||
Flag[T]
|
Flag[T]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// rootNode satisfies baseNode for [Command].
|
||||||
|
type rootNode struct{ *node }
|
||||||
|
|
||||||
|
func (r rootNode) Command(name, usage string, f HandlerFunc) Command {
|
||||||
|
r.node.Command(name, usage, f)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r rootNode) Flag(p any, name string, value FlagDefiner, usage string) Command {
|
||||||
|
r.node.Flag(p, name, value, usage)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|||||||
+7
-1
@@ -16,7 +16,13 @@ func (e FlagError) Is(target error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) Flag(p any, name string, value FlagDefiner, usage string) Node {
|
func (n *node) Flag(p any, name string, value FlagDefiner, usage string) Node {
|
||||||
value.Define(&n.suffix, n.set, p, name, usage)
|
var buf strings.Builder
|
||||||
|
value.Define(&buf, n.set, p, name, usage)
|
||||||
|
s := buf.String()
|
||||||
|
if len(s) > 0 && s[0] == ' ' {
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
n.suffix = append(n.suffix, s)
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-3
@@ -1,6 +1,7 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -13,14 +14,31 @@ var ErrHelp = errors.New("help requested")
|
|||||||
func (n *node) PrintHelp() { _ = n.writeHelp() }
|
func (n *node) PrintHelp() { _ = n.writeHelp() }
|
||||||
|
|
||||||
func (n *node) writeHelp() error {
|
func (n *node) writeHelp() error {
|
||||||
|
prefix := strings.Join(append(n.prefix, n.name), " ")
|
||||||
|
var buf strings.Builder
|
||||||
|
offset := 7 + len(prefix)
|
||||||
|
line := offset + 14
|
||||||
|
w := bytes.Repeat([]byte{' '}, offset+1)
|
||||||
|
w[0] = '\n'
|
||||||
|
|
||||||
|
for _, flag := range n.suffix {
|
||||||
|
line += len(flag) + 1
|
||||||
|
if line >= 80 {
|
||||||
|
line = offset + len(flag) + 1
|
||||||
|
buf.Write(w)
|
||||||
|
}
|
||||||
|
buf.WriteByte(' ')
|
||||||
|
buf.WriteString(flag)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(n.out,
|
if _, err := fmt.Fprintf(n.out,
|
||||||
"\nUsage:\t%s [-h | --help]%s COMMAND [OPTIONS]\n",
|
"usage: %s [-h | --help]%s <command> [<args>]\n",
|
||||||
strings.Join(append(n.prefix, n.name), " "), &n.suffix,
|
prefix, &buf,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if n.child != nil {
|
if n.child != nil {
|
||||||
if _, err := fmt.Fprint(n.out, "\nCommands:\n"); err != nil {
|
if _, err := fmt.Fprint(n.out, "\ncommands:\n"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -3,9 +3,9 @@ package command
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A node represents a command.
|
||||||
type node struct {
|
type node struct {
|
||||||
child, next *node
|
child, next *node
|
||||||
name, usage string
|
name, usage string
|
||||||
@@ -13,13 +13,16 @@ type node struct {
|
|||||||
out io.Writer
|
out io.Writer
|
||||||
logf LogFunc
|
logf LogFunc
|
||||||
|
|
||||||
|
// Names of commands preceding node.
|
||||||
prefix []string
|
prefix []string
|
||||||
suffix strings.Builder
|
// Short user-facing representations of flags received by node.
|
||||||
|
suffix []string
|
||||||
|
|
||||||
f HandlerFunc
|
f HandlerFunc
|
||||||
set *flag.FlagSet
|
set *flag.FlagSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adopt adds v as the last child of n.
|
||||||
func (n *node) adopt(v *node) bool {
|
func (n *node) adopt(v *node) bool {
|
||||||
if n.child != nil {
|
if n.child != nil {
|
||||||
return n.child.append(v)
|
return n.child.append(v)
|
||||||
@@ -28,6 +31,7 @@ func (n *node) adopt(v *node) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// append adds v as the last sibling of n.
|
||||||
func (n *node) append(v *node) bool {
|
func (n *node) append(v *node) bool {
|
||||||
if n.name == v.name {
|
if n.name == v.name {
|
||||||
return false
|
return false
|
||||||
|
|||||||
+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)
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-22
@@ -70,7 +70,7 @@ func TestParse(t *testing.T) {
|
|||||||
"d=0 out of order string flag",
|
"d=0 out of order string flag",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"string", "--string", "64d3b4b7b21788585845060e2199a78f"},
|
[]string{"string", "--string", "64d3b4b7b21788585845060e2199a78f"},
|
||||||
"flag provided but not defined: -string\n\nUsage:\ttest string [-h | --help] COMMAND [OPTIONS]\n\n", "",
|
"flag provided but not defined: -string\nusage: test string [-h | --help] <command> [<args>]\n\n", "",
|
||||||
errors.New("flag provided but not defined: -string"),
|
errors.New("flag provided but not defined: -string"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -120,7 +120,7 @@ func TestParse(t *testing.T) {
|
|||||||
"d=1 empty sub help",
|
"d=1 empty sub help",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"empty", "-h"},
|
[]string{"empty", "-h"},
|
||||||
"\nUsage:\ttest empty [-h | --help] COMMAND [OPTIONS]\n\n", "", flag.ErrHelp,
|
"usage: test empty [-h | --help] <command> [<args>]\n\n", "", flag.ErrHelp,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"d=1 no match",
|
"d=1 no match",
|
||||||
@@ -151,10 +151,10 @@ func TestParse(t *testing.T) {
|
|||||||
"d=0 help",
|
"d=0 help",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{},
|
[]string{},
|
||||||
`
|
`usage: test [-h | --help] [-v] [--fail] [--string <value>] [--int <int>]
|
||||||
Usage: test [-h | --help] [-v] [--fail] [--string <value>] [--int <int>] [--repeat <value>] COMMAND [OPTIONS]
|
[--repeat <value>] <command> [<args>]
|
||||||
|
|
||||||
Commands:
|
commands:
|
||||||
error return an error
|
error return an error
|
||||||
print wraps Fprint
|
print wraps Fprint
|
||||||
string print string passed by flag
|
string print string passed by flag
|
||||||
@@ -171,10 +171,10 @@ Commands:
|
|||||||
"d=0 help flag",
|
"d=0 help flag",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"-h"},
|
[]string{"-h"},
|
||||||
`
|
`usage: test [-h | --help] [-v] [--fail] [--string <value>] [--int <int>]
|
||||||
Usage: test [-h | --help] [-v] [--fail] [--string <value>] [--int <int>] [--repeat <value>] COMMAND [OPTIONS]
|
[--repeat <value>] <command> [<args>]
|
||||||
|
|
||||||
Commands:
|
commands:
|
||||||
error return an error
|
error return an error
|
||||||
print wraps Fprint
|
print wraps Fprint
|
||||||
string print string passed by flag
|
string print string passed by flag
|
||||||
@@ -185,7 +185,7 @@ Commands:
|
|||||||
succeed this command succeeds
|
succeed this command succeeds
|
||||||
deep top level of command tree with various levels
|
deep top level of command tree with various levels
|
||||||
|
|
||||||
Flags:
|
flags:
|
||||||
-fail
|
-fail
|
||||||
fail early
|
fail early
|
||||||
-int int
|
-int int
|
||||||
@@ -203,10 +203,9 @@ Flags:
|
|||||||
"d=1 help",
|
"d=1 help",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"join"},
|
[]string{"join"},
|
||||||
`
|
`usage: test join [-h | --help] <command> [<args>]
|
||||||
Usage: test join [-h | --help] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
Commands:
|
commands:
|
||||||
out write result to wout
|
out write result to wout
|
||||||
log log result to wlog
|
log log result to wlog
|
||||||
|
|
||||||
@@ -216,10 +215,9 @@ Commands:
|
|||||||
"d=1 help flag",
|
"d=1 help flag",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"join", "-h"},
|
[]string{"join", "-h"},
|
||||||
`
|
`usage: test join [-h | --help] <command> [<args>]
|
||||||
Usage: test join [-h | --help] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
Commands:
|
commands:
|
||||||
out write result to wout
|
out write result to wout
|
||||||
log log result to wlog
|
log log result to wlog
|
||||||
|
|
||||||
@@ -230,10 +228,9 @@ Commands:
|
|||||||
"d=2 help",
|
"d=2 help",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"deep", "d=2"},
|
[]string{"deep", "d=2"},
|
||||||
`
|
`usage: test deep d=2 [-h | --help] <command> [<args>]
|
||||||
Usage: test deep d=2 [-h | --help] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
Commands:
|
commands:
|
||||||
d=3 relative third level
|
d=3 relative third level
|
||||||
|
|
||||||
`, "", command.ErrHelp,
|
`, "", command.ErrHelp,
|
||||||
@@ -242,10 +239,9 @@ Commands:
|
|||||||
"d=2 help flag",
|
"d=2 help flag",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"deep", "d=2", "-h"},
|
[]string{"deep", "d=2", "-h"},
|
||||||
`
|
`usage: test deep d=2 [-h | --help] <command> [<args>]
|
||||||
Usage: test deep d=2 [-h | --help] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
Commands:
|
commands:
|
||||||
d=3 relative third level
|
d=3 relative third level
|
||||||
|
|
||||||
`, "", flag.ErrHelp,
|
`, "", flag.ErrHelp,
|
||||||
@@ -261,7 +257,7 @@ Commands:
|
|||||||
t.Errorf("Parse: error = %v; wantErr %v", err, tc.wantErr)
|
t.Errorf("Parse: error = %v; wantErr %v", err, tc.wantErr)
|
||||||
}
|
}
|
||||||
if got := wout.String(); got != tc.want {
|
if got := wout.String(); got != tc.want {
|
||||||
t.Errorf("Parse: %s want %s", got, tc.want)
|
t.Errorf("Parse:\n%s\nwant\n%s", got, tc.want)
|
||||||
}
|
}
|
||||||
if gotLog := wlog.String(); gotLog != tc.wantLog {
|
if gotLog := wlog.String(); gotLog != tc.wantLog {
|
||||||
t.Errorf("Parse: log = %s wantLog %s", gotLog, tc.wantLog)
|
t.Errorf("Parse: log = %s wantLog %s", gotLog, tc.wantLog)
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package command
|
|
||||||
|
|
||||||
// the top level node wants [Command] returned for its builder methods
|
|
||||||
type rootNode struct{ *node }
|
|
||||||
|
|
||||||
func (r rootNode) Command(name, usage string, f HandlerFunc) Command {
|
|
||||||
r.node.Command(name, usage, f)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r rootNode) Flag(p any, name string, value FlagDefiner, usage string) Command {
|
|
||||||
r.node.Flag(p, name, value, usage)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
@@ -154,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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+6
-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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
+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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package hst
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -64,10 +65,14 @@ const (
|
|||||||
// Some programs fail to connect to dbus session running as a different uid,
|
// Some programs fail to connect to dbus session running as a different uid,
|
||||||
// this option works around it by mapping priv-side caller uid in container.
|
// this option works around it by mapping priv-side caller uid in container.
|
||||||
FMapRealUID
|
FMapRealUID
|
||||||
|
// FNoPlace disables placement of /etc/passwd and /etc/group.
|
||||||
|
FNoPlace
|
||||||
|
|
||||||
// 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
|
||||||
@@ -98,8 +103,12 @@ func (flags Flags) String() string {
|
|||||||
return "tty"
|
return "tty"
|
||||||
case FMapRealUID:
|
case FMapRealUID:
|
||||||
return "mapuid"
|
return "mapuid"
|
||||||
|
case FNoPlace:
|
||||||
|
return "noplace"
|
||||||
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 +170,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.
|
||||||
@@ -188,9 +201,13 @@ type containerConfigJSON = struct {
|
|||||||
|
|
||||||
// Corresponds to [FMapRealUID].
|
// Corresponds to [FMapRealUID].
|
||||||
MapRealUID bool `json:"map_real_uid"`
|
MapRealUID bool `json:"map_real_uid"`
|
||||||
|
// Corresponds to [FNoPlace].
|
||||||
|
NoPlace bool `json:"noplace,omitempty"`
|
||||||
|
|
||||||
// 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"`
|
||||||
@@ -213,7 +230,9 @@ func (c *ContainerConfig) MarshalJSON() ([]byte, error) {
|
|||||||
Tty: c.Flags&FTty != 0,
|
Tty: c.Flags&FTty != 0,
|
||||||
Multiarch: c.Flags&FMultiarch != 0,
|
Multiarch: c.Flags&FMultiarch != 0,
|
||||||
MapRealUID: c.Flags&FMapRealUID != 0,
|
MapRealUID: c.Flags&FMapRealUID != 0,
|
||||||
|
NoPlace: c.Flags&FNoPlace != 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,
|
||||||
})
|
})
|
||||||
@@ -254,9 +273,15 @@ func (c *ContainerConfig) UnmarshalJSON(data []byte) error {
|
|||||||
if v.MapRealUID {
|
if v.MapRealUID {
|
||||||
c.Flags |= FMapRealUID
|
c.Flags |= FMapRealUID
|
||||||
}
|
}
|
||||||
|
if v.NoPlace {
|
||||||
|
c.Flags |= FNoPlace
|
||||||
|
}
|
||||||
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, noplace, 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, noplace, 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,"noplace":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 {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
[]*check.Absolute{o.Upper},
|
||||||
|
o.Lower,
|
||||||
|
)...)
|
||||||
|
}
|
||||||
} 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/upper", "/tmp/.src0", "/tmp/.src1"),
|
||||||
|
Upper: fhs.AbsRoot,
|
||||||
|
}}, m("/"), ms("/tmp/upper", "/tmp/.src0", "/tmp/.src1"),
|
||||||
|
"e*/:/tmp/upper:/tmp/.src0:/tmp/.src1"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,7 +244,9 @@ func TestTemplate(t *testing.T) {
|
|||||||
"tty": true,
|
"tty": true,
|
||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
|
"noplace": true,
|
||||||
"device": true,
|
"device": true,
|
||||||
|
"cover_run": true,
|
||||||
"share_runtime": true,
|
"share_runtime": true,
|
||||||
"share_tmpdir": true
|
"share_tmpdir": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ib := bytes.IndexByte([]byte("-_/.\\*"), b); ib != -1 { // - // _/.\*
|
if found := bytes.Contains([]byte("-_/.\\*"), []byte{b}); found { // - // _/.\*
|
||||||
goto opt
|
goto opt
|
||||||
} else if b >= '0' && b <= '9' { // 0-9
|
} else if b >= '0' && b <= '9' { // 0-9
|
||||||
goto opt
|
goto opt
|
||||||
@@ -101,7 +101,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
|
if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
|
||||||
if errors.As(err, new(hex.InvalidByteError)) {
|
if _, ok := errors.AsType[hex.InvalidByteError](err); ok {
|
||||||
errno = ErrBadValHexByte
|
errno = ErrBadValHexByte
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+24
-4
@@ -1,21 +1,29 @@
|
|||||||
// Package env provides the [Paths] struct for efficiently building paths from the environment.
|
// Package env provides the [Paths] struct for efficiently building paths from
|
||||||
|
// the environment.
|
||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const VarRunNscd = fhs.Var + "run/nscd"
|
||||||
|
|
||||||
// Paths holds paths copied from the environment and is used to create [hst.Paths].
|
// Paths holds paths copied from the environment and is used to create [hst.Paths].
|
||||||
type Paths struct {
|
type Paths struct {
|
||||||
// TempDir is returned by [os.TempDir].
|
// TempDir is returned by [os.TempDir].
|
||||||
TempDir *check.Absolute
|
TempDir *check.Absolute
|
||||||
// RuntimePath is copied from $XDG_RUNTIME_DIR.
|
// RuntimePath is copied from $XDG_RUNTIME_DIR.
|
||||||
RuntimePath *check.Absolute
|
RuntimePath *check.Absolute
|
||||||
|
// Whether [VarRunNscd] is a directory.
|
||||||
|
HasNscd bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy expands [Paths] into [hst.Paths].
|
// Copy expands [Paths] into [hst.Paths].
|
||||||
@@ -37,14 +45,17 @@ func (env *Paths) Copy(v *hst.Paths, userid int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CopyPaths returns a populated [Paths].
|
// CopyPaths returns a populated [Paths].
|
||||||
func CopyPaths() *Paths { return CopyPathsFunc(log.Fatalf, os.TempDir, os.Getenv) }
|
func CopyPaths() *Paths {
|
||||||
|
return CopyPathsFunc(log.Fatalf, os.TempDir, os.Getenv, os.Stat)
|
||||||
|
}
|
||||||
|
|
||||||
// CopyPathsFunc returns a populated [Paths],
|
// CopyPathsFunc returns a populated [Paths], using the provided [log.Fatalf],
|
||||||
// using the provided [log.Fatalf], [os.TempDir], [os.Getenv] functions.
|
// [os.TempDir], [os.Getenv] functions.
|
||||||
func CopyPathsFunc(
|
func CopyPathsFunc(
|
||||||
fatalf func(format string, v ...any),
|
fatalf func(format string, v ...any),
|
||||||
tempdir func() string,
|
tempdir func() string,
|
||||||
getenv func(key string) string,
|
getenv func(key string) string,
|
||||||
|
stat func(name string) (fs.FileInfo, error),
|
||||||
) *Paths {
|
) *Paths {
|
||||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
@@ -61,5 +72,14 @@ func CopyPathsFunc(
|
|||||||
env.RuntimePath = a
|
env.RuntimePath = a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fi, err := stat(VarRunNscd); err != nil {
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
fatalf("%v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
env.HasNscd = fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
return &env
|
return &env
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+4
-1
@@ -2,6 +2,7 @@ package env_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -104,7 +105,9 @@ func TestCopyPaths(t *testing.T) {
|
|||||||
t.Fatalf("fatalf: %q, want %q", got, tc.fatal)
|
t.Fatalf("fatalf: %q, want %q", got, tc.fatal)
|
||||||
}
|
}
|
||||||
panic(stub.PanicExit)
|
panic(stub.PanicExit)
|
||||||
}, func() string { return tc.tmp }, func(key string) string { return tc.env[key] })
|
}, func() string { return tc.tmp }, func(key string) string { return tc.env[key] }, func(name string) (fs.FileInfo, error) {
|
||||||
|
return nil, fs.ErrNotExist
|
||||||
|
})
|
||||||
|
|
||||||
if tc.fatal != "" {
|
if tc.fatal != "" {
|
||||||
t.Fatalf("copyPaths: expected fatal %q", tc.fatal)
|
t.Fatalf("copyPaths: expected fatal %q", tc.fatal)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package kobject
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"maps"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -28,6 +29,22 @@ type Event struct {
|
|||||||
Subsystem string `json:"subsystem"`
|
Subsystem string `json:"subsystem"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone returns a copy of e.
|
||||||
|
func (e *Event) Clone() Event {
|
||||||
|
v := *e
|
||||||
|
v.Env = maps.Clone(e.Env)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeColdboot allocates a new [Object] from e in [StateColdboot].
|
||||||
|
func (e *Event) makeColdboot() *Object {
|
||||||
|
return &Object{
|
||||||
|
State: StateColdboot,
|
||||||
|
DevPath: e.DevPath,
|
||||||
|
Subsystem: e.Subsystem,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Populate populates e with the contents of a [uevent.Message].
|
// Populate populates e with the contents of a [uevent.Message].
|
||||||
//
|
//
|
||||||
// The ACTION and DEVPATH environment variables are ignored and assumed to be
|
// The ACTION and DEVPATH environment variables are ignored and assumed to be
|
||||||
|
|||||||
@@ -0,0 +1,491 @@
|
|||||||
|
// Package kobject interprets uevent messages from a NETLINK_KOBJECT_UEVENT socket.
|
||||||
|
package kobject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"hakurei.app/internal/report"
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StateColdboot denotes an [Object] populated by a coldboot event. It is
|
||||||
|
// eligible for all event actions.
|
||||||
|
StateColdboot = iota
|
||||||
|
// StateNew denotes an [Object] previously populated by a [uevent.KOBJ_ADD]
|
||||||
|
// event, but has not yet been targeted by a [uevent.KOBJ_BIND] event, or
|
||||||
|
// has been targeted by a [uevent.KOBJ_UNBIND] event.
|
||||||
|
StateNew
|
||||||
|
// StateBound denotes an [Object] that has been targeted by a
|
||||||
|
// [uevent.KOBJ_BIND] and has not been targeted by a [uevent.KOBJ_UNBIND]
|
||||||
|
// after that.
|
||||||
|
StateBound
|
||||||
|
)
|
||||||
|
|
||||||
|
// Object represents a kernel object.
|
||||||
|
type Object struct {
|
||||||
|
// Origin of the object.
|
||||||
|
State int `json:"state,omitempty"`
|
||||||
|
// Set by [uevent.KOBJ_OFFLINE] and [uevent.KOBJ_ONLINE].
|
||||||
|
Offline bool `json:"offline,omitempty"`
|
||||||
|
|
||||||
|
// alloc_uevent_skb: devpath
|
||||||
|
DevPath string `json:"devpath"`
|
||||||
|
// registered per-driver (optional)
|
||||||
|
ModAlias string `json:"modalias,omitempty"`
|
||||||
|
// dev_driver_uevent: drv->name (optional)
|
||||||
|
Driver string `json:"driver,omitempty"`
|
||||||
|
|
||||||
|
// SUBSYSTEM value set by the kernel.
|
||||||
|
Subsystem string `json:"subsystem"`
|
||||||
|
|
||||||
|
// Uninterpreted environment variable pairs. An entry missing a separator
|
||||||
|
// gains the value "\x00".
|
||||||
|
Env map[string]string `json:"env"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns the address of a copy of o.
|
||||||
|
func (o *Object) Clone() *Object {
|
||||||
|
v := *o
|
||||||
|
v.Env = maps.Clone(o.Env)
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString returns compound literal for the underlying value.
|
||||||
|
func (o *Object) GoString() string {
|
||||||
|
return fmt.Sprintf("&%#v", *o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge merges uninterpreted environment variable pairs from an [Event].
|
||||||
|
func (o *Object) merge(env map[string]string) {
|
||||||
|
for k, v := range env {
|
||||||
|
if v == "\x00" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case "MODALIAS":
|
||||||
|
o.ModAlias = v
|
||||||
|
continue
|
||||||
|
|
||||||
|
case "DRIVER":
|
||||||
|
o.Driver = v
|
||||||
|
continue
|
||||||
|
|
||||||
|
default:
|
||||||
|
if o.Env == nil {
|
||||||
|
o.Env = make(map[string]string)
|
||||||
|
}
|
||||||
|
o.Env[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update updates o with pairs from env, optionally stripping visited pairs.
|
||||||
|
func (o *Object) update(env map[string]string, strip bool) {
|
||||||
|
for k := range o.Env {
|
||||||
|
if v, ok := env[k]; ok {
|
||||||
|
if strip {
|
||||||
|
delete(env, k)
|
||||||
|
}
|
||||||
|
o.Env[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A pendingIterator is a callback currently iterating through objects targeted
|
||||||
|
// by ongoing events.
|
||||||
|
type pendingIterator struct {
|
||||||
|
f func(o *Object, act uevent.KobjectAction) bool
|
||||||
|
done chan<- struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State processes a stream of [Event] populated from [uevent.Message] received
|
||||||
|
// from a NETLINK_KOBJECT_UEVENT socket and presents an efficient representation
|
||||||
|
// of kernel state.
|
||||||
|
type State struct {
|
||||||
|
// Next expected SEQNUM.
|
||||||
|
seq uint64
|
||||||
|
// DevPath to environment variables.
|
||||||
|
uevent map[string]*Object
|
||||||
|
// Synchronises access to uevent and its objects.
|
||||||
|
ueventMu sync.RWMutex
|
||||||
|
// Alive iterators.
|
||||||
|
iter []*pendingIterator
|
||||||
|
// Synchronises access to iter.
|
||||||
|
iterMu sync.Mutex
|
||||||
|
// UUID for synthetic [uevent.Coldboot] events.
|
||||||
|
coldboot uevent.UUID
|
||||||
|
// Called on [uevent.KOBJ_CHANGE] with stripped environment variables.
|
||||||
|
handleChange func(o *Object, env map[string]string)
|
||||||
|
// Reports errors populating [Event] from [uevent.Message]. A user-supplied
|
||||||
|
// nil value is replaced with a noop.
|
||||||
|
reportErr func(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns the address of a new [State].
|
||||||
|
func New(
|
||||||
|
coldboot uevent.UUID,
|
||||||
|
handleChange func(o *Object, env map[string]string),
|
||||||
|
reportErr func(error),
|
||||||
|
) *State {
|
||||||
|
return &State{
|
||||||
|
uevent: make(map[string]*Object),
|
||||||
|
coldboot: coldboot,
|
||||||
|
handleChange: handleChange,
|
||||||
|
reportErr: reportErr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteIter removes an iterator from s. Must be called after acquiring iterMu.
|
||||||
|
func (s *State) deleteIter(p *pendingIterator) {
|
||||||
|
s.iter = slices.DeleteFunc(s.iter, func(v *pendingIterator) bool {
|
||||||
|
return p == v
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatchIter broadcasts an [Object] to all alive iterators.
|
||||||
|
func (s *State) dispatchIter(o *Object, act uevent.KobjectAction) {
|
||||||
|
s.iterMu.Lock()
|
||||||
|
defer s.iterMu.Unlock()
|
||||||
|
|
||||||
|
for _, p := range s.iter {
|
||||||
|
if !p.f(o, act) {
|
||||||
|
s.deleteIter(p)
|
||||||
|
close(p.done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range calls f on all current and upcoming [Object] values tracked by s until
|
||||||
|
// f returns false or the context is cancelled. f must not retain o or modify
|
||||||
|
// the value it points to.
|
||||||
|
func (s *State) Range(
|
||||||
|
ctx context.Context,
|
||||||
|
f func(o *Object, act uevent.KobjectAction) bool,
|
||||||
|
) {
|
||||||
|
done := make(chan struct{})
|
||||||
|
p := pendingIterator{f, done}
|
||||||
|
|
||||||
|
s.iterMu.Lock()
|
||||||
|
s.ueventMu.RLock()
|
||||||
|
for _, o := range s.uevent {
|
||||||
|
if !f(o, uevent.KOBJ_ADD) {
|
||||||
|
s.ueventMu.RUnlock()
|
||||||
|
s.iterMu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.ueventMu.RUnlock()
|
||||||
|
s.iter = append(s.iter, &p)
|
||||||
|
s.iterMu.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
s.iterMu.Lock()
|
||||||
|
s.deleteIter(&p)
|
||||||
|
s.iterMu.Unlock()
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-done:
|
||||||
|
// deregistered by dispatchIter
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An EventError describes a malformed or inconsistent [Event].
|
||||||
|
type EventError struct {
|
||||||
|
Kind int `json:"fault"`
|
||||||
|
E Event `json:"event"`
|
||||||
|
O *Object `json:"object,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ report.RepresentableError = EventError{}
|
||||||
|
|
||||||
|
func (EventError) Representable() {}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EUnexpectedColdboot is reported for a coldboot event with action other
|
||||||
|
// than the expected [uevent.KOBJ_ADD].
|
||||||
|
EUnexpectedColdboot = iota
|
||||||
|
// EDuplicateAdd is reported for a [uevent.KOBJ_ADD] event on a
|
||||||
|
// still-existing entry that was not the result of a coldboot.
|
||||||
|
EDuplicateAdd
|
||||||
|
// EBadTarget is reported for an event on a nonexistent [Object]. This is
|
||||||
|
// generally only possible before coldboot completes.
|
||||||
|
EBadTarget
|
||||||
|
// ERemoveState is reported for a [uevent.KOBJ_REMOVE] event targeting an
|
||||||
|
// entry in a state other than [StateColdboot] and [StateNew].
|
||||||
|
ERemoveState
|
||||||
|
// EUnexpectedOffline is reported for a [uevent.KOBJ_OFFLINE] or
|
||||||
|
// [uevent.KOBJ_ONLINE] event targeting an already offline or online object.
|
||||||
|
EUnexpectedOffline
|
||||||
|
// EBindState is reported for a [uevent.KOBJ_BIND] event targeting an entry
|
||||||
|
// in a state other than [StateColdboot] and [StateNew].
|
||||||
|
EBindState
|
||||||
|
// EUnbindState is reported for a [uevent.KOBJ_UNBIND] event targeting an
|
||||||
|
// entry in a state other than [StateBound].
|
||||||
|
EUnbindState
|
||||||
|
// EMalformedMove is reported for a [uevent.KOBJ_MOVE] event missing the
|
||||||
|
// DEVPATH_OLD environment variable.
|
||||||
|
EMalformedMove
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e EventError) Error() string {
|
||||||
|
switch e.Kind {
|
||||||
|
case EUnexpectedColdboot:
|
||||||
|
return "unexpected " + e.E.Action.String() + " coldboot event"
|
||||||
|
case EDuplicateAdd:
|
||||||
|
return "duplicate add event on devpath " + strconv.Quote(e.E.DevPath)
|
||||||
|
case EBadTarget:
|
||||||
|
return "unexpected " + e.E.Action.String() + " event on devpath " +
|
||||||
|
strconv.Quote(e.E.DevPath)
|
||||||
|
case ERemoveState:
|
||||||
|
if e.O == nil {
|
||||||
|
return "invalid remove event error"
|
||||||
|
}
|
||||||
|
return "remove event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
||||||
|
" in state " + strconv.Itoa(e.O.State)
|
||||||
|
case EUnexpectedOffline:
|
||||||
|
if e.O == nil {
|
||||||
|
return "invalid unexpected offline error"
|
||||||
|
}
|
||||||
|
if e.O.Offline {
|
||||||
|
return "offline event targeting devpath " + strconv.Quote(e.E.DevPath)
|
||||||
|
}
|
||||||
|
return "online event targeting devpath " + strconv.Quote(e.E.DevPath)
|
||||||
|
case EBindState:
|
||||||
|
if e.O == nil {
|
||||||
|
return "invalid bind state error"
|
||||||
|
}
|
||||||
|
return "bind event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
||||||
|
" in state " + strconv.Itoa(e.O.State)
|
||||||
|
case EUnbindState:
|
||||||
|
if e.O == nil {
|
||||||
|
return "invalid unbind state error"
|
||||||
|
}
|
||||||
|
return "unbind event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
||||||
|
" in state " + strconv.Itoa(e.O.State)
|
||||||
|
case EMalformedMove:
|
||||||
|
return "move event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
||||||
|
" missing DEVPATH_OLD"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "invalid event error kind " + strconv.Itoa(e.Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewError returns a new [EventError] for e and o.
|
||||||
|
func (e *Event) NewError(kind int, o *Object) error {
|
||||||
|
if o != nil {
|
||||||
|
o = o.Clone()
|
||||||
|
}
|
||||||
|
return EventError{kind, e.Clone(), o}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processEvent merges an event into s.
|
||||||
|
func (s *State) processEvent(e *Event) {
|
||||||
|
s.ueventMu.Lock()
|
||||||
|
defer s.ueventMu.Unlock()
|
||||||
|
|
||||||
|
coldboot := e.Synth != nil
|
||||||
|
if e.Action != uevent.KOBJ_ADD && coldboot {
|
||||||
|
s.reportErr(e.NewError(EUnexpectedColdboot, nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch act := e.Action; act {
|
||||||
|
case uevent.KOBJ_ADD:
|
||||||
|
if e.Synth == nil {
|
||||||
|
if o, ok := s.uevent[e.DevPath]; ok {
|
||||||
|
s.reportErr(e.NewError(EDuplicateAdd, o))
|
||||||
|
o.merge(e.Env)
|
||||||
|
s.dispatchIter(o, act)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o := e.makeColdboot()
|
||||||
|
if !coldboot {
|
||||||
|
o.State = StateNew
|
||||||
|
}
|
||||||
|
o.merge(e.Env)
|
||||||
|
s.uevent[e.DevPath] = o
|
||||||
|
s.dispatchIter(o, act)
|
||||||
|
return
|
||||||
|
|
||||||
|
case uevent.KOBJ_REMOVE:
|
||||||
|
if o, ok := s.uevent[e.DevPath]; !ok {
|
||||||
|
s.reportErr(e.NewError(EBadTarget, nil))
|
||||||
|
return
|
||||||
|
} else if o.State != StateColdboot && o.State != StateNew {
|
||||||
|
s.reportErr(e.NewError(ERemoveState, o))
|
||||||
|
}
|
||||||
|
delete(s.uevent, e.DevPath)
|
||||||
|
return
|
||||||
|
|
||||||
|
case uevent.KOBJ_CHANGE:
|
||||||
|
o, ok := s.uevent[e.DevPath]
|
||||||
|
if !ok {
|
||||||
|
s.reportErr(e.NewError(EBadTarget, nil))
|
||||||
|
// this suffers from the coldboot race window similar to KOBJ_MOVE,
|
||||||
|
// however this action combines driver-specific and change-specific
|
||||||
|
// environment variables and combines them with environment
|
||||||
|
// variables meant to convey state of the kobject, and it is not
|
||||||
|
// possible to reliably separate them, so this fallback avoids the
|
||||||
|
// race at the cost of including some garbage in tracked state
|
||||||
|
o = e.makeColdboot()
|
||||||
|
o.merge(e.Env)
|
||||||
|
s.uevent[e.DevPath] = o
|
||||||
|
s.dispatchIter(o, act)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.update(e.Env, true)
|
||||||
|
if s.handleChange != nil {
|
||||||
|
s.handleChange(o, e.Env)
|
||||||
|
}
|
||||||
|
s.dispatchIter(o, act)
|
||||||
|
return
|
||||||
|
|
||||||
|
case uevent.KOBJ_MOVE:
|
||||||
|
var o *Object
|
||||||
|
if old, ok := e.Env["DEVPATH_OLD"]; !ok {
|
||||||
|
s.reportErr(e.NewError(EMalformedMove, nil))
|
||||||
|
// not reached
|
||||||
|
o = e.makeColdboot()
|
||||||
|
} else if o, ok = s.uevent[old]; !ok {
|
||||||
|
s.reportErr(e.NewError(EBadTarget, nil))
|
||||||
|
// this generally happens during coldboot, dropping the event here
|
||||||
|
// may cause inconsistent state if the coldboot event for this
|
||||||
|
// object was generated before the bind event
|
||||||
|
delete(e.Env, "DEVPATH_OLD")
|
||||||
|
o = e.makeColdboot()
|
||||||
|
} else {
|
||||||
|
delete(s.uevent, old)
|
||||||
|
delete(e.Env, "DEVPATH_OLD")
|
||||||
|
}
|
||||||
|
o.merge(e.Env)
|
||||||
|
s.uevent[e.DevPath] = o
|
||||||
|
o.DevPath = e.DevPath
|
||||||
|
s.dispatchIter(o, act)
|
||||||
|
return
|
||||||
|
|
||||||
|
case uevent.KOBJ_ONLINE:
|
||||||
|
o, ok := s.uevent[e.DevPath]
|
||||||
|
if !ok {
|
||||||
|
s.reportErr(e.NewError(EBadTarget, nil))
|
||||||
|
// coldboot race window similar to an unexpected KOBJ_MOVE
|
||||||
|
o = e.makeColdboot()
|
||||||
|
s.uevent[e.DevPath] = o
|
||||||
|
o.merge(e.Env)
|
||||||
|
}
|
||||||
|
if !o.Offline {
|
||||||
|
s.reportErr(e.NewError(EUnexpectedOffline, o))
|
||||||
|
}
|
||||||
|
o.Offline = false
|
||||||
|
s.dispatchIter(o, act)
|
||||||
|
return
|
||||||
|
|
||||||
|
case uevent.KOBJ_OFFLINE:
|
||||||
|
o, ok := s.uevent[e.DevPath]
|
||||||
|
if !ok {
|
||||||
|
s.reportErr(e.NewError(EBadTarget, nil))
|
||||||
|
// coldboot race window similar to an unexpected KOBJ_MOVE
|
||||||
|
o = e.makeColdboot()
|
||||||
|
s.uevent[e.DevPath] = o
|
||||||
|
o.merge(e.Env)
|
||||||
|
}
|
||||||
|
if o.Offline {
|
||||||
|
s.reportErr(e.NewError(EUnexpectedOffline, o))
|
||||||
|
}
|
||||||
|
o.Offline = true
|
||||||
|
s.dispatchIter(o, act)
|
||||||
|
return
|
||||||
|
|
||||||
|
case uevent.KOBJ_BIND:
|
||||||
|
o, ok := s.uevent[e.DevPath]
|
||||||
|
if !ok {
|
||||||
|
s.reportErr(e.NewError(EBadTarget, nil))
|
||||||
|
// coldboot race window similar to an unexpected KOBJ_MOVE
|
||||||
|
o = e.makeColdboot()
|
||||||
|
s.uevent[e.DevPath] = o
|
||||||
|
}
|
||||||
|
if o.State != StateColdboot && o.State != StateNew {
|
||||||
|
s.reportErr(e.NewError(EBindState, o))
|
||||||
|
}
|
||||||
|
o.State = StateBound
|
||||||
|
o.merge(e.Env)
|
||||||
|
s.dispatchIter(o, act)
|
||||||
|
return
|
||||||
|
|
||||||
|
case uevent.KOBJ_UNBIND:
|
||||||
|
o, ok := s.uevent[e.DevPath]
|
||||||
|
if !ok {
|
||||||
|
s.reportErr(e.NewError(EBadTarget, nil))
|
||||||
|
// coldboot race window similar to an unexpected KOBJ_MOVE, but does
|
||||||
|
// not result in inconsistent state if dropped
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if o.State != StateBound {
|
||||||
|
s.reportErr(e.NewError(EUnbindState, o))
|
||||||
|
}
|
||||||
|
o.State = StateNew
|
||||||
|
o.Driver = ""
|
||||||
|
s.dispatchIter(o, act)
|
||||||
|
return
|
||||||
|
|
||||||
|
default: // not reached
|
||||||
|
s.reportErr(fmt.Errorf("invalid action %d", e.Action))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BadSequenceError is reported for an unexpected SEQNUM.
|
||||||
|
type BadSequenceError struct{ Got, Want uint64 }
|
||||||
|
|
||||||
|
func (e BadSequenceError) Error() string {
|
||||||
|
return "SEQNUM=" + strconv.FormatUint(e.Got, 10) +
|
||||||
|
", want " + strconv.FormatUint(e.Want, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume receives uevent messages and updates s to reflect state of kernel.
|
||||||
|
func (s *State) Consume(ctx context.Context, events <-chan *uevent.Message) {
|
||||||
|
if s.uevent == nil {
|
||||||
|
s.uevent = make(map[string]*Object)
|
||||||
|
}
|
||||||
|
if s.reportErr == nil {
|
||||||
|
s.reportErr = func(error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var e Event
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
|
||||||
|
case m, ok := <-events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.Populate(s.reportErr, m)
|
||||||
|
|
||||||
|
// skip external synthetic event
|
||||||
|
if e.Synth != nil && *e.Synth != s.coldboot {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.seq == 0 {
|
||||||
|
s.seq = e.Sequence
|
||||||
|
}
|
||||||
|
if s.seq != e.Sequence {
|
||||||
|
s.reportErr(BadSequenceError{e.Sequence, s.seq})
|
||||||
|
}
|
||||||
|
s.seq++
|
||||||
|
s.processEvent(&e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
+266
@@ -0,0 +1,266 @@
|
|||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXPWRBN:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXPWRBN:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXPWRBN:","SEQNUM=777"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXPWRBN:00/wakeup/wakeup7","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXPWRBN:00/wakeup/wakeup7","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=778"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00/LNXCPU:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00/LNXCPU:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXCPU:","SEQNUM=779"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:ACPI0010:PNP0A05:","SEQNUM=780"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0103:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0103:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0103:","SEQNUM=781"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=782"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=783"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=784"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/QEMU0002:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/QEMU0002:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:QEMU0002:","SEQNUM=785"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=786"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00/wakeup/wakeup0","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00/wakeup/wakeup0","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=787"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0303:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0303:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0303:","SEQNUM=788"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0400:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0400:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0400:","SEQNUM=789"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0501:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0501:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0501:","SEQNUM=790"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00/device:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00/device:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=791"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0700:","SEQNUM=792"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0B00:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0B00:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0B00:","SEQNUM=793"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0F13:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0F13:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0F13:","SEQNUM=794"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=795"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/wakeup/wakeup1","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/wakeup/wakeup1","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=796"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=797"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03/wakeup/wakeup2","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03/wakeup/wakeup2","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=798"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=799"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04/wakeup/wakeup3","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04/wakeup/wakeup3","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=800"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=801"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05/wakeup/wakeup4","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05/wakeup/wakeup4","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=802"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=803"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06/wakeup/wakeup5","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06/wakeup/wakeup5","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=804"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=805"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=806"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:09","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:09","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=807"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0a","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0a","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=808"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0b","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0b","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=809"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0c","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0c","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=810"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0d","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0d","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=811"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0e","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0e","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=812"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0f","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0f","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=813"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:10","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:10","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=814"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:11","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:11","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=815"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:12","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:12","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=816"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:13","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:13","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=817"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:14","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:14","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=818"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:15","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:15","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=819"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:16","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:16","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=820"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:17","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:17","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=821"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:18","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:18","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=822"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:19","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:19","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=823"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1a","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1a","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=824"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1b","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1b","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=825"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1c","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1c","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=826"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1d","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1d","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=827"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1e","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1e","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=828"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1f","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1f","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=829"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:20","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:20","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=830"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:21","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:21","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=831"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:22","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:22","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=832"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A03:","SEQNUM=833"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/wakeup/wakeup6","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/wakeup/wakeup6","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=834"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=835"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=836"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=837"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:03","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:03","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=838"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:04","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:04","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=839"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYBUS:","SEQNUM=840"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYBUS:","SEQNUM=841"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYSTM:","SEQNUM=842"]}
|
||||||
|
{"action":"add","devpath":"/devices/breakpoint","env":["ACTION=add","DEVPATH=/devices/breakpoint","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=843"]}
|
||||||
|
{"action":"add","devpath":"/devices/cpu","env":["ACTION=add","DEVPATH=/devices/cpu","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=844"]}
|
||||||
|
{"action":"add","devpath":"/devices/kprobe","env":["ACTION=add","DEVPATH=/devices/kprobe","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=845"]}
|
||||||
|
{"action":"add","devpath":"/devices/msr","env":["ACTION=add","DEVPATH=/devices/msr","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=846"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:00.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:00.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=60000","PCI_ID=8086:1237","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:00.0","MODALIAS=pci:v00008086d00001237sv00001AF4sd00001100bc06sc00i00","SEQNUM=847"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=60100","PCI_ID=8086:7000","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.0","MODALIAS=pci:v00008086d00007000sv00001AF4sd00001100bc06sc01i00","SEQNUM=848"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/ata_port/ata1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/ata_port/ata1","SUBSYSTEM=ata_port","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=849"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/host0/scsi_host/host0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/host0/scsi_host/host0","SUBSYSTEM=scsi_host","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=850"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/host0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/host0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_host","SEQNUM=851"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/ata_link/link1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/ata_link/link1","SUBSYSTEM=ata_link","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=852"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.0/ata_device/dev1.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.0/ata_device/dev1.0","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=853"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.1/ata_device/dev1.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.1/ata_device/dev1.1","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=854"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/ata_port/ata2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/ata_port/ata2","SUBSYSTEM=ata_port","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=855"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/scsi_host/host1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/scsi_host/host1","SUBSYSTEM=scsi_host","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=856"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/bsg/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/bsg/1:0:0:0","SUBSYSTEM=bsg","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=251","MINOR=0","DEVNAME=bsg/1:0:0:0","SEQNUM=857"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/scsi_device/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/scsi_device/1:0:0:0","SUBSYSTEM=scsi_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=858"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_device","MODALIAS=scsi:t-0x05","SEQNUM=859"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_target","SEQNUM=860"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_host","SEQNUM=861"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/ata_link/link2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/ata_link/link2","SUBSYSTEM=ata_link","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=862"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.0/ata_device/dev2.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.0/ata_device/dev2.0","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=863"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.1/ata_device/dev2.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.1/ata_device/dev2.1","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=864"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=ata_piix","PCI_CLASS=10180","PCI_ID=8086:7010","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.1","MODALIAS=pci:v00008086d00007010sv00001AF4sd00001100bc01sc01i80","SEQNUM=865"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.3","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.3","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=68000","PCI_ID=8086:7113","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.3","MODALIAS=pci:v00008086d00007113sv00001AF4sd00001100bc06sc80i00","SEQNUM=866"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:02.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:02.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=20000","PCI_ID=8086:100E","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:02.0","MODALIAS=pci:v00008086d0000100Esv00001AF4sd00001100bc02sc00i00","SEQNUM=867"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=virtio-pci","PCI_CLASS=10000","PCI_ID=1AF4:1001","PCI_SUBSYS_ID=1AF4:0002","PCI_SLOT_NAME=0000:00:03.0","MODALIAS=pci:v00001AF4d00001001sv00001AF4sd00000002bc01sc00i00","SEQNUM=868"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0/virtio0/block/vda","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0/virtio0/block/vda","SUBSYSTEM=block","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=254","MINOR=0","DEVNAME=vda","DEVTYPE=disk","DISKSEQ=1","SEQNUM=869"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0/virtio0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0/virtio0","SUBSYSTEM=virtio","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=virtio_blk","MODALIAS=virtio:d00000002v00001AF4","SEQNUM=870"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/QEMU0002:00","env":["ACTION=add","DEVPATH=/devices/pci0000:00/QEMU0002:00","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:QEMU0002:","SEQNUM=871"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/pci_bus/0000:00","env":["ACTION=add","DEVPATH=/devices/pci0000:00/pci_bus/0000:00","SUBSYSTEM=pci_bus","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=872"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/PNP0103:00","env":["ACTION=add","DEVPATH=/devices/platform/PNP0103:00","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0103:","SEQNUM=873"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/pcspkr","env":["ACTION=add","DEVPATH=/devices/platform/pcspkr","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=platform:pcspkr","SEQNUM=874"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/reg-dummy/regulator/regulator.0","env":["ACTION=add","DEVPATH=/devices/platform/reg-dummy/regulator/regulator.0","SUBSYSTEM=regulator","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=875"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/reg-dummy","env":["ACTION=add","DEVPATH=/devices/platform/reg-dummy","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=reg-dummy","MODALIAS=platform:reg-dummy","SEQNUM=876"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.1/tty/ttyS1","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.1/tty/ttyS1","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=65","DEVNAME=ttyS1","SEQNUM=877"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.1","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.1","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=878"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.2/tty/ttyS2","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.2/tty/ttyS2","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=66","DEVNAME=ttyS2","SEQNUM=879"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.2","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.2","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=880"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.3/tty/ttyS3","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.3/tty/ttyS3","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=67","DEVNAME=ttyS3","SEQNUM=881"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.3","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.3","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=882"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=ctrl","DRIVER=ctrl","SEQNUM=883"]}
|
||||||
|
{"action":"add","devpath":"/devices/platform/serial8250","env":["ACTION=add","DEVPATH=/devices/platform/serial8250","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=serial8250","MODALIAS=platform:serial8250","SEQNUM=884"]}
|
||||||
|
{"action":"add","devpath":"/devices/pnp0/00:00","env":["ACTION=add","DEVPATH=/devices/pnp0/00:00","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=885"]}
|
||||||
|
{"action":"add","devpath":"/devices/pnp0/00:01","env":["ACTION=add","DEVPATH=/devices/pnp0/00:01","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=886"]}
|
||||||
|
{"action":"add","devpath":"/devices/pnp0/00:02","env":["ACTION=add","DEVPATH=/devices/pnp0/00:02","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=887"]}
|
||||||
|
{"action":"add","devpath":"/devices/pnp0/00:03","env":["ACTION=add","DEVPATH=/devices/pnp0/00:03","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=888"]}
|
||||||
|
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0/00:04:0.0/tty/ttyS0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0/00:04:0.0/tty/ttyS0","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=64","DEVNAME=ttyS0","SEQNUM=889"]}
|
||||||
|
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0/00:04:0.0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0/00:04:0.0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=890"]}
|
||||||
|
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=ctrl","DRIVER=ctrl","SEQNUM=891"]}
|
||||||
|
{"action":"add","devpath":"/devices/pnp0/00:04","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=serial","SEQNUM=892"]}
|
||||||
|
{"action":"add","devpath":"/devices/pnp0/00:05","env":["ACTION=add","DEVPATH=/devices/pnp0/00:05","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=893"]}
|
||||||
|
{"action":"add","devpath":"/devices/software","env":["ACTION=add","DEVPATH=/devices/software","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=894"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/clockevents/broadcast","env":["ACTION=add","DEVPATH=/devices/system/clockevents/broadcast","SUBSYSTEM=clockevents","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=895"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/clockevents/clockevent0","env":["ACTION=add","DEVPATH=/devices/system/clockevents/clockevent0","SUBSYSTEM=clockevents","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=896"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/clocksource/clocksource0","env":["ACTION=add","DEVPATH=/devices/system/clocksource/clocksource0","SUBSYSTEM=clocksource","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=897"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/container/PNP0A06:00","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:00","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=898"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/container/PNP0A06:01","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:01","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=899"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/container/PNP0A06:02","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:02","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=900"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/cpu/cpu0","env":["ACTION=add","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=901"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=902"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/memory/memory0","env":["ACTION=add","DEVPATH=/devices/system/memory/memory0","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=903"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/memory/memory1","env":["ACTION=add","DEVPATH=/devices/system/memory/memory1","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=904"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/memory/memory2","env":["ACTION=add","DEVPATH=/devices/system/memory/memory2","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=905"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/memory/memory3","env":["ACTION=add","DEVPATH=/devices/system/memory/memory3","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=906"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/memory/memory4","env":["ACTION=add","DEVPATH=/devices/system/memory/memory4","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=907"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/memory/memory5","env":["ACTION=add","DEVPATH=/devices/system/memory/memory5","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=908"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/memory/memory6","env":["ACTION=add","DEVPATH=/devices/system/memory/memory6","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=909"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/memory/memory7","env":["ACTION=add","DEVPATH=/devices/system/memory/memory7","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=910"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/node/node0","env":["ACTION=add","DEVPATH=/devices/system/node/node0","SUBSYSTEM=node","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=911"]}
|
||||||
|
{"action":"add","devpath":"/devices/tracepoint","env":["ACTION=add","DEVPATH=/devices/tracepoint","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=912"]}
|
||||||
|
{"action":"add","devpath":"/devices/uprobe","env":["ACTION=add","DEVPATH=/devices/uprobe","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=913"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/bdi/254:0","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/254:0","SUBSYSTEM=bdi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=914"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/devlink/:ata2--scsi:1:0:0:0","env":["ACTION=add","DEVPATH=/devices/virtual/devlink/:ata2--scsi:1:0:0:0","SUBSYSTEM=devlink","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=915"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/dmi/id","env":["ACTION=add","DEVPATH=/devices/virtual/dmi/id","SUBSYSTEM=dmi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=dmi:bvnSeaBIOS:bvrrel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org:bd04/01/2014:br0.0:svnQEMU:pnStandardPC(i440FX+PIIX,1996):pvrpc-i440fx-10.1:cvnQEMU:ct1:cvrpc-i440fx-10.1:sku:","SEQNUM=916"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/mem/full","env":["ACTION=add","DEVPATH=/devices/virtual/mem/full","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=7","DEVNAME=full","DEVMODE=0666","SEQNUM=917"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/mem/kmsg","env":["ACTION=add","DEVPATH=/devices/virtual/mem/kmsg","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=11","DEVNAME=kmsg","DEVMODE=0644","SEQNUM=918"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/mem/mem","env":["ACTION=add","DEVPATH=/devices/virtual/mem/mem","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=1","DEVNAME=mem","SEQNUM=919"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/mem/null","env":["ACTION=add","DEVPATH=/devices/virtual/mem/null","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=3","DEVNAME=null","DEVMODE=0666","SEQNUM=920"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/mem/port","env":["ACTION=add","DEVPATH=/devices/virtual/mem/port","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=4","DEVNAME=port","SEQNUM=921"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/mem/random","env":["ACTION=add","DEVPATH=/devices/virtual/mem/random","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=8","DEVNAME=random","DEVMODE=0666","SEQNUM=922"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/mem/urandom","env":["ACTION=add","DEVPATH=/devices/virtual/mem/urandom","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=9","DEVNAME=urandom","DEVMODE=0666","SEQNUM=923"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/mem/zero","env":["ACTION=add","DEVPATH=/devices/virtual/mem/zero","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=5","DEVNAME=zero","DEVMODE=0666","SEQNUM=924"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/memory_tiering/memory_tier4","env":["ACTION=add","DEVPATH=/devices/virtual/memory_tiering/memory_tier4","SUBSYSTEM=memory_tiering","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=925"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/misc/cpu_dma_latency","env":["ACTION=add","DEVPATH=/devices/virtual/misc/cpu_dma_latency","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=259","DEVNAME=cpu_dma_latency","SEQNUM=926"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/misc/hpet","env":["ACTION=add","DEVPATH=/devices/virtual/misc/hpet","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=228","DEVNAME=hpet","SEQNUM=927"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/misc/snapshot","env":["ACTION=add","DEVPATH=/devices/virtual/misc/snapshot","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=231","DEVNAME=snapshot","SEQNUM=928"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/misc/udmabuf","env":["ACTION=add","DEVPATH=/devices/virtual/misc/udmabuf","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=258","DEVNAME=udmabuf","SEQNUM=929"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/misc/userfaultfd","env":["ACTION=add","DEVPATH=/devices/virtual/misc/userfaultfd","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=257","DEVNAME=userfaultfd","SEQNUM=930"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/misc/vga_arbiter","env":["ACTION=add","DEVPATH=/devices/virtual/misc/vga_arbiter","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=256","DEVNAME=vga_arbiter","SEQNUM=931"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/net/lo","env":["ACTION=add","DEVPATH=/devices/virtual/net/lo","SUBSYSTEM=net","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","INTERFACE=lo","IFINDEX=1","SEQNUM=932"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/thermal/cooling_device0","env":["ACTION=add","DEVPATH=/devices/virtual/thermal/cooling_device0","SUBSYSTEM=thermal","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=933"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/console","env":["ACTION=add","DEVPATH=/devices/virtual/tty/console","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=1","DEVNAME=console","SEQNUM=934"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/ptmx","env":["ACTION=add","DEVPATH=/devices/virtual/tty/ptmx","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=2","DEVNAME=ptmx","DEVMODE=0666","SEQNUM=935"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=0","DEVNAME=tty","DEVMODE=0666","SEQNUM=936"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty0","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty0","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=0","DEVNAME=tty0","SEQNUM=937"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty1","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty1","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=1","DEVNAME=tty1","SEQNUM=938"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty10","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty10","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=10","DEVNAME=tty10","SEQNUM=939"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty11","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty11","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=11","DEVNAME=tty11","SEQNUM=940"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty12","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty12","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=12","DEVNAME=tty12","SEQNUM=941"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty13","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty13","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=13","DEVNAME=tty13","SEQNUM=942"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty14","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty14","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=14","DEVNAME=tty14","SEQNUM=943"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty15","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty15","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=15","DEVNAME=tty15","SEQNUM=944"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty16","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty16","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=16","DEVNAME=tty16","SEQNUM=945"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty17","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty17","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=17","DEVNAME=tty17","SEQNUM=946"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty18","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty18","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=18","DEVNAME=tty18","SEQNUM=947"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty19","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty19","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=19","DEVNAME=tty19","SEQNUM=948"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty2","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty2","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=2","DEVNAME=tty2","SEQNUM=949"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty20","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty20","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=20","DEVNAME=tty20","SEQNUM=950"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty21","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty21","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=21","DEVNAME=tty21","SEQNUM=951"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty22","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty22","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=22","DEVNAME=tty22","SEQNUM=952"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty23","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty23","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=23","DEVNAME=tty23","SEQNUM=953"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty24","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty24","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=24","DEVNAME=tty24","SEQNUM=954"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty25","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty25","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=25","DEVNAME=tty25","SEQNUM=955"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty26","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty26","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=26","DEVNAME=tty26","SEQNUM=956"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty27","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty27","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=27","DEVNAME=tty27","SEQNUM=957"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty28","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty28","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=28","DEVNAME=tty28","SEQNUM=958"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty29","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty29","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=29","DEVNAME=tty29","SEQNUM=959"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty3","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty3","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=3","DEVNAME=tty3","SEQNUM=960"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty30","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty30","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=30","DEVNAME=tty30","SEQNUM=961"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty31","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty31","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=31","DEVNAME=tty31","SEQNUM=962"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty32","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty32","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=32","DEVNAME=tty32","SEQNUM=963"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty33","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty33","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=33","DEVNAME=tty33","SEQNUM=964"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty34","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty34","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=34","DEVNAME=tty34","SEQNUM=965"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty35","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty35","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=35","DEVNAME=tty35","SEQNUM=966"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty36","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty36","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=36","DEVNAME=tty36","SEQNUM=967"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty37","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty37","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=37","DEVNAME=tty37","SEQNUM=968"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty38","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty38","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=38","DEVNAME=tty38","SEQNUM=969"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty39","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty39","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=39","DEVNAME=tty39","SEQNUM=970"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty4","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty4","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=4","DEVNAME=tty4","SEQNUM=971"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty40","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty40","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=40","DEVNAME=tty40","SEQNUM=972"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty41","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty41","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=41","DEVNAME=tty41","SEQNUM=973"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty42","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty42","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=42","DEVNAME=tty42","SEQNUM=974"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty43","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty43","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=43","DEVNAME=tty43","SEQNUM=975"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty44","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty44","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=44","DEVNAME=tty44","SEQNUM=976"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty45","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty45","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=45","DEVNAME=tty45","SEQNUM=977"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty46","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty46","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=46","DEVNAME=tty46","SEQNUM=978"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty47","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty47","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=47","DEVNAME=tty47","SEQNUM=979"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty48","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty48","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=48","DEVNAME=tty48","SEQNUM=980"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty49","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty49","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=49","DEVNAME=tty49","SEQNUM=981"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty5","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty5","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=5","DEVNAME=tty5","SEQNUM=982"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty50","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty50","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=50","DEVNAME=tty50","SEQNUM=983"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty51","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty51","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=51","DEVNAME=tty51","SEQNUM=984"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty52","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty52","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=52","DEVNAME=tty52","SEQNUM=985"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty53","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty53","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=53","DEVNAME=tty53","SEQNUM=986"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty54","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty54","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=54","DEVNAME=tty54","SEQNUM=987"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty55","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty55","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=55","DEVNAME=tty55","SEQNUM=988"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty56","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty56","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=56","DEVNAME=tty56","SEQNUM=989"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty57","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty57","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=57","DEVNAME=tty57","SEQNUM=990"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty58","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty58","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=58","DEVNAME=tty58","SEQNUM=991"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty59","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty59","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=59","DEVNAME=tty59","SEQNUM=992"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty6","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty6","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=6","DEVNAME=tty6","SEQNUM=993"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty60","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty60","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=60","DEVNAME=tty60","SEQNUM=994"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty61","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty61","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=61","DEVNAME=tty61","SEQNUM=995"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty62","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty62","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=62","DEVNAME=tty62","SEQNUM=996"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty63","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty63","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=63","DEVNAME=tty63","SEQNUM=997"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty7","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty7","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=7","DEVNAME=tty7","SEQNUM=998"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty8","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty8","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=8","DEVNAME=tty8","SEQNUM=999"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/tty/tty9","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty9","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=9","DEVNAME=tty9","SEQNUM=1000"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/vc/vcs","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcs","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=0","DEVNAME=vcs","SEQNUM=1001"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/vc/vcs1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcs1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=1","DEVNAME=vcs1","SEQNUM=1002"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/vc/vcsa","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsa","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=128","DEVNAME=vcsa","SEQNUM=1003"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/vc/vcsa1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsa1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=129","DEVNAME=vcsa1","SEQNUM=1004"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/vc/vcsu","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsu","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=64","DEVNAME=vcsu","SEQNUM=1005"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/vc/vcsu1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsu1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=65","DEVNAME=vcsu1","SEQNUM=1006"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/vtconsole/vtcon0","env":["ACTION=add","DEVPATH=/devices/virtual/vtconsole/vtcon0","SUBSYSTEM=vtconsole","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1007"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-auth-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-auth-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1008"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-delete-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-delete-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1009"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-reset-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-reset-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1010"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1011"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/workqueue/scsi_tmf_0","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/scsi_tmf_0","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1012"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/workqueue/scsi_tmf_1","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/scsi_tmf_1","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1013"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/workqueue/writeback","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/writeback","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1014"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1015"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1016"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1017"]}
|
||||||
|
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1018"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1019"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1020"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1021"]}
|
||||||
|
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1022"]}
|
||||||
|
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1023"]}
|
||||||
|
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","SEQNUM=1024"]}
|
||||||
|
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1025"]}
|
||||||
|
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1026"]}
|
||||||
|
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1027"]}
|
||||||
|
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","SEQNUM=1028"]}
|
||||||
|
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1029"]}
|
||||||
|
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1030"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1031"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1032"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1033"]}
|
||||||
|
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1034"]}
|
||||||
|
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1035"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1036"]}
|
||||||
|
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1037"]}
|
||||||
|
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1038"]}
|
||||||
|
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1039"]}
|
||||||
|
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","SEQNUM=1040"]}
|
||||||
|
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1041"]}
|
||||||
|
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1042"]}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
{"action":"move","devpath":"/devices/virtual/net/_lo","env":["ACTION=move","DEVPATH=/devices/virtual/net/_lo","SUBSYSTEM=net","DEVPATH_OLD=/devices/virtual/net/lo","INTERFACE=_lo","IFINDEX=1","SEQNUM=1043"]}
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
{"action":"remove","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=remove","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1044"]}
|
||||||
|
{"action":"offline","devpath":"/devices/system/cpu/cpu0","env":["ACTION=offline","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1045"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1046"]}
|
||||||
|
{"action":"online","devpath":"/devices/system/cpu/cpu0","env":["ACTION=online","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1047"]}
|
||||||
|
{"action":"remove","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=remove","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1048"]}
|
||||||
|
{"action":"offline","devpath":"/devices/system/cpu/cpu0","env":["ACTION=offline","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1049"]}
|
||||||
|
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1050"]}
|
||||||
|
{"action":"online","devpath":"/devices/system/cpu/cpu0","env":["ACTION=online","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1051"]}
|
||||||
Vendored
+19
@@ -0,0 +1,19 @@
|
|||||||
|
{"action":"add","devpath":"/devices/virtual/misc/loop-control","env":["ACTION=add","DEVPATH=/devices/virtual/misc/loop-control","SUBSYSTEM=misc","MAJOR=10","MINOR=237","DEVNAME=loop-control","SEQNUM=1052"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/bdi/7:0","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:0","SUBSYSTEM=bdi","SEQNUM=1053"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/block/loop0","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=2","SEQNUM=1054"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/bdi/7:1","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:1","SUBSYSTEM=bdi","SEQNUM=1055"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/block/loop1","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop1","SUBSYSTEM=block","MAJOR=7","MINOR=1","DEVNAME=loop1","DEVTYPE=disk","DISKSEQ=3","SEQNUM=1056"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/bdi/7:2","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:2","SUBSYSTEM=bdi","SEQNUM=1057"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/block/loop2","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop2","SUBSYSTEM=block","MAJOR=7","MINOR=2","DEVNAME=loop2","DEVTYPE=disk","DISKSEQ=4","SEQNUM=1058"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/bdi/7:3","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:3","SUBSYSTEM=bdi","SEQNUM=1059"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/block/loop3","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop3","SUBSYSTEM=block","MAJOR=7","MINOR=3","DEVNAME=loop3","DEVTYPE=disk","DISKSEQ=5","SEQNUM=1060"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/bdi/7:4","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:4","SUBSYSTEM=bdi","SEQNUM=1061"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/block/loop4","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop4","SUBSYSTEM=block","MAJOR=7","MINOR=4","DEVNAME=loop4","DEVTYPE=disk","DISKSEQ=6","SEQNUM=1062"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/bdi/7:5","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:5","SUBSYSTEM=bdi","SEQNUM=1063"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/block/loop5","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop5","SUBSYSTEM=block","MAJOR=7","MINOR=5","DEVNAME=loop5","DEVTYPE=disk","DISKSEQ=7","SEQNUM=1064"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/bdi/7:6","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:6","SUBSYSTEM=bdi","SEQNUM=1065"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/block/loop6","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop6","SUBSYSTEM=block","MAJOR=7","MINOR=6","DEVNAME=loop6","DEVTYPE=disk","DISKSEQ=8","SEQNUM=1066"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/bdi/7:7","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:7","SUBSYSTEM=bdi","SEQNUM=1067"]}
|
||||||
|
{"action":"add","devpath":"/devices/virtual/block/loop7","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop7","SUBSYSTEM=block","MAJOR=7","MINOR=7","DEVNAME=loop7","DEVTYPE=disk","DISKSEQ=9","SEQNUM=1068"]}
|
||||||
|
{"action":"add","devpath":"/module/loop","env":["ACTION=add","DEVPATH=/module/loop","SUBSYSTEM=module","SEQNUM=1069"]}
|
||||||
|
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1070"]}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1071"]}
|
||||||
|
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","DISK_MEDIA_CHANGE=1","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1072"]}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
{"action":"remove","devpath":"/devices/virtual/misc/loop-control","env":["ACTION=remove","DEVPATH=/devices/virtual/misc/loop-control","SUBSYSTEM=misc","MAJOR=10","MINOR=237","DEVNAME=loop-control","SEQNUM=1073"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/bdi/7:0","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:0","SUBSYSTEM=bdi","SEQNUM=1074"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/block/loop0","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=11","SEQNUM=1075"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/bdi/7:1","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:1","SUBSYSTEM=bdi","SEQNUM=1076"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/block/loop1","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop1","SUBSYSTEM=block","MAJOR=7","MINOR=1","DEVNAME=loop1","DEVTYPE=disk","DISKSEQ=3","SEQNUM=1077"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/bdi/7:2","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:2","SUBSYSTEM=bdi","SEQNUM=1078"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/block/loop2","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop2","SUBSYSTEM=block","MAJOR=7","MINOR=2","DEVNAME=loop2","DEVTYPE=disk","DISKSEQ=4","SEQNUM=1079"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/bdi/7:3","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:3","SUBSYSTEM=bdi","SEQNUM=1080"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/block/loop3","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop3","SUBSYSTEM=block","MAJOR=7","MINOR=3","DEVNAME=loop3","DEVTYPE=disk","DISKSEQ=5","SEQNUM=1081"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/bdi/7:4","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:4","SUBSYSTEM=bdi","SEQNUM=1082"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/block/loop4","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop4","SUBSYSTEM=block","MAJOR=7","MINOR=4","DEVNAME=loop4","DEVTYPE=disk","DISKSEQ=6","SEQNUM=1083"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/bdi/7:5","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:5","SUBSYSTEM=bdi","SEQNUM=1084"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/block/loop5","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop5","SUBSYSTEM=block","MAJOR=7","MINOR=5","DEVNAME=loop5","DEVTYPE=disk","DISKSEQ=7","SEQNUM=1085"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/bdi/7:6","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:6","SUBSYSTEM=bdi","SEQNUM=1086"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/block/loop6","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop6","SUBSYSTEM=block","MAJOR=7","MINOR=6","DEVNAME=loop6","DEVTYPE=disk","DISKSEQ=8","SEQNUM=1087"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/bdi/7:7","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:7","SUBSYSTEM=bdi","SEQNUM=1088"]}
|
||||||
|
{"action":"remove","devpath":"/devices/virtual/block/loop7","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop7","SUBSYSTEM=block","MAJOR=7","MINOR=7","DEVNAME=loop7","DEVTYPE=disk","DISKSEQ=9","SEQNUM=1089"]}
|
||||||
|
{"action":"remove","devpath":"/module/loop","env":["ACTION=remove","DEVPATH=/module/loop","SUBSYSTEM=module","SEQNUM=1090"]}
|
||||||
@@ -40,7 +40,7 @@ func TestTransform(t *testing.T) {
|
|||||||
|
|
||||||
const maxChunkWords = 8 << 10
|
const maxChunkWords = 8 << 10
|
||||||
buf := make([]byte, 2*maxChunkWords*8)
|
buf := make([]byte, 2*maxChunkWords*8)
|
||||||
for i := uint64(0); i < 2*maxChunkWords; i++ {
|
for i := range uint64(2 * maxChunkWords) {
|
||||||
binary.LittleEndian.PutUint64(buf[i*8:], i)
|
binary.LittleEndian.PutUint64(buf[i*8:], i)
|
||||||
}
|
}
|
||||||
if err := lockedfile.Write(path, bytes.NewReader(buf[:8]), 0666); err != nil {
|
if err := lockedfile.Write(path, bytes.NewReader(buf[:8]), 0666); err != nil {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/container/std"
|
"hakurei.app/container/std"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/env"
|
||||||
"hakurei.app/internal/stub"
|
"hakurei.app/internal/stub"
|
||||||
"hakurei.app/internal/system"
|
"hakurei.app/internal/system"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
@@ -174,6 +175,7 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
|||||||
call("cmdOutput", stub.ExpectArgs{container.Nonexistent, os.Stderr, []string{}, "/"}, []byte("0"), nil),
|
call("cmdOutput", stub.ExpectArgs{container.Nonexistent, os.Stderr, []string{}, "/"}, []byte("0"), nil),
|
||||||
call("tempdir", stub.ExpectArgs{}, container.Nonexistent+"/tmp", nil),
|
call("tempdir", stub.ExpectArgs{}, container.Nonexistent+"/tmp", nil),
|
||||||
call("lookupEnv", stub.ExpectArgs{"XDG_RUNTIME_DIR"}, wantRuntimePath, nil),
|
call("lookupEnv", stub.ExpectArgs{"XDG_RUNTIME_DIR"}, wantRuntimePath, nil),
|
||||||
|
call("stat", stub.ExpectArgs{env.VarRunNscd}, stubFileInfoIsDir(true), nil),
|
||||||
call("getuid", stub.ExpectArgs{}, 1000, nil),
|
call("getuid", stub.ExpectArgs{}, 1000, nil),
|
||||||
call("getgid", stub.ExpectArgs{}, 100, nil),
|
call("getgid", stub.ExpectArgs{}, 100, nil),
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ func (k *outcome) finalise(
|
|||||||
supp := make([]string, len(config.Groups))
|
supp := make([]string, len(config.Groups))
|
||||||
for i, name := range config.Groups {
|
for i, name := range config.Groups {
|
||||||
if gid, err := k.lookupGroupId(name); err != nil {
|
if gid, err := k.lookupGroupId(name); err != nil {
|
||||||
var unknownGroupError user.UnknownGroupError
|
if unknownGroupError, ok := errors.AsType[user.UnknownGroupError](err); ok {
|
||||||
if errors.As(err, &unknownGroupError) {
|
|
||||||
return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError)
|
return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError)
|
||||||
} else {
|
} else {
|
||||||
return &hst.AppError{Step: "look up group by name", Err: err, Msg: err.Error()}
|
return &hst.AppError{Step: "look up group by name", Err: err, Msg: err.Error()}
|
||||||
|
|||||||
@@ -51,18 +51,16 @@ func (h *Hsu) ID() (int, error) {
|
|||||||
cmd.Stderr = os.Stderr // pass through fatal messages
|
cmd.Stderr = os.Stderr // pass through fatal messages
|
||||||
cmd.Env = make([]string, 0)
|
cmd.Env = make([]string, 0)
|
||||||
cmd.Dir = fhs.Root
|
cmd.Dir = fhs.Root
|
||||||
var (
|
var p []byte
|
||||||
p []byte
|
|
||||||
exitError *exec.ExitError
|
|
||||||
)
|
|
||||||
|
|
||||||
const step = "obtain uid from hsu"
|
const step = "obtain uid from hsu"
|
||||||
if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil {
|
if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil {
|
||||||
h.id, h.idErr = strconv.Atoi(string(p))
|
h.id, h.idErr = strconv.Atoi(string(p))
|
||||||
if h.idErr != nil {
|
if h.idErr != nil {
|
||||||
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
|
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
|
||||||
}
|
}
|
||||||
} else if errors.As(h.idErr, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
} else if exitError, ok := errors.AsType[*exec.ExitError](h.idErr); ok &&
|
||||||
|
exitError != nil &&
|
||||||
|
exitError.ExitCode() == 1 {
|
||||||
// hsu prints an error message in this case
|
// hsu prints an error message in this case
|
||||||
h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess}
|
h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess}
|
||||||
} else if errors.Is(h.idErr, os.ErrNotExist) {
|
} else if errors.Is(h.idErr, os.ErrNotExist) {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *h
|
|||||||
Paths: env.CopyPathsFunc(k.fatalf, k.tempdir, func(key string) string {
|
Paths: env.CopyPathsFunc(k.fatalf, k.tempdir, func(key string) string {
|
||||||
v, _ := k.lookupEnv(key)
|
v, _ := k.lookupEnv(key)
|
||||||
return v
|
return v
|
||||||
}),
|
}, k.stat),
|
||||||
Container: config.Container,
|
Container: config.Container,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -328,11 +328,11 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := k.sys.Revert((*system.Criteria)(&ec)); err != nil {
|
if err := k.sys.Revert((*system.Criteria)(&ec)); err != nil {
|
||||||
var joinError interface {
|
joinError, ok := errors.AsType[interface {
|
||||||
Unwrap() []error
|
Unwrap() []error
|
||||||
error
|
error
|
||||||
}
|
}](err)
|
||||||
if !errors.As(err, &joinError) || joinError == nil {
|
if !ok || joinError == nil {
|
||||||
perror(err, "revert system setup")
|
perror(err, "revert system setup")
|
||||||
} else {
|
} else {
|
||||||
for _, v := range joinError.Unwrap() {
|
for _, v := range joinError.Unwrap() {
|
||||||
|
|||||||
@@ -136,16 +136,13 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
||||||
|
|
||||||
// spRuntimeOp
|
// spRuntimeOp
|
||||||
|
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/1971"), std.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/1971"), std.BindWritable).
|
||||||
|
|
||||||
// spTmpdirOp
|
// spTmpdirOp
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/9"), fhs.AbsTmp, std.BindWritable).
|
Bind(m("/tmp/hakurei.0/tmpdir/9"), fhs.AbsTmp, std.BindWritable).
|
||||||
|
|
||||||
// spAccountOp
|
|
||||||
Place(m("/etc/passwd"), []byte("chronos:x:1971:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
|
|
||||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
|
||||||
|
|
||||||
// spWaylandOp
|
// spWaylandOp
|
||||||
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1971/wayland-0"), 0).
|
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1971/wayland-0"), 0).
|
||||||
|
|
||||||
@@ -452,7 +449,7 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
|
|
||||||
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
|
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
|
||||||
|
|
||||||
Flags: hst.FUserns | hst.FHostNet | hst.FMapRealUID | hst.FShareRuntime | hst.FShareTmpdir,
|
Flags: hst.FUserns | hst.FHostNet | hst.FMapRealUID | hst.FNoPlace | hst.FShareRuntime | hst.FShareTmpdir,
|
||||||
},
|
},
|
||||||
SystemBus: &hst.BusConfig{
|
SystemBus: &hst.BusConfig{
|
||||||
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
||||||
@@ -547,8 +544,6 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
Tmpfs(m("/run/user/"), xdgRuntimeDirSize, 0755).
|
Tmpfs(m("/run/user/"), xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), std.BindWritable).
|
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), std.BindWritable).
|
||||||
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), std.BindWritable).
|
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), std.BindWritable).
|
||||||
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
|
|
||||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
|
||||||
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
|
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
|
||||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/pipewire"), m("/run/user/1971/pipewire-0"), 0).
|
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/pipewire"), m("/run/user/1971/pipewire-0"), 0).
|
||||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
||||||
@@ -715,7 +710,7 @@ func (k *stubNixOS) lookupEnv(key string) (string, bool) {
|
|||||||
func (k *stubNixOS) stat(name string) (fs.FileInfo, error) {
|
func (k *stubNixOS) stat(name string) (fs.FileInfo, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case "/var/run/nscd":
|
case "/var/run/nscd":
|
||||||
return nil, nil
|
return stubFileInfoIsDir(true), nil
|
||||||
case "/run/user/1971/pulse":
|
case "/run/user/1971/pulse":
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case "/run/user/1971/pulse/native":
|
case "/run/user/1971/pulse/native":
|
||||||
|
|||||||
@@ -390,8 +390,8 @@ func shimEntrypoint(k syscallDispatcher) {
|
|||||||
if err := k.containerWait(z); err != nil {
|
if err := k.containerWait(z); err != nil {
|
||||||
sp.destroy()
|
sp.destroy()
|
||||||
|
|
||||||
var exitError *exec.ExitError
|
exitError, ok := errors.AsType[*exec.ExitError](err)
|
||||||
if !errors.As(err, &exitError) {
|
if !ok {
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
k.exit(hst.ExitCancel)
|
k.exit(hst.ExitCancel)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,16 +71,13 @@ func TestShimEntrypoint(t *testing.T) {
|
|||||||
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
||||||
|
|
||||||
// spRuntimeOp
|
// spRuntimeOp
|
||||||
|
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/tmp/hakurei.10/runtime/9999"), m("/run/user/1000"), std.BindWritable).
|
Bind(m("/tmp/hakurei.10/runtime/9999"), m("/run/user/1000"), std.BindWritable).
|
||||||
|
|
||||||
// spTmpdirOp
|
// spTmpdirOp
|
||||||
Bind(m("/tmp/hakurei.10/tmpdir/9999"), fhs.AbsTmp, std.BindWritable).
|
Bind(m("/tmp/hakurei.10/tmpdir/9999"), fhs.AbsTmp, std.BindWritable).
|
||||||
|
|
||||||
// spAccountOp
|
|
||||||
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
|
|
||||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
|
||||||
|
|
||||||
// spWaylandOp
|
// spWaylandOp
|
||||||
Bind(m("/tmp/hakurei.10/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1000/wayland-0"), 0).
|
Bind(m("/tmp/hakurei.10/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1000/wayland-0"), 0).
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/validate"
|
"hakurei.app/internal/validate"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,16 +42,18 @@ func (s spAccountOp) toContainer(state *outcomeStateParams) error {
|
|||||||
state.env["USER"] = username
|
state.env["USER"] = username
|
||||||
state.env["SHELL"] = state.Container.Shell.String()
|
state.env["SHELL"] = state.Container.Shell.String()
|
||||||
|
|
||||||
state.params.
|
if state.Container.Flags&hst.FNoPlace == 0 {
|
||||||
Place(fhs.AbsEtc.Append("passwd"),
|
state.params.
|
||||||
[]byte(username+":x:"+
|
Place(fhs.AbsEtc.Append("passwd"),
|
||||||
state.mapuid.String()+":"+
|
[]byte(username+":x:"+
|
||||||
state.mapgid.String()+
|
state.mapuid.String()+":"+
|
||||||
":Hakurei:"+
|
state.mapgid.String()+
|
||||||
state.Container.Home.String()+":"+
|
":Hakurei:"+
|
||||||
state.Container.Shell.String()+"\n")).
|
state.Container.Home.String()+":"+
|
||||||
Place(fhs.AbsEtc.Append("group"),
|
state.Container.Shell.String()+"\n")).
|
||||||
[]byte("hakurei:x:"+state.mapgid.String()+":\n"))
|
Place(fhs.AbsEtc.Append("group"),
|
||||||
|
[]byte("hakurei:x:"+state.mapgid.String()+":\n"))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func TestSpAccountOp(t *testing.T) {
|
|||||||
{"success fallback username", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config {
|
{"success fallback username", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config {
|
||||||
c := hst.Template()
|
c := hst.Template()
|
||||||
c.Container.Username = ""
|
c.Container.Username = ""
|
||||||
|
c.Container.Flags = hst.FMapRealUID
|
||||||
return c
|
return c
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
// this op performs basic validation and does not make calls during toSystem
|
// this op performs basic validation and does not make calls during toSystem
|
||||||
@@ -60,9 +61,7 @@ func TestSpAccountOp(t *testing.T) {
|
|||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Dir: config.Container.Home,
|
Dir: config.Container.Home,
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops),
|
||||||
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
|
|
||||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")),
|
|
||||||
}, paramsWantEnv(config, map[string]string{
|
}, paramsWantEnv(config, map[string]string{
|
||||||
"HOME": config.Container.Home.String(),
|
"HOME": config.Container.Home.String(),
|
||||||
"USER": config.Container.Username,
|
"USER": config.Container.Username,
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ import (
|
|||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/acl"
|
"hakurei.app/internal/acl"
|
||||||
"hakurei.app/internal/dbus"
|
"hakurei.app/internal/dbus"
|
||||||
|
"hakurei.app/internal/env"
|
||||||
"hakurei.app/internal/system"
|
"hakurei.app/internal/system"
|
||||||
"hakurei.app/internal/validate"
|
"hakurei.app/internal/validate"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
const varRunNscd = fhs.Var + "run/nscd"
|
|
||||||
|
|
||||||
func init() { gob.Register(new(spParamsOp)) }
|
func init() { gob.Register(new(spParamsOp)) }
|
||||||
|
|
||||||
// spParamsOp initialises unordered fields of [container.Params] and the
|
// spParamsOp initialises unordered fields of [container.Params] and the
|
||||||
@@ -136,17 +135,23 @@ type spFilesystemOp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
||||||
/* retrieve paths and hide them if they're made available in the sandbox;
|
// retrieve paths and hide them if they're made available in the sandbox
|
||||||
|
//
|
||||||
this feature tries to improve user experience of permissive defaults, and
|
// this feature tries to improve user experience of permissive defaults, and
|
||||||
to warn about issues in custom configuration; it is NOT a security feature
|
// to warn about issues in custom configuration; it is NOT a security feature
|
||||||
and should not be treated as such, ALWAYS be careful with what you bind */
|
// and should not be treated as such, ALWAYS be careful with what you bind
|
||||||
hidePaths := []string{
|
hidePaths := []string{
|
||||||
state.sc.RuntimePath.String(),
|
state.sc.RuntimePath.String(),
|
||||||
state.sc.SharePath.String(),
|
state.sc.SharePath.String(),
|
||||||
|
}
|
||||||
|
|
||||||
// this causes emulated passwd database to be bypassed on some /etc/ setups
|
if state.Paths == nil || state.HasNscd {
|
||||||
varRunNscd,
|
hidePaths = append(hidePaths,
|
||||||
|
// this causes emulated passwd database to be bypassed on some /etc/
|
||||||
|
// setups, made optional to avoid needlessly creating it on
|
||||||
|
// non-glibc systems when invoking permissive defaults
|
||||||
|
env.VarRunNscd,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dbus.Address does not go through syscallDispatcher
|
// dbus.Address does not go through syscallDispatcher
|
||||||
@@ -382,6 +387,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...)}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ func (s *spRuntimeOp) toContainer(state *outcomeStateParams) error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state.Container.Flags&hst.FCoverRun != 0 {
|
||||||
|
state.params.Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755)
|
||||||
|
}
|
||||||
state.params.Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755)
|
state.params.Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755)
|
||||||
if state.Container.Flags&hst.FShareRuntime != 0 {
|
if state.Container.Flags&hst.FShareRuntime != 0 {
|
||||||
_, runtimeDirInst := s.commonPaths(state.outcomeState)
|
_, runtimeDirInst := s.commonPaths(state.outcomeState)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ func TestSpRuntimeOp(t *testing.T) {
|
|||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
|
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||||
}, paramsWantEnv(config, map[string]string{
|
}, paramsWantEnv(config, map[string]string{
|
||||||
@@ -67,6 +68,7 @@ func TestSpRuntimeOp(t *testing.T) {
|
|||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
|
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||||
}, paramsWantEnv(config, map[string]string{
|
}, paramsWantEnv(config, map[string]string{
|
||||||
@@ -94,6 +96,7 @@ func TestSpRuntimeOp(t *testing.T) {
|
|||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
|
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||||
}, paramsWantEnv(config, map[string]string{
|
}, paramsWantEnv(config, map[string]string{
|
||||||
@@ -117,6 +120,7 @@ func TestSpRuntimeOp(t *testing.T) {
|
|||||||
// this op configures the container state and does not make calls during toContainer
|
// this op configures the container state and does not make calls during toContainer
|
||||||
}, &container.Params{
|
}, &container.Params{
|
||||||
Ops: new(container.Ops).
|
Ops: new(container.Ops).
|
||||||
|
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||||
}, paramsWantEnv(config, map[string]string{
|
}, paramsWantEnv(config, map[string]string{
|
||||||
|
|||||||
@@ -176,8 +176,8 @@ func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
|
|||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
data = SPA_TYPE_Struct.append(data)
|
data = SPA_TYPE_Struct.append(data)
|
||||||
var err error
|
var err error
|
||||||
for i := 0; i < v.NumField(); i++ {
|
for _, field := range v.Fields() {
|
||||||
data, err = marshalValueAppend(data, v.Field(i))
|
data, err = marshalValueAppend(data, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
@@ -370,8 +370,8 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var fieldWireSize Word
|
var fieldWireSize Word
|
||||||
for i := 0; i < v.NumField(); i++ {
|
for _, field := range v.Fields() {
|
||||||
if err := unmarshalValue(data, v.Field(i), &fieldWireSize); err != nil {
|
if err := unmarshalValue(data, field, &fieldWireSize); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// bounds check completed in successful call to unmarshalValue
|
// bounds check completed in successful call to unmarshalValue
|
||||||
|
|||||||
@@ -0,0 +1,411 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
| mode uint32 | path_sz uint32 |
|
||||||
|
| data_sz uint64 |
|
||||||
|
| path string |
|
||||||
|
| data []byte |
|
||||||
|
*/
|
||||||
|
|
||||||
|
// An ArchiveHeader represents a single header in an archive.
|
||||||
|
type ArchiveHeader struct {
|
||||||
|
Mode fs.FileMode // file mode bits
|
||||||
|
Path string // pathname of the file
|
||||||
|
Size uint64 // size of data segment
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer implements sequential writing of an archive. [Writer.WriteHeader]
|
||||||
|
// begins a new file with the provided [ArchiveHeader], and then Writer can be
|
||||||
|
// treated as an [io.Writer] to supply that file's data.
|
||||||
|
//
|
||||||
|
// It is the caller's responsibility to write entries in lexical order.
|
||||||
|
type Writer struct {
|
||||||
|
// Underlying writer.
|
||||||
|
w io.Writer
|
||||||
|
// Current header.
|
||||||
|
h ArchiveHeader
|
||||||
|
// Fixed-size header segment.
|
||||||
|
buf [wordSize * 2]byte
|
||||||
|
// Current position in data segment.
|
||||||
|
n uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter returns the address of a new [Writer] writing to w.
|
||||||
|
func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
|
||||||
|
|
||||||
|
var zero [wordSize]byte
|
||||||
|
|
||||||
|
// padSize returns the padding size for aligning sz.
|
||||||
|
func padSize[T int | uint64](sz T) T {
|
||||||
|
return (wordSize - (sz)%wordSize) % wordSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush concludes writing to the current file and writes padding.
|
||||||
|
func (aw *Writer) flush() error {
|
||||||
|
if aw.h.Size > aw.n {
|
||||||
|
return fmt.Errorf("missed writing %d bytes", aw.h.Size-aw.n)
|
||||||
|
} else if aw.h.Size < aw.n {
|
||||||
|
return fmt.Errorf("wrote %d bytes beyond end of file", aw.n-aw.h.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
if psz := padSize(aw.h.Size); psz != 0 {
|
||||||
|
if _, err := aw.w.Write(zero[:psz]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aw.n = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader writes h and begins accepting its corresponding file.
|
||||||
|
func (aw *Writer) WriteHeader(h *ArchiveHeader) error {
|
||||||
|
if err := aw.flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
aw.h = *h
|
||||||
|
binary.LittleEndian.PutUint32(aw.buf[:], uint32(aw.h.Mode))
|
||||||
|
binary.LittleEndian.PutUint32(aw.buf[wordSize/2:], uint32(len(aw.h.Path)))
|
||||||
|
binary.LittleEndian.PutUint64(aw.buf[wordSize:], aw.h.Size)
|
||||||
|
if _, err := aw.w.Write(aw.buf[:]); err != nil {
|
||||||
|
return err
|
||||||
|
} else if _, err = aw.w.Write(
|
||||||
|
unsafe.Slice(unsafe.StringData(aw.h.Path), len(aw.h.Path)),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
} else if psz := padSize(len(aw.h.Path)); psz != 0 {
|
||||||
|
if _, err = aw.w.Write(zero[:psz]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes p to the underlying writer and records the new position. Invalid
|
||||||
|
// positions are reported by WriteHeader and Close.
|
||||||
|
func (aw *Writer) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = aw.w.Write(p)
|
||||||
|
aw.n += uint64(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close concludes writing to the archive stream.
|
||||||
|
func (aw *Writer) Close() (err error) {
|
||||||
|
err = aw.flush()
|
||||||
|
aw.w = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInsecurePath is returned by [FlatEntry.Decode] if validation is requested
|
||||||
|
// and a nonlocal path is encountered in the stream.
|
||||||
|
var ErrInsecurePath = errors.New("insecure file path")
|
||||||
|
|
||||||
|
// Reader implements sequential reading of an archive. [Reader.Next] advances to
|
||||||
|
// the next file in the archive (including the first), and then Reader can be
|
||||||
|
// treated as an [io.Reader] to access the file's data.
|
||||||
|
type Reader struct {
|
||||||
|
// Underlying reader.
|
||||||
|
r io.Reader
|
||||||
|
// Fixed-size header segment.
|
||||||
|
buf [wordSize * 2]byte
|
||||||
|
// Remaining bytes in current data segment.
|
||||||
|
n, pad uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns the address of a new [Reader] reading from r.
|
||||||
|
func NewReader(r io.Reader) *Reader { return &Reader{r: r} }
|
||||||
|
|
||||||
|
// Next advances ar to the next entry. Remaining bytes of the current data
|
||||||
|
// segment are discarded. Advancing beyond the final entry returns [io.EOF].
|
||||||
|
func (ar *Reader) Next() (*ArchiveHeader, error) {
|
||||||
|
if dsz := int64(ar.n + ar.pad); dsz > 0 {
|
||||||
|
if n, err := io.CopyN(io.Discard, ar.r, dsz); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) && n != dsz {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(ar.r, ar.buf[:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := ArchiveHeader{
|
||||||
|
Mode: fs.FileMode(binary.LittleEndian.Uint32(ar.buf[:])),
|
||||||
|
Size: binary.LittleEndian.Uint64(ar.buf[wordSize:]),
|
||||||
|
}
|
||||||
|
pathSize := int(binary.LittleEndian.Uint32(ar.buf[wordSize/2:]))
|
||||||
|
pPathSize := alignSize(pathSize)
|
||||||
|
|
||||||
|
buf := make([]byte, pPathSize)
|
||||||
|
if _, err := io.ReadFull(ar.r, buf); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Path = unsafe.String(unsafe.SliceData(buf), pathSize)
|
||||||
|
if !filepath.IsLocal(h.Path) {
|
||||||
|
return &h, ErrInsecurePath
|
||||||
|
}
|
||||||
|
|
||||||
|
ar.n = h.Size
|
||||||
|
ar.pad = padSize(h.Size)
|
||||||
|
return &h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements [io.Reader] for the data segment of the current entry.
|
||||||
|
func (ar *Reader) Read(p []byte) (n int, err error) {
|
||||||
|
if uint64(len(p)) > ar.n {
|
||||||
|
p = p[:ar.n]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p) > 0 {
|
||||||
|
n, err = ar.r.Read(p)
|
||||||
|
ar.n -= uint64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch err {
|
||||||
|
case io.EOF:
|
||||||
|
if ar.n > 0 {
|
||||||
|
return n, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
case nil:
|
||||||
|
if ar.n == 0 {
|
||||||
|
return n, io.EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes a deterministic representation of the contents of fsys to w.
|
||||||
|
// The resulting data can be hashed to produce a deterministic checksum for the
|
||||||
|
// directory.
|
||||||
|
func Write(fsys fs.FS, root string, w io.Writer) error {
|
||||||
|
aw := NewWriter(w)
|
||||||
|
if err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fi fs.FileInfo
|
||||||
|
fi, err = d.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := ArchiveHeader{
|
||||||
|
Path: path,
|
||||||
|
Mode: fi.Mode(),
|
||||||
|
}
|
||||||
|
if h.Mode.IsRegular() {
|
||||||
|
h.Size = uint64(fi.Size())
|
||||||
|
if err = aw.WriteHeader(&h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r fs.File
|
||||||
|
r, err = fsys.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(aw, r)
|
||||||
|
if _err := r.Close(); err == nil {
|
||||||
|
err = _err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
} else if h.Mode&fs.ModeSymlink != 0 {
|
||||||
|
var newpath string
|
||||||
|
if newpath, err = fs.ReadLink(fsys, path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Size = uint64(len(newpath))
|
||||||
|
if err = aw.WriteHeader(&h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = aw.Write(unsafe.Slice(unsafe.StringData(newpath), len(newpath)))
|
||||||
|
return err
|
||||||
|
} else if !h.Mode.IsDir() {
|
||||||
|
return InvalidFileModeError(h.Mode)
|
||||||
|
}
|
||||||
|
return aw.WriteHeader(&h)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return aw.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SumFS saves checksum of the archive of fsys to the value pointed to by buf.
|
||||||
|
func SumFS(buf *Checksum, fsys fs.FS, root string) error {
|
||||||
|
h := sha512.New384()
|
||||||
|
if err := Write(fsys, root, h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.Sum(buf[:0])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SumDir saves checksum of the archive of directory at pathname to the value
|
||||||
|
// pointed to by buf.
|
||||||
|
func SumDir(buf *Checksum, pathname *check.Absolute) error {
|
||||||
|
return SumFS(buf, os.DirFS(pathname.String()), ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
// archiveArtifact is an [Artifact] unpacking an archive supported by [Reader]
|
||||||
|
// backed by a [FileArtifact].
|
||||||
|
type archiveArtifact struct {
|
||||||
|
// Caller-supplied backing archive.
|
||||||
|
f Artifact
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ CuresExempt = archiveArtifact{}
|
||||||
|
|
||||||
|
// NewArchive returns a new [Artifact] backed by the supplied [Artifact]. The
|
||||||
|
// source [Artifact] must be a [FileArtifact] and produce a stream compatible
|
||||||
|
// with [Reader].
|
||||||
|
func NewArchive(a Artifact) Artifact {
|
||||||
|
return archiveArtifact{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kind returns the hardcoded [Kind] constant.
|
||||||
|
func (archiveArtifact) Kind() Kind { return KindArchive }
|
||||||
|
|
||||||
|
// Params is a noop.
|
||||||
|
func (archiveArtifact) Params(*IContext) {}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register(KindArchive, func(r *IRReader) Artifact {
|
||||||
|
a := NewArchive(r.Next())
|
||||||
|
if _, ok := r.Finalise(); ok {
|
||||||
|
panic(ErrUnexpectedChecksum)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inputs returns a slice containing the backing file.
|
||||||
|
func (a archiveArtifact) Inputs() []Artifact {
|
||||||
|
return []Artifact{a.f}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExclusive returns false: [Reader] is fully sequential.
|
||||||
|
func (archiveArtifact) IsExclusive() bool { return false }
|
||||||
|
|
||||||
|
// Cure cures the [Artifact], producing a directory located at work.
|
||||||
|
func (a archiveArtifact) Cure(t *TContext) (err error) {
|
||||||
|
var r io.ReadCloser
|
||||||
|
if r, err = t.Open(a.f); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
closeErr := r.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
type dirTargetPerm struct {
|
||||||
|
path string
|
||||||
|
mode fs.FileMode
|
||||||
|
}
|
||||||
|
var madeDirectories []dirTargetPerm
|
||||||
|
|
||||||
|
if err = os.MkdirAll(t.GetWorkDir().String(), 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var root *os.Root
|
||||||
|
if root, err = os.OpenRoot(t.GetWorkDir().String()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
closeErr := root.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var header *ArchiveHeader
|
||||||
|
ar := NewReader(r)
|
||||||
|
for header, err = ar.Next(); err == nil; header, err = ar.Next() {
|
||||||
|
if header.Mode.IsRegular() {
|
||||||
|
var f *os.File
|
||||||
|
if f, err = root.OpenFile(
|
||||||
|
header.Path,
|
||||||
|
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
|
||||||
|
header.Mode.Perm(),
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(f, ar); err != nil {
|
||||||
|
_ = f.Close()
|
||||||
|
return
|
||||||
|
} else if err = f.Close(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if header.Mode&fs.ModeSymlink != 0 {
|
||||||
|
var p []byte
|
||||||
|
if p, err = io.ReadAll(ar); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = root.Symlink(
|
||||||
|
unsafe.String(unsafe.SliceData(p), len(p)),
|
||||||
|
header.Path,
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if header.Mode.IsDir() {
|
||||||
|
if header.Path == "." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
madeDirectories = append(madeDirectories, dirTargetPerm{
|
||||||
|
path: header.Path,
|
||||||
|
mode: header.Mode,
|
||||||
|
})
|
||||||
|
if err = root.Mkdir(header.Path, 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return InvalidFileModeError(header.Mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
for _, e := range madeDirectories {
|
||||||
|
if err = root.Chmod(e.path, e.mode.Perm()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CuresExempt exempts the cheap [KindArchive] implementation often found at
|
||||||
|
// the end of a [FileArtifact] pipeline.
|
||||||
|
func (archiveArtifact) CuresExempt() {}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
package pkg_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"maps"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArchive(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
path string
|
||||||
|
mode fs.FileMode
|
||||||
|
data string
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
fsys fs.FS
|
||||||
|
entries []entry
|
||||||
|
sum pkg.Checksum
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{"bad type", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
"invalid": {Mode: fs.ModeCharDevice | 0400},
|
||||||
|
}, nil, pkg.Checksum{}, pkg.InvalidFileModeError(
|
||||||
|
fs.ModeCharDevice | 0400,
|
||||||
|
)},
|
||||||
|
|
||||||
|
{"coldboot", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"devices": {Mode: fs.ModeDir | 0700},
|
||||||
|
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||||
|
"devices/empty": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"devices/sub": {Mode: fs.ModeDir | 0700},
|
||||||
|
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||||
|
|
||||||
|
"block": {Mode: fs.ModeDir | 0700},
|
||||||
|
"block/uevent": {Mode: 0600},
|
||||||
|
}, []entry{
|
||||||
|
{".", fs.ModeDir | 0700, ""},
|
||||||
|
|
||||||
|
{"block", fs.ModeDir | 0700, ""},
|
||||||
|
{"block/uevent", 0600, ""},
|
||||||
|
|
||||||
|
{"devices", fs.ModeDir | 0700, ""},
|
||||||
|
{"devices/empty", fs.ModeDir | 0700, ""},
|
||||||
|
{"devices/sub", fs.ModeDir | 0700, ""},
|
||||||
|
{"devices/sub/uevent", 0600, "add"},
|
||||||
|
{"devices/uevent", 0600, "add"},
|
||||||
|
}, pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8"), nil},
|
||||||
|
|
||||||
|
{"empty", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, []entry{
|
||||||
|
{".", fs.ModeDir | 0700, ""},
|
||||||
|
{"checksum", fs.ModeDir | 0700, ""},
|
||||||
|
{"identifier", fs.ModeDir | 0700, ""},
|
||||||
|
{"work", fs.ModeDir | 0700, ""},
|
||||||
|
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C"), nil},
|
||||||
|
|
||||||
|
{"sample directory step garbage", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"lib": {Mode: fs.ModeDir | 0500},
|
||||||
|
"lib/check": {Mode: 0400},
|
||||||
|
|
||||||
|
"lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
||||||
|
}, []entry{
|
||||||
|
{".", fs.ModeDir | 0500, ""},
|
||||||
|
|
||||||
|
{"lib", fs.ModeDir | 0500, ""},
|
||||||
|
{"lib/check", 0400, ""},
|
||||||
|
|
||||||
|
{"lib/pkgconfig", fs.ModeDir | 0500, ""},
|
||||||
|
}, pkg.MustDecode("CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT"), nil},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("roundtrip", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := pkg.Write(
|
||||||
|
tc.fsys,
|
||||||
|
".",
|
||||||
|
&buf,
|
||||||
|
); !reflect.DeepEqual(err, tc.err) {
|
||||||
|
t.Fatalf("Flatten: error = %v, want %v", err, tc.err)
|
||||||
|
} else if tc.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r := pkg.NewReader(bytes.NewReader(buf.Bytes()))
|
||||||
|
var got []entry
|
||||||
|
for {
|
||||||
|
h, err := r.Next()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
t.Fatalf("Next: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
if data, err = io.ReadAll(r); err != nil {
|
||||||
|
t.Fatalf("Read: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got = append(got, entry{
|
||||||
|
path: h.Path,
|
||||||
|
mode: h.Mode,
|
||||||
|
data: unsafe.String(unsafe.SliceData(data), len(data)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, tc.entries) {
|
||||||
|
t.Fatalf("Reader: %#v, want %#v", got, tc.entries)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if tc.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("hash", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var got pkg.Checksum
|
||||||
|
if err := pkg.SumFS(&got, tc.fsys, "."); err != nil {
|
||||||
|
t.Fatalf("SumFS: error = %v", err)
|
||||||
|
} else if got != tc.sum {
|
||||||
|
t.Fatalf("SumFS: %v", &pkg.ChecksumMismatchError{
|
||||||
|
Got: got,
|
||||||
|
Want: tc.sum,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var archiveTestdata = fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"devices": {Mode: fs.ModeDir | 0700},
|
||||||
|
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||||
|
"devices/empty": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"devices/sub": {Mode: fs.ModeDir | 0700},
|
||||||
|
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||||
|
|
||||||
|
"block": {Mode: fs.ModeDir | 0700},
|
||||||
|
"block/uevent": {Mode: 0600},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArchiveArtifact(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
want := maps.Clone(archiveTestdata)
|
||||||
|
want["."].Mode = fs.ModeDir | 0500
|
||||||
|
|
||||||
|
checkWithCache(t, []cacheTestCase{
|
||||||
|
{"unpack", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cureMany(t, c, []cureStep{
|
||||||
|
{"sample", pkg.NewArchive(
|
||||||
|
pkg.NewFile("", buf.Bytes()),
|
||||||
|
), ignorePathname, expectsFS(want), pkg.WNew, nil},
|
||||||
|
})
|
||||||
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block/uevent": {Mode: 0600},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/empty": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||||
|
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/-zXKfphyWM2Ko7VMVURaEMSuixNokFSe0xFbnwR2RtRtvMrcPGWalV0LIn45PXTY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkArchiveRead(b *testing.B) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
testdata := buf.Bytes()
|
||||||
|
|
||||||
|
for b.Loop() {
|
||||||
|
r := pkg.NewReader(bytes.NewReader(testdata))
|
||||||
|
for {
|
||||||
|
_, err := r.Next()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkArchiveWrite(b *testing.B) {
|
||||||
|
for b.Loop() {
|
||||||
|
if err := pkg.Write(archiveTestdata, ".", io.Discard); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"unique"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clean destroys checksum backing entries without any identifier or substitute
|
||||||
|
// entry referring to it. If at least one keep [Artifact] is specified,
|
||||||
|
// identifier and substitute entries not kept alive by them are destroyed first.
|
||||||
|
func (c *Cache) Clean(dry, inputs bool, keep ...Artifact) (
|
||||||
|
[]unique.Handle[ID],
|
||||||
|
[]unique.Handle[Checksum],
|
||||||
|
error,
|
||||||
|
) {
|
||||||
|
c.identMu.Lock()
|
||||||
|
defer c.identMu.Unlock()
|
||||||
|
|
||||||
|
c.checksumMu.Lock()
|
||||||
|
defer c.checksumMu.Unlock()
|
||||||
|
|
||||||
|
dents, err := os.ReadDir(c.base.Append(dirChecksum).String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
checksums := make(map[unique.Handle[Checksum]]string, len(dents))
|
||||||
|
var buf Checksum
|
||||||
|
for _, dent := range dents {
|
||||||
|
name := dent.Name()
|
||||||
|
if err = Decode(&buf, name); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
checksums[unique.Make(buf)] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
type identPair struct {
|
||||||
|
id unique.Handle[ID]
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
dents, err = os.ReadDir(c.base.Append(dirIdentifier).String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keepIdents := make(map[unique.Handle[ID]]struct{})
|
||||||
|
if inputs {
|
||||||
|
for _, id := range Inputs((*Collect)(&keep)) {
|
||||||
|
keepIdents[id] = struct{}{}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, a := range keep {
|
||||||
|
keepIdents[c.Ident(a)] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idents := make([]identPair, 0, len(dents))
|
||||||
|
for _, dent := range dents {
|
||||||
|
name := dent.Name()
|
||||||
|
if err = Decode(&buf, name); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
id := unique.Make(ID(buf))
|
||||||
|
|
||||||
|
if _, ok := keepIdents[id]; len(keep) == 0 || ok {
|
||||||
|
if err = readlinkChecksum(c.base.Append(
|
||||||
|
dirIdentifier,
|
||||||
|
name,
|
||||||
|
), &buf); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
delete(checksums, unique.Make(buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c.msg.Verbosef(
|
||||||
|
"arranging for destruction of %s%s%s...",
|
||||||
|
c.sgrIdent, name, c.sgrRes,
|
||||||
|
)
|
||||||
|
idents = append(idents, identPair{id, name})
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyedIdents := make([]unique.Handle[ID], 0, len(idents))
|
||||||
|
for _, pair := range idents {
|
||||||
|
if !dry {
|
||||||
|
if err = os.Remove(c.base.Append(
|
||||||
|
dirStatus,
|
||||||
|
pair.name,
|
||||||
|
).String()); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return destroyedIdents, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.Remove(c.base.Append(
|
||||||
|
dirIdentifier,
|
||||||
|
pair.name,
|
||||||
|
).String()); err != nil {
|
||||||
|
return destroyedIdents, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destroyedIdents = append(destroyedIdents, pair.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyedChecksums := make([]unique.Handle[Checksum], 0, len(checksums))
|
||||||
|
for checksum, name := range checksums {
|
||||||
|
if err = c.parent.Err(); err != nil {
|
||||||
|
return destroyedIdents, destroyedChecksums, err
|
||||||
|
}
|
||||||
|
c.msg.Verbosef(
|
||||||
|
"destroying checksum %s%s%s...",
|
||||||
|
c.sgrIdent, name, c.sgrRes,
|
||||||
|
)
|
||||||
|
if !dry {
|
||||||
|
if err = errors.Join(removeAll(c.base.Append(
|
||||||
|
dirChecksum,
|
||||||
|
name,
|
||||||
|
))); err != nil {
|
||||||
|
return destroyedIdents, destroyedChecksums, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destroyedChecksums = append(destroyedChecksums, checksum)
|
||||||
|
}
|
||||||
|
|
||||||
|
dents, err = os.ReadDir(c.base.Append(dirSubstitute).String())
|
||||||
|
if err != nil {
|
||||||
|
return destroyedIdents, destroyedChecksums, err
|
||||||
|
}
|
||||||
|
for _, dent := range dents {
|
||||||
|
name := dent.Name()
|
||||||
|
if err = readlinkChecksum(c.base.Append(
|
||||||
|
dirSubstitute,
|
||||||
|
name,
|
||||||
|
), &buf); err != nil {
|
||||||
|
return destroyedIdents, destroyedChecksums, err
|
||||||
|
}
|
||||||
|
if _, ok := checksums[unique.Make(buf)]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c.msg.Verbosef(
|
||||||
|
"destroying substitute %s%s%s...",
|
||||||
|
c.sgrIdent, name, c.sgrRes,
|
||||||
|
)
|
||||||
|
if !dry {
|
||||||
|
if err = os.Remove(c.base.Append(
|
||||||
|
dirStatus,
|
||||||
|
name,
|
||||||
|
).String()); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return destroyedIdents, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.Remove(c.base.Append(
|
||||||
|
dirSubstitute,
|
||||||
|
name,
|
||||||
|
).String()); err != nil {
|
||||||
|
return destroyedIdents, destroyedChecksums, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return destroyedIdents, destroyedChecksums, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
package pkg_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha512"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"unique"
|
||||||
|
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// formatHandles returns a user-facing string representing h.
|
||||||
|
func formatHandles[T pkg.ID | pkg.Checksum](handles ...unique.Handle[T]) string {
|
||||||
|
var buf strings.Builder
|
||||||
|
for _, h := range handles {
|
||||||
|
buf.WriteString(pkg.Encode(pkg.Checksum(h.Value())))
|
||||||
|
buf.WriteString(", ")
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(buf.String(), ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClean(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ic := pkg.NewIR()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
a []pkg.Artifact
|
||||||
|
keep []pkg.Artifact
|
||||||
|
inputs bool
|
||||||
|
want expectsFS
|
||||||
|
|
||||||
|
wantIdents []unique.Handle[pkg.ID]
|
||||||
|
wantChecksums []unique.Handle[pkg.Checksum]
|
||||||
|
}{
|
||||||
|
{"simple", []pkg.Artifact{
|
||||||
|
pkg.NewFile("file", nil),
|
||||||
|
}, nil, false, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/h6qx7m0vsyhO6oLEJgY2rKa1c6RZuIZ4QXxZSh__NZJ9VU0MIj5bFB-KuaPcsfpE": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
|
|
||||||
|
"lock": {Mode: 0644},
|
||||||
|
"variant": {Mode: 0400},
|
||||||
|
"status": {Mode: fs.ModeDir | 0700},
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
"fault": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, nil, nil},
|
||||||
|
|
||||||
|
{"keep", []pkg.Artifact{
|
||||||
|
pkg.NewFile("removed-file", []byte("removed file")),
|
||||||
|
}, []pkg.Artifact{
|
||||||
|
pkg.NewFile("file", []byte("\xfd")),
|
||||||
|
}, false, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/KgZ-FjbGuU-XP2QEHInpgv-2Zn0cTH5NqFMgTU0XrSdKmSwyC-3baVs1BMCP5spk": {Mode: 0400, Data: []byte("\xfd")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/tkrOYhQfTRaArk4nNYNjTmHQUmuJMVIoDeAKIQBm3I4uuMimBKXKMIY2gGZqwQg4": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/KgZ-FjbGuU-XP2QEHInpgv-2Zn0cTH5NqFMgTU0XrSdKmSwyC-3baVs1BMCP5spk")},
|
||||||
|
|
||||||
|
"lock": {Mode: 0644},
|
||||||
|
"variant": {Mode: 0400},
|
||||||
|
"status": {Mode: fs.ModeDir | 0700},
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
"fault": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, []unique.Handle[pkg.ID]{
|
||||||
|
ic.Ident(pkg.NewFile("removed-file", []byte("removed file"))),
|
||||||
|
}, []unique.Handle[pkg.Checksum]{
|
||||||
|
unique.Make(sha512.Sum384([]byte("removed file"))),
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"inputs anchored substitute", []pkg.Artifact{
|
||||||
|
&stubArtifactF{
|
||||||
|
kind: pkg.KindExec,
|
||||||
|
params: []byte("destroyed"),
|
||||||
|
deps: []pkg.Artifact{
|
||||||
|
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||||
|
},
|
||||||
|
cure: func(f *pkg.FContext) error {
|
||||||
|
p := f.GetWorkDir()
|
||||||
|
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(p.Append("result").String(), nil, 0444)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []pkg.Artifact{
|
||||||
|
&stubArtifactF{
|
||||||
|
kind: pkg.KindExec,
|
||||||
|
params: []byte("kept"),
|
||||||
|
deps: []pkg.Artifact{
|
||||||
|
pkg.NewFile("kept-input", []byte("kept")),
|
||||||
|
},
|
||||||
|
cure: func(f *pkg.FContext) error {
|
||||||
|
p := f.GetWorkDir()
|
||||||
|
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(p.Append("result").String(), nil, 0444)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, true, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv": {Mode: 0400, Data: []byte("kept")},
|
||||||
|
"checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE/result": {Mode: 0444},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/KpxTUsUfGbsX6C6HU2j9cd9He3_tt2o2m2MgH4m0zial8lmoqSWY-Gc3KrS0k-UK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv")},
|
||||||
|
"identifier/xxQ43VMIS1WHMSEFnUdBNd6pulbtgiTorP_mtJpEsZENYhGBiDEL6Y2D8yqn0Sra": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||||
|
|
||||||
|
"lock": {Mode: 0644},
|
||||||
|
"variant": {Mode: 0400},
|
||||||
|
"status": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
"substitute/dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||||
|
"substitute/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||||
|
|
||||||
|
"fault": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, []unique.Handle[pkg.ID]{
|
||||||
|
ic.Ident(pkg.NewFile("destroyed-input", []byte("destroyed"))),
|
||||||
|
ic.Ident(&stubArtifactF{
|
||||||
|
kind: pkg.KindExec,
|
||||||
|
params: []byte("destroyed"),
|
||||||
|
deps: []pkg.Artifact{
|
||||||
|
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}, []unique.Handle[pkg.Checksum]{
|
||||||
|
unique.Make(sha512.Sum384([]byte("destroyed"))),
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"inputs", []pkg.Artifact{
|
||||||
|
&stubArtifactF{
|
||||||
|
kind: pkg.KindExec,
|
||||||
|
params: []byte("destroyed"),
|
||||||
|
deps: []pkg.Artifact{
|
||||||
|
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||||
|
},
|
||||||
|
cure: func(f *pkg.FContext) error {
|
||||||
|
if w, err := f.GetStatusWriter(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if _, err = w.Write([]byte("destroyed")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := f.GetWorkDir()
|
||||||
|
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(p.Append("result").String(), nil, 0444)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []pkg.Artifact{
|
||||||
|
&stubArtifactF{
|
||||||
|
kind: pkg.KindExec,
|
||||||
|
params: []byte("kept"),
|
||||||
|
deps: []pkg.Artifact{
|
||||||
|
pkg.NewFile("kept-input", []byte("kept")),
|
||||||
|
},
|
||||||
|
cure: func(f *pkg.FContext) error {
|
||||||
|
if w, err := f.GetStatusWriter(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if _, err = w.Write([]byte("kept")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := f.GetWorkDir()
|
||||||
|
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(p.Append("result").String(), []byte{0}, 0444)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, true, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W/result": {Mode: 0444, Data: []byte("\x00")},
|
||||||
|
"checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv": {Mode: 0400, Data: []byte("kept")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/KpxTUsUfGbsX6C6HU2j9cd9He3_tt2o2m2MgH4m0zial8lmoqSWY-Gc3KrS0k-UK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv")},
|
||||||
|
"identifier/xxQ43VMIS1WHMSEFnUdBNd6pulbtgiTorP_mtJpEsZENYhGBiDEL6Y2D8yqn0Sra": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")},
|
||||||
|
|
||||||
|
"lock": {Mode: 0644},
|
||||||
|
"variant": {Mode: 0400},
|
||||||
|
|
||||||
|
"status": {Mode: fs.ModeDir | 0700},
|
||||||
|
"status/xxQ43VMIS1WHMSEFnUdBNd6pulbtgiTorP_mtJpEsZENYhGBiDEL6Y2D8yqn0Sra": {Mode: 0400, Data: []byte(statusHeader + "kept")},
|
||||||
|
"status/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: 0400, Data: []byte(statusHeader + "kept")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
"substitute/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")},
|
||||||
|
|
||||||
|
"fault": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, []unique.Handle[pkg.ID]{
|
||||||
|
ic.Ident(pkg.NewFile("destroyed-input", []byte("destroyed"))),
|
||||||
|
ic.Ident(&stubArtifactF{
|
||||||
|
kind: pkg.KindExec,
|
||||||
|
params: []byte("destroyed"),
|
||||||
|
deps: []pkg.Artifact{
|
||||||
|
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}, []unique.Handle[pkg.Checksum]{
|
||||||
|
unique.Make(expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
"result": {Mode: 0444},
|
||||||
|
}.hash()),
|
||||||
|
unique.Make(sha512.Sum384([]byte("destroyed"))),
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
base := makeBase(t)
|
||||||
|
msg := message.New(log.New(os.Stderr, "clean: ", 0))
|
||||||
|
msg.SwapVerbose(testing.Verbose())
|
||||||
|
c, err := pkg.Open(t.Context(), msg, base, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(c.Close)
|
||||||
|
|
||||||
|
all := pkg.Collect(slices.Concat(tc.a, tc.keep))
|
||||||
|
if _, _, err = c.Cure(&all); !pkg.IsCollected(err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
idents []unique.Handle[pkg.ID]
|
||||||
|
checksums []unique.Handle[pkg.Checksum]
|
||||||
|
)
|
||||||
|
idents, checksums, err = c.Clean(false, tc.inputs, tc.keep...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Clean: error = %v", err)
|
||||||
|
}
|
||||||
|
var buf [2]pkg.Checksum
|
||||||
|
|
||||||
|
slices.SortFunc(idents, func(a, b unique.Handle[pkg.ID]) int {
|
||||||
|
buf[0], buf[1] = a.Value(), b.Value()
|
||||||
|
return bytes.Compare(buf[0][:], buf[1][:])
|
||||||
|
})
|
||||||
|
slices.SortFunc(checksums, func(a, b unique.Handle[pkg.Checksum]) int {
|
||||||
|
buf[0], buf[1] = a.Value(), b.Value()
|
||||||
|
return bytes.Compare(buf[0][:], buf[1][:])
|
||||||
|
})
|
||||||
|
|
||||||
|
if !slices.Equal(idents, tc.wantIdents) {
|
||||||
|
t.Errorf(
|
||||||
|
"Clean: idents = %s, want %s",
|
||||||
|
formatHandles(idents...), formatHandles(tc.wantIdents...),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !slices.Equal(checksums, tc.wantChecksums) {
|
||||||
|
t.Errorf(
|
||||||
|
"Clean: checksums = %s, want %s",
|
||||||
|
formatHandles(checksums...), formatHandles(tc.wantChecksums...),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := tc.want.hash()
|
||||||
|
var checksum pkg.Checksum
|
||||||
|
if err = pkg.SumDir(&checksum, base); err != nil {
|
||||||
|
t.Fatalf("SumDir: error = %v", err)
|
||||||
|
} else if checksum != want {
|
||||||
|
t.Error(expectsFrom(base.String()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/bzip2"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"hakurei.app/internal/xz"
|
||||||
|
"hakurei.app/internal/zstd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Gzip denotes a stream compressed via [gzip].
|
||||||
|
Gzip = iota
|
||||||
|
// Bzip2 denotes a stream compressed via [bzip2].
|
||||||
|
Bzip2
|
||||||
|
// Zstd denotes a stream compressed via [zstd].
|
||||||
|
Zstd
|
||||||
|
// XZ denotes a stream compressed via [xz].
|
||||||
|
XZ
|
||||||
|
)
|
||||||
|
|
||||||
|
// A decompressArtifact is a [FileArtifact] decompressing a backing
|
||||||
|
// [FileArtifact] stream.
|
||||||
|
type decompressArtifact struct {
|
||||||
|
// Caller-supplied backing stream.
|
||||||
|
f Artifact
|
||||||
|
// Compression on top of the stream.
|
||||||
|
compress uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ FileArtifact = new(decompressArtifact)
|
||||||
|
var _ CuresExempt = new(decompressArtifact)
|
||||||
|
|
||||||
|
// decompressArtifactNamed embeds decompressArtifact for a [fmt.Stringer] stream.
|
||||||
|
type decompressArtifactNamed struct {
|
||||||
|
decompressArtifact
|
||||||
|
// Copied from decompressArtifact.f.
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fmt.Stringer = new(decompressArtifactNamed)
|
||||||
|
|
||||||
|
// NewDecompress returns a [FileArtifact] decompressing the supplied [Artifact].
|
||||||
|
func NewDecompress(a Artifact, compress uint32) FileArtifact {
|
||||||
|
da := decompressArtifact{a, compress}
|
||||||
|
if s, ok := a.(fmt.Stringer); ok {
|
||||||
|
if name := s.String(); name != "" {
|
||||||
|
return &decompressArtifactNamed{da, name}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &da
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the name of the underlying [Artifact] prefixed with decompress.
|
||||||
|
func (a *decompressArtifactNamed) String() string { return "decompress-" + a.name }
|
||||||
|
|
||||||
|
// Kind returns the hardcoded [Kind] constant.
|
||||||
|
func (a *decompressArtifact) Kind() Kind { return KindDecompress }
|
||||||
|
|
||||||
|
// Params writes value of compression enum.
|
||||||
|
func (a *decompressArtifact) Params(ctx *IContext) { ctx.WriteUint32(a.compress) }
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register(KindDecompress, func(r *IRReader) Artifact {
|
||||||
|
a := NewDecompress(r.Next(), r.ReadUint32())
|
||||||
|
if _, ok := r.Finalise(); ok {
|
||||||
|
panic(ErrUnexpectedChecksum)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inputs returns a slice containing the backing file.
|
||||||
|
func (a *decompressArtifact) Inputs() []Artifact {
|
||||||
|
return []Artifact{a.f}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExclusive returns false: decompressor is fully sequential.
|
||||||
|
func (a *decompressArtifact) IsExclusive() bool { return false }
|
||||||
|
|
||||||
|
// compoundCloser is an [io.ReadCloser] with an additional [io.Closer] attached.
|
||||||
|
type compoundCloser struct {
|
||||||
|
io.ReadCloser
|
||||||
|
c io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes [io.ReadCloser] and the additional [io.Closer]. It returns the
|
||||||
|
// non-nil error returned by the underlying [io.ReadCloser], otherwise it
|
||||||
|
// returns the error returned by the additional [io.Closer].
|
||||||
|
func (c compoundCloser) Close() error {
|
||||||
|
err := c.ReadCloser.Close()
|
||||||
|
if _err := c.c.Close(); err == nil {
|
||||||
|
err = _err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExecutable returns false.
|
||||||
|
func (*decompressArtifact) IsExecutable() bool { return false }
|
||||||
|
|
||||||
|
// Cure returns a decompressor [io.ReadCloser].
|
||||||
|
func (a *decompressArtifact) Cure(r *RContext) (io.ReadCloser, error) {
|
||||||
|
sr, err := r.Open(a.f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
br := r.cache.getReaderRC(sr)
|
||||||
|
|
||||||
|
var dr io.ReadCloser
|
||||||
|
switch a.compress {
|
||||||
|
case Gzip:
|
||||||
|
if dr, err = gzip.NewReader(br); err != nil {
|
||||||
|
_ = br.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return compoundCloser{dr, br}, nil
|
||||||
|
|
||||||
|
case Bzip2:
|
||||||
|
return struct {
|
||||||
|
io.Reader
|
||||||
|
io.Closer
|
||||||
|
}{bzip2.NewReader(br), br}, nil
|
||||||
|
|
||||||
|
case Zstd:
|
||||||
|
return struct {
|
||||||
|
io.Reader
|
||||||
|
io.Closer
|
||||||
|
}{zstd.NewReader(br), br}, nil
|
||||||
|
|
||||||
|
case XZ:
|
||||||
|
var _dr io.Reader
|
||||||
|
if _dr, err = xz.NewReader(br, 0); err != nil {
|
||||||
|
_ = br.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return struct {
|
||||||
|
io.Reader
|
||||||
|
io.Closer
|
||||||
|
}{_dr, br}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CuresExempt exempts the cheap [KindDecompress] implementation often part of
|
||||||
|
// a [FileArtifact] pipeline.
|
||||||
|
func (*decompressArtifact) CuresExempt() {}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package pkg_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"crypto/sha512"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecompress(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
gw := gzip.NewWriter(&buf)
|
||||||
|
if _, err := gw.Write([]byte{0}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err = gw.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testdata := buf.String()
|
||||||
|
|
||||||
|
var transport http.Transport
|
||||||
|
client := http.Client{Transport: &transport}
|
||||||
|
transport.RegisterProtocol("file", http.NewFileTransportFS(fstest.MapFS{
|
||||||
|
"testdata": {Data: []byte(testdata), Mode: 0400},
|
||||||
|
}))
|
||||||
|
testdataChecksum := func() pkg.Checksum {
|
||||||
|
h := sha512.New384()
|
||||||
|
h.Write([]byte(testdata))
|
||||||
|
return (pkg.Checksum)(h.Sum(nil))
|
||||||
|
}()
|
||||||
|
|
||||||
|
checkWithCache(t, []cacheTestCase{
|
||||||
|
{"decompress", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
|
cureMany(t, c, []cureStep{
|
||||||
|
{"close", pkg.NewDecompress(pkg.NewHTTPGet(
|
||||||
|
&client,
|
||||||
|
"file:///testdata",
|
||||||
|
pkg.Checksum{0xfd},
|
||||||
|
), pkg.Gzip), nil, nil, pkg.WNew, &pkg.ChecksumMismatchError{
|
||||||
|
Got: testdataChecksum,
|
||||||
|
Want: pkg.Checksum{0xfd},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"gzip", pkg.NewDecompress(pkg.NewHTTPGet(
|
||||||
|
&client,
|
||||||
|
"file:///testdata",
|
||||||
|
testdataChecksum,
|
||||||
|
), pkg.Gzip), ignorePathname, expectsChecksum(sha512.Sum384([]byte{0})), pkg.WNew, nil},
|
||||||
|
})
|
||||||
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/" + pkg.Encode(sha512.Sum384([]byte{0})): {Mode: 0400, Data: []byte{0}},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/mae-iR_9LFT_BOEJ2IRzKsAlI754SalMjjt95fZeMd3gEyAzsrvuOXnXN0bFZJi4": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
||||||
|
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
package pkg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha512"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlatEntry is a directory entry to be encoded for [Flatten].
|
|
||||||
type FlatEntry struct {
|
|
||||||
Mode fs.FileMode // file mode bits
|
|
||||||
Path string // pathname of the file
|
|
||||||
Data []byte // file content or symlink destination
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
| mode uint32 | path_sz uint32 |
|
|
||||||
| data_sz uint64 |
|
|
||||||
| path string |
|
|
||||||
| data []byte |
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Encode encodes the entry for transmission or hashing.
|
|
||||||
func (ent *FlatEntry) Encode(w io.Writer) (n int, err error) {
|
|
||||||
pPathSize := alignSize(len(ent.Path))
|
|
||||||
if pPathSize > math.MaxUint32 {
|
|
||||||
return 0, syscall.E2BIG
|
|
||||||
}
|
|
||||||
pDataSize := alignSize(len(ent.Data))
|
|
||||||
|
|
||||||
payload := make([]byte, wordSize*2+pPathSize+pDataSize)
|
|
||||||
binary.LittleEndian.PutUint32(payload, uint32(ent.Mode))
|
|
||||||
binary.LittleEndian.PutUint32(payload[wordSize/2:], uint32(len(ent.Path)))
|
|
||||||
binary.LittleEndian.PutUint64(payload[wordSize:], uint64(len(ent.Data)))
|
|
||||||
copy(payload[wordSize*2:], ent.Path)
|
|
||||||
copy(payload[wordSize*2+pPathSize:], ent.Data)
|
|
||||||
return w.Write(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrInsecurePath is returned by [FlatEntry.Decode] if validation is requested
|
|
||||||
// and a nonlocal path is encountered in the stream.
|
|
||||||
var ErrInsecurePath = errors.New("insecure file path")
|
|
||||||
|
|
||||||
// Decode decodes the entry from its representation produced by Encode.
|
|
||||||
func (ent *FlatEntry) Decode(r io.Reader, validate bool) (n int, err error) {
|
|
||||||
var nr int
|
|
||||||
|
|
||||||
header := make([]byte, wordSize*2)
|
|
||||||
nr, err = r.Read(header)
|
|
||||||
n += nr
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) && n != 0 {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ent.Mode = fs.FileMode(binary.LittleEndian.Uint32(header))
|
|
||||||
pathSize := int(binary.LittleEndian.Uint32(header[wordSize/2:]))
|
|
||||||
pPathSize := alignSize(pathSize)
|
|
||||||
dataSize := int(binary.LittleEndian.Uint64(header[wordSize:]))
|
|
||||||
pDataSize := alignSize(dataSize)
|
|
||||||
|
|
||||||
buf := make([]byte, pPathSize+pDataSize)
|
|
||||||
nr, err = r.Read(buf)
|
|
||||||
n += nr
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
if nr != len(buf) {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ent.Path = string(buf[:pathSize])
|
|
||||||
if ent.Mode.IsDir() {
|
|
||||||
ent.Data = nil
|
|
||||||
} else {
|
|
||||||
ent.Data = buf[pPathSize : pPathSize+dataSize]
|
|
||||||
}
|
|
||||||
|
|
||||||
if validate && !filepath.IsLocal(ent.Path) {
|
|
||||||
err = ErrInsecurePath
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirScanner provides an efficient interface for reading a stream of encoded
|
|
||||||
// [FlatEntry]. Successive calls to the Scan method will step through the
|
|
||||||
// entries in the stream.
|
|
||||||
type DirScanner struct {
|
|
||||||
// Underlying reader to scan [FlatEntry] representations from.
|
|
||||||
r io.Reader
|
|
||||||
|
|
||||||
// First non-EOF I/O error, returned by the Err method.
|
|
||||||
err error
|
|
||||||
|
|
||||||
// Entry to store results in. Its address is returned by the Entry method
|
|
||||||
// and is updated on every call to Scan.
|
|
||||||
ent FlatEntry
|
|
||||||
|
|
||||||
// Validate pathnames during decoding.
|
|
||||||
validate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDirScanner returns the address of a new instance of [DirScanner] reading
|
|
||||||
// from r. The caller must no longer read from r after this function returns.
|
|
||||||
func NewDirScanner(r io.Reader, validate bool) *DirScanner {
|
|
||||||
return &DirScanner{r: r, validate: validate}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err returns the first non-EOF I/O error.
|
|
||||||
func (s *DirScanner) Err() error {
|
|
||||||
if errors.Is(s.err, io.EOF) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry returns the address to the [FlatEntry] value storing the last result.
|
|
||||||
func (s *DirScanner) Entry() *FlatEntry { return &s.ent }
|
|
||||||
|
|
||||||
// Scan advances to the next [FlatEntry].
|
|
||||||
func (s *DirScanner) Scan() bool {
|
|
||||||
if s.err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var n int
|
|
||||||
n, s.err = s.ent.Decode(s.r, s.validate)
|
|
||||||
if errors.Is(s.err, io.EOF) {
|
|
||||||
return n != 0
|
|
||||||
}
|
|
||||||
return s.err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flatten writes a deterministic representation of the contents of fsys to w.
|
|
||||||
// The resulting data can be hashed to produce a deterministic checksum for the
|
|
||||||
// directory.
|
|
||||||
func Flatten(fsys fs.FS, root string, w io.Writer) (n int, err error) {
|
|
||||||
var nr int
|
|
||||||
err = fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var fi fs.FileInfo
|
|
||||||
fi, err = d.Info()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ent := FlatEntry{
|
|
||||||
Path: path,
|
|
||||||
Mode: fi.Mode(),
|
|
||||||
}
|
|
||||||
if ent.Mode.IsRegular() {
|
|
||||||
if ent.Data, err = fs.ReadFile(fsys, path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if ent.Mode&fs.ModeSymlink != 0 {
|
|
||||||
var newpath string
|
|
||||||
if newpath, err = fs.ReadLink(fsys, path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ent.Data = []byte(newpath)
|
|
||||||
} else if !ent.Mode.IsDir() {
|
|
||||||
return InvalidFileModeError(ent.Mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
nr, err = ent.Encode(w)
|
|
||||||
n += nr
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashFS returns a checksum produced by hashing the result of [Flatten].
|
|
||||||
func HashFS(buf *Checksum, fsys fs.FS, root string) error {
|
|
||||||
h := sha512.New384()
|
|
||||||
if _, err := Flatten(fsys, root, h); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h.Sum(buf[:0])
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashDir returns a checksum produced by hashing the result of [Flatten].
|
|
||||||
func HashDir(buf *Checksum, pathname *check.Absolute) error {
|
|
||||||
return HashFS(buf, os.DirFS(pathname.String()), ".")
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
package pkg_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/fs"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"testing/fstest"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFlatten(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
fsys fs.FS
|
|
||||||
entries []pkg.FlatEntry
|
|
||||||
sum pkg.Checksum
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{"bad type", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
"invalid": {Mode: fs.ModeCharDevice | 0400},
|
|
||||||
}, nil, pkg.Checksum{}, pkg.InvalidFileModeError(
|
|
||||||
fs.ModeCharDevice | 0400,
|
|
||||||
)},
|
|
||||||
|
|
||||||
{"coldboot", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"devices": {Mode: fs.ModeDir | 0700},
|
|
||||||
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
|
||||||
"devices/empty": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"devices/sub": {Mode: fs.ModeDir | 0700},
|
|
||||||
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
|
||||||
|
|
||||||
"block": {Mode: fs.ModeDir | 0700},
|
|
||||||
"block/uevent": {Mode: 0600, Data: []byte{}},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "block"},
|
|
||||||
{Mode: 0600, Path: "block/uevent", Data: []byte{}},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "devices"},
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "devices/empty"},
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "devices/sub"},
|
|
||||||
{Mode: 0600, Path: "devices/sub/uevent", Data: []byte("add")},
|
|
||||||
{Mode: 0600, Path: "devices/uevent", Data: []byte("add")},
|
|
||||||
}, pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8"), nil},
|
|
||||||
|
|
||||||
{"empty", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
|
||||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
|
||||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C"), nil},
|
|
||||||
|
|
||||||
{"sample directory step garbage", fstest.MapFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"lib": {Mode: fs.ModeDir | 0500},
|
|
||||||
"lib/check": {Mode: 0400, Data: []byte{}},
|
|
||||||
|
|
||||||
"lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
|
||||||
}, []pkg.FlatEntry{
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "."},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "lib"},
|
|
||||||
{Mode: 0400, Path: "lib/check", Data: []byte{}},
|
|
||||||
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "lib/pkgconfig"},
|
|
||||||
}, pkg.MustDecode("CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT"), nil},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("roundtrip", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := pkg.Flatten(
|
|
||||||
tc.fsys,
|
|
||||||
".",
|
|
||||||
&buf,
|
|
||||||
); !reflect.DeepEqual(err, tc.err) {
|
|
||||||
t.Fatalf("Flatten: error = %v, want %v", err, tc.err)
|
|
||||||
} else if tc.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s := pkg.NewDirScanner(bytes.NewReader(buf.Bytes()), true)
|
|
||||||
var got []pkg.FlatEntry
|
|
||||||
for s.Scan() {
|
|
||||||
got = append(got, *s.Entry())
|
|
||||||
}
|
|
||||||
if err := s.Err(); err != nil {
|
|
||||||
t.Fatalf("Err: error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tc.entries) {
|
|
||||||
t.Fatalf("Scan: %#v, want %#v", got, tc.entries)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if tc.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("hash", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var got pkg.Checksum
|
|
||||||
if err := pkg.HashFS(&got, tc.fsys, "."); err != nil {
|
|
||||||
t.Fatalf("HashFS: error = %v", err)
|
|
||||||
} else if got != tc.sum {
|
|
||||||
t.Fatalf("HashFS: %v", &pkg.ChecksumMismatchError{
|
|
||||||
Got: got,
|
|
||||||
Want: tc.sum,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+51
-16
@@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -29,10 +30,16 @@ import (
|
|||||||
// AbsWork is the container pathname [TContext.GetWorkDir] is mounted on.
|
// AbsWork is the container pathname [TContext.GetWorkDir] is mounted on.
|
||||||
var AbsWork = fhs.AbsRoot.Append("work/")
|
var AbsWork = fhs.AbsRoot.Append("work/")
|
||||||
|
|
||||||
// EnvJobs is the name of the environment variable holding a decimal
|
const (
|
||||||
// representation of the preferred job count. Its value must not affect cure
|
// EnvJobs is the name of the environment variable holding a decimal
|
||||||
// outcome.
|
// representation of the preferred job count. Its value must not affect cure
|
||||||
const EnvJobs = "CURE_JOBS"
|
// outcome.
|
||||||
|
EnvJobs = "CURE_JOBS"
|
||||||
|
// EnvLoad is the name of the environment variable holding a decimal
|
||||||
|
// representation of the preferred loadavg target. Its value must not affect
|
||||||
|
// cure outcome.
|
||||||
|
EnvLoad = "CURE_LOAD"
|
||||||
|
)
|
||||||
|
|
||||||
// ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make
|
// ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make
|
||||||
// it available at under in the container.
|
// it available at under in the container.
|
||||||
@@ -122,6 +129,15 @@ func RegisterArch(arch string, e container.BinfmtEntry) {
|
|||||||
binfmt[arch] = e
|
binfmt[arch] = e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Arch returns a snapshot of currently registered [KindExec] and [KindExecNet]
|
||||||
|
// binfmt entries.
|
||||||
|
func Arch() map[string]container.BinfmtEntry {
|
||||||
|
binfmtMu.RLock()
|
||||||
|
r := maps.Clone(binfmt)
|
||||||
|
binfmtMu.RUnlock()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ExecTimeoutDefault replaces out of range [NewExec] timeout values.
|
// ExecTimeoutDefault replaces out of range [NewExec] timeout values.
|
||||||
ExecTimeoutDefault = 15 * time.Minute
|
ExecTimeoutDefault = 15 * time.Minute
|
||||||
@@ -397,9 +413,9 @@ func init() {
|
|||||||
func(r *IRReader) Artifact { return readExecArtifact(r, true) })
|
func(r *IRReader) Artifact { return readExecArtifact(r, true) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dependencies returns a slice of all artifacts collected from caller-supplied
|
// Inputs returns a slice of all artifacts collected from caller-supplied
|
||||||
// [ExecPath].
|
// [ExecPath].
|
||||||
func (a *execArtifact) Dependencies() []Artifact {
|
func (a *execArtifact) Inputs() []Artifact {
|
||||||
artifacts := make([][]Artifact, 0, len(a.paths))
|
artifacts := make([][]Artifact, 0, len(a.paths))
|
||||||
for _, p := range a.paths {
|
for _, p := range a.paths {
|
||||||
artifacts = append(artifacts, p.A)
|
artifacts = append(artifacts, p.A)
|
||||||
@@ -428,7 +444,7 @@ func scanVerbose(
|
|||||||
msg message.Msg,
|
msg message.Msg,
|
||||||
cancel context.CancelFunc,
|
cancel context.CancelFunc,
|
||||||
done chan<- struct{},
|
done chan<- struct{},
|
||||||
prefix string,
|
prefix, suffix string,
|
||||||
r io.Reader,
|
r io.Reader,
|
||||||
) {
|
) {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
@@ -438,7 +454,7 @@ func scanVerbose(
|
|||||||
bufio.MaxScanTokenSize<<12,
|
bufio.MaxScanTokenSize<<12,
|
||||||
)
|
)
|
||||||
for s.Scan() {
|
for s.Scan() {
|
||||||
msg.Verbose(prefix, s.Text())
|
msg.Verbose(prefix, s.Text()+suffix)
|
||||||
}
|
}
|
||||||
if err := s.Err(); err != nil && !errors.Is(err, os.ErrClosed) {
|
if err := s.Err(); err != nil && !errors.Is(err, os.ErrClosed) {
|
||||||
cancel()
|
cancel()
|
||||||
@@ -461,7 +477,7 @@ const SeccompPresets = std.PresetStrict &
|
|||||||
func (a *execArtifact) makeContainer(
|
func (a *execArtifact) makeContainer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
msg message.Msg,
|
msg message.Msg,
|
||||||
flags, jobs int,
|
flags, jobs, load int,
|
||||||
hostNet bool,
|
hostNet bool,
|
||||||
temp, work *check.Absolute,
|
temp, work *check.Absolute,
|
||||||
getArtifact GetArtifactFunc,
|
getArtifact GetArtifactFunc,
|
||||||
@@ -498,7 +514,10 @@ func (a *execArtifact) makeContainer(
|
|||||||
z.Quiet = flags&CSuppressInit != 0
|
z.Quiet = flags&CSuppressInit != 0
|
||||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
||||||
z.Dir, z.Path, z.Args = a.dir, a.path, a.args
|
z.Dir, z.Path, z.Args = a.dir, a.path, a.args
|
||||||
z.Env = slices.Concat(a.env, []string{EnvJobs + "=" + strconv.Itoa(jobs)})
|
z.Env = slices.Concat(a.env, []string{
|
||||||
|
EnvJobs + "=" + strconv.Itoa(jobs),
|
||||||
|
EnvLoad + "=" + strconv.Itoa(load),
|
||||||
|
})
|
||||||
z.Grow(len(a.paths) + 4)
|
z.Grow(len(a.paths) + 4)
|
||||||
|
|
||||||
if a.arch != runtime.GOARCH {
|
if a.arch != runtime.GOARCH {
|
||||||
@@ -581,6 +600,7 @@ var (
|
|||||||
func (c *Cache) EnterExec(
|
func (c *Cache) EnterExec(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
a Artifact,
|
a Artifact,
|
||||||
|
hostname string,
|
||||||
retainSession bool,
|
retainSession bool,
|
||||||
stdin io.Reader,
|
stdin io.Reader,
|
||||||
stdout, stderr io.Writer,
|
stdout, stderr io.Writer,
|
||||||
@@ -606,7 +626,7 @@ func (c *Cache) EnterExec(
|
|||||||
return ErrNotExec
|
return ErrNotExec
|
||||||
}
|
}
|
||||||
|
|
||||||
deps := Collect(a.Dependencies())
|
deps := Collect(a.Inputs())
|
||||||
if _, _, err = c.Cure(&deps); err == nil {
|
if _, _, err = c.Cure(&deps); err == nil {
|
||||||
return errors.New("unreachable")
|
return errors.New("unreachable")
|
||||||
} else if !IsCollected(err) {
|
} else if !IsCollected(err) {
|
||||||
@@ -638,8 +658,9 @@ func (c *Cache) EnterExec(
|
|||||||
var z *container.Container
|
var z *container.Container
|
||||||
z, err = e.makeContainer(
|
z, err = e.makeContainer(
|
||||||
ctx, c.msg,
|
ctx, c.msg,
|
||||||
c.flags,
|
c.attr.Flags,
|
||||||
c.jobs,
|
c.attr.Jobs,
|
||||||
|
c.attr.Load,
|
||||||
hostNet,
|
hostNet,
|
||||||
temp, work,
|
temp, work,
|
||||||
func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
|
func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
|
||||||
@@ -661,6 +682,9 @@ func (c *Cache) EnterExec(
|
|||||||
z.Env = append(z.Env, "TERM="+s)
|
z.Env = append(z.Env, "TERM="+s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if hostname != "" {
|
||||||
|
z.Hostname = hostname
|
||||||
|
}
|
||||||
|
|
||||||
if err = z.Start(); err != nil {
|
if err = z.Start(); err != nil {
|
||||||
return
|
return
|
||||||
@@ -679,7 +703,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
msg := f.GetMessage()
|
msg := f.GetMessage()
|
||||||
var z *container.Container
|
var z *container.Container
|
||||||
if z, err = a.makeContainer(
|
if z, err = a.makeContainer(
|
||||||
ctx, msg, f.cache.flags, f.GetJobs(), hostNet,
|
ctx, msg, f.cache.attr.Flags, f.GetJobs(), f.GetLoad(), hostNet,
|
||||||
f.GetTempDir(), f.GetWorkDir(),
|
f.GetTempDir(), f.GetWorkDir(),
|
||||||
f.GetArtifact,
|
f.GetArtifact,
|
||||||
f.cache.Ident,
|
f.cache.Ident,
|
||||||
@@ -704,14 +728,25 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
|
|
||||||
brStdout, brStderr := f.cache.getReader(stdout), f.cache.getReader(stderr)
|
brStdout, brStderr := f.cache.getReader(stdout), f.cache.getReader(stderr)
|
||||||
stdoutDone, stderrDone := make(chan struct{}), make(chan struct{})
|
stdoutDone, stderrDone := make(chan struct{}), make(chan struct{})
|
||||||
|
|
||||||
|
var suffix, prefixO, prefixE string
|
||||||
|
if f.cache.attr.Flags&CColourOutput != 0 {
|
||||||
|
suffix = "\x1b[0m"
|
||||||
|
prefixO = "\x1b[1;37m(" + a.name + ")\x1b[0m"
|
||||||
|
prefixE = "\x1b[1;97m(" + a.name + ")\x1b[0m"
|
||||||
|
} else {
|
||||||
|
prefixO = "(" + a.name + ":1)"
|
||||||
|
prefixE = "(" + a.name + ":2)"
|
||||||
|
}
|
||||||
|
|
||||||
go scanVerbose(
|
go scanVerbose(
|
||||||
msg, cancel, stdoutDone,
|
msg, cancel, stdoutDone,
|
||||||
"("+a.name+":1)",
|
prefixO, suffix,
|
||||||
io.TeeReader(brStdout, status),
|
io.TeeReader(brStdout, status),
|
||||||
)
|
)
|
||||||
go scanVerbose(
|
go scanVerbose(
|
||||||
msg, cancel, stderrDone,
|
msg, cancel, stderrDone,
|
||||||
"("+a.name+":2)",
|
prefixE, suffix,
|
||||||
io.TeeReader(brStderr, status),
|
io.TeeReader(brStderr, status),
|
||||||
)
|
)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
+21
-21
@@ -84,7 +84,7 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantOffline, pkg.WNew, nil},
|
||||||
|
|
||||||
{"substitution", pkg.NewExec(
|
{"substitution", pkg.NewExec(
|
||||||
"exec-offline", "", new(wantOffline.hash()), 0, false, false,
|
"exec-offline", "", new(wantOffline.hash()), 0, false, false,
|
||||||
@@ -108,7 +108,7 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantOffline, pkg.WSubstitute, nil},
|
||||||
|
|
||||||
{"error passthrough", pkg.NewExec(
|
{"error passthrough", pkg.NewExec(
|
||||||
"", "", nil, 0, false, true,
|
"", "", nil, 0, false, true,
|
||||||
@@ -118,7 +118,7 @@ func TestExec(t *testing.T) {
|
|||||||
[]string{"testtool"},
|
[]string{"testtool"},
|
||||||
|
|
||||||
pkg.MustPath("/proc/nonexistent", false, failingArtifact),
|
pkg.MustPath("/proc/nonexistent", false, failingArtifact),
|
||||||
), nil, nil, &pkg.DependencyCureError{
|
), nil, nil, pkg.WNew, &pkg.DependencyCureError{
|
||||||
{
|
{
|
||||||
A: failingArtifact,
|
A: failingArtifact,
|
||||||
Err: stub.UniqueError(0xcafe),
|
Err: stub.UniqueError(0xcafe),
|
||||||
@@ -133,7 +133,7 @@ func TestExec(t *testing.T) {
|
|||||||
[]string{"testtool"},
|
[]string{"testtool"},
|
||||||
|
|
||||||
pkg.ExecPath{},
|
pkg.ExecPath{},
|
||||||
), nil, nil, pkg.ErrInvalidPaths},
|
), nil, nil, pkg.WNew, pkg.ErrInvalidPaths},
|
||||||
})
|
})
|
||||||
|
|
||||||
// check init failure passthrough
|
// check init failure passthrough
|
||||||
@@ -183,11 +183,11 @@ func TestExec(t *testing.T) {
|
|||||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
"identifier/QwS7SmiatdqryQYgESdGw7Yw2PcpNf0vNfpvUA0t92BTlKiUjfCrXyMW17G2X77X": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
"identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
"identifier/" + expected.Offline: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
"identifier/" + expected.Offline: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
"identifier/" + expected.OfflineS: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
"identifier/" + expected.OfflineS: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
"identifier/_OHzj_xdmfpaWfFN-_D9JpGpPlIjh84873E3Ui9xwv9PPPIt0BlzvH-cnPNeRsTl": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
"substitute": {Mode: fs.ModeDir | 0700},
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantNet, nil},
|
), ignorePathname, wantNet, pkg.WNew, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
destroyStatus(t, base, 2, 0)
|
destroyStatus(t, base, 2, 0)
|
||||||
@@ -242,7 +242,7 @@ func TestExec(t *testing.T) {
|
|||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
"identifier/" + expected.Net: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
|
"identifier/" + expected.Net: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
|
||||||
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
"identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
"substitute": {Mode: fs.ModeDir | 0700},
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantOffline, pkg.WNew, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
destroyStatus(t, base, 2, 0)
|
destroyStatus(t, base, 2, 0)
|
||||||
@@ -284,7 +284,7 @@ func TestExec(t *testing.T) {
|
|||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
"identifier/" + expected.OvlRoot: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
"identifier/" + expected.OvlRoot: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
"identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
"substitute": {Mode: fs.ModeDir | 0700},
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ func TestExec(t *testing.T) {
|
|||||||
return os.MkdirAll(t.GetWorkDir().String(), 0700)
|
return os.MkdirAll(t.GetWorkDir().String(), 0700)
|
||||||
},
|
},
|
||||||
}), pkg.Path(pkg.AbsWork, false /* ignored */, testtool),
|
}), pkg.Path(pkg.AbsWork, false /* ignored */, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantOffline, pkg.WNew, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
destroyStatus(t, base, 2, 0)
|
destroyStatus(t, base, 2, 0)
|
||||||
@@ -331,7 +331,7 @@ func TestExec(t *testing.T) {
|
|||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
"identifier/" + expected.Work: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
"identifier/" + expected.Work: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
"identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
"substitute": {Mode: fs.ModeDir | 0700},
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
@@ -385,7 +385,7 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantOffline, pkg.WNew, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
destroyStatus(t, base, 2, 0)
|
destroyStatus(t, base, 2, 0)
|
||||||
@@ -403,9 +403,9 @@ func TestExec(t *testing.T) {
|
|||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
"identifier/B-kc5iJMx8GtlCua4dz6BiJHnDAOUfPjgpbKq4e-QEn0_CZkSYs3fOA1ve06qMs2": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
|
"identifier/2uIvmnqtzAx4Ed4PWV8xjZCWeJCwfE2IcCc0evM-Nd8mDUlgSU6nLtPLcpPUcMlG": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
|
||||||
"identifier/" + expected.Layers: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
"identifier/" + expected.Layers: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
"identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
"substitute": {Mode: fs.ModeDir | 0700},
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
@@ -438,7 +438,7 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantOffline, pkg.WNew, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
destroyStatus(t, base, 2, 0)
|
destroyStatus(t, base, 2, 0)
|
||||||
@@ -452,8 +452,8 @@ func TestExec(t *testing.T) {
|
|||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
"identifier/kvJIqZo5DKFOxC2ZQ-8_nPaQzEAz9cIm3p6guO-uLqm-xaiPu7oRkSnsu411jd_U": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
"identifier/Vzj9qaSVBtNyBQv_PW6uojFOWTwraHoaC0C6xdaGG1q_HTvSh4KLVxRcEQU1jd2Y": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
"identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
"identifier/" + expected.Promote: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
"identifier/" + expected.Promote: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||||
|
|
||||||
"substitute": {Mode: fs.ModeDir | 0700},
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
@@ -503,7 +503,7 @@ func TestExec(t *testing.T) {
|
|||||||
".": {Mode: fs.ModeDir | 0500},
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
"check": {Mode: 0400, Data: []byte("binfmt")},
|
"check": {Mode: 0400, Data: []byte("binfmt")},
|
||||||
}, nil},
|
}, pkg.WNew, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
destroyStatus(t, base, 2, 0)
|
destroyStatus(t, base, 2, 0)
|
||||||
@@ -519,9 +519,9 @@ func TestExec(t *testing.T) {
|
|||||||
"checksum/UnDo4B5KneEUY5b4vRUk_y9MWgkWuw2N8f8a2XayO686xXur-aZmX2-7n_8tKMe3/check": {Mode: 0400, Data: []byte("binfmt")},
|
"checksum/UnDo4B5KneEUY5b4vRUk_y9MWgkWuw2N8f8a2XayO686xXur-aZmX2-7n_8tKMe3/check": {Mode: 0400, Data: []byte("binfmt")},
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
"identifier/6VQTJ1lI5BmVuI1YFYJ8ClO3MRORvTTrcWFDcUU-l5Ga8EofxCxGlSTYN-u8dKj_": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UnDo4B5KneEUY5b4vRUk_y9MWgkWuw2N8f8a2XayO686xXur-aZmX2-7n_8tKMe3")},
|
"identifier/c-HodmoEG0f_WGdst9aN-duLMH7QDDXOUi7UcyArXMeM1fc9lYKKQlTCW8ktaK8X": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UnDo4B5KneEUY5b4vRUk_y9MWgkWuw2N8f8a2XayO686xXur-aZmX2-7n_8tKMe3")},
|
||||||
"identifier/_v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/5aevg3YpDxjqQZ-pdvXK7YqgkL5JKqcoStYQxeD96kuYar6K2mRQWMHib6NQRnpV")},
|
"identifier/_v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/5aevg3YpDxjqQZ-pdvXK7YqgkL5JKqcoStYQxeD96kuYar6K2mRQWMHib6NQRnpV")},
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
"identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
"substitute": {Mode: fs.ModeDir | 0700},
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
|||||||
+21
-2
@@ -11,6 +11,8 @@ import (
|
|||||||
type fileArtifact []byte
|
type fileArtifact []byte
|
||||||
|
|
||||||
var _ KnownChecksum = new(fileArtifact)
|
var _ KnownChecksum = new(fileArtifact)
|
||||||
|
var _ CuresExempt = new(fileArtifact)
|
||||||
|
var _ RevisionArtifact = new(fileArtifact)
|
||||||
|
|
||||||
// fileArtifactNamed embeds fileArtifact alongside a caller-supplied name.
|
// fileArtifactNamed embeds fileArtifact alongside a caller-supplied name.
|
||||||
type fileArtifactNamed struct {
|
type fileArtifactNamed struct {
|
||||||
@@ -62,8 +64,8 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dependencies returns a nil slice.
|
// Inputs returns a nil slice.
|
||||||
func (*fileArtifact) Dependencies() []Artifact { return nil }
|
func (*fileArtifact) Inputs() []Artifact { return nil }
|
||||||
|
|
||||||
// IsExclusive returns false: Cure returns a prepopulated buffer.
|
// IsExclusive returns false: Cure returns a prepopulated buffer.
|
||||||
func (*fileArtifact) IsExclusive() bool { return false }
|
func (*fileArtifact) IsExclusive() bool { return false }
|
||||||
@@ -75,7 +77,24 @@ func (a *fileArtifact) Checksum() Checksum {
|
|||||||
return Checksum(h.Sum(nil))
|
return Checksum(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Revision satisfies [RevisionArtifact] for incompatible IsExecutable behaviour.
|
||||||
|
func (*fileArtifact) Revision() uint64 { return 0 }
|
||||||
|
|
||||||
|
// IsExecutable returns whether the contents begin with shebang or the ELF
|
||||||
|
// magic number.
|
||||||
|
func (a *fileArtifact) IsExecutable() bool {
|
||||||
|
return a != nil &&
|
||||||
|
(bytes.HasPrefix(
|
||||||
|
*a, []byte{'#', '!'},
|
||||||
|
) || bytes.HasPrefix(
|
||||||
|
*a, []byte{0x7f, 'E', 'L', 'F'},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// Cure returns the caller-supplied data.
|
// Cure returns the caller-supplied data.
|
||||||
func (a *fileArtifact) Cure(*RContext) (io.ReadCloser, error) {
|
func (a *fileArtifact) Cure(*RContext) (io.ReadCloser, error) {
|
||||||
return io.NopCloser(bytes.NewReader(*a)), nil
|
return io.NopCloser(bytes.NewReader(*a)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CuresExempt exempts the cheap [KindFile] implementation.
|
||||||
|
func (*fileArtifact) CuresExempt() {}
|
||||||
|
|||||||
@@ -12,22 +12,41 @@ func TestFile(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
want := expectsFile{0}
|
want := expectsFile{0}
|
||||||
|
const ident = "N7M6W8-e55Wky_1q1a6qRKFVDw1S5XtPdB4HE2YZObG8OFum8HittyJgZUG5Ru2u"
|
||||||
|
wantShebang := expectsFile("#!/bin/sh\n")
|
||||||
|
const identShebang = "oKTGZb_TRtJf5G1iGGj4SOx1WUbI6YhmsUeEsCNvmK9tZYRwdwQf-DLFUxRPx5qm"
|
||||||
|
wantELF := expectsFile("\x7fELF\xde\xad\xbe\xef")
|
||||||
|
const identELF = "LjeHmIJgD1SiXIH8ewpGRed2_LU84NLapstnOAa_0hxLd8YY7x4KM0aApsj-RwCh"
|
||||||
checkWithCache(t, []cacheTestCase{
|
checkWithCache(t, []cacheTestCase{
|
||||||
{"file", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"file", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
cureMany(t, c, []cureStep{
|
cureMany(t, c, []cureStep{
|
||||||
{"short", pkg.NewFile("null", []byte{0}), base.Append(
|
{"short", pkg.NewFile("null", want), base.Append(
|
||||||
"identifier",
|
"identifier",
|
||||||
"3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi",
|
ident,
|
||||||
), want, nil},
|
), want, pkg.WNew, nil},
|
||||||
|
|
||||||
|
{"shebang", pkg.NewFile("shebang", wantShebang), base.Append(
|
||||||
|
"identifier",
|
||||||
|
identShebang,
|
||||||
|
), wantShebang, pkg.WNew, nil},
|
||||||
|
|
||||||
|
{"elf", pkg.NewFile("ELF", wantELF), base.Append(
|
||||||
|
"identifier",
|
||||||
|
identELF,
|
||||||
|
), wantELF, pkg.WNew, nil},
|
||||||
})
|
})
|
||||||
}, expectsFS{
|
}, expectsFS{
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
"checksum/" + pkg.Encode(want.hash()): {Mode: 0400, Data: []byte{0}},
|
"checksum/" + pkg.Encode(want.hash()): {Mode: 0400, Data: want},
|
||||||
|
"checksum/" + pkg.Encode(wantShebang.hash()): {Mode: 0500, Data: wantShebang},
|
||||||
|
"checksum/" + pkg.Encode(wantELF.hash()): {Mode: 0500, Data: wantELF},
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
"identifier/3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
"identifier/" + ident: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + pkg.Encode(want.hash()))},
|
||||||
|
"identifier/" + identShebang: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + pkg.Encode(wantShebang.hash()))},
|
||||||
|
"identifier/" + identELF: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + pkg.Encode(wantELF.hash()))},
|
||||||
|
|
||||||
"substitute": {Mode: fs.ModeDir | 0700},
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package expected
|
package expected
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Offline = "q5ktDTq0miP-VvB2blxqXQeaRXCUWgP_KbC18KNtUDtyoaI_h5mHmGuPMArVEBDs"
|
Offline = "IKmVhALejtYA3JhN1PCfjKF-Dio1XVcOeMWDHhfSHCbb4iBNhGjFy2UspZveZvb8"
|
||||||
OfflineS = "IY91PCtOpCYy21AaIK0c9f8-Z6fb2_2ewoHWkt4dxoLf0GOrWqS8yAGFLV84b1Dw"
|
OfflineS = "6ZubjSyCnz4-TKgQZi_BbY-JUwsStYCtD_f-BTDOOotfEbqkm47W20AazHRaLviz"
|
||||||
OvlRoot = "NacZGXwuRkTvcHaG08a22ujJ8qCWN0RSoFlRSR5FSt0ZcBbJ28FRvkYsHEtX7G8i"
|
OvlRoot = "Yox7WpG8B_OqIXWF4HWFZkUfsEl7DGzpFyZM0qtd9T8nQ-7SAkEUurlAe6_DJ6Iu"
|
||||||
Layers = "WBJDrATtX6rIE5yAu8ePX3WmDF0Tt9kFiue0m3cRnyRoVx1my8a67fh3CAW486oP"
|
Layers = "aAwfxDB-efVo379FNl178jK7HEKU3LEjHbZdaVVjk7GnXGZGEANNsXnB3SCUxM9A"
|
||||||
Net = "CmYtj2sNB3LHtqiDuck_Lz3MjLLIiwyP8N4NDitQ1Icvv__LVP9p8tm-sHeQaKKp"
|
Net = "IU3GwU2jTBd7HjotvFo2wsDWBQGVGpamwiSKdc2G1EnhM-ZaacxAuK6q0wbTVxra"
|
||||||
Promote = "TX3eCloaQFkV-SZIH6Jg6E5WKH--rcXY1P0jnZKmLFKWrNqnOzd4G9eIBh6i5ywN"
|
Promote = "rOjetGHzT_jPErrBR24IAZKDj8Q9dLLqxFQqloMgjsULss0VXDI--n6LxuMBHvmZ"
|
||||||
Work = "OuNiLSC68pZhAOr1YQ4WbV1tzASA0nxLEBcK7lO7MqxDY_j8dmP_C612RTuF23Lu"
|
Work = "7f9XDeREfvJIjCe0qesND0s0tasExAIcc-OH33-eq9tsrQPUXhJp4KEkpsYH4hCD"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package expected
|
package expected
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Offline = "WapqyoPxbWSnq07dWHt71mHaJXq99pAjJfFlELlJljSiZMhTFqqlzU1_mN86shSj"
|
Offline = "MTgCSzwPrzGigp9phvDPRwUTNGLm1KM2EsEtbz-l2cNieqjaGtnzm65CDXlSxXrl"
|
||||||
OfflineS = "ibQZHcdXgNQ1OiMX1FrburBbGPVvKEHvPilbQCkm_0oV0BQCHomyyTbYNrFMGIwl"
|
OfflineS = "p89bQg7_jR4nwaSbpEd08ne1jHOEw7Wlnlyv5FHq7ba1Kml3HSBZwEKRzu0gCqEb"
|
||||||
OvlRoot = "V9anFOiRvjGfAeBhLl14AL8TKdWZyD0WTPYe4fS9mOBw8iW5Lmarvt6TG6MV8uWm"
|
OvlRoot = "9ifwO6MAaV9kTqRR2O5PJzm8XhfXbr50mFLWyEHQm8OHHcJtAEUgXZzJYoZt9YVV"
|
||||||
Layers = "tKx7JNRoSBdK_7MdzI-nwTNV2wmiPzwYdcd17oLmXKL_iLmUzUiA79qTqdrTasrv"
|
Layers = "INuGActFPwJtS7mAR9Bkt7FD4yoHlXHSJ9CaSAQJrpkUBL_k815Cs_jvRfxy5-a7"
|
||||||
Net = "aXyDLzBCJ9XltXZIfetEVsEkrqHfcXuD5XE_FcUnYbN3emwL55N6P8LlHzNfGnM5"
|
Net = "kJvy24uk4ePvHoxSKf4vMOhR4NxLbmyWlA9bTS7d3z3fE9WF1Tvpao9g-QuYU4vm"
|
||||||
Promote = "3k4V16n96Lq04gjFSKmm4sFjyQ883FFBNXgTy9s_DjeTwxT3pg_iacEh8yMb_S4m"
|
Promote = "lYqyYrfjPawIbld5Qz0NbKvd_bmI3nJC1eEnlIQWzsjWE9uA1HuKPAaClnDeyG6L"
|
||||||
Work = "6Q49MhFWRE3Ne6MycwAotgl1GtoU5WCHqJNWG2byYZCY-zX-IxPrWiKk7bKkNzhE"
|
Work = "V6k4FqOBMRCmHiqchs5-joVGZ_MhNWZyXm3gdbU0S7S5eMze7G4SwYMTf66ojKQL"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package expected
|
package expected
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Offline = "Z6yXE5gOJScL3srmnVMWgCXccDiUNZ5snSrf6RkXuU1_U0rX_kGVwsfHUgNG_awd"
|
Offline = "LlieSHVDe1lnKvfI8t4oGRg_2hPxwyy1drmaqc-EKCOENuS9C2YHrp2VY5pSyhbg"
|
||||||
OfflineS = "zN16xv6LKRJRipUJwupyxg2rZcvf-qpsMn_qCxUmgxlTSuNwYI70ZEb7dHW5k0gO"
|
OfflineS = "qccjM8oYjUuEYPKFFzAxDFVzMerSUKUZc18NdVQoiPWf-70XOsNK8NaUQqjTgTAD"
|
||||||
OvlRoot = "zYXJHFRLuxvUhuisZEXgGgVvdQd6piMfp5jmtT6jdVjvC2gICXquOq-UTwlrSD5I"
|
OvlRoot = "lpV2bb_KGYFHPiOn-jtGop8K_ew0y1p3BezRp0ebM93_a-T9J-vkqGD5uokda1aN"
|
||||||
Layers = "_F8EDazHbcLeT0sVSQXRN_kn9IjduqJcDYgzXpsT-hpKU4EBcZ0PISN2zchpqMbm"
|
Layers = "sM3DJm210CHPGHkAquIbrXhspB81FpVC60ApQrvJGD5El2i7cSONj7LWf7EWqKpT"
|
||||||
Net = "CA_FAaSIYJgapBEHV40doxpH23PdUEy_6s1TZc7wfSPN0XYqwGpMceXXDSabGveO"
|
Net = "JihQUwsVETRYjb2-yIXLYZ6WVsaFebwl8btSYAsNQ2QapS5MZVaI-dYwrVoySycR"
|
||||||
Promote = "_3LPrLp--4h9k4GsNNApu9hHtAafq-GUhfU6d4hJKBDKT3bz_szOsvkXxc5sK53d"
|
Promote = "lGqwR4W2OjylSVitMQP1lc6wApOBkbMjsmIqkxQbfEZ3DHEQBbJr84ErW6Xb54zo"
|
||||||
Work = "FEgHeiCD_WT4wsfB-9kDH5n6cRWCEYtJmXdKZgmUUukAOoXumH_hLlosXREC-tqq"
|
Work = "aZgtyv6aMtoHFkHAaJr6nLaA7N_SHjShgWPyQ5UdzaIqYSiVqvCrZqSokGTCcTii"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -38,7 +38,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
environ := slices.DeleteFunc(slices.Clone(os.Environ()), func(s string) bool {
|
environ := slices.DeleteFunc(slices.Clone(os.Environ()), func(s string) bool {
|
||||||
return s == "CURE_JOBS="+strconv.Itoa(runtime.NumCPU())
|
for _, t := range []string{
|
||||||
|
"CURE_JOBS=" + strconv.Itoa(runtime.NumCPU()),
|
||||||
|
"CURE_LOAD=" + strconv.Itoa(runtime.NumCPU()+2),
|
||||||
|
} {
|
||||||
|
if s == t {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
var hostNet, layers, promote bool
|
var hostNet, layers, promote bool
|
||||||
|
|||||||
+58
-27
@@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"iter"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -20,7 +21,7 @@ import (
|
|||||||
const wordSize = 8
|
const wordSize = 8
|
||||||
|
|
||||||
// alignSize returns the padded size for aligning sz.
|
// alignSize returns the padded size for aligning sz.
|
||||||
func alignSize(sz int) int {
|
func alignSize[T int | uint64](sz T) T {
|
||||||
return sz + (wordSize-(sz)%wordSize)%wordSize
|
return sz + (wordSize-(sz)%wordSize)%wordSize
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +66,18 @@ func NewIR() *IRCache {
|
|||||||
return &IRCache{zeroIRCache()}
|
return &IRCache{zeroIRCache()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inputs returns an iterator over direct and transitive inputs of an [Artifact]
|
||||||
|
// in randomised order.
|
||||||
|
func Inputs(a Artifact) iter.Seq2[Artifact, unique.Handle[ID]] {
|
||||||
|
ic := NewIR()
|
||||||
|
ic.Ident(a)
|
||||||
|
return func(yield func(Artifact, unique.Handle[ID]) bool) {
|
||||||
|
ic.artifact.Range(func(key, value any) bool {
|
||||||
|
return yield(key.(Artifact), value.(unique.Handle[ID]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// IContext is passed to [Artifact.Params] and provides methods for writing
|
// IContext is passed to [Artifact.Params] and provides methods for writing
|
||||||
// values to the IR writer. It does not expose the underlying [io.Writer].
|
// values to the IR writer. It does not expose the underlying [io.Writer].
|
||||||
//
|
//
|
||||||
@@ -77,7 +90,7 @@ type IContext struct {
|
|||||||
// returns and must not be exposed directly.
|
// returns and must not be exposed directly.
|
||||||
w io.Writer
|
w io.Writer
|
||||||
// Optional [Artifact] to cureRes cache, replaces [IRKindIdent] with
|
// Optional [Artifact] to cureRes cache, replaces [IRKindIdent] with
|
||||||
// checksum values if non-nil.
|
// checksum values if non-nil. The pathname field may not be populated.
|
||||||
inputs map[Artifact]cureRes
|
inputs map[Artifact]cureRes
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +173,7 @@ func (i *IContext) mustWrite(p []byte) {
|
|||||||
|
|
||||||
// WriteIdent writes the identifier of [Artifact] to the IR. The behaviour of
|
// WriteIdent writes the identifier of [Artifact] to the IR. The behaviour of
|
||||||
// WriteIdent is not defined for an [Artifact] not part of the slice returned by
|
// WriteIdent is not defined for an [Artifact] not part of the slice returned by
|
||||||
// [Artifact.Dependencies].
|
// [Artifact.Inputs].
|
||||||
func (i *IContext) WriteIdent(a Artifact) {
|
func (i *IContext) WriteIdent(a Artifact) {
|
||||||
buf := i.ic.getIdentBuf()
|
buf := i.ic.getIdentBuf()
|
||||||
defer i.ic.putIdentBuf(buf)
|
defer i.ic.putIdentBuf(buf)
|
||||||
@@ -222,13 +235,14 @@ func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encode implements Encode but replaces identifiers with their cured checksums
|
// encode implements Encode but replaces identifiers with their cured checksums
|
||||||
// for a non-nil ident. Caller must acquire Cache.identMu.
|
// for a non-nil ident. The pathname field is unused. Caller must acquire
|
||||||
|
// Cache.identMu.
|
||||||
func (ic *irCache) encode(
|
func (ic *irCache) encode(
|
||||||
w io.Writer,
|
w io.Writer,
|
||||||
a Artifact,
|
a Artifact,
|
||||||
inputs map[Artifact]cureRes,
|
inputs map[Artifact]cureRes,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
deps := a.Dependencies()
|
deps := a.Inputs()
|
||||||
idents := make([]*extIdent, len(deps))
|
idents := make([]*extIdent, len(deps))
|
||||||
if inputs == nil {
|
if inputs == nil {
|
||||||
for i, d := range deps {
|
for i, d := range deps {
|
||||||
@@ -264,10 +278,11 @@ func (ic *irCache) encode(
|
|||||||
return *a == *b
|
return *a == *b
|
||||||
})
|
})
|
||||||
|
|
||||||
// kind uint64 | deps_sz uint64
|
// kind uint64 | rev uint64 | deps_sz uint64
|
||||||
var buf [wordSize * 2]byte
|
var buf [wordSize * 3]byte
|
||||||
binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
|
binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
|
||||||
binary.LittleEndian.PutUint64(buf[wordSize:], uint64(len(idents)))
|
binary.LittleEndian.PutUint64(buf[wordSize:], GetRevision(a))
|
||||||
|
binary.LittleEndian.PutUint64(buf[wordSize*2:], uint64(len(idents)))
|
||||||
if _, err = w.Write(buf[:]); err != nil {
|
if _, err = w.Write(buf[:]); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -317,7 +332,7 @@ func (ic *irCache) encodeAll(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range a.Dependencies() {
|
for _, d := range a.Inputs() {
|
||||||
if err = ic.encodeAll(w, d, encoded); err != nil {
|
if err = ic.encodeAll(w, d, encoded); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -419,9 +434,9 @@ type (
|
|||||||
// Common buffer for word-sized reads.
|
// Common buffer for word-sized reads.
|
||||||
buf [wordSize]byte
|
buf [wordSize]byte
|
||||||
|
|
||||||
// Dependencies sent before params, sorted by identifier. Resliced on
|
// Inputs sent before params, sorted by identifier. Resliced on
|
||||||
// each call to Next and checked to be depleted during Finalise.
|
// each call to Next and checked to be depleted during Finalise.
|
||||||
deps []*extIdent
|
inputs []*extIdent
|
||||||
|
|
||||||
// Number of values already read, -1 denotes a finalised IRReader.
|
// Number of values already read, -1 denotes a finalised IRReader.
|
||||||
count int
|
count int
|
||||||
@@ -505,7 +520,7 @@ const (
|
|||||||
irMaxValues = 1 << 12
|
irMaxValues = 1 << 12
|
||||||
|
|
||||||
// irMaxDeps is the arbitrary maximum number of direct dependencies allowed
|
// irMaxDeps is the arbitrary maximum number of direct dependencies allowed
|
||||||
// to be returned by [Artifact.Dependencies] and subsequently decoded by
|
// to be returned by [Artifact.Inputs] and subsequently decoded by
|
||||||
// [IRDecoder].
|
// [IRDecoder].
|
||||||
irMaxDeps = 1 << 10
|
irMaxDeps = 1 << 10
|
||||||
)
|
)
|
||||||
@@ -590,17 +605,17 @@ func (ir *IRReader) mustReadHeader(k IRValueKind) {
|
|||||||
|
|
||||||
// putAll returns all dependency buffers to the underlying [Cache].
|
// putAll returns all dependency buffers to the underlying [Cache].
|
||||||
func (ir *IRReader) putAll() {
|
func (ir *IRReader) putAll() {
|
||||||
for _, buf := range ir.deps {
|
for _, buf := range ir.inputs {
|
||||||
ir.d.c.putIdentBuf(buf)
|
ir.d.c.putIdentBuf(buf)
|
||||||
}
|
}
|
||||||
ir.deps = nil
|
ir.inputs = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscardAll discards all unstructured dependencies. This is useful to
|
// DiscardAll discards all unstructured dependencies. This is useful to
|
||||||
// implementations that encode dependencies as [IRKindIdent] which are read back
|
// implementations that encode dependencies as [IRKindIdent] which are read back
|
||||||
// via ReadIdent.
|
// via ReadIdent.
|
||||||
func (ir *IRReader) DiscardAll() {
|
func (ir *IRReader) DiscardAll() {
|
||||||
if ir.deps == nil {
|
if ir.inputs == nil {
|
||||||
panic("attempting to discard dependencies twice")
|
panic("attempting to discard dependencies twice")
|
||||||
}
|
}
|
||||||
ir.putAll()
|
ir.putAll()
|
||||||
@@ -612,13 +627,13 @@ var ErrDependencyDepleted = errors.New("reading past end of dependencies")
|
|||||||
|
|
||||||
// Next returns the next unstructured dependency.
|
// Next returns the next unstructured dependency.
|
||||||
func (ir *IRReader) Next() Artifact {
|
func (ir *IRReader) Next() Artifact {
|
||||||
if len(ir.deps) == 0 {
|
if len(ir.inputs) == 0 {
|
||||||
panic(ErrDependencyDepleted)
|
panic(ErrDependencyDepleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
id := unique.Make(ID(ir.deps[0][wordSize:]))
|
id := unique.Make(ID(ir.inputs[0][wordSize:]))
|
||||||
ir.d.c.putIdentBuf(ir.deps[0])
|
ir.d.c.putIdentBuf(ir.inputs[0])
|
||||||
ir.deps = ir.deps[1:]
|
ir.inputs = ir.inputs[1:]
|
||||||
|
|
||||||
if a, ok := ir.d.ident[id]; !ok {
|
if a, ok := ir.d.ident[id]; !ok {
|
||||||
ir.putAll()
|
ir.putAll()
|
||||||
@@ -671,8 +686,8 @@ func (ir *IRReader) Finalise() (checksum unique.Handle[Checksum], ok bool) {
|
|||||||
}
|
}
|
||||||
ir.r, ir.ibw = nil, nil
|
ir.r, ir.ibw = nil, nil
|
||||||
|
|
||||||
if len(ir.deps) != 0 {
|
if len(ir.inputs) != 0 {
|
||||||
panic(MissedDependencyError(len(ir.deps)))
|
panic(MissedDependencyError(len(ir.inputs)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -726,6 +741,15 @@ func (ir *IRReader) ReadString() string {
|
|||||||
return unsafe.String(unsafe.SliceData(p), len(p))
|
return unsafe.String(unsafe.SliceData(p), len(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A RevisionError describes the first [Artifact] in an IR stream claiming a
|
||||||
|
// revision not supported by the registered implementation.
|
||||||
|
type RevisionError [2]uint64
|
||||||
|
|
||||||
|
func (e RevisionError) Error() string {
|
||||||
|
return "claimed revision " + strconv.FormatUint(e[0], 10) +
|
||||||
|
" differs from implementation value " + strconv.FormatUint(e[1], 10)
|
||||||
|
}
|
||||||
|
|
||||||
// decode decodes the next [Artifact] in the IR stream and returns any buffer
|
// decode decodes the next [Artifact] in the IR stream and returns any buffer
|
||||||
// originating from [Cache] before returning. decode returns [io.EOF] if and
|
// originating from [Cache] before returning. decode returns [io.EOF] if and
|
||||||
// only if the underlying [io.Reader] is already read to EOF.
|
// only if the underlying [io.Reader] is already read to EOF.
|
||||||
@@ -758,6 +782,9 @@ func (d *IRDecoder) decode() (a Artifact, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ir.mustRead(ir.buf[:])
|
||||||
|
rev := binary.LittleEndian.Uint64(ir.buf[:])
|
||||||
|
|
||||||
defer ir.putAll()
|
defer ir.putAll()
|
||||||
ir.mustRead(ir.buf[:])
|
ir.mustRead(ir.buf[:])
|
||||||
sz := binary.LittleEndian.Uint64(ir.buf[:])
|
sz := binary.LittleEndian.Uint64(ir.buf[:])
|
||||||
@@ -765,11 +792,11 @@ func (d *IRDecoder) decode() (a Artifact, err error) {
|
|||||||
err = ErrIRDepend
|
err = ErrIRDepend
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ir.deps = make([]*extIdent, sz)
|
ir.inputs = make([]*extIdent, sz)
|
||||||
for i := range ir.deps {
|
for i := range ir.inputs {
|
||||||
ir.deps[i] = d.c.getIdentBuf()
|
ir.inputs[i] = d.c.getIdentBuf()
|
||||||
}
|
}
|
||||||
for _, buf := range ir.deps {
|
for _, buf := range ir.inputs {
|
||||||
ir.mustRead(buf[:])
|
ir.mustRead(buf[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,6 +805,10 @@ func (d *IRDecoder) decode() (a Artifact, err error) {
|
|||||||
err = syscall.ENOTRECOVERABLE
|
err = syscall.ENOTRECOVERABLE
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if _rev := GetRevision(a); _rev != rev {
|
||||||
|
err = RevisionError{rev, _rev}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ir.count != -1 {
|
if ir.count != -1 {
|
||||||
err = ErrRemainingIR
|
err = ErrRemainingIR
|
||||||
@@ -792,8 +823,8 @@ func (d *IRDecoder) decode() (a Artifact, err error) {
|
|||||||
d.ident[id] = a
|
d.ident[id] = a
|
||||||
} else {
|
} else {
|
||||||
d.c.msg.Verbosef(
|
d.c.msg.Verbosef(
|
||||||
"artifact %s appeared more than once in IR stream",
|
"artifact %s%s%s appeared more than once in IR stream",
|
||||||
Encode(id.Value()),
|
d.c.sgrIdent, Encode(id.Value()), d.c.sgrRes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+50
-21
@@ -27,16 +27,14 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||||
)},
|
)},
|
||||||
|
|
||||||
{"http get tar", pkg.NewHTTPGetTar(
|
{"http get tar", pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet(
|
||||||
nil, "file:///testdata",
|
nil, "file:///testdata",
|
||||||
pkg.Checksum(bytes.Repeat([]byte{0xff}, len(pkg.Checksum{}))),
|
pkg.Checksum(bytes.Repeat([]byte{0xff}, len(pkg.Checksum{}))),
|
||||||
pkg.TarBzip2,
|
), pkg.Bzip2))},
|
||||||
)},
|
{"http get tar unaligned", pkg.NewTar(pkg.NewHTTPGet(
|
||||||
{"http get tar unaligned", pkg.NewHTTPGetTar(
|
|
||||||
nil, "https://hakurei.app",
|
nil, "https://hakurei.app",
|
||||||
pkg.Checksum(bytes.Repeat([]byte{0xfe}, len(pkg.Checksum{}))),
|
pkg.Checksum(bytes.Repeat([]byte{0xfe}, len(pkg.Checksum{}))),
|
||||||
pkg.TarUncompressed,
|
))},
|
||||||
)},
|
|
||||||
|
|
||||||
{"exec offline", pkg.NewExec(
|
{"exec offline", pkg.NewExec(
|
||||||
"exec-offline", "", nil, 0, false, false,
|
"exec-offline", "", nil, 0, false, false,
|
||||||
@@ -47,15 +45,13 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
|
|
||||||
pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
|
pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
|
||||||
"stub file",
|
"stub file",
|
||||||
))), pkg.MustPath("/.hakurei", false, pkg.NewHTTPGetTar(
|
))), pkg.MustPath("/.hakurei", false, pkg.NewTar(pkg.NewHTTPGet(
|
||||||
nil, "file:///hakurei.tar",
|
nil, "file:///hakurei.tar",
|
||||||
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||||
pkg.TarUncompressed,
|
))), pkg.MustPath("/opt", false, pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet(
|
||||||
)), pkg.MustPath("/opt", false, pkg.NewHTTPGetTar(
|
|
||||||
nil, "file:///testtool.tar.gz",
|
nil, "file:///testtool.tar.gz",
|
||||||
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||||
pkg.TarGzip,
|
), pkg.Gzip))),
|
||||||
)),
|
|
||||||
)},
|
)},
|
||||||
|
|
||||||
{"exec net", pkg.NewExec(
|
{"exec net", pkg.NewExec(
|
||||||
@@ -69,15 +65,13 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
|
|
||||||
pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
|
pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
|
||||||
"stub file",
|
"stub file",
|
||||||
))), pkg.MustPath("/.hakurei", false, pkg.NewHTTPGetTar(
|
))), pkg.MustPath("/.hakurei", false, pkg.NewTar(pkg.NewHTTPGet(
|
||||||
nil, "file:///hakurei.tar",
|
nil, "file:///hakurei.tar",
|
||||||
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||||
pkg.TarUncompressed,
|
))), pkg.MustPath("/opt", false, pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet(
|
||||||
)), pkg.MustPath("/opt", false, pkg.NewHTTPGetTar(
|
|
||||||
nil, "file:///testtool.tar.gz",
|
nil, "file:///testtool.tar.gz",
|
||||||
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||||
pkg.TarGzip,
|
), pkg.Gzip))),
|
||||||
)),
|
|
||||||
)},
|
)},
|
||||||
|
|
||||||
{"exec measured", pkg.NewExec(
|
{"exec measured", pkg.NewExec(
|
||||||
@@ -91,19 +85,21 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
|
|
||||||
pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
|
pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
|
||||||
"stub file",
|
"stub file",
|
||||||
))), pkg.MustPath("/.hakurei", false, pkg.NewHTTPGetTar(
|
))), pkg.MustPath("/.hakurei", false, pkg.NewTar(pkg.NewHTTPGet(
|
||||||
nil, "file:///hakurei.tar",
|
nil, "file:///hakurei.tar",
|
||||||
pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))),
|
pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))),
|
||||||
pkg.TarUncompressed,
|
))), pkg.MustPath("/opt", false, pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet(
|
||||||
)), pkg.MustPath("/opt", false, pkg.NewHTTPGetTar(
|
|
||||||
nil, "file:///testtool.tar.gz",
|
nil, "file:///testtool.tar.gz",
|
||||||
pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))),
|
pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))),
|
||||||
pkg.TarGzip,
|
), pkg.Gzip))),
|
||||||
)),
|
|
||||||
)},
|
)},
|
||||||
|
|
||||||
{"file anonymous", pkg.NewFile("", []byte{0})},
|
{"file anonymous", pkg.NewFile("", []byte{0})},
|
||||||
{"file", pkg.NewFile("stub", []byte("stub"))},
|
{"file", pkg.NewFile("stub", []byte("stub"))},
|
||||||
|
|
||||||
|
{"decompress", pkg.NewDecompress(pkg.NewFile("", []byte{0}), pkg.Bzip2)},
|
||||||
|
|
||||||
|
{"archive", pkg.NewArchive(pkg.NewFile("", []byte{0}))},
|
||||||
}
|
}
|
||||||
testCasesCache := make([]cacheTestCase, len(testCases))
|
testCasesCache := make([]cacheTestCase, len(testCases))
|
||||||
for i, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
@@ -139,3 +135,36 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
}
|
}
|
||||||
checkWithCache(t, testCasesCache)
|
checkWithCache(t, testCasesCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRevision(t *testing.T) {
|
||||||
|
checkWithCache(t, []cacheTestCase{{"revision", 0, nil,
|
||||||
|
func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
|
a := pkg.NewFile("", nil)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := c.EncodeAll(&buf, a); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := c.NewDecoder(
|
||||||
|
bytes.NewReader(buf.Bytes()),
|
||||||
|
).Decode(); err != nil {
|
||||||
|
t.Fatalf("Decode: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := buf.Bytes()
|
||||||
|
p[8] = 0xfd
|
||||||
|
wantErr := pkg.RevisionError{0xfd, 0}
|
||||||
|
if _, err := c.NewDecoder(
|
||||||
|
bytes.NewReader(p),
|
||||||
|
).Decode(); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
t.Fatalf("Decode: error = %v, want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
}, expectsFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
},
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|||||||
+5
-2
@@ -54,8 +54,8 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dependencies returns a nil slice.
|
// Inputs returns a nil slice.
|
||||||
func (*httpArtifact) Dependencies() []Artifact { return nil }
|
func (*httpArtifact) Inputs() []Artifact { return nil }
|
||||||
|
|
||||||
// IsExclusive returns false: Cure returns as soon as a response is received.
|
// IsExclusive returns false: Cure returns as soon as a response is received.
|
||||||
func (*httpArtifact) IsExclusive() bool { return false }
|
func (*httpArtifact) IsExclusive() bool { return false }
|
||||||
@@ -74,6 +74,9 @@ func (e ResponseStatusError) Error() string {
|
|||||||
return "the requested URL returned non-OK status: " + http.StatusText(int(e))
|
return "the requested URL returned non-OK status: " + http.StatusText(int(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsExecutable returns false.
|
||||||
|
func (*httpArtifact) IsExecutable() bool { return false }
|
||||||
|
|
||||||
// Cure sends the http request and returns the resulting response body reader
|
// Cure sends the http request and returns the resulting response body reader
|
||||||
// wrapped to perform checksum validation. It is valid but not encouraged to
|
// wrapped to perform checksum validation. It is valid but not encouraged to
|
||||||
// close the resulting [io.ReadCloser] before it is read to EOF, as that causes
|
// close the resulting [io.ReadCloser] before it is read to EOF, as that causes
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
)
|
)
|
||||||
wantPathname := base.Append(
|
wantPathname := base.Append(
|
||||||
"identifier",
|
"identifier",
|
||||||
"oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_",
|
"COBRUwkxpIvZWJggpRxn7HVXiw6epXZOex8jGRJKpowpwukV7GRBY_LBvOJNQHue",
|
||||||
)
|
)
|
||||||
if pathname, checksum, err := c.Cure(f); err != nil {
|
if pathname, checksum, err := c.Cure(f); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
@@ -158,7 +158,7 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
"checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU": {Mode: 0400, Data: []byte("\x7f\xe1\x69\xa2\xdd\x63\x96\x26\x83\x79\x61\x8b\xf0\x3f\xd5\x16\x9a\x39\x3a\xdb\xcf\xb1\xbc\x8d\x33\xff\x75\xee\x62\x56\xa9\xf0\x27\xac\x13\x94\x69")},
|
"checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU": {Mode: 0400, Data: []byte("\x7f\xe1\x69\xa2\xdd\x63\x96\x26\x83\x79\x61\x8b\xf0\x3f\xd5\x16\x9a\x39\x3a\xdb\xcf\xb1\xbc\x8d\x33\xff\x75\xee\x62\x56\xa9\xf0\x27\xac\x13\x94\x69")},
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
"identifier/oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
|
"identifier/COBRUwkxpIvZWJggpRxn7HVXiw6epXZOex8jGRJKpowpwukV7GRBY_LBvOJNQHue": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
|
||||||
|
|
||||||
"substitute": {Mode: fs.ModeDir | 0700},
|
"substitute": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
|||||||
+713
-148
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user