forked from rosa/hakurei
Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a38b614c6 | ||
|
|
3286fff076 | ||
|
|
fd1884a84b | ||
|
|
fe6424bd6d | ||
|
|
004ac511a9 | ||
|
|
ba17f9d4f3 | ||
|
|
ea62f64b8f | ||
|
|
86669363ac | ||
|
|
6f5b7964f4 | ||
|
|
a195c3760c | ||
|
|
cfe52dce82 | ||
|
|
8483d8a005 | ||
|
|
5bc5aed024 | ||
|
|
9465649d13 | ||
|
|
33c461aa67 | ||
|
|
dee0204fc0 | ||
|
|
2f916ed0c0 | ||
|
|
55ce3a2f90 | ||
|
|
3f6a07ef59 | ||
|
|
02941e7c23 | ||
|
|
b9601881b7 | ||
|
|
58596f0af5 | ||
|
|
02cde40289 | ||
|
|
5014534884 | ||
|
|
13cf99ced4 | ||
|
|
6bfb258fd0 | ||
|
|
b649645189 | ||
|
|
3ddba4e21f | ||
|
|
40a906c6c2 | ||
|
|
06894e2104 | ||
|
|
56f0392b86 | ||
|
|
e2315f6c1a | ||
|
|
e4aee49eb0 | ||
|
|
6c03cc8b8a | ||
|
|
59ade6a86b | ||
|
|
59ab493035 | ||
|
|
d80a3346e2 | ||
|
|
327a34aacb | ||
|
|
ea7c6b3b48 |
@@ -1 +0,0 @@
|
|||||||
*.az linguist-language=Azalea
|
|
||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build for release
|
- name: Build for release
|
||||||
run: nix build --print-out-paths --print-build-logs ./test#dist
|
run: nix build --print-out-paths --print-build-logs .#dist
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: https://gitea.com/actions/release-action@main
|
uses: https://gitea.com/actions/release-action@main
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run NixOS test
|
- name: Run NixOS test
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs ./test#checks.x86_64-linux.hakurei
|
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.hakurei
|
||||||
|
|
||||||
- name: Upload test output
|
- name: Upload test output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run NixOS test
|
- name: Run NixOS test
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs ./test#checks.x86_64-linux.race
|
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.race
|
||||||
|
|
||||||
- name: Upload test output
|
- name: Upload test output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run NixOS test
|
- name: Run NixOS test
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox
|
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.sandbox
|
||||||
|
|
||||||
- name: Upload test output
|
- name: Upload test output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run NixOS test
|
- name: Run NixOS test
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox-race
|
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.sandbox-race
|
||||||
|
|
||||||
- name: Upload test output
|
- name: Upload test output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run NixOS test
|
- name: Run NixOS test
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sharefs
|
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.sharefs
|
||||||
|
|
||||||
- name: Upload test output
|
- name: Upload test output
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run checks
|
- name: Run checks
|
||||||
run: nix --print-build-logs --experimental-features 'nix-command flakes' flake check ./test
|
run: nix --print-build-logs --experimental-features 'nix-command flakes' flake check
|
||||||
|
|
||||||
dist:
|
dist:
|
||||||
name: Create distribution
|
name: Create distribution
|
||||||
@@ -116,9 +116,9 @@ jobs:
|
|||||||
id: build-test
|
id: build-test
|
||||||
run: >-
|
run: >-
|
||||||
export HAKUREI_REV="$(git rev-parse --short HEAD)" &&
|
export HAKUREI_REV="$(git rev-parse --short HEAD)" &&
|
||||||
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' test/package.nix &&
|
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' package.nix &&
|
||||||
nix build --print-out-paths --print-build-logs ./test#dist &&
|
nix build --print-out-paths --print-build-logs .#dist &&
|
||||||
mv test/{package.nix.old,package.nix} &&
|
mv package.nix.old package.nix &&
|
||||||
echo "rev=$HAKUREI_REV" >> $GITHUB_OUTPUT
|
echo "rev=$HAKUREI_REV" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload test build
|
- name: Upload test build
|
||||||
|
|||||||
+2
-6
@@ -1,5 +1,4 @@
|
|||||||
# produced by tools and text editors
|
# produced by tools and text editors
|
||||||
*.swp
|
|
||||||
*.qcow2
|
*.qcow2
|
||||||
*.test
|
*.test
|
||||||
*.out
|
*.out
|
||||||
@@ -8,12 +7,9 @@
|
|||||||
|
|
||||||
# go generate
|
# go generate
|
||||||
/cmd/hakurei/LICENSE
|
/cmd/hakurei/LICENSE
|
||||||
/cmd/mbf/internal/pkgserver/ui/static
|
/cmd/pkgserver/ui/static/*.js
|
||||||
/internal/pkg/internal/testtool/testtool
|
/internal/pkg/testdata/testtool
|
||||||
/internal/rosa/hakurei_current.tar.gz
|
/internal/rosa/hakurei_current.tar.gz
|
||||||
|
|
||||||
# cmd/dist default destination
|
# cmd/dist default destination
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
# local packages
|
|
||||||
/internal/rosa/package/local
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<a href="https://git.gensokyo.uk/rosa/hakurei/actions"><img src="https://git.gensokyo.uk/rosa/hakurei/actions/workflows/test.yml/badge.svg?branch=staging&style=flat-square" alt="Gitea Workflow Status" /></a>
|
<a href="https://git.gensokyo.uk/rosa/hakurei/actions"><img src="https://git.gensokyo.uk/rosa/hakurei/actions/workflows/test.yml/badge.svg?branch=staging&style=flat-square" alt="Gitea Workflow Status" /></a>
|
||||||
<br/>
|
<br/>
|
||||||
<a href="https://git.gensokyo.uk/rosa/hakurei/releases"><img src="https://img.shields.io/gitea/v/release/rosa/hakurei?gitea_url=https%3A%2F%2Fgit.gensokyo.uk&color=purple" alt="Release" /></a>
|
<a href="https://git.gensokyo.uk/rosa/hakurei/releases"><img src="https://img.shields.io/gitea/v/release/rosa/hakurei?gitea_url=https%3A%2F%2Fgit.gensokyo.uk&color=purple" alt="Release" /></a>
|
||||||
<a href="https://git.gensokyo.uk/rosa/hakurei/src/branch/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-pink" alt="MIT License" /></a>
|
<a href="https://goreportcard.com/report/hakurei.app"><img src="https://goreportcard.com/badge/hakurei.app" alt="Go Report Card" /></a>
|
||||||
<a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
|
<a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -58,3 +58,8 @@ package [internal/rosa](https://pkg.go.dev/hakurei.app/internal/rosa).
|
|||||||
|
|
||||||
New dependencies will generally not be added. Patches adding new dependencies
|
New dependencies will generally not be added. Patches adding new dependencies
|
||||||
are very likely to be rejected.
|
are very likely to be rejected.
|
||||||
|
|
||||||
|
## NixOS Module (deprecated)
|
||||||
|
|
||||||
|
The NixOS module is in maintenance mode and will be removed once planterette is
|
||||||
|
feature-complete. Full module documentation can be found [here](options.md).
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
#!/bin/sh -e
|
#!/bin/sh -e
|
||||||
|
|
||||||
HAKUREI_DIST_MAKE='' exec "$(dirname -- "$0")/cmd/dist/dist.sh"
|
TOOLCHAIN_VERSION="$(go version)"
|
||||||
|
cd "$(dirname -- "$0")/"
|
||||||
|
echo "# Building cmd/dist using ${TOOLCHAIN_VERSION}."
|
||||||
|
go run -v --tags=dist ./cmd/dist
|
||||||
|
|||||||
+2
-8
@@ -20,8 +20,8 @@ func (e AbsoluteError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e AbsoluteError) Is(target error) bool {
|
func (e AbsoluteError) Is(target error) bool {
|
||||||
ce, ok := errors.AsType[AbsoluteError](target)
|
var ce AbsoluteError
|
||||||
if !ok {
|
if !errors.As(target, &ce) {
|
||||||
return errors.Is(target, syscall.EINVAL)
|
return errors.Is(target, syscall.EINVAL)
|
||||||
}
|
}
|
||||||
return e == ce
|
return e == ce
|
||||||
@@ -31,8 +31,6 @@ func (e AbsoluteError) Is(target error) bool {
|
|||||||
type Absolute struct{ pathname unique.Handle[string] }
|
type Absolute struct{ pathname unique.Handle[string] }
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ fmt.GoStringer = new(Absolute)
|
|
||||||
|
|
||||||
_ encoding.TextAppender = new(Absolute)
|
_ encoding.TextAppender = new(Absolute)
|
||||||
_ encoding.TextMarshaler = new(Absolute)
|
_ encoding.TextMarshaler = new(Absolute)
|
||||||
_ encoding.TextUnmarshaler = new(Absolute)
|
_ encoding.TextUnmarshaler = new(Absolute)
|
||||||
@@ -42,10 +40,6 @@ var (
|
|||||||
_ encoding.BinaryUnmarshaler = new(Absolute)
|
_ encoding.BinaryUnmarshaler = new(Absolute)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *Absolute) GoString() string {
|
|
||||||
return fmt.Sprintf("check.MustAbs(%q)", a.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ok returns whether [Absolute] is not the zero value.
|
// ok returns whether [Absolute] is not the zero value.
|
||||||
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
|
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
|
||||||
|
|
||||||
|
|||||||
@@ -4,23 +4,15 @@ import "strings"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// SpecialOverlayEscape is the escape string for overlay mount options.
|
// SpecialOverlayEscape is the escape string for overlay mount options.
|
||||||
//
|
|
||||||
// Deprecated: This is no longer used and will be removed in 0.5.
|
|
||||||
SpecialOverlayEscape = `\`
|
SpecialOverlayEscape = `\`
|
||||||
// SpecialOverlayOption is the separator string between overlay mount options.
|
// SpecialOverlayOption is the separator string between overlay mount options.
|
||||||
//
|
|
||||||
// Deprecated: This is no longer used and will be removed in 0.5.
|
|
||||||
SpecialOverlayOption = ","
|
SpecialOverlayOption = ","
|
||||||
// SpecialOverlayPath is the separator string between overlay paths.
|
// SpecialOverlayPath is the separator string between overlay paths.
|
||||||
//
|
|
||||||
// Deprecated: This is no longer used and will be removed in 0.5.
|
|
||||||
SpecialOverlayPath = ":"
|
SpecialOverlayPath = ":"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EscapeOverlayDataSegment escapes a string for formatting into the data
|
// EscapeOverlayDataSegment escapes a string for formatting into the data
|
||||||
// argument of an overlay mount system call.
|
// argument of an overlay mount system call.
|
||||||
//
|
|
||||||
// Deprecated: This is no longer used and will be removed in 0.5.
|
|
||||||
func EscapeOverlayDataSegment(s string) string {
|
func EscapeOverlayDataSegment(s string) string {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
-354
@@ -1,354 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/ext"
|
|
||||||
"hakurei.app/fhs"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
)
|
|
||||||
|
|
||||||
// parsePair parses a NUL-delimited quoted paths pair.
|
|
||||||
func parsePair(s string) (source, target *check.Absolute, err error) {
|
|
||||||
var p string
|
|
||||||
if p, err = strconv.Unquote(s); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_source, _target, ok := strings.Cut(p, "\x00")
|
|
||||||
if source, err = check.NewAbs(_source); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target, err = check.NewAbs(_target)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse decodes a high-level configuration stream and returns its
|
|
||||||
// corresponding [hst.Config].
|
|
||||||
func parse(
|
|
||||||
id string,
|
|
||||||
base *check.Absolute,
|
|
||||||
r io.Reader,
|
|
||||||
templateP *string,
|
|
||||||
) (*hst.Config, error) {
|
|
||||||
shell := fhs.AbsRoot.Append("bin", "zsh")
|
|
||||||
home := hst.AbsPrivateTmp.Append("home")
|
|
||||||
|
|
||||||
root := hst.FSOverlay{
|
|
||||||
Target: fhs.AbsRoot,
|
|
||||||
Lower: []*check.Absolute{base.Append("initial")},
|
|
||||||
}
|
|
||||||
c := hst.Config{
|
|
||||||
ID: id,
|
|
||||||
Enablements: new(hst.Enablements),
|
|
||||||
|
|
||||||
SessionBus: &hst.BusConfig{
|
|
||||||
Own: []string{
|
|
||||||
id + ".*",
|
|
||||||
"org.mpris.MediaPlayer2." + id + ".*",
|
|
||||||
},
|
|
||||||
Filter: true,
|
|
||||||
},
|
|
||||||
SystemBus: &hst.BusConfig{Filter: true},
|
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
|
||||||
Env: make(map[string]string),
|
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
|
||||||
{FilesystemConfig: &root},
|
|
||||||
{FilesystemConfig: &hst.FSBind{
|
|
||||||
Target: home,
|
|
||||||
Source: base.Append("state", id),
|
|
||||||
Write: true,
|
|
||||||
Ensure: true,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{FilesystemConfig: &hst.FSEphemeral{
|
|
||||||
Target: fhs.AbsVar.Append("tmp"),
|
|
||||||
Write: true,
|
|
||||||
Perm: 01777,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
|
|
||||||
Username: "chronos",
|
|
||||||
Shell: shell,
|
|
||||||
Home: home,
|
|
||||||
Path: shell,
|
|
||||||
Args: []string{"zsh", "-c"},
|
|
||||||
|
|
||||||
Flags: hst.FCoverRun,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
s := bufio.NewScanner(r)
|
|
||||||
scanOnce := func() error {
|
|
||||||
if s.Scan() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := s.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := scanOnce(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if template, identity, ok := strings.Cut(s.Text(), ":"); !ok {
|
|
||||||
return nil, io.ErrUnexpectedEOF
|
|
||||||
} else if v, err := strconv.Atoi(identity); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
if templateP != nil {
|
|
||||||
*templateP = template
|
|
||||||
}
|
|
||||||
c.Identity = v
|
|
||||||
root.Upper = base.Append("template", template)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := scanOnce(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.Container.Args = append(c.Container.Args, s.Text(), "")
|
|
||||||
|
|
||||||
var flagInteractive, flagGPU, flagSystemBus bool
|
|
||||||
flags := map[string]*bool{
|
|
||||||
"interactive": &flagInteractive,
|
|
||||||
"gpu": &flagGPU,
|
|
||||||
"system_bus": &flagSystemBus,
|
|
||||||
}
|
|
||||||
|
|
||||||
for s.Scan() {
|
|
||||||
key, value, ok := strings.Cut(s.Text(), " ")
|
|
||||||
if key != "" && key[0] == ';' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
if key == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var p *bool
|
|
||||||
if p, ok = flags[key]; ok {
|
|
||||||
*p = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "wayland":
|
|
||||||
*c.Enablements |= hst.EWayland
|
|
||||||
case "x11":
|
|
||||||
*c.Enablements |= hst.EX11
|
|
||||||
case "dbus":
|
|
||||||
*c.Enablements |= hst.EDBus
|
|
||||||
case "pipewire":
|
|
||||||
*c.Enablements |= hst.EPipeWire
|
|
||||||
|
|
||||||
case "multiarch":
|
|
||||||
c.Container.Flags |= hst.FMultiarch
|
|
||||||
case "devel":
|
|
||||||
c.Container.Flags |= hst.FDevel
|
|
||||||
case "userns":
|
|
||||||
c.Container.Flags |= hst.FUserns
|
|
||||||
case "net":
|
|
||||||
c.Container.Flags |= hst.FHostNet
|
|
||||||
case "abstract":
|
|
||||||
c.Container.Flags |= hst.FHostAbstract
|
|
||||||
case "tty":
|
|
||||||
c.Container.Flags |= hst.FTty
|
|
||||||
case "mapuid":
|
|
||||||
c.Container.Flags |= hst.FMapRealUID
|
|
||||||
case "device":
|
|
||||||
c.Container.Flags |= hst.FDevice
|
|
||||||
|
|
||||||
case "share_runtime":
|
|
||||||
c.Container.Flags |= hst.FShareRuntime
|
|
||||||
case "share_tmpdir":
|
|
||||||
c.Container.Flags |= hst.FShareTmpdir
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid flag %q", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "username":
|
|
||||||
c.Container.Username = value
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "hostname":
|
|
||||||
c.Container.Hostname = value
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "group":
|
|
||||||
c.Groups = append(c.Groups, value)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "sched_policy":
|
|
||||||
if err := c.SchedPolicy.UnmarshalText([]byte(value)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "sched_priority":
|
|
||||||
v, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.SchedPriority = ext.Int(v)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "insecure":
|
|
||||||
switch value {
|
|
||||||
case "pipewire":
|
|
||||||
*c.Enablements |= hst.EPipeWire
|
|
||||||
c.DirectPipeWire = true
|
|
||||||
continue
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid insecure flag %q", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "env":
|
|
||||||
if key, value, ok = strings.Cut(value, "="); !ok {
|
|
||||||
return nil, fmt.Errorf("invalid environment %q", key)
|
|
||||||
}
|
|
||||||
c.Container.Env[key] = value
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "ro":
|
|
||||||
source, target, err := parsePair(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.Container.Filesystem = append(c.Container.Filesystem,
|
|
||||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
|
||||||
Target: target,
|
|
||||||
Source: source,
|
|
||||||
}},
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "ro+":
|
|
||||||
source, target, err := parsePair(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.Container.Filesystem = append(c.Container.Filesystem,
|
|
||||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
|
||||||
Target: target,
|
|
||||||
Source: source,
|
|
||||||
Optional: true,
|
|
||||||
}},
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "rw":
|
|
||||||
source, target, err := parsePair(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.Container.Filesystem = append(c.Container.Filesystem,
|
|
||||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
|
||||||
Target: target,
|
|
||||||
Source: source,
|
|
||||||
Write: true,
|
|
||||||
}},
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "dev":
|
|
||||||
source, target, err := parsePair(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.Container.Filesystem = append(c.Container.Filesystem,
|
|
||||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
|
|
||||||
Target: target,
|
|
||||||
Source: source,
|
|
||||||
Device: true,
|
|
||||||
}},
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "own":
|
|
||||||
c.SessionBus.Own = append(c.SessionBus.Own, value)
|
|
||||||
continue
|
|
||||||
case "own_system":
|
|
||||||
c.SystemBus.Own = append(c.SystemBus.Own, value)
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "talk":
|
|
||||||
c.SessionBus.Talk = append(c.SessionBus.Talk, value)
|
|
||||||
continue
|
|
||||||
case "talk_system":
|
|
||||||
c.SystemBus.Talk = append(c.SystemBus.Talk, value)
|
|
||||||
continue
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid key %q", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := s.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if flagInteractive {
|
|
||||||
c.Container.Args[1] += "i"
|
|
||||||
}
|
|
||||||
|
|
||||||
if flagGPU {
|
|
||||||
c.Container.Filesystem = append(c.Container.Filesystem, []hst.FilesystemConfigJSON{
|
|
||||||
{FilesystemConfig: &hst.FSBind{
|
|
||||||
Source: fhs.AbsDev.Append("dri"),
|
|
||||||
Device: true,
|
|
||||||
Optional: true,
|
|
||||||
}},
|
|
||||||
}...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !flagSystemBus {
|
|
||||||
c.SystemBus = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Container.Flags&hst.FShareTmpdir == 0 &&
|
|
||||||
(c.Enablements.Unwrap()&hst.EX11 == 0 ||
|
|
||||||
c.Container.Flags&(hst.FHostNet|hst.FHostAbstract) ==
|
|
||||||
hst.FHostNet|hst.FHostAbstract) {
|
|
||||||
c.Container.Filesystem = append(c.Container.Filesystem,
|
|
||||||
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{
|
|
||||||
Target: fhs.AbsTmp,
|
|
||||||
Write: true,
|
|
||||||
Perm: 01777,
|
|
||||||
}},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// list prints names of non-hidden entries in pathname.
|
|
||||||
func list(pathname *check.Absolute, dir bool) error {
|
|
||||||
dents, err := os.ReadDir(pathname.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, dent := range dents {
|
|
||||||
name := dent.Name()
|
|
||||||
if (dent.IsDir() != dir) || (len(name) > 0 && name[0] == '.') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Println(dent.Name())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/fhs"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
base := fhs.AbsProc.Append("nonexistent")
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
data string
|
|
||||||
want *hst.Config
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{"com.discordapp.Discord", `nonfree:8
|
|
||||||
exec Discord --ozone-platform-hint=wayland
|
|
||||||
|
|
||||||
gpu
|
|
||||||
wayland
|
|
||||||
dbus
|
|
||||||
system_bus
|
|
||||||
pipewire
|
|
||||||
userns
|
|
||||||
net
|
|
||||||
mapuid
|
|
||||||
|
|
||||||
share_runtime
|
|
||||||
share_tmpdir
|
|
||||||
|
|
||||||
group media_rw
|
|
||||||
env ELECTRON_TRASH=gio
|
|
||||||
rw "/sdcard"
|
|
||||||
; remove before reusing
|
|
||||||
ro "/bin\x00/.hakurei/bin"
|
|
||||||
|
|
||||||
talk org.kde.StatusNotifierWatcher
|
|
||||||
talk com.canonical.AppMenu.Registrar
|
|
||||||
talk com.canonical.indicator.application
|
|
||||||
talk com.canonical.Unity
|
|
||||||
`, &hst.Config{
|
|
||||||
Identity: 8,
|
|
||||||
ID: "com.discordapp.Discord",
|
|
||||||
Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire),
|
|
||||||
Groups: []string{"media_rw"},
|
|
||||||
|
|
||||||
SessionBus: &hst.BusConfig{
|
|
||||||
Talk: []string{
|
|
||||||
"org.kde.StatusNotifierWatcher",
|
|
||||||
"com.canonical.AppMenu.Registrar",
|
|
||||||
"com.canonical.indicator.application",
|
|
||||||
"com.canonical.Unity",
|
|
||||||
},
|
|
||||||
Own: []string{
|
|
||||||
"com.discordapp.Discord.*",
|
|
||||||
"org.mpris.MediaPlayer2.com.discordapp.Discord.*",
|
|
||||||
},
|
|
||||||
Filter: true,
|
|
||||||
},
|
|
||||||
SystemBus: &hst.BusConfig{Filter: true},
|
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
|
||||||
Env: map[string]string{
|
|
||||||
"ELECTRON_TRASH": "gio",
|
|
||||||
},
|
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
|
||||||
{FilesystemConfig: &hst.FSOverlay{
|
|
||||||
Target: fhs.AbsRoot,
|
|
||||||
Lower: []*check.Absolute{
|
|
||||||
base.Append("initial"),
|
|
||||||
},
|
|
||||||
Upper: base.Append("template", "nonfree"),
|
|
||||||
}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{
|
|
||||||
Target: hst.AbsPrivateTmp.Append("home"),
|
|
||||||
Source: base.Append("state", "com.discordapp.Discord"),
|
|
||||||
Write: true,
|
|
||||||
Ensure: true,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{FilesystemConfig: &hst.FSEphemeral{
|
|
||||||
Target: fhs.AbsVar.Append("tmp"),
|
|
||||||
Write: true,
|
|
||||||
Perm: 01777,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{FilesystemConfig: &hst.FSBind{
|
|
||||||
Source: check.MustAbs("/sdcard"),
|
|
||||||
Write: true,
|
|
||||||
}},
|
|
||||||
{FilesystemConfig: &hst.FSBind{
|
|
||||||
Target: check.MustAbs("/.hakurei/bin"),
|
|
||||||
Source: check.MustAbs("/bin"),
|
|
||||||
}},
|
|
||||||
|
|
||||||
{FilesystemConfig: &hst.FSBind{
|
|
||||||
Source: fhs.AbsDev.Append("dri"),
|
|
||||||
Device: true,
|
|
||||||
Optional: true,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
|
|
||||||
Username: "chronos",
|
|
||||||
Shell: fhs.AbsRoot.Append("bin", "zsh"),
|
|
||||||
Home: hst.AbsPrivateTmp.Append("home"),
|
|
||||||
Path: fhs.AbsRoot.Append("bin", "zsh"),
|
|
||||||
Args: []string{
|
|
||||||
"zsh", "-c",
|
|
||||||
"exec Discord --ozone-platform-hint=wayland",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
|
|
||||||
Flags: hst.FCoverRun | hst.FUserns | hst.FHostNet | hst.FMapRealUID |
|
|
||||||
hst.FShareRuntime | hst.FShareTmpdir,
|
|
||||||
},
|
|
||||||
}, nil},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
got, err := parse(
|
|
||||||
tc.name,
|
|
||||||
base,
|
|
||||||
strings.NewReader(tc.data),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(err, tc.err) {
|
|
||||||
t.Errorf("parse: error = %v, want %v", err, tc.err)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tc.want) {
|
|
||||||
t.Errorf("parse: %#v, want %#v", got, tc.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-112
@@ -1,112 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/fhs"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/internal/env"
|
|
||||||
"hakurei.app/internal/lockedfile"
|
|
||||||
"hakurei.app/internal/outcome"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MutationConflictError describes an active mutable instance.
|
|
||||||
type MutationConflictError string
|
|
||||||
|
|
||||||
func (e MutationConflictError) Error() string {
|
|
||||||
return "mutable instance active at " + string(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// informTemplate guards intention of a template or its derivatives.
|
|
||||||
func informTemplate(base *check.Absolute, name string, mutable bool) (func() error, error) {
|
|
||||||
mu := lockedfile.MutexAt(base.Append("lock", name).String())
|
|
||||||
if unlock, err := mu.Lock(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
defer unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
marker := base.Append("lock", "."+name)
|
|
||||||
if p, err := os.ReadFile(marker.String()); err == nil {
|
|
||||||
if _, err = os.Stat(fhs.AbsProc.Append(string(p)).String()); err == nil {
|
|
||||||
return nil, MutationConflictError(p)
|
|
||||||
} else if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Printf("removing stale marker by %s", string(p))
|
|
||||||
if err = os.Remove(marker.String()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !mutable {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var active []hst.ID
|
|
||||||
var sc hst.Paths
|
|
||||||
env.CopyPaths().Copy(&sc, new(outcome.Hsu).MustID(nil))
|
|
||||||
entries, copyError := outcome.NewStore(&sc).All()
|
|
||||||
var s hst.State
|
|
||||||
for eh := range entries {
|
|
||||||
s = hst.State{}
|
|
||||||
if _, err := eh.Load(&s); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Validate(0) != nil || len(s.Container.Filesystem) < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
root, ok := s.Container.Filesystem[0].FilesystemConfig.(*hst.FSOverlay)
|
|
||||||
if !ok || root == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !root.Target.Is(fhs.AbsRoot) ||
|
|
||||||
len(root.Lower) != 1 ||
|
|
||||||
!root.Lower[0].Is(base.Append("initial")) ||
|
|
||||||
!root.Upper.Is(base.Append("template", name)) ||
|
|
||||||
root.Work != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
active = append(active, s.ID)
|
|
||||||
}
|
|
||||||
if err := copyError(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(active) != 0 {
|
|
||||||
var buf strings.Builder
|
|
||||||
buf.WriteString("derivative instances still active:")
|
|
||||||
for _, id := range active {
|
|
||||||
buf.WriteString("\n\t")
|
|
||||||
buf.WriteString(id.String())
|
|
||||||
}
|
|
||||||
return nil, errors.New(buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return func() error { return os.RemoveAll(marker.String()) }, os.WriteFile(
|
|
||||||
marker.String(),
|
|
||||||
[]byte(strconv.Itoa(os.Getpid())),
|
|
||||||
0400,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// acquireTemplate obtains exclusivity of a template.
|
|
||||||
func acquireTemplate(base *check.Absolute, name string) (remove func() error, err error) {
|
|
||||||
return informTemplate(base, name, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// enterTemplate checks against exclusivity of a template.
|
|
||||||
func enterTemplate(base *check.Absolute, name string) error {
|
|
||||||
_, err := informTemplate(base, name, false)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
-226
@@ -1,226 +0,0 @@
|
|||||||
// The app program is a proof-of-concept frontend for cmd/hakurei.
|
|
||||||
//
|
|
||||||
// This program is not covered by the compatibility promise. The command line
|
|
||||||
// interface and configuration syntax may change at any time.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/command"
|
|
||||||
"hakurei.app/fhs"
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
log.SetPrefix("app: ")
|
|
||||||
msg := message.New(log.Default())
|
|
||||||
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
|
||||||
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
|
||||||
defer stop()
|
|
||||||
|
|
||||||
var (
|
|
||||||
flagVerbose bool
|
|
||||||
flagBase string
|
|
||||||
flagInsecure bool
|
|
||||||
|
|
||||||
base, template, initial *check.Absolute
|
|
||||||
)
|
|
||||||
c := command.New(os.Stderr, log.Printf, "app", func([]string) (err error) {
|
|
||||||
msg.SwapVerbose(flagVerbose)
|
|
||||||
flagBase = os.ExpandEnv(flagBase)
|
|
||||||
if flagBase == "" {
|
|
||||||
flagBase = "state"
|
|
||||||
}
|
|
||||||
if flagBase, err = filepath.Abs(flagBase); err != nil {
|
|
||||||
return
|
|
||||||
} else if base, err = check.NewAbs(flagBase); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
template = base.Append("template")
|
|
||||||
initial = base.Append("initial")
|
|
||||||
return
|
|
||||||
}).Flag(
|
|
||||||
&flagVerbose,
|
|
||||||
"v", command.BoolFlag(false),
|
|
||||||
"Increase log verbosity",
|
|
||||||
).Flag(
|
|
||||||
&flagBase,
|
|
||||||
"d", command.StringFlag("$ROSA_APP_PATH"),
|
|
||||||
"Configuration and state directory",
|
|
||||||
).Flag(
|
|
||||||
&flagInsecure,
|
|
||||||
"insecure", command.BoolFlag(false),
|
|
||||||
"Allow use of insecure compatibility options",
|
|
||||||
)
|
|
||||||
|
|
||||||
{
|
|
||||||
var (
|
|
||||||
flagShell string
|
|
||||||
flagHome string
|
|
||||||
)
|
|
||||||
c.NewCommand(
|
|
||||||
"enter", "Enter mutable state template",
|
|
||||||
func(args []string) error {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return list(template, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := hst.Config{
|
|
||||||
ID: "app.hakurei.mutable." + args[0],
|
|
||||||
Container: &hst.ContainerConfig{
|
|
||||||
Hostname: args[0] + "-mutable",
|
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
|
||||||
{FilesystemConfig: &hst.FSOverlay{
|
|
||||||
Target: fhs.AbsRoot,
|
|
||||||
Lower: []*check.Absolute{initial},
|
|
||||||
Upper: template.Append(args[0]),
|
|
||||||
Work: base.Append("work", args[0]),
|
|
||||||
}},
|
|
||||||
{FilesystemConfig: &hst.FSEphemeral{
|
|
||||||
Target: fhs.AbsTmp,
|
|
||||||
Write: true,
|
|
||||||
Perm: 0755,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
Username: "chronos",
|
|
||||||
Flags: hst.FNoPlace |
|
|
||||||
hst.FMultiarch |
|
|
||||||
hst.FDevel |
|
|
||||||
hst.FUserns |
|
|
||||||
hst.FHostNet |
|
|
||||||
hst.FTty,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if a, err := check.NewAbs(flagShell); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
config.Container.Shell = a
|
|
||||||
config.Container.Path = a
|
|
||||||
config.Container.Args = []string{
|
|
||||||
"-" + filepath.Base(flagShell),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a, err := check.NewAbs(flagHome); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
config.Container.Home = a
|
|
||||||
}
|
|
||||||
|
|
||||||
remove, err := acquireTemplate(base, args[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = run(ctx, msg, false, &config)
|
|
||||||
return errors.Join(err, remove())
|
|
||||||
},
|
|
||||||
).Flag(
|
|
||||||
&flagShell,
|
|
||||||
"shell", command.StringFlag("/bin/zsh"),
|
|
||||||
"Shell program within container",
|
|
||||||
).Flag(
|
|
||||||
&flagHome,
|
|
||||||
"home", command.StringFlag("/home/chronos"),
|
|
||||||
"Home directory within container",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var (
|
|
||||||
flagCommand string
|
|
||||||
)
|
|
||||||
c.NewCommand(
|
|
||||||
"run", "Start the named application",
|
|
||||||
func(args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return list(base.Append("app"), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
var config *hst.Config
|
|
||||||
var r io.Reader
|
|
||||||
f, err := os.Open(base.Append("app", args[0]).String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r = f
|
|
||||||
|
|
||||||
var common *os.File
|
|
||||||
if common, err = os.Open(base.Append("common").String()); err != nil {
|
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
_ = f.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
r = io.MultiReader(f, common)
|
|
||||||
}
|
|
||||||
|
|
||||||
var name string
|
|
||||||
config, err = parse(args[0], base, r, &name)
|
|
||||||
if closeErr := f.Close(); err == nil {
|
|
||||||
err = closeErr
|
|
||||||
}
|
|
||||||
if common != nil {
|
|
||||||
if closeErr := common.Close(); err == nil {
|
|
||||||
err = closeErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if flagCommand != "" {
|
|
||||||
config.Container.Args[2] = flagCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = enterTemplate(base, name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return run(ctx, msg, flagInsecure, config, args[1:]...)
|
|
||||||
},
|
|
||||||
).
|
|
||||||
Flag(
|
|
||||||
&flagCommand,
|
|
||||||
"command", command.StringFlag(""),
|
|
||||||
"Override configured command",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
|
||||||
if e, ok := errors.AsType[*exec.ExitError](err); ok && e != nil {
|
|
||||||
os.Exit(e.ExitCode())
|
|
||||||
}
|
|
||||||
|
|
||||||
if w, ok := err.(interface{ Unwrap() []error }); !ok {
|
|
||||||
var m string
|
|
||||||
m, ok = message.GetMessage(err)
|
|
||||||
if !ok {
|
|
||||||
log.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Fatal(m)
|
|
||||||
} else {
|
|
||||||
errs := w.Unwrap()
|
|
||||||
for i, e := range errs {
|
|
||||||
if i == len(errs)-1 {
|
|
||||||
log.Fatal(e)
|
|
||||||
}
|
|
||||||
log.Println(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"hakurei.app/hst"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
// run starts a container via cmd/hakurei and returns after it terminates.
|
|
||||||
func run(
|
|
||||||
ctx context.Context,
|
|
||||||
msg message.Msg,
|
|
||||||
insecure bool,
|
|
||||||
config *hst.Config,
|
|
||||||
args ...string,
|
|
||||||
) error {
|
|
||||||
c, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
cmd := exec.CommandContext(c, "hakurei")
|
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
||||||
cmd.Cancel = func() error {
|
|
||||||
return cmd.Process.Signal(syscall.SIGINT)
|
|
||||||
}
|
|
||||||
if msg.IsVerbose() {
|
|
||||||
cmd.Args = append(cmd.Args, "-v")
|
|
||||||
}
|
|
||||||
if insecure {
|
|
||||||
cmd.Args = append(cmd.Args, "--insecure")
|
|
||||||
}
|
|
||||||
cmd.Args = append(cmd.Args, "run", "3")
|
|
||||||
cmd.Args = append(cmd.Args, args...)
|
|
||||||
|
|
||||||
r, w, err := os.Pipe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd.ExtraFiles = append(cmd.ExtraFiles, r)
|
|
||||||
|
|
||||||
if err = cmd.Start(); err != nil {
|
|
||||||
_, _ = r.Close(), w.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = r.Close(); err != nil {
|
|
||||||
_ = w.Close()
|
|
||||||
return err
|
|
||||||
} else if err = json.NewEncoder(w).Encode(&config); err != nil {
|
|
||||||
_ = w.Close()
|
|
||||||
return err
|
|
||||||
} else if err = w.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd.Wait()
|
|
||||||
}
|
|
||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
v0.4.6
|
|
||||||
Vendored
-6
@@ -36,11 +36,6 @@ _hakurei_show() {
|
|||||||
'files:files:__hakurei_files'
|
'files:files:__hakurei_files'
|
||||||
}
|
}
|
||||||
|
|
||||||
_hakurei_kill() {
|
|
||||||
_alternative \
|
|
||||||
'instances:domains:__hakurei_instances'
|
|
||||||
}
|
|
||||||
|
|
||||||
__hakurei_files() {
|
__hakurei_files() {
|
||||||
_files -g "*.(json|hakurei)"
|
_files -g "*.(json|hakurei)"
|
||||||
return $?
|
return $?
|
||||||
@@ -65,7 +60,6 @@ __hakurei_instances() {
|
|||||||
"run:Load and start container from configuration file"
|
"run:Load and start container from configuration file"
|
||||||
"exec:Configure and start a permissive container"
|
"exec:Configure and start a permissive container"
|
||||||
"show:Show live or local instance configuration"
|
"show:Show live or local instance configuration"
|
||||||
"kill:Terminate an active instance"
|
|
||||||
"ps:List active instances"
|
"ps:List active instances"
|
||||||
"version:Display version information"
|
"version:Display version information"
|
||||||
"license:Show full license text"
|
"license:Show full license text"
|
||||||
|
|||||||
Vendored
-10
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/sh -e
|
|
||||||
|
|
||||||
TOOLCHAIN_VERSION="$(go version)"
|
|
||||||
cd "$(dirname -- "$0")/../.."
|
|
||||||
echo "Building cmd/dist using ${TOOLCHAIN_VERSION}."
|
|
||||||
FLAGS=''
|
|
||||||
if test -n "$VERBOSE"; then
|
|
||||||
FLAGS="$FLAGS -v"
|
|
||||||
fi
|
|
||||||
go run $FLAGS --tags=dist ./cmd/dist
|
|
||||||
Vendored
+19
-51
@@ -18,13 +18,8 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate sh -c "git describe --tags > VERSION"
|
|
||||||
//go:embed VERSION
|
|
||||||
var version string
|
|
||||||
|
|
||||||
// getenv looks up an environment variable, and returns fallback if it is unset.
|
// getenv looks up an environment variable, and returns fallback if it is unset.
|
||||||
func getenv(key, fallback string) string {
|
func getenv(key, fallback string) string {
|
||||||
if v, ok := os.LookupEnv(key); ok {
|
if v, ok := os.LookupEnv(key); ok {
|
||||||
@@ -35,11 +30,8 @@ func getenv(key, fallback string) string {
|
|||||||
|
|
||||||
// mustRun runs a command with the current process's environment and panics
|
// mustRun runs a command with the current process's environment and panics
|
||||||
// on error or non-zero exit code.
|
// on error or non-zero exit code.
|
||||||
func mustRun(ctx context.Context, env []string, name string, arg ...string) {
|
func mustRun(ctx context.Context, name string, arg ...string) {
|
||||||
cmd := exec.CommandContext(ctx, name, arg...)
|
cmd := exec.CommandContext(ctx, name, arg...)
|
||||||
if env != nil {
|
|
||||||
cmd.Env = append(cmd.Environ(), env...)
|
|
||||||
}
|
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -50,20 +42,14 @@ func mustRun(ctx context.Context, env []string, name string, arg ...string) {
|
|||||||
var comp []byte
|
var comp []byte
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
fmt.Println()
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("")
|
log.SetPrefix("# ")
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
|
|
||||||
verbose := os.Getenv("VERBOSE") != ""
|
version := getenv("HAKUREI_VERSION", "untagged")
|
||||||
runTests := os.Getenv("HAKUREI_DIST_MAKE") == ""
|
prefix := getenv("PREFIX", "/usr")
|
||||||
version = getenv("HAKUREI_VERSION", strings.TrimSpace(version))
|
|
||||||
prefix := getenv("PREFIX", "/usr/local")
|
|
||||||
destdir := getenv("DESTDIR", "dist")
|
destdir := getenv("DESTDIR", "dist")
|
||||||
|
|
||||||
if verbose {
|
|
||||||
log.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(destdir, 0755); err != nil {
|
if err := os.MkdirAll(destdir, 0755); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -90,49 +76,31 @@ func main() {
|
|||||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
verboseFlag := "-v"
|
log.Println("Building hakurei.")
|
||||||
if !verbose {
|
mustRun(ctx, "go", "generate", "./...")
|
||||||
verboseFlag = "-buildvcs=false"
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Building hakurei %s for %s/%s.", version, runtime.GOOS, runtime.GOARCH)
|
|
||||||
mustRun(ctx, nil, "go", "generate", "./...")
|
|
||||||
mustRun(
|
mustRun(
|
||||||
ctx, nil, "go", "build",
|
ctx, "go", "build",
|
||||||
"-trimpath",
|
"-trimpath",
|
||||||
verboseFlag, "-o", s,
|
"-v", "-o", s,
|
||||||
"-ldflags=-s -w "+
|
"-ldflags=-s -w "+
|
||||||
"-buildid= -linkmode external -extldflags=-static "+
|
"-buildid= -linkmode external -extldflags=-static "+
|
||||||
"-X hakurei.app/internal/info.buildVersion="+version+" "+
|
"-X hakurei.app/internal/info.buildVersion="+version+" "+
|
||||||
"-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+
|
"-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+
|
||||||
"-X hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu",
|
"-X hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu "+
|
||||||
"./cmd/hakurei",
|
|
||||||
"./cmd/sharefs",
|
|
||||||
)
|
|
||||||
|
|
||||||
log.Printf("Building cmd/hsu for %s/%s.", runtime.GOOS, runtime.GOARCH)
|
|
||||||
mustRun(
|
|
||||||
ctx, []string{"CGO_ENABLED=0"}, "go", "build",
|
|
||||||
"-trimpath",
|
|
||||||
verboseFlag, "-o", s,
|
|
||||||
"-ldflags=-s -w "+
|
|
||||||
"-buildid= "+
|
|
||||||
"-X main.hakureiPath="+prefix+"/bin/hakurei",
|
"-X main.hakureiPath="+prefix+"/bin/hakurei",
|
||||||
"./cmd/hsu",
|
"./...",
|
||||||
)
|
)
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
log.Println()
|
log.Println("Testing Hakurei.")
|
||||||
if runTests {
|
|
||||||
log.Println("##### Testing Hakurei.")
|
|
||||||
mustRun(
|
mustRun(
|
||||||
ctx, nil, "go", "test",
|
ctx, "go", "test",
|
||||||
"-ldflags=-buildid= -linkmode external -extldflags=-static",
|
"-ldflags=-buildid= -linkmode external -extldflags=-static",
|
||||||
"./...",
|
"./...",
|
||||||
)
|
)
|
||||||
log.Println()
|
fmt.Println()
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("##### Creating distribution.")
|
log.Println("Creating distribution.")
|
||||||
const suffix = ".tar.gz"
|
const suffix = ".tar.gz"
|
||||||
distName := "hakurei-" + version + "-" + runtime.GOARCH
|
distName := "hakurei-" + version + "-" + runtime.GOARCH
|
||||||
var f *os.File
|
var f *os.File
|
||||||
@@ -153,7 +121,7 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
h := sha512.New()
|
h := sha512.New()
|
||||||
gw, _ := gzip.NewWriterLevel(io.MultiWriter(f, h), gzip.BestCompression)
|
gw := gzip.NewWriter(io.MultiWriter(f, h))
|
||||||
tw := tar.NewWriter(gw)
|
tw := tar.NewWriter(gw)
|
||||||
|
|
||||||
mustWriteHeader := func(name string, size int64, mode os.FileMode) {
|
mustWriteHeader := func(name string, size int64, mode os.FileMode) {
|
||||||
@@ -217,7 +185,7 @@ func main() {
|
|||||||
mustWriteFile("comp/", nil, os.ModeDir|0755)
|
mustWriteFile("comp/", nil, os.ModeDir|0755)
|
||||||
mustWriteFile("comp/_hakurei", comp, 0644)
|
mustWriteFile("comp/_hakurei", comp, 0644)
|
||||||
mustWriteFile("install.sh", []byte(`#!/bin/sh -e
|
mustWriteFile("install.sh", []byte(`#!/bin/sh -e
|
||||||
cd "$(dirname -- "$0")"
|
cd "$(dirname -- "$0")" || exit 1
|
||||||
|
|
||||||
install -vDm0755 "bin/hakurei" "${DESTDIR}`+prefix+`/bin/hakurei"
|
install -vDm0755 "bin/hakurei" "${DESTDIR}`+prefix+`/bin/hakurei"
|
||||||
install -vDm0755 "bin/sharefs" "${DESTDIR}`+prefix+`/bin/sharefs"
|
install -vDm0755 "bin/sharefs" "${DESTDIR}`+prefix+`/bin/sharefs"
|
||||||
@@ -230,7 +198,7 @@ fi
|
|||||||
install -vDm0644 "comp/_hakurei" "${DESTDIR}`+prefix+`/share/zsh/site-functions/_hakurei"
|
install -vDm0644 "comp/_hakurei" "${DESTDIR}`+prefix+`/share/zsh/site-functions/_hakurei"
|
||||||
`), 0755)
|
`), 0755)
|
||||||
|
|
||||||
mustWriteFromPath("LICENSE", "LICENSE", 0)
|
mustWriteFromPath("README.md", "README.md", 0)
|
||||||
mustWriteFile("hsurc.default", []byte("1000 0"), 0400)
|
mustWriteFile("hsurc.default", []byte("1000 0"), 0400)
|
||||||
mustWriteFromPath("bin/hsu", filepath.Join(s, "hsu"), 04511)
|
mustWriteFromPath("bin/hsu", filepath.Join(s, "hsu"), 04511)
|
||||||
for _, name := range []string{
|
for _, name := range []string{
|
||||||
|
|||||||
+20
-155
@@ -5,93 +5,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
|
|
||||||
"hakurei.app/internal/kobject"
|
|
||||||
"hakurei.app/internal/report"
|
|
||||||
"hakurei.app/internal/uevent"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
var r report.Reporter
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
log.SetPrefix("earlyinit: ")
|
|
||||||
r.SetOutput(log.Default())
|
|
||||||
|
|
||||||
// this handles SIGQUIT to provide useful debugging information without
|
|
||||||
// terminating, and prevents the runtime from throwing on the must family
|
|
||||||
// of early error reporting functions, DO NOT REMOVE
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(c, SIGQUIT)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
<-c
|
|
||||||
if p := pprof.Lookup("goroutine"); p == nil {
|
|
||||||
log.Println("initial built-in goroutine profile does not exist")
|
|
||||||
} else if err := p.WriteTo(os.Stderr, 2); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// fatal calls [log.Println] with v and blocks forever. Must be called from
|
|
||||||
// main. Must not be used after error reporting is set up.
|
|
||||||
func fatal(v ...any) {
|
|
||||||
log.Println(v...)
|
|
||||||
log.Println("unable to continue, please reboot and resolve the problem manually")
|
|
||||||
log.SetOutput(io.Discard)
|
|
||||||
select {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// must calls fatal with err if it is non-nil.
|
|
||||||
func must(err error) {
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
select {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mustSyscall is like must, but with an additional action name.
|
|
||||||
func mustSyscall(action string, err error) {
|
|
||||||
if err != nil {
|
|
||||||
fatal("cannot "+action+":", err)
|
|
||||||
select {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// must1 is like must, but with an additional passed through value.
|
|
||||||
func must1[T any](v T, err error) T {
|
|
||||||
must(err)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// optionSystem specifies devpath of the system device.
|
|
||||||
optionSystem = "system"
|
|
||||||
|
|
||||||
// flagVerbose increases output verbosity.
|
|
||||||
flagVerbose = "verbose"
|
|
||||||
// flagStrict sets [report.DStrict] on r.
|
|
||||||
flagStrict = "strict"
|
|
||||||
// flagNoRecover sets [report.DNoRecover] on r.
|
|
||||||
flagNoRecover = "no_recover"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("earlyinit: ")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
option map[string]string
|
option map[string]string
|
||||||
@@ -109,44 +33,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if err := Mount(
|
||||||
var flag uint64
|
|
||||||
if slices.Contains(flags, flagStrict) {
|
|
||||||
flag |= report.DStrict
|
|
||||||
}
|
|
||||||
if slices.Contains(flags, flagNoRecover) {
|
|
||||||
flag |= report.DNoRecover
|
|
||||||
}
|
|
||||||
log.Printf("reporting flags %x", flag)
|
|
||||||
r.SetFlags(flag)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := message.New(log.Default())
|
|
||||||
msg.SwapVerbose(slices.Contains(flags, flagVerbose))
|
|
||||||
|
|
||||||
mustSyscall("mount devtmpfs", Mount(
|
|
||||||
"devtmpfs",
|
"devtmpfs",
|
||||||
"/dev/",
|
"/dev/",
|
||||||
"devtmpfs",
|
"devtmpfs",
|
||||||
MS_NOSUID|MS_NOEXEC,
|
MS_NOSUID|MS_NOEXEC,
|
||||||
"",
|
"",
|
||||||
))
|
); err != nil {
|
||||||
must(os.Mkdir("/dev/pts/", 0))
|
log.Fatalf("cannot mount devtmpfs: %v", err)
|
||||||
mustSyscall("mount devpts", Mount(
|
}
|
||||||
"devpts",
|
|
||||||
"/dev/pts/",
|
|
||||||
"devpts",
|
|
||||||
MS_NOSUID|MS_NOEXEC,
|
|
||||||
"mode=620,ptmxmode=666",
|
|
||||||
))
|
|
||||||
must(os.Mkdir("/dev/shm/", 0))
|
|
||||||
mustSyscall("mount shm", Mount(
|
|
||||||
"shm",
|
|
||||||
"/dev/shm/",
|
|
||||||
"tmpfs",
|
|
||||||
MS_NOSUID|MS_NODEV,
|
|
||||||
"",
|
|
||||||
))
|
|
||||||
|
|
||||||
// The kernel might be unable to set up the console. When that happens,
|
// The kernel might be unable to set up the console. When that happens,
|
||||||
// printk is called with "Warning: unable to open an initial console."
|
// printk is called with "Warning: unable to open an initial console."
|
||||||
@@ -203,49 +98,6 @@ func main() {
|
|||||||
"",
|
"",
|
||||||
))
|
))
|
||||||
|
|
||||||
conn := must1(uevent.Dial(-128 * 1024 * 1024))
|
|
||||||
events := make(chan *uevent.Message, 1<<10)
|
|
||||||
var uuid uevent.UUID
|
|
||||||
must1(rand.Read(uuid[:]))
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
go consume(ctx, msg, &r, conn, uuid, events)
|
|
||||||
s := kobject.New(uuid, func(o *kobject.Object, env map[string]string) {
|
|
||||||
p := make([]string, 0, len(env))
|
|
||||||
for k, v := range env {
|
|
||||||
p = append(p, k+"="+v)
|
|
||||||
}
|
|
||||||
slices.Sort(p)
|
|
||||||
log.Printf("change %s: %s", o.DevPath, strings.Join(p, ", "))
|
|
||||||
}, func(err error) {
|
|
||||||
severity := report.Inconsistent
|
|
||||||
if e, ok := err.(kobject.EventError); ok && e.Kind == kobject.EBadTarget {
|
|
||||||
severity = report.Trivial
|
|
||||||
}
|
|
||||||
r.Dispatch(
|
|
||||||
severity,
|
|
||||||
"processed inconsistent uevent",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
go func() {
|
|
||||||
s.Consume(ctx, events)
|
|
||||||
|
|
||||||
log.Println("closing NETLINK_KOBJECT_UEVENT socket")
|
|
||||||
cancel()
|
|
||||||
if err := conn.Close(); err != nil {
|
|
||||||
log.Fatal(err) // not reached
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
must(os.Mkdir("/system", 0))
|
|
||||||
if devpath := option[optionSystem]; devpath == "" {
|
|
||||||
fatal("system must be nonempty")
|
|
||||||
} else {
|
|
||||||
log.Printf("waiting for devpath pattern %q", devpath)
|
|
||||||
mustMountSystem(ctx, s, devpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// after top level has been set up
|
// after top level has been set up
|
||||||
mustSyscall("remount root", Mount(
|
mustSyscall("remount root", Mount(
|
||||||
"",
|
"",
|
||||||
@@ -261,6 +113,19 @@ func main() {
|
|||||||
[]byte("/system/lib/firmware"),
|
[]byte("/system/lib/firmware"),
|
||||||
0,
|
0,
|
||||||
))
|
))
|
||||||
go dispatchModprobe(ctx, s)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mustSyscall calls [log.Fatalln] if err is non-nil.
|
||||||
|
func mustSyscall(action string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("cannot "+action+":", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// must calls [log.Fatal] with err if it is non-nil.
|
||||||
|
func must(err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"hakurei.app/internal/kobject"
|
|
||||||
"hakurei.app/internal/report"
|
|
||||||
"hakurei.app/internal/uevent"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ModprobeError describes an unsuccessful modprobe invocation.
|
|
||||||
type ModprobeError struct {
|
|
||||||
ModAlias string `json:"modalias"`
|
|
||||||
Stdout string `json:"stdout"`
|
|
||||||
Stderr string `json:"stderr"`
|
|
||||||
ExitCode int `json:"exit_code"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ report.RepresentableError = ModprobeError{}
|
|
||||||
|
|
||||||
func (ModprobeError) Representable() {}
|
|
||||||
func (e ModprobeError) Error() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%s (exit status %d)",
|
|
||||||
strings.TrimPrefix(strings.TrimSpace(e.Stderr), "modprobe: "),
|
|
||||||
e.ExitCode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dispatchModprobe invokes modprobe for [uevent.KOBJ_ADD] events raising new
|
|
||||||
// MODALIAS strings.
|
|
||||||
func dispatchModprobe(
|
|
||||||
ctx context.Context,
|
|
||||||
s *kobject.State,
|
|
||||||
) {
|
|
||||||
aliases := make(chan string, 1<<8)
|
|
||||||
go func() {
|
|
||||||
defer close(aliases)
|
|
||||||
s.Range(ctx, func(o *kobject.Object, act uevent.KobjectAction) bool {
|
|
||||||
if act == uevent.KOBJ_ADD && o.Driver == "" && o.ModAlias != "" {
|
|
||||||
aliases <- o.ModAlias
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
|
|
||||||
for alias := range aliases {
|
|
||||||
stdout, err := exec.Command("/system/sbin/modprobe", alias).Output()
|
|
||||||
if err == nil {
|
|
||||||
if len(stdout) > 0 {
|
|
||||||
log.Println(string(stdout))
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
exitError, ok := errors.AsType[*exec.ExitError](err)
|
|
||||||
if !ok || exitError == nil {
|
|
||||||
r.Dispatch(report.Degraded, "invoke modprobe", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Dispatch(report.Trivial, "load device driver", ModprobeError{
|
|
||||||
ModAlias: alias,
|
|
||||||
Stdout: string(stdout),
|
|
||||||
Stderr: string(exitError.Stderr),
|
|
||||||
ExitCode: exitError.ExitCode(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/fhs"
|
|
||||||
"hakurei.app/internal/kobject"
|
|
||||||
"hakurei.app/internal/uevent"
|
|
||||||
)
|
|
||||||
|
|
||||||
// mustMountSystem waits for and mounts a system device matching pattern.
|
|
||||||
func mustMountSystem(
|
|
||||||
ctx context.Context,
|
|
||||||
s *kobject.State,
|
|
||||||
pattern string,
|
|
||||||
) {
|
|
||||||
c, stop := context.WithTimeout(ctx, 30*time.Second)
|
|
||||||
defer stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
var matchErr error
|
|
||||||
var systemPath *check.Absolute
|
|
||||||
s.Range(c, func(o *kobject.Object, act uevent.KobjectAction) bool {
|
|
||||||
if (act != uevent.KOBJ_ADD && act != uevent.KOBJ_CHANGE) ||
|
|
||||||
o.Subsystem != "block" ||
|
|
||||||
o.Env["DEVTYPE"] != "disk" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, err := filepath.Match(pattern, o.DevPath); err != nil {
|
|
||||||
matchErr = err
|
|
||||||
return false
|
|
||||||
} else if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
name, ok := o.Env["DEVNAME"]
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
systemPath = fhs.AbsDev.Append(name)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
if c.Err() != nil {
|
|
||||||
fatal("devpath", strconv.Quote(pattern), "never appeared")
|
|
||||||
}
|
|
||||||
if matchErr != nil {
|
|
||||||
fatal("cannot match system devpath:", matchErr)
|
|
||||||
}
|
|
||||||
err := syscall.Mount(
|
|
||||||
systemPath.String(),
|
|
||||||
"/system/",
|
|
||||||
"squashfs",
|
|
||||||
0,
|
|
||||||
"threads=multi",
|
|
||||||
)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
fatal("cannot mount system:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"hakurei.app/fhs"
|
|
||||||
"hakurei.app/internal/report"
|
|
||||||
"hakurei.app/internal/uevent"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newRejectColdboot returns a function to be called on every subsequent pending
|
|
||||||
// coldboot, and returns whether coldboot should proceed. Rejection is sticky.
|
|
||||||
func newRejectColdboot() func() bool {
|
|
||||||
// one coldboot per five minutes, two consecutive coldboot
|
|
||||||
const (
|
|
||||||
coldbootInterval = 5 * time.Minute
|
|
||||||
coldbootBurst = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
s := make(chan struct{}, coldbootBurst)
|
|
||||||
s <- struct{}{} // for early fault before reporting is ready
|
|
||||||
go func() {
|
|
||||||
t := time.NewTicker(coldbootInterval)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
|
|
||||||
case <-t.C:
|
|
||||||
select {
|
|
||||||
case s <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return func() bool {
|
|
||||||
select {
|
|
||||||
case <-s:
|
|
||||||
return true
|
|
||||||
|
|
||||||
case <-done:
|
|
||||||
return false
|
|
||||||
|
|
||||||
default:
|
|
||||||
close(done)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// consume continuously consumes events from conn with retries.
|
|
||||||
func consume(
|
|
||||||
ctx context.Context,
|
|
||||||
msg message.Msg,
|
|
||||||
r *report.Reporter,
|
|
||||||
conn *uevent.Conn,
|
|
||||||
uuid uevent.UUID,
|
|
||||||
events chan<- *uevent.Message,
|
|
||||||
) {
|
|
||||||
defer close(events)
|
|
||||||
|
|
||||||
nextColdboot := newRejectColdboot()
|
|
||||||
coldboot := true
|
|
||||||
retry:
|
|
||||||
if dispatchErr := conn.Consume(ctx, fhs.Sys, &uuid, events, coldboot, func(path string) {
|
|
||||||
msg.Verbose("coldboot visited", path)
|
|
||||||
}, func(err error) bool {
|
|
||||||
if _, ok := err.(uevent.NeedsColdboot); ok && !nextColdboot() {
|
|
||||||
r.Dispatch(
|
|
||||||
report.Degraded,
|
|
||||||
"rejecting coldboot loop",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
r.Dispatch(
|
|
||||||
report.Inconsistent,
|
|
||||||
"consumed invalid message",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
return true
|
|
||||||
}, nil); dispatchErr != nil {
|
|
||||||
if _, ok := dispatchErr.(uevent.Recoverable); !ok {
|
|
||||||
r.Dispatch(
|
|
||||||
report.Fatal,
|
|
||||||
"discontinuing uevent processing due to nonrecoverable error",
|
|
||||||
dispatchErr,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := dispatchErr.(uevent.NeedsColdboot); ok {
|
|
||||||
// coldboot loop rejected by handler
|
|
||||||
coldboot = false
|
|
||||||
}
|
|
||||||
|
|
||||||
goto retry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"testing/synctest"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRejectColdboot(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
|
||||||
nextColdboot := newRejectColdboot()
|
|
||||||
want := func(want bool) {
|
|
||||||
if got := nextColdboot(); got != want {
|
|
||||||
t.Fatalf("nextColdboot: %v, want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synctest.Wait()
|
|
||||||
want(true)
|
|
||||||
time.Sleep(time.Hour)
|
|
||||||
synctest.Wait()
|
|
||||||
want(true)
|
|
||||||
want(true)
|
|
||||||
time.Sleep(5 * time.Minute)
|
|
||||||
synctest.Wait()
|
|
||||||
want(true)
|
|
||||||
want(false)
|
|
||||||
time.Sleep(time.Hour)
|
|
||||||
synctest.Wait()
|
|
||||||
want(false)
|
|
||||||
want(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
@@ -373,23 +372,6 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
Flag(&flagNoStore, "no-store", command.BoolFlag(false), "Do not attempt to match from active instances")
|
Flag(&flagNoStore, "no-store", command.BoolFlag(false), "Do not attempt to match from active instances")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.NewCommand("kill", "Terminate an active instance", func(args []string) error {
|
|
||||||
if len(args) != 1 {
|
|
||||||
log.Fatal("kill requires 1 argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
var sc hst.Paths
|
|
||||||
env.CopyPaths().Copy(&sc, new(outcome.Hsu).MustID(nil))
|
|
||||||
entry := tryIdentifier(msg, args[0], outcome.NewStore(&sc))
|
|
||||||
if entry == nil {
|
|
||||||
log.Fatalf("%q does not match any active instance", args[0])
|
|
||||||
}
|
|
||||||
if err := syscall.Kill(entry.PID, syscall.SIGTERM); err != nil {
|
|
||||||
log.Fatalf("cannot terminate %s: %v", entry.ID.String(), err)
|
|
||||||
}
|
|
||||||
return errSuccess
|
|
||||||
})
|
|
||||||
|
|
||||||
{
|
{
|
||||||
var flagShort bool
|
var flagShort bool
|
||||||
c.NewCommand("ps", "List active instances", func(args []string) error {
|
c.NewCommand("ps", "List active instances", func(args []string) error {
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ func TestHelp(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"main", []string{}, `usage: hakurei [-h | --help] [-v] [--insecure] [--json] <command> [<args>]
|
"main", []string{}, `
|
||||||
|
Usage: hakurei [-h | --help] [-v] [--insecure] [--json] COMMAND [OPTIONS]
|
||||||
|
|
||||||
commands:
|
Commands:
|
||||||
run Load and start container from configuration file
|
run Load and start container from configuration file
|
||||||
exec Configure and start a permissive container
|
exec Configure and start a permissive container
|
||||||
show Show live or local instance configuration
|
show Show live or local instance configuration
|
||||||
kill Terminate an active instance
|
|
||||||
ps List active instances
|
ps List active instances
|
||||||
version Display version information
|
version Display version information
|
||||||
license Show full license text
|
license Show full license text
|
||||||
@@ -35,14 +35,10 @@ commands:
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exec", []string{"exec", "-h"}, `usage: hakurei exec [-h | --help] [--dbus-config <value>]
|
"exec", []string{"exec", "-h"}, `
|
||||||
[--dbus-system <value>] [--mpris] [--dbus-log]
|
Usage: hakurei exec [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--policy <value>] [--priority <int>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS]
|
||||||
[--id <value>] [-a <int>] [-g <value>] [-d <value>]
|
|
||||||
[-u <value>] [--policy <value>] [--priority <int>]
|
|
||||||
[--private-runtime] [--private-tmpdir] [--wayland] [-X]
|
|
||||||
[--dbus] [--pipewire] [--pulse] <command> [<args>]
|
|
||||||
|
|
||||||
flags:
|
Flags:
|
||||||
-X Enable direct connection to X11
|
-X Enable direct connection to X11
|
||||||
-a int
|
-a int
|
||||||
Application identity
|
Application identity
|
||||||
|
|||||||
+5
-6
@@ -7,8 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// decodeJSON decodes json from r and stores it in v. A non-nil error results in
|
// decodeJSON decodes json from r and stores it in v. A non-nil error results in a call to fatal.
|
||||||
// a call to fatal.
|
|
||||||
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any) {
|
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any) {
|
||||||
err := json.NewDecoder(r).Decode(v)
|
err := json.NewDecoder(r).Decode(v)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -48,14 +47,14 @@ func encodeJSON(fatal func(v ...any), output io.Writer, short bool, v any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := encoder.Encode(v); err != nil {
|
if err := encoder.Encode(v); err != nil {
|
||||||
if e, ok := errors.AsType[*json.MarshalerError](err); ok && e != nil {
|
var marshalerError *json.MarshalerError
|
||||||
|
if errors.As(err, &marshalerError) && marshalerError != nil {
|
||||||
// this likely indicates an implementation error in hst
|
// this likely indicates an implementation error in hst
|
||||||
fatal("cannot encode json for " + e.Type.String() + ": " + e.Err.Error())
|
fatal("cannot encode json for " + marshalerError.Type.String() + ": " + marshalerError.Err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does
|
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does not need to be handled
|
||||||
// not need to be handled
|
|
||||||
fatal("cannot write json: " + err.Error())
|
fatal("cannot write json: " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func TestPrintShowInstance(t *testing.T) {
|
|||||||
Identity: 9 (org.chromium.Chromium)
|
Identity: 9 (org.chromium.Chromium)
|
||||||
Enablements: wayland, dbus, pipewire
|
Enablements: wayland, dbus, pipewire
|
||||||
Groups: video, dialout, plugdev
|
Groups: video, dialout, plugdev
|
||||||
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, noplace, device, cover_run, runtime, tmpdir
|
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
||||||
Home: /data/data/org.chromium.Chromium
|
Home: /data/data/org.chromium.Chromium
|
||||||
Hostname: localhost
|
Hostname: localhost
|
||||||
Path: /run/current-system/sw/bin/chromium
|
Path: /run/current-system/sw/bin/chromium
|
||||||
@@ -161,7 +161,7 @@ App
|
|||||||
Identity: 9 (org.chromium.Chromium)
|
Identity: 9 (org.chromium.Chromium)
|
||||||
Enablements: wayland, dbus, pipewire
|
Enablements: wayland, dbus, pipewire
|
||||||
Groups: video, dialout, plugdev
|
Groups: video, dialout, plugdev
|
||||||
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, noplace, device, cover_run, runtime, tmpdir
|
Flags: multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir
|
||||||
Home: /data/data/org.chromium.Chromium
|
Home: /data/data/org.chromium.Chromium
|
||||||
Hostname: localhost
|
Hostname: localhost
|
||||||
Path: /run/current-system/sw/bin/chromium
|
Path: /run/current-system/sw/bin/chromium
|
||||||
@@ -354,9 +354,7 @@ App
|
|||||||
"tty": true,
|
"tty": true,
|
||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
"noplace": true,
|
|
||||||
"device": true,
|
"device": true,
|
||||||
"cover_run": true,
|
|
||||||
"share_runtime": true,
|
"share_runtime": true,
|
||||||
"share_tmpdir": true
|
"share_tmpdir": true
|
||||||
},
|
},
|
||||||
@@ -507,9 +505,7 @@ App
|
|||||||
"tty": true,
|
"tty": true,
|
||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
"noplace": true,
|
|
||||||
"device": true,
|
"device": true,
|
||||||
"cover_run": true,
|
|
||||||
"share_runtime": true,
|
"share_runtime": true,
|
||||||
"share_tmpdir": true
|
"share_tmpdir": true
|
||||||
}
|
}
|
||||||
@@ -707,9 +703,7 @@ func TestPrintPs(t *testing.T) {
|
|||||||
"tty": true,
|
"tty": true,
|
||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
"noplace": true,
|
|
||||||
"device": true,
|
"device": true,
|
||||||
"cover_run": true,
|
|
||||||
"share_runtime": true,
|
"share_runtime": true,
|
||||||
"share_tmpdir": true
|
"share_tmpdir": true
|
||||||
},
|
},
|
||||||
|
|||||||
+23
-1
@@ -21,6 +21,15 @@
|
|||||||
// following paragraphs are considered an internal detail and not covered by the
|
// following paragraphs are considered an internal detail and not covered by the
|
||||||
// compatibility promise.
|
// compatibility promise.
|
||||||
//
|
//
|
||||||
|
// After checking credentials, hsu checks via /proc/ the absolute pathname of
|
||||||
|
// its parent process, and fails if it does not match the hakurei pathname set
|
||||||
|
// at link time. This is not a security feature: the priv-side is considered
|
||||||
|
// trusted, and this feature makes no attempt to address the racy nature of
|
||||||
|
// querying /proc/, or debuggers attached to the parent process. Instead, this
|
||||||
|
// aims to discourage misuse and reduce confusion if the user accidentally
|
||||||
|
// stumbles upon this program. It also prevents accidental use of the incorrect
|
||||||
|
// installation of hsu in some environments.
|
||||||
|
//
|
||||||
// Since target container environment variables are set up in shim via the
|
// Since target container environment variables are set up in shim via the
|
||||||
// [container] infrastructure, the environment is used for parameters from the
|
// [container] infrastructure, the environment is used for parameters from the
|
||||||
// parent process.
|
// parent process.
|
||||||
@@ -53,6 +62,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,6 +107,18 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var toolPath string
|
||||||
|
pexe := filepath.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
|
||||||
|
if p, err := os.Readlink(pexe); err != nil {
|
||||||
|
log.Fatalf("cannot read parent executable path: %v", err)
|
||||||
|
} else if strings.HasSuffix(p, " (deleted)") {
|
||||||
|
log.Fatal("hakurei executable has been deleted")
|
||||||
|
} else if p != hakureiPath {
|
||||||
|
log.Fatal("this program must be started by hakurei")
|
||||||
|
} else {
|
||||||
|
toolPath = p
|
||||||
|
}
|
||||||
|
|
||||||
// refuse to run if hsurc is not protected correctly
|
// refuse to run if hsurc is not protected correctly
|
||||||
if s, err := os.Stat(hsuConfPath); err != nil {
|
if s, err := os.Stat(hsuConfPath); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -183,7 +205,7 @@ func main() {
|
|||||||
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := syscall.Exec(hakureiPath, []string{
|
if err := syscall.Exec(toolPath, []string{
|
||||||
"hakurei",
|
"hakurei",
|
||||||
"shim",
|
"shim",
|
||||||
}, []string{
|
}, []string{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ buildGoModule {
|
|||||||
pname = "${hakurei.pname}-hsu";
|
pname = "${hakurei.pname}-hsu";
|
||||||
inherit (hakurei) version;
|
inherit (hakurei) version;
|
||||||
|
|
||||||
src = ../cmd/hsu;
|
src = ./.;
|
||||||
inherit (hakurei) vendorHash;
|
inherit (hakurei) vendorHash;
|
||||||
env.CGO_ENABLED = 0;
|
env.CGO_ENABLED = 0;
|
||||||
|
|
||||||
+11
-109
@@ -2,15 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
"hakurei.app/internal/rosa"
|
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,29 +19,10 @@ type cache struct {
|
|||||||
// Should generally not be used directly.
|
// Should generally not be used directly.
|
||||||
c *pkg.Cache
|
c *pkg.Cache
|
||||||
|
|
||||||
attr pkg.CacheAttr
|
cures, jobs int
|
||||||
// Primarily to work around missing landlock LSM.
|
hostAbstract, idle bool
|
||||||
hostAbstract bool
|
|
||||||
// Set SCHED_IDLE.
|
|
||||||
idle bool
|
|
||||||
// Unset [pkg.CSuppressInit].
|
|
||||||
verboseInit bool
|
|
||||||
// Whether to enable output colours.
|
|
||||||
color bool
|
|
||||||
// Unset [pkg.CExternShallow].
|
|
||||||
deep bool
|
|
||||||
// Loaded artifact of [rosa.QEMU].
|
|
||||||
qemu pkg.Artifact
|
|
||||||
|
|
||||||
base, mirror string
|
base string
|
||||||
}
|
|
||||||
|
|
||||||
// writeTitle sets title of the terminal connected to [message.Msg].
|
|
||||||
func writeTitle(msg message.Msg, s string) {
|
|
||||||
msg.Suspend()
|
|
||||||
w := msg.GetLogger().Writer()
|
|
||||||
_, _ = w.Write([]byte("\x1b]0;" + s + "\a"))
|
|
||||||
msg.Resume()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// open opens the underlying [pkg.Cache].
|
// open opens the underlying [pkg.Cache].
|
||||||
@@ -53,35 +31,22 @@ func (cache *cache) open() (err error) {
|
|||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
var hostname string
|
if cache.base == "" {
|
||||||
if hostname, err = os.Hostname(); err != nil {
|
cache.base = "cache"
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var base *check.Absolute
|
var base *check.Absolute
|
||||||
if cache.base, err = filepath.Abs(cache.base); err != nil {
|
if cache.base, err = filepath.Abs(cache.base); err != nil {
|
||||||
return
|
return
|
||||||
} else if base, err = check.NewAbs(cache.base); err != nil {
|
} else if base, err = check.NewAbs(cache.base); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var notify chan bool
|
|
||||||
|
|
||||||
|
var flags int
|
||||||
if cache.idle {
|
if cache.idle {
|
||||||
cache.attr.Flags |= pkg.CSchedIdle
|
flags |= pkg.CSchedIdle
|
||||||
}
|
}
|
||||||
if cache.hostAbstract {
|
if cache.hostAbstract {
|
||||||
cache.attr.Flags |= pkg.CHostAbstract
|
flags |= pkg.CHostAbstract
|
||||||
}
|
|
||||||
if !cache.verboseInit {
|
|
||||||
cache.attr.Flags |= pkg.CSuppressInit
|
|
||||||
}
|
|
||||||
if !cache.deep {
|
|
||||||
cache.attr.Flags |= pkg.CExternShallow
|
|
||||||
}
|
|
||||||
if cache.color {
|
|
||||||
cache.attr.Flags |= pkg.CColourOutput
|
|
||||||
notify = make(chan bool, 1<<12)
|
|
||||||
cache.attr.Notify = notify
|
|
||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
@@ -103,69 +68,11 @@ func (cache *cache) open() (err error) {
|
|||||||
cache.c, err = pkg.Open(
|
cache.c, err = pkg.Open(
|
||||||
cache.ctx,
|
cache.ctx,
|
||||||
cache.msg,
|
cache.msg,
|
||||||
|
flags,
|
||||||
|
cache.cures,
|
||||||
|
cache.jobs,
|
||||||
base,
|
base,
|
||||||
&cache.attr,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
done <- struct{}{}
|
|
||||||
|
|
||||||
if cache.mirror != "" {
|
|
||||||
var pub []byte
|
|
||||||
pub, err = os.ReadFile(base.Append("ed25519.pub").String())
|
|
||||||
if err != nil {
|
|
||||||
cache.c.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var r rosa.Remote
|
|
||||||
if r, err = rosa.NewRemote(http.DefaultClient, cache.mirror, pub); err != nil {
|
|
||||||
cache.c.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cache.c.SetExternal(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cache.attr.Notify != nil {
|
|
||||||
cache.msg.Suspend()
|
|
||||||
_, _ = cache.msg.GetLogger().Writer().Write([]byte("\x1b[22;0t"))
|
|
||||||
cache.msg.Resume()
|
|
||||||
|
|
||||||
prefix := hostname + ": mbf "
|
|
||||||
go func() {
|
|
||||||
var n, nc uint64
|
|
||||||
for enter := range notify {
|
|
||||||
if enter {
|
|
||||||
nc++
|
|
||||||
} else {
|
|
||||||
nc--
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
|
|
||||||
if nc == 0 {
|
|
||||||
writeTitle(cache.msg, prefix[:len(prefix)-1])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
writeTitle(cache.msg, prefix+
|
|
||||||
"("+strconv.FormatUint(nc, 10)+
|
|
||||||
" running, "+strconv.FormatUint(n, 10)+" complete)")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if cache.qemu != nil {
|
|
||||||
var pathname *check.Absolute
|
|
||||||
pathname, _, err = cache.c.Cure(cache.qemu)
|
|
||||||
if err != nil {
|
|
||||||
cache.c.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for arch, entry := range rosa.Arches(pathname) {
|
|
||||||
pkg.RegisterArch(arch, entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,11 +80,6 @@ func (cache *cache) open() (err error) {
|
|||||||
func (cache *cache) Close() {
|
func (cache *cache) Close() {
|
||||||
if cache.c != nil {
|
if cache.c != nil {
|
||||||
cache.c.Close()
|
cache.c.Close()
|
||||||
if cache.attr.Notify != nil {
|
|
||||||
cache.msg.Suspend()
|
|
||||||
_, _ = cache.msg.GetLogger().Writer().Write([]byte("\x1b[23;0t"))
|
|
||||||
cache.msg.Resume()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-17
@@ -99,9 +99,10 @@ func cancelIdent(
|
|||||||
var ident pkg.ID
|
var ident pkg.ID
|
||||||
if _, err := io.ReadFull(conn, ident[:]); err != nil {
|
if _, err := io.ReadFull(conn, ident[:]); err != nil {
|
||||||
return nil, false, errors.Join(err, conn.Close())
|
return nil, false, errors.Join(err, conn.Close())
|
||||||
|
} else if err = conn.Close(); err != nil {
|
||||||
|
return nil, false, err
|
||||||
}
|
}
|
||||||
ok := cache.Cancel(unique.Make(ident))
|
return &ident, cache.Cancel(unique.Make(ident)), nil
|
||||||
return &ident, ok, conn.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serve services connections from a [net.UnixListener].
|
// serve services connections from a [net.UnixListener].
|
||||||
@@ -193,11 +194,11 @@ func serve(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case specialAbort:
|
case specialAbort:
|
||||||
log.Println("aborting all pending cures")
|
|
||||||
cm.c.Abort()
|
|
||||||
if _err := conn.Close(); _err != nil {
|
if _err := conn.Close(); _err != nil {
|
||||||
log.Println(_err)
|
log.Println(_err)
|
||||||
}
|
}
|
||||||
|
log.Println("aborting all pending cures")
|
||||||
|
cm.c.Abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -305,7 +306,6 @@ func cancelRemote(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
addr *net.UnixAddr,
|
addr *net.UnixAddr,
|
||||||
a pkg.Artifact,
|
a pkg.Artifact,
|
||||||
wait bool,
|
|
||||||
) error {
|
) error {
|
||||||
done, conn, err := dial(ctx, addr)
|
done, conn, err := dial(ctx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -324,19 +324,13 @@ func cancelRemote(
|
|||||||
} else if n != len(id) {
|
} else if n != len(id) {
|
||||||
return errors.Join(io.ErrShortWrite, conn.Close())
|
return errors.Join(io.ErrShortWrite, conn.Close())
|
||||||
}
|
}
|
||||||
if wait {
|
return conn.Close()
|
||||||
if _, err = conn.Read(make([]byte, 1)); err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.Join(err, conn.Close())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// abortRemote aborts all [pkg.Artifact] curing on a daemon.
|
// abortRemote aborts all [pkg.Artifact] curing on a daemon.
|
||||||
func abortRemote(
|
func abortRemote(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
addr *net.UnixAddr,
|
addr *net.UnixAddr,
|
||||||
wait bool,
|
|
||||||
) error {
|
) error {
|
||||||
done, conn, err := dial(ctx, addr)
|
done, conn, err := dial(ctx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -345,10 +339,5 @@ func abortRemote(
|
|||||||
defer close(done)
|
defer close(done)
|
||||||
|
|
||||||
err = writeSpecialHeader(conn, specialAbort)
|
err = writeSpecialHeader(conn, specialAbort)
|
||||||
if wait && err == nil {
|
|
||||||
if _, err = conn.Read(make([]byte, 1)); err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.Join(err, conn.Close())
|
return errors.Join(err, conn.Close())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ func TestNoReply(t *testing.T) {
|
|||||||
c, err := pkg.Open(
|
c, err := pkg.Open(
|
||||||
t.Context(),
|
t.Context(),
|
||||||
message.New(log.New(os.Stderr, "cir: ", 0)),
|
message.New(log.New(os.Stderr, "cir: ", 0)),
|
||||||
|
0, 0, 0,
|
||||||
check.MustAbs(t.TempDir()),
|
check.MustAbs(t.TempDir()),
|
||||||
nil,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Open: error = %v", err)
|
t.Fatalf("Open: error = %v", err)
|
||||||
@@ -63,7 +63,7 @@ func TestNoReply(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<-done
|
<-done
|
||||||
wantIdent := pkg.MustDecode("bpVsJ69hdKPBntMh_u777g7-quFNVmMlBJQOlM_thiQa9JnOFLAbe5OlR4zNjZ32")
|
wantIdent := pkg.MustDecode("fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG")
|
||||||
if gotIdent := c.Ident(a).Value(); gotIdent != wantIdent {
|
if gotIdent := c.Ident(a).Value(); gotIdent != wantIdent {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"cureFromIR: %s, want %s",
|
"cureFromIR: %s, want %s",
|
||||||
@@ -106,11 +106,11 @@ func TestDaemon(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err = cancelRemote(ctx, &addr, pkg.NewFile("nonexistent", nil), true); err != nil {
|
if err = cancelRemote(ctx, &addr, pkg.NewFile("nonexistent", nil)); err != nil {
|
||||||
t.Fatalf("cancelRemote: error = %v", err)
|
t.Fatalf("cancelRemote: error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = abortRemote(ctx, &addr, true); err != nil {
|
if err = abortRemote(ctx, &addr); err != nil {
|
||||||
t.Fatalf("abortRemote: error = %v", err)
|
t.Fatalf("abortRemote: error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ func TestDaemon(t *testing.T) {
|
|||||||
cancel()
|
cancel()
|
||||||
<-done
|
<-done
|
||||||
|
|
||||||
const want = "bpVsJ69hdKPBntMh_u777g7-quFNVmMlBJQOlM_thiQa9JnOFLAbe5OlR4zNjZ32"
|
const want = "fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG"
|
||||||
if got := filepath.Base(p.String()); got != want {
|
if got := filepath.Base(p.String()); got != want {
|
||||||
t.Errorf("cureRemote: %s, want %s", got, want)
|
t.Errorf("cureRemote: %s, want %s", got, want)
|
||||||
}
|
}
|
||||||
@@ -132,8 +132,8 @@ func TestDaemon(t *testing.T) {
|
|||||||
wantLog := []string{
|
wantLog := []string{
|
||||||
"",
|
"",
|
||||||
"daemon: aborting all pending cures",
|
"daemon: aborting all pending cures",
|
||||||
"daemon: attempting to cancel invalid artifact 58Vy_5beLZCvZX__KzRKyIE6vATleaZP1ZBxbqoWnekz9aZ1zkxNgcIUFI5V1_Jf",
|
"daemon: attempting to cancel invalid artifact kQm9fmnCmXST1-MMmxzcau2oKZCXXrlZydo4PkeV5hO_2PKfeC8t98hrbV_ZZx_j",
|
||||||
"daemon: fulfilled artifact " + want,
|
"daemon: fulfilled artifact fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG",
|
||||||
}
|
}
|
||||||
gotLog := strings.Split(buf.String(), "\n")
|
gotLog := strings.Split(buf.String(), "\n")
|
||||||
slices.Sort(gotLog)
|
slices.Sort(gotLog)
|
||||||
|
|||||||
+23
-17
@@ -6,7 +6,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"unique"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
"hakurei.app/internal/rosa"
|
"hakurei.app/internal/rosa"
|
||||||
@@ -18,13 +17,23 @@ func commandInfo(
|
|||||||
args []string,
|
args []string,
|
||||||
w io.Writer,
|
w io.Writer,
|
||||||
writeStatus bool,
|
writeStatus bool,
|
||||||
r *rosa.Report,
|
reportPath string,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
for _, h := range rosa.Native().CollectAll() {
|
return errors.New("info requires at least 1 argument")
|
||||||
fmt.Println(h)
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
var r *rosa.Report
|
||||||
|
if reportPath != "" {
|
||||||
|
if r, err = rosa.OpenReport(reportPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if closeErr := r.Close(); err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer r.HandleAccess(&err)()
|
||||||
}
|
}
|
||||||
|
|
||||||
// recovered by HandleAccess
|
// recovered by HandleAccess
|
||||||
@@ -39,19 +48,17 @@ func commandInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t := rosa.Native().Std()
|
|
||||||
for i, name := range args {
|
for i, name := range args {
|
||||||
handle := rosa.ArtifactH(unique.Make(name))
|
if p, ok := rosa.ResolveName(name); !ok {
|
||||||
if meta, a := t.Load(handle); meta == nil {
|
|
||||||
return fmt.Errorf("unknown artifact %q", name)
|
return fmt.Errorf("unknown artifact %q", name)
|
||||||
} else {
|
} else {
|
||||||
var suffix string
|
var suffix string
|
||||||
|
if version := rosa.Std.Version(p); version != rosa.Unversioned {
|
||||||
if meta.Version != rosa.Unversioned {
|
suffix += "-" + version
|
||||||
suffix += "-" + meta.Version
|
|
||||||
}
|
}
|
||||||
mustPrintln("name : " + name + suffix)
|
mustPrintln("name : " + name + suffix)
|
||||||
|
|
||||||
|
meta := rosa.GetMetadata(p)
|
||||||
mustPrintln("description : " + meta.Description)
|
mustPrintln("description : " + meta.Description)
|
||||||
if meta.Website != "" {
|
if meta.Website != "" {
|
||||||
mustPrintln("website : " +
|
mustPrintln("website : " +
|
||||||
@@ -60,10 +67,9 @@ func commandInfo(
|
|||||||
if len(meta.Dependencies) > 0 {
|
if len(meta.Dependencies) > 0 {
|
||||||
mustPrint("depends on :")
|
mustPrint("depends on :")
|
||||||
for _, d := range meta.Dependencies {
|
for _, d := range meta.Dependencies {
|
||||||
_meta, _ := rosa.Native().Std().MustLoad(d)
|
s := rosa.GetMetadata(d).Name
|
||||||
s := _meta.Name
|
if version := rosa.Std.Version(d); version != rosa.Unversioned {
|
||||||
if _meta.Version != rosa.Unversioned {
|
s += "-" + version
|
||||||
s += "-" + _meta.Version
|
|
||||||
}
|
}
|
||||||
mustPrint(" " + s)
|
mustPrint(" " + s)
|
||||||
}
|
}
|
||||||
@@ -75,7 +81,7 @@ func commandInfo(
|
|||||||
if r == nil {
|
if r == nil {
|
||||||
var f io.ReadSeekCloser
|
var f io.ReadSeekCloser
|
||||||
err = cm.Do(func(cache *pkg.Cache) (err error) {
|
err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
f, err = cache.OpenStatus(a)
|
f, err = cache.OpenStatus(rosa.Std.Load(p))
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -94,7 +100,7 @@ func commandInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
} else if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||||
status, n := r.ArtifactOf(cache.Ident(a))
|
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
|
||||||
if status == nil {
|
if status == nil {
|
||||||
mustPrintln(
|
mustPrintln(
|
||||||
statusPrefix + "not in report",
|
statusPrefix + "not in report",
|
||||||
|
|||||||
+19
-39
@@ -10,7 +10,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"unique"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
@@ -21,14 +20,6 @@ import (
|
|||||||
func TestInfo(t *testing.T) {
|
func TestInfo(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
_t := rosa.Native().Std()
|
|
||||||
qemuMeta, _ := _t.Load(rosa.H("qemu"))
|
|
||||||
glibMeta, _ := _t.Load(rosa.H("glib"))
|
|
||||||
zlibMeta, zlib := _t.Load(rosa.H("zlib"))
|
|
||||||
zstdMeta, _ := _t.Load(rosa.H("zstd"))
|
|
||||||
hakureiMeta, _ := _t.Load(rosa.H("hakurei"))
|
|
||||||
hakureiDistMeta, _ := _t.Load(rosa.H("hakurei-dist"))
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
args []string
|
args []string
|
||||||
@@ -38,24 +29,24 @@ func TestInfo(t *testing.T) {
|
|||||||
wantErr any
|
wantErr any
|
||||||
}{
|
}{
|
||||||
{"qemu", []string{"qemu"}, nil, "", `
|
{"qemu", []string{"qemu"}, nil, "", `
|
||||||
name : qemu-` + qemuMeta.Version + `
|
name : qemu-` + rosa.Std.Version(rosa.QEMU) + `
|
||||||
description : a generic and open source machine emulator and virtualizer
|
description : a generic and open source machine emulator and virtualizer
|
||||||
website : https://www.qemu.org
|
website : https://www.qemu.org
|
||||||
depends on : glib-` + glibMeta.Version + ` zstd-` + zstdMeta.Version + `
|
depends on : glib-` + rosa.Std.Version(rosa.GLib) + ` zstd-` + rosa.Std.Version(rosa.Zstd) + `
|
||||||
`, nil},
|
`, nil},
|
||||||
|
|
||||||
{"multi", []string{"hakurei", "hakurei-dist"}, nil, "", `
|
{"multi", []string{"hakurei", "hakurei-dist"}, nil, "", `
|
||||||
name : hakurei-` + hakureiMeta.Version + `
|
name : hakurei-` + rosa.Std.Version(rosa.Hakurei) + `
|
||||||
description : low-level userspace tooling for Rosa OS
|
description : low-level userspace tooling for Rosa OS
|
||||||
website : https://hakurei.app
|
website : https://hakurei.app
|
||||||
|
|
||||||
name : hakurei-dist-` + hakureiDistMeta.Version + `
|
name : hakurei-dist-` + rosa.Std.Version(rosa.HakureiDist) + `
|
||||||
description : low-level userspace tooling for Rosa OS (distribution tarball)
|
description : low-level userspace tooling for Rosa OS (distribution tarball)
|
||||||
website : https://hakurei.app
|
website : https://hakurei.app
|
||||||
`, nil},
|
`, nil},
|
||||||
|
|
||||||
{"nonexistent", []string{"zlib", "\x00"}, nil, "", `
|
{"nonexistent", []string{"zlib", "\x00"}, nil, "", `
|
||||||
name : zlib-` + zlibMeta.Version + `
|
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||||
description : lossless data-compression library
|
description : lossless data-compression library
|
||||||
website : https://zlib.net
|
website : https://zlib.net
|
||||||
|
|
||||||
@@ -65,12 +56,12 @@ website : https://zlib.net
|
|||||||
"zstd": "internal/pkg (amd64) on satori\n",
|
"zstd": "internal/pkg (amd64) on satori\n",
|
||||||
"hakurei": "internal/pkg (amd64) on satori\n\n",
|
"hakurei": "internal/pkg (amd64) on satori\n\n",
|
||||||
}, "", `
|
}, "", `
|
||||||
name : zlib-` + zlibMeta.Version + `
|
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||||
description : lossless data-compression library
|
description : lossless data-compression library
|
||||||
website : https://zlib.net
|
website : https://zlib.net
|
||||||
status : not yet cured
|
status : not yet cured
|
||||||
|
|
||||||
name : zstd-` + zstdMeta.Version + `
|
name : zstd-` + rosa.Std.Version(rosa.Zstd) + `
|
||||||
description : a fast compression algorithm
|
description : a fast compression algorithm
|
||||||
website : https://facebook.github.io/zstd
|
website : https://facebook.github.io/zstd
|
||||||
status : internal/pkg (amd64) on satori
|
status : internal/pkg (amd64) on satori
|
||||||
@@ -79,19 +70,19 @@ status : internal/pkg (amd64) on satori
|
|||||||
{"status cache perm", []string{"zlib"}, map[string]string{
|
{"status cache perm", []string{"zlib"}, map[string]string{
|
||||||
"zlib": "\x00",
|
"zlib": "\x00",
|
||||||
}, "", `
|
}, "", `
|
||||||
name : zlib-` + zlibMeta.Version + `
|
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||||
description : lossless data-compression library
|
description : lossless data-compression library
|
||||||
website : https://zlib.net
|
website : https://zlib.net
|
||||||
`, func(cm *cache) error {
|
`, func(cm *cache) error {
|
||||||
return &os.PathError{
|
return &os.PathError{
|
||||||
Op: "open",
|
Op: "open",
|
||||||
Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(zlib).Value())),
|
Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(rosa.Std.Load(rosa.Zlib)).Value())),
|
||||||
Err: syscall.EACCES,
|
Err: syscall.EACCES,
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"status report", []string{"zlib"}, nil, strings.Repeat("\x00", len(pkg.Checksum{})+8), `
|
{"status report", []string{"zlib"}, nil, strings.Repeat("\x00", len(pkg.Checksum{})+8), `
|
||||||
name : zlib-` + zlibMeta.Version + `
|
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||||
description : lossless data-compression library
|
description : lossless data-compression library
|
||||||
website : https://zlib.net
|
website : https://zlib.net
|
||||||
status : not in report
|
status : not in report
|
||||||
@@ -104,7 +95,7 @@ status : not in report
|
|||||||
var (
|
var (
|
||||||
cm *cache
|
cm *cache
|
||||||
buf strings.Builder
|
buf strings.Builder
|
||||||
r *rosa.Report
|
rp string
|
||||||
)
|
)
|
||||||
|
|
||||||
if tc.status != nil || tc.report != "" {
|
if tc.status != nil || tc.report != "" {
|
||||||
@@ -117,31 +108,20 @@ status : not in report
|
|||||||
}
|
}
|
||||||
|
|
||||||
if tc.report != "" {
|
if tc.report != "" {
|
||||||
pathname := filepath.Join(t.TempDir(), "report")
|
rp = filepath.Join(t.TempDir(), "report")
|
||||||
err := os.WriteFile(
|
if err := os.WriteFile(
|
||||||
pathname,
|
rp,
|
||||||
unsafe.Slice(unsafe.StringData(tc.report), len(tc.report)),
|
unsafe.Slice(unsafe.StringData(tc.report), len(tc.report)),
|
||||||
0400,
|
0400,
|
||||||
)
|
); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err = rosa.OpenReport(pathname)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err = r.Close(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc.status != nil {
|
if tc.status != nil {
|
||||||
for name, status := range tc.status {
|
for name, status := range tc.status {
|
||||||
_, a := _t.Load(rosa.ArtifactH(unique.Make(name)))
|
p, ok := rosa.ResolveName(name)
|
||||||
if a == nil {
|
if !ok {
|
||||||
t.Fatalf("invalid name %q", name)
|
t.Fatalf("invalid name %q", name)
|
||||||
}
|
}
|
||||||
perm := os.FileMode(0400)
|
perm := os.FileMode(0400)
|
||||||
@@ -152,7 +132,7 @@ status : not in report
|
|||||||
return os.WriteFile(filepath.Join(
|
return os.WriteFile(filepath.Join(
|
||||||
cm.base,
|
cm.base,
|
||||||
"status",
|
"status",
|
||||||
pkg.Encode(cache.Ident(a).Value()),
|
pkg.Encode(cache.Ident(rosa.Std.Load(p)).Value()),
|
||||||
), unsafe.Slice(unsafe.StringData(status), len(status)), perm)
|
), unsafe.Slice(unsafe.StringData(status), len(status)), perm)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatalf("Do: error = %v", err)
|
t.Fatalf("Do: error = %v", err)
|
||||||
@@ -177,7 +157,7 @@ status : not in report
|
|||||||
tc.args,
|
tc.args,
|
||||||
&buf,
|
&buf,
|
||||||
cm != nil,
|
cm != nil,
|
||||||
r,
|
rp,
|
||||||
); !reflect.DeepEqual(err, wantErr) {
|
); !reflect.DeepEqual(err, wantErr) {
|
||||||
t.Fatalf("commandInfo: error = %v, want %v", err, wantErr)
|
t.Fatalf("commandInfo: error = %v, want %v", err, wantErr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
// Package ui holds the static web UI.
|
|
||||||
package ui
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
// Register arranges for mux to serve the embedded frontend.
|
|
||||||
func Register(mux *http.ServeMux) {
|
|
||||||
mux.Handle("GET /", http.FileServer(http.FS(static)))
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
//go:build frontend
|
|
||||||
|
|
||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate tsc
|
|
||||||
//go:generate cp index.html style.css static
|
|
||||||
//go:embed static
|
|
||||||
var _static embed.FS
|
|
||||||
|
|
||||||
var static = func() fs.FS {
|
|
||||||
if f, err := fs.Sub(_static, "static"); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
+202
-712
File diff suppressed because it is too large
Load Diff
@@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/internal/rosa"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
rosa.Native().DropCaches("", rosa.OptLLVMNoLTO)
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCureAll(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
const env = "ROSA_TEST_DAEMON"
|
|
||||||
|
|
||||||
if !testing.Verbose() {
|
|
||||||
t.Skip("verbose flag not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
pathname, ok := os.LookupEnv(env)
|
|
||||||
if !ok {
|
|
||||||
t.Skip(env + " not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := net.UnixAddr{Net: "unix", Name: pathname}
|
|
||||||
t.Cleanup(func() {
|
|
||||||
if t.Failed() {
|
|
||||||
if err := abortRemote(t.Context(), &addr, false); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, handle := range rosa.Native().Collect() {
|
|
||||||
_, a := rosa.Native().Std().MustLoad(handle)
|
|
||||||
t.Run(handle.String(), func(t *testing.T) {
|
|
||||||
_, err := cureRemote(t.Context(), &addr, a, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
// Package pkgserver implements the package metadata service backend.
|
package main
|
||||||
package pkgserver
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -10,7 +8,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"hakurei.app/internal/info"
|
"hakurei.app/internal/info"
|
||||||
"hakurei.app/internal/rosa"
|
"hakurei.app/internal/rosa"
|
||||||
@@ -30,7 +27,7 @@ var (
|
|||||||
// handleInfo writes constant system information.
|
// handleInfo writes constant system information.
|
||||||
func handleInfo(w http.ResponseWriter, _ *http.Request) {
|
func handleInfo(w http.ResponseWriter, _ *http.Request) {
|
||||||
infoPayloadOnce.Do(func() {
|
infoPayloadOnce.Do(func() {
|
||||||
infoPayload.Count = len(rosa.Native().Collect())
|
infoPayload.Count = int(rosa.PresetUnexportedStart)
|
||||||
infoPayload.HakureiVersion = info.Version()
|
infoPayload.HakureiVersion = info.Version()
|
||||||
})
|
})
|
||||||
// TODO(mae): cache entire response if no additional fields are planned
|
// TODO(mae): cache entire response if no additional fields are planned
|
||||||
@@ -91,7 +88,7 @@ func (index *packageIndex) handleGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
||||||
http.Error(
|
http.Error(
|
||||||
w, "index must be an integer between 0 and "+
|
w, "index must be an integer between 0 and "+
|
||||||
strconv.Itoa(len(index.sorts[0])-1),
|
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||||
http.StatusBadRequest,
|
http.StatusBadRequest,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -125,7 +122,7 @@ func (index *packageIndex) handleSearch(w http.ResponseWriter, r *http.Request)
|
|||||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
||||||
http.Error(
|
http.Error(
|
||||||
w, "index must be an integer between 0 and "+
|
w, "index must be an integer between 0 and "+
|
||||||
strconv.Itoa(len(index.sorts[0])-1),
|
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||||
http.StatusBadRequest,
|
http.StatusBadRequest,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -161,29 +158,6 @@ func (index *packageIndex) registerAPI(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("GET /status/", index.newStatusHandler(true))
|
mux.HandleFunc("GET /status/", index.newStatusHandler(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register arranges for mux to service API requests.
|
|
||||||
func Register(ctx context.Context, mux *http.ServeMux, report *rosa.Report) error {
|
|
||||||
var index packageIndex
|
|
||||||
index.search = make(searchCache)
|
|
||||||
if err := index.populate(report); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
ticker.Stop()
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
index.search.clean()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
index.registerAPI(mux)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeAPIPayload sets headers common to API responses and encodes payload as
|
// writeAPIPayload sets headers common to API responses and encodes payload as
|
||||||
// JSON for the response body.
|
// JSON for the response body.
|
||||||
func writeAPIPayload(w http.ResponseWriter, payload any) {
|
func writeAPIPayload(w http.ResponseWriter, payload any) {
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
package pkgserver
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ func TestAPIInfo(t *testing.T) {
|
|||||||
checkPayload(t, resp, struct {
|
checkPayload(t, resp, struct {
|
||||||
Count int `json:"count"`
|
Count int `json:"count"`
|
||||||
HakureiVersion string `json:"hakurei_version"`
|
HakureiVersion string `json:"hakurei_version"`
|
||||||
}{len(rosa.Native().Collect()), info.Version()})
|
}{int(rosa.PresetUnexportedStart), info.Version()})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIGet(t *testing.T) {
|
func TestAPIGet(t *testing.T) {
|
||||||
@@ -92,12 +93,11 @@ func TestAPIGet(t *testing.T) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
count := len(rosa.Native().Collect())
|
|
||||||
t.Run("index", func(t *testing.T) {
|
t.Run("index", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
checkValidate(
|
checkValidate(
|
||||||
t, "limit=1&sort=0&index", 0, count-1,
|
t, "limit=1&sort=0&index", 0, int(rosa.PresetUnexportedStart-1),
|
||||||
"index must be an integer between 0 and "+strconv.Itoa(count-1),
|
"index must be an integer between 0 and "+strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -108,4 +108,76 @@ func TestAPIGet(t *testing.T) {
|
|||||||
"sort must be an integer between 0 and "+strconv.Itoa(int(sortOrderEnd)),
|
"sort must be an integer between 0 and "+strconv.Itoa(int(sortOrderEnd)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
checkWithSuffix := func(name, suffix string, want []*metadata) {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
w := newRequest(suffix)
|
||||||
|
resp := w.Result()
|
||||||
|
checkStatus(t, resp, http.StatusOK)
|
||||||
|
checkAPIHeader(t, w.Header())
|
||||||
|
checkPayloadFunc(t, resp, func(got *struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Values []*metadata `json:"values"`
|
||||||
|
}) bool {
|
||||||
|
return got.Count == len(want) &&
|
||||||
|
slices.EqualFunc(got.Values, want, func(a, b *metadata) bool {
|
||||||
|
return (a.Version == b.Version ||
|
||||||
|
a.Version == rosa.Unversioned ||
|
||||||
|
b.Version == rosa.Unversioned) &&
|
||||||
|
a.HasReport == b.HasReport &&
|
||||||
|
a.Name == b.Name &&
|
||||||
|
a.Description == b.Description &&
|
||||||
|
a.Website == b.Website
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
checkWithSuffix("declarationAscending", "?limit=2&index=0&sort=0", []*metadata{
|
||||||
|
{
|
||||||
|
Metadata: rosa.GetMetadata(0),
|
||||||
|
Version: rosa.Std.Version(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Metadata: rosa.GetMetadata(1),
|
||||||
|
Version: rosa.Std.Version(1),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
checkWithSuffix("declarationAscending offset", "?limit=3&index=5&sort=0", []*metadata{
|
||||||
|
{
|
||||||
|
Metadata: rosa.GetMetadata(5),
|
||||||
|
Version: rosa.Std.Version(5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Metadata: rosa.GetMetadata(6),
|
||||||
|
Version: rosa.Std.Version(6),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Metadata: rosa.GetMetadata(7),
|
||||||
|
Version: rosa.Std.Version(7),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
checkWithSuffix("declarationDescending", "?limit=3&index=0&sort=1", []*metadata{
|
||||||
|
{
|
||||||
|
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 1),
|
||||||
|
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 2),
|
||||||
|
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 3),
|
||||||
|
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 3),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
checkWithSuffix("declarationDescending offset", "?limit=1&index=37&sort=1", []*metadata{
|
||||||
|
{
|
||||||
|
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 38),
|
||||||
|
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 38),
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package pkgserver
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
@@ -23,7 +23,7 @@ const (
|
|||||||
|
|
||||||
// packageIndex refers to metadata by name and various sort orders.
|
// packageIndex refers to metadata by name and various sort orders.
|
||||||
type packageIndex struct {
|
type packageIndex struct {
|
||||||
sorts [sortOrderEnd + 1][]*metadata
|
sorts [sortOrderEnd + 1][rosa.PresetUnexportedStart]*metadata
|
||||||
names map[string]*metadata
|
names map[string]*metadata
|
||||||
search searchCache
|
search searchCache
|
||||||
// Taken from [rosa.Report] if available.
|
// Taken from [rosa.Report] if available.
|
||||||
@@ -32,11 +32,11 @@ type packageIndex struct {
|
|||||||
|
|
||||||
// metadata holds [rosa.Metadata] extended with additional information.
|
// metadata holds [rosa.Metadata] extended with additional information.
|
||||||
type metadata struct {
|
type metadata struct {
|
||||||
handle rosa.ArtifactH
|
p rosa.PArtifact
|
||||||
*rosa.Metadata
|
*rosa.Metadata
|
||||||
|
|
||||||
// Copied from [rosa.Metadata], [rosa.Unversioned] is equivalent to the zero
|
// Populated via [rosa.Toolchain.Version], [rosa.Unversioned] is equivalent
|
||||||
// value. Otherwise, the zero value is invalid.
|
// to the zero value. Otherwise, the zero value is invalid.
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
// Output data size, available if present in report.
|
// Output data size, available if present in report.
|
||||||
Size int64 `json:"size,omitempty"`
|
Size int64 `json:"size,omitempty"`
|
||||||
@@ -50,23 +50,20 @@ type metadata struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// populate deterministically populates packageIndex, optionally with a report.
|
// populate deterministically populates packageIndex, optionally with a report.
|
||||||
func (index *packageIndex) populate(report *rosa.Report) (err error) {
|
func (index *packageIndex) populate(cache *pkg.Cache, report *rosa.Report) (err error) {
|
||||||
if report != nil {
|
if report != nil {
|
||||||
defer report.HandleAccess(&err)()
|
defer report.HandleAccess(&err)()
|
||||||
index.handleAccess = report.HandleAccess
|
index.handleAccess = report.HandleAccess
|
||||||
}
|
}
|
||||||
|
|
||||||
handles := rosa.Native().Collect()
|
var work [rosa.PresetUnexportedStart]*metadata
|
||||||
work := make([]*metadata, len(handles))
|
|
||||||
index.names = make(map[string]*metadata)
|
index.names = make(map[string]*metadata)
|
||||||
ir := pkg.NewIR()
|
for p := range rosa.PresetUnexportedStart {
|
||||||
for i, handle := range handles {
|
|
||||||
meta, a := rosa.Native().Std().MustLoad(handle)
|
|
||||||
m := metadata{
|
m := metadata{
|
||||||
handle: handle,
|
p: p,
|
||||||
|
|
||||||
Metadata: meta,
|
Metadata: rosa.GetMetadata(p),
|
||||||
Version: meta.Version,
|
Version: rosa.Std.Version(p),
|
||||||
}
|
}
|
||||||
if m.Version == "" {
|
if m.Version == "" {
|
||||||
return errors.New("invalid version from " + m.Name)
|
return errors.New("invalid version from " + m.Name)
|
||||||
@@ -75,33 +72,33 @@ func (index *packageIndex) populate(report *rosa.Report) (err error) {
|
|||||||
m.Version = ""
|
m.Version = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if report != nil {
|
if cache != nil && report != nil {
|
||||||
id := ir.Ident(a)
|
id := cache.Ident(rosa.Std.Load(p))
|
||||||
m.ids = pkg.Encode(id.Value())
|
m.ids = pkg.Encode(id.Value())
|
||||||
m.status, m.Size = report.ArtifactOf(id)
|
m.status, m.Size = report.ArtifactOf(id)
|
||||||
m.HasReport = m.Size >= 0
|
m.HasReport = m.Size >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
work[i] = &m
|
work[p] = &m
|
||||||
index.names[m.Name] = &m
|
index.names[m.Name] = &m
|
||||||
}
|
}
|
||||||
|
|
||||||
index.sorts[declarationAscending] = work
|
index.sorts[declarationAscending] = work
|
||||||
index.sorts[declarationDescending] = slices.Clone(work)
|
index.sorts[declarationDescending] = work
|
||||||
slices.Reverse(index.sorts[declarationDescending][:])
|
slices.Reverse(index.sorts[declarationDescending][:])
|
||||||
|
|
||||||
index.sorts[nameAscending] = slices.Clone(work)
|
index.sorts[nameAscending] = work
|
||||||
slices.SortFunc(index.sorts[nameAscending][:], func(a, b *metadata) int {
|
slices.SortFunc(index.sorts[nameAscending][:], func(a, b *metadata) int {
|
||||||
return strings.Compare(a.Name, b.Name)
|
return strings.Compare(a.Name, b.Name)
|
||||||
})
|
})
|
||||||
index.sorts[nameDescending] = slices.Clone(index.sorts[nameAscending])
|
index.sorts[nameDescending] = index.sorts[nameAscending]
|
||||||
slices.Reverse(index.sorts[nameDescending][:])
|
slices.Reverse(index.sorts[nameDescending][:])
|
||||||
|
|
||||||
index.sorts[sizeAscending] = slices.Clone(work)
|
index.sorts[sizeAscending] = work
|
||||||
slices.SortFunc(index.sorts[sizeAscending][:], func(a, b *metadata) int {
|
slices.SortFunc(index.sorts[sizeAscending][:], func(a, b *metadata) int {
|
||||||
return cmp.Compare(a.Size, b.Size)
|
return cmp.Compare(a.Size, b.Size)
|
||||||
})
|
})
|
||||||
index.sorts[sizeDescending] = slices.Clone(index.sorts[sizeAscending])
|
index.sorts[sizeDescending] = index.sorts[sizeAscending]
|
||||||
slices.Reverse(index.sorts[sizeDescending][:])
|
slices.Reverse(index.sorts[sizeDescending][:])
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/command"
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/internal/rosa"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shutdownTimeout = 15 * time.Second
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("pkgserver: ")
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagBaseDir string
|
||||||
|
flagAddr string
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||||
|
defer stop()
|
||||||
|
msg := message.New(log.Default())
|
||||||
|
|
||||||
|
c := command.New(os.Stderr, log.Printf, "pkgserver", func(args []string) error {
|
||||||
|
var (
|
||||||
|
cache *pkg.Cache
|
||||||
|
report *rosa.Report
|
||||||
|
)
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
baseDir, err := check.NewAbs(flagBaseDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cache, err = pkg.Open(ctx, msg, 0, 0, 0, baseDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cache.Close()
|
||||||
|
|
||||||
|
report, err = rosa.OpenReport(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.New("pkgserver requires 1 argument")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var index packageIndex
|
||||||
|
index.search = make(searchCache)
|
||||||
|
if err := index.populate(cache, report); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
index.search.clean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var mux http.ServeMux
|
||||||
|
uiRoutes(&mux)
|
||||||
|
index.registerAPI(&mux)
|
||||||
|
server := http.Server{
|
||||||
|
Addr: flagAddr,
|
||||||
|
Handler: &mux,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
c, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := server.Shutdown(c); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return server.ListenAndServe()
|
||||||
|
}).Flag(
|
||||||
|
&flagBaseDir,
|
||||||
|
"b", command.StringFlag(""),
|
||||||
|
"base directory for cache",
|
||||||
|
).Flag(
|
||||||
|
&flagAddr,
|
||||||
|
"addr", command.StringFlag(":8067"),
|
||||||
|
"TCP network address to listen on",
|
||||||
|
)
|
||||||
|
c.MustParse(os.Args[1:], func(err error) {
|
||||||
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
log.Fatal(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package pkgserver
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -15,7 +15,7 @@ func newIndex(t *testing.T) *packageIndex {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
var index packageIndex
|
var index packageIndex
|
||||||
if err := index.populate(nil); err != nil {
|
if err := index.populate(nil, nil); err != nil {
|
||||||
t.Fatalf("populate: error = %v", err)
|
t.Fatalf("populate: error = %v", err)
|
||||||
}
|
}
|
||||||
return &index
|
return &index
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package pkgserver
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
@@ -74,7 +74,7 @@ func (s *searchCache) clean() {
|
|||||||
}
|
}
|
||||||
func indexsum(in [][]int) int {
|
func indexsum(in [][]int) int {
|
||||||
sum := 0
|
sum := 0
|
||||||
for i := range in {
|
for i := 0; i < len(in); i++ {
|
||||||
sum += in[i][1] - in[i][0]
|
sum += in[i][1] - in[i][0]
|
||||||
}
|
}
|
||||||
return sum
|
return sum
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func serveWebUI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Expires", "0")
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
w.Header().Set("X-XSS-Protection", "1")
|
||||||
|
w.Header().Set("X-Frame-Options", "DENY")
|
||||||
|
|
||||||
|
http.ServeFileFS(w, r, content, "ui/index.html")
|
||||||
|
}
|
||||||
|
func serveStaticContent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/static/style.css":
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/style.css")
|
||||||
|
case "/favicon.ico":
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/favicon.ico")
|
||||||
|
case "/static/index.js":
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/index.js")
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uiRoutes(mux *http.ServeMux) {
|
||||||
|
mux.HandleFunc("GET /{$}", serveWebUI)
|
||||||
|
mux.HandleFunc("GET /favicon.ico", serveStaticContent)
|
||||||
|
mux.HandleFunc("GET /static/", serveStaticContent)
|
||||||
|
}
|
||||||
@@ -3,13 +3,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="static/style.css">
|
||||||
<link rel="icon" href="https://hakurei.app/favicon.ico"/>
|
<title>Hakurei PkgServer</title>
|
||||||
<title>Rosa OS Packages</title>
|
<script src="static/index.js"></script>
|
||||||
<script src="index.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Rosa OS Packages</h1>
|
<h1>Hakurei PkgServer</h1>
|
||||||
<div class="top-controls" id="top-controls-regular">
|
<div class="top-controls" id="top-controls-regular">
|
||||||
<p>Showing entries <span id="entry-counter"></span>.</p>
|
<p>Showing entries <span id="entry-counter"></span>.</p>
|
||||||
<span id="search-bar">
|
<span id="search-bar">
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build frontend
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:generate tsc -p ui
|
||||||
|
//go:embed ui/*
|
||||||
|
var content embed.FS
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//go:build !frontend
|
//go:build !frontend
|
||||||
|
|
||||||
package ui
|
package main
|
||||||
|
|
||||||
import "testing/fstest"
|
import "testing/fstest"
|
||||||
|
|
||||||
var static fstest.MapFS
|
var content fstest.MapFS
|
||||||
+2
-2
@@ -508,8 +508,8 @@ func _main(s ...string) (exitCode int) {
|
|||||||
|
|
||||||
if !z.AllowOrphan {
|
if !z.AllowOrphan {
|
||||||
if err := z.Wait(); err != nil {
|
if err := z.Wait(); err != nil {
|
||||||
exitError, ok := errors.AsType[*exec.ExitError](err)
|
var exitError *exec.ExitError
|
||||||
if !ok || exitError == nil {
|
if !errors.As(err, &exitError) || exitError == nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return 5
|
return 5
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
virtualisation = {
|
virtualisation = {
|
||||||
# Hopefully reduces spurious test failures:
|
|
||||||
memorySize = if pkgs.stdenv.hostPlatform.is32bit then 2046 else 8192;
|
|
||||||
|
|
||||||
diskSize = 6 * 1024;
|
diskSize = 6 * 1024;
|
||||||
|
|
||||||
qemu.options = [
|
qemu.options = [
|
||||||
# Increase test performance:
|
# Increase test performance:
|
||||||
"-smp 16"
|
"-smp 8"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ testers.nixosTest {
|
|||||||
# For go tests:
|
# For go tests:
|
||||||
(pkgs.writeShellScriptBin "sharefs-workload-hakurei-tests" ''
|
(pkgs.writeShellScriptBin "sharefs-workload-hakurei-tests" ''
|
||||||
cp -r "${self.packages.${system}.hakurei.src}" "/sdcard/hakurei" && cd "/sdcard/hakurei"
|
cp -r "${self.packages.${system}.hakurei.src}" "/sdcard/hakurei" && cd "/sdcard/hakurei"
|
||||||
${fhs}/bin/hakurei-fhs -c 'ROSA_SKIP_BINFMT=1 CC="clang -O3 -Werror" go test ./...'
|
${fhs}/bin/hakurei-fhs -c 'CC="clang -O3 -Werror" go test ./...'
|
||||||
'')
|
'')
|
||||||
];
|
];
|
||||||
|
|
||||||
+2
-3
@@ -13,7 +13,6 @@ func New(output io.Writer, logf LogFunc, name string, early HandlerFunc) Command
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// newNode initialises a subcommand tree and returns its address.
|
|
||||||
func newNode(output io.Writer, logf LogFunc, name, usage string) *node {
|
func newNode(output io.Writer, logf LogFunc, name, usage string) *node {
|
||||||
n := &node{
|
n := &node{
|
||||||
name: name, usage: usage,
|
name: name, usage: usage,
|
||||||
@@ -23,8 +22,8 @@ func newNode(output io.Writer, logf LogFunc, name, usage string) *node {
|
|||||||
n.set.SetOutput(output)
|
n.set.SetOutput(output)
|
||||||
n.set.Usage = func() {
|
n.set.Usage = func() {
|
||||||
_ = n.writeHelp()
|
_ = n.writeHelp()
|
||||||
if len(n.suffix) > 0 {
|
if n.suffix.Len() > 0 {
|
||||||
_, _ = fmt.Fprintln(output, "flags:")
|
_, _ = fmt.Fprintln(output, "Flags:")
|
||||||
n.set.PrintDefaults()
|
n.set.PrintDefaults()
|
||||||
_, _ = fmt.Fprintln(output)
|
_, _ = fmt.Fprintln(output)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-16
@@ -18,14 +18,8 @@ func TestBuild(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("direct zero length", func(t *testing.T) {
|
t.Run("direct zero length", func(t *testing.T) {
|
||||||
wantPanic := "invalid subcommand"
|
wantPanic := "invalid subcommand"
|
||||||
t.Run("zero length name", func(t *testing.T) {
|
t.Run("zero length name", func(t *testing.T) { defer checkRecover(t, "Command", wantPanic); c.Command("", "usage", stubHandler) })
|
||||||
defer checkRecover(t, "Command", wantPanic)
|
t.Run("zero length usage", func(t *testing.T) { defer checkRecover(t, "Command", wantPanic); c.Command("name", "", stubHandler) })
|
||||||
c.Command("", "usage", stubHandler)
|
|
||||||
})
|
|
||||||
t.Run("zero length usage", func(t *testing.T) {
|
|
||||||
defer checkRecover(t, "Command", wantPanic)
|
|
||||||
c.Command("name", "", stubHandler)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("direct adopt unique names", func(t *testing.T) {
|
t.Run("direct adopt unique names", func(t *testing.T) {
|
||||||
@@ -40,14 +34,8 @@ func TestBuild(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("zero length", func(t *testing.T) {
|
t.Run("zero length", func(t *testing.T) {
|
||||||
wantPanic := "invalid subcommand tree"
|
wantPanic := "invalid subcommand tree"
|
||||||
t.Run("zero length name", func(t *testing.T) {
|
t.Run("zero length name", func(t *testing.T) { defer checkRecover(t, "New", wantPanic); c.New("", "usage") })
|
||||||
defer checkRecover(t, "New", wantPanic)
|
t.Run("zero length usage", func(t *testing.T) { defer checkRecover(t, "New", wantPanic); c.New("name", "") })
|
||||||
c.New("", "usage")
|
|
||||||
})
|
|
||||||
t.Run("zero length usage", func(t *testing.T) {
|
|
||||||
defer checkRecover(t, "New", wantPanic)
|
|
||||||
c.New("name", "")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("direct adopt unique names", func(t *testing.T) {
|
t.Run("direct adopt unique names", func(t *testing.T) {
|
||||||
|
|||||||
+6
-26
@@ -6,43 +6,36 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsageInternal is a special usage string that hides the command from the
|
// UsageInternal causes the command to be hidden from help text when set as the usage string.
|
||||||
// generated help message.
|
const UsageInternal = "internal"
|
||||||
const UsageInternal = "\x00"
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// HandlerFunc is called when matching a directly handled subcommand tree.
|
// HandlerFunc is called when matching a directly handled subcommand tree.
|
||||||
HandlerFunc = func(args []string) error
|
HandlerFunc = func(args []string) error
|
||||||
|
|
||||||
// LogFunc is the function signature of a printf function. The zero value
|
// LogFunc is the function signature of a printf function.
|
||||||
// implies [log.Printf].
|
|
||||||
LogFunc = func(format string, a ...any)
|
LogFunc = func(format string, a ...any)
|
||||||
|
|
||||||
// FlagDefiner is a deferred flag definer value, usually encapsulating the
|
// FlagDefiner is a deferred flag definer value, usually encapsulating the default value.
|
||||||
// default value.
|
|
||||||
FlagDefiner interface {
|
FlagDefiner interface {
|
||||||
// Define defines the flag in set.
|
// Define defines the flag in set.
|
||||||
Define(b *strings.Builder, set *flag.FlagSet, p any, name, usage string)
|
Define(b *strings.Builder, set *flag.FlagSet, p any, name, usage string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Flag is satisfied by command objects capable of receiving flags.
|
|
||||||
Flag[T any] interface {
|
Flag[T any] interface {
|
||||||
// Flag defines a generic flag type in Node's flag set.
|
// Flag defines a generic flag type in Node's flag set.
|
||||||
Flag(p any, name string, value FlagDefiner, usage string) T
|
Flag(p any, name string, value FlagDefiner, usage string) T
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Command is the root of a command tree.
|
|
||||||
Command interface {
|
Command interface {
|
||||||
Parse(arguments []string) error
|
Parse(arguments []string) error
|
||||||
|
|
||||||
// MustParse determines exit outcomes for Parse errors and calls
|
// MustParse determines exit outcomes for Parse errors
|
||||||
// handleError if [HandlerFunc] returns a non-nil error.
|
// and calls handleError if [HandlerFunc] returns a non-nil error.
|
||||||
MustParse(arguments []string, handleError func(error))
|
MustParse(arguments []string, handleError func(error))
|
||||||
|
|
||||||
baseNode[Command]
|
baseNode[Command]
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Node is a subcommand under a [Command].
|
|
||||||
Node baseNode[Node]
|
Node baseNode[Node]
|
||||||
|
|
||||||
baseNode[T any] interface {
|
baseNode[T any] interface {
|
||||||
@@ -60,16 +53,3 @@ type (
|
|||||||
Flag[T]
|
Flag[T]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// rootNode satisfies baseNode for [Command].
|
|
||||||
type rootNode struct{ *node }
|
|
||||||
|
|
||||||
func (r rootNode) Command(name, usage string, f HandlerFunc) Command {
|
|
||||||
r.node.Command(name, usage, f)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r rootNode) Flag(p any, name string, value FlagDefiner, usage string) Command {
|
|
||||||
r.node.Flag(p, name, value, usage)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-7
@@ -16,13 +16,7 @@ func (e FlagError) Is(target error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) Flag(p any, name string, value FlagDefiner, usage string) Node {
|
func (n *node) Flag(p any, name string, value FlagDefiner, usage string) Node {
|
||||||
var buf strings.Builder
|
value.Define(&n.suffix, n.set, p, name, usage)
|
||||||
value.Define(&buf, n.set, p, name, usage)
|
|
||||||
s := buf.String()
|
|
||||||
if len(s) > 0 && s[0] == ' ' {
|
|
||||||
s = s[1:]
|
|
||||||
}
|
|
||||||
n.suffix = append(n.suffix, s)
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-21
@@ -1,7 +1,6 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -14,31 +13,14 @@ var ErrHelp = errors.New("help requested")
|
|||||||
func (n *node) PrintHelp() { _ = n.writeHelp() }
|
func (n *node) PrintHelp() { _ = n.writeHelp() }
|
||||||
|
|
||||||
func (n *node) writeHelp() error {
|
func (n *node) writeHelp() error {
|
||||||
prefix := strings.Join(append(n.prefix, n.name), " ")
|
|
||||||
var buf strings.Builder
|
|
||||||
offset := 7 + len(prefix)
|
|
||||||
line := offset + 14
|
|
||||||
w := bytes.Repeat([]byte{' '}, offset+1)
|
|
||||||
w[0] = '\n'
|
|
||||||
|
|
||||||
for _, flag := range n.suffix {
|
|
||||||
line += len(flag) + 1
|
|
||||||
if line >= 80 {
|
|
||||||
line = offset + len(flag) + 1
|
|
||||||
buf.Write(w)
|
|
||||||
}
|
|
||||||
buf.WriteByte(' ')
|
|
||||||
buf.WriteString(flag)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(n.out,
|
if _, err := fmt.Fprintf(n.out,
|
||||||
"usage: %s [-h | --help]%s <command> [<args>]\n",
|
"\nUsage:\t%s [-h | --help]%s COMMAND [OPTIONS]\n",
|
||||||
prefix, &buf,
|
strings.Join(append(n.prefix, n.name), " "), &n.suffix,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if n.child != nil {
|
if n.child != nil {
|
||||||
if _, err := fmt.Fprint(n.out, "\ncommands:\n"); err != nil {
|
if _, err := fmt.Fprint(n.out, "\nCommands:\n"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -3,9 +3,9 @@ package command
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A node represents a command.
|
|
||||||
type node struct {
|
type node struct {
|
||||||
child, next *node
|
child, next *node
|
||||||
name, usage string
|
name, usage string
|
||||||
@@ -13,16 +13,13 @@ type node struct {
|
|||||||
out io.Writer
|
out io.Writer
|
||||||
logf LogFunc
|
logf LogFunc
|
||||||
|
|
||||||
// Names of commands preceding node.
|
|
||||||
prefix []string
|
prefix []string
|
||||||
// Short user-facing representations of flags received by node.
|
suffix strings.Builder
|
||||||
suffix []string
|
|
||||||
|
|
||||||
f HandlerFunc
|
f HandlerFunc
|
||||||
set *flag.FlagSet
|
set *flag.FlagSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// adopt adds v as the last child of n.
|
|
||||||
func (n *node) adopt(v *node) bool {
|
func (n *node) adopt(v *node) bool {
|
||||||
if n.child != nil {
|
if n.child != nil {
|
||||||
return n.child.append(v)
|
return n.child.append(v)
|
||||||
@@ -31,7 +28,6 @@ func (n *node) adopt(v *node) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// append adds v as the last sibling of n.
|
|
||||||
func (n *node) append(v *node) bool {
|
func (n *node) append(v *node) bool {
|
||||||
if n.name == v.name {
|
if n.name == v.name {
|
||||||
return false
|
return false
|
||||||
|
|||||||
+2
-2
@@ -91,8 +91,8 @@ func (n *node) MustParse(arguments []string, handleError func(error)) {
|
|||||||
case ErrEmptyTree:
|
case ErrEmptyTree:
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
default:
|
default:
|
||||||
flagError, ok := errors.AsType[FlagError](err)
|
var flagError FlagError
|
||||||
if !ok { // returned by HandlerFunc
|
if !errors.As(err, &flagError) { // returned by HandlerFunc
|
||||||
handleError(err)
|
handleError(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-18
@@ -70,7 +70,7 @@ func TestParse(t *testing.T) {
|
|||||||
"d=0 out of order string flag",
|
"d=0 out of order string flag",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"string", "--string", "64d3b4b7b21788585845060e2199a78f"},
|
[]string{"string", "--string", "64d3b4b7b21788585845060e2199a78f"},
|
||||||
"flag provided but not defined: -string\nusage: test string [-h | --help] <command> [<args>]\n\n", "",
|
"flag provided but not defined: -string\n\nUsage:\ttest string [-h | --help] COMMAND [OPTIONS]\n\n", "",
|
||||||
errors.New("flag provided but not defined: -string"),
|
errors.New("flag provided but not defined: -string"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -120,7 +120,7 @@ func TestParse(t *testing.T) {
|
|||||||
"d=1 empty sub help",
|
"d=1 empty sub help",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"empty", "-h"},
|
[]string{"empty", "-h"},
|
||||||
"usage: test empty [-h | --help] <command> [<args>]\n\n", "", flag.ErrHelp,
|
"\nUsage:\ttest empty [-h | --help] COMMAND [OPTIONS]\n\n", "", flag.ErrHelp,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"d=1 no match",
|
"d=1 no match",
|
||||||
@@ -151,10 +151,10 @@ func TestParse(t *testing.T) {
|
|||||||
"d=0 help",
|
"d=0 help",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{},
|
[]string{},
|
||||||
`usage: test [-h | --help] [-v] [--fail] [--string <value>] [--int <int>]
|
`
|
||||||
[--repeat <value>] <command> [<args>]
|
Usage: test [-h | --help] [-v] [--fail] [--string <value>] [--int <int>] [--repeat <value>] COMMAND [OPTIONS]
|
||||||
|
|
||||||
commands:
|
Commands:
|
||||||
error return an error
|
error return an error
|
||||||
print wraps Fprint
|
print wraps Fprint
|
||||||
string print string passed by flag
|
string print string passed by flag
|
||||||
@@ -171,10 +171,10 @@ commands:
|
|||||||
"d=0 help flag",
|
"d=0 help flag",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"-h"},
|
[]string{"-h"},
|
||||||
`usage: test [-h | --help] [-v] [--fail] [--string <value>] [--int <int>]
|
`
|
||||||
[--repeat <value>] <command> [<args>]
|
Usage: test [-h | --help] [-v] [--fail] [--string <value>] [--int <int>] [--repeat <value>] COMMAND [OPTIONS]
|
||||||
|
|
||||||
commands:
|
Commands:
|
||||||
error return an error
|
error return an error
|
||||||
print wraps Fprint
|
print wraps Fprint
|
||||||
string print string passed by flag
|
string print string passed by flag
|
||||||
@@ -185,7 +185,7 @@ commands:
|
|||||||
succeed this command succeeds
|
succeed this command succeeds
|
||||||
deep top level of command tree with various levels
|
deep top level of command tree with various levels
|
||||||
|
|
||||||
flags:
|
Flags:
|
||||||
-fail
|
-fail
|
||||||
fail early
|
fail early
|
||||||
-int int
|
-int int
|
||||||
@@ -203,9 +203,10 @@ flags:
|
|||||||
"d=1 help",
|
"d=1 help",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"join"},
|
[]string{"join"},
|
||||||
`usage: test join [-h | --help] <command> [<args>]
|
`
|
||||||
|
Usage: test join [-h | --help] COMMAND [OPTIONS]
|
||||||
|
|
||||||
commands:
|
Commands:
|
||||||
out write result to wout
|
out write result to wout
|
||||||
log log result to wlog
|
log log result to wlog
|
||||||
|
|
||||||
@@ -215,9 +216,10 @@ commands:
|
|||||||
"d=1 help flag",
|
"d=1 help flag",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"join", "-h"},
|
[]string{"join", "-h"},
|
||||||
`usage: test join [-h | --help] <command> [<args>]
|
`
|
||||||
|
Usage: test join [-h | --help] COMMAND [OPTIONS]
|
||||||
|
|
||||||
commands:
|
Commands:
|
||||||
out write result to wout
|
out write result to wout
|
||||||
log log result to wlog
|
log log result to wlog
|
||||||
|
|
||||||
@@ -228,9 +230,10 @@ commands:
|
|||||||
"d=2 help",
|
"d=2 help",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"deep", "d=2"},
|
[]string{"deep", "d=2"},
|
||||||
`usage: test deep d=2 [-h | --help] <command> [<args>]
|
`
|
||||||
|
Usage: test deep d=2 [-h | --help] COMMAND [OPTIONS]
|
||||||
|
|
||||||
commands:
|
Commands:
|
||||||
d=3 relative third level
|
d=3 relative third level
|
||||||
|
|
||||||
`, "", command.ErrHelp,
|
`, "", command.ErrHelp,
|
||||||
@@ -239,9 +242,10 @@ commands:
|
|||||||
"d=2 help flag",
|
"d=2 help flag",
|
||||||
buildTestCommand,
|
buildTestCommand,
|
||||||
[]string{"deep", "d=2", "-h"},
|
[]string{"deep", "d=2", "-h"},
|
||||||
`usage: test deep d=2 [-h | --help] <command> [<args>]
|
`
|
||||||
|
Usage: test deep d=2 [-h | --help] COMMAND [OPTIONS]
|
||||||
|
|
||||||
commands:
|
Commands:
|
||||||
d=3 relative third level
|
d=3 relative third level
|
||||||
|
|
||||||
`, "", flag.ErrHelp,
|
`, "", flag.ErrHelp,
|
||||||
@@ -257,7 +261,7 @@ commands:
|
|||||||
t.Errorf("Parse: error = %v; wantErr %v", err, tc.wantErr)
|
t.Errorf("Parse: error = %v; wantErr %v", err, tc.wantErr)
|
||||||
}
|
}
|
||||||
if got := wout.String(); got != tc.want {
|
if got := wout.String(); got != tc.want {
|
||||||
t.Errorf("Parse:\n%s\nwant\n%s", got, tc.want)
|
t.Errorf("Parse: %s want %s", got, tc.want)
|
||||||
}
|
}
|
||||||
if gotLog := wlog.String(); gotLog != tc.wantLog {
|
if gotLog := wlog.String(); gotLog != tc.wantLog {
|
||||||
t.Errorf("Parse: log = %s wantLog %s", gotLog, tc.wantLog)
|
t.Errorf("Parse: log = %s wantLog %s", gotLog, tc.wantLog)
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
// the top level node wants [Command] returned for its builder methods
|
||||||
|
type rootNode struct{ *node }
|
||||||
|
|
||||||
|
func (r rootNode) Command(name, usage string, f HandlerFunc) Command {
|
||||||
|
r.node.Command(name, usage, f)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r rootNode) Flag(p any, name string, value FlagDefiner, usage string) Command {
|
||||||
|
r.node.Flag(p, name, value, usage)
|
||||||
|
return r
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
)
|
|
||||||
|
|
||||||
// escapeBinfmt escapes magic/mask sequences in a [BinfmtEntry].
|
|
||||||
func escapeBinfmt(buf *strings.Builder, s string) string {
|
|
||||||
const lowerhex = "0123456789abcdef"
|
|
||||||
|
|
||||||
buf.Reset()
|
|
||||||
for _, c := range unsafe.Slice(unsafe.StringData(s), len(s)) {
|
|
||||||
switch c {
|
|
||||||
case 0, '\\', ':':
|
|
||||||
buf.WriteString(`\x`)
|
|
||||||
buf.WriteByte(lowerhex[c>>4])
|
|
||||||
buf.WriteByte(lowerhex[c&0xf])
|
|
||||||
|
|
||||||
default:
|
|
||||||
buf.WriteByte(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BinfmtEntry is an entry to be registered by the init process.
|
|
||||||
type BinfmtEntry struct {
|
|
||||||
// The offset of the magic/mask in the file, counted in bytes.
|
|
||||||
Offset byte
|
|
||||||
// The byte sequence binfmt_misc is matching for.
|
|
||||||
Magic string
|
|
||||||
// An (optional, defaults to all 0xff) mask.
|
|
||||||
Mask string
|
|
||||||
// The program that should be invoked with the binary as first argument.
|
|
||||||
Interpreter *check.Absolute
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid returns whether e can be registered into the kernel.
|
|
||||||
func (e *BinfmtEntry) Valid() bool {
|
|
||||||
return e != nil &&
|
|
||||||
int(e.Offset)+max(len(e.Magic), len(e.Mask)) < 128 &&
|
|
||||||
e.Interpreter != nil && len(e.Interpreter.String()) < 128
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/fhs"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEscapeBinfmt(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
magic string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"packed DOS applications", "\x0eDEX", "\x0eDEX"},
|
|
||||||
|
|
||||||
{"riscv64 magic",
|
|
||||||
"\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00",
|
|
||||||
"\x7fELF\x02\x01\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\x02\\x00\xf3\\x00"},
|
|
||||||
{"riscv64 mask",
|
|
||||||
"\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
|
|
||||||
"\xff\xff\xff\xff\xff\xff\xff\\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
got := escapeBinfmt(new(strings.Builder), tc.magic)
|
|
||||||
if got != tc.want {
|
|
||||||
t.Errorf("escapeBinfmt: %q, want %q", got, tc.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBinfmtEntry(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
e BinfmtEntry
|
|
||||||
valid bool
|
|
||||||
}{
|
|
||||||
{"zero", BinfmtEntry{}, false},
|
|
||||||
{"large offset", BinfmtEntry{Offset: 128}, false},
|
|
||||||
{"long magic", BinfmtEntry{Magic: strings.Repeat("\x00", 128)}, false},
|
|
||||||
{"long mask", BinfmtEntry{Mask: strings.Repeat("\x00", 128)}, false},
|
|
||||||
{"valid", BinfmtEntry{Interpreter: fhs.AbsRoot}, true},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if tc.e.Valid() != tc.valid {
|
|
||||||
t.Errorf("Valid: %v", !tc.valid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ const (
|
|||||||
CAP_SETPCAP = 0x8
|
CAP_SETPCAP = 0x8
|
||||||
CAP_NET_ADMIN = 0xc
|
CAP_NET_ADMIN = 0xc
|
||||||
CAP_DAC_OVERRIDE = 0x1
|
CAP_DAC_OVERRIDE = 0x1
|
||||||
CAP_SETFCAP = 0x1f
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|||||||
+11
-29
@@ -67,9 +67,6 @@ type (
|
|||||||
// Copied to the underlying [exec.Cmd].
|
// Copied to the underlying [exec.Cmd].
|
||||||
WaitDelay time.Duration
|
WaitDelay time.Duration
|
||||||
|
|
||||||
// Suppress verbose output of init.
|
|
||||||
Quiet bool
|
|
||||||
|
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
msg message.Msg
|
msg message.Msg
|
||||||
@@ -91,20 +88,12 @@ type (
|
|||||||
// Time to wait for processes lingering after the initial process terminates.
|
// Time to wait for processes lingering after the initial process terminates.
|
||||||
AdoptWaitDelay time.Duration
|
AdoptWaitDelay time.Duration
|
||||||
|
|
||||||
// Map uid/gid 0 in the init process. Requires [FstypeProc] attached to
|
|
||||||
// [fhs.Proc] in the container filesystem.
|
|
||||||
InitAsRoot bool
|
|
||||||
// Mapped Uid in user namespace.
|
// Mapped Uid in user namespace.
|
||||||
Uid int
|
Uid int
|
||||||
// Mapped Gid in user namespace.
|
// Mapped Gid in user namespace.
|
||||||
Gid int
|
Gid int
|
||||||
// Hostname value in UTS namespace.
|
// Hostname value in UTS namespace.
|
||||||
Hostname string
|
Hostname string
|
||||||
// Register binfmt_misc entries.
|
|
||||||
Binfmt []BinfmtEntry
|
|
||||||
// Alternative pathname to attach binfmt_misc filesystem. The zero value
|
|
||||||
// requires [FstypeProc] to be made available at [fhs.Proc].
|
|
||||||
BinfmtPath *check.Absolute
|
|
||||||
// Sequential container setup ops.
|
// Sequential container setup ops.
|
||||||
*Ops
|
*Ops
|
||||||
|
|
||||||
@@ -154,8 +143,11 @@ func (e *StartError) Error() string {
|
|||||||
return e.Step
|
return e.Step
|
||||||
}
|
}
|
||||||
|
|
||||||
if se, ok := errors.AsType[*os.SyscallError](e.Err); ok && se != nil {
|
{
|
||||||
return e.Step + " " + se.Error()
|
var syscallError *os.SyscallError
|
||||||
|
if errors.As(e.Err, &syscallError) && syscallError != nil {
|
||||||
|
return e.Step + " " + syscallError.Error()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.Step + ": " + e.Err.Error()
|
return e.Step + ": " + e.Err.Error()
|
||||||
@@ -221,9 +213,6 @@ func (p *Container) Start() error {
|
|||||||
if p.cmd.Process != nil {
|
if p.cmd.Process != nil {
|
||||||
return errors.New("container: already started")
|
return errors.New("container: already started")
|
||||||
}
|
}
|
||||||
if !p.InitAsRoot && len(p.Binfmt) > 0 {
|
|
||||||
return errors.New("container: init as root required, but not enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ensureCloseOnExec(); err != nil {
|
if err := ensureCloseOnExec(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -294,18 +283,6 @@ func (p *Container) Start() error {
|
|||||||
if !p.HostNet {
|
if !p.HostNet {
|
||||||
p.cmd.SysProcAttr.Cloneflags |= CLONE_NEWNET
|
p.cmd.SysProcAttr.Cloneflags |= CLONE_NEWNET
|
||||||
}
|
}
|
||||||
if p.InitAsRoot {
|
|
||||||
p.cmd.SysProcAttr.AmbientCaps = append(p.cmd.SysProcAttr.AmbientCaps,
|
|
||||||
// mappings during init as root
|
|
||||||
CAP_SETFCAP,
|
|
||||||
)
|
|
||||||
|
|
||||||
if !p.SeccompDisable &&
|
|
||||||
len(p.SeccompRules) == 0 &&
|
|
||||||
p.SeccompPresets&std.PresetDenyNS != 0 {
|
|
||||||
return errors.New("container: as root requires late namespace creation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// place setup pipe before user supplied extra files, this is later restored by init
|
// place setup pipe before user supplied extra files, this is later restored by init
|
||||||
if r, w, err := os.Pipe(); err != nil {
|
if r, w, err := os.Pipe(); err != nil {
|
||||||
@@ -365,6 +342,8 @@ func (p *Container) Start() error {
|
|||||||
Err: ENOSYS,
|
Err: ENOSYS,
|
||||||
Origin: true,
|
Origin: true,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
p.msg.Verbosef("landlock abi version %d", abi)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
||||||
@@ -374,6 +353,7 @@ func (p *Container) Start() error {
|
|||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
||||||
if err = landlock.RestrictSelf(rulesetFd, 0); err != nil {
|
if err = landlock.RestrictSelf(rulesetFd, 0); err != nil {
|
||||||
_ = Close(rulesetFd)
|
_ = Close(rulesetFd)
|
||||||
return &StartError{
|
return &StartError{
|
||||||
@@ -430,6 +410,7 @@ func (p *Container) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.msg.Verbose("starting container init")
|
||||||
if err := p.cmd.Start(); err != nil {
|
if err := p.cmd.Start(); err != nil {
|
||||||
return &StartError{
|
return &StartError{
|
||||||
Step: "start container init",
|
Step: "start container init",
|
||||||
@@ -500,6 +481,7 @@ func (p *Container) Serve() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case <-done:
|
case <-done:
|
||||||
|
p.msg.Verbose("setup payload took", time.Since(t))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}(p.setup[1])
|
}(p.setup[1])
|
||||||
@@ -509,7 +491,7 @@ func (p *Container) Serve() (err error) {
|
|||||||
Getuid(),
|
Getuid(),
|
||||||
Getgid(),
|
Getgid(),
|
||||||
len(p.ExtraFiles),
|
len(p.ExtraFiles),
|
||||||
p.msg.IsVerbose() && !p.Quiet,
|
p.msg.IsVerbose(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+53
-175
@@ -16,8 +16,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
@@ -235,9 +233,6 @@ func earlyMnt(mnt ...*vfs.MountInfoEntry) func(*testing.T, context.Context) []*v
|
|||||||
return func(*testing.T, context.Context) []*vfs.MountInfoEntry { return mnt }
|
return func(*testing.T, context.Context) []*vfs.MountInfoEntry { return mnt }
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:linkname toHost hakurei.app/container.toHost
|
|
||||||
func toHost(name string) string
|
|
||||||
|
|
||||||
var containerTestCases = []struct {
|
var containerTestCases = []struct {
|
||||||
name string
|
name string
|
||||||
filter bool
|
filter bool
|
||||||
@@ -337,15 +332,13 @@ var containerTestCases = []struct {
|
|||||||
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
||||||
return []*vfs.MountInfoEntry{
|
return []*vfs.MountInfoEntry{
|
||||||
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
||||||
"rw"+
|
"rw,lowerdir="+
|
||||||
",lowerdir+="+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
|
||||||
toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
||||||
",lowerdir+="+
|
|
||||||
toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
|
||||||
",upperdir="+
|
",upperdir="+
|
||||||
toHost(ctx.Value(testVal("upper")).(*check.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("upper")).(*check.Absolute).String())+
|
||||||
",workdir="+
|
",workdir="+
|
||||||
toHost(ctx.Value(testVal("work")).(*check.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("work")).(*check.Absolute).String())+
|
||||||
",redirect_dir=nofollow,uuid=on,userxattr"),
|
",redirect_dir=nofollow,uuid=on,userxattr"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -395,11 +388,9 @@ var containerTestCases = []struct {
|
|||||||
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
||||||
return []*vfs.MountInfoEntry{
|
return []*vfs.MountInfoEntry{
|
||||||
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
||||||
"ro"+
|
"ro,lowerdir="+
|
||||||
",lowerdir+="+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
|
||||||
toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
||||||
",lowerdir+="+
|
|
||||||
toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
|
||||||
",redirect_dir=nofollow,userxattr"),
|
",redirect_dir=nofollow,userxattr"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -409,11 +400,39 @@ var containerTestCases = []struct {
|
|||||||
func TestContainer(t *testing.T) {
|
func TestContainer(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var suffix string
|
t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) {
|
||||||
runTests:
|
wantErr := context.Canceled
|
||||||
|
wantExitCode := 0
|
||||||
|
if err := c.Wait(); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
|
t.Error(m)
|
||||||
|
}
|
||||||
|
t.Errorf("Wait: error = %#v, want %#v", err, wantErr)
|
||||||
|
}
|
||||||
|
if ps := c.ProcessState(); ps == nil {
|
||||||
|
t.Errorf("ProcessState unexpectedly returned nil")
|
||||||
|
} else if code := ps.ExitCode(); code != wantExitCode {
|
||||||
|
t.Errorf("ExitCode: %d, want %d", code, wantExitCode)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
t.Run("forward", testContainerCancel(func(c *container.Container) {
|
||||||
|
c.ForwardCancel = true
|
||||||
|
}, func(t *testing.T, c *container.Container) {
|
||||||
|
var exitError *exec.ExitError
|
||||||
|
if err := c.Wait(); !errors.As(err, &exitError) {
|
||||||
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
|
t.Error(m)
|
||||||
|
}
|
||||||
|
t.Errorf("Wait: error = %v", err)
|
||||||
|
}
|
||||||
|
if code := exitError.ExitCode(); code != blockExitCodeInterrupt {
|
||||||
|
t.Errorf("ExitCode: %d, want %d", code, blockExitCodeInterrupt)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
for i, tc := range containerTestCases {
|
for i, tc := range containerTestCases {
|
||||||
_suffix := suffix
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Run(tc.name+_suffix, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantOps, wantOpsCtx := tc.ops(t)
|
wantOps, wantOpsCtx := tc.ops(t)
|
||||||
@@ -437,8 +456,6 @@ runTests:
|
|||||||
c.SeccompDisable = !tc.filter
|
c.SeccompDisable = !tc.filter
|
||||||
c.RetainSession = tc.session
|
c.RetainSession = tc.session
|
||||||
c.HostNet = tc.net
|
c.HostNet = tc.net
|
||||||
c.InitAsRoot = _suffix != ""
|
|
||||||
c.Env = append(c.Env, "HAKUREI_TEST_SUFFIX="+_suffix)
|
|
||||||
if info.CanDegrade {
|
if info.CanDegrade {
|
||||||
if _, err := landlock.GetABI(); err != nil {
|
if _, err := landlock.GetABI(); err != nil {
|
||||||
if !errors.Is(err, syscall.ENOSYS) {
|
if !errors.Is(err, syscall.ENOSYS) {
|
||||||
@@ -448,9 +465,6 @@ runTests:
|
|||||||
t.Log("Landlock LSM is unavailable, enabling HostAbstract")
|
t.Log("Landlock LSM is unavailable, enabling HostAbstract")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.InitAsRoot {
|
|
||||||
c.SeccompPresets &= ^std.PresetDenyNS
|
|
||||||
}
|
|
||||||
|
|
||||||
c.
|
c.
|
||||||
Readonly(check.MustAbs(pathReadonly), 0755).
|
Readonly(check.MustAbs(pathReadonly), 0755).
|
||||||
@@ -519,11 +533,6 @@ runTests:
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if suffix == "" {
|
|
||||||
suffix = " as root"
|
|
||||||
goto runTests
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ent(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoEntry {
|
func ent(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoEntry {
|
||||||
@@ -546,10 +555,11 @@ func hostnameFromTestCase(name string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testContainerCancel(
|
func testContainerCancel(
|
||||||
t *testing.T,
|
|
||||||
containerExtra func(c *container.Container),
|
containerExtra func(c *container.Container),
|
||||||
waitCheck func(ps *os.ProcessState, waitErr error),
|
waitCheck func(t *testing.T, c *container.Container),
|
||||||
) {
|
) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
ctx, cancel := context.WithCancel(t.Context())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
|
||||||
c := helperNewContainer(ctx, "block")
|
c := helperNewContainer(ctx, "block")
|
||||||
@@ -559,36 +569,25 @@ func testContainerCancel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ready := make(chan struct{})
|
ready := make(chan struct{})
|
||||||
var waitErr error
|
if r, w, err := os.Pipe(); err != nil {
|
||||||
r, w, err := os.Pipe()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("cannot pipe: %v", err)
|
t.Fatalf("cannot pipe: %v", err)
|
||||||
}
|
} else {
|
||||||
|
|
||||||
c.ExtraFiles = append(c.ExtraFiles, w)
|
c.ExtraFiles = append(c.ExtraFiles, w)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ready)
|
defer close(ready)
|
||||||
if _, _err := r.Read(make([]byte, 1)); _err != nil {
|
if _, err = r.Read(make([]byte, 1)); err != nil {
|
||||||
panic(_err)
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if err = c.Start(); err != nil {
|
if err := c.Start(); err != nil {
|
||||||
if m, ok := container.InternalMessageFromError(err); ok {
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
t.Fatal(m)
|
t.Fatal(m)
|
||||||
} else {
|
} else {
|
||||||
t.Fatalf("cannot start container: %v", err)
|
t.Fatalf("cannot start container: %v", err)
|
||||||
}
|
}
|
||||||
}
|
} else if err = c.Serve(); err != nil {
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
defer close(done)
|
|
||||||
waitErr = c.Wait()
|
|
||||||
_ = r.SetReadDeadline(time.Now())
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = c.Serve(); err != nil {
|
|
||||||
if m, ok := container.InternalMessageFromError(err); ok {
|
if m, ok := container.InternalMessageFromError(err); ok {
|
||||||
t.Error(m)
|
t.Error(m)
|
||||||
} else {
|
} else {
|
||||||
@@ -597,67 +596,8 @@ func testContainerCancel(
|
|||||||
}
|
}
|
||||||
<-ready
|
<-ready
|
||||||
cancel()
|
cancel()
|
||||||
<-done
|
waitCheck(t, c)
|
||||||
waitCheck(c.ProcessState(), waitErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestForward(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
f := func(ps *os.ProcessState, waitErr error) {
|
|
||||||
var exitError *exec.ExitError
|
|
||||||
if !errors.As(waitErr, &exitError) {
|
|
||||||
if m, ok := container.InternalMessageFromError(waitErr); ok {
|
|
||||||
t.Error(m)
|
|
||||||
}
|
}
|
||||||
t.Errorf("Wait: error = %v", waitErr)
|
|
||||||
}
|
|
||||||
if code := exitError.ExitCode(); code != blockExitCodeInterrupt {
|
|
||||||
t.Errorf("ExitCode: %d, want %d", code, blockExitCodeInterrupt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Run("direct", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
testContainerCancel(t, func(c *container.Container) {
|
|
||||||
c.ForwardCancel = true
|
|
||||||
}, f)
|
|
||||||
})
|
|
||||||
t.Run("as root", func(t *testing.T) {
|
|
||||||
testContainerCancel(t, func(c *container.Container) {
|
|
||||||
c.ForwardCancel = true
|
|
||||||
c.InitAsRoot = true
|
|
||||||
c.Proc(fhs.AbsProc)
|
|
||||||
}, f)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCancel(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
f := func(ps *os.ProcessState, waitErr error) {
|
|
||||||
wantErr := context.Canceled
|
|
||||||
if !reflect.DeepEqual(waitErr, wantErr) {
|
|
||||||
if m, ok := container.InternalMessageFromError(waitErr); ok {
|
|
||||||
t.Error(m)
|
|
||||||
}
|
|
||||||
t.Errorf("Wait: error = %#v, want %#v", waitErr, wantErr)
|
|
||||||
}
|
|
||||||
if ps == nil {
|
|
||||||
t.Errorf("ProcessState unexpectedly returned nil")
|
|
||||||
} else if code := ps.ExitCode(); code != 0 {
|
|
||||||
t.Errorf("ExitCode: %d, want %d", code, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Run("direct", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
testContainerCancel(t, nil, f)
|
|
||||||
})
|
|
||||||
t.Run("as root", func(t *testing.T) {
|
|
||||||
testContainerCancel(t, func(c *container.Container) {
|
|
||||||
c.InitAsRoot = true
|
|
||||||
c.Proc(fhs.AbsProc)
|
|
||||||
}, f)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContainerString(t *testing.T) {
|
func TestContainerString(t *testing.T) {
|
||||||
@@ -693,8 +633,6 @@ func init() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
c.Command("container", command.UsageInternal, func(args []string) error {
|
c.Command("container", command.UsageInternal, func(args []string) error {
|
||||||
asRoot := os.Getenv("HAKUREI_TEST_SUFFIX") == " as root"
|
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return syscall.EINVAL
|
return syscall.EINVAL
|
||||||
}
|
}
|
||||||
@@ -712,66 +650,6 @@ func init() {
|
|||||||
return fmt.Errorf("gid: %d, want %d", gid, tc.gid)
|
return fmt.Errorf("gid: %d, want %d", gid, tc.gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// no attack surface increase during as root due to no_new_privs
|
|
||||||
var wantBounding uintptr = 1
|
|
||||||
asRootNot := " not"
|
|
||||||
if !asRoot {
|
|
||||||
wantBounding = 0
|
|
||||||
asRootNot = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
PR_CAP_AMBIENT = 0x2f
|
|
||||||
PR_CAP_AMBIENT_IS_SET = 0x1
|
|
||||||
)
|
|
||||||
for i := range container.LastCap(nil) + 1 {
|
|
||||||
r, _, errno := syscall.Syscall(
|
|
||||||
syscall.SYS_PRCTL,
|
|
||||||
PR_CAP_AMBIENT,
|
|
||||||
PR_CAP_AMBIENT_IS_SET,
|
|
||||||
i,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
return os.NewSyscallError("prctl", errno)
|
|
||||||
}
|
|
||||||
if r != 0 {
|
|
||||||
return fmt.Errorf("capability %d in ambient set", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, errno = syscall.Syscall(
|
|
||||||
syscall.SYS_PRCTL,
|
|
||||||
syscall.PR_CAPBSET_READ,
|
|
||||||
i,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
return os.NewSyscallError("prctl", errno)
|
|
||||||
}
|
|
||||||
if r != wantBounding {
|
|
||||||
return fmt.Errorf("capability %d%s in bounding set", i, asRootNot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _LINUX_CAPABILITY_VERSION_3 = 0x20080522
|
|
||||||
var capData struct {
|
|
||||||
effective uint32
|
|
||||||
permitted uint32
|
|
||||||
inheritable uint32
|
|
||||||
}
|
|
||||||
if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&struct {
|
|
||||||
version uint32
|
|
||||||
pid int32
|
|
||||||
}{_LINUX_CAPABILITY_VERSION_3, 0})), uintptr(unsafe.Pointer(&capData)), 0); errno != 0 {
|
|
||||||
return os.NewSyscallError("capget", errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
if max(capData.effective, capData.permitted, capData.inheritable) != 0 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"effective = %d, permitted = %d, inheritable = %d",
|
|
||||||
capData.effective, capData.permitted, capData.inheritable,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
wantHost := hostnameFromTestCase(tc.name)
|
wantHost := hostnameFromTestCase(tc.name)
|
||||||
if host, err := os.Hostname(); err != nil {
|
if host, err := os.Hostname(); err != nil {
|
||||||
return fmt.Errorf("cannot get hostname: %v", err)
|
return fmt.Errorf("cannot get hostname: %v", err)
|
||||||
@@ -889,7 +767,7 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
c.MustParse(os.Args[1:], func(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ type syscallDispatcher interface {
|
|||||||
remount(msg message.Msg, target string, flags uintptr) error
|
remount(msg message.Msg, target string, flags uintptr) error
|
||||||
// mountTmpfs provides mountTmpfs.
|
// mountTmpfs provides mountTmpfs.
|
||||||
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
|
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
|
||||||
// mountOverlay provides mountOverlay.
|
|
||||||
mountOverlay(target string, options [][2]string) error
|
|
||||||
// ensureFile provides ensureFile.
|
// ensureFile provides ensureFile.
|
||||||
ensureFile(name string, perm, pperm os.FileMode) error
|
ensureFile(name string, perm, pperm os.FileMode) error
|
||||||
// mustLoopback provides mustLoopback.
|
// mustLoopback provides mustLoopback.
|
||||||
@@ -171,9 +169,6 @@ func (direct) remount(msg message.Msg, target string, flags uintptr) error {
|
|||||||
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
|
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
|
||||||
return mountTmpfs(k, fsname, target, flags, size, perm)
|
return mountTmpfs(k, fsname, target, flags, size, perm)
|
||||||
}
|
}
|
||||||
func (k direct) mountOverlay(target string, options [][2]string) error {
|
|
||||||
return mountOverlay(target, options)
|
|
||||||
}
|
|
||||||
func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
|
func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
|
||||||
return ensureFile(name, perm, pperm)
|
return ensureFile(name, perm, pperm)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,6 +235,8 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sliceAddr[S any](s []S) *[]S { return &s }
|
||||||
|
|
||||||
func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
|
func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
|
||||||
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
|
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
|
||||||
// check happens in Close, and cleanup is not guaranteed to run, so relying
|
// check happens in Close, and cleanup is not guaranteed to run, so relying
|
||||||
@@ -466,14 +468,6 @@ func (k *kstub) mountTmpfs(fsname, target string, flags uintptr, size int, perm
|
|||||||
stub.CheckArg(k.Stub, "perm", perm, 4))
|
stub.CheckArg(k.Stub, "perm", perm, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) mountOverlay(target string, options [][2]string) error {
|
|
||||||
k.Helper()
|
|
||||||
return k.Expects("mountOverlay").Error(
|
|
||||||
stub.CheckArg(k.Stub, "target", target, 0),
|
|
||||||
stub.CheckArgReflect(k.Stub, "options", options, 1),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
|
func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
return k.Expects("ensureFile").Error(
|
return k.Expects("ensureFile").Error(
|
||||||
|
|||||||
+8
-10
@@ -46,8 +46,9 @@ func messageFromError(err error) (m string, ok bool) {
|
|||||||
// While this is usable for pointer errors, such use should be avoided as nil
|
// While this is usable for pointer errors, such use should be avoided as nil
|
||||||
// check is omitted.
|
// check is omitted.
|
||||||
func messagePrefix[T error](prefix string, err error) (string, bool) {
|
func messagePrefix[T error](prefix string, err error) (string, bool) {
|
||||||
if e, ok := errors.AsType[T](err); ok {
|
var targetError T
|
||||||
return prefix + e.Error(), true
|
if errors.As(err, &targetError) {
|
||||||
|
return prefix + targetError.Error(), true
|
||||||
}
|
}
|
||||||
return zeroString, false
|
return zeroString, false
|
||||||
}
|
}
|
||||||
@@ -57,8 +58,9 @@ func messagePrefixP[V any, T interface {
|
|||||||
*V
|
*V
|
||||||
error
|
error
|
||||||
}](prefix string, err error) (string, bool) {
|
}](prefix string, err error) (string, bool) {
|
||||||
if e, ok := errors.AsType[T](err); ok && e != nil {
|
var targetError T
|
||||||
return prefix + e.Error(), true
|
if errors.As(err, &targetError) && targetError != nil {
|
||||||
|
return prefix + targetError.Error(), true
|
||||||
}
|
}
|
||||||
return zeroString, false
|
return zeroString, false
|
||||||
}
|
}
|
||||||
@@ -107,8 +109,8 @@ func optionalErrorUnwrap(err error) error {
|
|||||||
|
|
||||||
// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
|
// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
|
||||||
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
||||||
errno, ok := errors.AsType[syscall.Errno](err)
|
var errno syscall.Errno
|
||||||
if !ok {
|
if !errors.As(err, &errno) {
|
||||||
return 0, &os.PathError{Op: op, Path: path, Err: err}
|
return 0, &os.PathError{Op: op, Path: path, Err: err}
|
||||||
}
|
}
|
||||||
return errno, nil
|
return errno, nil
|
||||||
@@ -116,10 +118,6 @@ func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
|||||||
|
|
||||||
// mount wraps syscall.Mount for error handling.
|
// mount wraps syscall.Mount for error handling.
|
||||||
func mount(source, target, fstype string, flags uintptr, data string) error {
|
func mount(source, target, fstype string, flags uintptr, data string) error {
|
||||||
if max(len(source), len(target), len(data))+1 > os.Getpagesize() {
|
|
||||||
return &MountError{source, target, fstype, flags, data, syscall.ENOMEM}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := syscall.Mount(source, target, fstype, flags, data)
|
err := syscall.Mount(source, target, fstype, flags, data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+23
-106
@@ -11,13 +11,11 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/ext"
|
"hakurei.app/ext"
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
@@ -184,33 +182,23 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
uid, gid := param.Uid, param.Gid
|
|
||||||
if param.InitAsRoot {
|
|
||||||
uid, gid = 0, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// write uid/gid map here so parent does not need to set dumpable
|
// write uid/gid map here so parent does not need to set dumpable
|
||||||
if err := k.setDumpable(ext.SUID_DUMP_USER); err != nil {
|
if err := k.setDumpable(ext.SUID_DUMP_USER); err != nil {
|
||||||
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
|
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
|
||||||
}
|
}
|
||||||
if err := k.writeFile(
|
if err := k.writeFile(fhs.Proc+"self/uid_map",
|
||||||
fhs.Proc+"self/uid_map",
|
append([]byte{}, strconv.Itoa(param.Uid)+" "+strconv.Itoa(param.HostUid)+" 1\n"...),
|
||||||
[]byte(strconv.Itoa(uid)+" "+strconv.Itoa(param.HostUid)+" 1\n"),
|
0); err != nil {
|
||||||
0,
|
|
||||||
); err != nil {
|
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
if err := k.writeFile(
|
if err := k.writeFile(fhs.Proc+"self/setgroups",
|
||||||
fhs.Proc+"self/setgroups",
|
|
||||||
[]byte("deny\n"),
|
[]byte("deny\n"),
|
||||||
0,
|
0); err != nil && !os.IsNotExist(err) {
|
||||||
); err != nil && !os.IsNotExist(err) {
|
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
if err := k.writeFile(fhs.Proc+"self/gid_map",
|
if err := k.writeFile(fhs.Proc+"self/gid_map",
|
||||||
[]byte(strconv.Itoa(gid)+" "+strconv.Itoa(param.HostGid)+" 1\n"),
|
append([]byte{}, strconv.Itoa(param.Gid)+" "+strconv.Itoa(param.HostGid)+" 1\n"...),
|
||||||
0,
|
0); err != nil {
|
||||||
); err != nil {
|
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
if err := k.setDumpable(ext.SUID_DUMP_DISABLE); err != nil {
|
if err := k.setDumpable(ext.SUID_DUMP_DISABLE); err != nil {
|
||||||
@@ -235,23 +223,6 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
state := &setupState{process: make(map[int]WaitStatus), Params: ¶m.Params, Msg: msg, Context: ctx}
|
state := &setupState{process: make(map[int]WaitStatus), Params: ¶m.Params, Msg: msg, Context: ctx}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
|
|
||||||
k.fatalf(msg, "cannot mount intermediate root: %v", optionalErrorUnwrap(err))
|
|
||||||
}
|
|
||||||
if err := k.chdir(intermediateHostPath); err != nil {
|
|
||||||
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(param.Binfmt) > 0 {
|
|
||||||
for i, e := range param.Binfmt {
|
|
||||||
if pathname, err := k.evalSymlinks(e.Interpreter.String()); err != nil {
|
|
||||||
k.fatal(msg, err)
|
|
||||||
} else if param.Binfmt[i].Interpreter, err = check.NewAbs(pathname); err != nil {
|
|
||||||
k.fatal(msg, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* early is called right before pivot_root into intermediate root;
|
/* early is called right before pivot_root into intermediate root;
|
||||||
this step is mostly for gathering information that would otherwise be
|
this step is mostly for gathering information that would otherwise be
|
||||||
difficult to obtain via library functions after pivot_root, and
|
difficult to obtain via library functions after pivot_root, and
|
||||||
@@ -271,6 +242,13 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
|
||||||
|
k.fatalf(msg, "cannot mount intermediate root: %v", optionalErrorUnwrap(err))
|
||||||
|
}
|
||||||
|
if err := k.chdir(intermediateHostPath); err != nil {
|
||||||
|
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := k.mkdir(sysrootDir, 0755); err != nil {
|
if err := k.mkdir(sysrootDir, 0755); err != nil {
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
@@ -307,48 +285,6 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(param.Binfmt) > 0 {
|
|
||||||
const interpreter = "/interpreter"
|
|
||||||
|
|
||||||
if param.BinfmtPath == nil {
|
|
||||||
param.BinfmtPath = fhs.AbsProcSys.Append("fs/binfmt_misc")
|
|
||||||
}
|
|
||||||
binfmt := sysrootPath + param.BinfmtPath.String()
|
|
||||||
if err := k.mkdirAll(binfmt, 0); err != nil {
|
|
||||||
k.fatal(msg, err)
|
|
||||||
}
|
|
||||||
if err := k.mount(
|
|
||||||
SourceBinfmtMisc,
|
|
||||||
binfmt,
|
|
||||||
FstypeBinfmtMisc,
|
|
||||||
MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
|
||||||
zeroString,
|
|
||||||
); err != nil {
|
|
||||||
k.fatal(msg, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf strings.Builder
|
|
||||||
buf.Grow(1920)
|
|
||||||
|
|
||||||
register := binfmt + "/register"
|
|
||||||
for i, e := range param.Binfmt {
|
|
||||||
if err := k.symlink(hostPath+e.Interpreter.String(), interpreter); err != nil {
|
|
||||||
k.fatal(msg, err)
|
|
||||||
} else if err = k.writeFile(register, []byte(":"+
|
|
||||||
strconv.Itoa(i)+":"+
|
|
||||||
"M:"+
|
|
||||||
strconv.Itoa(int(e.Offset))+":"+
|
|
||||||
escapeBinfmt(&buf, e.Magic)+":"+
|
|
||||||
escapeBinfmt(&buf, e.Mask)+":"+
|
|
||||||
interpreter+":"+
|
|
||||||
"F"), 0); err != nil {
|
|
||||||
k.fatal(msg, err)
|
|
||||||
} else if err = k.remove(interpreter); err != nil {
|
|
||||||
k.fatal(msg, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup requiring host root complete at this point
|
// setup requiring host root complete at this point
|
||||||
if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil {
|
if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil {
|
||||||
k.fatalf(msg, "cannot make host root rprivate: %v", optionalErrorUnwrap(err))
|
k.fatalf(msg, "cannot make host root rprivate: %v", optionalErrorUnwrap(err))
|
||||||
@@ -387,19 +323,11 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var keepCaps []uintptr
|
|
||||||
if param.Privileged {
|
|
||||||
keepCaps = append(keepCaps, CAP_SYS_ADMIN, CAP_SETPCAP)
|
|
||||||
}
|
|
||||||
if param.InitAsRoot {
|
|
||||||
keepCaps = append(keepCaps, CAP_SETFCAP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := k.capAmbientClearAll(); err != nil {
|
if err := k.capAmbientClearAll(); err != nil {
|
||||||
k.fatalf(msg, "cannot clear the ambient capability set: %v", err)
|
k.fatalf(msg, "cannot clear the ambient capability set: %v", err)
|
||||||
}
|
}
|
||||||
for i := range lastcap + 1 {
|
for i := uintptr(0); i <= lastcap; i++ {
|
||||||
if slices.Contains(keepCaps, i) {
|
if param.Privileged && i == CAP_SYS_ADMIN {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := k.capBoundingSetDrop(i); err != nil {
|
if err := k.capBoundingSetDrop(i); err != nil {
|
||||||
@@ -408,23 +336,20 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var keep [2]uint32
|
var keep [2]uint32
|
||||||
for _, c := range keepCaps {
|
if param.Privileged {
|
||||||
keep[capToIndex(c)] |= capToMask(c)
|
keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN)
|
||||||
}
|
|
||||||
|
|
||||||
|
if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil {
|
||||||
|
k.fatalf(msg, "cannot raise CAP_SYS_ADMIN: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := k.capset(
|
if err := k.capset(
|
||||||
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
|
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
|
||||||
&[2]capData{{keep[0], keep[0], keep[0]}, {keep[1], keep[1], keep[1]}},
|
&[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
k.fatalf(msg, "cannot capset: %v", err)
|
k.fatalf(msg, "cannot capset: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range keepCaps {
|
|
||||||
if err := k.capAmbientRaise(c); err != nil {
|
|
||||||
k.fatalf(msg, "cannot raise %#x: %v", c, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !param.SeccompDisable {
|
if !param.SeccompDisable {
|
||||||
rules := param.SeccompRules
|
rules := param.SeccompRules
|
||||||
if len(rules) == 0 { // non-empty rules slice always overrides presets
|
if len(rules) == 0 { // non-empty rules slice always overrides presets
|
||||||
@@ -549,14 +474,6 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
cmd.ExtraFiles = extraFiles
|
cmd.ExtraFiles = extraFiles
|
||||||
cmd.Dir = param.Dir.String()
|
cmd.Dir = param.Dir.String()
|
||||||
|
|
||||||
if param.InitAsRoot {
|
|
||||||
cmd.SysProcAttr = &SysProcAttr{
|
|
||||||
Cloneflags: CLONE_NEWUSER,
|
|
||||||
UidMappings: []SysProcIDMap{{ContainerID: param.Uid, HostID: 0, Size: 1}},
|
|
||||||
GidMappings: []SysProcIDMap{{ContainerID: param.Gid, HostID: 0, Size: 1}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Verbosef("starting initial process %s", param.Path)
|
msg.Verbosef("starting initial process %s", param.Path)
|
||||||
if err := k.start(cmd); err != nil {
|
if err := k.start(cmd); err != nil {
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
|
|||||||
+81
-81
@@ -95,7 +95,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: new(make(Ops, 1)),
|
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -123,7 +123,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: new(make(Ops, 1)),
|
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -152,7 +152,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: new(make(Ops, 1)),
|
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -182,7 +182,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: new(make(Ops, 1)),
|
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -213,7 +213,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: new(make(Ops, 1)),
|
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -245,7 +245,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: new(make(Ops, 1)),
|
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -279,7 +279,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: new(make(Ops, 1)),
|
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -315,7 +315,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
Uid: 1 << 16,
|
Uid: 1 << 16,
|
||||||
Gid: 1 << 15,
|
Gid: 1 << 15,
|
||||||
Hostname: "hakurei-check",
|
Hostname: "hakurei-check",
|
||||||
Ops: new(make(Ops, 1)),
|
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||||
SeccompRules: make([]std.NativeRule, 0),
|
SeccompRules: make([]std.NativeRule, 0),
|
||||||
SeccompPresets: std.PresetStrict,
|
SeccompPresets: std.PresetStrict,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
@@ -332,8 +332,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
@@ -372,8 +370,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
@@ -412,8 +408,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", stub.UniqueError(61)),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", stub.UniqueError(61)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot prepare op at index %d: %v", []any{0, stub.UniqueError(61)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot prepare op at index %d: %v", []any{0, stub.UniqueError(61)}}, nil, nil),
|
||||||
@@ -453,8 +447,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", &os.PathError{Op: "readlink", Path: "/", Err: stub.UniqueError(60)}),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", &os.PathError{Op: "readlink", Path: "/", Err: stub.UniqueError(60)}),
|
||||||
call("fatal", stub.ExpectArgs{[]any{"cannot readlink /: unique error 60 injected by the test suite"}}, nil, nil),
|
call("fatal", stub.ExpectArgs{[]any{"cannot readlink /: unique error 60 injected by the test suite"}}, nil, nil),
|
||||||
@@ -494,6 +486,9 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
/* begin early */
|
||||||
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, stub.UniqueError(58)),
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, stub.UniqueError(58)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot mount intermediate root: %v", []any{stub.UniqueError(58)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot mount intermediate root: %v", []any{stub.UniqueError(58)}}, nil, nil),
|
||||||
},
|
},
|
||||||
@@ -531,6 +526,9 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
/* begin early */
|
||||||
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, stub.UniqueError(56)),
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, stub.UniqueError(56)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot enter intermediate host path: %v", []any{stub.UniqueError(56)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot enter intermediate host path: %v", []any{stub.UniqueError(56)}}, nil, nil),
|
||||||
@@ -569,11 +567,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, stub.UniqueError(54)),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, stub.UniqueError(54)),
|
||||||
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(54)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(54)}}, nil, nil),
|
||||||
},
|
},
|
||||||
@@ -611,11 +609,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, stub.UniqueError(52)),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, stub.UniqueError(52)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot bind sysroot: %v", []any{stub.UniqueError(52)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot bind sysroot: %v", []any{stub.UniqueError(52)}}, nil, nil),
|
||||||
@@ -654,11 +652,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, stub.UniqueError(50)),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, stub.UniqueError(50)),
|
||||||
@@ -698,11 +696,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -743,11 +741,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -789,11 +787,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -844,11 +842,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -899,11 +897,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -955,11 +953,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1012,11 +1010,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1071,11 +1069,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1131,11 +1129,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1192,11 +1190,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1254,11 +1252,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1317,11 +1315,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1381,11 +1379,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1446,11 +1444,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1512,11 +1510,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1586,11 +1584,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1624,6 +1622,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
||||||
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
||||||
@@ -1655,9 +1654,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
||||||
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, nil),
|
|
||||||
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, stub.UniqueError(19)),
|
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, stub.UniqueError(19)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot raise %#x: %v", []any{uintptr(0x15), stub.UniqueError(19)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot raise CAP_SYS_ADMIN: %v", []any{stub.UniqueError(19)}}, nil, nil),
|
||||||
},
|
},
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
@@ -1693,11 +1691,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1731,6 +1729,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
||||||
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
||||||
@@ -1762,7 +1761,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
||||||
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, stub.UniqueError(17)),
|
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
|
||||||
|
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0, 0x200000, 0x200000}, {0, 0, 0}}}, nil, stub.UniqueError(17)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot capset: %v", []any{stub.UniqueError(17)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot capset: %v", []any{stub.UniqueError(17)}}, nil, nil),
|
||||||
},
|
},
|
||||||
}, nil},
|
}, nil},
|
||||||
@@ -1799,11 +1799,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1837,6 +1837,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
||||||
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
||||||
@@ -1868,9 +1869,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
||||||
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, nil),
|
|
||||||
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
|
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
|
||||||
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0, 0x200000, 0x200000}, {0, 0, 0}}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil),
|
||||||
call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, stub.UniqueError(15)),
|
call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, stub.UniqueError(15)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot load syscall filter: %v", []any{stub.UniqueError(15)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot load syscall filter: %v", []any{stub.UniqueError(15)}}, nil, nil),
|
||||||
@@ -1908,11 +1908,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2032,11 +2032,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2132,11 +2132,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2232,11 +2232,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2323,11 +2323,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2418,11 +2418,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2520,11 +2520,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2659,11 +2659,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2697,6 +2697,7 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x5)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x6)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x7)}, nil, nil),
|
||||||
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x9)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xa)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0xb)}, nil, nil),
|
||||||
@@ -2728,9 +2729,8 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x26)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x27)}, nil, nil),
|
||||||
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x28)}, nil, nil),
|
||||||
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0x200100, 0x200100, 0x200100}, {0, 0, 0}}}, nil, nil),
|
|
||||||
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
|
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x15)}, nil, nil),
|
||||||
call("capAmbientRaise", stub.ExpectArgs{uintptr(0x8)}, nil, nil),
|
call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0, 0x200000, 0x200000}, {0, 0, 0}}}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"resolving presets %#x", []any{std.FilterPreset(0xf)}}, nil, nil),
|
||||||
call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, nil),
|
call("seccompLoad", stub.ExpectArgs{seccomp.Preset(0xf, 0), seccomp.ExportFlag(0)}, nil, nil),
|
||||||
call("verbosef", stub.ExpectArgs{"%d filter rules loaded", []any{73}}, nil, nil),
|
call("verbosef", stub.ExpectArgs{"%d filter rules loaded", []any{73}}, nil, nil),
|
||||||
|
|||||||
+12
-40
@@ -4,9 +4,9 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/ext"
|
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
|
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.upper = toHost(v)
|
o.upper = check.EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
|
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.work = toHost(v)
|
o.work = check.EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,39 +168,12 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(a.String()); err != nil {
|
if v, err := k.evalSymlinks(a.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.lower[i] = toHost(v)
|
o.lower[i] = check.EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mountOverlay sets up an overlay mount via [ext.FS].
|
|
||||||
func mountOverlay(target string, options [][2]string) error {
|
|
||||||
fs, err := ext.OpenFS(SourceOverlay, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = fs.SetString("source", SourceOverlay); err != nil {
|
|
||||||
_ = fs.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, option := range options {
|
|
||||||
if err = fs.SetString(option[0], option[1]); err != nil {
|
|
||||||
_ = fs.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = fs.SetFlag(OptionOverlayUserxattr); err != nil {
|
|
||||||
_ = fs.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = fs.Mount(target, 0); err != nil {
|
|
||||||
_ = fs.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fs.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
target := o.Target.String()
|
target := o.Target.String()
|
||||||
if !o.noPrefix {
|
if !o.noPrefix {
|
||||||
@@ -221,7 +194,7 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options := make([][2]string, 0, 2+len(o.lower))
|
options := make([]string, 0, 4)
|
||||||
|
|
||||||
if o.upper == zeroString && o.work == zeroString { // readonly
|
if o.upper == zeroString && o.work == zeroString { // readonly
|
||||||
if len(o.Lower) < 2 {
|
if len(o.Lower) < 2 {
|
||||||
@@ -232,16 +205,15 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
if len(o.Lower) == 0 {
|
if len(o.Lower) == 0 {
|
||||||
return &OverlayArgumentError{OverlayEmptyLower, zeroString}
|
return &OverlayArgumentError{OverlayEmptyLower, zeroString}
|
||||||
}
|
}
|
||||||
options = append(options, [][2]string{
|
options = append(options,
|
||||||
{OptionOverlayUpperdir, o.upper},
|
OptionOverlayUpperdir+"="+o.upper,
|
||||||
{OptionOverlayWorkdir, o.work},
|
OptionOverlayWorkdir+"="+o.work)
|
||||||
}...)
|
|
||||||
}
|
|
||||||
for _, lower := range o.lower {
|
|
||||||
options = append(options, [2]string{OptionOverlayLowerdir + "+", lower})
|
|
||||||
}
|
}
|
||||||
|
options = append(options,
|
||||||
|
OptionOverlayLowerdir+"="+strings.Join(o.lower, check.SpecialOverlayPath),
|
||||||
|
OptionOverlayUserxattr)
|
||||||
|
|
||||||
return k.mountOverlay(target, options)
|
return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, check.SpecialOverlayOption))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *MountOverlayOp) late(*setupState, syscallDispatcher) error { return nil }
|
func (o *MountOverlayOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|||||||
@@ -97,12 +97,13 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil),
|
||||||
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil),
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil),
|
||||||
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil),
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/sysroot", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot", "overlay", uintptr(0), "" +
|
||||||
{"upperdir", "overlay.upper.32768"},
|
"upperdir=overlay.upper.32768," +
|
||||||
{"workdir", "overlay.work.32768"},
|
"workdir=overlay.work.32768," +
|
||||||
{"lowerdir+", `/host/var/lib/planterette/base/debian:f92c9052`},
|
"lowerdir=" +
|
||||||
{"lowerdir+", `/host/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052`},
|
`/host/var/lib/planterette/base/debian\:f92c9052:` +
|
||||||
}}, nil, nil),
|
`/host/var/lib/planterette/app/org.chromium.Chromium@debian\:f92c9052,` +
|
||||||
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
@@ -128,10 +129,11 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/nix/store", os.FileMode(0755)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/nix/store", os.FileMode(0755)}, nil, nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/nix/store", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/nix/store", "overlay", uintptr(0), "" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/.ro-store"},
|
"lowerdir=" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/.ro-store0"},
|
"/host/mnt-root/nix/.ro-store:" +
|
||||||
}}, nil, nil),
|
"/host/mnt-root/nix/.ro-store0," +
|
||||||
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
@@ -145,10 +147,11 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/.ro-store"},
|
"lowerdir=" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/.ro-store0"},
|
"/host/mnt-root/nix/.ro-store:" +
|
||||||
}}, nil, nil),
|
"/host/mnt-root/nix/.ro-store0," +
|
||||||
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
@@ -216,11 +219,7 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "upperdir=/host/mnt-root/nix/.rw-store/.upper,workdir=/host/mnt-root/nix/.rw-store/.work,lowerdir=/host/mnt-root/nix/ro-store,userxattr"}, nil, stub.UniqueError(0)),
|
||||||
{"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
|
|
||||||
{"workdir", "/host/mnt-root/nix/.rw-store/.work"},
|
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store"},
|
|
||||||
}}, nil, stub.UniqueError(0)),
|
|
||||||
}, stub.UniqueError(0)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
@@ -234,11 +233,11 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
||||||
{"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
|
"upperdir=/host/mnt-root/nix/.rw-store/.upper," +
|
||||||
{"workdir", "/host/mnt-root/nix/.rw-store/.work"},
|
"workdir=/host/mnt-root/nix/.rw-store/.work," +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store"},
|
"lowerdir=/host/mnt-root/nix/ro-store," +
|
||||||
}}, nil, nil),
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
@@ -262,15 +261,16 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
||||||
{"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
|
"upperdir=/host/mnt-root/nix/.rw-store/.upper," +
|
||||||
{"workdir", "/host/mnt-root/nix/.rw-store/.work"},
|
"workdir=/host/mnt-root/nix/.rw-store/.work," +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store"},
|
"lowerdir=" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store0"},
|
"/host/mnt-root/nix/ro-store:" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store1"},
|
"/host/mnt-root/nix/ro-store0:" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store2"},
|
"/host/mnt-root/nix/ro-store1:" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store3"},
|
"/host/mnt-root/nix/ro-store2:" +
|
||||||
}}, nil, nil),
|
"/host/mnt-root/nix/ro-store3," +
|
||||||
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,6 @@ const (
|
|||||||
// SourceMqueue is used when mounting mqueue.
|
// SourceMqueue is used when mounting mqueue.
|
||||||
// Note that any source value is allowed when fstype is [FstypeMqueue].
|
// Note that any source value is allowed when fstype is [FstypeMqueue].
|
||||||
SourceMqueue = "mqueue"
|
SourceMqueue = "mqueue"
|
||||||
// SourceBinfmtMisc is used when mounting binfmt_misc.
|
|
||||||
// Note that any source value is allowed when fstype is [SourceBinfmtMisc].
|
|
||||||
SourceBinfmtMisc = "binfmt_misc"
|
|
||||||
// SourceOverlay is used when mounting overlay.
|
// SourceOverlay is used when mounting overlay.
|
||||||
// Note that any source value is allowed when fstype is [FstypeOverlay].
|
// Note that any source value is allowed when fstype is [FstypeOverlay].
|
||||||
SourceOverlay = "overlay"
|
SourceOverlay = "overlay"
|
||||||
@@ -73,9 +70,6 @@ const (
|
|||||||
// FstypeMqueue represents the mqueue pseudo-filesystem.
|
// FstypeMqueue represents the mqueue pseudo-filesystem.
|
||||||
// This filesystem type is usually mounted on /dev/mqueue.
|
// This filesystem type is usually mounted on /dev/mqueue.
|
||||||
FstypeMqueue = "mqueue"
|
FstypeMqueue = "mqueue"
|
||||||
// FstypeBinfmtMisc represents the binfmt_misc pseudo-filesystem.
|
|
||||||
// This filesystem type is usually mounted on /proc/sys/fs/binfmt_misc.
|
|
||||||
FstypeBinfmtMisc = "binfmt_misc"
|
|
||||||
// FstypeOverlay represents the overlay pseudo-filesystem.
|
// FstypeOverlay represents the overlay pseudo-filesystem.
|
||||||
// This filesystem type can be mounted anywhere in the container filesystem.
|
// This filesystem type can be mounted anywhere in the container filesystem.
|
||||||
FstypeOverlay = "overlay"
|
FstypeOverlay = "overlay"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
"hakurei.app/vfs"
|
"hakurei.app/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,6 +50,9 @@ func TestToHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InternalToHostOvlEscape exports toHost passed to [check.EscapeOverlayDataSegment].
|
||||||
|
func InternalToHostOvlEscape(s string) string { return check.EscapeOverlayDataSegment(toHost(s)) }
|
||||||
|
|
||||||
func TestCreateFile(t *testing.T) {
|
func TestCreateFile(t *testing.T) {
|
||||||
t.Run("nonexistent", func(t *testing.T) {
|
t.Run("nonexistent", func(t *testing.T) {
|
||||||
t.Run("mkdir", func(t *testing.T) {
|
t.Run("mkdir", func(t *testing.T) {
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ func TestSyscall(t *testing.T) {
|
|||||||
t.Errorf("Unmarshal: %v, want %v", got, tc.want)
|
t.Errorf("Unmarshal: %v, want %v", got, tc.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if _, ok := errors.AsType[ext.SyscallNameError](tc.err); ok {
|
if errors.As(tc.err, new(ext.SyscallNameError)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,267 +0,0 @@
|
|||||||
package ext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// include/uapi/linux/mount.h
|
|
||||||
|
|
||||||
/*
|
|
||||||
* move_mount() flags.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
MOVE_MOUNT_F_SYMLINKS = 1 << iota /* Follow symlinks on from path */
|
|
||||||
MOVE_MOUNT_F_AUTOMOUNTS /* Follow automounts on from path */
|
|
||||||
MOVE_MOUNT_F_EMPTY_PATH /* Empty from path permitted */
|
|
||||||
_
|
|
||||||
MOVE_MOUNT_T_SYMLINKS /* Follow symlinks on to path */
|
|
||||||
MOVE_MOUNT_T_AUTOMOUNTS /* Follow automounts on to path */
|
|
||||||
MOVE_MOUNT_T_EMPTY_PATH /* Empty to path permitted */
|
|
||||||
_
|
|
||||||
MOVE_MOUNT_SET_GROUP /* Set sharing group instead */
|
|
||||||
MOVE_MOUNT_BENEATH /* Mount beneath top mount */
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fsopen() flags.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
FSOPEN_CLOEXEC = 1 << iota
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fspick() flags.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
FSPICK_CLOEXEC = 1 << iota
|
|
||||||
FSPICK_SYMLINK_NOFOLLOW
|
|
||||||
FSPICK_NO_AUTOMOUNT
|
|
||||||
FSPICK_EMPTY_PATH
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The type of fsconfig() call made.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
FSCONFIG_SET_FLAG = iota /* Set parameter, supplying no value */
|
|
||||||
FSCONFIG_SET_STRING /* Set parameter, supplying a string value */
|
|
||||||
FSCONFIG_SET_BINARY /* Set parameter, supplying a binary blob value */
|
|
||||||
FSCONFIG_SET_PATH /* Set parameter, supplying an object by path */
|
|
||||||
FSCONFIG_SET_PATH_EMPTY /* Set parameter, supplying an object by (empty) path */
|
|
||||||
FSCONFIG_SET_FD /* Set parameter, supplying an object by fd */
|
|
||||||
FSCONFIG_CMD_CREATE /* Create new or reuse existing superblock */
|
|
||||||
FSCONFIG_CMD_RECONFIGURE /* Invoke superblock reconfiguration */
|
|
||||||
FSCONFIG_CMD_CREATE_EXCL /* Create new superblock, fail if reusing existing superblock */
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fsmount() flags.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
FSMOUNT_CLOEXEC = 1 << iota
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Mount attributes.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
MOUNT_ATTR_RDONLY = 0x00000001 /* Mount read-only */
|
|
||||||
MOUNT_ATTR_NOSUID = 0x00000002 /* Ignore suid and sgid bits */
|
|
||||||
MOUNT_ATTR_NODEV = 0x00000004 /* Disallow access to device special files */
|
|
||||||
MOUNT_ATTR_NOEXEC = 0x00000008 /* Disallow program execution */
|
|
||||||
MOUNT_ATTR__ATIME = 0x00000070 /* Setting on how atime should be updated */
|
|
||||||
MOUNT_ATTR_RELATIME = 0x00000000 /* - Update atime relative to mtime/ctime. */
|
|
||||||
MOUNT_ATTR_NOATIME = 0x00000010 /* - Do not update access times. */
|
|
||||||
MOUNT_ATTR_STRICTATIME = 0x00000020 /* - Always perform atime updates */
|
|
||||||
MOUNT_ATTR_NODIRATIME = 0x00000080 /* Do not update directory access times */
|
|
||||||
MOUNT_ATTR_IDMAP = 0x00100000 /* Idmap mount to @userns_fd in struct mount_attr. */
|
|
||||||
MOUNT_ATTR_NOSYMFOLLOW = 0x00200000 /* Do not follow symlinks */
|
|
||||||
)
|
|
||||||
|
|
||||||
// FS provides low-level wrappers around the suite of file-descriptor-based
|
|
||||||
// mount facilities in Linux.
|
|
||||||
type FS struct {
|
|
||||||
fd uintptr
|
|
||||||
c runtime.Cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFS allocates a new [FS] for the specified fd.
|
|
||||||
func newFS(fd uintptr) *FS {
|
|
||||||
fs := FS{fd: fd}
|
|
||||||
fs.c = runtime.AddCleanup(&fs, func(fd uintptr) {
|
|
||||||
_ = syscall.Close(int(fd))
|
|
||||||
}, fd)
|
|
||||||
return &fs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the underlying filesystem context.
|
|
||||||
func (fs *FS) Close() error {
|
|
||||||
if fs == nil {
|
|
||||||
return syscall.EINVAL
|
|
||||||
}
|
|
||||||
err := syscall.Close(int(fs.fd))
|
|
||||||
fs.c.Stop()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenFS creates a new filesystem context.
|
|
||||||
func OpenFS(fsname string, flags int) (fs *FS, err error) {
|
|
||||||
var s *byte
|
|
||||||
s, err = syscall.BytePtrFromString(fsname)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fd, _, errno := syscall.Syscall(
|
|
||||||
SYS_FSOPEN,
|
|
||||||
uintptr(unsafe.Pointer(s)),
|
|
||||||
uintptr(flags|FSOPEN_CLOEXEC),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
err = os.NewSyscallError("fsopen", errno)
|
|
||||||
} else {
|
|
||||||
fs = newFS(fd)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// PickFS selects filesystem for reconfiguration.
|
|
||||||
func PickFS(dirfd int, pathname string, flags int) (fs *FS, err error) {
|
|
||||||
var s *byte
|
|
||||||
s, err = syscall.BytePtrFromString(pathname)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fd, _, errno := syscall.Syscall(
|
|
||||||
SYS_FSPICK,
|
|
||||||
uintptr(dirfd),
|
|
||||||
uintptr(unsafe.Pointer(s)),
|
|
||||||
uintptr(flags|FSPICK_CLOEXEC),
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
err = os.NewSyscallError("fspick", errno)
|
|
||||||
} else {
|
|
||||||
fs = newFS(fd)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// config configures new or existing filesystem context.
|
|
||||||
func (fs *FS) config(cmd uint, key *byte, value unsafe.Pointer, aux int) (err error) {
|
|
||||||
_, _, errno := syscall.Syscall6(
|
|
||||||
SYS_FSCONFIG,
|
|
||||||
fs.fd,
|
|
||||||
uintptr(cmd),
|
|
||||||
uintptr(unsafe.Pointer(key)),
|
|
||||||
uintptr(value),
|
|
||||||
uintptr(aux),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
err = os.NewSyscallError("fsconfig", errno)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFlag sets the flag parameter named by key. ([FSCONFIG_SET_FLAG])
|
|
||||||
func (fs *FS) SetFlag(key string) (err error) {
|
|
||||||
var s *byte
|
|
||||||
s, err = syscall.BytePtrFromString(key)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.config(FSCONFIG_SET_FLAG, s, nil, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetString sets the string parameter named by key to the value specified by
|
|
||||||
// value. ([FSCONFIG_SET_STRING])
|
|
||||||
func (fs *FS) SetString(key, value string) (err error) {
|
|
||||||
var s0 *byte
|
|
||||||
s0, err = syscall.BytePtrFromString(key)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var s1 *byte
|
|
||||||
s1, err = syscall.BytePtrFromString(value)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.config(FSCONFIG_SET_STRING, s0, unsafe.Pointer(s1), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mount instantiates mount object from filesystem context.
|
|
||||||
func (fs *FS) mount(flags, attrFlags int) (fsfd int, err error) {
|
|
||||||
r, _, errno := syscall.Syscall(
|
|
||||||
SYS_FSMOUNT,
|
|
||||||
fs.fd,
|
|
||||||
uintptr(flags|FSMOUNT_CLOEXEC),
|
|
||||||
uintptr(attrFlags),
|
|
||||||
)
|
|
||||||
fsfd = int(r)
|
|
||||||
if errno != 0 {
|
|
||||||
err = os.NewSyscallError("fsmount", errno)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveMount moves or attaches mount object to filesystem.
|
|
||||||
func MoveMount(
|
|
||||||
fromDirfd int,
|
|
||||||
fromPathname string,
|
|
||||||
toDirfd int,
|
|
||||||
toPathname string,
|
|
||||||
flags int,
|
|
||||||
) (err error) {
|
|
||||||
var s0 *byte
|
|
||||||
s0, err = syscall.BytePtrFromString(fromPathname)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var s1 *byte
|
|
||||||
s1, err = syscall.BytePtrFromString(toPathname)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall6(
|
|
||||||
SYS_MOVE_MOUNT,
|
|
||||||
uintptr(fromDirfd),
|
|
||||||
uintptr(unsafe.Pointer(s0)),
|
|
||||||
uintptr(toDirfd),
|
|
||||||
uintptr(unsafe.Pointer(s1)),
|
|
||||||
uintptr(flags),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
err = os.NewSyscallError("move_mount", errno)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mount attaches the underlying filesystem context to the specified pathname.
|
|
||||||
func (fs *FS) Mount(pathname string, attrFlags int) error {
|
|
||||||
if err := fs.config(FSCONFIG_CMD_CREATE_EXCL, nil, nil, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fd, err := fs.mount(0, attrFlags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = MoveMount(
|
|
||||||
fd, "",
|
|
||||||
-1, pathname,
|
|
||||||
MOVE_MOUNT_F_EMPTY_PATH,
|
|
||||||
)
|
|
||||||
closeErr := syscall.Close(fd)
|
|
||||||
if err == nil {
|
|
||||||
err = closeErr
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -42,8 +42,6 @@ var (
|
|||||||
AbsDevShm = unsafeAbs(DevShm)
|
AbsDevShm = unsafeAbs(DevShm)
|
||||||
// AbsProc is [Proc] as [check.Absolute].
|
// AbsProc is [Proc] as [check.Absolute].
|
||||||
AbsProc = unsafeAbs(Proc)
|
AbsProc = unsafeAbs(Proc)
|
||||||
// AbsProcSys is [ProcSys] as [check.Absolute].
|
|
||||||
AbsProcSys = unsafeAbs(ProcSys)
|
|
||||||
// AbsProcSelfExe is [ProcSelfExe] as [check.Absolute].
|
// AbsProcSelfExe is [ProcSelfExe] as [check.Absolute].
|
||||||
AbsProcSelfExe = unsafeAbs(ProcSelfExe)
|
AbsProcSelfExe = unsafeAbs(ProcSelfExe)
|
||||||
// AbsSys is [Sys] as [check.Absolute].
|
// AbsSys is [Sys] as [check.Absolute].
|
||||||
|
|||||||
Generated
+8
-8
@@ -7,32 +7,32 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780361225,
|
"lastModified": 1772985280,
|
||||||
"narHash": "sha256-wnV9ttf4fPWNonBIQmvlrSlNpQYgx5HgWWd007mwIFA=",
|
"narHash": "sha256-FdrNykOoY9VStevU4zjSUdvsL9SzJTcXt4omdEDZDLk=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "e28654b71096e08c019d4861ca26acb646f583d8",
|
"rev": "8f736f007139d7f70752657dff6a401a585d6cbc",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"ref": "release-26.05",
|
"ref": "release-25.11",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780453794,
|
"lastModified": 1772822230,
|
||||||
"narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=",
|
"narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338",
|
"rev": "71caefce12ba78d84fe618cf61644dce01cf3a96",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-26.05",
|
"ref": "nixos-25.11",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
+16
-15
@@ -2,10 +2,10 @@
|
|||||||
description = "hakurei container tool and nixos module";
|
description = "hakurei container tool and nixos module";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||||
|
|
||||||
home-manager = {
|
home-manager = {
|
||||||
url = "github:nix-community/home-manager/release-26.05";
|
url = "github:nix-community/home-manager/release-25.11";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -37,27 +37,27 @@
|
|||||||
inherit (pkgs)
|
inherit (pkgs)
|
||||||
runCommandLocal
|
runCommandLocal
|
||||||
callPackage
|
callPackage
|
||||||
nixfmt
|
nixfmt-rfc-style
|
||||||
deadnix
|
deadnix
|
||||||
statix
|
statix
|
||||||
;
|
;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
hakurei = callPackage ./. { inherit system self; };
|
hakurei = callPackage ./test { inherit system self; };
|
||||||
race = callPackage ./. {
|
race = callPackage ./test {
|
||||||
inherit system self;
|
inherit system self;
|
||||||
withRace = true;
|
withRace = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
sandbox = callPackage ./sandbox { inherit self; };
|
sandbox = callPackage ./test/sandbox { inherit self; };
|
||||||
sandbox-race = callPackage ./sandbox {
|
sandbox-race = callPackage ./test/sandbox {
|
||||||
inherit self;
|
inherit self;
|
||||||
withRace = true;
|
withRace = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
sharefs = callPackage ./sharefs { inherit system self; };
|
sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
|
||||||
|
|
||||||
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt ]; } ''
|
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
|
||||||
cd ${./.}
|
cd ${./.}
|
||||||
|
|
||||||
echo "running nixfmt..."
|
echo "running nixfmt..."
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
nettools
|
nettools
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
hsu = pkgs.callPackage ./hsu.nix { inherit (self.packages.${system}) hakurei; };
|
hsu = pkgs.callPackage ./cmd/hsu/package.nix { inherit (self.packages.${system}) hakurei; };
|
||||||
sharefs = pkgs.linkFarm "sharefs" {
|
sharefs = pkgs.linkFarm "sharefs" {
|
||||||
"bin/sharefs" = "${hakurei}/libexec/sharefs";
|
"bin/sharefs" = "${hakurei}/libexec/sharefs";
|
||||||
"bin/mount.fuse.sharefs" = "${hakurei}/libexec/sharefs";
|
"bin/mount.fuse.sharefs" = "${hakurei}/libexec/sharefs";
|
||||||
@@ -139,6 +139,7 @@
|
|||||||
GOCACHE="$(mktemp -d)" \
|
GOCACHE="$(mktemp -d)" \
|
||||||
PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
|
PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
|
||||||
DESTDIR="$out" \
|
DESTDIR="$out" \
|
||||||
|
HAKUREI_VERSION="v${hakurei.version}" \
|
||||||
./all.sh
|
./all.sh
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
@@ -190,11 +191,11 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
./interactive/configuration.nix
|
./test/interactive/configuration.nix
|
||||||
./interactive/vm.nix
|
./test/interactive/vm.nix
|
||||||
./interactive/hakurei.nix
|
./test/interactive/hakurei.nix
|
||||||
./interactive/trace.nix
|
./test/interactive/trace.nix
|
||||||
./interactive/raceattr.nix
|
./test/interactive/raceattr.nix
|
||||||
|
|
||||||
self.nixosModules.hakurei
|
self.nixosModules.hakurei
|
||||||
home-manager.nixosModules.home-manager
|
home-manager.nixosModules.home-manager
|
||||||
@@ -2,7 +2,6 @@ package hst
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -65,14 +64,10 @@ const (
|
|||||||
// Some programs fail to connect to dbus session running as a different uid,
|
// Some programs fail to connect to dbus session running as a different uid,
|
||||||
// this option works around it by mapping priv-side caller uid in container.
|
// this option works around it by mapping priv-side caller uid in container.
|
||||||
FMapRealUID
|
FMapRealUID
|
||||||
// FNoPlace disables placement of /etc/passwd and /etc/group.
|
|
||||||
FNoPlace
|
|
||||||
|
|
||||||
// FDevice mount /dev/ from the init mount namespace as is in the container
|
// FDevice mount /dev/ from the init mount namespace as is in the container
|
||||||
// mount namespace.
|
// mount namespace.
|
||||||
FDevice
|
FDevice
|
||||||
// FCoverRun covers /run/ in the container mount namespace early.
|
|
||||||
FCoverRun
|
|
||||||
|
|
||||||
// FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity.
|
// FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity.
|
||||||
FShareRuntime
|
FShareRuntime
|
||||||
@@ -103,12 +98,8 @@ func (flags Flags) String() string {
|
|||||||
return "tty"
|
return "tty"
|
||||||
case FMapRealUID:
|
case FMapRealUID:
|
||||||
return "mapuid"
|
return "mapuid"
|
||||||
case FNoPlace:
|
|
||||||
return "noplace"
|
|
||||||
case FDevice:
|
case FDevice:
|
||||||
return "device"
|
return "device"
|
||||||
case FCoverRun:
|
|
||||||
return "cover_run"
|
|
||||||
case FShareRuntime:
|
case FShareRuntime:
|
||||||
return "runtime"
|
return "runtime"
|
||||||
case FShareTmpdir:
|
case FShareTmpdir:
|
||||||
@@ -170,10 +161,6 @@ type ContainerConfig struct {
|
|||||||
Flags Flags `json:"-"`
|
Flags Flags `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContainerConfig) GoString() string {
|
|
||||||
return fmt.Sprintf("&%#v", *c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerConfigF is [ContainerConfig] stripped of its methods.
|
// ContainerConfigF is [ContainerConfig] stripped of its methods.
|
||||||
//
|
//
|
||||||
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
|
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
|
||||||
@@ -201,13 +188,9 @@ type containerConfigJSON = struct {
|
|||||||
|
|
||||||
// Corresponds to [FMapRealUID].
|
// Corresponds to [FMapRealUID].
|
||||||
MapRealUID bool `json:"map_real_uid"`
|
MapRealUID bool `json:"map_real_uid"`
|
||||||
// Corresponds to [FNoPlace].
|
|
||||||
NoPlace bool `json:"noplace,omitempty"`
|
|
||||||
|
|
||||||
// Corresponds to [FDevice].
|
// Corresponds to [FDevice].
|
||||||
Device bool `json:"device,omitempty"`
|
Device bool `json:"device,omitempty"`
|
||||||
// Corresponds to [FCoverRun].
|
|
||||||
CoverRun bool `json:"cover_run,omitempty"`
|
|
||||||
|
|
||||||
// Corresponds to [FShareRuntime].
|
// Corresponds to [FShareRuntime].
|
||||||
ShareRuntime bool `json:"share_runtime,omitempty"`
|
ShareRuntime bool `json:"share_runtime,omitempty"`
|
||||||
@@ -230,9 +213,7 @@ func (c *ContainerConfig) MarshalJSON() ([]byte, error) {
|
|||||||
Tty: c.Flags&FTty != 0,
|
Tty: c.Flags&FTty != 0,
|
||||||
Multiarch: c.Flags&FMultiarch != 0,
|
Multiarch: c.Flags&FMultiarch != 0,
|
||||||
MapRealUID: c.Flags&FMapRealUID != 0,
|
MapRealUID: c.Flags&FMapRealUID != 0,
|
||||||
NoPlace: c.Flags&FNoPlace != 0,
|
|
||||||
Device: c.Flags&FDevice != 0,
|
Device: c.Flags&FDevice != 0,
|
||||||
CoverRun: c.Flags&FCoverRun != 0,
|
|
||||||
ShareRuntime: c.Flags&FShareRuntime != 0,
|
ShareRuntime: c.Flags&FShareRuntime != 0,
|
||||||
ShareTmpdir: c.Flags&FShareTmpdir != 0,
|
ShareTmpdir: c.Flags&FShareTmpdir != 0,
|
||||||
})
|
})
|
||||||
@@ -273,15 +254,9 @@ func (c *ContainerConfig) UnmarshalJSON(data []byte) error {
|
|||||||
if v.MapRealUID {
|
if v.MapRealUID {
|
||||||
c.Flags |= FMapRealUID
|
c.Flags |= FMapRealUID
|
||||||
}
|
}
|
||||||
if v.NoPlace {
|
|
||||||
c.Flags |= FNoPlace
|
|
||||||
}
|
|
||||||
if v.Device {
|
if v.Device {
|
||||||
c.Flags |= FDevice
|
c.Flags |= FDevice
|
||||||
}
|
}
|
||||||
if v.CoverRun {
|
|
||||||
c.Flags |= FCoverRun
|
|
||||||
}
|
|
||||||
if v.ShareRuntime {
|
if v.ShareRuntime {
|
||||||
c.Flags |= FShareRuntime
|
c.Flags |= FShareRuntime
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ func TestFlagsString(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"none", 0, "none"},
|
{"none", 0, "none"},
|
||||||
{"none high", hst.FAll + 1, "none"},
|
{"none high", hst.FAll + 1, "none"},
|
||||||
{"all", hst.FAll, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, noplace, device, cover_run, runtime, tmpdir"},
|
{"all", hst.FAll, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir"},
|
||||||
{"all high", math.MaxUint, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, noplace, device, cover_run, runtime, tmpdir"},
|
{"all high", math.MaxUint, "multiarch, compat, devel, userns, net, abstract, tty, mapuid, device, runtime, tmpdir"},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -53,7 +53,7 @@ func TestContainerConfig(t *testing.T) {
|
|||||||
{"hostnet hostabstract mapuid", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID},
|
{"hostnet hostabstract mapuid", &hst.ContainerConfig{Flags: hst.FHostNet | hst.FHostAbstract | hst.FMapRealUID},
|
||||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":true}`},
|
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"host_net":true,"host_abstract":true,"map_real_uid":true}`},
|
||||||
{"all", &hst.ContainerConfig{Flags: hst.FAll},
|
{"all", &hst.ContainerConfig{Flags: hst.FAll},
|
||||||
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"noplace":true,"device":true,"cover_run":true,"share_runtime":true,"share_tmpdir":true}`},
|
`{"env":null,"filesystem":null,"shell":null,"home":null,"args":null,"seccomp_compat":true,"devel":true,"userns":true,"host_net":true,"host_abstract":true,"tty":true,"multiarch":true,"map_real_uid":true,"device":true,"share_runtime":true,"share_tmpdir":true}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package hst
|
package hst
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -62,10 +61,6 @@ type BusConfig struct {
|
|||||||
Filter bool `json:"filter"`
|
Filter bool `json:"filter"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BusConfig) GoString() string {
|
|
||||||
return fmt.Sprintf("&%#v", *c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interfaces iterates over all interface strings specified in [BusConfig].
|
// Interfaces iterates over all interface strings specified in [BusConfig].
|
||||||
func (c *BusConfig) Interfaces(yield func(string) bool) {
|
func (c *BusConfig) Interfaces(yield func(string) bool) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
)
|
)
|
||||||
@@ -37,8 +36,6 @@ type Ops interface {
|
|||||||
Bind(source, target *check.Absolute, flags int) Ops
|
Bind(source, target *check.Absolute, flags int) Ops
|
||||||
// Overlay appends an op that mounts the overlay pseudo filesystem.
|
// Overlay appends an op that mounts the overlay pseudo filesystem.
|
||||||
Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) Ops
|
Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) Ops
|
||||||
// OverlayEphemeral appends a MountOverlayOp with an ephemeral upperdir and workdir.
|
|
||||||
OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) Ops
|
|
||||||
// OverlayReadonly appends an op that mounts the overlay pseudo filesystem readonly.
|
// OverlayReadonly appends an op that mounts the overlay pseudo filesystem readonly.
|
||||||
OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) Ops
|
OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) Ops
|
||||||
|
|
||||||
@@ -81,17 +78,17 @@ type FSImplError struct{ Value FilesystemConfig }
|
|||||||
|
|
||||||
func (f FSImplError) Error() string {
|
func (f FSImplError) Error() string {
|
||||||
implType := reflect.TypeOf(f.Value)
|
implType := reflect.TypeOf(f.Value)
|
||||||
var buf strings.Builder
|
var name string
|
||||||
for implType != nil && implType.Kind() == reflect.Pointer {
|
for implType != nil && implType.Kind() == reflect.Ptr {
|
||||||
buf.WriteByte('*')
|
name += "*"
|
||||||
implType = implType.Elem()
|
implType = implType.Elem()
|
||||||
}
|
}
|
||||||
if implType != nil {
|
if implType != nil {
|
||||||
buf.WriteString(implType.Name())
|
name += implType.Name()
|
||||||
} else {
|
} else {
|
||||||
buf.WriteString("nil")
|
name += "nil"
|
||||||
}
|
}
|
||||||
return "implementation " + buf.String() + " not supported"
|
return fmt.Sprintf("implementation %s not supported", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilesystemConfigJSON is the [json] adapter for [FilesystemConfig].
|
// FilesystemConfigJSON is the [json] adapter for [FilesystemConfig].
|
||||||
|
|||||||
+4
-9
@@ -3,7 +3,6 @@ package hst_test
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -104,7 +103,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
|||||||
t.Run("marshal", func(t *testing.T) {
|
t.Run("marshal", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
wantErr := tc.wantErr
|
wantErr := tc.wantErr
|
||||||
if _, ok := errors.AsType[hst.FSTypeError](wantErr); ok {
|
if errors.As(wantErr, new(hst.FSTypeError)) {
|
||||||
// for unsupported implementation tc
|
// for unsupported implementation tc
|
||||||
wantErr = hst.FSImplError{Value: stubFS{"cat"}}
|
wantErr = hst.FSImplError{Value: stubFS{"cat"}}
|
||||||
}
|
}
|
||||||
@@ -140,7 +139,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
|||||||
t.Run("unmarshal", func(t *testing.T) {
|
t.Run("unmarshal", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
if tc.data == "\x00" && tc.sData == "\x00" {
|
if tc.data == "\x00" && tc.sData == "\x00" {
|
||||||
if _, ok := errors.AsType[hst.FSImplError](tc.wantErr); ok {
|
if errors.As(tc.wantErr, new(hst.FSImplError)) {
|
||||||
// this error is only returned on marshal
|
// this error is only returned on marshal
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -284,11 +283,11 @@ func checkFs(t *testing.T, testCases []fsTestCase) {
|
|||||||
if !reflect.DeepEqual(ops, &tc.ops) {
|
if !reflect.DeepEqual(ops, &tc.ops) {
|
||||||
gotString := new(strings.Builder)
|
gotString := new(strings.Builder)
|
||||||
for _, op := range *ops {
|
for _, op := range *ops {
|
||||||
gotString.WriteString("\n" + fmt.Sprintf("%#v", op))
|
gotString.WriteString("\n" + op.String())
|
||||||
}
|
}
|
||||||
wantString := new(strings.Builder)
|
wantString := new(strings.Builder)
|
||||||
for _, op := range tc.ops {
|
for _, op := range tc.ops {
|
||||||
wantString.WriteString("\n" + fmt.Sprintf("%#v", op))
|
wantString.WriteString("\n" + op.String())
|
||||||
}
|
}
|
||||||
t.Errorf("Apply: %s, want %s", gotString, wantString)
|
t.Errorf("Apply: %s, want %s", gotString, wantString)
|
||||||
}
|
}
|
||||||
@@ -340,10 +339,6 @@ func (p opsAdapter) Overlay(target, state, work *check.Absolute, layers ...*chec
|
|||||||
return opsAdapter{p.Ops.Overlay(target, state, work, layers...)}
|
return opsAdapter{p.Ops.Overlay(target, state, work, layers...)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p opsAdapter) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
|
|
||||||
return opsAdapter{p.Ops.OverlayEphemeral(target, layers...)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p opsAdapter) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
|
func (p opsAdapter) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) hst.Ops {
|
||||||
return opsAdapter{p.Ops.OverlayReadonly(target, layers...)}
|
return opsAdapter{p.Ops.OverlayReadonly(target, layers...)}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -43,13 +43,18 @@ func (e *FSEphemeral) Apply(z *ApplyState) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size := e.Size
|
||||||
|
if size < 0 {
|
||||||
|
size = 0
|
||||||
|
}
|
||||||
|
|
||||||
perm := e.Perm
|
perm := e.Perm
|
||||||
if perm == 0 {
|
if perm == 0 {
|
||||||
perm = fsEphemeralDefaultPerm
|
perm = fsEphemeralDefaultPerm
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Write {
|
if e.Write {
|
||||||
z.Tmpfs(e.Target, max(e.Size, 0), perm)
|
z.Tmpfs(e.Target, size, perm)
|
||||||
} else {
|
} else {
|
||||||
z.Readonly(e.Target, perm)
|
z.Readonly(e.Target, perm)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-24
@@ -2,7 +2,6 @@ package hst
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
@@ -41,7 +40,7 @@ func (o *FSOverlay) Valid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if o.Upper != nil { // rw
|
if o.Upper != nil { // rw
|
||||||
return o.Work != nil || len(o.Lower) > 0
|
return o.Work != nil && len(o.Lower) > 0
|
||||||
} else { // ro
|
} else { // ro
|
||||||
return len(o.Lower) >= 2
|
return len(o.Lower) >= 2
|
||||||
}
|
}
|
||||||
@@ -59,11 +58,8 @@ func (o *FSOverlay) Host() []*check.Absolute {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
p := make([]*check.Absolute, 0, 2+len(o.Lower))
|
p := make([]*check.Absolute, 0, 2+len(o.Lower))
|
||||||
if o.Upper != nil {
|
if o.Upper != nil && o.Work != nil {
|
||||||
p = append(p, o.Upper)
|
p = append(p, o.Upper, o.Work)
|
||||||
if o.Work != nil {
|
|
||||||
p = append(p, o.Work)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
p = append(p, o.Lower...)
|
p = append(p, o.Lower...)
|
||||||
return p
|
return p
|
||||||
@@ -74,18 +70,11 @@ func (o *FSOverlay) Apply(z *ApplyState) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Upper != nil {
|
if o.Upper != nil && o.Work != nil {
|
||||||
|
z.Overlay(o.Target, o.Upper, o.Work, o.Lower...)
|
||||||
if o.Target.Is(fhs.AbsRoot) {
|
if o.Target.Is(fhs.AbsRoot) {
|
||||||
z.NoRemountRoot = true
|
z.NoRemountRoot = true
|
||||||
}
|
}
|
||||||
if o.Work != nil {
|
|
||||||
z.Overlay(o.Target, o.Upper, o.Work, o.Lower...)
|
|
||||||
} else {
|
|
||||||
z.OverlayEphemeral(o.Target, slices.Concat(
|
|
||||||
[]*check.Absolute{o.Upper},
|
|
||||||
o.Lower,
|
|
||||||
)...)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
z.OverlayReadonly(o.Target, o.Lower...)
|
z.OverlayReadonly(o.Target, o.Lower...)
|
||||||
}
|
}
|
||||||
@@ -101,19 +90,12 @@ func (o *FSOverlay) String() string {
|
|||||||
lower[i] = check.EscapeOverlayDataSegment(a.String())
|
lower[i] = check.EscapeOverlayDataSegment(a.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Upper != nil {
|
if o.Upper != nil && o.Work != nil {
|
||||||
if o.Work != nil {
|
|
||||||
return "w*" + strings.Join(append([]string{
|
return "w*" + strings.Join(append([]string{
|
||||||
check.EscapeOverlayDataSegment(o.Target.String()),
|
check.EscapeOverlayDataSegment(o.Target.String()),
|
||||||
check.EscapeOverlayDataSegment(o.Upper.String()),
|
check.EscapeOverlayDataSegment(o.Upper.String()),
|
||||||
check.EscapeOverlayDataSegment(o.Work.String())},
|
check.EscapeOverlayDataSegment(o.Work.String())},
|
||||||
lower...), check.SpecialOverlayPath)
|
lower...), check.SpecialOverlayPath)
|
||||||
}
|
|
||||||
return "e*" + strings.Join(append([]string{
|
|
||||||
check.EscapeOverlayDataSegment(o.Target.String()),
|
|
||||||
check.EscapeOverlayDataSegment(o.Upper.String())},
|
|
||||||
lower...), check.SpecialOverlayPath)
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return "*" + strings.Join(append([]string{
|
return "*" + strings.Join(append([]string{
|
||||||
check.EscapeOverlayDataSegment(o.Target.String())},
|
check.EscapeOverlayDataSegment(o.Target.String())},
|
||||||
|
|||||||
+1
-13
@@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/fhs"
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ func TestFSOverlay(t *testing.T) {
|
|||||||
checkFs(t, []fsTestCase{
|
checkFs(t, []fsTestCase{
|
||||||
{"nil", (*hst.FSOverlay)(nil), false, nil, nil, nil, "<invalid>"},
|
{"nil", (*hst.FSOverlay)(nil), false, nil, nil, nil, "<invalid>"},
|
||||||
{"nil lower", &hst.FSOverlay{Target: m("/etc"), Lower: []*check.Absolute{nil}}, false, nil, nil, nil, "<invalid>"},
|
{"nil lower", &hst.FSOverlay{Target: m("/etc"), Lower: []*check.Absolute{nil}}, false, nil, nil, nil, "<invalid>"},
|
||||||
{"zero lower", &hst.FSOverlay{Target: m("/etc"), Work: m("/")}, false, nil, nil, nil, "<invalid>"},
|
{"zero lower", &hst.FSOverlay{Target: m("/etc"), Upper: m("/"), Work: m("/")}, false, nil, nil, nil, "<invalid>"},
|
||||||
{"zero lower ro", &hst.FSOverlay{Target: m("/etc")}, false, nil, nil, nil, "<invalid>"},
|
{"zero lower ro", &hst.FSOverlay{Target: m("/etc")}, false, nil, nil, nil, "<invalid>"},
|
||||||
{"short lower", &hst.FSOverlay{Target: m("/etc"), Lower: ms("/etc")}, false, nil, nil, nil, "<invalid>"},
|
{"short lower", &hst.FSOverlay{Target: m("/etc"), Lower: ms("/etc")}, false, nil, nil, nil, "<invalid>"},
|
||||||
|
|
||||||
@@ -63,16 +62,5 @@ func TestFSOverlay(t *testing.T) {
|
|||||||
Work: m("/tmp/work"),
|
Work: m("/tmp/work"),
|
||||||
}}, m("/"), ms("/tmp/upper", "/tmp/work", "/tmp/.src0", "/tmp/.src1"),
|
}}, m("/"), ms("/tmp/upper", "/tmp/work", "/tmp/.src0", "/tmp/.src1"),
|
||||||
"w*/:/tmp/upper:/tmp/work:/tmp/.src0:/tmp/.src1"},
|
"w*/:/tmp/upper:/tmp/work:/tmp/.src0:/tmp/.src1"},
|
||||||
|
|
||||||
{"ephemeral", &hst.FSOverlay{
|
|
||||||
Target: m("/"),
|
|
||||||
Lower: ms("/tmp/.src0", "/tmp/.src1"),
|
|
||||||
Upper: m("/tmp/upper"),
|
|
||||||
}, true, container.Ops{&container.MountOverlayOp{
|
|
||||||
Target: m("/"),
|
|
||||||
Lower: ms("/tmp/upper", "/tmp/.src0", "/tmp/.src1"),
|
|
||||||
Upper: fhs.AbsRoot,
|
|
||||||
}}, m("/"), ms("/tmp/upper", "/tmp/.src0", "/tmp/.src1"),
|
|
||||||
"e*/:/tmp/upper:/tmp/.src0:/tmp/.src1"},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,9 +244,7 @@ func TestTemplate(t *testing.T) {
|
|||||||
"tty": true,
|
"tty": true,
|
||||||
"multiarch": true,
|
"multiarch": true,
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
"noplace": true,
|
|
||||||
"device": true,
|
"device": true,
|
||||||
"cover_run": true,
|
|
||||||
"share_runtime": true,
|
"share_runtime": true,
|
||||||
"share_tmpdir": true
|
"share_tmpdir": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if found := bytes.Contains([]byte("-_/.\\*"), []byte{b}); found { // - // _/.\*
|
if ib := bytes.IndexByte([]byte("-_/.\\*"), b); ib != -1 { // - // _/.\*
|
||||||
goto opt
|
goto opt
|
||||||
} else if b >= '0' && b <= '9' { // 0-9
|
} else if b >= '0' && b <= '9' { // 0-9
|
||||||
goto opt
|
goto opt
|
||||||
@@ -101,7 +101,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
|
if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
|
||||||
if _, ok := errors.AsType[hex.InvalidByteError](err); ok {
|
if errors.As(err, new(hex.InvalidByteError)) {
|
||||||
errno = ErrBadValHexByte
|
errno = ErrBadValHexByte
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+4
-24
@@ -1,29 +1,21 @@
|
|||||||
// Package env provides the [Paths] struct for efficiently building paths from
|
// Package env provides the [Paths] struct for efficiently building paths from the environment.
|
||||||
// the environment.
|
|
||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io/fs"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/fhs"
|
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VarRunNscd = fhs.Var + "run/nscd"
|
|
||||||
|
|
||||||
// Paths holds paths copied from the environment and is used to create [hst.Paths].
|
// Paths holds paths copied from the environment and is used to create [hst.Paths].
|
||||||
type Paths struct {
|
type Paths struct {
|
||||||
// TempDir is returned by [os.TempDir].
|
// TempDir is returned by [os.TempDir].
|
||||||
TempDir *check.Absolute
|
TempDir *check.Absolute
|
||||||
// RuntimePath is copied from $XDG_RUNTIME_DIR.
|
// RuntimePath is copied from $XDG_RUNTIME_DIR.
|
||||||
RuntimePath *check.Absolute
|
RuntimePath *check.Absolute
|
||||||
// Whether [VarRunNscd] is a directory.
|
|
||||||
HasNscd bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy expands [Paths] into [hst.Paths].
|
// Copy expands [Paths] into [hst.Paths].
|
||||||
@@ -45,17 +37,14 @@ func (env *Paths) Copy(v *hst.Paths, userid int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CopyPaths returns a populated [Paths].
|
// CopyPaths returns a populated [Paths].
|
||||||
func CopyPaths() *Paths {
|
func CopyPaths() *Paths { return CopyPathsFunc(log.Fatalf, os.TempDir, os.Getenv) }
|
||||||
return CopyPathsFunc(log.Fatalf, os.TempDir, os.Getenv, os.Stat)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyPathsFunc returns a populated [Paths], using the provided [log.Fatalf],
|
// CopyPathsFunc returns a populated [Paths],
|
||||||
// [os.TempDir], [os.Getenv] functions.
|
// using the provided [log.Fatalf], [os.TempDir], [os.Getenv] functions.
|
||||||
func CopyPathsFunc(
|
func CopyPathsFunc(
|
||||||
fatalf func(format string, v ...any),
|
fatalf func(format string, v ...any),
|
||||||
tempdir func() string,
|
tempdir func() string,
|
||||||
getenv func(key string) string,
|
getenv func(key string) string,
|
||||||
stat func(name string) (fs.FileInfo, error),
|
|
||||||
) *Paths {
|
) *Paths {
|
||||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
@@ -72,14 +61,5 @@ func CopyPathsFunc(
|
|||||||
env.RuntimePath = a
|
env.RuntimePath = a
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi, err := stat(VarRunNscd); err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
fatalf("%v", err)
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
env.HasNscd = fi.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &env
|
return &env
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-4
@@ -2,7 +2,6 @@ package env_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -105,9 +104,7 @@ func TestCopyPaths(t *testing.T) {
|
|||||||
t.Fatalf("fatalf: %q, want %q", got, tc.fatal)
|
t.Fatalf("fatalf: %q, want %q", got, tc.fatal)
|
||||||
}
|
}
|
||||||
panic(stub.PanicExit)
|
panic(stub.PanicExit)
|
||||||
}, func() string { return tc.tmp }, func(key string) string { return tc.env[key] }, func(name string) (fs.FileInfo, error) {
|
}, func() string { return tc.tmp }, func(key string) string { return tc.env[key] })
|
||||||
return nil, fs.ErrNotExist
|
|
||||||
})
|
|
||||||
|
|
||||||
if tc.fatal != "" {
|
if tc.fatal != "" {
|
||||||
t.Fatalf("copyPaths: expected fatal %q", tc.fatal)
|
t.Fatalf("copyPaths: expected fatal %q", tc.fatal)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package kobject
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"maps"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -29,22 +28,6 @@ type Event struct {
|
|||||||
Subsystem string `json:"subsystem"`
|
Subsystem string `json:"subsystem"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone returns a copy of e.
|
|
||||||
func (e *Event) Clone() Event {
|
|
||||||
v := *e
|
|
||||||
v.Env = maps.Clone(e.Env)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeColdboot allocates a new [Object] from e in [StateColdboot].
|
|
||||||
func (e *Event) makeColdboot() *Object {
|
|
||||||
return &Object{
|
|
||||||
State: StateColdboot,
|
|
||||||
DevPath: e.DevPath,
|
|
||||||
Subsystem: e.Subsystem,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate populates e with the contents of a [uevent.Message].
|
// Populate populates e with the contents of a [uevent.Message].
|
||||||
//
|
//
|
||||||
// The ACTION and DEVPATH environment variables are ignored and assumed to be
|
// The ACTION and DEVPATH environment variables are ignored and assumed to be
|
||||||
|
|||||||
@@ -1,491 +0,0 @@
|
|||||||
// Package kobject interprets uevent messages from a NETLINK_KOBJECT_UEVENT socket.
|
|
||||||
package kobject
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"maps"
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"hakurei.app/internal/report"
|
|
||||||
"hakurei.app/internal/uevent"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StateColdboot denotes an [Object] populated by a coldboot event. It is
|
|
||||||
// eligible for all event actions.
|
|
||||||
StateColdboot = iota
|
|
||||||
// StateNew denotes an [Object] previously populated by a [uevent.KOBJ_ADD]
|
|
||||||
// event, but has not yet been targeted by a [uevent.KOBJ_BIND] event, or
|
|
||||||
// has been targeted by a [uevent.KOBJ_UNBIND] event.
|
|
||||||
StateNew
|
|
||||||
// StateBound denotes an [Object] that has been targeted by a
|
|
||||||
// [uevent.KOBJ_BIND] and has not been targeted by a [uevent.KOBJ_UNBIND]
|
|
||||||
// after that.
|
|
||||||
StateBound
|
|
||||||
)
|
|
||||||
|
|
||||||
// Object represents a kernel object.
|
|
||||||
type Object struct {
|
|
||||||
// Origin of the object.
|
|
||||||
State int `json:"state,omitempty"`
|
|
||||||
// Set by [uevent.KOBJ_OFFLINE] and [uevent.KOBJ_ONLINE].
|
|
||||||
Offline bool `json:"offline,omitempty"`
|
|
||||||
|
|
||||||
// alloc_uevent_skb: devpath
|
|
||||||
DevPath string `json:"devpath"`
|
|
||||||
// registered per-driver (optional)
|
|
||||||
ModAlias string `json:"modalias,omitempty"`
|
|
||||||
// dev_driver_uevent: drv->name (optional)
|
|
||||||
Driver string `json:"driver,omitempty"`
|
|
||||||
|
|
||||||
// SUBSYSTEM value set by the kernel.
|
|
||||||
Subsystem string `json:"subsystem"`
|
|
||||||
|
|
||||||
// Uninterpreted environment variable pairs. An entry missing a separator
|
|
||||||
// gains the value "\x00".
|
|
||||||
Env map[string]string `json:"env"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone returns the address of a copy of o.
|
|
||||||
func (o *Object) Clone() *Object {
|
|
||||||
v := *o
|
|
||||||
v.Env = maps.Clone(o.Env)
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// GoString returns compound literal for the underlying value.
|
|
||||||
func (o *Object) GoString() string {
|
|
||||||
return fmt.Sprintf("&%#v", *o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge merges uninterpreted environment variable pairs from an [Event].
|
|
||||||
func (o *Object) merge(env map[string]string) {
|
|
||||||
for k, v := range env {
|
|
||||||
if v == "\x00" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch k {
|
|
||||||
case "MODALIAS":
|
|
||||||
o.ModAlias = v
|
|
||||||
continue
|
|
||||||
|
|
||||||
case "DRIVER":
|
|
||||||
o.Driver = v
|
|
||||||
continue
|
|
||||||
|
|
||||||
default:
|
|
||||||
if o.Env == nil {
|
|
||||||
o.Env = make(map[string]string)
|
|
||||||
}
|
|
||||||
o.Env[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update updates o with pairs from env, optionally stripping visited pairs.
|
|
||||||
func (o *Object) update(env map[string]string, strip bool) {
|
|
||||||
for k := range o.Env {
|
|
||||||
if v, ok := env[k]; ok {
|
|
||||||
if strip {
|
|
||||||
delete(env, k)
|
|
||||||
}
|
|
||||||
o.Env[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A pendingIterator is a callback currently iterating through objects targeted
|
|
||||||
// by ongoing events.
|
|
||||||
type pendingIterator struct {
|
|
||||||
f func(o *Object, act uevent.KobjectAction) bool
|
|
||||||
done chan<- struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// State processes a stream of [Event] populated from [uevent.Message] received
|
|
||||||
// from a NETLINK_KOBJECT_UEVENT socket and presents an efficient representation
|
|
||||||
// of kernel state.
|
|
||||||
type State struct {
|
|
||||||
// Next expected SEQNUM.
|
|
||||||
seq uint64
|
|
||||||
// DevPath to environment variables.
|
|
||||||
uevent map[string]*Object
|
|
||||||
// Synchronises access to uevent and its objects.
|
|
||||||
ueventMu sync.RWMutex
|
|
||||||
// Alive iterators.
|
|
||||||
iter []*pendingIterator
|
|
||||||
// Synchronises access to iter.
|
|
||||||
iterMu sync.Mutex
|
|
||||||
// UUID for synthetic [uevent.Coldboot] events.
|
|
||||||
coldboot uevent.UUID
|
|
||||||
// Called on [uevent.KOBJ_CHANGE] with stripped environment variables.
|
|
||||||
handleChange func(o *Object, env map[string]string)
|
|
||||||
// Reports errors populating [Event] from [uevent.Message]. A user-supplied
|
|
||||||
// nil value is replaced with a noop.
|
|
||||||
reportErr func(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns the address of a new [State].
|
|
||||||
func New(
|
|
||||||
coldboot uevent.UUID,
|
|
||||||
handleChange func(o *Object, env map[string]string),
|
|
||||||
reportErr func(error),
|
|
||||||
) *State {
|
|
||||||
return &State{
|
|
||||||
uevent: make(map[string]*Object),
|
|
||||||
coldboot: coldboot,
|
|
||||||
handleChange: handleChange,
|
|
||||||
reportErr: reportErr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteIter removes an iterator from s. Must be called after acquiring iterMu.
|
|
||||||
func (s *State) deleteIter(p *pendingIterator) {
|
|
||||||
s.iter = slices.DeleteFunc(s.iter, func(v *pendingIterator) bool {
|
|
||||||
return p == v
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// dispatchIter broadcasts an [Object] to all alive iterators.
|
|
||||||
func (s *State) dispatchIter(o *Object, act uevent.KobjectAction) {
|
|
||||||
s.iterMu.Lock()
|
|
||||||
defer s.iterMu.Unlock()
|
|
||||||
|
|
||||||
for _, p := range s.iter {
|
|
||||||
if !p.f(o, act) {
|
|
||||||
s.deleteIter(p)
|
|
||||||
close(p.done)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range calls f on all current and upcoming [Object] values tracked by s until
|
|
||||||
// f returns false or the context is cancelled. f must not retain o or modify
|
|
||||||
// the value it points to.
|
|
||||||
func (s *State) Range(
|
|
||||||
ctx context.Context,
|
|
||||||
f func(o *Object, act uevent.KobjectAction) bool,
|
|
||||||
) {
|
|
||||||
done := make(chan struct{})
|
|
||||||
p := pendingIterator{f, done}
|
|
||||||
|
|
||||||
s.iterMu.Lock()
|
|
||||||
s.ueventMu.RLock()
|
|
||||||
for _, o := range s.uevent {
|
|
||||||
if !f(o, uevent.KOBJ_ADD) {
|
|
||||||
s.ueventMu.RUnlock()
|
|
||||||
s.iterMu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.ueventMu.RUnlock()
|
|
||||||
s.iter = append(s.iter, &p)
|
|
||||||
s.iterMu.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
s.iterMu.Lock()
|
|
||||||
s.deleteIter(&p)
|
|
||||||
s.iterMu.Unlock()
|
|
||||||
return
|
|
||||||
|
|
||||||
case <-done:
|
|
||||||
// deregistered by dispatchIter
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// An EventError describes a malformed or inconsistent [Event].
|
|
||||||
type EventError struct {
|
|
||||||
Kind int `json:"fault"`
|
|
||||||
E Event `json:"event"`
|
|
||||||
O *Object `json:"object,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ report.RepresentableError = EventError{}
|
|
||||||
|
|
||||||
func (EventError) Representable() {}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// EUnexpectedColdboot is reported for a coldboot event with action other
|
|
||||||
// than the expected [uevent.KOBJ_ADD].
|
|
||||||
EUnexpectedColdboot = iota
|
|
||||||
// EDuplicateAdd is reported for a [uevent.KOBJ_ADD] event on a
|
|
||||||
// still-existing entry that was not the result of a coldboot.
|
|
||||||
EDuplicateAdd
|
|
||||||
// EBadTarget is reported for an event on a nonexistent [Object]. This is
|
|
||||||
// generally only possible before coldboot completes.
|
|
||||||
EBadTarget
|
|
||||||
// ERemoveState is reported for a [uevent.KOBJ_REMOVE] event targeting an
|
|
||||||
// entry in a state other than [StateColdboot] and [StateNew].
|
|
||||||
ERemoveState
|
|
||||||
// EUnexpectedOffline is reported for a [uevent.KOBJ_OFFLINE] or
|
|
||||||
// [uevent.KOBJ_ONLINE] event targeting an already offline or online object.
|
|
||||||
EUnexpectedOffline
|
|
||||||
// EBindState is reported for a [uevent.KOBJ_BIND] event targeting an entry
|
|
||||||
// in a state other than [StateColdboot] and [StateNew].
|
|
||||||
EBindState
|
|
||||||
// EUnbindState is reported for a [uevent.KOBJ_UNBIND] event targeting an
|
|
||||||
// entry in a state other than [StateBound].
|
|
||||||
EUnbindState
|
|
||||||
// EMalformedMove is reported for a [uevent.KOBJ_MOVE] event missing the
|
|
||||||
// DEVPATH_OLD environment variable.
|
|
||||||
EMalformedMove
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e EventError) Error() string {
|
|
||||||
switch e.Kind {
|
|
||||||
case EUnexpectedColdboot:
|
|
||||||
return "unexpected " + e.E.Action.String() + " coldboot event"
|
|
||||||
case EDuplicateAdd:
|
|
||||||
return "duplicate add event on devpath " + strconv.Quote(e.E.DevPath)
|
|
||||||
case EBadTarget:
|
|
||||||
return "unexpected " + e.E.Action.String() + " event on devpath " +
|
|
||||||
strconv.Quote(e.E.DevPath)
|
|
||||||
case ERemoveState:
|
|
||||||
if e.O == nil {
|
|
||||||
return "invalid remove event error"
|
|
||||||
}
|
|
||||||
return "remove event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
|
||||||
" in state " + strconv.Itoa(e.O.State)
|
|
||||||
case EUnexpectedOffline:
|
|
||||||
if e.O == nil {
|
|
||||||
return "invalid unexpected offline error"
|
|
||||||
}
|
|
||||||
if e.O.Offline {
|
|
||||||
return "offline event targeting devpath " + strconv.Quote(e.E.DevPath)
|
|
||||||
}
|
|
||||||
return "online event targeting devpath " + strconv.Quote(e.E.DevPath)
|
|
||||||
case EBindState:
|
|
||||||
if e.O == nil {
|
|
||||||
return "invalid bind state error"
|
|
||||||
}
|
|
||||||
return "bind event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
|
||||||
" in state " + strconv.Itoa(e.O.State)
|
|
||||||
case EUnbindState:
|
|
||||||
if e.O == nil {
|
|
||||||
return "invalid unbind state error"
|
|
||||||
}
|
|
||||||
return "unbind event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
|
||||||
" in state " + strconv.Itoa(e.O.State)
|
|
||||||
case EMalformedMove:
|
|
||||||
return "move event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
|
||||||
" missing DEVPATH_OLD"
|
|
||||||
|
|
||||||
default:
|
|
||||||
return "invalid event error kind " + strconv.Itoa(e.Kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewError returns a new [EventError] for e and o.
|
|
||||||
func (e *Event) NewError(kind int, o *Object) error {
|
|
||||||
if o != nil {
|
|
||||||
o = o.Clone()
|
|
||||||
}
|
|
||||||
return EventError{kind, e.Clone(), o}
|
|
||||||
}
|
|
||||||
|
|
||||||
// processEvent merges an event into s.
|
|
||||||
func (s *State) processEvent(e *Event) {
|
|
||||||
s.ueventMu.Lock()
|
|
||||||
defer s.ueventMu.Unlock()
|
|
||||||
|
|
||||||
coldboot := e.Synth != nil
|
|
||||||
if e.Action != uevent.KOBJ_ADD && coldboot {
|
|
||||||
s.reportErr(e.NewError(EUnexpectedColdboot, nil))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch act := e.Action; act {
|
|
||||||
case uevent.KOBJ_ADD:
|
|
||||||
if e.Synth == nil {
|
|
||||||
if o, ok := s.uevent[e.DevPath]; ok {
|
|
||||||
s.reportErr(e.NewError(EDuplicateAdd, o))
|
|
||||||
o.merge(e.Env)
|
|
||||||
s.dispatchIter(o, act)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o := e.makeColdboot()
|
|
||||||
if !coldboot {
|
|
||||||
o.State = StateNew
|
|
||||||
}
|
|
||||||
o.merge(e.Env)
|
|
||||||
s.uevent[e.DevPath] = o
|
|
||||||
s.dispatchIter(o, act)
|
|
||||||
return
|
|
||||||
|
|
||||||
case uevent.KOBJ_REMOVE:
|
|
||||||
if o, ok := s.uevent[e.DevPath]; !ok {
|
|
||||||
s.reportErr(e.NewError(EBadTarget, nil))
|
|
||||||
return
|
|
||||||
} else if o.State != StateColdboot && o.State != StateNew {
|
|
||||||
s.reportErr(e.NewError(ERemoveState, o))
|
|
||||||
}
|
|
||||||
delete(s.uevent, e.DevPath)
|
|
||||||
return
|
|
||||||
|
|
||||||
case uevent.KOBJ_CHANGE:
|
|
||||||
o, ok := s.uevent[e.DevPath]
|
|
||||||
if !ok {
|
|
||||||
s.reportErr(e.NewError(EBadTarget, nil))
|
|
||||||
// this suffers from the coldboot race window similar to KOBJ_MOVE,
|
|
||||||
// however this action combines driver-specific and change-specific
|
|
||||||
// environment variables and combines them with environment
|
|
||||||
// variables meant to convey state of the kobject, and it is not
|
|
||||||
// possible to reliably separate them, so this fallback avoids the
|
|
||||||
// race at the cost of including some garbage in tracked state
|
|
||||||
o = e.makeColdboot()
|
|
||||||
o.merge(e.Env)
|
|
||||||
s.uevent[e.DevPath] = o
|
|
||||||
s.dispatchIter(o, act)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
o.update(e.Env, true)
|
|
||||||
if s.handleChange != nil {
|
|
||||||
s.handleChange(o, e.Env)
|
|
||||||
}
|
|
||||||
s.dispatchIter(o, act)
|
|
||||||
return
|
|
||||||
|
|
||||||
case uevent.KOBJ_MOVE:
|
|
||||||
var o *Object
|
|
||||||
if old, ok := e.Env["DEVPATH_OLD"]; !ok {
|
|
||||||
s.reportErr(e.NewError(EMalformedMove, nil))
|
|
||||||
// not reached
|
|
||||||
o = e.makeColdboot()
|
|
||||||
} else if o, ok = s.uevent[old]; !ok {
|
|
||||||
s.reportErr(e.NewError(EBadTarget, nil))
|
|
||||||
// this generally happens during coldboot, dropping the event here
|
|
||||||
// may cause inconsistent state if the coldboot event for this
|
|
||||||
// object was generated before the bind event
|
|
||||||
delete(e.Env, "DEVPATH_OLD")
|
|
||||||
o = e.makeColdboot()
|
|
||||||
} else {
|
|
||||||
delete(s.uevent, old)
|
|
||||||
delete(e.Env, "DEVPATH_OLD")
|
|
||||||
}
|
|
||||||
o.merge(e.Env)
|
|
||||||
s.uevent[e.DevPath] = o
|
|
||||||
o.DevPath = e.DevPath
|
|
||||||
s.dispatchIter(o, act)
|
|
||||||
return
|
|
||||||
|
|
||||||
case uevent.KOBJ_ONLINE:
|
|
||||||
o, ok := s.uevent[e.DevPath]
|
|
||||||
if !ok {
|
|
||||||
s.reportErr(e.NewError(EBadTarget, nil))
|
|
||||||
// coldboot race window similar to an unexpected KOBJ_MOVE
|
|
||||||
o = e.makeColdboot()
|
|
||||||
s.uevent[e.DevPath] = o
|
|
||||||
o.merge(e.Env)
|
|
||||||
}
|
|
||||||
if !o.Offline {
|
|
||||||
s.reportErr(e.NewError(EUnexpectedOffline, o))
|
|
||||||
}
|
|
||||||
o.Offline = false
|
|
||||||
s.dispatchIter(o, act)
|
|
||||||
return
|
|
||||||
|
|
||||||
case uevent.KOBJ_OFFLINE:
|
|
||||||
o, ok := s.uevent[e.DevPath]
|
|
||||||
if !ok {
|
|
||||||
s.reportErr(e.NewError(EBadTarget, nil))
|
|
||||||
// coldboot race window similar to an unexpected KOBJ_MOVE
|
|
||||||
o = e.makeColdboot()
|
|
||||||
s.uevent[e.DevPath] = o
|
|
||||||
o.merge(e.Env)
|
|
||||||
}
|
|
||||||
if o.Offline {
|
|
||||||
s.reportErr(e.NewError(EUnexpectedOffline, o))
|
|
||||||
}
|
|
||||||
o.Offline = true
|
|
||||||
s.dispatchIter(o, act)
|
|
||||||
return
|
|
||||||
|
|
||||||
case uevent.KOBJ_BIND:
|
|
||||||
o, ok := s.uevent[e.DevPath]
|
|
||||||
if !ok {
|
|
||||||
s.reportErr(e.NewError(EBadTarget, nil))
|
|
||||||
// coldboot race window similar to an unexpected KOBJ_MOVE
|
|
||||||
o = e.makeColdboot()
|
|
||||||
s.uevent[e.DevPath] = o
|
|
||||||
}
|
|
||||||
if o.State != StateColdboot && o.State != StateNew {
|
|
||||||
s.reportErr(e.NewError(EBindState, o))
|
|
||||||
}
|
|
||||||
o.State = StateBound
|
|
||||||
o.merge(e.Env)
|
|
||||||
s.dispatchIter(o, act)
|
|
||||||
return
|
|
||||||
|
|
||||||
case uevent.KOBJ_UNBIND:
|
|
||||||
o, ok := s.uevent[e.DevPath]
|
|
||||||
if !ok {
|
|
||||||
s.reportErr(e.NewError(EBadTarget, nil))
|
|
||||||
// coldboot race window similar to an unexpected KOBJ_MOVE, but does
|
|
||||||
// not result in inconsistent state if dropped
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if o.State != StateBound {
|
|
||||||
s.reportErr(e.NewError(EUnbindState, o))
|
|
||||||
}
|
|
||||||
o.State = StateNew
|
|
||||||
o.Driver = ""
|
|
||||||
s.dispatchIter(o, act)
|
|
||||||
return
|
|
||||||
|
|
||||||
default: // not reached
|
|
||||||
s.reportErr(fmt.Errorf("invalid action %d", e.Action))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BadSequenceError is reported for an unexpected SEQNUM.
|
|
||||||
type BadSequenceError struct{ Got, Want uint64 }
|
|
||||||
|
|
||||||
func (e BadSequenceError) Error() string {
|
|
||||||
return "SEQNUM=" + strconv.FormatUint(e.Got, 10) +
|
|
||||||
", want " + strconv.FormatUint(e.Want, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume receives uevent messages and updates s to reflect state of kernel.
|
|
||||||
func (s *State) Consume(ctx context.Context, events <-chan *uevent.Message) {
|
|
||||||
if s.uevent == nil {
|
|
||||||
s.uevent = make(map[string]*Object)
|
|
||||||
}
|
|
||||||
if s.reportErr == nil {
|
|
||||||
s.reportErr = func(error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var e Event
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
|
|
||||||
case m, ok := <-events:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.Populate(s.reportErr, m)
|
|
||||||
|
|
||||||
// skip external synthetic event
|
|
||||||
if e.Synth != nil && *e.Synth != s.coldboot {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.seq == 0 {
|
|
||||||
s.seq = e.Sequence
|
|
||||||
}
|
|
||||||
if s.seq != e.Sequence {
|
|
||||||
s.reportErr(BadSequenceError{e.Sequence, s.seq})
|
|
||||||
}
|
|
||||||
s.seq++
|
|
||||||
s.processEvent(&e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user