forked from security/hakurei
Compare commits
208 Commits
c4f64f7606
...
pkgserver
| Author | SHA1 | Date | |
|---|---|---|---|
| 0cf14847ce | |||
| 10fe8a0a1e | |||
| 86cd57f5f8 | |||
| ca114393cc | |||
| f864200c9b | |||
| 61acc41a18 | |||
| 02de87f9aa | |||
| 5b8c7a87a9 | |||
| 8d6ad63e5e | |||
|
48cdf8bf85
|
|||
|
7fb42ba49d
|
|||
|
19a2737148
|
|||
|
baf2def9cc
|
|||
|
242e042cb9
|
|||
|
6988c9c4db
|
|||
|
d6e0ed8c76
|
|||
|
53be3309c5
|
|||
|
644dd18a52
|
|||
|
27c6f976df
|
|||
|
279a973633
|
|||
|
9c1b522689
|
|||
|
5c8cd46c02
|
|||
|
2dba550a2b
|
|||
|
8c64812b34
|
|||
|
d1423d980d
|
|||
|
104da0f66a
|
|||
|
d996d9fbb7
|
|||
|
469f97ccc1
|
|||
|
af7a6180a1
|
|||
|
03b5c0e20a
|
|||
|
6a31fb4fa3
|
|||
|
bae45363bc
|
|||
|
2c17d1abe0
|
|||
|
0aa459d1a9
|
|||
|
00053e6287
|
|||
|
3a0c020150
|
|||
|
78655f159e
|
|||
|
30bb52e380
|
|||
|
66197ebdb2
|
|||
|
f7a2744025
|
|||
|
f16b7bfaf0
|
|||
|
6228cda7ad
|
|||
|
86c336de88
|
|||
|
ba5d882ef2
|
|||
|
1e0d68a29e
|
|||
|
80f2367c16
|
|||
|
5ea4dae4b8
|
|||
|
eb1a3918a8
|
|||
|
349011a5e6
|
|||
|
861249751a
|
|||
|
e3445c2a7e
|
|||
|
7315e64a8a
|
|||
|
7d74454f6d
|
|||
|
96956c849a
|
|||
|
aabdcbba1c
|
|||
|
38cc4a6429
|
|||
|
27ef7f81fa
|
|||
|
f7888074b9
|
|||
|
95ffe0429c
|
|||
|
16d0cf04c1
|
|||
|
6a2b32b48c
|
|||
|
c1472fc54d
|
|||
|
179cf07e48
|
|||
|
c2d2795e2b
|
|||
|
2c1d7edd7a
|
|||
|
1ee8d09223
|
|||
|
7f01cb3d59
|
|||
|
65ae4f57c2
|
|||
|
77110601cc
|
|||
|
c5b1949430
|
|||
|
17805cdfa8
|
|||
|
9c9befb4c9
|
|||
|
fcdf9ecee4
|
|||
|
fbd97b658f
|
|||
|
c93725ac58
|
|||
|
f14ab80253
|
|||
|
9989881dd9
|
|||
|
a36b3ece16
|
|||
|
75970a5650
|
|||
|
572c99825d
|
|||
|
ebdf9dcecc
|
|||
|
8ea2a56d5b
|
|||
|
159a45c027
|
|||
|
0eb2bfa12e
|
|||
|
e19a98244a
|
|||
|
7e2f13fa1b
|
|||
|
97448e2104
|
|||
|
a87ad28b8b
|
|||
|
883d4ee4af
|
|||
|
d2c6d486b0
|
|||
|
6fdd800b2b
|
|||
|
94e3debc63
|
|||
|
ea87664a75
|
|||
|
04d9984da0
|
|||
|
145ccd1c92
|
|||
|
c5089cad78
|
|||
|
c83905f311
|
|||
|
b7cc14f296
|
|||
|
57e1e5141d
|
|||
|
1440195c3f
|
|||
|
cc60e0d15d
|
|||
|
9deaf853f0
|
|||
|
2baa9df133
|
|||
|
51d3df2419
|
|||
|
1d0fcf3a75
|
|||
|
e92971e0c2
|
|||
|
6159c74e96
|
|||
|
2a34a269d0
|
|||
|
ef130adb27
|
|||
|
5694e528e6
|
|||
|
b4e82e68a7
|
|||
|
d041fee791
|
|||
|
cefd02e960
|
|||
|
ad8f799703
|
|||
|
c74c269b66
|
|||
|
4b0cce4db5
|
|||
|
cd9b534d6b
|
|||
|
84e6922f30
|
|||
|
c16725a679
|
|||
|
a6160cd410
|
|||
|
826347fe1f
|
|||
|
085eaed7ba
|
|||
|
37d368a7f9
|
|||
|
2aeac7f582
|
|||
|
2b93631f52
|
|||
|
b3749aaf0b
|
|||
|
c8bb88cced
|
|||
|
f7f80f95b9
|
|||
|
6ea6c794fb
|
|||
|
6c2da4c4b2
|
|||
|
90f915a708
|
|||
|
a5fea4686e
|
|||
|
ae8c365c0f
|
|||
|
485db515f7
|
|||
|
ec7ee0789e
|
|||
|
42c93a57a4
|
|||
|
b1b14810ac
|
|||
|
de117ef365
|
|||
|
5e4bf23e0c
|
|||
|
d4519e2075
|
|||
|
7f1e4cf43c
|
|||
|
d021621fba
|
|||
|
56567307ec
|
|||
|
0264a1ef09
|
|||
|
0123bbee3d
|
|||
|
771adad603
|
|||
|
178305cb22
|
|||
|
c2456e252c
|
|||
|
273068b90c
|
|||
|
16b20e1d34
|
|||
|
b983917a6e
|
|||
|
e1b8f40add
|
|||
|
6df0d37c5a
|
|||
|
1619b06541
|
|||
|
e335d99c6b
|
|||
|
d888d09b6d
|
|||
|
54176e7315
|
|||
|
3bfe99d3d8
|
|||
|
149dfbb6af
|
|||
|
58801b44d4
|
|||
|
e065bbf792
|
|||
|
a883e57e7d
|
|||
|
ef9bd8ecbf
|
|||
|
a40527dcb2
|
|||
|
88d9a6163e
|
|||
|
47860b0387
|
|||
|
50c9da8b6d
|
|||
|
16966043c7
|
|||
|
a3515a6ef5
|
|||
|
7f05baab28
|
|||
|
d4d5e631ae
|
|||
|
1df3bcc3b9
|
|||
|
1809b53e52
|
|||
|
67b2914c94
|
|||
|
74dee11822
|
|||
|
a58c9258cc
|
|||
|
710b164c91
|
|||
|
93911d6015
|
|||
|
bb097536d4
|
|||
|
49b6526a38
|
|||
|
f9c31df94d
|
|||
|
4f570cc5c9
|
|||
|
5828631e79
|
|||
|
4f9f4875d7
|
|||
|
d49e654482
|
|||
|
b746e352e5
|
|||
|
c620d88dce
|
|||
|
7cd14b8865
|
|||
|
3e18a4b397
|
|||
|
1791b604b5
|
|||
|
59ff6db7ec
|
|||
|
430e099556
|
|||
|
17b64bb42c
|
|||
|
dbb89dfb0f
|
|||
|
de06ea2be4
|
|||
|
1ef7bedfb5
|
|||
|
05a828c474
|
|||
|
0061d11f93
|
|||
|
fb101a02f2
|
|||
|
3dbd67d113
|
|||
|
f511f0a9e9
|
|||
|
47995137b3
|
|||
|
e1b8607101
|
|||
|
3d3bd45b95
|
|||
|
9fb0b2452e
|
|||
|
a3e87dd0ef
|
|||
|
90a38c0708
|
|||
|
39cc8caa93
|
@@ -89,23 +89,6 @@ jobs:
|
|||||||
path: result/*
|
path: result/*
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
hpkg:
|
|
||||||
name: Hpkg
|
|
||||||
runs-on: nix
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Run NixOS test
|
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.hpkg
|
|
||||||
|
|
||||||
- name: Upload test output
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: "hpkg-vm-output"
|
|
||||||
path: result/*
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
check:
|
check:
|
||||||
name: Flake checks
|
name: Flake checks
|
||||||
needs:
|
needs:
|
||||||
@@ -114,7 +97,6 @@ jobs:
|
|||||||
- sandbox
|
- sandbox
|
||||||
- sandbox-race
|
- sandbox-race
|
||||||
- sharefs
|
- sharefs
|
||||||
- hpkg
|
|
||||||
runs-on: nix
|
runs-on: nix
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
5
.github/workflows/README
vendored
5
.github/workflows/README
vendored
@@ -1,5 +0,0 @@
|
|||||||
DO NOT ADD NEW ACTIONS HERE
|
|
||||||
|
|
||||||
This port is solely for releasing to the github mirror and serves no purpose during development.
|
|
||||||
All development happens at https://git.gensokyo.uk/security/hakurei. If you wish to contribute,
|
|
||||||
request for an account on git.gensokyo.uk.
|
|
||||||
46
.github/workflows/release.yml
vendored
46
.github/workflows/release.yml
vendored
@@ -1,46 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Create release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Nix
|
|
||||||
uses: nixbuild/nix-quick-install-action@v32
|
|
||||||
with:
|
|
||||||
nix_conf: |
|
|
||||||
keep-env-derivations = true
|
|
||||||
keep-outputs = true
|
|
||||||
|
|
||||||
- name: Restore and cache Nix store
|
|
||||||
uses: nix-community/cache-nix-action@v6
|
|
||||||
with:
|
|
||||||
primary-key: build-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
|
|
||||||
restore-prefixes-first-match: build-${{ runner.os }}-
|
|
||||||
gc-max-store-size-linux: 1G
|
|
||||||
purge: true
|
|
||||||
purge-prefixes: build-${{ runner.os }}-
|
|
||||||
purge-created: 60
|
|
||||||
purge-primary-key: never
|
|
||||||
|
|
||||||
- name: Build for release
|
|
||||||
run: nix build --print-out-paths --print-build-logs .#dist
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: |-
|
|
||||||
result/hakurei-**
|
|
||||||
48
.github/workflows/test.yml
vendored
48
.github/workflows/test.yml
vendored
@@ -1,48 +0,0 @@
|
|||||||
name: Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
- push
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
dist:
|
|
||||||
name: Create distribution
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Nix
|
|
||||||
uses: nixbuild/nix-quick-install-action@v32
|
|
||||||
with:
|
|
||||||
nix_conf: |
|
|
||||||
keep-env-derivations = true
|
|
||||||
keep-outputs = true
|
|
||||||
|
|
||||||
- name: Restore and cache Nix store
|
|
||||||
uses: nix-community/cache-nix-action@v6
|
|
||||||
with:
|
|
||||||
primary-key: build-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
|
|
||||||
restore-prefixes-first-match: build-${{ runner.os }}-
|
|
||||||
gc-max-store-size-linux: 1G
|
|
||||||
purge: true
|
|
||||||
purge-prefixes: build-${{ runner.os }}-
|
|
||||||
purge-created: 60
|
|
||||||
purge-primary-key: never
|
|
||||||
|
|
||||||
- name: Build for test
|
|
||||||
id: build-test
|
|
||||||
run: >-
|
|
||||||
export HAKUREI_REV="$(git rev-parse --short HEAD)" &&
|
|
||||||
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' package.nix &&
|
|
||||||
nix build --print-out-paths --print-build-logs .#dist &&
|
|
||||||
mv package.nix.old package.nix &&
|
|
||||||
echo "rev=$HAKUREI_REV" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Upload test build
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: "hakurei-${{ steps.build-test.outputs.rev }}"
|
|
||||||
path: result/*
|
|
||||||
retention-days: 1
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -28,6 +28,7 @@ go.work.sum
|
|||||||
# go generate
|
# go generate
|
||||||
/cmd/hakurei/LICENSE
|
/cmd/hakurei/LICENSE
|
||||||
/internal/pkg/testdata/testtool
|
/internal/pkg/testdata/testtool
|
||||||
|
/internal/rosa/hakurei_current.tar.gz
|
||||||
|
|
||||||
# release
|
# release
|
||||||
/dist/hakurei-*
|
/dist/hakurei-*
|
||||||
|
|||||||
181
README.md
181
README.md
@@ -15,164 +15,51 @@
|
|||||||
<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>
|
||||||
|
|
||||||
Hakurei is a tool for running sandboxed graphical applications as dedicated subordinate users on the Linux kernel.
|
Hakurei is a tool for running sandboxed desktop applications as dedicated
|
||||||
It implements the application container of [planterette (WIP)](https://git.gensokyo.uk/security/planterette),
|
subordinate users on the Linux kernel. It implements the application container
|
||||||
a self-contained Android-like package manager with modern security features.
|
of [planterette (WIP)](https://git.gensokyo.uk/security/planterette), a
|
||||||
|
self-contained Android-like package manager with modern security features.
|
||||||
|
|
||||||
## NixOS Module usage
|
Interaction with hakurei happens entirely through structures described by
|
||||||
|
package [hst](https://pkg.go.dev/hakurei.app/hst). No native API is available
|
||||||
|
due to internal details of uid isolation.
|
||||||
|
|
||||||
The NixOS module currently requires home-manager to configure subordinate users. Full module documentation can be found [here](options.md).
|
## Notable Packages
|
||||||
|
|
||||||
To use the module, import it into your configuration with
|
Package [container](https://pkg.go.dev/hakurei.app/container) is general purpose
|
||||||
|
container tooling. It is used by the hakurei shim process running as the target
|
||||||
|
subordinate user to set up the application container. It has a single dependency,
|
||||||
|
[libseccomp](https://github.com/seccomp/libseccomp), to create BPF programs
|
||||||
|
for the [system call filter](https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html).
|
||||||
|
|
||||||
```nix
|
Package [internal/pkg](https://pkg.go.dev/hakurei.app/internal/pkg) provides
|
||||||
{
|
infrastructure for hermetic builds. This replaces the legacy nix-based testing
|
||||||
inputs = {
|
framework and serves as the build system of Rosa OS, currently developed under
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
package [internal/rosa](https://pkg.go.dev/hakurei.app/internal/rosa).
|
||||||
|
|
||||||
hakurei = {
|
## Dependencies
|
||||||
url = "git+https://git.gensokyo.uk/security/hakurei";
|
|
||||||
|
|
||||||
# Optional but recommended to limit the size of your system closure.
|
`container` depends on:
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, hakurei, ... }:
|
- [libseccomp](https://github.com/seccomp/libseccomp) to generate BPF programs.
|
||||||
{
|
|
||||||
nixosConfigurations.hakurei = nixpkgs.lib.nixosSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
modules = [
|
|
||||||
hakurei.nixosModules.hakurei
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds the `environment.hakurei` option:
|
`cmd/hakurei` depends on:
|
||||||
|
|
||||||
```nix
|
- [acl](https://savannah.nongnu.org/projects/acl/) to export sockets to
|
||||||
{ pkgs, ... }:
|
subordinate users.
|
||||||
|
- [wayland](https://gitlab.freedesktop.org/wayland/wayland) to set up
|
||||||
|
[security-context-v1](https://wayland.app/protocols/security-context-v1).
|
||||||
|
- [xcb](https://xcb.freedesktop.org/) to grant and revoke subordinate users
|
||||||
|
access to the X server.
|
||||||
|
|
||||||
{
|
`cmd/sharefs` depends on:
|
||||||
environment.hakurei = {
|
|
||||||
enable = true;
|
|
||||||
stateDir = "/var/lib/hakurei";
|
|
||||||
users = {
|
|
||||||
alice = 0;
|
|
||||||
nixos = 10;
|
|
||||||
};
|
|
||||||
|
|
||||||
commonPaths = [
|
- [fuse](https://github.com/libfuse/libfuse) to implement the filesystem.
|
||||||
{
|
|
||||||
src = "/sdcard";
|
|
||||||
write = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
extraHomeConfig = {
|
New dependencies will generally not be added. Patches adding new dependencies
|
||||||
home.stateVersion = "23.05";
|
are very likely to be rejected.
|
||||||
};
|
|
||||||
|
|
||||||
apps = {
|
## NixOS Module (deprecated)
|
||||||
"org.chromium.Chromium" = {
|
|
||||||
name = "chromium";
|
|
||||||
identity = 1;
|
|
||||||
packages = [ pkgs.chromium ];
|
|
||||||
userns = true;
|
|
||||||
mapRealUid = true;
|
|
||||||
dbus = {
|
|
||||||
system = {
|
|
||||||
filter = true;
|
|
||||||
talk = [
|
|
||||||
"org.bluez"
|
|
||||||
"org.freedesktop.Avahi"
|
|
||||||
"org.freedesktop.UPower"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
session =
|
|
||||||
f:
|
|
||||||
f {
|
|
||||||
talk = [
|
|
||||||
"org.freedesktop.FileManager1"
|
|
||||||
"org.freedesktop.Notifications"
|
|
||||||
"org.freedesktop.ScreenSaver"
|
|
||||||
"org.freedesktop.secrets"
|
|
||||||
"org.kde.kwalletd5"
|
|
||||||
"org.kde.kwalletd6"
|
|
||||||
];
|
|
||||||
own = [
|
|
||||||
"org.chromium.Chromium.*"
|
|
||||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*"
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*"
|
|
||||||
];
|
|
||||||
call = { };
|
|
||||||
broadcast = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
"org.claws_mail.Claws-Mail" = {
|
The NixOS module is in maintenance mode and will be removed once planterette is
|
||||||
name = "claws-mail";
|
feature-complete. Full module documentation can be found [here](options.md).
|
||||||
identity = 2;
|
|
||||||
packages = [ pkgs.claws-mail ];
|
|
||||||
gpu = false;
|
|
||||||
capability.pulse = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
"org.weechat" = {
|
|
||||||
name = "weechat";
|
|
||||||
identity = 3;
|
|
||||||
shareUid = true;
|
|
||||||
packages = [ pkgs.weechat ];
|
|
||||||
capability = {
|
|
||||||
wayland = false;
|
|
||||||
x11 = false;
|
|
||||||
dbus = true;
|
|
||||||
pulse = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
"dev.vencord.Vesktop" = {
|
|
||||||
name = "discord";
|
|
||||||
identity = 3;
|
|
||||||
shareUid = true;
|
|
||||||
packages = [ pkgs.vesktop ];
|
|
||||||
share = pkgs.vesktop;
|
|
||||||
command = "vesktop --ozone-platform-hint=wayland";
|
|
||||||
userns = true;
|
|
||||||
mapRealUid = true;
|
|
||||||
capability.x11 = true;
|
|
||||||
dbus = {
|
|
||||||
session =
|
|
||||||
f:
|
|
||||||
f {
|
|
||||||
talk = [ "org.kde.StatusNotifierWatcher" ];
|
|
||||||
own = [ ];
|
|
||||||
call = { };
|
|
||||||
broadcast = { };
|
|
||||||
};
|
|
||||||
system.filter = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
"io.looking-glass" = {
|
|
||||||
name = "looking-glass-client";
|
|
||||||
identity = 4;
|
|
||||||
useCommonPaths = false;
|
|
||||||
groups = [ "plugdev" ];
|
|
||||||
extraPaths = [
|
|
||||||
{
|
|
||||||
src = "/dev/shm/looking-glass";
|
|
||||||
write = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
extraConfig = {
|
|
||||||
programs.looking-glass-client.enable = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
58
cmd/earlyinit/main.go
Normal file
58
cmd/earlyinit/main.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
. "syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("earlyinit: ")
|
||||||
|
|
||||||
|
if err := Mount(
|
||||||
|
"devtmpfs",
|
||||||
|
"/dev/",
|
||||||
|
"devtmpfs",
|
||||||
|
MS_NOSUID|MS_NOEXEC,
|
||||||
|
"",
|
||||||
|
); err != nil {
|
||||||
|
log.Fatalf("cannot mount devtmpfs: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The kernel might be unable to set up the console. When that happens,
|
||||||
|
// printk is called with "Warning: unable to open an initial console."
|
||||||
|
// and the init runs with no files. The checkfds runtime function
|
||||||
|
// populates 0-2 by opening /dev/null for them.
|
||||||
|
//
|
||||||
|
// This check replaces 1 and 2 with /dev/kmsg to improve the chance
|
||||||
|
// of output being visible to the user.
|
||||||
|
if fi, err := os.Stdout.Stat(); err == nil {
|
||||||
|
if stat, ok := fi.Sys().(*Stat_t); ok {
|
||||||
|
if stat.Rdev == 0x103 {
|
||||||
|
var fd int
|
||||||
|
if fd, err = Open(
|
||||||
|
"/dev/kmsg",
|
||||||
|
O_WRONLY|O_CLOEXEC,
|
||||||
|
0,
|
||||||
|
); err != nil {
|
||||||
|
log.Fatalf("cannot open kmsg: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = Dup3(fd, Stdout, 0); err != nil {
|
||||||
|
log.Fatalf("cannot open stdout: %v", err)
|
||||||
|
}
|
||||||
|
if err = Dup3(fd, Stderr, 0); err != nil {
|
||||||
|
log.Fatalf("cannot open stderr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = Close(fd); err != nil {
|
||||||
|
log.Printf("cannot close kmsg: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
This program is a proof of concept and is now deprecated. It is only kept
|
|
||||||
around for API demonstration purposes and to make the most out of the test
|
|
||||||
suite.
|
|
||||||
|
|
||||||
This program is replaced by planterette, which can be found at
|
|
||||||
https://git.gensokyo.uk/security/planterette. Development effort should be
|
|
||||||
focused there instead.
|
|
||||||
173
cmd/hpkg/app.go
173
cmd/hpkg/app.go
@@ -1,173 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
|
||||||
"hakurei.app/container/fhs"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
)
|
|
||||||
|
|
||||||
type appInfo struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
ID string `json:"id"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
Identity int `json:"identity"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
Groups []string `json:"groups,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
Devel bool `json:"devel,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
Userns bool `json:"userns,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
HostNet bool `json:"net,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
HostAbstract bool `json:"abstract,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
Device bool `json:"dev,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
Tty bool `json:"tty,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
MapRealUID bool `json:"map_real_uid,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
DirectWayland bool `json:"direct_wayland,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
SystemBus *hst.BusConfig `json:"system_bus,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
SessionBus *hst.BusConfig `json:"session_bus,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
Enablements *hst.Enablements `json:"enablements,omitempty"`
|
|
||||||
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
Multiarch bool `json:"multiarch,omitempty"`
|
|
||||||
// passed through to [hst.Config]
|
|
||||||
Bluetooth bool `json:"bluetooth,omitempty"`
|
|
||||||
|
|
||||||
// allow gpu access within sandbox
|
|
||||||
GPU bool `json:"gpu"`
|
|
||||||
// store path to nixGL mesa wrappers
|
|
||||||
Mesa string `json:"mesa,omitempty"`
|
|
||||||
// store path to nixGL source
|
|
||||||
NixGL string `json:"nix_gl,omitempty"`
|
|
||||||
// store path to activate-and-exec script
|
|
||||||
Launcher *check.Absolute `json:"launcher"`
|
|
||||||
// store path to /run/current-system
|
|
||||||
CurrentSystem *check.Absolute `json:"current_system"`
|
|
||||||
// store path to home-manager activation package
|
|
||||||
ActivationPackage string `json:"activation_package"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *appInfo) toHst(pathSet *appPathSet, pathname *check.Absolute, argv []string, flagDropShell bool) *hst.Config {
|
|
||||||
config := &hst.Config{
|
|
||||||
ID: app.ID,
|
|
||||||
|
|
||||||
Enablements: app.Enablements,
|
|
||||||
|
|
||||||
SystemBus: app.SystemBus,
|
|
||||||
SessionBus: app.SessionBus,
|
|
||||||
DirectWayland: app.DirectWayland,
|
|
||||||
|
|
||||||
Identity: app.Identity,
|
|
||||||
Groups: app.Groups,
|
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
|
||||||
Hostname: formatHostname(app.Name),
|
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
|
||||||
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append("store"), Target: pathNixStore}},
|
|
||||||
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
|
|
||||||
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
|
|
||||||
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.metaPath, Target: hst.AbsPrivateTmp.Append("app")}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
|
|
||||||
},
|
|
||||||
|
|
||||||
Username: "hakurei",
|
|
||||||
Shell: pathShell,
|
|
||||||
Home: pathDataData.Append(app.ID),
|
|
||||||
|
|
||||||
Path: pathname,
|
|
||||||
Args: argv,
|
|
||||||
},
|
|
||||||
ExtraPerms: []hst.ExtraPermConfig{
|
|
||||||
{Path: dataHome, Execute: true},
|
|
||||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if app.Devel {
|
|
||||||
config.Container.Flags |= hst.FDevel
|
|
||||||
}
|
|
||||||
if app.Userns {
|
|
||||||
config.Container.Flags |= hst.FUserns
|
|
||||||
}
|
|
||||||
if app.HostNet {
|
|
||||||
config.Container.Flags |= hst.FHostNet
|
|
||||||
}
|
|
||||||
if app.HostAbstract {
|
|
||||||
config.Container.Flags |= hst.FHostAbstract
|
|
||||||
}
|
|
||||||
if app.Device {
|
|
||||||
config.Container.Flags |= hst.FDevice
|
|
||||||
}
|
|
||||||
if app.Tty || flagDropShell {
|
|
||||||
config.Container.Flags |= hst.FTty
|
|
||||||
}
|
|
||||||
if app.MapRealUID {
|
|
||||||
config.Container.Flags |= hst.FMapRealUID
|
|
||||||
}
|
|
||||||
if app.Multiarch {
|
|
||||||
config.Container.Flags |= hst.FMultiarch
|
|
||||||
}
|
|
||||||
config.Container.Flags |= hst.FShareRuntime | hst.FShareTmpdir
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadAppInfo(name string, beforeFail func()) *appInfo {
|
|
||||||
bundle := new(appInfo)
|
|
||||||
if f, err := os.Open(name); err != nil {
|
|
||||||
beforeFail()
|
|
||||||
log.Fatalf("cannot open bundle: %v", err)
|
|
||||||
} else if err = json.NewDecoder(f).Decode(&bundle); err != nil {
|
|
||||||
beforeFail()
|
|
||||||
log.Fatalf("cannot parse bundle metadata: %v", err)
|
|
||||||
} else if err = f.Close(); err != nil {
|
|
||||||
log.Printf("cannot close bundle metadata: %v", err)
|
|
||||||
// not fatal
|
|
||||||
}
|
|
||||||
|
|
||||||
if bundle.ID == "" {
|
|
||||||
beforeFail()
|
|
||||||
log.Fatal("application identifier must not be empty")
|
|
||||||
}
|
|
||||||
if bundle.Launcher == nil {
|
|
||||||
beforeFail()
|
|
||||||
log.Fatal("launcher must not be empty")
|
|
||||||
}
|
|
||||||
if bundle.CurrentSystem == nil {
|
|
||||||
beforeFail()
|
|
||||||
log.Fatal("current-system must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return bundle
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatHostname(name string) string {
|
|
||||||
if h, err := os.Hostname(); err != nil {
|
|
||||||
log.Printf("cannot get hostname: %v", err)
|
|
||||||
return "hakurei-" + name
|
|
||||||
} else {
|
|
||||||
return h + "-" + name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
{
|
|
||||||
nixpkgsFor,
|
|
||||||
system,
|
|
||||||
nixpkgs,
|
|
||||||
home-manager,
|
|
||||||
}:
|
|
||||||
|
|
||||||
{
|
|
||||||
lib,
|
|
||||||
stdenv,
|
|
||||||
closureInfo,
|
|
||||||
writeScript,
|
|
||||||
runtimeShell,
|
|
||||||
writeText,
|
|
||||||
symlinkJoin,
|
|
||||||
vmTools,
|
|
||||||
runCommand,
|
|
||||||
fetchFromGitHub,
|
|
||||||
|
|
||||||
zstd,
|
|
||||||
nix,
|
|
||||||
sqlite,
|
|
||||||
|
|
||||||
name ? throw "name is required",
|
|
||||||
version ? throw "version is required",
|
|
||||||
pname ? "${name}-${version}",
|
|
||||||
modules ? [ ],
|
|
||||||
nixosModules ? [ ],
|
|
||||||
script ? ''
|
|
||||||
exec "$SHELL" "$@"
|
|
||||||
'',
|
|
||||||
|
|
||||||
id ? name,
|
|
||||||
identity ? throw "identity is required",
|
|
||||||
groups ? [ ],
|
|
||||||
userns ? false,
|
|
||||||
net ? true,
|
|
||||||
dev ? false,
|
|
||||||
no_new_session ? false,
|
|
||||||
map_real_uid ? false,
|
|
||||||
direct_wayland ? false,
|
|
||||||
system_bus ? null,
|
|
||||||
session_bus ? null,
|
|
||||||
|
|
||||||
allow_wayland ? true,
|
|
||||||
allow_x11 ? false,
|
|
||||||
allow_dbus ? true,
|
|
||||||
allow_audio ? true,
|
|
||||||
gpu ? allow_wayland || allow_x11,
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit (lib) optionals;
|
|
||||||
|
|
||||||
homeManagerConfiguration = home-manager.lib.homeManagerConfiguration {
|
|
||||||
pkgs = nixpkgsFor.${system};
|
|
||||||
modules = modules ++ [
|
|
||||||
{
|
|
||||||
home = {
|
|
||||||
username = "hakurei";
|
|
||||||
homeDirectory = "/data/data/${id}";
|
|
||||||
stateVersion = "22.11";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
launcher = writeScript "hakurei-${pname}" ''
|
|
||||||
#!${runtimeShell} -el
|
|
||||||
${script}
|
|
||||||
'';
|
|
||||||
|
|
||||||
extraNixOSConfig =
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
environment = {
|
|
||||||
etc.nixpkgs.source = nixpkgs.outPath;
|
|
||||||
systemPackages = [ pkgs.nix ];
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = nixosModules;
|
|
||||||
};
|
|
||||||
nixos = nixpkgs.lib.nixosSystem {
|
|
||||||
inherit system;
|
|
||||||
modules = [
|
|
||||||
extraNixOSConfig
|
|
||||||
{ nix.settings.experimental-features = [ "flakes" ]; }
|
|
||||||
{ nix.settings.experimental-features = [ "nix-command" ]; }
|
|
||||||
{ boot.isContainer = true; }
|
|
||||||
{ system.stateVersion = "22.11"; }
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
etc = vmTools.runInLinuxVM (
|
|
||||||
runCommand "etc" { } ''
|
|
||||||
mkdir -p /etc
|
|
||||||
${nixos.config.system.build.etcActivationCommands}
|
|
||||||
|
|
||||||
# remove unused files
|
|
||||||
rm -rf /etc/sudoers
|
|
||||||
|
|
||||||
mkdir -p $out
|
|
||||||
tar -C /etc -cf "$out/etc.tar" .
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
extendSessionDefault = id: ext: {
|
|
||||||
filter = true;
|
|
||||||
|
|
||||||
talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
|
|
||||||
own =
|
|
||||||
(optionals (id != null) [
|
|
||||||
"${id}.*"
|
|
||||||
"org.mpris.MediaPlayer2.${id}.*"
|
|
||||||
])
|
|
||||||
++ ext.own;
|
|
||||||
|
|
||||||
inherit (ext) call broadcast;
|
|
||||||
};
|
|
||||||
|
|
||||||
nixGL = fetchFromGitHub {
|
|
||||||
owner = "nix-community";
|
|
||||||
repo = "nixGL";
|
|
||||||
rev = "310f8e49a149e4c9ea52f1adf70cdc768ec53f8a";
|
|
||||||
hash = "sha256-lnzZQYG0+EXl/6NkGpyIz+FEOc/DSEG57AP1VsdeNrM=";
|
|
||||||
};
|
|
||||||
|
|
||||||
mesaWrappers =
|
|
||||||
let
|
|
||||||
isIntelX86Platform = system == "x86_64-linux";
|
|
||||||
nixGLPackages = import (nixGL + "/default.nix") {
|
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
|
||||||
enable32bits = isIntelX86Platform;
|
|
||||||
enableIntelX86Extensions = isIntelX86Platform;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
symlinkJoin {
|
|
||||||
name = "nixGL-mesa";
|
|
||||||
paths = with nixGLPackages; [
|
|
||||||
nixGLIntel
|
|
||||||
nixVulkanIntel
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
info = builtins.toJSON {
|
|
||||||
inherit
|
|
||||||
name
|
|
||||||
version
|
|
||||||
id
|
|
||||||
identity
|
|
||||||
launcher
|
|
||||||
groups
|
|
||||||
userns
|
|
||||||
net
|
|
||||||
dev
|
|
||||||
no_new_session
|
|
||||||
map_real_uid
|
|
||||||
direct_wayland
|
|
||||||
system_bus
|
|
||||||
gpu
|
|
||||||
;
|
|
||||||
|
|
||||||
session_bus =
|
|
||||||
if session_bus != null then
|
|
||||||
(session_bus (extendSessionDefault id))
|
|
||||||
else
|
|
||||||
(extendSessionDefault id {
|
|
||||||
talk = [ ];
|
|
||||||
own = [ ];
|
|
||||||
call = { };
|
|
||||||
broadcast = { };
|
|
||||||
});
|
|
||||||
|
|
||||||
enablements = {
|
|
||||||
wayland = allow_wayland;
|
|
||||||
x11 = allow_x11;
|
|
||||||
dbus = allow_dbus;
|
|
||||||
pipewire = allow_audio;
|
|
||||||
};
|
|
||||||
|
|
||||||
mesa = if gpu then mesaWrappers else null;
|
|
||||||
nix_gl = if gpu then nixGL else null;
|
|
||||||
current_system = nixos.config.system.build.toplevel;
|
|
||||||
activation_package = homeManagerConfiguration.activationPackage;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
|
|
||||||
stdenv.mkDerivation {
|
|
||||||
name = "${pname}.pkg";
|
|
||||||
inherit version;
|
|
||||||
__structuredAttrs = true;
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
zstd
|
|
||||||
nix
|
|
||||||
sqlite
|
|
||||||
];
|
|
||||||
|
|
||||||
buildCommand = ''
|
|
||||||
NIX_ROOT="$(mktemp -d)"
|
|
||||||
export USER="nobody"
|
|
||||||
|
|
||||||
# create bootstrap store
|
|
||||||
bootstrapClosureInfo="${
|
|
||||||
closureInfo {
|
|
||||||
rootPaths = [
|
|
||||||
nix
|
|
||||||
nixos.config.system.build.toplevel
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
echo "copying bootstrap store paths..."
|
|
||||||
mkdir -p "$NIX_ROOT/nix/store"
|
|
||||||
xargs -n 1 -a "$bootstrapClosureInfo/store-paths" cp -at "$NIX_ROOT/nix/store/"
|
|
||||||
NIX_REMOTE="local?root=$NIX_ROOT" nix-store --load-db < "$bootstrapClosureInfo/registration"
|
|
||||||
NIX_REMOTE="local?root=$NIX_ROOT" nix-store --optimise
|
|
||||||
sqlite3 "$NIX_ROOT/nix/var/nix/db/db.sqlite" "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
|
|
||||||
chmod -R +r "$NIX_ROOT/nix/var"
|
|
||||||
|
|
||||||
# create binary cache
|
|
||||||
closureInfo="${
|
|
||||||
closureInfo {
|
|
||||||
rootPaths = [
|
|
||||||
homeManagerConfiguration.activationPackage
|
|
||||||
launcher
|
|
||||||
]
|
|
||||||
++ optionals gpu [
|
|
||||||
mesaWrappers
|
|
||||||
nixGL
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
echo "copying application paths..."
|
|
||||||
TMP_STORE="$(mktemp -d)"
|
|
||||||
mkdir -p "$TMP_STORE/nix/store"
|
|
||||||
xargs -n 1 -a "$closureInfo/store-paths" cp -at "$TMP_STORE/nix/store/"
|
|
||||||
NIX_REMOTE="local?root=$TMP_STORE" nix-store --load-db < "$closureInfo/registration"
|
|
||||||
sqlite3 "$TMP_STORE/nix/var/nix/db/db.sqlite" "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
|
|
||||||
NIX_REMOTE="local?root=$TMP_STORE" nix --offline --extra-experimental-features nix-command \
|
|
||||||
--verbose --log-format raw-with-logs \
|
|
||||||
copy --all --no-check-sigs --to \
|
|
||||||
"file://$NIX_ROOT/res?compression=zstd&compression-level=19¶llel-compression=true"
|
|
||||||
|
|
||||||
# package /etc
|
|
||||||
mkdir -p "$NIX_ROOT/etc"
|
|
||||||
tar -C "$NIX_ROOT/etc" -xf "${etc}/etc.tar"
|
|
||||||
|
|
||||||
# write metadata
|
|
||||||
cp "${writeText "bundle.json" info}" "$NIX_ROOT/bundle.json"
|
|
||||||
|
|
||||||
# create an intermediate file to improve zstd performance
|
|
||||||
INTER="$(mktemp)"
|
|
||||||
tar -C "$NIX_ROOT" -cf "$INTER" .
|
|
||||||
zstd -T0 -19 -fo "$out" "$INTER"
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
335
cmd/hpkg/main.go
335
cmd/hpkg/main.go
@@ -1,335 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"hakurei.app/command"
|
|
||||||
"hakurei.app/container/check"
|
|
||||||
"hakurei.app/container/fhs"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errSuccess = errors.New("success")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.SetPrefix("hpkg: ")
|
|
||||||
log.SetFlags(0)
|
|
||||||
msg := message.New(log.Default())
|
|
||||||
|
|
||||||
if err := os.Setenv("SHELL", pathShell.String()); err != nil {
|
|
||||||
log.Fatalf("cannot set $SHELL: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Geteuid() == 0 {
|
|
||||||
log.Fatal("this program must not run as root")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
|
||||||
syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
defer stop() // unreachable
|
|
||||||
|
|
||||||
var (
|
|
||||||
flagVerbose bool
|
|
||||||
flagDropShell bool
|
|
||||||
)
|
|
||||||
c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { msg.SwapVerbose(flagVerbose); return nil }).
|
|
||||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
|
||||||
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
|
|
||||||
|
|
||||||
{
|
|
||||||
var (
|
|
||||||
flagDropShellActivate bool
|
|
||||||
)
|
|
||||||
c.NewCommand("install", "Install an application from its package", func(args []string) error {
|
|
||||||
if len(args) != 1 {
|
|
||||||
log.Println("invalid argument")
|
|
||||||
return syscall.EINVAL
|
|
||||||
}
|
|
||||||
pkgPath := args[0]
|
|
||||||
if !path.IsAbs(pkgPath) {
|
|
||||||
if dir, err := os.Getwd(); err != nil {
|
|
||||||
log.Printf("cannot get current directory: %v", err)
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
pkgPath = path.Join(dir, pkgPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Look up paths to programs started by hpkg.
|
|
||||||
This is done here to ease error handling as cleanup is not yet required.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ = lookPath("zstd")
|
|
||||||
tar = lookPath("tar")
|
|
||||||
chmod = lookPath("chmod")
|
|
||||||
rm = lookPath("rm")
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Extract package and set up for cleanup.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var workDir *check.Absolute
|
|
||||||
if p, err := os.MkdirTemp("", "hpkg.*"); err != nil {
|
|
||||||
log.Printf("cannot create temporary directory: %v", err)
|
|
||||||
return err
|
|
||||||
} else if workDir, err = check.NewAbs(p); err != nil {
|
|
||||||
log.Printf("invalid temporary directory: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cleanup := func() {
|
|
||||||
// should be faster than a native implementation
|
|
||||||
mustRun(msg, chmod, "-R", "+w", workDir.String())
|
|
||||||
mustRun(msg, rm, "-rf", workDir.String())
|
|
||||||
}
|
|
||||||
beforeRunFail.Store(&cleanup)
|
|
||||||
|
|
||||||
mustRun(msg, tar, "-C", workDir.String(), "-xf", pkgPath)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Parse bundle and app metadata, do pre-install checks.
|
|
||||||
*/
|
|
||||||
|
|
||||||
bundle := loadAppInfo(path.Join(workDir.String(), "bundle.json"), cleanup)
|
|
||||||
pathSet := pathSetByApp(bundle.ID)
|
|
||||||
|
|
||||||
a := bundle
|
|
||||||
if s, err := os.Stat(pathSet.metaPath.String()); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("cannot access %q: %v", pathSet.metaPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// did not modify app, clean installation condition met later
|
|
||||||
} else if s.IsDir() {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("metadata path %q is not a file", pathSet.metaPath)
|
|
||||||
return syscall.EBADMSG
|
|
||||||
} else {
|
|
||||||
a = loadAppInfo(pathSet.metaPath.String(), cleanup)
|
|
||||||
if a.ID != bundle.ID {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("app %q claims to have identifier %q",
|
|
||||||
bundle.ID, a.ID)
|
|
||||||
return syscall.EBADE
|
|
||||||
}
|
|
||||||
// sec: should verify credentials
|
|
||||||
}
|
|
||||||
|
|
||||||
if a != bundle {
|
|
||||||
// do not try to re-install
|
|
||||||
if a.NixGL == bundle.NixGL &&
|
|
||||||
a.CurrentSystem == bundle.CurrentSystem &&
|
|
||||||
a.Launcher == bundle.Launcher &&
|
|
||||||
a.ActivationPackage == bundle.ActivationPackage {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("package %q is identical to local application %q",
|
|
||||||
pkgPath, a.ID)
|
|
||||||
return errSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
// identity determines uid
|
|
||||||
if a.Identity != bundle.Identity {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("package %q identity %d differs from installed %d",
|
|
||||||
pkgPath, bundle.Identity, a.Identity)
|
|
||||||
return syscall.EBADE
|
|
||||||
}
|
|
||||||
|
|
||||||
// sec: should compare version string
|
|
||||||
msg.Verbosef("installing application %q version %q over local %q",
|
|
||||||
bundle.ID, bundle.Version, a.Version)
|
|
||||||
} else {
|
|
||||||
msg.Verbosef("application %q clean installation", bundle.ID)
|
|
||||||
// sec: should install credentials
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Setup steps for files owned by the target user.
|
|
||||||
*/
|
|
||||||
|
|
||||||
withCacheDir(ctx, msg, "install", []string{
|
|
||||||
// export inner bundle path in the environment
|
|
||||||
"export BUNDLE=" + hst.PrivateTmp + "/bundle",
|
|
||||||
// replace inner /etc
|
|
||||||
"mkdir -p etc",
|
|
||||||
"chmod -R +w etc",
|
|
||||||
"rm -rf etc",
|
|
||||||
"cp -dRf $BUNDLE/etc etc",
|
|
||||||
// replace inner /nix
|
|
||||||
"mkdir -p nix",
|
|
||||||
"chmod -R +w nix",
|
|
||||||
"rm -rf nix",
|
|
||||||
"cp -dRf /nix nix",
|
|
||||||
// copy from binary cache
|
|
||||||
"nix copy --offline --no-check-sigs --all --from file://$BUNDLE/res --to $PWD",
|
|
||||||
// deduplicate nix store
|
|
||||||
"nix store --offline --store $PWD optimise",
|
|
||||||
// make cache directory world-readable for autoetc
|
|
||||||
"chmod 0755 .",
|
|
||||||
}, workDir, bundle, pathSet, flagDropShell, cleanup)
|
|
||||||
|
|
||||||
if bundle.GPU {
|
|
||||||
withCacheDir(ctx, msg, "mesa-wrappers", []string{
|
|
||||||
// link nixGL mesa wrappers
|
|
||||||
"mkdir -p nix/.nixGL",
|
|
||||||
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
|
|
||||||
"ln -s " + bundle.Mesa + "/bin/nixVulkanIntel nix/.nixGL/nixVulkan",
|
|
||||||
}, workDir, bundle, pathSet, false, cleanup)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Activate home-manager generation.
|
|
||||||
*/
|
|
||||||
|
|
||||||
withNixDaemon(ctx, msg, "activate", []string{
|
|
||||||
// clean up broken links
|
|
||||||
"mkdir -p .local/state/{nix,home-manager}",
|
|
||||||
"chmod -R +w .local/state/{nix,home-manager}",
|
|
||||||
"rm -rf .local/state/{nix,home-manager}",
|
|
||||||
// run activation script
|
|
||||||
bundle.ActivationPackage + "/activate",
|
|
||||||
}, false, func(config *hst.Config) *hst.Config { return config },
|
|
||||||
bundle, pathSet, flagDropShellActivate, cleanup)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Installation complete. Write metadata to block re-installs or downgrades.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// serialise metadata to ensure consistency
|
|
||||||
if f, err := os.OpenFile(pathSet.metaPath.String()+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("cannot create metadata file: %v", err)
|
|
||||||
return err
|
|
||||||
} else if err = json.NewEncoder(f).Encode(bundle); err != nil {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("cannot write metadata: %v", err)
|
|
||||||
return err
|
|
||||||
} else if err = f.Close(); err != nil {
|
|
||||||
log.Printf("cannot close metadata file: %v", err)
|
|
||||||
// not fatal
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Rename(pathSet.metaPath.String()+"~", pathSet.metaPath.String()); err != nil {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("cannot rename metadata file: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
return errSuccess
|
|
||||||
}).
|
|
||||||
Flag(&flagDropShellActivate, "s", command.BoolFlag(false), "Drop to a shell on activation")
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var (
|
|
||||||
flagDropShellNixGL bool
|
|
||||||
flagAutoDrivers bool
|
|
||||||
)
|
|
||||||
c.NewCommand("start", "Start an application", func(args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
log.Println("invalid argument")
|
|
||||||
return syscall.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Parse app metadata.
|
|
||||||
*/
|
|
||||||
|
|
||||||
id := args[0]
|
|
||||||
pathSet := pathSetByApp(id)
|
|
||||||
a := loadAppInfo(pathSet.metaPath.String(), func() {})
|
|
||||||
if a.ID != id {
|
|
||||||
log.Printf("app %q claims to have identifier %q", id, a.ID)
|
|
||||||
return syscall.EBADE
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prepare nixGL.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if a.GPU && flagAutoDrivers {
|
|
||||||
withNixDaemon(ctx, msg, "nix-gl", []string{
|
|
||||||
"mkdir -p /nix/.nixGL/auto",
|
|
||||||
"rm -rf /nix/.nixGL/auto",
|
|
||||||
"export NIXPKGS_ALLOW_UNFREE=1",
|
|
||||||
"nix build --impure " +
|
|
||||||
"--out-link /nix/.nixGL/auto/opengl " +
|
|
||||||
"--override-input nixpkgs path:/etc/nixpkgs " +
|
|
||||||
"path:" + a.NixGL,
|
|
||||||
"nix build --impure " +
|
|
||||||
"--out-link /nix/.nixGL/auto/vulkan " +
|
|
||||||
"--override-input nixpkgs path:/etc/nixpkgs " +
|
|
||||||
"path:" + a.NixGL + "#nixVulkanNvidia",
|
|
||||||
}, true, func(config *hst.Config) *hst.Config {
|
|
||||||
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev"), Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices"), Optional: true}},
|
|
||||||
}...)
|
|
||||||
appendGPUFilesystem(config)
|
|
||||||
return config
|
|
||||||
}, a, pathSet, flagDropShellNixGL, func() {})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Create app configuration.
|
|
||||||
*/
|
|
||||||
|
|
||||||
pathname := a.Launcher
|
|
||||||
argv := make([]string, 1, len(args))
|
|
||||||
if flagDropShell {
|
|
||||||
pathname = pathShell
|
|
||||||
argv[0] = bash
|
|
||||||
} else {
|
|
||||||
argv[0] = a.Launcher.String()
|
|
||||||
}
|
|
||||||
argv = append(argv, args[1:]...)
|
|
||||||
config := a.toHst(pathSet, pathname, argv, flagDropShell)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Expose GPU devices.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if a.GPU {
|
|
||||||
config.Container.Filesystem = append(config.Container.Filesystem,
|
|
||||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append(".nixGL"), Target: hst.AbsPrivateTmp.Append("nixGL")}})
|
|
||||||
appendGPUFilesystem(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Spawn app.
|
|
||||||
*/
|
|
||||||
|
|
||||||
mustRunApp(ctx, msg, config, func() {})
|
|
||||||
return errSuccess
|
|
||||||
}).
|
|
||||||
Flag(&flagDropShellNixGL, "s", command.BoolFlag(false), "Drop to a shell on nixGL build").
|
|
||||||
Flag(&flagAutoDrivers, "auto-drivers", command.BoolFlag(false), "Attempt automatic opengl driver detection")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
|
||||||
msg.Verbosef("command returned %v", err)
|
|
||||||
if errors.Is(err, errSuccess) {
|
|
||||||
msg.BeforeExit()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
log.Fatal("unreachable")
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
|
||||||
"hakurei.app/container/fhs"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
const bash = "bash"
|
|
||||||
|
|
||||||
var (
|
|
||||||
dataHome *check.Absolute
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// dataHome
|
|
||||||
if a, err := check.NewAbs(os.Getenv("HAKUREI_DATA_HOME")); err == nil {
|
|
||||||
dataHome = a
|
|
||||||
} else {
|
|
||||||
dataHome = fhs.AbsVarLib.Append("hakurei/" + strconv.Itoa(os.Getuid()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
pathBin = fhs.AbsRoot.Append("bin")
|
|
||||||
|
|
||||||
pathNix = check.MustAbs("/nix/")
|
|
||||||
pathNixStore = pathNix.Append("store/")
|
|
||||||
pathCurrentSystem = fhs.AbsRun.Append("current-system")
|
|
||||||
pathSwBin = pathCurrentSystem.Append("sw/bin/")
|
|
||||||
pathShell = pathSwBin.Append(bash)
|
|
||||||
|
|
||||||
pathData = check.MustAbs("/data")
|
|
||||||
pathDataData = pathData.Append("data")
|
|
||||||
)
|
|
||||||
|
|
||||||
func lookPath(file string) string {
|
|
||||||
if p, err := exec.LookPath(file); err != nil {
|
|
||||||
log.Fatalf("%s: command not found", file)
|
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var beforeRunFail = new(atomic.Pointer[func()])
|
|
||||||
|
|
||||||
func mustRun(msg message.Msg, name string, arg ...string) {
|
|
||||||
msg.Verbosef("spawning process: %q %q", name, arg)
|
|
||||||
cmd := exec.Command(name, arg...)
|
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
if f := beforeRunFail.Swap(nil); f != nil {
|
|
||||||
(*f)()
|
|
||||||
}
|
|
||||||
log.Fatalf("%s: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type appPathSet struct {
|
|
||||||
// ${dataHome}/${id}
|
|
||||||
baseDir *check.Absolute
|
|
||||||
// ${baseDir}/app
|
|
||||||
metaPath *check.Absolute
|
|
||||||
// ${baseDir}/files
|
|
||||||
homeDir *check.Absolute
|
|
||||||
// ${baseDir}/cache
|
|
||||||
cacheDir *check.Absolute
|
|
||||||
// ${baseDir}/cache/nix
|
|
||||||
nixPath *check.Absolute
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathSetByApp(id string) *appPathSet {
|
|
||||||
pathSet := new(appPathSet)
|
|
||||||
pathSet.baseDir = dataHome.Append(id)
|
|
||||||
pathSet.metaPath = pathSet.baseDir.Append("app")
|
|
||||||
pathSet.homeDir = pathSet.baseDir.Append("files")
|
|
||||||
pathSet.cacheDir = pathSet.baseDir.Append("cache")
|
|
||||||
pathSet.nixPath = pathSet.cacheDir.Append("nix")
|
|
||||||
return pathSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendGPUFilesystem(config *hst.Config) {
|
|
||||||
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
|
|
||||||
// flatpak commit 763a686d874dd668f0236f911de00b80766ffe79
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("dri"), Device: true, Optional: true}},
|
|
||||||
// mali
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("mali"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("mali0"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("umplock"), Device: true, Optional: true}},
|
|
||||||
// nvidia
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidiactl"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-modeset"), Device: true, Optional: true}},
|
|
||||||
// nvidia OpenCL/CUDA
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-uvm"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-uvm-tools"), Device: true, Optional: true}},
|
|
||||||
|
|
||||||
// flatpak commit d2dff2875bb3b7e2cd92d8204088d743fd07f3ff
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia0"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia1"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia2"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia3"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia4"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia5"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia6"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia7"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia8"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia9"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia10"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia11"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia12"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia13"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia14"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia15"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia16"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia17"), Device: true, Optional: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia18"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia19"), Device: true, Optional: true}},
|
|
||||||
}...)
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/internal/info"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
var hakureiPathVal = info.MustHakureiPath().String()
|
|
||||||
|
|
||||||
func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, beforeFail func()) {
|
|
||||||
var (
|
|
||||||
cmd *exec.Cmd
|
|
||||||
st io.WriteCloser
|
|
||||||
)
|
|
||||||
|
|
||||||
if r, w, err := os.Pipe(); err != nil {
|
|
||||||
beforeFail()
|
|
||||||
log.Fatalf("cannot pipe: %v", err)
|
|
||||||
} else {
|
|
||||||
if msg.IsVerbose() {
|
|
||||||
cmd = exec.CommandContext(ctx, hakureiPathVal, "-v", "app", "3")
|
|
||||||
} else {
|
|
||||||
cmd = exec.CommandContext(ctx, hakureiPathVal, "app", "3")
|
|
||||||
}
|
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
||||||
cmd.ExtraFiles = []*os.File{r}
|
|
||||||
st = w
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
if err := json.NewEncoder(st).Encode(config); err != nil {
|
|
||||||
beforeFail()
|
|
||||||
log.Fatalf("cannot send configuration: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
beforeFail()
|
|
||||||
log.Fatalf("cannot start hakurei: %v", err)
|
|
||||||
}
|
|
||||||
if err := cmd.Wait(); err != nil {
|
|
||||||
var exitError *exec.ExitError
|
|
||||||
if errors.As(err, &exitError) {
|
|
||||||
beforeFail()
|
|
||||||
msg.BeforeExit()
|
|
||||||
os.Exit(exitError.ExitCode())
|
|
||||||
} else {
|
|
||||||
beforeFail()
|
|
||||||
log.Fatalf("cannot wait: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
users.users = {
|
|
||||||
alice = {
|
|
||||||
isNormalUser = true;
|
|
||||||
description = "Alice Foobar";
|
|
||||||
password = "foobar";
|
|
||||||
uid = 1000;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
home-manager.users.alice.home.stateVersion = "24.11";
|
|
||||||
|
|
||||||
# Automatically login on tty1 as a normal user:
|
|
||||||
services.getty.autologinUser = "alice";
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
variables = {
|
|
||||||
SWAYSOCK = "/tmp/sway-ipc.sock";
|
|
||||||
WLR_RENDERER = "pixman";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Automatically configure and start Sway when logging in on tty1:
|
|
||||||
programs.bash.loginShellInit = ''
|
|
||||||
if [ "$(tty)" = "/dev/tty1" ]; then
|
|
||||||
set -e
|
|
||||||
|
|
||||||
mkdir -p ~/.config/sway
|
|
||||||
(sed s/Mod4/Mod1/ /etc/sway/config &&
|
|
||||||
echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' &&
|
|
||||||
echo 'output Virtual-1 res 1680x1050') > ~/.config/sway/config
|
|
||||||
|
|
||||||
sway --validate
|
|
||||||
systemd-cat --identifier=session sway && touch /tmp/sway-exit-ok
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
programs.sway.enable = true;
|
|
||||||
|
|
||||||
virtualisation = {
|
|
||||||
diskSize = 6 * 1024;
|
|
||||||
|
|
||||||
qemu.options = [
|
|
||||||
# Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
|
|
||||||
"-vga none -device virtio-gpu-pci"
|
|
||||||
|
|
||||||
# Increase zstd performance:
|
|
||||||
"-smp 8"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.hakurei = {
|
|
||||||
enable = true;
|
|
||||||
stateDir = "/var/lib/hakurei";
|
|
||||||
users.alice = 0;
|
|
||||||
|
|
||||||
extraHomeConfig = {
|
|
||||||
home.stateVersion = "23.05";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
testers,
|
|
||||||
callPackage,
|
|
||||||
|
|
||||||
system,
|
|
||||||
self,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
buildPackage = self.buildPackage.${system};
|
|
||||||
in
|
|
||||||
testers.nixosTest {
|
|
||||||
name = "hpkg";
|
|
||||||
nodes.machine = {
|
|
||||||
environment.etc = {
|
|
||||||
"foot.pkg".source = callPackage ./foot.nix { inherit buildPackage; };
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
./configuration.nix
|
|
||||||
|
|
||||||
self.nixosModules.hakurei
|
|
||||||
self.inputs.home-manager.nixosModules.home-manager
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# adapted from nixos sway integration tests
|
|
||||||
|
|
||||||
# testScriptWithTypes:49: error: Cannot call function of unknown type
|
|
||||||
# (machine.succeed if succeed else machine.execute)(
|
|
||||||
# ^
|
|
||||||
# Found 1 error in 1 file (checked 1 source file)
|
|
||||||
skipTypeCheck = true;
|
|
||||||
testScript = builtins.readFile ./test.py;
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
buildPackage,
|
|
||||||
foot,
|
|
||||||
wayland-utils,
|
|
||||||
inconsolata,
|
|
||||||
}:
|
|
||||||
|
|
||||||
buildPackage {
|
|
||||||
name = "foot";
|
|
||||||
inherit (foot) version;
|
|
||||||
|
|
||||||
identity = 2;
|
|
||||||
id = "org.codeberg.dnkl.foot";
|
|
||||||
|
|
||||||
modules = [
|
|
||||||
{
|
|
||||||
home.packages = [
|
|
||||||
foot
|
|
||||||
|
|
||||||
# For wayland-info:
|
|
||||||
wayland-utils
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
nixosModules = [
|
|
||||||
{
|
|
||||||
# To help with OCR:
|
|
||||||
environment.etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
|
|
||||||
main = {
|
|
||||||
font = "inconsolata:size=14";
|
|
||||||
};
|
|
||||||
colors = rec {
|
|
||||||
foreground = "000000";
|
|
||||||
background = "ffffff";
|
|
||||||
regular2 = foreground;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
fonts.packages = [ inconsolata ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
exec foot "$@"
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import json
|
|
||||||
import shlex
|
|
||||||
|
|
||||||
q = shlex.quote
|
|
||||||
NODE_GROUPS = ["nodes", "floating_nodes"]
|
|
||||||
|
|
||||||
|
|
||||||
def swaymsg(command: str = "", succeed=True, type="command"):
|
|
||||||
assert command != "" or type != "command", "Must specify command or type"
|
|
||||||
shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
|
|
||||||
with machine.nested(
|
|
||||||
f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
|
|
||||||
):
|
|
||||||
ret = (machine.succeed if succeed else machine.execute)(
|
|
||||||
f"su - alice -c {shell}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# execute also returns a status code, but disregard.
|
|
||||||
if not succeed:
|
|
||||||
_, ret = ret
|
|
||||||
|
|
||||||
if not succeed and not ret:
|
|
||||||
return None
|
|
||||||
|
|
||||||
parsed = json.loads(ret)
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
|
|
||||||
def walk(tree):
|
|
||||||
yield tree
|
|
||||||
for group in NODE_GROUPS:
|
|
||||||
for node in tree.get(group, []):
|
|
||||||
yield from walk(node)
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_window(pattern):
|
|
||||||
def func(last_chance):
|
|
||||||
nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
|
|
||||||
|
|
||||||
if last_chance:
|
|
||||||
nodes = list(nodes)
|
|
||||||
machine.log(f"Last call! Current list of windows: {nodes}")
|
|
||||||
|
|
||||||
return any(pattern in name for name in nodes)
|
|
||||||
|
|
||||||
retry(func)
|
|
||||||
|
|
||||||
|
|
||||||
def collect_state_ui(name):
|
|
||||||
swaymsg(f"exec hakurei ps > '/tmp/{name}.ps'")
|
|
||||||
machine.copy_from_vm(f"/tmp/{name}.ps", "")
|
|
||||||
swaymsg(f"exec hakurei --json ps > '/tmp/{name}.json'")
|
|
||||||
machine.copy_from_vm(f"/tmp/{name}.json", "")
|
|
||||||
machine.screenshot(name)
|
|
||||||
|
|
||||||
|
|
||||||
def check_state(name, enablements):
|
|
||||||
instances = json.loads(machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei --json ps"))
|
|
||||||
if len(instances) != 1:
|
|
||||||
raise Exception(f"unexpected state length {len(instances)}")
|
|
||||||
instance = instances[0]
|
|
||||||
|
|
||||||
if len(instance['container']['args']) != 1 or not (instance['container']['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (instance['container']['args'][0]):
|
|
||||||
raise Exception(f"unexpected args {instance['container']['args']}")
|
|
||||||
|
|
||||||
if instance['enablements'] != enablements:
|
|
||||||
raise Exception(f"unexpected enablements {instance['enablements']}")
|
|
||||||
|
|
||||||
|
|
||||||
start_all()
|
|
||||||
machine.wait_for_unit("multi-user.target")
|
|
||||||
|
|
||||||
# To check hakurei's version:
|
|
||||||
print(machine.succeed("sudo -u alice -i hakurei version"))
|
|
||||||
|
|
||||||
# Wait for Sway to complete startup:
|
|
||||||
machine.wait_for_file("/run/user/1000/wayland-1")
|
|
||||||
machine.wait_for_file("/tmp/sway-ipc.sock")
|
|
||||||
|
|
||||||
# Prepare hpkg directory:
|
|
||||||
machine.succeed("install -dm 0700 -o alice -g users /var/lib/hakurei/1000")
|
|
||||||
|
|
||||||
# Install hpkg app:
|
|
||||||
swaymsg("exec hpkg -v install /etc/foot.pkg && touch /tmp/hpkg-install-ok")
|
|
||||||
machine.wait_for_file("/tmp/hpkg-install-ok")
|
|
||||||
|
|
||||||
# Start app (foot) with Wayland enablement:
|
|
||||||
swaymsg("exec hpkg -v start org.codeberg.dnkl.foot")
|
|
||||||
wait_for_window("hakurei@machine-foot")
|
|
||||||
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
|
|
||||||
machine.wait_for_file("/tmp/hakurei.0/tmpdir/2/success-client")
|
|
||||||
collect_state_ui("app_wayland")
|
|
||||||
check_state("foot", {"wayland": True, "dbus": True, "pipewire": True})
|
|
||||||
# Verify acl on XDG_RUNTIME_DIR:
|
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002"))
|
|
||||||
machine.send_chars("exit\n")
|
|
||||||
machine.wait_until_fails("pgrep foot")
|
|
||||||
# Verify acl cleanup on XDG_RUNTIME_DIR:
|
|
||||||
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002")
|
|
||||||
|
|
||||||
# Exit Sway and verify process exit status 0:
|
|
||||||
swaymsg("exit", succeed=False)
|
|
||||||
machine.wait_for_file("/tmp/sway-exit-ok")
|
|
||||||
|
|
||||||
# Print hakurei share and rundir contents:
|
|
||||||
print(machine.succeed("find /tmp/hakurei.0 "
|
|
||||||
+ "-path '/tmp/hakurei.0/runtime/*/*' -prune -o "
|
|
||||||
+ "-path '/tmp/hakurei.0/tmpdir/*/*' -prune -o "
|
|
||||||
+ "-print"))
|
|
||||||
print(machine.fail("ls /run/user/1000/hakurei"))
|
|
||||||
130
cmd/hpkg/with.go
130
cmd/hpkg/with.go
@@ -1,130 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
|
||||||
"hakurei.app/container/fhs"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
func withNixDaemon(
|
|
||||||
ctx context.Context,
|
|
||||||
msg message.Msg,
|
|
||||||
action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config,
|
|
||||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
|
||||||
) {
|
|
||||||
flags := hst.FMultiarch | hst.FUserns // nix sandbox requires userns
|
|
||||||
if net {
|
|
||||||
flags |= hst.FHostNet
|
|
||||||
}
|
|
||||||
if dropShell {
|
|
||||||
flags |= hst.FTty
|
|
||||||
}
|
|
||||||
|
|
||||||
mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{
|
|
||||||
ID: app.ID,
|
|
||||||
|
|
||||||
ExtraPerms: []hst.ExtraPermConfig{
|
|
||||||
{Path: dataHome, Execute: true},
|
|
||||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
|
||||||
},
|
|
||||||
|
|
||||||
Identity: app.Identity,
|
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
|
||||||
Hostname: formatHostname(app.Name) + "-" + action,
|
|
||||||
|
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
|
||||||
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath, Target: pathNix, Write: true}},
|
|
||||||
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
|
|
||||||
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
|
|
||||||
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
|
|
||||||
},
|
|
||||||
|
|
||||||
Username: "hakurei",
|
|
||||||
Shell: pathShell,
|
|
||||||
Home: pathDataData.Append(app.ID),
|
|
||||||
|
|
||||||
Path: pathShell,
|
|
||||||
Args: []string{bash, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
|
|
||||||
// start nix-daemon
|
|
||||||
"nix-daemon --store / & " +
|
|
||||||
// wait for socket to appear
|
|
||||||
"(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " +
|
|
||||||
// create directory so nix stops complaining
|
|
||||||
"mkdir -p /nix/var/nix/profiles/per-user/root/channels && " +
|
|
||||||
strings.Join(command, " && ") +
|
|
||||||
// terminate nix-daemon
|
|
||||||
" && pkill nix-daemon",
|
|
||||||
},
|
|
||||||
|
|
||||||
Flags: flags,
|
|
||||||
},
|
|
||||||
}), dropShell, beforeFail)
|
|
||||||
}
|
|
||||||
|
|
||||||
func withCacheDir(
|
|
||||||
ctx context.Context,
|
|
||||||
msg message.Msg,
|
|
||||||
action string, command []string, workDir *check.Absolute,
|
|
||||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
|
||||||
) {
|
|
||||||
flags := hst.FMultiarch
|
|
||||||
if dropShell {
|
|
||||||
flags |= hst.FTty
|
|
||||||
}
|
|
||||||
|
|
||||||
mustRunAppDropShell(ctx, msg, &hst.Config{
|
|
||||||
ID: app.ID,
|
|
||||||
|
|
||||||
ExtraPerms: []hst.ExtraPermConfig{
|
|
||||||
{Path: dataHome, Execute: true},
|
|
||||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
|
||||||
{Path: workDir, Execute: true},
|
|
||||||
},
|
|
||||||
|
|
||||||
Identity: app.Identity,
|
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
|
||||||
Hostname: formatHostname(app.Name) + "-" + action,
|
|
||||||
|
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
|
||||||
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: workDir.Append(fhs.Etc), Special: true}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: workDir.Append("nix"), Target: pathNix}},
|
|
||||||
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
|
|
||||||
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
|
|
||||||
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Source: workDir, Target: hst.AbsPrivateTmp.Append("bundle")}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID, "cache"), Source: pathSet.cacheDir, Write: true, Ensure: true}},
|
|
||||||
},
|
|
||||||
|
|
||||||
Username: "nixos",
|
|
||||||
Shell: pathShell,
|
|
||||||
Home: pathDataData.Append(app.ID, "cache"),
|
|
||||||
|
|
||||||
Path: pathShell,
|
|
||||||
Args: []string{bash, "-lc", strings.Join(command, " && ")},
|
|
||||||
|
|
||||||
Flags: flags,
|
|
||||||
},
|
|
||||||
}, dropShell, beforeFail)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustRunAppDropShell(ctx context.Context, msg message.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
|
|
||||||
if dropShell {
|
|
||||||
if config.Container != nil {
|
|
||||||
config.Container.Args = []string{bash, "-l"}
|
|
||||||
}
|
|
||||||
mustRunApp(ctx, msg, config, beforeFail)
|
|
||||||
beforeFail()
|
|
||||||
msg.BeforeExit()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
mustRunApp(ctx, msg, config, beforeFail)
|
|
||||||
}
|
|
||||||
428
cmd/mbf/main.go
428
cmd/mbf/main.go
@@ -4,17 +4,26 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
"unique"
|
"unique"
|
||||||
|
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
|
"hakurei.app/container/seccomp"
|
||||||
|
"hakurei.app/container/std"
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
"hakurei.app/internal/rosa"
|
"hakurei.app/internal/rosa"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
@@ -51,10 +60,16 @@ func main() {
|
|||||||
flagCures int
|
flagCures int
|
||||||
flagBase string
|
flagBase string
|
||||||
flagTShift int
|
flagTShift int
|
||||||
|
flagIdle bool
|
||||||
)
|
)
|
||||||
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) {
|
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) {
|
||||||
msg.SwapVerbose(!flagQuiet)
|
msg.SwapVerbose(!flagQuiet)
|
||||||
|
|
||||||
|
flagBase = os.ExpandEnv(flagBase)
|
||||||
|
if flagBase == "" {
|
||||||
|
flagBase = "cache"
|
||||||
|
}
|
||||||
|
|
||||||
var base *check.Absolute
|
var base *check.Absolute
|
||||||
if flagBase, err = filepath.Abs(flagBase); err != nil {
|
if flagBase, err = filepath.Abs(flagBase); err != nil {
|
||||||
return
|
return
|
||||||
@@ -70,6 +85,11 @@ func main() {
|
|||||||
cache.SetThreshold(1 << flagTShift)
|
cache.SetThreshold(1 << flagTShift)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flagIdle {
|
||||||
|
pkg.SchedPolicy = container.SCHED_IDLE
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}).Flag(
|
}).Flag(
|
||||||
&flagQuiet,
|
&flagQuiet,
|
||||||
@@ -81,12 +101,16 @@ func main() {
|
|||||||
"Maximum number of dependencies to cure at any given time",
|
"Maximum number of dependencies to cure at any given time",
|
||||||
).Flag(
|
).Flag(
|
||||||
&flagBase,
|
&flagBase,
|
||||||
"d", command.StringFlag("cache"),
|
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
||||||
"Directory to store cured artifacts",
|
"Directory to store cured artifacts",
|
||||||
).Flag(
|
).Flag(
|
||||||
&flagTShift,
|
&flagTShift,
|
||||||
"tshift", command.IntFlag(-1),
|
"tshift", command.IntFlag(-1),
|
||||||
"Dependency graph size exponent, to the power of 2",
|
"Dependency graph size exponent, to the power of 2",
|
||||||
|
).Flag(
|
||||||
|
&flagIdle,
|
||||||
|
"sched-idle", command.BoolFlag(false),
|
||||||
|
"Set SCHED_IDLE scheduling policy",
|
||||||
)
|
)
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -109,13 +133,234 @@ func main() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var (
|
||||||
|
flagStatus bool
|
||||||
|
flagReport string
|
||||||
|
)
|
||||||
|
c.NewCommand(
|
||||||
|
"info",
|
||||||
|
"Display out-of-band metadata of an artifact",
|
||||||
|
func(args []string) (err error) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return errors.New("info requires at least 1 argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *rosa.Report
|
||||||
|
if flagReport != "" {
|
||||||
|
if r, err = rosa.OpenReport(flagReport); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if closeErr := r.Close(); err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer r.HandleAccess(&err)()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, name := range args {
|
||||||
|
if p, ok := rosa.ResolveName(name); !ok {
|
||||||
|
return fmt.Errorf("unknown artifact %q", name)
|
||||||
|
} else {
|
||||||
|
var suffix string
|
||||||
|
if version := rosa.Std.Version(p); version != rosa.Unversioned {
|
||||||
|
suffix += "-" + version
|
||||||
|
}
|
||||||
|
fmt.Println("name : " + name + suffix)
|
||||||
|
|
||||||
|
meta := rosa.GetMetadata(p)
|
||||||
|
fmt.Println("description : " + meta.Description)
|
||||||
|
if meta.Website != "" {
|
||||||
|
fmt.Println("website : " +
|
||||||
|
strings.TrimSuffix(meta.Website, "/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusPrefix = "status : "
|
||||||
|
if flagStatus {
|
||||||
|
if r == nil {
|
||||||
|
var f io.ReadSeekCloser
|
||||||
|
f, err = cache.OpenStatus(rosa.Std.Load(p))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
fmt.Println(
|
||||||
|
statusPrefix + "not yet cured",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Print(statusPrefix)
|
||||||
|
_, err = io.Copy(os.Stdout, f)
|
||||||
|
if err = errors.Join(err, f.Close()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
|
||||||
|
if status == nil {
|
||||||
|
fmt.Println(
|
||||||
|
statusPrefix + "not in report",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fmt.Println("size :", n)
|
||||||
|
fmt.Print(statusPrefix)
|
||||||
|
if _, err = os.Stdout.Write(status); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != len(args)-1 {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
).
|
||||||
|
Flag(
|
||||||
|
&flagStatus,
|
||||||
|
"status", command.BoolFlag(false),
|
||||||
|
"Display cure status if available",
|
||||||
|
).
|
||||||
|
Flag(
|
||||||
|
&flagReport,
|
||||||
|
"report", command.StringFlag(""),
|
||||||
|
"Load cure status from this report file instead of cache",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.NewCommand(
|
||||||
|
"report",
|
||||||
|
"Generate an artifact cure report for the current cache",
|
||||||
|
func(args []string) (err error) {
|
||||||
|
var w *os.File
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
w = os.Stdout
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
if w, err = os.OpenFile(
|
||||||
|
args[0],
|
||||||
|
os.O_CREATE|os.O_EXCL|syscall.O_WRONLY,
|
||||||
|
0400,
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
closeErr := w.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.New("report requires 1 argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
if container.Isatty(int(w.Fd())) {
|
||||||
|
return errors.New("output appears to be a terminal")
|
||||||
|
}
|
||||||
|
return rosa.WriteReport(msg, w, cache)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
|
var flagJobs int
|
||||||
|
c.NewCommand("updates", command.UsageInternal, func([]string) error {
|
||||||
|
var (
|
||||||
|
errsMu sync.Mutex
|
||||||
|
errs []error
|
||||||
|
|
||||||
|
n atomic.Uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
w := make(chan rosa.PArtifact)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for range max(flagJobs, 1) {
|
||||||
|
wg.Go(func() {
|
||||||
|
for p := range w {
|
||||||
|
meta := rosa.GetMetadata(p)
|
||||||
|
if meta.ID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := meta.GetVersions(ctx)
|
||||||
|
if err != nil {
|
||||||
|
errsMu.Lock()
|
||||||
|
errs = append(errs, err)
|
||||||
|
errsMu.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if current, latest :=
|
||||||
|
rosa.Std.Version(p),
|
||||||
|
meta.GetLatest(v); current != latest {
|
||||||
|
|
||||||
|
n.Add(1)
|
||||||
|
log.Printf("%s %s < %s", meta.Name, current, latest)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Verbosef("%s is up to date", meta.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
for i := range rosa.PresetEnd {
|
||||||
|
select {
|
||||||
|
case w <- rosa.PArtifact(i):
|
||||||
|
break
|
||||||
|
case <-ctx.Done():
|
||||||
|
break done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(w)
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if v := n.Load(); v > 0 {
|
||||||
|
errs = append(errs, errors.New(strconv.Itoa(int(v))+
|
||||||
|
" package(s) are out of date"))
|
||||||
|
}
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}).
|
||||||
|
Flag(
|
||||||
|
&flagJobs,
|
||||||
|
"j", command.IntFlag(32),
|
||||||
|
"Maximum number of simultaneous connections",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var (
|
||||||
|
flagGentoo string
|
||||||
|
flagChecksum string
|
||||||
|
|
||||||
|
flagStage0 bool
|
||||||
|
)
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
"stage3",
|
"stage3",
|
||||||
"Check for toolchain 3-stage non-determinism",
|
"Check for toolchain 3-stage non-determinism",
|
||||||
func(args []string) (err error) {
|
func(args []string) (err error) {
|
||||||
_, _, _, stage1 := (rosa.Std - 2).NewLLVM()
|
t := rosa.Std
|
||||||
_, _, _, stage2 := (rosa.Std - 1).NewLLVM()
|
if flagGentoo != "" {
|
||||||
_, _, _, stage3 := rosa.Std.NewLLVM()
|
t -= 3 // magic number to discourage misuse
|
||||||
|
|
||||||
|
var checksum pkg.Checksum
|
||||||
|
if len(flagChecksum) != 0 {
|
||||||
|
if err = pkg.Decode(&checksum, flagChecksum); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rosa.SetGentooStage3(flagGentoo, checksum)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, stage1 := (t - 2).NewLLVM()
|
||||||
|
_, _, _, stage2 := (t - 1).NewLLVM()
|
||||||
|
_, _, _, stage3 := t.NewLLVM()
|
||||||
var (
|
var (
|
||||||
pathname *check.Absolute
|
pathname *check.Absolute
|
||||||
checksum [2]unique.Handle[pkg.Checksum]
|
checksum [2]unique.Handle[pkg.Checksum]
|
||||||
@@ -146,9 +391,35 @@ func main() {
|
|||||||
"("+pkg.Encode(checksum[0].Value())+")",
|
"("+pkg.Encode(checksum[0].Value())+")",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flagStage0 {
|
||||||
|
if pathname, _, err = cache.Cure(
|
||||||
|
t.Load(rosa.Stage0),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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",
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
@@ -162,7 +433,7 @@ func main() {
|
|||||||
return errors.New("cure requires 1 argument")
|
return errors.New("cure requires 1 argument")
|
||||||
}
|
}
|
||||||
if p, ok := rosa.ResolveName(args[0]); !ok {
|
if p, ok := rosa.ResolveName(args[0]); !ok {
|
||||||
return fmt.Errorf("unsupported artifact %q", args[0])
|
return fmt.Errorf("unknown artifact %q", args[0])
|
||||||
} else if flagDump == "" {
|
} else if flagDump == "" {
|
||||||
pathname, _, err := cache.Cure(rosa.Std.Load(p))
|
pathname, _, err := cache.Cure(rosa.Std.Load(p))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -195,10 +466,157 @@ func main() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var (
|
||||||
|
flagNet bool
|
||||||
|
flagSession bool
|
||||||
|
|
||||||
|
flagWithToolchain bool
|
||||||
|
)
|
||||||
|
c.NewCommand(
|
||||||
|
"shell",
|
||||||
|
"Interactive shell in the specified Rosa OS environment",
|
||||||
|
func(args []string) error {
|
||||||
|
root := make([]pkg.Artifact, 0, 6+len(args))
|
||||||
|
for _, arg := range args {
|
||||||
|
p, ok := rosa.ResolveName(arg)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown artifact %q", arg)
|
||||||
|
}
|
||||||
|
root = append(root, rosa.Std.Load(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagWithToolchain {
|
||||||
|
musl, compilerRT, runtimes, clang := rosa.Std.NewLLVM()
|
||||||
|
root = append(root, musl, compilerRT, runtimes, clang)
|
||||||
|
} else {
|
||||||
|
root = append(root, rosa.Std.Load(rosa.Musl))
|
||||||
|
}
|
||||||
|
root = append(root,
|
||||||
|
rosa.Std.Load(rosa.Mksh),
|
||||||
|
rosa.Std.Load(rosa.Toybox),
|
||||||
|
)
|
||||||
|
|
||||||
|
type cureRes struct {
|
||||||
|
pathname *check.Absolute
|
||||||
|
checksum unique.Handle[pkg.Checksum]
|
||||||
|
}
|
||||||
|
cured := make(map[pkg.Artifact]cureRes)
|
||||||
|
for _, a := range root {
|
||||||
|
pathname, checksum, err := cache.Cure(a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cured[a] = cureRes{pathname, checksum}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(cache.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
|
||||||
|
|
||||||
|
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(
|
||||||
|
&flagNet,
|
||||||
|
"net", command.BoolFlag(false),
|
||||||
|
"Share host net namespace",
|
||||||
|
).
|
||||||
|
Flag(
|
||||||
|
&flagSession,
|
||||||
|
"session", command.BoolFlag(false),
|
||||||
|
"Retain session",
|
||||||
|
).
|
||||||
|
Flag(
|
||||||
|
&flagWithToolchain,
|
||||||
|
"with-toolchain", command.BoolFlag(false),
|
||||||
|
"Include the stage3 LLVM toolchain",
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Command(
|
||||||
|
"help",
|
||||||
|
"Show this help message",
|
||||||
|
func([]string) error { c.PrintHelp(); return nil },
|
||||||
|
)
|
||||||
|
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
c.MustParse(os.Args[1:], func(err error) {
|
||||||
if cache != nil {
|
if cache != nil {
|
||||||
cache.Close()
|
cache.Close()
|
||||||
}
|
}
|
||||||
|
if w, ok := err.(interface{ Unwrap() []error }); !ok {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
} else {
|
||||||
|
errs := w.Unwrap()
|
||||||
|
for i, e := range errs {
|
||||||
|
if i == len(errs)-1 {
|
||||||
|
log.Fatal(e)
|
||||||
|
}
|
||||||
|
log.Println(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
229
cmd/pkgserver/main.go
Normal file
229
cmd/pkgserver/main.go
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/command"
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/internal/rosa"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate sh -c "sass ui/static/dark.scss ui/static/dark.css && sass ui/static/light.scss ui/static/light.css && tsc ui/static/index.ts"
|
||||||
|
//go:embed ui/*
|
||||||
|
var content embed.FS
|
||||||
|
|
||||||
|
func serveWebUI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("serveWebUI: %s\n", r.URL.Path)
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Expires", "0")
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
w.Header().Set("X-XSS-Protection", "1")
|
||||||
|
w.Header().Set("X-Frame-Options", "DENY")
|
||||||
|
|
||||||
|
http.ServeFileFS(w, r, content, "ui/index.html")
|
||||||
|
}
|
||||||
|
func serveStaticContent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("serveStaticContent: %s\n", r.URL.Path)
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/static/style.css":
|
||||||
|
darkTheme := r.CookiesNamed("dark_theme")
|
||||||
|
if len(darkTheme) > 0 && darkTheme[0].Value == "true" {
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/dark.css")
|
||||||
|
} else {
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/light.css")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "/favicon.ico":
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/favicon.ico")
|
||||||
|
break
|
||||||
|
case "/static/index.js":
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/index.js")
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func serveAPI(index *PackageIndex) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveStatus(index *PackageIndex) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if index == nil {
|
||||||
|
http.Error(w, "index is nil", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
base := path.Base(r.URL.Path)
|
||||||
|
name := strings.TrimSuffix(base, ".log")
|
||||||
|
p, ok := rosa.ResolveName(name)
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m := rosa.GetMetadata(p)
|
||||||
|
pk, ok := index.names[m.Name]
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(pk.status) > 0 {
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, err := io.Copy(w, bytes.NewReader(pk.status))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortOrders int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DeclarationAscending SortOrders = iota
|
||||||
|
DeclarationDescending
|
||||||
|
NameAscending
|
||||||
|
NameDescending
|
||||||
|
limitSortOrders
|
||||||
|
)
|
||||||
|
|
||||||
|
type PackageIndex struct {
|
||||||
|
sorts [limitSortOrders][rosa.PresetUnexportedStart]*PackageIndexEntry
|
||||||
|
names map[string]*PackageIndexEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type PackageIndexEntry struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Website string `json:"website"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
status []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPackageIndex(cache *pkg.Cache, report *rosa.Report) (_ *PackageIndex, err error) {
|
||||||
|
index := new(PackageIndex)
|
||||||
|
index.names = make(map[string]*PackageIndexEntry, rosa.PresetUnexportedStart)
|
||||||
|
work := make([]PackageIndexEntry, rosa.PresetUnexportedStart)
|
||||||
|
defer report.HandleAccess(&err)()
|
||||||
|
for p := range rosa.PresetUnexportedStart {
|
||||||
|
m := rosa.GetMetadata(p)
|
||||||
|
v := rosa.Std.Version(p)
|
||||||
|
a := rosa.Std.Load(p)
|
||||||
|
id := cache.Ident(a)
|
||||||
|
st, n := report.ArtifactOf(id)
|
||||||
|
var status []byte
|
||||||
|
if n < 1 {
|
||||||
|
status = nil
|
||||||
|
} else {
|
||||||
|
status = st
|
||||||
|
}
|
||||||
|
log.Printf("Processing package %s...\n", m.Name)
|
||||||
|
entry := PackageIndexEntry{
|
||||||
|
Name: m.Name,
|
||||||
|
Description: m.Description,
|
||||||
|
Website: m.Website,
|
||||||
|
Version: v,
|
||||||
|
status: status,
|
||||||
|
}
|
||||||
|
work[p] = entry
|
||||||
|
index.names[m.Name] = &entry
|
||||||
|
}
|
||||||
|
for i, p := range work {
|
||||||
|
index.sorts[DeclarationAscending][i] = &p
|
||||||
|
}
|
||||||
|
slices.Reverse(work)
|
||||||
|
for i, p := range work {
|
||||||
|
index.sorts[DeclarationDescending][i] = &p
|
||||||
|
}
|
||||||
|
slices.SortFunc(work, func(a PackageIndexEntry, b PackageIndexEntry) int {
|
||||||
|
return cmp.Compare(a.Name, b.Name)
|
||||||
|
})
|
||||||
|
for i, p := range work {
|
||||||
|
index.sorts[NameAscending][i] = &p
|
||||||
|
}
|
||||||
|
slices.Reverse(work)
|
||||||
|
for i, p := range work {
|
||||||
|
index.sorts[NameDescending][i] = &p
|
||||||
|
}
|
||||||
|
return index, err
|
||||||
|
}
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("pkgserver: ")
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagBaseDir string
|
||||||
|
flagPort int
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||||
|
defer stop()
|
||||||
|
msg := message.New(log.Default())
|
||||||
|
|
||||||
|
c := command.New(os.Stderr, log.Printf, "pkgserver", func(args []string) error {
|
||||||
|
reportPath := args[0]
|
||||||
|
baseDir, err := check.NewAbs(flagBaseDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("baseDir:", baseDir)
|
||||||
|
cache, err := pkg.Open(ctx, msg, 0, baseDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
report, err := rosa.OpenReport(reportPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("reportPath:", reportPath)
|
||||||
|
log.Println("indexing packages...")
|
||||||
|
index, err := createPackageIndex(cache, report)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("created package index")
|
||||||
|
http.HandleFunc("GET /{$}", serveWebUI)
|
||||||
|
http.HandleFunc("GET /favicon.ico", serveStaticContent)
|
||||||
|
http.HandleFunc("GET /static/", serveStaticContent)
|
||||||
|
http.HandleFunc("GET /api/", serveAPI(index))
|
||||||
|
http.HandleFunc("GET /api/status/", serveStatus(index))
|
||||||
|
log.Println("listening on", flagPort)
|
||||||
|
err = http.ListenAndServe(fmt.Sprintf(":%d", flagPort), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}).Flag(
|
||||||
|
&flagBaseDir,
|
||||||
|
"b", command.StringFlag(""),
|
||||||
|
"base directory for cache",
|
||||||
|
).Flag(
|
||||||
|
&flagPort,
|
||||||
|
"p", command.IntFlag(8067),
|
||||||
|
"http listen port",
|
||||||
|
)
|
||||||
|
c.MustParse(os.Args[1:], func(e error) {
|
||||||
|
log.Fatal(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
26
cmd/pkgserver/ui/index.html
Normal file
26
cmd/pkgserver/ui/index.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="static/style.css">
|
||||||
|
<title>Hakurei PkgServer</title>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||||
|
<script src="static/index.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hakurei PkgServer</h1>
|
||||||
|
|
||||||
|
<table id="pkg-list">
|
||||||
|
<tr><th>Status</th><th>Name</th><th>Version</th></tr>
|
||||||
|
</table>
|
||||||
|
<p>Showing entries <span id="entry-counter"></span>.</p>
|
||||||
|
<span class="bottom-nav"><a href="javascript:prevPage()">« Previous</a> <span id="page-number">1</span> <a href="javascript:nextPage()">Next »</a></span>
|
||||||
|
<span><label for="count">Entries per page:</label><select name="count" id="count">
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="100">100</option>
|
||||||
|
</select></span>
|
||||||
|
</body>
|
||||||
|
<footer>© <a href="https://hakurei.app/">Hakurei</a>. Licensed under the MIT license.</footer>
|
||||||
|
</html>
|
||||||
0
cmd/pkgserver/ui/static/_common.scss
Normal file
0
cmd/pkgserver/ui/static/_common.scss
Normal file
6
cmd/pkgserver/ui/static/dark.css
Normal file
6
cmd/pkgserver/ui/static/dark.css
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
@use 'common';
|
||||||
|
html {
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
color: ghostwhite; }
|
||||||
|
|
||||||
|
/*# sourceMappingURL=dark.css.map */
|
||||||
7
cmd/pkgserver/ui/static/dark.css.map
Normal file
7
cmd/pkgserver/ui/static/dark.css.map
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"mappings": "AAAA,aAAa;AAEb,IAAK;EACH,gBAAgB,EAAE,OAAO;EACzB,KAAK,EAAE,UAAU",
|
||||||
|
"sources": ["dark.scss"],
|
||||||
|
"names": [],
|
||||||
|
"file": "dark.css"
|
||||||
|
}
|
||||||
6
cmd/pkgserver/ui/static/dark.scss
Normal file
6
cmd/pkgserver/ui/static/dark.scss
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
@use 'common';
|
||||||
|
|
||||||
|
html {
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
color: ghostwhite;
|
||||||
|
}
|
||||||
BIN
cmd/pkgserver/ui/static/favicon.ico
Normal file
BIN
cmd/pkgserver/ui/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
67
cmd/pkgserver/ui/static/index.js
Normal file
67
cmd/pkgserver/ui/static/index.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
"use strict";
|
||||||
|
var PackageEntry = /** @class */ (function () {
|
||||||
|
function PackageEntry() {
|
||||||
|
}
|
||||||
|
return PackageEntry;
|
||||||
|
}());
|
||||||
|
var State = /** @class */ (function () {
|
||||||
|
function State() {
|
||||||
|
this.entriesPerPage = 10;
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.entryIndex = 0;
|
||||||
|
this.loadedEntries = [];
|
||||||
|
}
|
||||||
|
State.prototype.getEntriesPerPage = function () {
|
||||||
|
return this.entriesPerPage;
|
||||||
|
};
|
||||||
|
State.prototype.setEntriesPerPage = function (entriesPerPage) {
|
||||||
|
this.entriesPerPage = entriesPerPage;
|
||||||
|
this.updateRange();
|
||||||
|
};
|
||||||
|
State.prototype.getCurrentPage = function () {
|
||||||
|
return this.currentPage;
|
||||||
|
};
|
||||||
|
State.prototype.setCurrentPage = function (page) {
|
||||||
|
this.currentPage = page;
|
||||||
|
document.getElementById("page-number").innerText = String(this.currentPage);
|
||||||
|
this.updateRange();
|
||||||
|
};
|
||||||
|
State.prototype.getEntryIndex = function () {
|
||||||
|
return this.entryIndex;
|
||||||
|
};
|
||||||
|
State.prototype.setEntryIndex = function (entryIndex) {
|
||||||
|
this.entryIndex = entryIndex;
|
||||||
|
this.updateRange();
|
||||||
|
};
|
||||||
|
State.prototype.getLoadedEntries = function () {
|
||||||
|
return this.loadedEntries;
|
||||||
|
};
|
||||||
|
State.prototype.getMaxPage = function () {
|
||||||
|
return this.loadedEntries.length / this.entriesPerPage;
|
||||||
|
};
|
||||||
|
State.prototype.updateRange = function () {
|
||||||
|
var max = Math.min(this.entryIndex + this.entriesPerPage, this.loadedEntries.length);
|
||||||
|
document.getElementById("entry-counter").innerText = "".concat(this.entryIndex, "-").concat(max, " of ").concat(this.loadedEntries.length);
|
||||||
|
};
|
||||||
|
return State;
|
||||||
|
}());
|
||||||
|
var STATE;
|
||||||
|
function prevPage() {
|
||||||
|
var current = STATE.getCurrentPage();
|
||||||
|
if (current > 1) {
|
||||||
|
STATE.setCurrentPage(STATE.getCurrentPage() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function nextPage() {
|
||||||
|
var current = STATE.getCurrentPage();
|
||||||
|
if (current < STATE.getMaxPage()) {
|
||||||
|
STATE.setCurrentPage(STATE.getCurrentPage() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
STATE = new State();
|
||||||
|
STATE.updateRange();
|
||||||
|
document.getElementById("count").addEventListener("change", function (event) {
|
||||||
|
STATE.setEntriesPerPage(parseInt(event.target.value));
|
||||||
|
});
|
||||||
|
});
|
||||||
66
cmd/pkgserver/ui/static/index.ts
Normal file
66
cmd/pkgserver/ui/static/index.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
"use strict"
|
||||||
|
|
||||||
|
class PackageEntry {
|
||||||
|
|
||||||
|
}
|
||||||
|
class State {
|
||||||
|
entriesPerPage: number = 10
|
||||||
|
currentPage: number = 1
|
||||||
|
entryIndex: number = 0
|
||||||
|
loadedEntries: PackageEntry[] = []
|
||||||
|
getEntriesPerPage(): number {
|
||||||
|
return this.entriesPerPage
|
||||||
|
}
|
||||||
|
setEntriesPerPage(entriesPerPage: number) {
|
||||||
|
this.entriesPerPage = entriesPerPage
|
||||||
|
this.updateRange()
|
||||||
|
}
|
||||||
|
getCurrentPage(): number {
|
||||||
|
return this.currentPage
|
||||||
|
}
|
||||||
|
setCurrentPage(page: number) {
|
||||||
|
this.currentPage = page
|
||||||
|
document.getElementById("page-number").innerText = String(this.currentPage)
|
||||||
|
this.updateRange()
|
||||||
|
}
|
||||||
|
getEntryIndex(): number {
|
||||||
|
return this.entryIndex
|
||||||
|
}
|
||||||
|
setEntryIndex(entryIndex: number) {
|
||||||
|
this.entryIndex = entryIndex
|
||||||
|
this.updateRange()
|
||||||
|
}
|
||||||
|
getLoadedEntries(): PackageEntry[] {
|
||||||
|
return this.loadedEntries
|
||||||
|
}
|
||||||
|
getMaxPage(): number {
|
||||||
|
return this.loadedEntries.length / this.entriesPerPage
|
||||||
|
}
|
||||||
|
updateRange() {
|
||||||
|
let max = Math.min(this.entryIndex + this.entriesPerPage, this.loadedEntries.length)
|
||||||
|
document.getElementById("entry-counter").innerText = `${this.entryIndex}-${max} of ${this.loadedEntries.length}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let STATE: State
|
||||||
|
|
||||||
|
function prevPage() {
|
||||||
|
let current = STATE.getCurrentPage()
|
||||||
|
if (current > 1) {
|
||||||
|
STATE.setCurrentPage(STATE.getCurrentPage() - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function nextPage() {
|
||||||
|
let current = STATE.getCurrentPage()
|
||||||
|
if (current < STATE.getMaxPage()) {
|
||||||
|
STATE.setCurrentPage(STATE.getCurrentPage() + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
STATE = new State()
|
||||||
|
STATE.updateRange()
|
||||||
|
document.getElementById("count").addEventListener("change", (event) => {
|
||||||
|
STATE.setEntriesPerPage(parseInt((event.target as HTMLSelectElement).value))
|
||||||
|
})
|
||||||
|
})
|
||||||
6
cmd/pkgserver/ui/static/light.css
Normal file
6
cmd/pkgserver/ui/static/light.css
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
@use 'common';
|
||||||
|
html {
|
||||||
|
background-color: #d3d3d3;
|
||||||
|
color: black; }
|
||||||
|
|
||||||
|
/*# sourceMappingURL=light.css.map */
|
||||||
7
cmd/pkgserver/ui/static/light.css.map
Normal file
7
cmd/pkgserver/ui/static/light.css.map
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"mappings": "AAAA,aAAa;AAEb,IAAK;EACH,gBAAgB,EAAE,OAAO;EACzB,KAAK,EAAE,KAAK",
|
||||||
|
"sources": ["light.scss"],
|
||||||
|
"names": [],
|
||||||
|
"file": "light.css"
|
||||||
|
}
|
||||||
6
cmd/pkgserver/ui/static/light.scss
Normal file
6
cmd/pkgserver/ui/static/light.scss
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
@use 'common';
|
||||||
|
|
||||||
|
html {
|
||||||
|
background-color: #d3d3d3;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/container/std"
|
"hakurei.app/container/std"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/helper/proc"
|
"hakurei.app/internal/helper/proc"
|
||||||
@@ -441,12 +442,7 @@ func _main(s ...string) (exitCode int) {
|
|||||||
// keep fuse_parse_cmdline happy in the container
|
// keep fuse_parse_cmdline happy in the container
|
||||||
z.Tmpfs(check.MustAbs(container.Nonexistent), 1<<10, 0755)
|
z.Tmpfs(check.MustAbs(container.Nonexistent), 1<<10, 0755)
|
||||||
|
|
||||||
if a, err := check.NewAbs(container.MustExecutable(msg)); err != nil {
|
z.Path = fhs.AbsProcSelfExe
|
||||||
log.Println(err)
|
|
||||||
return 5
|
|
||||||
} else {
|
|
||||||
z.Path = a
|
|
||||||
}
|
|
||||||
z.Args = s
|
z.Args = s
|
||||||
z.ForwardCancel = true
|
z.ForwardCancel = true
|
||||||
z.SeccompPresets |= std.PresetStrict
|
z.SeccompPresets |= std.PresetStrict
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import (
|
|||||||
|
|
||||||
func init() { gob.Register(new(AutoEtcOp)) }
|
func init() { gob.Register(new(AutoEtcOp)) }
|
||||||
|
|
||||||
// Etc appends an [Op] that expands host /etc into a toplevel symlink mirror with /etc semantics.
|
// Etc is a helper for appending [AutoEtcOp] to [Ops].
|
||||||
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
|
||||||
func (f *Ops) Etc(host *check.Absolute, prefix string) *Ops {
|
func (f *Ops) Etc(host *check.Absolute, prefix string) *Ops {
|
||||||
e := &AutoEtcOp{prefix}
|
e := &AutoEtcOp{prefix}
|
||||||
f.Mkdir(fhs.AbsEtc, 0755)
|
f.Mkdir(fhs.AbsEtc, 0755)
|
||||||
@@ -20,6 +19,9 @@ func (f *Ops) Etc(host *check.Absolute, prefix string) *Ops {
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AutoEtcOp expands host /etc into a toplevel symlink mirror with /etc semantics.
|
||||||
|
//
|
||||||
|
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
||||||
type AutoEtcOp struct{ Prefix string }
|
type AutoEtcOp struct{ Prefix string }
|
||||||
|
|
||||||
func (e *AutoEtcOp) Valid() bool { return e != nil }
|
func (e *AutoEtcOp) Valid() bool { return e != nil }
|
||||||
|
|||||||
@@ -11,13 +11,15 @@ import (
|
|||||||
|
|
||||||
func init() { gob.Register(new(AutoRootOp)) }
|
func init() { gob.Register(new(AutoRootOp)) }
|
||||||
|
|
||||||
// Root appends an [Op] that expands a directory into a toplevel bind mount mirror on container root.
|
// Root is a helper for appending [AutoRootOp] to [Ops].
|
||||||
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
|
||||||
func (f *Ops) Root(host *check.Absolute, flags int) *Ops {
|
func (f *Ops) Root(host *check.Absolute, flags int) *Ops {
|
||||||
*f = append(*f, &AutoRootOp{host, flags, nil})
|
*f = append(*f, &AutoRootOp{host, flags, nil})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AutoRootOp expands a directory into a toplevel bind mount mirror on container root.
|
||||||
|
//
|
||||||
|
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
||||||
type AutoRootOp struct {
|
type AutoRootOp struct {
|
||||||
Host *check.Absolute
|
Host *check.Absolute
|
||||||
// passed through to bindMount
|
// passed through to bindMount
|
||||||
|
|||||||
@@ -50,10 +50,16 @@ func capset(hdrp *capHeader, datap *[2]capData) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// capBoundingSetDrop drops a capability from the calling thread's capability bounding set.
|
// capBoundingSetDrop drops a capability from the calling thread's capability bounding set.
|
||||||
func capBoundingSetDrop(cap uintptr) error { return Prctl(syscall.PR_CAPBSET_DROP, cap, 0) }
|
func capBoundingSetDrop(cap uintptr) error {
|
||||||
|
return Prctl(syscall.PR_CAPBSET_DROP, cap, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// capAmbientClearAll clears the ambient capability set of the calling thread.
|
// capAmbientClearAll clears the ambient capability set of the calling thread.
|
||||||
func capAmbientClearAll() error { return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0) }
|
func capAmbientClearAll() error {
|
||||||
|
return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// capAmbientRaise adds to the ambient capability set of the calling thread.
|
// capAmbientRaise adds to the ambient capability set of the calling thread.
|
||||||
func capAmbientRaise(cap uintptr) error { return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap) }
|
func capAmbientRaise(cap uintptr) error {
|
||||||
|
return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap)
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ const (
|
|||||||
SpecialOverlayPath = ":"
|
SpecialOverlayPath = ":"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EscapeOverlayDataSegment escapes a string for formatting into the data argument of an overlay mount call.
|
// EscapeOverlayDataSegment escapes a string for formatting into the data
|
||||||
|
// argument of an overlay mount system call.
|
||||||
func EscapeOverlayDataSegment(s string) string {
|
func EscapeOverlayDataSegment(s string) string {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Package container implements unprivileged Linux containers with built-in support for syscall filtering.
|
// Package container implements unprivileged Linux containers with built-in
|
||||||
|
// support for syscall filtering.
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -37,24 +38,30 @@ type (
|
|||||||
Container struct {
|
Container struct {
|
||||||
// Whether the container init should stay alive after its parent terminates.
|
// Whether the container init should stay alive after its parent terminates.
|
||||||
AllowOrphan bool
|
AllowOrphan bool
|
||||||
|
// Scheduling policy to set via sched_setscheduler(2). The zero value
|
||||||
|
// skips this call. Supported policies are [SCHED_BATCH], [SCHED_IDLE].
|
||||||
|
SchedPolicy SchedPolicy
|
||||||
// Cgroup fd, nil to disable.
|
// Cgroup fd, nil to disable.
|
||||||
Cgroup *int
|
Cgroup *int
|
||||||
// ExtraFiles passed through to initial process in the container,
|
// ExtraFiles passed through to initial process in the container, with
|
||||||
// with behaviour identical to its [exec.Cmd] counterpart.
|
// behaviour identical to its [exec.Cmd] counterpart.
|
||||||
ExtraFiles []*os.File
|
ExtraFiles []*os.File
|
||||||
|
|
||||||
// param pipe for shim and init
|
// Write end of a pipe connected to the init to deliver [Params].
|
||||||
setup *os.File
|
setup *os.File
|
||||||
// cancels cmd
|
// Cancels the context passed to the underlying cmd.
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
// closed after Wait returns
|
// Closed after Wait returns. Keeps the spawning thread alive.
|
||||||
wait chan struct{}
|
wait chan struct{}
|
||||||
|
|
||||||
Stdin io.Reader
|
Stdin io.Reader
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
|
// Custom cancellation behaviour for the underlying [exec.Cmd]. Must
|
||||||
|
// deliver [CancelSignal] before returning.
|
||||||
Cancel func(cmd *exec.Cmd) error
|
Cancel func(cmd *exec.Cmd) error
|
||||||
|
// Copied to the underlying [exec.Cmd].
|
||||||
WaitDelay time.Duration
|
WaitDelay time.Duration
|
||||||
|
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
@@ -283,7 +290,11 @@ func (p *Container) Start() error {
|
|||||||
|
|
||||||
// place setup pipe before user supplied extra files, this is later restored by init
|
// place setup pipe before user supplied extra files, this is later restored by init
|
||||||
if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil {
|
if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil {
|
||||||
return &StartError{true, "set up params stream", err, false, false}
|
return &StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "set up params stream",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
p.setup = f
|
p.setup = f
|
||||||
p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)}
|
p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)}
|
||||||
@@ -295,10 +306,16 @@ func (p *Container) Start() error {
|
|||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
p.wait = make(chan struct{})
|
p.wait = make(chan struct{})
|
||||||
|
|
||||||
done <- func() error { // setup depending on per-thread state must happen here
|
// setup depending on per-thread state must happen here
|
||||||
// PR_SET_NO_NEW_PRIVS: depends on per-thread state but acts on all processes created from that thread
|
done <- func() error {
|
||||||
|
// PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes
|
||||||
|
// created from the calling thread
|
||||||
if err := SetNoNewPrivs(); err != nil {
|
if err := SetNoNewPrivs(); err != nil {
|
||||||
return &StartError{true, "prctl(PR_SET_NO_NEW_PRIVS)", err, false, false}
|
return &StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "prctl(PR_SET_NO_NEW_PRIVS)",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// landlock: depends on per-thread state but acts on a process group
|
// landlock: depends on per-thread state but acts on a process group
|
||||||
@@ -310,28 +327,40 @@ func (p *Container) Start() error {
|
|||||||
|
|
||||||
if abi, err := LandlockGetABI(); err != nil {
|
if abi, err := LandlockGetABI(); err != nil {
|
||||||
if p.HostAbstract {
|
if p.HostAbstract {
|
||||||
// landlock can be skipped here as it restricts access to resources
|
// landlock can be skipped here as it restricts access
|
||||||
// already covered by namespaces (pid)
|
// to resources already covered by namespaces (pid)
|
||||||
goto landlockOut
|
goto landlockOut
|
||||||
}
|
}
|
||||||
return &StartError{false, "get landlock ABI", err, false, false}
|
return &StartError{Step: "get landlock ABI", Err: err}
|
||||||
} else if abi < 6 {
|
} else if abi < 6 {
|
||||||
if p.HostAbstract {
|
if p.HostAbstract {
|
||||||
// see above comment
|
// see above comment
|
||||||
goto landlockOut
|
goto landlockOut
|
||||||
}
|
}
|
||||||
return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false}
|
return &StartError{
|
||||||
|
Step: "kernel too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET",
|
||||||
|
Err: ENOSYS,
|
||||||
|
Origin: true,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
p.msg.Verbosef("landlock abi version %d", abi)
|
p.msg.Verbosef("landlock abi version %d", abi)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
||||||
return &StartError{true, "create landlock ruleset", err, false, false}
|
return &StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "create landlock ruleset",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
||||||
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
|
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
|
||||||
_ = Close(rulesetFd)
|
_ = Close(rulesetFd)
|
||||||
return &StartError{true, "enforce landlock ruleset", err, false, false}
|
return &StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "enforce landlock ruleset",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err = Close(rulesetFd); err != nil {
|
if err = Close(rulesetFd); err != nil {
|
||||||
p.msg.Verbosef("cannot close landlock ruleset: %v", err)
|
p.msg.Verbosef("cannot close landlock ruleset: %v", err)
|
||||||
@@ -342,9 +371,41 @@ func (p *Container) Start() error {
|
|||||||
landlockOut:
|
landlockOut:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sched_setscheduler: thread-directed but acts on all processes
|
||||||
|
// created from the calling thread
|
||||||
|
if p.SchedPolicy > 0 && p.SchedPolicy <= _SCHED_LAST {
|
||||||
|
var param schedParam
|
||||||
|
if priority, err := p.SchedPolicy.GetPriorityMin(); err != nil {
|
||||||
|
return &StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "get minimum priority",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
param.priority = priority
|
||||||
|
}
|
||||||
|
|
||||||
|
p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy)
|
||||||
|
if err := schedSetscheduler(
|
||||||
|
0, // calling thread
|
||||||
|
p.SchedPolicy,
|
||||||
|
¶m,
|
||||||
|
); err != nil {
|
||||||
|
return &StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "enforce landlock ruleset",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p.msg.Verbose("starting container init")
|
p.msg.Verbose("starting container init")
|
||||||
if err := p.cmd.Start(); err != nil {
|
if err := p.cmd.Start(); err != nil {
|
||||||
return &StartError{false, "start container init", err, false, true}
|
return &StartError{
|
||||||
|
Step: "start container init",
|
||||||
|
Err: err,
|
||||||
|
Passthrough: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
@@ -356,6 +417,7 @@ func (p *Container) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Serve serves [Container.Params] to the container init.
|
// Serve serves [Container.Params] to the container init.
|
||||||
|
//
|
||||||
// Serve must only be called once.
|
// Serve must only be called once.
|
||||||
func (p *Container) Serve() error {
|
func (p *Container) Serve() error {
|
||||||
if p.setup == nil {
|
if p.setup == nil {
|
||||||
@@ -365,12 +427,21 @@ func (p *Container) Serve() error {
|
|||||||
setup := p.setup
|
setup := p.setup
|
||||||
p.setup = nil
|
p.setup = nil
|
||||||
if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil {
|
if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil {
|
||||||
return &StartError{true, "set init pipe deadline", err, false, true}
|
return &StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "set init pipe deadline",
|
||||||
|
Err: err,
|
||||||
|
Passthrough: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Path == nil {
|
if p.Path == nil {
|
||||||
p.cancel()
|
p.cancel()
|
||||||
return &StartError{false, "invalid executable pathname", EINVAL, true, false}
|
return &StartError{
|
||||||
|
Step: "invalid executable pathname",
|
||||||
|
Err: EINVAL,
|
||||||
|
Origin: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not transmit nil
|
// do not transmit nil
|
||||||
@@ -395,7 +466,8 @@ func (p *Container) Serve() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait waits for the container init process to exit and releases any resources associated with the [Container].
|
// Wait blocks until the container init process to exit and releases any
|
||||||
|
// resources associated with the [Container].
|
||||||
func (p *Container) Wait() error {
|
func (p *Container) Wait() error {
|
||||||
if p.cmd == nil || p.cmd.Process == nil {
|
if p.cmd == nil || p.cmd.Process == nil {
|
||||||
return EINVAL
|
return EINVAL
|
||||||
@@ -440,11 +512,13 @@ func (p *Container) StderrPipe() (r io.ReadCloser, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Container) String() string {
|
func (p *Container) String() string {
|
||||||
return fmt.Sprintf("argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x",
|
return fmt.Sprintf(
|
||||||
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets))
|
"argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x",
|
||||||
|
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessState returns the address to os.ProcessState held by the underlying [exec.Cmd].
|
// ProcessState returns the address of os.ProcessState held by the underlying [exec.Cmd].
|
||||||
func (p *Container) ProcessState() *os.ProcessState {
|
func (p *Container) ProcessState() *os.ProcessState {
|
||||||
if p.cmd == nil {
|
if p.cmd == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -452,7 +526,8 @@ func (p *Container) ProcessState() *os.ProcessState {
|
|||||||
return p.cmd.ProcessState
|
return p.cmd.ProcessState
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns the address to a new instance of [Container] that requires further initialisation before use.
|
// New returns the address to a new instance of [Container]. This value requires
|
||||||
|
// further initialisation before use.
|
||||||
func New(ctx context.Context, msg message.Msg) *Container {
|
func New(ctx context.Context, msg message.Msg) *Container {
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
msg = message.New(nil)
|
msg = message.New(nil)
|
||||||
@@ -461,12 +536,18 @@ func New(ctx context.Context, msg message.Msg) *Container {
|
|||||||
p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}}
|
p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}}
|
||||||
c, cancel := context.WithCancel(ctx)
|
c, cancel := context.WithCancel(ctx)
|
||||||
p.cancel = cancel
|
p.cancel = cancel
|
||||||
p.cmd = exec.CommandContext(c, MustExecutable(msg))
|
p.cmd = exec.CommandContext(c, fhs.ProcSelfExe)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields.
|
// NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields.
|
||||||
func NewCommand(ctx context.Context, msg message.Msg, pathname *check.Absolute, name string, args ...string) *Container {
|
func NewCommand(
|
||||||
|
ctx context.Context,
|
||||||
|
msg message.Msg,
|
||||||
|
pathname *check.Absolute,
|
||||||
|
name string,
|
||||||
|
args ...string,
|
||||||
|
) *Container {
|
||||||
z := New(ctx, msg)
|
z := New(ctx, msg)
|
||||||
z.Path = pathname
|
z.Path = pathname
|
||||||
z.Args = append([]string{name}, args...)
|
z.Args = append([]string{name}, args...)
|
||||||
|
|||||||
@@ -274,13 +274,13 @@ var containerTestCases = []struct {
|
|||||||
Dev(check.MustAbs("/dev"), true),
|
Dev(check.MustAbs("/dev"), true),
|
||||||
),
|
),
|
||||||
earlyMnt(
|
earlyMnt(
|
||||||
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
|
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", ignore, ignore),
|
||||||
ent("/null", "/dev/null", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/null", "/dev/null", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/zero", "/dev/zero", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/zero", "/dev/zero", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/full", "/dev/full", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/full", "/dev/full", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/random", "/dev/random", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/random", "/dev/random", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/urandom", "/dev/urandom", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/urandom", "/dev/urandom", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/tty", "/dev/tty", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/tty", "/dev/tty", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
||||||
ent("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
|
ent("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
|
||||||
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
||||||
@@ -292,13 +292,13 @@ var containerTestCases = []struct {
|
|||||||
Dev(check.MustAbs("/dev"), false),
|
Dev(check.MustAbs("/dev"), false),
|
||||||
),
|
),
|
||||||
earlyMnt(
|
earlyMnt(
|
||||||
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
|
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", ignore, ignore),
|
||||||
ent("/null", "/dev/null", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/null", "/dev/null", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/zero", "/dev/zero", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/zero", "/dev/zero", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/full", "/dev/full", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/full", "/dev/full", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/random", "/dev/random", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/random", "/dev/random", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/urandom", "/dev/urandom", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/urandom", "/dev/urandom", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/tty", "/dev/tty", ignore, "devtmpfs", "devtmpfs", ignore),
|
ent("/tty", "/dev/tty", ignore, "devtmpfs", ignore, ignore),
|
||||||
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
|
||||||
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
|
||||||
),
|
),
|
||||||
@@ -690,14 +690,22 @@ func init() {
|
|||||||
return fmt.Errorf("got more than %d entries", len(mnt))
|
return fmt.Errorf("got more than %d entries", len(mnt))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ugly hack but should be reliable and is less likely to false negative than comparing by parsed flags
|
// ugly hack but should be reliable and is less likely to
|
||||||
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",relatime")
|
//false negative than comparing by parsed flags
|
||||||
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ",noatime")
|
for _, s := range []string{
|
||||||
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",relatime")
|
"relatime",
|
||||||
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",noatime")
|
"noatime",
|
||||||
|
} {
|
||||||
cur.FsOptstr = strings.Replace(cur.FsOptstr, ",seclabel", "", 1)
|
cur.VfsOptstr = strings.TrimSuffix(cur.VfsOptstr, ","+s)
|
||||||
mnt[i].FsOptstr = strings.Replace(mnt[i].FsOptstr, ",seclabel", "", 1)
|
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ","+s)
|
||||||
|
}
|
||||||
|
for _, s := range []string{
|
||||||
|
"seclabel",
|
||||||
|
"inode64",
|
||||||
|
} {
|
||||||
|
cur.FsOptstr = strings.Replace(cur.FsOptstr, ","+s, "", 1)
|
||||||
|
mnt[i].FsOptstr = strings.Replace(mnt[i].FsOptstr, ","+s, "", 1)
|
||||||
|
}
|
||||||
|
|
||||||
if !cur.EqualWithIgnore(mnt[i], "\x00") {
|
if !cur.EqualWithIgnore(mnt[i], "\x00") {
|
||||||
fail = true
|
fail = true
|
||||||
@@ -765,14 +773,13 @@ func TestMain(m *testing.M) {
|
|||||||
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) {
|
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) {
|
||||||
msg := message.New(nil)
|
msg := message.New(nil)
|
||||||
msg.SwapVerbose(testing.Verbose())
|
msg.SwapVerbose(testing.Verbose())
|
||||||
executable := check.MustAbs(container.MustExecutable(msg))
|
|
||||||
|
|
||||||
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
|
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
|
||||||
c.Env = append(c.Env, envDoCheck+"=1")
|
c.Env = append(c.Env, envDoCheck+"=1")
|
||||||
c.Bind(executable, absHelperInnerPath, 0)
|
c.Bind(fhs.AbsProcSelfExe, absHelperInnerPath, 0)
|
||||||
|
|
||||||
// in case test has cgo enabled
|
// in case test has cgo enabled
|
||||||
if entries, err := ldd.Resolve(ctx, msg, executable); err != nil {
|
if entries, err := ldd.Resolve(ctx, msg, nil); err != nil {
|
||||||
log.Fatalf("ldd: %v", err)
|
log.Fatalf("ldd: %v", err)
|
||||||
} else {
|
} else {
|
||||||
*libPaths = ldd.Path(entries)
|
*libPaths = ldd.Path(entries)
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ type osFile interface {
|
|||||||
fs.File
|
fs.File
|
||||||
}
|
}
|
||||||
|
|
||||||
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
|
// syscallDispatcher provides methods that make state-dependent system calls as
|
||||||
|
// part of their behaviour.
|
||||||
type syscallDispatcher interface {
|
type syscallDispatcher interface {
|
||||||
// new starts a goroutine with a new instance of syscallDispatcher.
|
// new starts a goroutine with a new instance of syscallDispatcher.
|
||||||
// A syscallDispatcher must never be used in any goroutine other than the one owning it,
|
// A syscallDispatcher must never be used in any goroutine other than the one owning it,
|
||||||
|
|||||||
@@ -238,8 +238,11 @@ 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 on it for sloppy implementations will cause sporadic test results
|
// check happens in Close, and cleanup is not guaranteed to run, so relying
|
||||||
f.cleanup = runtime.AddCleanup(f, func(name string) { f.t.Fatalf("checkedOsFile %s became unreachable without a call to Close", name) }, f.name)
|
// on it for sloppy implementations will cause sporadic test results
|
||||||
|
f.cleanup = runtime.AddCleanup(f, func(name string) {
|
||||||
|
panic("checkedOsFile " + name + " became unreachable without a call to Close")
|
||||||
|
}, name)
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ func messageFromError(err error) (m string, ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// messagePrefix checks and prefixes the error message of a non-pointer error.
|
// messagePrefix checks and prefixes the error message of a non-pointer error.
|
||||||
// While this is usable for pointer errors, such use should be avoided as nil check is omitted.
|
// While this is usable for pointer errors, such use should be avoided as nil
|
||||||
|
// 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
|
var targetError T
|
||||||
if errors.As(err, &targetError) {
|
if errors.As(err, &targetError) {
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ func copyExecutable(msg message.Msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustExecutable calls [os.Executable] and terminates the process on error.
|
||||||
|
//
|
||||||
|
// Deprecated: This is no longer used and will be removed in 0.4.
|
||||||
func MustExecutable(msg message.Msg) string {
|
func MustExecutable(msg message.Msg) string {
|
||||||
executableOnce.Do(func() { copyExecutable(msg) })
|
executableOnce.Do(func() { copyExecutable(msg) })
|
||||||
return executable
|
return executable
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ var (
|
|||||||
AbsDevShm = unsafeAbs(DevShm)
|
AbsDevShm = unsafeAbs(DevShm)
|
||||||
// AbsProc is [Proc] as [check.Absolute].
|
// AbsProc is [Proc] as [check.Absolute].
|
||||||
AbsProc = unsafeAbs(Proc)
|
AbsProc = unsafeAbs(Proc)
|
||||||
|
// AbsProcSelfExe is [ProcSelfExe] as [check.Absolute].
|
||||||
|
AbsProcSelfExe = unsafeAbs(ProcSelfExe)
|
||||||
// AbsSys is [Sys] as [check.Absolute].
|
// AbsSys is [Sys] as [check.Absolute].
|
||||||
AbsSys = unsafeAbs(Sys)
|
AbsSys = unsafeAbs(Sys)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ const (
|
|||||||
// Tmp points to the place for small temporary files.
|
// Tmp points to the place for small temporary files.
|
||||||
Tmp = "/tmp/"
|
Tmp = "/tmp/"
|
||||||
|
|
||||||
// Run points to a "tmpfs" file system for system packages to place runtime data, socket files, and similar.
|
// Run points to a "tmpfs" file system for system packages to place runtime
|
||||||
|
// data, socket files, and similar.
|
||||||
Run = "/run/"
|
Run = "/run/"
|
||||||
// RunUser points to a directory containing per-user runtime directories,
|
// RunUser points to a directory containing per-user runtime directories,
|
||||||
// each usually individually mounted "tmpfs" instances.
|
// each usually individually mounted "tmpfs" instances.
|
||||||
@@ -17,10 +18,12 @@ const (
|
|||||||
|
|
||||||
// Usr points to vendor-supplied operating system resources.
|
// Usr points to vendor-supplied operating system resources.
|
||||||
Usr = "/usr/"
|
Usr = "/usr/"
|
||||||
// UsrBin points to binaries and executables for user commands that shall appear in the $PATH search path.
|
// UsrBin points to binaries and executables for user commands that shall
|
||||||
|
// appear in the $PATH search path.
|
||||||
UsrBin = Usr + "bin/"
|
UsrBin = Usr + "bin/"
|
||||||
|
|
||||||
// Var points to persistent, variable system data. Writable during normal system operation.
|
// Var points to persistent, variable system data. Writable during normal
|
||||||
|
// system operation.
|
||||||
Var = "/var/"
|
Var = "/var/"
|
||||||
// VarLib points to persistent system data.
|
// VarLib points to persistent system data.
|
||||||
VarLib = Var + "lib/"
|
VarLib = Var + "lib/"
|
||||||
@@ -29,12 +32,20 @@ const (
|
|||||||
|
|
||||||
// Dev points to the root directory for device nodes.
|
// Dev points to the root directory for device nodes.
|
||||||
Dev = "/dev/"
|
Dev = "/dev/"
|
||||||
// DevShm is the place for POSIX shared memory segments, as created via shm_open(3).
|
// DevShm is the place for POSIX shared memory segments, as created via
|
||||||
|
// shm_open(3).
|
||||||
DevShm = "/dev/shm/"
|
DevShm = "/dev/shm/"
|
||||||
// Proc points to a virtual kernel file system exposing the process list and other functionality.
|
// Proc points to a virtual kernel file system exposing the process list and
|
||||||
|
// other functionality.
|
||||||
Proc = "/proc/"
|
Proc = "/proc/"
|
||||||
// ProcSys points to a hierarchy below /proc/ that exposes a number of kernel tunables.
|
// ProcSys points to a hierarchy below /proc/ that exposes a number of
|
||||||
|
// kernel tunables.
|
||||||
ProcSys = Proc + "sys/"
|
ProcSys = Proc + "sys/"
|
||||||
// Sys points to a virtual kernel file system exposing discovered devices and other functionality.
|
// ProcSelf resolves to the process's own /proc/pid directory.
|
||||||
|
ProcSelf = Proc + "self/"
|
||||||
|
// ProcSelfExe is a symbolic link to program pathname.
|
||||||
|
ProcSelfExe = ProcSelf + "exe"
|
||||||
|
// Sys points to a virtual kernel file system exposing discovered devices
|
||||||
|
// and other functionality.
|
||||||
Sys = "/sys/"
|
Sys = "/sys/"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ const (
|
|||||||
- This path is only accessible by init and root:
|
- This path is only accessible by init and root:
|
||||||
The container init sets SUID_DUMP_DISABLE and terminates if that fails.
|
The container init sets SUID_DUMP_DISABLE and terminates if that fails.
|
||||||
|
|
||||||
It should be noted that none of this should become relevant at any point since the resulting
|
It should be noted that none of this should become relevant at any point
|
||||||
intermediate root tmpfs should be effectively anonymous. */
|
since the resulting intermediate root tmpfs should be effectively anonymous. */
|
||||||
intermediateHostPath = fhs.Proc + "self/fd"
|
intermediateHostPath = fhs.Proc + "self/fd"
|
||||||
|
|
||||||
// setupEnv is the name of the environment variable holding the string representation of
|
// setupEnv is the name of the environment variable holding the string
|
||||||
// the read end file descriptor of the setup params pipe.
|
// representation of the read end file descriptor of the setup params pipe.
|
||||||
setupEnv = "HAKUREI_SETUP"
|
setupEnv = "HAKUREI_SETUP"
|
||||||
|
|
||||||
// exitUnexpectedWait4 is the exit code if wait4 returns an unexpected errno.
|
// exitUnexpectedWait4 is the exit code if wait4 returns an unexpected errno.
|
||||||
@@ -59,7 +59,8 @@ type (
|
|||||||
// late is called right before starting the initial process.
|
// late is called right before starting the initial process.
|
||||||
late(state *setupState, k syscallDispatcher) error
|
late(state *setupState, k syscallDispatcher) error
|
||||||
|
|
||||||
// prefix returns a log message prefix, and whether this Op prints no identifying message on its own.
|
// prefix returns a log message prefix, and whether this Op prints no
|
||||||
|
// identifying message on its own.
|
||||||
prefix() (string, bool)
|
prefix() (string, bool)
|
||||||
|
|
||||||
Is(op Op) bool
|
Is(op Op) bool
|
||||||
@@ -71,9 +72,11 @@ type (
|
|||||||
setupState struct {
|
setupState struct {
|
||||||
nonrepeatable uintptr
|
nonrepeatable uintptr
|
||||||
|
|
||||||
// Whether early reaping has concluded. Must only be accessed in the wait4 loop.
|
// Whether early reaping has concluded. Must only be accessed in the
|
||||||
|
// wait4 loop.
|
||||||
processConcluded bool
|
processConcluded bool
|
||||||
// Process to syscall.WaitStatus populated in the wait4 loop. Freed after early reaping concludes.
|
// Process to syscall.WaitStatus populated in the wait4 loop. Freed
|
||||||
|
// after early reaping concludes.
|
||||||
process map[int]WaitStatus
|
process map[int]WaitStatus
|
||||||
// Synchronises access to process.
|
// Synchronises access to process.
|
||||||
processMu sync.RWMutex
|
processMu sync.RWMutex
|
||||||
@@ -216,9 +219,10 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
/* early is called right before pivot_root into intermediate root;
|
/* early is called right before pivot_root into intermediate root;
|
||||||
this step is mostly for gathering information that would otherwise be difficult to obtain
|
this step is mostly for gathering information that would otherwise be
|
||||||
via library functions after pivot_root, and implementations are expected to avoid changing
|
difficult to obtain via library functions after pivot_root, and
|
||||||
the state of the mount namespace */
|
implementations are expected to avoid changing the state of the mount
|
||||||
|
namespace */
|
||||||
for i, op := range *params.Ops {
|
for i, op := range *params.Ops {
|
||||||
if op == nil || !op.Valid() {
|
if op == nil || !op.Valid() {
|
||||||
k.fatalf(msg, "invalid op at index %d", i)
|
k.fatalf(msg, "invalid op at index %d", i)
|
||||||
@@ -258,10 +262,10 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
k.fatalf(msg, "cannot enter intermediate root: %v", err)
|
k.fatalf(msg, "cannot enter intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* apply is called right after pivot_root and entering the new root;
|
/* apply is called right after pivot_root and entering the new root. This
|
||||||
this step sets up the container filesystem, and implementations are expected to keep the host root
|
step sets up the container filesystem, and implementations are expected to
|
||||||
and sysroot mount points intact but otherwise can do whatever they need to;
|
keep the host root and sysroot mount points intact but otherwise can do
|
||||||
chdir is allowed but discouraged */
|
whatever they need to. Calling chdir is allowed but discouraged. */
|
||||||
for i, op := range *params.Ops {
|
for i, op := range *params.Ops {
|
||||||
// ops already checked during early setup
|
// ops already checked during early setup
|
||||||
if prefix, ok := op.prefix(); ok {
|
if prefix, ok := op.prefix(); ok {
|
||||||
|
|||||||
@@ -12,14 +12,16 @@ import (
|
|||||||
|
|
||||||
func init() { gob.Register(new(BindMountOp)) }
|
func init() { gob.Register(new(BindMountOp)) }
|
||||||
|
|
||||||
// Bind appends an [Op] that bind mounts host path [BindMountOp.Source] on container path [BindMountOp.Target].
|
// Bind is a helper for appending [BindMountOp] to [Ops].
|
||||||
func (f *Ops) Bind(source, target *check.Absolute, flags int) *Ops {
|
func (f *Ops) Bind(source, target *check.Absolute, flags int) *Ops {
|
||||||
*f = append(*f, &BindMountOp{nil, source, target, flags})
|
*f = append(*f, &BindMountOp{nil, source, target, flags})
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindMountOp bind mounts host path Source on container path Target.
|
// BindMountOp creates a bind mount from host path Source to container path Target.
|
||||||
// Note that Flags uses bits declared in this package and should not be set with constants in [syscall].
|
//
|
||||||
|
// Note that Flags uses bits declared in the [std] package and should not be set
|
||||||
|
// with constants in [syscall].
|
||||||
type BindMountOp struct {
|
type BindMountOp struct {
|
||||||
sourceFinal, Source, Target *check.Absolute
|
sourceFinal, Source, Target *check.Absolute
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ const (
|
|||||||
daemonTimeout = 5 * time.Second
|
daemonTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// Daemon appends an [Op] that starts a daemon in the container and blocks until
|
// Daemon is a helper for appending [DaemonOp] to [Ops].
|
||||||
// [DaemonOp.Target] appears.
|
|
||||||
func (f *Ops) Daemon(target, path *check.Absolute, args ...string) *Ops {
|
func (f *Ops) Daemon(target, path *check.Absolute, args ...string) *Ops {
|
||||||
*f = append(*f, &DaemonOp{target, path, args})
|
*f = append(*f, &DaemonOp{target, path, args})
|
||||||
return f
|
return f
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ func (f *Ops) Dev(target *check.Absolute, mqueue bool) *Ops {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DevWritable appends an [Op] that mounts a writable subset of host /dev.
|
// DevWritable appends an [Op] that mounts a writable subset of host /dev.
|
||||||
// There is usually no good reason to write to /dev, so this should always be followed by a [RemountOp].
|
//
|
||||||
|
// There is usually no good reason to write to /dev, so this should always be
|
||||||
|
// followed by a [RemountOp].
|
||||||
func (f *Ops) DevWritable(target *check.Absolute, mqueue bool) *Ops {
|
func (f *Ops) DevWritable(target *check.Absolute, mqueue bool) *Ops {
|
||||||
*f = append(*f, &MountDevOp{target, mqueue, true})
|
*f = append(*f, &MountDevOp{target, mqueue, true})
|
||||||
return f
|
return f
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func init() { gob.Register(new(MkdirOp)) }
|
func init() { gob.Register(new(MkdirOp)) }
|
||||||
|
|
||||||
// Mkdir appends an [Op] that creates a directory in the container filesystem.
|
// Mkdir is a helper for appending [MkdirOp] to [Ops].
|
||||||
func (f *Ops) Mkdir(name *check.Absolute, perm os.FileMode) *Ops {
|
func (f *Ops) Mkdir(name *check.Absolute, perm os.FileMode) *Ops {
|
||||||
*f = append(*f, &MkdirOp{name, perm})
|
*f = append(*f, &MkdirOp{name, perm})
|
||||||
return f
|
return f
|
||||||
|
|||||||
@@ -54,8 +54,11 @@ func (e *OverlayArgumentError) Error() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overlay appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target].
|
// Overlay is a helper for appending [MountOverlayOp] to [Ops].
|
||||||
func (f *Ops) Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) *Ops {
|
func (f *Ops) Overlay(
|
||||||
|
target, state, work *check.Absolute,
|
||||||
|
layers ...*check.Absolute,
|
||||||
|
) *Ops {
|
||||||
*f = append(*f, &MountOverlayOp{
|
*f = append(*f, &MountOverlayOp{
|
||||||
Target: target,
|
Target: target,
|
||||||
Lower: layers,
|
Lower: layers,
|
||||||
@@ -65,13 +68,12 @@ func (f *Ops) Overlay(target, state, work *check.Absolute, layers ...*check.Abso
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// OverlayEphemeral appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target]
|
// OverlayEphemeral appends a [MountOverlayOp] with an ephemeral upperdir and workdir.
|
||||||
// with an ephemeral upperdir and workdir.
|
|
||||||
func (f *Ops) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) *Ops {
|
func (f *Ops) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) *Ops {
|
||||||
return f.Overlay(target, fhs.AbsRoot, nil, layers...)
|
return f.Overlay(target, fhs.AbsRoot, nil, layers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OverlayReadonly appends an [Op] that mounts the overlay pseudo filesystem readonly on [MountOverlayOp.Target]
|
// OverlayReadonly appends a readonly [MountOverlayOp].
|
||||||
func (f *Ops) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) *Ops {
|
func (f *Ops) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) *Ops {
|
||||||
return f.Overlay(target, nil, nil, layers...)
|
return f.Overlay(target, nil, nil, layers...)
|
||||||
}
|
}
|
||||||
@@ -82,25 +84,34 @@ type MountOverlayOp struct {
|
|||||||
|
|
||||||
// Any filesystem, does not need to be on a writable filesystem.
|
// Any filesystem, does not need to be on a writable filesystem.
|
||||||
Lower []*check.Absolute
|
Lower []*check.Absolute
|
||||||
// formatted for [OptionOverlayLowerdir], resolved, prefixed and escaped during early
|
// Formatted for [OptionOverlayLowerdir].
|
||||||
|
//
|
||||||
|
// Resolved, prefixed and escaped during early.
|
||||||
lower []string
|
lower []string
|
||||||
|
|
||||||
// The upperdir is normally on a writable filesystem.
|
// The upperdir is normally on a writable filesystem.
|
||||||
//
|
//
|
||||||
// If Work is nil and Upper holds the special value [fhs.AbsRoot],
|
// If Work is nil and Upper holds the special value [fhs.AbsRoot], an
|
||||||
// an ephemeral upperdir and workdir will be set up.
|
// ephemeral upperdir and workdir will be set up.
|
||||||
//
|
//
|
||||||
// If both Work and Upper are nil, upperdir and workdir is omitted and the overlay is mounted readonly.
|
// If both Work and Upper are nil, upperdir and workdir is omitted and the
|
||||||
|
// overlay is mounted readonly.
|
||||||
Upper *check.Absolute
|
Upper *check.Absolute
|
||||||
// formatted for [OptionOverlayUpperdir], resolved, prefixed and escaped during early
|
// Formatted for [OptionOverlayUpperdir].
|
||||||
|
//
|
||||||
|
// Resolved, prefixed and escaped during early.
|
||||||
upper string
|
upper string
|
||||||
|
|
||||||
// The workdir needs to be an empty directory on the same filesystem as upperdir.
|
// The workdir needs to be an empty directory on the same filesystem as upperdir.
|
||||||
Work *check.Absolute
|
Work *check.Absolute
|
||||||
// formatted for [OptionOverlayWorkdir], resolved, prefixed and escaped during early
|
// Formatted for [OptionOverlayWorkdir].
|
||||||
|
//
|
||||||
|
// Resolved, prefixed and escaped during early.
|
||||||
work string
|
work string
|
||||||
|
|
||||||
ephemeral bool
|
ephemeral bool
|
||||||
|
|
||||||
// used internally for mounting to the intermediate root
|
// Used internally for mounting to the intermediate root.
|
||||||
noPrefix bool
|
noPrefix bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const (
|
|||||||
|
|
||||||
func init() { gob.Register(new(TmpfileOp)) }
|
func init() { gob.Register(new(TmpfileOp)) }
|
||||||
|
|
||||||
// Place appends an [Op] that places a file in container path [TmpfileOp.Path] containing [TmpfileOp.Data].
|
// Place is a helper for appending [TmpfileOp] to [Ops].
|
||||||
func (f *Ops) Place(name *check.Absolute, data []byte) *Ops {
|
func (f *Ops) Place(name *check.Absolute, data []byte) *Ops {
|
||||||
*f = append(*f, &TmpfileOp{name, data})
|
*f = append(*f, &TmpfileOp{name, data})
|
||||||
return f
|
return f
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func TestTmpfileOp(t *testing.T) {
|
|||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), stub.UniqueError(5)),
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, (*checkedOsFile)(nil), stub.UniqueError(5)),
|
||||||
}, stub.UniqueError(5)},
|
}, stub.UniqueError(5)},
|
||||||
|
|
||||||
{"Write", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"Write", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
@@ -35,14 +35,14 @@ func TestTmpfileOp(t *testing.T) {
|
|||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, stub.UniqueError(3)), nil),
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.Close", sampleDataString, stub.UniqueError(3)), nil),
|
||||||
}, stub.UniqueError(3)},
|
}, stub.UniqueError(3)},
|
||||||
|
|
||||||
{"ensureFile", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"ensureFile", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil),
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.ensureFile", sampleDataString, nil), nil),
|
||||||
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, stub.UniqueError(2)),
|
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, stub.UniqueError(2)),
|
||||||
}, stub.UniqueError(2)},
|
}, stub.UniqueError(2)},
|
||||||
|
|
||||||
@@ -50,29 +50,29 @@ func TestTmpfileOp(t *testing.T) {
|
|||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil),
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.bindMount", sampleDataString, nil), nil),
|
||||||
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
||||||
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, stub.UniqueError(1)),
|
call("bindMount", stub.ExpectArgs{"tmp.bindMount", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, stub.UniqueError(1)),
|
||||||
}, stub.UniqueError(1)},
|
}, stub.UniqueError(1)},
|
||||||
|
|
||||||
{"remove", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"remove", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil),
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.remove", sampleDataString, nil), nil),
|
||||||
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
||||||
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
|
call("bindMount", stub.ExpectArgs{"tmp.remove", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
|
||||||
call("remove", stub.ExpectArgs{"tmp.32768"}, nil, stub.UniqueError(0)),
|
call("remove", stub.ExpectArgs{"tmp.remove"}, nil, stub.UniqueError(0)),
|
||||||
}, stub.UniqueError(0)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0700}, &TmpfileOp{
|
{"success", &Params{ParentPerm: 0700}, &TmpfileOp{
|
||||||
Path: samplePath,
|
Path: samplePath,
|
||||||
Data: sampleData,
|
Data: sampleData,
|
||||||
}, nil, nil, []stub.Call{
|
}, nil, nil, []stub.Call{
|
||||||
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil),
|
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.success", sampleDataString, nil), nil),
|
||||||
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
|
||||||
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
|
call("bindMount", stub.ExpectArgs{"tmp.success", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
|
||||||
call("remove", stub.ExpectArgs{"tmp.32768"}, nil, nil),
|
call("remove", stub.ExpectArgs{"tmp.success"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func init() { gob.Register(new(MountProcOp)) }
|
func init() { gob.Register(new(MountProcOp)) }
|
||||||
|
|
||||||
// Proc appends an [Op] that mounts a private instance of proc.
|
// Proc is a helper for appending [MountProcOp] to [Ops].
|
||||||
func (f *Ops) Proc(target *check.Absolute) *Ops {
|
func (f *Ops) Proc(target *check.Absolute) *Ops {
|
||||||
*f = append(*f, &MountProcOp{target})
|
*f = append(*f, &MountProcOp{target})
|
||||||
return f
|
return f
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func init() { gob.Register(new(RemountOp)) }
|
func init() { gob.Register(new(RemountOp)) }
|
||||||
|
|
||||||
// Remount appends an [Op] that applies [RemountOp.Flags] on container path [RemountOp.Target].
|
// Remount is a helper for appending [RemountOp] to [Ops].
|
||||||
func (f *Ops) Remount(target *check.Absolute, flags uintptr) *Ops {
|
func (f *Ops) Remount(target *check.Absolute, flags uintptr) *Ops {
|
||||||
*f = append(*f, &RemountOp{target, flags})
|
*f = append(*f, &RemountOp{target, flags})
|
||||||
return f
|
return f
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const (
|
|||||||
_LANDLOCK_ACCESS_FS_DELIM
|
_LANDLOCK_ACCESS_FS_DELIM
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String returns a space-separated string of [LandlockAccessFS] flags.
|
||||||
func (f LandlockAccessFS) String() string {
|
func (f LandlockAccessFS) String() string {
|
||||||
switch f {
|
switch f {
|
||||||
case LANDLOCK_ACCESS_FS_EXECUTE:
|
case LANDLOCK_ACCESS_FS_EXECUTE:
|
||||||
@@ -116,6 +117,7 @@ const (
|
|||||||
_LANDLOCK_ACCESS_NET_DELIM
|
_LANDLOCK_ACCESS_NET_DELIM
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String returns a space-separated string of [LandlockAccessNet] flags.
|
||||||
func (f LandlockAccessNet) String() string {
|
func (f LandlockAccessNet) String() string {
|
||||||
switch f {
|
switch f {
|
||||||
case LANDLOCK_ACCESS_NET_BIND_TCP:
|
case LANDLOCK_ACCESS_NET_BIND_TCP:
|
||||||
@@ -152,6 +154,7 @@ const (
|
|||||||
_LANDLOCK_SCOPE_DELIM
|
_LANDLOCK_SCOPE_DELIM
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String returns a space-separated string of [LandlockScope] flags.
|
||||||
func (f LandlockScope) String() string {
|
func (f LandlockScope) String() string {
|
||||||
switch f {
|
switch f {
|
||||||
case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET:
|
case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET:
|
||||||
@@ -184,10 +187,12 @@ type RulesetAttr struct {
|
|||||||
HandledAccessFS LandlockAccessFS
|
HandledAccessFS LandlockAccessFS
|
||||||
// Bitmask of handled network actions.
|
// Bitmask of handled network actions.
|
||||||
HandledAccessNet LandlockAccessNet
|
HandledAccessNet LandlockAccessNet
|
||||||
// Bitmask of scopes restricting a Landlock domain from accessing outside resources (e.g. IPCs).
|
// Bitmask of scopes restricting a Landlock domain from accessing outside
|
||||||
|
// resources (e.g. IPCs).
|
||||||
Scoped LandlockScope
|
Scoped LandlockScope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a user-facing description of [RulesetAttr].
|
||||||
func (rulesetAttr *RulesetAttr) String() string {
|
func (rulesetAttr *RulesetAttr) String() string {
|
||||||
if rulesetAttr == nil {
|
if rulesetAttr == nil {
|
||||||
return "NULL"
|
return "NULL"
|
||||||
@@ -208,6 +213,7 @@ func (rulesetAttr *RulesetAttr) String() string {
|
|||||||
return strings.Join(elems, ", ")
|
return strings.Join(elems, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create loads the ruleset into the kernel.
|
||||||
func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
|
func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
|
||||||
var pointer, size uintptr
|
var pointer, size uintptr
|
||||||
// NULL needed for abi version
|
// NULL needed for abi version
|
||||||
@@ -216,10 +222,13 @@ func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
|
|||||||
size = unsafe.Sizeof(*rulesetAttr)
|
size = unsafe.Sizeof(*rulesetAttr)
|
||||||
}
|
}
|
||||||
|
|
||||||
rulesetFd, _, errno := syscall.Syscall(std.SYS_LANDLOCK_CREATE_RULESET, pointer, size, flags)
|
rulesetFd, _, errno := syscall.Syscall(
|
||||||
|
std.SYS_LANDLOCK_CREATE_RULESET,
|
||||||
|
pointer, size,
|
||||||
|
flags,
|
||||||
|
)
|
||||||
fd = int(rulesetFd)
|
fd = int(rulesetFd)
|
||||||
err = errno
|
err = errno
|
||||||
|
|
||||||
if fd < 0 {
|
if fd < 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -230,12 +239,19 @@ func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
|
|||||||
return fd, nil
|
return fd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LandlockGetABI returns the ABI version supported by the kernel.
|
||||||
func LandlockGetABI() (int, error) {
|
func LandlockGetABI() (int, error) {
|
||||||
return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION)
|
return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LandlockRestrictSelf applies a loaded ruleset to the calling thread.
|
||||||
func LandlockRestrictSelf(rulesetFd int, flags uintptr) error {
|
func LandlockRestrictSelf(rulesetFd int, flags uintptr) error {
|
||||||
r, _, errno := syscall.Syscall(std.SYS_LANDLOCK_RESTRICT_SELF, uintptr(rulesetFd), flags, 0)
|
r, _, errno := syscall.Syscall(
|
||||||
|
std.SYS_LANDLOCK_RESTRICT_SELF,
|
||||||
|
uintptr(rulesetFd),
|
||||||
|
flags,
|
||||||
|
0,
|
||||||
|
)
|
||||||
if r != 0 {
|
if r != 0 {
|
||||||
return errno
|
return errno
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ done:
|
|||||||
}
|
}
|
||||||
if m.Header.Type == NLMSG_ERROR {
|
if m.Header.Type == NLMSG_ERROR {
|
||||||
if len(m.Data) >= 4 {
|
if len(m.Data) >= 4 {
|
||||||
errno := Errno(-std.ScmpInt(binary.NativeEndian.Uint32(m.Data)))
|
errno := Errno(-std.Int(binary.NativeEndian.Uint32(m.Data)))
|
||||||
if errno == 0 {
|
if errno == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Nonexistent is a path that cannot exist.
|
// Nonexistent is a path that cannot exist.
|
||||||
// /proc is chosen because a system with covered /proc is unsupported by this package.
|
//
|
||||||
|
// This path can never be presented by the kernel if proc is mounted on
|
||||||
|
// /proc/. This can only exist if parts of /proc/ is covered, or proc is not
|
||||||
|
// mounted at all. Neither configuration is supported by this package.
|
||||||
Nonexistent = fhs.Proc + "nonexistent"
|
Nonexistent = fhs.Proc + "nonexistent"
|
||||||
|
|
||||||
hostPath = fhs.Root + hostDir
|
hostPath = fhs.Root + hostDir
|
||||||
|
|||||||
@@ -88,18 +88,22 @@ var resPrefix = [...]string{
|
|||||||
7: "seccomp_load failed",
|
7: "seccomp_load failed",
|
||||||
}
|
}
|
||||||
|
|
||||||
// cbAllocateBuffer is the function signature for the function handle passed to hakurei_export_filter
|
// cbAllocateBuffer is the function signature for the function handle passed to
|
||||||
// which allocates the buffer that the resulting bpf program is copied into, and writes its slice header
|
// hakurei_scmp_make_filter which allocates the buffer that the resulting bpf
|
||||||
// to a value held by the caller.
|
// program is copied into, and writes its slice header to a value held by the caller.
|
||||||
type cbAllocateBuffer = func(len C.size_t) (buf unsafe.Pointer)
|
type cbAllocateBuffer = func(len C.size_t) (buf unsafe.Pointer)
|
||||||
|
|
||||||
|
// hakurei_scmp_allocate allocates a buffer of specified size known to the
|
||||||
|
// runtime through a callback passed in a [cgo.Handle].
|
||||||
|
//
|
||||||
//export hakurei_scmp_allocate
|
//export hakurei_scmp_allocate
|
||||||
func hakurei_scmp_allocate(f C.uintptr_t, len C.size_t) (buf unsafe.Pointer) {
|
func hakurei_scmp_allocate(f C.uintptr_t, len C.size_t) (buf unsafe.Pointer) {
|
||||||
return cgo.Handle(f).Value().(cbAllocateBuffer)(len)
|
return cgo.Handle(f).Value().(cbAllocateBuffer)(len)
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeFilter generates a bpf program from a slice of [std.NativeRule] and writes the resulting byte slice to p.
|
// makeFilter generates a bpf program from a slice of [std.NativeRule] and
|
||||||
// The filter is installed to the current process if p is nil.
|
// writes the resulting byte slice to p. The filter is installed to the current
|
||||||
|
// process if p is nil.
|
||||||
func makeFilter(rules []std.NativeRule, flags ExportFlag, p *[]byte) error {
|
func makeFilter(rules []std.NativeRule, flags ExportFlag, p *[]byte) error {
|
||||||
if len(rules) == 0 {
|
if len(rules) == 0 {
|
||||||
return ErrInvalidRules
|
return ErrInvalidRules
|
||||||
@@ -170,8 +174,8 @@ func Export(rules []std.NativeRule, flags ExportFlag) (data []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load generates a bpf program from a slice of [std.NativeRule] and enforces it on the current process.
|
// Load generates a bpf program from a slice of [std.NativeRule] and enforces it
|
||||||
// Errors returned by libseccomp is wrapped in [LibraryError].
|
// on the current process. Errors returned by libseccomp is wrapped in [LibraryError].
|
||||||
func Load(rules []std.NativeRule, flags ExportFlag) error { return makeFilter(rules, flags, nil) }
|
func Load(rules []std.NativeRule, flags ExportFlag) error { return makeFilter(rules, flags, nil) }
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|||||||
27
container/seccomp/presets_riscv64_test.go
Normal file
27
container/seccomp/presets_riscv64_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package seccomp_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "hakurei.app/container/seccomp"
|
||||||
|
. "hakurei.app/container/std"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bpfExpected = bpfLookup{
|
||||||
|
{AllowMultiarch | AllowCAN |
|
||||||
|
AllowBluetooth, PresetExt |
|
||||||
|
PresetDenyNS | PresetDenyTTY | PresetDenyDevel |
|
||||||
|
PresetLinux32}: toHash(
|
||||||
|
"a1c4ffa35f4bfbf38061184760b9a09edfcb4964c3b534395e47327b83f3fb61f2f9573ddfcc4772424cc2f5dd12fd32471e6531dbe10e85eda3797dd4fa179f"),
|
||||||
|
|
||||||
|
{0, 0}: toHash(
|
||||||
|
"f3910fd727d087def593e3876c2c6ab9ace71d82ec8cbc992a26223e7bba85e1d7a0b56c5fc6303703f24595825dad8561637edaedd5384b34a6cd080946633c"),
|
||||||
|
{0, PresetExt}: toHash(
|
||||||
|
"741438c5e3f11c36c92ae8c5934f13440675c6e719541c2dbffeda79a10081bcfd9ad8314a60c1d1f53db86c8080c13fffa3bbcf7fe753935679b4b902737286"),
|
||||||
|
{0, PresetStrict}: toHash(
|
||||||
|
"79e9e464d02405c6d74fd2c771bd72a1311e488221c73a9c32db9270219837c54fccec2f36fe2474895547e60c311514567e2e6cf4e7a7fcf909c1ecd1e254a7"),
|
||||||
|
{0, PresetDenyNS | PresetDenyTTY | PresetDenyDevel}: toHash(
|
||||||
|
"3c443715a6c1e557a284862ea8efb70a5d4ecbe67d1226627323e861cd3646fb3e7768ec5b94b93760b7f652cf6916f66e317a4fbf8716d10c3673aa4fc3ae58"),
|
||||||
|
{0, PresetExt | PresetDenyDevel}: toHash(
|
||||||
|
"4448a74e8cc75a4ab63799c4f2cc2a5af63e5f4e8e9b8ac15a1873d647dfa67a4c67b39ed466d8dd32abc64136d401879fc6185c9ab00feeaf59ccf4305f8201"),
|
||||||
|
{0, PresetExt | PresetDenyNS | PresetDenyDevel}: toHash(
|
||||||
|
"c7c86e793cb7192f5f6c735f372cda27eb43ae1045e587f8eadb64c849520a3280b6570a3d7b601d32cddb38021585a2234db38e506cebfd10aa3d6c75440f17"),
|
||||||
|
}
|
||||||
@@ -24,8 +24,8 @@ func TestSyscallResolveName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRuleType(t *testing.T) {
|
func TestRuleType(t *testing.T) {
|
||||||
assertKind[std.ScmpUint, scmpUint](t)
|
assertKind[std.Uint, scmpUint](t)
|
||||||
assertKind[std.ScmpInt, scmpInt](t)
|
assertKind[std.Int, scmpInt](t)
|
||||||
|
|
||||||
assertSize[std.NativeRule, syscallRule](t)
|
assertSize[std.NativeRule, syscallRule](t)
|
||||||
assertKind[std.ScmpDatum, scmpDatum](t)
|
assertKind[std.ScmpDatum, scmpDatum](t)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ my %syscall_cutoff_arch = (
|
|||||||
"x86" => 340,
|
"x86" => 340,
|
||||||
"x86_64" => 302,
|
"x86_64" => 302,
|
||||||
"aarch64" => 281,
|
"aarch64" => 281,
|
||||||
|
"riscv64" => 281,
|
||||||
);
|
);
|
||||||
|
|
||||||
print <<EOF;
|
print <<EOF;
|
||||||
|
|||||||
@@ -7,24 +7,28 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
// ScmpUint is equivalent to C.uint.
|
// ScmpUint is equivalent to C.uint.
|
||||||
ScmpUint uint32
|
//
|
||||||
|
// Deprecated: This type has been renamed to Uint and will be removed in 0.4.
|
||||||
|
ScmpUint = Uint
|
||||||
// ScmpInt is equivalent to C.int.
|
// ScmpInt is equivalent to C.int.
|
||||||
ScmpInt int32
|
//
|
||||||
|
// Deprecated: This type has been renamed to Int and will be removed in 0.4.
|
||||||
|
ScmpInt = Int
|
||||||
|
|
||||||
// ScmpSyscall represents a syscall number passed to libseccomp via [NativeRule.Syscall].
|
// ScmpSyscall represents a syscall number passed to libseccomp via [NativeRule.Syscall].
|
||||||
ScmpSyscall ScmpInt
|
ScmpSyscall Int
|
||||||
// ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno].
|
// ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno].
|
||||||
ScmpErrno ScmpInt
|
ScmpErrno Int
|
||||||
|
|
||||||
// ScmpCompare is equivalent to enum scmp_compare;
|
// ScmpCompare is equivalent to enum scmp_compare;
|
||||||
ScmpCompare ScmpUint
|
ScmpCompare Uint
|
||||||
// ScmpDatum is equivalent to scmp_datum_t.
|
// ScmpDatum is equivalent to scmp_datum_t.
|
||||||
ScmpDatum uint64
|
ScmpDatum uint64
|
||||||
|
|
||||||
// ScmpArgCmp is equivalent to struct scmp_arg_cmp.
|
// ScmpArgCmp is equivalent to struct scmp_arg_cmp.
|
||||||
ScmpArgCmp struct {
|
ScmpArgCmp struct {
|
||||||
// argument number, starting at 0
|
// argument number, starting at 0
|
||||||
Arg ScmpUint `json:"arg"`
|
Arg Uint `json:"arg"`
|
||||||
// the comparison op, e.g. SCMP_CMP_*
|
// the comparison op, e.g. SCMP_CMP_*
|
||||||
Op ScmpCompare `json:"op"`
|
Op ScmpCompare `json:"op"`
|
||||||
|
|
||||||
|
|||||||
55
container/std/syscall_extra_linux_riscv64.go
Normal file
55
container/std/syscall_extra_linux_riscv64.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package std
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const (
|
||||||
|
SYS_NEWFSTATAT = syscall.SYS_FSTATAT
|
||||||
|
)
|
||||||
|
|
||||||
|
var syscallNumExtra = map[string]ScmpSyscall{
|
||||||
|
"uselib": SNR_USELIB,
|
||||||
|
"clock_adjtime64": SNR_CLOCK_ADJTIME64,
|
||||||
|
"clock_settime64": SNR_CLOCK_SETTIME64,
|
||||||
|
"umount": SNR_UMOUNT,
|
||||||
|
"chown": SNR_CHOWN,
|
||||||
|
"chown32": SNR_CHOWN32,
|
||||||
|
"fchown32": SNR_FCHOWN32,
|
||||||
|
"lchown": SNR_LCHOWN,
|
||||||
|
"lchown32": SNR_LCHOWN32,
|
||||||
|
"setgid32": SNR_SETGID32,
|
||||||
|
"setgroups32": SNR_SETGROUPS32,
|
||||||
|
"setregid32": SNR_SETREGID32,
|
||||||
|
"setresgid32": SNR_SETRESGID32,
|
||||||
|
"setresuid32": SNR_SETRESUID32,
|
||||||
|
"setreuid32": SNR_SETREUID32,
|
||||||
|
"setuid32": SNR_SETUID32,
|
||||||
|
"modify_ldt": SNR_MODIFY_LDT,
|
||||||
|
"subpage_prot": SNR_SUBPAGE_PROT,
|
||||||
|
"switch_endian": SNR_SWITCH_ENDIAN,
|
||||||
|
"vm86": SNR_VM86,
|
||||||
|
"vm86old": SNR_VM86OLD,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SNR_USELIB ScmpSyscall = __PNR_uselib
|
||||||
|
SNR_CLOCK_ADJTIME64 ScmpSyscall = __PNR_clock_adjtime64
|
||||||
|
SNR_CLOCK_SETTIME64 ScmpSyscall = __PNR_clock_settime64
|
||||||
|
SNR_UMOUNT ScmpSyscall = __PNR_umount
|
||||||
|
SNR_CHOWN ScmpSyscall = __PNR_chown
|
||||||
|
SNR_CHOWN32 ScmpSyscall = __PNR_chown32
|
||||||
|
SNR_FCHOWN32 ScmpSyscall = __PNR_fchown32
|
||||||
|
SNR_LCHOWN ScmpSyscall = __PNR_lchown
|
||||||
|
SNR_LCHOWN32 ScmpSyscall = __PNR_lchown32
|
||||||
|
SNR_SETGID32 ScmpSyscall = __PNR_setgid32
|
||||||
|
SNR_SETGROUPS32 ScmpSyscall = __PNR_setgroups32
|
||||||
|
SNR_SETREGID32 ScmpSyscall = __PNR_setregid32
|
||||||
|
SNR_SETRESGID32 ScmpSyscall = __PNR_setresgid32
|
||||||
|
SNR_SETRESUID32 ScmpSyscall = __PNR_setresuid32
|
||||||
|
SNR_SETREUID32 ScmpSyscall = __PNR_setreuid32
|
||||||
|
SNR_SETUID32 ScmpSyscall = __PNR_setuid32
|
||||||
|
SNR_MODIFY_LDT ScmpSyscall = __PNR_modify_ldt
|
||||||
|
SNR_SUBPAGE_PROT ScmpSyscall = __PNR_subpage_prot
|
||||||
|
SNR_SWITCH_ENDIAN ScmpSyscall = __PNR_switch_endian
|
||||||
|
SNR_VM86 ScmpSyscall = __PNR_vm86
|
||||||
|
SNR_VM86OLD ScmpSyscall = __PNR_vm86old
|
||||||
|
)
|
||||||
719
container/std/syscall_linux_riscv64.go
Normal file
719
container/std/syscall_linux_riscv64.go
Normal file
@@ -0,0 +1,719 @@
|
|||||||
|
// mksysnum_linux.pl /usr/include/riscv64-linux-gnu/asm/unistd.h
|
||||||
|
// Code generated by the command above; DO NOT EDIT.
|
||||||
|
|
||||||
|
package std
|
||||||
|
|
||||||
|
import . "syscall"
|
||||||
|
|
||||||
|
var syscallNum = map[string]ScmpSyscall{
|
||||||
|
"io_setup": SNR_IO_SETUP,
|
||||||
|
"io_destroy": SNR_IO_DESTROY,
|
||||||
|
"io_submit": SNR_IO_SUBMIT,
|
||||||
|
"io_cancel": SNR_IO_CANCEL,
|
||||||
|
"io_getevents": SNR_IO_GETEVENTS,
|
||||||
|
"setxattr": SNR_SETXATTR,
|
||||||
|
"lsetxattr": SNR_LSETXATTR,
|
||||||
|
"fsetxattr": SNR_FSETXATTR,
|
||||||
|
"getxattr": SNR_GETXATTR,
|
||||||
|
"lgetxattr": SNR_LGETXATTR,
|
||||||
|
"fgetxattr": SNR_FGETXATTR,
|
||||||
|
"listxattr": SNR_LISTXATTR,
|
||||||
|
"llistxattr": SNR_LLISTXATTR,
|
||||||
|
"flistxattr": SNR_FLISTXATTR,
|
||||||
|
"removexattr": SNR_REMOVEXATTR,
|
||||||
|
"lremovexattr": SNR_LREMOVEXATTR,
|
||||||
|
"fremovexattr": SNR_FREMOVEXATTR,
|
||||||
|
"getcwd": SNR_GETCWD,
|
||||||
|
"lookup_dcookie": SNR_LOOKUP_DCOOKIE,
|
||||||
|
"eventfd2": SNR_EVENTFD2,
|
||||||
|
"epoll_create1": SNR_EPOLL_CREATE1,
|
||||||
|
"epoll_ctl": SNR_EPOLL_CTL,
|
||||||
|
"epoll_pwait": SNR_EPOLL_PWAIT,
|
||||||
|
"dup": SNR_DUP,
|
||||||
|
"dup3": SNR_DUP3,
|
||||||
|
"fcntl": SNR_FCNTL,
|
||||||
|
"inotify_init1": SNR_INOTIFY_INIT1,
|
||||||
|
"inotify_add_watch": SNR_INOTIFY_ADD_WATCH,
|
||||||
|
"inotify_rm_watch": SNR_INOTIFY_RM_WATCH,
|
||||||
|
"ioctl": SNR_IOCTL,
|
||||||
|
"ioprio_set": SNR_IOPRIO_SET,
|
||||||
|
"ioprio_get": SNR_IOPRIO_GET,
|
||||||
|
"flock": SNR_FLOCK,
|
||||||
|
"mknodat": SNR_MKNODAT,
|
||||||
|
"mkdirat": SNR_MKDIRAT,
|
||||||
|
"unlinkat": SNR_UNLINKAT,
|
||||||
|
"symlinkat": SNR_SYMLINKAT,
|
||||||
|
"linkat": SNR_LINKAT,
|
||||||
|
"umount2": SNR_UMOUNT2,
|
||||||
|
"mount": SNR_MOUNT,
|
||||||
|
"pivot_root": SNR_PIVOT_ROOT,
|
||||||
|
"nfsservctl": SNR_NFSSERVCTL,
|
||||||
|
"statfs": SNR_STATFS,
|
||||||
|
"fstatfs": SNR_FSTATFS,
|
||||||
|
"truncate": SNR_TRUNCATE,
|
||||||
|
"ftruncate": SNR_FTRUNCATE,
|
||||||
|
"fallocate": SNR_FALLOCATE,
|
||||||
|
"faccessat": SNR_FACCESSAT,
|
||||||
|
"chdir": SNR_CHDIR,
|
||||||
|
"fchdir": SNR_FCHDIR,
|
||||||
|
"chroot": SNR_CHROOT,
|
||||||
|
"fchmod": SNR_FCHMOD,
|
||||||
|
"fchmodat": SNR_FCHMODAT,
|
||||||
|
"fchownat": SNR_FCHOWNAT,
|
||||||
|
"fchown": SNR_FCHOWN,
|
||||||
|
"openat": SNR_OPENAT,
|
||||||
|
"close": SNR_CLOSE,
|
||||||
|
"vhangup": SNR_VHANGUP,
|
||||||
|
"pipe2": SNR_PIPE2,
|
||||||
|
"quotactl": SNR_QUOTACTL,
|
||||||
|
"getdents64": SNR_GETDENTS64,
|
||||||
|
"lseek": SNR_LSEEK,
|
||||||
|
"read": SNR_READ,
|
||||||
|
"write": SNR_WRITE,
|
||||||
|
"readv": SNR_READV,
|
||||||
|
"writev": SNR_WRITEV,
|
||||||
|
"pread64": SNR_PREAD64,
|
||||||
|
"pwrite64": SNR_PWRITE64,
|
||||||
|
"preadv": SNR_PREADV,
|
||||||
|
"pwritev": SNR_PWRITEV,
|
||||||
|
"sendfile": SNR_SENDFILE,
|
||||||
|
"pselect6": SNR_PSELECT6,
|
||||||
|
"ppoll": SNR_PPOLL,
|
||||||
|
"signalfd4": SNR_SIGNALFD4,
|
||||||
|
"vmsplice": SNR_VMSPLICE,
|
||||||
|
"splice": SNR_SPLICE,
|
||||||
|
"tee": SNR_TEE,
|
||||||
|
"readlinkat": SNR_READLINKAT,
|
||||||
|
"newfstatat": SNR_NEWFSTATAT,
|
||||||
|
"fstat": SNR_FSTAT,
|
||||||
|
"sync": SNR_SYNC,
|
||||||
|
"fsync": SNR_FSYNC,
|
||||||
|
"fdatasync": SNR_FDATASYNC,
|
||||||
|
"sync_file_range": SNR_SYNC_FILE_RANGE,
|
||||||
|
"timerfd_create": SNR_TIMERFD_CREATE,
|
||||||
|
"timerfd_settime": SNR_TIMERFD_SETTIME,
|
||||||
|
"timerfd_gettime": SNR_TIMERFD_GETTIME,
|
||||||
|
"utimensat": SNR_UTIMENSAT,
|
||||||
|
"acct": SNR_ACCT,
|
||||||
|
"capget": SNR_CAPGET,
|
||||||
|
"capset": SNR_CAPSET,
|
||||||
|
"personality": SNR_PERSONALITY,
|
||||||
|
"exit": SNR_EXIT,
|
||||||
|
"exit_group": SNR_EXIT_GROUP,
|
||||||
|
"waitid": SNR_WAITID,
|
||||||
|
"set_tid_address": SNR_SET_TID_ADDRESS,
|
||||||
|
"unshare": SNR_UNSHARE,
|
||||||
|
"futex": SNR_FUTEX,
|
||||||
|
"set_robust_list": SNR_SET_ROBUST_LIST,
|
||||||
|
"get_robust_list": SNR_GET_ROBUST_LIST,
|
||||||
|
"nanosleep": SNR_NANOSLEEP,
|
||||||
|
"getitimer": SNR_GETITIMER,
|
||||||
|
"setitimer": SNR_SETITIMER,
|
||||||
|
"kexec_load": SNR_KEXEC_LOAD,
|
||||||
|
"init_module": SNR_INIT_MODULE,
|
||||||
|
"delete_module": SNR_DELETE_MODULE,
|
||||||
|
"timer_create": SNR_TIMER_CREATE,
|
||||||
|
"timer_gettime": SNR_TIMER_GETTIME,
|
||||||
|
"timer_getoverrun": SNR_TIMER_GETOVERRUN,
|
||||||
|
"timer_settime": SNR_TIMER_SETTIME,
|
||||||
|
"timer_delete": SNR_TIMER_DELETE,
|
||||||
|
"clock_settime": SNR_CLOCK_SETTIME,
|
||||||
|
"clock_gettime": SNR_CLOCK_GETTIME,
|
||||||
|
"clock_getres": SNR_CLOCK_GETRES,
|
||||||
|
"clock_nanosleep": SNR_CLOCK_NANOSLEEP,
|
||||||
|
"syslog": SNR_SYSLOG,
|
||||||
|
"ptrace": SNR_PTRACE,
|
||||||
|
"sched_setparam": SNR_SCHED_SETPARAM,
|
||||||
|
"sched_setscheduler": SNR_SCHED_SETSCHEDULER,
|
||||||
|
"sched_getscheduler": SNR_SCHED_GETSCHEDULER,
|
||||||
|
"sched_getparam": SNR_SCHED_GETPARAM,
|
||||||
|
"sched_setaffinity": SNR_SCHED_SETAFFINITY,
|
||||||
|
"sched_getaffinity": SNR_SCHED_GETAFFINITY,
|
||||||
|
"sched_yield": SNR_SCHED_YIELD,
|
||||||
|
"sched_get_priority_max": SNR_SCHED_GET_PRIORITY_MAX,
|
||||||
|
"sched_get_priority_min": SNR_SCHED_GET_PRIORITY_MIN,
|
||||||
|
"sched_rr_get_interval": SNR_SCHED_RR_GET_INTERVAL,
|
||||||
|
"restart_syscall": SNR_RESTART_SYSCALL,
|
||||||
|
"kill": SNR_KILL,
|
||||||
|
"tkill": SNR_TKILL,
|
||||||
|
"tgkill": SNR_TGKILL,
|
||||||
|
"sigaltstack": SNR_SIGALTSTACK,
|
||||||
|
"rt_sigsuspend": SNR_RT_SIGSUSPEND,
|
||||||
|
"rt_sigaction": SNR_RT_SIGACTION,
|
||||||
|
"rt_sigprocmask": SNR_RT_SIGPROCMASK,
|
||||||
|
"rt_sigpending": SNR_RT_SIGPENDING,
|
||||||
|
"rt_sigtimedwait": SNR_RT_SIGTIMEDWAIT,
|
||||||
|
"rt_sigqueueinfo": SNR_RT_SIGQUEUEINFO,
|
||||||
|
"rt_sigreturn": SNR_RT_SIGRETURN,
|
||||||
|
"setpriority": SNR_SETPRIORITY,
|
||||||
|
"getpriority": SNR_GETPRIORITY,
|
||||||
|
"reboot": SNR_REBOOT,
|
||||||
|
"setregid": SNR_SETREGID,
|
||||||
|
"setgid": SNR_SETGID,
|
||||||
|
"setreuid": SNR_SETREUID,
|
||||||
|
"setuid": SNR_SETUID,
|
||||||
|
"setresuid": SNR_SETRESUID,
|
||||||
|
"getresuid": SNR_GETRESUID,
|
||||||
|
"setresgid": SNR_SETRESGID,
|
||||||
|
"getresgid": SNR_GETRESGID,
|
||||||
|
"setfsuid": SNR_SETFSUID,
|
||||||
|
"setfsgid": SNR_SETFSGID,
|
||||||
|
"times": SNR_TIMES,
|
||||||
|
"setpgid": SNR_SETPGID,
|
||||||
|
"getpgid": SNR_GETPGID,
|
||||||
|
"getsid": SNR_GETSID,
|
||||||
|
"setsid": SNR_SETSID,
|
||||||
|
"getgroups": SNR_GETGROUPS,
|
||||||
|
"setgroups": SNR_SETGROUPS,
|
||||||
|
"uname": SNR_UNAME,
|
||||||
|
"sethostname": SNR_SETHOSTNAME,
|
||||||
|
"setdomainname": SNR_SETDOMAINNAME,
|
||||||
|
"getrlimit": SNR_GETRLIMIT,
|
||||||
|
"setrlimit": SNR_SETRLIMIT,
|
||||||
|
"getrusage": SNR_GETRUSAGE,
|
||||||
|
"umask": SNR_UMASK,
|
||||||
|
"prctl": SNR_PRCTL,
|
||||||
|
"getcpu": SNR_GETCPU,
|
||||||
|
"gettimeofday": SNR_GETTIMEOFDAY,
|
||||||
|
"settimeofday": SNR_SETTIMEOFDAY,
|
||||||
|
"adjtimex": SNR_ADJTIMEX,
|
||||||
|
"getpid": SNR_GETPID,
|
||||||
|
"getppid": SNR_GETPPID,
|
||||||
|
"getuid": SNR_GETUID,
|
||||||
|
"geteuid": SNR_GETEUID,
|
||||||
|
"getgid": SNR_GETGID,
|
||||||
|
"getegid": SNR_GETEGID,
|
||||||
|
"gettid": SNR_GETTID,
|
||||||
|
"sysinfo": SNR_SYSINFO,
|
||||||
|
"mq_open": SNR_MQ_OPEN,
|
||||||
|
"mq_unlink": SNR_MQ_UNLINK,
|
||||||
|
"mq_timedsend": SNR_MQ_TIMEDSEND,
|
||||||
|
"mq_timedreceive": SNR_MQ_TIMEDRECEIVE,
|
||||||
|
"mq_notify": SNR_MQ_NOTIFY,
|
||||||
|
"mq_getsetattr": SNR_MQ_GETSETATTR,
|
||||||
|
"msgget": SNR_MSGGET,
|
||||||
|
"msgctl": SNR_MSGCTL,
|
||||||
|
"msgrcv": SNR_MSGRCV,
|
||||||
|
"msgsnd": SNR_MSGSND,
|
||||||
|
"semget": SNR_SEMGET,
|
||||||
|
"semctl": SNR_SEMCTL,
|
||||||
|
"semtimedop": SNR_SEMTIMEDOP,
|
||||||
|
"semop": SNR_SEMOP,
|
||||||
|
"shmget": SNR_SHMGET,
|
||||||
|
"shmctl": SNR_SHMCTL,
|
||||||
|
"shmat": SNR_SHMAT,
|
||||||
|
"shmdt": SNR_SHMDT,
|
||||||
|
"socket": SNR_SOCKET,
|
||||||
|
"socketpair": SNR_SOCKETPAIR,
|
||||||
|
"bind": SNR_BIND,
|
||||||
|
"listen": SNR_LISTEN,
|
||||||
|
"accept": SNR_ACCEPT,
|
||||||
|
"connect": SNR_CONNECT,
|
||||||
|
"getsockname": SNR_GETSOCKNAME,
|
||||||
|
"getpeername": SNR_GETPEERNAME,
|
||||||
|
"sendto": SNR_SENDTO,
|
||||||
|
"recvfrom": SNR_RECVFROM,
|
||||||
|
"setsockopt": SNR_SETSOCKOPT,
|
||||||
|
"getsockopt": SNR_GETSOCKOPT,
|
||||||
|
"shutdown": SNR_SHUTDOWN,
|
||||||
|
"sendmsg": SNR_SENDMSG,
|
||||||
|
"recvmsg": SNR_RECVMSG,
|
||||||
|
"readahead": SNR_READAHEAD,
|
||||||
|
"brk": SNR_BRK,
|
||||||
|
"munmap": SNR_MUNMAP,
|
||||||
|
"mremap": SNR_MREMAP,
|
||||||
|
"add_key": SNR_ADD_KEY,
|
||||||
|
"request_key": SNR_REQUEST_KEY,
|
||||||
|
"keyctl": SNR_KEYCTL,
|
||||||
|
"clone": SNR_CLONE,
|
||||||
|
"execve": SNR_EXECVE,
|
||||||
|
"mmap": SNR_MMAP,
|
||||||
|
"fadvise64": SNR_FADVISE64,
|
||||||
|
"swapon": SNR_SWAPON,
|
||||||
|
"swapoff": SNR_SWAPOFF,
|
||||||
|
"mprotect": SNR_MPROTECT,
|
||||||
|
"msync": SNR_MSYNC,
|
||||||
|
"mlock": SNR_MLOCK,
|
||||||
|
"munlock": SNR_MUNLOCK,
|
||||||
|
"mlockall": SNR_MLOCKALL,
|
||||||
|
"munlockall": SNR_MUNLOCKALL,
|
||||||
|
"mincore": SNR_MINCORE,
|
||||||
|
"madvise": SNR_MADVISE,
|
||||||
|
"remap_file_pages": SNR_REMAP_FILE_PAGES,
|
||||||
|
"mbind": SNR_MBIND,
|
||||||
|
"get_mempolicy": SNR_GET_MEMPOLICY,
|
||||||
|
"set_mempolicy": SNR_SET_MEMPOLICY,
|
||||||
|
"migrate_pages": SNR_MIGRATE_PAGES,
|
||||||
|
"move_pages": SNR_MOVE_PAGES,
|
||||||
|
"rt_tgsigqueueinfo": SNR_RT_TGSIGQUEUEINFO,
|
||||||
|
"perf_event_open": SNR_PERF_EVENT_OPEN,
|
||||||
|
"accept4": SNR_ACCEPT4,
|
||||||
|
"recvmmsg": SNR_RECVMMSG,
|
||||||
|
"wait4": SNR_WAIT4,
|
||||||
|
"prlimit64": SNR_PRLIMIT64,
|
||||||
|
"fanotify_init": SNR_FANOTIFY_INIT,
|
||||||
|
"fanotify_mark": SNR_FANOTIFY_MARK,
|
||||||
|
"name_to_handle_at": SNR_NAME_TO_HANDLE_AT,
|
||||||
|
"open_by_handle_at": SNR_OPEN_BY_HANDLE_AT,
|
||||||
|
"clock_adjtime": SNR_CLOCK_ADJTIME,
|
||||||
|
"syncfs": SNR_SYNCFS,
|
||||||
|
"setns": SNR_SETNS,
|
||||||
|
"sendmmsg": SNR_SENDMMSG,
|
||||||
|
"process_vm_readv": SNR_PROCESS_VM_READV,
|
||||||
|
"process_vm_writev": SNR_PROCESS_VM_WRITEV,
|
||||||
|
"kcmp": SNR_KCMP,
|
||||||
|
"finit_module": SNR_FINIT_MODULE,
|
||||||
|
"sched_setattr": SNR_SCHED_SETATTR,
|
||||||
|
"sched_getattr": SNR_SCHED_GETATTR,
|
||||||
|
"renameat2": SNR_RENAMEAT2,
|
||||||
|
"seccomp": SNR_SECCOMP,
|
||||||
|
"getrandom": SNR_GETRANDOM,
|
||||||
|
"memfd_create": SNR_MEMFD_CREATE,
|
||||||
|
"bpf": SNR_BPF,
|
||||||
|
"execveat": SNR_EXECVEAT,
|
||||||
|
"userfaultfd": SNR_USERFAULTFD,
|
||||||
|
"membarrier": SNR_MEMBARRIER,
|
||||||
|
"mlock2": SNR_MLOCK2,
|
||||||
|
"copy_file_range": SNR_COPY_FILE_RANGE,
|
||||||
|
"preadv2": SNR_PREADV2,
|
||||||
|
"pwritev2": SNR_PWRITEV2,
|
||||||
|
"pkey_mprotect": SNR_PKEY_MPROTECT,
|
||||||
|
"pkey_alloc": SNR_PKEY_ALLOC,
|
||||||
|
"pkey_free": SNR_PKEY_FREE,
|
||||||
|
"statx": SNR_STATX,
|
||||||
|
"io_pgetevents": SNR_IO_PGETEVENTS,
|
||||||
|
"rseq": SNR_RSEQ,
|
||||||
|
"kexec_file_load": SNR_KEXEC_FILE_LOAD,
|
||||||
|
"pidfd_send_signal": SNR_PIDFD_SEND_SIGNAL,
|
||||||
|
"io_uring_setup": SNR_IO_URING_SETUP,
|
||||||
|
"io_uring_enter": SNR_IO_URING_ENTER,
|
||||||
|
"io_uring_register": SNR_IO_URING_REGISTER,
|
||||||
|
"open_tree": SNR_OPEN_TREE,
|
||||||
|
"move_mount": SNR_MOVE_MOUNT,
|
||||||
|
"fsopen": SNR_FSOPEN,
|
||||||
|
"fsconfig": SNR_FSCONFIG,
|
||||||
|
"fsmount": SNR_FSMOUNT,
|
||||||
|
"fspick": SNR_FSPICK,
|
||||||
|
"pidfd_open": SNR_PIDFD_OPEN,
|
||||||
|
"clone3": SNR_CLONE3,
|
||||||
|
"close_range": SNR_CLOSE_RANGE,
|
||||||
|
"openat2": SNR_OPENAT2,
|
||||||
|
"pidfd_getfd": SNR_PIDFD_GETFD,
|
||||||
|
"faccessat2": SNR_FACCESSAT2,
|
||||||
|
"process_madvise": SNR_PROCESS_MADVISE,
|
||||||
|
"epoll_pwait2": SNR_EPOLL_PWAIT2,
|
||||||
|
"mount_setattr": SNR_MOUNT_SETATTR,
|
||||||
|
"quotactl_fd": SNR_QUOTACTL_FD,
|
||||||
|
"landlock_create_ruleset": SNR_LANDLOCK_CREATE_RULESET,
|
||||||
|
"landlock_add_rule": SNR_LANDLOCK_ADD_RULE,
|
||||||
|
"landlock_restrict_self": SNR_LANDLOCK_RESTRICT_SELF,
|
||||||
|
"memfd_secret": SNR_MEMFD_SECRET,
|
||||||
|
"process_mrelease": SNR_PROCESS_MRELEASE,
|
||||||
|
"futex_waitv": SNR_FUTEX_WAITV,
|
||||||
|
"set_mempolicy_home_node": SNR_SET_MEMPOLICY_HOME_NODE,
|
||||||
|
"cachestat": SNR_CACHESTAT,
|
||||||
|
"fchmodat2": SNR_FCHMODAT2,
|
||||||
|
"map_shadow_stack": SNR_MAP_SHADOW_STACK,
|
||||||
|
"futex_wake": SNR_FUTEX_WAKE,
|
||||||
|
"futex_wait": SNR_FUTEX_WAIT,
|
||||||
|
"futex_requeue": SNR_FUTEX_REQUEUE,
|
||||||
|
"statmount": SNR_STATMOUNT,
|
||||||
|
"listmount": SNR_LISTMOUNT,
|
||||||
|
"lsm_get_self_attr": SNR_LSM_GET_SELF_ATTR,
|
||||||
|
"lsm_set_self_attr": SNR_LSM_SET_SELF_ATTR,
|
||||||
|
"lsm_list_modules": SNR_LSM_LIST_MODULES,
|
||||||
|
"mseal": SNR_MSEAL,
|
||||||
|
"setxattrat": SNR_SETXATTRAT,
|
||||||
|
"getxattrat": SNR_GETXATTRAT,
|
||||||
|
"listxattrat": SNR_LISTXATTRAT,
|
||||||
|
"removexattrat": SNR_REMOVEXATTRAT,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SYS_USERFAULTFD = 282
|
||||||
|
SYS_MEMBARRIER = 283
|
||||||
|
SYS_MLOCK2 = 284
|
||||||
|
SYS_COPY_FILE_RANGE = 285
|
||||||
|
SYS_PREADV2 = 286
|
||||||
|
SYS_PWRITEV2 = 287
|
||||||
|
SYS_PKEY_MPROTECT = 288
|
||||||
|
SYS_PKEY_ALLOC = 289
|
||||||
|
SYS_PKEY_FREE = 290
|
||||||
|
SYS_STATX = 291
|
||||||
|
SYS_IO_PGETEVENTS = 292
|
||||||
|
SYS_RSEQ = 293
|
||||||
|
SYS_KEXEC_FILE_LOAD = 294
|
||||||
|
SYS_PIDFD_SEND_SIGNAL = 424
|
||||||
|
SYS_IO_URING_SETUP = 425
|
||||||
|
SYS_IO_URING_ENTER = 426
|
||||||
|
SYS_IO_URING_REGISTER = 427
|
||||||
|
SYS_OPEN_TREE = 428
|
||||||
|
SYS_MOVE_MOUNT = 429
|
||||||
|
SYS_FSOPEN = 430
|
||||||
|
SYS_FSCONFIG = 431
|
||||||
|
SYS_FSMOUNT = 432
|
||||||
|
SYS_FSPICK = 433
|
||||||
|
SYS_PIDFD_OPEN = 434
|
||||||
|
SYS_CLONE3 = 435
|
||||||
|
SYS_CLOSE_RANGE = 436
|
||||||
|
SYS_OPENAT2 = 437
|
||||||
|
SYS_PIDFD_GETFD = 438
|
||||||
|
SYS_FACCESSAT2 = 439
|
||||||
|
SYS_PROCESS_MADVISE = 440
|
||||||
|
SYS_EPOLL_PWAIT2 = 441
|
||||||
|
SYS_MOUNT_SETATTR = 442
|
||||||
|
SYS_QUOTACTL_FD = 443
|
||||||
|
SYS_LANDLOCK_CREATE_RULESET = 444
|
||||||
|
SYS_LANDLOCK_ADD_RULE = 445
|
||||||
|
SYS_LANDLOCK_RESTRICT_SELF = 446
|
||||||
|
SYS_MEMFD_SECRET = 447
|
||||||
|
SYS_PROCESS_MRELEASE = 448
|
||||||
|
SYS_FUTEX_WAITV = 449
|
||||||
|
SYS_SET_MEMPOLICY_HOME_NODE = 450
|
||||||
|
SYS_CACHESTAT = 451
|
||||||
|
SYS_FCHMODAT2 = 452
|
||||||
|
SYS_MAP_SHADOW_STACK = 453
|
||||||
|
SYS_FUTEX_WAKE = 454
|
||||||
|
SYS_FUTEX_WAIT = 455
|
||||||
|
SYS_FUTEX_REQUEUE = 456
|
||||||
|
SYS_STATMOUNT = 457
|
||||||
|
SYS_LISTMOUNT = 458
|
||||||
|
SYS_LSM_GET_SELF_ATTR = 459
|
||||||
|
SYS_LSM_SET_SELF_ATTR = 460
|
||||||
|
SYS_LSM_LIST_MODULES = 461
|
||||||
|
SYS_MSEAL = 462
|
||||||
|
SYS_SETXATTRAT = 463
|
||||||
|
SYS_GETXATTRAT = 464
|
||||||
|
SYS_LISTXATTRAT = 465
|
||||||
|
SYS_REMOVEXATTRAT = 466
|
||||||
|
SYS_OPEN_TREE_ATTR = 467
|
||||||
|
SYS_FILE_GETATTR = 468
|
||||||
|
SYS_FILE_SETATTR = 469
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SNR_IO_SETUP ScmpSyscall = SYS_IO_SETUP
|
||||||
|
SNR_IO_DESTROY ScmpSyscall = SYS_IO_DESTROY
|
||||||
|
SNR_IO_SUBMIT ScmpSyscall = SYS_IO_SUBMIT
|
||||||
|
SNR_IO_CANCEL ScmpSyscall = SYS_IO_CANCEL
|
||||||
|
SNR_IO_GETEVENTS ScmpSyscall = SYS_IO_GETEVENTS
|
||||||
|
SNR_SETXATTR ScmpSyscall = SYS_SETXATTR
|
||||||
|
SNR_LSETXATTR ScmpSyscall = SYS_LSETXATTR
|
||||||
|
SNR_FSETXATTR ScmpSyscall = SYS_FSETXATTR
|
||||||
|
SNR_GETXATTR ScmpSyscall = SYS_GETXATTR
|
||||||
|
SNR_LGETXATTR ScmpSyscall = SYS_LGETXATTR
|
||||||
|
SNR_FGETXATTR ScmpSyscall = SYS_FGETXATTR
|
||||||
|
SNR_LISTXATTR ScmpSyscall = SYS_LISTXATTR
|
||||||
|
SNR_LLISTXATTR ScmpSyscall = SYS_LLISTXATTR
|
||||||
|
SNR_FLISTXATTR ScmpSyscall = SYS_FLISTXATTR
|
||||||
|
SNR_REMOVEXATTR ScmpSyscall = SYS_REMOVEXATTR
|
||||||
|
SNR_LREMOVEXATTR ScmpSyscall = SYS_LREMOVEXATTR
|
||||||
|
SNR_FREMOVEXATTR ScmpSyscall = SYS_FREMOVEXATTR
|
||||||
|
SNR_GETCWD ScmpSyscall = SYS_GETCWD
|
||||||
|
SNR_LOOKUP_DCOOKIE ScmpSyscall = SYS_LOOKUP_DCOOKIE
|
||||||
|
SNR_EVENTFD2 ScmpSyscall = SYS_EVENTFD2
|
||||||
|
SNR_EPOLL_CREATE1 ScmpSyscall = SYS_EPOLL_CREATE1
|
||||||
|
SNR_EPOLL_CTL ScmpSyscall = SYS_EPOLL_CTL
|
||||||
|
SNR_EPOLL_PWAIT ScmpSyscall = SYS_EPOLL_PWAIT
|
||||||
|
SNR_DUP ScmpSyscall = SYS_DUP
|
||||||
|
SNR_DUP3 ScmpSyscall = SYS_DUP3
|
||||||
|
SNR_FCNTL ScmpSyscall = SYS_FCNTL
|
||||||
|
SNR_INOTIFY_INIT1 ScmpSyscall = SYS_INOTIFY_INIT1
|
||||||
|
SNR_INOTIFY_ADD_WATCH ScmpSyscall = SYS_INOTIFY_ADD_WATCH
|
||||||
|
SNR_INOTIFY_RM_WATCH ScmpSyscall = SYS_INOTIFY_RM_WATCH
|
||||||
|
SNR_IOCTL ScmpSyscall = SYS_IOCTL
|
||||||
|
SNR_IOPRIO_SET ScmpSyscall = SYS_IOPRIO_SET
|
||||||
|
SNR_IOPRIO_GET ScmpSyscall = SYS_IOPRIO_GET
|
||||||
|
SNR_FLOCK ScmpSyscall = SYS_FLOCK
|
||||||
|
SNR_MKNODAT ScmpSyscall = SYS_MKNODAT
|
||||||
|
SNR_MKDIRAT ScmpSyscall = SYS_MKDIRAT
|
||||||
|
SNR_UNLINKAT ScmpSyscall = SYS_UNLINKAT
|
||||||
|
SNR_SYMLINKAT ScmpSyscall = SYS_SYMLINKAT
|
||||||
|
SNR_LINKAT ScmpSyscall = SYS_LINKAT
|
||||||
|
SNR_UMOUNT2 ScmpSyscall = SYS_UMOUNT2
|
||||||
|
SNR_MOUNT ScmpSyscall = SYS_MOUNT
|
||||||
|
SNR_PIVOT_ROOT ScmpSyscall = SYS_PIVOT_ROOT
|
||||||
|
SNR_NFSSERVCTL ScmpSyscall = SYS_NFSSERVCTL
|
||||||
|
SNR_STATFS ScmpSyscall = SYS_STATFS
|
||||||
|
SNR_FSTATFS ScmpSyscall = SYS_FSTATFS
|
||||||
|
SNR_TRUNCATE ScmpSyscall = SYS_TRUNCATE
|
||||||
|
SNR_FTRUNCATE ScmpSyscall = SYS_FTRUNCATE
|
||||||
|
SNR_FALLOCATE ScmpSyscall = SYS_FALLOCATE
|
||||||
|
SNR_FACCESSAT ScmpSyscall = SYS_FACCESSAT
|
||||||
|
SNR_CHDIR ScmpSyscall = SYS_CHDIR
|
||||||
|
SNR_FCHDIR ScmpSyscall = SYS_FCHDIR
|
||||||
|
SNR_CHROOT ScmpSyscall = SYS_CHROOT
|
||||||
|
SNR_FCHMOD ScmpSyscall = SYS_FCHMOD
|
||||||
|
SNR_FCHMODAT ScmpSyscall = SYS_FCHMODAT
|
||||||
|
SNR_FCHOWNAT ScmpSyscall = SYS_FCHOWNAT
|
||||||
|
SNR_FCHOWN ScmpSyscall = SYS_FCHOWN
|
||||||
|
SNR_OPENAT ScmpSyscall = SYS_OPENAT
|
||||||
|
SNR_CLOSE ScmpSyscall = SYS_CLOSE
|
||||||
|
SNR_VHANGUP ScmpSyscall = SYS_VHANGUP
|
||||||
|
SNR_PIPE2 ScmpSyscall = SYS_PIPE2
|
||||||
|
SNR_QUOTACTL ScmpSyscall = SYS_QUOTACTL
|
||||||
|
SNR_GETDENTS64 ScmpSyscall = SYS_GETDENTS64
|
||||||
|
SNR_LSEEK ScmpSyscall = SYS_LSEEK
|
||||||
|
SNR_READ ScmpSyscall = SYS_READ
|
||||||
|
SNR_WRITE ScmpSyscall = SYS_WRITE
|
||||||
|
SNR_READV ScmpSyscall = SYS_READV
|
||||||
|
SNR_WRITEV ScmpSyscall = SYS_WRITEV
|
||||||
|
SNR_PREAD64 ScmpSyscall = SYS_PREAD64
|
||||||
|
SNR_PWRITE64 ScmpSyscall = SYS_PWRITE64
|
||||||
|
SNR_PREADV ScmpSyscall = SYS_PREADV
|
||||||
|
SNR_PWRITEV ScmpSyscall = SYS_PWRITEV
|
||||||
|
SNR_SENDFILE ScmpSyscall = SYS_SENDFILE
|
||||||
|
SNR_PSELECT6 ScmpSyscall = SYS_PSELECT6
|
||||||
|
SNR_PPOLL ScmpSyscall = SYS_PPOLL
|
||||||
|
SNR_SIGNALFD4 ScmpSyscall = SYS_SIGNALFD4
|
||||||
|
SNR_VMSPLICE ScmpSyscall = SYS_VMSPLICE
|
||||||
|
SNR_SPLICE ScmpSyscall = SYS_SPLICE
|
||||||
|
SNR_TEE ScmpSyscall = SYS_TEE
|
||||||
|
SNR_READLINKAT ScmpSyscall = SYS_READLINKAT
|
||||||
|
SNR_NEWFSTATAT ScmpSyscall = SYS_NEWFSTATAT
|
||||||
|
SNR_FSTAT ScmpSyscall = SYS_FSTAT
|
||||||
|
SNR_SYNC ScmpSyscall = SYS_SYNC
|
||||||
|
SNR_FSYNC ScmpSyscall = SYS_FSYNC
|
||||||
|
SNR_FDATASYNC ScmpSyscall = SYS_FDATASYNC
|
||||||
|
SNR_SYNC_FILE_RANGE ScmpSyscall = SYS_SYNC_FILE_RANGE
|
||||||
|
SNR_TIMERFD_CREATE ScmpSyscall = SYS_TIMERFD_CREATE
|
||||||
|
SNR_TIMERFD_SETTIME ScmpSyscall = SYS_TIMERFD_SETTIME
|
||||||
|
SNR_TIMERFD_GETTIME ScmpSyscall = SYS_TIMERFD_GETTIME
|
||||||
|
SNR_UTIMENSAT ScmpSyscall = SYS_UTIMENSAT
|
||||||
|
SNR_ACCT ScmpSyscall = SYS_ACCT
|
||||||
|
SNR_CAPGET ScmpSyscall = SYS_CAPGET
|
||||||
|
SNR_CAPSET ScmpSyscall = SYS_CAPSET
|
||||||
|
SNR_PERSONALITY ScmpSyscall = SYS_PERSONALITY
|
||||||
|
SNR_EXIT ScmpSyscall = SYS_EXIT
|
||||||
|
SNR_EXIT_GROUP ScmpSyscall = SYS_EXIT_GROUP
|
||||||
|
SNR_WAITID ScmpSyscall = SYS_WAITID
|
||||||
|
SNR_SET_TID_ADDRESS ScmpSyscall = SYS_SET_TID_ADDRESS
|
||||||
|
SNR_UNSHARE ScmpSyscall = SYS_UNSHARE
|
||||||
|
SNR_FUTEX ScmpSyscall = SYS_FUTEX
|
||||||
|
SNR_SET_ROBUST_LIST ScmpSyscall = SYS_SET_ROBUST_LIST
|
||||||
|
SNR_GET_ROBUST_LIST ScmpSyscall = SYS_GET_ROBUST_LIST
|
||||||
|
SNR_NANOSLEEP ScmpSyscall = SYS_NANOSLEEP
|
||||||
|
SNR_GETITIMER ScmpSyscall = SYS_GETITIMER
|
||||||
|
SNR_SETITIMER ScmpSyscall = SYS_SETITIMER
|
||||||
|
SNR_KEXEC_LOAD ScmpSyscall = SYS_KEXEC_LOAD
|
||||||
|
SNR_INIT_MODULE ScmpSyscall = SYS_INIT_MODULE
|
||||||
|
SNR_DELETE_MODULE ScmpSyscall = SYS_DELETE_MODULE
|
||||||
|
SNR_TIMER_CREATE ScmpSyscall = SYS_TIMER_CREATE
|
||||||
|
SNR_TIMER_GETTIME ScmpSyscall = SYS_TIMER_GETTIME
|
||||||
|
SNR_TIMER_GETOVERRUN ScmpSyscall = SYS_TIMER_GETOVERRUN
|
||||||
|
SNR_TIMER_SETTIME ScmpSyscall = SYS_TIMER_SETTIME
|
||||||
|
SNR_TIMER_DELETE ScmpSyscall = SYS_TIMER_DELETE
|
||||||
|
SNR_CLOCK_SETTIME ScmpSyscall = SYS_CLOCK_SETTIME
|
||||||
|
SNR_CLOCK_GETTIME ScmpSyscall = SYS_CLOCK_GETTIME
|
||||||
|
SNR_CLOCK_GETRES ScmpSyscall = SYS_CLOCK_GETRES
|
||||||
|
SNR_CLOCK_NANOSLEEP ScmpSyscall = SYS_CLOCK_NANOSLEEP
|
||||||
|
SNR_SYSLOG ScmpSyscall = SYS_SYSLOG
|
||||||
|
SNR_PTRACE ScmpSyscall = SYS_PTRACE
|
||||||
|
SNR_SCHED_SETPARAM ScmpSyscall = SYS_SCHED_SETPARAM
|
||||||
|
SNR_SCHED_SETSCHEDULER ScmpSyscall = SYS_SCHED_SETSCHEDULER
|
||||||
|
SNR_SCHED_GETSCHEDULER ScmpSyscall = SYS_SCHED_GETSCHEDULER
|
||||||
|
SNR_SCHED_GETPARAM ScmpSyscall = SYS_SCHED_GETPARAM
|
||||||
|
SNR_SCHED_SETAFFINITY ScmpSyscall = SYS_SCHED_SETAFFINITY
|
||||||
|
SNR_SCHED_GETAFFINITY ScmpSyscall = SYS_SCHED_GETAFFINITY
|
||||||
|
SNR_SCHED_YIELD ScmpSyscall = SYS_SCHED_YIELD
|
||||||
|
SNR_SCHED_GET_PRIORITY_MAX ScmpSyscall = SYS_SCHED_GET_PRIORITY_MAX
|
||||||
|
SNR_SCHED_GET_PRIORITY_MIN ScmpSyscall = SYS_SCHED_GET_PRIORITY_MIN
|
||||||
|
SNR_SCHED_RR_GET_INTERVAL ScmpSyscall = SYS_SCHED_RR_GET_INTERVAL
|
||||||
|
SNR_RESTART_SYSCALL ScmpSyscall = SYS_RESTART_SYSCALL
|
||||||
|
SNR_KILL ScmpSyscall = SYS_KILL
|
||||||
|
SNR_TKILL ScmpSyscall = SYS_TKILL
|
||||||
|
SNR_TGKILL ScmpSyscall = SYS_TGKILL
|
||||||
|
SNR_SIGALTSTACK ScmpSyscall = SYS_SIGALTSTACK
|
||||||
|
SNR_RT_SIGSUSPEND ScmpSyscall = SYS_RT_SIGSUSPEND
|
||||||
|
SNR_RT_SIGACTION ScmpSyscall = SYS_RT_SIGACTION
|
||||||
|
SNR_RT_SIGPROCMASK ScmpSyscall = SYS_RT_SIGPROCMASK
|
||||||
|
SNR_RT_SIGPENDING ScmpSyscall = SYS_RT_SIGPENDING
|
||||||
|
SNR_RT_SIGTIMEDWAIT ScmpSyscall = SYS_RT_SIGTIMEDWAIT
|
||||||
|
SNR_RT_SIGQUEUEINFO ScmpSyscall = SYS_RT_SIGQUEUEINFO
|
||||||
|
SNR_RT_SIGRETURN ScmpSyscall = SYS_RT_SIGRETURN
|
||||||
|
SNR_SETPRIORITY ScmpSyscall = SYS_SETPRIORITY
|
||||||
|
SNR_GETPRIORITY ScmpSyscall = SYS_GETPRIORITY
|
||||||
|
SNR_REBOOT ScmpSyscall = SYS_REBOOT
|
||||||
|
SNR_SETREGID ScmpSyscall = SYS_SETREGID
|
||||||
|
SNR_SETGID ScmpSyscall = SYS_SETGID
|
||||||
|
SNR_SETREUID ScmpSyscall = SYS_SETREUID
|
||||||
|
SNR_SETUID ScmpSyscall = SYS_SETUID
|
||||||
|
SNR_SETRESUID ScmpSyscall = SYS_SETRESUID
|
||||||
|
SNR_GETRESUID ScmpSyscall = SYS_GETRESUID
|
||||||
|
SNR_SETRESGID ScmpSyscall = SYS_SETRESGID
|
||||||
|
SNR_GETRESGID ScmpSyscall = SYS_GETRESGID
|
||||||
|
SNR_SETFSUID ScmpSyscall = SYS_SETFSUID
|
||||||
|
SNR_SETFSGID ScmpSyscall = SYS_SETFSGID
|
||||||
|
SNR_TIMES ScmpSyscall = SYS_TIMES
|
||||||
|
SNR_SETPGID ScmpSyscall = SYS_SETPGID
|
||||||
|
SNR_GETPGID ScmpSyscall = SYS_GETPGID
|
||||||
|
SNR_GETSID ScmpSyscall = SYS_GETSID
|
||||||
|
SNR_SETSID ScmpSyscall = SYS_SETSID
|
||||||
|
SNR_GETGROUPS ScmpSyscall = SYS_GETGROUPS
|
||||||
|
SNR_SETGROUPS ScmpSyscall = SYS_SETGROUPS
|
||||||
|
SNR_UNAME ScmpSyscall = SYS_UNAME
|
||||||
|
SNR_SETHOSTNAME ScmpSyscall = SYS_SETHOSTNAME
|
||||||
|
SNR_SETDOMAINNAME ScmpSyscall = SYS_SETDOMAINNAME
|
||||||
|
SNR_GETRLIMIT ScmpSyscall = SYS_GETRLIMIT
|
||||||
|
SNR_SETRLIMIT ScmpSyscall = SYS_SETRLIMIT
|
||||||
|
SNR_GETRUSAGE ScmpSyscall = SYS_GETRUSAGE
|
||||||
|
SNR_UMASK ScmpSyscall = SYS_UMASK
|
||||||
|
SNR_PRCTL ScmpSyscall = SYS_PRCTL
|
||||||
|
SNR_GETCPU ScmpSyscall = SYS_GETCPU
|
||||||
|
SNR_GETTIMEOFDAY ScmpSyscall = SYS_GETTIMEOFDAY
|
||||||
|
SNR_SETTIMEOFDAY ScmpSyscall = SYS_SETTIMEOFDAY
|
||||||
|
SNR_ADJTIMEX ScmpSyscall = SYS_ADJTIMEX
|
||||||
|
SNR_GETPID ScmpSyscall = SYS_GETPID
|
||||||
|
SNR_GETPPID ScmpSyscall = SYS_GETPPID
|
||||||
|
SNR_GETUID ScmpSyscall = SYS_GETUID
|
||||||
|
SNR_GETEUID ScmpSyscall = SYS_GETEUID
|
||||||
|
SNR_GETGID ScmpSyscall = SYS_GETGID
|
||||||
|
SNR_GETEGID ScmpSyscall = SYS_GETEGID
|
||||||
|
SNR_GETTID ScmpSyscall = SYS_GETTID
|
||||||
|
SNR_SYSINFO ScmpSyscall = SYS_SYSINFO
|
||||||
|
SNR_MQ_OPEN ScmpSyscall = SYS_MQ_OPEN
|
||||||
|
SNR_MQ_UNLINK ScmpSyscall = SYS_MQ_UNLINK
|
||||||
|
SNR_MQ_TIMEDSEND ScmpSyscall = SYS_MQ_TIMEDSEND
|
||||||
|
SNR_MQ_TIMEDRECEIVE ScmpSyscall = SYS_MQ_TIMEDRECEIVE
|
||||||
|
SNR_MQ_NOTIFY ScmpSyscall = SYS_MQ_NOTIFY
|
||||||
|
SNR_MQ_GETSETATTR ScmpSyscall = SYS_MQ_GETSETATTR
|
||||||
|
SNR_MSGGET ScmpSyscall = SYS_MSGGET
|
||||||
|
SNR_MSGCTL ScmpSyscall = SYS_MSGCTL
|
||||||
|
SNR_MSGRCV ScmpSyscall = SYS_MSGRCV
|
||||||
|
SNR_MSGSND ScmpSyscall = SYS_MSGSND
|
||||||
|
SNR_SEMGET ScmpSyscall = SYS_SEMGET
|
||||||
|
SNR_SEMCTL ScmpSyscall = SYS_SEMCTL
|
||||||
|
SNR_SEMTIMEDOP ScmpSyscall = SYS_SEMTIMEDOP
|
||||||
|
SNR_SEMOP ScmpSyscall = SYS_SEMOP
|
||||||
|
SNR_SHMGET ScmpSyscall = SYS_SHMGET
|
||||||
|
SNR_SHMCTL ScmpSyscall = SYS_SHMCTL
|
||||||
|
SNR_SHMAT ScmpSyscall = SYS_SHMAT
|
||||||
|
SNR_SHMDT ScmpSyscall = SYS_SHMDT
|
||||||
|
SNR_SOCKET ScmpSyscall = SYS_SOCKET
|
||||||
|
SNR_SOCKETPAIR ScmpSyscall = SYS_SOCKETPAIR
|
||||||
|
SNR_BIND ScmpSyscall = SYS_BIND
|
||||||
|
SNR_LISTEN ScmpSyscall = SYS_LISTEN
|
||||||
|
SNR_ACCEPT ScmpSyscall = SYS_ACCEPT
|
||||||
|
SNR_CONNECT ScmpSyscall = SYS_CONNECT
|
||||||
|
SNR_GETSOCKNAME ScmpSyscall = SYS_GETSOCKNAME
|
||||||
|
SNR_GETPEERNAME ScmpSyscall = SYS_GETPEERNAME
|
||||||
|
SNR_SENDTO ScmpSyscall = SYS_SENDTO
|
||||||
|
SNR_RECVFROM ScmpSyscall = SYS_RECVFROM
|
||||||
|
SNR_SETSOCKOPT ScmpSyscall = SYS_SETSOCKOPT
|
||||||
|
SNR_GETSOCKOPT ScmpSyscall = SYS_GETSOCKOPT
|
||||||
|
SNR_SHUTDOWN ScmpSyscall = SYS_SHUTDOWN
|
||||||
|
SNR_SENDMSG ScmpSyscall = SYS_SENDMSG
|
||||||
|
SNR_RECVMSG ScmpSyscall = SYS_RECVMSG
|
||||||
|
SNR_READAHEAD ScmpSyscall = SYS_READAHEAD
|
||||||
|
SNR_BRK ScmpSyscall = SYS_BRK
|
||||||
|
SNR_MUNMAP ScmpSyscall = SYS_MUNMAP
|
||||||
|
SNR_MREMAP ScmpSyscall = SYS_MREMAP
|
||||||
|
SNR_ADD_KEY ScmpSyscall = SYS_ADD_KEY
|
||||||
|
SNR_REQUEST_KEY ScmpSyscall = SYS_REQUEST_KEY
|
||||||
|
SNR_KEYCTL ScmpSyscall = SYS_KEYCTL
|
||||||
|
SNR_CLONE ScmpSyscall = SYS_CLONE
|
||||||
|
SNR_EXECVE ScmpSyscall = SYS_EXECVE
|
||||||
|
SNR_MMAP ScmpSyscall = SYS_MMAP
|
||||||
|
SNR_FADVISE64 ScmpSyscall = SYS_FADVISE64
|
||||||
|
SNR_SWAPON ScmpSyscall = SYS_SWAPON
|
||||||
|
SNR_SWAPOFF ScmpSyscall = SYS_SWAPOFF
|
||||||
|
SNR_MPROTECT ScmpSyscall = SYS_MPROTECT
|
||||||
|
SNR_MSYNC ScmpSyscall = SYS_MSYNC
|
||||||
|
SNR_MLOCK ScmpSyscall = SYS_MLOCK
|
||||||
|
SNR_MUNLOCK ScmpSyscall = SYS_MUNLOCK
|
||||||
|
SNR_MLOCKALL ScmpSyscall = SYS_MLOCKALL
|
||||||
|
SNR_MUNLOCKALL ScmpSyscall = SYS_MUNLOCKALL
|
||||||
|
SNR_MINCORE ScmpSyscall = SYS_MINCORE
|
||||||
|
SNR_MADVISE ScmpSyscall = SYS_MADVISE
|
||||||
|
SNR_REMAP_FILE_PAGES ScmpSyscall = SYS_REMAP_FILE_PAGES
|
||||||
|
SNR_MBIND ScmpSyscall = SYS_MBIND
|
||||||
|
SNR_GET_MEMPOLICY ScmpSyscall = SYS_GET_MEMPOLICY
|
||||||
|
SNR_SET_MEMPOLICY ScmpSyscall = SYS_SET_MEMPOLICY
|
||||||
|
SNR_MIGRATE_PAGES ScmpSyscall = SYS_MIGRATE_PAGES
|
||||||
|
SNR_MOVE_PAGES ScmpSyscall = SYS_MOVE_PAGES
|
||||||
|
SNR_RT_TGSIGQUEUEINFO ScmpSyscall = SYS_RT_TGSIGQUEUEINFO
|
||||||
|
SNR_PERF_EVENT_OPEN ScmpSyscall = SYS_PERF_EVENT_OPEN
|
||||||
|
SNR_ACCEPT4 ScmpSyscall = SYS_ACCEPT4
|
||||||
|
SNR_RECVMMSG ScmpSyscall = SYS_RECVMMSG
|
||||||
|
SNR_WAIT4 ScmpSyscall = SYS_WAIT4
|
||||||
|
SNR_PRLIMIT64 ScmpSyscall = SYS_PRLIMIT64
|
||||||
|
SNR_FANOTIFY_INIT ScmpSyscall = SYS_FANOTIFY_INIT
|
||||||
|
SNR_FANOTIFY_MARK ScmpSyscall = SYS_FANOTIFY_MARK
|
||||||
|
SNR_NAME_TO_HANDLE_AT ScmpSyscall = SYS_NAME_TO_HANDLE_AT
|
||||||
|
SNR_OPEN_BY_HANDLE_AT ScmpSyscall = SYS_OPEN_BY_HANDLE_AT
|
||||||
|
SNR_CLOCK_ADJTIME ScmpSyscall = SYS_CLOCK_ADJTIME
|
||||||
|
SNR_SYNCFS ScmpSyscall = SYS_SYNCFS
|
||||||
|
SNR_SETNS ScmpSyscall = SYS_SETNS
|
||||||
|
SNR_SENDMMSG ScmpSyscall = SYS_SENDMMSG
|
||||||
|
SNR_PROCESS_VM_READV ScmpSyscall = SYS_PROCESS_VM_READV
|
||||||
|
SNR_PROCESS_VM_WRITEV ScmpSyscall = SYS_PROCESS_VM_WRITEV
|
||||||
|
SNR_KCMP ScmpSyscall = SYS_KCMP
|
||||||
|
SNR_FINIT_MODULE ScmpSyscall = SYS_FINIT_MODULE
|
||||||
|
SNR_SCHED_SETATTR ScmpSyscall = SYS_SCHED_SETATTR
|
||||||
|
SNR_SCHED_GETATTR ScmpSyscall = SYS_SCHED_GETATTR
|
||||||
|
SNR_RENAMEAT2 ScmpSyscall = SYS_RENAMEAT2
|
||||||
|
SNR_SECCOMP ScmpSyscall = SYS_SECCOMP
|
||||||
|
SNR_GETRANDOM ScmpSyscall = SYS_GETRANDOM
|
||||||
|
SNR_MEMFD_CREATE ScmpSyscall = SYS_MEMFD_CREATE
|
||||||
|
SNR_BPF ScmpSyscall = SYS_BPF
|
||||||
|
SNR_EXECVEAT ScmpSyscall = SYS_EXECVEAT
|
||||||
|
SNR_USERFAULTFD ScmpSyscall = SYS_USERFAULTFD
|
||||||
|
SNR_MEMBARRIER ScmpSyscall = SYS_MEMBARRIER
|
||||||
|
SNR_MLOCK2 ScmpSyscall = SYS_MLOCK2
|
||||||
|
SNR_COPY_FILE_RANGE ScmpSyscall = SYS_COPY_FILE_RANGE
|
||||||
|
SNR_PREADV2 ScmpSyscall = SYS_PREADV2
|
||||||
|
SNR_PWRITEV2 ScmpSyscall = SYS_PWRITEV2
|
||||||
|
SNR_PKEY_MPROTECT ScmpSyscall = SYS_PKEY_MPROTECT
|
||||||
|
SNR_PKEY_ALLOC ScmpSyscall = SYS_PKEY_ALLOC
|
||||||
|
SNR_PKEY_FREE ScmpSyscall = SYS_PKEY_FREE
|
||||||
|
SNR_STATX ScmpSyscall = SYS_STATX
|
||||||
|
SNR_IO_PGETEVENTS ScmpSyscall = SYS_IO_PGETEVENTS
|
||||||
|
SNR_RSEQ ScmpSyscall = SYS_RSEQ
|
||||||
|
SNR_KEXEC_FILE_LOAD ScmpSyscall = SYS_KEXEC_FILE_LOAD
|
||||||
|
SNR_PIDFD_SEND_SIGNAL ScmpSyscall = SYS_PIDFD_SEND_SIGNAL
|
||||||
|
SNR_IO_URING_SETUP ScmpSyscall = SYS_IO_URING_SETUP
|
||||||
|
SNR_IO_URING_ENTER ScmpSyscall = SYS_IO_URING_ENTER
|
||||||
|
SNR_IO_URING_REGISTER ScmpSyscall = SYS_IO_URING_REGISTER
|
||||||
|
SNR_OPEN_TREE ScmpSyscall = SYS_OPEN_TREE
|
||||||
|
SNR_MOVE_MOUNT ScmpSyscall = SYS_MOVE_MOUNT
|
||||||
|
SNR_FSOPEN ScmpSyscall = SYS_FSOPEN
|
||||||
|
SNR_FSCONFIG ScmpSyscall = SYS_FSCONFIG
|
||||||
|
SNR_FSMOUNT ScmpSyscall = SYS_FSMOUNT
|
||||||
|
SNR_FSPICK ScmpSyscall = SYS_FSPICK
|
||||||
|
SNR_PIDFD_OPEN ScmpSyscall = SYS_PIDFD_OPEN
|
||||||
|
SNR_CLONE3 ScmpSyscall = SYS_CLONE3
|
||||||
|
SNR_CLOSE_RANGE ScmpSyscall = SYS_CLOSE_RANGE
|
||||||
|
SNR_OPENAT2 ScmpSyscall = SYS_OPENAT2
|
||||||
|
SNR_PIDFD_GETFD ScmpSyscall = SYS_PIDFD_GETFD
|
||||||
|
SNR_FACCESSAT2 ScmpSyscall = SYS_FACCESSAT2
|
||||||
|
SNR_PROCESS_MADVISE ScmpSyscall = SYS_PROCESS_MADVISE
|
||||||
|
SNR_EPOLL_PWAIT2 ScmpSyscall = SYS_EPOLL_PWAIT2
|
||||||
|
SNR_MOUNT_SETATTR ScmpSyscall = SYS_MOUNT_SETATTR
|
||||||
|
SNR_QUOTACTL_FD ScmpSyscall = SYS_QUOTACTL_FD
|
||||||
|
SNR_LANDLOCK_CREATE_RULESET ScmpSyscall = SYS_LANDLOCK_CREATE_RULESET
|
||||||
|
SNR_LANDLOCK_ADD_RULE ScmpSyscall = SYS_LANDLOCK_ADD_RULE
|
||||||
|
SNR_LANDLOCK_RESTRICT_SELF ScmpSyscall = SYS_LANDLOCK_RESTRICT_SELF
|
||||||
|
SNR_MEMFD_SECRET ScmpSyscall = SYS_MEMFD_SECRET
|
||||||
|
SNR_PROCESS_MRELEASE ScmpSyscall = SYS_PROCESS_MRELEASE
|
||||||
|
SNR_FUTEX_WAITV ScmpSyscall = SYS_FUTEX_WAITV
|
||||||
|
SNR_SET_MEMPOLICY_HOME_NODE ScmpSyscall = SYS_SET_MEMPOLICY_HOME_NODE
|
||||||
|
SNR_CACHESTAT ScmpSyscall = SYS_CACHESTAT
|
||||||
|
SNR_FCHMODAT2 ScmpSyscall = SYS_FCHMODAT2
|
||||||
|
SNR_MAP_SHADOW_STACK ScmpSyscall = SYS_MAP_SHADOW_STACK
|
||||||
|
SNR_FUTEX_WAKE ScmpSyscall = SYS_FUTEX_WAKE
|
||||||
|
SNR_FUTEX_WAIT ScmpSyscall = SYS_FUTEX_WAIT
|
||||||
|
SNR_FUTEX_REQUEUE ScmpSyscall = SYS_FUTEX_REQUEUE
|
||||||
|
SNR_STATMOUNT ScmpSyscall = SYS_STATMOUNT
|
||||||
|
SNR_LISTMOUNT ScmpSyscall = SYS_LISTMOUNT
|
||||||
|
SNR_LSM_GET_SELF_ATTR ScmpSyscall = SYS_LSM_GET_SELF_ATTR
|
||||||
|
SNR_LSM_SET_SELF_ATTR ScmpSyscall = SYS_LSM_SET_SELF_ATTR
|
||||||
|
SNR_LSM_LIST_MODULES ScmpSyscall = SYS_LSM_LIST_MODULES
|
||||||
|
SNR_MSEAL ScmpSyscall = SYS_MSEAL
|
||||||
|
SNR_SETXATTRAT ScmpSyscall = SYS_SETXATTRAT
|
||||||
|
SNR_GETXATTRAT ScmpSyscall = SYS_GETXATTRAT
|
||||||
|
SNR_LISTXATTRAT ScmpSyscall = SYS_LISTXATTRAT
|
||||||
|
SNR_REMOVEXATTRAT ScmpSyscall = SYS_REMOVEXATTRAT
|
||||||
|
SNR_OPEN_TREE_ATTR ScmpSyscall = SYS_OPEN_TREE_ATTR
|
||||||
|
SNR_FILE_GETATTR ScmpSyscall = SYS_FILE_GETATTR
|
||||||
|
SNR_FILE_SETATTR ScmpSyscall = SYS_FILE_SETATTR
|
||||||
|
)
|
||||||
8
container/std/types.go
Normal file
8
container/std/types.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package std
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Uint is equivalent to C.uint.
|
||||||
|
Uint uint32
|
||||||
|
// Int is equivalent to C.int.
|
||||||
|
Int int32
|
||||||
|
)
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/container/std"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Prctl manipulates various aspects of the behavior of the calling thread or process.
|
// Prctl manipulates various aspects of the behavior of the calling thread or process.
|
||||||
@@ -41,6 +46,163 @@ func Isatty(fd int) bool {
|
|||||||
return r == 0
|
return r == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SchedPolicy denotes a scheduling policy defined in include/uapi/linux/sched.h.
|
||||||
|
type SchedPolicy int
|
||||||
|
|
||||||
|
// include/uapi/linux/sched.h
|
||||||
|
const (
|
||||||
|
SCHED_NORMAL SchedPolicy = iota
|
||||||
|
SCHED_FIFO
|
||||||
|
SCHED_RR
|
||||||
|
SCHED_BATCH
|
||||||
|
_SCHED_ISO // SCHED_ISO: reserved but not implemented yet
|
||||||
|
SCHED_IDLE
|
||||||
|
SCHED_DEADLINE
|
||||||
|
SCHED_EXT
|
||||||
|
|
||||||
|
_SCHED_LAST SchedPolicy = iota - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ encoding.TextMarshaler = _SCHED_LAST
|
||||||
|
var _ encoding.TextUnmarshaler = new(_SCHED_LAST)
|
||||||
|
|
||||||
|
// String returns a unique representation of policy, also used in encoding.
|
||||||
|
func (policy SchedPolicy) String() string {
|
||||||
|
switch policy {
|
||||||
|
case SCHED_NORMAL:
|
||||||
|
return ""
|
||||||
|
case SCHED_FIFO:
|
||||||
|
return "fifo"
|
||||||
|
case SCHED_RR:
|
||||||
|
return "rr"
|
||||||
|
case SCHED_BATCH:
|
||||||
|
return "batch"
|
||||||
|
case SCHED_IDLE:
|
||||||
|
return "idle"
|
||||||
|
case SCHED_DEADLINE:
|
||||||
|
return "deadline"
|
||||||
|
case SCHED_EXT:
|
||||||
|
return "ext"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "invalid policy " + strconv.Itoa(int(policy))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText performs bounds checking and returns the result of String.
|
||||||
|
func (policy SchedPolicy) MarshalText() ([]byte, error) {
|
||||||
|
if policy == _SCHED_ISO || policy < 0 || policy > _SCHED_LAST {
|
||||||
|
return nil, EINVAL
|
||||||
|
}
|
||||||
|
return []byte(policy.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidSchedPolicyError is an invalid string representation of a [SchedPolicy].
|
||||||
|
type InvalidSchedPolicyError string
|
||||||
|
|
||||||
|
func (InvalidSchedPolicyError) Unwrap() error { return EINVAL }
|
||||||
|
func (e InvalidSchedPolicyError) Error() string {
|
||||||
|
return "invalid scheduling policy " + strconv.Quote(string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText is the inverse of MarshalText.
|
||||||
|
func (policy *SchedPolicy) UnmarshalText(text []byte) error {
|
||||||
|
switch string(text) {
|
||||||
|
case "fifo":
|
||||||
|
*policy = SCHED_FIFO
|
||||||
|
case "rr":
|
||||||
|
*policy = SCHED_RR
|
||||||
|
case "batch":
|
||||||
|
*policy = SCHED_BATCH
|
||||||
|
case "idle":
|
||||||
|
*policy = SCHED_IDLE
|
||||||
|
case "deadline":
|
||||||
|
*policy = SCHED_DEADLINE
|
||||||
|
case "ext":
|
||||||
|
*policy = SCHED_EXT
|
||||||
|
|
||||||
|
case "":
|
||||||
|
*policy = 0
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return InvalidSchedPolicyError(text)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// for sched_get_priority_max and sched_get_priority_min
|
||||||
|
var (
|
||||||
|
schedPriority [_SCHED_LAST + 1][2]std.Int
|
||||||
|
schedPriorityErr [_SCHED_LAST + 1][2]error
|
||||||
|
schedPriorityOnce [_SCHED_LAST + 1][2]sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPriorityMax returns the maximum priority value that can be used with the
|
||||||
|
// scheduling algorithm identified by policy.
|
||||||
|
func (policy SchedPolicy) GetPriorityMax() (std.Int, error) {
|
||||||
|
schedPriorityOnce[policy][0].Do(func() {
|
||||||
|
priority, _, errno := Syscall(
|
||||||
|
SYS_SCHED_GET_PRIORITY_MAX,
|
||||||
|
uintptr(policy),
|
||||||
|
0, 0,
|
||||||
|
)
|
||||||
|
schedPriority[policy][0] = std.Int(priority)
|
||||||
|
if schedPriority[policy][0] < 0 {
|
||||||
|
schedPriorityErr[policy][0] = errno
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return schedPriority[policy][0], schedPriorityErr[policy][0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPriorityMin returns the minimum priority value that can be used with the
|
||||||
|
// scheduling algorithm identified by policy.
|
||||||
|
func (policy SchedPolicy) GetPriorityMin() (std.Int, error) {
|
||||||
|
schedPriorityOnce[policy][1].Do(func() {
|
||||||
|
priority, _, errno := Syscall(
|
||||||
|
SYS_SCHED_GET_PRIORITY_MIN,
|
||||||
|
uintptr(policy),
|
||||||
|
0, 0,
|
||||||
|
)
|
||||||
|
schedPriority[policy][1] = std.Int(priority)
|
||||||
|
if schedPriority[policy][1] < 0 {
|
||||||
|
schedPriorityErr[policy][1] = errno
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return schedPriority[policy][1], schedPriorityErr[policy][1]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// schedParam is equivalent to struct sched_param from include/linux/sched.h.
|
||||||
|
type schedParam struct {
|
||||||
|
// sched_priority
|
||||||
|
priority std.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// schedSetscheduler sets both the scheduling policy and parameters for the
|
||||||
|
// thread whose ID is specified in tid. If tid equals zero, the scheduling
|
||||||
|
// policy and parameters of the calling thread will be set.
|
||||||
|
//
|
||||||
|
// This function is unexported because it is [very subtle to use correctly]. The
|
||||||
|
// function signature in libc is misleading: pid actually refers to a thread ID.
|
||||||
|
// The glibc wrapper for this system call ignores this semantic and exposes
|
||||||
|
// this counterintuitive behaviour.
|
||||||
|
//
|
||||||
|
// This function is only called from the container setup thread. Do not reuse
|
||||||
|
// this if you do not have something similar in place!
|
||||||
|
//
|
||||||
|
// [very subtle to use correctly]: https://www.openwall.com/lists/musl/2016/03/01/4
|
||||||
|
func schedSetscheduler(tid int, policy SchedPolicy, param *schedParam) error {
|
||||||
|
if r, _, errno := Syscall(
|
||||||
|
SYS_SCHED_SETSCHEDULER,
|
||||||
|
uintptr(tid),
|
||||||
|
uintptr(policy),
|
||||||
|
uintptr(unsafe.Pointer(param)),
|
||||||
|
); r < 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// IgnoringEINTR makes a function call and repeats it if it returns an
|
// IgnoringEINTR makes a function call and repeats it if it returns an
|
||||||
// EINTR error. This appears to be required even though we install all
|
// EINTR error. This appears to be required even though we install all
|
||||||
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
|
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
|
||||||
|
|||||||
100
container/syscall_test.go
Normal file
100
container/syscall_test.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package container_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/std"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSchedPolicyJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
policy container.SchedPolicy
|
||||||
|
want string
|
||||||
|
encodeErr error
|
||||||
|
decodeErr error
|
||||||
|
}{
|
||||||
|
{container.SCHED_NORMAL, `""`, nil, nil},
|
||||||
|
{container.SCHED_FIFO, `"fifo"`, nil, nil},
|
||||||
|
{container.SCHED_RR, `"rr"`, nil, nil},
|
||||||
|
{container.SCHED_BATCH, `"batch"`, nil, nil},
|
||||||
|
{4, `"invalid policy 4"`, syscall.EINVAL, container.InvalidSchedPolicyError("invalid policy 4")},
|
||||||
|
{container.SCHED_IDLE, `"idle"`, nil, nil},
|
||||||
|
{container.SCHED_DEADLINE, `"deadline"`, nil, nil},
|
||||||
|
{container.SCHED_EXT, `"ext"`, nil, nil},
|
||||||
|
{math.MaxInt, `"iso"`, syscall.EINVAL, container.InvalidSchedPolicyError("iso")},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
name := tc.policy.String()
|
||||||
|
if tc.policy == container.SCHED_NORMAL {
|
||||||
|
name = "normal"
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got, err := json.Marshal(tc.policy)
|
||||||
|
if !errors.Is(err, tc.encodeErr) {
|
||||||
|
t.Fatalf("Marshal: error = %v, want %v", err, tc.encodeErr)
|
||||||
|
}
|
||||||
|
if err == nil && string(got) != tc.want {
|
||||||
|
t.Fatalf("Marshal: %s, want %s", string(got), tc.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v container.SchedPolicy
|
||||||
|
if err = json.Unmarshal([]byte(tc.want), &v); !reflect.DeepEqual(err, tc.decodeErr) {
|
||||||
|
t.Fatalf("Unmarshal: error = %v, want %v", err, tc.decodeErr)
|
||||||
|
}
|
||||||
|
if err == nil && v != tc.policy {
|
||||||
|
t.Fatalf("Unmarshal: %d, want %d", v, tc.policy)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchedPolicyMinMax(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
policy container.SchedPolicy
|
||||||
|
min, max std.Int
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{container.SCHED_NORMAL, 0, 0, nil},
|
||||||
|
{container.SCHED_FIFO, 1, 99, nil},
|
||||||
|
{container.SCHED_RR, 1, 99, nil},
|
||||||
|
{container.SCHED_BATCH, 0, 0, nil},
|
||||||
|
{4, -1, -1, syscall.EINVAL},
|
||||||
|
{container.SCHED_IDLE, 0, 0, nil},
|
||||||
|
{container.SCHED_DEADLINE, 0, 0, nil},
|
||||||
|
{container.SCHED_EXT, 0, 0, nil},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
name := tc.policy.String()
|
||||||
|
if tc.policy == container.SCHED_NORMAL {
|
||||||
|
name = "normal"
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if priority, err := tc.policy.GetPriorityMax(); !reflect.DeepEqual(err, tc.err) {
|
||||||
|
t.Fatalf("GetPriorityMax: error = %v, want %v", err, tc.err)
|
||||||
|
} else if priority != tc.max {
|
||||||
|
t.Fatalf("GetPriorityMax: %d, want %d", priority, tc.max)
|
||||||
|
}
|
||||||
|
if priority, err := tc.policy.GetPriorityMin(); !reflect.DeepEqual(err, tc.err) {
|
||||||
|
t.Fatalf("GetPriorityMin: error = %v, want %v", err, tc.err)
|
||||||
|
} else if priority != tc.min {
|
||||||
|
t.Fatalf("GetPriorityMin: %d, want %d", priority, tc.min)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package vfs
|
|||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
|
// Unmangle reverses mangling of strings done by the kernel. Its behaviour is
|
||||||
|
// consistent with the equivalent function in util-linux.
|
||||||
func Unmangle(s string) string {
|
func Unmangle(s string) string {
|
||||||
if !strings.ContainsRune(s, '\\') {
|
if !strings.ContainsRune(s, '\\') {
|
||||||
return s
|
return s
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ var (
|
|||||||
ErrMountInfoSep = errors.New("bad optional fields separator")
|
ErrMountInfoSep = errors.New("bad optional fields separator")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A DecoderError describes a nonrecoverable error decoding a mountinfo stream.
|
||||||
type DecoderError struct {
|
type DecoderError struct {
|
||||||
Op string
|
Op string
|
||||||
Line int
|
Line int
|
||||||
@@ -51,7 +52,8 @@ func (e *DecoderError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from an input stream.
|
// A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from
|
||||||
|
// an input stream.
|
||||||
MountInfoDecoder struct {
|
MountInfoDecoder struct {
|
||||||
s *bufio.Scanner
|
s *bufio.Scanner
|
||||||
m *MountInfo
|
m *MountInfo
|
||||||
@@ -72,13 +74,16 @@ type (
|
|||||||
MountInfoEntry struct {
|
MountInfoEntry struct {
|
||||||
// mount ID: a unique ID for the mount (may be reused after umount(2)).
|
// mount ID: a unique ID for the mount (may be reused after umount(2)).
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
// parent ID: the ID of the parent mount (or of self for the root of this mount namespace's mount tree).
|
// parent ID: the ID of the parent mount (or of self for the root of
|
||||||
|
// this mount namespace's mount tree).
|
||||||
Parent int `json:"parent"`
|
Parent int `json:"parent"`
|
||||||
// major:minor: the value of st_dev for files on this filesystem (see stat(2)).
|
// major:minor: the value of st_dev for files on this filesystem (see stat(2)).
|
||||||
Devno DevT `json:"devno"`
|
Devno DevT `json:"devno"`
|
||||||
// root: the pathname of the directory in the filesystem which forms the root of this mount.
|
// root: the pathname of the directory in the filesystem which forms the
|
||||||
|
// root of this mount.
|
||||||
Root string `json:"root"`
|
Root string `json:"root"`
|
||||||
// mount point: the pathname of the mount point relative to the process's root directory.
|
// mount point: the pathname of the mount point relative to the
|
||||||
|
// process's root directory.
|
||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
// mount options: per-mount options (see mount(2)).
|
// mount options: per-mount options (see mount(2)).
|
||||||
VfsOptstr string `json:"vfs_optstr"`
|
VfsOptstr string `json:"vfs_optstr"`
|
||||||
@@ -126,7 +131,8 @@ func (e *MountInfoEntry) Flags() (flags uintptr, unmatched []string) {
|
|||||||
|
|
||||||
// NewMountInfoDecoder returns a new decoder that reads from r.
|
// NewMountInfoDecoder returns a new decoder that reads from r.
|
||||||
//
|
//
|
||||||
// The decoder introduces its own buffering and may read data from r beyond the mountinfo entries requested.
|
// The decoder introduces its own buffering and may read data from r beyond the
|
||||||
|
// mountinfo entries requested.
|
||||||
func NewMountInfoDecoder(r io.Reader) *MountInfoDecoder {
|
func NewMountInfoDecoder(r io.Reader) *MountInfoDecoder {
|
||||||
return &MountInfoDecoder{s: bufio.NewScanner(r)}
|
return &MountInfoDecoder{s: bufio.NewScanner(r)}
|
||||||
}
|
}
|
||||||
@@ -271,6 +277,8 @@ func parseMountInfoLine(s string, ent *MountInfoEntry) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EqualWithIgnore compares to [MountInfoEntry] values, ignoring fields that
|
||||||
|
// compare equal to ignore.
|
||||||
func (e *MountInfoEntry) EqualWithIgnore(want *MountInfoEntry, ignore string) bool {
|
func (e *MountInfoEntry) EqualWithIgnore(want *MountInfoEntry, ignore string) bool {
|
||||||
return (e.ID == want.ID || want.ID == -1) &&
|
return (e.ID == want.ID || want.ID == -1) &&
|
||||||
(e.Parent == want.Parent || want.Parent == -1) &&
|
(e.Parent == want.Parent || want.Parent == -1) &&
|
||||||
@@ -284,6 +292,8 @@ func (e *MountInfoEntry) EqualWithIgnore(want *MountInfoEntry, ignore string) bo
|
|||||||
(e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore)
|
(e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a user-facing representation of a [MountInfoEntry]. It fits
|
||||||
|
// roughly into the mountinfo format, but without mangling.
|
||||||
func (e *MountInfoEntry) String() string {
|
func (e *MountInfoEntry) String() string {
|
||||||
return fmt.Sprintf("%d %d %d:%d %s %s %s %s %s %s %s",
|
return fmt.Sprintf("%d %d %d:%d %s %s %s %s %s %s %s",
|
||||||
e.ID, e.Parent, e.Devno[0], e.Devno[1], e.Root, e.Target, e.VfsOptstr,
|
e.ID, e.Parent, e.Devno[0], e.Devno[1], e.Root, e.Target, e.VfsOptstr,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UnfoldTargetError is a pathname that never appeared in a mount hierarchy.
|
||||||
type UnfoldTargetError string
|
type UnfoldTargetError string
|
||||||
|
|
||||||
func (e UnfoldTargetError) Error() string {
|
func (e UnfoldTargetError) Error() string {
|
||||||
@@ -27,6 +28,7 @@ func (n *MountInfoNode) Collective() iter.Seq[*MountInfoNode] {
|
|||||||
return func(yield func(*MountInfoNode) bool) { n.visit(yield) }
|
return func(yield func(*MountInfoNode) bool) { n.visit(yield) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// visit recursively visits all visible mountinfo nodes.
|
||||||
func (n *MountInfoNode) visit(yield func(*MountInfoNode) bool) bool {
|
func (n *MountInfoNode) visit(yield func(*MountInfoNode) bool) bool {
|
||||||
if !n.Covered && !yield(n) {
|
if !n.Covered && !yield(n) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
4
dist/release.sh
vendored
4
dist/release.sh
vendored
@@ -13,7 +13,7 @@ echo
|
|||||||
echo '# Building hakurei.'
|
echo '# Building hakurei.'
|
||||||
go generate ./...
|
go generate ./...
|
||||||
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
|
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
|
||||||
-buildid= -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=/usr/bin/hakurei
|
-X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
|
||||||
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
|
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
|
||||||
@@ -21,7 +21,7 @@ go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
|
|||||||
echo
|
echo
|
||||||
|
|
||||||
echo '# Testing hakurei.'
|
echo '# Testing hakurei.'
|
||||||
go test -ldflags='-buildid= -extldflags=-static' ./...
|
go test -ldflags='-buildid= -linkmode external -extldflags=-static' ./...
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo '# Creating distribution.'
|
echo '# Creating distribution.'
|
||||||
|
|||||||
12
flake.lock
generated
12
flake.lock
generated
@@ -7,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1765384171,
|
"lastModified": 1772985280,
|
||||||
"narHash": "sha256-FuFtkJrW1Z7u+3lhzPRau69E0CNjADku1mLQQflUORo=",
|
"narHash": "sha256-FdrNykOoY9VStevU4zjSUdvsL9SzJTcXt4omdEDZDLk=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "44777152652bc9eacf8876976fa72cc77ca8b9d8",
|
"rev": "8f736f007139d7f70752657dff6a401a585d6cbc",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -23,11 +23,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1765311797,
|
"lastModified": 1772822230,
|
||||||
"narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=",
|
"narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b",
|
"rev": "71caefce12ba78d84fe618cf61644dce01cf3a96",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
27
flake.nix
27
flake.nix
@@ -29,20 +29,6 @@
|
|||||||
{
|
{
|
||||||
nixosModules.hakurei = import ./nixos.nix self.packages;
|
nixosModules.hakurei = import ./nixos.nix self.packages;
|
||||||
|
|
||||||
buildPackage = forAllSystems (
|
|
||||||
system:
|
|
||||||
nixpkgsFor.${system}.callPackage (
|
|
||||||
import ./cmd/hpkg/build.nix {
|
|
||||||
inherit
|
|
||||||
nixpkgsFor
|
|
||||||
system
|
|
||||||
nixpkgs
|
|
||||||
home-manager
|
|
||||||
;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
checks = forAllSystems (
|
checks = forAllSystems (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
@@ -71,8 +57,6 @@
|
|||||||
|
|
||||||
sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
|
sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
|
||||||
|
|
||||||
hpkg = callPackage ./cmd/hpkg/test { inherit system self; };
|
|
||||||
|
|
||||||
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
|
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
|
||||||
cd ${./.}
|
cd ${./.}
|
||||||
|
|
||||||
@@ -115,7 +99,7 @@
|
|||||||
hakurei = pkgs.pkgsStatic.callPackage ./package.nix {
|
hakurei = pkgs.pkgsStatic.callPackage ./package.nix {
|
||||||
inherit (pkgs)
|
inherit (pkgs)
|
||||||
# passthru.buildInputs
|
# passthru.buildInputs
|
||||||
go
|
go_1_26
|
||||||
clang
|
clang
|
||||||
|
|
||||||
# nativeBuildInputs
|
# nativeBuildInputs
|
||||||
@@ -127,11 +111,6 @@
|
|||||||
glibc
|
glibc
|
||||||
xdg-dbus-proxy
|
xdg-dbus-proxy
|
||||||
|
|
||||||
# hpkg
|
|
||||||
zstd
|
|
||||||
gnutar
|
|
||||||
coreutils
|
|
||||||
|
|
||||||
# for check
|
# for check
|
||||||
util-linux
|
util-linux
|
||||||
nettools
|
nettools
|
||||||
@@ -203,7 +182,7 @@
|
|||||||
let
|
let
|
||||||
# this is used for interactive vm testing during development, where tests might be broken
|
# this is used for interactive vm testing during development, where tests might be broken
|
||||||
package = self.packages.${pkgs.stdenv.hostPlatform.system}.hakurei.override {
|
package = self.packages.${pkgs.stdenv.hostPlatform.system}.hakurei.override {
|
||||||
buildGoModule = previousArgs: pkgs.pkgsStatic.buildGoModule (previousArgs // { doCheck = false; });
|
buildGo126Module = previousArgs: pkgs.pkgsStatic.buildGo126Module (previousArgs // { doCheck = false; });
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -219,7 +198,7 @@
|
|||||||
./test/interactive/trace.nix
|
./test/interactive/trace.nix
|
||||||
|
|
||||||
self.nixosModules.hakurei
|
self.nixosModules.hakurei
|
||||||
self.inputs.home-manager.nixosModules.home-manager
|
home-manager.nixosModules.home-manager
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|||||||
0
internal/azalea/azalea.bnf
Normal file
0
internal/azalea/azalea.bnf
Normal file
69
internal/azalea/azalea.go
Normal file
69
internal/azalea/azalea.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
//go:generate gocc -a azalea.bnf
|
||||||
|
package azalea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
Generator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewParser(gen Generator) *Parser {
|
||||||
|
return &Parser{
|
||||||
|
Generator: gen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (p Parser) Initialise() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Parser) Consume(ns string, file io.Reader) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsumeDir walks a directory and consumes all Azalea source files within it and all its subdirectories, as long as they end with the .az extension.
|
||||||
|
func (p Parser) ConsumeDir(dir *check.Absolute) error {
|
||||||
|
ds := dir.String()
|
||||||
|
return filepath.WalkDir(ds, func(path string, d fs.DirEntry, err error) (e error) {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.IsDir() || !strings.HasSuffix(d.Name(), ".az") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rel, e := filepath.Rel(ds, path)
|
||||||
|
ns := strings.TrimSuffix(rel, ".az")
|
||||||
|
f, e := os.Open(path)
|
||||||
|
return p.Consume(ns, f)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsumeAll consumes all provided readers as Azalea source code, each given the namespace `r%d` where `%d` is the index of the reader in the provided arguments.
|
||||||
|
func (p Parser) ConsumeAll(in ...io.Reader) error {
|
||||||
|
for i, r := range in {
|
||||||
|
err := p.Consume("r"+strconv.FormatInt(int64(i), 10), r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsumeStrings consumes all provided strings as Azalea source code, each given the namespace `s%d` where `%d` is the index of the string in the provided arugments.
|
||||||
|
func (p Parser) ConsumeStrings(in ...string) error {
|
||||||
|
for i, s := range in {
|
||||||
|
err := p.Consume("s"+strconv.FormatInt(int64(i), 10), strings.NewReader(s))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
36
internal/azalea/generator.go
Normal file
36
internal/azalea/generator.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package azalea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Generator interface {
|
||||||
|
Finalise() (error, io.Writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
type JsonGenerator struct {
|
||||||
|
t any
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJsonGenerator[T any]() JsonGenerator {
|
||||||
|
t := new(T)
|
||||||
|
|
||||||
|
return JsonGenerator{
|
||||||
|
t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonGenerator) Finalise() (error, io.Writer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type PkgIRGenerator struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPkgIRGenerator() PkgIRGenerator {
|
||||||
|
return PkgIRGenerator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PkgIRGenerator) Finalise() (error, io.Writer) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
package filelock
|
package filelock
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,10 +73,3 @@ func (lt lockType) String() string {
|
|||||||
return "Unlock"
|
return "Unlock"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNotSupported returns a boolean indicating whether the error is known to
|
|
||||||
// report that a function is not supported (possibly for a specific input).
|
|
||||||
// It is satisfied by errors.ErrUnsupported as well as some syscall errors.
|
|
||||||
func IsNotSupported(err error) bool {
|
|
||||||
return errors.Is(err, errors.ErrUnsupported)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/internal/lockedfile/internal/filelock"
|
"hakurei.app/internal/lockedfile/internal/filelock"
|
||||||
"hakurei.app/internal/lockedfile/internal/testexec"
|
"hakurei.app/internal/lockedfile/internal/testexec"
|
||||||
)
|
)
|
||||||
@@ -197,7 +197,7 @@ func TestLockNotDroppedByExecCommand(t *testing.T) {
|
|||||||
// Some kinds of file locks are dropped when a duplicated or forked file
|
// Some kinds of file locks are dropped when a duplicated or forked file
|
||||||
// descriptor is unlocked. Double-check that the approach used by os/exec does
|
// descriptor is unlocked. Double-check that the approach used by os/exec does
|
||||||
// not accidentally drop locks.
|
// not accidentally drop locks.
|
||||||
cmd := testexec.CommandContext(t, t.Context(), container.MustExecutable(nil), "-test.run=^$")
|
cmd := testexec.CommandContext(t, t.Context(), fhs.ProcSelfExe, "-test.run=^$")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("exec failed: %v", err)
|
t.Fatalf("exec failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,11 @@ func (f *File) Close() error {
|
|||||||
|
|
||||||
err := closeFile(f.osFile.File)
|
err := closeFile(f.osFile.File)
|
||||||
f.cleanup.Stop()
|
f.cleanup.Stop()
|
||||||
|
// f may be dead at the moment after we access f.cleanup,
|
||||||
|
// so the cleanup can fire before Stop completes. Keep f
|
||||||
|
// alive while we call Stop. See the documentation for
|
||||||
|
// runtime.Cleanup.Stop.
|
||||||
|
runtime.KeepAlive(f)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/internal/lockedfile"
|
"hakurei.app/internal/lockedfile"
|
||||||
"hakurei.app/internal/lockedfile/internal/testexec"
|
"hakurei.app/internal/lockedfile/internal/testexec"
|
||||||
)
|
)
|
||||||
@@ -215,7 +215,7 @@ func TestSpuriousEDEADLK(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := testexec.CommandContext(t, t.Context(), container.MustExecutable(nil), "-test.run=^"+t.Name()+"$")
|
cmd := testexec.CommandContext(t, t.Context(), fhs.ProcSelfExe, "-test.run=^"+t.Name()+"$")
|
||||||
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", dirVar, dir))
|
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", dirVar, dir))
|
||||||
|
|
||||||
qDone := make(chan struct{})
|
qDone := make(chan struct{})
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AbsWork is the container pathname [CureContext.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/")
|
||||||
|
|
||||||
// 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
|
||||||
@@ -39,22 +39,23 @@ type ExecPath struct {
|
|||||||
W bool
|
W bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// layers returns pathnames collected from A deduplicated by checksum.
|
// SchedPolicy is the [container] scheduling policy.
|
||||||
func (p *ExecPath) layers(f *FContext) []*check.Absolute {
|
var SchedPolicy container.SchedPolicy
|
||||||
msg := f.GetMessage()
|
|
||||||
|
|
||||||
layers := make([]*check.Absolute, 0, len(p.A))
|
// PromoteLayers returns artifacts with identical-by-content layers promoted to
|
||||||
checksums := make(map[unique.Handle[Checksum]]struct{}, len(p.A))
|
// the highest priority instance, as if mounted via [ExecPath].
|
||||||
for i := range p.A {
|
func PromoteLayers(
|
||||||
d := p.A[len(p.A)-1-i]
|
artifacts []Artifact,
|
||||||
pathname, checksum := f.GetArtifact(d)
|
getArtifact func(Artifact) (*check.Absolute, unique.Handle[Checksum]),
|
||||||
|
report func(i int, d Artifact),
|
||||||
|
) []*check.Absolute {
|
||||||
|
layers := make([]*check.Absolute, 0, len(artifacts))
|
||||||
|
checksums := make(map[unique.Handle[Checksum]]struct{}, len(artifacts))
|
||||||
|
for i := range artifacts {
|
||||||
|
d := artifacts[len(artifacts)-1-i]
|
||||||
|
pathname, checksum := getArtifact(d)
|
||||||
if _, ok := checksums[checksum]; ok {
|
if _, ok := checksums[checksum]; ok {
|
||||||
if msg.IsVerbose() {
|
report(len(artifacts)-1-i, d)
|
||||||
msg.Verbosef(
|
|
||||||
"promoted layer %d as %s",
|
|
||||||
len(p.A)-1-i, reportName(d, f.cache.Ident(d)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
checksums[checksum] = struct{}{}
|
checksums[checksum] = struct{}{}
|
||||||
@@ -64,6 +65,19 @@ func (p *ExecPath) layers(f *FContext) []*check.Absolute {
|
|||||||
return layers
|
return layers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// layers returns pathnames collected from A deduplicated via [PromoteLayers].
|
||||||
|
func (p *ExecPath) layers(f *FContext) []*check.Absolute {
|
||||||
|
msg := f.GetMessage()
|
||||||
|
return PromoteLayers(p.A, f.GetArtifact, func(i int, d Artifact) {
|
||||||
|
if msg.IsVerbose() {
|
||||||
|
msg.Verbosef(
|
||||||
|
"promoted layer %d as %s",
|
||||||
|
i, reportName(d, f.cache.Ident(d)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Path returns a populated [ExecPath].
|
// Path returns a populated [ExecPath].
|
||||||
func Path(pathname *check.Absolute, writable bool, a ...Artifact) ExecPath {
|
func Path(pathname *check.Absolute, writable bool, a ...Artifact) ExecPath {
|
||||||
return ExecPath{pathname, a, writable}
|
return ExecPath{pathname, a, writable}
|
||||||
@@ -347,6 +361,7 @@ const (
|
|||||||
// scanVerbose prefixes program output for a verbose [message.Msg].
|
// scanVerbose prefixes program output for a verbose [message.Msg].
|
||||||
func scanVerbose(
|
func scanVerbose(
|
||||||
msg message.Msg,
|
msg message.Msg,
|
||||||
|
cancel context.CancelFunc,
|
||||||
done chan<- struct{},
|
done chan<- struct{},
|
||||||
prefix string,
|
prefix string,
|
||||||
r io.Reader,
|
r io.Reader,
|
||||||
@@ -361,10 +376,15 @@ func scanVerbose(
|
|||||||
msg.Verbose(prefix, s.Text())
|
msg.Verbose(prefix, s.Text())
|
||||||
}
|
}
|
||||||
if err := s.Err(); err != nil && !errors.Is(err, os.ErrClosed) {
|
if err := s.Err(); err != nil && !errors.Is(err, os.ErrClosed) {
|
||||||
|
cancel()
|
||||||
msg.Verbose("*"+prefix, err)
|
msg.Verbose("*"+prefix, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SeccompPresets is the [seccomp] presets used by exec artifacts.
|
||||||
|
const SeccompPresets = std.PresetStrict &
|
||||||
|
^(std.PresetDenyNS | std.PresetDenyDevel)
|
||||||
|
|
||||||
// cure is like Cure but allows optional host net namespace. This is used for
|
// cure is like Cure but allows optional host net namespace. This is used for
|
||||||
// the [KnownChecksum] variant where networking is allowed.
|
// the [KnownChecksum] variant where networking is allowed.
|
||||||
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
||||||
@@ -388,15 +408,22 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
|
|
||||||
z := container.New(ctx, f.GetMessage())
|
z := container.New(ctx, f.GetMessage())
|
||||||
z.WaitDelay = execWaitDelay
|
z.WaitDelay = execWaitDelay
|
||||||
z.SeccompPresets |= std.PresetStrict & ^std.PresetDenyNS
|
z.SeccompPresets = SeccompPresets
|
||||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||||
z.ParentPerm = 0700
|
z.ParentPerm = 0700
|
||||||
z.HostNet = hostNet
|
z.HostNet = hostNet
|
||||||
z.Hostname = "cure"
|
z.Hostname = "cure"
|
||||||
|
z.SchedPolicy = SchedPolicy
|
||||||
if z.HostNet {
|
if z.HostNet {
|
||||||
z.Hostname = "cure-net"
|
z.Hostname = "cure-net"
|
||||||
}
|
}
|
||||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
||||||
|
|
||||||
|
var status io.Writer
|
||||||
|
if status, err = f.GetStatusWriter(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if msg := f.GetMessage(); msg.IsVerbose() {
|
if msg := f.GetMessage(); msg.IsVerbose() {
|
||||||
var stdout, stderr io.ReadCloser
|
var stdout, stderr io.ReadCloser
|
||||||
if stdout, err = z.StdoutPipe(); err != nil {
|
if stdout, err = z.StdoutPipe(); err != nil {
|
||||||
@@ -413,10 +440,26 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
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{})
|
||||||
go scanVerbose(msg, stdoutDone, "("+a.name+":1)", stdout)
|
go scanVerbose(
|
||||||
go scanVerbose(msg, stderrDone, "("+a.name+":2)", stderr)
|
msg, cancel, stdoutDone,
|
||||||
defer func() { <-stdoutDone; <-stderrDone }()
|
"("+a.name+":1)",
|
||||||
|
io.TeeReader(brStdout, status),
|
||||||
|
)
|
||||||
|
go scanVerbose(
|
||||||
|
msg, cancel, stderrDone,
|
||||||
|
"("+a.name+":2)",
|
||||||
|
io.TeeReader(brStderr, status),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
<-stdoutDone
|
||||||
|
<-stderrDone
|
||||||
|
f.cache.putReader(brStdout)
|
||||||
|
f.cache.putReader(brStderr)
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
z.Stdout, z.Stderr = status, status
|
||||||
}
|
}
|
||||||
|
|
||||||
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
|
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func (i *IContext) WriteUint32(v uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// irMaxStringLength is the maximum acceptable wire size of [IRKindString].
|
// irMaxStringLength is the maximum acceptable wire size of [IRKindString].
|
||||||
const irMaxStringLength = 1 << 20
|
const irMaxStringLength = 1 << 24
|
||||||
|
|
||||||
// IRStringError is a string value too big to encode in IR.
|
// IRStringError is a string value too big to encode in IR.
|
||||||
type IRStringError string
|
type IRStringError string
|
||||||
@@ -310,6 +310,13 @@ type (
|
|||||||
// verbose logging is enabled. Artifacts may only depend on artifacts
|
// verbose logging is enabled. Artifacts may only depend on artifacts
|
||||||
// previously described in the IR stream.
|
// previously described in the IR stream.
|
||||||
//
|
//
|
||||||
|
// IRDecoder rejects an IR stream on the first decoding error, it does not
|
||||||
|
// check against nonzero reserved ancillary data or incorrectly ordered or
|
||||||
|
// redundant unstructured dependencies. An invalid IR stream as such will
|
||||||
|
// yield [Artifact] values with identifiers disagreeing with those computed
|
||||||
|
// by IRDecoder. For this reason, IRDecoder does not access the ident cache
|
||||||
|
// to avoid putting [Cache] into an inconsistent state.
|
||||||
|
//
|
||||||
// Methods of IRDecoder are not safe for concurrent use.
|
// Methods of IRDecoder are not safe for concurrent use.
|
||||||
IRDecoder struct {
|
IRDecoder struct {
|
||||||
// Address of underlying [Cache], must not be exposed directly.
|
// Address of underlying [Cache], must not be exposed directly.
|
||||||
|
|||||||
@@ -28,15 +28,21 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
|
"hakurei.app/internal/info"
|
||||||
"hakurei.app/internal/lockedfile"
|
"hakurei.app/internal/lockedfile"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// programName is the string identifying this build system.
|
||||||
|
programName = "internal/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// A Checksum is a SHA-384 checksum computed for a cured [Artifact].
|
// A Checksum is a SHA-384 checksum computed for a cured [Artifact].
|
||||||
Checksum = [sha512.Size384]byte
|
Checksum = [sha512.Size384]byte
|
||||||
|
|
||||||
// An ID is a unique identifier returned by [Artifact.ID]. This value must
|
// An ID is a unique identifier returned by [KnownIdent.ID]. This value must
|
||||||
// be deterministically determined ahead of time.
|
// be deterministically determined ahead of time.
|
||||||
ID Checksum
|
ID Checksum
|
||||||
)
|
)
|
||||||
@@ -65,18 +71,75 @@ func MustDecode(s string) (checksum Checksum) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// common holds elements and receives methods shared between different contexts.
|
||||||
|
type common struct {
|
||||||
|
// Address of underlying [Cache], should be zeroed or made unusable after
|
||||||
|
// Cure returns and must not be exposed directly.
|
||||||
|
cache *Cache
|
||||||
|
}
|
||||||
|
|
||||||
// TContext is passed to [TrivialArtifact.Cure] and provides information and
|
// TContext is passed to [TrivialArtifact.Cure] and provides information and
|
||||||
// methods required for curing the [TrivialArtifact].
|
// methods required for curing the [TrivialArtifact].
|
||||||
//
|
//
|
||||||
// Methods of TContext are safe for concurrent use. TContext is valid
|
// Methods of TContext are safe for concurrent use. TContext is valid
|
||||||
// until [TrivialArtifact.Cure] returns.
|
// until [TrivialArtifact.Cure] returns.
|
||||||
type TContext struct {
|
type TContext struct {
|
||||||
// Address of underlying [Cache], should be zeroed or made unusable after
|
|
||||||
// [TrivialArtifact.Cure] returns and must not be exposed directly.
|
|
||||||
cache *Cache
|
|
||||||
|
|
||||||
// Populated during [Cache.Cure].
|
// Populated during [Cache.Cure].
|
||||||
work, temp *check.Absolute
|
work, temp *check.Absolute
|
||||||
|
|
||||||
|
// Target [Artifact] encoded identifier.
|
||||||
|
ids string
|
||||||
|
// Pathname status was created at.
|
||||||
|
statusPath *check.Absolute
|
||||||
|
// File statusHeader and logs are written to.
|
||||||
|
status *os.File
|
||||||
|
// Error value during prepareStatus.
|
||||||
|
statusErr error
|
||||||
|
|
||||||
|
common
|
||||||
|
}
|
||||||
|
|
||||||
|
// statusHeader is the header written to all status files in dirStatus.
|
||||||
|
var statusHeader = func() string {
|
||||||
|
s := programName
|
||||||
|
if v := info.Version(); v != info.FallbackVersion {
|
||||||
|
s += " " + v
|
||||||
|
}
|
||||||
|
s += " (" + runtime.GOARCH + ")"
|
||||||
|
if name, err := os.Hostname(); err == nil {
|
||||||
|
s += " on " + name
|
||||||
|
}
|
||||||
|
s += "\n\n"
|
||||||
|
return s
|
||||||
|
}()
|
||||||
|
|
||||||
|
// prepareStatus initialises the status file once.
|
||||||
|
func (t *TContext) prepareStatus() error {
|
||||||
|
if t.statusPath != nil || t.status != nil {
|
||||||
|
return t.statusErr
|
||||||
|
}
|
||||||
|
|
||||||
|
t.statusPath = t.cache.base.Append(
|
||||||
|
dirStatus,
|
||||||
|
t.ids,
|
||||||
|
)
|
||||||
|
if t.status, t.statusErr = os.OpenFile(
|
||||||
|
t.statusPath.String(),
|
||||||
|
syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY,
|
||||||
|
0400,
|
||||||
|
); t.statusErr != nil {
|
||||||
|
return t.statusErr
|
||||||
|
}
|
||||||
|
|
||||||
|
_, t.statusErr = t.status.WriteString(statusHeader)
|
||||||
|
return t.statusErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStatusWriter returns a [io.Writer] for build logs. The caller must not
|
||||||
|
// seek this writer before the position it was first returned in.
|
||||||
|
func (t *TContext) GetStatusWriter() (io.Writer, error) {
|
||||||
|
err := t.prepareStatus()
|
||||||
|
return t.status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// destroy destroys the temporary directory and joins its errors with the error
|
// destroy destroys the temporary directory and joins its errors with the error
|
||||||
@@ -84,12 +147,15 @@ type TContext struct {
|
|||||||
// directory is removed similarly. [Cache] is responsible for making sure work
|
// directory is removed similarly. [Cache] is responsible for making sure work
|
||||||
// is never left behind for a successful [Cache.Cure].
|
// is never left behind for a successful [Cache.Cure].
|
||||||
//
|
//
|
||||||
|
// If implementation had requested status, it is closed with error joined with
|
||||||
|
// the error referred to by errP. If the error referred to by errP is non-nil,
|
||||||
|
// the status file is removed from the filesystem.
|
||||||
|
//
|
||||||
// destroy must be deferred by [Cache.Cure] if [TContext] is passed to any Cure
|
// destroy must be deferred by [Cache.Cure] if [TContext] is passed to any Cure
|
||||||
// implementation. It should not be called prior to that point.
|
// implementation. It should not be called prior to that point.
|
||||||
func (t *TContext) destroy(errP *error) {
|
func (t *TContext) destroy(errP *error) {
|
||||||
if chmodErr, removeErr := removeAll(t.temp); chmodErr != nil || removeErr != nil {
|
if chmodErr, removeErr := removeAll(t.temp); chmodErr != nil || removeErr != nil {
|
||||||
*errP = errors.Join(*errP, chmodErr, removeErr)
|
*errP = errors.Join(*errP, chmodErr, removeErr)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *errP != nil {
|
if *errP != nil {
|
||||||
@@ -97,17 +163,31 @@ func (t *TContext) destroy(errP *error) {
|
|||||||
if chmodErr != nil || removeErr != nil {
|
if chmodErr != nil || removeErr != nil {
|
||||||
*errP = errors.Join(*errP, chmodErr, removeErr)
|
*errP = errors.Join(*errP, chmodErr, removeErr)
|
||||||
} else if errors.Is(*errP, os.ErrExist) {
|
} else if errors.Is(*errP, os.ErrExist) {
|
||||||
|
var linkError *os.LinkError
|
||||||
|
if errors.As(*errP, &linkError) && linkError != nil &&
|
||||||
|
linkError.Op == "rename" {
|
||||||
// two artifacts may be backed by the same file
|
// two artifacts may be backed by the same file
|
||||||
*errP = nil
|
*errP = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.status != nil {
|
||||||
|
if err := t.status.Close(); err != nil {
|
||||||
|
*errP = errors.Join(*errP, err)
|
||||||
|
}
|
||||||
|
if *errP != nil {
|
||||||
|
*errP = errors.Join(*errP, os.Remove(t.statusPath.String()))
|
||||||
|
}
|
||||||
|
t.status = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the underlying [context.Context].
|
// Unwrap returns the underlying [context.Context].
|
||||||
func (t *TContext) Unwrap() context.Context { return t.cache.ctx }
|
func (c *common) Unwrap() context.Context { return c.cache.ctx }
|
||||||
|
|
||||||
// GetMessage returns [message.Msg] held by the underlying [Cache].
|
// GetMessage returns [message.Msg] held by the underlying [Cache].
|
||||||
func (t *TContext) GetMessage() message.Msg { return t.cache.msg }
|
func (c *common) GetMessage() message.Msg { return c.cache.msg }
|
||||||
|
|
||||||
// GetWorkDir returns a pathname to a directory which [Artifact] is expected to
|
// GetWorkDir returns a pathname to a directory which [Artifact] is expected to
|
||||||
// write its output to. This is not the final resting place of the [Artifact]
|
// write its output to. This is not the final resting place of the [Artifact]
|
||||||
@@ -126,13 +206,13 @@ func (t *TContext) GetTempDir() *check.Absolute { return t.temp }
|
|||||||
// If err is nil, the caller must close the resulting [io.ReadCloser] and return
|
// If err is nil, the caller must close the resulting [io.ReadCloser] and return
|
||||||
// its error, if any. Failure to read r to EOF may result in a spurious
|
// its error, if any. Failure to read r to EOF may result in a spurious
|
||||||
// [ChecksumMismatchError], or the underlying implementation may block on Close.
|
// [ChecksumMismatchError], or the underlying implementation may block on Close.
|
||||||
func (t *TContext) Open(a Artifact) (r io.ReadCloser, err error) {
|
func (c *common) Open(a Artifact) (r io.ReadCloser, err error) {
|
||||||
if f, ok := a.(FileArtifact); ok {
|
if f, ok := a.(FileArtifact); ok {
|
||||||
return t.cache.openFile(f)
|
return c.cache.openFile(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathname *check.Absolute
|
var pathname *check.Absolute
|
||||||
if pathname, _, err = t.cache.Cure(a); err != nil {
|
if pathname, _, err = c.cache.Cure(a); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +244,7 @@ type FContext struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InvalidLookupError is the identifier of non-dependency [Artifact] looked up
|
// InvalidLookupError is the identifier of non-dependency [Artifact] looked up
|
||||||
// via [FContext.Pathname] by a misbehaving [Artifact] implementation.
|
// via [FContext.GetArtifact] by a misbehaving [Artifact] implementation.
|
||||||
type InvalidLookupError ID
|
type InvalidLookupError ID
|
||||||
|
|
||||||
func (e InvalidLookupError) Error() string {
|
func (e InvalidLookupError) Error() string {
|
||||||
@@ -191,14 +271,7 @@ func (f *FContext) GetArtifact(a Artifact) (
|
|||||||
//
|
//
|
||||||
// Methods of RContext are safe for concurrent use. RContext is valid
|
// Methods of RContext are safe for concurrent use. RContext is valid
|
||||||
// until [FileArtifact.Cure] returns.
|
// until [FileArtifact.Cure] returns.
|
||||||
type RContext struct {
|
type RContext struct{ common }
|
||||||
// Address of underlying [Cache], should be zeroed or made unusable after
|
|
||||||
// [FileArtifact.Cure] returns and must not be exposed directly.
|
|
||||||
cache *Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwrap returns the underlying [context.Context].
|
|
||||||
func (r *RContext) Unwrap() context.Context { return r.cache.ctx }
|
|
||||||
|
|
||||||
// An Artifact is a read-only reference to a piece of data that may be created
|
// An Artifact is a read-only reference to a piece of data that may be created
|
||||||
// deterministically but might not currently be available in memory or on the
|
// deterministically but might not currently be available in memory or on the
|
||||||
@@ -377,6 +450,9 @@ const (
|
|||||||
// dirChecksum is the directory name appended to Cache.base for storing
|
// dirChecksum is the directory name appended to Cache.base for storing
|
||||||
// artifacts named after their [Checksum].
|
// artifacts named after their [Checksum].
|
||||||
dirChecksum = "checksum"
|
dirChecksum = "checksum"
|
||||||
|
// dirStatus is the directory name appended to Cache.base for storing
|
||||||
|
// artifact metadata and logs named after their [ID].
|
||||||
|
dirStatus = "status"
|
||||||
|
|
||||||
// dirWork is the directory name appended to Cache.base for working
|
// dirWork is the directory name appended to Cache.base for working
|
||||||
// pathnames set up during [Cache.Cure].
|
// pathnames set up during [Cache.Cure].
|
||||||
@@ -466,7 +542,7 @@ type Cache struct {
|
|||||||
// Synchronises entry into exclusive artifacts for the cure method.
|
// Synchronises entry into exclusive artifacts for the cure method.
|
||||||
exclMu sync.Mutex
|
exclMu sync.Mutex
|
||||||
// Buffered I/O free list, must not be accessed directly.
|
// Buffered I/O free list, must not be accessed directly.
|
||||||
bufioPool sync.Pool
|
brPool, bwPool sync.Pool
|
||||||
|
|
||||||
// Unlocks the on-filesystem cache. Must only be called from Close.
|
// Unlocks the on-filesystem cache. Must only be called from Close.
|
||||||
unlock func()
|
unlock func()
|
||||||
@@ -548,6 +624,26 @@ func (c *Cache) unsafeIdent(a Artifact, encodeKind bool) (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getReader is like [bufio.NewReader] but for brPool.
|
||||||
|
func (c *Cache) getReader(r io.Reader) *bufio.Reader {
|
||||||
|
br := c.brPool.Get().(*bufio.Reader)
|
||||||
|
br.Reset(r)
|
||||||
|
return br
|
||||||
|
}
|
||||||
|
|
||||||
|
// putReader adds br to brPool.
|
||||||
|
func (c *Cache) putReader(br *bufio.Reader) { c.brPool.Put(br) }
|
||||||
|
|
||||||
|
// getWriter is like [bufio.NewWriter] but for bwPool.
|
||||||
|
func (c *Cache) getWriter(w io.Writer) *bufio.Writer {
|
||||||
|
bw := c.bwPool.Get().(*bufio.Writer)
|
||||||
|
bw.Reset(w)
|
||||||
|
return bw
|
||||||
|
}
|
||||||
|
|
||||||
|
// putWriter adds bw to bwPool.
|
||||||
|
func (c *Cache) putWriter(bw *bufio.Writer) { c.bwPool.Put(bw) }
|
||||||
|
|
||||||
// A ChecksumMismatchError describes an [Artifact] with unexpected content.
|
// A ChecksumMismatchError describes an [Artifact] with unexpected content.
|
||||||
type ChecksumMismatchError struct {
|
type ChecksumMismatchError struct {
|
||||||
// Actual and expected checksums.
|
// Actual and expected checksums.
|
||||||
@@ -569,6 +665,9 @@ type ScrubError struct {
|
|||||||
// Dangling identifier symlinks. This can happen if the content-addressed
|
// Dangling identifier symlinks. This can happen if the content-addressed
|
||||||
// entry was removed while scrubbing due to a checksum mismatch.
|
// entry was removed while scrubbing due to a checksum mismatch.
|
||||||
DanglingIdentifiers []ID
|
DanglingIdentifiers []ID
|
||||||
|
// Dangling status files. This can happen if a dangling status symlink was
|
||||||
|
// removed while scrubbing.
|
||||||
|
DanglingStatus []ID
|
||||||
// Miscellaneous errors, including [os.ReadDir] on checksum and identifier
|
// Miscellaneous errors, including [os.ReadDir] on checksum and identifier
|
||||||
// directories, [Decode] on entry names and [os.RemoveAll] on inconsistent
|
// directories, [Decode] on entry names and [os.RemoveAll] on inconsistent
|
||||||
// entries.
|
// entries.
|
||||||
@@ -620,6 +719,13 @@ func (e *ScrubError) Error() string {
|
|||||||
}
|
}
|
||||||
segments = append(segments, s)
|
segments = append(segments, s)
|
||||||
}
|
}
|
||||||
|
if len(e.DanglingStatus) > 0 {
|
||||||
|
s := "dangling status:\n"
|
||||||
|
for _, id := range e.DanglingStatus {
|
||||||
|
s += Encode(id) + "\n"
|
||||||
|
}
|
||||||
|
segments = append(segments, s)
|
||||||
|
}
|
||||||
if len(e.Errs) > 0 {
|
if len(e.Errs) > 0 {
|
||||||
s := "errors during scrub:\n"
|
s := "errors during scrub:\n"
|
||||||
for pathname, errs := range e.errs {
|
for pathname, errs := range e.errs {
|
||||||
@@ -798,6 +904,36 @@ func (c *Cache) Scrub(checks int) error {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dir = c.base.Append(dirStatus)
|
||||||
|
if entries, readdirErr := os.ReadDir(dir.String()); readdirErr != nil {
|
||||||
|
if !errors.Is(readdirErr, os.ErrNotExist) {
|
||||||
|
addErr(dir, readdirErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wg.Add(len(entries))
|
||||||
|
for _, ent := range entries {
|
||||||
|
w <- checkEntry{ent, func(ent os.DirEntry, want *Checksum) bool {
|
||||||
|
got := p.Get().(*Checksum)
|
||||||
|
defer p.Put(got)
|
||||||
|
|
||||||
|
if _, err := os.Stat(c.base.Append(
|
||||||
|
dirIdentifier,
|
||||||
|
ent.Name(),
|
||||||
|
).String()); err != nil {
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
addErr(dir.Append(ent.Name()), err)
|
||||||
|
}
|
||||||
|
seMu.Lock()
|
||||||
|
se.DanglingStatus = append(se.DanglingStatus, *want)
|
||||||
|
seMu.Unlock()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
if len(c.identPending) > 0 {
|
if len(c.identPending) > 0 {
|
||||||
addErr(c.base, errors.New(
|
addErr(c.base, errors.New(
|
||||||
"scrub began with pending artifacts",
|
"scrub began with pending artifacts",
|
||||||
@@ -828,6 +964,7 @@ func (c *Cache) Scrub(checks int) error {
|
|||||||
|
|
||||||
if len(se.ChecksumMismatches) > 0 ||
|
if len(se.ChecksumMismatches) > 0 ||
|
||||||
len(se.DanglingIdentifiers) > 0 ||
|
len(se.DanglingIdentifiers) > 0 ||
|
||||||
|
len(se.DanglingStatus) > 0 ||
|
||||||
len(se.Errs) > 0 {
|
len(se.Errs) > 0 {
|
||||||
slices.SortFunc(se.ChecksumMismatches, func(a, b ChecksumMismatchError) int {
|
slices.SortFunc(se.ChecksumMismatches, func(a, b ChecksumMismatchError) int {
|
||||||
return bytes.Compare(a.Want[:], b.Want[:])
|
return bytes.Compare(a.Want[:], b.Want[:])
|
||||||
@@ -835,6 +972,9 @@ func (c *Cache) Scrub(checks int) error {
|
|||||||
slices.SortFunc(se.DanglingIdentifiers, func(a, b ID) int {
|
slices.SortFunc(se.DanglingIdentifiers, func(a, b ID) int {
|
||||||
return bytes.Compare(a[:], b[:])
|
return bytes.Compare(a[:], b[:])
|
||||||
})
|
})
|
||||||
|
slices.SortFunc(se.DanglingStatus, func(a, b ID) int {
|
||||||
|
return bytes.Compare(a[:], b[:])
|
||||||
|
})
|
||||||
return &se
|
return &se
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
@@ -925,16 +1065,17 @@ func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
|
|||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
id := c.Ident(f)
|
||||||
if c.msg.IsVerbose() {
|
if c.msg.IsVerbose() {
|
||||||
rn := reportName(f, c.Ident(f))
|
rn := reportName(f, id)
|
||||||
c.msg.Verbosef("curing %s to memory...", rn)
|
c.msg.Verbosef("curing %s in memory...", rn)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.msg.Verbosef("cured %s to memory", rn)
|
c.msg.Verbosef("opened %s for reading", rn)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
return f.Cure(&RContext{c})
|
return f.Cure(&RContext{common{c}})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1131,6 +1272,9 @@ type DependencyCureError []*CureError
|
|||||||
// unwrapM recursively expands underlying errors into a caller-supplied map.
|
// unwrapM recursively expands underlying errors into a caller-supplied map.
|
||||||
func (e *DependencyCureError) unwrapM(me map[unique.Handle[ID]]*CureError) {
|
func (e *DependencyCureError) unwrapM(me map[unique.Handle[ID]]*CureError) {
|
||||||
for _, err := range *e {
|
for _, err := range *e {
|
||||||
|
if _, ok := me[err.Ident]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _e, ok := err.Err.(*DependencyCureError); ok {
|
if _e, ok := err.Err.(*DependencyCureError); ok {
|
||||||
_e.unwrapM(me)
|
_e.unwrapM(me)
|
||||||
continue
|
continue
|
||||||
@@ -1214,13 +1358,6 @@ func (c *Cache) exitCure(a Artifact, curesExempt bool) {
|
|||||||
<-c.cures
|
<-c.cures
|
||||||
}
|
}
|
||||||
|
|
||||||
// getWriter is like [bufio.NewWriter] but for bufioPool.
|
|
||||||
func (c *Cache) getWriter(w io.Writer) *bufio.Writer {
|
|
||||||
bw := c.bufioPool.Get().(*bufio.Writer)
|
|
||||||
bw.Reset(w)
|
|
||||||
return bw
|
|
||||||
}
|
|
||||||
|
|
||||||
// measuredReader implements [io.ReadCloser] and measures the checksum during
|
// measuredReader implements [io.ReadCloser] and measures the checksum during
|
||||||
// Close. If the underlying reader is not read to EOF, Close blocks until all
|
// Close. If the underlying reader is not read to EOF, Close blocks until all
|
||||||
// remaining data is consumed and validated.
|
// remaining data is consumed and validated.
|
||||||
@@ -1303,9 +1440,6 @@ func (r *RContext) NewMeasuredReader(
|
|||||||
return r.cache.newMeasuredReader(rc, checksum)
|
return r.cache.newMeasuredReader(rc, checksum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// putWriter adds bw to bufioPool.
|
|
||||||
func (c *Cache) putWriter(bw *bufio.Writer) { c.bufioPool.Put(bw) }
|
|
||||||
|
|
||||||
// cure implements Cure without checking the full dependency graph.
|
// cure implements Cure without checking the full dependency graph.
|
||||||
func (c *Cache) cure(a Artifact, curesExempt bool) (
|
func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||||
pathname *check.Absolute,
|
pathname *check.Absolute,
|
||||||
@@ -1437,7 +1571,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
|||||||
if err = c.enterCure(a, curesExempt); err != nil {
|
if err = c.enterCure(a, curesExempt); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r, err = f.Cure(&RContext{c})
|
r, err = f.Cure(&RContext{common{c}})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if checksumPathname == nil || c.IsStrict() {
|
if checksumPathname == nil || c.IsStrict() {
|
||||||
h := sha512.New384()
|
h := sha512.New384()
|
||||||
@@ -1513,7 +1647,12 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t := TContext{c, c.base.Append(dirWork, ids), c.base.Append(dirTemp, ids)}
|
t := TContext{
|
||||||
|
c.base.Append(dirWork, ids),
|
||||||
|
c.base.Append(dirTemp, ids),
|
||||||
|
ids, nil, nil, nil,
|
||||||
|
common{c},
|
||||||
|
}
|
||||||
switch ca := a.(type) {
|
switch ca := a.(type) {
|
||||||
case TrivialArtifact:
|
case TrivialArtifact:
|
||||||
defer t.destroy(&err)
|
defer t.destroy(&err)
|
||||||
@@ -1651,6 +1790,18 @@ func (pending *pendingArtifactDep) cure(c *Cache) {
|
|||||||
pending.errsMu.Unlock()
|
pending.errsMu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenStatus attempts to open the status file associated to an [Artifact]. If
|
||||||
|
// err is nil, the caller must close the resulting reader.
|
||||||
|
func (c *Cache) OpenStatus(a Artifact) (r io.ReadSeekCloser, err error) {
|
||||||
|
c.identMu.RLock()
|
||||||
|
r, err = os.Open(c.base.Append(
|
||||||
|
dirStatus,
|
||||||
|
Encode(c.Ident(a).Value())).String(),
|
||||||
|
)
|
||||||
|
c.identMu.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Close cancels all pending cures and waits for them to clean up.
|
// Close cancels all pending cures and waits for them to clean up.
|
||||||
func (c *Cache) Close() {
|
func (c *Cache) Close() {
|
||||||
c.closeOnce.Do(func() {
|
c.closeOnce.Do(func() {
|
||||||
@@ -1699,6 +1850,7 @@ func open(
|
|||||||
for _, name := range []string{
|
for _, name := range []string{
|
||||||
dirIdentifier,
|
dirIdentifier,
|
||||||
dirChecksum,
|
dirChecksum,
|
||||||
|
dirStatus,
|
||||||
dirWork,
|
dirWork,
|
||||||
} {
|
} {
|
||||||
if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil &&
|
if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil &&
|
||||||
@@ -1713,13 +1865,16 @@ func open(
|
|||||||
msg: msg,
|
msg: msg,
|
||||||
base: base,
|
base: base,
|
||||||
|
|
||||||
|
identPool: sync.Pool{New: func() any { return new(extIdent) }},
|
||||||
|
|
||||||
ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]),
|
ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]),
|
||||||
identErr: make(map[unique.Handle[ID]]error),
|
identErr: make(map[unique.Handle[ID]]error),
|
||||||
identPending: make(map[unique.Handle[ID]]<-chan struct{}),
|
identPending: make(map[unique.Handle[ID]]<-chan struct{}),
|
||||||
|
|
||||||
|
brPool: sync.Pool{New: func() any { return new(bufio.Reader) }},
|
||||||
|
bwPool: sync.Pool{New: func() any { return new(bufio.Writer) }},
|
||||||
}
|
}
|
||||||
c.ctx, c.cancel = context.WithCancel(ctx)
|
c.ctx, c.cancel = context.WithCancel(ctx)
|
||||||
c.identPool.New = func() any { return new(extIdent) }
|
|
||||||
c.bufioPool.New = func() any { return new(bufio.Writer) }
|
|
||||||
|
|
||||||
if lock || !testing.Testing() {
|
if lock || !testing.Testing() {
|
||||||
if unlock, err := lockedfile.MutexAt(
|
if unlock, err := lockedfile.MutexAt(
|
||||||
|
|||||||
@@ -314,6 +314,11 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// destroy non-deterministic status files
|
||||||
|
if err := os.RemoveAll(base.Append("status").String()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
var checksum pkg.Checksum
|
var checksum pkg.Checksum
|
||||||
if err := pkg.HashDir(&checksum, base); err != nil {
|
if err := pkg.HashDir(&checksum, base); err != nil {
|
||||||
t.Fatalf("HashDir: error = %v", err)
|
t.Fatalf("HashDir: error = %v", err)
|
||||||
@@ -382,6 +387,9 @@ func cureMany(t *testing.T, c *pkg.Cache, steps []cureStep) {
|
|||||||
} else if step.pathname != ignorePathname && !pathname.Is(step.pathname) {
|
} else if step.pathname != ignorePathname && !pathname.Is(step.pathname) {
|
||||||
t.Fatalf("Cure: pathname = %q, want %q", pathname, step.pathname)
|
t.Fatalf("Cure: pathname = %q, want %q", pathname, step.pathname)
|
||||||
} else if checksum != makeChecksumH(step.checksum) {
|
} else if checksum != makeChecksumH(step.checksum) {
|
||||||
|
if checksum == (unique.Handle[pkg.Checksum]{}) {
|
||||||
|
checksum = unique.Make(pkg.Checksum{})
|
||||||
|
}
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"Cure: checksum = %s, want %s",
|
"Cure: checksum = %s, want %s",
|
||||||
pkg.Encode(checksum.Value()), pkg.Encode(step.checksum),
|
pkg.Encode(checksum.Value()), pkg.Encode(step.checksum),
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"hakurei.app/container/check"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -100,7 +99,6 @@ func (e DisallowedTypeflagError) Error() string {
|
|||||||
|
|
||||||
// Cure cures the [Artifact], producing a directory located at work.
|
// Cure cures the [Artifact], producing a directory located at work.
|
||||||
func (a *tarArtifact) Cure(t *TContext) (err error) {
|
func (a *tarArtifact) Cure(t *TContext) (err error) {
|
||||||
temp := t.GetTempDir()
|
|
||||||
var tr io.ReadCloser
|
var tr io.ReadCloser
|
||||||
if tr, err = t.Open(a.f); err != nil {
|
if tr, err = t.Open(a.f); err != nil {
|
||||||
return
|
return
|
||||||
@@ -116,7 +114,9 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
|||||||
err = closeErr
|
err = closeErr
|
||||||
}
|
}
|
||||||
}(tr)
|
}(tr)
|
||||||
tr = io.NopCloser(tr)
|
br := t.cache.getReader(tr)
|
||||||
|
defer t.cache.putReader(br)
|
||||||
|
tr = io.NopCloser(br)
|
||||||
|
|
||||||
switch a.compression {
|
switch a.compression {
|
||||||
case TarUncompressed:
|
case TarUncompressed:
|
||||||
@@ -137,14 +137,24 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type dirTargetPerm struct {
|
type dirTargetPerm struct {
|
||||||
path *check.Absolute
|
path string
|
||||||
mode fs.FileMode
|
mode fs.FileMode
|
||||||
}
|
}
|
||||||
var madeDirectories []dirTargetPerm
|
var madeDirectories []dirTargetPerm
|
||||||
|
|
||||||
if err = os.MkdirAll(temp.String(), 0700); err != nil {
|
if err = os.MkdirAll(t.GetTempDir().String(), 0700); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var root *os.Root
|
||||||
|
if root, err = os.OpenRoot(t.GetTempDir().String()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
closeErr := root.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var header *tar.Header
|
var header *tar.Header
|
||||||
r := tar.NewReader(tr)
|
r := tar.NewReader(tr)
|
||||||
@@ -158,9 +168,8 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pathname := temp.Append(header.Name)
|
|
||||||
if typeflag >= '0' && typeflag <= '9' && typeflag != tar.TypeDir {
|
if typeflag >= '0' && typeflag <= '9' && typeflag != tar.TypeDir {
|
||||||
if err = os.MkdirAll(pathname.Dir().String(), 0700); err != nil {
|
if err = root.MkdirAll(path.Dir(header.Name), 0700); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,8 +177,8 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
|||||||
switch typeflag {
|
switch typeflag {
|
||||||
case tar.TypeReg:
|
case tar.TypeReg:
|
||||||
var f *os.File
|
var f *os.File
|
||||||
if f, err = os.OpenFile(
|
if f, err = root.OpenFile(
|
||||||
pathname.String(),
|
header.Name,
|
||||||
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
|
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
|
||||||
header.FileInfo().Mode()&0500,
|
header.FileInfo().Mode()&0500,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -184,26 +193,29 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case tar.TypeLink:
|
case tar.TypeLink:
|
||||||
if err = os.Link(
|
if err = root.Link(
|
||||||
temp.Append(header.Linkname).String(),
|
header.Linkname,
|
||||||
pathname.String(),
|
header.Name,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case tar.TypeSymlink:
|
case tar.TypeSymlink:
|
||||||
if err = os.Symlink(header.Linkname, pathname.String()); err != nil {
|
if err = root.Symlink(
|
||||||
|
header.Linkname,
|
||||||
|
header.Name,
|
||||||
|
); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case tar.TypeDir:
|
case tar.TypeDir:
|
||||||
madeDirectories = append(madeDirectories, dirTargetPerm{
|
madeDirectories = append(madeDirectories, dirTargetPerm{
|
||||||
path: pathname,
|
path: header.Name,
|
||||||
mode: header.FileInfo().Mode(),
|
mode: header.FileInfo().Mode(),
|
||||||
})
|
})
|
||||||
if err = os.MkdirAll(pathname.String(), 0700); err != nil {
|
if err = root.MkdirAll(header.Name, 0700); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -220,7 +232,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
|||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, e := range madeDirectories {
|
for _, e := range madeDirectories {
|
||||||
if err = os.Chmod(e.path.String(), e.mode&0500); err != nil {
|
if err = root.Chmod(e.path, e.mode&0500); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,6 +240,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
temp := t.GetTempDir()
|
||||||
if err = os.Chmod(temp.String(), 0700); err != nil {
|
if err = os.Chmod(temp.String(), 0700); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,19 @@ package rosa
|
|||||||
|
|
||||||
import "hakurei.app/internal/pkg"
|
import "hakurei.app/internal/pkg"
|
||||||
|
|
||||||
func (t Toolchain) newAttr() pkg.Artifact {
|
func (t Toolchain) newAttr() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "2.5.2"
|
version = "2.5.2"
|
||||||
checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l"
|
checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l"
|
||||||
)
|
)
|
||||||
return t.NewViaMake("attr", version, t.NewPatchedSource(
|
return t.NewPackage("attr", version, pkg.NewHTTPGetTar(
|
||||||
"attr", version, pkg.NewHTTPGetTar(
|
|
||||||
nil, "https://download.savannah.nongnu.org/releases/attr/"+
|
nil, "https://download.savannah.nongnu.org/releases/attr/"+
|
||||||
"attr-"+version+".tar.gz",
|
"attr-"+version+".tar.gz",
|
||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), true, [2]string{"libgen-basename", `From 8a80d895dfd779373363c3a4b62ecce5a549efb2 Mon Sep 17 00:00:00 2001
|
), &PackageAttr{
|
||||||
|
Patches: [][2]string{
|
||||||
|
{"libgen-basename", `From 8a80d895dfd779373363c3a4b62ecce5a549efb2 Mon Sep 17 00:00:00 2001
|
||||||
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
|
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
|
||||||
Date: Sat, 30 Mar 2024 10:17:10 +0100
|
Date: Sat, 30 Mar 2024 10:17:10 +0100
|
||||||
Subject: tools/attr.c: Add missing libgen.h include for basename(3)
|
Subject: tools/attr.c: Add missing libgen.h include for basename(3)
|
||||||
@@ -38,7 +39,9 @@ index f12e4af..6a3c1e9 100644
|
|||||||
#include <attr/attributes.h>
|
#include <attr/attributes.h>
|
||||||
|
|
||||||
--
|
--
|
||||||
cgit v1.1`}, [2]string{"musl-errno", `diff --git a/test/attr.test b/test/attr.test
|
cgit v1.1`},
|
||||||
|
|
||||||
|
{"musl-errno", `diff --git a/test/attr.test b/test/attr.test
|
||||||
index 6ce2f9b..e9bde92 100644
|
index 6ce2f9b..e9bde92 100644
|
||||||
--- a/test/attr.test
|
--- a/test/attr.test
|
||||||
+++ b/test/attr.test
|
+++ b/test/attr.test
|
||||||
@@ -52,39 +55,52 @@ index 6ce2f9b..e9bde92 100644
|
|||||||
$ setfattr -n user. -v value f
|
$ setfattr -n user. -v value f
|
||||||
> setfattr: f: Invalid argument
|
> setfattr: f: Invalid argument
|
||||||
`},
|
`},
|
||||||
), &MakeAttr{
|
},
|
||||||
|
|
||||||
ScriptEarly: `
|
ScriptEarly: `
|
||||||
ln -s ../../system/bin/perl /usr/bin
|
ln -s ../../system/bin/perl /usr/bin
|
||||||
`,
|
`,
|
||||||
Configure: [][2]string{
|
}, (*MakeHelper)(nil),
|
||||||
{"enable-static"},
|
Perl,
|
||||||
},
|
), version
|
||||||
},
|
|
||||||
t.Load(Perl),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
func init() { artifactsF[Attr] = Toolchain.newAttr }
|
func init() {
|
||||||
|
artifactsM[Attr] = Metadata{
|
||||||
|
f: Toolchain.newAttr,
|
||||||
|
|
||||||
func (t Toolchain) newACL() pkg.Artifact {
|
Name: "attr",
|
||||||
|
Description: "Commands for Manipulating Filesystem Extended Attributes",
|
||||||
|
Website: "https://savannah.nongnu.org/projects/attr/",
|
||||||
|
|
||||||
|
ID: 137,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Toolchain) newACL() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P"
|
checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P"
|
||||||
)
|
)
|
||||||
return t.NewViaMake("acl", version, pkg.NewHTTPGetTar(
|
return t.NewPackage("acl", version, pkg.NewHTTPGetTar(
|
||||||
nil,
|
nil, "https://download.savannah.nongnu.org/releases/acl/"+
|
||||||
"https://download.savannah.nongnu.org/releases/acl/"+
|
|
||||||
"acl-"+version+".tar.gz",
|
"acl-"+version+".tar.gz",
|
||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), &MakeAttr{
|
), nil, &MakeHelper{
|
||||||
Configure: [][2]string{
|
|
||||||
{"enable-static"},
|
|
||||||
},
|
|
||||||
|
|
||||||
// makes assumptions about uid_map/gid_map
|
// makes assumptions about uid_map/gid_map
|
||||||
SkipCheck: true,
|
SkipCheck: true,
|
||||||
},
|
},
|
||||||
t.Load(Attr),
|
Attr,
|
||||||
)
|
), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[ACL] = Metadata{
|
||||||
|
f: Toolchain.newACL,
|
||||||
|
|
||||||
|
Name: "acl",
|
||||||
|
Description: "Commands for Manipulating POSIX Access Control Lists",
|
||||||
|
Website: "https://savannah.nongnu.org/projects/acl/",
|
||||||
|
|
||||||
|
ID: 16,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func init() { artifactsF[ACL] = Toolchain.newACL }
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package rosa
|
package rosa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
@@ -10,20 +15,48 @@ import (
|
|||||||
type PArtifact int
|
type PArtifact int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ACL PArtifact = iota
|
LLVMCompilerRT PArtifact = iota
|
||||||
|
LLVMRuntimes
|
||||||
|
LLVMClang
|
||||||
|
|
||||||
|
// EarlyInit is the Rosa OS initramfs init program.
|
||||||
|
EarlyInit
|
||||||
|
// ImageInitramfs is the Rosa OS initramfs archive.
|
||||||
|
ImageInitramfs
|
||||||
|
|
||||||
|
// Kernel is the generic Rosa OS Linux kernel.
|
||||||
|
Kernel
|
||||||
|
// KernelHeaders is an installation of kernel headers for [Kernel].
|
||||||
|
KernelHeaders
|
||||||
|
// KernelSource is a writable kernel source tree installed to [AbsUsrSrc].
|
||||||
|
KernelSource
|
||||||
|
// Firmware is firmware blobs for use with the Linux kernel.
|
||||||
|
Firmware
|
||||||
|
|
||||||
|
ACL
|
||||||
|
ArgpStandalone
|
||||||
Attr
|
Attr
|
||||||
Autoconf
|
Autoconf
|
||||||
Automake
|
Automake
|
||||||
|
BC
|
||||||
Bash
|
Bash
|
||||||
Binutils
|
Binutils
|
||||||
|
Bison
|
||||||
|
Bzip2
|
||||||
CMake
|
CMake
|
||||||
Coreutils
|
Coreutils
|
||||||
Curl
|
Curl
|
||||||
|
DTC
|
||||||
Diffutils
|
Diffutils
|
||||||
|
Elfutils
|
||||||
|
Fakeroot
|
||||||
Findutils
|
Findutils
|
||||||
|
Flex
|
||||||
Fuse
|
Fuse
|
||||||
Gawk
|
|
||||||
GMP
|
GMP
|
||||||
|
GLib
|
||||||
|
Gawk
|
||||||
|
GenInitCPIO
|
||||||
Gettext
|
Gettext
|
||||||
Git
|
Git
|
||||||
Go
|
Go
|
||||||
@@ -32,10 +65,11 @@ const (
|
|||||||
Gzip
|
Gzip
|
||||||
Hakurei
|
Hakurei
|
||||||
HakureiDist
|
HakureiDist
|
||||||
IniConfig
|
Kmod
|
||||||
KernelHeaders
|
|
||||||
LibXau
|
LibXau
|
||||||
|
Libcap
|
||||||
Libexpat
|
Libexpat
|
||||||
|
Libiconv
|
||||||
Libpsl
|
Libpsl
|
||||||
Libffi
|
Libffi
|
||||||
Libgd
|
Libgd
|
||||||
@@ -43,31 +77,66 @@ const (
|
|||||||
Libseccomp
|
Libseccomp
|
||||||
Libucontext
|
Libucontext
|
||||||
Libxml2
|
Libxml2
|
||||||
|
Libxslt
|
||||||
M4
|
M4
|
||||||
MPC
|
MPC
|
||||||
MPFR
|
MPFR
|
||||||
Make
|
Make
|
||||||
Meson
|
Meson
|
||||||
Mksh
|
Mksh
|
||||||
|
MuslFts
|
||||||
|
MuslObstack
|
||||||
NSS
|
NSS
|
||||||
NSSCACert
|
NSSCACert
|
||||||
|
Ncurses
|
||||||
|
Nettle
|
||||||
Ninja
|
Ninja
|
||||||
OpenSSL
|
OpenSSL
|
||||||
Packaging
|
PCRE2
|
||||||
|
Parallel
|
||||||
Patch
|
Patch
|
||||||
Perl
|
Perl
|
||||||
|
PerlLocaleGettext
|
||||||
|
PerlMIMECharset
|
||||||
|
PerlModuleBuild
|
||||||
|
PerlPodParser
|
||||||
|
PerlSGMLS
|
||||||
|
PerlTermReadKey
|
||||||
|
PerlTextCharWidth
|
||||||
|
PerlTextWrapI18N
|
||||||
|
PerlUnicodeGCString
|
||||||
|
PerlYAMLTiny
|
||||||
PkgConfig
|
PkgConfig
|
||||||
Pluggy
|
Procps
|
||||||
PyTest
|
|
||||||
Pygments
|
|
||||||
Python
|
Python
|
||||||
|
PythonCfgv
|
||||||
|
PythonDiscovery
|
||||||
|
PythonDistlib
|
||||||
|
PythonFilelock
|
||||||
|
PythonIdentify
|
||||||
|
PythonIniConfig
|
||||||
|
PythonNodeenv
|
||||||
|
PythonPackaging
|
||||||
|
PythonPlatformdirs
|
||||||
|
PythonPluggy
|
||||||
|
PythonPreCommit
|
||||||
|
PythonPyTest
|
||||||
|
PythonPyYAML
|
||||||
|
PythonPygments
|
||||||
|
PythonVirtualenv
|
||||||
|
QEMU
|
||||||
|
Rdfind
|
||||||
Rsync
|
Rsync
|
||||||
Sed
|
Sed
|
||||||
Setuptools
|
Setuptools
|
||||||
|
SquashfsTools
|
||||||
|
TamaGo
|
||||||
|
Tar
|
||||||
|
Texinfo
|
||||||
Toybox
|
Toybox
|
||||||
toyboxEarly
|
toyboxEarly
|
||||||
Unzip
|
Unzip
|
||||||
utilMacros
|
UtilLinux
|
||||||
Wayland
|
Wayland
|
||||||
WaylandProtocols
|
WaylandProtocols
|
||||||
XCB
|
XCB
|
||||||
@@ -75,101 +144,150 @@ const (
|
|||||||
Xproto
|
Xproto
|
||||||
XZ
|
XZ
|
||||||
Zlib
|
Zlib
|
||||||
|
Zstd
|
||||||
|
|
||||||
buildcatrust
|
// PresetUnexportedStart is the first unexported preset.
|
||||||
|
PresetUnexportedStart
|
||||||
|
|
||||||
|
buildcatrust = iota - 1
|
||||||
|
utilMacros
|
||||||
|
|
||||||
|
// Musl is a standalone libc that does not depend on the toolchain.
|
||||||
|
Musl
|
||||||
|
|
||||||
// gcc is a hacked-to-pieces GCC toolchain meant for use in intermediate
|
// gcc is a hacked-to-pieces GCC toolchain meant for use in intermediate
|
||||||
// stages only. This preset and its direct output must never be exposed.
|
// stages only. This preset and its direct output must never be exposed.
|
||||||
gcc
|
gcc
|
||||||
|
|
||||||
// _presetEnd is the total number of presets and does not denote a preset.
|
// Stage0 is a tarball containing all compile-time dependencies of artifacts
|
||||||
_presetEnd
|
// part of the [Std] toolchain.
|
||||||
|
Stage0
|
||||||
|
|
||||||
|
// PresetEnd is the total number of presets and does not denote a preset.
|
||||||
|
PresetEnd
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Metadata is stage-agnostic information of a [PArtifact] not directly
|
||||||
|
// representable in the resulting [pkg.Artifact].
|
||||||
|
type Metadata struct {
|
||||||
|
f func(t Toolchain) (a pkg.Artifact, version string)
|
||||||
|
|
||||||
|
// Unique package name.
|
||||||
|
Name string `json:"name"`
|
||||||
|
// Short user-facing description.
|
||||||
|
Description string `json:"description"`
|
||||||
|
// Project home page.
|
||||||
|
Website string `json:"website,omitempty"`
|
||||||
|
|
||||||
|
// Project identifier on [Anitya].
|
||||||
|
//
|
||||||
|
// [Anitya]: https://release-monitoring.org/
|
||||||
|
ID int `json:"-"`
|
||||||
|
|
||||||
|
// Optional custom version checking behaviour.
|
||||||
|
latest func(v *Versions) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatest returns the latest version described by v.
|
||||||
|
func (meta *Metadata) GetLatest(v *Versions) string {
|
||||||
|
if meta.latest != nil {
|
||||||
|
return meta.latest(v)
|
||||||
|
}
|
||||||
|
return v.Latest
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unversioned denotes an unversioned [PArtifact].
|
||||||
|
const Unversioned = "\x00"
|
||||||
|
|
||||||
|
// UnpopulatedIDError is returned by [Metadata.GetLatest] for an instance of
|
||||||
|
// [Metadata] where ID is not populated.
|
||||||
|
type UnpopulatedIDError struct{}
|
||||||
|
|
||||||
|
func (UnpopulatedIDError) Unwrap() error { return errors.ErrUnsupported }
|
||||||
|
func (UnpopulatedIDError) Error() string { return "Anitya ID is not populated" }
|
||||||
|
|
||||||
|
// Versions are package versions returned by Anitya.
|
||||||
|
type Versions struct {
|
||||||
|
// The latest version for the project, as determined by the version sorting algorithm.
|
||||||
|
Latest string `json:"latest_version"`
|
||||||
|
// List of all versions that aren’t flagged as pre-release.
|
||||||
|
Stable []string `json:"stable_versions"`
|
||||||
|
// List of all versions stored, sorted from newest to oldest.
|
||||||
|
All []string `json:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStable returns the first Stable version, or Latest if that is unavailable.
|
||||||
|
func (v *Versions) getStable() string {
|
||||||
|
if len(v.Stable) == 0 {
|
||||||
|
return v.Latest
|
||||||
|
}
|
||||||
|
return v.Stable[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersions returns versions fetched from Anitya.
|
||||||
|
func (meta *Metadata) GetVersions(ctx context.Context) (*Versions, error) {
|
||||||
|
if meta.ID == 0 {
|
||||||
|
return nil, UnpopulatedIDError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp *http.Response
|
||||||
|
if req, err := http.NewRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
|
"https://release-monitoring.org/api/v2/versions/?project_id="+
|
||||||
|
strconv.Itoa(meta.ID),
|
||||||
|
nil,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
req.Header.Set("User-Agent", "Rosa/1.1")
|
||||||
|
if resp, err = http.DefaultClient.Do(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var v Versions
|
||||||
|
err := json.NewDecoder(resp.Body).Decode(&v)
|
||||||
|
return &v, errors.Join(err, resp.Body.Close())
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// artifactsF is an array of functions for the result of [PArtifact].
|
// artifactsM is an array of [PArtifact] metadata.
|
||||||
artifactsF [_presetEnd]func(t Toolchain) pkg.Artifact
|
artifactsM [PresetEnd]Metadata
|
||||||
|
|
||||||
// artifacts stores the result of artifactsF.
|
// artifacts stores the result of Metadata.f.
|
||||||
artifacts [_toolchainEnd][len(artifactsF)]pkg.Artifact
|
artifacts [_toolchainEnd][len(artifactsM)]pkg.Artifact
|
||||||
|
// versions stores the version of [PArtifact].
|
||||||
|
versions [_toolchainEnd][len(artifactsM)]string
|
||||||
// artifactsOnce is for lazy initialisation of artifacts.
|
// artifactsOnce is for lazy initialisation of artifacts.
|
||||||
artifactsOnce [_toolchainEnd][len(artifactsF)]sync.Once
|
artifactsOnce [_toolchainEnd][len(artifactsM)]sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetMetadata returns [Metadata] of a [PArtifact].
|
||||||
|
func GetMetadata(p PArtifact) *Metadata { return &artifactsM[p] }
|
||||||
|
|
||||||
// Load returns the resulting [pkg.Artifact] of [PArtifact].
|
// Load returns the resulting [pkg.Artifact] of [PArtifact].
|
||||||
func (t Toolchain) Load(p PArtifact) pkg.Artifact {
|
func (t Toolchain) Load(p PArtifact) pkg.Artifact {
|
||||||
artifactsOnce[t][p].Do(func() {
|
artifactsOnce[t][p].Do(func() {
|
||||||
artifacts[t][p] = artifactsF[p](t)
|
artifacts[t][p], versions[t][p] = artifactsM[p].f(t)
|
||||||
})
|
})
|
||||||
return artifacts[t][p]
|
return artifacts[t][p]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Version returns the version string of [PArtifact].
|
||||||
|
func (t Toolchain) Version(p PArtifact) string {
|
||||||
|
artifactsOnce[t][p].Do(func() {
|
||||||
|
artifacts[t][p], versions[t][p] = artifactsM[p].f(t)
|
||||||
|
})
|
||||||
|
return versions[t][p]
|
||||||
|
}
|
||||||
|
|
||||||
// ResolveName returns a [PArtifact] by name.
|
// ResolveName returns a [PArtifact] by name.
|
||||||
func ResolveName(name string) (p PArtifact, ok bool) {
|
func ResolveName(name string) (p PArtifact, ok bool) {
|
||||||
p, ok = map[string]PArtifact{
|
for i := range PresetUnexportedStart {
|
||||||
"acl": ACL,
|
if name == artifactsM[i].Name {
|
||||||
"attr": Attr,
|
return i, true
|
||||||
"autoconf": Autoconf,
|
}
|
||||||
"automake": Automake,
|
}
|
||||||
"bash": Bash,
|
return 0, false
|
||||||
"binutils": Binutils,
|
|
||||||
"cmake": CMake,
|
|
||||||
"coreutils": Coreutils,
|
|
||||||
"curl": Curl,
|
|
||||||
"diffutils": Diffutils,
|
|
||||||
"findutils": Findutils,
|
|
||||||
"fuse": Fuse,
|
|
||||||
"gawk": Gawk,
|
|
||||||
"gmp": GMP,
|
|
||||||
"gettext": Gettext,
|
|
||||||
"git": Git,
|
|
||||||
"go": Go,
|
|
||||||
"gperf": Gperf,
|
|
||||||
"grep": Grep,
|
|
||||||
"gzip": Gzip,
|
|
||||||
"hakurei": Hakurei,
|
|
||||||
"hakurei-dist": HakureiDist,
|
|
||||||
"iniconfig": IniConfig,
|
|
||||||
"kernel-headers": KernelHeaders,
|
|
||||||
"libXau": LibXau,
|
|
||||||
"libexpat": Libexpat,
|
|
||||||
"libpsl": Libpsl,
|
|
||||||
"libseccomp": Libseccomp,
|
|
||||||
"libucontext": Libucontext,
|
|
||||||
"libxml2": Libxml2,
|
|
||||||
"libffi": Libffi,
|
|
||||||
"libgd": Libgd,
|
|
||||||
"libtool": Libtool,
|
|
||||||
"m4": M4,
|
|
||||||
"mpc": MPC,
|
|
||||||
"mpfr": MPFR,
|
|
||||||
"make": Make,
|
|
||||||
"meson": Meson,
|
|
||||||
"mksh": Mksh,
|
|
||||||
"nss": NSS,
|
|
||||||
"nss-cacert": NSSCACert,
|
|
||||||
"ninja": Ninja,
|
|
||||||
"openssl": OpenSSL,
|
|
||||||
"packaging": Packaging,
|
|
||||||
"patch": Patch,
|
|
||||||
"perl": Perl,
|
|
||||||
"pkg-config": PkgConfig,
|
|
||||||
"pluggy": Pluggy,
|
|
||||||
"pytest": PyTest,
|
|
||||||
"pygments": Pygments,
|
|
||||||
"python": Python,
|
|
||||||
"rsync": Rsync,
|
|
||||||
"sed": Sed,
|
|
||||||
"setuptools": Setuptools,
|
|
||||||
"toybox": Toybox,
|
|
||||||
"unzip": Unzip,
|
|
||||||
"wayland": Wayland,
|
|
||||||
"wayland-protocols": WaylandProtocols,
|
|
||||||
"xcb": XCB,
|
|
||||||
"xcb-proto": XCBProto,
|
|
||||||
"xproto": Xproto,
|
|
||||||
"xz": XZ,
|
|
||||||
"zlib": Zlib,
|
|
||||||
}[name]
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
66
internal/rosa/all_test.go
Normal file
66
internal/rosa/all_test.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package rosa_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/rosa"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoad(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for i := range rosa.PresetEnd {
|
||||||
|
p := rosa.PArtifact(i)
|
||||||
|
t.Run(rosa.GetMetadata(p).Name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
rosa.Std.Load(p)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for i := range rosa.PresetUnexportedStart {
|
||||||
|
p := i
|
||||||
|
name := rosa.GetMetadata(p).Name
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if got, ok := rosa.ResolveName(name); !ok {
|
||||||
|
t.Fatal("ResolveName: ok = false")
|
||||||
|
} else if got != p {
|
||||||
|
t.Fatalf("ResolveName: %d, want %d", got, p)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestResolveNameUnexported(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for i := rosa.PresetUnexportedStart; i < rosa.PresetEnd; i++ {
|
||||||
|
p := i
|
||||||
|
name := rosa.GetMetadata(p).Name
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if got, ok := rosa.ResolveName(name); ok {
|
||||||
|
t.Fatalf("ResolveName: resolved unexported preset %d", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnique(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
names := make(map[string]struct{})
|
||||||
|
for i := range rosa.PresetEnd {
|
||||||
|
name := rosa.GetMetadata(rosa.PArtifact(i)).Name
|
||||||
|
if _, ok := names[name]; ok {
|
||||||
|
t.Fatalf("name %s is not unique", name)
|
||||||
|
}
|
||||||
|
names[name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
internal/rosa/argp-standalone.go
Normal file
36
internal/rosa/argp-standalone.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package rosa
|
||||||
|
|
||||||
|
import "hakurei.app/internal/pkg"
|
||||||
|
|
||||||
|
func (t Toolchain) newArgpStandalone() (pkg.Artifact, string) {
|
||||||
|
const (
|
||||||
|
version = "1.3"
|
||||||
|
checksum = "vtW0VyO2pJ-hPyYmDI2zwSLS8QL0sPAUKC1t3zNYbwN2TmsaE-fADhaVtNd3eNFl"
|
||||||
|
)
|
||||||
|
return t.NewPackage("argp-standalone", version, pkg.NewHTTPGetTar(
|
||||||
|
nil, "http://www.lysator.liu.se/~nisse/misc/"+
|
||||||
|
"argp-standalone-"+version+".tar.gz",
|
||||||
|
mustDecode(checksum),
|
||||||
|
pkg.TarGzip,
|
||||||
|
), &PackageAttr{
|
||||||
|
Env: []string{
|
||||||
|
"CC=cc -std=gnu89 -fPIC",
|
||||||
|
},
|
||||||
|
}, &MakeHelper{
|
||||||
|
Install: `
|
||||||
|
install -D -m644 /usr/src/argp-standalone/argp.h /work/system/include/argp.h
|
||||||
|
install -D -m755 libargp.a /work/system/lib/libargp.a
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
Diffutils,
|
||||||
|
), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[ArgpStandalone] = Metadata{
|
||||||
|
f: Toolchain.newArgpStandalone,
|
||||||
|
|
||||||
|
Name: "argp-standalone",
|
||||||
|
Description: "hierarchical argument parsing library broken out from glibc",
|
||||||
|
Website: "http://www.lysator.liu.se/~nisse/misc/",
|
||||||
|
}
|
||||||
|
}
|
||||||
38
internal/rosa/bzip2.go
Normal file
38
internal/rosa/bzip2.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package rosa
|
||||||
|
|
||||||
|
import "hakurei.app/internal/pkg"
|
||||||
|
|
||||||
|
func (t Toolchain) newBzip2() (pkg.Artifact, string) {
|
||||||
|
const (
|
||||||
|
version = "1.0.8"
|
||||||
|
checksum = "cTLykcco7boom-s05H1JVsQi1AtChYL84nXkg_92Dm1Xt94Ob_qlMg_-NSguIK-c"
|
||||||
|
)
|
||||||
|
return t.NewPackage("bzip2", version, pkg.NewHTTPGetTar(
|
||||||
|
nil, "https://sourceware.org/pub/bzip2/bzip2-"+version+".tar.gz",
|
||||||
|
mustDecode(checksum),
|
||||||
|
pkg.TarGzip,
|
||||||
|
), &PackageAttr{
|
||||||
|
Writable: true,
|
||||||
|
EnterSource: true,
|
||||||
|
}, &MakeHelper{
|
||||||
|
// uses source tree as scratch space
|
||||||
|
SkipConfigure: true,
|
||||||
|
SkipCheck: true,
|
||||||
|
InPlace: true,
|
||||||
|
Make: []string{
|
||||||
|
"CC=cc",
|
||||||
|
},
|
||||||
|
Install: "make PREFIX=/work/system install",
|
||||||
|
}), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[Bzip2] = Metadata{
|
||||||
|
f: Toolchain.newBzip2,
|
||||||
|
|
||||||
|
Name: "bzip2",
|
||||||
|
Description: "a freely available, patent free, high-quality data compressor",
|
||||||
|
Website: "https://sourceware.org/bzip2/",
|
||||||
|
|
||||||
|
ID: 237,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,82 +1,175 @@
|
|||||||
package rosa
|
package rosa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t Toolchain) newCMake() pkg.Artifact {
|
func (t Toolchain) newCMake() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "4.2.1"
|
version = "4.2.3"
|
||||||
checksum = "Y3OdbMsob6Xk2y1DCME6z4Fryb5_TkFD7knRT8dTNIRtSqbiCJyyDN9AxggN_I75"
|
checksum = "Y4uYGnLrDQX78UdzH7fMzfok46Nt_1taDIHSmqgboU1yFi6f0iAXBDegMCu4eS-J"
|
||||||
)
|
)
|
||||||
return t.New("cmake-"+version, 0, []pkg.Artifact{
|
return t.NewPackage("cmake", version, pkg.NewHTTPGetTar(
|
||||||
t.Load(Make),
|
|
||||||
t.Load(KernelHeaders),
|
|
||||||
}, nil, nil, `
|
|
||||||
cd "$(mktemp -d)"
|
|
||||||
/usr/src/cmake/bootstrap \
|
|
||||||
--prefix=/system \
|
|
||||||
--parallel="$(nproc)" \
|
|
||||||
-- \
|
|
||||||
-DCMAKE_USE_OPENSSL=OFF
|
|
||||||
make "-j$(nproc)"
|
|
||||||
make DESTDIR=/work install
|
|
||||||
`, pkg.Path(AbsUsrSrc.Append("cmake"), true, t.NewPatchedSource(
|
|
||||||
// expected to be writable in the copy made during bootstrap
|
|
||||||
"cmake", version, pkg.NewHTTPGetTar(
|
|
||||||
nil, "https://github.com/Kitware/CMake/releases/download/"+
|
nil, "https://github.com/Kitware/CMake/releases/download/"+
|
||||||
"v"+version+"/cmake-"+version+".tar.gz",
|
"v"+version+"/cmake-"+version+".tar.gz",
|
||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), false,
|
), &PackageAttr{
|
||||||
)))
|
// test suite expects writable source tree
|
||||||
}
|
Writable: true,
|
||||||
func init() { artifactsF[CMake] = Toolchain.newCMake }
|
|
||||||
|
// expected to be writable in the copy made during bootstrap
|
||||||
|
Chmod: true,
|
||||||
|
|
||||||
|
Patches: [][2]string{
|
||||||
|
{"bootstrap-test-no-openssl", `diff --git a/Tests/BootstrapTest.cmake b/Tests/BootstrapTest.cmake
|
||||||
|
index 137de78bc1..b4da52e664 100644
|
||||||
|
--- a/Tests/BootstrapTest.cmake
|
||||||
|
+++ b/Tests/BootstrapTest.cmake
|
||||||
|
@@ -9,7 +9,7 @@ if(NOT nproc EQUAL 0)
|
||||||
|
endif()
|
||||||
|
message(STATUS "running bootstrap: ${bootstrap} ${ninja_arg} ${parallel_arg}")
|
||||||
|
execute_process(
|
||||||
|
- COMMAND ${bootstrap} ${ninja_arg} ${parallel_arg}
|
||||||
|
+ COMMAND ${bootstrap} ${ninja_arg} ${parallel_arg} -- -DCMAKE_USE_OPENSSL=OFF
|
||||||
|
WORKING_DIRECTORY "${bin_dir}"
|
||||||
|
RESULT_VARIABLE result
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
|
||||||
|
{"disable-broken-tests-musl", `diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
|
||||||
|
index 2ead810437..f85cbb8b1c 100644
|
||||||
|
--- a/Tests/CMakeLists.txt
|
||||||
|
+++ b/Tests/CMakeLists.txt
|
||||||
|
@@ -384,7 +384,6 @@ if(BUILD_TESTING)
|
||||||
|
add_subdirectory(CMakeLib)
|
||||||
|
endif()
|
||||||
|
add_subdirectory(CMakeOnly)
|
||||||
|
- add_subdirectory(RunCMake)
|
||||||
|
|
||||||
|
add_subdirectory(FindPackageModeMakefileTest)
|
||||||
|
|
||||||
|
@@ -528,9 +527,6 @@ if(BUILD_TESTING)
|
||||||
|
-DCMake_TEST_CUDA:BOOL=${CMake_TEST_CUDA}
|
||||||
|
-DCMake_INSTALL_NAME_TOOL_BUG:BOOL=${CMake_INSTALL_NAME_TOOL_BUG}
|
||||||
|
)
|
||||||
|
- ADD_TEST_MACRO(ExportImport ExportImport)
|
||||||
|
- set_property(TEST ExportImport APPEND
|
||||||
|
- PROPERTY LABELS "CUDA")
|
||||||
|
ADD_TEST_MACRO(Unset Unset)
|
||||||
|
ADD_TEST_MACRO(PolicyScope PolicyScope)
|
||||||
|
ADD_TEST_MACRO(EmptyLibrary EmptyLibrary)
|
||||||
|
@@ -624,7 +620,6 @@ if(BUILD_TESTING)
|
||||||
|
# run test for BundleUtilities on supported platforms/compilers
|
||||||
|
if((MSVC OR
|
||||||
|
MINGW OR
|
||||||
|
- CMAKE_SYSTEM_NAME MATCHES "Linux" OR
|
||||||
|
CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||||
|
AND NOT CMAKE_GENERATOR STREQUAL "Watcom WMake")
|
||||||
|
|
||||||
|
@@ -3095,10 +3090,6 @@ if(BUILD_TESTING)
|
||||||
|
"${CMake_SOURCE_DIR}/Tests/CTestTestFdSetSize/test.cmake.in"
|
||||||
|
"${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/test.cmake"
|
||||||
|
@ONLY ESCAPE_QUOTES)
|
||||||
|
- add_test(CTestTestFdSetSize ${CMAKE_CTEST_COMMAND}
|
||||||
|
- -S "${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/test.cmake" -j20 -V --timeout 120
|
||||||
|
- --output-log "${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/testOutput.log"
|
||||||
|
- )
|
||||||
|
|
||||||
|
if(CMAKE_TESTS_CDASH_SERVER)
|
||||||
|
set(regex "^([^:]+)://([^/]+)(.*)$")
|
||||||
|
`},
|
||||||
|
},
|
||||||
|
}, &MakeHelper{
|
||||||
|
OmitDefaults: true,
|
||||||
|
|
||||||
|
ConfigureName: "/usr/src/cmake/bootstrap",
|
||||||
|
Configure: [][2]string{
|
||||||
|
{"prefix", "/system"},
|
||||||
|
{"parallel", `"$(nproc)"`},
|
||||||
|
{"--"},
|
||||||
|
{"-DCMAKE_USE_OPENSSL", "OFF"},
|
||||||
|
{"-DCMake_TEST_NO_NETWORK", "ON"},
|
||||||
|
},
|
||||||
|
Check: []string{
|
||||||
|
"CTEST_OUTPUT_ON_FAILURE=1",
|
||||||
|
"CTEST_PARALLEL_LEVEL=128",
|
||||||
|
"test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KernelHeaders,
|
||||||
|
), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[CMake] = Metadata{
|
||||||
|
f: Toolchain.newCMake,
|
||||||
|
|
||||||
|
Name: "cmake",
|
||||||
|
Description: "cross-platform, open-source build system",
|
||||||
|
Website: "https://cmake.org/",
|
||||||
|
|
||||||
|
ID: 306,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMakeHelper is the [CMake] build system helper.
|
||||||
|
type CMakeHelper struct {
|
||||||
|
// Joined with name with a dash if non-empty.
|
||||||
|
Variant string
|
||||||
|
|
||||||
// CMakeAttr holds the project-specific attributes that will be applied to a new
|
|
||||||
// [pkg.Artifact] compiled via [CMake].
|
|
||||||
type CMakeAttr struct {
|
|
||||||
// Path elements joined with source.
|
// Path elements joined with source.
|
||||||
Append []string
|
Append []string
|
||||||
// Use source tree as scratch space.
|
|
||||||
Writable bool
|
|
||||||
|
|
||||||
// CMake CACHE entries.
|
// CMake CACHE entries.
|
||||||
Cache [][2]string
|
Cache [][2]string
|
||||||
// Additional environment variables.
|
|
||||||
Env []string
|
|
||||||
// Runs before cmake.
|
|
||||||
ScriptEarly string
|
|
||||||
// Runs after cmake, replaces default.
|
|
||||||
ScriptConfigured string
|
|
||||||
// Runs after install.
|
// Runs after install.
|
||||||
Script string
|
Script string
|
||||||
|
|
||||||
// Override the default installation prefix [AbsSystem].
|
// Whether to generate Makefile instead.
|
||||||
Prefix *check.Absolute
|
Make bool
|
||||||
|
|
||||||
// Passed through to [Toolchain.New].
|
|
||||||
Paths []pkg.ExecPath
|
|
||||||
// Passed through to [Toolchain.New].
|
|
||||||
Flag int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewViaCMake returns a [pkg.Artifact] for compiling and installing via [CMake].
|
var _ Helper = new(CMakeHelper)
|
||||||
func (t Toolchain) NewViaCMake(
|
|
||||||
name, version, variant string,
|
// name returns its arguments and an optional variant string joined with '-'.
|
||||||
source pkg.Artifact,
|
func (attr *CMakeHelper) name(name, version string) string {
|
||||||
attr *CMakeAttr,
|
if attr != nil && attr.Variant != "" {
|
||||||
extra ...pkg.Artifact,
|
name += "-" + attr.Variant
|
||||||
) pkg.Artifact {
|
|
||||||
if name == "" || version == "" || variant == "" {
|
|
||||||
panic("names must be non-empty")
|
|
||||||
}
|
}
|
||||||
|
return name + "-" + version
|
||||||
|
}
|
||||||
|
|
||||||
|
// extra returns a hardcoded slice of [CMake] and [Ninja].
|
||||||
|
func (attr *CMakeHelper) extra(int) []PArtifact {
|
||||||
|
if attr != nil && attr.Make {
|
||||||
|
return []PArtifact{CMake, Make}
|
||||||
|
}
|
||||||
|
return []PArtifact{CMake, Ninja}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wantsChmod returns false.
|
||||||
|
func (*CMakeHelper) wantsChmod() bool { return false }
|
||||||
|
|
||||||
|
// wantsWrite returns false.
|
||||||
|
func (*CMakeHelper) wantsWrite() bool { return false }
|
||||||
|
|
||||||
|
// scriptEarly returns the zero value.
|
||||||
|
func (*CMakeHelper) scriptEarly() string { return "" }
|
||||||
|
|
||||||
|
// createDir returns true.
|
||||||
|
func (*CMakeHelper) createDir() bool { return true }
|
||||||
|
|
||||||
|
// wantsDir returns a hardcoded, deterministic pathname.
|
||||||
|
func (*CMakeHelper) wantsDir() string { return "/cure/" }
|
||||||
|
|
||||||
|
// script generates the cure script.
|
||||||
|
func (attr *CMakeHelper) script(name string) string {
|
||||||
if attr == nil {
|
if attr == nil {
|
||||||
attr = &CMakeAttr{
|
attr = &CMakeHelper{
|
||||||
Cache: [][2]string{
|
Cache: [][2]string{
|
||||||
{"CMAKE_BUILD_TYPE", "Release"},
|
{"CMAKE_BUILD_TYPE", "Release"},
|
||||||
},
|
},
|
||||||
@@ -86,40 +179,29 @@ func (t Toolchain) NewViaCMake(
|
|||||||
panic("CACHE must be non-empty")
|
panic("CACHE must be non-empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptConfigured := "cmake --build .\ncmake --install .\n"
|
generate := "Ninja"
|
||||||
if attr.ScriptConfigured != "" {
|
jobs := ""
|
||||||
scriptConfigured = attr.ScriptConfigured
|
if attr.Make {
|
||||||
|
generate = "'Unix Makefiles'"
|
||||||
|
jobs += ` "--parallel=$(nproc)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix := attr.Prefix
|
return `
|
||||||
if prefix == nil {
|
cmake -G ` + generate + ` \
|
||||||
prefix = AbsSystem
|
|
||||||
}
|
|
||||||
|
|
||||||
sourcePath := AbsUsrSrc.Append(name)
|
|
||||||
return t.New(name+"-"+variant+"-"+version, attr.Flag, stage3Concat(t, extra,
|
|
||||||
t.Load(CMake),
|
|
||||||
t.Load(Ninja),
|
|
||||||
), nil, slices.Concat([]string{
|
|
||||||
"ROSA_SOURCE=" + sourcePath.String(),
|
|
||||||
"ROSA_CMAKE_SOURCE=" + sourcePath.Append(attr.Append...).String(),
|
|
||||||
"ROSA_INSTALL_PREFIX=/work" + prefix.String(),
|
|
||||||
}, attr.Env), attr.ScriptEarly+`
|
|
||||||
mkdir /cure && cd /cure
|
|
||||||
cmake -G Ninja \
|
|
||||||
-DCMAKE_C_COMPILER_TARGET="${ROSA_TRIPLE}" \
|
-DCMAKE_C_COMPILER_TARGET="${ROSA_TRIPLE}" \
|
||||||
-DCMAKE_CXX_COMPILER_TARGET="${ROSA_TRIPLE}" \
|
-DCMAKE_CXX_COMPILER_TARGET="${ROSA_TRIPLE}" \
|
||||||
-DCMAKE_ASM_COMPILER_TARGET="${ROSA_TRIPLE}" \
|
-DCMAKE_ASM_COMPILER_TARGET="${ROSA_TRIPLE}" \
|
||||||
`+strings.Join(slices.Collect(func(yield func(string) bool) {
|
-DCMAKE_INSTALL_LIBDIR=lib \
|
||||||
|
` + strings.Join(slices.Collect(func(yield func(string) bool) {
|
||||||
for _, v := range attr.Cache {
|
for _, v := range attr.Cache {
|
||||||
if !yield("-D" + v[0] + "=" + v[1]) {
|
if !yield("-D" + v[0] + "=" + v[1]) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), " \\\n\t")+` \
|
}), " \\\n\t") + ` \
|
||||||
-DCMAKE_INSTALL_PREFIX="${ROSA_INSTALL_PREFIX}" \
|
-DCMAKE_INSTALL_PREFIX=/system \
|
||||||
"${ROSA_CMAKE_SOURCE}"
|
'/usr/src/` + name + `/` + path.Join(attr.Append...) + `'
|
||||||
`+scriptConfigured+attr.Script, slices.Concat([]pkg.ExecPath{
|
cmake --build .` + jobs + `
|
||||||
pkg.Path(sourcePath, attr.Writable, source),
|
cmake --install . --prefix=/work/system
|
||||||
}, attr.Paths)...)
|
` + attr.Script
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,39 @@ package rosa
|
|||||||
|
|
||||||
import "hakurei.app/internal/pkg"
|
import "hakurei.app/internal/pkg"
|
||||||
|
|
||||||
func (t Toolchain) newCurl() pkg.Artifact {
|
func (t Toolchain) newCurl() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "8.18.0"
|
version = "8.18.0"
|
||||||
checksum = "YpOolP_sx1DIrCEJ3elgVAu0wTLDS-EZMZFvOP0eha7FaLueZUlEpuMwDzJNyi7i"
|
checksum = "YpOolP_sx1DIrCEJ3elgVAu0wTLDS-EZMZFvOP0eha7FaLueZUlEpuMwDzJNyi7i"
|
||||||
)
|
)
|
||||||
return t.NewViaMake("curl", version, pkg.NewHTTPGetTar(
|
return t.NewPackage("curl", version, pkg.NewHTTPGetTar(
|
||||||
nil, "https://curl.se/download/curl-"+version+".tar.bz2",
|
nil, "https://curl.se/download/curl-"+version+".tar.bz2",
|
||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarBzip2,
|
pkg.TarBzip2,
|
||||||
), &MakeAttr{
|
), nil, &MakeHelper{
|
||||||
Env: []string{
|
|
||||||
"TFLAGS=-j256",
|
|
||||||
},
|
|
||||||
Configure: [][2]string{
|
Configure: [][2]string{
|
||||||
{"with-openssl"},
|
{"with-openssl"},
|
||||||
{"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"},
|
{"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"},
|
||||||
},
|
},
|
||||||
ScriptConfigured: `
|
Check: []string{
|
||||||
make "-j$(nproc)"
|
"TFLAGS=-j256",
|
||||||
`,
|
"check",
|
||||||
},
|
},
|
||||||
t.Load(Perl),
|
},
|
||||||
|
Perl,
|
||||||
|
|
||||||
t.Load(Libpsl),
|
Libpsl,
|
||||||
t.Load(OpenSSL),
|
OpenSSL,
|
||||||
)
|
), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[Curl] = Metadata{
|
||||||
|
f: Toolchain.newCurl,
|
||||||
|
|
||||||
|
Name: "curl",
|
||||||
|
Description: "command line tool and library for transferring data with URLs",
|
||||||
|
Website: "https://curl.se/",
|
||||||
|
|
||||||
|
ID: 381,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func init() { artifactsF[Curl] = Toolchain.newCurl }
|
|
||||||
|
|||||||
43
internal/rosa/dtc.go
Normal file
43
internal/rosa/dtc.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package rosa
|
||||||
|
|
||||||
|
import "hakurei.app/internal/pkg"
|
||||||
|
|
||||||
|
func (t Toolchain) newDTC() (pkg.Artifact, string) {
|
||||||
|
const (
|
||||||
|
version = "1.7.2"
|
||||||
|
checksum = "vUoiRynPyYRexTpS6USweT5p4SVHvvVJs8uqFkkVD-YnFjwf6v3elQ0-Etrh00Dt"
|
||||||
|
)
|
||||||
|
return t.NewPackage("dtc", version, pkg.NewHTTPGetTar(
|
||||||
|
nil, "https://git.kernel.org/pub/scm/utils/dtc/dtc.git/snapshot/"+
|
||||||
|
"dtc-v"+version+".tar.gz",
|
||||||
|
mustDecode(checksum),
|
||||||
|
pkg.TarGzip,
|
||||||
|
), &PackageAttr{
|
||||||
|
// works around buggy test:
|
||||||
|
// fdtdump-runtest.sh /usr/src/dtc/tests/fdtdump.dts
|
||||||
|
Writable: true,
|
||||||
|
Chmod: true,
|
||||||
|
}, &MesonHelper{
|
||||||
|
Setup: [][2]string{
|
||||||
|
{"Dyaml", "disabled"},
|
||||||
|
{"Dstatic-build", "true"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Flex,
|
||||||
|
Bison,
|
||||||
|
M4,
|
||||||
|
Coreutils,
|
||||||
|
Diffutils,
|
||||||
|
), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[DTC] = Metadata{
|
||||||
|
f: Toolchain.newDTC,
|
||||||
|
|
||||||
|
Name: "dtc",
|
||||||
|
Description: "The Device Tree Compiler",
|
||||||
|
Website: "https://git.kernel.org/pub/scm/utils/dtc/dtc.git/",
|
||||||
|
|
||||||
|
ID: 16911,
|
||||||
|
}
|
||||||
|
}
|
||||||
51
internal/rosa/elfutils.go
Normal file
51
internal/rosa/elfutils.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package rosa
|
||||||
|
|
||||||
|
import "hakurei.app/internal/pkg"
|
||||||
|
|
||||||
|
func (t Toolchain) newElfutils() (pkg.Artifact, string) {
|
||||||
|
const (
|
||||||
|
version = "0.194"
|
||||||
|
checksum = "Q3XUygUPv9vR1TkWucwUsQ8Kb1_F6gzk-KMPELr3cC_4AcTrprhVPMvN0CKkiYRa"
|
||||||
|
)
|
||||||
|
return t.NewPackage("elfutils", version, pkg.NewHTTPGetTar(
|
||||||
|
nil, "https://sourceware.org/elfutils/ftp/"+
|
||||||
|
version+"/elfutils-"+version+".tar.bz2",
|
||||||
|
mustDecode(checksum),
|
||||||
|
pkg.TarBzip2,
|
||||||
|
), &PackageAttr{
|
||||||
|
Env: []string{
|
||||||
|
"CC=cc" +
|
||||||
|
// nonstandard glibc extension
|
||||||
|
" -DFNM_EXTMATCH=0",
|
||||||
|
},
|
||||||
|
}, &MakeHelper{
|
||||||
|
// nonstandard glibc extension
|
||||||
|
SkipCheck: true,
|
||||||
|
|
||||||
|
Configure: [][2]string{
|
||||||
|
{"enable-deterministic-archives"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
M4,
|
||||||
|
PkgConfig,
|
||||||
|
|
||||||
|
Zlib,
|
||||||
|
Bzip2,
|
||||||
|
Zstd,
|
||||||
|
ArgpStandalone,
|
||||||
|
MuslFts,
|
||||||
|
MuslObstack,
|
||||||
|
KernelHeaders,
|
||||||
|
), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[Elfutils] = Metadata{
|
||||||
|
f: Toolchain.newElfutils,
|
||||||
|
|
||||||
|
Name: "elfutils",
|
||||||
|
Description: "utilities and libraries to handle ELF files and DWARF data",
|
||||||
|
Website: "https://sourceware.org/elfutils/",
|
||||||
|
|
||||||
|
ID: 5679,
|
||||||
|
}
|
||||||
|
}
|
||||||
61
internal/rosa/fakeroot.go
Normal file
61
internal/rosa/fakeroot.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package rosa
|
||||||
|
|
||||||
|
import "hakurei.app/internal/pkg"
|
||||||
|
|
||||||
|
func (t Toolchain) newFakeroot() (pkg.Artifact, string) {
|
||||||
|
const (
|
||||||
|
version = "1.37.2"
|
||||||
|
checksum = "4ve-eDqVspzQ6VWDhPS0NjW3aSenBJcPAJq_BFT7OOFgUdrQzoTBxZWipDAGWxF8"
|
||||||
|
)
|
||||||
|
return t.NewPackage("fakeroot", version, pkg.NewHTTPGetTar(
|
||||||
|
nil, "https://salsa.debian.org/clint/fakeroot/-/archive/upstream/"+
|
||||||
|
version+"/fakeroot-upstream-"+version+".tar.bz2",
|
||||||
|
mustDecode(checksum),
|
||||||
|
pkg.TarBzip2,
|
||||||
|
), &PackageAttr{
|
||||||
|
Patches: [][2]string{
|
||||||
|
{"remove-broken-docs", `diff --git a/doc/Makefile.am b/doc/Makefile.am
|
||||||
|
index f135ad9..85c784c 100644
|
||||||
|
--- a/doc/Makefile.am
|
||||||
|
+++ b/doc/Makefile.am
|
||||||
|
@@ -1,5 +1,4 @@
|
||||||
|
AUTOMAKE_OPTIONS=foreign
|
||||||
|
-SUBDIRS = de es fr nl pt ro sv
|
||||||
|
|
||||||
|
man_MANS = faked.1 fakeroot.1
|
||||||
|
|
||||||
|
`},
|
||||||
|
},
|
||||||
|
|
||||||
|
Env: []string{
|
||||||
|
"CONFIG_SHELL=/bin/sh",
|
||||||
|
},
|
||||||
|
}, &MakeHelper{
|
||||||
|
Generate: "./bootstrap",
|
||||||
|
|
||||||
|
// makes assumptions about /etc/passwd
|
||||||
|
SkipCheck: true,
|
||||||
|
},
|
||||||
|
M4,
|
||||||
|
Perl,
|
||||||
|
Autoconf,
|
||||||
|
Automake,
|
||||||
|
Libtool,
|
||||||
|
PkgConfig,
|
||||||
|
|
||||||
|
Attr,
|
||||||
|
Libcap,
|
||||||
|
KernelHeaders,
|
||||||
|
), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[Fakeroot] = Metadata{
|
||||||
|
f: Toolchain.newFakeroot,
|
||||||
|
|
||||||
|
Name: "fakeroot",
|
||||||
|
Description: "tool for simulating superuser privileges",
|
||||||
|
Website: "https://salsa.debian.org/clint/fakeroot",
|
||||||
|
|
||||||
|
ID: 12048,
|
||||||
|
}
|
||||||
|
}
|
||||||
31
internal/rosa/flex.go
Normal file
31
internal/rosa/flex.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package rosa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Toolchain) newFlex() (pkg.Artifact, string) {
|
||||||
|
const (
|
||||||
|
version = "2.6.4"
|
||||||
|
checksum = "p9POjQU7VhgOf3x5iFro8fjhy0NOanvA7CTeuWS_veSNgCixIJshTrWVkc5XLZkB"
|
||||||
|
)
|
||||||
|
return t.NewPackage("flex", version, pkg.NewHTTPGetTar(
|
||||||
|
nil, "https://github.com/westes/flex/releases/download/"+
|
||||||
|
"v"+version+"/flex-"+version+".tar.gz",
|
||||||
|
mustDecode(checksum),
|
||||||
|
pkg.TarGzip,
|
||||||
|
), nil, (*MakeHelper)(nil),
|
||||||
|
M4,
|
||||||
|
), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[Flex] = Metadata{
|
||||||
|
f: Toolchain.newFlex,
|
||||||
|
|
||||||
|
Name: "flex",
|
||||||
|
Description: "scanner generator for lexing in C and C++",
|
||||||
|
Website: "https://github.com/westes/flex/",
|
||||||
|
|
||||||
|
ID: 819,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,44 +2,45 @@ package rosa
|
|||||||
|
|
||||||
import "hakurei.app/internal/pkg"
|
import "hakurei.app/internal/pkg"
|
||||||
|
|
||||||
func (t Toolchain) newFuse() pkg.Artifact {
|
func (t Toolchain) newFuse() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "3.18.1"
|
version = "3.18.1"
|
||||||
checksum = "COb-BgJRWXLbt9XUkNeuiroQizpMifXqxgieE1SlkMXhs_WGSyJStrmyewAw2hd6"
|
checksum = "COb-BgJRWXLbt9XUkNeuiroQizpMifXqxgieE1SlkMXhs_WGSyJStrmyewAw2hd6"
|
||||||
)
|
)
|
||||||
return t.New("fuse-"+version, 0, []pkg.Artifact{
|
return t.NewPackage("fuse", version, pkg.NewHTTPGetTar(
|
||||||
t.Load(Python),
|
|
||||||
t.Load(Meson),
|
|
||||||
t.Load(Ninja),
|
|
||||||
|
|
||||||
t.Load(IniConfig),
|
|
||||||
t.Load(Packaging),
|
|
||||||
t.Load(Pluggy),
|
|
||||||
t.Load(Pygments),
|
|
||||||
t.Load(PyTest),
|
|
||||||
|
|
||||||
t.Load(KernelHeaders),
|
|
||||||
}, nil, nil, `
|
|
||||||
cd "$(mktemp -d)"
|
|
||||||
meson setup \
|
|
||||||
--reconfigure \
|
|
||||||
--buildtype=release \
|
|
||||||
--prefix=/system \
|
|
||||||
--prefer-static \
|
|
||||||
-Dtests=true \
|
|
||||||
-Duseroot=false \
|
|
||||||
-Dinitscriptdir=/system/init.d \
|
|
||||||
-Ddefault_library=both \
|
|
||||||
. /usr/src/fuse
|
|
||||||
meson compile
|
|
||||||
python3 -m pytest test/
|
|
||||||
meson install \
|
|
||||||
--destdir=/work
|
|
||||||
`, pkg.Path(AbsUsrSrc.Append("fuse"), false, pkg.NewHTTPGetTar(
|
|
||||||
nil, "https://github.com/libfuse/libfuse/releases/download/"+
|
nil, "https://github.com/libfuse/libfuse/releases/download/"+
|
||||||
"fuse-"+version+"/fuse-"+version+".tar.gz",
|
"fuse-"+version+"/fuse-"+version+".tar.gz",
|
||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
)))
|
), nil, &MesonHelper{
|
||||||
|
Setup: [][2]string{
|
||||||
|
{"Ddefault_library", "both"},
|
||||||
|
{"Dtests", "true"},
|
||||||
|
{"Duseroot", "false"},
|
||||||
|
{"Dinitscriptdir", "/system/etc"},
|
||||||
|
},
|
||||||
|
|
||||||
|
ScriptCompiled: "python3 -m pytest test/",
|
||||||
|
// this project uses pytest
|
||||||
|
SkipTest: true,
|
||||||
|
},
|
||||||
|
PythonIniConfig,
|
||||||
|
PythonPackaging,
|
||||||
|
PythonPluggy,
|
||||||
|
PythonPygments,
|
||||||
|
PythonPyTest,
|
||||||
|
|
||||||
|
KernelHeaders,
|
||||||
|
), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[Fuse] = Metadata{
|
||||||
|
f: Toolchain.newFuse,
|
||||||
|
|
||||||
|
Name: "fuse",
|
||||||
|
Description: "the reference implementation of the Linux FUSE interface",
|
||||||
|
Website: "https://github.com/libfuse/libfuse/",
|
||||||
|
|
||||||
|
ID: 861,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func init() { artifactsF[Fuse] = Toolchain.newFuse }
|
|
||||||
|
|||||||
@@ -1,40 +1,30 @@
|
|||||||
package rosa
|
package rosa
|
||||||
|
|
||||||
import (
|
import "hakurei.app/internal/pkg"
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t Toolchain) newGit() pkg.Artifact {
|
func (t Toolchain) newGit() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "2.52.0"
|
version = "2.53.0"
|
||||||
checksum = "uH3J1HAN_c6PfGNJd2OBwW4zo36n71wmkdvityYnrh8Ak0D1IifiAvEWz9Vi9DmS"
|
checksum = "rlqSTeNgSeVKJA7nvzGqddFH8q3eFEPB4qRZft-4zth8wTHnbTbm7J90kp_obHGm"
|
||||||
)
|
)
|
||||||
return t.NewViaMake("git", version, t.NewPatchedSource(
|
return t.NewPackage("git", version, pkg.NewHTTPGetTar(
|
||||||
"git", version, pkg.NewHTTPGetTar(
|
|
||||||
nil, "https://www.kernel.org/pub/software/scm/git/"+
|
nil, "https://www.kernel.org/pub/software/scm/git/"+
|
||||||
"git-"+version+".tar.gz",
|
"git-"+version+".tar.gz",
|
||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), false,
|
), &PackageAttr{
|
||||||
), &MakeAttr{
|
|
||||||
// uses source tree as scratch space
|
|
||||||
Writable: true,
|
|
||||||
InPlace: true,
|
|
||||||
|
|
||||||
// test suite in subdirectory
|
|
||||||
SkipCheck: true,
|
|
||||||
|
|
||||||
Make: []string{"all"},
|
|
||||||
ScriptEarly: `
|
ScriptEarly: `
|
||||||
cd /usr/src/git
|
|
||||||
|
|
||||||
make configure
|
|
||||||
`,
|
|
||||||
Script: `
|
|
||||||
ln -s ../../system/bin/perl /usr/bin/ || true
|
ln -s ../../system/bin/perl /usr/bin/ || true
|
||||||
|
`,
|
||||||
|
|
||||||
|
// uses source tree as scratch space
|
||||||
|
EnterSource: true,
|
||||||
|
}, &MakeHelper{
|
||||||
|
InPlace: true,
|
||||||
|
Generate: "make configure",
|
||||||
|
ScriptMakeEarly: `
|
||||||
function disable_test {
|
function disable_test {
|
||||||
local test=$1 pattern=$2
|
local test=$1 pattern=${2:-''}
|
||||||
if [ $# -eq 1 ]; then
|
if [ $# -eq 1 ]; then
|
||||||
rm "t/${test}.sh"
|
rm "t/${test}.sh"
|
||||||
else
|
else
|
||||||
@@ -56,26 +46,36 @@ disable_test t9300-fast-import
|
|||||||
disable_test t0211-trace2-perf
|
disable_test t0211-trace2-perf
|
||||||
disable_test t1517-outside-repo
|
disable_test t1517-outside-repo
|
||||||
disable_test t2200-add-update
|
disable_test t2200-add-update
|
||||||
|
|
||||||
make \
|
|
||||||
-C t \
|
|
||||||
GIT_PROVE_OPTS="--jobs 32 --failures" \
|
|
||||||
prove
|
|
||||||
`,
|
`,
|
||||||
|
Check: []string{
|
||||||
|
"-C t",
|
||||||
|
`GIT_PROVE_OPTS="--jobs 32 --failures"`,
|
||||||
|
"prove",
|
||||||
},
|
},
|
||||||
t.Load(Perl),
|
},
|
||||||
t.Load(Diffutils),
|
Perl,
|
||||||
t.Load(M4),
|
Diffutils,
|
||||||
t.Load(Autoconf),
|
M4,
|
||||||
t.Load(Gettext),
|
Autoconf,
|
||||||
|
Gettext,
|
||||||
|
|
||||||
t.Load(Zlib),
|
Zlib,
|
||||||
t.Load(Curl),
|
Curl,
|
||||||
t.Load(OpenSSL),
|
OpenSSL,
|
||||||
t.Load(Libexpat),
|
Libexpat,
|
||||||
)
|
), version
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
artifactsM[Git] = Metadata{
|
||||||
|
f: Toolchain.newGit,
|
||||||
|
|
||||||
|
Name: "git",
|
||||||
|
Description: "distributed version control system",
|
||||||
|
Website: "https://www.git-scm.com/",
|
||||||
|
|
||||||
|
ID: 5350,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
func init() { artifactsF[Git] = Toolchain.newGit }
|
|
||||||
|
|
||||||
// NewViaGit returns a [pkg.Artifact] for cloning a git repository.
|
// NewViaGit returns a [pkg.Artifact] for cloning a git repository.
|
||||||
func (t Toolchain) NewViaGit(
|
func (t Toolchain) NewViaGit(
|
||||||
|
|||||||
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