internal/uevent: synthetic events for coldboot
All checks were successful
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m5s
Test / Hakurei (push) Successful in 4m12s
Test / ShareFS (push) Successful in 4m16s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 6m39s
Test / Flake checks (push) Successful in 1m24s
All checks were successful
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m5s
Test / Hakurei (push) Successful in 4m12s
Test / ShareFS (push) Successful in 4m16s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 6m39s
Test / Flake checks (push) Successful in 1m24s
This causes the kernel to regenerate events that happened before earlyinit started. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -27,6 +27,31 @@ func TestFlatten(t *testing.T) {
|
|||||||
fs.ModeCharDevice | 0400,
|
fs.ModeCharDevice | 0400,
|
||||||
)},
|
)},
|
||||||
|
|
||||||
|
{"coldboot", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"devices": {Mode: fs.ModeDir | 0700},
|
||||||
|
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||||
|
"devices/empty": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"devices/sub": {Mode: fs.ModeDir | 0700},
|
||||||
|
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||||
|
|
||||||
|
"block": {Mode: fs.ModeDir | 0700},
|
||||||
|
"block/uevent": {Mode: 0600, Data: []byte{}},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "."},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "block"},
|
||||||
|
{Mode: 0600, Path: "block/uevent", Data: []byte{}},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "devices"},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "devices/empty"},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "devices/sub"},
|
||||||
|
{Mode: 0600, Path: "devices/sub/uevent", Data: []byte("add")},
|
||||||
|
{Mode: 0600, Path: "devices/uevent", Data: []byte("add")},
|
||||||
|
}, pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8"), nil},
|
||||||
|
|
||||||
{"empty", fstest.MapFS{
|
{"empty", fstest.MapFS{
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
|||||||
71
internal/uevent/coldboot.go
Normal file
71
internal/uevent/coldboot.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package uevent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// synthAdd is prepared bytes written to uevent to cause a synthetic add event
|
||||||
|
// to be emitted during coldboot.
|
||||||
|
var synthAdd = []byte(KOBJ_ADD.String())
|
||||||
|
|
||||||
|
// Coldboot writes "add" to every uevent file that it finds in /sys/devices.
|
||||||
|
// This causes the kernel to regenerate the uevents for these paths.
|
||||||
|
//
|
||||||
|
// The specified pathname must present the sysfs root.
|
||||||
|
//
|
||||||
|
// Note that while [AOSP documentation] claims to also scan /sys/class and
|
||||||
|
// /sys/block, this is no longer the case, and the documentation was not updated
|
||||||
|
// when this changed.
|
||||||
|
//
|
||||||
|
// [AOSP documentation]: https://android.googlesource.com/platform/system/core/+/master/init/README.ueventd.md
|
||||||
|
func Coldboot(
|
||||||
|
ctx context.Context,
|
||||||
|
pathname string,
|
||||||
|
visited chan<- string,
|
||||||
|
handleWalkErr func(error) error,
|
||||||
|
) error {
|
||||||
|
if handleWalkErr == nil {
|
||||||
|
handleWalkErr = func(err error) error {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
log.Println("coldboot", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.WalkDir(filepath.Join(pathname, "devices"), func(
|
||||||
|
path string,
|
||||||
|
d fs.DirEntry,
|
||||||
|
err error,
|
||||||
|
) error {
|
||||||
|
if err != nil {
|
||||||
|
return handleWalkErr(err)
|
||||||
|
}
|
||||||
|
if err = ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.IsDir() || d.Name() != "uevent" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(path, synthAdd, 0); err != nil {
|
||||||
|
return handleWalkErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case visited <- path:
|
||||||
|
break
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
227
internal/uevent/coldboot_test.go
Normal file
227
internal/uevent/coldboot_test.go
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
package uevent_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestColdboot(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
d := t.TempDir()
|
||||||
|
if err := os.Chmod(d, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range []string{
|
||||||
|
"devices",
|
||||||
|
"devices/sub",
|
||||||
|
"devices/empty",
|
||||||
|
"block",
|
||||||
|
} {
|
||||||
|
if err := os.MkdirAll(filepath.Join(d, s), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range [][2]string{
|
||||||
|
{"devices/uevent", ""},
|
||||||
|
{"devices/sub/uevent", ""},
|
||||||
|
{"block/uevent", ""},
|
||||||
|
} {
|
||||||
|
if err := os.WriteFile(
|
||||||
|
filepath.Join(d, f[0]),
|
||||||
|
[]byte(f[1]),
|
||||||
|
0600,
|
||||||
|
); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
defer wg.Wait()
|
||||||
|
|
||||||
|
visited := make(chan string)
|
||||||
|
var got []string
|
||||||
|
wg.Go(func() {
|
||||||
|
for path := range visited {
|
||||||
|
got = append(got, path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
err := uevent.Coldboot(t.Context(), d, visited, func(err error) error {
|
||||||
|
t.Errorf("handleWalkErr: %v", err)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
close(visited)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Coldboot: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
want := []string{
|
||||||
|
"devices/sub/uevent",
|
||||||
|
"devices/uevent",
|
||||||
|
}
|
||||||
|
for i, rel := range want {
|
||||||
|
want[i] = filepath.Join(d, rel)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got, want) {
|
||||||
|
t.Errorf("Coldboot: %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
var checksum pkg.Checksum
|
||||||
|
if err = pkg.HashDir(&checksum, check.MustAbs(d)); err != nil {
|
||||||
|
t.Fatalf("HashDir: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantChecksum := pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8")
|
||||||
|
if checksum != wantChecksum {
|
||||||
|
t.Errorf(
|
||||||
|
"Coldboot: checksum = %s, want %s",
|
||||||
|
pkg.Encode(checksum),
|
||||||
|
pkg.Encode(wantChecksum),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColdbootError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
dF func(t *testing.T, d string) (wantErr error)
|
||||||
|
vF func(<-chan string, context.Context, context.CancelFunc)
|
||||||
|
hF func(d string, err error) error
|
||||||
|
}{
|
||||||
|
{"walk", func(t *testing.T, d string) (wantErr error) {
|
||||||
|
wantErr = &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: filepath.Join(d, "devices"),
|
||||||
|
Err: syscall.EACCES,
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(filepath.Join(d, "devices"), 0); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}, nil, nil},
|
||||||
|
|
||||||
|
{"write", func(t *testing.T, d string) (wantErr error) {
|
||||||
|
wantErr = &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: filepath.Join(d, "devices/uevent"),
|
||||||
|
Err: syscall.EACCES,
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err = os.WriteFile(filepath.Join(d, "devices/uevent"), nil, 0); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}, nil, nil},
|
||||||
|
|
||||||
|
{"deref", func(t *testing.T, d string) (wantErr error) {
|
||||||
|
if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err = os.Symlink("/proc/nonexistent", filepath.Join(d, "devices/uevent")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}, nil, nil},
|
||||||
|
|
||||||
|
{"deref handle", func(t *testing.T, d string) (wantErr error) {
|
||||||
|
if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err = os.Symlink("/proc/nonexistent", filepath.Join(d, "devices/uevent")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}, nil, func(d string, err error) error {
|
||||||
|
if reflect.DeepEqual(err, &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: filepath.Join(d, "devices/uevent"),
|
||||||
|
Err: syscall.ENOENT,
|
||||||
|
}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"cancel early", func(t *testing.T, d string) (wantErr error) {
|
||||||
|
wantErr = context.Canceled
|
||||||
|
if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}, func(visited <-chan string, ctx context.Context, cancel context.CancelFunc) {
|
||||||
|
if visited == nil {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}, nil},
|
||||||
|
|
||||||
|
{"cancel", func(t *testing.T, d string) (wantErr error) {
|
||||||
|
wantErr = context.Canceled
|
||||||
|
if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err = os.WriteFile(filepath.Join(d, "devices/uevent"), nil, 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err = os.Mkdir(filepath.Join(d, "devices/sub"), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err = os.WriteFile(filepath.Join(d, "devices/sub/uevent"), nil, 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}, func(visited <-chan string, ctx context.Context, cancel context.CancelFunc) {
|
||||||
|
if visited == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
<-visited
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}, nil},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
d := t.TempDir()
|
||||||
|
wantErr := tc.dF(t, d)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
defer wg.Wait()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var visited chan string
|
||||||
|
if tc.vF != nil {
|
||||||
|
tc.vF(nil, ctx, cancel)
|
||||||
|
visited = make(chan string)
|
||||||
|
defer close(visited)
|
||||||
|
wg.Go(func() { tc.vF(visited, ctx, cancel) })
|
||||||
|
}
|
||||||
|
|
||||||
|
var handleWalkErr func(error) error
|
||||||
|
if tc.hF != nil {
|
||||||
|
handleWalkErr = func(err error) error {
|
||||||
|
return tc.hF(d, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := uevent.Coldboot(ctx, d, visited, handleWalkErr); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
t.Errorf("Coldboot: error = %v, want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user