Compare commits
5
Commits
v0.4.6
..
77d1b14830
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77d1b14830 | ||
|
|
c4d2a9471d | ||
|
|
afbb4ad6ba | ||
|
|
cb4c4a2a59 | ||
|
|
324e09aa10 |
@@ -1 +0,0 @@
|
||||
*.az linguist-language=Azalea
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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:
|
||||
name: Create distribution
|
||||
@@ -116,9 +116,9 @@ jobs:
|
||||
id: build-test
|
||||
run: >-
|
||||
export HAKUREI_REV="$(git rev-parse --short HEAD)" &&
|
||||
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' test/package.nix &&
|
||||
nix build --print-out-paths --print-build-logs ./test#dist &&
|
||||
mv test/{package.nix.old,package.nix} &&
|
||||
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' package.nix &&
|
||||
nix build --print-out-paths --print-build-logs .#dist &&
|
||||
mv package.nix.old package.nix &&
|
||||
echo "rev=$HAKUREI_REV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload test build
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# produced by tools and text editors
|
||||
*.swp
|
||||
*.qcow2
|
||||
*.test
|
||||
*.out
|
||||
@@ -14,6 +13,3 @@
|
||||
|
||||
# cmd/dist default destination
|
||||
/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>
|
||||
<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/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>
|
||||
</p>
|
||||
|
||||
@@ -57,4 +57,9 @@ package [internal/rosa](https://pkg.go.dev/hakurei.app/internal/rosa).
|
||||
- [fuse](https://github.com/libfuse/libfuse) to implement the filesystem.
|
||||
|
||||
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).
|
||||
|
||||
+2
-8
@@ -20,8 +20,8 @@ func (e AbsoluteError) Error() string {
|
||||
}
|
||||
|
||||
func (e AbsoluteError) Is(target error) bool {
|
||||
ce, ok := errors.AsType[AbsoluteError](target)
|
||||
if !ok {
|
||||
var ce AbsoluteError
|
||||
if !errors.As(target, &ce) {
|
||||
return errors.Is(target, syscall.EINVAL)
|
||||
}
|
||||
return e == ce
|
||||
@@ -31,8 +31,6 @@ func (e AbsoluteError) Is(target error) bool {
|
||||
type Absolute struct{ pathname unique.Handle[string] }
|
||||
|
||||
var (
|
||||
_ fmt.GoStringer = new(Absolute)
|
||||
|
||||
_ encoding.TextAppender = new(Absolute)
|
||||
_ encoding.TextMarshaler = new(Absolute)
|
||||
_ encoding.TextUnmarshaler = new(Absolute)
|
||||
@@ -42,10 +40,6 @@ var (
|
||||
_ 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.
|
||||
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
|
||||
|
||||
|
||||
-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
+12
-32
@@ -18,13 +18,8 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"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.
|
||||
func getenv(key, fallback string) string {
|
||||
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
|
||||
// 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...)
|
||||
if env != nil {
|
||||
cmd.Env = append(cmd.Environ(), env...)
|
||||
}
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
@@ -52,12 +44,11 @@ var comp []byte
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("")
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
verbose := os.Getenv("VERBOSE") != ""
|
||||
runTests := os.Getenv("HAKUREI_DIST_MAKE") == ""
|
||||
version = getenv("HAKUREI_VERSION", strings.TrimSpace(version))
|
||||
prefix := getenv("PREFIX", "/usr/local")
|
||||
version := getenv("HAKUREI_VERSION", "untagged")
|
||||
prefix := getenv("PREFIX", "/usr")
|
||||
destdir := getenv("DESTDIR", "dist")
|
||||
|
||||
if verbose {
|
||||
@@ -95,37 +86,26 @@ func main() {
|
||||
verboseFlag = "-buildvcs=false"
|
||||
}
|
||||
|
||||
log.Printf("Building hakurei %s for %s/%s.", version, runtime.GOOS, runtime.GOARCH)
|
||||
mustRun(ctx, nil, "go", "generate", "./...")
|
||||
log.Printf("Building hakurei for %s/%s.", runtime.GOOS, runtime.GOARCH)
|
||||
mustRun(ctx, "go", "generate", "./...")
|
||||
mustRun(
|
||||
ctx, nil, "go", "build",
|
||||
ctx, "go", "build",
|
||||
"-trimpath",
|
||||
verboseFlag, "-o", s,
|
||||
"-ldflags=-s -w "+
|
||||
"-buildid= -linkmode external -extldflags=-static "+
|
||||
"-X hakurei.app/internal/info.buildVersion="+version+" "+
|
||||
"-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+
|
||||
"-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 hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu "+
|
||||
"-X main.hakureiPath="+prefix+"/bin/hakurei",
|
||||
"./cmd/hsu",
|
||||
"./...",
|
||||
)
|
||||
|
||||
log.Println()
|
||||
|
||||
if runTests {
|
||||
log.Println("##### Testing Hakurei.")
|
||||
mustRun(
|
||||
ctx, nil, "go", "test",
|
||||
ctx, "go", "test",
|
||||
"-ldflags=-buildid= -linkmode external -extldflags=-static",
|
||||
"./...",
|
||||
)
|
||||
@@ -217,7 +197,7 @@ func main() {
|
||||
mustWriteFile("comp/", nil, os.ModeDir|0755)
|
||||
mustWriteFile("comp/_hakurei", comp, 0644)
|
||||
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/sharefs" "${DESTDIR}`+prefix+`/bin/sharefs"
|
||||
@@ -230,7 +210,7 @@ fi
|
||||
install -vDm0644 "comp/_hakurei" "${DESTDIR}`+prefix+`/share/zsh/site-functions/_hakurei"
|
||||
`), 0755)
|
||||
|
||||
mustWriteFromPath("LICENSE", "LICENSE", 0)
|
||||
mustWriteFromPath("README.md", "README.md", 0)
|
||||
mustWriteFile("hsurc.default", []byte("1000 0"), 0400)
|
||||
mustWriteFromPath("bin/hsu", filepath.Join(s, "hsu"), 04511)
|
||||
for _, name := range []string{
|
||||
|
||||
+20
-155
@@ -5,93 +5,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"slices"
|
||||
"strings"
|
||||
. "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() {
|
||||
runtime.LockOSThread()
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("earlyinit: ")
|
||||
|
||||
var (
|
||||
option map[string]string
|
||||
@@ -109,44 +33,15 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
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(
|
||||
if err := Mount(
|
||||
"devtmpfs",
|
||||
"/dev/",
|
||||
"devtmpfs",
|
||||
MS_NOSUID|MS_NOEXEC,
|
||||
"",
|
||||
))
|
||||
must(os.Mkdir("/dev/pts/", 0))
|
||||
mustSyscall("mount devpts", Mount(
|
||||
"devpts",
|
||||
"/dev/pts/",
|
||||
"devpts",
|
||||
MS_NOSUID|MS_NOEXEC,
|
||||
"mode=620,ptmxmode=666",
|
||||
))
|
||||
must(os.Mkdir("/dev/shm/", 0))
|
||||
mustSyscall("mount shm", Mount(
|
||||
"shm",
|
||||
"/dev/shm/",
|
||||
"tmpfs",
|
||||
MS_NOSUID|MS_NODEV,
|
||||
"",
|
||||
))
|
||||
); err != nil {
|
||||
log.Fatalf("cannot mount devtmpfs: %v", err)
|
||||
}
|
||||
|
||||
// The kernel might be unable to set up the console. When that happens,
|
||||
// printk is called with "Warning: unable to open an initial console."
|
||||
@@ -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
|
||||
mustSyscall("remount root", Mount(
|
||||
"",
|
||||
@@ -261,6 +113,19 @@ func main() {
|
||||
[]byte("/system/lib/firmware"),
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -19,9 +19,10 @@ func TestHelp(t *testing.T) {
|
||||
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
|
||||
exec Configure and start a permissive container
|
||||
show Show live or local instance configuration
|
||||
@@ -34,14 +35,10 @@ commands:
|
||||
`,
|
||||
},
|
||||
{
|
||||
"exec", []string{"exec", "-h"}, `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> [<args>]
|
||||
"exec", []string{"exec", "-h"}, `
|
||||
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]
|
||||
|
||||
flags:
|
||||
Flags:
|
||||
-X Enable direct connection to X11
|
||||
-a int
|
||||
Application identity
|
||||
|
||||
+5
-6
@@ -7,8 +7,7 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// decodeJSON decodes json from r and stores it in v. A non-nil error results in
|
||||
// a call to fatal.
|
||||
// decodeJSON decodes json from r and stores it in v. A non-nil error results in a call to fatal.
|
||||
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any) {
|
||||
err := json.NewDecoder(r).Decode(v)
|
||||
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 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
|
||||
fatal("cannot encode json for " + e.Type.String() + ": " + e.Err.Error())
|
||||
fatal("cannot encode json for " + marshalerError.Type.String() + ": " + marshalerError.Err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does
|
||||
// not need to be handled
|
||||
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does not need to be handled
|
||||
fatal("cannot write json: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestPrintShowInstance(t *testing.T) {
|
||||
Identity: 9 (org.chromium.Chromium)
|
||||
Enablements: wayland, dbus, pipewire
|
||||
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
|
||||
Hostname: localhost
|
||||
Path: /run/current-system/sw/bin/chromium
|
||||
@@ -161,7 +161,7 @@ App
|
||||
Identity: 9 (org.chromium.Chromium)
|
||||
Enablements: wayland, dbus, pipewire
|
||||
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
|
||||
Hostname: localhost
|
||||
Path: /run/current-system/sw/bin/chromium
|
||||
@@ -354,9 +354,7 @@ App
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"map_real_uid": true,
|
||||
"noplace": true,
|
||||
"device": true,
|
||||
"cover_run": true,
|
||||
"share_runtime": true,
|
||||
"share_tmpdir": true
|
||||
},
|
||||
@@ -507,9 +505,7 @@ App
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"map_real_uid": true,
|
||||
"noplace": true,
|
||||
"device": true,
|
||||
"cover_run": true,
|
||||
"share_runtime": true,
|
||||
"share_tmpdir": true
|
||||
}
|
||||
@@ -707,9 +703,7 @@ func TestPrintPs(t *testing.T) {
|
||||
"tty": true,
|
||||
"multiarch": true,
|
||||
"map_real_uid": true,
|
||||
"noplace": true,
|
||||
"device": true,
|
||||
"cover_run": true,
|
||||
"share_runtime": true,
|
||||
"share_tmpdir": true
|
||||
},
|
||||
|
||||
+23
-1
@@ -21,6 +21,15 @@
|
||||
// following paragraphs are considered an internal detail and not covered by the
|
||||
// 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
|
||||
// [container] infrastructure, the environment is used for parameters from the
|
||||
// parent process.
|
||||
@@ -53,6 +62,7 @@ import (
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
@@ -97,6 +107,18 @@ func main() {
|
||||
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
|
||||
if s, err := os.Stat(hsuConfPath); err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -183,7 +205,7 @@ func main() {
|
||||
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
||||
}
|
||||
|
||||
if err := syscall.Exec(hakureiPath, []string{
|
||||
if err := syscall.Exec(toolPath, []string{
|
||||
"hakurei",
|
||||
"shim",
|
||||
}, []string{
|
||||
|
||||
@@ -8,7 +8,7 @@ buildGoModule {
|
||||
pname = "${hakurei.pname}-hsu";
|
||||
inherit (hakurei) version;
|
||||
|
||||
src = ../cmd/hsu;
|
||||
src = ./.;
|
||||
inherit (hakurei) vendorHash;
|
||||
env.CGO_ENABLED = 0;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("irdump: ")
|
||||
|
||||
var (
|
||||
flagOutput string
|
||||
flagReal bool
|
||||
flagHeader bool
|
||||
flagForce bool
|
||||
flagRaw bool
|
||||
)
|
||||
c := command.New(os.Stderr, log.Printf, "irdump", func(args []string) (err error) {
|
||||
var input *os.File
|
||||
if len(args) != 1 {
|
||||
return errors.New("irdump requires 1 argument")
|
||||
}
|
||||
if input, err = os.Open(args[0]); err != nil {
|
||||
return
|
||||
}
|
||||
defer input.Close()
|
||||
|
||||
var output *os.File
|
||||
if flagOutput == "" {
|
||||
output = os.Stdout
|
||||
} else {
|
||||
defer output.Close()
|
||||
if output, err = os.Create(flagOutput); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var out string
|
||||
if out, err = pkg.Disassemble(input, flagReal, flagHeader, flagForce, flagRaw); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = output.WriteString(out); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}).Flag(
|
||||
&flagOutput,
|
||||
"o", command.StringFlag(""),
|
||||
"Output file for asm (leave empty for stdout)",
|
||||
).Flag(
|
||||
&flagReal,
|
||||
"r", command.BoolFlag(false),
|
||||
"skip label generation; idents print real value",
|
||||
).Flag(
|
||||
&flagHeader,
|
||||
"H", command.BoolFlag(false),
|
||||
"display artifact headers",
|
||||
).Flag(
|
||||
&flagForce,
|
||||
"f", command.BoolFlag(false),
|
||||
"force display (skip validations)",
|
||||
).Flag(
|
||||
&flagRaw,
|
||||
"R", command.BoolFlag(false),
|
||||
"don't format output",
|
||||
)
|
||||
|
||||
c.MustParse(os.Args[1:], func(err error) {
|
||||
log.Fatal(err)
|
||||
})
|
||||
}
|
||||
+28
-85
@@ -2,15 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/rosa"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
@@ -22,29 +20,17 @@ type cache struct {
|
||||
// Should generally not be used directly.
|
||||
c *pkg.Cache
|
||||
|
||||
attr pkg.CacheAttr
|
||||
cures, jobs int
|
||||
// Primarily to work around missing landlock LSM.
|
||||
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
|
||||
}
|
||||
|
||||
// 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()
|
||||
base string
|
||||
}
|
||||
|
||||
// open opens the underlying [pkg.Cache].
|
||||
@@ -53,35 +39,22 @@ func (cache *cache) open() (err error) {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
var hostname string
|
||||
if hostname, err = os.Hostname(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var base *check.Absolute
|
||||
if cache.base, err = filepath.Abs(cache.base); err != nil {
|
||||
return
|
||||
} else if base, err = check.NewAbs(cache.base); err != nil {
|
||||
return
|
||||
}
|
||||
var notify chan bool
|
||||
|
||||
var flags int
|
||||
if cache.idle {
|
||||
cache.attr.Flags |= pkg.CSchedIdle
|
||||
flags |= pkg.CSchedIdle
|
||||
}
|
||||
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
|
||||
flags |= pkg.CSuppressInit
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
@@ -103,56 +76,16 @@ func (cache *cache) open() (err error) {
|
||||
cache.c, err = pkg.Open(
|
||||
cache.ctx,
|
||||
cache.msg,
|
||||
flags,
|
||||
cache.cures,
|
||||
cache.jobs,
|
||||
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)
|
||||
@@ -161,9 +94,24 @@ func (cache *cache) open() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
for arch, entry := range rosa.Arches(pathname) {
|
||||
pkg.RegisterArch(arch, entry)
|
||||
}
|
||||
pkg.RegisterArch("riscv64", container.BinfmtEntry{
|
||||
Offset: 0,
|
||||
Magic: "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00",
|
||||
Mask: "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
|
||||
Interpreter: pathname.Append(
|
||||
"system/bin",
|
||||
"qemu-riscv64",
|
||||
),
|
||||
})
|
||||
pkg.RegisterArch("arm64", container.BinfmtEntry{
|
||||
Offset: 0,
|
||||
Magic: "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00",
|
||||
Mask: "\xff\xff\xff\xff\xff\xff\xff\xfc\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
|
||||
Interpreter: pathname.Append(
|
||||
"system/bin",
|
||||
"qemu-aarch64",
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
@@ -173,11 +121,6 @@ func (cache *cache) open() (err error) {
|
||||
func (cache *cache) Close() {
|
||||
if cache.c != nil {
|
||||
cache.c.Close()
|
||||
if cache.attr.Notify != nil {
|
||||
cache.msg.Suspend()
|
||||
_, _ = cache.msg.GetLogger().Writer().Write([]byte("\x1b[23;0t"))
|
||||
cache.msg.Resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ func TestNoReply(t *testing.T) {
|
||||
c, err := pkg.Open(
|
||||
t.Context(),
|
||||
message.New(log.New(os.Stderr, "cir: ", 0)),
|
||||
0, 0, 0,
|
||||
check.MustAbs(t.TempDir()),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Open: error = %v", err)
|
||||
@@ -63,7 +63,7 @@ func TestNoReply(t *testing.T) {
|
||||
}
|
||||
|
||||
<-done
|
||||
wantIdent := pkg.MustDecode("bpVsJ69hdKPBntMh_u777g7-quFNVmMlBJQOlM_thiQa9JnOFLAbe5OlR4zNjZ32")
|
||||
wantIdent := pkg.MustDecode("fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG")
|
||||
if gotIdent := c.Ident(a).Value(); gotIdent != wantIdent {
|
||||
t.Errorf(
|
||||
"cureFromIR: %s, want %s",
|
||||
@@ -124,7 +124,7 @@ func TestDaemon(t *testing.T) {
|
||||
cancel()
|
||||
<-done
|
||||
|
||||
const want = "bpVsJ69hdKPBntMh_u777g7-quFNVmMlBJQOlM_thiQa9JnOFLAbe5OlR4zNjZ32"
|
||||
const want = "fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG"
|
||||
if got := filepath.Base(p.String()); got != want {
|
||||
t.Errorf("cureRemote: %s, want %s", got, want)
|
||||
}
|
||||
@@ -132,8 +132,8 @@ func TestDaemon(t *testing.T) {
|
||||
wantLog := []string{
|
||||
"",
|
||||
"daemon: aborting all pending cures",
|
||||
"daemon: attempting to cancel invalid artifact 58Vy_5beLZCvZX__KzRKyIE6vATleaZP1ZBxbqoWnekz9aZ1zkxNgcIUFI5V1_Jf",
|
||||
"daemon: fulfilled artifact " + want,
|
||||
"daemon: attempting to cancel invalid artifact kQm9fmnCmXST1-MMmxzcau2oKZCXXrlZydo4PkeV5hO_2PKfeC8t98hrbV_ZZx_j",
|
||||
"daemon: fulfilled artifact fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG",
|
||||
}
|
||||
gotLog := strings.Split(buf.String(), "\n")
|
||||
slices.Sort(gotLog)
|
||||
|
||||
+10
-17
@@ -6,7 +6,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"unique"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/rosa"
|
||||
@@ -21,10 +20,7 @@ func commandInfo(
|
||||
r *rosa.Report,
|
||||
) (err error) {
|
||||
if len(args) == 0 {
|
||||
for _, h := range rosa.Native().CollectAll() {
|
||||
fmt.Println(h)
|
||||
}
|
||||
return nil
|
||||
return errors.New("info requires at least 1 argument")
|
||||
}
|
||||
|
||||
// recovered by HandleAccess
|
||||
@@ -39,19 +35,17 @@ func commandInfo(
|
||||
}
|
||||
}
|
||||
|
||||
t := rosa.Native().Std()
|
||||
for i, name := range args {
|
||||
handle := rosa.ArtifactH(unique.Make(name))
|
||||
if meta, a := t.Load(handle); meta == nil {
|
||||
if p, ok := rosa.ResolveName(name); !ok {
|
||||
return fmt.Errorf("unknown artifact %q", name)
|
||||
} else {
|
||||
var suffix string
|
||||
|
||||
if meta.Version != rosa.Unversioned {
|
||||
suffix += "-" + meta.Version
|
||||
if version := rosa.Std.Version(p); version != rosa.Unversioned {
|
||||
suffix += "-" + version
|
||||
}
|
||||
mustPrintln("name : " + name + suffix)
|
||||
|
||||
meta := rosa.GetMetadata(p)
|
||||
mustPrintln("description : " + meta.Description)
|
||||
if meta.Website != "" {
|
||||
mustPrintln("website : " +
|
||||
@@ -60,10 +54,9 @@ func commandInfo(
|
||||
if len(meta.Dependencies) > 0 {
|
||||
mustPrint("depends on :")
|
||||
for _, d := range meta.Dependencies {
|
||||
_meta, _ := rosa.Native().Std().MustLoad(d)
|
||||
s := _meta.Name
|
||||
if _meta.Version != rosa.Unversioned {
|
||||
s += "-" + _meta.Version
|
||||
s := rosa.GetMetadata(d).Name
|
||||
if version := rosa.Std.Version(d); version != rosa.Unversioned {
|
||||
s += "-" + version
|
||||
}
|
||||
mustPrint(" " + s)
|
||||
}
|
||||
@@ -75,7 +68,7 @@ func commandInfo(
|
||||
if r == nil {
|
||||
var f io.ReadSeekCloser
|
||||
err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
f, err = cache.OpenStatus(a)
|
||||
f, err = cache.OpenStatus(rosa.Std.Load(p))
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
@@ -94,7 +87,7 @@ func commandInfo(
|
||||
}
|
||||
}
|
||||
} 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 {
|
||||
mustPrintln(
|
||||
statusPrefix + "not in report",
|
||||
|
||||
+13
-22
@@ -10,7 +10,6 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unique"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
@@ -21,14 +20,6 @@ import (
|
||||
func TestInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_t := rosa.Native().Std()
|
||||
qemuMeta, _ := _t.Load(rosa.H("qemu"))
|
||||
glibMeta, _ := _t.Load(rosa.H("glib"))
|
||||
zlibMeta, zlib := _t.Load(rosa.H("zlib"))
|
||||
zstdMeta, _ := _t.Load(rosa.H("zstd"))
|
||||
hakureiMeta, _ := _t.Load(rosa.H("hakurei"))
|
||||
hakureiDistMeta, _ := _t.Load(rosa.H("hakurei-dist"))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
@@ -38,24 +29,24 @@ func TestInfo(t *testing.T) {
|
||||
wantErr any
|
||||
}{
|
||||
{"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
|
||||
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},
|
||||
|
||||
{"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
|
||||
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)
|
||||
website : https://hakurei.app
|
||||
`, nil},
|
||||
|
||||
{"nonexistent", []string{"zlib", "\x00"}, nil, "", `
|
||||
name : zlib-` + zlibMeta.Version + `
|
||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||
description : lossless data-compression library
|
||||
website : https://zlib.net
|
||||
|
||||
@@ -65,12 +56,12 @@ website : https://zlib.net
|
||||
"zstd": "internal/pkg (amd64) on satori\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
|
||||
website : https://zlib.net
|
||||
status : not yet cured
|
||||
|
||||
name : zstd-` + zstdMeta.Version + `
|
||||
name : zstd-` + rosa.Std.Version(rosa.Zstd) + `
|
||||
description : a fast compression algorithm
|
||||
website : https://facebook.github.io/zstd
|
||||
status : internal/pkg (amd64) on satori
|
||||
@@ -79,19 +70,19 @@ status : internal/pkg (amd64) on satori
|
||||
{"status cache perm", []string{"zlib"}, map[string]string{
|
||||
"zlib": "\x00",
|
||||
}, "", `
|
||||
name : zlib-` + zlibMeta.Version + `
|
||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||
description : lossless data-compression library
|
||||
website : https://zlib.net
|
||||
`, func(cm *cache) error {
|
||||
return &os.PathError{
|
||||
Op: "open",
|
||||
Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(zlib).Value())),
|
||||
Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(rosa.Std.Load(rosa.Zlib)).Value())),
|
||||
Err: syscall.EACCES,
|
||||
}
|
||||
}},
|
||||
|
||||
{"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
|
||||
website : https://zlib.net
|
||||
status : not in report
|
||||
@@ -140,8 +131,8 @@ status : not in report
|
||||
|
||||
if tc.status != nil {
|
||||
for name, status := range tc.status {
|
||||
_, a := _t.Load(rosa.ArtifactH(unique.Make(name)))
|
||||
if a == nil {
|
||||
p, ok := rosa.ResolveName(name)
|
||||
if !ok {
|
||||
t.Fatalf("invalid name %q", name)
|
||||
}
|
||||
perm := os.FileMode(0400)
|
||||
@@ -152,7 +143,7 @@ status : not in report
|
||||
return os.WriteFile(filepath.Join(
|
||||
cm.base,
|
||||
"status",
|
||||
pkg.Encode(cache.Ident(a).Value()),
|
||||
pkg.Encode(cache.Ident(rosa.Std.Load(p)).Value()),
|
||||
), unsafe.Slice(unsafe.StringData(status), len(status)), perm)
|
||||
}); err != nil {
|
||||
t.Fatalf("Do: error = %v", err)
|
||||
|
||||
@@ -30,7 +30,7 @@ var (
|
||||
// handleInfo writes constant system information.
|
||||
func handleInfo(w http.ResponseWriter, _ *http.Request) {
|
||||
infoPayloadOnce.Do(func() {
|
||||
infoPayload.Count = len(rosa.Native().Collect())
|
||||
infoPayload.Count = int(rosa.PresetUnexportedStart)
|
||||
infoPayload.HakureiVersion = info.Version()
|
||||
})
|
||||
// TODO(mae): cache entire response if no additional fields are planned
|
||||
@@ -91,7 +91,7 @@ func (index *packageIndex) handleGet(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
||||
http.Error(
|
||||
w, "index must be an integer between 0 and "+
|
||||
strconv.Itoa(len(index.sorts[0])-1),
|
||||
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
@@ -125,7 +125,7 @@ func (index *packageIndex) handleSearch(w http.ResponseWriter, r *http.Request)
|
||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
||||
http.Error(
|
||||
w, "index must be an integer between 0 and "+
|
||||
strconv.Itoa(len(index.sorts[0])-1),
|
||||
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -3,6 +3,7 @@ package pkgserver
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
@@ -31,7 +32,7 @@ func TestAPIInfo(t *testing.T) {
|
||||
checkPayload(t, resp, struct {
|
||||
Count int `json:"count"`
|
||||
HakureiVersion string `json:"hakurei_version"`
|
||||
}{len(rosa.Native().Collect()), info.Version()})
|
||||
}{int(rosa.PresetUnexportedStart), info.Version()})
|
||||
}
|
||||
|
||||
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.Parallel()
|
||||
checkValidate(
|
||||
t, "limit=1&sort=0&index", 0, count-1,
|
||||
"index must be an integer between 0 and "+strconv.Itoa(count-1),
|
||||
t, "limit=1&sort=0&index", 0, int(rosa.PresetUnexportedStart-1),
|
||||
"index must be an integer between 0 and "+strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -108,4 +108,74 @@ func TestAPIGet(t *testing.T) {
|
||||
"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 {
|
||||
Values []*metadata `json:"values"`
|
||||
}) bool {
|
||||
return 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=1&sort=0", []*metadata{
|
||||
{
|
||||
Metadata: rosa.GetMetadata(1),
|
||||
Version: rosa.Std.Version(1),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(2),
|
||||
Version: rosa.Std.Version(2),
|
||||
},
|
||||
})
|
||||
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),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ const (
|
||||
|
||||
// packageIndex refers to metadata by name and various sort orders.
|
||||
type packageIndex struct {
|
||||
sorts [sortOrderEnd + 1][]*metadata
|
||||
sorts [sortOrderEnd + 1][rosa.PresetUnexportedStart]*metadata
|
||||
names map[string]*metadata
|
||||
search searchCache
|
||||
// Taken from [rosa.Report] if available.
|
||||
@@ -32,11 +32,11 @@ type packageIndex struct {
|
||||
|
||||
// metadata holds [rosa.Metadata] extended with additional information.
|
||||
type metadata struct {
|
||||
handle rosa.ArtifactH
|
||||
p rosa.PArtifact
|
||||
*rosa.Metadata
|
||||
|
||||
// Copied from [rosa.Metadata], [rosa.Unversioned] is equivalent to the zero
|
||||
// value. Otherwise, the zero value is invalid.
|
||||
// Populated via [rosa.Toolchain.Version], [rosa.Unversioned] is equivalent
|
||||
// to the zero value. Otherwise, the zero value is invalid.
|
||||
Version string `json:"version,omitempty"`
|
||||
// Output data size, available if present in report.
|
||||
Size int64 `json:"size,omitempty"`
|
||||
@@ -56,17 +56,15 @@ func (index *packageIndex) populate(report *rosa.Report) (err error) {
|
||||
index.handleAccess = report.HandleAccess
|
||||
}
|
||||
|
||||
handles := rosa.Native().Collect()
|
||||
work := make([]*metadata, len(handles))
|
||||
var work [rosa.PresetUnexportedStart]*metadata
|
||||
index.names = make(map[string]*metadata)
|
||||
ir := pkg.NewIR()
|
||||
for i, handle := range handles {
|
||||
meta, a := rosa.Native().Std().MustLoad(handle)
|
||||
for p := range rosa.PresetUnexportedStart {
|
||||
m := metadata{
|
||||
handle: handle,
|
||||
p: p,
|
||||
|
||||
Metadata: meta,
|
||||
Version: meta.Version,
|
||||
Metadata: rosa.GetMetadata(p),
|
||||
Version: rosa.Std.Version(p),
|
||||
}
|
||||
if m.Version == "" {
|
||||
return errors.New("invalid version from " + m.Name)
|
||||
@@ -76,32 +74,32 @@ func (index *packageIndex) populate(report *rosa.Report) (err error) {
|
||||
}
|
||||
|
||||
if report != nil {
|
||||
id := ir.Ident(a)
|
||||
id := ir.Ident(rosa.Std.Load(p))
|
||||
m.ids = pkg.Encode(id.Value())
|
||||
m.status, m.Size = report.ArtifactOf(id)
|
||||
m.HasReport = m.Size >= 0
|
||||
}
|
||||
|
||||
work[i] = &m
|
||||
work[p] = &m
|
||||
index.names[m.Name] = &m
|
||||
}
|
||||
|
||||
index.sorts[declarationAscending] = work
|
||||
index.sorts[declarationDescending] = slices.Clone(work)
|
||||
index.sorts[declarationDescending] = work
|
||||
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 {
|
||||
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][:])
|
||||
|
||||
index.sorts[sizeAscending] = slices.Clone(work)
|
||||
index.sorts[sizeAscending] = work
|
||||
slices.SortFunc(index.sorts[sizeAscending][:], func(a, b *metadata) int {
|
||||
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][:])
|
||||
|
||||
return
|
||||
|
||||
@@ -74,7 +74,7 @@ func (s *searchCache) clean() {
|
||||
}
|
||||
func indexsum(in [][]int) int {
|
||||
sum := 0
|
||||
for i := range in {
|
||||
for i := 0; i < len(in); i++ {
|
||||
sum += in[i][1] - in[i][0]
|
||||
}
|
||||
return sum
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="icon" href="https://hakurei.app/favicon.ico"/>
|
||||
<title>Rosa OS Packages</title>
|
||||
<title>Hakurei PkgServer</title>
|
||||
<script src="index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Rosa OS Packages</h1>
|
||||
<h1>Hakurei PkgServer</h1>
|
||||
<div class="top-controls" id="top-controls-regular">
|
||||
<p>Showing entries <span id="entry-counter"></span>.</p>
|
||||
<span id="search-bar">
|
||||
@@ -55,4 +54,4 @@
|
||||
</footer>
|
||||
<script>main();</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
+201
-579
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
rosa.Native().DropCaches("", rosa.OptLLVMNoLTO)
|
||||
rosa.DropCaches("", rosa.OptLLVMNoLTO)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -35,10 +35,10 @@ func TestCureAll(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
for i := range rosa.PresetEnd {
|
||||
p := rosa.PArtifact(i)
|
||||
t.Run(rosa.GetMetadata(p).Name, func(t *testing.T) {
|
||||
_, err := cureRemote(t.Context(), &addr, rosa.Std.Load(p), 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
+2
-2
@@ -508,8 +508,8 @@ func _main(s ...string) (exitCode int) {
|
||||
|
||||
if !z.AllowOrphan {
|
||||
if err := z.Wait(); err != nil {
|
||||
exitError, ok := errors.AsType[*exec.ExitError](err)
|
||||
if !ok || exitError == nil {
|
||||
var exitError *exec.ExitError
|
||||
if !errors.As(err, &exitError) || exitError == nil {
|
||||
log.Println(err)
|
||||
return 5
|
||||
}
|
||||
|
||||
+2
-3
@@ -13,7 +13,6 @@ func New(output io.Writer, logf LogFunc, name string, early HandlerFunc) Command
|
||||
return c
|
||||
}
|
||||
|
||||
// newNode initialises a subcommand tree and returns its address.
|
||||
func newNode(output io.Writer, logf LogFunc, name, usage string) *node {
|
||||
n := &node{
|
||||
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.Usage = func() {
|
||||
_ = n.writeHelp()
|
||||
if len(n.suffix) > 0 {
|
||||
_, _ = fmt.Fprintln(output, "flags:")
|
||||
if n.suffix.Len() > 0 {
|
||||
_, _ = fmt.Fprintln(output, "Flags:")
|
||||
n.set.PrintDefaults()
|
||||
_, _ = fmt.Fprintln(output)
|
||||
}
|
||||
|
||||
+4
-16
@@ -18,14 +18,8 @@ func TestBuild(t *testing.T) {
|
||||
|
||||
t.Run("direct zero length", func(t *testing.T) {
|
||||
wantPanic := "invalid subcommand"
|
||||
t.Run("zero length name", func(t *testing.T) {
|
||||
defer checkRecover(t, "Command", wantPanic)
|
||||
c.Command("", "usage", stubHandler)
|
||||
})
|
||||
t.Run("zero length usage", func(t *testing.T) {
|
||||
defer checkRecover(t, "Command", wantPanic)
|
||||
c.Command("name", "", stubHandler)
|
||||
})
|
||||
t.Run("zero length name", func(t *testing.T) { defer checkRecover(t, "Command", wantPanic); c.Command("", "usage", stubHandler) })
|
||||
t.Run("zero length usage", func(t *testing.T) { defer checkRecover(t, "Command", wantPanic); c.Command("name", "", stubHandler) })
|
||||
})
|
||||
|
||||
t.Run("direct adopt unique names", func(t *testing.T) {
|
||||
@@ -40,14 +34,8 @@ func TestBuild(t *testing.T) {
|
||||
|
||||
t.Run("zero length", func(t *testing.T) {
|
||||
wantPanic := "invalid subcommand tree"
|
||||
t.Run("zero length name", func(t *testing.T) {
|
||||
defer checkRecover(t, "New", wantPanic)
|
||||
c.New("", "usage")
|
||||
})
|
||||
t.Run("zero length usage", func(t *testing.T) {
|
||||
defer checkRecover(t, "New", wantPanic)
|
||||
c.New("name", "")
|
||||
})
|
||||
t.Run("zero length name", func(t *testing.T) { defer checkRecover(t, "New", wantPanic); c.New("", "usage") })
|
||||
t.Run("zero length usage", func(t *testing.T) { defer checkRecover(t, "New", wantPanic); c.New("name", "") })
|
||||
})
|
||||
|
||||
t.Run("direct adopt unique names", func(t *testing.T) {
|
||||
|
||||
+6
-26
@@ -6,43 +6,36 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UsageInternal is a special usage string that hides the command from the
|
||||
// generated help message.
|
||||
const UsageInternal = "\x00"
|
||||
// UsageInternal causes the command to be hidden from help text when set as the usage string.
|
||||
const UsageInternal = "internal"
|
||||
|
||||
type (
|
||||
// HandlerFunc is called when matching a directly handled subcommand tree.
|
||||
HandlerFunc = func(args []string) error
|
||||
|
||||
// LogFunc is the function signature of a printf function. The zero value
|
||||
// implies [log.Printf].
|
||||
// LogFunc is the function signature of a printf function.
|
||||
LogFunc = func(format string, a ...any)
|
||||
|
||||
// FlagDefiner is a deferred flag definer value, usually encapsulating the
|
||||
// default value.
|
||||
// FlagDefiner is a deferred flag definer value, usually encapsulating the default value.
|
||||
FlagDefiner interface {
|
||||
// Define defines the flag in set.
|
||||
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 defines a generic flag type in Node's flag set.
|
||||
Flag(p any, name string, value FlagDefiner, usage string) T
|
||||
}
|
||||
|
||||
// A Command is the root of a command tree.
|
||||
Command interface {
|
||||
Parse(arguments []string) error
|
||||
|
||||
// MustParse determines exit outcomes for Parse errors and calls
|
||||
// handleError if [HandlerFunc] returns a non-nil error.
|
||||
// MustParse determines exit outcomes for Parse errors
|
||||
// and calls handleError if [HandlerFunc] returns a non-nil error.
|
||||
MustParse(arguments []string, handleError func(error))
|
||||
|
||||
baseNode[Command]
|
||||
}
|
||||
|
||||
// A Node is a subcommand under a [Command].
|
||||
Node baseNode[Node]
|
||||
|
||||
baseNode[T any] interface {
|
||||
@@ -60,16 +53,3 @@ type (
|
||||
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 {
|
||||
var buf strings.Builder
|
||||
value.Define(&buf, n.set, p, name, usage)
|
||||
s := buf.String()
|
||||
if len(s) > 0 && s[0] == ' ' {
|
||||
s = s[1:]
|
||||
}
|
||||
n.suffix = append(n.suffix, s)
|
||||
value.Define(&n.suffix, n.set, p, name, usage)
|
||||
return n
|
||||
}
|
||||
|
||||
|
||||
+3
-21
@@ -1,7 +1,6 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -14,31 +13,14 @@ var ErrHelp = errors.New("help requested")
|
||||
func (n *node) PrintHelp() { _ = n.writeHelp() }
|
||||
|
||||
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,
|
||||
"usage: %s [-h | --help]%s <command> [<args>]\n",
|
||||
prefix, &buf,
|
||||
"\nUsage:\t%s [-h | --help]%s COMMAND [OPTIONS]\n",
|
||||
strings.Join(append(n.prefix, n.name), " "), &n.suffix,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
+2
-6
@@ -3,9 +3,9 @@ package command
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A node represents a command.
|
||||
type node struct {
|
||||
child, next *node
|
||||
name, usage string
|
||||
@@ -13,16 +13,13 @@ type node struct {
|
||||
out io.Writer
|
||||
logf LogFunc
|
||||
|
||||
// Names of commands preceding node.
|
||||
prefix []string
|
||||
// Short user-facing representations of flags received by node.
|
||||
suffix []string
|
||||
suffix strings.Builder
|
||||
|
||||
f HandlerFunc
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// adopt adds v as the last child of n.
|
||||
func (n *node) adopt(v *node) bool {
|
||||
if n.child != nil {
|
||||
return n.child.append(v)
|
||||
@@ -31,7 +28,6 @@ func (n *node) adopt(v *node) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// append adds v as the last sibling of n.
|
||||
func (n *node) append(v *node) bool {
|
||||
if n.name == v.name {
|
||||
return false
|
||||
|
||||
+2
-2
@@ -91,8 +91,8 @@ func (n *node) MustParse(arguments []string, handleError func(error)) {
|
||||
case ErrEmptyTree:
|
||||
os.Exit(1)
|
||||
default:
|
||||
flagError, ok := errors.AsType[FlagError](err)
|
||||
if !ok { // returned by HandlerFunc
|
||||
var flagError FlagError
|
||||
if !errors.As(err, &flagError) { // returned by HandlerFunc
|
||||
handleError(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
+22
-18
@@ -70,7 +70,7 @@ func TestParse(t *testing.T) {
|
||||
"d=0 out of order string flag",
|
||||
buildTestCommand,
|
||||
[]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"),
|
||||
},
|
||||
{
|
||||
@@ -120,7 +120,7 @@ func TestParse(t *testing.T) {
|
||||
"d=1 empty sub help",
|
||||
buildTestCommand,
|
||||
[]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",
|
||||
@@ -151,10 +151,10 @@ func TestParse(t *testing.T) {
|
||||
"d=0 help",
|
||||
buildTestCommand,
|
||||
[]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
|
||||
print wraps Fprint
|
||||
string print string passed by flag
|
||||
@@ -171,10 +171,10 @@ commands:
|
||||
"d=0 help flag",
|
||||
buildTestCommand,
|
||||
[]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
|
||||
print wraps Fprint
|
||||
string print string passed by flag
|
||||
@@ -185,7 +185,7 @@ commands:
|
||||
succeed this command succeeds
|
||||
deep top level of command tree with various levels
|
||||
|
||||
flags:
|
||||
Flags:
|
||||
-fail
|
||||
fail early
|
||||
-int int
|
||||
@@ -203,9 +203,10 @@ flags:
|
||||
"d=1 help",
|
||||
buildTestCommand,
|
||||
[]string{"join"},
|
||||
`usage: test join [-h | --help] <command> [<args>]
|
||||
`
|
||||
Usage: test join [-h | --help] COMMAND [OPTIONS]
|
||||
|
||||
commands:
|
||||
Commands:
|
||||
out write result to wout
|
||||
log log result to wlog
|
||||
|
||||
@@ -215,9 +216,10 @@ commands:
|
||||
"d=1 help flag",
|
||||
buildTestCommand,
|
||||
[]string{"join", "-h"},
|
||||
`usage: test join [-h | --help] <command> [<args>]
|
||||
`
|
||||
Usage: test join [-h | --help] COMMAND [OPTIONS]
|
||||
|
||||
commands:
|
||||
Commands:
|
||||
out write result to wout
|
||||
log log result to wlog
|
||||
|
||||
@@ -228,9 +230,10 @@ commands:
|
||||
"d=2 help",
|
||||
buildTestCommand,
|
||||
[]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
|
||||
|
||||
`, "", command.ErrHelp,
|
||||
@@ -239,9 +242,10 @@ commands:
|
||||
"d=2 help flag",
|
||||
buildTestCommand,
|
||||
[]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
|
||||
|
||||
`, "", flag.ErrHelp,
|
||||
@@ -257,7 +261,7 @@ commands:
|
||||
t.Errorf("Parse: error = %v; wantErr %v", err, tc.wantErr)
|
||||
}
|
||||
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 {
|
||||
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
|
||||
}
|
||||
@@ -154,8 +154,11 @@ func (e *StartError) Error() string {
|
||||
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()
|
||||
|
||||
@@ -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 {
|
||||
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
|
||||
// check happens in Close, and cleanup is not guaranteed to run, so relying
|
||||
|
||||
+8
-6
@@ -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
|
||||
// check is omitted.
|
||||
func messagePrefix[T error](prefix string, err error) (string, bool) {
|
||||
if e, ok := errors.AsType[T](err); ok {
|
||||
return prefix + e.Error(), true
|
||||
var targetError T
|
||||
if errors.As(err, &targetError) {
|
||||
return prefix + targetError.Error(), true
|
||||
}
|
||||
return zeroString, false
|
||||
}
|
||||
@@ -57,8 +58,9 @@ func messagePrefixP[V any, T interface {
|
||||
*V
|
||||
error
|
||||
}](prefix string, err error) (string, bool) {
|
||||
if e, ok := errors.AsType[T](err); ok && e != nil {
|
||||
return prefix + e.Error(), true
|
||||
var targetError T
|
||||
if errors.As(err, &targetError) && targetError != nil {
|
||||
return prefix + targetError.Error(), true
|
||||
}
|
||||
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.
|
||||
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
||||
errno, ok := errors.AsType[syscall.Errno](err)
|
||||
if !ok {
|
||||
var errno syscall.Errno
|
||||
if !errors.As(err, &errno) {
|
||||
return 0, &os.PathError{Op: op, Path: path, Err: err}
|
||||
}
|
||||
return errno, nil
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -123,7 +123,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -152,7 +152,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -182,7 +182,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -213,7 +213,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -245,7 +245,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -279,7 +279,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -315,7 +315,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ func TestSyscall(t *testing.T) {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
Generated
+8
-8
@@ -7,32 +7,32 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1780361225,
|
||||
"narHash": "sha256-wnV9ttf4fPWNonBIQmvlrSlNpQYgx5HgWWd007mwIFA=",
|
||||
"lastModified": 1772985280,
|
||||
"narHash": "sha256-FdrNykOoY9VStevU4zjSUdvsL9SzJTcXt4omdEDZDLk=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "e28654b71096e08c019d4861ca26acb646f583d8",
|
||||
"rev": "8f736f007139d7f70752657dff6a401a585d6cbc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "release-26.05",
|
||||
"ref": "release-25.11",
|
||||
"repo": "home-manager",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1780453794,
|
||||
"narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=",
|
||||
"lastModified": 1772822230,
|
||||
"narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338",
|
||||
"rev": "71caefce12ba78d84fe618cf61644dce01cf3a96",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-26.05",
|
||||
"ref": "nixos-25.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
+16
-15
@@ -2,10 +2,10 @@
|
||||
description = "hakurei container tool and nixos module";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||
|
||||
home-manager = {
|
||||
url = "github:nix-community/home-manager/release-26.05";
|
||||
url = "github:nix-community/home-manager/release-25.11";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
@@ -37,27 +37,27 @@
|
||||
inherit (pkgs)
|
||||
runCommandLocal
|
||||
callPackage
|
||||
nixfmt
|
||||
nixfmt-rfc-style
|
||||
deadnix
|
||||
statix
|
||||
;
|
||||
in
|
||||
{
|
||||
hakurei = callPackage ./. { inherit system self; };
|
||||
race = callPackage ./. {
|
||||
hakurei = callPackage ./test { inherit system self; };
|
||||
race = callPackage ./test {
|
||||
inherit system self;
|
||||
withRace = true;
|
||||
};
|
||||
|
||||
sandbox = callPackage ./sandbox { inherit self; };
|
||||
sandbox-race = callPackage ./sandbox {
|
||||
sandbox = callPackage ./test/sandbox { inherit self; };
|
||||
sandbox-race = callPackage ./test/sandbox {
|
||||
inherit self;
|
||||
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 ${./.}
|
||||
|
||||
echo "running nixfmt..."
|
||||
@@ -116,7 +116,7 @@
|
||||
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" {
|
||||
"bin/sharefs" = "${hakurei}/libexec/sharefs";
|
||||
"bin/mount.fuse.sharefs" = "${hakurei}/libexec/sharefs";
|
||||
@@ -139,6 +139,7 @@
|
||||
GOCACHE="$(mktemp -d)" \
|
||||
PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
|
||||
DESTDIR="$out" \
|
||||
HAKUREI_VERSION="v${hakurei.version}" \
|
||||
./all.sh
|
||||
'';
|
||||
}
|
||||
@@ -190,11 +191,11 @@
|
||||
};
|
||||
}
|
||||
|
||||
./interactive/configuration.nix
|
||||
./interactive/vm.nix
|
||||
./interactive/hakurei.nix
|
||||
./interactive/trace.nix
|
||||
./interactive/raceattr.nix
|
||||
./test/interactive/configuration.nix
|
||||
./test/interactive/vm.nix
|
||||
./test/interactive/hakurei.nix
|
||||
./test/interactive/trace.nix
|
||||
./test/interactive/raceattr.nix
|
||||
|
||||
self.nixosModules.hakurei
|
||||
home-manager.nixosModules.home-manager
|
||||
@@ -2,7 +2,6 @@ package hst
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -65,14 +64,10 @@ const (
|
||||
// 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.
|
||||
FMapRealUID
|
||||
// FNoPlace disables placement of /etc/passwd and /etc/group.
|
||||
FNoPlace
|
||||
|
||||
// FDevice mount /dev/ from the init mount namespace as is in the container
|
||||
// mount namespace.
|
||||
FDevice
|
||||
// FCoverRun covers /run/ in the container mount namespace early.
|
||||
FCoverRun
|
||||
|
||||
// FShareRuntime shares XDG_RUNTIME_DIR between containers under the same identity.
|
||||
FShareRuntime
|
||||
@@ -103,12 +98,8 @@ func (flags Flags) String() string {
|
||||
return "tty"
|
||||
case FMapRealUID:
|
||||
return "mapuid"
|
||||
case FNoPlace:
|
||||
return "noplace"
|
||||
case FDevice:
|
||||
return "device"
|
||||
case FCoverRun:
|
||||
return "cover_run"
|
||||
case FShareRuntime:
|
||||
return "runtime"
|
||||
case FShareTmpdir:
|
||||
@@ -170,10 +161,6 @@ type ContainerConfig struct {
|
||||
Flags Flags `json:"-"`
|
||||
}
|
||||
|
||||
func (c *ContainerConfig) GoString() string {
|
||||
return fmt.Sprintf("&%#v", *c)
|
||||
}
|
||||
|
||||
// ContainerConfigF is [ContainerConfig] stripped of its methods.
|
||||
//
|
||||
// The [ContainerConfig.Flags] field does not survive a [json] round trip.
|
||||
@@ -201,13 +188,9 @@ type containerConfigJSON = struct {
|
||||
|
||||
// Corresponds to [FMapRealUID].
|
||||
MapRealUID bool `json:"map_real_uid"`
|
||||
// Corresponds to [FNoPlace].
|
||||
NoPlace bool `json:"noplace,omitempty"`
|
||||
|
||||
// Corresponds to [FDevice].
|
||||
Device bool `json:"device,omitempty"`
|
||||
// Corresponds to [FCoverRun].
|
||||
CoverRun bool `json:"cover_run,omitempty"`
|
||||
|
||||
// Corresponds to [FShareRuntime].
|
||||
ShareRuntime bool `json:"share_runtime,omitempty"`
|
||||
@@ -230,9 +213,7 @@ func (c *ContainerConfig) MarshalJSON() ([]byte, error) {
|
||||
Tty: c.Flags&FTty != 0,
|
||||
Multiarch: c.Flags&FMultiarch != 0,
|
||||
MapRealUID: c.Flags&FMapRealUID != 0,
|
||||
NoPlace: c.Flags&FNoPlace != 0,
|
||||
Device: c.Flags&FDevice != 0,
|
||||
CoverRun: c.Flags&FCoverRun != 0,
|
||||
ShareRuntime: c.Flags&FShareRuntime != 0,
|
||||
ShareTmpdir: c.Flags&FShareTmpdir != 0,
|
||||
})
|
||||
@@ -273,15 +254,9 @@ func (c *ContainerConfig) UnmarshalJSON(data []byte) error {
|
||||
if v.MapRealUID {
|
||||
c.Flags |= FMapRealUID
|
||||
}
|
||||
if v.NoPlace {
|
||||
c.Flags |= FNoPlace
|
||||
}
|
||||
if v.Device {
|
||||
c.Flags |= FDevice
|
||||
}
|
||||
if v.CoverRun {
|
||||
c.Flags |= FCoverRun
|
||||
}
|
||||
if v.ShareRuntime {
|
||||
c.Flags |= FShareRuntime
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ func TestFlagsString(t *testing.T) {
|
||||
}{
|
||||
{"none", 0, "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 high", math.MaxUint, "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, device, runtime, tmpdir"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
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},
|
||||
`{"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},
|
||||
`{"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 {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package hst
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -62,10 +61,6 @@ type BusConfig struct {
|
||||
Filter bool `json:"filter"`
|
||||
}
|
||||
|
||||
func (c *BusConfig) GoString() string {
|
||||
return fmt.Sprintf("&%#v", *c)
|
||||
}
|
||||
|
||||
// Interfaces iterates over all interface strings specified in [BusConfig].
|
||||
func (c *BusConfig) Interfaces(yield func(string) bool) {
|
||||
if c == nil {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
@@ -37,8 +36,6 @@ type Ops interface {
|
||||
Bind(source, target *check.Absolute, flags int) Ops
|
||||
// Overlay appends an op that mounts the overlay pseudo filesystem.
|
||||
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(target *check.Absolute, layers ...*check.Absolute) Ops
|
||||
|
||||
@@ -81,17 +78,17 @@ type FSImplError struct{ Value FilesystemConfig }
|
||||
|
||||
func (f FSImplError) Error() string {
|
||||
implType := reflect.TypeOf(f.Value)
|
||||
var buf strings.Builder
|
||||
for implType != nil && implType.Kind() == reflect.Pointer {
|
||||
buf.WriteByte('*')
|
||||
var name string
|
||||
for implType != nil && implType.Kind() == reflect.Ptr {
|
||||
name += "*"
|
||||
implType = implType.Elem()
|
||||
}
|
||||
if implType != nil {
|
||||
buf.WriteString(implType.Name())
|
||||
name += implType.Name()
|
||||
} 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].
|
||||
|
||||
+4
-9
@@ -3,7 +3,6 @@ package hst_test
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -104,7 +103,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
wantErr := tc.wantErr
|
||||
if _, ok := errors.AsType[hst.FSTypeError](wantErr); ok {
|
||||
if errors.As(wantErr, new(hst.FSTypeError)) {
|
||||
// for unsupported implementation tc
|
||||
wantErr = hst.FSImplError{Value: stubFS{"cat"}}
|
||||
}
|
||||
@@ -140,7 +139,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
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
|
||||
return
|
||||
}
|
||||
@@ -284,11 +283,11 @@ func checkFs(t *testing.T, testCases []fsTestCase) {
|
||||
if !reflect.DeepEqual(ops, &tc.ops) {
|
||||
gotString := new(strings.Builder)
|
||||
for _, op := range *ops {
|
||||
gotString.WriteString("\n" + fmt.Sprintf("%#v", op))
|
||||
gotString.WriteString("\n" + op.String())
|
||||
}
|
||||
wantString := new(strings.Builder)
|
||||
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)
|
||||
}
|
||||
@@ -340,10 +339,6 @@ func (p opsAdapter) Overlay(target, state, work *check.Absolute, layers ...*chec
|
||||
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 {
|
||||
return opsAdapter{p.Ops.OverlayReadonly(target, layers...)}
|
||||
}
|
||||
|
||||
+6
-1
@@ -43,13 +43,18 @@ func (e *FSEphemeral) Apply(z *ApplyState) {
|
||||
return
|
||||
}
|
||||
|
||||
size := e.Size
|
||||
if size < 0 {
|
||||
size = 0
|
||||
}
|
||||
|
||||
perm := e.Perm
|
||||
if perm == 0 {
|
||||
perm = fsEphemeralDefaultPerm
|
||||
}
|
||||
|
||||
if e.Write {
|
||||
z.Tmpfs(e.Target, max(e.Size, 0), perm)
|
||||
z.Tmpfs(e.Target, size, perm)
|
||||
} else {
|
||||
z.Readonly(e.Target, perm)
|
||||
}
|
||||
|
||||
+9
-27
@@ -2,7 +2,6 @@ package hst
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/check"
|
||||
@@ -41,7 +40,7 @@ func (o *FSOverlay) Valid() bool {
|
||||
}
|
||||
|
||||
if o.Upper != nil { // rw
|
||||
return o.Work != nil || len(o.Lower) > 0
|
||||
return o.Work != nil && len(o.Lower) > 0
|
||||
} else { // ro
|
||||
return len(o.Lower) >= 2
|
||||
}
|
||||
@@ -59,11 +58,8 @@ func (o *FSOverlay) Host() []*check.Absolute {
|
||||
return nil
|
||||
}
|
||||
p := make([]*check.Absolute, 0, 2+len(o.Lower))
|
||||
if o.Upper != nil {
|
||||
p = append(p, o.Upper)
|
||||
if o.Work != nil {
|
||||
p = append(p, o.Work)
|
||||
}
|
||||
if o.Upper != nil && o.Work != nil {
|
||||
p = append(p, o.Upper, o.Work)
|
||||
}
|
||||
p = append(p, o.Lower...)
|
||||
return p
|
||||
@@ -74,18 +70,11 @@ func (o *FSOverlay) Apply(z *ApplyState) {
|
||||
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) {
|
||||
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 {
|
||||
z.OverlayReadonly(o.Target, o.Lower...)
|
||||
}
|
||||
@@ -101,19 +90,12 @@ func (o *FSOverlay) String() string {
|
||||
lower[i] = check.EscapeOverlayDataSegment(a.String())
|
||||
}
|
||||
|
||||
if o.Upper != nil {
|
||||
if o.Work != nil {
|
||||
return "w*" + strings.Join(append([]string{
|
||||
check.EscapeOverlayDataSegment(o.Target.String()),
|
||||
check.EscapeOverlayDataSegment(o.Upper.String()),
|
||||
check.EscapeOverlayDataSegment(o.Work.String())},
|
||||
lower...), check.SpecialOverlayPath)
|
||||
}
|
||||
return "e*" + strings.Join(append([]string{
|
||||
if o.Upper != nil && o.Work != nil {
|
||||
return "w*" + strings.Join(append([]string{
|
||||
check.EscapeOverlayDataSegment(o.Target.String()),
|
||||
check.EscapeOverlayDataSegment(o.Upper.String())},
|
||||
check.EscapeOverlayDataSegment(o.Upper.String()),
|
||||
check.EscapeOverlayDataSegment(o.Work.String())},
|
||||
lower...), check.SpecialOverlayPath)
|
||||
|
||||
} else {
|
||||
return "*" + strings.Join(append([]string{
|
||||
check.EscapeOverlayDataSegment(o.Target.String())},
|
||||
|
||||
+1
-13
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
@@ -15,7 +14,7 @@ func TestFSOverlay(t *testing.T) {
|
||||
checkFs(t, []fsTestCase{
|
||||
{"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>"},
|
||||
{"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>"},
|
||||
{"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"),
|
||||
}}, m("/"), ms("/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,
|
||||
"multiarch": true,
|
||||
"map_real_uid": true,
|
||||
"noplace": true,
|
||||
"device": true,
|
||||
"cover_run": true,
|
||||
"share_runtime": true,
|
||||
"share_tmpdir": true
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
|
||||
continue
|
||||
}
|
||||
|
||||
if found := bytes.Contains([]byte("-_/.\\*"), []byte{b}); found { // - // _/.\*
|
||||
if ib := bytes.IndexByte([]byte("-_/.\\*"), b); ib != -1 { // - // _/.\*
|
||||
goto opt
|
||||
} else if b >= '0' && b <= '9' { // 0-9
|
||||
goto opt
|
||||
@@ -101,7 +101,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
|
||||
break
|
||||
}
|
||||
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
|
||||
break
|
||||
}
|
||||
|
||||
Vendored
+4
-24
@@ -1,29 +1,21 @@
|
||||
// Package env provides the [Paths] struct for efficiently building paths from
|
||||
// the environment.
|
||||
// Package env provides the [Paths] struct for efficiently building paths from the environment.
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/hst"
|
||||
)
|
||||
|
||||
const VarRunNscd = fhs.Var + "run/nscd"
|
||||
|
||||
// Paths holds paths copied from the environment and is used to create [hst.Paths].
|
||||
type Paths struct {
|
||||
// TempDir is returned by [os.TempDir].
|
||||
TempDir *check.Absolute
|
||||
// RuntimePath is copied from $XDG_RUNTIME_DIR.
|
||||
RuntimePath *check.Absolute
|
||||
// Whether [VarRunNscd] is a directory.
|
||||
HasNscd bool
|
||||
}
|
||||
|
||||
// Copy expands [Paths] into [hst.Paths].
|
||||
@@ -45,17 +37,14 @@ func (env *Paths) Copy(v *hst.Paths, userid int) {
|
||||
}
|
||||
|
||||
// CopyPaths returns a populated [Paths].
|
||||
func CopyPaths() *Paths {
|
||||
return CopyPathsFunc(log.Fatalf, os.TempDir, os.Getenv, os.Stat)
|
||||
}
|
||||
func CopyPaths() *Paths { return CopyPathsFunc(log.Fatalf, os.TempDir, os.Getenv) }
|
||||
|
||||
// CopyPathsFunc returns a populated [Paths], using the provided [log.Fatalf],
|
||||
// [os.TempDir], [os.Getenv] functions.
|
||||
// CopyPathsFunc returns a populated [Paths],
|
||||
// using the provided [log.Fatalf], [os.TempDir], [os.Getenv] functions.
|
||||
func CopyPathsFunc(
|
||||
fatalf func(format string, v ...any),
|
||||
tempdir func() string,
|
||||
getenv func(key string) string,
|
||||
stat func(name string) (fs.FileInfo, error),
|
||||
) *Paths {
|
||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||
|
||||
@@ -72,14 +61,5 @@ func CopyPathsFunc(
|
||||
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
|
||||
}
|
||||
|
||||
Vendored
+1
-4
@@ -2,7 +2,6 @@ package env_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -105,9 +104,7 @@ func TestCopyPaths(t *testing.T) {
|
||||
t.Fatalf("fatalf: %q, want %q", got, tc.fatal)
|
||||
}
|
||||
panic(stub.PanicExit)
|
||||
}, func() string { return tc.tmp }, func(key string) string { return tc.env[key] }, func(name string) (fs.FileInfo, error) {
|
||||
return nil, fs.ErrNotExist
|
||||
})
|
||||
}, func() string { return tc.tmp }, func(key string) string { return tc.env[key] })
|
||||
|
||||
if tc.fatal != "" {
|
||||
t.Fatalf("copyPaths: expected fatal %q", tc.fatal)
|
||||
|
||||
@@ -2,7 +2,6 @@ package kobject
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"maps"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
@@ -29,22 +28,6 @@ type Event struct {
|
||||
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].
|
||||
//
|
||||
// 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
-266
@@ -1,266 +0,0 @@
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXPWRBN:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXPWRBN:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXPWRBN:","SEQNUM=777"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXPWRBN:00/wakeup/wakeup7","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXPWRBN:00/wakeup/wakeup7","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=778"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00/LNXCPU:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00/LNXCPU:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXCPU:","SEQNUM=779"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:ACPI0010:PNP0A05:","SEQNUM=780"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0103:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0103:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0103:","SEQNUM=781"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=782"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=783"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=784"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/QEMU0002:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/QEMU0002:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:QEMU0002:","SEQNUM=785"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=786"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00/wakeup/wakeup0","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00/wakeup/wakeup0","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=787"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0303:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0303:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0303:","SEQNUM=788"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0400:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0400:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0400:","SEQNUM=789"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0501:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0501:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0501:","SEQNUM=790"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00/device:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00/device:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=791"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0700:","SEQNUM=792"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0B00:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0B00:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0B00:","SEQNUM=793"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0F13:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0F13:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0F13:","SEQNUM=794"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=795"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/wakeup/wakeup1","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/wakeup/wakeup1","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=796"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=797"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03/wakeup/wakeup2","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03/wakeup/wakeup2","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=798"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=799"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04/wakeup/wakeup3","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04/wakeup/wakeup3","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=800"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=801"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05/wakeup/wakeup4","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05/wakeup/wakeup4","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=802"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=803"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06/wakeup/wakeup5","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06/wakeup/wakeup5","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=804"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=805"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=806"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:09","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:09","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=807"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0a","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0a","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=808"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0b","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0b","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=809"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0c","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0c","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=810"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0d","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0d","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=811"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0e","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0e","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=812"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0f","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0f","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=813"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:10","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:10","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=814"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:11","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:11","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=815"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:12","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:12","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=816"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:13","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:13","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=817"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:14","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:14","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=818"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:15","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:15","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=819"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:16","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:16","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=820"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:17","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:17","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=821"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:18","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:18","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=822"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:19","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:19","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=823"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1a","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1a","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=824"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1b","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1b","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=825"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1c","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1c","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=826"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1d","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1d","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=827"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1e","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1e","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=828"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1f","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1f","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=829"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:20","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:20","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=830"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:21","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:21","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=831"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:22","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:22","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=832"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A03:","SEQNUM=833"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/wakeup/wakeup6","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/wakeup/wakeup6","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=834"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=835"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=836"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=837"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:03","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:03","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=838"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:04","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:04","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=839"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYBUS:","SEQNUM=840"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYBUS:","SEQNUM=841"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYSTM:","SEQNUM=842"]}
|
||||
{"action":"add","devpath":"/devices/breakpoint","env":["ACTION=add","DEVPATH=/devices/breakpoint","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=843"]}
|
||||
{"action":"add","devpath":"/devices/cpu","env":["ACTION=add","DEVPATH=/devices/cpu","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=844"]}
|
||||
{"action":"add","devpath":"/devices/kprobe","env":["ACTION=add","DEVPATH=/devices/kprobe","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=845"]}
|
||||
{"action":"add","devpath":"/devices/msr","env":["ACTION=add","DEVPATH=/devices/msr","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=846"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:00.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:00.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=60000","PCI_ID=8086:1237","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:00.0","MODALIAS=pci:v00008086d00001237sv00001AF4sd00001100bc06sc00i00","SEQNUM=847"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=60100","PCI_ID=8086:7000","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.0","MODALIAS=pci:v00008086d00007000sv00001AF4sd00001100bc06sc01i00","SEQNUM=848"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/ata_port/ata1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/ata_port/ata1","SUBSYSTEM=ata_port","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=849"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/host0/scsi_host/host0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/host0/scsi_host/host0","SUBSYSTEM=scsi_host","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=850"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/host0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/host0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_host","SEQNUM=851"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/ata_link/link1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/ata_link/link1","SUBSYSTEM=ata_link","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=852"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.0/ata_device/dev1.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.0/ata_device/dev1.0","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=853"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.1/ata_device/dev1.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.1/ata_device/dev1.1","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=854"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/ata_port/ata2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/ata_port/ata2","SUBSYSTEM=ata_port","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=855"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/scsi_host/host1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/scsi_host/host1","SUBSYSTEM=scsi_host","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=856"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/bsg/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/bsg/1:0:0:0","SUBSYSTEM=bsg","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=251","MINOR=0","DEVNAME=bsg/1:0:0:0","SEQNUM=857"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/scsi_device/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/scsi_device/1:0:0:0","SUBSYSTEM=scsi_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=858"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_device","MODALIAS=scsi:t-0x05","SEQNUM=859"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_target","SEQNUM=860"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_host","SEQNUM=861"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/ata_link/link2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/ata_link/link2","SUBSYSTEM=ata_link","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=862"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.0/ata_device/dev2.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.0/ata_device/dev2.0","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=863"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.1/ata_device/dev2.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.1/ata_device/dev2.1","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=864"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=ata_piix","PCI_CLASS=10180","PCI_ID=8086:7010","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.1","MODALIAS=pci:v00008086d00007010sv00001AF4sd00001100bc01sc01i80","SEQNUM=865"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.3","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.3","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=68000","PCI_ID=8086:7113","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.3","MODALIAS=pci:v00008086d00007113sv00001AF4sd00001100bc06sc80i00","SEQNUM=866"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:02.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:02.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=20000","PCI_ID=8086:100E","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:02.0","MODALIAS=pci:v00008086d0000100Esv00001AF4sd00001100bc02sc00i00","SEQNUM=867"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=virtio-pci","PCI_CLASS=10000","PCI_ID=1AF4:1001","PCI_SUBSYS_ID=1AF4:0002","PCI_SLOT_NAME=0000:00:03.0","MODALIAS=pci:v00001AF4d00001001sv00001AF4sd00000002bc01sc00i00","SEQNUM=868"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0/virtio0/block/vda","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0/virtio0/block/vda","SUBSYSTEM=block","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=254","MINOR=0","DEVNAME=vda","DEVTYPE=disk","DISKSEQ=1","SEQNUM=869"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0/virtio0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0/virtio0","SUBSYSTEM=virtio","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=virtio_blk","MODALIAS=virtio:d00000002v00001AF4","SEQNUM=870"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/QEMU0002:00","env":["ACTION=add","DEVPATH=/devices/pci0000:00/QEMU0002:00","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:QEMU0002:","SEQNUM=871"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/pci_bus/0000:00","env":["ACTION=add","DEVPATH=/devices/pci0000:00/pci_bus/0000:00","SUBSYSTEM=pci_bus","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=872"]}
|
||||
{"action":"add","devpath":"/devices/platform/PNP0103:00","env":["ACTION=add","DEVPATH=/devices/platform/PNP0103:00","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0103:","SEQNUM=873"]}
|
||||
{"action":"add","devpath":"/devices/platform/pcspkr","env":["ACTION=add","DEVPATH=/devices/platform/pcspkr","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=platform:pcspkr","SEQNUM=874"]}
|
||||
{"action":"add","devpath":"/devices/platform/reg-dummy/regulator/regulator.0","env":["ACTION=add","DEVPATH=/devices/platform/reg-dummy/regulator/regulator.0","SUBSYSTEM=regulator","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=875"]}
|
||||
{"action":"add","devpath":"/devices/platform/reg-dummy","env":["ACTION=add","DEVPATH=/devices/platform/reg-dummy","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=reg-dummy","MODALIAS=platform:reg-dummy","SEQNUM=876"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.1/tty/ttyS1","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.1/tty/ttyS1","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=65","DEVNAME=ttyS1","SEQNUM=877"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.1","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.1","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=878"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.2/tty/ttyS2","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.2/tty/ttyS2","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=66","DEVNAME=ttyS2","SEQNUM=879"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.2","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.2","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=880"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.3/tty/ttyS3","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.3/tty/ttyS3","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=67","DEVNAME=ttyS3","SEQNUM=881"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.3","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.3","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=882"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=ctrl","DRIVER=ctrl","SEQNUM=883"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250","env":["ACTION=add","DEVPATH=/devices/platform/serial8250","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=serial8250","MODALIAS=platform:serial8250","SEQNUM=884"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:00","env":["ACTION=add","DEVPATH=/devices/pnp0/00:00","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=885"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:01","env":["ACTION=add","DEVPATH=/devices/pnp0/00:01","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=886"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:02","env":["ACTION=add","DEVPATH=/devices/pnp0/00:02","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=887"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:03","env":["ACTION=add","DEVPATH=/devices/pnp0/00:03","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=888"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0/00:04:0.0/tty/ttyS0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0/00:04:0.0/tty/ttyS0","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=64","DEVNAME=ttyS0","SEQNUM=889"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0/00:04:0.0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0/00:04:0.0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=890"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=ctrl","DRIVER=ctrl","SEQNUM=891"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:04","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=serial","SEQNUM=892"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:05","env":["ACTION=add","DEVPATH=/devices/pnp0/00:05","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=893"]}
|
||||
{"action":"add","devpath":"/devices/software","env":["ACTION=add","DEVPATH=/devices/software","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=894"]}
|
||||
{"action":"add","devpath":"/devices/system/clockevents/broadcast","env":["ACTION=add","DEVPATH=/devices/system/clockevents/broadcast","SUBSYSTEM=clockevents","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=895"]}
|
||||
{"action":"add","devpath":"/devices/system/clockevents/clockevent0","env":["ACTION=add","DEVPATH=/devices/system/clockevents/clockevent0","SUBSYSTEM=clockevents","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=896"]}
|
||||
{"action":"add","devpath":"/devices/system/clocksource/clocksource0","env":["ACTION=add","DEVPATH=/devices/system/clocksource/clocksource0","SUBSYSTEM=clocksource","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=897"]}
|
||||
{"action":"add","devpath":"/devices/system/container/PNP0A06:00","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:00","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=898"]}
|
||||
{"action":"add","devpath":"/devices/system/container/PNP0A06:01","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:01","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=899"]}
|
||||
{"action":"add","devpath":"/devices/system/container/PNP0A06:02","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:02","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=900"]}
|
||||
{"action":"add","devpath":"/devices/system/cpu/cpu0","env":["ACTION=add","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=901"]}
|
||||
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=902"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory0","env":["ACTION=add","DEVPATH=/devices/system/memory/memory0","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=903"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory1","env":["ACTION=add","DEVPATH=/devices/system/memory/memory1","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=904"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory2","env":["ACTION=add","DEVPATH=/devices/system/memory/memory2","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=905"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory3","env":["ACTION=add","DEVPATH=/devices/system/memory/memory3","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=906"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory4","env":["ACTION=add","DEVPATH=/devices/system/memory/memory4","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=907"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory5","env":["ACTION=add","DEVPATH=/devices/system/memory/memory5","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=908"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory6","env":["ACTION=add","DEVPATH=/devices/system/memory/memory6","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=909"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory7","env":["ACTION=add","DEVPATH=/devices/system/memory/memory7","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=910"]}
|
||||
{"action":"add","devpath":"/devices/system/node/node0","env":["ACTION=add","DEVPATH=/devices/system/node/node0","SUBSYSTEM=node","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=911"]}
|
||||
{"action":"add","devpath":"/devices/tracepoint","env":["ACTION=add","DEVPATH=/devices/tracepoint","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=912"]}
|
||||
{"action":"add","devpath":"/devices/uprobe","env":["ACTION=add","DEVPATH=/devices/uprobe","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=913"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/254:0","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/254:0","SUBSYSTEM=bdi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=914"]}
|
||||
{"action":"add","devpath":"/devices/virtual/devlink/:ata2--scsi:1:0:0:0","env":["ACTION=add","DEVPATH=/devices/virtual/devlink/:ata2--scsi:1:0:0:0","SUBSYSTEM=devlink","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=915"]}
|
||||
{"action":"add","devpath":"/devices/virtual/dmi/id","env":["ACTION=add","DEVPATH=/devices/virtual/dmi/id","SUBSYSTEM=dmi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=dmi:bvnSeaBIOS:bvrrel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org:bd04/01/2014:br0.0:svnQEMU:pnStandardPC(i440FX+PIIX,1996):pvrpc-i440fx-10.1:cvnQEMU:ct1:cvrpc-i440fx-10.1:sku:","SEQNUM=916"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/full","env":["ACTION=add","DEVPATH=/devices/virtual/mem/full","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=7","DEVNAME=full","DEVMODE=0666","SEQNUM=917"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/kmsg","env":["ACTION=add","DEVPATH=/devices/virtual/mem/kmsg","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=11","DEVNAME=kmsg","DEVMODE=0644","SEQNUM=918"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/mem","env":["ACTION=add","DEVPATH=/devices/virtual/mem/mem","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=1","DEVNAME=mem","SEQNUM=919"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/null","env":["ACTION=add","DEVPATH=/devices/virtual/mem/null","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=3","DEVNAME=null","DEVMODE=0666","SEQNUM=920"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/port","env":["ACTION=add","DEVPATH=/devices/virtual/mem/port","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=4","DEVNAME=port","SEQNUM=921"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/random","env":["ACTION=add","DEVPATH=/devices/virtual/mem/random","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=8","DEVNAME=random","DEVMODE=0666","SEQNUM=922"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/urandom","env":["ACTION=add","DEVPATH=/devices/virtual/mem/urandom","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=9","DEVNAME=urandom","DEVMODE=0666","SEQNUM=923"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/zero","env":["ACTION=add","DEVPATH=/devices/virtual/mem/zero","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=5","DEVNAME=zero","DEVMODE=0666","SEQNUM=924"]}
|
||||
{"action":"add","devpath":"/devices/virtual/memory_tiering/memory_tier4","env":["ACTION=add","DEVPATH=/devices/virtual/memory_tiering/memory_tier4","SUBSYSTEM=memory_tiering","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=925"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/cpu_dma_latency","env":["ACTION=add","DEVPATH=/devices/virtual/misc/cpu_dma_latency","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=259","DEVNAME=cpu_dma_latency","SEQNUM=926"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/hpet","env":["ACTION=add","DEVPATH=/devices/virtual/misc/hpet","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=228","DEVNAME=hpet","SEQNUM=927"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/snapshot","env":["ACTION=add","DEVPATH=/devices/virtual/misc/snapshot","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=231","DEVNAME=snapshot","SEQNUM=928"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/udmabuf","env":["ACTION=add","DEVPATH=/devices/virtual/misc/udmabuf","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=258","DEVNAME=udmabuf","SEQNUM=929"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/userfaultfd","env":["ACTION=add","DEVPATH=/devices/virtual/misc/userfaultfd","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=257","DEVNAME=userfaultfd","SEQNUM=930"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/vga_arbiter","env":["ACTION=add","DEVPATH=/devices/virtual/misc/vga_arbiter","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=256","DEVNAME=vga_arbiter","SEQNUM=931"]}
|
||||
{"action":"add","devpath":"/devices/virtual/net/lo","env":["ACTION=add","DEVPATH=/devices/virtual/net/lo","SUBSYSTEM=net","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","INTERFACE=lo","IFINDEX=1","SEQNUM=932"]}
|
||||
{"action":"add","devpath":"/devices/virtual/thermal/cooling_device0","env":["ACTION=add","DEVPATH=/devices/virtual/thermal/cooling_device0","SUBSYSTEM=thermal","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=933"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/console","env":["ACTION=add","DEVPATH=/devices/virtual/tty/console","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=1","DEVNAME=console","SEQNUM=934"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/ptmx","env":["ACTION=add","DEVPATH=/devices/virtual/tty/ptmx","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=2","DEVNAME=ptmx","DEVMODE=0666","SEQNUM=935"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=0","DEVNAME=tty","DEVMODE=0666","SEQNUM=936"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty0","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty0","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=0","DEVNAME=tty0","SEQNUM=937"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty1","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty1","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=1","DEVNAME=tty1","SEQNUM=938"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty10","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty10","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=10","DEVNAME=tty10","SEQNUM=939"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty11","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty11","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=11","DEVNAME=tty11","SEQNUM=940"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty12","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty12","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=12","DEVNAME=tty12","SEQNUM=941"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty13","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty13","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=13","DEVNAME=tty13","SEQNUM=942"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty14","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty14","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=14","DEVNAME=tty14","SEQNUM=943"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty15","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty15","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=15","DEVNAME=tty15","SEQNUM=944"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty16","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty16","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=16","DEVNAME=tty16","SEQNUM=945"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty17","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty17","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=17","DEVNAME=tty17","SEQNUM=946"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty18","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty18","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=18","DEVNAME=tty18","SEQNUM=947"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty19","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty19","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=19","DEVNAME=tty19","SEQNUM=948"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty2","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty2","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=2","DEVNAME=tty2","SEQNUM=949"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty20","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty20","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=20","DEVNAME=tty20","SEQNUM=950"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty21","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty21","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=21","DEVNAME=tty21","SEQNUM=951"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty22","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty22","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=22","DEVNAME=tty22","SEQNUM=952"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty23","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty23","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=23","DEVNAME=tty23","SEQNUM=953"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty24","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty24","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=24","DEVNAME=tty24","SEQNUM=954"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty25","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty25","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=25","DEVNAME=tty25","SEQNUM=955"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty26","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty26","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=26","DEVNAME=tty26","SEQNUM=956"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty27","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty27","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=27","DEVNAME=tty27","SEQNUM=957"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty28","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty28","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=28","DEVNAME=tty28","SEQNUM=958"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty29","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty29","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=29","DEVNAME=tty29","SEQNUM=959"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty3","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty3","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=3","DEVNAME=tty3","SEQNUM=960"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty30","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty30","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=30","DEVNAME=tty30","SEQNUM=961"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty31","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty31","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=31","DEVNAME=tty31","SEQNUM=962"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty32","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty32","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=32","DEVNAME=tty32","SEQNUM=963"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty33","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty33","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=33","DEVNAME=tty33","SEQNUM=964"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty34","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty34","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=34","DEVNAME=tty34","SEQNUM=965"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty35","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty35","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=35","DEVNAME=tty35","SEQNUM=966"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty36","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty36","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=36","DEVNAME=tty36","SEQNUM=967"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty37","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty37","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=37","DEVNAME=tty37","SEQNUM=968"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty38","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty38","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=38","DEVNAME=tty38","SEQNUM=969"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty39","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty39","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=39","DEVNAME=tty39","SEQNUM=970"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty4","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty4","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=4","DEVNAME=tty4","SEQNUM=971"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty40","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty40","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=40","DEVNAME=tty40","SEQNUM=972"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty41","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty41","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=41","DEVNAME=tty41","SEQNUM=973"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty42","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty42","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=42","DEVNAME=tty42","SEQNUM=974"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty43","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty43","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=43","DEVNAME=tty43","SEQNUM=975"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty44","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty44","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=44","DEVNAME=tty44","SEQNUM=976"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty45","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty45","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=45","DEVNAME=tty45","SEQNUM=977"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty46","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty46","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=46","DEVNAME=tty46","SEQNUM=978"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty47","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty47","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=47","DEVNAME=tty47","SEQNUM=979"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty48","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty48","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=48","DEVNAME=tty48","SEQNUM=980"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty49","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty49","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=49","DEVNAME=tty49","SEQNUM=981"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty5","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty5","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=5","DEVNAME=tty5","SEQNUM=982"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty50","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty50","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=50","DEVNAME=tty50","SEQNUM=983"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty51","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty51","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=51","DEVNAME=tty51","SEQNUM=984"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty52","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty52","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=52","DEVNAME=tty52","SEQNUM=985"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty53","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty53","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=53","DEVNAME=tty53","SEQNUM=986"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty54","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty54","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=54","DEVNAME=tty54","SEQNUM=987"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty55","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty55","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=55","DEVNAME=tty55","SEQNUM=988"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty56","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty56","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=56","DEVNAME=tty56","SEQNUM=989"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty57","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty57","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=57","DEVNAME=tty57","SEQNUM=990"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty58","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty58","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=58","DEVNAME=tty58","SEQNUM=991"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty59","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty59","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=59","DEVNAME=tty59","SEQNUM=992"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty6","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty6","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=6","DEVNAME=tty6","SEQNUM=993"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty60","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty60","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=60","DEVNAME=tty60","SEQNUM=994"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty61","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty61","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=61","DEVNAME=tty61","SEQNUM=995"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty62","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty62","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=62","DEVNAME=tty62","SEQNUM=996"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty63","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty63","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=63","DEVNAME=tty63","SEQNUM=997"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty7","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty7","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=7","DEVNAME=tty7","SEQNUM=998"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty8","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty8","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=8","DEVNAME=tty8","SEQNUM=999"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty9","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty9","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=9","DEVNAME=tty9","SEQNUM=1000"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcs","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcs","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=0","DEVNAME=vcs","SEQNUM=1001"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcs1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcs1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=1","DEVNAME=vcs1","SEQNUM=1002"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcsa","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsa","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=128","DEVNAME=vcsa","SEQNUM=1003"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcsa1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsa1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=129","DEVNAME=vcsa1","SEQNUM=1004"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcsu","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsu","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=64","DEVNAME=vcsu","SEQNUM=1005"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcsu1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsu1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=65","DEVNAME=vcsu1","SEQNUM=1006"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vtconsole/vtcon0","env":["ACTION=add","DEVPATH=/devices/virtual/vtconsole/vtcon0","SUBSYSTEM=vtconsole","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1007"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-auth-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-auth-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1008"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-delete-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-delete-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1009"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-reset-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-reset-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1010"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1011"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/scsi_tmf_0","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/scsi_tmf_0","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1012"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/scsi_tmf_1","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/scsi_tmf_1","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1013"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/writeback","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/writeback","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1014"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1015"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1016"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1017"]}
|
||||
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1018"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1019"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1020"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1021"]}
|
||||
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1022"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1023"]}
|
||||
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","SEQNUM=1024"]}
|
||||
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1025"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1026"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1027"]}
|
||||
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","SEQNUM=1028"]}
|
||||
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1029"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1030"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1031"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1032"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1033"]}
|
||||
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1034"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1035"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1036"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1037"]}
|
||||
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1038"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1039"]}
|
||||
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","SEQNUM=1040"]}
|
||||
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1041"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1042"]}
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"action":"move","devpath":"/devices/virtual/net/_lo","env":["ACTION=move","DEVPATH=/devices/virtual/net/_lo","SUBSYSTEM=net","DEVPATH_OLD=/devices/virtual/net/lo","INTERFACE=_lo","IFINDEX=1","SEQNUM=1043"]}
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
{"action":"remove","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=remove","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1044"]}
|
||||
{"action":"offline","devpath":"/devices/system/cpu/cpu0","env":["ACTION=offline","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1045"]}
|
||||
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1046"]}
|
||||
{"action":"online","devpath":"/devices/system/cpu/cpu0","env":["ACTION=online","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1047"]}
|
||||
{"action":"remove","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=remove","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1048"]}
|
||||
{"action":"offline","devpath":"/devices/system/cpu/cpu0","env":["ACTION=offline","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1049"]}
|
||||
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1050"]}
|
||||
{"action":"online","devpath":"/devices/system/cpu/cpu0","env":["ACTION=online","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1051"]}
|
||||
Vendored
-19
@@ -1,19 +0,0 @@
|
||||
{"action":"add","devpath":"/devices/virtual/misc/loop-control","env":["ACTION=add","DEVPATH=/devices/virtual/misc/loop-control","SUBSYSTEM=misc","MAJOR=10","MINOR=237","DEVNAME=loop-control","SEQNUM=1052"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:0","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:0","SUBSYSTEM=bdi","SEQNUM=1053"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop0","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=2","SEQNUM=1054"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:1","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:1","SUBSYSTEM=bdi","SEQNUM=1055"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop1","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop1","SUBSYSTEM=block","MAJOR=7","MINOR=1","DEVNAME=loop1","DEVTYPE=disk","DISKSEQ=3","SEQNUM=1056"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:2","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:2","SUBSYSTEM=bdi","SEQNUM=1057"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop2","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop2","SUBSYSTEM=block","MAJOR=7","MINOR=2","DEVNAME=loop2","DEVTYPE=disk","DISKSEQ=4","SEQNUM=1058"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:3","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:3","SUBSYSTEM=bdi","SEQNUM=1059"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop3","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop3","SUBSYSTEM=block","MAJOR=7","MINOR=3","DEVNAME=loop3","DEVTYPE=disk","DISKSEQ=5","SEQNUM=1060"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:4","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:4","SUBSYSTEM=bdi","SEQNUM=1061"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop4","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop4","SUBSYSTEM=block","MAJOR=7","MINOR=4","DEVNAME=loop4","DEVTYPE=disk","DISKSEQ=6","SEQNUM=1062"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:5","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:5","SUBSYSTEM=bdi","SEQNUM=1063"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop5","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop5","SUBSYSTEM=block","MAJOR=7","MINOR=5","DEVNAME=loop5","DEVTYPE=disk","DISKSEQ=7","SEQNUM=1064"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:6","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:6","SUBSYSTEM=bdi","SEQNUM=1065"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop6","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop6","SUBSYSTEM=block","MAJOR=7","MINOR=6","DEVNAME=loop6","DEVTYPE=disk","DISKSEQ=8","SEQNUM=1066"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:7","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:7","SUBSYSTEM=bdi","SEQNUM=1067"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop7","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop7","SUBSYSTEM=block","MAJOR=7","MINOR=7","DEVNAME=loop7","DEVTYPE=disk","DISKSEQ=9","SEQNUM=1068"]}
|
||||
{"action":"add","devpath":"/module/loop","env":["ACTION=add","DEVPATH=/module/loop","SUBSYSTEM=module","SEQNUM=1069"]}
|
||||
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1070"]}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1071"]}
|
||||
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","DISK_MEDIA_CHANGE=1","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1072"]}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{"action":"remove","devpath":"/devices/virtual/misc/loop-control","env":["ACTION=remove","DEVPATH=/devices/virtual/misc/loop-control","SUBSYSTEM=misc","MAJOR=10","MINOR=237","DEVNAME=loop-control","SEQNUM=1073"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:0","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:0","SUBSYSTEM=bdi","SEQNUM=1074"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop0","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=11","SEQNUM=1075"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:1","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:1","SUBSYSTEM=bdi","SEQNUM=1076"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop1","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop1","SUBSYSTEM=block","MAJOR=7","MINOR=1","DEVNAME=loop1","DEVTYPE=disk","DISKSEQ=3","SEQNUM=1077"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:2","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:2","SUBSYSTEM=bdi","SEQNUM=1078"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop2","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop2","SUBSYSTEM=block","MAJOR=7","MINOR=2","DEVNAME=loop2","DEVTYPE=disk","DISKSEQ=4","SEQNUM=1079"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:3","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:3","SUBSYSTEM=bdi","SEQNUM=1080"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop3","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop3","SUBSYSTEM=block","MAJOR=7","MINOR=3","DEVNAME=loop3","DEVTYPE=disk","DISKSEQ=5","SEQNUM=1081"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:4","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:4","SUBSYSTEM=bdi","SEQNUM=1082"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop4","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop4","SUBSYSTEM=block","MAJOR=7","MINOR=4","DEVNAME=loop4","DEVTYPE=disk","DISKSEQ=6","SEQNUM=1083"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:5","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:5","SUBSYSTEM=bdi","SEQNUM=1084"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop5","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop5","SUBSYSTEM=block","MAJOR=7","MINOR=5","DEVNAME=loop5","DEVTYPE=disk","DISKSEQ=7","SEQNUM=1085"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:6","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:6","SUBSYSTEM=bdi","SEQNUM=1086"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop6","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop6","SUBSYSTEM=block","MAJOR=7","MINOR=6","DEVNAME=loop6","DEVTYPE=disk","DISKSEQ=8","SEQNUM=1087"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:7","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:7","SUBSYSTEM=bdi","SEQNUM=1088"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop7","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop7","SUBSYSTEM=block","MAJOR=7","MINOR=7","DEVNAME=loop7","DEVTYPE=disk","DISKSEQ=9","SEQNUM=1089"]}
|
||||
{"action":"remove","devpath":"/module/loop","env":["ACTION=remove","DEVPATH=/module/loop","SUBSYSTEM=module","SEQNUM=1090"]}
|
||||
@@ -40,7 +40,7 @@ func TestTransform(t *testing.T) {
|
||||
|
||||
const maxChunkWords = 8 << 10
|
||||
buf := make([]byte, 2*maxChunkWords*8)
|
||||
for i := range uint64(2 * maxChunkWords) {
|
||||
for i := uint64(0); i < 2*maxChunkWords; i++ {
|
||||
binary.LittleEndian.PutUint64(buf[i*8:], i)
|
||||
}
|
||||
if err := lockedfile.Write(path, bytes.NewReader(buf[:8]), 0666); err != nil {
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/env"
|
||||
"hakurei.app/internal/stub"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/message"
|
||||
@@ -175,7 +174,6 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
||||
call("cmdOutput", stub.ExpectArgs{container.Nonexistent, os.Stderr, []string{}, "/"}, []byte("0"), nil),
|
||||
call("tempdir", stub.ExpectArgs{}, container.Nonexistent+"/tmp", nil),
|
||||
call("lookupEnv", stub.ExpectArgs{"XDG_RUNTIME_DIR"}, wantRuntimePath, nil),
|
||||
call("stat", stub.ExpectArgs{env.VarRunNscd}, stubFileInfoIsDir(true), nil),
|
||||
call("getuid", stub.ExpectArgs{}, 1000, nil),
|
||||
call("getgid", stub.ExpectArgs{}, 100, nil),
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ func (k *outcome) finalise(
|
||||
supp := make([]string, len(config.Groups))
|
||||
for i, name := range config.Groups {
|
||||
if gid, err := k.lookupGroupId(name); err != nil {
|
||||
if unknownGroupError, ok := errors.AsType[user.UnknownGroupError](err); ok {
|
||||
var unknownGroupError user.UnknownGroupError
|
||||
if errors.As(err, &unknownGroupError) {
|
||||
return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError)
|
||||
} else {
|
||||
return &hst.AppError{Step: "look up group by name", Err: err, Msg: err.Error()}
|
||||
|
||||
@@ -51,16 +51,18 @@ func (h *Hsu) ID() (int, error) {
|
||||
cmd.Stderr = os.Stderr // pass through fatal messages
|
||||
cmd.Env = make([]string, 0)
|
||||
cmd.Dir = fhs.Root
|
||||
var p []byte
|
||||
var (
|
||||
p []byte
|
||||
exitError *exec.ExitError
|
||||
)
|
||||
|
||||
const step = "obtain uid from hsu"
|
||||
if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil {
|
||||
h.id, h.idErr = strconv.Atoi(string(p))
|
||||
if h.idErr != nil {
|
||||
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
|
||||
}
|
||||
} else if exitError, ok := errors.AsType[*exec.ExitError](h.idErr); ok &&
|
||||
exitError != nil &&
|
||||
exitError.ExitCode() == 1 {
|
||||
} else if errors.As(h.idErr, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
||||
// hsu prints an error message in this case
|
||||
h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess}
|
||||
} else if errors.Is(h.idErr, os.ErrNotExist) {
|
||||
|
||||
@@ -110,7 +110,7 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *h
|
||||
Paths: env.CopyPathsFunc(k.fatalf, k.tempdir, func(key string) string {
|
||||
v, _ := k.lookupEnv(key)
|
||||
return v
|
||||
}, k.stat),
|
||||
}),
|
||||
Container: config.Container,
|
||||
}
|
||||
|
||||
|
||||
@@ -328,11 +328,11 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
||||
}
|
||||
|
||||
if err := k.sys.Revert((*system.Criteria)(&ec)); err != nil {
|
||||
joinError, ok := errors.AsType[interface {
|
||||
var joinError interface {
|
||||
Unwrap() []error
|
||||
error
|
||||
}](err)
|
||||
if !ok || joinError == nil {
|
||||
}
|
||||
if !errors.As(err, &joinError) || joinError == nil {
|
||||
perror(err, "revert system setup")
|
||||
} else {
|
||||
for _, v := range joinError.Unwrap() {
|
||||
|
||||
@@ -136,13 +136,16 @@ func TestOutcomeRun(t *testing.T) {
|
||||
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
||||
|
||||
// spRuntimeOp
|
||||
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/1971"), std.BindWritable).
|
||||
|
||||
// spTmpdirOp
|
||||
Bind(m("/tmp/hakurei.0/tmpdir/9"), fhs.AbsTmp, std.BindWritable).
|
||||
|
||||
// spAccountOp
|
||||
Place(m("/etc/passwd"), []byte("chronos:x:1971:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
|
||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
||||
|
||||
// spWaylandOp
|
||||
Bind(m("/tmp/hakurei.0/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1971/wayland-0"), 0).
|
||||
|
||||
@@ -449,7 +452,7 @@ func TestOutcomeRun(t *testing.T) {
|
||||
|
||||
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
|
||||
|
||||
Flags: hst.FUserns | hst.FHostNet | hst.FMapRealUID | hst.FNoPlace | hst.FShareRuntime | hst.FShareTmpdir,
|
||||
Flags: hst.FUserns | hst.FHostNet | hst.FMapRealUID | hst.FShareRuntime | hst.FShareTmpdir,
|
||||
},
|
||||
SystemBus: &hst.BusConfig{
|
||||
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
||||
@@ -544,6 +547,8 @@ func TestOutcomeRun(t *testing.T) {
|
||||
Tmpfs(m("/run/user/"), xdgRuntimeDirSize, 0755).
|
||||
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), std.BindWritable).
|
||||
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), std.BindWritable).
|
||||
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
|
||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
||||
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
|
||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/pipewire"), m("/run/user/1971/pipewire-0"), 0).
|
||||
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
|
||||
@@ -710,7 +715,7 @@ func (k *stubNixOS) lookupEnv(key string) (string, bool) {
|
||||
func (k *stubNixOS) stat(name string) (fs.FileInfo, error) {
|
||||
switch name {
|
||||
case "/var/run/nscd":
|
||||
return stubFileInfoIsDir(true), nil
|
||||
return nil, nil
|
||||
case "/run/user/1971/pulse":
|
||||
return nil, nil
|
||||
case "/run/user/1971/pulse/native":
|
||||
|
||||
@@ -390,8 +390,8 @@ func shimEntrypoint(k syscallDispatcher) {
|
||||
if err := k.containerWait(z); err != nil {
|
||||
sp.destroy()
|
||||
|
||||
exitError, ok := errors.AsType[*exec.ExitError](err)
|
||||
if !ok {
|
||||
var exitError *exec.ExitError
|
||||
if !errors.As(err, &exitError) {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
k.exit(hst.ExitCancel)
|
||||
}
|
||||
|
||||
@@ -71,13 +71,16 @@ func TestShimEntrypoint(t *testing.T) {
|
||||
Tmpfs(fhs.AbsDevShm, 0, 01777).
|
||||
|
||||
// spRuntimeOp
|
||||
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||
Bind(m("/tmp/hakurei.10/runtime/9999"), m("/run/user/1000"), std.BindWritable).
|
||||
|
||||
// spTmpdirOp
|
||||
Bind(m("/tmp/hakurei.10/tmpdir/9999"), fhs.AbsTmp, std.BindWritable).
|
||||
|
||||
// spAccountOp
|
||||
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
|
||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
|
||||
|
||||
// spWaylandOp
|
||||
Bind(m("/tmp/hakurei.10/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/wayland"), m("/run/user/1000/wayland-0"), 0).
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/validate"
|
||||
)
|
||||
|
||||
@@ -42,18 +41,16 @@ func (s spAccountOp) toContainer(state *outcomeStateParams) error {
|
||||
state.env["USER"] = username
|
||||
state.env["SHELL"] = state.Container.Shell.String()
|
||||
|
||||
if state.Container.Flags&hst.FNoPlace == 0 {
|
||||
state.params.
|
||||
Place(fhs.AbsEtc.Append("passwd"),
|
||||
[]byte(username+":x:"+
|
||||
state.mapuid.String()+":"+
|
||||
state.mapgid.String()+
|
||||
":Hakurei:"+
|
||||
state.Container.Home.String()+":"+
|
||||
state.Container.Shell.String()+"\n")).
|
||||
Place(fhs.AbsEtc.Append("group"),
|
||||
[]byte("hakurei:x:"+state.mapgid.String()+":\n"))
|
||||
}
|
||||
state.params.
|
||||
Place(fhs.AbsEtc.Append("passwd"),
|
||||
[]byte(username+":x:"+
|
||||
state.mapuid.String()+":"+
|
||||
state.mapgid.String()+
|
||||
":Hakurei:"+
|
||||
state.Container.Home.String()+":"+
|
||||
state.Container.Shell.String()+"\n")).
|
||||
Place(fhs.AbsEtc.Append("group"),
|
||||
[]byte("hakurei:x:"+state.mapgid.String()+":\n"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ func TestSpAccountOp(t *testing.T) {
|
||||
{"success fallback username", func(bool, bool) outcomeOp { return spAccountOp{} }, func() *hst.Config {
|
||||
c := hst.Template()
|
||||
c.Container.Username = ""
|
||||
c.Container.Flags = hst.FMapRealUID
|
||||
return c
|
||||
}, nil, []stub.Call{
|
||||
// this op performs basic validation and does not make calls during toSystem
|
||||
@@ -61,7 +60,9 @@ func TestSpAccountOp(t *testing.T) {
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Dir: config.Container.Home,
|
||||
Ops: new(container.Ops),
|
||||
Ops: new(container.Ops).
|
||||
Place(m("/etc/passwd"), []byte("chronos:x:1000:100:Hakurei:/data/data/org.chromium.Chromium:/run/current-system/sw/bin/zsh\n")).
|
||||
Place(m("/etc/group"), []byte("hakurei:x:100:\n")),
|
||||
}, paramsWantEnv(config, map[string]string{
|
||||
"HOME": config.Container.Home.String(),
|
||||
"USER": config.Container.Username,
|
||||
|
||||
@@ -18,12 +18,13 @@ import (
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/internal/env"
|
||||
"hakurei.app/internal/system"
|
||||
"hakurei.app/internal/validate"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
const varRunNscd = fhs.Var + "run/nscd"
|
||||
|
||||
func init() { gob.Register(new(spParamsOp)) }
|
||||
|
||||
// spParamsOp initialises unordered fields of [container.Params] and the
|
||||
@@ -135,23 +136,17 @@ type spFilesystemOp struct {
|
||||
}
|
||||
|
||||
func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
||||
// retrieve paths and hide them if they're made available in the sandbox
|
||||
//
|
||||
// this feature tries to improve user experience of permissive defaults, and
|
||||
// to warn about issues in custom configuration; it is NOT a security feature
|
||||
// and should not be treated as such, ALWAYS be careful with what you bind
|
||||
/* retrieve paths and hide them if they're made available in the sandbox;
|
||||
|
||||
this feature tries to improve user experience of permissive defaults, and
|
||||
to warn about issues in custom configuration; it is NOT a security feature
|
||||
and should not be treated as such, ALWAYS be careful with what you bind */
|
||||
hidePaths := []string{
|
||||
state.sc.RuntimePath.String(),
|
||||
state.sc.SharePath.String(),
|
||||
}
|
||||
|
||||
if state.Paths == nil || state.HasNscd {
|
||||
hidePaths = append(hidePaths,
|
||||
// this causes emulated passwd database to be bypassed on some /etc/
|
||||
// setups, made optional to avoid needlessly creating it on
|
||||
// non-glibc systems when invoking permissive defaults
|
||||
env.VarRunNscd,
|
||||
)
|
||||
// this causes emulated passwd database to be bypassed on some /etc/ setups
|
||||
varRunNscd,
|
||||
}
|
||||
|
||||
// dbus.Address does not go through syscallDispatcher
|
||||
@@ -387,10 +382,6 @@ func (p opsAdapter) Overlay(target, state, work *check.Absolute, layers ...*chec
|
||||
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 {
|
||||
return opsAdapter{p.Ops.OverlayReadonly(target, layers...)}
|
||||
}
|
||||
|
||||
@@ -113,9 +113,6 @@ func (s *spRuntimeOp) toContainer(state *outcomeStateParams) error {
|
||||
|
||||
}
|
||||
|
||||
if state.Container.Flags&hst.FCoverRun != 0 {
|
||||
state.params.Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755)
|
||||
}
|
||||
state.params.Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755)
|
||||
if state.Container.Flags&hst.FShareRuntime != 0 {
|
||||
_, runtimeDirInst := s.commonPaths(state.outcomeState)
|
||||
|
||||
@@ -40,7 +40,6 @@ func TestSpRuntimeOp(t *testing.T) {
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Ops: new(container.Ops).
|
||||
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||
}, paramsWantEnv(config, map[string]string{
|
||||
@@ -68,7 +67,6 @@ func TestSpRuntimeOp(t *testing.T) {
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Ops: new(container.Ops).
|
||||
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||
}, paramsWantEnv(config, map[string]string{
|
||||
@@ -96,7 +94,6 @@ func TestSpRuntimeOp(t *testing.T) {
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Ops: new(container.Ops).
|
||||
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||
}, paramsWantEnv(config, map[string]string{
|
||||
@@ -120,7 +117,6 @@ func TestSpRuntimeOp(t *testing.T) {
|
||||
// this op configures the container state and does not make calls during toContainer
|
||||
}, &container.Params{
|
||||
Ops: new(container.Ops).
|
||||
Tmpfs(fhs.AbsRun, xdgRuntimeDirSize, 0755).
|
||||
Tmpfs(fhs.AbsRunUser, xdgRuntimeDirSize, 0755).
|
||||
Bind(m("/proc/nonexistent/tmp/hakurei.0/runtime/9"), m("/run/user/1000"), std.BindWritable),
|
||||
}, paramsWantEnv(config, map[string]string{
|
||||
|
||||
@@ -176,8 +176,8 @@ func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
|
||||
case reflect.Struct:
|
||||
data = SPA_TYPE_Struct.append(data)
|
||||
var err error
|
||||
for _, field := range v.Fields() {
|
||||
data, err = marshalValueAppend(data, field)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
data, err = marshalValueAppend(data, v.Field(i))
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
@@ -370,8 +370,8 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
|
||||
}
|
||||
|
||||
var fieldWireSize Word
|
||||
for _, field := range v.Fields() {
|
||||
if err := unmarshalValue(data, field, &fieldWireSize); err != nil {
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if err := unmarshalValue(data, v.Field(i), &fieldWireSize); err != nil {
|
||||
return err
|
||||
}
|
||||
// bounds check completed in successful call to unmarshalValue
|
||||
|
||||
@@ -1,411 +0,0 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
|
||||
/*
|
||||
| mode uint32 | path_sz uint32 |
|
||||
| data_sz uint64 |
|
||||
| path string |
|
||||
| data []byte |
|
||||
*/
|
||||
|
||||
// An ArchiveHeader represents a single header in an archive.
|
||||
type ArchiveHeader struct {
|
||||
Mode fs.FileMode // file mode bits
|
||||
Path string // pathname of the file
|
||||
Size uint64 // size of data segment
|
||||
}
|
||||
|
||||
// Writer implements sequential writing of an archive. [Writer.WriteHeader]
|
||||
// begins a new file with the provided [ArchiveHeader], and then Writer can be
|
||||
// treated as an [io.Writer] to supply that file's data.
|
||||
//
|
||||
// It is the caller's responsibility to write entries in lexical order.
|
||||
type Writer struct {
|
||||
// Underlying writer.
|
||||
w io.Writer
|
||||
// Current header.
|
||||
h ArchiveHeader
|
||||
// Fixed-size header segment.
|
||||
buf [wordSize * 2]byte
|
||||
// Current position in data segment.
|
||||
n uint64
|
||||
}
|
||||
|
||||
// NewWriter returns the address of a new [Writer] writing to w.
|
||||
func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
|
||||
|
||||
var zero [wordSize]byte
|
||||
|
||||
// padSize returns the padding size for aligning sz.
|
||||
func padSize[T int | uint64](sz T) T {
|
||||
return (wordSize - (sz)%wordSize) % wordSize
|
||||
}
|
||||
|
||||
// flush concludes writing to the current file and writes padding.
|
||||
func (aw *Writer) flush() error {
|
||||
if aw.h.Size > aw.n {
|
||||
return fmt.Errorf("missed writing %d bytes", aw.h.Size-aw.n)
|
||||
} else if aw.h.Size < aw.n {
|
||||
return fmt.Errorf("wrote %d bytes beyond end of file", aw.n-aw.h.Size)
|
||||
}
|
||||
|
||||
if psz := padSize(aw.h.Size); psz != 0 {
|
||||
if _, err := aw.w.Write(zero[:psz]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
aw.n = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHeader writes h and begins accepting its corresponding file.
|
||||
func (aw *Writer) WriteHeader(h *ArchiveHeader) error {
|
||||
if err := aw.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aw.h = *h
|
||||
binary.LittleEndian.PutUint32(aw.buf[:], uint32(aw.h.Mode))
|
||||
binary.LittleEndian.PutUint32(aw.buf[wordSize/2:], uint32(len(aw.h.Path)))
|
||||
binary.LittleEndian.PutUint64(aw.buf[wordSize:], aw.h.Size)
|
||||
if _, err := aw.w.Write(aw.buf[:]); err != nil {
|
||||
return err
|
||||
} else if _, err = aw.w.Write(
|
||||
unsafe.Slice(unsafe.StringData(aw.h.Path), len(aw.h.Path)),
|
||||
); err != nil {
|
||||
return err
|
||||
} else if psz := padSize(len(aw.h.Path)); psz != 0 {
|
||||
if _, err = aw.w.Write(zero[:psz]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write writes p to the underlying writer and records the new position. Invalid
|
||||
// positions are reported by WriteHeader and Close.
|
||||
func (aw *Writer) Write(p []byte) (n int, err error) {
|
||||
n, err = aw.w.Write(p)
|
||||
aw.n += uint64(n)
|
||||
return
|
||||
}
|
||||
|
||||
// Close concludes writing to the archive stream.
|
||||
func (aw *Writer) Close() (err error) {
|
||||
err = aw.flush()
|
||||
aw.w = nil
|
||||
return
|
||||
}
|
||||
|
||||
// ErrInsecurePath is returned by [FlatEntry.Decode] if validation is requested
|
||||
// and a nonlocal path is encountered in the stream.
|
||||
var ErrInsecurePath = errors.New("insecure file path")
|
||||
|
||||
// Reader implements sequential reading of an archive. [Reader.Next] advances to
|
||||
// the next file in the archive (including the first), and then Reader can be
|
||||
// treated as an [io.Reader] to access the file's data.
|
||||
type Reader struct {
|
||||
// Underlying reader.
|
||||
r io.Reader
|
||||
// Fixed-size header segment.
|
||||
buf [wordSize * 2]byte
|
||||
// Remaining bytes in current data segment.
|
||||
n, pad uint64
|
||||
}
|
||||
|
||||
// NewReader returns the address of a new [Reader] reading from r.
|
||||
func NewReader(r io.Reader) *Reader { return &Reader{r: r} }
|
||||
|
||||
// Next advances ar to the next entry. Remaining bytes of the current data
|
||||
// segment are discarded. Advancing beyond the final entry returns [io.EOF].
|
||||
func (ar *Reader) Next() (*ArchiveHeader, error) {
|
||||
if dsz := int64(ar.n + ar.pad); dsz > 0 {
|
||||
if n, err := io.CopyN(io.Discard, ar.r, dsz); err != nil {
|
||||
if errors.Is(err, io.EOF) && n != dsz {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(ar.r, ar.buf[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := ArchiveHeader{
|
||||
Mode: fs.FileMode(binary.LittleEndian.Uint32(ar.buf[:])),
|
||||
Size: binary.LittleEndian.Uint64(ar.buf[wordSize:]),
|
||||
}
|
||||
pathSize := int(binary.LittleEndian.Uint32(ar.buf[wordSize/2:]))
|
||||
pPathSize := alignSize(pathSize)
|
||||
|
||||
buf := make([]byte, pPathSize)
|
||||
if _, err := io.ReadFull(ar.r, buf); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h.Path = unsafe.String(unsafe.SliceData(buf), pathSize)
|
||||
if !filepath.IsLocal(h.Path) {
|
||||
return &h, ErrInsecurePath
|
||||
}
|
||||
|
||||
ar.n = h.Size
|
||||
ar.pad = padSize(h.Size)
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// Read implements [io.Reader] for the data segment of the current entry.
|
||||
func (ar *Reader) Read(p []byte) (n int, err error) {
|
||||
if uint64(len(p)) > ar.n {
|
||||
p = p[:ar.n]
|
||||
}
|
||||
|
||||
if len(p) > 0 {
|
||||
n, err = ar.r.Read(p)
|
||||
ar.n -= uint64(n)
|
||||
}
|
||||
|
||||
switch err {
|
||||
case io.EOF:
|
||||
if ar.n > 0 {
|
||||
return n, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
case nil:
|
||||
if ar.n == 0 {
|
||||
return n, io.EOF
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Write writes a deterministic representation of the contents of fsys to w.
|
||||
// The resulting data can be hashed to produce a deterministic checksum for the
|
||||
// directory.
|
||||
func Write(fsys fs.FS, root string, w io.Writer) error {
|
||||
aw := NewWriter(w)
|
||||
if err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var fi fs.FileInfo
|
||||
fi, err = d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := ArchiveHeader{
|
||||
Path: path,
|
||||
Mode: fi.Mode(),
|
||||
}
|
||||
if h.Mode.IsRegular() {
|
||||
h.Size = uint64(fi.Size())
|
||||
if err = aw.WriteHeader(&h); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var r fs.File
|
||||
r, err = fsys.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(aw, r)
|
||||
if _err := r.Close(); err == nil {
|
||||
err = _err
|
||||
}
|
||||
return err
|
||||
} else if h.Mode&fs.ModeSymlink != 0 {
|
||||
var newpath string
|
||||
if newpath, err = fs.ReadLink(fsys, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Size = uint64(len(newpath))
|
||||
if err = aw.WriteHeader(&h); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = aw.Write(unsafe.Slice(unsafe.StringData(newpath), len(newpath)))
|
||||
return err
|
||||
} else if !h.Mode.IsDir() {
|
||||
return InvalidFileModeError(h.Mode)
|
||||
}
|
||||
return aw.WriteHeader(&h)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return aw.Close()
|
||||
}
|
||||
|
||||
// SumFS saves checksum of the archive of fsys to the value pointed to by buf.
|
||||
func SumFS(buf *Checksum, fsys fs.FS, root string) error {
|
||||
h := sha512.New384()
|
||||
if err := Write(fsys, root, h); err != nil {
|
||||
return err
|
||||
}
|
||||
h.Sum(buf[:0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// SumDir saves checksum of the archive of directory at pathname to the value
|
||||
// pointed to by buf.
|
||||
func SumDir(buf *Checksum, pathname *check.Absolute) error {
|
||||
return SumFS(buf, os.DirFS(pathname.String()), ".")
|
||||
}
|
||||
|
||||
// archiveArtifact is an [Artifact] unpacking an archive supported by [Reader]
|
||||
// backed by a [FileArtifact].
|
||||
type archiveArtifact struct {
|
||||
// Caller-supplied backing archive.
|
||||
f Artifact
|
||||
}
|
||||
|
||||
var _ CuresExempt = archiveArtifact{}
|
||||
|
||||
// NewArchive returns a new [Artifact] backed by the supplied [Artifact]. The
|
||||
// source [Artifact] must be a [FileArtifact] and produce a stream compatible
|
||||
// with [Reader].
|
||||
func NewArchive(a Artifact) Artifact {
|
||||
return archiveArtifact{a}
|
||||
}
|
||||
|
||||
// Kind returns the hardcoded [Kind] constant.
|
||||
func (archiveArtifact) Kind() Kind { return KindArchive }
|
||||
|
||||
// Params is a noop.
|
||||
func (archiveArtifact) Params(*IContext) {}
|
||||
|
||||
func init() {
|
||||
register(KindArchive, func(r *IRReader) Artifact {
|
||||
a := NewArchive(r.Next())
|
||||
if _, ok := r.Finalise(); ok {
|
||||
panic(ErrUnexpectedChecksum)
|
||||
}
|
||||
return a
|
||||
})
|
||||
}
|
||||
|
||||
// Inputs returns a slice containing the backing file.
|
||||
func (a archiveArtifact) Inputs() []Artifact {
|
||||
return []Artifact{a.f}
|
||||
}
|
||||
|
||||
// IsExclusive returns false: [Reader] is fully sequential.
|
||||
func (archiveArtifact) IsExclusive() bool { return false }
|
||||
|
||||
// Cure cures the [Artifact], producing a directory located at work.
|
||||
func (a archiveArtifact) Cure(t *TContext) (err error) {
|
||||
var r io.ReadCloser
|
||||
if r, err = t.Open(a.f); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
closeErr := r.Close()
|
||||
if err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
type dirTargetPerm struct {
|
||||
path string
|
||||
mode fs.FileMode
|
||||
}
|
||||
var madeDirectories []dirTargetPerm
|
||||
|
||||
if err = os.MkdirAll(t.GetWorkDir().String(), 0700); err != nil {
|
||||
return
|
||||
}
|
||||
var root *os.Root
|
||||
if root, err = os.OpenRoot(t.GetWorkDir().String()); err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
closeErr := root.Close()
|
||||
if err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
var header *ArchiveHeader
|
||||
ar := NewReader(r)
|
||||
for header, err = ar.Next(); err == nil; header, err = ar.Next() {
|
||||
if header.Mode.IsRegular() {
|
||||
var f *os.File
|
||||
if f, err = root.OpenFile(
|
||||
header.Path,
|
||||
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
|
||||
header.Mode.Perm(),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = io.Copy(f, ar); err != nil {
|
||||
_ = f.Close()
|
||||
return
|
||||
} else if err = f.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
} else if header.Mode&fs.ModeSymlink != 0 {
|
||||
var p []byte
|
||||
if p, err = io.ReadAll(ar); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = root.Symlink(
|
||||
unsafe.String(unsafe.SliceData(p), len(p)),
|
||||
header.Path,
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
} else if header.Mode.IsDir() {
|
||||
if header.Path == "." {
|
||||
continue
|
||||
}
|
||||
|
||||
madeDirectories = append(madeDirectories, dirTargetPerm{
|
||||
path: header.Path,
|
||||
mode: header.Mode,
|
||||
})
|
||||
if err = root.Mkdir(header.Path, 0700); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return InvalidFileModeError(header.Mode)
|
||||
}
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
err = nil
|
||||
}
|
||||
if err == nil {
|
||||
for _, e := range madeDirectories {
|
||||
if err = root.Chmod(e.path, e.mode.Perm()); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CuresExempt exempts the cheap [KindArchive] implementation often found at
|
||||
// the end of a [FileArtifact] pipeline.
|
||||
func (archiveArtifact) CuresExempt() {}
|
||||
@@ -1,240 +0,0 @@
|
||||
package pkg_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/fs"
|
||||
"maps"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func TestArchive(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type entry struct {
|
||||
path string
|
||||
mode fs.FileMode
|
||||
data string
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
fsys fs.FS
|
||||
entries []entry
|
||||
sum pkg.Checksum
|
||||
err error
|
||||
}{
|
||||
{"bad type", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
"invalid": {Mode: fs.ModeCharDevice | 0400},
|
||||
}, nil, pkg.Checksum{}, pkg.InvalidFileModeError(
|
||||
fs.ModeCharDevice | 0400,
|
||||
)},
|
||||
|
||||
{"coldboot", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices": {Mode: fs.ModeDir | 0700},
|
||||
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
"devices/empty": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices/sub": {Mode: fs.ModeDir | 0700},
|
||||
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
|
||||
"block": {Mode: fs.ModeDir | 0700},
|
||||
"block/uevent": {Mode: 0600},
|
||||
}, []entry{
|
||||
{".", fs.ModeDir | 0700, ""},
|
||||
|
||||
{"block", fs.ModeDir | 0700, ""},
|
||||
{"block/uevent", 0600, ""},
|
||||
|
||||
{"devices", fs.ModeDir | 0700, ""},
|
||||
{"devices/empty", fs.ModeDir | 0700, ""},
|
||||
{"devices/sub", fs.ModeDir | 0700, ""},
|
||||
{"devices/sub/uevent", 0600, "add"},
|
||||
{"devices/uevent", 0600, "add"},
|
||||
}, pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8"), nil},
|
||||
|
||||
{"empty", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, []entry{
|
||||
{".", fs.ModeDir | 0700, ""},
|
||||
{"checksum", fs.ModeDir | 0700, ""},
|
||||
{"identifier", fs.ModeDir | 0700, ""},
|
||||
{"work", fs.ModeDir | 0700, ""},
|
||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C"), nil},
|
||||
|
||||
{"sample directory step garbage", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0500},
|
||||
|
||||
"lib": {Mode: fs.ModeDir | 0500},
|
||||
"lib/check": {Mode: 0400},
|
||||
|
||||
"lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
||||
}, []entry{
|
||||
{".", fs.ModeDir | 0500, ""},
|
||||
|
||||
{"lib", fs.ModeDir | 0500, ""},
|
||||
{"lib/check", 0400, ""},
|
||||
|
||||
{"lib/pkgconfig", fs.ModeDir | 0500, ""},
|
||||
}, pkg.MustDecode("CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT"), nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("roundtrip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := pkg.Write(
|
||||
tc.fsys,
|
||||
".",
|
||||
&buf,
|
||||
); !reflect.DeepEqual(err, tc.err) {
|
||||
t.Fatalf("Flatten: error = %v, want %v", err, tc.err)
|
||||
} else if tc.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r := pkg.NewReader(bytes.NewReader(buf.Bytes()))
|
||||
var got []entry
|
||||
for {
|
||||
h, err := r.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Fatalf("Next: error = %v", err)
|
||||
}
|
||||
|
||||
var data []byte
|
||||
if data, err = io.ReadAll(r); err != nil {
|
||||
t.Fatalf("Read: error = %v", err)
|
||||
}
|
||||
|
||||
got = append(got, entry{
|
||||
path: h.Path,
|
||||
mode: h.Mode,
|
||||
data: unsafe.String(unsafe.SliceData(data), len(data)),
|
||||
})
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tc.entries) {
|
||||
t.Fatalf("Reader: %#v, want %#v", got, tc.entries)
|
||||
}
|
||||
})
|
||||
|
||||
if tc.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var got pkg.Checksum
|
||||
if err := pkg.SumFS(&got, tc.fsys, "."); err != nil {
|
||||
t.Fatalf("SumFS: error = %v", err)
|
||||
} else if got != tc.sum {
|
||||
t.Fatalf("SumFS: %v", &pkg.ChecksumMismatchError{
|
||||
Got: got,
|
||||
Want: tc.sum,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var archiveTestdata = fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices": {Mode: fs.ModeDir | 0700},
|
||||
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
"devices/empty": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices/sub": {Mode: fs.ModeDir | 0700},
|
||||
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
|
||||
"block": {Mode: fs.ModeDir | 0700},
|
||||
"block/uevent": {Mode: 0600},
|
||||
}
|
||||
|
||||
func TestArchiveArtifact(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := maps.Clone(archiveTestdata)
|
||||
want["."].Mode = fs.ModeDir | 0500
|
||||
|
||||
checkWithCache(t, []cacheTestCase{
|
||||
{"unpack", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
var buf bytes.Buffer
|
||||
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
{"sample", pkg.NewArchive(
|
||||
pkg.NewFile("", buf.Bytes()),
|
||||
), ignorePathname, expectsFS(want), pkg.WNew, nil},
|
||||
})
|
||||
}, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F": {Mode: fs.ModeDir | 0500},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block/uevent": {Mode: 0600},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/empty": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/-zXKfphyWM2Ko7VMVURaEMSuixNokFSe0xFbnwR2RtRtvMrcPGWalV0LIn45PXTY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F")},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkArchiveRead(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
testdata := buf.Bytes()
|
||||
|
||||
for b.Loop() {
|
||||
r := pkg.NewReader(bytes.NewReader(testdata))
|
||||
for {
|
||||
_, err := r.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkArchiveWrite(b *testing.B) {
|
||||
for b.Loop() {
|
||||
if err := pkg.Write(archiveTestdata, ".", io.Discard); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"unique"
|
||||
)
|
||||
|
||||
// Clean destroys checksum backing entries without any identifier or substitute
|
||||
// entry referring to it. If at least one keep [Artifact] is specified,
|
||||
// identifier and substitute entries not kept alive by them are destroyed first.
|
||||
func (c *Cache) Clean(dry, inputs bool, keep ...Artifact) (
|
||||
[]unique.Handle[ID],
|
||||
[]unique.Handle[Checksum],
|
||||
error,
|
||||
) {
|
||||
c.identMu.Lock()
|
||||
defer c.identMu.Unlock()
|
||||
|
||||
c.checksumMu.Lock()
|
||||
defer c.checksumMu.Unlock()
|
||||
|
||||
dents, err := os.ReadDir(c.base.Append(dirChecksum).String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
checksums := make(map[unique.Handle[Checksum]]string, len(dents))
|
||||
var buf Checksum
|
||||
for _, dent := range dents {
|
||||
name := dent.Name()
|
||||
if err = Decode(&buf, name); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
checksums[unique.Make(buf)] = name
|
||||
}
|
||||
|
||||
type identPair struct {
|
||||
id unique.Handle[ID]
|
||||
name string
|
||||
}
|
||||
dents, err = os.ReadDir(c.base.Append(dirIdentifier).String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
keepIdents := make(map[unique.Handle[ID]]struct{})
|
||||
if inputs {
|
||||
for _, id := range Inputs((*Collect)(&keep)) {
|
||||
keepIdents[id] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
for _, a := range keep {
|
||||
keepIdents[c.Ident(a)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
idents := make([]identPair, 0, len(dents))
|
||||
for _, dent := range dents {
|
||||
name := dent.Name()
|
||||
if err = Decode(&buf, name); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
id := unique.Make(ID(buf))
|
||||
|
||||
if _, ok := keepIdents[id]; len(keep) == 0 || ok {
|
||||
if err = readlinkChecksum(c.base.Append(
|
||||
dirIdentifier,
|
||||
name,
|
||||
), &buf); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
delete(checksums, unique.Make(buf))
|
||||
continue
|
||||
}
|
||||
|
||||
c.msg.Verbosef(
|
||||
"arranging for destruction of %s%s%s...",
|
||||
c.sgrIdent, name, c.sgrRes,
|
||||
)
|
||||
idents = append(idents, identPair{id, name})
|
||||
}
|
||||
|
||||
destroyedIdents := make([]unique.Handle[ID], 0, len(idents))
|
||||
for _, pair := range idents {
|
||||
if !dry {
|
||||
if err = os.Remove(c.base.Append(
|
||||
dirStatus,
|
||||
pair.name,
|
||||
).String()); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return destroyedIdents, nil, err
|
||||
}
|
||||
|
||||
if err = os.Remove(c.base.Append(
|
||||
dirIdentifier,
|
||||
pair.name,
|
||||
).String()); err != nil {
|
||||
return destroyedIdents, nil, err
|
||||
}
|
||||
}
|
||||
destroyedIdents = append(destroyedIdents, pair.id)
|
||||
}
|
||||
|
||||
destroyedChecksums := make([]unique.Handle[Checksum], 0, len(checksums))
|
||||
for checksum, name := range checksums {
|
||||
if err = c.parent.Err(); err != nil {
|
||||
return destroyedIdents, destroyedChecksums, err
|
||||
}
|
||||
c.msg.Verbosef(
|
||||
"destroying checksum %s%s%s...",
|
||||
c.sgrIdent, name, c.sgrRes,
|
||||
)
|
||||
if !dry {
|
||||
if err = errors.Join(removeAll(c.base.Append(
|
||||
dirChecksum,
|
||||
name,
|
||||
))); err != nil {
|
||||
return destroyedIdents, destroyedChecksums, err
|
||||
}
|
||||
}
|
||||
destroyedChecksums = append(destroyedChecksums, checksum)
|
||||
}
|
||||
|
||||
dents, err = os.ReadDir(c.base.Append(dirSubstitute).String())
|
||||
if err != nil {
|
||||
return destroyedIdents, destroyedChecksums, err
|
||||
}
|
||||
for _, dent := range dents {
|
||||
name := dent.Name()
|
||||
if err = readlinkChecksum(c.base.Append(
|
||||
dirSubstitute,
|
||||
name,
|
||||
), &buf); err != nil {
|
||||
return destroyedIdents, destroyedChecksums, err
|
||||
}
|
||||
if _, ok := checksums[unique.Make(buf)]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
c.msg.Verbosef(
|
||||
"destroying substitute %s%s%s...",
|
||||
c.sgrIdent, name, c.sgrRes,
|
||||
)
|
||||
if !dry {
|
||||
if err = os.Remove(c.base.Append(
|
||||
dirStatus,
|
||||
name,
|
||||
).String()); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return destroyedIdents, nil, err
|
||||
}
|
||||
|
||||
if err = os.Remove(c.base.Append(
|
||||
dirSubstitute,
|
||||
name,
|
||||
).String()); err != nil {
|
||||
return destroyedIdents, destroyedChecksums, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return destroyedIdents, destroyedChecksums, nil
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
package pkg_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"unique"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// formatHandles returns a user-facing string representing h.
|
||||
func formatHandles[T pkg.ID | pkg.Checksum](handles ...unique.Handle[T]) string {
|
||||
var buf strings.Builder
|
||||
for _, h := range handles {
|
||||
buf.WriteString(pkg.Encode(pkg.Checksum(h.Value())))
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
return strings.TrimSuffix(buf.String(), ", ")
|
||||
}
|
||||
|
||||
func TestClean(t *testing.T) {
|
||||
t.Parallel()
|
||||
ic := pkg.NewIR()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
a []pkg.Artifact
|
||||
keep []pkg.Artifact
|
||||
inputs bool
|
||||
want expectsFS
|
||||
|
||||
wantIdents []unique.Handle[pkg.ID]
|
||||
wantChecksums []unique.Handle[pkg.Checksum]
|
||||
}{
|
||||
{"simple", []pkg.Artifact{
|
||||
pkg.NewFile("file", nil),
|
||||
}, nil, false, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/h6qx7m0vsyhO6oLEJgY2rKa1c6RZuIZ4QXxZSh__NZJ9VU0MIj5bFB-KuaPcsfpE": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||
|
||||
"lock": {Mode: 0644},
|
||||
"variant": {Mode: 0400},
|
||||
"status": {Mode: fs.ModeDir | 0700},
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"fault": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, nil, nil},
|
||||
|
||||
{"keep", []pkg.Artifact{
|
||||
pkg.NewFile("removed-file", []byte("removed file")),
|
||||
}, []pkg.Artifact{
|
||||
pkg.NewFile("file", []byte("\xfd")),
|
||||
}, false, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/KgZ-FjbGuU-XP2QEHInpgv-2Zn0cTH5NqFMgTU0XrSdKmSwyC-3baVs1BMCP5spk": {Mode: 0400, Data: []byte("\xfd")},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/tkrOYhQfTRaArk4nNYNjTmHQUmuJMVIoDeAKIQBm3I4uuMimBKXKMIY2gGZqwQg4": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/KgZ-FjbGuU-XP2QEHInpgv-2Zn0cTH5NqFMgTU0XrSdKmSwyC-3baVs1BMCP5spk")},
|
||||
|
||||
"lock": {Mode: 0644},
|
||||
"variant": {Mode: 0400},
|
||||
"status": {Mode: fs.ModeDir | 0700},
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"fault": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, []unique.Handle[pkg.ID]{
|
||||
ic.Ident(pkg.NewFile("removed-file", []byte("removed file"))),
|
||||
}, []unique.Handle[pkg.Checksum]{
|
||||
unique.Make(sha512.Sum384([]byte("removed file"))),
|
||||
}},
|
||||
|
||||
{"inputs anchored substitute", []pkg.Artifact{
|
||||
&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("destroyed"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||
},
|
||||
cure: func(f *pkg.FContext) error {
|
||||
p := f.GetWorkDir()
|
||||
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(p.Append("result").String(), nil, 0444)
|
||||
},
|
||||
},
|
||||
}, []pkg.Artifact{
|
||||
&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("kept"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("kept-input", []byte("kept")),
|
||||
},
|
||||
cure: func(f *pkg.FContext) error {
|
||||
p := f.GetWorkDir()
|
||||
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(p.Append("result").String(), nil, 0444)
|
||||
},
|
||||
},
|
||||
}, true, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv": {Mode: 0400, Data: []byte("kept")},
|
||||
"checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE": {Mode: fs.ModeDir | 0500},
|
||||
"checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE/result": {Mode: 0444},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/KpxTUsUfGbsX6C6HU2j9cd9He3_tt2o2m2MgH4m0zial8lmoqSWY-Gc3KrS0k-UK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv")},
|
||||
"identifier/xxQ43VMIS1WHMSEFnUdBNd6pulbtgiTorP_mtJpEsZENYhGBiDEL6Y2D8yqn0Sra": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||
|
||||
"lock": {Mode: 0644},
|
||||
"variant": {Mode: 0400},
|
||||
"status": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"substitute/dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||
"substitute/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||
|
||||
"fault": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, []unique.Handle[pkg.ID]{
|
||||
ic.Ident(pkg.NewFile("destroyed-input", []byte("destroyed"))),
|
||||
ic.Ident(&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("destroyed"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||
},
|
||||
}),
|
||||
}, []unique.Handle[pkg.Checksum]{
|
||||
unique.Make(sha512.Sum384([]byte("destroyed"))),
|
||||
}},
|
||||
|
||||
{"inputs", []pkg.Artifact{
|
||||
&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("destroyed"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||
},
|
||||
cure: func(f *pkg.FContext) error {
|
||||
if w, err := f.GetStatusWriter(); err != nil {
|
||||
return err
|
||||
} else if _, err = w.Write([]byte("destroyed")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := f.GetWorkDir()
|
||||
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(p.Append("result").String(), nil, 0444)
|
||||
},
|
||||
},
|
||||
}, []pkg.Artifact{
|
||||
&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("kept"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("kept-input", []byte("kept")),
|
||||
},
|
||||
cure: func(f *pkg.FContext) error {
|
||||
if w, err := f.GetStatusWriter(); err != nil {
|
||||
return err
|
||||
} else if _, err = w.Write([]byte("kept")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := f.GetWorkDir()
|
||||
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(p.Append("result").String(), []byte{0}, 0444)
|
||||
},
|
||||
},
|
||||
}, true, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W": {Mode: fs.ModeDir | 0500},
|
||||
"checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W/result": {Mode: 0444, Data: []byte("\x00")},
|
||||
"checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv": {Mode: 0400, Data: []byte("kept")},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/KpxTUsUfGbsX6C6HU2j9cd9He3_tt2o2m2MgH4m0zial8lmoqSWY-Gc3KrS0k-UK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv")},
|
||||
"identifier/xxQ43VMIS1WHMSEFnUdBNd6pulbtgiTorP_mtJpEsZENYhGBiDEL6Y2D8yqn0Sra": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")},
|
||||
|
||||
"lock": {Mode: 0644},
|
||||
"variant": {Mode: 0400},
|
||||
|
||||
"status": {Mode: fs.ModeDir | 0700},
|
||||
"status/xxQ43VMIS1WHMSEFnUdBNd6pulbtgiTorP_mtJpEsZENYhGBiDEL6Y2D8yqn0Sra": {Mode: 0400, Data: []byte(statusHeader + "kept")},
|
||||
"status/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: 0400, Data: []byte(statusHeader + "kept")},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"substitute/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")},
|
||||
|
||||
"fault": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, []unique.Handle[pkg.ID]{
|
||||
ic.Ident(pkg.NewFile("destroyed-input", []byte("destroyed"))),
|
||||
ic.Ident(&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("destroyed"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||
},
|
||||
}),
|
||||
}, []unique.Handle[pkg.Checksum]{
|
||||
unique.Make(expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0500},
|
||||
"result": {Mode: 0444},
|
||||
}.hash()),
|
||||
unique.Make(sha512.Sum384([]byte("destroyed"))),
|
||||
}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
base := makeBase(t)
|
||||
msg := message.New(log.New(os.Stderr, "clean: ", 0))
|
||||
msg.SwapVerbose(testing.Verbose())
|
||||
c, err := pkg.Open(t.Context(), msg, base, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(c.Close)
|
||||
|
||||
all := pkg.Collect(slices.Concat(tc.a, tc.keep))
|
||||
if _, _, err = c.Cure(&all); !pkg.IsCollected(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var (
|
||||
idents []unique.Handle[pkg.ID]
|
||||
checksums []unique.Handle[pkg.Checksum]
|
||||
)
|
||||
idents, checksums, err = c.Clean(false, tc.inputs, tc.keep...)
|
||||
if err != nil {
|
||||
t.Fatalf("Clean: error = %v", err)
|
||||
}
|
||||
var buf [2]pkg.Checksum
|
||||
|
||||
slices.SortFunc(idents, func(a, b unique.Handle[pkg.ID]) int {
|
||||
buf[0], buf[1] = a.Value(), b.Value()
|
||||
return bytes.Compare(buf[0][:], buf[1][:])
|
||||
})
|
||||
slices.SortFunc(checksums, func(a, b unique.Handle[pkg.Checksum]) int {
|
||||
buf[0], buf[1] = a.Value(), b.Value()
|
||||
return bytes.Compare(buf[0][:], buf[1][:])
|
||||
})
|
||||
|
||||
if !slices.Equal(idents, tc.wantIdents) {
|
||||
t.Errorf(
|
||||
"Clean: idents = %s, want %s",
|
||||
formatHandles(idents...), formatHandles(tc.wantIdents...),
|
||||
)
|
||||
}
|
||||
if !slices.Equal(checksums, tc.wantChecksums) {
|
||||
t.Errorf(
|
||||
"Clean: checksums = %s, want %s",
|
||||
formatHandles(checksums...), formatHandles(tc.wantChecksums...),
|
||||
)
|
||||
}
|
||||
|
||||
want := tc.want.hash()
|
||||
var checksum pkg.Checksum
|
||||
if err = pkg.SumDir(&checksum, base); err != nil {
|
||||
t.Fatalf("SumDir: error = %v", err)
|
||||
} else if checksum != want {
|
||||
t.Error(expectsFrom(base.String()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"hakurei.app/internal/xz"
|
||||
"hakurei.app/internal/zstd"
|
||||
)
|
||||
|
||||
const (
|
||||
// Gzip denotes a stream compressed via [gzip].
|
||||
Gzip = iota
|
||||
// Bzip2 denotes a stream compressed via [bzip2].
|
||||
Bzip2
|
||||
// Zstd denotes a stream compressed via [zstd].
|
||||
Zstd
|
||||
// XZ denotes a stream compressed via [xz].
|
||||
XZ
|
||||
)
|
||||
|
||||
// A decompressArtifact is a [FileArtifact] decompressing a backing
|
||||
// [FileArtifact] stream.
|
||||
type decompressArtifact struct {
|
||||
// Caller-supplied backing stream.
|
||||
f Artifact
|
||||
// Compression on top of the stream.
|
||||
compress uint32
|
||||
}
|
||||
|
||||
var _ FileArtifact = new(decompressArtifact)
|
||||
var _ CuresExempt = new(decompressArtifact)
|
||||
|
||||
// decompressArtifactNamed embeds decompressArtifact for a [fmt.Stringer] stream.
|
||||
type decompressArtifactNamed struct {
|
||||
decompressArtifact
|
||||
// Copied from decompressArtifact.f.
|
||||
name string
|
||||
}
|
||||
|
||||
var _ fmt.Stringer = new(decompressArtifactNamed)
|
||||
|
||||
// NewDecompress returns a [FileArtifact] decompressing the supplied [Artifact].
|
||||
func NewDecompress(a Artifact, compress uint32) FileArtifact {
|
||||
da := decompressArtifact{a, compress}
|
||||
if s, ok := a.(fmt.Stringer); ok {
|
||||
if name := s.String(); name != "" {
|
||||
return &decompressArtifactNamed{da, name}
|
||||
}
|
||||
}
|
||||
return &da
|
||||
}
|
||||
|
||||
// String returns the name of the underlying [Artifact] prefixed with decompress.
|
||||
func (a *decompressArtifactNamed) String() string { return "decompress-" + a.name }
|
||||
|
||||
// Kind returns the hardcoded [Kind] constant.
|
||||
func (a *decompressArtifact) Kind() Kind { return KindDecompress }
|
||||
|
||||
// Params writes value of compression enum.
|
||||
func (a *decompressArtifact) Params(ctx *IContext) { ctx.WriteUint32(a.compress) }
|
||||
|
||||
func init() {
|
||||
register(KindDecompress, func(r *IRReader) Artifact {
|
||||
a := NewDecompress(r.Next(), r.ReadUint32())
|
||||
if _, ok := r.Finalise(); ok {
|
||||
panic(ErrUnexpectedChecksum)
|
||||
}
|
||||
return a
|
||||
})
|
||||
}
|
||||
|
||||
// Inputs returns a slice containing the backing file.
|
||||
func (a *decompressArtifact) Inputs() []Artifact {
|
||||
return []Artifact{a.f}
|
||||
}
|
||||
|
||||
// IsExclusive returns false: decompressor is fully sequential.
|
||||
func (a *decompressArtifact) IsExclusive() bool { return false }
|
||||
|
||||
// compoundCloser is an [io.ReadCloser] with an additional [io.Closer] attached.
|
||||
type compoundCloser struct {
|
||||
io.ReadCloser
|
||||
c io.Closer
|
||||
}
|
||||
|
||||
// Close closes [io.ReadCloser] and the additional [io.Closer]. It returns the
|
||||
// non-nil error returned by the underlying [io.ReadCloser], otherwise it
|
||||
// returns the error returned by the additional [io.Closer].
|
||||
func (c compoundCloser) Close() error {
|
||||
err := c.ReadCloser.Close()
|
||||
if _err := c.c.Close(); err == nil {
|
||||
err = _err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// IsExecutable returns false.
|
||||
func (*decompressArtifact) IsExecutable() bool { return false }
|
||||
|
||||
// Cure returns a decompressor [io.ReadCloser].
|
||||
func (a *decompressArtifact) Cure(r *RContext) (io.ReadCloser, error) {
|
||||
sr, err := r.Open(a.f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
br := r.cache.getReaderRC(sr)
|
||||
|
||||
var dr io.ReadCloser
|
||||
switch a.compress {
|
||||
case Gzip:
|
||||
if dr, err = gzip.NewReader(br); err != nil {
|
||||
_ = br.Close()
|
||||
return nil, err
|
||||
}
|
||||
return compoundCloser{dr, br}, nil
|
||||
|
||||
case Bzip2:
|
||||
return struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}{bzip2.NewReader(br), br}, nil
|
||||
|
||||
case Zstd:
|
||||
return struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}{zstd.NewReader(br), br}, nil
|
||||
|
||||
case XZ:
|
||||
var _dr io.Reader
|
||||
if _dr, err = xz.NewReader(br, 0); err != nil {
|
||||
_ = br.Close()
|
||||
return nil, err
|
||||
}
|
||||
return struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}{_dr, br}, nil
|
||||
|
||||
default:
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
}
|
||||
|
||||
// CuresExempt exempts the cheap [KindDecompress] implementation often part of
|
||||
// a [FileArtifact] pipeline.
|
||||
func (*decompressArtifact) CuresExempt() {}
|
||||
@@ -1,70 +0,0 @@
|
||||
package pkg_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha512"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func TestDecompress(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
gw := gzip.NewWriter(&buf)
|
||||
if _, err := gw.Write([]byte{0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = gw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testdata := buf.String()
|
||||
|
||||
var transport http.Transport
|
||||
client := http.Client{Transport: &transport}
|
||||
transport.RegisterProtocol("file", http.NewFileTransportFS(fstest.MapFS{
|
||||
"testdata": {Data: []byte(testdata), Mode: 0400},
|
||||
}))
|
||||
testdataChecksum := func() pkg.Checksum {
|
||||
h := sha512.New384()
|
||||
h.Write([]byte(testdata))
|
||||
return (pkg.Checksum)(h.Sum(nil))
|
||||
}()
|
||||
|
||||
checkWithCache(t, []cacheTestCase{
|
||||
{"decompress", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
cureMany(t, c, []cureStep{
|
||||
{"close", pkg.NewDecompress(pkg.NewHTTPGet(
|
||||
&client,
|
||||
"file:///testdata",
|
||||
pkg.Checksum{0xfd},
|
||||
), pkg.Gzip), nil, nil, pkg.WNew, &pkg.ChecksumMismatchError{
|
||||
Got: testdataChecksum,
|
||||
Want: pkg.Checksum{0xfd},
|
||||
}},
|
||||
|
||||
{"gzip", pkg.NewDecompress(pkg.NewHTTPGet(
|
||||
&client,
|
||||
"file:///testdata",
|
||||
testdataChecksum,
|
||||
), pkg.Gzip), ignorePathname, expectsChecksum(sha512.Sum384([]byte{0})), pkg.WNew, nil},
|
||||
})
|
||||
}, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/" + pkg.Encode(sha512.Sum384([]byte{0})): {Mode: 0400, Data: []byte{0}},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/mae-iR_9LFT_BOEJ2IRzKsAlI754SalMjjt95fZeMd3gEyAzsrvuOXnXN0bFZJi4": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
|
||||
// FlatEntry is a directory entry to be encoded for [Flatten].
|
||||
type FlatEntry struct {
|
||||
Mode fs.FileMode // file mode bits
|
||||
Path string // pathname of the file
|
||||
Data []byte // file content or symlink destination
|
||||
}
|
||||
|
||||
/*
|
||||
| mode uint32 | path_sz uint32 |
|
||||
| data_sz uint64 |
|
||||
| path string |
|
||||
| data []byte |
|
||||
*/
|
||||
|
||||
// Encode encodes the entry for transmission or hashing.
|
||||
func (ent *FlatEntry) Encode(w io.Writer) (n int, err error) {
|
||||
pPathSize := alignSize(len(ent.Path))
|
||||
if pPathSize > math.MaxUint32 {
|
||||
return 0, syscall.E2BIG
|
||||
}
|
||||
pDataSize := alignSize(len(ent.Data))
|
||||
|
||||
payload := make([]byte, wordSize*2+pPathSize+pDataSize)
|
||||
binary.LittleEndian.PutUint32(payload, uint32(ent.Mode))
|
||||
binary.LittleEndian.PutUint32(payload[wordSize/2:], uint32(len(ent.Path)))
|
||||
binary.LittleEndian.PutUint64(payload[wordSize:], uint64(len(ent.Data)))
|
||||
copy(payload[wordSize*2:], ent.Path)
|
||||
copy(payload[wordSize*2+pPathSize:], ent.Data)
|
||||
return w.Write(payload)
|
||||
}
|
||||
|
||||
// ErrInsecurePath is returned by [FlatEntry.Decode] if validation is requested
|
||||
// and a nonlocal path is encountered in the stream.
|
||||
var ErrInsecurePath = errors.New("insecure file path")
|
||||
|
||||
// Decode decodes the entry from its representation produced by Encode.
|
||||
func (ent *FlatEntry) Decode(r io.Reader, validate bool) (n int, err error) {
|
||||
var nr int
|
||||
|
||||
header := make([]byte, wordSize*2)
|
||||
nr, err = r.Read(header)
|
||||
n += nr
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) && n != 0 {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ent.Mode = fs.FileMode(binary.LittleEndian.Uint32(header))
|
||||
pathSize := int(binary.LittleEndian.Uint32(header[wordSize/2:]))
|
||||
pPathSize := alignSize(pathSize)
|
||||
dataSize := int(binary.LittleEndian.Uint64(header[wordSize:]))
|
||||
pDataSize := alignSize(dataSize)
|
||||
|
||||
buf := make([]byte, pPathSize+pDataSize)
|
||||
nr, err = r.Read(buf)
|
||||
n += nr
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
if nr != len(buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ent.Path = string(buf[:pathSize])
|
||||
if ent.Mode.IsDir() {
|
||||
ent.Data = nil
|
||||
} else {
|
||||
ent.Data = buf[pPathSize : pPathSize+dataSize]
|
||||
}
|
||||
|
||||
if validate && !filepath.IsLocal(ent.Path) {
|
||||
err = ErrInsecurePath
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DirScanner provides an efficient interface for reading a stream of encoded
|
||||
// [FlatEntry]. Successive calls to the Scan method will step through the
|
||||
// entries in the stream.
|
||||
type DirScanner struct {
|
||||
// Underlying reader to scan [FlatEntry] representations from.
|
||||
r io.Reader
|
||||
|
||||
// First non-EOF I/O error, returned by the Err method.
|
||||
err error
|
||||
|
||||
// Entry to store results in. Its address is returned by the Entry method
|
||||
// and is updated on every call to Scan.
|
||||
ent FlatEntry
|
||||
|
||||
// Validate pathnames during decoding.
|
||||
validate bool
|
||||
}
|
||||
|
||||
// NewDirScanner returns the address of a new instance of [DirScanner] reading
|
||||
// from r. The caller must no longer read from r after this function returns.
|
||||
func NewDirScanner(r io.Reader, validate bool) *DirScanner {
|
||||
return &DirScanner{r: r, validate: validate}
|
||||
}
|
||||
|
||||
// Err returns the first non-EOF I/O error.
|
||||
func (s *DirScanner) Err() error {
|
||||
if errors.Is(s.err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return s.err
|
||||
}
|
||||
|
||||
// Entry returns the address to the [FlatEntry] value storing the last result.
|
||||
func (s *DirScanner) Entry() *FlatEntry { return &s.ent }
|
||||
|
||||
// Scan advances to the next [FlatEntry].
|
||||
func (s *DirScanner) Scan() bool {
|
||||
if s.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var n int
|
||||
n, s.err = s.ent.Decode(s.r, s.validate)
|
||||
if errors.Is(s.err, io.EOF) {
|
||||
return n != 0
|
||||
}
|
||||
return s.err == nil
|
||||
}
|
||||
|
||||
// Flatten writes a deterministic representation of the contents of fsys to w.
|
||||
// The resulting data can be hashed to produce a deterministic checksum for the
|
||||
// directory.
|
||||
func Flatten(fsys fs.FS, root string, w io.Writer) (n int, err error) {
|
||||
var nr int
|
||||
err = fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var fi fs.FileInfo
|
||||
fi, err = d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ent := FlatEntry{
|
||||
Path: path,
|
||||
Mode: fi.Mode(),
|
||||
}
|
||||
if ent.Mode.IsRegular() {
|
||||
if ent.Data, err = fs.ReadFile(fsys, path); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if ent.Mode&fs.ModeSymlink != 0 {
|
||||
var newpath string
|
||||
if newpath, err = fs.ReadLink(fsys, path); err != nil {
|
||||
return err
|
||||
}
|
||||
ent.Data = []byte(newpath)
|
||||
} else if !ent.Mode.IsDir() {
|
||||
return InvalidFileModeError(ent.Mode)
|
||||
}
|
||||
|
||||
nr, err = ent.Encode(w)
|
||||
n += nr
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// HashFS returns a checksum produced by hashing the result of [Flatten].
|
||||
func HashFS(buf *Checksum, fsys fs.FS, root string) error {
|
||||
h := sha512.New384()
|
||||
if _, err := Flatten(fsys, root, h); err != nil {
|
||||
return err
|
||||
}
|
||||
h.Sum(buf[:0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// HashDir returns a checksum produced by hashing the result of [Flatten].
|
||||
func HashDir(buf *Checksum, pathname *check.Absolute) error {
|
||||
return HashFS(buf, os.DirFS(pathname.String()), ".")
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user