Compare commits
42 Commits
master
...
wip-sysfs-
| Author | SHA1 | Date | |
|---|---|---|---|
|
121fcfa406
|
|||
|
19c76e0831
|
|||
|
71fcc972ba
|
|||
|
62002efd08
|
|||
|
e33294db9c
|
|||
|
b1ea3b4acf
|
|||
|
2c254c70b8
|
|||
|
ea014d6af2
|
|||
|
1b48484c16
|
|||
|
713bff3eb0
|
|||
|
30f459e690
|
|||
|
8766fddcb3
|
|||
|
2745602be3
|
|||
|
ee22847dde
|
|||
|
c61188649b
|
|||
|
6a87a96838
|
|||
|
2548a681e9
|
|||
|
d514d0679f
|
|||
|
4407892632
|
|||
|
e661260607
|
|||
|
044490e0a5
|
|||
|
af038c89ff
|
|||
|
d2f30173cd
|
|||
|
5319ea994c
|
|||
|
bbe178be3e
|
|||
|
ca28e9936b
|
|||
|
f61c6ade56
|
|||
|
fce3d63823
|
|||
|
722c3cc54f
|
|||
|
372d509e5c
|
|||
|
d62516ed1e
|
|||
|
d2b635eb55
|
|||
|
50403e9d60
|
|||
|
b98c5f2e21
|
|||
|
d972cffe5a
|
|||
|
d8648304bb
|
|||
|
f7bfa9a6c2
|
|||
|
7035b4b598
|
|||
|
094b8400dd
|
|||
|
4652d921d8
|
|||
|
066213c245
|
|||
|
98832c21ee
|
@@ -1,3 +1,7 @@
|
|||||||
|
// The earlyinit is part of the Rosa OS initramfs and serves as the system init.
|
||||||
|
//
|
||||||
|
// This program is an internal detail of Rosa OS and is not usable on its own.
|
||||||
|
// It is not covered by the compatibility promise.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@@ -11,7 +12,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
_ "unsafe" // for go:linkname
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
@@ -27,9 +27,14 @@ import (
|
|||||||
|
|
||||||
// optionalErrorUnwrap calls [errors.Unwrap] and returns the resulting value
|
// optionalErrorUnwrap calls [errors.Unwrap] and returns the resulting value
|
||||||
// if it is not nil, or the original value if it is.
|
// if it is not nil, or the original value if it is.
|
||||||
//
|
func optionalErrorUnwrap(err error) error {
|
||||||
//go:linkname optionalErrorUnwrap hakurei.app/container.optionalErrorUnwrap
|
if underlyingErr := errors.Unwrap(err); underlyingErr != nil {
|
||||||
func optionalErrorUnwrap(err error) error
|
return underlyingErr
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var errSuccess = errors.New("success")
|
||||||
|
|
||||||
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
||||||
var (
|
var (
|
||||||
@@ -60,9 +65,9 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
var (
|
var (
|
||||||
flagIdentifierFile int
|
flagIdentifierFile int
|
||||||
)
|
)
|
||||||
c.NewCommand("app", "Load and start container from configuration file", func(args []string) error {
|
c.NewCommand("run", "Load and start container from configuration file", func(args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
log.Fatal("app requires at least 1 argument")
|
log.Fatal("run requires at least 1 argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
config := tryPath(msg, args[0])
|
config := tryPath(msg, args[0])
|
||||||
@@ -98,7 +103,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool
|
flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool
|
||||||
)
|
)
|
||||||
|
|
||||||
c.NewCommand("run", "Configure and start a permissive container", func(args []string) error {
|
c.NewCommand("exec", "Configure and start a permissive container", func(args []string) error {
|
||||||
if flagIdentity < hst.IdentityStart || flagIdentity > hst.IdentityEnd {
|
if flagIdentity < hst.IdentityStart || flagIdentity > hst.IdentityEnd {
|
||||||
log.Fatalf("identity %d out of range", flagIdentity)
|
log.Fatalf("identity %d out of range", flagIdentity)
|
||||||
}
|
}
|
||||||
@@ -323,7 +328,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
flagShort bool
|
flagShort bool
|
||||||
flagNoStore bool
|
flagNoStore bool
|
||||||
)
|
)
|
||||||
c.NewCommand("show", "Show live or local app configuration", func(args []string) error {
|
c.NewCommand("show", "Show live or local instance configuration", func(args []string) error {
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0: // system
|
case 0: // system
|
||||||
printShowSystem(os.Stdout, flagShort, flagJSON)
|
printShowSystem(os.Stdout, flagShort, flagJSON)
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ func TestHelp(t *testing.T) {
|
|||||||
Usage: hakurei [-h | --help] [-v] [--json] COMMAND [OPTIONS]
|
Usage: hakurei [-h | --help] [-v] [--json] COMMAND [OPTIONS]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
app Load and start container from configuration file
|
run Load and start container from configuration file
|
||||||
run Configure and start a permissive container
|
exec Configure and start a permissive container
|
||||||
show Show live or local app configuration
|
show Show live or local instance configuration
|
||||||
ps List active instances
|
ps List active instances
|
||||||
version Display version information
|
version Display version information
|
||||||
license Show full license text
|
license Show full license text
|
||||||
@@ -35,8 +35,8 @@ Commands:
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"run", []string{"run", "-h"}, `
|
"exec", []string{"exec", "-h"}, `
|
||||||
Usage: hakurei run [-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]
|
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
|
-X Enable direct connection to X11
|
||||||
|
|||||||
@@ -1,8 +1,42 @@
|
|||||||
|
// Hakurei runs user-specified containers as subordinate users.
|
||||||
|
//
|
||||||
|
// This program is generally invoked by another, higher level program, which
|
||||||
|
// creates container configuration via package [hst] or an implementation of it.
|
||||||
|
//
|
||||||
|
// The parent may leave files open and specify their file descriptor for various
|
||||||
|
// uses. In these cases, standard streams and netpoll files are treated as
|
||||||
|
// invalid file descriptors and rejected. All string representations must be in
|
||||||
|
// decimal.
|
||||||
|
//
|
||||||
|
// When specifying a [hst.Config] JSON stream or file to the run subcommand, the
|
||||||
|
// argument "-" is equivalent to stdin. Otherwise, file descriptor rules
|
||||||
|
// described above applies. Invalid file descriptors are treated as file names
|
||||||
|
// in their string representation, with the exception that if a netpoll file
|
||||||
|
// descriptor is attempted, the program fails.
|
||||||
|
//
|
||||||
|
// The flag --identifier-fd can be optionally specified to the run subcommand to
|
||||||
|
// receive the identifier of the newly started instance. File descriptor rules
|
||||||
|
// described above applies, and the file must be writable. This is sent after
|
||||||
|
// its state is made available, so the client must not attempt to poll for it.
|
||||||
|
// This uses the internal binary format of [hst.ID].
|
||||||
|
//
|
||||||
|
// For the show and ps subcommands, the flag --json can be applied to the main
|
||||||
|
// hakurei command to serialise output in JSON when applicable. Additionally,
|
||||||
|
// the flag --short targeting each subcommand is used to omit some information
|
||||||
|
// in both JSON and user-facing output. Only JSON-encoded output is covered
|
||||||
|
// under the compatibility promise.
|
||||||
|
//
|
||||||
|
// A template for [hst.Config] demonstrating all available configuration fields
|
||||||
|
// is returned by [hst.Template]. The JSON-encoded equivalent of this can be
|
||||||
|
// obtained via the template subcommand. Fields left unpopulated in the template
|
||||||
|
// (the direct_* family of fields, which are insecure under any configuration if
|
||||||
|
// enabled) are unsupported.
|
||||||
|
//
|
||||||
|
// For simple (but insecure) testing scenarios, the exec subcommand can be used
|
||||||
|
// to generate a simple, permissive configuration in-memory. See its help
|
||||||
|
// message for all available options.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// this works around go:embed '..' limitation
|
|
||||||
//go:generate cp ../../LICENSE .
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
@@ -17,12 +51,9 @@ import (
|
|||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
//go:generate cp ../../LICENSE .
|
||||||
errSuccess = errors.New("success")
|
//go:embed LICENSE
|
||||||
|
var license string
|
||||||
//go:embed LICENSE
|
|
||||||
license string
|
|
||||||
)
|
|
||||||
|
|
||||||
// earlyHardeningErrs are errors collected while setting up early hardening feature.
|
// earlyHardeningErrs are errors collected while setting up early hardening feature.
|
||||||
type earlyHardeningErrs struct{ yamaLSM, dumpable error }
|
type earlyHardeningErrs struct{ yamaLSM, dumpable error }
|
||||||
@@ -31,8 +62,8 @@ func main() {
|
|||||||
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
||||||
container.TryArgv0(nil)
|
container.TryArgv0(nil)
|
||||||
|
|
||||||
log.SetPrefix("hakurei: ")
|
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("hakurei: ")
|
||||||
msg := message.New(log.Default())
|
msg := message.New(log.Default())
|
||||||
|
|
||||||
early := earlyHardeningErrs{
|
early := earlyHardeningErrs{
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// tryPath attempts to read [hst.Config] from multiple sources.
|
// tryPath attempts to read [hst.Config] from multiple sources.
|
||||||
// tryPath reads from [os.Stdin] if name has value "-".
|
//
|
||||||
// Otherwise, name is passed to tryFd, and if that returns nil, name is passed to [os.Open].
|
// tryPath reads from [os.Stdin] if name has value "-". Otherwise, name is
|
||||||
|
// passed to tryFd, and if that returns nil, name is passed to [os.Open].
|
||||||
func tryPath(msg message.Msg, name string) (config *hst.Config) {
|
func tryPath(msg message.Msg, name string) (config *hst.Config) {
|
||||||
var r io.ReadCloser
|
var r io.ReadCloser
|
||||||
config = new(hst.Config)
|
config = new(hst.Config)
|
||||||
@@ -46,7 +47,8 @@ func tryPath(msg message.Msg, name string) (config *hst.Config) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryFd returns a [io.ReadCloser] if name represents an integer corresponding to a valid file descriptor.
|
// tryFd returns a [io.ReadCloser] if name represents an integer corresponding
|
||||||
|
// to a valid file descriptor.
|
||||||
func tryFd(msg message.Msg, name string) io.ReadCloser {
|
func tryFd(msg message.Msg, name string) io.ReadCloser {
|
||||||
if v, err := strconv.Atoi(name); err != nil {
|
if v, err := strconv.Atoi(name); err != nil {
|
||||||
if !errors.Is(err, strconv.ErrSyntax) {
|
if !errors.Is(err, strconv.ErrSyntax) {
|
||||||
@@ -60,7 +62,12 @@ func tryFd(msg message.Msg, name string) io.ReadCloser {
|
|||||||
|
|
||||||
msg.Verbosef("trying config stream from %d", v)
|
msg.Verbosef("trying config stream from %d", v)
|
||||||
fd := uintptr(v)
|
fd := uintptr(v)
|
||||||
if _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_GETFD, 0); errno != 0 {
|
if _, _, errno := syscall.Syscall(
|
||||||
|
syscall.SYS_FCNTL,
|
||||||
|
fd,
|
||||||
|
syscall.F_GETFD,
|
||||||
|
0,
|
||||||
|
); errno != 0 {
|
||||||
if errors.Is(errno, syscall.EBADF) { // reject bad fd
|
if errors.Is(errno, syscall.EBADF) { // reject bad fd
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -75,10 +82,12 @@ func tryFd(msg message.Msg, name string) io.ReadCloser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// shortLengthMin is the minimum length a short form identifier can have and still be interpreted as an identifier.
|
// shortLengthMin is the minimum length a short form identifier can have and
|
||||||
|
// still be interpreted as an identifier.
|
||||||
const shortLengthMin = 1 << 3
|
const shortLengthMin = 1 << 3
|
||||||
|
|
||||||
// shortIdentifier returns an eight character short representation of [hst.ID] from its random bytes.
|
// shortIdentifier returns an eight character short representation of [hst.ID]
|
||||||
|
// from its random bytes.
|
||||||
func shortIdentifier(id *hst.ID) string {
|
func shortIdentifier(id *hst.ID) string {
|
||||||
return shortIdentifierString(id.String())
|
return shortIdentifierString(id.String())
|
||||||
}
|
}
|
||||||
@@ -88,7 +97,8 @@ func shortIdentifierString(s string) string {
|
|||||||
return s[len(hst.ID{}) : len(hst.ID{})+shortLengthMin]
|
return s[len(hst.ID{}) : len(hst.ID{})+shortLengthMin]
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryIdentifier attempts to match [hst.State] from a [hex] representation of [hst.ID] or a prefix of its lower half.
|
// tryIdentifier attempts to match [hst.State] from a [hex] representation of
|
||||||
|
// [hst.ID] or a prefix of its lower half.
|
||||||
func tryIdentifier(msg message.Msg, name string, s *store.Store) *hst.State {
|
func tryIdentifier(msg message.Msg, name string, s *store.Store) *hst.State {
|
||||||
const (
|
const (
|
||||||
likeShort = 1 << iota
|
likeShort = 1 << iota
|
||||||
@@ -96,7 +106,8 @@ func tryIdentifier(msg message.Msg, name string, s *store.Store) *hst.State {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var likely uintptr
|
var likely uintptr
|
||||||
if len(name) >= shortLengthMin && len(name) <= len(hst.ID{}) { // half the hex representation
|
// half the hex representation
|
||||||
|
if len(name) >= shortLengthMin && len(name) <= len(hst.ID{}) {
|
||||||
// cannot safely decode here due to unknown alignment
|
// cannot safely decode here due to unknown alignment
|
||||||
for _, c := range name {
|
for _, c := range name {
|
||||||
if c >= '0' && c <= '9' {
|
if c >= '0' && c <= '9' {
|
||||||
|
|||||||
7
cmd/hsu/conf.go
Normal file
7
cmd/hsu/conf.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !rosa
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// hsuConfPath is an absolute pathname to the hsu configuration file. Its
|
||||||
|
// contents are interpreted by parseConfig.
|
||||||
|
const hsuConfPath = "/etc/hsurc"
|
||||||
7
cmd/hsu/config_rosa.go
Normal file
7
cmd/hsu/config_rosa.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build rosa
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// hsuConfPath is the pathname to the hsu configuration file, specific to
|
||||||
|
// Rosa OS. Its contents are interpreted by parseConfig.
|
||||||
|
const hsuConfPath = "/system/etc/hsurc"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
/* copied from hst and must never be changed */
|
/* keep in sync with hst */
|
||||||
|
|
||||||
const (
|
const (
|
||||||
userOffset = 100000
|
userOffset = 100000
|
||||||
|
|||||||
@@ -1,7 +1,58 @@
|
|||||||
|
// hsu starts the hakurei shim as the target subordinate user.
|
||||||
|
//
|
||||||
|
// The hsu program must be installed with the setuid and setgid bit set, and
|
||||||
|
// owned by root. A configuration file must be installed at /etc/hsurc with
|
||||||
|
// permission bits 0400, and owned by root. Each line of the file specifies a
|
||||||
|
// hakurei userid to kernel uid mapping. A line consists of the decimal string
|
||||||
|
// representation of the uid of the user wishing to start hakurei containers,
|
||||||
|
// followed by a space, followed by the decimal string representation of its
|
||||||
|
// userid. Duplicate uid entries are ignored, with the first occurrence taking
|
||||||
|
// effect.
|
||||||
|
//
|
||||||
|
// For example, to map the kernel uid 1000 to the hakurei user id 0:
|
||||||
|
//
|
||||||
|
// 1000 0
|
||||||
|
//
|
||||||
|
// # Internals
|
||||||
|
//
|
||||||
|
// Hakurei and hsu holds pathnames pointing to each other set at link time. For
|
||||||
|
// this reason, a distribution of hakurei has fixed installation prefix. Since
|
||||||
|
// this program is never invoked by the user, behaviour described in the
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// HAKUREI_SHIM specifies a single byte between '3' and '9' representing the
|
||||||
|
// setup pipe file descriptor. It is passed as is to the shim process and is the
|
||||||
|
// only value in the environment of the shim process. Since hsurc is not
|
||||||
|
// accessible to the parent process, leaving this unset causes hsu to print the
|
||||||
|
// corresponding hakurei user id of the parent and terminate.
|
||||||
|
//
|
||||||
|
// HAKUREI_IDENTITY specifies the identity of the instance being started and is
|
||||||
|
// used to produce the kernel uid alongside hakurei user id looked up from hsurc.
|
||||||
|
//
|
||||||
|
// HAKUREI_GROUPS specifies supplementary groups to inherit from the credentials
|
||||||
|
// of the parent process in a ' ' separated list of decimal string
|
||||||
|
// representations of gid. This has the unfortunate consequence of allowing
|
||||||
|
// users mapped via hsurc to effectively drop group membership, so special care
|
||||||
|
// must be taken to ensure this does not lead to an increase in access. This is
|
||||||
|
// not applicable to Rosa OS since unsigned code execution is not permitted
|
||||||
|
// outside hakurei containers, and is generally nonapplicable to the security
|
||||||
|
// model of hakurei, where all untrusted code runs within containers.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// minimise imports to avoid inadvertently calling init or global variable functions
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -16,10 +67,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// envIdentity is the name of the environment variable holding a
|
// envShim is the name of the environment variable holding a single byte
|
||||||
// single byte representing the shim setup pipe file descriptor.
|
// representing the shim setup pipe file descriptor.
|
||||||
envShim = "HAKUREI_SHIM"
|
envShim = "HAKUREI_SHIM"
|
||||||
// envGroups holds a ' ' separated list of string representations of
|
// envIdentity is the name of the environment variable holding a decimal
|
||||||
|
// string representation of the current application identity.
|
||||||
|
envIdentity = "HAKUREI_IDENTITY"
|
||||||
|
// envGroups holds a ' ' separated list of decimal string representations of
|
||||||
// supplementary group gid. Membership requirements are enforced.
|
// supplementary group gid. Membership requirements are enforced.
|
||||||
envGroups = "HAKUREI_GROUPS"
|
envGroups = "HAKUREI_GROUPS"
|
||||||
)
|
)
|
||||||
@@ -35,7 +89,6 @@ func main() {
|
|||||||
|
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("hsu: ")
|
log.SetPrefix("hsu: ")
|
||||||
log.SetOutput(os.Stderr)
|
|
||||||
|
|
||||||
if os.Geteuid() != 0 {
|
if os.Geteuid() != 0 {
|
||||||
log.Fatal("this program must be owned by uid 0 and have the setuid bit set")
|
log.Fatal("this program must be owned by uid 0 and have the setuid bit set")
|
||||||
@@ -99,8 +152,6 @@ func main() {
|
|||||||
// last possible uid outcome
|
// last possible uid outcome
|
||||||
uidEnd = 999919999
|
uidEnd = 999919999
|
||||||
)
|
)
|
||||||
|
|
||||||
// cast to int for use with library functions
|
|
||||||
uid := int(toUser(userid, identity))
|
uid := int(toUser(userid, identity))
|
||||||
|
|
||||||
// final bounds check to catch any bugs
|
// final bounds check to catch any bugs
|
||||||
@@ -136,7 +187,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// careful! users in the allowlist is effectively allowed to drop groups via hsu
|
// careful! users in the allowlist is effectively allowed to drop groups via hsu
|
||||||
|
|
||||||
if err := syscall.Setresgid(uid, uid, uid); err != nil {
|
if err := syscall.Setresgid(uid, uid, uid); err != nil {
|
||||||
log.Fatalf("cannot set gid: %v", err)
|
log.Fatalf("cannot set gid: %v", err)
|
||||||
}
|
}
|
||||||
@@ -146,10 +196,21 @@ func main() {
|
|||||||
if err := syscall.Setresuid(uid, uid, uid); err != nil {
|
if err := syscall.Setresuid(uid, uid, uid); err != nil {
|
||||||
log.Fatalf("cannot set uid: %v", err)
|
log.Fatalf("cannot set uid: %v", err)
|
||||||
}
|
}
|
||||||
if _, _, errno := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0); errno != 0 {
|
|
||||||
|
if _, _, errno := syscall.AllThreadsSyscall(
|
||||||
|
syscall.SYS_PRCTL,
|
||||||
|
PR_SET_NO_NEW_PRIVS, 1,
|
||||||
|
0,
|
||||||
|
); errno != 0 {
|
||||||
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
||||||
}
|
}
|
||||||
if err := syscall.Exec(toolPath, []string{"hakurei", "shim"}, []string{envShim + "=" + shimSetupFd}); err != nil {
|
|
||||||
|
if err := syscall.Exec(toolPath, []string{
|
||||||
|
"hakurei",
|
||||||
|
"shim",
|
||||||
|
}, []string{
|
||||||
|
envShim + "=" + shimSetupFd,
|
||||||
|
}); err != nil {
|
||||||
log.Fatalf("cannot start shim: %v", err)
|
log.Fatalf("cannot start shim: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ const (
|
|||||||
useridEnd = useridStart + rangeSize - 1
|
useridEnd = useridStart + rangeSize - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseUint32Fast parses a string representation of an unsigned 32-bit integer value
|
// parseUint32Fast parses a string representation of an unsigned 32-bit integer
|
||||||
// using the fast path only. This limits the range of values it is defined in.
|
// value using the fast path only. This limits the range of values it is defined
|
||||||
|
// in but is perfectly adequate for this use case.
|
||||||
func parseUint32Fast(s string) (uint32, error) {
|
func parseUint32Fast(s string) (uint32, error) {
|
||||||
sLen := len(s)
|
sLen := len(s)
|
||||||
if sLen < 1 {
|
if sLen < 1 {
|
||||||
@@ -40,12 +41,14 @@ func parseUint32Fast(s string) (uint32, error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseConfig reads a list of allowed users from r until it encounters puid or [io.EOF].
|
// parseConfig reads a list of allowed users from r until it encounters puid or
|
||||||
|
// [io.EOF].
|
||||||
//
|
//
|
||||||
// Each line of the file specifies a hakurei userid to kernel uid mapping. A line consists
|
// Each line of the file specifies a hakurei userid to kernel uid mapping. A
|
||||||
// of the string representation of the uid of the user wishing to start hakurei containers,
|
// line consists of the string representation of the uid of the user wishing to
|
||||||
// followed by a space, followed by the string representation of its userid. Duplicate uid
|
// start hakurei containers, followed by a space, followed by the string
|
||||||
// entries are ignored, with the first occurrence taking effect.
|
// representation of its userid. Duplicate uid entries are ignored, with the
|
||||||
|
// first occurrence taking effect.
|
||||||
//
|
//
|
||||||
// All string representations are parsed by calling parseUint32Fast.
|
// All string representations are parsed by calling parseUint32Fast.
|
||||||
func parseConfig(r io.Reader, puid uint32) (userid uint32, ok bool, err error) {
|
func parseConfig(r io.Reader, puid uint32) (userid uint32, ok bool, err error) {
|
||||||
@@ -81,10 +84,6 @@ func parseConfig(r io.Reader, puid uint32) (userid uint32, ok bool, err error) {
|
|||||||
return useridEnd + 1, false, s.Err()
|
return useridEnd + 1, false, s.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// hsuConfPath is an absolute pathname to the hsu configuration file.
|
|
||||||
// Its contents are interpreted by parseConfig.
|
|
||||||
const hsuConfPath = "/etc/hsurc"
|
|
||||||
|
|
||||||
// mustParseConfig calls parseConfig to interpret the contents of hsuConfPath,
|
// mustParseConfig calls parseConfig to interpret the contents of hsuConfPath,
|
||||||
// terminating the program if an error is encountered, the syntax is incorrect,
|
// terminating the program if an error is encountered, the syntax is incorrect,
|
||||||
// or the current user is not authorised to use hsu because its uid is missing.
|
// or the current user is not authorised to use hsu because its uid is missing.
|
||||||
@@ -112,10 +111,6 @@ func mustParseConfig(puid int) (userid uint32) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// envIdentity is the name of the environment variable holding a
|
|
||||||
// string representation of the current application identity.
|
|
||||||
var envIdentity = "HAKUREI_IDENTITY"
|
|
||||||
|
|
||||||
// mustReadIdentity calls parseUint32Fast to interpret the value stored in envIdentity,
|
// mustReadIdentity calls parseUint32Fast to interpret the value stored in envIdentity,
|
||||||
// terminating the program if the value is not set, malformed, or out of bounds.
|
// terminating the program if the value is not set, malformed, or out of bounds.
|
||||||
func mustReadIdentity() uint32 {
|
func mustReadIdentity() uint32 {
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
// The mbf program is a frontend for [hakurei.app/internal/rosa].
|
||||||
|
//
|
||||||
|
// This program is not covered by the compatibility promise. The command line
|
||||||
|
// interface, available packages and their behaviour, and even the on-disk
|
||||||
|
// format, may change at any time.
|
||||||
|
//
|
||||||
|
// # Name
|
||||||
|
//
|
||||||
|
// The name mbf stands for maiden's best friend, as a tribute to the DOOM source
|
||||||
|
// port of [the same name]. This name is a placeholder and is subject to change.
|
||||||
|
//
|
||||||
|
// [the same name]: https://www.doomwiki.org/wiki/MBF
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -436,6 +448,7 @@ func main() {
|
|||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
flagDump string
|
flagDump string
|
||||||
|
flagEnter bool
|
||||||
flagExport string
|
flagExport string
|
||||||
)
|
)
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
@@ -445,9 +458,13 @@ func main() {
|
|||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.New("cure requires 1 argument")
|
return errors.New("cure requires 1 argument")
|
||||||
}
|
}
|
||||||
if p, ok := rosa.ResolveName(args[0]); !ok {
|
p, ok := rosa.ResolveName(args[0])
|
||||||
|
if !ok {
|
||||||
return fmt.Errorf("unknown artifact %q", args[0])
|
return fmt.Errorf("unknown artifact %q", args[0])
|
||||||
} else if flagDump == "" {
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
pathname, _, err := cache.Cure(rosa.Std.Load(p))
|
pathname, _, err := cache.Cure(rosa.Std.Load(p))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -477,7 +494,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
} else {
|
|
||||||
|
case flagDump != "":
|
||||||
f, err := os.OpenFile(
|
f, err := os.OpenFile(
|
||||||
flagDump,
|
flagDump,
|
||||||
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
|
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
|
||||||
@@ -493,6 +511,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return f.Close()
|
return f.Close()
|
||||||
|
|
||||||
|
case flagEnter:
|
||||||
|
return cache.EnterExec(
|
||||||
|
ctx,
|
||||||
|
rosa.Std.Load(p),
|
||||||
|
true, os.Stdin, os.Stdout, os.Stderr,
|
||||||
|
rosa.AbsSystem.Append("bin", "mksh"),
|
||||||
|
"sh",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
).
|
).
|
||||||
@@ -505,6 +532,11 @@ func main() {
|
|||||||
&flagExport,
|
&flagExport,
|
||||||
"export", command.StringFlag(""),
|
"export", command.StringFlag(""),
|
||||||
"Export cured artifact to specified pathname",
|
"Export cured artifact to specified pathname",
|
||||||
|
).
|
||||||
|
Flag(
|
||||||
|
&flagEnter,
|
||||||
|
"enter", command.BoolFlag(false),
|
||||||
|
"Enter cure container with an interactive shell",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,7 +559,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
presets[i] = p
|
presets[i] = p
|
||||||
}
|
}
|
||||||
root := make(rosa.Collect, 0, 6+len(args))
|
root := make(pkg.Collect, 0, 6+len(args))
|
||||||
root = rosa.Std.AppendPresets(root, presets...)
|
root = rosa.Std.AppendPresets(root, presets...)
|
||||||
|
|
||||||
if flagWithToolchain {
|
if flagWithToolchain {
|
||||||
@@ -543,7 +575,7 @@ func main() {
|
|||||||
|
|
||||||
if _, _, err := cache.Cure(&root); err == nil {
|
if _, _, err := cache.Cure(&root); err == nil {
|
||||||
return errors.New("unreachable")
|
return errors.New("unreachable")
|
||||||
} else if !errors.Is(err, rosa.Collected{}) {
|
} else if !pkg.IsCollected(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,13 +668,13 @@ func main() {
|
|||||||
).
|
).
|
||||||
Flag(
|
Flag(
|
||||||
&flagSession,
|
&flagSession,
|
||||||
"session", command.BoolFlag(false),
|
"session", command.BoolFlag(true),
|
||||||
"Retain session",
|
"Retain session",
|
||||||
).
|
).
|
||||||
Flag(
|
Flag(
|
||||||
&flagWithToolchain,
|
&flagWithToolchain,
|
||||||
"with-toolchain", command.BoolFlag(false),
|
"with-toolchain", command.BoolFlag(false),
|
||||||
"Include the stage3 LLVM toolchain",
|
"Include the stage2 LLVM toolchain",
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,10 @@ func destroySetup(private_data unsafe.Pointer) (ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export sharefs_init
|
//export sharefs_init
|
||||||
func sharefs_init(_ *C.struct_fuse_conn_info, cfg *C.struct_fuse_config) unsafe.Pointer {
|
func sharefs_init(
|
||||||
|
_ *C.struct_fuse_conn_info,
|
||||||
|
cfg *C.struct_fuse_config,
|
||||||
|
) unsafe.Pointer {
|
||||||
ctx := C.fuse_get_context()
|
ctx := C.fuse_get_context()
|
||||||
priv := (*C.struct_sharefs_private)(ctx.private_data)
|
priv := (*C.struct_sharefs_private)(ctx.private_data)
|
||||||
setup := cgo.Handle(priv.setup).Value().(*setupState)
|
setup := cgo.Handle(priv.setup).Value().(*setupState)
|
||||||
@@ -103,7 +106,11 @@ func sharefs_init(_ *C.struct_fuse_conn_info, cfg *C.struct_fuse_config) unsafe.
|
|||||||
cfg.negative_timeout = 0
|
cfg.negative_timeout = 0
|
||||||
|
|
||||||
// all future filesystem operations happen through this dirfd
|
// all future filesystem operations happen through this dirfd
|
||||||
if fd, err := syscall.Open(setup.Source.String(), syscall.O_DIRECTORY|syscall.O_RDONLY|syscall.O_CLOEXEC, 0); err != nil {
|
if fd, err := syscall.Open(
|
||||||
|
setup.Source.String(),
|
||||||
|
syscall.O_DIRECTORY|syscall.O_RDONLY|syscall.O_CLOEXEC,
|
||||||
|
0,
|
||||||
|
); err != nil {
|
||||||
log.Printf("cannot open %q: %v", setup.Source, err)
|
log.Printf("cannot open %q: %v", setup.Source, err)
|
||||||
goto fail
|
goto fail
|
||||||
} else if err = syscall.Fchdir(fd); err != nil {
|
} else if err = syscall.Fchdir(fd); err != nil {
|
||||||
@@ -169,8 +176,11 @@ func parseOpts(args *fuseArgs, setup *setupState, log *log.Logger) (ok bool) {
|
|||||||
// Decimal string representation of gid to set when running as root.
|
// Decimal string representation of gid to set when running as root.
|
||||||
setgid *C.char
|
setgid *C.char
|
||||||
|
|
||||||
// Decimal string representation of open file descriptor to read setupState from.
|
// Decimal string representation of open file descriptor to read
|
||||||
// This is an internal detail for containerisation and must not be specified directly.
|
// setupState from.
|
||||||
|
//
|
||||||
|
// This is an internal detail for containerisation and must not be
|
||||||
|
// specified directly.
|
||||||
setup *C.char
|
setup *C.char
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +263,8 @@ func parseOpts(args *fuseArgs, setup *setupState, log *log.Logger) (ok bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyArgs returns a heap allocated copy of an argument slice in fuse_args representation.
|
// copyArgs returns a heap allocated copy of an argument slice in fuse_args
|
||||||
|
// representation.
|
||||||
func copyArgs(s ...string) fuseArgs {
|
func copyArgs(s ...string) fuseArgs {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return fuseArgs{argc: 0, argv: nil, allocated: 0}
|
return fuseArgs{argc: 0, argv: nil, allocated: 0}
|
||||||
@@ -269,6 +280,7 @@ func copyArgs(s ...string) fuseArgs {
|
|||||||
func freeArgs(args *fuseArgs) { C.fuse_opt_free_args(args) }
|
func freeArgs(args *fuseArgs) { C.fuse_opt_free_args(args) }
|
||||||
|
|
||||||
// unsafeAddArgument adds an argument to fuseArgs via fuse_opt_add_arg.
|
// unsafeAddArgument adds an argument to fuseArgs via fuse_opt_add_arg.
|
||||||
|
//
|
||||||
// The last byte of arg must be 0.
|
// The last byte of arg must be 0.
|
||||||
func unsafeAddArgument(args *fuseArgs, arg string) {
|
func unsafeAddArgument(args *fuseArgs, arg string) {
|
||||||
C.fuse_opt_add_arg(args, (*C.char)(unsafe.Pointer(unsafe.StringData(arg))))
|
C.fuse_opt_add_arg(args, (*C.char)(unsafe.Pointer(unsafe.StringData(arg))))
|
||||||
@@ -288,8 +300,8 @@ func _main(s ...string) (exitCode int) {
|
|||||||
args := copyArgs(s...)
|
args := copyArgs(s...)
|
||||||
defer freeArgs(&args)
|
defer freeArgs(&args)
|
||||||
|
|
||||||
// this causes the kernel to enforce access control based on
|
// this causes the kernel to enforce access control based on struct stat
|
||||||
// struct stat populated by sharefs_getattr
|
// populated by sharefs_getattr
|
||||||
unsafeAddArgument(&args, "-odefault_permissions\x00")
|
unsafeAddArgument(&args, "-odefault_permissions\x00")
|
||||||
|
|
||||||
var priv C.struct_sharefs_private
|
var priv C.struct_sharefs_private
|
||||||
@@ -453,7 +465,10 @@ func _main(s ...string) (exitCode int) {
|
|||||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
}
|
}
|
||||||
z.Bind(z.Path, z.Path, 0)
|
z.Bind(z.Path, z.Path, 0)
|
||||||
setup.Fuse = int(proc.ExtraFileSlice(&z.ExtraFiles, os.NewFile(uintptr(C.fuse_session_fd(se)), "fuse")))
|
setup.Fuse = int(proc.ExtraFileSlice(
|
||||||
|
&z.ExtraFiles,
|
||||||
|
os.NewFile(uintptr(C.fuse_session_fd(se)), "fuse"),
|
||||||
|
))
|
||||||
|
|
||||||
var setupWriter io.WriteCloser
|
var setupWriter io.WriteCloser
|
||||||
if fd, w, err := container.Setup(&z.ExtraFiles); err != nil {
|
if fd, w, err := container.Setup(&z.ExtraFiles); err != nil {
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
// The sharefs FUSE filesystem is a permissionless shared filesystem.
|
||||||
|
//
|
||||||
|
// This filesystem is the primary means of file sharing between hakurei
|
||||||
|
// application containers. It serves the same purpose in Rosa OS as /sdcard
|
||||||
|
// does in AOSP.
|
||||||
|
//
|
||||||
|
// See help message for all available options.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
@@ -66,7 +67,7 @@ type syscallDispatcher interface {
|
|||||||
// ensureFile provides ensureFile.
|
// ensureFile provides ensureFile.
|
||||||
ensureFile(name string, perm, pperm os.FileMode) error
|
ensureFile(name string, perm, pperm os.FileMode) error
|
||||||
// mustLoopback provides mustLoopback.
|
// mustLoopback provides mustLoopback.
|
||||||
mustLoopback(msg message.Msg)
|
mustLoopback(ctx context.Context, msg message.Msg)
|
||||||
|
|
||||||
// seccompLoad provides [seccomp.Load].
|
// seccompLoad provides [seccomp.Load].
|
||||||
seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error
|
seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error
|
||||||
@@ -170,7 +171,7 @@ func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm
|
|||||||
func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
|
func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
|
||||||
return ensureFile(name, perm, pperm)
|
return ensureFile(name, perm, pperm)
|
||||||
}
|
}
|
||||||
func (direct) mustLoopback(msg message.Msg) {
|
func (direct) mustLoopback(ctx context.Context, msg message.Msg) {
|
||||||
var lo int
|
var lo int
|
||||||
if ifi, err := net.InterfaceByName("lo"); err != nil {
|
if ifi, err := net.InterfaceByName("lo"); err != nil {
|
||||||
msg.GetLogger().Fatalln(err)
|
msg.GetLogger().Fatalln(err)
|
||||||
@@ -199,11 +200,14 @@ func (direct) mustLoopback(msg message.Msg) {
|
|||||||
msg.GetLogger().Fatalf("RTNETLINK answers: %v", err)
|
msg.GetLogger().Fatalf("RTNETLINK answers: %v", err)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
msg.GetLogger().Fatalf("RTNETLINK answers with malformed message")
|
if err == context.DeadlineExceeded || err == context.Canceled {
|
||||||
|
msg.GetLogger().Fatalf("interrupted RTNETLINK operation")
|
||||||
|
}
|
||||||
|
msg.GetLogger().Fatal("RTNETLINK answers with malformed message")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
must(c.SendNewaddrLo(uint32(lo)))
|
must(c.SendNewaddrLo(ctx, uint32(lo)))
|
||||||
must(c.SendIfInfomsg(syscall.RTM_NEWLINK, 0, &syscall.IfInfomsg{
|
must(c.SendIfInfomsg(ctx, syscall.RTM_NEWLINK, 0, &syscall.IfInfomsg{
|
||||||
Family: syscall.AF_UNSPEC,
|
Family: syscall.AF_UNSPEC,
|
||||||
Index: int32(lo),
|
Index: int32(lo),
|
||||||
Flags: syscall.IFF_UP,
|
Flags: syscall.IFF_UP,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@@ -468,7 +469,7 @@ func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
|
|||||||
stub.CheckArg(k.Stub, "pperm", pperm, 2))
|
stub.CheckArg(k.Stub, "pperm", pperm, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*kstub) mustLoopback(message.Msg) { /* noop */ }
|
func (*kstub) mustLoopback(context.Context, message.Msg) { /* noop */ }
|
||||||
|
|
||||||
func (k *kstub) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error {
|
func (k *kstub) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -175,7 +176,11 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !params.HostNet {
|
if !params.HostNet {
|
||||||
k.mustLoopback(msg)
|
ctx, cancel := signal.NotifyContext(context.Background(), CancelSignal,
|
||||||
|
os.Interrupt, SIGTERM, SIGQUIT)
|
||||||
|
defer cancel() // for panics
|
||||||
|
k.mustLoopback(ctx, msg)
|
||||||
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
// write uid/gid map here so parent does not need to set dumpable
|
// write uid/gid map here so parent does not need to set dumpable
|
||||||
|
|||||||
10
dist/comp/_hakurei
vendored
10
dist/comp/_hakurei
vendored
@@ -1,11 +1,11 @@
|
|||||||
#compdef hakurei
|
#compdef hakurei
|
||||||
|
|
||||||
_hakurei_app() {
|
_hakurei_run() {
|
||||||
__hakurei_files
|
__hakurei_files
|
||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
_hakurei_run() {
|
_hakurei_exec() {
|
||||||
_arguments \
|
_arguments \
|
||||||
'--id[Reverse-DNS style Application identifier, leave empty to inherit instance identifier]:id' \
|
'--id[Reverse-DNS style Application identifier, leave empty to inherit instance identifier]:id' \
|
||||||
'-a[Application identity]: :_numbers' \
|
'-a[Application identity]: :_numbers' \
|
||||||
@@ -57,9 +57,9 @@ __hakurei_instances() {
|
|||||||
{
|
{
|
||||||
local -a _hakurei_cmds
|
local -a _hakurei_cmds
|
||||||
_hakurei_cmds=(
|
_hakurei_cmds=(
|
||||||
"app:Load and start container from configuration file"
|
"run:Load and start container from configuration file"
|
||||||
"run:Configure and start a permissive container"
|
"exec:Configure and start a permissive container"
|
||||||
"show:Show live or local app configuration"
|
"show:Show live or local instance configuration"
|
||||||
"ps:List active instances"
|
"ps:List active instances"
|
||||||
"version:Display version information"
|
"version:Display version information"
|
||||||
"license:Show full license text"
|
"license:Show full license text"
|
||||||
|
|||||||
@@ -2,29 +2,32 @@
|
|||||||
package netlink
|
package netlink
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AF_NETLINK socket is never shared
|
// net/netlink/af_netlink.c
|
||||||
var (
|
const maxRecvmsgLen = 32768
|
||||||
nlPid uint32
|
|
||||||
nlPidOnce sync.Once
|
const (
|
||||||
|
// stateOpen denotes an open conn.
|
||||||
|
stateOpen uint32 = 1 << iota
|
||||||
)
|
)
|
||||||
|
|
||||||
// getpid returns a cached pid value.
|
// A Conn represents resources associated to a netlink socket.
|
||||||
func getpid() uint32 {
|
type Conn struct {
|
||||||
nlPidOnce.Do(func() { nlPid = uint32(os.Getpid()) })
|
|
||||||
return nlPid
|
|
||||||
}
|
|
||||||
|
|
||||||
// A conn represents resources associated to a netlink socket.
|
|
||||||
type conn struct {
|
|
||||||
// AF_NETLINK socket.
|
// AF_NETLINK socket.
|
||||||
fd int
|
f *os.File
|
||||||
|
// For using runtime polling via f.
|
||||||
|
raw syscall.RawConn
|
||||||
|
// Port ID assigned by the kernel.
|
||||||
|
port uint32
|
||||||
|
// Internal connection status.
|
||||||
|
state uint32
|
||||||
// Kernel module or netlink group to communicate with.
|
// Kernel module or netlink group to communicate with.
|
||||||
family int
|
family int
|
||||||
// Message sequence number.
|
// Message sequence number.
|
||||||
@@ -33,40 +36,155 @@ type conn struct {
|
|||||||
typ, flags uint16
|
typ, flags uint16
|
||||||
// Outgoing position in buf.
|
// Outgoing position in buf.
|
||||||
pos int
|
pos int
|
||||||
// A page holding incoming and outgoing messages.
|
// Pages holding incoming and outgoing messages.
|
||||||
buf []byte
|
buf [maxRecvmsgLen]byte
|
||||||
|
// An instant some time after conn was established, but before the first
|
||||||
|
// I/O operation on f through raw. This serves as a cached deadline to
|
||||||
|
// cancel blocking I/O.
|
||||||
|
t time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// dial returns the address of a newly connected conn of specified family.
|
// Dial returns the address of a newly connected generic netlink connection of
|
||||||
func dial(family int) (*conn, error) {
|
// specified family and groups.
|
||||||
var c conn
|
func Dial(family int, groups uint32) (*Conn, error) {
|
||||||
|
var c Conn
|
||||||
if fd, err := syscall.Socket(
|
if fd, err := syscall.Socket(
|
||||||
syscall.AF_NETLINK,
|
syscall.AF_NETLINK,
|
||||||
syscall.SOCK_RAW|syscall.SOCK_CLOEXEC,
|
syscall.SOCK_RAW|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC,
|
||||||
family,
|
family,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, os.NewSyscallError("socket", err)
|
return nil, os.NewSyscallError("socket", err)
|
||||||
} else if err = syscall.Bind(fd, &syscall.SockaddrNetlink{
|
} else if err = syscall.Bind(fd, &syscall.SockaddrNetlink{
|
||||||
Family: syscall.AF_NETLINK,
|
Family: syscall.AF_NETLINK,
|
||||||
Pid: getpid(),
|
Groups: groups,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
_ = syscall.Close(fd)
|
_ = syscall.Close(fd)
|
||||||
return nil, os.NewSyscallError("bind", err)
|
return nil, os.NewSyscallError("bind", err)
|
||||||
} else {
|
} else {
|
||||||
c.fd, c.family = fd, family
|
var addr syscall.Sockaddr
|
||||||
|
if addr, err = syscall.Getsockname(fd); err != nil {
|
||||||
|
_ = syscall.Close(fd)
|
||||||
|
return nil, os.NewSyscallError("getsockname", err)
|
||||||
|
}
|
||||||
|
switch a := addr.(type) {
|
||||||
|
case *syscall.SockaddrNetlink:
|
||||||
|
c.port = a.Pid
|
||||||
|
|
||||||
|
default: // unreachable
|
||||||
|
_ = syscall.Close(fd)
|
||||||
|
return nil, syscall.ENOTRECOVERABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
c.family = family
|
||||||
|
c.f = os.NewFile(uintptr(fd), "netlink")
|
||||||
|
if c.raw, err = c.f.SyscallConn(); err != nil {
|
||||||
|
_ = c.f.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.state |= stateOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
c.pos = syscall.NLMSG_HDRLEN
|
c.pos = syscall.NLMSG_HDRLEN
|
||||||
c.buf = make([]byte, os.Getpagesize())
|
c.t = time.Now().UTC()
|
||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ok returns whether conn is still open.
|
||||||
|
func (c *Conn) ok() bool { return c.state&stateOpen != 0 }
|
||||||
|
|
||||||
// Close closes the underlying socket.
|
// Close closes the underlying socket.
|
||||||
func (c *conn) Close() error {
|
func (c *Conn) Close() error {
|
||||||
if c.buf == nil {
|
if !c.ok() {
|
||||||
return syscall.EINVAL
|
return syscall.EINVAL
|
||||||
}
|
}
|
||||||
c.buf = nil
|
c.state &= ^stateOpen
|
||||||
return syscall.Close(c.fd)
|
return c.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recvfrom 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(
|
||||||
|
ctx context.Context,
|
||||||
|
flags int,
|
||||||
|
) (data []byte, from syscall.Sockaddr, err error) {
|
||||||
|
if err = c.f.SetReadDeadline(time.Time{}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
data = c.buf[:]
|
||||||
|
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)
|
||||||
|
return err != syscall.EWOULDBLOCK
|
||||||
|
})
|
||||||
|
if n >= 0 {
|
||||||
|
data = data[:n]
|
||||||
|
}
|
||||||
|
done <- rcErr
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case rcErr := <-done:
|
||||||
|
if err != nil {
|
||||||
|
err = os.NewSyscallError("recvfrom", err)
|
||||||
|
} else {
|
||||||
|
err = rcErr
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
cancelErr := c.f.SetReadDeadline(c.t)
|
||||||
|
<-done
|
||||||
|
if cancelErr != nil {
|
||||||
|
err = cancelErr
|
||||||
|
} else {
|
||||||
|
err = ctx.Err()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sendto wraps send(2) with nonblocking behaviour via the runtime network poller.
|
||||||
|
func (c *Conn) Sendto(
|
||||||
|
ctx context.Context,
|
||||||
|
p []byte,
|
||||||
|
flags int,
|
||||||
|
to syscall.Sockaddr,
|
||||||
|
) (err error) {
|
||||||
|
if err = c.f.SetWriteDeadline(time.Time{}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- c.raw.Write(func(fd uintptr) (done bool) {
|
||||||
|
err = syscall.Sendto(int(fd), p, flags, to)
|
||||||
|
return err != syscall.EWOULDBLOCK
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case rcErr := <-done:
|
||||||
|
if err != nil {
|
||||||
|
err = os.NewSyscallError("sendto", err)
|
||||||
|
} else {
|
||||||
|
err = rcErr
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
cancelErr := c.f.SetWriteDeadline(c.t)
|
||||||
|
<-done
|
||||||
|
if cancelErr != nil {
|
||||||
|
err = cancelErr
|
||||||
|
} else {
|
||||||
|
err = ctx.Err()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Msg is type constraint for types sent over the wire via netlink.
|
// Msg is type constraint for types sent over the wire via netlink.
|
||||||
@@ -88,7 +206,7 @@ func As[M Msg](data []byte) *M {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add queues a value to be sent by conn.
|
// add queues a value to be sent by conn.
|
||||||
func add[M Msg](c *conn, p *M) bool {
|
func add[M Msg](c *Conn, p *M) bool {
|
||||||
pos := c.pos
|
pos := c.pos
|
||||||
c.pos += int(unsafe.Sizeof(*p))
|
c.pos += int(unsafe.Sizeof(*p))
|
||||||
if c.pos > len(c.buf) {
|
if c.pos > len(c.buf) {
|
||||||
@@ -122,8 +240,16 @@ func (e *InconsistentError) Error() string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkReply checks the message header of a reply from the kernel.
|
||||||
|
func (c *Conn) checkReply(header *syscall.NlMsghdr) error {
|
||||||
|
if header.Seq != c.seq || header.Pid != c.port {
|
||||||
|
return &InconsistentError{*header, c.seq, c.port}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// pending returns the valid slice of buf and initialises pos.
|
// pending returns the valid slice of buf and initialises pos.
|
||||||
func (c *conn) pending() []byte {
|
func (c *Conn) pending() []byte {
|
||||||
buf := c.buf[:c.pos]
|
buf := c.buf[:c.pos]
|
||||||
c.pos = syscall.NLMSG_HDRLEN
|
c.pos = syscall.NLMSG_HDRLEN
|
||||||
|
|
||||||
@@ -132,7 +258,7 @@ func (c *conn) pending() []byte {
|
|||||||
Type: c.typ,
|
Type: c.typ,
|
||||||
Flags: c.flags,
|
Flags: c.flags,
|
||||||
Seq: c.seq,
|
Seq: c.seq,
|
||||||
Pid: getpid(),
|
Pid: c.port,
|
||||||
}
|
}
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
@@ -143,44 +269,44 @@ type Complete struct{}
|
|||||||
// Error returns a hardcoded string that should never be displayed to the user.
|
// Error returns a hardcoded string that should never be displayed to the user.
|
||||||
func (Complete) Error() string { return "returning from roundtrip" }
|
func (Complete) Error() string { return "returning from roundtrip" }
|
||||||
|
|
||||||
|
// HandlerFunc handles [syscall.NetlinkMessage] and returns a non-nil error to
|
||||||
|
// discontinue the receiving of more messages.
|
||||||
|
type HandlerFunc func(resp []syscall.NetlinkMessage) error
|
||||||
|
|
||||||
|
// receive receives from a socket with specified flags until a non-nil error is
|
||||||
|
// returned by f. An error of type [Complete] is returned as nil.
|
||||||
|
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 {
|
||||||
|
return err
|
||||||
|
} else if len(data) < syscall.NLMSG_HDRLEN {
|
||||||
|
return syscall.EBADE
|
||||||
|
} else if resp, err = syscall.ParseNetlinkMessage(data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f(resp); err != nil {
|
||||||
|
if err == (Complete{}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Roundtrip sends the pending message and handles the reply.
|
// Roundtrip sends the pending message and handles the reply.
|
||||||
func (c *conn) Roundtrip(f func(msg *syscall.NetlinkMessage) error) error {
|
func (c *Conn) Roundtrip(ctx context.Context, f HandlerFunc) error {
|
||||||
if c.buf == nil {
|
if !c.ok() {
|
||||||
return syscall.EINVAL
|
return syscall.EINVAL
|
||||||
}
|
}
|
||||||
defer func() { c.seq++ }()
|
defer func() { c.seq++ }()
|
||||||
|
|
||||||
if err := syscall.Sendto(c.fd, c.pending(), 0, &syscall.SockaddrNetlink{
|
if err := c.Sendto(ctx, c.pending(), 0, &syscall.SockaddrNetlink{
|
||||||
Family: syscall.AF_NETLINK,
|
Family: syscall.AF_NETLINK,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return os.NewSyscallError("sendto", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
return c.receive(ctx, f, 0)
|
||||||
buf := c.buf
|
|
||||||
if n, _, err := syscall.Recvfrom(c.fd, buf, 0); err != nil {
|
|
||||||
return os.NewSyscallError("recvfrom", err)
|
|
||||||
} else if n < syscall.NLMSG_HDRLEN {
|
|
||||||
return syscall.EBADE
|
|
||||||
} else {
|
|
||||||
buf = buf[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
msgs, err := syscall.ParseNetlinkMessage(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, msg := range msgs {
|
|
||||||
if msg.Header.Seq != c.seq || msg.Header.Pid != getpid() {
|
|
||||||
return &InconsistentError{msg.Header, c.seq, getpid()}
|
|
||||||
}
|
|
||||||
if err = f(&msg); err != nil {
|
|
||||||
if err == (Complete{}) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
package netlink
|
package netlink
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { nlPidOnce.Do(func() {}); nlPid = 1 }
|
|
||||||
|
|
||||||
type payloadTestCase struct {
|
type payloadTestCase struct {
|
||||||
name string
|
name string
|
||||||
f func(c *conn)
|
f func(c *Conn)
|
||||||
want []byte
|
want []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,11 +19,9 @@ func checkPayload(t *testing.T, testCases []payloadTestCase) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
c := conn{
|
c := Conn{port: 1, pos: syscall.NLMSG_HDRLEN}
|
||||||
pos: syscall.NLMSG_HDRLEN,
|
|
||||||
buf: make([]byte, os.Getpagesize()),
|
|
||||||
}
|
|
||||||
tc.f(&c)
|
tc.f(&c)
|
||||||
if got := c.pending(); string(got) != string(tc.want) {
|
if got := c.pending(); string(got) != string(tc.want) {
|
||||||
t.Errorf("pending: %#v, want %#v", got, tc.want)
|
t.Errorf("pending: %#v, want %#v", got, tc.want)
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
package netlink
|
package netlink
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RouteConn represents a NETLINK_ROUTE socket.
|
// RouteConn represents a NETLINK_ROUTE socket.
|
||||||
type RouteConn struct{ *conn }
|
type RouteConn struct{ conn *Conn }
|
||||||
|
|
||||||
|
// Close closes the underlying socket.
|
||||||
|
func (c *RouteConn) Close() error { return c.conn.Close() }
|
||||||
|
|
||||||
// DialRoute returns the address of a newly connected [RouteConn].
|
// DialRoute returns the address of a newly connected [RouteConn].
|
||||||
func DialRoute() (*RouteConn, error) {
|
func DialRoute() (*RouteConn, error) {
|
||||||
c, err := dial(syscall.NETLINK_ROUTE)
|
c, err := Dial(syscall.NETLINK_ROUTE, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -18,23 +22,27 @@ func DialRoute() (*RouteConn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// rtnlConsume consumes a message from rtnetlink.
|
// rtnlConsume consumes a message from rtnetlink.
|
||||||
func rtnlConsume(msg *syscall.NetlinkMessage) error {
|
func (c *RouteConn) rtnlConsume(resp []syscall.NetlinkMessage) error {
|
||||||
switch msg.Header.Type {
|
for i := range resp {
|
||||||
case syscall.NLMSG_DONE:
|
if err := c.conn.checkReply(&resp[i].Header); err != nil {
|
||||||
return Complete{}
|
return err
|
||||||
|
|
||||||
case syscall.NLMSG_ERROR:
|
|
||||||
if e := As[syscall.NlMsgerr](msg.Data); e != nil {
|
|
||||||
if e.Error == 0 {
|
|
||||||
return Complete{}
|
|
||||||
}
|
|
||||||
return syscall.Errno(-e.Error)
|
|
||||||
}
|
}
|
||||||
return syscall.EBADE
|
|
||||||
|
|
||||||
default:
|
switch resp[i].Header.Type {
|
||||||
return nil
|
case syscall.NLMSG_DONE:
|
||||||
|
return Complete{}
|
||||||
|
|
||||||
|
case syscall.NLMSG_ERROR:
|
||||||
|
if e := As[syscall.NlMsgerr](resp[i].Data); e != nil {
|
||||||
|
if e.Error == 0 {
|
||||||
|
return Complete{}
|
||||||
|
}
|
||||||
|
return syscall.Errno(-e.Error)
|
||||||
|
}
|
||||||
|
return syscall.EBADE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InAddr is equivalent to struct in_addr.
|
// InAddr is equivalent to struct in_addr.
|
||||||
@@ -57,7 +65,7 @@ func (c *RouteConn) writeIfAddrmsg(
|
|||||||
msg *syscall.IfAddrmsg,
|
msg *syscall.IfAddrmsg,
|
||||||
attrs ...RtAttrMsg[InAddr],
|
attrs ...RtAttrMsg[InAddr],
|
||||||
) bool {
|
) bool {
|
||||||
c.typ, c.flags = typ, syscall.NLM_F_REQUEST|syscall.NLM_F_ACK|flags
|
c.conn.typ, c.conn.flags = typ, syscall.NLM_F_REQUEST|syscall.NLM_F_ACK|flags
|
||||||
if !add(c.conn, msg) {
|
if !add(c.conn, msg) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -72,6 +80,7 @@ func (c *RouteConn) writeIfAddrmsg(
|
|||||||
|
|
||||||
// SendIfAddrmsg sends an ifaddrmsg structure to rtnetlink.
|
// SendIfAddrmsg sends an ifaddrmsg structure to rtnetlink.
|
||||||
func (c *RouteConn) SendIfAddrmsg(
|
func (c *RouteConn) SendIfAddrmsg(
|
||||||
|
ctx context.Context,
|
||||||
typ, flags uint16,
|
typ, flags uint16,
|
||||||
msg *syscall.IfAddrmsg,
|
msg *syscall.IfAddrmsg,
|
||||||
attrs ...RtAttrMsg[InAddr],
|
attrs ...RtAttrMsg[InAddr],
|
||||||
@@ -79,7 +88,7 @@ func (c *RouteConn) SendIfAddrmsg(
|
|||||||
if !c.writeIfAddrmsg(typ, flags, msg, attrs...) {
|
if !c.writeIfAddrmsg(typ, flags, msg, attrs...) {
|
||||||
return syscall.ENOMEM
|
return syscall.ENOMEM
|
||||||
}
|
}
|
||||||
return c.Roundtrip(rtnlConsume)
|
return c.conn.Roundtrip(ctx, c.rtnlConsume)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeNewaddrLo writes a RTM_NEWADDR message for the loopback address.
|
// writeNewaddrLo writes a RTM_NEWADDR message for the loopback address.
|
||||||
@@ -104,11 +113,11 @@ func (c *RouteConn) writeNewaddrLo(lo uint32) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendNewaddrLo sends a RTM_NEWADDR message for the loopback address to the kernel.
|
// SendNewaddrLo sends a RTM_NEWADDR message for the loopback address to the kernel.
|
||||||
func (c *RouteConn) SendNewaddrLo(lo uint32) error {
|
func (c *RouteConn) SendNewaddrLo(ctx context.Context, lo uint32) error {
|
||||||
if !c.writeNewaddrLo(lo) {
|
if !c.writeNewaddrLo(lo) {
|
||||||
return syscall.ENOMEM
|
return syscall.ENOMEM
|
||||||
}
|
}
|
||||||
return c.Roundtrip(rtnlConsume)
|
return c.conn.Roundtrip(ctx, c.rtnlConsume)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeIfInfomsg writes an ifinfomsg structure to conn.
|
// writeIfInfomsg writes an ifinfomsg structure to conn.
|
||||||
@@ -116,17 +125,18 @@ func (c *RouteConn) writeIfInfomsg(
|
|||||||
typ, flags uint16,
|
typ, flags uint16,
|
||||||
msg *syscall.IfInfomsg,
|
msg *syscall.IfInfomsg,
|
||||||
) bool {
|
) bool {
|
||||||
c.typ, c.flags = typ, syscall.NLM_F_REQUEST|syscall.NLM_F_ACK|flags
|
c.conn.typ, c.conn.flags = typ, syscall.NLM_F_REQUEST|syscall.NLM_F_ACK|flags
|
||||||
return add(c.conn, msg)
|
return add(c.conn, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendIfInfomsg sends an ifinfomsg structure to rtnetlink.
|
// SendIfInfomsg sends an ifinfomsg structure to rtnetlink.
|
||||||
func (c *RouteConn) SendIfInfomsg(
|
func (c *RouteConn) SendIfInfomsg(
|
||||||
|
ctx context.Context,
|
||||||
typ, flags uint16,
|
typ, flags uint16,
|
||||||
msg *syscall.IfInfomsg,
|
msg *syscall.IfInfomsg,
|
||||||
) error {
|
) error {
|
||||||
if !c.writeIfInfomsg(typ, flags, msg) {
|
if !c.writeIfInfomsg(typ, flags, msg) {
|
||||||
return syscall.ENOMEM
|
return syscall.ENOMEM
|
||||||
}
|
}
|
||||||
return c.Roundtrip(rtnlConsume)
|
return c.conn.Roundtrip(ctx, c.rtnlConsume)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ func TestPayloadRTNETLINK(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
checkPayload(t, []payloadTestCase{
|
checkPayload(t, []payloadTestCase{
|
||||||
{"RTM_NEWADDR lo", func(c *conn) {
|
{"RTM_NEWADDR lo", func(c *Conn) {
|
||||||
(&RouteConn{c}).writeNewaddrLo(1)
|
(&RouteConn{c}).writeNewaddrLo(1)
|
||||||
}, []byte{
|
}, []byte{
|
||||||
/* Len */ 0x28, 0, 0, 0,
|
/* Len */ 0x28, 0, 0, 0,
|
||||||
@@ -33,7 +33,7 @@ func TestPayloadRTNETLINK(t *testing.T) {
|
|||||||
/* in_addr */ 127, 0, 0, 1,
|
/* in_addr */ 127, 0, 0, 1,
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"RTM_NEWLINK", func(c *conn) {
|
{"RTM_NEWLINK", func(c *Conn) {
|
||||||
c.seq++
|
c.seq++
|
||||||
(&RouteConn{c}).writeIfInfomsg(
|
(&RouteConn{c}).writeIfInfomsg(
|
||||||
syscall.RTM_NEWLINK, 0,
|
syscall.RTM_NEWLINK, 0,
|
||||||
|
|||||||
@@ -40,14 +40,17 @@ type ExecPath struct {
|
|||||||
W bool
|
W bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSchedIdle is whether to set [std.SCHED_IDLE] scheduling priority.
|
// SetSchedIdle is whether to set [ext.SCHED_IDLE] scheduling priority.
|
||||||
var SetSchedIdle bool
|
var SetSchedIdle bool
|
||||||
|
|
||||||
|
// GetArtifactFunc is the function signature of [FContext.GetArtifact].
|
||||||
|
type GetArtifactFunc func(Artifact) (*check.Absolute, unique.Handle[Checksum])
|
||||||
|
|
||||||
// PromoteLayers returns artifacts with identical-by-content layers promoted to
|
// PromoteLayers returns artifacts with identical-by-content layers promoted to
|
||||||
// the highest priority instance, as if mounted via [ExecPath].
|
// the highest priority instance, as if mounted via [ExecPath].
|
||||||
func PromoteLayers(
|
func PromoteLayers(
|
||||||
artifacts []Artifact,
|
artifacts []Artifact,
|
||||||
getArtifact func(Artifact) (*check.Absolute, unique.Handle[Checksum]),
|
getArtifact GetArtifactFunc,
|
||||||
report func(i int, d Artifact),
|
report func(i int, d Artifact),
|
||||||
) []*check.Absolute {
|
) []*check.Absolute {
|
||||||
layers := make([]*check.Absolute, 0, len(artifacts))
|
layers := make([]*check.Absolute, 0, len(artifacts))
|
||||||
@@ -67,14 +70,14 @@ func PromoteLayers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// layers returns pathnames collected from A deduplicated via [PromoteLayers].
|
// layers returns pathnames collected from A deduplicated via [PromoteLayers].
|
||||||
func (p *ExecPath) layers(f *FContext) []*check.Absolute {
|
func (p *ExecPath) layers(
|
||||||
msg := f.GetMessage()
|
msg message.Msg,
|
||||||
return PromoteLayers(p.A, f.GetArtifact, func(i int, d Artifact) {
|
getArtifact GetArtifactFunc,
|
||||||
|
ident func(a Artifact) unique.Handle[ID],
|
||||||
|
) []*check.Absolute {
|
||||||
|
return PromoteLayers(p.A, getArtifact, func(i int, d Artifact) {
|
||||||
if msg.IsVerbose() {
|
if msg.IsVerbose() {
|
||||||
msg.Verbosef(
|
msg.Verbosef("promoted layer %d as %s", i, reportName(d, ident(d)))
|
||||||
"promoted layer %d as %s",
|
|
||||||
i, reportName(d, f.cache.Ident(d)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -382,17 +385,30 @@ func scanVerbose(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidPaths is returned for an [Artifact] of [KindExec] or
|
||||||
|
// [KindExecNet] specified with invalid paths.
|
||||||
|
ErrInvalidPaths = errors.New("invalid mount point")
|
||||||
|
)
|
||||||
|
|
||||||
// SeccompPresets is the [seccomp] presets used by exec artifacts.
|
// SeccompPresets is the [seccomp] presets used by exec artifacts.
|
||||||
const SeccompPresets = std.PresetStrict &
|
const SeccompPresets = std.PresetStrict &
|
||||||
^(std.PresetDenyNS | std.PresetDenyDevel)
|
^(std.PresetDenyNS | std.PresetDenyDevel)
|
||||||
|
|
||||||
// cure is like Cure but allows optional host net namespace. This is used for
|
// makeContainer sets up the specified temp and work directories and returns the
|
||||||
// the [KnownChecksum] variant where networking is allowed.
|
// corresponding [container.Container] that would have run for cure.
|
||||||
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
func (a *execArtifact) makeContainer(
|
||||||
|
ctx context.Context,
|
||||||
|
msg message.Msg,
|
||||||
|
hostNet bool,
|
||||||
|
temp, work *check.Absolute,
|
||||||
|
getArtifact GetArtifactFunc,
|
||||||
|
ident func(a Artifact) unique.Handle[ID],
|
||||||
|
) (z *container.Container, err error) {
|
||||||
overlayWorkIndex := -1
|
overlayWorkIndex := -1
|
||||||
for i, p := range a.paths {
|
for i, p := range a.paths {
|
||||||
if p.P == nil || len(p.A) == 0 {
|
if p.P == nil || len(p.A) == 0 {
|
||||||
return os.ErrInvalid
|
return nil, ErrInvalidPaths
|
||||||
}
|
}
|
||||||
if p.P.Is(AbsWork) {
|
if p.P.Is(AbsWork) {
|
||||||
overlayWorkIndex = i
|
overlayWorkIndex = i
|
||||||
@@ -404,10 +420,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
artifactCount += len(p.A)
|
artifactCount += len(p.A)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(f.Unwrap(), a.timeout)
|
z = container.New(ctx, msg)
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
z := container.New(ctx, f.GetMessage())
|
|
||||||
z.WaitDelay = execWaitDelay
|
z.WaitDelay = execWaitDelay
|
||||||
z.SeccompPresets = SeccompPresets
|
z.SeccompPresets = SeccompPresets
|
||||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||||
@@ -421,12 +434,183 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
}
|
}
|
||||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
||||||
|
|
||||||
|
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
|
||||||
|
z.Grow(len(a.paths) + 4)
|
||||||
|
|
||||||
|
for i, b := range a.paths {
|
||||||
|
if i == overlayWorkIndex {
|
||||||
|
if err = os.MkdirAll(work.String(), 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tempWork := temp.Append(".work")
|
||||||
|
if err = os.MkdirAll(tempWork.String(), 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Overlay(
|
||||||
|
AbsWork,
|
||||||
|
work,
|
||||||
|
tempWork,
|
||||||
|
b.layers(msg, getArtifact, ident)...,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.paths[i].W {
|
||||||
|
tempUpper, tempWork := temp.Append(
|
||||||
|
".upper", strconv.Itoa(i),
|
||||||
|
), temp.Append(
|
||||||
|
".work", strconv.Itoa(i),
|
||||||
|
)
|
||||||
|
if err = os.MkdirAll(tempUpper.String(), 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = os.MkdirAll(tempWork.String(), 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Overlay(b.P, tempUpper, tempWork, b.layers(msg, getArtifact, ident)...)
|
||||||
|
} else if len(b.A) == 1 {
|
||||||
|
pathname, _ := getArtifact(b.A[0])
|
||||||
|
z.Bind(pathname, b.P, 0)
|
||||||
|
} else {
|
||||||
|
z.OverlayReadonly(b.P, b.layers(msg, getArtifact, ident)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if overlayWorkIndex < 0 {
|
||||||
|
z.Bind(
|
||||||
|
work,
|
||||||
|
AbsWork,
|
||||||
|
std.BindWritable|std.BindEnsure,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
z.Bind(
|
||||||
|
temp,
|
||||||
|
fhs.AbsTmp,
|
||||||
|
std.BindWritable|std.BindEnsure,
|
||||||
|
)
|
||||||
|
z.Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrExecBusy is returned entering [Cache.EnterExec] while another
|
||||||
|
// goroutine has not yet returned from it.
|
||||||
|
ErrExecBusy = errors.New("scratch directories in use")
|
||||||
|
// ErrNotExec is returned for unsupported implementations of [Artifact]
|
||||||
|
// passed to [Cache.EnterExec].
|
||||||
|
ErrNotExec = errors.New("attempting to run a non-exec artifact")
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnterExec runs the container of an [Artifact] of [KindExec] or [KindExecNet]
|
||||||
|
// with its entry point, argument, and standard streams replaced with values
|
||||||
|
// supplied by the caller.
|
||||||
|
func (c *Cache) EnterExec(
|
||||||
|
ctx context.Context,
|
||||||
|
a Artifact,
|
||||||
|
retainSession bool,
|
||||||
|
stdin io.Reader,
|
||||||
|
stdout, stderr io.Writer,
|
||||||
|
path *check.Absolute,
|
||||||
|
args ...string,
|
||||||
|
) (err error) {
|
||||||
|
if !c.inExec.CompareAndSwap(false, true) {
|
||||||
|
return ErrExecBusy
|
||||||
|
}
|
||||||
|
defer c.inExec.Store(false)
|
||||||
|
|
||||||
|
var hostNet bool
|
||||||
|
var e *execArtifact
|
||||||
|
switch f := a.(type) {
|
||||||
|
case *execArtifact:
|
||||||
|
e = f
|
||||||
|
|
||||||
|
case *execNetArtifact:
|
||||||
|
e = &f.execArtifact
|
||||||
|
hostNet = true
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ErrNotExec
|
||||||
|
}
|
||||||
|
|
||||||
|
deps := Collect(a.Dependencies())
|
||||||
|
if _, _, err = c.Cure(&deps); err == nil {
|
||||||
|
return errors.New("unreachable")
|
||||||
|
} else if !IsCollected(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dm := make(map[Artifact]cureRes)
|
||||||
|
for i, p := range deps {
|
||||||
|
var res cureRes
|
||||||
|
res.pathname, res.checksum, err = c.Cure(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dm[deps[i]] = res
|
||||||
|
}
|
||||||
|
|
||||||
|
scratch := c.base.Append(dirExecScratch)
|
||||||
|
temp, work := scratch.Append("temp"), scratch.Append("work")
|
||||||
|
// work created during makeContainer
|
||||||
|
if err = os.MkdirAll(temp.String(), 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if chmodErr, removeErr := removeAll(scratch); chmodErr != nil || removeErr != nil {
|
||||||
|
err = errors.Join(err, chmodErr, removeErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var z *container.Container
|
||||||
|
z, err = e.makeContainer(
|
||||||
|
ctx, c.msg,
|
||||||
|
hostNet,
|
||||||
|
temp, work,
|
||||||
|
func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
|
||||||
|
if res, ok := dm[a]; ok {
|
||||||
|
return res.pathname, res.checksum
|
||||||
|
}
|
||||||
|
panic(InvalidLookupError(c.Ident(a).Value()))
|
||||||
|
},
|
||||||
|
c.Ident,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Stdin, z.Stdout, z.Stderr = stdin, stdout, stderr
|
||||||
|
z.Path, z.Args = path, args
|
||||||
|
z.RetainSession = retainSession
|
||||||
|
|
||||||
|
if err = z.Start(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = z.Serve(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return z.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// cure is like Cure but allows optional host net namespace.
|
||||||
|
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(f.Unwrap(), a.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
msg := f.GetMessage()
|
||||||
|
var z *container.Container
|
||||||
|
if z, err = a.makeContainer(
|
||||||
|
ctx, msg, hostNet,
|
||||||
|
f.GetTempDir(), f.GetWorkDir(),
|
||||||
|
f.GetArtifact,
|
||||||
|
f.cache.Ident,
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var status io.Writer
|
var status io.Writer
|
||||||
if status, err = f.GetStatusWriter(); err != nil {
|
if status, err = f.GetStatusWriter(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg := f.GetMessage(); msg.IsVerbose() {
|
if msg.IsVerbose() {
|
||||||
var stdout, stderr io.ReadCloser
|
var stdout, stderr io.ReadCloser
|
||||||
if stdout, err = z.StdoutPipe(); err != nil {
|
if stdout, err = z.StdoutPipe(); err != nil {
|
||||||
return
|
return
|
||||||
@@ -464,62 +648,6 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
z.Stdout, z.Stderr = status, status
|
z.Stdout, z.Stderr = status, status
|
||||||
}
|
}
|
||||||
|
|
||||||
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
|
|
||||||
z.Grow(len(a.paths) + 4)
|
|
||||||
|
|
||||||
temp, work := f.GetTempDir(), f.GetWorkDir()
|
|
||||||
for i, b := range a.paths {
|
|
||||||
if i == overlayWorkIndex {
|
|
||||||
if err = os.MkdirAll(work.String(), 0700); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tempWork := temp.Append(".work")
|
|
||||||
if err = os.MkdirAll(tempWork.String(), 0700); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
z.Overlay(
|
|
||||||
AbsWork,
|
|
||||||
work,
|
|
||||||
tempWork,
|
|
||||||
b.layers(f)...,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.paths[i].W {
|
|
||||||
tempUpper, tempWork := temp.Append(
|
|
||||||
".upper", strconv.Itoa(i),
|
|
||||||
), temp.Append(
|
|
||||||
".work", strconv.Itoa(i),
|
|
||||||
)
|
|
||||||
if err = os.MkdirAll(tempUpper.String(), 0700); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = os.MkdirAll(tempWork.String(), 0700); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
z.Overlay(b.P, tempUpper, tempWork, b.layers(f)...)
|
|
||||||
} else if len(b.A) == 1 {
|
|
||||||
pathname, _ := f.GetArtifact(b.A[0])
|
|
||||||
z.Bind(pathname, b.P, 0)
|
|
||||||
} else {
|
|
||||||
z.OverlayReadonly(b.P, b.layers(f)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if overlayWorkIndex < 0 {
|
|
||||||
z.Bind(
|
|
||||||
work,
|
|
||||||
AbsWork,
|
|
||||||
std.BindWritable|std.BindEnsure,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
z.Bind(
|
|
||||||
f.GetTempDir(),
|
|
||||||
fhs.AbsTmp,
|
|
||||||
std.BindWritable|std.BindEnsure,
|
|
||||||
)
|
|
||||||
z.Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
|
|
||||||
|
|
||||||
if err = z.Start(); err != nil {
|
if err = z.Start(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -532,7 +660,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
|
|
||||||
// do not allow empty directories to succeed
|
// do not allow empty directories to succeed
|
||||||
for {
|
for {
|
||||||
err = syscall.Rmdir(work.String())
|
err = syscall.Rmdir(f.GetWorkDir().String())
|
||||||
if err != syscall.EINTR {
|
if err != syscall.EINTR {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func TestExec(t *testing.T) {
|
|||||||
[]string{"testtool"},
|
[]string{"testtool"},
|
||||||
|
|
||||||
pkg.ExecPath{},
|
pkg.ExecPath{},
|
||||||
), nil, pkg.Checksum{}, os.ErrInvalid},
|
), nil, pkg.Checksum{}, pkg.ErrInvalidPaths},
|
||||||
})
|
})
|
||||||
|
|
||||||
// check init failure passthrough
|
// check init failure passthrough
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"unique"
|
"unique"
|
||||||
@@ -366,7 +367,7 @@ type TrivialArtifact interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// KnownIdent is optionally implemented by [Artifact] and is used instead of
|
// KnownIdent is optionally implemented by [Artifact] and is used instead of
|
||||||
// [Kind.Ident] when it is available.
|
// [Cache.Ident] when it is available.
|
||||||
//
|
//
|
||||||
// This is very subtle to use correctly. The implementation must ensure that
|
// This is very subtle to use correctly. The implementation must ensure that
|
||||||
// this value is globally unique, otherwise [Cache] can enter an inconsistent
|
// this value is globally unique, otherwise [Cache] can enter an inconsistent
|
||||||
@@ -439,6 +440,11 @@ const (
|
|||||||
KindCustomOffset = 1 << 31
|
KindCustomOffset = 1 << 31
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// kindCollection is the kind of [Collect]. It never cures successfully.
|
||||||
|
kindCollection Kind = KindCustomOffset - 1 - iota
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// fileLock is the file name appended to Cache.base for guaranteeing
|
// fileLock is the file name appended to Cache.base for guaranteeing
|
||||||
// exclusive access to the cache directory.
|
// exclusive access to the cache directory.
|
||||||
@@ -461,6 +467,11 @@ const (
|
|||||||
// pathnames allocated during [Cache.Cure].
|
// pathnames allocated during [Cache.Cure].
|
||||||
dirTemp = "temp"
|
dirTemp = "temp"
|
||||||
|
|
||||||
|
// dirExecScratch is the directory name appended to Cache.base for scratch
|
||||||
|
// space setting up the container started by [Cache.EnterExec]. Exclusivity
|
||||||
|
// via Cache.inExec.
|
||||||
|
dirExecScratch = "scratch"
|
||||||
|
|
||||||
// checksumLinknamePrefix is prepended to the encoded [Checksum] value
|
// checksumLinknamePrefix is prepended to the encoded [Checksum] value
|
||||||
// of an [Artifact] when creating a symbolic link to dirChecksum.
|
// of an [Artifact] when creating a symbolic link to dirChecksum.
|
||||||
checksumLinknamePrefix = "../" + dirChecksum + "/"
|
checksumLinknamePrefix = "../" + dirChecksum + "/"
|
||||||
@@ -476,7 +487,7 @@ type cureRes struct {
|
|||||||
// subject to the cures limit. Values pointed to by result addresses are safe
|
// subject to the cures limit. Values pointed to by result addresses are safe
|
||||||
// to access after the [sync.WaitGroup] associated with this pendingArtifactDep
|
// to access after the [sync.WaitGroup] associated with this pendingArtifactDep
|
||||||
// is done. pendingArtifactDep must not be reused or modified after it is sent
|
// is done. pendingArtifactDep must not be reused or modified after it is sent
|
||||||
// to Cache.cureDep.
|
// to cure.
|
||||||
type pendingArtifactDep struct {
|
type pendingArtifactDep struct {
|
||||||
// Dependency artifact populated during [Cache.Cure].
|
// Dependency artifact populated during [Cache.Cure].
|
||||||
a Artifact
|
a Artifact
|
||||||
@@ -548,6 +559,9 @@ type Cache struct {
|
|||||||
unlock func()
|
unlock func()
|
||||||
// Synchronises calls to Close.
|
// Synchronises calls to Close.
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
|
|
||||||
|
// Whether EnterExec has not yet returned.
|
||||||
|
inExec atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsStrict returns whether the [Cache] strictly verifies checksums.
|
// IsStrict returns whether the [Cache] strictly verifies checksums.
|
||||||
@@ -1890,3 +1904,33 @@ func open(
|
|||||||
|
|
||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collected is returned by [Collect.Cure] to indicate a successful collection.
|
||||||
|
type Collected struct{}
|
||||||
|
|
||||||
|
// Error returns a constant string to satisfy error, but should never be seen
|
||||||
|
// by the user.
|
||||||
|
func (Collected) Error() string { return "artifacts successfully collected" }
|
||||||
|
|
||||||
|
// IsCollected returns whether the underlying error contains that of the result
|
||||||
|
// of curing a [Collect] helper.
|
||||||
|
func IsCollected(err error) bool { return errors.As(err, new(Collected)) }
|
||||||
|
|
||||||
|
// Collect implements [pkg.FloodArtifact] to concurrently cure multiple
|
||||||
|
// [pkg.Artifact]. It returns [Collected].
|
||||||
|
type Collect []Artifact
|
||||||
|
|
||||||
|
// Cure returns [Collected].
|
||||||
|
func (*Collect) Cure(*FContext) error { return Collected{} }
|
||||||
|
|
||||||
|
// Kind returns the hardcoded [pkg.Kind] value.
|
||||||
|
func (*Collect) Kind() Kind { return kindCollection }
|
||||||
|
|
||||||
|
// Params is a noop: dependencies are already represented in the header.
|
||||||
|
func (*Collect) Params(*IContext) {}
|
||||||
|
|
||||||
|
// Dependencies returns [Collect] as is.
|
||||||
|
func (c *Collect) Dependencies() []Artifact { return *c }
|
||||||
|
|
||||||
|
// IsExclusive returns false: Cure is a noop.
|
||||||
|
func (*Collect) IsExclusive() bool { return false }
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func (t Toolchain) newAttr() (pkg.Artifact, string) {
|
|||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
Patches: [][2]string{
|
Patches: []KV{
|
||||||
{"libgen-basename", `From 8a80d895dfd779373363c3a4b62ecce5a549efb2 Mon Sep 17 00:00:00 2001
|
{"libgen-basename", `From 8a80d895dfd779373363c3a4b62ecce5a549efb2 Mon Sep 17 00:00:00 2001
|
||||||
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
|
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
|
||||||
Date: Sat, 30 Mar 2024 10:17:10 +0100
|
Date: Sat, 30 Mar 2024 10:17:10 +0100
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
func (t Toolchain) newCMake() (pkg.Artifact, string) {
|
func (t Toolchain) newCMake() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "4.2.3"
|
version = "4.3.0"
|
||||||
checksum = "Y4uYGnLrDQX78UdzH7fMzfok46Nt_1taDIHSmqgboU1yFi6f0iAXBDegMCu4eS-J"
|
checksum = "amBtnY2eGsEdlrB-cTRuOESBTsIqtyaxWlEKNlnp2EWLwAKWINjssilo4KXE6El9"
|
||||||
)
|
)
|
||||||
return t.NewPackage("cmake", version, pkg.NewHTTPGetTar(
|
return t.NewPackage("cmake", version, pkg.NewHTTPGetTar(
|
||||||
nil, "https://github.com/Kitware/CMake/releases/download/"+
|
nil, "https://github.com/Kitware/CMake/releases/download/"+
|
||||||
@@ -25,7 +25,7 @@ func (t Toolchain) newCMake() (pkg.Artifact, string) {
|
|||||||
// expected to be writable in the copy made during bootstrap
|
// expected to be writable in the copy made during bootstrap
|
||||||
Chmod: true,
|
Chmod: true,
|
||||||
|
|
||||||
Patches: [][2]string{
|
Patches: []KV{
|
||||||
{"bootstrap-test-no-openssl", `diff --git a/Tests/BootstrapTest.cmake b/Tests/BootstrapTest.cmake
|
{"bootstrap-test-no-openssl", `diff --git a/Tests/BootstrapTest.cmake b/Tests/BootstrapTest.cmake
|
||||||
index 137de78bc1..b4da52e664 100644
|
index 137de78bc1..b4da52e664 100644
|
||||||
--- a/Tests/BootstrapTest.cmake
|
--- a/Tests/BootstrapTest.cmake
|
||||||
@@ -88,7 +88,7 @@ index 2ead810437..f85cbb8b1c 100644
|
|||||||
OmitDefaults: true,
|
OmitDefaults: true,
|
||||||
|
|
||||||
ConfigureName: "/usr/src/cmake/bootstrap",
|
ConfigureName: "/usr/src/cmake/bootstrap",
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"prefix", "/system"},
|
{"prefix", "/system"},
|
||||||
{"parallel", `"$(nproc)"`},
|
{"parallel", `"$(nproc)"`},
|
||||||
{"--"},
|
{"--"},
|
||||||
@@ -125,7 +125,7 @@ type CMakeHelper struct {
|
|||||||
Append []string
|
Append []string
|
||||||
|
|
||||||
// CMake CACHE entries.
|
// CMake CACHE entries.
|
||||||
Cache [][2]string
|
Cache []KV
|
||||||
// Runs after install.
|
// Runs after install.
|
||||||
Script string
|
Script string
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ func (*CMakeHelper) wantsDir() string { return "/cure/" }
|
|||||||
func (attr *CMakeHelper) script(name string) string {
|
func (attr *CMakeHelper) script(name string) string {
|
||||||
if attr == nil {
|
if attr == nil {
|
||||||
attr = &CMakeHelper{
|
attr = &CMakeHelper{
|
||||||
Cache: [][2]string{
|
Cache: []KV{
|
||||||
{"CMAKE_BUILD_TYPE", "Release"},
|
{"CMAKE_BUILD_TYPE", "Release"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func (t Toolchain) newCurl() (pkg.Artifact, string) {
|
|||||||
chmod +w tests/data && rm tests/data/test459
|
chmod +w tests/data && rm tests/data/test459
|
||||||
`,
|
`,
|
||||||
}, &MakeHelper{
|
}, &MakeHelper{
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"with-openssl"},
|
{"with-openssl"},
|
||||||
{"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"},
|
{"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"},
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func (t Toolchain) newDTC() (pkg.Artifact, string) {
|
|||||||
Writable: true,
|
Writable: true,
|
||||||
Chmod: true,
|
Chmod: true,
|
||||||
}, &MesonHelper{
|
}, &MesonHelper{
|
||||||
Setup: [][2]string{
|
Setup: []KV{
|
||||||
{"Dyaml", "disabled"},
|
{"Dyaml", "disabled"},
|
||||||
{"Dstatic-build", "true"},
|
{"Dstatic-build", "true"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func (t Toolchain) newElfutils() (pkg.Artifact, string) {
|
|||||||
// nonstandard glibc extension
|
// nonstandard glibc extension
|
||||||
SkipCheck: true,
|
SkipCheck: true,
|
||||||
|
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"enable-deterministic-archives"},
|
{"enable-deterministic-archives"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func (a cureEtc) Cure(t *pkg.FContext) (err error) {
|
|||||||
if err = os.MkdirAll(etc.String(), 0700); err != nil {
|
if err = os.MkdirAll(etc.String(), 0700); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, f := range [][2]string{
|
for _, f := range []KV{
|
||||||
{"hosts", "127.0.0.1 localhost cure cure-net\n"},
|
{"hosts", "127.0.0.1 localhost cure cure-net\n"},
|
||||||
{"passwd", `root:x:0:0:System administrator:/proc/nonexistent:/bin/sh
|
{"passwd", `root:x:0:0:System administrator:/proc/nonexistent:/bin/sh
|
||||||
cure:x:1023:1023:Cure:/usr/src:/bin/sh
|
cure:x:1023:1023:Cure:/usr/src:/bin/sh
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func (t Toolchain) newFakeroot() (pkg.Artifact, string) {
|
|||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarBzip2,
|
pkg.TarBzip2,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
Patches: [][2]string{
|
Patches: []KV{
|
||||||
{"remove-broken-docs", `diff --git a/doc/Makefile.am b/doc/Makefile.am
|
{"remove-broken-docs", `diff --git a/doc/Makefile.am b/doc/Makefile.am
|
||||||
index f135ad9..85c784c 100644
|
index f135ad9..85c784c 100644
|
||||||
--- a/doc/Makefile.am
|
--- a/doc/Makefile.am
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import "hakurei.app/internal/pkg"
|
|||||||
|
|
||||||
func (t Toolchain) newFuse() (pkg.Artifact, string) {
|
func (t Toolchain) newFuse() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "3.18.1"
|
version = "3.18.2"
|
||||||
checksum = "COb-BgJRWXLbt9XUkNeuiroQizpMifXqxgieE1SlkMXhs_WGSyJStrmyewAw2hd6"
|
checksum = "iL-7b7eUtmlVSf5cSq0dzow3UiqSjBmzV3cI_ENPs1tXcHdktkG45j1V12h-4jZe"
|
||||||
)
|
)
|
||||||
return t.NewPackage("fuse", version, pkg.NewHTTPGetTar(
|
return t.NewPackage("fuse", version, pkg.NewHTTPGetTar(
|
||||||
nil, "https://github.com/libfuse/libfuse/releases/download/"+
|
nil, "https://github.com/libfuse/libfuse/releases/download/"+
|
||||||
@@ -13,7 +13,7 @@ func (t Toolchain) newFuse() (pkg.Artifact, string) {
|
|||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), nil, &MesonHelper{
|
), nil, &MesonHelper{
|
||||||
Setup: [][2]string{
|
Setup: []KV{
|
||||||
{"Ddefault_library", "both"},
|
{"Ddefault_library", "both"},
|
||||||
{"Dtests", "true"},
|
{"Dtests", "true"},
|
||||||
{"Duseroot", "false"},
|
{"Duseroot", "false"},
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ func init() {
|
|||||||
|
|
||||||
func (t Toolchain) newAutoconf() (pkg.Artifact, string) {
|
func (t Toolchain) newAutoconf() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "2.72"
|
version = "2.73"
|
||||||
checksum = "-c5blYkC-xLDer3TWEqJTyh1RLbOd1c5dnRLKsDnIrg_wWNOLBpaqMY8FvmUFJ33"
|
checksum = "yGabDTeOfaCUB0JX-h3REYLYzMzvpDwFmFFzHNR7QilChCUNE4hR6q7nma4viDYg"
|
||||||
)
|
)
|
||||||
return t.NewPackage("autoconf", version, pkg.NewHTTPGetTar(
|
return t.NewPackage("autoconf", version, pkg.NewHTTPGetTar(
|
||||||
nil, "https://ftpmirror.gnu.org/gnu/autoconf/autoconf-"+version+".tar.gz",
|
nil, "https://ftpmirror.gnu.org/gnu/autoconf/autoconf-"+version+".tar.gz",
|
||||||
@@ -351,7 +351,7 @@ func (t Toolchain) newBash() (pkg.Artifact, string) {
|
|||||||
Flag: TEarly,
|
Flag: TEarly,
|
||||||
}, &MakeHelper{
|
}, &MakeHelper{
|
||||||
Script: "ln -s bash /work/system/bin/sh\n",
|
Script: "ln -s bash /work/system/bin/sh\n",
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"without-bash-malloc"},
|
{"without-bash-malloc"},
|
||||||
},
|
},
|
||||||
}), version
|
}), version
|
||||||
@@ -390,7 +390,7 @@ test_disable 'int main(){return 0;}' gnulib-tests/test-fchownat.c
|
|||||||
test_disable 'int main(){return 0;}' gnulib-tests/test-lchown.c
|
test_disable 'int main(){return 0;}' gnulib-tests/test-lchown.c
|
||||||
`,
|
`,
|
||||||
|
|
||||||
Patches: [][2]string{
|
Patches: []KV{
|
||||||
{"tests-fix-job-control", `From 21d287324aa43aa3a31f39619ade0deac7fd6013 Mon Sep 17 00:00:00 2001
|
{"tests-fix-job-control", `From 21d287324aa43aa3a31f39619ade0deac7fd6013 Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P@draigBrady.com>
|
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P@draigBrady.com>
|
||||||
Date: Tue, 24 Feb 2026 15:44:41 +0000
|
Date: Tue, 24 Feb 2026 15:44:41 +0000
|
||||||
@@ -485,7 +485,7 @@ index 9a395416b..fbb043312 100755
|
|||||||
|
|
||||||
Flag: TEarly,
|
Flag: TEarly,
|
||||||
}, &MakeHelper{
|
}, &MakeHelper{
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"enable-single-binary", "symlinks"},
|
{"enable-single-binary", "symlinks"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -720,7 +720,7 @@ func (t Toolchain) newTar() (pkg.Artifact, string) {
|
|||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), nil, &MakeHelper{
|
), nil, &MakeHelper{
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"disable-acl"},
|
{"disable-acl"},
|
||||||
{"without-posix-acls"},
|
{"without-posix-acls"},
|
||||||
{"without-xattrs"},
|
{"without-xattrs"},
|
||||||
@@ -903,7 +903,7 @@ func (t Toolchain) newGCC() (pkg.Artifact, string) {
|
|||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
Patches: [][2]string{
|
Patches: []KV{
|
||||||
{"musl-off64_t-loff_t", `diff --git a/libgo/sysinfo.c b/libgo/sysinfo.c
|
{"musl-off64_t-loff_t", `diff --git a/libgo/sysinfo.c b/libgo/sysinfo.c
|
||||||
index 180f5c31d74..44d7ea73f7d 100644
|
index 180f5c31d74..44d7ea73f7d 100644
|
||||||
--- a/libgo/sysinfo.c
|
--- a/libgo/sysinfo.c
|
||||||
@@ -1062,7 +1062,7 @@ ln -s system/lib /work/
|
|||||||
// it also saturates the CPU for a consequential amount of time.
|
// it also saturates the CPU for a consequential amount of time.
|
||||||
Flag: TExclusive,
|
Flag: TExclusive,
|
||||||
}, &MakeHelper{
|
}, &MakeHelper{
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"disable-multilib"},
|
{"disable-multilib"},
|
||||||
{"with-multilib-list", `""`},
|
{"with-multilib-list", `""`},
|
||||||
{"enable-default-pie"},
|
{"enable-default-pie"},
|
||||||
|
|||||||
@@ -135,7 +135,8 @@ sed -i \
|
|||||||
cmd/link/internal/`+runtime.GOARCH+`/obj.go
|
cmd/link/internal/`+runtime.GOARCH+`/obj.go
|
||||||
|
|
||||||
rm \
|
rm \
|
||||||
os/root_unix_test.go
|
os/root_unix_test.go \
|
||||||
|
net/smtp/smtp_test.go
|
||||||
`, go123,
|
`, go123,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func (t Toolchain) newGLib() (pkg.Artifact, string) {
|
|||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
}, &MesonHelper{
|
}, &MesonHelper{
|
||||||
Setup: [][2]string{
|
Setup: []KV{
|
||||||
{"Ddefault_library", "both"},
|
{"Ddefault_library", "both"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ mkdir -p /work/system/libexec/hakurei/
|
|||||||
|
|
||||||
echo '# Building hakurei.'
|
echo '# Building hakurei.'
|
||||||
go generate -v ./...
|
go generate -v ./...
|
||||||
go build -trimpath -v -o /work/system/libexec/hakurei -ldflags="-s -w
|
go build -trimpath -v -tags=rosa -o /work/system/libexec/hakurei -ldflags="-s -w
|
||||||
-buildid=
|
-buildid=
|
||||||
-linkmode external
|
-linkmode external
|
||||||
-extldflags=-static
|
-extldflags=-static
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ var hakureiSource = pkg.NewTar(pkg.NewFile(
|
|||||||
), pkg.TarGzip)
|
), pkg.TarGzip)
|
||||||
|
|
||||||
// hakureiPatches are patches applied against the compile-time source tree.
|
// hakureiPatches are patches applied against the compile-time source tree.
|
||||||
var hakureiPatches [][2]string
|
var hakureiPatches []KV
|
||||||
|
|||||||
@@ -15,4 +15,4 @@ var hakureiSource = pkg.NewHTTPGetTar(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// hakureiPatches are patches applied against a hakurei release.
|
// hakureiPatches are patches applied against a hakurei release.
|
||||||
var hakureiPatches [][2]string
|
var hakureiPatches []KV
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package rosa
|
|||||||
|
|
||||||
import "hakurei.app/internal/pkg"
|
import "hakurei.app/internal/pkg"
|
||||||
|
|
||||||
const kernelVersion = "6.12.77"
|
const kernelVersion = "6.12.78"
|
||||||
|
|
||||||
var kernelSource = pkg.NewHTTPGetTar(
|
var kernelSource = pkg.NewHTTPGetTar(
|
||||||
nil, "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/"+
|
nil, "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/"+
|
||||||
"snapshot/linux-"+kernelVersion+".tar.gz",
|
"snapshot/linux-"+kernelVersion+".tar.gz",
|
||||||
mustDecode("_MyFL0MqqNwAJx4fP8L9FkUayXIqEJto5trAPr_9UJvaT5TK1tvlU8leS82Hw2uw"),
|
mustDecode("iUlZA-nv04TUOL0TmgDGBjaOe0sIaXTqLvuR4owYgHMZM8vecusnMMqbeuuZP4_G"),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ exec /system/sbin/depmod -m /lib/modules "$@"
|
|||||||
`))),
|
`))),
|
||||||
},
|
},
|
||||||
|
|
||||||
Patches: [][2]string{
|
Patches: []KV{
|
||||||
{"f54a91f5337cd918eb86cf600320d25b6cfd8209", `From f54a91f5337cd918eb86cf600320d25b6cfd8209 Mon Sep 17 00:00:00 2001
|
{"f54a91f5337cd918eb86cf600320d25b6cfd8209", `From f54a91f5337cd918eb86cf600320d25b6cfd8209 Mon Sep 17 00:00:00 2001
|
||||||
From: Nathan Chancellor <nathan@kernel.org>
|
From: Nathan Chancellor <nathan@kernel.org>
|
||||||
Date: Sat, 13 Dec 2025 19:58:10 +0900
|
Date: Sat, 13 Dec 2025 19:58:10 +0900
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
#
|
#
|
||||||
# Automatically generated file; DO NOT EDIT.
|
# Automatically generated file; DO NOT EDIT.
|
||||||
# Linux/x86 6.12.76 Kernel Configuration
|
# Linux/x86 6.12.78 Kernel Configuration
|
||||||
#
|
#
|
||||||
CONFIG_CC_VERSION_TEXT="clang version 22.1.1"
|
CONFIG_CC_VERSION_TEXT="clang version 22.1.2"
|
||||||
CONFIG_GCC_VERSION=0
|
CONFIG_GCC_VERSION=0
|
||||||
CONFIG_CC_IS_CLANG=y
|
CONFIG_CC_IS_CLANG=y
|
||||||
CONFIG_CLANG_VERSION=220101
|
CONFIG_CLANG_VERSION=220102
|
||||||
CONFIG_AS_IS_LLVM=y
|
CONFIG_AS_IS_LLVM=y
|
||||||
CONFIG_AS_VERSION=220101
|
CONFIG_AS_VERSION=220102
|
||||||
CONFIG_LD_VERSION=0
|
CONFIG_LD_VERSION=0
|
||||||
CONFIG_LD_IS_LLD=y
|
CONFIG_LD_IS_LLD=y
|
||||||
CONFIG_LLD_VERSION=220101
|
CONFIG_LLD_VERSION=220102
|
||||||
CONFIG_RUSTC_VERSION=0
|
CONFIG_RUSTC_VERSION=0
|
||||||
CONFIG_RUSTC_LLVM_VERSION=0
|
CONFIG_RUSTC_LLVM_VERSION=0
|
||||||
CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y
|
CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
#
|
#
|
||||||
# Automatically generated file; DO NOT EDIT.
|
# Automatically generated file; DO NOT EDIT.
|
||||||
# Linux/arm64 6.12.76 Kernel Configuration
|
# Linux/arm64 6.12.78 Kernel Configuration
|
||||||
#
|
#
|
||||||
CONFIG_CC_VERSION_TEXT="clang version 22.1.1"
|
CONFIG_CC_VERSION_TEXT="clang version 21.1.8"
|
||||||
CONFIG_GCC_VERSION=0
|
CONFIG_GCC_VERSION=0
|
||||||
CONFIG_CC_IS_CLANG=y
|
CONFIG_CC_IS_CLANG=y
|
||||||
CONFIG_CLANG_VERSION=220101
|
CONFIG_CLANG_VERSION=210108
|
||||||
CONFIG_AS_IS_LLVM=y
|
CONFIG_AS_IS_LLVM=y
|
||||||
CONFIG_AS_VERSION=220101
|
CONFIG_AS_VERSION=210108
|
||||||
CONFIG_LD_VERSION=0
|
CONFIG_LD_VERSION=0
|
||||||
CONFIG_LD_IS_LLD=y
|
CONFIG_LD_IS_LLD=y
|
||||||
CONFIG_LLD_VERSION=220101
|
CONFIG_LLD_VERSION=210108
|
||||||
CONFIG_RUSTC_VERSION=0
|
CONFIG_RUSTC_VERSION=0
|
||||||
CONFIG_RUSTC_LLVM_VERSION=0
|
CONFIG_RUSTC_LLVM_VERSION=0
|
||||||
CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y
|
CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y
|
||||||
@@ -73,6 +73,7 @@ CONFIG_IRQ_MSI_IOMMU=y
|
|||||||
CONFIG_IRQ_FORCED_THREADING=y
|
CONFIG_IRQ_FORCED_THREADING=y
|
||||||
CONFIG_SPARSE_IRQ=y
|
CONFIG_SPARSE_IRQ=y
|
||||||
# CONFIG_GENERIC_IRQ_DEBUGFS is not set
|
# CONFIG_GENERIC_IRQ_DEBUGFS is not set
|
||||||
|
CONFIG_GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD=y
|
||||||
# end of IRQ subsystem
|
# end of IRQ subsystem
|
||||||
|
|
||||||
CONFIG_GENERIC_TIME_VSYSCALL=y
|
CONFIG_GENERIC_TIME_VSYSCALL=y
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func (t Toolchain) newKmod() (pkg.Artifact, string) {
|
|||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), nil, &MesonHelper{
|
), nil, &MesonHelper{
|
||||||
Setup: [][2]string{
|
Setup: []KV{
|
||||||
{"Dmoduledir", "/system/lib/modules"},
|
{"Dmoduledir", "/system/lib/modules"},
|
||||||
{"Dsysconfdir", "/system/etc"},
|
{"Dsysconfdir", "/system/etc"},
|
||||||
{"Dbashcompletiondir", "no"},
|
{"Dbashcompletiondir", "no"},
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func (t Toolchain) newLibexpat() (pkg.Artifact, string) {
|
func (t Toolchain) newLibexpat() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "2.7.4"
|
version = "2.7.5"
|
||||||
checksum = "W6NI2FESBjrTqRPcvs15fK5c3nwF6f9RT8U-XHKQKblXVzJB3nt_ez5B5jO0ZVDG"
|
checksum = "vTRUjjg-qbHSXUBYKXgzVHkUO7UNyuhrkSYrE7ikApQm0g-OvQ8tspw4w55M-1Tp"
|
||||||
)
|
)
|
||||||
return t.NewPackage("libexpat", version, pkg.NewHTTPGetTar(
|
return t.NewPackage("libexpat", version, pkg.NewHTTPGetTar(
|
||||||
nil, "https://github.com/libexpat/libexpat/releases/download/"+
|
nil, "https://github.com/libexpat/libexpat/releases/download/"+
|
||||||
|
|||||||
@@ -16,6 +16,23 @@ func (t Toolchain) newLibseccomp() (pkg.Artifact, string) {
|
|||||||
ScriptEarly: `
|
ScriptEarly: `
|
||||||
ln -s ../system/bin/bash /bin/
|
ln -s ../system/bin/bash /bin/
|
||||||
`,
|
`,
|
||||||
|
|
||||||
|
Patches: []KV{
|
||||||
|
{"fix-export-oob-read", `diff --git a/src/api.c b/src/api.c
|
||||||
|
index adccef3..65a277a 100644
|
||||||
|
--- a/src/api.c
|
||||||
|
+++ b/src/api.c
|
||||||
|
@@ -786,7 +786,7 @@ API int seccomp_export_bpf_mem(const scmp_filter_ctx ctx, void *buf,
|
||||||
|
if (BPF_PGM_SIZE(program) > *len)
|
||||||
|
rc = _rc_filter(-ERANGE);
|
||||||
|
else
|
||||||
|
- memcpy(buf, program->blks, *len);
|
||||||
|
+ memcpy(buf, program->blks, BPF_PGM_SIZE(program));
|
||||||
|
}
|
||||||
|
*len = BPF_PGM_SIZE(program);
|
||||||
|
|
||||||
|
`},
|
||||||
|
},
|
||||||
}, (*MakeHelper)(nil),
|
}, (*MakeHelper)(nil),
|
||||||
Bash,
|
Bash,
|
||||||
Diffutils,
|
Diffutils,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type llvmAttr struct {
|
|||||||
// Concatenated with default environment for PackageAttr.Env.
|
// Concatenated with default environment for PackageAttr.Env.
|
||||||
env []string
|
env []string
|
||||||
// Concatenated with generated entries for CMakeHelper.Cache.
|
// Concatenated with generated entries for CMakeHelper.Cache.
|
||||||
cmake [][2]string
|
cmake []KV
|
||||||
// Override CMakeHelper.Append.
|
// Override CMakeHelper.Append.
|
||||||
append []string
|
append []string
|
||||||
// Passed through to PackageAttr.NonStage0.
|
// Passed through to PackageAttr.NonStage0.
|
||||||
@@ -30,7 +30,7 @@ type llvmAttr struct {
|
|||||||
script string
|
script string
|
||||||
|
|
||||||
// Patch name and body pairs.
|
// Patch name and body pairs.
|
||||||
patches [][2]string
|
patches []KV
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -94,43 +94,45 @@ func (t Toolchain) newLLVMVariant(variant string, attr *llvmAttr) pkg.Artifact {
|
|||||||
|
|
||||||
var script string
|
var script string
|
||||||
|
|
||||||
cache := [][2]string{
|
cache := []KV{
|
||||||
{"CMAKE_BUILD_TYPE", "Release"},
|
{"CMAKE_BUILD_TYPE", "Release"},
|
||||||
|
|
||||||
{"LLVM_HOST_TRIPLE", `"${ROSA_TRIPLE}"`},
|
{"LLVM_HOST_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||||
{"LLVM_DEFAULT_TARGET_TRIPLE", `"${ROSA_TRIPLE}"`},
|
{"LLVM_DEFAULT_TARGET_TRIPLE", `"${ROSA_TRIPLE}"`},
|
||||||
}
|
}
|
||||||
if len(projects) > 0 {
|
if len(projects) > 0 {
|
||||||
cache = append(cache,
|
cache = append(cache, []KV{
|
||||||
[2]string{"LLVM_ENABLE_PROJECTS", `"${ROSA_LLVM_PROJECTS}"`})
|
{"LLVM_ENABLE_PROJECTS", `"${ROSA_LLVM_PROJECTS}"`},
|
||||||
|
}...)
|
||||||
}
|
}
|
||||||
if len(runtimes) > 0 {
|
if len(runtimes) > 0 {
|
||||||
cache = append(cache,
|
cache = append(cache, []KV{
|
||||||
[2]string{"LLVM_ENABLE_RUNTIMES", `"${ROSA_LLVM_RUNTIMES}"`})
|
{"LLVM_ENABLE_RUNTIMES", `"${ROSA_LLVM_RUNTIMES}"`},
|
||||||
|
}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmakeAppend := []string{"llvm"}
|
cmakeAppend := []string{"llvm"}
|
||||||
if attr.append != nil {
|
if attr.append != nil {
|
||||||
cmakeAppend = attr.append
|
cmakeAppend = attr.append
|
||||||
} else {
|
} else {
|
||||||
cache = append(cache,
|
cache = append(cache, []KV{
|
||||||
[2]string{"LLVM_ENABLE_LIBCXX", "ON"},
|
{"LLVM_ENABLE_LIBCXX", "ON"},
|
||||||
[2]string{"LLVM_USE_LINKER", "lld"},
|
{"LLVM_USE_LINKER", "lld"},
|
||||||
|
|
||||||
[2]string{"LLVM_INSTALL_BINUTILS_SYMLINKS", "ON"},
|
{"LLVM_INSTALL_BINUTILS_SYMLINKS", "ON"},
|
||||||
[2]string{"LLVM_INSTALL_CCTOOLS_SYMLINKS", "ON"},
|
{"LLVM_INSTALL_CCTOOLS_SYMLINKS", "ON"},
|
||||||
|
|
||||||
[2]string{"LLVM_LIT_ARGS", "'--verbose'"},
|
{"LLVM_LIT_ARGS", "'--verbose'"},
|
||||||
)
|
}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr.flags&llvmProjectClang != 0 {
|
if attr.flags&llvmProjectClang != 0 {
|
||||||
cache = append(cache,
|
cache = append(cache, []KV{
|
||||||
[2]string{"CLANG_DEFAULT_LINKER", "lld"},
|
{"CLANG_DEFAULT_LINKER", "lld"},
|
||||||
[2]string{"CLANG_DEFAULT_CXX_STDLIB", "libc++"},
|
{"CLANG_DEFAULT_CXX_STDLIB", "libc++"},
|
||||||
[2]string{"CLANG_DEFAULT_RTLIB", "compiler-rt"},
|
{"CLANG_DEFAULT_RTLIB", "compiler-rt"},
|
||||||
[2]string{"CLANG_DEFAULT_UNWINDLIB", "libunwind"},
|
{"CLANG_DEFAULT_UNWINDLIB", "libunwind"},
|
||||||
)
|
}...)
|
||||||
}
|
}
|
||||||
if attr.flags&llvmProjectLld != 0 {
|
if attr.flags&llvmProjectLld != 0 {
|
||||||
script += `
|
script += `
|
||||||
@@ -139,25 +141,27 @@ ln -s ld.lld /work/system/bin/ld
|
|||||||
}
|
}
|
||||||
if attr.flags&llvmRuntimeCompilerRT != 0 {
|
if attr.flags&llvmRuntimeCompilerRT != 0 {
|
||||||
if attr.append == nil {
|
if attr.append == nil {
|
||||||
cache = append(cache,
|
cache = append(cache, []KV{
|
||||||
[2]string{"COMPILER_RT_USE_LLVM_UNWINDER", "ON"})
|
{"COMPILER_RT_USE_LLVM_UNWINDER", "ON"},
|
||||||
|
}...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if attr.flags&llvmRuntimeLibunwind != 0 {
|
if attr.flags&llvmRuntimeLibunwind != 0 {
|
||||||
cache = append(cache,
|
cache = append(cache, []KV{
|
||||||
[2]string{"LIBUNWIND_USE_COMPILER_RT", "ON"})
|
{"LIBUNWIND_USE_COMPILER_RT", "ON"},
|
||||||
|
}...)
|
||||||
}
|
}
|
||||||
if attr.flags&llvmRuntimeLibcxx != 0 {
|
if attr.flags&llvmRuntimeLibcxx != 0 {
|
||||||
cache = append(cache,
|
cache = append(cache, []KV{
|
||||||
[2]string{"LIBCXX_HAS_MUSL_LIBC", "ON"},
|
{"LIBCXX_HAS_MUSL_LIBC", "ON"},
|
||||||
[2]string{"LIBCXX_USE_COMPILER_RT", "ON"},
|
{"LIBCXX_USE_COMPILER_RT", "ON"},
|
||||||
)
|
}...)
|
||||||
}
|
}
|
||||||
if attr.flags&llvmRuntimeLibcxxABI != 0 {
|
if attr.flags&llvmRuntimeLibcxxABI != 0 {
|
||||||
cache = append(cache,
|
cache = append(cache, []KV{
|
||||||
[2]string{"LIBCXXABI_USE_COMPILER_RT", "ON"},
|
{"LIBCXXABI_USE_COMPILER_RT", "ON"},
|
||||||
[2]string{"LIBCXXABI_USE_LLVM_UNWINDER", "ON"},
|
{"LIBCXXABI_USE_LLVM_UNWINDER", "ON"},
|
||||||
)
|
}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.NewPackage("llvm", llvmVersion, pkg.NewHTTPGetTar(
|
return t.NewPackage("llvm", llvmVersion, pkg.NewHTTPGetTar(
|
||||||
@@ -208,7 +212,7 @@ func (t Toolchain) newLLVM() (musl, compilerRT, runtimes, clang pkg.Artifact) {
|
|||||||
panic("unsupported target " + runtime.GOARCH)
|
panic("unsupported target " + runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
minimalDeps := [][2]string{
|
minimalDeps := []KV{
|
||||||
{"LLVM_ENABLE_ZLIB", "OFF"},
|
{"LLVM_ENABLE_ZLIB", "OFF"},
|
||||||
{"LLVM_ENABLE_ZSTD", "OFF"},
|
{"LLVM_ENABLE_ZSTD", "OFF"},
|
||||||
{"LLVM_ENABLE_LIBXML2", "OFF"},
|
{"LLVM_ENABLE_LIBXML2", "OFF"},
|
||||||
@@ -222,7 +226,7 @@ func (t Toolchain) newLLVM() (musl, compilerRT, runtimes, clang pkg.Artifact) {
|
|||||||
env: stage0ExclConcat(t, []string{},
|
env: stage0ExclConcat(t, []string{},
|
||||||
"LDFLAGS="+earlyLDFLAGS(false),
|
"LDFLAGS="+earlyLDFLAGS(false),
|
||||||
),
|
),
|
||||||
cmake: [][2]string{
|
cmake: []KV{
|
||||||
// libc++ not yet available
|
// libc++ not yet available
|
||||||
{"CMAKE_CXX_COMPILER_TARGET", ""},
|
{"CMAKE_CXX_COMPILER_TARGET", ""},
|
||||||
|
|
||||||
@@ -272,7 +276,7 @@ ln -s \
|
|||||||
"LDFLAGS="+earlyLDFLAGS(false),
|
"LDFLAGS="+earlyLDFLAGS(false),
|
||||||
),
|
),
|
||||||
flags: llvmRuntimeLibunwind | llvmRuntimeLibcxx | llvmRuntimeLibcxxABI,
|
flags: llvmRuntimeLibunwind | llvmRuntimeLibcxx | llvmRuntimeLibcxxABI,
|
||||||
cmake: slices.Concat([][2]string{
|
cmake: slices.Concat([]KV{
|
||||||
// libc++ not yet available
|
// libc++ not yet available
|
||||||
{"CMAKE_CXX_COMPILER_WORKS", "ON"},
|
{"CMAKE_CXX_COMPILER_WORKS", "ON"},
|
||||||
|
|
||||||
@@ -293,7 +297,7 @@ ln -s \
|
|||||||
"CXXFLAGS="+earlyCXXFLAGS(),
|
"CXXFLAGS="+earlyCXXFLAGS(),
|
||||||
"LDFLAGS="+earlyLDFLAGS(false),
|
"LDFLAGS="+earlyLDFLAGS(false),
|
||||||
),
|
),
|
||||||
cmake: slices.Concat([][2]string{
|
cmake: slices.Concat([]KV{
|
||||||
{"LLVM_TARGETS_TO_BUILD", target},
|
{"LLVM_TARGETS_TO_BUILD", target},
|
||||||
{"CMAKE_CROSSCOMPILING", "OFF"},
|
{"CMAKE_CROSSCOMPILING", "OFF"},
|
||||||
{"CXX_SUPPORTS_CUSTOM_LINKER", "ON"},
|
{"CXX_SUPPORTS_CUSTOM_LINKER", "ON"},
|
||||||
@@ -310,7 +314,7 @@ ln -s clang++ /work/system/bin/c++
|
|||||||
ninja check-all
|
ninja check-all
|
||||||
`,
|
`,
|
||||||
|
|
||||||
patches: slices.Concat([][2]string{
|
patches: slices.Concat([]KV{
|
||||||
{"add-rosa-vendor", `diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
|
{"add-rosa-vendor", `diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
|
||||||
index 9c83abeeb3b1..5acfe5836a23 100644
|
index 9c83abeeb3b1..5acfe5836a23 100644
|
||||||
--- a/llvm/include/llvm/TargetParser/Triple.h
|
--- a/llvm/include/llvm/TargetParser/Triple.h
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package rosa
|
package rosa
|
||||||
|
|
||||||
// clangPatches are patches applied to the LLVM source tree for building clang.
|
// clangPatches are patches applied to the LLVM source tree for building clang.
|
||||||
var clangPatches [][2]string
|
var clangPatches []KV
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package rosa
|
package rosa
|
||||||
|
|
||||||
// clangPatches are patches applied to the LLVM source tree for building clang.
|
// clangPatches are patches applied to the LLVM source tree for building clang.
|
||||||
var clangPatches [][2]string
|
var clangPatches []KV
|
||||||
|
|
||||||
// one version behind, latest fails 5 tests with 2 flaky on arm64
|
// one version behind, latest fails 5 tests with 2 flaky on arm64
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package rosa
|
|||||||
// latest version of LLVM, conditional to temporarily avoid broken new releases
|
// latest version of LLVM, conditional to temporarily avoid broken new releases
|
||||||
const (
|
const (
|
||||||
llvmVersionMajor = "22"
|
llvmVersionMajor = "22"
|
||||||
llvmVersion = llvmVersionMajor + ".1.1"
|
llvmVersion = llvmVersionMajor + ".1.2"
|
||||||
|
|
||||||
llvmChecksum = "bQvV6D8AZvQykg7-uQb_saTbVavnSo1ykNJ3g57F5iE-evU3HuOYtcRnVIXTK76e"
|
llvmChecksum = "FwsmurWDVyYYQlOowowFjekwIGSB5__aKTpW_VGP3eWoZGXvBny-bOn1DuQ1U5xE"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ type MakeHelper struct {
|
|||||||
// Alternative name for the configure script.
|
// Alternative name for the configure script.
|
||||||
ConfigureName string
|
ConfigureName string
|
||||||
// Flags passed to the configure script.
|
// Flags passed to the configure script.
|
||||||
Configure [][2]string
|
Configure []KV
|
||||||
// Host target triple, zero value is equivalent to the Rosa OS triple.
|
// Host target triple, zero value is equivalent to the Rosa OS triple.
|
||||||
Host string
|
Host string
|
||||||
// Target triple, zero value is equivalent to the Rosa OS triple.
|
// Target triple, zero value is equivalent to the Rosa OS triple.
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ type MesonHelper struct {
|
|||||||
Script string
|
Script string
|
||||||
|
|
||||||
// Flags passed to the setup command.
|
// Flags passed to the setup command.
|
||||||
Setup [][2]string
|
Setup []KV
|
||||||
// Whether to skip meson test.
|
// Whether to skip meson test.
|
||||||
SkipTest bool
|
SkipTest bool
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ meson test \
|
|||||||
cd "$(mktemp -d)"
|
cd "$(mktemp -d)"
|
||||||
meson setup \
|
meson setup \
|
||||||
` + strings.Join(slices.Collect(func(yield func(string) bool) {
|
` + strings.Join(slices.Collect(func(yield func(string) bool) {
|
||||||
for _, v := range append([][2]string{
|
for _, v := range append([]KV{
|
||||||
{"prefix", "/system"},
|
{"prefix", "/system"},
|
||||||
{"buildtype", "release"},
|
{"buildtype", "release"},
|
||||||
}, attr.Setup...) {
|
}, attr.Setup...) {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ func (t Toolchain) newMusl(
|
|||||||
extra ...pkg.Artifact,
|
extra ...pkg.Artifact,
|
||||||
) (pkg.Artifact, string) {
|
) (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "1.2.5"
|
version = "1.2.6"
|
||||||
checksum = "y6USdIeSdHER_Fw2eT2CNjqShEye85oEg2jnOur96D073ukmIpIqDOLmECQroyDb"
|
checksum = "WtWb_OV_XxLDAB5NerOL9loLlHVadV00MmGk65PPBU1evaolagoMHfvpZp_vxEzS"
|
||||||
)
|
)
|
||||||
|
|
||||||
name := "musl"
|
name := "musl"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func (t Toolchain) newNcurses() (pkg.Artifact, string) {
|
|||||||
// "tests" are actual demo programs, not a test suite.
|
// "tests" are actual demo programs, not a test suite.
|
||||||
SkipCheck: true,
|
SkipCheck: true,
|
||||||
|
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"with-pkg-config"},
|
{"with-pkg-config"},
|
||||||
{"enable-pc-files"},
|
{"enable-pc-files"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func (t Toolchain) newNSS() (pkg.Artifact, string) {
|
func (t Toolchain) newNSS() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "3.121"
|
version = "3.122"
|
||||||
checksum = "MTS4Eg-1vBN3T7gdUAdNO0y_e9x9BE3f_k_DHdM_BIovc7y57vhsZTfB5f6BeQfi"
|
checksum = "QvC6TBO4BAUEh6wmgUrb1hwH5podQAN-QdcAaWL32cWEppmZs6oKkZpD9GvZf59S"
|
||||||
|
|
||||||
version0 = "4_38_2"
|
version0 = "4_38_2"
|
||||||
checksum0 = "25x2uJeQnOHIiq_zj17b4sYqKgeoU8-IsySUptoPcdHZ52PohFZfGuIisBreWzx0"
|
checksum0 = "25x2uJeQnOHIiq_zj17b4sYqKgeoU8-IsySUptoPcdHZ52PohFZfGuIisBreWzx0"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (t Toolchain) newOpenSSL() (pkg.Artifact, string) {
|
|||||||
OmitDefaults: true,
|
OmitDefaults: true,
|
||||||
|
|
||||||
ConfigureName: "/usr/src/openssl/Configure",
|
ConfigureName: "/usr/src/openssl/Configure",
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"prefix", "/system"},
|
{"prefix", "/system"},
|
||||||
{"libdir", "lib"},
|
{"libdir", "lib"},
|
||||||
{"openssldir", "etc/ssl"},
|
{"openssldir", "etc/ssl"},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (t Toolchain) newPCRE2() (pkg.Artifact, string) {
|
|||||||
ln -s ../system/bin/toybox /bin/echo
|
ln -s ../system/bin/toybox /bin/echo
|
||||||
`,
|
`,
|
||||||
}, &MakeHelper{
|
}, &MakeHelper{
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"enable-jit"},
|
{"enable-jit"},
|
||||||
{"enable-pcre2-8"},
|
{"enable-pcre2-8"},
|
||||||
{"enable-pcre2-16"},
|
{"enable-pcre2-16"},
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ rm -f /system/bin/ps # perl does not like toybox ps
|
|||||||
InPlace: true,
|
InPlace: true,
|
||||||
|
|
||||||
ConfigureName: "./Configure",
|
ConfigureName: "./Configure",
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"-des"},
|
{"-des"},
|
||||||
{"Dprefix", "/system"},
|
{"Dprefix", "/system"},
|
||||||
{"Dcc", "clang"},
|
{"Dcc", "clang"},
|
||||||
@@ -67,7 +67,7 @@ func init() {
|
|||||||
func (t Toolchain) newViaPerlModuleBuild(
|
func (t Toolchain) newViaPerlModuleBuild(
|
||||||
name, version string,
|
name, version string,
|
||||||
source pkg.Artifact,
|
source pkg.Artifact,
|
||||||
patches [][2]string,
|
patches []KV,
|
||||||
extra ...PArtifact,
|
extra ...PArtifact,
|
||||||
) pkg.Artifact {
|
) pkg.Artifact {
|
||||||
if name == "" || version == "" {
|
if name == "" || version == "" {
|
||||||
@@ -116,7 +116,7 @@ func init() {
|
|||||||
func (t Toolchain) newViaPerlMakeMaker(
|
func (t Toolchain) newViaPerlMakeMaker(
|
||||||
name, version string,
|
name, version string,
|
||||||
source pkg.Artifact,
|
source pkg.Artifact,
|
||||||
patches [][2]string,
|
patches []KV,
|
||||||
extra ...PArtifact,
|
extra ...PArtifact,
|
||||||
) pkg.Artifact {
|
) pkg.Artifact {
|
||||||
return t.NewPackage("perl-"+name, version, source, &PackageAttr{
|
return t.NewPackage("perl-"+name, version, source, &PackageAttr{
|
||||||
@@ -131,7 +131,7 @@ func (t Toolchain) newViaPerlMakeMaker(
|
|||||||
InPlace: true,
|
InPlace: true,
|
||||||
|
|
||||||
ConfigureName: "perl Makefile.PL",
|
ConfigureName: "perl Makefile.PL",
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"PREFIX", "/system"},
|
{"PREFIX", "/system"},
|
||||||
},
|
},
|
||||||
Check: []string{"test"},
|
Check: []string{"test"},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func (t Toolchain) newPkgConfig() (pkg.Artifact, string) {
|
|||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), nil, &MakeHelper{
|
), nil, &MakeHelper{
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"CFLAGS", "'-Wno-int-conversion'"},
|
{"CFLAGS", "'-Wno-int-conversion'"},
|
||||||
{"with-internal-glib"},
|
{"with-internal-glib"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func (t Toolchain) newProcps() (pkg.Artifact, string) {
|
|||||||
pkg.TarBzip2,
|
pkg.TarBzip2,
|
||||||
), nil, &MakeHelper{
|
), nil, &MakeHelper{
|
||||||
Generate: "./autogen.sh",
|
Generate: "./autogen.sh",
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"without-ncurses"},
|
{"without-ncurses"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import "hakurei.app/internal/pkg"
|
|||||||
|
|
||||||
func (t Toolchain) newQEMU() (pkg.Artifact, string) {
|
func (t Toolchain) newQEMU() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "10.2.1"
|
version = "10.2.2"
|
||||||
checksum = "rjLTSgHJd3X3Vgpxrsus_ZZiaYLiNix1YhcHaGbLd_odYixwZjCcAIt8CVQPJGdZ"
|
checksum = "uNzRxlrVoLWe-EmZmBp75SezymgE512iE5XN90Bl7wi6CjE_oQGQB-9ocs7E16QG"
|
||||||
)
|
)
|
||||||
return t.NewPackage("qemu", version, pkg.NewHTTPGetTar(
|
return t.NewPackage("qemu", version, pkg.NewHTTPGetTar(
|
||||||
nil, "https://download.qemu.org/qemu-"+version+".tar.bz2",
|
nil, "https://download.qemu.org/qemu-"+version+".tar.bz2",
|
||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarBzip2,
|
pkg.TarBzip2,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
Patches: [][2]string{
|
Patches: []KV{
|
||||||
{"disable-mcast-test", `diff --git a/tests/qtest/netdev-socket.c b/tests/qtest/netdev-socket.c
|
{"disable-mcast-test", `diff --git a/tests/qtest/netdev-socket.c b/tests/qtest/netdev-socket.c
|
||||||
index b731af0ad9..b5cbed4801 100644
|
index b731af0ad9..b5cbed4801 100644
|
||||||
--- a/tests/qtest/netdev-socket.c
|
--- a/tests/qtest/netdev-socket.c
|
||||||
@@ -58,7 +58,7 @@ _notrun 'appears to spuriously fail on zfs'
|
|||||||
EOF
|
EOF
|
||||||
`,
|
`,
|
||||||
}, &MakeHelper{
|
}, &MakeHelper{
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"disable-download"},
|
{"disable-download"},
|
||||||
{"disable-docs"},
|
{"disable-docs"},
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ const (
|
|||||||
|
|
||||||
// kindBusyboxBin is the kind of [pkg.Artifact] of busyboxBin.
|
// kindBusyboxBin is the kind of [pkg.Artifact] of busyboxBin.
|
||||||
kindBusyboxBin
|
kindBusyboxBin
|
||||||
|
|
||||||
// kindCollection is the kind of [Collect]. It never cures successfully.
|
|
||||||
kindCollection
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// mustDecode is like [pkg.MustDecode], but replaces the zero value and prints
|
// mustDecode is like [pkg.MustDecode], but replaces the zero value and prints
|
||||||
@@ -40,6 +37,9 @@ func mustDecode(s string) pkg.Checksum {
|
|||||||
return pkg.MustDecode(s)
|
return pkg.MustDecode(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KV is a key-value pair of strings.
|
||||||
|
type KV [2]string
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// AbsUsrSrc is the conventional directory to place source code under.
|
// AbsUsrSrc is the conventional directory to place source code under.
|
||||||
AbsUsrSrc = fhs.AbsUsr.Append("src")
|
AbsUsrSrc = fhs.AbsUsr.Append("src")
|
||||||
@@ -202,6 +202,10 @@ func lastIndexFunc[S ~[]E, E any](s S, f func(E) bool) (i int) {
|
|||||||
|
|
||||||
// fixupEnviron fixes up PATH, prepends extras and returns the resulting slice.
|
// fixupEnviron fixes up PATH, prepends extras and returns the resulting slice.
|
||||||
func fixupEnviron(env, extras []string, paths ...string) []string {
|
func fixupEnviron(env, extras []string, paths ...string) []string {
|
||||||
|
// some python tools try to be clever and buffers their output, making the
|
||||||
|
// build process appear to hang
|
||||||
|
env = append(env, "PYTHONUNBUFFERED=1")
|
||||||
|
|
||||||
const pathPrefix = "PATH="
|
const pathPrefix = "PATH="
|
||||||
pathVal := strings.Join(paths, ":")
|
pathVal := strings.Join(paths, ":")
|
||||||
|
|
||||||
@@ -363,7 +367,7 @@ func (t Toolchain) NewPatchedSource(
|
|||||||
name, version string,
|
name, version string,
|
||||||
source pkg.Artifact,
|
source pkg.Artifact,
|
||||||
passthrough bool,
|
passthrough bool,
|
||||||
patches ...[2]string,
|
patches ...KV,
|
||||||
) pkg.Artifact {
|
) pkg.Artifact {
|
||||||
if passthrough && len(patches) == 0 {
|
if passthrough && len(patches) == 0 {
|
||||||
return source
|
return source
|
||||||
@@ -445,7 +449,7 @@ type PackageAttr struct {
|
|||||||
ScriptEarly string
|
ScriptEarly string
|
||||||
|
|
||||||
// Passed to [Toolchain.NewPatchedSource].
|
// Passed to [Toolchain.NewPatchedSource].
|
||||||
Patches [][2]string
|
Patches []KV
|
||||||
// Kind of source artifact.
|
// Kind of source artifact.
|
||||||
SourceKind int
|
SourceKind int
|
||||||
|
|
||||||
@@ -591,29 +595,3 @@ cd '/usr/src/` + name + `/'
|
|||||||
})...,
|
})...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collected is returned by [Collect.Cure] to indicate a successful collection.
|
|
||||||
type Collected struct{}
|
|
||||||
|
|
||||||
// Error returns a constant string to satisfy error, but should never be seen
|
|
||||||
// by the user.
|
|
||||||
func (Collected) Error() string { return "artifacts successfully collected" }
|
|
||||||
|
|
||||||
// Collect implements [pkg.FloodArtifact] to concurrently cure multiple
|
|
||||||
// [pkg.Artifact]. It returns [Collected].
|
|
||||||
type Collect []pkg.Artifact
|
|
||||||
|
|
||||||
// Cure returns [Collected].
|
|
||||||
func (*Collect) Cure(*pkg.FContext) error { return Collected{} }
|
|
||||||
|
|
||||||
// Kind returns the hardcoded [pkg.Kind] value.
|
|
||||||
func (*Collect) Kind() pkg.Kind { return kindCollection }
|
|
||||||
|
|
||||||
// Params does not write anything, dependencies are already represented in the header.
|
|
||||||
func (*Collect) Params(*pkg.IContext) {}
|
|
||||||
|
|
||||||
// Dependencies returns [Collect] as is.
|
|
||||||
func (c *Collect) Dependencies() []pkg.Artifact { return *c }
|
|
||||||
|
|
||||||
// IsExclusive returns false: Cure is a noop.
|
|
||||||
func (*Collect) IsExclusive() bool { return false }
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func (t Toolchain) newRsync() (pkg.Artifact, string) {
|
|||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
Flag: TEarly,
|
Flag: TEarly,
|
||||||
}, &MakeHelper{
|
}, &MakeHelper{
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"disable-openssl"},
|
{"disable-openssl"},
|
||||||
{"disable-xxhash"},
|
{"disable-xxhash"},
|
||||||
{"disable-zstd"},
|
{"disable-zstd"},
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ sed -i 's/unsigned int msg_len;$/uint32_t msg_len;/g' \
|
|||||||
tests/nlattr.c
|
tests/nlattr.c
|
||||||
`,
|
`,
|
||||||
}, &MakeHelper{
|
}, &MakeHelper{
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
// tests broken on clang
|
// tests broken on clang
|
||||||
{"disable-gcc-Werror"},
|
{"disable-gcc-Werror"},
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func (t Toolchain) newUtilLinux() (pkg.Artifact, string) {
|
|||||||
ln -s ../system/bin/bash /bin/
|
ln -s ../system/bin/bash /bin/
|
||||||
`,
|
`,
|
||||||
}, &MakeHelper{
|
}, &MakeHelper{
|
||||||
Configure: [][2]string{
|
Configure: []KV{
|
||||||
{"disable-use-tty-group"},
|
{"disable-use-tty-group"},
|
||||||
{"disable-makeinstall-setuid"},
|
{"disable-makeinstall-setuid"},
|
||||||
{"disable-makeinstall-chown"},
|
{"disable-makeinstall-chown"},
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import "hakurei.app/internal/pkg"
|
|||||||
|
|
||||||
func (t Toolchain) newWayland() (pkg.Artifact, string) {
|
func (t Toolchain) newWayland() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "1.24.91"
|
version = "1.25.0"
|
||||||
checksum = "SQkjYShk2TutoBOfmeJcdLU9iDExVKOg0DZhLeL8U_qjc9olLTC7h3vuUBvVtx9w"
|
checksum = "q-4dYXme46JPgLGtXAxyZGTy7udll9RfT0VXtcW2YRR1WWViUhvdZXZneXzLqpCg"
|
||||||
)
|
)
|
||||||
return t.NewPackage("wayland", version, pkg.NewHTTPGetTar(
|
return t.NewPackage("wayland", version, pkg.NewHTTPGetTar(
|
||||||
nil, "https://gitlab.freedesktop.org/wayland/wayland/"+
|
nil, "https://gitlab.freedesktop.org/wayland/wayland/"+
|
||||||
@@ -20,7 +20,7 @@ chmod +w tests tests/sanity-test.c
|
|||||||
echo 'int main(){}' > tests/sanity-test.c
|
echo 'int main(){}' > tests/sanity-test.c
|
||||||
`,
|
`,
|
||||||
}, &MesonHelper{
|
}, &MesonHelper{
|
||||||
Setup: [][2]string{
|
Setup: []KV{
|
||||||
{"Ddefault_library", "both"},
|
{"Ddefault_library", "both"},
|
||||||
{"Ddocumentation", "false"},
|
{"Ddocumentation", "false"},
|
||||||
{"Dtests", "true"},
|
{"Dtests", "true"},
|
||||||
@@ -63,7 +63,7 @@ func (t Toolchain) newWaylandProtocols() (pkg.Artifact, string) {
|
|||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarBzip2,
|
pkg.TarBzip2,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
Patches: [][2]string{
|
Patches: []KV{
|
||||||
{"build-only", `From 8b4c76275fa1b6e0a99a53494151d9a2c907144d Mon Sep 17 00:00:00 2001
|
{"build-only", `From 8b4c76275fa1b6e0a99a53494151d9a2c907144d Mon Sep 17 00:00:00 2001
|
||||||
From: "A. Wilcox" <AWilcox@Wilcox-Tech.com>
|
From: "A. Wilcox" <AWilcox@Wilcox-Tech.com>
|
||||||
Date: Fri, 8 Nov 2024 11:27:25 -0600
|
Date: Fri, 8 Nov 2024 11:27:25 -0600
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func (t Toolchain) newZlib() (pkg.Artifact, string) {
|
|||||||
mustDecode(checksum),
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), nil, &CMakeHelper{
|
), nil, &CMakeHelper{
|
||||||
Cache: [][2]string{
|
Cache: []KV{
|
||||||
{"CMAKE_BUILD_TYPE", "Release"},
|
{"CMAKE_BUILD_TYPE", "Release"},
|
||||||
|
|
||||||
{"ZLIB_BUILD_TESTING", "OFF"},
|
{"ZLIB_BUILD_TESTING", "OFF"},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func (t Toolchain) newZstd() (pkg.Artifact, string) {
|
|||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), nil, &CMakeHelper{
|
), nil, &CMakeHelper{
|
||||||
Append: []string{"build", "cmake"},
|
Append: []string{"build", "cmake"},
|
||||||
Cache: [][2]string{
|
Cache: []KV{
|
||||||
{"CMAKE_BUILD_TYPE", "Release"},
|
{"CMAKE_BUILD_TYPE", "Release"},
|
||||||
},
|
},
|
||||||
}), version
|
}), version
|
||||||
|
|||||||
85
internal/uevent/action.go
Normal file
85
internal/uevent/action.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package uevent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KobjectAction represents enum kobject_action found in include/linux/kobject.h
|
||||||
|
// and their corresponding string representations in lib/kobject_uevent.c.
|
||||||
|
type KobjectAction uint32
|
||||||
|
|
||||||
|
// include/linux/kobject.h
|
||||||
|
const (
|
||||||
|
KOBJ_ADD KobjectAction = iota
|
||||||
|
KOBJ_REMOVE
|
||||||
|
KOBJ_CHANGE
|
||||||
|
KOBJ_MOVE
|
||||||
|
KOBJ_ONLINE
|
||||||
|
KOBJ_OFFLINE
|
||||||
|
KOBJ_BIND
|
||||||
|
KOBJ_UNBIND
|
||||||
|
|
||||||
|
// Synthetic denotes a [Message] that originates from outside the kernel. It
|
||||||
|
// is not valid in the wire format and is only meaningful within this package.
|
||||||
|
Synthetic KobjectAction = 0xfeed
|
||||||
|
)
|
||||||
|
|
||||||
|
// lib/kobject_uevent.c
|
||||||
|
var kobject_actions = [...]string{
|
||||||
|
KOBJ_ADD: "add",
|
||||||
|
KOBJ_REMOVE: "remove",
|
||||||
|
KOBJ_CHANGE: "change",
|
||||||
|
KOBJ_MOVE: "move",
|
||||||
|
KOBJ_ONLINE: "online",
|
||||||
|
KOBJ_OFFLINE: "offline",
|
||||||
|
KOBJ_BIND: "bind",
|
||||||
|
KOBJ_UNBIND: "unbind",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns whether the value of act is defined.
|
||||||
|
func (act KobjectAction) Valid() bool { return int(act) < len(kobject_actions) }
|
||||||
|
|
||||||
|
// String returns the corresponding string sent over netlink.
|
||||||
|
func (act KobjectAction) String() string {
|
||||||
|
if act == Synthetic {
|
||||||
|
return "synthetic"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !act.Valid() {
|
||||||
|
return "unsupported kobject_action " + strconv.Itoa(int(act))
|
||||||
|
}
|
||||||
|
return kobject_actions[act]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (act KobjectAction) AppendText(b []byte) ([]byte, error) {
|
||||||
|
if !act.Valid() && act != Synthetic {
|
||||||
|
return b, syscall.EINVAL
|
||||||
|
}
|
||||||
|
return append(b, act.String()...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (act KobjectAction) MarshalText() ([]byte, error) {
|
||||||
|
return act.AppendText(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An UnsupportedActionError describes a string representation of [KobjectAction]
|
||||||
|
// not yet supported by this package.
|
||||||
|
type UnsupportedActionError string
|
||||||
|
|
||||||
|
var _ Recoverable = UnsupportedActionError("")
|
||||||
|
|
||||||
|
func (UnsupportedActionError) recoverable() {}
|
||||||
|
func (e UnsupportedActionError) Error() string {
|
||||||
|
return "unsupported kobject_action " + strconv.Quote(string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (act *KobjectAction) UnmarshalText(data []byte) error {
|
||||||
|
for v, s := range kobject_actions {
|
||||||
|
if string(data) == s {
|
||||||
|
*act = KobjectAction(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnsupportedActionError(data)
|
||||||
|
}
|
||||||
43
internal/uevent/action_test.go
Normal file
43
internal/uevent/action_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package uevent_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKobjectAction(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
adeT(t, "add", uevent.KOBJ_ADD, "add", nil, nil)
|
||||||
|
adeT(t, "remove", uevent.KOBJ_REMOVE, "remove", nil, nil)
|
||||||
|
adeT(t, "change", uevent.KOBJ_CHANGE, "change", nil, nil)
|
||||||
|
adeT(t, "move", uevent.KOBJ_MOVE, "move", nil, nil)
|
||||||
|
adeT(t, "online", uevent.KOBJ_ONLINE, "online", nil, nil)
|
||||||
|
adeT(t, "offline", uevent.KOBJ_OFFLINE, "offline", nil, nil)
|
||||||
|
adeT(t, "bind", uevent.KOBJ_BIND, "bind", nil, nil)
|
||||||
|
adeT(t, "unbind", uevent.KOBJ_UNBIND, "unbind", nil, nil)
|
||||||
|
|
||||||
|
adeT(t, "unsupported", uevent.KobjectAction(0xbad), "explode",
|
||||||
|
uevent.UnsupportedActionError("explode"), syscall.EINVAL)
|
||||||
|
t.Run("oob string", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const want = "unsupported kobject_action 2989"
|
||||||
|
if got := uevent.KobjectAction(0xbad).String(); got != want {
|
||||||
|
t.Errorf("String: %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
adeT(t, "synthetic", uevent.Synthetic, "synthetic",
|
||||||
|
uevent.UnsupportedActionError("synthetic"), nil)
|
||||||
|
|
||||||
|
t.Run("validate synthetic", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if uevent.Synthetic.Valid() {
|
||||||
|
t.Errorf("Valid unexpectedly succeeded")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
137
internal/uevent/message.go
Normal file
137
internal/uevent/message.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package uevent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Message represents a kernel message to userspace.
|
||||||
|
type Message struct {
|
||||||
|
// alloc_uevent_skb: action_string
|
||||||
|
Action KobjectAction `json:"action"`
|
||||||
|
// alloc_uevent_skb: devpath
|
||||||
|
DevPath string `json:"devpath"`
|
||||||
|
// add_uevent_var: key value strings
|
||||||
|
Env []string `json:"env"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a multiline user-facing string representation of [Message].
|
||||||
|
func (msg *Message) String() string {
|
||||||
|
var buf strings.Builder
|
||||||
|
buf.WriteString(msg.Action.String() + " event")
|
||||||
|
if msg.DevPath != "" {
|
||||||
|
buf.WriteString(" on " + msg.DevPath)
|
||||||
|
}
|
||||||
|
buf.WriteString(":")
|
||||||
|
for _, s := range msg.Env {
|
||||||
|
buf.WriteString("\n" + s)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// zero is a single pre-allocated NUL character.
|
||||||
|
zero = []byte{0}
|
||||||
|
|
||||||
|
// sepHeader is the separator in a [Message] header.
|
||||||
|
sepHeader = []byte{'@'}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (msg *Message) AppendBinary(b []byte) (_ []byte, err error) {
|
||||||
|
if b, err = msg.Action.AppendText(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = append(b, sepHeader...)
|
||||||
|
b = append(b, msg.DevPath...)
|
||||||
|
b = append(b, zero...)
|
||||||
|
for _, s := range msg.Env {
|
||||||
|
b = append(b, s...)
|
||||||
|
b = append(b, zero...)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Message) MarshalBinary() ([]byte, error) {
|
||||||
|
return msg.AppendBinary(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MissingHeaderError is an invalid representation of [Message] which is missing
|
||||||
|
// its header added by alloc_uevent_skb.
|
||||||
|
type MissingHeaderError string
|
||||||
|
|
||||||
|
var _ Recoverable = MissingHeaderError("")
|
||||||
|
|
||||||
|
func (MissingHeaderError) recoverable() {}
|
||||||
|
func (e MissingHeaderError) Error() string {
|
||||||
|
return "message " + strconv.Quote(string(e)) + " has no header"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageError describes a malformed representation of [Message].
|
||||||
|
type MessageError struct {
|
||||||
|
// Full offending data.
|
||||||
|
Data string `json:"data"`
|
||||||
|
// Offending section.
|
||||||
|
Section string `json:"section"`
|
||||||
|
// Part of header in Section.
|
||||||
|
Kind int `json:"kind"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Recoverable = new(MessageError)
|
||||||
|
var _ Nontrivial = new(MessageError)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MErrorKindHeaderSep denotes a message header missing its separator.
|
||||||
|
MErrorKindHeaderSep = iota
|
||||||
|
// MErrorKindFinalNUL denotes a message body missing its final NUL terminator.
|
||||||
|
MErrorKindFinalNUL
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*MessageError) recoverable() {}
|
||||||
|
func (*MessageError) nontrivial() {}
|
||||||
|
func (e *MessageError) Error() string {
|
||||||
|
switch e.Kind {
|
||||||
|
case MErrorKindHeaderSep:
|
||||||
|
return "header " + strconv.Quote(e.Section) + " missing separator"
|
||||||
|
case MErrorKindFinalNUL:
|
||||||
|
return "entry " + strconv.Quote(e.Section) + " missing NUL"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "section " + strconv.Quote(e.Section) + " is invalid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Message) UnmarshalBinary(data []byte) error {
|
||||||
|
header, body, ok := bytes.Cut(data, zero)
|
||||||
|
if !ok {
|
||||||
|
return MissingHeaderError(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
action_string, devpath, ok := bytes.Cut(header, sepHeader)
|
||||||
|
if !ok {
|
||||||
|
return &MessageError{string(data), string(header), MErrorKindHeaderSep}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := msg.Action.UnmarshalText(action_string); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.DevPath = string(devpath)
|
||||||
|
|
||||||
|
if len(body) == 0 {
|
||||||
|
msg.Env = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
msg.Env = make([]string, 0, bytes.Count(body, zero))
|
||||||
|
|
||||||
|
var s []byte
|
||||||
|
for len(body) != 0 {
|
||||||
|
var r []byte
|
||||||
|
s, r, ok = bytes.Cut(body, zero)
|
||||||
|
if !ok {
|
||||||
|
return &MessageError{string(data), string(body), MErrorKindFinalNUL}
|
||||||
|
}
|
||||||
|
body = r
|
||||||
|
msg.Env = append(msg.Env, string(s))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
132
internal/uevent/message_test.go
Normal file
132
internal/uevent/message_test.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package uevent_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMessage(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
v uevent.Message
|
||||||
|
want string
|
||||||
|
wantErr error
|
||||||
|
wantErrE error
|
||||||
|
|
||||||
|
s string
|
||||||
|
}{
|
||||||
|
{"sample virtio-sound-pci add", uevent.Message{
|
||||||
|
Action: uevent.KOBJ_ADD,
|
||||||
|
DevPath: "/devices/pci0000:00/0000:00:04.0/virtio1",
|
||||||
|
Env: []string{
|
||||||
|
"ACTION=add",
|
||||||
|
"DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1",
|
||||||
|
"SUBSYSTEM=virtio",
|
||||||
|
"MODALIAS=virtio:d00000019v00001AF4",
|
||||||
|
"SEQNUM=779",
|
||||||
|
},
|
||||||
|
}, "add@/devices/pci0000:00/0000:00:04.0/virtio1\x00" +
|
||||||
|
"ACTION=add\x00" +
|
||||||
|
"DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1\x00" +
|
||||||
|
"SUBSYSTEM=virtio\x00" +
|
||||||
|
"MODALIAS=virtio:d00000019v00001AF4\x00" +
|
||||||
|
"SEQNUM=779\x00",
|
||||||
|
nil, nil, `add event on /devices/pci0000:00/0000:00:04.0/virtio1:
|
||||||
|
ACTION=add
|
||||||
|
DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1
|
||||||
|
SUBSYSTEM=virtio
|
||||||
|
MODALIAS=virtio:d00000019v00001AF4
|
||||||
|
SEQNUM=779`},
|
||||||
|
|
||||||
|
{"sample virtio-sound-pci bind", uevent.Message{
|
||||||
|
Action: uevent.KOBJ_BIND,
|
||||||
|
DevPath: "/devices/pci0000:00/0000:00:04.0",
|
||||||
|
Env: []string{
|
||||||
|
"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=780",
|
||||||
|
},
|
||||||
|
}, "bind@/devices/pci0000:00/0000:00:04.0\x00" +
|
||||||
|
"ACTION=bind\x00" +
|
||||||
|
"DEVPATH=/devices/pci0000:00/0000:00:04.0\x00" +
|
||||||
|
"SUBSYSTEM=pci\x00" +
|
||||||
|
"DRIVER=virtio-pci\x00" +
|
||||||
|
"PCI_CLASS=40100\x00" +
|
||||||
|
"PCI_ID=1AF4:1059\x00" +
|
||||||
|
"PCI_SUBSYS_ID=1AF4:1100\x00" +
|
||||||
|
"PCI_SLOT_NAME=0000:00:04.0\x00" +
|
||||||
|
"MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00\x00" +
|
||||||
|
"SEQNUM=780\x00", nil, nil, `bind event on /devices/pci0000:00/0000:00:04.0:
|
||||||
|
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=780`},
|
||||||
|
|
||||||
|
{"zero devpath env", uevent.Message{
|
||||||
|
Action: uevent.KOBJ_MOVE,
|
||||||
|
}, "move@\x00", nil, nil, "move event:"},
|
||||||
|
|
||||||
|
{"d final NUL e bad action", uevent.Message{
|
||||||
|
Action: 0xbad,
|
||||||
|
}, "move@\x00truncated", &uevent.MessageError{
|
||||||
|
Data: "move@\x00truncated",
|
||||||
|
Section: "truncated",
|
||||||
|
Kind: uevent.MErrorKindFinalNUL,
|
||||||
|
}, syscall.EINVAL, "unsupported kobject_action 2989 event:"},
|
||||||
|
|
||||||
|
{"bad action", uevent.Message{
|
||||||
|
Action: 0xbad,
|
||||||
|
}, "nonexistent@\x00", uevent.UnsupportedActionError(
|
||||||
|
"nonexistent",
|
||||||
|
), syscall.EINVAL, "unsupported kobject_action 2989 event:"},
|
||||||
|
|
||||||
|
{"d header sep e bad action", uevent.Message{
|
||||||
|
Action: 0xbad,
|
||||||
|
}, "move\x00", &uevent.MessageError{
|
||||||
|
Data: "move\x00",
|
||||||
|
Section: "move",
|
||||||
|
Kind: uevent.MErrorKindHeaderSep,
|
||||||
|
}, syscall.EINVAL, "unsupported kobject_action 2989 event:"},
|
||||||
|
|
||||||
|
{"d missing header e bad action", uevent.Message{
|
||||||
|
Action: 0xbad,
|
||||||
|
}, "move", uevent.MissingHeaderError(
|
||||||
|
"move",
|
||||||
|
), syscall.EINVAL, "unsupported kobject_action 2989 event:"},
|
||||||
|
|
||||||
|
{"synthetic", uevent.Message{
|
||||||
|
Action: uevent.Synthetic,
|
||||||
|
}, "synthetic@\x00", uevent.UnsupportedActionError(
|
||||||
|
"synthetic",
|
||||||
|
), nil, "synthetic event:"},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
adeB(t, "", tc.v, tc.want, tc.wantErr, tc.wantErrE)
|
||||||
|
t.Run("string", func(t *testing.T) {
|
||||||
|
if got := tc.v.String(); got != tc.s {
|
||||||
|
t.Errorf("String: %q, want %q", got, tc.s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
87
internal/uevent/sysfs.go
Normal file
87
internal/uevent/sysfs.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package uevent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enumerate scans sysfs and emits [Synthetic] events. It returns the first
|
||||||
|
// error it encounters.
|
||||||
|
//
|
||||||
|
// The specified filesystem must present the sysfs root.
|
||||||
|
func Enumerate(
|
||||||
|
sysfs fs.FS,
|
||||||
|
handleWalkErr func(error) error,
|
||||||
|
events chan<- *Message,
|
||||||
|
) error {
|
||||||
|
if handleWalkErr == nil {
|
||||||
|
handleWalkErr = func(err error) error {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
log.Println("enumerate", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.WalkDir(sysfs, "devices", func(
|
||||||
|
path string,
|
||||||
|
d fs.DirEntry,
|
||||||
|
err error,
|
||||||
|
) error {
|
||||||
|
if err != nil {
|
||||||
|
return handleWalkErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.IsDir() || d.Name() != "uevent" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := Message{
|
||||||
|
Action: Synthetic,
|
||||||
|
|
||||||
|
// cleans path, appears to be compatible with kernel behaviour
|
||||||
|
DevPath: filepath.Dir(path),
|
||||||
|
}
|
||||||
|
|
||||||
|
var target string
|
||||||
|
if target, err = fs.ReadLink(
|
||||||
|
sysfs,
|
||||||
|
filepath.Join(msg.DevPath, "subsystem"),
|
||||||
|
); err != nil {
|
||||||
|
if err = handleWalkErr(err); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msg.Env = append(msg.Env, "SUBSYSTEM="+filepath.Base(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// read entire file: slicing does not copy
|
||||||
|
var env []byte
|
||||||
|
if env, err = fs.ReadFile(sysfs, path); err != nil {
|
||||||
|
return handleWalkErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range bytes.Split(env, []byte{'\n'}) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg.Env = append(msg.Env, unsafe.String(unsafe.SliceData(s), len(s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(msg.Env) == 0 {
|
||||||
|
// this implies absent subsystem, its error is already handled
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.DevPath != "" && msg.DevPath[0] != '/' {
|
||||||
|
msg.DevPath = "/" + msg.DevPath
|
||||||
|
}
|
||||||
|
events <- &msg
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
28
internal/uevent/sysfs_test.go
Normal file
28
internal/uevent/sysfs_test.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package uevent_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnumerate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
defer wg.Wait()
|
||||||
|
|
||||||
|
events := make(chan *uevent.Message, 1<<10)
|
||||||
|
wg.Go(func() {
|
||||||
|
for msg := range events {
|
||||||
|
t.Log(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := uevent.Enumerate(os.DirFS("/sys"), nil, events); err != nil {
|
||||||
|
t.Fatalf("Enumerate: error = %v", err)
|
||||||
|
}
|
||||||
|
close(events)
|
||||||
|
}
|
||||||
105
internal/uevent/uevent.go
Normal file
105
internal/uevent/uevent.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// Package uevent provides userspace client for consuming events from a
|
||||||
|
// NETLINK_KOBJECT_UEVENT socket, as well as helpers for supplementing
|
||||||
|
// events received from the kernel.
|
||||||
|
package uevent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"hakurei.app/internal/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Recoverable is satisfied by errors that are safe to recover from.
|
||||||
|
Recoverable interface{ recoverable() }
|
||||||
|
// Nontrivial is satisfied by errors preferring a JSON encoding.
|
||||||
|
Nontrivial interface{ nontrivial() }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Conn represents a NETLINK_KOBJECT_UEVENT socket.
|
||||||
|
type Conn struct {
|
||||||
|
conn *netlink.Conn
|
||||||
|
|
||||||
|
// Whether currently between a call to enterExcl and exitExcl.
|
||||||
|
excl 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) {
|
||||||
|
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) }
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// kernel group is hard coded in lib/kobject_uevent.c, undocumented
|
||||||
|
c, err := netlink.Dial(syscall.NETLINK_KOBJECT_UEVENT, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Conn{conn: c}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrBadSocket is returned by [Conn.Consume] for a reply from a
|
||||||
|
// syscall.Sockaddr with unexpected concrete type.
|
||||||
|
ErrBadSocket = errors.New("unexpected socket address")
|
||||||
|
)
|
||||||
|
|
||||||
|
// BadPortError is returned by [Conn.Consume] upon receiving a message that did
|
||||||
|
// not come from the kernel.
|
||||||
|
type BadPortError syscall.SockaddrNetlink
|
||||||
|
|
||||||
|
var _ Recoverable = new(BadPortError)
|
||||||
|
|
||||||
|
func (*BadPortError) recoverable() {}
|
||||||
|
func (e *BadPortError) Error() string {
|
||||||
|
return "unexpected message from port id " + strconv.Itoa(int(e.Pid)) +
|
||||||
|
" on NETLINK_KOBJECT_UEVENT"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume continuously receives and parses events from the kernel. It returns
|
||||||
|
// the first error it encounters.
|
||||||
|
//
|
||||||
|
// 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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.exitExcl()
|
||||||
|
|
||||||
|
for {
|
||||||
|
data, from, err := c.conn.Recvfrom(ctx, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg Message
|
||||||
|
if err = msg.UnmarshalBinary(data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
events <- &msg
|
||||||
|
}
|
||||||
|
}
|
||||||
246
internal/uevent/uevent_test.go
Normal file
246
internal/uevent/uevent_test.go
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
package uevent_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// adeT sets up a parallel subtest for a textual appender/decoder/encoder.
|
||||||
|
func adeT[V any, S interface {
|
||||||
|
encoding.TextAppender
|
||||||
|
encoding.TextMarshaler
|
||||||
|
encoding.TextUnmarshaler
|
||||||
|
|
||||||
|
*V
|
||||||
|
}](t *testing.T, name string, v V, want string, wantErr, wantErrE error) {
|
||||||
|
t.Helper()
|
||||||
|
f := func(t *testing.T) {
|
||||||
|
if name != "" {
|
||||||
|
t.Parallel()
|
||||||
|
}
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
t.Run("decode", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var got V
|
||||||
|
if err := S(&got).UnmarshalText([]byte(want)); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
t.Fatalf("UnmarshalText: error = %v, want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
if wantErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(&got, &v) {
|
||||||
|
t.Errorf("UnmarshalText: %#v, want %#v", got, v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("encode", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if got, err := S(&v).MarshalText(); !reflect.DeepEqual(err, wantErrE) {
|
||||||
|
t.Fatalf("MarshalText: error = %v, want %v", err, wantErrE)
|
||||||
|
} else if err == nil && string(got) != want {
|
||||||
|
t.Errorf("MarshalText: %q, want %q", string(got), want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if name != "" {
|
||||||
|
t.Run(name, f)
|
||||||
|
} else {
|
||||||
|
f(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// adeT sets up a binary subtest for a textual appender/decoder/encoder.
|
||||||
|
func adeB[V any, S interface {
|
||||||
|
encoding.BinaryAppender
|
||||||
|
encoding.BinaryMarshaler
|
||||||
|
encoding.BinaryUnmarshaler
|
||||||
|
|
||||||
|
*V
|
||||||
|
}](t *testing.T, name string, v V, want string, wantErr, wantErrE error) {
|
||||||
|
t.Helper()
|
||||||
|
f := func(t *testing.T) {
|
||||||
|
if name != "" {
|
||||||
|
t.Parallel()
|
||||||
|
}
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
t.Run("decode", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var got V
|
||||||
|
if err := S(&got).UnmarshalBinary([]byte(want)); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
t.Fatalf("UnmarshalBinary: error = %v, want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
if wantErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(&got, &v) {
|
||||||
|
t.Errorf("UnmarshalBinary: %#v, want %#v", got, v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("encode", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if got, err := S(&v).MarshalBinary(); !reflect.DeepEqual(err, wantErrE) {
|
||||||
|
t.Fatalf("MarshalBinary: error = %v, want %v", err, wantErrE)
|
||||||
|
} else if err == nil && string(got) != want {
|
||||||
|
t.Errorf("MarshalBinary: %q, want %q", string(got), want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if name != "" {
|
||||||
|
t.Run(name, f)
|
||||||
|
} else {
|
||||||
|
f(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialConsume(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
c, err := uevent.Dial()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: error = %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if closeErr := c.Close(); closeErr != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// check kernel-assigned port id
|
||||||
|
c0, err0 := uevent.Dial()
|
||||||
|
if err0 != nil {
|
||||||
|
t.Fatalf("Dial: error = %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if closeErr := c0.Close(); closeErr != nil {
|
||||||
|
t.Fatal(closeErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
done := make(chan struct{})
|
||||||
|
events := make(chan *uevent.Message, 1<<10)
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
for msg := range events {
|
||||||
|
t.Log(msg)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
t.Cleanup(func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(events)
|
||||||
|
<-done
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
wg.Go(func() {
|
||||||
|
if err = c.Consume(ctx, events); err != context.Canceled {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
wg.Go(func() {
|
||||||
|
if err0 = c0.Consume(ctx, events); err0 != context.Canceled {
|
||||||
|
panic(err0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if testing.Verbose() {
|
||||||
|
if d, perr := time.ParseDuration(os.Getenv(
|
||||||
|
"ROSA_UEVENT_TEST_DURATION",
|
||||||
|
)); perr != nil {
|
||||||
|
t.Logf("skipping long test: error = %v", perr)
|
||||||
|
} else {
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
ctx, cancel = context.WithCancel(t.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var errs [2]error
|
||||||
|
exclExit := make(chan struct{})
|
||||||
|
wg.Go(func() {
|
||||||
|
defer func() { exclExit <- struct{}{} }()
|
||||||
|
errs[0] = c.Consume(ctx, events)
|
||||||
|
})
|
||||||
|
wg.Go(func() {
|
||||||
|
defer func() { exclExit <- struct{}{} }()
|
||||||
|
errs[1] = c.Consume(ctx, events)
|
||||||
|
})
|
||||||
|
<-exclExit
|
||||||
|
cancel()
|
||||||
|
<-exclExit
|
||||||
|
if errs[0] != syscall.EAGAIN && errs[1] != syscall.EAGAIN {
|
||||||
|
t.Fatalf("enterExcl: err0 = %v, err1 = %v", errs[0], errs[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"UnsupportedActionError", uevent.UnsupportedActionError("explode"),
|
||||||
|
`unsupported kobject_action "explode"`},
|
||||||
|
|
||||||
|
{"MissingHeaderError", uevent.MissingHeaderError("move"),
|
||||||
|
`message "move" has no header`},
|
||||||
|
|
||||||
|
{"MessageError MErrorKindHeaderSep", &uevent.MessageError{
|
||||||
|
Data: "move\x00",
|
||||||
|
Section: "move",
|
||||||
|
Kind: uevent.MErrorKindHeaderSep,
|
||||||
|
}, `header "move" missing separator`},
|
||||||
|
|
||||||
|
{"MessageError MErrorKindFinalNUL", &uevent.MessageError{
|
||||||
|
Data: "move\x00truncated",
|
||||||
|
Section: "truncated",
|
||||||
|
Kind: uevent.MErrorKindFinalNUL,
|
||||||
|
}, `entry "truncated" missing NUL`},
|
||||||
|
|
||||||
|
{"MessageError bad", &uevent.MessageError{
|
||||||
|
Data: "\x00",
|
||||||
|
Kind: 0xbad,
|
||||||
|
}, `section "" is invalid`},
|
||||||
|
|
||||||
|
{"BadPortError", &uevent.BadPortError{
|
||||||
|
Pid: 1,
|
||||||
|
}, "unexpected message from port id 1 on NETLINK_KOBJECT_UEVENT"},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if got := tc.err.Error(); got != tc.want {
|
||||||
|
t.Errorf("Error: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -265,7 +265,7 @@ in
|
|||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
pkgs.writeShellScriptBin app.name ''
|
pkgs.writeShellScriptBin app.name ''
|
||||||
exec hakurei${if app.verbose then " -v" else ""} app ${checkedConfig "hakurei-app-${app.name}.json" conf} $@
|
exec hakurei${if app.verbose then " -v" else ""} run ${checkedConfig "hakurei-app-${app.name}.json" conf} $@
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ in
|
|||||||
|
|
||||||
# For checking pd outcome:
|
# For checking pd outcome:
|
||||||
(pkgs.writeShellScriptBin "check-sandbox-pd" ''
|
(pkgs.writeShellScriptBin "check-sandbox-pd" ''
|
||||||
hakurei -v run hakurei-test \
|
hakurei -v exec hakurei-test \
|
||||||
-p "/var/tmp/.hakurei-check-ok.0" \
|
-p "/var/tmp/.hakurei-check-ok.0" \
|
||||||
-t ${toString (builtins.toFile "hakurei-pd-want.json" (builtins.toJSON testCases.pd.want))} \
|
-t ${toString (builtins.toFile "hakurei-pd-want.json" (builtins.toJSON testCases.pd.want))} \
|
||||||
-s ${testCases.pd.expectedFilter.${pkgs.stdenv.hostPlatform.system}} "$@"
|
-s ${testCases.pd.expectedFilter.${pkgs.stdenv.hostPlatform.system}} "$@"
|
||||||
|
|||||||
@@ -42,23 +42,23 @@ machine.wait_for_file("/run/user/1000/wayland-1")
|
|||||||
machine.wait_for_file("/tmp/sway-ipc.sock")
|
machine.wait_for_file("/tmp/sway-ipc.sock")
|
||||||
|
|
||||||
# Check pd seccomp outcome:
|
# Check pd seccomp outcome:
|
||||||
swaymsg("exec hakurei run cat")
|
swaymsg("exec hakurei exec cat")
|
||||||
check_filter(0, "pdlike", "cat")
|
check_filter(0, "pdlike", "cat")
|
||||||
|
|
||||||
# Check fd leak:
|
# Check fd leak:
|
||||||
swaymsg("exec exec 127</proc/cmdline && hakurei -v run sleep infinity")
|
swaymsg("exec exec 127</proc/cmdline && hakurei -v exec sleep infinity")
|
||||||
pd_identity0_sleep_pid = int(machine.wait_until_succeeds("pgrep -U 10000 -x sleep", timeout=60))
|
pd_identity0_sleep_pid = int(machine.wait_until_succeeds("pgrep -U 10000 -x sleep", timeout=60))
|
||||||
print(machine.succeed(f"hakurei-test fd {pd_identity0_sleep_pid}"))
|
print(machine.succeed(f"hakurei-test fd {pd_identity0_sleep_pid}"))
|
||||||
machine.succeed(f"kill -INT {pd_identity0_sleep_pid}")
|
machine.succeed(f"kill -INT {pd_identity0_sleep_pid}")
|
||||||
|
|
||||||
# Verify capabilities/securebits in user namespace:
|
# Verify capabilities/securebits in user namespace:
|
||||||
print(machine.succeed("sudo -u alice -i hakurei run capsh --print"))
|
print(machine.succeed("sudo -u alice -i hakurei exec capsh --print"))
|
||||||
print(machine.succeed("sudo -u alice -i hakurei run capsh --has-no-new-privs"))
|
print(machine.succeed("sudo -u alice -i hakurei exec capsh --has-no-new-privs"))
|
||||||
print(machine.fail("sudo -u alice -i hakurei run capsh --has-a=CAP_SYS_ADMIN"))
|
print(machine.fail("sudo -u alice -i hakurei exec capsh --has-a=CAP_SYS_ADMIN"))
|
||||||
print(machine.fail("sudo -u alice -i hakurei run capsh --has-b=CAP_SYS_ADMIN"))
|
print(machine.fail("sudo -u alice -i hakurei exec capsh --has-b=CAP_SYS_ADMIN"))
|
||||||
print(machine.fail("sudo -u alice -i hakurei run capsh --has-i=CAP_SYS_ADMIN"))
|
print(machine.fail("sudo -u alice -i hakurei exec capsh --has-i=CAP_SYS_ADMIN"))
|
||||||
print(machine.fail("sudo -u alice -i hakurei run capsh --has-p=CAP_SYS_ADMIN"))
|
print(machine.fail("sudo -u alice -i hakurei exec capsh --has-p=CAP_SYS_ADMIN"))
|
||||||
print(machine.fail("sudo -u alice -i hakurei run umount -R /dev"))
|
print(machine.fail("sudo -u alice -i hakurei exec umount -R /dev"))
|
||||||
|
|
||||||
# Check sandbox outcome:
|
# Check sandbox outcome:
|
||||||
machine.succeed("install -dm0777 /tmp/.hakurei-store-rw/{upper,work}")
|
machine.succeed("install -dm0777 /tmp/.hakurei-store-rw/{upper,work}")
|
||||||
|
|||||||
40
test/test.py
40
test/test.py
@@ -87,9 +87,9 @@ machine.wait_for_file("/tmp/sway-ipc.sock")
|
|||||||
swaymsg("exec hakurei-test")
|
swaymsg("exec hakurei-test")
|
||||||
|
|
||||||
# Deny unmapped uid:
|
# Deny unmapped uid:
|
||||||
denyOutput = machine.fail("sudo -u untrusted -i hakurei run &>/dev/stdout")
|
denyOutput = machine.fail("sudo -u untrusted -i hakurei exec &>/dev/stdout")
|
||||||
print(denyOutput)
|
print(denyOutput)
|
||||||
denyOutputVerbose = machine.fail("sudo -u untrusted -i hakurei -v run &>/dev/stdout")
|
denyOutputVerbose = machine.fail("sudo -u untrusted -i hakurei -v exec &>/dev/stdout")
|
||||||
print(denyOutputVerbose)
|
print(denyOutputVerbose)
|
||||||
|
|
||||||
# Fail direct hsu call:
|
# Fail direct hsu call:
|
||||||
@@ -118,11 +118,11 @@ def hakurei_identity(offset):
|
|||||||
|
|
||||||
|
|
||||||
# Start hakurei permissive defaults outside Wayland session:
|
# Start hakurei permissive defaults outside Wayland session:
|
||||||
print(machine.succeed("sudo -u alice -i hakurei -v run -a 0 touch /tmp/pd-bare-ok"))
|
print(machine.succeed("sudo -u alice -i hakurei -v exec -a 0 touch /tmp/pd-bare-ok"))
|
||||||
machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-bare-ok", timeout=5)
|
machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-bare-ok", timeout=5)
|
||||||
|
|
||||||
# Verify silent output permissive defaults:
|
# Verify silent output permissive defaults:
|
||||||
output = machine.succeed("sudo -u alice -i hakurei run -a 0 true &>/dev/stdout")
|
output = machine.succeed("sudo -u alice -i hakurei exec -a 0 true &>/dev/stdout")
|
||||||
if output != "":
|
if output != "":
|
||||||
raise Exception(f"unexpected output\n{output}")
|
raise Exception(f"unexpected output\n{output}")
|
||||||
|
|
||||||
@@ -131,12 +131,12 @@ def silent_output_interrupt(flags):
|
|||||||
swaymsg("exec foot")
|
swaymsg("exec foot")
|
||||||
wait_for_window("alice@machine")
|
wait_for_window("alice@machine")
|
||||||
# identity 0 does not have home-manager
|
# identity 0 does not have home-manager
|
||||||
machine.send_chars(f"exec hakurei run {flags}-a 0 sh -c 'export PATH=/run/current-system/sw/bin:$PATH && touch /tmp/pd-silent-ready && sleep infinity' &>/tmp/pd-silent\n")
|
machine.send_chars(f"exec hakurei exec {flags}-a 0 sh -c 'export PATH=/run/current-system/sw/bin:$PATH && touch /tmp/pd-silent-ready && sleep infinity' &>/tmp/pd-silent\n")
|
||||||
machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-silent-ready", timeout=15)
|
machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-silent-ready", timeout=15)
|
||||||
machine.succeed("rm /tmp/hakurei.0/tmpdir/0/pd-silent-ready")
|
machine.succeed("rm /tmp/hakurei.0/tmpdir/0/pd-silent-ready")
|
||||||
machine.send_key("ctrl-c")
|
machine.send_key("ctrl-c")
|
||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
machine.wait_until_fails(f"pgrep -u alice -f 'hakurei run {flags}-a 0 '", timeout=5)
|
machine.wait_until_fails(f"pgrep -u alice -f 'hakurei exec {flags}-a 0 '", timeout=5)
|
||||||
output = machine.succeed("cat /tmp/pd-silent && rm /tmp/pd-silent")
|
output = machine.succeed("cat /tmp/pd-silent && rm /tmp/pd-silent")
|
||||||
if output != "":
|
if output != "":
|
||||||
raise Exception(f"unexpected output\n{output}")
|
raise Exception(f"unexpected output\n{output}")
|
||||||
@@ -147,10 +147,10 @@ silent_output_interrupt("--dbus ") # this one is especially painful as it mainta
|
|||||||
silent_output_interrupt("--wayland -X --dbus --pulse ")
|
silent_output_interrupt("--wayland -X --dbus --pulse ")
|
||||||
|
|
||||||
# Verify graceful failure on bad Wayland display name:
|
# Verify graceful failure on bad Wayland display name:
|
||||||
print(machine.fail("sudo -u alice -i hakurei -v run --wayland true"))
|
print(machine.fail("sudo -u alice -i hakurei -v exec --wayland true"))
|
||||||
|
|
||||||
# Start hakurei permissive defaults within Wayland session:
|
# Start hakurei permissive defaults within Wayland session:
|
||||||
hakurei('-v run --wayland --dbus --dbus-log notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-ok')
|
hakurei('-v exec --wayland --dbus --dbus-log notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-ok')
|
||||||
machine.wait_for_file("/tmp/dbus-ok", timeout=15)
|
machine.wait_for_file("/tmp/dbus-ok", timeout=15)
|
||||||
collect_state_ui("dbus_notify_exited")
|
collect_state_ui("dbus_notify_exited")
|
||||||
# not in pid namespace, verify termination
|
# not in pid namespace, verify termination
|
||||||
@@ -158,10 +158,10 @@ machine.wait_until_fails("pgrep xdg-dbus-proxy")
|
|||||||
machine.succeed("pkill -9 mako")
|
machine.succeed("pkill -9 mako")
|
||||||
|
|
||||||
# Check revert type selection:
|
# Check revert type selection:
|
||||||
hakurei("-v run --wayland -X --dbus --pulse -u p0 foot && touch /tmp/p0-exit-ok")
|
hakurei("-v exec --wayland -X --dbus --pulse -u p0 foot && touch /tmp/p0-exit-ok")
|
||||||
wait_for_window("p0@machine")
|
wait_for_window("p0@machine")
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
|
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
|
||||||
hakurei("-v run --wayland -X --dbus --pulse -u p1 foot && touch /tmp/p1-exit-ok")
|
hakurei("-v exec --wayland -X --dbus --pulse -u p1 foot && touch /tmp/p1-exit-ok")
|
||||||
wait_for_window("p1@machine")
|
wait_for_window("p1@machine")
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
|
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
|
||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
@@ -173,14 +173,14 @@ machine.wait_for_file("/tmp/p0-exit-ok", timeout=15)
|
|||||||
machine.fail("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")
|
machine.fail("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")
|
||||||
|
|
||||||
# Check invalid identifier fd behaviour:
|
# Check invalid identifier fd behaviour:
|
||||||
machine.fail('echo \'{"container":{"shell":"/proc/nonexistent","home":"/proc/nonexistent","path":"/proc/nonexistent"}}\' | sudo -u alice -i hakurei -v app --identifier-fd 32767 - 2>&1 | tee > /tmp/invalid-identifier-fd')
|
machine.fail('echo \'{"container":{"shell":"/proc/nonexistent","home":"/proc/nonexistent","path":"/proc/nonexistent"}}\' | sudo -u alice -i hakurei -v run --identifier-fd 32767 - 2>&1 | tee > /tmp/invalid-identifier-fd')
|
||||||
machine.wait_for_file("/tmp/invalid-identifier-fd")
|
machine.wait_for_file("/tmp/invalid-identifier-fd")
|
||||||
print(machine.succeed('grep "^hakurei: cannot write identifier: bad file descriptor$" /tmp/invalid-identifier-fd'))
|
print(machine.succeed('grep "^hakurei: cannot write identifier: bad file descriptor$" /tmp/invalid-identifier-fd'))
|
||||||
|
|
||||||
# Check interrupt shim behaviour:
|
# Check interrupt shim behaviour:
|
||||||
swaymsg("exec sh -c 'ne-foot; echo -n $? > /tmp/monitor-exit-code'")
|
swaymsg("exec sh -c 'ne-foot; echo -n $? > /tmp/monitor-exit-code'")
|
||||||
wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
|
wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
|
||||||
machine.succeed("pkill -INT -f 'hakurei -v app '")
|
machine.succeed("pkill -INT -f 'hakurei -v run '")
|
||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
machine.wait_for_file("/tmp/monitor-exit-code")
|
machine.wait_for_file("/tmp/monitor-exit-code")
|
||||||
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
||||||
@@ -190,7 +190,7 @@ if interrupt_exit_code != 230:
|
|||||||
# Check interrupt shim behaviour immediate termination:
|
# Check interrupt shim behaviour immediate termination:
|
||||||
swaymsg("exec sh -c 'ne-foot-immediate; echo -n $? > /tmp/monitor-exit-code'")
|
swaymsg("exec sh -c 'ne-foot-immediate; echo -n $? > /tmp/monitor-exit-code'")
|
||||||
wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
|
wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
|
||||||
machine.succeed("pkill -INT -f 'hakurei -v app '")
|
machine.succeed("pkill -INT -f 'hakurei -v run '")
|
||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
machine.wait_for_file("/tmp/monitor-exit-code")
|
machine.wait_for_file("/tmp/monitor-exit-code")
|
||||||
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
||||||
@@ -201,19 +201,19 @@ if interrupt_exit_code != 254:
|
|||||||
swaymsg("exec sh -c 'ne-foot &> /tmp/shim-cont-unexpected-pid'")
|
swaymsg("exec sh -c 'ne-foot &> /tmp/shim-cont-unexpected-pid'")
|
||||||
wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
|
wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
|
||||||
machine.succeed("pkill -CONT -f 'hakurei shim'")
|
machine.succeed("pkill -CONT -f 'hakurei shim'")
|
||||||
machine.succeed("pkill -INT -f 'hakurei -v app '")
|
machine.succeed("pkill -INT -f 'hakurei -v run '")
|
||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
machine.wait_for_file("/tmp/shim-cont-unexpected-pid")
|
machine.wait_for_file("/tmp/shim-cont-unexpected-pid")
|
||||||
print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid'))
|
print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid'))
|
||||||
|
|
||||||
# Check setscheduler:
|
# Check setscheduler:
|
||||||
sched_unset = int(machine.succeed("sudo -u alice -i hakurei -v run cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
|
sched_unset = int(machine.succeed("sudo -u alice -i hakurei -v exec cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
|
||||||
if sched_unset != 0:
|
if sched_unset != 0:
|
||||||
raise Exception(f"unexpected unset policy: {sched_unset}")
|
raise Exception(f"unexpected unset policy: {sched_unset}")
|
||||||
sched_idle = int(machine.succeed("sudo -u alice -i hakurei -v run --policy=idle cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
|
sched_idle = int(machine.succeed("sudo -u alice -i hakurei -v exec --policy=idle cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
|
||||||
if sched_idle != 5:
|
if sched_idle != 5:
|
||||||
raise Exception(f"unexpected idle policy: {sched_idle}")
|
raise Exception(f"unexpected idle policy: {sched_idle}")
|
||||||
sched_rr = int(machine.succeed("sudo -u alice -i hakurei -v run --policy=rr cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
|
sched_rr = int(machine.succeed("sudo -u alice -i hakurei -v exec --policy=rr cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
|
||||||
if sched_rr != 2:
|
if sched_rr != 2:
|
||||||
raise Exception(f"unexpected round-robin policy: {sched_idle}")
|
raise Exception(f"unexpected round-robin policy: {sched_idle}")
|
||||||
|
|
||||||
@@ -243,11 +243,11 @@ machine.wait_until_fails("pgrep foot", timeout=5)
|
|||||||
machine.wait_until_fails("pgrep -x hakurei", timeout=5)
|
machine.wait_until_fails("pgrep -x hakurei", timeout=5)
|
||||||
machine.succeed("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +")
|
machine.succeed("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +")
|
||||||
# Test PipeWire SecurityContext:
|
# Test PipeWire SecurityContext:
|
||||||
machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pulse pactl info")
|
machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pulse pactl info")
|
||||||
machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pulse pactl set-sink-mute @DEFAULT_SINK@ toggle")
|
machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pulse pactl set-sink-mute @DEFAULT_SINK@ toggle")
|
||||||
# Test PipeWire direct access:
|
# Test PipeWire direct access:
|
||||||
machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 pw-dump")
|
machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 pw-dump")
|
||||||
machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pipewire pw-dump")
|
machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pipewire pw-dump")
|
||||||
|
|
||||||
# Test XWayland (foot does not support X):
|
# Test XWayland (foot does not support X):
|
||||||
swaymsg("exec x11-alacritty")
|
swaymsg("exec x11-alacritty")
|
||||||
|
|||||||
Reference in New Issue
Block a user