forked from rosa/hakurei
Compare commits
14 Commits
19c76e0831
...
389844b1ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
389844b1ea
|
|||
|
5b7ab35633
|
|||
|
52b1a5a725
|
|||
|
6b78df8714
|
|||
|
dadf170a46
|
|||
|
9594832302
|
|||
|
91a2d4d6e1
|
|||
|
a854719b9f
|
|||
|
f03c0fb249
|
|||
|
a6600be34a
|
|||
|
b5592633f5
|
|||
|
584e302168
|
|||
|
141958656f
|
|||
|
648079f42c
|
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -61,7 +61,7 @@ func (a *Absolute) Is(v *Absolute) bool {
|
||||
|
||||
// NewAbs checks pathname and returns a new [Absolute] if pathname is absolute.
|
||||
func NewAbs(pathname string) (*Absolute, error) {
|
||||
if !path.IsAbs(pathname) {
|
||||
if !filepath.IsAbs(pathname) {
|
||||
return nil, AbsoluteError(pathname)
|
||||
}
|
||||
return unsafeAbs(pathname), nil
|
||||
@@ -76,13 +76,13 @@ func MustAbs(pathname string) *Absolute {
|
||||
}
|
||||
}
|
||||
|
||||
// Append calls [path.Join] with [Absolute] as the first element.
|
||||
// Append calls [filepath.Join] with [Absolute] as the first element.
|
||||
func (a *Absolute) Append(elem ...string) *Absolute {
|
||||
return unsafeAbs(path.Join(append([]string{a.String()}, elem...)...))
|
||||
return unsafeAbs(filepath.Join(append([]string{a.String()}, elem...)...))
|
||||
}
|
||||
|
||||
// Dir calls [path.Dir] with [Absolute] as its argument.
|
||||
func (a *Absolute) Dir() *Absolute { return unsafeAbs(path.Dir(a.String())) }
|
||||
// Dir calls [filepath.Dir] with [Absolute] as its argument.
|
||||
func (a *Absolute) Dir() *Absolute { return unsafeAbs(filepath.Dir(a.String())) }
|
||||
|
||||
// GobEncode returns the checked pathname.
|
||||
func (a *Absolute) GobEncode() ([]byte, error) {
|
||||
@@ -92,7 +92,7 @@ func (a *Absolute) GobEncode() ([]byte, error) {
|
||||
// GobDecode stores data if it represents an absolute pathname.
|
||||
func (a *Absolute) GobDecode(data []byte) error {
|
||||
pathname := string(data)
|
||||
if !path.IsAbs(pathname) {
|
||||
if !filepath.IsAbs(pathname) {
|
||||
return AbsoluteError(pathname)
|
||||
}
|
||||
a.pathname = unique.Make(pathname)
|
||||
@@ -110,7 +110,7 @@ func (a *Absolute) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &pathname); err != nil {
|
||||
return err
|
||||
}
|
||||
if !path.IsAbs(pathname) {
|
||||
if !filepath.IsAbs(pathname) {
|
||||
return AbsoluteError(pathname)
|
||||
}
|
||||
a.pathname = unique.Make(pathname)
|
||||
|
||||
@@ -58,7 +58,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -102,13 +102,13 @@ func main() {
|
||||
log.Fatal("this program must not be started by root")
|
||||
}
|
||||
|
||||
if !path.IsAbs(hakureiPath) {
|
||||
if !filepath.IsAbs(hakureiPath) {
|
||||
log.Fatal("this program is compiled incorrectly")
|
||||
return
|
||||
}
|
||||
|
||||
var toolPath string
|
||||
pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
|
||||
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)") {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"strconv"
|
||||
@@ -145,9 +145,9 @@ func sharefs_destroy(private_data unsafe.Pointer) {
|
||||
func showHelp(args *fuseArgs) {
|
||||
executableName := sharefsName
|
||||
if args.argc > 0 {
|
||||
executableName = path.Base(C.GoString(*args.argv))
|
||||
executableName = filepath.Base(C.GoString(*args.argv))
|
||||
} else if name, err := os.Executable(); err == nil {
|
||||
executableName = path.Base(name)
|
||||
executableName = filepath.Base(name)
|
||||
}
|
||||
|
||||
fmt.Printf("usage: %s [options] <mountpoint>\n\n", executableName)
|
||||
|
||||
@@ -179,7 +179,7 @@ func (direct) mustLoopback(ctx context.Context, msg message.Msg) {
|
||||
lo = ifi.Index
|
||||
}
|
||||
|
||||
c, err := netlink.DialRoute()
|
||||
c, err := netlink.DialRoute(0)
|
||||
if err != nil {
|
||||
msg.GetLogger().Fatalln(err)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -569,7 +569,7 @@ func TryArgv0(msg message.Msg) {
|
||||
msg = message.New(log.Default())
|
||||
}
|
||||
|
||||
if len(os.Args) > 0 && path.Base(os.Args[0]) == initName {
|
||||
if len(os.Args) > 0 && filepath.Base(os.Args[0]) == initName {
|
||||
Init(msg)
|
||||
msg.BeforeExit()
|
||||
os.Exit(0)
|
||||
|
||||
@@ -3,7 +3,7 @@ package container
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
. "syscall"
|
||||
|
||||
"hakurei.app/check"
|
||||
@@ -46,7 +46,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
}
|
||||
|
||||
for _, name := range []string{"null", "zero", "full", "random", "urandom", "tty"} {
|
||||
targetPath := path.Join(target, name)
|
||||
targetPath := filepath.Join(target, name)
|
||||
if err := k.ensureFile(targetPath, 0444, state.ParentPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
for i, name := range []string{"stdin", "stdout", "stderr"} {
|
||||
if err := k.symlink(
|
||||
fhs.Proc+"self/fd/"+string(rune(i+'0')),
|
||||
path.Join(target, name),
|
||||
filepath.Join(target, name),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -72,13 +72,13 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
{fhs.Proc + "kcore", "core"},
|
||||
{"pts/ptmx", "ptmx"},
|
||||
} {
|
||||
if err := k.symlink(pair[0], path.Join(target, pair[1])); err != nil {
|
||||
if err := k.symlink(pair[0], filepath.Join(target, pair[1])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
devShmPath := path.Join(target, "shm")
|
||||
devPtsPath := path.Join(target, "pts")
|
||||
devShmPath := filepath.Join(target, "shm")
|
||||
devPtsPath := filepath.Join(target, "pts")
|
||||
for _, name := range []string{devShmPath, devPtsPath} {
|
||||
if err := k.mkdir(name, state.ParentPerm); err != nil {
|
||||
return err
|
||||
@@ -92,7 +92,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
|
||||
if state.RetainSession {
|
||||
if k.isatty(Stdout) {
|
||||
consolePath := path.Join(target, "console")
|
||||
consolePath := filepath.Join(target, "console")
|
||||
if err := k.ensureFile(consolePath, 0444, state.ParentPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
}
|
||||
|
||||
if d.Mqueue {
|
||||
mqueueTarget := path.Join(target, "mqueue")
|
||||
mqueueTarget := filepath.Join(target, "mqueue")
|
||||
if err := k.mkdir(mqueueTarget, state.ParentPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package container
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
@@ -30,7 +30,7 @@ func (l *SymlinkOp) Valid() bool { return l != nil && l.Target != nil && l.LinkN
|
||||
|
||||
func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
|
||||
if l.Dereference {
|
||||
if !path.IsAbs(l.LinkName) {
|
||||
if !filepath.IsAbs(l.LinkName) {
|
||||
return check.AbsoluteError(l.LinkName)
|
||||
}
|
||||
if name, err := k.readlink(l.LinkName); err != nil {
|
||||
@@ -44,7 +44,7 @@ func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
|
||||
|
||||
func (l *SymlinkOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
target := toSysroot(l.Target.String())
|
||||
if err := k.mkdirAll(path.Dir(target), state.ParentPerm); err != nil {
|
||||
if err := k.mkdirAll(filepath.Dir(target), state.ParentPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
return k.symlink(l.LinkName, target)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -29,16 +29,16 @@ const (
|
||||
|
||||
func toSysroot(name string) string {
|
||||
name = strings.TrimLeftFunc(name, func(r rune) bool { return r == '/' })
|
||||
return path.Join(sysrootPath, name)
|
||||
return filepath.Join(sysrootPath, name)
|
||||
}
|
||||
|
||||
func toHost(name string) string {
|
||||
name = strings.TrimLeftFunc(name, func(r rune) bool { return r == '/' })
|
||||
return path.Join(hostPath, name)
|
||||
return filepath.Join(hostPath, name)
|
||||
}
|
||||
|
||||
func createFile(name string, perm, pperm os.FileMode, content []byte) error {
|
||||
if err := os.MkdirAll(path.Dir(name), pperm); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(name), pperm); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(name, syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, perm)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
@@ -61,7 +61,7 @@ func TestCreateFile(t *testing.T) {
|
||||
Path: "/proc/nonexistent",
|
||||
Err: syscall.ENOENT,
|
||||
}
|
||||
if err := createFile(path.Join(Nonexistent, ":3"), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
|
||||
if err := createFile(filepath.Join(Nonexistent, ":3"), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("createFile: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
})
|
||||
@@ -72,7 +72,7 @@ func TestCreateFile(t *testing.T) {
|
||||
Path: "/proc/nonexistent",
|
||||
Err: syscall.ENOENT,
|
||||
}
|
||||
if err := createFile(path.Join(Nonexistent), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
|
||||
if err := createFile(filepath.Join(Nonexistent), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("createFile: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
})
|
||||
@@ -80,7 +80,7 @@ func TestCreateFile(t *testing.T) {
|
||||
|
||||
t.Run("touch", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
pathname := path.Join(tempDir, "empty")
|
||||
pathname := filepath.Join(tempDir, "empty")
|
||||
if err := createFile(pathname, 0644, 0755, nil); err != nil {
|
||||
t.Fatalf("createFile: error = %v", err)
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func TestCreateFile(t *testing.T) {
|
||||
|
||||
t.Run("write", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
pathname := path.Join(tempDir, "zero")
|
||||
pathname := filepath.Join(tempDir, "zero")
|
||||
if err := createFile(pathname, 0644, 0755, []byte{0}); err != nil {
|
||||
t.Fatalf("createFile: error = %v", err)
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func TestCreateFile(t *testing.T) {
|
||||
|
||||
func TestEnsureFile(t *testing.T) {
|
||||
t.Run("create", func(t *testing.T) {
|
||||
if err := ensureFile(path.Join(t.TempDir(), "ensure"), 0644, 0755); err != nil {
|
||||
if err := ensureFile(filepath.Join(t.TempDir(), "ensure"), 0644, 0755); err != nil {
|
||||
t.Errorf("ensureFile: error = %v", err)
|
||||
}
|
||||
})
|
||||
@@ -115,7 +115,7 @@ func TestEnsureFile(t *testing.T) {
|
||||
t.Run("stat", func(t *testing.T) {
|
||||
t.Run("inaccessible", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
pathname := path.Join(tempDir, "inaccessible")
|
||||
pathname := filepath.Join(tempDir, "inaccessible")
|
||||
if f, err := os.Create(pathname); err != nil {
|
||||
t.Fatalf("Create: error = %v", err)
|
||||
} else {
|
||||
@@ -150,7 +150,7 @@ func TestEnsureFile(t *testing.T) {
|
||||
|
||||
t.Run("ensure", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
pathname := path.Join(tempDir, "ensure")
|
||||
pathname := filepath.Join(tempDir, "ensure")
|
||||
if f, err := os.Create(pathname); err != nil {
|
||||
t.Fatalf("Create: error = %v", err)
|
||||
} else {
|
||||
@@ -195,12 +195,12 @@ func TestProcPaths(t *testing.T) {
|
||||
|
||||
t.Run("sample", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
if err := os.MkdirAll(path.Join(tempDir, "proc/self"), 0755); err != nil {
|
||||
if err := os.MkdirAll(filepath.Join(tempDir, "proc/self"), 0755); err != nil {
|
||||
t.Fatalf("MkdirAll: error = %v", err)
|
||||
}
|
||||
|
||||
t.Run("clean", func(t *testing.T) {
|
||||
if err := os.WriteFile(path.Join(tempDir, "proc/self/mountinfo"), []byte(`15 20 0:3 / /proc rw,relatime - proc /proc rw
|
||||
if err := os.WriteFile(filepath.Join(tempDir, "proc/self/mountinfo"), []byte(`15 20 0:3 / /proc rw,relatime - proc /proc rw
|
||||
16 20 0:15 / /sys rw,relatime - sysfs /sys rw
|
||||
17 20 0:5 / /dev rw,relatime - devtmpfs udev rw,size=1983516k,nr_inodes=495879,mode=755`), 0644); err != nil {
|
||||
t.Fatalf("WriteFile: error = %v", err)
|
||||
@@ -243,8 +243,8 @@ func TestProcPaths(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("malformed", func(t *testing.T) {
|
||||
path.Join(tempDir, "proc/self/mountinfo")
|
||||
if err := os.WriteFile(path.Join(tempDir, "proc/self/mountinfo"), []byte{0}, 0644); err != nil {
|
||||
filepath.Join(tempDir, "proc/self/mountinfo")
|
||||
if err := os.WriteFile(filepath.Join(tempDir, "proc/self/mountinfo"), []byte{0}, 0644); err != nil {
|
||||
t.Fatalf("WriteFile: error = %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package hst
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
@@ -28,7 +28,7 @@ func (l *FSLink) Valid() bool {
|
||||
if l == nil || l.Target == nil || l.Linkname == "" {
|
||||
return false
|
||||
}
|
||||
return !l.Dereference || path.IsAbs(l.Linkname)
|
||||
return !l.Dereference || filepath.IsAbs(l.Linkname)
|
||||
}
|
||||
|
||||
func (l *FSLink) Path() *check.Absolute {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
@@ -28,7 +28,7 @@ func TestUpdate(t *testing.T) {
|
||||
t.Skip("acl test skipped")
|
||||
}
|
||||
|
||||
testFilePath := path.Join(t.TempDir(), testFileName)
|
||||
testFilePath := filepath.Join(t.TempDir(), testFileName)
|
||||
|
||||
if f, err := os.Create(testFilePath); err != nil {
|
||||
t.Fatalf("Create: error = %v", err)
|
||||
|
||||
@@ -46,7 +46,11 @@ type Conn struct {
|
||||
|
||||
// Dial returns the address of a newly connected generic netlink connection of
|
||||
// specified family and groups.
|
||||
func Dial(family int, groups uint32) (*Conn, error) {
|
||||
//
|
||||
// For a nonzero rcvbuf, the socket receive buffer size is set to its absolute
|
||||
// value via SO_RCVBUF for a positive value, or SO_RCVBUFFORCE for a negative
|
||||
// value.
|
||||
func Dial(family int, groups uint32, rcvbuf int64) (*Conn, error) {
|
||||
var c Conn
|
||||
if fd, err := syscall.Socket(
|
||||
syscall.AF_NETLINK,
|
||||
@@ -75,6 +79,23 @@ func Dial(family int, groups uint32) (*Conn, error) {
|
||||
return nil, syscall.ENOTRECOVERABLE
|
||||
}
|
||||
|
||||
if rcvbuf != 0 {
|
||||
opt := syscall.SO_RCVBUF
|
||||
if rcvbuf < 0 {
|
||||
opt = syscall.SO_RCVBUFFORCE
|
||||
rcvbuf = -rcvbuf
|
||||
}
|
||||
if err = syscall.SetsockoptInt(
|
||||
fd,
|
||||
syscall.SOL_SOCKET,
|
||||
opt,
|
||||
int(rcvbuf),
|
||||
); err != nil {
|
||||
_ = syscall.Close(fd)
|
||||
return nil, os.NewSyscallError("setsockopt", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.family = family
|
||||
c.f = os.NewFile(uintptr(fd), "netlink")
|
||||
if c.raw, err = c.f.SyscallConn(); err != nil {
|
||||
@@ -101,23 +122,40 @@ func (c *Conn) Close() error {
|
||||
return c.f.Close()
|
||||
}
|
||||
|
||||
// Recvfrom wraps recv(2) with nonblocking behaviour via the runtime network poller.
|
||||
// Recvmsg wraps recv(2) with nonblocking behaviour via the runtime network poller.
|
||||
//
|
||||
// The returned slice is valid until the next call to Recvfrom.
|
||||
func (c *Conn) Recvfrom(
|
||||
// The returned slice is valid until the next call to Recvmsg.
|
||||
func (c *Conn) Recvmsg(
|
||||
ctx context.Context,
|
||||
flags int,
|
||||
) (data []byte, from syscall.Sockaddr, err error) {
|
||||
) (data []byte, recvflags int, from syscall.Sockaddr, err error) {
|
||||
if err = c.f.SetReadDeadline(time.Time{}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var n int
|
||||
data = c.buf[:]
|
||||
|
||||
if ctx == nil {
|
||||
rcErr := c.raw.Control(func(fd uintptr) {
|
||||
n, _, recvflags, from, err = syscall.Recvmsg(int(fd), data, nil, flags)
|
||||
})
|
||||
if n >= 0 {
|
||||
data = data[:n]
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = os.NewSyscallError("recvmsg", err)
|
||||
} else {
|
||||
err = rcErr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
rcErr := c.raw.Read(func(fd uintptr) (done bool) {
|
||||
n, from, err = syscall.Recvfrom(int(fd), data, flags)
|
||||
n, _, recvflags, from, err = syscall.Recvmsg(int(fd), data, nil, flags)
|
||||
return err != syscall.EWOULDBLOCK
|
||||
})
|
||||
if n >= 0 {
|
||||
@@ -129,7 +167,7 @@ func (c *Conn) Recvfrom(
|
||||
select {
|
||||
case rcErr := <-done:
|
||||
if err != nil {
|
||||
err = os.NewSyscallError("recvfrom", err)
|
||||
err = os.NewSyscallError("recvmsg", err)
|
||||
} else {
|
||||
err = rcErr
|
||||
}
|
||||
@@ -147,12 +185,12 @@ func (c *Conn) Recvfrom(
|
||||
}
|
||||
}
|
||||
|
||||
// Sendto wraps send(2) with nonblocking behaviour via the runtime network poller.
|
||||
func (c *Conn) Sendto(
|
||||
// Sendmsg wraps send(2) with nonblocking behaviour via the runtime network poller.
|
||||
func (c *Conn) Sendmsg(
|
||||
ctx context.Context,
|
||||
p []byte,
|
||||
flags int,
|
||||
to syscall.Sockaddr,
|
||||
flags int,
|
||||
) (err error) {
|
||||
if err = c.f.SetWriteDeadline(time.Time{}); err != nil {
|
||||
return
|
||||
@@ -161,7 +199,7 @@ func (c *Conn) Sendto(
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- c.raw.Write(func(fd uintptr) (done bool) {
|
||||
err = syscall.Sendto(int(fd), p, flags, to)
|
||||
err = syscall.Sendmsg(int(fd), p, nil, to, flags)
|
||||
return err != syscall.EWOULDBLOCK
|
||||
})
|
||||
}()
|
||||
@@ -169,7 +207,7 @@ func (c *Conn) Sendto(
|
||||
select {
|
||||
case rcErr := <-done:
|
||||
if err != nil {
|
||||
err = os.NewSyscallError("sendto", err)
|
||||
err = os.NewSyscallError("sendmsg", err)
|
||||
} else {
|
||||
err = rcErr
|
||||
}
|
||||
@@ -278,7 +316,7 @@ type HandlerFunc func(resp []syscall.NetlinkMessage) error
|
||||
func (c *Conn) receive(ctx context.Context, f HandlerFunc, flags int) error {
|
||||
for {
|
||||
var resp []syscall.NetlinkMessage
|
||||
if data, _, err := c.Recvfrom(ctx, flags); err != nil {
|
||||
if data, _, _, err := c.Recvmsg(ctx, flags); err != nil {
|
||||
return err
|
||||
} else if len(data) < syscall.NLMSG_HDRLEN {
|
||||
return syscall.EBADE
|
||||
@@ -302,9 +340,9 @@ func (c *Conn) Roundtrip(ctx context.Context, f HandlerFunc) error {
|
||||
}
|
||||
defer func() { c.seq++ }()
|
||||
|
||||
if err := c.Sendto(ctx, c.pending(), 0, &syscall.SockaddrNetlink{
|
||||
if err := c.Sendmsg(ctx, c.pending(), &syscall.SockaddrNetlink{
|
||||
Family: syscall.AF_NETLINK,
|
||||
}); err != nil {
|
||||
}, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ type RouteConn struct{ conn *Conn }
|
||||
func (c *RouteConn) Close() error { return c.conn.Close() }
|
||||
|
||||
// DialRoute returns the address of a newly connected [RouteConn].
|
||||
func DialRoute() (*RouteConn, error) {
|
||||
c, err := Dial(syscall.NETLINK_ROUTE, 0)
|
||||
func DialRoute(rcvbuf int64) (*RouteConn, error) {
|
||||
c, err := Dial(syscall.NETLINK_ROUTE, 0, rcvbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"syscall"
|
||||
@@ -165,9 +165,9 @@ func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
||||
}
|
||||
for _, pair := range entry.Values {
|
||||
if pair[0] == "path" {
|
||||
if path.IsAbs(pair[1]) {
|
||||
if filepath.IsAbs(pair[1]) {
|
||||
// get parent dir of socket
|
||||
dir := path.Dir(pair[1])
|
||||
dir := filepath.Dir(pair[1])
|
||||
if dir == "." || dir == fhs.Root {
|
||||
state.msg.Verbosef("dbus socket %q is in an unusual location", pair[1])
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -973,23 +973,23 @@ func connectName(name string, manager bool) (conn Conn, err error) {
|
||||
return connectName(name+"-manager", false)
|
||||
}
|
||||
|
||||
if path.IsAbs(name) || (len(name) > 0 && name[0] == '@') {
|
||||
if filepath.IsAbs(name) || (len(name) > 0 && name[0] == '@') {
|
||||
return Dial(name)
|
||||
} else {
|
||||
runtimeDir, ok := os.LookupEnv("PIPEWIRE_RUNTIME_DIR")
|
||||
if !ok || !path.IsAbs(runtimeDir) {
|
||||
if !ok || !filepath.IsAbs(runtimeDir) {
|
||||
runtimeDir, ok = os.LookupEnv("XDG_RUNTIME_DIR")
|
||||
}
|
||||
if !ok || !path.IsAbs(runtimeDir) {
|
||||
if !ok || !filepath.IsAbs(runtimeDir) {
|
||||
// this is cargo culted from windows stuff and has no effect normally;
|
||||
// keeping it to maintain compatibility in case someone sets this
|
||||
runtimeDir, ok = os.LookupEnv("USERPROFILE")
|
||||
}
|
||||
|
||||
if !ok || !path.IsAbs(runtimeDir) {
|
||||
if !ok || !filepath.IsAbs(runtimeDir) {
|
||||
runtimeDir = DEFAULT_SYSTEM_RUNTIME_DIR
|
||||
}
|
||||
return Dial(path.Join(runtimeDir, name))
|
||||
return Dial(filepath.Join(runtimeDir, name))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,31 @@ func TestFlatten(t *testing.T) {
|
||||
fs.ModeCharDevice | 0400,
|
||||
)},
|
||||
|
||||
{"coldboot", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices": {Mode: fs.ModeDir | 0700},
|
||||
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
"devices/empty": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices/sub": {Mode: fs.ModeDir | 0700},
|
||||
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
|
||||
"block": {Mode: fs.ModeDir | 0700},
|
||||
"block/uevent": {Mode: 0600, Data: []byte{}},
|
||||
}, []pkg.FlatEntry{
|
||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
||||
|
||||
{Mode: fs.ModeDir | 0700, Path: "block"},
|
||||
{Mode: 0600, Path: "block/uevent", Data: []byte{}},
|
||||
|
||||
{Mode: fs.ModeDir | 0700, Path: "devices"},
|
||||
{Mode: fs.ModeDir | 0700, Path: "devices/empty"},
|
||||
{Mode: fs.ModeDir | 0700, Path: "devices/sub"},
|
||||
{Mode: 0600, Path: "devices/sub/uevent", Data: []byte("add")},
|
||||
{Mode: 0600, Path: "devices/uevent", Data: []byte("add")},
|
||||
}, pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8"), nil},
|
||||
|
||||
{"empty", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"syscall"
|
||||
@@ -189,7 +189,7 @@ func NewExec(
|
||||
paths ...ExecPath,
|
||||
) Artifact {
|
||||
if name == "" {
|
||||
name = "exec-" + path.Base(pathname.String())
|
||||
name = "exec-" + filepath.Base(pathname.String())
|
||||
}
|
||||
if timeout <= 0 {
|
||||
timeout = ExecTimeoutDefault
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"iter"
|
||||
"maps"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
@@ -894,7 +893,7 @@ func (c *Cache) Scrub(checks int) error {
|
||||
se.DanglingIdentifiers = append(se.DanglingIdentifiers, *want)
|
||||
seMu.Unlock()
|
||||
return false
|
||||
} else if err = Decode(got, path.Base(linkname)); err != nil {
|
||||
} else if err = Decode(got, filepath.Base(linkname)); err != nil {
|
||||
seMu.Lock()
|
||||
lnp := dir.Append(linkname)
|
||||
se.Errs[lnp.Handle()] = append(se.Errs[lnp.Handle()], err)
|
||||
@@ -1488,7 +1487,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
return
|
||||
}
|
||||
buf := c.getIdentBuf()
|
||||
err = Decode((*Checksum)(buf[:]), path.Base(name))
|
||||
err = Decode((*Checksum)(buf[:]), filepath.Base(name))
|
||||
if err == nil {
|
||||
checksum = unique.Make(Checksum(buf[:]))
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -169,7 +169,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
||||
}
|
||||
|
||||
if typeflag >= '0' && typeflag <= '9' && typeflag != tar.TypeDir {
|
||||
if err = root.MkdirAll(path.Dir(header.Name), 0700); err != nil {
|
||||
if err = root.MkdirAll(filepath.Dir(header.Name), 0700); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
18
internal/pkg/testdata/main.go
vendored
18
internal/pkg/testdata/main.go
vendored
@@ -7,7 +7,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -68,7 +68,7 @@ func main() {
|
||||
if got, err := os.Executable(); err != nil {
|
||||
log.Fatalf("Executable: error = %v", err)
|
||||
} else {
|
||||
iftPath = path.Join(path.Dir(path.Dir(got)), "ift")
|
||||
iftPath = filepath.Join(filepath.Dir(filepath.Dir(got)), "ift")
|
||||
|
||||
if got != wantExec {
|
||||
switch got {
|
||||
@@ -161,7 +161,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
if !layers {
|
||||
if path.Base(lowerdir) != checksumEmptyDir {
|
||||
if filepath.Base(lowerdir) != checksumEmptyDir {
|
||||
log.Fatal("unexpected artifact checksum")
|
||||
}
|
||||
} else {
|
||||
@@ -187,8 +187,8 @@ func main() {
|
||||
}
|
||||
|
||||
if len(lowerdirs) != 2 ||
|
||||
path.Base(lowerdirs[0]) != "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU" ||
|
||||
path.Base(lowerdirs[1]) != "nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK" {
|
||||
filepath.Base(lowerdirs[0]) != "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU" ||
|
||||
filepath.Base(lowerdirs[1]) != "nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK" {
|
||||
log.Fatalf("unexpected lowerdirs %s", strings.Join(lowerdirs, ", "))
|
||||
}
|
||||
}
|
||||
@@ -202,12 +202,12 @@ func main() {
|
||||
}
|
||||
|
||||
next()
|
||||
if path.Base(m.Root) != "OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb" {
|
||||
if filepath.Base(m.Root) != "OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb" {
|
||||
log.Fatal("unexpected file artifact checksum")
|
||||
}
|
||||
|
||||
next()
|
||||
if path.Base(m.Root) != checksumEmptyDir {
|
||||
if filepath.Base(m.Root) != checksumEmptyDir {
|
||||
log.Fatal("unexpected artifact checksum")
|
||||
}
|
||||
}
|
||||
@@ -226,13 +226,13 @@ func main() {
|
||||
log.Fatal("unexpected work mount entry")
|
||||
}
|
||||
} else {
|
||||
if path.Base(m.Root) != ident || m.Target != "/work" {
|
||||
if filepath.Base(m.Root) != ident || m.Target != "/work" {
|
||||
log.Fatal("unexpected work mount entry")
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
if path.Base(m.Root) != ident || m.Target != "/tmp" {
|
||||
if filepath.Base(m.Root) != ident || m.Target != "/tmp" {
|
||||
log.Fatal("unexpected temp mount entry")
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ const (
|
||||
CMake
|
||||
Coreutils
|
||||
Curl
|
||||
DBus
|
||||
DTC
|
||||
Diffutils
|
||||
Elfutils
|
||||
@@ -68,14 +69,19 @@ const (
|
||||
Gzip
|
||||
Hakurei
|
||||
HakureiDist
|
||||
IPTables
|
||||
Kmod
|
||||
LibXau
|
||||
Libbsd
|
||||
Libcap
|
||||
Libexpat
|
||||
Libiconv
|
||||
Libpsl
|
||||
Libffi
|
||||
Libgd
|
||||
Libmd
|
||||
Libmnl
|
||||
Libnftnl
|
||||
Libtool
|
||||
Libseccomp
|
||||
Libucontext
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -144,11 +144,11 @@ func (attr *CMakeHelper) name(name, version string) string {
|
||||
}
|
||||
|
||||
// extra returns a hardcoded slice of [CMake] and [Ninja].
|
||||
func (attr *CMakeHelper) extra(int) []PArtifact {
|
||||
func (attr *CMakeHelper) extra(int) P {
|
||||
if attr != nil && attr.Make {
|
||||
return []PArtifact{CMake, Make}
|
||||
return P{CMake, Make}
|
||||
}
|
||||
return []PArtifact{CMake, Ninja}
|
||||
return P{CMake, Ninja}
|
||||
}
|
||||
|
||||
// wantsChmod returns false.
|
||||
@@ -200,7 +200,7 @@ cmake -G ` + generate + ` \
|
||||
}
|
||||
}), " \\\n\t") + ` \
|
||||
-DCMAKE_INSTALL_PREFIX=/system \
|
||||
'/usr/src/` + name + `/` + path.Join(attr.Append...) + `'
|
||||
'/usr/src/` + name + `/` + filepath.Join(attr.Append...) + `'
|
||||
cmake --build .` + jobs + `
|
||||
cmake --install . --prefix=/work/system
|
||||
` + attr.Script
|
||||
|
||||
46
internal/rosa/dbus.go
Normal file
46
internal/rosa/dbus.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newDBus() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.16.2"
|
||||
checksum = "INwOuNdrDG7XW5ilW_vn8JSxEa444rRNc5ho97i84I1CNF09OmcFcV-gzbF4uCyg"
|
||||
)
|
||||
return t.NewPackage("dbus", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://gitlab.freedesktop.org/dbus/dbus/-/archive/"+
|
||||
"dbus-"+version+"/dbus-dbus-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
// OSError: [Errno 30] Read-only file system: '/usr/src/dbus/subprojects/packagecache'
|
||||
Writable: true,
|
||||
// PermissionError: [Errno 13] Permission denied: '/usr/src/dbus/subprojects/packagecache'
|
||||
Chmod: true,
|
||||
}, &MesonHelper{
|
||||
Setup: []KV{
|
||||
{"Depoll", "enabled"},
|
||||
{"Dinotify", "enabled"},
|
||||
{"Dx11_autolaunch", "disabled"},
|
||||
},
|
||||
},
|
||||
GLib,
|
||||
Libexpat,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[DBus] = Metadata{
|
||||
f: Toolchain.newDBus,
|
||||
|
||||
Name: "dbus",
|
||||
Description: "a message bus system",
|
||||
Website: "https://www.freedesktop.org/wiki/Software/dbus/",
|
||||
|
||||
Dependencies: P{
|
||||
GLib,
|
||||
Libexpat,
|
||||
},
|
||||
|
||||
ID: 5356,
|
||||
}
|
||||
}
|
||||
@@ -864,15 +864,17 @@ func init() {
|
||||
|
||||
func (t Toolchain) newMPC() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.3.1"
|
||||
checksum = "o8r8K9R4x7PuRx0-JE3-bC5jZQrtxGV2nkB773aqJ3uaxOiBDCID1gKjPaaDxX4V"
|
||||
version = "1.4.0"
|
||||
checksum = "75Sgr2hcDTltHYgFaHsRGsFgW74i2jqAUS0oXaBdJYKjMj_CvEeJ1zwGbNYjEl1H"
|
||||
)
|
||||
return t.NewPackage("mpc", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://gcc.gnu.org/pub/gcc/infrastructure/"+
|
||||
"mpc-"+version+".tar.gz",
|
||||
return t.NewPackage("mpc", version, pkg.NewHTTPGet(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/mpc/mpc-"+version+".tar.xz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
), &PackageAttr{
|
||||
SourceKind: SourceKindTarXZ,
|
||||
}, (*MakeHelper)(nil),
|
||||
XZ,
|
||||
|
||||
MPFR,
|
||||
), version
|
||||
}
|
||||
|
||||
57
internal/rosa/libbsd.go
Normal file
57
internal/rosa/libbsd.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newLibmd() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.1.0"
|
||||
checksum = "72w7Na04b9ji6nOe2h-Tz5JeQ6iStDZN3FOG1JNZ9M_jMO8K2FceG6DZv7lYThZJ"
|
||||
)
|
||||
return t.NewPackage("libmd", version, pkg.NewHTTPGet(
|
||||
nil, "https://libbsd.freedesktop.org/releases/libmd-"+version+".tar.xz",
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
SourceKind: SourceKindTarXZ,
|
||||
}, (*MakeHelper)(nil),
|
||||
XZ,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libmd] = Metadata{
|
||||
f: Toolchain.newLibmd,
|
||||
|
||||
Name: "libmd",
|
||||
Description: "Message Digest functions from BSD systems",
|
||||
Website: "https://www.hadrons.org/software/libmd/",
|
||||
|
||||
ID: 15525,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newLibbsd() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "0.12.2"
|
||||
checksum = "MEJ9MuLai32-gSJUrfmlDgGl7rszjdSxgb3ph9AcI5jv70VwlwwXJy1kxdAixm5Y"
|
||||
)
|
||||
return t.NewPackage("libbsd", version, pkg.NewHTTPGet(
|
||||
nil, "https://libbsd.freedesktop.org/releases/libbsd-"+version+".tar.xz",
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
SourceKind: SourceKindTarXZ,
|
||||
}, (*MakeHelper)(nil),
|
||||
XZ,
|
||||
|
||||
Libmd,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libbsd] = Metadata{
|
||||
f: Toolchain.newLibbsd,
|
||||
|
||||
Name: "libbsd",
|
||||
Description: "provides useful functions commonly found on BSD systems",
|
||||
Website: "https://libbsd.freedesktop.org/",
|
||||
|
||||
ID: 1567,
|
||||
}
|
||||
}
|
||||
@@ -84,8 +84,8 @@ func (*MakeHelper) name(name, version string) string {
|
||||
}
|
||||
|
||||
// extra returns make and other optional dependencies.
|
||||
func (attr *MakeHelper) extra(flag int) []PArtifact {
|
||||
extra := []PArtifact{Make}
|
||||
func (attr *MakeHelper) extra(flag int) P {
|
||||
extra := P{Make}
|
||||
if (attr == nil || !attr.OmitDefaults) && flag&TEarly == 0 {
|
||||
extra = append(extra,
|
||||
Gawk,
|
||||
|
||||
@@ -72,9 +72,7 @@ func (*MesonHelper) name(name, version string) string {
|
||||
}
|
||||
|
||||
// extra returns hardcoded meson runtime dependencies.
|
||||
func (*MesonHelper) extra(int) []PArtifact {
|
||||
return []PArtifact{Meson}
|
||||
}
|
||||
func (*MesonHelper) extra(int) P { return P{Meson} }
|
||||
|
||||
// wantsChmod returns false.
|
||||
func (*MesonHelper) wantsChmod() bool { return false }
|
||||
@@ -114,6 +112,7 @@ cd "$(mktemp -d)"
|
||||
meson setup \
|
||||
` + strings.Join(slices.Collect(func(yield func(string) bool) {
|
||||
for _, v := range append([]KV{
|
||||
{"wrap-mode", "nodownload"},
|
||||
{"prefix", "/system"},
|
||||
{"buildtype", "release"},
|
||||
}, attr.Setup...) {
|
||||
|
||||
149
internal/rosa/netfilter.go
Normal file
149
internal/rosa/netfilter.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newLibmnl() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.0.5"
|
||||
checksum = "DN-vbbvQDpxXJm0TJ6xlluILvfrB86avrCTX50XyE9SEFSAZ_o8nuKc5Gu0Am7-u"
|
||||
)
|
||||
return t.NewPackage("libmnl", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://www.netfilter.org/projects/libmnl/files/"+
|
||||
"libmnl-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
{"libbsd-sys-queue", `diff --git a/examples/netfilter/nfct-daemon.c b/examples/netfilter/nfct-daemon.c
|
||||
index d223ac2..a7878d0 100644
|
||||
--- a/examples/netfilter/nfct-daemon.c
|
||||
+++ b/examples/netfilter/nfct-daemon.c
|
||||
@@ -20,7 +20,7 @@
|
||||
#include <linux/netfilter/nfnetlink.h>
|
||||
#include <linux/netfilter/nfnetlink_conntrack.h>
|
||||
|
||||
-#include <sys/queue.h>
|
||||
+#include <bsd/sys/queue.h>
|
||||
|
||||
struct nstats {
|
||||
LIST_ENTRY(nstats) list;
|
||||
`},
|
||||
},
|
||||
}, &MakeHelper{
|
||||
Configure: []KV{
|
||||
{"enable-static"},
|
||||
},
|
||||
},
|
||||
Libbsd,
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libmnl] = Metadata{
|
||||
f: Toolchain.newLibmnl,
|
||||
|
||||
Name: "libmnl",
|
||||
Description: "a minimalistic user-space library oriented to Netlink developers",
|
||||
Website: "https://www.netfilter.org/projects/libmnl/",
|
||||
|
||||
ID: 1663,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newLibnftnl() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.3.1"
|
||||
checksum = "A6EFNv2TbOcjcsXX2hQ-pKsF5FvlSh-BNEf9LrgnVH4nDjcv6NbtyHkTriz9kIEu"
|
||||
)
|
||||
return t.NewPackage("libnftnl", version, pkg.NewHTTPGet(
|
||||
nil, "https://www.netfilter.org/projects/libnftnl/files/"+
|
||||
"libnftnl-"+version+".tar.xz",
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
SourceKind: SourceKindTarXZ,
|
||||
|
||||
Env: []string{
|
||||
"CFLAGS=-D_GNU_SOURCE",
|
||||
},
|
||||
}, &MakeHelper{
|
||||
Configure: []KV{
|
||||
{"enable-static"},
|
||||
},
|
||||
},
|
||||
XZ,
|
||||
PkgConfig,
|
||||
|
||||
Libmnl,
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libnftnl] = Metadata{
|
||||
f: Toolchain.newLibnftnl,
|
||||
|
||||
Name: "libnftnl",
|
||||
Description: "a userspace library providing a low-level netlink API to the in-kernel nf_tables subsystem",
|
||||
Website: "https://www.netfilter.org/projects/libnftnl/",
|
||||
|
||||
Dependencies: P{
|
||||
Libmnl,
|
||||
},
|
||||
|
||||
ID: 1681,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newIPTables() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.8.13"
|
||||
checksum = "JsNI7dyZHnHLtDkKWAxzAIMZ5t-ff3LkSPqNJsn5VM5Eq2m1bA5NKI-XfMRpQsg6"
|
||||
)
|
||||
return t.NewPackage("iptables", version, pkg.NewHTTPGet(
|
||||
nil, "https://www.netfilter.org/projects/iptables/files/"+
|
||||
"iptables-"+version+".tar.xz",
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
SourceKind: SourceKindTarXZ,
|
||||
|
||||
ScriptEarly: `
|
||||
rm \
|
||||
extensions/libxt_connlabel.txlate \
|
||||
extensions/libxt_conntrack.txlate
|
||||
sed -i \
|
||||
's/de:ad:0:be:ee:ff/DE:AD:00:BE:EE:FF/g' \
|
||||
extensions/libebt_dnat.txlate \
|
||||
extensions/libebt_snat.txlate
|
||||
`,
|
||||
}, &MakeHelper{
|
||||
Configure: []KV{
|
||||
{"enable-static"},
|
||||
},
|
||||
ScriptCheckEarly: `
|
||||
ln -s ../system/bin/bash /bin/
|
||||
chmod +w /etc/ && ln -s ../usr/src/iptables/etc/ethertypes /etc/
|
||||
`,
|
||||
},
|
||||
XZ,
|
||||
PkgConfig,
|
||||
Bash,
|
||||
Python,
|
||||
|
||||
Libnftnl,
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[IPTables] = Metadata{
|
||||
f: Toolchain.newIPTables,
|
||||
|
||||
Name: "iptables",
|
||||
Description: "the userspace command line program used to configure the Linux 2.4.x and later packet filtering ruleset",
|
||||
Website: "https://www.netfilter.org/projects/iptables/",
|
||||
|
||||
Dependencies: P{
|
||||
Libnftnl,
|
||||
},
|
||||
|
||||
ID: 1394,
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func (t Toolchain) newViaPerlMakeMaker(
|
||||
{"PREFIX", "/system"},
|
||||
},
|
||||
Check: []string{"test"},
|
||||
}, slices.Concat(extra, []PArtifact{
|
||||
}, slices.Concat(extra, P{
|
||||
Perl,
|
||||
})...)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package rosa_test
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unique"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func TestReportZeroLength(t *testing.T) {
|
||||
report := path.Join(t.TempDir(), "report")
|
||||
report := filepath.Join(t.TempDir(), "report")
|
||||
if err := os.WriteFile(report, nil, 0400); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func TestReportZeroLength(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReportSIGSEGV(t *testing.T) {
|
||||
report := path.Join(t.TempDir(), "report")
|
||||
report := filepath.Join(t.TempDir(), "report")
|
||||
if err := os.WriteFile(report, make([]byte, 64), 0400); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ type Helper interface {
|
||||
// name returns the value passed to the name argument of [Toolchain.New].
|
||||
name(name, version string) string
|
||||
// extra returns helper-specific dependencies.
|
||||
extra(flag int) []PArtifact
|
||||
extra(flag int) P
|
||||
|
||||
// wantsChmod returns whether the source directory should be made writable.
|
||||
wantsChmod() bool
|
||||
|
||||
@@ -3,7 +3,7 @@ package system
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -18,7 +18,7 @@ func TestPipeWireOp(t *testing.T) {
|
||||
|
||||
checkOpBehaviour(t, checkNoParallel, []opBehaviourTestCase{
|
||||
{"success", 0xbeef, 0xff, &pipewireOp{nil,
|
||||
m(path.Join(t.TempDir(), "pipewire")),
|
||||
m(filepath.Join(t.TempDir(), "pipewire")),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, []stub.Call{
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,19 @@ type (
|
||||
Recoverable interface{ recoverable() }
|
||||
// Nontrivial is satisfied by errors preferring a JSON encoding.
|
||||
Nontrivial interface{ nontrivial() }
|
||||
|
||||
// NeedsColdboot is satisfied by errors indicating divergence of local state
|
||||
// from the kernel, usually from lost uevent data.
|
||||
NeedsColdboot interface {
|
||||
Recoverable
|
||||
coldboot()
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
exclConsume = iota
|
||||
|
||||
_exclLen
|
||||
)
|
||||
|
||||
// Conn represents a NETLINK_KOBJECT_UEVENT socket.
|
||||
@@ -25,27 +38,27 @@ type Conn struct {
|
||||
conn *netlink.Conn
|
||||
|
||||
// Whether currently between a call to enterExcl and exitExcl.
|
||||
excl atomic.Bool
|
||||
excl [_exclLen]atomic.Bool
|
||||
}
|
||||
|
||||
// enterExcl must be called entering a critical section that interacts with conn.
|
||||
func (c *Conn) enterExcl() error {
|
||||
if !c.excl.CompareAndSwap(false, true) {
|
||||
func (c *Conn) enterExcl(k int) error {
|
||||
if !c.excl[k].CompareAndSwap(false, true) {
|
||||
return syscall.EAGAIN
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// exitExcl must be called exiting a critical section that interacts with conn.
|
||||
func (c *Conn) exitExcl() { c.excl.Store(false) }
|
||||
func (c *Conn) exitExcl(k int) { c.excl[k].Store(false) }
|
||||
|
||||
// Close closes the underlying socket.
|
||||
func (c *Conn) Close() error { return c.conn.Close() }
|
||||
|
||||
// Dial returns the address of a newly connected [Conn].
|
||||
func Dial() (*Conn, error) {
|
||||
func Dial(rcvbuf int64) (*Conn, error) {
|
||||
// kernel group is hard coded in lib/kobject_uevent.c, undocumented
|
||||
c, err := netlink.Dial(syscall.NETLINK_KOBJECT_UEVENT, 1)
|
||||
c, err := netlink.Dial(syscall.NETLINK_KOBJECT_UEVENT, 1, rcvbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -58,6 +71,18 @@ var (
|
||||
ErrBadSocket = errors.New("unexpected socket address")
|
||||
)
|
||||
|
||||
// ReceiveBufferError indicates one or more [Message] being lost due to the
|
||||
// socket receive buffer filling up. This is usually caused by epoll waking the
|
||||
// receiving program up too late.
|
||||
type ReceiveBufferError struct{ _ [0]*ReceiveBufferError }
|
||||
|
||||
var _ NeedsColdboot = ReceiveBufferError{}
|
||||
|
||||
func (ReceiveBufferError) recoverable() {}
|
||||
func (ReceiveBufferError) coldboot() {}
|
||||
func (ReceiveBufferError) Unwrap() error { return syscall.ENOBUFS }
|
||||
func (e ReceiveBufferError) Error() string { return syscall.ENOBUFS.Error() }
|
||||
|
||||
// BadPortError is returned by [Conn.Consume] upon receiving a message that did
|
||||
// not come from the kernel.
|
||||
type BadPortError syscall.SockaddrNetlink
|
||||
@@ -70,36 +95,128 @@ func (e *BadPortError) Error() string {
|
||||
" on NETLINK_KOBJECT_UEVENT"
|
||||
}
|
||||
|
||||
// Consume continuously receives and parses events from the kernel. It returns
|
||||
// the first error it encounters.
|
||||
// receiveEvent receives a single event and returns the address of its [Message].
|
||||
func (c *Conn) receiveEvent(ctx context.Context) (*Message, error) {
|
||||
data, _, from, err := c.conn.Recvmsg(ctx, 0)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.ENOBUFS) {
|
||||
return nil, ReceiveBufferError{}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// lib/kobject_uevent.c:
|
||||
// set portid 0 to inform userspace message comes from kernel
|
||||
if v, ok := from.(*syscall.SockaddrNetlink); !ok {
|
||||
return nil, ErrBadSocket
|
||||
} else if v.Pid != 0 {
|
||||
return nil, (*BadPortError)(v)
|
||||
|
||||
}
|
||||
|
||||
var msg Message
|
||||
if err = msg.UnmarshalBinary(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &msg, err
|
||||
}
|
||||
|
||||
// Consume continuously receives and parses events from the kernel and handles
|
||||
// [Recoverable] and [NeedsColdboot] errors via caller-supplied functions,
|
||||
// entering coldboot when required.
|
||||
//
|
||||
// For each uevent file visited by [Coldboot], handleColdbootVisited is called
|
||||
// with its pathname. This function must never block.
|
||||
//
|
||||
// When consuming events, a non-nil error not satisfying [Recoverable] is
|
||||
// returned immediately. Otherwise, handleConsumeErr is called with the error
|
||||
// value. If the error satisfies [NeedsColdboot], a [Coldboot] is arranged
|
||||
// before event processing resumes. If handleConsumeErr returns false, the error
|
||||
// value is immediately returned as is.
|
||||
//
|
||||
// Callers are expected to reject excessively frequent [NeedsColdboot] errors
|
||||
// in handleConsumeErr to avoid being stuck in a [Coldboot] loop. Event
|
||||
// processing is allowed to restart without initial coldboot after recovering
|
||||
// from such a condition, provided the caller adequately reports the degraded,
|
||||
// diverging state to the user.
|
||||
//
|
||||
// Callers must not restart event processing after a non-nil error that does not
|
||||
// satisfy [Recoverable] is returned.
|
||||
func (c *Conn) Consume(ctx context.Context, events chan<- *Message) error {
|
||||
if err := c.enterExcl(); err != nil {
|
||||
func (c *Conn) Consume(
|
||||
ctx context.Context,
|
||||
sysfs string,
|
||||
events chan<- *Message,
|
||||
coldboot bool,
|
||||
|
||||
handleColdbootVisited func(string),
|
||||
handleConsumeErr func(error) bool,
|
||||
handleWalkErr func(error) error,
|
||||
) error {
|
||||
if err := c.enterExcl(exclConsume); err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.exitExcl()
|
||||
defer c.exitExcl(exclConsume)
|
||||
|
||||
for {
|
||||
data, from, err := c.conn.Recvfrom(ctx, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
filterErr := func(err error) (error, bool) {
|
||||
if _, ok := err.(Recoverable); !ok {
|
||||
return err, true
|
||||
}
|
||||
|
||||
// lib/kobject_uevent.c:
|
||||
// set portid 0 to inform userspace message comes from kernel
|
||||
if v, ok := from.(*syscall.SockaddrNetlink); !ok {
|
||||
return ErrBadSocket
|
||||
} else if v.Pid != 0 {
|
||||
return (*BadPortError)(v)
|
||||
|
||||
// avoids dropping pending coldboot
|
||||
if _, ok := err.(NeedsColdboot); ok {
|
||||
coldboot = true
|
||||
}
|
||||
|
||||
var msg Message
|
||||
if err = msg.UnmarshalBinary(data); err != nil {
|
||||
return err
|
||||
}
|
||||
events <- &msg
|
||||
return err, !handleConsumeErr(err)
|
||||
}
|
||||
|
||||
retry:
|
||||
if coldboot {
|
||||
goto coldboot
|
||||
}
|
||||
for {
|
||||
msg, err := c.receiveEvent(ctx)
|
||||
if err == nil {
|
||||
events <- msg
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := filterErr(err); ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
coldboot:
|
||||
coldboot = false
|
||||
|
||||
visited := make(chan string)
|
||||
ctxColdboot, cancelColdboot := context.WithCancel(ctx)
|
||||
var coldbootErr error
|
||||
go func() {
|
||||
coldbootErr = Coldboot(ctxColdboot, sysfs, visited, handleWalkErr)
|
||||
close(visited)
|
||||
}()
|
||||
for pathname := range visited {
|
||||
handleColdbootVisited(pathname)
|
||||
|
||||
for {
|
||||
msg, err := c.receiveEvent(nil)
|
||||
if err == nil {
|
||||
events <- msg
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, syscall.EWOULDBLOCK) {
|
||||
break
|
||||
}
|
||||
if filteredErr, ok := filterErr(err); ok {
|
||||
cancelColdboot()
|
||||
return filteredErr
|
||||
}
|
||||
}
|
||||
}
|
||||
cancelColdboot()
|
||||
if coldbootErr != nil {
|
||||
return coldbootErr
|
||||
}
|
||||
goto retry
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/uevent"
|
||||
)
|
||||
|
||||
@@ -116,7 +117,7 @@ func adeB[V any, S interface {
|
||||
func TestDialConsume(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c, err := uevent.Dial()
|
||||
c, err := uevent.Dial(0)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial: error = %v", err)
|
||||
}
|
||||
@@ -127,7 +128,7 @@ func TestDialConsume(t *testing.T) {
|
||||
})
|
||||
|
||||
// check kernel-assigned port id
|
||||
c0, err0 := uevent.Dial()
|
||||
c0, err0 := uevent.Dial(0)
|
||||
if err0 != nil {
|
||||
t.Fatalf("Dial: error = %v", err)
|
||||
}
|
||||
@@ -155,13 +156,23 @@ func TestDialConsume(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
consume := func(c *uevent.Conn, ctx context.Context) error {
|
||||
return c.Consume(ctx, fhs.Sys, events, false, func(path string) {
|
||||
t.Log("coldboot visited", path)
|
||||
}, func(err error) bool {
|
||||
t.Log(err)
|
||||
_, ok := err.(uevent.NeedsColdboot)
|
||||
return !ok
|
||||
}, nil)
|
||||
}
|
||||
|
||||
wg.Go(func() {
|
||||
if err = c.Consume(ctx, events); err != context.Canceled {
|
||||
if err = consume(c, ctx); err != context.Canceled {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
wg.Go(func() {
|
||||
if err0 = c0.Consume(ctx, events); err0 != context.Canceled {
|
||||
if err0 = consume(c0, ctx); err0 != context.Canceled {
|
||||
panic(err0)
|
||||
}
|
||||
})
|
||||
@@ -185,11 +196,11 @@ func TestDialConsume(t *testing.T) {
|
||||
exclExit := make(chan struct{})
|
||||
wg.Go(func() {
|
||||
defer func() { exclExit <- struct{}{} }()
|
||||
errs[0] = c.Consume(ctx, events)
|
||||
errs[0] = consume(c, ctx)
|
||||
})
|
||||
wg.Go(func() {
|
||||
defer func() { exclExit <- struct{}{} }()
|
||||
errs[1] = c.Consume(ctx, events)
|
||||
errs[1] = consume(c, ctx)
|
||||
})
|
||||
<-exclExit
|
||||
cancel()
|
||||
|
||||
@@ -3,7 +3,7 @@ package wayland
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
@@ -19,7 +19,7 @@ func TestSecurityContextClose(t *testing.T) {
|
||||
}
|
||||
|
||||
var ctx SecurityContext
|
||||
if f, err := os.Create(path.Join(t.TempDir(), "remove")); err != nil {
|
||||
if f, err := os.Create(filepath.Join(t.TempDir(), "remove")); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
ctx.bindPath = check.MustAbs(f.Name())
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
@@ -54,7 +54,7 @@ func (t *T) MustCheckFile(wantFilePath string) {
|
||||
}
|
||||
|
||||
func mustAbs(s string) string {
|
||||
if !path.IsAbs(s) {
|
||||
if !filepath.IsAbs(s) {
|
||||
fatalf("[FAIL] %q is not absolute", s)
|
||||
panic("unreachable")
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func (t *T) MustCheck(want *TestCase) {
|
||||
os.Getenv("XDG_RUNTIME_DIR"),
|
||||
}
|
||||
for _, a := range checkWritableDirPaths {
|
||||
pathname := path.Join(mustAbs(a), ".hakurei-check")
|
||||
pathname := filepath.Join(mustAbs(a), ".hakurei-check")
|
||||
if err := os.WriteFile(pathname, make([]byte, 1<<8), 0600); err != nil {
|
||||
fatalf("[FAIL] %s", err)
|
||||
} else if err = os.Remove(pathname); err != nil {
|
||||
|
||||
@@ -5,7 +5,7 @@ package sandbox
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ func SwapPrint(f F) (old F) { old = printfFunc; printfFunc = f; return }
|
||||
func SwapFatal(f F) (old F) { old = fatalfFunc; fatalfFunc = f; return }
|
||||
|
||||
func MustWantFile(t *testing.T, v any) (wantFile string) {
|
||||
wantFile = path.Join(t.TempDir(), "want.json")
|
||||
wantFile = filepath.Join(t.TempDir(), "want.json")
|
||||
if f, err := os.OpenFile(wantFile, os.O_CREATE|os.O_WRONLY, 0400); err != nil {
|
||||
t.Fatalf("cannot create %q: %v", wantFile, err)
|
||||
} else if err = json.NewEncoder(f).Encode(v); err != nil {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -68,7 +68,7 @@ func (s *FS) Compare(prefix string, e fs.FS) error {
|
||||
printDir(prefix, dir)
|
||||
return ErrFSInvalidEnt
|
||||
} else {
|
||||
name = path.Join(prefix, name)
|
||||
name = filepath.Join(prefix, name)
|
||||
|
||||
if fi, err := got.Info(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -4,7 +4,7 @@ package sandbox_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/test/internal/sandbox"
|
||||
@@ -87,7 +87,7 @@ func TestMountinfo(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
name := path.Join(t.TempDir(), "sample")
|
||||
name := filepath.Join(t.TempDir(), "sample")
|
||||
if err := os.WriteFile(name, []byte(tc.sample), 0400); err != nil {
|
||||
t.Fatalf("cannot write sample: %v", err)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"iter"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -394,7 +394,7 @@ func mn(
|
||||
},
|
||||
FirstChild: firstChild,
|
||||
NextSibling: nextSibling,
|
||||
Clean: path.Clean(target),
|
||||
Clean: filepath.Clean(target),
|
||||
Covered: covered,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package vfs
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -43,7 +43,7 @@ func (n *MountInfoNode) visit(yield func(*MountInfoNode) bool) bool {
|
||||
|
||||
// Unfold unfolds the mount hierarchy and resolves covered paths.
|
||||
func (d *MountInfoDecoder) Unfold(target string) (*MountInfoNode, error) {
|
||||
targetClean := path.Clean(target)
|
||||
targetClean := filepath.Clean(target)
|
||||
|
||||
var mountinfoSize int
|
||||
for range d.Entries() {
|
||||
@@ -61,7 +61,7 @@ func (d *MountInfoDecoder) Unfold(target string) (*MountInfoNode, error) {
|
||||
{
|
||||
i := 0
|
||||
for ent := range d.Entries() {
|
||||
mountinfo[i] = &MountInfoNode{Clean: path.Clean(ent.Target), MountInfoEntry: ent}
|
||||
mountinfo[i] = &MountInfoNode{Clean: filepath.Clean(ent.Target), MountInfoEntry: ent}
|
||||
idIndex[ent.ID] = i
|
||||
if mountinfo[i].Clean == targetClean {
|
||||
targetIndex = i
|
||||
|
||||
Reference in New Issue
Block a user